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