display the measurement value in the measurebutton tooltip,
[gwave-svn.git] / src / draw.c
blobffe5f50de51861a7bb3650d105005a4aa5567790
1 /*
2 * draw.c, part of the gwave waveform viewer tool
4 * Functions for drawing waveforms
6 * Copyright (C) 1998, 1999 Stephen G. Tell
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library; if not, write to the Free
20 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include <ctype.h>
25 #include <math.h>
26 #include <setjmp.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <errno.h>
33 #include <sys/time.h>
35 #include <gtk/gtk.h>
37 #include <gwave.h>
38 #include <wavewin.h>
40 /* convert double value to printable text.
41 * Two modes:
42 * 0: using suffixes
43 * We always try to print 4 significant figures, with one nonzero digit
44 * to the left of the decimal point.
45 * maximum field width: 7 characters
47 * 1: use scientific notation, printf %g format.
48 * maximum field width appears to be 10 characters
50 char *val2txt(double val, int mode)
52 static char buf[64];
53 double aval = fabs(val);
54 double sval, asval;
55 char suffix;
56 int ddigits;
58 switch(mode) {
59 case 1:
60 sprintf(buf, "% .5g", val);
61 break;
62 case 0:
63 default:
64 if(1e12 <= aval) {
65 suffix = 'T';
66 sval = val / 1e12;
67 } else if(1e9 <= aval && aval < 1e12) {
68 suffix = 'G';
69 sval = val / 1e9;
70 } else if(1e6 <= aval && aval < 1e9) {
71 suffix = 'M';
72 sval = val / 1e6;
73 } else if(1e3 <= aval && aval < 1e6) {
74 suffix = 'K';
75 sval = val / 1000;
76 } else if(1e-3 <= aval && aval < 1) {
77 suffix = 'm';
78 sval = val * 1000;
79 } else if(1e-6 <= aval && aval < 1e-3) {
80 suffix = 'u';
81 sval = val * 1e6;
82 } else if(1e-9 <= aval && aval < 1e-6) {
83 suffix = 'n';
84 sval = val * 1e9;
85 } else if(1e-12 <= aval && aval < 1e-9) {
86 suffix = 'p';
87 sval = val * 1e12;
88 } else if(1e-15 <= aval && aval < 1e-12) {
89 suffix = 'f';
90 sval = val * 1e15;
91 } else if(DBL_EPSILON < aval && aval < 1e-15) {
92 suffix = 'a';
93 sval = val * 1e18;
94 } else {
95 suffix = ' ';
96 sval = val;
98 asval = fabs(sval);
99 if(1.0 <= asval && asval < 10.0)
100 ddigits = 4;
101 else if(10.0 <= asval && asval < 100.0)
102 ddigits = 3;
103 else
104 ddigits = 2;
105 sprintf(buf, "% .*f%c", ddigits, sval, suffix);
106 break;
108 return buf;
111 /* convert pixmap Y coordinate to user dependent-variable value */
112 double y2val(WavePanel *wp, int y)
114 int h = wp->drawing->allocation.height;
115 double frac = - (double)(y - h + 3) / (double)(h - 6);
117 if(wp->logy) {
118 double a;
119 a = frac * (log10(wp->end_yval) - log10(wp->start_yval))
120 + log10(wp->start_yval);
121 return pow(10, a);
122 } else {
123 return frac * (wp->end_yval - wp->start_yval)
124 + wp->start_yval;
128 /* convert user value to pixmap y coord */
129 int val2y(WavePanel *wp, double val)
131 double top = wp->end_yval;
132 double bot = wp->start_yval;
133 int h = wp->drawing->allocation.height;
134 double frac;
136 if(wp->logy) {
137 if(bot < 0 || val < 0)
138 return -1;
140 frac = (log10(val) - log10(bot)) /
141 (log10(top) - log10(bot));
142 } else {
143 frac = (val - bot ) / (top - bot);
146 return h - ((h-6) * frac) - 3;
149 /* convert pixmap X coordinate to user independent-variable value */
150 double x2val(WavePanel *wp, int x, int log)
152 int w = wp->drawing->allocation.width;
153 double frac = (double)x / (double)w;
155 if(log) {
156 double a;
157 a = frac * (log10(wp->end_xval) - log10(wp->start_xval))
158 + log10(wp->start_xval);
159 return pow(10, a);
160 } else {
161 return frac * (wp->end_xval - wp->start_xval)
162 + wp->start_xval;
166 /* convert independent-variable value to pixmap X coordinate */
167 int val2x(WavePanel *wp, double val, int log)
169 int w = wp->drawing->allocation.width;
170 double frac;
172 if(log) {
173 if(val < 0 || val < wp->start_xval)
174 return -1;
176 frac = (log10(val) - log10(wp->start_xval)) /
177 (log10(wp->end_xval) - log10(wp->start_xval));
178 } else {
179 frac = (val - wp->start_xval) /
180 (wp->end_xval - wp->start_xval);
182 return w * frac;
185 typedef void (*WaveDrawFunc) (VisibleWave *vw, WavePanel *wp);
186 struct wavedraw_method {
187 WaveDrawFunc func;
188 char *desc;
191 void vw_wp_draw_ppixel(VisibleWave *vw, WavePanel *wp);
192 void vw_wp_draw_lineclip(VisibleWave *vw, WavePanel *wp);
194 struct wavedraw_method wavedraw_method_tab[] = {
195 vw_wp_draw_ppixel, "per-pixel",
196 vw_wp_draw_lineclip, "correct-line"
199 const int n_wavedraw_methods = sizeof(wavedraw_method_tab)/sizeof(struct wavedraw_method);
203 * We know how to do this right, but working on other things has taken
204 * precedence. Remarkably, this isn't particularly slow or ugly looking.
206 * TODO: smarter partial redraws on scrolling, expose, etc.
208 void
209 vw_wp_visit_draw(VisibleWave *vw, WavePanel *wp)
211 if(!vw->gc) {
212 if(!vw->label) {
213 fprintf(stderr, "visit_draw(%s): label=NULL\n",
214 vw->varname);
215 return;
217 if(!gdk_color_alloc(win_colormap,
218 &vw->label->style->fg[GTK_STATE_NORMAL])) {
219 fprintf(stderr,
220 "visit_draw(%s): gdk_color_alloc failed\n",
221 vw->varname);
222 return;
224 vw->gc = gdk_gc_new(wp->drawing->window);
225 gdk_gc_set_foreground(vw->gc,
226 &vw->label->style->fg[GTK_STATE_NORMAL]);
228 g_assert(vw->gc != NULL);
229 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vw->button)))
230 gdk_gc_set_line_attributes(vw->gc,
231 2, GDK_LINE_SOLID, GDK_CAP_BUTT,
232 GDK_JOIN_ROUND);
233 else
234 gdk_gc_set_line_attributes(vw->gc,
235 1, GDK_LINE_SOLID, GDK_CAP_BUTT,
236 GDK_JOIN_ROUND);
238 (wavedraw_method_tab[1].func)(vw, wp);
241 /* finish what we started in vw_wp_visit_draw(),
242 * using various different drawing algorithms
245 /* half-assed pixel-steping wave-drawing routine.
246 * gets data value and draws a line for every pixel.
247 * will exhibit aliasing if data has samples at higher frequency than
248 * the screen has pixels.
249 * Fast but can alias badly.
251 void
252 vw_wp_draw_ppixel(VisibleWave *vw, WavePanel *wp)
254 int x0, x1;
255 int y0, y1;
256 int i;
257 double xstep;
258 double xval;
259 double yval;
260 int w = wp->drawing->allocation.width;
261 int h = wp->drawing->allocation.height;
263 xstep = (wp->end_xval - wp->start_xval)/w; /* linear only */
265 x1 = 0;
266 yval = wv_interp_value(vw->var, wp->start_xval);
267 y1 = val2y(wp, yval);
269 for(i = 0, xval = wp->start_xval; i < w; i++ ) {
270 x0 = x1; y0 = y1;
271 x1 = x0 + 1;
272 if(vw->var->wv_iv->wds->min <= xval
273 && xval <= vw->var->wv_iv->wds->max) {
275 yval = wv_interp_value(vw->var, xval);
276 y1 = val2y(wp, yval);
277 gdk_draw_line(wp->pixmap, vw->gc, x0,y0, x1,y1);
279 if(wtable->logx)
280 xval = x2val(wp, x0+1, wtable->logx);
281 else
282 xval += xstep;
287 int point_code (double x, double y,
288 double xmn, double ymn,
289 double xmx, double ymx);
290 void swap_i (int *a, int *b);
291 void swap_d (double *a, double *b);
293 /*-----------------------------------------------------------------------
294 ROUTINE: line_clip
295 PURPOSE: To clip a line segment against a rectangular window.
297 INPUT: x1, y1, double endpoints of line segment to be clipped.
298 x2, y2
299 xmn, ymn double corners of clipping window.
300 xmx, ymx
301 returns int TRUE if line segment inside window.
302 FALSE if outside.
303 LOCAL: i1, i2 int codes indicating location of points
304 relative to viewport boundaries. (see
305 description in point_code()).
307 KEYWORD: TWODIM * CLIP LINE VIEWPORT
309 REMARKS: The direction of the line segment may be reversed.
311 DATE: July '80 J.A.T, May 2002 C.D.
312 -----------------------------------------------------------------------*/
313 int line_clip (double *x1, double *y1, double *x2, double *y2,
314 double xmn, double ymn, double xmx, double ymx) {
316 int i1, i2;
318 /* Code each point. */
319 i1 = point_code (*x1, *y1, xmn, ymn, xmx, ymx);
320 i2 = point_code (*x2, *y2, xmn, ymn, xmx, ymx);
322 /* ARE BOTH ENDPOINTS IN VIEWING PORT? */
323 while(i1 != 0 || i2 != 0) {
324 if((i1 & i2) != 0) /* Is segment outside viewport? */
325 return FALSE;
326 if(i1 == 0) { /* Is point 1 outside viewport? */
327 swap_i(&i1, &i2);
328 swap_d(x1, x2);
329 swap_d(y1, y2);
332 if((i1 & 1) != 0) { /* Move toward left edge. */
333 *y1 = *y1 + (*y2 - *y1) * (xmn - *x1) / (*x2 - *x1);
334 *x1 = xmn;
335 } else
336 if((i1 & 2) != 0) { /* Move toward right edge. */
337 *y1 = *y1 + (*y2 - *y1) * (xmx - *x1) / (*x2 - *x1);
338 *x1 = xmx;
339 } else
340 if((i1 & 4) != 0) { /* Move toward bottom. */
341 *x1 = *x1 + (*x2 - *x1) * (ymn - *y1) / (*y2 - *y1);
342 *y1 = ymn;
343 } else { /* Move toward top. */
344 *x1 = *x1 + (*x2 - *x1) * (ymx - *y1) / (*y2 - *y1);
345 *y1 = ymx;
347 i1 = point_code(*x1, *y1, xmn, ymn, xmx, ymx);
349 return TRUE;
352 /*-----------------------------------------------------------------------
353 FUNCTION: point_code
354 PURPOSE: To encode a point.
355 REMARKS: This routine returns an integer code for
356 the point based on this mapping:
359 1001 * 1000 * 1010
361 ********************
363 0001 * 0000 * 0010
365 ********************
367 0101 * 0100 * 0110
370 INPUT: x,y double coordinates of point.
371 xmn, ymn double corners of window
372 xmx, ymx
373 returns int code from above (0-10)
375 KEYWORD: TWODIM * CODE POINT
377 DATE: July '80 J.A.T. May 2002 C.D.
378 -----------------------------------------------------------------------*/
379 int point_code(double x, double y,
380 double xmn, double ymn,
381 double xmx, double ymx) {
382 int ic;
384 ic = 0;
385 if(x < xmn) ic = 1;
386 if(x > xmx) ic = 2;
387 if(y < ymn) ic+=4;
388 if(y > ymx) ic+=8;
389 return ic;
392 void swap_i(int *a, int *b) {
393 int temp;
394 temp = *a;
395 *a = *b;
396 *b = temp;
397 return;
400 void swap_d(double *a, double *b) {
401 double temp;
402 temp = *a;
403 *a = *b;
404 *b = temp;
405 return;
408 /* visit all data points, applying line-clipping algorithm */
409 void
410 vw_wp_draw_lineclip(VisibleWave *vw, WavePanel *wp)
412 int x0, x1;
413 int y0, y1;
414 int i;
415 double xval1, yval1;
416 double xval0d, yval0d, xval1d, yval1d;
418 xval1 = wds_get_point(&vw->var->wv_iv->wds[0], 0);
419 yval1 = wds_get_point(&vw->var->wds[0], 0);
421 for(i = 1; i < vw->var->wtable->nvalues; i++) {
422 xval0d = xval1;
423 yval0d = yval1;
424 xval1d = xval1 = wds_get_point(&vw->var->wv_iv->wds[0], i);
425 yval1d = yval1 = wds_get_point(&vw->var->wds[0], i);
427 if(line_clip(&xval0d, &yval0d, &xval1d, &yval1d,
428 wp->start_xval, wp->start_yval,
429 wp->end_xval, wp->end_yval)) {
430 x0 = val2x(wp, xval0d, wtable->logx);
431 y0 = val2y(wp, yval0d);
432 x1 = val2x(wp, xval1d, wtable->logx);
433 y1 = val2y(wp, yval1d);
434 if(x0 != x1 || y0 != y1)
435 gdk_draw_line(wp->pixmap, vw->gc, x0,y0, x1,y1);
441 * Repaint all or part of a wavepanel.
443 void
444 draw_wavepanel(GtkWidget *widget, GdkEventExpose *event, WavePanel *wp)
446 int w = widget->allocation.width;
447 int h = widget->allocation.height;
448 int x, y;
449 int i;
451 if(wp->pixmap == NULL)
452 return;
454 gdk_draw_rectangle(wp->pixmap, bg_gdk_gc, TRUE, 0,0, w,h);
456 if(wp->selected) {
457 /* gdk_draw_line(wp->pixmap, hl_gdk_gc, 0, 0, w-1, 0);
458 gdk_draw_line(wp->pixmap, hl_gdk_gc, w-1, 0, w-1, h-1);
459 gdk_draw_line(wp->pixmap, hl_gdk_gc, w-1, h-1, 0, h-1);
460 gdk_draw_line(wp->pixmap, hl_gdk_gc, 0, h-1, 0, 0);
462 gdk_draw_line(wp->pixmap, hl_gdk_gc, 1, 1, w-2, 1);
463 gdk_draw_line(wp->pixmap, hl_gdk_gc, w-2, 1, w-2, h-2);
464 gdk_draw_line(wp->pixmap, hl_gdk_gc, w-2, h-2, 1, h-2);
465 gdk_draw_line(wp->pixmap, hl_gdk_gc, 1, h-2, 1, 1);
468 /* draw horizontal line at y=zero. future: do real graticule here */
469 if(wp->start_yval < 0 && wp->end_yval > 0) {
470 y = val2y(wp, 0);
471 gdk_draw_line(wp->pixmap, pg_gdk_gc, 0, y, w, y);
474 /* draw waves */
475 g_list_foreach(wp->vwlist, (GFunc)vw_wp_visit_draw, wp);
477 /* draw cursors */
478 for(i = 0; i < 2; i++) {
479 VBCursor *csp = wtable->cursor[i];
480 if(csp->shown) {
481 if(wp->start_xval <= csp->xval
482 && csp->xval <= wp->end_xval) {
483 x = val2x(wp, csp->xval, wtable->logx);
484 gdk_draw_line(wp->pixmap, csp->gdk_gc,
485 x, 0, x, h);
489 /* draw select-range line, if in this WavePanel */
490 if(wtable->srange->drawn && wtable->srange->wp == wp)
491 draw_srange(wtable->srange);
493 if(event) {
494 /* Draw the exposed portions of the pixmap in its window. */
495 gdk_draw_pixmap(widget->window,
496 widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
497 wp->pixmap,
498 event->area.x, event->area.y,
499 event->area.x, event->area.y,
500 event->area.width, event->area.height);
501 } else {
502 /* Draw the whole thing. */
503 gdk_draw_pixmap(widget->window,
504 widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
505 wp->pixmap,
506 0, 0, 0, 0, w, h);
511 * update text labeling the waveform graphs' X-axis
513 void draw_labels(WaveTable *wt)
515 gtk_label_set(GTK_LABEL(wt->xlabel_left), val2txt(wt->start_xval, 0));
516 gtk_label_set(GTK_LABEL(wt->xlabel_right), val2txt(wt->end_xval, 0));
520 * redraw contents of all wavepanels
522 SCM_DEFINE(wtable_redraw_x, "wtable-redraw!", 0, 0, 0, (),
523 "Redraw the waveforms in all wavepanels")
524 #define FUNC_NAME s_wtable_redraw_x
526 int i;
527 WavePanel *wp;
528 wtable->suppress_redraw = 0;
529 for(i = 0; i < wtable->npanels; i++) {
530 wp = wtable->panels[i];
531 draw_wavepanel(wp->drawing, NULL, wp);
533 return SCM_UNSPECIFIED;
535 #undef FUNC_NAME
537 /* Color allocation and related stuff for waveform drawing area
538 * background and cursors, done on first expose event.
539 * Actually, we do it all on the first expose of the first drawing area,
540 * and hope that this is OK. They are all in the same GtkWindow.
542 void alloc_colors(GtkWidget *widget)
544 int i;
545 if(win_colormap == NULL)
546 win_colormap = gdk_window_get_colormap(widget->window);
548 /* background */
549 if(bg_color_name) { /* explicitly set background color */
550 gdk_color_alloc(win_colormap, &bg_gdk_color);
551 bg_gdk_gc = gdk_gc_new(widget->window);
552 gdk_gc_set_foreground(bg_gdk_gc, &bg_gdk_color);
553 } else { /* use the widget's default one - usually grey */
554 bg_gdk_color = widget->style->bg[GTK_WIDGET_STATE(widget)];
555 bg_gdk_gc = widget->style->bg_gc[GTK_WIDGET_STATE(widget)];
557 if(v_flag)
558 fprintf(stderr, "panel background pixel=0x%x rgb=%d,%d,%d\n",
559 bg_gdk_color.pixel,
560 bg_gdk_color.red,
561 bg_gdk_color.green,
562 bg_gdk_color.blue);
564 /* vertical bar cursors */
565 for(i = 0; i < 2; i++) {
566 GdkColor tmp_color;
567 VBCursor *csp = wtable->cursor[i];
568 if(!gdk_colormap_alloc_color(win_colormap, &csp->gdk_color, FALSE, TRUE)) {
569 fprintf(stderr, "gdk_color_alloc failed for cursor %d\n", i);
570 exit(2);
572 csp->gdk_gc = gdk_gc_new(widget->window);
573 if(!csp->gdk_gc) {
574 fprintf(stderr, "couldn't allocate cursor %d gc\n", i);
575 exit(2);
577 /* compute pixel to draw so XOR makes it come out right
578 * on the background.
580 tmp_color.pixel = csp->gdk_color.pixel ^ bg_gdk_color.pixel;
581 gdk_gc_set_foreground(csp->gdk_gc, &tmp_color);
582 gdk_gc_set_function(csp->gdk_gc, GDK_XOR);
584 if(v_flag)
585 fprintf(stderr, "cursor[%d] pixel=0x%x drawpix=0x%x rgb=%d,%d,%d\n",
586 i, csp->gdk_color.pixel, tmp_color.pixel,
587 csp->gdk_color.red,
588 csp->gdk_color.green,
589 csp->gdk_color.blue);
592 /* graticule or zero-line */
593 gdk_colormap_alloc_color(win_colormap, &pg_gdk_color, FALSE, TRUE);
594 pg_gdk_gc = gdk_gc_new(widget->window);
595 if(!pg_gdk_gc) {
596 fprintf(stderr, "couldn't allocate graticule gc\n");
597 exit(2);
599 gdk_gc_set_foreground(pg_gdk_gc, &pg_gdk_color);
601 /* panel highlight */
602 gdk_colormap_alloc_color(win_colormap, &hl_gdk_color, FALSE, TRUE);
603 hl_gdk_gc = gdk_gc_new(widget->window);
604 if(!hl_gdk_gc) {
605 fprintf(stderr, "couldn't allocate highlight gc\n");
606 exit(2);
608 gdk_gc_set_foreground(hl_gdk_gc, &hl_gdk_color);
612 /* TODO: figure out how to get these colors from styles in wv.gtkrc
613 * without the hack that we use for waveform colors (picking them up from
614 * labels of the same color).
616 void setup_colors(WaveTable *wt)
618 int i;
620 /* cursors */
621 wt->cursor[0]->color_name = "white";
622 wt->cursor[1]->color_name = "yellow";
623 for(i = 0; i < 2; i++) {
624 if(!gdk_color_parse(wt->cursor[i]->color_name,
625 &wt->cursor[i]->gdk_color)) {
626 fprintf(stderr, "failed to parse cursor %d color\n", i);
627 exit(1);
631 /* range-select line */
632 if(!gdk_color_parse("white", &wt->srange->gdk_color)) {
633 fprintf(stderr, "failed to parse selrange color\n");
634 exit(1);
637 /* waveform background */
638 if(bg_color_name) {
639 if(!gdk_color_parse(bg_color_name, &bg_gdk_color)) {
640 fprintf(stderr, "failed to parse bg color\n");
641 exit(1);
645 /* waveform panel axis lines or graticule */
646 if(pg_color_name) {
647 if(!gdk_color_parse(pg_color_name, &pg_gdk_color)) {
648 fprintf(stderr, "failed to parse panel graticule color\n");
649 exit(1);
652 /* waveform panel axis lines or graticule */
653 if(hl_color_name) {
654 if(!gdk_color_parse(hl_color_name, &hl_gdk_color)) {
655 fprintf(stderr, "failed to parse highlight color\n");
656 exit(1);
661 /* given a string containing a number, possibly with a spice-syntax suffix,
662 * return the value */
663 double spice2val(char *s)
665 double val, mul;
666 char *ep;
667 val = strtod(s, &ep);
669 if(ep && ep != s && *ep) {
670 switch(*ep) {
671 case 'T':
672 mul = 1e12;
673 break;
674 case 'G':
675 mul = 1e9;
676 break;
677 case 'M':
678 mul = 1e6;
679 break;
680 case 'k':
681 case 'K':
682 mul = 1e3;
683 break;
684 case 'm':
685 mul = 1e-3;
686 break;
687 case 'u':
688 mul = 1e-6;
689 break;
690 case 'n':
691 mul = 1e-9;
692 break;
693 case 'p':
694 mul = 1e-12;
695 break;
696 case 'f':
697 mul = 1e-15;
698 break;
699 case 'a':
700 mul = 1e-18;
701 break;
702 default:
703 mul = 1;
704 break;
707 return val * mul;
708 } else {
709 return val;
713 SCM_DEFINE(spice_number, "spice->number", 1, 0, 0, (SCM str),
714 "Given a string SSTR containing a representation of a number,"
715 "possibly containing spice-style multiplier suffixes, return a real number.")
716 #define FUNC_NAME s_spice_number
718 double dval;
719 char *s;
720 VALIDATE_ARG_STR_NEWCOPY(1, str, s);
722 dval = spice2val(s);
723 free(s);
724 return scm_make_real(dval);
726 #undef FUNC_NAME
728 SCM_DEFINE(number_spice, "number->spice", 1, 0, 0, (SCM val),
729 "Given a real number VAL, return a string representation "
730 "in spice suffix syntax.")
731 #define FUNC_NAME s_number_spice
733 double dval;
734 char *s;
735 VALIDATE_ARG_DBL_COPY(1, val, dval);
736 s = val2txt(dval, 0);
737 return scm_makfrom0str(s);
739 #undef FUNC_NAME
741 /* guile initialization */
742 void init_draw()
745 #ifndef SCM_MAGIC_SNARF_INITS
746 #include "draw.x"
747 #endif