/ src / hal / utils / scope_disp.c
scope_disp.c
  1  /** This file, 'halsc_disp.c', contains the portion of halscope
  2      that actually displays waveforms.
  3  */
  4  
  5  /** Copyright (C) 2003 John Kasunich
  6                         <jmkasunich AT users DOT sourceforge DOT net>
  7  */
  8  
  9  /** This program is free software; you can redistribute it and/or
 10      modify it under the terms of version 2 of the GNU General
 11      Public License as published by the Free Software Foundation.
 12      This library is distributed in the hope that it will be useful,
 13      but WITHOUT ANY WARRANTY; without even the implied warranty of
 14      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15      GNU General Public License for more details.
 16  
 17      You should have received a copy of the GNU General Public
 18      License along with this library; if not, write to the Free Software
 19      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 20  
 21      THE AUTHORS OF THIS LIBRARY ACCEPT ABSOLUTELY NO LIABILITY FOR
 22      ANY HARM OR LOSS RESULTING FROM ITS USE.  IT IS _EXTREMELY_ UNWISE
 23      TO RELY ON SOFTWARE ALONE FOR SAFETY.  Any machinery capable of
 24      harming persons must have provisions for completely removing power
 25      from all motors, etc, before persons enter any danger area.  All
 26      machinery must be designed to comply with local and national safety
 27      codes, and the authors of this software can not, and do not, take
 28      any responsibility for such compliance.
 29  
 30      This code was written as part of the EMC HAL project.  For more
 31      information, go to www.linuxcnc.org.
 32  */
 33  
 34  #include <math.h>
 35  #include <sys/types.h>
 36  #include <unistd.h>
 37  #include <stdio.h>
 38  #include <stdlib.h>
 39  #include <ctype.h>
 40  #include <string.h>
 41  
 42  #include "rtapi.h"		/* RTAPI realtime OS API */
 43  #include "hal.h"		/* HAL public API decls */
 44  #include "../hal_priv.h"	/* private HAL decls */
 45  
 46  #include <gtk/gtk.h>
 47  #include "miscgtk.h"		/* generic GTK stuff */
 48  #include "scope_usr.h"		/* scope related declarations */
 49  
 50  #define BUFLEN 80		/* length for sprintf buffers */
 51  
 52  /***********************************************************************
 53  *                  GLOBAL VARIABLES DECLARATIONS                       *
 54  ************************************************************************/
 55  
 56  /***********************************************************************
 57  *                   LOCAL FUNCTION PROTOTYPES                          *
 58  ************************************************************************/
 59  
 60  static void init_display_window(void);
 61  static void clear_display_window(void);
 62  static void update_readout(void);
 63  static void draw_grid(void);
 64  static void draw_baseline(int chan_num, int highlight);
 65  static void draw_waveform(int chan_num, int highlight);
 66  static void draw_triggerline(int chan_num, int highlight);
 67  static void handle_window_expose(GtkWidget * widget, gpointer data);
 68  static int handle_click(GtkWidget *widget, GdkEventButton *event, gpointer data);
 69  static int handle_release(GtkWidget *widget, GdkEventButton *event, gpointer data);
 70  static int handle_motion(GtkWidget *widget, GdkEventButton *event, gpointer data);
 71  static int handle_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer data);
 72  static void conflict_avoid(int *y, int h);
 73  static int conflict_avoid_dy(int y, int h, int dy);
 74  static void conflict_reset(int height);
 75  
 76  /***********************************************************************
 77  *                       PUBLIC FUNCTIONS                               *
 78  ************************************************************************/
 79  
 80  static int DRAWING = 0;
 81  static int cursor_valid = 0;
 82  static double cursor_time = 0;
 83  static double cursor_prev_value = 0.;
 84  static double cursor_value = 0;
 85  
 86  void init_display(void)
 87  {
 88  
 89      /* allocate a user space buffer */
 90      ctrl_usr->disp_buf = g_malloc(sizeof(scope_data_t) * ctrl_shm->buf_len);
 91      if (ctrl_usr->disp_buf == 0) {
 92  	/* malloc failed */
 93  	/* should never get here - gmalloc checks its return value */
 94  	exit(-1);
 95      }
 96      /* initialize waveform window */
 97      init_display_window();
 98  
 99      invalidate_all_channels();
100  }
101  
102  void invalidate_channel(int chan)
103  {
104      ctrl_usr->vert.data_offset[chan - 1] = -1;
105  }
106  
107  void invalidate_all_channels(void)
108  {
109      int n;
110  
111      for (n = 0; n < 16; n++) {
112  	ctrl_usr->vert.data_offset[n] = -1;
113      }
114  }
115  
116  void request_display_refresh(int delay)
117  {
118      if (delay > 5) {
119  	delay = 5;
120      }
121      if (delay < 1) {
122  	delay = 1;
123      }
124      /* request a refresh after approximately delay/10 seconds of idleness */
125      ctrl_usr->display_refresh_timer = delay;
126  }
127  
128  static int motion_x = -1, motion_y = -1;
129  
130  static void calculate_offset(int chan_num) {
131      int n;
132      scope_chan_t *chan = &(ctrl_usr->chan[chan_num]);
133      scope_data_t *dptr =
134          ctrl_usr->disp_buf + ctrl_usr->vert.data_offset[chan_num];
135      int type = chan->data_type;
136  
137      double sum=0, value;
138  
139      if(!chan->ac_offset) return;
140  
141      for(n=0; n < ctrl_usr->samples; n++) {
142  	switch (type) {
143  	case HAL_BIT:
144  	    if (dptr->d_u8) {
145  		value = 1.0;
146  	    } else {
147  		value = 0.0;
148  	    };
149  	    break;
150  	case HAL_FLOAT:
151  	    value = dptr->d_real;
152  	    break;
153  	case HAL_S32:
154  	    value = dptr->d_s32;
155  	    break;
156  	case HAL_U32:
157  	    value = dptr->d_u32;
158  	    break;
159  	default:
160  	    value = 0.0;
161  	    break;
162          }
163          sum = sum + value;
164      }
165      if(n == 0) {
166          chan->vert_offset = 0;
167      } else {
168          chan->vert_offset = sum / n;
169      }
170  }
171  
172  void refresh_display(void)
173  {
174      int n;
175      scope_disp_t *disp;
176      scope_vert_t *vert;
177      scope_horiz_t *horiz;
178      int depth;
179      double pixels_per_div, pixels_per_sec, overall_record_length;
180      double screen_center_time, screen_start_time, screen_end_time;
181  
182      /* cancel any pending refresh request */
183      ctrl_usr->display_refresh_timer = 0;
184      /* set pointers */
185      disp = &(ctrl_usr->disp);
186      vert = &(ctrl_usr->vert);
187      horiz = &(ctrl_usr->horiz);
188      /* get window pointer */
189      disp->win = disp->drawing->window;
190      if (disp->win == NULL) {
191  	/* window isn't visible yet, do nothing */
192  	printf("refresh_display(): win = NULL, bailing!\n");
193  	return;
194      }
195      /* create drawing context if needed */
196      if (disp->context == NULL) {
197  	disp->context = gdk_gc_new(disp->win);
198      }
199  
200      /* get window dimensions */
201      gdk_window_get_geometry(disp->win, NULL, NULL, &(disp->width),
202  	&(disp->height), &depth);
203      /* calculate horizontal params that depend on width */
204      pixels_per_div = disp->width * 0.1;
205      pixels_per_sec = pixels_per_div / horiz->disp_scale;
206      disp->pixels_per_sample = pixels_per_sec * horiz->sample_period;
207      overall_record_length = horiz->sample_period * ctrl_shm->rec_len;
208      screen_center_time = overall_record_length * horiz->pos_setting;
209      screen_start_time = screen_center_time - (5.0 * horiz->disp_scale);
210      disp->horiz_offset = screen_start_time * pixels_per_sec;
211      disp->start_sample = screen_start_time / horiz->sample_period;
212      if (disp->start_sample < 0) {
213  	disp->start_sample = 0;
214      }
215      screen_end_time = screen_center_time + (5.0 * horiz->disp_scale);
216      disp->end_sample = (screen_end_time / horiz->sample_period) + 1;
217      if (disp->end_sample > ctrl_shm->rec_len - 1) {
218  	disp->end_sample = ctrl_shm->rec_len - 1;
219      }
220  
221      {
222          GdkRectangle rect = {0, 0, disp->width, disp->height};
223          GdkRegion *region = gdk_region_rectangle(&rect);
224          gdk_window_begin_paint_region(disp->drawing->window, region);
225          gdk_region_destroy(region);
226      }
227  
228      DRAWING = 1;
229      clear_display_window();
230      draw_grid();
231  
232      /* calculate offsets for AC-offset channels */
233      for (n = 0; n < 16; n++) {
234          if (vert->chan_enabled[n]) calculate_offset(n);
235      }
236  
237      /* draw baselines first */
238      for (n = 0; n < 16; n++) {
239  	if ((vert->chan_enabled[n]) && (n + 1 != vert->selected)) {
240  	    draw_baseline(n + 1, FALSE);
241  	}
242      }
243      if (vert->chan_enabled[vert->selected - 1]) {
244          draw_baseline(vert->selected, TRUE);
245      }
246  
247      /* Draw trigger line */
248      if (vert->chan_enabled[ctrl_shm->trig_chan - 1]) {
249          draw_triggerline(ctrl_shm->trig_chan,
250                  ctrl_shm->trig_chan == vert->selected);
251      }
252  
253      conflict_reset(disp->height);
254  
255      /* draw non-highlighted waveforms next */
256      for (n = 0; n < 16; n++) {
257  	if ((vert->chan_enabled[n]) && (vert->data_offset[n] >= 0)
258  	    && (n + 1 != vert->selected)) {
259  	    draw_waveform(n + 1, FALSE);
260  	}
261      }
262      /* draw highlighted waveform last */
263      if ((vert->chan_enabled[vert->selected - 1])
264  	&& (vert->data_offset[vert->selected - 1] >= 0)) {
265  	draw_waveform(vert->selected, TRUE);
266      }
267  
268      update_readout();
269  
270      gdk_window_end_paint(disp->drawing->window);
271  }
272  
273  /***********************************************************************
274  *                       LOCAL FUNCTIONS                                *
275  ************************************************************************/
276  
277  gboolean alloc_color(GdkColor * color, GdkColormap * map,
278      unsigned char red, unsigned char green, unsigned char blue)
279  {
280      int retval;
281  
282      color->red = ((unsigned long) red) << 8;
283      color->green = ((unsigned long) green) << 8;
284      color->blue = ((unsigned long) blue) << 8;
285      color->pixel =
286  	((unsigned long) red) << 16 | ((unsigned long) green) << 8 |
287  	((unsigned long) blue);
288      retval = gdk_colormap_alloc_color(map, color, FALSE, TRUE);
289      if (retval == 0) {
290  	printf("alloc_color( %d, %d, %d ) failed\n", red, green, blue);
291      }
292      return retval;
293  }
294  
295  #if 0 /* this will be needed if/when I allow user defined colors */
296  static void free_color(GdkColor * color, GdkColormap * map)
297  {
298      gdk_colormap_free_colors(map, color, 1);
299  }
300  #endif
301  
302  int normal_colors[16][3] = {
303  	{204,   0,   0},
304  	{  0, 204, 204},
305  	{102, 204,   0},
306  	{102,   0, 204},
307  	{204, 153,   0},
308  	{  0, 204,  51},
309  	{  0,  51, 204},
310  	{204,   0, 153},
311  	{153,  95,  61},
312  	{141, 153,  61},
313  	{ 72, 153,  61},
314  	{ 61, 153, 118},
315  	{ 61, 118, 153},
316  	{ 72,  61, 153},
317  	{141,  61, 153},
318  	{153,  61,  95},
319  };
320  
321  int selected_colors[16][3] = {
322  	{255, 204, 204},
323  	{204, 255, 255},
324  	{229, 255, 204},
325  	{229, 204, 255},
326  	{255, 242, 204},
327  	{204, 255, 216},
328  	{204, 216, 255},
329  	{255, 204, 242},
330  	{229, 186, 160},
331  	{220, 229, 160},
332  	{169, 229, 160},
333  	{160, 229, 203},
334  	{160, 203, 229},
335  	{169, 160, 229},
336  	{220, 160, 229},
337  	{229, 160, 186},
338  };
339  
340  
341  
342  static void init_display_window(void)
343  {
344      scope_disp_t *disp;
345      int i;
346  
347      disp = &(ctrl_usr->disp);
348  
349      /* allocate a drawing area */
350      disp->drawing = gtk_drawing_area_new();
351      /* put it into the display window */
352      gtk_box_pack_start(GTK_BOX(ctrl_usr->waveform_win), disp->drawing, TRUE,
353  	TRUE, 0);
354      /* hook up a function to handle expose events */
355      gtk_signal_connect(GTK_OBJECT(disp->drawing), "expose_event",
356  	GTK_SIGNAL_FUNC(handle_window_expose), NULL);
357      gtk_signal_connect(GTK_OBJECT(disp->drawing), "button_release_event",
358          GTK_SIGNAL_FUNC(handle_release), NULL);
359      gtk_signal_connect(GTK_OBJECT(disp->drawing), "button_press_event",
360          GTK_SIGNAL_FUNC(handle_click), NULL);
361      gtk_signal_connect(GTK_OBJECT(disp->drawing), "motion_notify_event",
362          GTK_SIGNAL_FUNC(handle_motion), NULL);
363      gtk_signal_connect(GTK_OBJECT(disp->drawing), "scroll_event",
364                  GTK_SIGNAL_FUNC(handle_scroll), NULL);
365      gtk_widget_add_events(GTK_WIDGET(disp->drawing),
366              GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
367              | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
368              | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
369              | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON1_MOTION_MASK 
370              | GDK_SCROLL_MASK );
371      gtk_widget_show(disp->drawing);
372      /* get color map */
373      disp->map = gtk_widget_get_colormap(disp->drawing);
374      /* allocate colors */
375      alloc_color(&(disp->color_bg), disp->map, 0, 0, 0);
376      alloc_color(&(disp->color_grid), disp->map, 255, 255, 255);
377      alloc_color(&disp->color_baseline, disp->map, 128, 128, 128);
378      for(i = 0; i<16; i++) {
379          alloc_color(&(disp->color_normal[i]), disp->map, normal_colors[i][0], normal_colors[i][1], normal_colors[i][2]);
380          alloc_color(&(disp->color_selected[i]), disp->map, selected_colors[i][0], selected_colors[i][1], selected_colors[i][2]);
381      }
382  
383  }
384  
385  static void handle_window_expose(GtkWidget * widget, gpointer data)
386  {
387      /* we don't want to react immediately - sometime we get a burst of expose 
388         events - instead we request a refresh for later */
389      request_display_refresh(2);
390  }
391  
392  void clear_display_window(void)
393  {
394      scope_disp_t *disp;
395  
396      disp = &(ctrl_usr->disp);
397      /* set color to draw */
398      gdk_gc_set_foreground(disp->context, &(disp->color_bg));
399      /* draw a big rectangle to clear the screen */
400      gdk_draw_rectangle(disp->win, disp->context, TRUE, 0, 0, disp->width,
401  	disp->height);
402  }
403  
404  void draw_grid(void)
405  {
406      scope_disp_t *disp;
407      double xscale, yscale;
408      int xmajor, xminor, ymajor, yminor;
409      int nx, ny, m;
410      double fx, fy;
411      int x, y;
412  
413      disp = &(ctrl_usr->disp);
414      /* set color to draw */
415      gdk_gc_set_foreground(disp->context, &(disp->color_grid));
416      /* calculate scale factors */
417      xscale = disp->width - 1.0;
418      yscale = disp->height - 1.0;
419      /* calculate grid spacings */
420      xmajor = xscale * 0.1;
421      if (xmajor >= 40) {
422  	xminor = 10;
423      } else if (xmajor >= 20) {
424  	xminor = 5;
425      } else {
426  	xminor = xmajor / 4;
427      }
428      ymajor = yscale * 0.1;
429      if (ymajor >= 40) {
430  	yminor = 10;
431      } else if (ymajor >= 20) {
432  	yminor = 5;
433      } else {
434  	yminor = ymajor / 4;
435      }
436      /* draw the vertical lines */
437      for (nx = 0; nx <= 10; nx++) {
438  	/* calc the major division x coordinate */
439  	fx = nx * 0.1;
440  	for (ny = 0; ny <= 10; ny++) {
441  	    /* calc the major division y coordinate */
442  	    fy = ny * 0.1;
443  	    /* draw the major division point */
444  	    x = fx * xscale;
445  	    y = fy * yscale;
446  	    gdk_draw_point(disp->win, disp->context, x, y);
447  	    /* draw minor divisions (vertical) */
448  	    if (ny < 10) {
449  		for (m = 1; m < yminor; m++) {
450  		    y = (((0.1 * m) / yminor) + fy) * yscale;
451  		    gdk_draw_point(disp->win, disp->context, x, y);
452  		}
453  	    }
454  	    /* draw minor divisions (horizontal) */
455  	    if (nx < 10) {
456  		y = fy * yscale;
457  		for (m = 1; m < xminor; m++) {
458  		    x = (((0.1 * m) / xminor) + fx) * xscale;
459  		    gdk_draw_point(disp->win, disp->context, x, y);
460  		}
461  	    }
462  	}
463      }
464  }
465  
466  static int select_x, select_y, target;
467  static double min_dist;
468  static int select_trace(int x, int y) {
469      int n;
470      scope_disp_t *disp = &(ctrl_usr->disp);
471  
472      min_dist = hypot(disp->width, disp->height) / 100.;
473      if(min_dist < 5) min_dist = 5;
474      target = -1;
475  
476      DRAWING = 0;
477      select_x = x;
478      select_y = y;
479      for(n=0; n<16; n++) {
480          scope_vert_t *vert = &(ctrl_usr->vert);
481          draw_baseline(n+1, FALSE);
482          if((vert->chan_enabled[n]) && (vert->data_offset[n] >= 0)) {
483              draw_waveform(n+1, FALSE);
484          }
485      }
486      draw_triggerline(ctrl_shm->trig_chan, FALSE);
487      return target;
488  }
489  
490  static int handle_release(GtkWidget *widget, GdkEventButton *event, gpointer data) {
491      return 1;
492  }
493  
494  static void change_zoom(int dir, int x) {
495      scope_horiz_t *horiz = &(ctrl_usr->horiz);
496      scope_disp_t *disp = &(ctrl_usr->disp);
497  
498      double old_pixels_per_sample, pixels_per_div,
499             pixels_per_sec, new_pixels_per_sample, old_fraction, new_fraction;
500  
501      old_pixels_per_sample = disp->pixels_per_sample;
502  
503      set_horiz_zoom(horiz->zoom_setting + dir);
504  
505      /* calculate horizontal params that depend on width */
506      pixels_per_div = disp->width * 0.1;
507      pixels_per_sec = pixels_per_div / horiz->disp_scale;
508      disp->pixels_per_sample = new_pixels_per_sample = 
509          pixels_per_sec * horiz->sample_period;
510  
511      // how many samples away from the center of the window is this
512      // pixel?
513      old_fraction = (x - disp->width / 2) / old_pixels_per_sample / ctrl_shm->rec_len;
514      // and new?
515      new_fraction = (x - disp->width / 2) / new_pixels_per_sample / ctrl_shm->rec_len;
516      // displace by the difference
517      set_horiz_pos( horiz->pos_setting - new_fraction + old_fraction );
518  }
519  
520  static int handle_click(GtkWidget *widget, GdkEventButton *event, gpointer data) {
521      scope_vert_t *vert = &(ctrl_usr->vert);
522      scope_disp_t *disp = &(ctrl_usr->disp);
523      motion_y = event->y;
524      motion_x = event->x;
525      if(event->button == 4) { // zoom in
526          change_zoom(1, event->x);
527      } else if(event->button == 5) { // zoom out
528          change_zoom(-1, event->x);
529      } else {
530          int z = select_trace(event->x, event->y);
531          int new_channel = z & 0xff;
532          int channel_part = z >> 8; 
533  
534          disp->selected_part = channel_part;
535  
536          if(new_channel != vert->selected) {
537              if(z == -1) vert->selected = -1;
538              else vert->selected = new_channel;
539              channel_changed();
540          }
541          if(channel_part == 3) {
542              set_trigger_polarity(!ctrl_shm->trig_edge);
543          }
544      }
545      return 1;
546  }
547  
548  static int get_cursor_info(double *t, double *p, double *v) {
549      if(!cursor_valid) return 0;
550      if(t) *t = cursor_time;
551      if(p) *p = cursor_prev_value;
552      if(v) *v = cursor_value;
553      return 1;
554  }
555  
556  static int handle_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer data) {
557      change_zoom(event->direction ? -1 : 1, event->x);
558      return TRUE;
559  }
560  
561  static void middle_drag(int dx) {
562      scope_disp_t *disp = &(ctrl_usr->disp);
563      scope_horiz_t *horiz = &(ctrl_usr->horiz);
564      double dt = (dx / disp->pixels_per_sample) / ctrl_shm->rec_len;
565      set_horiz_pos(horiz->pos_setting + 5 * dt);
566      refresh_display();
567  }
568  
569  static double snap(int y) {
570      scope_disp_t *disp = &(ctrl_usr->disp);
571      double new_position = y * 1.0 / disp->height;
572      double mod = fmod(new_position, 0.05);
573      if(mod > .045) new_position = new_position + (.05-mod);
574      if(mod < .005) new_position = new_position - mod;
575      return new_position;
576  }
577  
578  static void left_drag(int dy, int y, GdkModifierType state) {
579      scope_disp_t *disp = &(ctrl_usr->disp);
580      scope_vert_t *vert = &(ctrl_usr->vert);
581      scope_chan_t *chan = &(ctrl_usr->chan[vert->selected - 1]);
582  
583      if(vert->selected == -1) return;
584  
585      if(disp->selected_part == 2 || (state & GDK_CONTROL_MASK)) {
586          double new_position = snap(y);
587          set_trigger_level(new_position);
588      } else if(disp->selected_part == 1 || (state & GDK_SHIFT_MASK)) {
589          double new_position = snap(y);
590          set_vert_pos(new_position);
591          // chan->position = new_position;
592          refresh_display();
593          motion_y = y;
594      } else {
595          if(abs(dy) > 5) {
596              int direction = dy > 0 ? 1 : -1;
597              int baseline_y = chan->position * disp-> height;
598              int side = select_y > baseline_y ? -1 : 1;
599              set_vert_scale(chan->scale_index + direction * side);
600              motion_y = y;
601          }
602      }
603  }
604  
605  static int handle_motion(GtkWidget *widget, GdkEventButton *event, gpointer data) {
606      scope_disp_t *disp = &(ctrl_usr->disp);
607      GdkModifierType mod;
608      int x, y;
609  
610      gdk_window_get_pointer(disp->drawing->window, &x, &y, &mod);
611      if(mod & GDK_BUTTON1_MASK) {
612          left_drag(y-motion_y, y, event->state);
613          return TRUE;
614      }
615      if(mod & GDK_BUTTON2_MASK) {
616          middle_drag(motion_x - x);
617      }
618      motion_x = x;
619      refresh_display();
620      return TRUE;
621  }
622  
623  #define TIPFORMAT "<tt>f(% 8.5f) = % 8.5f (ddt % 8.5f)</tt>"
624  void update_readout(void) {
625      scope_vert_t *vert = &(ctrl_usr->vert);
626      scope_horiz_t *horiz = &(ctrl_usr->horiz);
627      char tip[512];
628      GdkRectangle r = {vert->readout_label->allocation.x,
629              vert->readout_label->allocation.y,
630              vert->readout_label->allocation.width,
631              vert->readout_label->allocation.height};
632      if(vert->selected != -1) {
633          double t=0, p=0, v=0;
634          int result = get_cursor_info(&t, &p, &v);
635          if(result > 0) { 
636              snprintf(tip, sizeof(tip), TIPFORMAT, t, v, (v - p)/horiz->sample_period);
637          } else { 
638  	    strcpy(tip, "");
639          }
640      } else {
641              strcpy(tip, "");
642      }
643  
644      gtk_label_set_markup(GTK_LABEL(vert->readout_label), tip);
645  
646      gtk_widget_draw(vert->readout_label, &r);
647  
648  }
649  
650  struct pt { double x, y; };
651  static double dot(struct pt *a, struct pt *b) {
652      return a->x * b->x + a->y * b->y;
653  }
654  
655  static double mag(struct pt *p) {
656      return hypot(p->x, p->y);
657  }
658  
659  static double distance_point_line(int x, int y, int x1, int y1, int x2, int y2) {
660      struct pt M = {x2-x1, y2-y1},
661             Q = {x-x1, y-y1},
662             R;
663  
664      double t0 = dot(&M, &Q) / dot(&M, &M);
665      if(t0 < 0) t0 = 0;
666      if(t0 > 1) t0 = 1;
667      R.x = x - (x1 + t0 * M.x);
668      R.y = y - (y1 + t0 * M.y);
669      return mag(&R);
670  }
671  
672  #define COORDINATE_CLIP(coord)  ((coord < -32768) ? -32768 : (coord > 32767) ? 32767 : coord)
673  
674  void line(int chan_num, int x1, int y1, int x2, int y2) {
675      scope_disp_t *disp = &(ctrl_usr->disp);
676      if(DRAWING) {
677          gdk_draw_line(disp->win, disp->context, COORDINATE_CLIP(x1), COORDINATE_CLIP(y1), COORDINATE_CLIP(x2), COORDINATE_CLIP(y2));
678      } else {
679          double dist = distance_point_line(select_x, select_y, x1, y1, x2, y2);
680          if(dist < min_dist) {
681              min_dist = dist;
682              target = chan_num;
683          }
684      }
685  }
686  
687  void lines(int chan_num, GdkPoint points[], gint npoints) {
688      double dist;
689  
690      scope_disp_t *disp = &(ctrl_usr->disp);
691      if(DRAWING) {
692          gdk_draw_lines(disp->win, disp->context, points, npoints);
693      } else {
694          int x1 = points[0].x, y1 = points[0].y, x2, y2, i;
695          for(i=1; i<npoints; i++) {
696              x2 = points[i].x; y2 = points[i].y;
697              dist = distance_point_line(select_x, select_y, x1, y1, x2, y2);
698              if(dist < min_dist) {
699                  min_dist = dist;
700                  target = chan_num;
701              }
702              x1 = x2; y1 = y2;
703          }
704      }
705  }
706  
707  
708  
709  static
710  void draw_triggerline(int chan_num, int highlight) {
711      static gint8 dashes[2] = {2,4};
712      scope_disp_t *disp = &(ctrl_usr->disp);
713      scope_chan_t *chan = &(ctrl_usr->chan[chan_num - 1]);
714      scope_trig_t *trig = &(ctrl_usr->trig);
715      double yfoffset = chan->vert_offset;
716      double ypoffset = chan->position * disp->height;
717      double yscale = disp->height / (-10.0 * chan->scale);
718      double fp_level =
719          chan->scale * ((chan->position - trig->level) * 10) +
720  	chan->vert_offset;
721  
722      int y1 = (fp_level-yfoffset) * yscale + ypoffset;
723      double dx = hypot(disp->width, disp->height) * .01;
724      double dy = dx * 1.3;
725      if(dx < 5) dx = 5;
726      if(dy < dx + 1) dy = dx + 1;
727  
728      if(chan->data_type == HAL_BIT)
729          y1 = ypoffset;
730  
731      if(ctrl_shm->trig_edge) dy = -dy;
732  
733      if(highlight) {
734  	gdk_gc_set_foreground(disp->context, &(disp->color_selected[chan_num-1]));
735      } else {
736  	gdk_gc_set_foreground(disp->context, &(disp->color_normal[chan_num-1]));
737      }
738      gdk_gc_set_dashes(disp->context, 0, dashes, 2);
739      gdk_gc_set_line_attributes(disp->context, 0, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER);
740      line(chan_num | 0x200, 0, y1, disp->width, y1);
741      gdk_gc_set_line_attributes(disp->context, 0, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
742      if(highlight) {
743          gdk_gc_set_foreground(disp->context, &(disp->color_grid));
744      } else {
745          gdk_gc_set_foreground(disp->context, &(disp->color_baseline));
746      }
747      line(chan_num | 0x300, 2*dx, y1, 2*dx, y1 + 2*dy);
748      line(chan_num | 0x300, dx, y1+dy, 2*dx, y1 + 2*dy);
749      line(chan_num | 0x300, 3*dx, y1+dy, 2*dx, y1 + 2*dy);
750  }
751  
752  
753  void draw_baseline(int chan_num, int highlight) {
754      scope_disp_t *disp = &(ctrl_usr->disp);
755      scope_chan_t *chan = &(ctrl_usr->chan[chan_num - 1]);
756      double yfoffset = chan->vert_offset;
757      double ypoffset = chan->position * disp->height;
758      double yscale = disp->height / (-10.0 * chan->scale);
759      int y1 = -yfoffset * yscale + ypoffset;;
760      if(highlight) {
761          gdk_gc_set_foreground(disp->context, &(disp->color_grid));
762      } else {
763          gdk_gc_set_foreground(disp->context, &(disp->color_baseline));
764      }
765      line(chan_num | 0x100, 0, y1, disp->width, y1);
766  }
767  
768  /* waveform styles: if neither is defined, an intermediate style is used */
769  // #define DRAW_STEPPED
770  // #define DRAW_SMOOTH
771  
772  void draw_waveform(int chan_num, int highlight)
773  {
774      scope_data_t *dptr;
775      int start, end, n, sample_len;
776      scope_disp_t *disp;
777      scope_chan_t *chan;
778      double xscale, xoffset;
779      double yscale, yfoffset, ypoffset, fy, prev_fy = 0.;
780      hal_type_t type;
781      int x1, y1, x2, y2, miny, maxy, midx, ct, pn;
782      int first=1;
783      scope_horiz_t *horiz = &(ctrl_usr->horiz);
784  
785      cursor_valid = 0;
786      disp = &(ctrl_usr->disp);
787      chan = &(ctrl_usr->chan[chan_num - 1]);
788      /* calculate a bunch of local vars */
789      sample_len = ctrl_shm->sample_len;
790      xscale = disp->pixels_per_sample;
791      xoffset = disp->horiz_offset;
792      miny = -disp->height;
793      maxy = 2 * disp->height;
794      type = chan->data_type;
795      yscale = disp->height / (-10.0 * chan->scale);
796      yfoffset = chan->vert_offset;
797      ypoffset = chan->position * disp->height;
798      /* point to first sample in the record for this channel */
799      dptr = ctrl_usr->disp_buf + ctrl_usr->vert.data_offset[chan_num - 1];
800      /* point to first one that gets displayed */
801      start = disp->start_sample;
802      end = disp->end_sample;
803      ct = end - start + 1;
804      GdkPoint points[2*ct];
805      pn = 0;
806      n = start;
807      dptr += n * sample_len;
808  
809  
810      /* set color to draw */
811      if (highlight) {
812  	gdk_gc_set_foreground(disp->context, &(disp->color_selected[chan_num-1]));
813      } else {
814  	gdk_gc_set_foreground(disp->context, &(disp->color_normal[chan_num-1]));
815      }
816  
817      x1 = y1 = 0;
818      while (n <= end) {
819  	/* calc x coordinate of this point */
820  	x2 = (n * xscale) - xoffset;
821  	/* calc y coordinate of this point */
822  	switch (type) {
823  	case HAL_BIT:
824  	    if (dptr->d_u8) {
825  		fy = 1.0;
826  	    } else {
827  		fy = 0.0;
828  	    };
829  	    break;
830  	case HAL_FLOAT:
831  	    fy = dptr->d_real;
832  	    break;
833  	case HAL_S32:
834  	    fy = dptr->d_s32;
835  	    break;
836  	case HAL_U32:
837  	    fy = dptr->d_u32;
838  	    break;
839  	default:
840  	    fy = 0.0;
841  	    break;
842  	}
843  	y2 = ((fy - yfoffset) * yscale) + ypoffset;
844  	if (y2 < miny) {
845  	    y2 = miny;
846  	} else if (y2 > maxy) {
847  	    y2 = maxy;
848  	}
849          x1 = COORDINATE_CLIP(x1);
850          x2 = COORDINATE_CLIP(x2);
851          y1 = COORDINATE_CLIP(y1);
852          y2 = COORDINATE_CLIP(y2);
853  	/* don't draw segment ending at first point */
854  	if (n > start) {
855              if(pn == 0) {
856                  points[pn].x = x1; points[pn].y = y1; pn++;
857              }
858              if(xscale < 1) {
859                  if(points[pn-1].x != x2 || points[pn-1].y != y2) {
860                      points[pn].x = x2; points[pn].y = y2; pn++;
861                  }
862              } else {
863  #if defined(DRAW_SMOOTH)
864                  /* this is a smoothed line display */
865                  points[pn].x = x2; points[pn].y = y2; pn++;
866  #elif defined(DRAW_STEPPED)
867                  /* this is a stepped one */
868                  points[pn].x = x1; points[pn].y = y2; pn++;
869                  points[pn].x = x2; points[pn].y = y2; pn++;
870  #else
871                  /* this is halfway between the two extremes */
872                  midx = (x1 + x2) / 2;
873                  if(midx != x2) {
874                      points[pn].x = midx; points[pn].y = y2; pn++;
875                  }
876                  points[pn].x = x2; points[pn].y = y2; pn++;
877  #endif
878              }
879  	    if(first && highlight && DRAWING && x2 >= motion_x) {
880  		    first = 0;
881  		    gdk_draw_arc(disp->win, disp->context, TRUE,
882  				x2-3, y2-3, 7, 7, 0, 360*64);
883                      cursor_prev_value = prev_fy;
884                      cursor_value = fy;
885                      cursor_time = (n - ctrl_shm->pre_trig)*horiz->sample_period;
886                      cursor_valid = 1;
887  	    }
888  	}
889  	/* end of this segment is start of next one */
890  	x1 = x2;
891  	y1 = y2;
892  
893  	/* point to next sample */
894  	dptr += sample_len;
895  	n++;
896          prev_fy = fy;
897      }
898      if(pn) {
899          lines(chan_num, points, pn);
900          if(DRAWING) {
901              PangoLayout *p;
902              int y = points[0].y;
903              char scale[HAL_NAME_LEN];
904              char buffer[2 * HAL_NAME_LEN];
905              int h;
906              PangoRectangle r;
907  
908              format_scale_value(scale, sizeof(scale), chan->scale);
909              snprintf(buffer, sizeof(buffer), "%s\n%s", chan->name, scale);
910              p=gtk_widget_create_pango_layout(disp->drawing, buffer);
911              pango_layout_get_extents(p, NULL, &r);
912              h = PANGO_PIXELS(r.height);
913  
914              if(y < 0 || y+h > disp->height)
915                  // if the first sample isn't visible, try the zero value
916                  y = (0-yfoffset) * yscale + ypoffset;
917              if(y < 0 || y+h > disp->height)
918                  // if that's not visible either, try the offset value
919                  y = ypoffset;
920  
921              conflict_avoid(&y, h);
922              gdk_draw_layout(disp->win, disp->context, 5, y, p);
923              g_object_unref(p);
924          }
925      }
926  }
927  
928  static int ch=0;
929  // X limits all windows to 16-bit heights, so this static array will be OK
930  static char conflict_map[32768];
931  
932  void conflict_reset(int h) {
933      ch = h;
934      memset(conflict_map, 0, sizeof(conflict_map));
935  }
936  
937  int conflict_avoid_dy(int y0, int h, int dy) {
938      int oc = 0;
939      for(; y0 > 0 && y0 < ch-h; y0 += dy) {
940          if(conflict_map[y0]) { oc = 0; }
941          oc++;
942          if(oc == h) break;
943      }
944      return y0;
945  }
946  
947  void conflict_avoid(int *y, int h) {
948      int yd = conflict_avoid_dy(*y, h, 1)-h;
949      int yu = conflict_avoid_dy(*y, h, -1);
950      if(abs(yd-*y) < abs(yu-*y)) *y = yd;
951      else *y = yu;
952      memset(conflict_map+*y, 1, h);
953  }
954  
955  // vim:sts=4:sw=4:et