1 /* Metageek WiSPY interface
2  * Mike Kershaw/Dragorn <dragorn@kismetwireless.net>
3  *
4  * This code is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This code is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * Extra thanks to Ryan Woodings @ Metageek for interface documentation
15  */
16 
17 #include <math.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 
22 #include "spectool_gtk_spectral.h"
23 
24 char spectral_help_txt[] =
25 "<b>Spectral View</b>\n\n"
26 "\"Waterfall\" style view over time showing the peak spectrum usage for each "
27 "time slice.";
28 
29 /* Default set of colors inherited from the old spectool_gtk code */
30 float spect_default_colormap[] = {
31 	HC2CC(0x00), HC2CC(0x45), HC2CC(0xFE),
32 	HC2CC(0x00), HC2CC(0x5C), HC2CC(0xFE),
33 	HC2CC(0x00), HC2CC(0x73), HC2CC(0xFE),
34 	HC2CC(0x00), HC2CC(0x8A), HC2CC(0xFE),
35 	HC2CC(0x00), HC2CC(0xA1), HC2CC(0xFE),
36 	HC2CC(0x00), HC2CC(0xB8), HC2CC(0xFE),
37 	HC2CC(0x00), HC2CC(0xCF), HC2CC(0xFE),
38 	HC2CC(0x00), HC2CC(0xE6), HC2CC(0xFE),
39 	HC2CC(0x1A), HC2CC(0xEB), HC2CC(0xCB),
40 	HC2CC(0x33), HC2CC(0xF0), HC2CC(0x98),
41 	HC2CC(0x4D), HC2CC(0xF5), HC2CC(0x66),
42 	HC2CC(0x66), HC2CC(0xFA), HC2CC(0x33),
43 	HC2CC(0x80), HC2CC(0xFF), HC2CC(0x00),
44 	HC2CC(0x99), HC2CC(0xF5), HC2CC(0x00),
45 	HC2CC(0xB3), HC2CC(0xEA), HC2CC(0x00),
46 	HC2CC(0xCC), HC2CC(0xE0), HC2CC(0x00),
47 	HC2CC(0xE6), HC2CC(0xD5), HC2CC(0x00),
48 	HC2CC(0xFF), HC2CC(0xCB), HC2CC(0x00),
49 	HC2CC(0xFF), HC2CC(0xBC), HC2CC(0x00),
50 	HC2CC(0xFF), HC2CC(0xAD), HC2CC(0x00),
51 	HC2CC(0xFF), HC2CC(0x9E), HC2CC(0x00),
52 	HC2CC(0xFF), HC2CC(0x8F), HC2CC(0x00),
53 	HC2CC(0xFF), HC2CC(0x80), HC2CC(0x00),
54 	HC2CC(0xFF), HC2CC(0x74), HC2CC(0x0E),
55 	HC2CC(0xFF), HC2CC(0x68), HC2CC(0x1C),
56 	HC2CC(0xFF), HC2CC(0x5D), HC2CC(0x29),
57 	HC2CC(0xFF), HC2CC(0x51), HC2CC(0x37),
58 	HC2CC(0xFF), HC2CC(0x45), HC2CC(0x45),
59 	HC2CC(0xFF), HC2CC(0x39), HC2CC(0x53),
60 	HC2CC(0xFF), HC2CC(0x2D), HC2CC(0x61),
61 	HC2CC(0xFF), HC2CC(0x25), HC2CC(0x65)
62 };
63 int spect_default_colormap_len = 31;
64 
65 float spect_21_colormap[] = {
66 	HC2CC(0x00), HC2CC(0x00), HC2CC(0x00),
67 	HC2CC(0), HC2CC(0), HC2CC(46),
68 	HC2CC(0), HC2CC(0), HC2CC(51),
69 	HC2CC(0), HC2CC(5), HC2CC(55),
70 	HC2CC(0), HC2CC(10), HC2CC(60),
71 	HC2CC(0), HC2CC(16), HC2CC(64),
72 	HC2CC(0), HC2CC(23), HC2CC(68),
73 	HC2CC(0), HC2CC(30), HC2CC(72),
74 	HC2CC(0), HC2CC(38), HC2CC(77),
75 	HC2CC(0), HC2CC(47), HC2CC(81),
76 	HC2CC(0), HC2CC(57), HC2CC(85),
77 	HC2CC(0), HC2CC(67), HC2CC(89),
78 	HC2CC(0), HC2CC(78), HC2CC(94),
79 	HC2CC(0), HC2CC(90), HC2CC(98),
80 	HC2CC(0), HC2CC(102), HC2CC(102),
81 	HC2CC(0), HC2CC(106), HC2CC(97),
82 	HC2CC(0), HC2CC(111), HC2CC(92),
83 	HC2CC(0), HC2CC(115), HC2CC(86),
84 	HC2CC(0), HC2CC(119), HC2CC(79),
85 	HC2CC(0), HC2CC(123), HC2CC(72),
86 	HC2CC(0), HC2CC(128), HC2CC(64),
87 	HC2CC(0), HC2CC(132), HC2CC(55),
88 	HC2CC(0), HC2CC(136), HC2CC(45),
89 	HC2CC(0), HC2CC(140), HC2CC(35),
90 	HC2CC(0), HC2CC(145), HC2CC(24),
91 	HC2CC(0), HC2CC(149), HC2CC(12),
92 	HC2CC(0), HC2CC(153), HC2CC(0),
93 	HC2CC(13), HC2CC(157), HC2CC(0),
94 	HC2CC(27), HC2CC(162), HC2CC(0),
95 	HC2CC(41), HC2CC(166), HC2CC(0),
96 	HC2CC(57), HC2CC(170), HC2CC(0),
97 	HC2CC(73), HC2CC(174), HC2CC(0),
98 	HC2CC(89), HC2CC(179), HC2CC(0),
99 	HC2CC(107), HC2CC(183), HC2CC(0),
100 	HC2CC(125), HC2CC(187), HC2CC(0),
101 	HC2CC(143), HC2CC(191), HC2CC(0),
102 	HC2CC(163), HC2CC(195), HC2CC(0),
103 	HC2CC(183), HC2CC(200), HC2CC(0),
104 	HC2CC(204), HC2CC(204), HC2CC(0),
105 	HC2CC(208), HC2CC(191), HC2CC(0),
106 	HC2CC(212), HC2CC(177), HC2CC(0),
107 	HC2CC(217), HC2CC(163), HC2CC(0),
108 	HC2CC(221), HC2CC(147), HC2CC(0),
109 	HC2CC(225), HC2CC(131), HC2CC(0),
110 	HC2CC(229), HC2CC(115), HC2CC(0),
111 	HC2CC(234), HC2CC(97), HC2CC(0),
112 	HC2CC(238), HC2CC(79), HC2CC(0),
113 	HC2CC(242), HC2CC(61), HC2CC(0),
114 	HC2CC(246), HC2CC(41), HC2CC(0),
115 	HC2CC(251), HC2CC(21), HC2CC(0),
116 	HC2CC(255), HC2CC(0), HC2CC(0)
117 };
118 int spect_21_colormap_len = 50;
119 
120 static void spectool_spectral_class_init(SpectoolSpectralClass *class);
121 static void spectool_spectral_init(SpectoolSpectral *graph);
122 static void spectool_spectral_destroy(GtkObject *object);
123 
124 static gint spectool_spectral_configure(GtkWidget *widget,
125 									 GdkEventConfigure *event);
126 
127 G_DEFINE_TYPE(SpectoolSpectral, spectool_spectral, SPECTOOL_TYPE_WIDGET);
128 
spectool_spectral_draw(GtkWidget * widget,cairo_t * cr,SpectoolWidget * wwidget)129 void spectool_spectral_draw(GtkWidget *widget, cairo_t *cr, SpectoolWidget *wwidget) {
130 	SpectoolSpectral *spectral;
131 	int nsamp, pos, sp;
132 	int sh, b_s_y;
133 	cairo_pattern_t *pattern;
134 	cairo_matrix_t matrix;
135 	GdkPixmap *linemap;
136 	GdkColormap *cmap = gdk_colormap_get_system();
137 	cairo_t *lcr;
138 
139 	g_return_if_fail(widget != NULL);
140 
141 	spectral = SPECTOOL_SPECTRAL(wwidget);
142 
143 	cairo_save(cr);
144 
145 	/* Figure out the height mod our number of samples...  wwidget->wbar is
146 	 * the width of each rectangle */
147 	sh = (double) wwidget->g_len_y / wwidget->sweepcache->num_alloc;
148 
149 	if (sh < 3)
150 		sh = 3;
151 	else
152 		sh = sh + 1;
153 
154 	/* Plot across for each sample, with funny looping to handle the
155 	 * ring allocation.  The oldest sample is the first non-null sample
156 	 * after the current position.  We want to go back /drawable/ samples,
157 	 * so hack in a smarter decrement based on how we know the ring works.
158 	 * Probably an ugly way of doing it. */
159 	if (wwidget->sweepcache->pos >= (wwidget->g_len_y / sh)) {
160 		nsamp = wwidget->sweepcache->pos - (wwidget->g_len_y / sh);
161 	} else if (wwidget->sweepcache->sweeplist[wwidget->sweepcache->num_alloc - 1] !=
162 			   NULL) {
163 		nsamp = wwidget->sweepcache->num_alloc - ((wwidget->g_len_y / sh) -
164 												  wwidget->sweepcache->pos);
165 	} else {
166 		nsamp = 0;
167 	}
168 
169 	/* Catch rounding errors and other silliness */
170 	if (nsamp < 0)
171 		nsamp = 0;
172 
173 	pos = 0;
174 	while (nsamp != wwidget->sweepcache->pos &&
175 		   pos <= wwidget->sweepcache->num_alloc) {
176 		spectool_sample_sweep *samp;
177 
178 		/* Loop around the ring */
179 		if (nsamp >= wwidget->sweepcache->num_alloc)
180 			nsamp = 0;
181 
182 		/* Skip empty allocations in the ring */
183 		if (wwidget->sweepcache->sweeplist[nsamp] == NULL) {
184 			nsamp++;
185 			continue;
186 		}
187 
188 		/* Skip ones too old to be seen */
189 		if (sh * pos > wwidget->g_len_y) {
190 			break;
191 		}
192 
193 		/* Thrash out if we're outside our cache bounds, but it shouldn't
194 		 * happen */
195 		if (nsamp < 0 || nsamp >= spectral->line_cache_len) {
196 			printf("debug - outside of cache range!  %d, cache %d\n",
197 				   nsamp, spectral->line_cache_len);
198 			return;
199 		}
200 
201 		if (spectral->line_cache[nsamp] == NULL) {
202 			/* Current sample */
203 			samp = wwidget->sweepcache->sweeplist[nsamp];
204 
205 			linemap = gdk_pixmap_new(NULL, wwidget->g_len_x, sh,
206 									 cmap->visual->depth);
207 			gdk_drawable_set_colormap(linemap, cmap);
208 
209 			/* Make a pattern for cairo to draw */
210 			pattern =
211 				cairo_pattern_create_linear(0, 0, wwidget->g_len_x, 0);
212 			lcr = gdk_cairo_create(linemap);
213 			cairo_rectangle(lcr, 0, 0, wwidget->g_len_x, sh);
214 			cairo_clip(lcr);
215 
216 			for (sp = 0; sp < samp->num_samples - 1; sp++) {
217 				int cpos;
218 				int sdb =
219 					SPECTOOL_RSSI_CONVERT(wwidget->amp_offset_mdbm, wwidget->amp_res_mdbm,
220 									   samp->sample_data[sp]);
221 
222 				cpos =
223 					(float) (spectral->colormap_len) *
224 					((float) (abs(sdb) + wwidget->base_db_offset) /
225 					 (float) (abs(wwidget->min_db_draw) -
226 							  abs(wwidget->base_db_offset)));
227 
228 				if (cpos < 0)
229 					cpos = 0;
230 				else if (cpos >= spectral->colormap_len)
231 					cpos = spectral->colormap_len - 1;
232 
233 				cpos = spectral->colormap_len - cpos;
234 
235 				/* Pattern add uses 0.0 - 1.0 for stop lengths so we have to calculate
236 				 * them that way */
237 				cairo_pattern_add_color_stop_rgb(pattern,
238 								 (float) (sp) / (samp->num_samples - 1),
239 								 SPECTOOL_SPECTRAL_COLOR(spectral->colormap, cpos, 0),
240 								 SPECTOOL_SPECTRAL_COLOR(spectral->colormap, cpos, 1),
241 								 SPECTOOL_SPECTRAL_COLOR(spectral->colormap, cpos, 2));
242 			}
243 
244 			/* Draw a line over our pixmap */
245 			cairo_set_source(lcr, pattern);
246 			cairo_rectangle(lcr, -1, -1, wwidget->g_len_x + 1, sh + 1);
247 			cairo_fill(lcr);
248 			cairo_destroy(lcr);
249 			cairo_pattern_destroy(pattern);
250 			spectral->line_cache[nsamp] = linemap;
251 		}
252 
253 		/* Top position of the pixmap line, drawing from the bottom up */
254 		b_s_y = wwidget->g_end_y - ((pos + 1) * sh);
255 
256 		gdk_cairo_set_source_pixmap(cr, spectral->line_cache[nsamp],
257 									wwidget->g_start_x, b_s_y);
258 		cairo_rectangle(cr, wwidget->g_start_x + 0.5, b_s_y + 0.5,
259 						wwidget->g_len_x, sh + 1);
260 		cairo_fill(cr);
261 
262 		nsamp++;
263 		pos++;
264 
265 	}
266 
267 	cairo_restore(cr);
268 }
269 
spectool_spectral_size_allocate(GtkWidget * widget,GtkAllocation * allocation)270 static void spectool_spectral_size_allocate(GtkWidget *widget,
271 										 GtkAllocation *allocation) {
272 	SpectoolSpectral *spectral = SPECTOOL_SPECTRAL(widget);
273 	int x;
274 
275 	GTK_WIDGET_CLASS(spectool_spectral_parent_class)->size_allocate(widget, allocation);
276 
277 	/* Wipe the line cache on resize */
278 	if (allocation->width != spectral->oldx ||
279 		allocation->height != spectral->oldy) {
280 		spectral->oldx = allocation->width;
281 		spectral->oldy = allocation->height;
282 
283 		if (spectral->line_cache != NULL) {
284 			for (x = 0; x < spectral->line_cache_len; x++) {
285 				if (spectral->line_cache[x] != NULL) {
286 					gdk_pixmap_unref(spectral->line_cache[x]);
287 					spectral->line_cache[x] = NULL;
288 				}
289 
290 			}
291 		}
292 	}
293 }
294 
spectool_spectral_class_init(SpectoolSpectralClass * class)295 static void spectool_spectral_class_init(SpectoolSpectralClass *class) {
296 	GObjectClass *gobject_class;
297 	GtkObjectClass *object_class;
298 	GtkWidgetClass *widget_class;
299 
300 	gobject_class = G_OBJECT_CLASS(class);
301 	object_class = GTK_OBJECT_CLASS(class);
302 	widget_class = GTK_WIDGET_CLASS(class);
303 
304 	object_class->destroy = spectool_spectral_destroy;
305 	widget_class->size_allocate = spectool_spectral_size_allocate;
306 }
307 
spectool_spectral_destroy(GtkObject * object)308 static void spectool_spectral_destroy(GtkObject *object) {
309 	SpectoolSpectral *spectral = SPECTOOL_SPECTRAL(object);
310 	SpectoolWidget *wwidget;
311 
312 	wwidget = SPECTOOL_WIDGET(spectral);
313 
314 	GTK_OBJECT_CLASS(spectool_spectral_parent_class)->destroy(object);
315 }
316 
spectool_spectral_wdr_devbind(GtkWidget * widget,spectool_device_registry * wdr,int slot)317 static void spectool_spectral_wdr_devbind(GtkWidget *widget, spectool_device_registry *wdr,
318 									   int slot) {
319 	SpectoolSpectral *spectral;
320 	SpectoolWidget *wwidget;
321 
322 	g_return_if_fail(widget != NULL);
323 	g_return_if_fail(IS_SPECTOOL_SPECTRAL(widget));
324 	g_return_if_fail(IS_SPECTOOL_WIDGET(widget));
325 
326 	spectral = SPECTOOL_SPECTRAL(widget);
327 	wwidget = SPECTOOL_WIDGET(widget);
328 }
329 
spectool_spectral_wdr_sweep(int slot,int mode,spectool_sample_sweep * sweep,void * aux)330 static void spectool_spectral_wdr_sweep(int slot, int mode,
331 									 spectool_sample_sweep *sweep,
332 									 void *aux) {
333 	SpectoolSpectral *spectral;
334 	SpectoolWidget *wwidget;
335 	int x, tout;
336 	spectool_phy *pd;
337 
338 	g_return_if_fail(aux != NULL);
339 	g_return_if_fail(IS_SPECTOOL_SPECTRAL(aux));
340 	g_return_if_fail(IS_SPECTOOL_WIDGET(aux));
341 
342 	spectral = SPECTOOL_SPECTRAL(aux);
343 	wwidget = SPECTOOL_WIDGET(aux);
344 
345 	tout = wwidget->draw_timeout;
346 
347 	/* Update the timer */
348 	if (sweep != NULL && sweep->phydev != NULL) {
349 		pd = (spectool_phy *) sweep->phydev;
350 #ifdef HAVE_HILDON
351 		tout = 500 * pd->draw_agg_suggestion;
352 #else
353 		tout = 150 * pd->draw_agg_suggestion;
354 #endif
355 	}
356 
357 	if (tout != wwidget->draw_timeout) {
358 		wwidget->draw_timeout = tout;
359 		g_source_remove(wwidget->timeout_ref);
360 		wwidget->timeout_ref =
361 			g_timeout_add(wwidget->draw_timeout,
362 						  (GSourceFunc) spectool_widget_timeout, wwidget);
363 	}
364 
365 	if ((mode & SPECTOOL_POLL_CONFIGURED)) {
366 		/* Allocate the cache of lines to draw */
367 		if (spectral->line_cache != NULL) {
368 			for (x = 0; x < spectral->line_cache_len; x++) {
369 				if (spectral->line_cache[x] != NULL) {
370 					gdk_pixmap_unref(spectral->line_cache[x]);
371 				}
372 
373 			}
374 		}
375 		free(spectral->line_cache);
376 
377 		spectral->line_cache = malloc(sizeof(GdkPixmap *) *
378 									  wwidget->sweepcache->num_alloc);
379 		spectral->line_cache_len = wwidget->sweepcache->num_alloc;
380 		spectral->n_sweeps_delta = 0;
381 
382 		for (x = 0; x < spectral->line_cache_len; x++) {
383 			spectral->line_cache[x] = NULL;
384 		}
385 	} else if ((mode & SPECTOOL_POLL_SWEEPCOMPLETE)) {
386 		/* Null out this sweep in the cache so we have to recalculate it */
387 		if (wwidget->sweepcache->pos >= 0 &&
388 			wwidget->sweepcache->pos < spectral->line_cache_len) {
389 			if (spectral->line_cache[wwidget->sweepcache->pos] != NULL)
390 				gdk_pixmap_unref(spectral->line_cache[wwidget->sweepcache->pos]);
391 			spectral->line_cache[wwidget->sweepcache->pos] = NULL;
392 		}
393 
394 		spectral->n_sweeps_delta++;
395 	}
396 }
397 
spectool_spectral_new(void)398 GtkWidget *spectool_spectral_new(void) {
399 	SpectoolSpectral *spectral;
400 	SpectoolWidget *wwidget;
401 
402 	spectral = gtk_type_new(spectool_spectral_get_type());
403 
404 	wwidget = SPECTOOL_WIDGET(spectral);
405 
406 	return GTK_WIDGET(spectral);
407 }
408 
spectool_spectral_legend_expose(GtkWidget * widget,GdkEventExpose * event,gpointer * aux)409 static gboolean spectool_spectral_legend_expose(GtkWidget *widget,
410 											 GdkEventExpose *event,
411 											 gpointer *aux) {
412 	cairo_t *cr;
413 	int x, y, w, h, dw, dh;
414 	SpectoolSpectral *spectral;
415 	cairo_pattern_t *pattern;
416 	int cp;
417 
418 	g_return_val_if_fail(widget != NULL, FALSE);
419 	g_return_val_if_fail(aux != NULL, FALSE);
420 	g_return_val_if_fail(IS_SPECTOOL_SPECTRAL(aux), FALSE);
421 
422 	spectral = SPECTOOL_SPECTRAL(aux);
423 
424 	cr = gdk_cairo_create(widget->window);
425 
426 	if (event != NULL) {
427 		x = event->area.x;
428 		y = event->area.y;
429 		w = event->area.width;
430 		h = event->area.height;
431 	} else {
432 		x = 0;
433 		y = 0;
434 		w = widget->allocation.width;
435 		h = widget->allocation.height;
436 	}
437 	dw = widget->allocation.width;
438 	dh = widget->allocation.height;
439 
440 	cairo_rectangle(cr, x, y, w, h);
441 
442 	cairo_clip(cr);
443 
444 	pattern = cairo_pattern_create_linear(0, 0, dw, 0);
445 
446 	for (cp = 0; cp < spectral->colormap_len; cp++) {
447 		cairo_pattern_add_color_stop_rgb(pattern,
448 										 (float) (cp) / (spectral->colormap_len),
449 								 SPECTOOL_SPECTRAL_COLOR(spectral->colormap, cp, 0),
450 								 SPECTOOL_SPECTRAL_COLOR(spectral->colormap, cp, 1),
451 								 SPECTOOL_SPECTRAL_COLOR(spectral->colormap, cp, 2));
452 	}
453 
454 	cairo_set_source(cr, pattern);
455 	cairo_rectangle(cr, -1, -1, dw + 1, dh + 1);
456 	cairo_fill(cr);
457 	cairo_pattern_destroy(pattern);
458 
459 	cairo_destroy(cr);
460 
461 	return FALSE;
462 }
463 
spectool_spectral_context_help(gpointer * aux)464 void spectool_spectral_context_help(gpointer *aux) {
465 	Spectool_Help_Dialog("Spectral View", spectral_help_txt);
466 }
467 
spectool_spectral_init(SpectoolSpectral * spectral)468 static void spectool_spectral_init(SpectoolSpectral *spectral) {
469 	SpectoolWidget *wwidget;
470 
471 	GtkWidget *temp;
472 	GtkWidget *legendv, *legendh;
473 	PangoAttrList *attr_list;
474 	PangoAttribute *attr;
475 
476 	wwidget = SPECTOOL_WIDGET(spectral);
477 
478 	wwidget->sweep_num_samples = SPECTOOL_SPECTRAL_NUM_SAMPLES;
479 	wwidget->sweep_keep_avg = 0;
480 	wwidget->sweep_keep_peak = 0;
481 
482 	wwidget->sweep_num_aggregate = 3;
483 
484 	wwidget->hlines = 8;
485 	wwidget->base_db_offset = -30;
486 
487 	wwidget->graph_title = strdup("<b>Spectral View</b>");
488 	wwidget->graph_title_bg = strdup("#CC0000");
489 	wwidget->graph_control_bg = strdup("#CCA0A0");
490 
491 	wwidget->show_channels = 0;
492 	wwidget->show_dbm = 0;
493 	wwidget->show_dbm_lines = 0;
494 
495 	wwidget->wdr_sweep_func = spectool_spectral_wdr_sweep;
496 	wwidget->wdr_devbind_func = spectool_spectral_wdr_devbind;
497 	wwidget->draw_mouse_move_func = NULL;
498 	wwidget->draw_mouse_click_func = NULL;
499 #ifdef HAVE_HILDON
500 	wwidget->draw_timeout = 1500;
501 #else
502 	wwidget->draw_timeout = 500;
503 #endif
504 	wwidget->draw_func = spectool_spectral_draw;
505 
506 	wwidget->menu_func = NULL;
507 	wwidget->help_func = spectool_spectral_context_help;
508 
509 	spectral->colormap = spect_21_colormap;
510 	spectral->colormap_len = spect_21_colormap_len;
511 
512 	wwidget->timeout_ref =
513 		g_timeout_add(wwidget->draw_timeout,
514 					  (GSourceFunc) spectool_widget_timeout, wwidget);
515 
516 	spectool_widget_buildgui(wwidget);
517 
518 	temp = gtk_frame_new("Legend");
519 	gtk_box_pack_start(GTK_BOX(wwidget->vbox), temp, FALSE, FALSE, 2);
520 	legendv = gtk_vbox_new(FALSE, 2);
521 	gtk_container_add(GTK_CONTAINER(temp), legendv);
522 	gtk_widget_show(temp);
523 
524 	legendh = gtk_hbox_new(TRUE, 2);
525 	gtk_box_pack_start(GTK_BOX(legendv), legendh, TRUE, TRUE, 0);
526 
527 	spectral->legend_pix = gtk_drawing_area_new();
528 	gtk_box_pack_start(GTK_BOX(legendv), spectral->legend_pix, FALSE, FALSE, 2);
529 	gtk_drawing_area_size(GTK_DRAWING_AREA(spectral->legend_pix), -1, 10);
530 	gtk_signal_connect(GTK_OBJECT(spectral->legend_pix), "expose_event",
531 					   (GtkSignalFunc) spectool_spectral_legend_expose, spectral);
532 
533 	attr_list = pango_attr_list_new();
534 	attr = pango_attr_size_new(7.0 * PANGO_SCALE);
535 	attr->start_index = 0;
536 	attr->end_index = 100;
537 	pango_attr_list_insert(attr_list, attr);
538 
539 	temp = gtk_label_new("Min");
540 	gtk_label_set_use_markup(GTK_LABEL(temp), FALSE);
541 	gtk_label_set_use_underline(GTK_LABEL(temp), FALSE);
542 	gtk_label_set_attributes(GTK_LABEL(temp), attr_list);
543 	gtk_misc_set_alignment(GTK_MISC(temp), 0, 0.5);
544 	gtk_box_pack_start(GTK_BOX(legendh), temp, TRUE, TRUE, 0);
545 	gtk_widget_show(temp);
546 
547 	temp = gtk_label_new("Max");
548 	gtk_label_set_use_markup(GTK_LABEL(temp), FALSE);
549 	gtk_label_set_use_underline(GTK_LABEL(temp), FALSE);
550 	gtk_label_set_attributes(GTK_LABEL(temp), attr_list);
551 	gtk_misc_set_alignment(GTK_MISC(temp), 1, 0.5);
552 	gtk_box_pack_start(GTK_BOX(legendh), temp, TRUE, TRUE, 0);
553 	gtk_widget_show(temp);
554 
555 	pango_attr_list_unref(attr_list);
556 
557 	gtk_widget_show(legendv);
558 	gtk_widget_show(legendh);
559 	gtk_widget_show(spectral->legend_pix);
560 
561 	spectral->oldx = spectral->oldy = 0;
562 }
563 
564