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.
40 /* convert double value to printable text.
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
)
53 double aval
= fabs(val
);
60 sprintf(buf
, "% .5g", val
);
67 } else if(1e9
<= aval
&& aval
< 1e12
) {
70 } else if(1e6
<= aval
&& aval
< 1e9
) {
73 } else if(1e3
<= aval
&& aval
< 1e6
) {
76 } else if(1e-3 <= aval
&& aval
< 1) {
79 } else if(1e-6 <= aval
&& aval
< 1e-3) {
82 } else if(1e-9 <= aval
&& aval
< 1e-6) {
85 } else if(1e-12 <= aval
&& aval
< 1e-9) {
88 } else if(1e-15 <= aval
&& aval
< 1e-12) {
91 } else if(DBL_EPSILON
< aval
&& aval
< 1e-15) {
99 if(1.0 <= asval
&& asval
< 10.0)
101 else if(10.0 <= asval
&& asval
< 100.0)
105 sprintf(buf
, "% .*f%c", ddigits
, sval
, suffix
);
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);
119 a
= frac
* (log10(wp
->end_yval
) - log10(wp
->start_yval
))
120 + log10(wp
->start_yval
);
123 return frac
* (wp
->end_yval
- 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
;
137 if(bot
< 0 || val
< 0)
140 frac
= (log10(val
) - log10(bot
)) /
141 (log10(top
) - log10(bot
));
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
;
157 a
= frac
* (log10(wp
->end_xval
) - log10(wp
->start_xval
))
158 + log10(wp
->start_xval
);
161 return frac
* (wp
->end_xval
- 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
;
173 if(val
< 0 || val
< wp
->start_xval
)
176 frac
= (log10(val
) - log10(wp
->start_xval
)) /
177 (log10(wp
->end_xval
) - log10(wp
->start_xval
));
179 frac
= (val
- wp
->start_xval
) /
180 (wp
->end_xval
- wp
->start_xval
);
185 typedef void (*WaveDrawFunc
) (VisibleWave
*vw
, WavePanel
*wp
);
186 struct wavedraw_method
{
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.
209 vw_wp_visit_draw(VisibleWave
*vw
, WavePanel
*wp
)
213 fprintf(stderr
, "visit_draw(%s): label=NULL\n",
217 if(!gdk_color_alloc(win_colormap
,
218 &vw
->label
->style
->fg
[GTK_STATE_NORMAL
])) {
220 "visit_draw(%s): gdk_color_alloc failed\n",
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
,
234 gdk_gc_set_line_attributes(vw
->gc
,
235 1, GDK_LINE_SOLID
, GDK_CAP_BUTT
,
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.
252 vw_wp_draw_ppixel(VisibleWave
*vw
, WavePanel
*wp
)
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 */
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
++ ) {
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
);
280 xval
= x2val(wp
, x0
+1, wtable
->logx
);
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 /*-----------------------------------------------------------------------
295 PURPOSE: To clip a line segment against a rectangular window.
297 INPUT: x1, y1, double endpoints of line segment to be clipped.
299 xmn, ymn double corners of clipping window.
301 returns int TRUE if line segment inside window.
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
) {
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? */
326 if(i1
== 0) { /* Is point 1 outside viewport? */
332 if((i1
& 1) != 0) { /* Move toward left edge. */
333 *y1
= *y1
+ (*y2
- *y1
) * (xmn
- *x1
) / (*x2
- *x1
);
336 if((i1
& 2) != 0) { /* Move toward right edge. */
337 *y1
= *y1
+ (*y2
- *y1
) * (xmx
- *x1
) / (*x2
- *x1
);
340 if((i1
& 4) != 0) { /* Move toward bottom. */
341 *x1
= *x1
+ (*x2
- *x1
) * (ymn
- *y1
) / (*y2
- *y1
);
343 } else { /* Move toward top. */
344 *x1
= *x1
+ (*x2
- *x1
) * (ymx
- *y1
) / (*y2
- *y1
);
347 i1
= point_code(*x1
, *y1
, xmn
, ymn
, xmx
, ymx
);
352 /*-----------------------------------------------------------------------
354 PURPOSE: To encode a point.
355 REMARKS: This routine returns an integer code for
356 the point based on this mapping:
370 INPUT: x,y double coordinates of point.
371 xmn, ymn double corners of window
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
) {
392 void swap_i(int *a
, int *b
) {
400 void swap_d(double *a
, double *b
) {
408 /* visit all data points, applying line-clipping algorithm */
410 vw_wp_draw_lineclip(VisibleWave
*vw
, WavePanel
*wp
)
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
++) {
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.
444 draw_wavepanel(GtkWidget
*widget
, GdkEventExpose
*event
, WavePanel
*wp
)
446 int w
= widget
->allocation
.width
;
447 int h
= widget
->allocation
.height
;
451 if(wp
->pixmap
== NULL
)
454 gdk_draw_rectangle(wp
->pixmap
, bg_gdk_gc
, TRUE
, 0,0, w
,h
);
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) {
471 gdk_draw_line(wp
->pixmap
, pg_gdk_gc
, 0, y
, w
, y
);
475 g_list_foreach(wp
->vwlist
, (GFunc
)vw_wp_visit_draw
, wp
);
478 for(i
= 0; i
< 2; i
++) {
479 VBCursor
*csp
= wtable
->cursor
[i
];
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
,
489 /* draw select-range line, if in this WavePanel */
490 if(wtable
->srange
->drawn
&& wtable
->srange
->wp
== wp
)
491 draw_srange(wtable
->srange
);
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
)],
498 event
->area
.x
, event
->area
.y
,
499 event
->area
.x
, event
->area
.y
,
500 event
->area
.width
, event
->area
.height
);
502 /* Draw the whole thing. */
503 gdk_draw_pixmap(widget
->window
,
504 widget
->style
->fg_gc
[GTK_WIDGET_STATE(widget
)],
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
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
;
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
)
545 if(win_colormap
== NULL
)
546 win_colormap
= gdk_window_get_colormap(widget
->window
);
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
)];
558 fprintf(stderr
, "panel background pixel=0x%x rgb=%d,%d,%d\n",
564 /* vertical bar cursors */
565 for(i
= 0; i
< 2; i
++) {
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
);
572 csp
->gdk_gc
= gdk_gc_new(widget
->window
);
574 fprintf(stderr
, "couldn't allocate cursor %d gc\n", i
);
577 /* compute pixel to draw so XOR makes it come out right
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
);
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
,
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
);
596 fprintf(stderr
, "couldn't allocate graticule gc\n");
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
);
605 fprintf(stderr
, "couldn't allocate highlight gc\n");
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
)
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
);
631 /* range-select line */
632 if(!gdk_color_parse("white", &wt
->srange
->gdk_color
)) {
633 fprintf(stderr
, "failed to parse selrange color\n");
637 /* waveform background */
639 if(!gdk_color_parse(bg_color_name
, &bg_gdk_color
)) {
640 fprintf(stderr
, "failed to parse bg color\n");
645 /* waveform panel axis lines or graticule */
647 if(!gdk_color_parse(pg_color_name
, &pg_gdk_color
)) {
648 fprintf(stderr
, "failed to parse panel graticule color\n");
652 /* waveform panel axis lines or graticule */
654 if(!gdk_color_parse(hl_color_name
, &hl_gdk_color
)) {
655 fprintf(stderr
, "failed to parse highlight color\n");
661 /* given a string containing a number, possibly with a spice-syntax suffix,
662 * return the value */
663 double spice2val(char *s
)
667 val
= strtod(s
, &ep
);
669 if(ep
&& ep
!= s
&& *ep
) {
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
720 VALIDATE_ARG_STR_NEWCOPY(1, str
, s
);
724 return scm_make_real(dval
);
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
735 VALIDATE_ARG_DBL_COPY(1, val
, dval
);
736 s
= val2txt(dval
, 0);
737 return scm_makfrom0str(s
);
741 /* guile initialization */
745 #ifndef SCM_MAGIC_SNARF_INITS