1 /* Spectrum analyzer display base
2  *
3  * Mike Kershaw/Dragorn <dragorn@kismetwireless.net>
4  *
5  * This code is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This code is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * Extra thanks to Ryan Woodings @ Metageek for interface documentation
16  */
17 
18 #include <math.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "spectool_gtk_widget.h"
24 
25 /* control/picker pane width and initial height */
26 #define SPECTOOL_WIDGET_PADDING	5
27 
28 static void spectool_widget_class_init(SpectoolWidgetClass *class);
29 static void spectool_widget_init(SpectoolWidget *graph);
30 static void spectool_widget_destroy(GtkObject *object);
31 static void spectool_widget_realize(GtkWidget *widget);
32 
33 static gint spectool_widget_configure(GtkWidget *widget,
34 									 GdkEventConfigure *event);
35 static gboolean spectool_widget_expose(GtkWidget *widget,
36 									  GdkEventExpose *event,
37 									  gpointer *aux);
38 static void spectool_widget_size_allocate(GtkWidget *widget,
39 										 GtkAllocation *allocation);
40 static void spectool_widget_wdr_sweep(int slot, int mode,
41 								   spectool_sample_sweep *sweep, void *aux);
42 
43 static GType spectool_widget_child_type (GtkContainer *container);
44 
45 G_DEFINE_TYPE(SpectoolWidget, spectool_widget, GTK_TYPE_BIN);
46 
spectoolchannelopts_init(SpectoolChannelOpts * in)47 void spectoolchannelopts_init(SpectoolChannelOpts *in) {
48 	in->chan_h = -1;
49 	in->chanhit = NULL;
50 	in->chancolors = NULL;
51 	in->chanset = NULL;
52 	in->hi_chan = -1;
53 }
54 
spectool_widget_destroy(GtkObject * object)55 static void spectool_widget_destroy(GtkObject *object) {
56 	SpectoolWidget *wwidget = SPECTOOL_WIDGET(object);
57 
58 	wdr_del_sweepcb(wwidget->wdr, wwidget->wdr_slot,
59 					spectool_widget_wdr_sweep, wwidget);
60 
61 	if (wwidget->wdr_slot >= 0) {
62 		wdr_del_ref(wwidget->wdr, wwidget->wdr_slot);
63 		wwidget->wdr_slot = -1;
64 	}
65 
66 	if (wwidget->timeout_ref >= 0) {
67 		g_source_remove(wwidget->timeout_ref);
68 		wwidget->timeout_ref = -1;
69 	}
70 
71 	GTK_OBJECT_CLASS(spectool_widget_parent_class)->destroy(object);
72 }
73 
spectool_widget_new()74 GtkWidget *spectool_widget_new() {
75 	SpectoolWidget *wwidget;
76 
77 	wwidget = gtk_type_new(spectool_widget_get_type());
78 
79 	return GTK_WIDGET(wwidget);
80 }
81 
spectool_widget_wdr_sweep(int slot,int mode,spectool_sample_sweep * sweep,void * aux)82 static void spectool_widget_wdr_sweep(int slot, int mode,
83 								   spectool_sample_sweep *sweep, void *aux) {
84 	SpectoolWidget *wwidget;
85 
86 	g_return_if_fail(aux != NULL);
87 	g_return_if_fail(IS_SPECTOOL_WIDGET(aux));
88 
89 	wwidget = SPECTOOL_WIDGET(aux);
90 
91 	wwidget->dirty = 1;
92 
93 	/* Generic sweep handler to add it to our cache, all things get this */
94 	if ((mode & SPECTOOL_POLL_ERROR)) {
95 		wwidget->phydev = NULL;
96 		if (wwidget->sweepcache != NULL) {
97 			spectool_cache_free(wwidget->sweepcache);
98 			wwidget->sweepcache = NULL;
99 		}
100 		wdr_del_ref(wwidget->wdr, wwidget->wdr_slot);
101 		wwidget->wdr_slot = -1;
102 	} else if ((mode & SPECTOOL_POLL_CONFIGURED)) {
103 		if (wwidget->sweepcache != NULL) {
104 			spectool_cache_free(wwidget->sweepcache);
105 			wwidget->sweepcache = NULL;
106 		}
107 
108 		if (wwidget->sweep_num_samples > 0) {
109 			wwidget->sweepcache =
110 				spectool_cache_alloc(wwidget->sweep_num_samples,
111 								  wwidget->sweep_keep_avg,
112 								  wwidget->sweep_keep_peak);
113 		}
114 
115 		wwidget->amp_offset_mdbm =
116 			spectool_phy_getcurprofile(wwidget->phydev)->amp_offset_mdbm;
117 		wwidget->amp_res_mdbm =
118 			spectool_phy_getcurprofile(wwidget->phydev)->amp_res_mdbm;
119 
120 		wwidget->base_db_offset =
121 			SPECTOOL_RSSI_CONVERT(wwidget->amp_offset_mdbm, wwidget->amp_res_mdbm,
122 							   spectool_phy_getcurprofile(wwidget->phydev)->rssi_max);
123 		wwidget->min_db_draw =
124 			SPECTOOL_RSSI_CONVERT(wwidget->amp_offset_mdbm, wwidget->amp_res_mdbm, 0);
125 		printf("debug - min db draw %d\n", wwidget->min_db_draw);
126 
127 	} else if (wwidget->sweepcache != NULL && sweep != NULL) {
128 		spectool_cache_append(wwidget->sweepcache, sweep);
129 		wwidget->min_db_draw =
130 			SPECTOOL_RSSI_CONVERT(wwidget->amp_offset_mdbm, wwidget->amp_res_mdbm,
131 							   sweep->min_rssi_seen > 2 ?
132 							   sweep->min_rssi_seen - 2: sweep->min_rssi_seen);
133 	}
134 
135 	/* Call the secondary sweep handler */
136 	if (wwidget->wdr_sweep_func != NULL)
137 		(*(wwidget->wdr_sweep_func))(slot, mode, sweep, aux);
138 }
139 
140 /* Common level function for opening a device, calls the secondary level
141  * function if one exists after device is linked in */
spectool_widget_bind_dev(GtkWidget * widget,spectool_device_registry * wdr,int slot)142 void spectool_widget_bind_dev(GtkWidget *widget, spectool_device_registry *wdr,
143 						   int slot) {
144 	SpectoolWidget *wwidget;
145 	char ltxt[256];
146 	spectool_sample_sweep *ran;
147 
148 	g_return_if_fail(widget != NULL);
149 	g_return_if_fail(IS_SPECTOOL_WIDGET(widget));
150 
151 	wwidget = SPECTOOL_WIDGET(widget);
152 
153 	wwidget->wdr = wdr;
154 
155 	/* Unref using the old slot, but only after we've reffed using the
156 	 * new one, incase someone picked the same dev twice, we need this ordering
157 	 * to prevent it from getting reaped from a 0 count on the ref*/
158 	wdr_add_ref(wdr, slot);
159 	wdr_del_ref(wdr, wwidget->wdr_slot);
160 
161 	/* Allocate the new device and drop our old sweep cache if one existed */
162 	wwidget->wdr_slot = slot;
163 	wwidget->phydev = wdr_get_phy(wwidget->wdr, slot);
164 
165 	/* register a sweep callback */
166 	wdr_add_sweepcb(wwidget->wdr, wwidget->wdr_slot,
167 					spectool_widget_wdr_sweep, wwidget->sweep_num_aggregate,
168 					wwidget);
169 
170 	/* Call our secondary open function */
171 	if (wwidget->wdr_devbind_func != NULL)
172 		(*(wwidget->wdr_devbind_func))(widget, wdr, slot);
173 
174 	/* Force calibration */
175 	if (spectool_get_state(wwidget->phydev) > SPECTOOL_STATE_CONFIGURING)
176 		spectool_widget_wdr_sweep(-1, SPECTOOL_POLL_CONFIGURED, NULL, widget);
177 
178 	/* Toggle off the "no device" panel and give us the graph */
179 	gtk_widget_show(wwidget->draw);
180 }
181 
spectool_widget_menu_button_press(gpointer * widget,GdkEvent * event)182 static gboolean spectool_widget_menu_button_press(gpointer *widget,
183 											   GdkEvent *event) {
184 	SpectoolWidgetController *con = (SpectoolWidgetController *) widget;
185 	char alt_title_text[32];
186 
187 	g_return_val_if_fail(widget != NULL, FALSE);
188 	g_return_val_if_fail(event != NULL, FALSE);
189 
190 	if (event->type == GDK_BUTTON_PRESS) {
191 		GdkEventButton *bevent = (GdkEventButton *) event;
192 
193 		/* toggle showing/hiding the widgets */
194 		if (GTK_WIDGET_VISIBLE(con->wwidget)) {
195 			gtk_widget_hide(GTK_WIDGET(con->wwidget));
196 			if (con->wwidget->graph_title != NULL) {
197 				snprintf(alt_title_text, 32, "%s (hidden)",
198 						 con->wwidget->graph_title);
199 				gtk_label_set_markup(GTK_LABEL(con->label), alt_title_text);
200 			}
201 
202 			gtk_widget_destroy(con->arrow);
203 			con->arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
204 			gtk_container_add(GTK_CONTAINER(con->menubutton), con->arrow);
205 			gtk_widget_show(con->arrow);
206 		} else {
207 			gtk_widget_show(GTK_WIDGET(con->wwidget));
208 			if (con->wwidget->graph_title != NULL) {
209 				gtk_label_set_markup(GTK_LABEL(con->label),
210 									 con->wwidget->graph_title);
211 			}
212 
213 			gtk_widget_destroy(con->arrow);
214 			con->arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_OUT);
215 			gtk_container_add(GTK_CONTAINER(con->menubutton), con->arrow);
216 			gtk_widget_show(con->arrow);
217 		}
218 
219 		return TRUE;
220 	}
221 
222 	return FALSE;
223 }
224 
spectool_widget_buildcontroller(GtkWidget * widget)225 SpectoolWidgetController *spectool_widget_buildcontroller(GtkWidget *widget) {
226 	GtkWidget *hbox;
227 	SpectoolWidgetController *con =
228 		(SpectoolWidgetController *) malloc(sizeof(SpectoolWidgetController));
229 	SpectoolWidget *wwidget;
230 
231 	GdkColor c;
232 	GtkStyle *style;
233 
234 	g_return_val_if_fail(widget != NULL, NULL);
235 	g_return_val_if_fail(IS_SPECTOOL_WIDGET(widget), NULL);
236 
237 	wwidget = SPECTOOL_WIDGET(widget);
238 
239 	con->wwidget = wwidget;
240 
241 	/* Colored titled box and label */
242 	con->evbox = gtk_event_box_new();
243 
244 	if (wwidget->graph_title_bg != NULL) {
245 		gdk_color_parse(wwidget->graph_title_bg, &c);
246 		style = gtk_style_new();
247 		gtk_widget_set_style(GTK_WIDGET(con->evbox), style);
248 		style->bg[GTK_STATE_NORMAL] = c;
249 		gtk_style_unref(style);
250 	} else {
251 		fprintf(stderr, "BUG: %p Missing graph_title_bg in widget base\n", widget);
252 	}
253 
254 	if (wwidget->graph_title != NULL) {
255 		hbox = gtk_hbox_new(FALSE, 2);
256 		gtk_container_add(GTK_CONTAINER(con->evbox), hbox);
257 
258 		con->label = gtk_label_new(NULL);
259 		gtk_label_set_markup(GTK_LABEL(con->label), wwidget->graph_title);
260 		gtk_box_pack_start(GTK_BOX(hbox), con->label, FALSE, FALSE, 0);
261 
262 		con->menubutton = gtk_button_new();
263 		con->arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_OUT);
264 		gtk_container_add(GTK_CONTAINER(con->menubutton), con->arrow);
265 		gtk_box_pack_end(GTK_BOX(hbox), con->menubutton, FALSE, FALSE, 0);
266 		g_signal_connect_swapped(G_OBJECT(con->menubutton), "event",
267 								 G_CALLBACK(spectool_widget_menu_button_press),
268 								 G_OBJECT(con));
269 
270 		gtk_widget_show(con->arrow);
271 		gtk_widget_show(con->menubutton);
272 		gtk_widget_show(hbox);
273 		gtk_widget_show(con->label);
274 	} else {
275 		fprintf(stderr, "BUG: %p Missing graph_title in widget base\n", widget);
276 	}
277 
278 	return con;
279 }
280 
spectool_widget_link_channel(GtkWidget * widget,SpectoolChannelOpts * opts)281 void spectool_widget_link_channel(GtkWidget *widget, SpectoolChannelOpts *opts) {
282 	SpectoolWidget *wwidget;
283 
284 	g_return_if_fail(widget != NULL);
285 	g_return_if_fail(IS_SPECTOOL_WIDGET(widget));
286 
287 	wwidget = SPECTOOL_WIDGET(widget);
288 
289 	wwidget->chanopts = opts;
290 }
291 
spectool_widget_mouse_click(GtkWidget * widget,GdkEventButton * button,gpointer * aux)292 gint spectool_widget_mouse_click(GtkWidget *widget, GdkEventButton *button, gpointer *aux) {
293 	SpectoolWidget *wwidget;
294 	GtkWidget *menu;
295 	GdkEvent *event = (GdkEvent *) button;
296 
297 	g_return_val_if_fail(widget != NULL, 0);
298 	g_return_val_if_fail(aux != NULL, 0);
299 	g_return_val_if_fail(IS_SPECTOOL_WIDGET(aux), 0);
300 
301 	wwidget = SPECTOOL_WIDGET(aux);
302 
303 	/* Catch rightclick */
304 	if (event->type == GDK_BUTTON_PRESS && event->button.button == 3 &&
305 		wwidget->menu_func != NULL) {
306 
307 		menu = gtk_menu_new();
308 		gtk_widget_show(menu);
309 
310 		(*wwidget->menu_func)(GTK_WIDGET(wwidget), menu);
311 
312 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
313 					   button->button, button->time);
314 
315 		return 1;
316 	}
317 
318 	if (wwidget->draw_mouse_click_func != NULL)
319 		return (*wwidget->draw_mouse_click_func)(widget, button, aux);
320 
321 	return 0;
322 }
323 
spectool_widget_buildgui(SpectoolWidget * widget)324 void spectool_widget_buildgui(SpectoolWidget *widget) {
325 	GtkWidget *vbox, *hbox, *hbox2;
326 	GtkWidget *temp;
327 	/* Sigh, is this really the only way to change the colors here? */
328 	GtkWidget *eb;
329 
330 	GdkColor c;
331 	GtkStyle *style;
332 
333 	g_return_if_fail(widget != NULL);
334 
335 	/* Brute override of the style */
336 	gdk_color_parse("#505050", &c);
337 	style = gtk_style_new();
338 	gtk_widget_set_style(GTK_WIDGET(widget), style);
339 	style->bg[GTK_STATE_NORMAL] = c;
340 	style->base[GTK_STATE_NORMAL] = c;
341 	gtk_style_unref(style);
342 
343 	/* Main packing of graph and sidebar */
344 	hbox = GTK_BIN(widget)->child;
345 
346 	/* Make the cairo drawing area and link it into events */
347 	widget->draw = gtk_drawing_area_new();
348 
349 	gtk_signal_connect(GTK_OBJECT(widget->draw), "expose_event",
350 					   (GtkSignalFunc) spectool_widget_expose, widget);
351 
352 	/* Set mask */
353 	gtk_widget_set_events(widget->draw, GDK_EXPOSURE_MASK |
354 						  GDK_LEAVE_NOTIFY_MASK |
355 						  GDK_BUTTON_PRESS_MASK |
356 						  GDK_POINTER_MOTION_MASK |
357 						  GDK_POINTER_MOTION_HINT_MASK);
358 
359 	/* Attach mouse */
360 	gtk_signal_connect(GTK_OBJECT(widget->draw), "button_press_event",
361 					   (GtkSignalFunc) spectool_widget_mouse_click,
362 					   widget);
363 #ifndef HAVE_HILDON
364 	/* Hildon doesn't get mouseover events since it doesn't have mouse
365 	 * movements */
366 	if (widget->draw_mouse_move_func != NULL)
367 		gtk_signal_connect(GTK_OBJECT(widget->draw), "motion_notify_event",
368 						   (GtkSignalFunc) widget->draw_mouse_move_func,
369 						   widget);
370 #else
371 	/* Hildon DOES get context menus for menufunc instead of right-click
372 	 * though */
373 	if (widget->menu_func != NULL) {
374 		temp = gtk_menu_new();
375 		gtk_widget_show(temp);
376 
377 		(*widget->menu_func)(GTK_WIDGET(widget), temp);
378 
379 		gtk_widget_tap_and_hold_setup(GTK_WIDGET(widget->draw), temp, NULL, 0);
380 	}
381 #endif
382 
383 	gtk_box_pack_start(GTK_BOX(hbox), widget->draw, TRUE, TRUE, 0);
384 
385 	/* Sidebar contents */
386 	vbox = gtk_vbox_new(FALSE, 0);
387 	gtk_container_set_border_width(GTK_CONTAINER(vbox), SPECTOOL_WIDGET_PADDING);
388 	gtk_box_pack_end(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
389 
390 	eb = gtk_event_box_new();
391 	gtk_box_pack_start(GTK_BOX(vbox), eb, TRUE, TRUE, 0);
392 
393 	if (widget->graph_control_bg != NULL) {
394 		gdk_color_parse(widget->graph_control_bg, &c);
395 		style = gtk_style_new();
396 		gtk_widget_set_style(GTK_WIDGET(eb), style);
397 		style->bg[GTK_STATE_NORMAL] = c;
398 		gtk_style_unref(style);
399 	} else {
400 		fprintf(stderr, "BUG: %p Missing graph_control_bg in widget base\n", widget);
401 	}
402 
403 	widget->infoeb = eb;
404 	/* widget->framevbox = vbox; */ /* Framing vbox holds title, etc */
405 	widget->vbox = gtk_vbox_new(FALSE, 0); /* vbox is where extra GUI bits go */
406 	gtk_container_add(GTK_CONTAINER(eb), widget->vbox);
407 
408 	gtk_widget_show(widget->draw);
409 	gtk_widget_show(widget->vbox);
410 	gtk_widget_show(vbox);
411 	gtk_widget_show(eb);
412 	gtk_widget_show(hbox);
413 }
414 
spectool_widget_init(SpectoolWidget * widget)415 static void spectool_widget_init(SpectoolWidget *widget) {
416 	widget->chanopts = NULL;
417 	widget->g_start_x = widget->g_end_x = widget->g_len_x = 0;
418 	widget->g_start_y = widget->g_end_y = widget->g_len_y = 0;
419 
420 	widget->timeout_ref = -1;
421 
422 	widget->phydev = NULL;
423 	widget->sweepcache = NULL;
424 	widget->wdr_slot = -1;
425 
426 	widget->hbox = gtk_hbox_new(FALSE, SPECTOOL_WIDGET_PADDING);
427 	gtk_widget_set_parent(widget->hbox, GTK_WIDGET(widget));
428 	GTK_BIN(widget)->child = widget->hbox;
429 
430 	widget->offscreen = NULL;
431 	widget->old_width = widget->old_height = 0;
432 
433 	widget->dirty = 0;
434 }
435 
spectool_widget_size_allocate(GtkWidget * widget,GtkAllocation * allocation)436 static void spectool_widget_size_allocate(GtkWidget *widget,
437 										 GtkAllocation *allocation) {
438 	SpectoolWidget *wwidget = SPECTOOL_WIDGET(widget);
439 
440 	widget->allocation = *allocation;
441 
442 	gtk_widget_set_size_request(wwidget->vbox, allocation->width / 5 + 20, -1);
443 
444 	if (GTK_BIN(wwidget)->child && GTK_WIDGET_VISIBLE(GTK_BIN(wwidget)->child)) {
445 		gtk_widget_size_allocate(GTK_BIN(wwidget)->child, allocation);
446     }
447 
448 	if (wwidget->old_width != allocation->width ||
449 		wwidget->old_height != allocation->height) {
450 		if (wwidget->offscreen) {
451 			cairo_surface_destroy(wwidget->offscreen);
452 			wwidget->offscreen = NULL;
453 		}
454 
455 		wwidget->old_width = allocation->width;
456 		wwidget->old_height = allocation->height;
457 	}
458 
459 	if (wwidget->sizechange_func != NULL)
460 		(*(wwidget->sizechange_func))(widget, allocation);
461 }
462 
spectool_widget_size_request(GtkWidget * widget,GtkRequisition * requisition)463 static void spectool_widget_size_request (GtkWidget *widget, GtkRequisition *requisition) {
464 	SpectoolWidget *wwidget = SPECTOOL_WIDGET(widget);
465 
466 	requisition->width = 0;
467 	requisition->height = 0;
468 
469 	if (GTK_BIN(wwidget)->child && GTK_WIDGET_VISIBLE(GTK_BIN(wwidget)->child)) {
470 		GtkRequisition child_requisition;
471 
472 		gtk_widget_size_request(GTK_BIN(wwidget)->child, &child_requisition);
473 
474 		requisition->width += child_requisition.width;
475 		requisition->height += child_requisition.height;
476 	}
477 }
478 
spectool_widget_draw(GtkWidget * widget,cairo_t * cr,SpectoolWidget * wwidget)479 void spectool_widget_draw(GtkWidget *widget, cairo_t *cr, SpectoolWidget *wwidget) {
480 	cairo_text_extents_t extents;
481 	int x, chpix, maxcw, start_db;
482 	const double dash_onoff[] = {2, 4};
483 	const double dash_ononoff[] = {4, 2};
484 
485 	char mtext[128];
486 
487 	int chanmod;
488 
489 	g_return_if_fail(widget != NULL);
490 
491 	if (GTK_WIDGET_VISIBLE(wwidget) == 0) {
492 		return;
493 	}
494 
495 	cairo_save(cr);
496 	cairo_rectangle(cr, 0, 0, widget->allocation.width, widget->allocation.height);
497 	cairo_set_source_rgb(cr, HC2CC(0x50), HC2CC(0x50), HC2CC(0x50));
498 	cairo_fill(cr);
499 	cairo_stroke(cr);
500 
501 	/* Render the offscreen widget */
502 	if (wwidget->offscreen == NULL) {
503 		return;
504 	}
505 
506 	cairo_save(cr);
507 	cairo_set_source_surface(cr, wwidget->offscreen, 0, 0);
508 	cairo_paint(cr);
509 	cairo_restore(cr);
510 
511 	/* Render the selected channel text on top of any other data */
512 	if (wwidget->show_dbm || wwidget->show_dbm_lines) {
513 		/* Draw the dBm lines and power labels */
514 		maxcw = 0;
515 		cairo_save(cr);
516 		cairo_set_line_width(cr, 0.5);
517 
518 		start_db = 0;
519 		for (x = wwidget->base_db_offset - 1; x > wwidget->min_db_draw;
520 			 x--) {
521 			if (x % 10 == 0) {
522 				start_db = x;
523 				break;
524 			}
525 		}
526 
527 		if (start_db == 0)
528 			start_db = wwidget->base_db_offset;
529 
530 		for (x = start_db; x > wwidget->min_db_draw; x -= 10) {
531 			int py;
532 
533 			py = (float) wwidget->g_len_y *
534 				(float) ((float) (abs(x) + wwidget->base_db_offset) /
535 						 (float) (abs(wwidget->min_db_draw) +
536 								  wwidget->base_db_offset));
537 
538 			cairo_set_source_rgb(cr, 1, 1, 1);
539 
540 			if (wwidget->show_dbm_lines) {
541 				cairo_set_dash(cr, dash_onoff, 2, 0);
542 				/* .5 hack for pixel alignment */
543 				cairo_move_to(cr, wwidget->g_start_x,
544 							  wwidget->g_start_y + py + 0.5);
545 				cairo_line_to(cr, wwidget->g_end_x, wwidget->g_start_y + py + 0.5);
546 				cairo_stroke(cr);
547 			}
548 
549 			if (wwidget->show_dbm) {
550 				snprintf(mtext, 128, "%d dBm", x);
551 
552 				cairo_select_font_face(cr, "Helvetica",
553 									   CAIRO_FONT_SLANT_NORMAL,
554 									   CAIRO_FONT_WEIGHT_BOLD);
555 				cairo_set_font_size(cr, 10);
556 				cairo_text_extents(cr, mtext, &extents);
557 				cairo_move_to(cr, wwidget->g_start_x - wwidget->dbm_w,
558 							  wwidget->g_start_y + py + (extents.height / 2));
559 
560 				cairo_show_text(cr, mtext);
561 			}
562 
563 		}
564 		cairo_restore(cr);
565 	}
566 
567 	if (wwidget->chanopts != NULL && wwidget->chanopts->chanset != NULL) {
568 		/* Plot the highlighted channels */
569 		for (x = 0; x < wwidget->chanopts->chanset->chan_num; x++) {
570 			int center, spread;
571 			int start, end;
572 
573 			if (wwidget->chanopts->chanhit[x] == 0 &&
574 				wwidget->chanopts->hi_chan != x)
575 				continue;
576 
577 			if (wwidget->sweepcache->latest == NULL) {
578 				continue;
579 			}
580 
581 			/*
582 			 * We'll draw the sidebar lines on this at the end so it's on
583 			 * top of the spectral data
584 			 */
585 			center = ((float) wwidget->g_len_x /
586 					  (wwidget->sweepcache->latest->end_khz -
587 					   wwidget->sweepcache->latest->start_khz)) *
588 				(wwidget->chanopts->chanset->chan_freqs[x] -
589 				 wwidget->sweepcache->latest->start_khz) + wwidget->g_start_x;
590 
591 			spread = ((float) wwidget->g_len_x /
592 					  (wwidget->sweepcache->latest->end_khz -
593 					   wwidget->sweepcache->latest->start_khz)) *
594 				(wwidget->chanopts->chanset->chan_width);
595 
596 			start = center - (spread / 2);
597 			end = center + (spread / 2);
598 
599 			if (start < wwidget->g_start_x) {
600 				start = wwidget->g_start_x;
601 			}
602 
603 			if (end > wwidget->g_end_x) {
604 				end = wwidget->g_end_x;
605 			}
606 
607 			cairo_save(cr);
608 			/* White for highlighted channel, color for active channel */
609 			if (wwidget->chanopts->hi_chan == x) {
610 				cairo_set_source_rgba(cr, 1, 1, 1, 0.20);
611 			} else {
612 				cairo_set_source_rgba(cr,
613 									  wwidget->chanopts->chancolors[(3 * x) + 0],
614 									  wwidget->chanopts->chancolors[(3 * x) + 1],
615 									  wwidget->chanopts->chancolors[(3 * x) + 2],
616 									  0.25);
617 			}
618 			cairo_rectangle(cr, start + 0.5, wwidget->g_start_y + 0.5,
619 							end - start, wwidget->g_len_y);
620 			cairo_fill_preserve(cr);
621 			cairo_stroke(cr);
622 			cairo_move_to(cr, center, wwidget->g_start_y);
623 			cairo_line_to(cr, center, wwidget->g_end_y);
624 			cairo_stroke(cr);
625 			cairo_restore(cr);
626 
627 			/*
628 			if (wwidget->chanopts->hi_chan > -1) {
629 				snprintf(mtext, 128, "Channel %s, %d%s",
630 				wwidget->chanopts->chanset->chan_text[wwidget->chanopts->hi_chan],
631 				wwidget->chanopts->chanset->startkhz >= 1000 ?
632 				wwidget->chanopts->chanset->chan_freqs[wwidget->chanopts->hi_chan]/ 1000 :
633 				wwidget->chanopts->chanset->chan_freqs[x],
634 				wwidget->chanopts->chanset->startkhz >= 1000 ? "MHz" : "KHz");
635 
636 				cairo_save(cr);
637 				cairo_set_source_rgb(cr, 1, 1, 1);
638 				cairo_select_font_face(cr, "Helvetica",
639 									   CAIRO_FONT_SLANT_NORMAL,
640 									   CAIRO_FONT_WEIGHT_BOLD);
641 				cairo_set_font_size(cr, 14);
642 				cairo_text_extents(cr, mtext, &extents);
643 				cairo_move_to(cr, wwidget->g_end_x - extents.width - 5,
644 							  wwidget->g_start_y + extents.height + 5);
645 				cairo_show_text(cr, mtext);
646 				cairo_restore(cr);
647 			}
648 			*/
649 		}
650 	}
651 
652 	/* Redraw the bounding box */
653 	cairo_save(cr);
654 	cairo_rectangle(cr, wwidget->g_start_x, wwidget->g_start_y,
655 					wwidget->g_len_x + 0.5, wwidget->g_len_y + 0.5);
656 	cairo_set_source_rgb(cr, 1, 1, 1);
657 	cairo_stroke(cr);
658 	cairo_restore(cr);
659 
660 	cairo_restore(cr);
661 }
662 
spectool_widget_graphics_update(SpectoolWidget * wwidget)663 void spectool_widget_graphics_update(SpectoolWidget *wwidget) {
664 	cairo_text_extents_t extents;
665 	int x, chpix, maxcw, start_db;
666 	const double dash_onoff[] = {2, 4};
667 	const double dash_ononoff[] = {4, 2};
668 	cairo_t *offcr;
669 	GtkWidget *widget;
670 
671 	char mtext[128];
672 
673 	int chanmod;
674 
675 	g_return_if_fail(wwidget != NULL);
676 
677 	if (GTK_WIDGET_VISIBLE(wwidget) == 0) {
678 		return;
679 	}
680 
681 	widget = wwidget->draw;
682 
683 	/* Make an offscreen surface for this spectoolwidget if one doesn't exist (ie, it's a
684 	 * new widget, or it's been resized) */
685 	if (wwidget->offscreen == NULL) {
686 		wwidget->offscreen =
687 			cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
688 									   widget->allocation.width,
689 									   widget->allocation.height);
690 	}
691 	offcr = cairo_create(wwidget->offscreen);
692 
693 	/* Draw all our base stuff into the offscreen cr */
694 	cairo_save(offcr);
695 	cairo_rectangle(offcr, 0, 0, widget->allocation.width, widget->allocation.height);
696 	cairo_set_source_rgb(offcr, HC2CC(0x50), HC2CC(0x50), HC2CC(0x50));
697 	cairo_fill(offcr);
698 	cairo_stroke(offcr);
699 
700 	wwidget->g_len_x = widget->allocation.width - (SPECTOOL_WIDGET_PADDING * 2);
701 	wwidget->g_len_y = widget->allocation.height - (SPECTOOL_WIDGET_PADDING * 2);
702 	wwidget->g_start_x = SPECTOOL_WIDGET_PADDING;
703 	wwidget->g_start_y = SPECTOOL_WIDGET_PADDING;
704 	wwidget->g_end_x = wwidget->g_start_x + wwidget->g_len_x;
705 	wwidget->g_end_y = wwidget->g_start_y + wwidget->g_len_y;
706 
707 	/* We haven't been initialized so we don't know... anything */
708 	if (wwidget->wdr_slot < 0) {
709 		cairo_destroy(offcr);
710 		return;
711 	}
712 
713 	/* We haven't calibrated, so we don't know our channels, etc */
714 	if (wwidget->sweepcache == NULL ||
715 		(wwidget->sweepcache != NULL && wwidget->sweepcache->pos < 0)) {
716 		cairo_set_source_rgb(offcr, HC2CC(0xFF), HC2CC(0xFF), HC2CC(0xFF));
717 		cairo_select_font_face(offcr, "Helvetica",
718 							   CAIRO_FONT_SLANT_NORMAL,
719 							   CAIRO_FONT_WEIGHT_BOLD);
720 		cairo_set_font_size(offcr, 16);
721 		cairo_text_extents(offcr, "Device calibrating...", &extents);
722 		cairo_move_to(offcr, wwidget->g_start_x + (wwidget->g_len_x / 2) -
723 					  (extents.width / 2),
724 					  wwidget->g_start_y + (wwidget->g_len_y / 2) -
725 					  (extents.height / 2));
726 		cairo_show_text(offcr, "Device calibrating...");
727 		cairo_destroy(offcr);
728 		return;
729 	}
730 
731 	/* Assume we have at most a 3 digit DBM rating, and figure out the scaling.
732 	 * We use 000 since they're nice fat digits even with a variable-width font,
733 	 * and then we slap another 10% on the result.
734 	 * We re-use the same string for height calcs for the channel list, just
735 	 * new size */
736 	cairo_save(offcr);
737 	snprintf(mtext, 128, "-000 dBm");
738 	cairo_select_font_face(offcr, "Helvetica",
739 						   CAIRO_FONT_SLANT_NORMAL,
740 						   CAIRO_FONT_WEIGHT_BOLD);
741 	cairo_set_font_size(offcr, 10);
742 	cairo_text_extents(offcr, mtext, &extents);
743 	wwidget->dbm_w = extents.width + ((double) extents.width * 0.1);
744 	cairo_set_font_size(offcr, 14);
745 	cairo_text_extents(offcr, mtext, &extents);
746 	cairo_restore(offcr);
747 
748 	/* Figure out a square scaled to the number of samples we have and
749 	 * build the size of the animated graph.  wbar ends up being the
750 	 * width of a sample, so we can use that for all sorts of math later */
751 	wwidget->wbar =
752 		(double) (widget->allocation.width - wwidget->dbm_w -
753 		 (SPECTOOL_WIDGET_PADDING * 2) - 5) /
754 		(double) (wwidget->sweepcache->latest->num_samples - 1);
755 	wwidget->g_len_x = wwidget->wbar *
756 		(wwidget->sweepcache->latest->num_samples - 1);
757 	wwidget->g_len_y = widget->allocation.height - (SPECTOOL_WIDGET_PADDING * 2);
758 	wwidget->g_start_x = SPECTOOL_WIDGET_PADDING + wwidget->dbm_w;
759 	wwidget->g_start_y = SPECTOOL_WIDGET_PADDING;
760 	wwidget->g_end_x = wwidget->g_start_x + wwidget->g_len_x;
761 	wwidget->g_end_y = wwidget->g_start_y + wwidget->g_len_y;
762 
763 	cairo_rectangle(offcr, wwidget->g_start_x, wwidget->g_start_y,
764 					wwidget->g_len_x, wwidget->g_len_y);
765 	cairo_set_source_rgb(offcr, HC2CC(0x00), HC2CC(0x00), HC2CC(0x00));
766 	cairo_fill_preserve(offcr);
767 	cairo_stroke(offcr);
768 
769 	/* Call the second-level draw */
770 	if (wwidget->draw_func != NULL)
771 		(*(wwidget->draw_func))(widget, offcr, wwidget);
772 
773 	cairo_destroy(offcr);
774 }
775 
776 /* Expose event on the drawable widget */
spectool_widget_expose(GtkWidget * widget,GdkEventExpose * event,gpointer * aux)777 static gint spectool_widget_expose(GtkWidget *widget, GdkEventExpose *event,
778 								gpointer *aux) {
779 	int x, y, w, h;
780 	SpectoolWidget *wwidget;
781 	cairo_t *cr;
782 
783 	g_return_val_if_fail(widget != NULL, 0);
784 	g_return_val_if_fail(IS_SPECTOOL_WIDGET(aux), 0);
785 	wwidget = SPECTOOL_WIDGET(aux);
786 
787 	cr = gdk_cairo_create(widget->window);
788 
789 	if (event != NULL) {
790 		x = event->area.x;
791 		y = event->area.y;
792 		w = event->area.width;
793 		h = event->area.height;
794 	} else {
795 		x = 0;
796 		y = 0;
797 		w = widget->allocation.width;
798 		h = widget->allocation.height;
799 	}
800 
801 	cairo_rectangle(cr, x, y, w, h);
802 
803 	cairo_clip(cr);
804 
805 	spectool_widget_draw(widget, cr, wwidget);
806 
807 	cairo_destroy(cr);
808 
809 	return FALSE;
810 }
811 
spectool_widget_update(GtkWidget * widget)812 void spectool_widget_update(GtkWidget *widget) {
813 	SpectoolWidget *wwidget;
814 	GdkRectangle update_rect;
815 
816 	g_return_if_fail(widget != NULL);
817 	g_return_if_fail(IS_SPECTOOL_WIDGET(widget));
818 
819 	wwidget = SPECTOOL_WIDGET(widget);
820 
821 	g_return_if_fail(wwidget->draw != NULL);
822 
823 	update_rect.x = wwidget->draw->allocation.x;
824 	update_rect.y = wwidget->draw->allocation.y;
825 	update_rect.width = wwidget->draw->allocation.width;
826 	update_rect.height = wwidget->draw->allocation.height;
827 
828 	gtk_widget_draw(widget, &update_rect);
829 
830 	if (wwidget->update_func != NULL)
831 		(*(wwidget->update_func))(widget);
832 }
833 
spectool_widget_timeout(gpointer * data)834 gint spectool_widget_timeout(gpointer *data) {
835 	/* Kick the graphics update out here during a timered update */
836 	if (SPECTOOL_WIDGET(data)->dirty)
837 		spectool_widget_graphics_update(SPECTOOL_WIDGET(data));
838 
839 	SPECTOOL_WIDGET(data)->dirty = 0;
840 
841 	/* do a GTK level update */
842 	spectool_widget_update(GTK_WIDGET(data));
843 	return TRUE;
844 }
845 
spectool_widget_child_type(GtkContainer * container)846 static GType spectool_widget_child_type(GtkContainer *container) {
847 	if (!GTK_BIN(container)->child)
848 		return GTK_TYPE_WIDGET;
849 	else
850 		return G_TYPE_NONE;
851 }
852 
spectool_widget_class_init(SpectoolWidgetClass * class)853 static void spectool_widget_class_init(SpectoolWidgetClass *class) {
854 	GObjectClass *gobject_class;
855 	GtkObjectClass *object_class;
856 	GtkWidgetClass *widget_class;
857 	GtkContainerClass *container_class;
858 
859 	gobject_class = G_OBJECT_CLASS(class);
860 	object_class = GTK_OBJECT_CLASS(class);
861 	widget_class = GTK_WIDGET_CLASS(class);
862 	container_class = (GtkContainerClass*) class;
863 
864 	object_class->destroy = spectool_widget_destroy;
865 	widget_class->size_allocate = spectool_widget_size_allocate;
866 	widget_class->size_request = spectool_widget_size_request;
867 
868 	container_class->child_type = spectool_widget_child_type;
869 }
870 
871 /* Annoying that nothing else seems to include this, but we need it for
872  * calculating the gradient of colors for the channel highlights */
rgb_to_hsv(double r,double g,double b,double * h,double * s,double * v)873 inline void rgb_to_hsv(double r, double g, double b,
874 					   double *h, double *s, double *v) {
875 	double min, delta;
876 
877 	if ((b > g) && (b > r)) {
878 		*v = b;
879 		if (v != 0) {
880 			if (r > g)
881 				min = g;
882 			else
883 				min = r;
884 			delta = *v - min;
885 			if (delta != 0) {
886 				*s = (delta / *v);
887 				*h = 4 + (r - g) / delta;
888 			} else {
889 				*s = 0;
890 				*h = 4 + (r - g);
891 			}
892 			*h *= 60;
893 			if (*h < 0)
894 				*h += 360;
895 			*v = *v / 255;
896 		} else {
897 			*s = 0;
898 			*h = 0;
899 		}
900 	} else if (g > r) {
901 		*v = g;
902 		if (*v != 0) {
903 			if (r > b)
904 				min = b;
905 			else
906 				min = r;
907 
908 			delta = *v - min;
909 			if (delta != 0) {
910 				*s = (delta / *v);
911 				*h = 2 + (b - r) / delta;
912 			} else {
913 				*s = 0;
914 				*h = 2 + (b - r);
915 			}
916 			*h *= 60;
917 			if (*h < 0)
918 				*h += 360;
919 			*v = *v / 255;
920 		} else {
921 			*s = 0;
922 			*h = 0;
923 		}
924 	} else {
925 		*v = r;
926 		if (v != 0) {
927 			if (g > b)
928 				min = b;
929 			else
930 				min = g;
931 
932 			delta = *v - min;
933 
934 			if (delta != 0) {
935 				*s = (delta / *v);
936 				*h = (g - b) / delta;
937 			} else {
938 				*s = 0;
939 				*h = (g - b);
940 			}
941 
942 			*h *= 60;
943 			if (*h < 0)
944 				*h += 360;
945 			*v = *v / 255;
946 		} else {
947 			*s = 0;
948 			*h = 0;
949 		}
950 	}
951 }
952 
hsv_to_rgb(double * r,double * g,double * b,double h,double s,double v)953 inline void hsv_to_rgb(double *r, double *g, double *b,
954 					   double h, double s, double v) {
955 	double hf = h / 60;
956 	int i = floor(hf);
957 	double f = hf - i;
958 	double pv = v * (1 - s);
959 	double qv = v * (1 - s * f);
960 	double tv = v * (1 - s * (1 - f));
961 
962 	if (v == 0) {
963 		*r = 0;
964 		*g = 0;
965 		*b = 0;
966 		return;
967 	}
968 
969 	if (i == -1) {
970 		*r = v;
971 		*g = pv;
972 		*b = qv;
973 	} else if (i == 0) {
974 		*r = v;
975 		*g = tv;
976 		*b = pv;
977 	} else if (i == 1) {
978 		*r = qv;
979 		*g = v;
980 		*b = pv;
981 	} else if (i == 2) {
982 		*r = pv;
983 		*g = v;
984 		*b = tv;
985 	} else if (i == 3) {
986 		*r = pv;
987 		*g = qv;
988 		*b = v;
989 	} else if (i == 4) {
990 		*r = tv;
991 		*g = pv;
992 		*b = v;
993 	} else if (i == 5) {
994 		*r = v;
995 		*g = pv;
996 		*b = qv;
997 	} else if (i == 6) {
998 		*r = v;
999 		*g = tv;
1000 		*b = pv;
1001 	} else {
1002 		*r = 0;
1003 		*b = 0;
1004 		*g = 0;
1005 	}
1006 
1007 	*r *= 255;
1008 	*b *= 255;
1009 	*g *= 255;
1010 }
1011 
spectool_widget_context_channels(gpointer * aux)1012 void spectool_widget_context_channels(gpointer *aux) {
1013 	SpectoolWidget *wwidget;
1014 
1015 	g_return_if_fail(aux != NULL);
1016 	g_return_if_fail(IS_SPECTOOL_WIDGET(aux));
1017 
1018 	wwidget = SPECTOOL_WIDGET(aux);
1019 
1020 	if (wwidget->show_channels) {
1021 		wwidget->show_channels = 0;
1022 	} else {
1023 		wwidget->show_channels = 1;
1024 	}
1025 
1026 	spectool_widget_update(GTK_WIDGET(wwidget));
1027 }
1028 
spectool_widget_context_dbm(gpointer * aux)1029 void spectool_widget_context_dbm(gpointer *aux) {
1030 	SpectoolWidget *wwidget;
1031 
1032 	g_return_if_fail(aux != NULL);
1033 	g_return_if_fail(IS_SPECTOOL_WIDGET(aux));
1034 
1035 	wwidget = SPECTOOL_WIDGET(aux);
1036 
1037 	if (wwidget->show_dbm) {
1038 		wwidget->show_dbm = 0;
1039 	} else {
1040 		wwidget->show_dbm = 1;
1041 	}
1042 
1043 	spectool_widget_update(GTK_WIDGET(wwidget));
1044 }
1045 
spectool_widget_context_dbmlines(gpointer * aux)1046 void spectool_widget_context_dbmlines(gpointer *aux) {
1047 	SpectoolWidget *wwidget;
1048 
1049 	g_return_if_fail(aux != NULL);
1050 	g_return_if_fail(IS_SPECTOOL_WIDGET(aux));
1051 
1052 	wwidget = SPECTOOL_WIDGET(aux);
1053 
1054 	if (wwidget->show_dbm_lines) {
1055 		wwidget->show_dbm_lines = 0;
1056 	} else {
1057 		wwidget->show_dbm_lines = 1;
1058 	}
1059 
1060 	spectool_widget_update(GTK_WIDGET(wwidget));
1061 }
1062 
1063