/ src / hal / utils / scope_vert.c
scope_vert.c
   1  /** This file, 'halsc_vert.c', contains the portion of halscope
   2      that deals with vertical stuff - signal sources, scaling,
   3      position and such.
   4  */
   5  
   6  /** Copyright (C) 2003 John Kasunich
   7                         <jmkasunich AT users DOT sourceforge DOT net>
   8  */
   9  
  10  /** This program is free software; you can redistribute it and/or
  11      modify it under the terms of version 2 of the GNU General
  12      Public License as published by the Free Software Foundation.
  13      This library 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.
  17  
  18      You should have received a copy of the GNU General Public
  19      License along with this library; if not, write to the Free Software
  20      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  21  
  22      THE AUTHORS OF THIS LIBRARY ACCEPT ABSOLUTELY NO LIABILITY FOR
  23      ANY HARM OR LOSS RESULTING FROM ITS USE.  IT IS _EXTREMELY_ UNWISE
  24      TO RELY ON SOFTWARE ALONE FOR SAFETY.  Any machinery capable of
  25      harming persons must have provisions for completely removing power
  26      from all motors, etc, before persons enter any danger area.  All
  27      machinery must be designed to comply with local and national safety
  28      codes, and the authors of this software can not, and do not, take
  29      any responsibility for such compliance.
  30  
  31      This code was written as part of the EMC HAL project.  For more
  32      information, go to www.linuxcnc.org.
  33  */
  34  
  35  #include "config.h"
  36  #include <locale.h>
  37  #include <libintl.h>
  38  #define _(x) gettext(x)
  39  
  40  #include <sys/types.h>
  41  #include <unistd.h>
  42  #include <stdio.h>
  43  #include <stdlib.h>
  44  #include <ctype.h>
  45  #include <string.h>
  46  
  47  #include "rtapi.h"		/* RTAPI realtime OS API */
  48  #include <rtapi_mutex.h>
  49  #include "hal.h"		/* HAL public API decls */
  50  #include "../hal_priv.h"	/* private HAL decls */
  51  
  52  #include <gtk/gtk.h>
  53  #include <gdk/gdkkeysyms.h>
  54  
  55  #include "miscgtk.h"		/* generic GTK stuff */
  56  #include "scope_usr.h"		/* scope related declarations */
  57  
  58  #define BUFLEN 80		/* length for sprintf buffers */
  59  
  60  /***********************************************************************
  61  *                  GLOBAL VARIABLES DECLARATIONS                       *
  62  ************************************************************************/
  63  
  64  #define VERT_POS_RESOLUTION 100.0
  65  
  66  /* The channel select buttons sometimes need to be toggled by
  67     the code rather than the user, without causing any action.
  68     This global is used for that */
  69  static int ignore_click = 0;
  70  
  71  /***********************************************************************
  72  *                  LOCAL FUNCTION PROTOTYPES                           *
  73  ************************************************************************/
  74  
  75  struct offset_data {
  76      char buf[BUFLEN];
  77      int ac_coupled;
  78  };
  79  
  80  static void init_chan_sel_window(void);
  81  static void init_chan_info_window(void);
  82  static void init_vert_info_window(void);
  83  
  84  static gboolean dialog_select_source(int chan_num);
  85  static void selection_made(GtkWidget * clist, gint row, gint column,
  86      GdkEventButton * event, dialog_generic_t * dptr);
  87  static void change_source_button(GtkWidget * widget, gpointer gdata);
  88  static void channel_off_button(GtkWidget * widget, gpointer gdata);
  89  static void offset_button(GtkWidget * widget, gpointer gdata);
  90  static gboolean dialog_set_offset(int chan_num);
  91  static void scale_changed(GtkAdjustment * adj, gpointer gdata);
  92  static void offset_changed(GtkEditable * editable, struct offset_data *);
  93  static void offset_activated(GtkEditable * editable, gchar * button);
  94  static void pos_changed(GtkAdjustment * adj, gpointer gdata);
  95  static void chan_sel_button(GtkWidget * widget, gpointer gdata);
  96  
  97  /* helper functions */
  98  static void write_chan_config(FILE *fp, scope_chan_t *chan);
  99  
 100  /***********************************************************************
 101  *                       PUBLIC FUNCTIONS                               *
 102  ************************************************************************/
 103  
 104  void init_vert(void)
 105  {
 106      scope_chan_t *chan;
 107      int n;
 108  
 109      /* stop sampling */
 110      ctrl_shm->state = IDLE;
 111      /* init non-zero members of the vertical structure */
 112      invalidate_all_channels();
 113      /* init non-zero members of the channel structures */
 114      for (n = 1; n <= 16; n++) {
 115  	chan = &(ctrl_usr->chan[n - 1]);
 116  	chan->position = 0.5;
 117      }
 118      /* set up the windows */
 119      init_chan_sel_window();
 120      init_chan_info_window();
 121      init_vert_info_window();
 122  }
 123  
 124  int set_active_channel(int chan_num)
 125  {
 126      int n, count;
 127      scope_vert_t *vert;
 128      scope_chan_t *chan;
 129      if (( chan_num < 1 ) || ( chan_num > 16 )) {
 130  	return -1;
 131      }
 132      vert = &(ctrl_usr->vert);
 133      chan = &(ctrl_usr->chan[chan_num - 1]);
 134  
 135      if (vert->chan_enabled[chan_num - 1] == 0 ) {
 136  	/* channel is disabled, want to enable it */
 137  	if (ctrl_shm->state != IDLE) {
 138  	    /* acquisition in progress, must restart it */
 139              prepare_scope_restart();
 140  	}
 141  	count = 0;
 142  	for (n = 0; n < 16; n++) {
 143  	    if (vert->chan_enabled[n]) {
 144  		count++;
 145  	    }
 146  	}
 147  	if (count >= ctrl_shm->sample_len) {
 148  	    /* max number of channels already enabled */
 149  	    return -2;
 150  	}
 151  	if (chan->name == NULL) {
 152  	    /* no signal source */
 153  	    return -3;
 154  	}
 155  	/* "push" the button in to indicate enabled channel */
 156  	ignore_click = 1;
 157  	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vert->chan_sel_buttons[chan_num-1]), TRUE);
 158  	vert->chan_enabled[chan_num - 1] = 1;
 159      }
 160      if (vert->selected != chan_num) {
 161  	/* make chan_num the selected channel */
 162  	vert->selected = chan_num;
 163  	channel_changed();
 164      }
 165      return 0;
 166  }
 167  
 168  int set_channel_off(int chan_num)
 169  {
 170      scope_vert_t *vert;
 171      int n;
 172  
 173      if ((chan_num < 1) || (chan_num > 16)) {
 174  	return -1;
 175      }
 176      vert = &(ctrl_usr->vert);
 177      if ( vert->chan_enabled[chan_num - 1] == 0 ) {
 178  	/* channel is already off, nothing to do */
 179  	return -1;
 180      }
 181      vert->chan_enabled[chan_num - 1] = 0;
 182      /* force the button to pop out */
 183      ignore_click = 1;
 184      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vert->
 185  	    chan_sel_buttons[chan_num - 1]), FALSE);
 186      if ( chan_num == vert->selected ) {
 187  	/* channel was selected, pick new selected channel */
 188  	n = 0;
 189  	vert->selected = 0;
 190  	do {
 191  	    chan_num++;
 192  	    if (chan_num > 16) {
 193  		chan_num = 1;
 194  	    }
 195  	    if (vert->chan_enabled[chan_num - 1] != 0) {
 196  		vert->selected = chan_num;
 197  	    }
 198  	} while ((++n < 16) && (vert->selected == 0));
 199      }
 200      channel_changed();
 201      return 0;
 202  }
 203  
 204  int set_channel_source(int chan_num, int type, char *name)
 205  {
 206      scope_vert_t *vert;
 207      scope_chan_t *chan;
 208      hal_pin_t *pin;
 209      hal_sig_t *sig;
 210      hal_param_t *param;
 211  
 212      vert = &(ctrl_usr->vert);
 213      chan = &(ctrl_usr->chan[chan_num - 1]);
 214      /* locate the selected item in the HAL */
 215      if (type == 0) {
 216  	/* search the pin list */
 217  	pin = halpr_find_pin_by_name(name);
 218  	if (pin == NULL) {
 219  	    /* pin not found */
 220  	    return -1;
 221  	}
 222  	chan->data_source_type = 0;
 223  	chan->data_source = SHMOFF(pin);
 224  	chan->data_type = pin->type;
 225  	chan->name = pin->name;
 226      } else if (type == 1) {
 227  	/* search the signal list */
 228  	sig = halpr_find_sig_by_name(name);
 229  	if (sig == NULL) {
 230  	    /* signal not found */
 231  	    return -1;
 232  	}
 233  	chan->data_source_type = 1;
 234  	chan->data_source = SHMOFF(sig);
 235  	chan->data_type = sig->type;
 236  	chan->name = sig->name;
 237      } else if (type == 2) {
 238  	/* search the parameter list */
 239  	param = halpr_find_param_by_name(name);
 240  	if (param == NULL) {
 241  	    /* parameter not found */
 242  	    return -1;
 243  	}
 244  	chan->data_source_type = 2;
 245  	chan->data_source = SHMOFF(param);
 246  	chan->data_type = param->type;
 247  	chan->name = param->name;
 248      }
 249      switch (chan->data_type) {
 250      case HAL_BIT:
 251  	chan->data_len = sizeof(hal_bit_t);
 252  	chan->min_index = -2;
 253  	chan->max_index = 2;
 254  	break;
 255      case HAL_FLOAT:
 256  	chan->data_len = sizeof(hal_float_t);
 257  	chan->min_index = -36;
 258  	chan->max_index = 36;
 259  	break;
 260      case HAL_S32:
 261  	chan->data_len = sizeof(hal_s32_t);
 262  	chan->min_index = -2;
 263  	chan->max_index = 30;
 264  	break;
 265      case HAL_U32:
 266  	chan->data_len = sizeof(hal_u32_t);
 267  	chan->min_index = -2;
 268  	chan->max_index = 30;
 269  	break;
 270      default:
 271  	/* Shouldn't get here, but just in case... */
 272  	chan->data_len = 0;
 273  	chan->min_index = -1;
 274  	chan->max_index = 1;
 275      }
 276      /* invalidate any data in the buffer for this channel */
 277      vert->data_offset[chan_num - 1] = -1;
 278      /* set scale and offset to nominal values */
 279      chan->vert_offset = 0.0;
 280      chan->scale_index = 0;
 281      /* return success */
 282      return 0;
 283  }
 284  
 285  int set_vert_scale(int setting)
 286  {
 287      scope_vert_t *vert;
 288      scope_chan_t *chan;
 289      GtkAdjustment *adj;
 290      int chan_num, index;
 291      double scale;
 292      gchar buf[BUFLEN];
 293  
 294      vert = &(ctrl_usr->vert);
 295      chan_num = vert->selected;
 296      if ((chan_num < 1) || (chan_num > 16)) {
 297  	return -1;
 298      }
 299      chan = &(ctrl_usr->chan[chan_num - 1]);
 300      if ((setting > chan->max_index) || (setting < chan->min_index)) {
 301  	/* value out of range for this data type */
 302  	return -1;
 303      }
 304      /* save new index */
 305      chan->scale_index = setting;
 306      /* set scale slider based on new setting */
 307      adj = GTK_ADJUSTMENT(vert->scale_adj);
 308      gtk_adjustment_set_value(adj, setting);
 309  
 310      /* compute scale factor */
 311      scale = 1.0;
 312      index = chan->scale_index;
 313      while (index >= 3) {
 314  	scale *= 10.0;
 315  	index -= 3;
 316      }
 317      while (index <= -3) {
 318  	scale *= 0.1;
 319  	index += 3;
 320      }
 321      switch (index) {
 322      case 2:
 323  	scale *= 5.0;
 324  	break;
 325      case 1:
 326  	scale *= 2.0;
 327  	break;
 328      case -1:
 329  	scale *= 0.5;
 330  	break;
 331      case -2:
 332  	scale *= 0.2;
 333  	break;
 334      default:
 335  	break;
 336      }
 337      chan->scale = scale;
 338      format_scale_value(buf, BUFLEN - 1, scale);
 339      gtk_label_set_text_if(vert->scale_label, buf);
 340      if (chan_num == ctrl_shm->trig_chan) {
 341  	refresh_trigger();
 342      }
 343      request_display_refresh(1);
 344      return 0;
 345  }
 346  
 347  int set_vert_pos(double setting)
 348  {
 349      scope_vert_t *vert;
 350      scope_chan_t *chan;
 351      int chan_num;
 352      GtkAdjustment *adj;
 353  
 354      /* range check setting */
 355      if (( setting < 0.0 ) || ( setting > 1.0 )) {
 356  	return -1;
 357      }
 358      /* point to data */
 359      vert = &(ctrl_usr->vert);
 360      chan_num = vert->selected;
 361      if ((chan_num < 1) || (chan_num > 16)) {
 362  	return -1;
 363      }
 364      chan = &(ctrl_usr->chan[chan_num - 1]);
 365      chan->position = setting;
 366      /* set position slider based on new setting */
 367      adj = GTK_ADJUSTMENT(vert->pos_adj);
 368      gtk_adjustment_set_value(adj, chan->position * VERT_POS_RESOLUTION);
 369      /* refresh other stuff */    
 370      if (chan_num == ctrl_shm->trig_chan) {
 371  	refresh_trigger();
 372      }
 373      request_display_refresh(1);
 374      return 0;
 375  }
 376  
 377  int set_vert_offset(double setting, int ac_coupled)
 378  {
 379      scope_vert_t *vert;
 380      scope_chan_t *chan;
 381      int chan_num;
 382      gchar buf1[BUFLEN + 1], buf2[BUFLEN + 1];
 383  
 384      /* point to data */
 385      vert = &(ctrl_usr->vert);
 386      chan_num = vert->selected;
 387      if ((chan_num < 1) || (chan_num > 16)) {
 388  	return -1;
 389      }
 390      chan = &(ctrl_usr->chan[chan_num - 1]);
 391      /* set the new offset */ 
 392      chan->vert_offset = setting;
 393      chan->ac_offset = ac_coupled;
 394      /* update the offset display */
 395      if (chan->data_type == HAL_BIT) {
 396  	snprintf(buf1, BUFLEN, "----");
 397      } else {
 398          if(chan->ac_offset) {
 399              snprintf(buf1, BUFLEN, "(AC)");
 400          } else {
 401              format_signal_value(buf1, BUFLEN, chan->vert_offset);
 402          }
 403      }
 404      snprintf(buf2, BUFLEN, _("Offset\n%s"), buf1);
 405      gtk_label_set_text_if(vert->offset_label, buf2);
 406      /* refresh other stuff */    
 407      if (chan_num == ctrl_shm->trig_chan) {
 408  	refresh_trigger();
 409      }
 410      request_display_refresh(1);
 411      return 0;
 412  }
 413  
 414  void format_signal_value(char *buf, int buflen, double value)
 415  {
 416      char *units;
 417      int decimals;
 418      char sign, symbols[] = "pnum KMGT";
 419  
 420      if (value < 0) {
 421  	value = -value;
 422  	sign = '-';
 423      } else {
 424  	sign = '+';
 425      }
 426      if (value <= 1.0e-24) {
 427  	/* pretty damn small, call it zero */
 428  	snprintf(buf, buflen, "0.000");
 429  	return;
 430      }
 431      if (value <= 1.0e-12) {
 432  	/* less than pico units, use scientific notation */
 433  	snprintf(buf, buflen, "%c%10.3e", sign, value);
 434  	return;
 435      }
 436      if (value >= 1.0e+12) {
 437  	/* greater than tera-units, use scientific notation */
 438  	snprintf(buf, buflen, "%c%10.3e", sign, value);
 439  	return;
 440      }
 441      units = &(symbols[4]);
 442      while (value < 1.0) {
 443  	value *= 1000.0;
 444  	units--;
 445      }
 446      while (value >= 1000.0) {
 447  	value /= 1000.0;
 448  	units++;
 449      }
 450      decimals = 3;
 451      if (value >= 9.999) {
 452  	decimals--;
 453      }
 454      if (value >= 99.99) {
 455  	decimals--;
 456      }
 457      snprintf(buf, buflen, "%c%0.*f%c", sign, decimals, value, *units);
 458  }
 459  
 460  void write_vert_config(FILE *fp)
 461  {
 462      int n;
 463      scope_vert_t *vert;
 464      scope_chan_t *chan;
 465  
 466      vert = &(ctrl_usr->vert);
 467      /* first write disabled channels */
 468      for ( n = 1 ; n <= 16 ; n++ ) {
 469  	if ( vert->chan_enabled[n-1] != 0 ) {
 470  	    // channel enabled, do it later
 471  	    continue;
 472  	}
 473  	chan = &(ctrl_usr->chan[n-1]);
 474  	if ( chan->name == NULL ) {
 475  	    // no source for this channel, skip it
 476  	    continue;
 477  	}
 478  	fprintf(fp, "CHAN %d\n", n);
 479  	write_chan_config(fp, chan);
 480  	fprintf(fp, "CHOFF\n");
 481      }
 482      /* next write enabled channels */
 483      for ( n = 1 ; n <= 16 ; n++ ) {
 484  	if ( vert->chan_enabled[n-1] == 0 ) {
 485  	    // channel disabled, skip it
 486  	    continue;
 487  	}
 488  	if ( vert->selected == n ) {
 489  	    // channel selected, do it last
 490  	    continue;
 491  	}
 492  	chan = &(ctrl_usr->chan[n-1]);
 493  	if ( chan->name == NULL ) {
 494  	    // no source for this channel, skip it
 495  	    continue;
 496  	}
 497  	fprintf(fp, "CHAN %d\n", n);
 498  	write_chan_config(fp, chan);
 499      }
 500      /* write selected channel last */
 501      if ((vert->selected < 1) || (vert->selected > 16)) {
 502  	return;
 503      }
 504      chan = &(ctrl_usr->chan[vert->selected-1]);
 505      fprintf(fp, "CHAN %d\n", vert->selected);
 506      write_chan_config(fp, chan);
 507  }
 508  
 509  
 510  /***********************************************************************
 511  *                       LOCAL FUNCTIONS                                *
 512  ************************************************************************/
 513  
 514  void set_color(GdkColor *color, unsigned char red,
 515  		unsigned char green, unsigned char blue) {
 516      color->red = ((unsigned long) red) << 8;
 517      color->green = ((unsigned long) green) << 8;
 518      color->blue = ((unsigned long) blue) << 8;
 519      color->pixel =
 520  	((unsigned long) red) << 16 | ((unsigned long) green) << 8 |
 521  	((unsigned long) blue);
 522  }
 523  
 524  extern int normal_colors[16][3], selected_colors[16][3];
 525  static void init_chan_sel_window(void)
 526  {
 527      scope_vert_t *vert;
 528      GtkWidget *button;
 529      long n;
 530      gchar buf[5];
 531      GdkColor c;
 532  
 533      vert = &(ctrl_usr->vert);
 534      for (n = 0; n < 16; n++) {
 535  	snprintf(buf, 4, "%ld", n + 1);
 536  	/* define the button */
 537  	button = gtk_toggle_button_new_with_label(buf);
 538  
 539  	/* set up colors of the label */
 540  	set_color(&c, normal_colors[n][0],
 541  			normal_colors[n][1], normal_colors[n][2]);
 542  	gtk_widget_modify_bg(button, GTK_STATE_ACTIVE, &c);
 543  	gtk_widget_modify_bg(button, GTK_STATE_SELECTED, &c);
 544  
 545  	set_color(&c, selected_colors[n][0],
 546  			selected_colors[n][1], selected_colors[n][2]);
 547  	gtk_widget_modify_bg(button, GTK_STATE_PRELIGHT, &c);
 548  
 549  	set_color(&c, 0, 0, 0);
 550  	gtk_widget_modify_fg(button, GTK_STATE_ACTIVE, &c);
 551  	gtk_widget_modify_fg(button, GTK_STATE_SELECTED, &c);
 552  	gtk_widget_modify_fg(button, GTK_STATE_PRELIGHT, &c);
 553  
 554  	/* put it in the window */
 555  	gtk_box_pack_start(GTK_BOX(ctrl_usr->chan_sel_win), button, TRUE,
 556  	    TRUE, 0);
 557  	gtk_widget_show(button);
 558  	/* hook a callback function to it */
 559  	gtk_signal_connect(GTK_OBJECT(button), "clicked",
 560  	    GTK_SIGNAL_FUNC(chan_sel_button), (gpointer) n + 1);
 561  	/* save the button pointer */
 562  	vert->chan_sel_buttons[n] = button;
 563      }
 564  }
 565  
 566  static void init_chan_info_window(void)
 567  {
 568      scope_vert_t *vert;
 569      char dummyname[HAL_NAME_LEN+1];
 570      int n;
 571  
 572      vert = &(ctrl_usr->vert);
 573  
 574      vert->chan_num_label =
 575  	gtk_label_new_in_box("--", ctrl_usr->chan_info_win, FALSE, FALSE, 5);
 576      gtk_label_size_to_fit(GTK_LABEL(vert->chan_num_label), "99");
 577      gtk_vseparator_new_in_box(ctrl_usr->chan_info_win, 3);
 578  
 579      /* a button to change the source */
 580      vert->source_name_button = gtk_button_new_with_label("------");
 581      gtk_box_pack_start(GTK_BOX(ctrl_usr->chan_info_win),
 582  	vert->source_name_button, FALSE, FALSE, 3);
 583  
 584      vert->source_name_label = (GTK_BIN(vert->source_name_button))->child;
 585      gtk_label_set_justify(GTK_LABEL(vert->source_name_label),
 586  	GTK_JUSTIFY_LEFT);
 587      /* longest source name we ever need to display */
 588      for ( n = 0 ; n < HAL_NAME_LEN ; n++) dummyname[n] = 'x';
 589      dummyname[n] = '\0';
 590      gtk_label_size_to_fit(GTK_LABEL(vert->source_name_label), dummyname);
 591      /* activate the source selection dialog if button is clicked */
 592      gtk_signal_connect(GTK_OBJECT(vert->source_name_button), "clicked",
 593  	GTK_SIGNAL_FUNC(change_source_button), NULL);
 594      gtk_widget_show(vert->source_name_button);
 595  
 596  
 597      vert->readout_label = gtk_label_new_in_box("",
 598  		    ctrl_usr->chan_info_win, FALSE, FALSE, 0);
 599      gtk_misc_set_alignment(GTK_MISC(vert->readout_label), 0, 0);
 600      gtk_label_set_justify(GTK_LABEL(vert->readout_label), GTK_JUSTIFY_LEFT);
 601      gtk_label_size_to_fit(GTK_LABEL(vert->readout_label),
 602  		    "f(99999.9999) = 99999.9999 (ddt 99999.9999)");
 603  }
 604  
 605  static void init_vert_info_window(void)
 606  {
 607      scope_vert_t *vert;
 608      GtkWidget *hbox, *vbox;
 609      GtkWidget *button;
 610  
 611      vert = &(ctrl_usr->vert);
 612  
 613      /* box for the two sliders */
 614      hbox =
 615  	gtk_hbox_new_in_box(TRUE, 0, 0, ctrl_usr->vert_info_win, TRUE, TRUE,
 616  	0);
 617      /* box for the scale slider */
 618      vbox = gtk_vbox_new_in_box(FALSE, 0, 0, hbox, TRUE, TRUE, 0);
 619      gtk_label_new_in_box(_("Gain"), vbox, FALSE, FALSE, 0);
 620      vert->scale_adj = gtk_adjustment_new(0, -5, 5, 1, 1, 0);
 621      vert->scale_slider = gtk_vscale_new(GTK_ADJUSTMENT(vert->scale_adj));
 622      gtk_scale_set_digits(GTK_SCALE(vert->scale_slider), 0);
 623      gtk_scale_set_draw_value(GTK_SCALE(vert->scale_slider), FALSE);
 624      gtk_box_pack_start(GTK_BOX(vbox), vert->scale_slider, TRUE, TRUE, 0);
 625      /* connect the slider to a function that re-calcs vertical scale */
 626      gtk_signal_connect(GTK_OBJECT(vert->scale_adj), "value_changed",
 627  	GTK_SIGNAL_FUNC(scale_changed), NULL);
 628      gtk_widget_show(vert->scale_slider);
 629      /* box for the position slider */
 630      vbox = gtk_vbox_new_in_box(FALSE, 0, 0, hbox, TRUE, TRUE, 0);
 631      gtk_label_new_in_box(_("Pos"), vbox, FALSE, FALSE, 0);
 632      vert->pos_adj =
 633  	gtk_adjustment_new(VERT_POS_RESOLUTION / 2, 0, VERT_POS_RESOLUTION, 1,
 634  	1, 0);
 635      vert->pos_slider = gtk_vscale_new(GTK_ADJUSTMENT(vert->pos_adj));
 636      gtk_scale_set_digits(GTK_SCALE(vert->pos_slider), 0);
 637      gtk_scale_set_draw_value(GTK_SCALE(vert->pos_slider), FALSE);
 638      gtk_box_pack_start(GTK_BOX(vbox), vert->pos_slider, TRUE, TRUE, 0);
 639      /* connect the slider to a function that re-calcs vertical pos */
 640      gtk_signal_connect(GTK_OBJECT(vert->pos_adj), "value_changed",
 641  	GTK_SIGNAL_FUNC(pos_changed), NULL);
 642      gtk_widget_show(vert->pos_slider);
 643      /* Scale display */
 644      gtk_hseparator_new_in_box(ctrl_usr->vert_info_win, 3);
 645      gtk_label_new_in_box(_("Scale"), ctrl_usr->vert_info_win, FALSE, FALSE, 0);
 646      vert->scale_label =
 647  	gtk_label_new_in_box(" ---- ", ctrl_usr->vert_info_win, FALSE, FALSE,
 648  	0);
 649      /* Offset control */
 650      vert->offset_button = gtk_button_new_with_label(_("Offset\n----"));
 651      vert->offset_label = (GTK_BIN(vert->offset_button))->child;
 652      gtk_box_pack_start(GTK_BOX(ctrl_usr->vert_info_win),
 653  	vert->offset_button, FALSE, FALSE, 0);
 654      gtk_signal_connect(GTK_OBJECT(vert->offset_button), "clicked",
 655  	GTK_SIGNAL_FUNC(offset_button), NULL);
 656      gtk_widget_show(vert->offset_button);
 657      /* a button to turn off the channel */
 658      button = gtk_button_new_with_label(_("Chan Off"));
 659      gtk_box_pack_start(GTK_BOX(ctrl_usr->vert_info_win), button, FALSE, FALSE,
 660  	0);
 661      /* turn off the channel if button is clicked */
 662      gtk_signal_connect(GTK_OBJECT(button), "clicked",
 663  	GTK_SIGNAL_FUNC(channel_off_button), NULL);
 664      gtk_widget_show(button);
 665  }
 666  
 667  static void scale_changed(GtkAdjustment * adj, gpointer gdata)
 668  {
 669      set_vert_scale(adj->value);
 670  }
 671  
 672  static void pos_changed(GtkAdjustment * adj, gpointer gdata)
 673  {
 674      set_vert_pos(adj->value / VERT_POS_RESOLUTION);
 675  }
 676  
 677  static void offset_button(GtkWidget * widget, gpointer gdata)
 678  {
 679      scope_vert_t *vert;
 680      scope_chan_t *chan;
 681      int chan_num;
 682  
 683      vert = &(ctrl_usr->vert);
 684      chan_num = vert->selected;
 685      if ((chan_num < 1) || (chan_num > 16)) {
 686  	return;
 687      }
 688      chan = &(ctrl_usr->chan[chan_num - 1]);
 689      if (chan->data_type == HAL_BIT) {
 690  	/* no offset for bits */
 691  	return;
 692      }
 693      if (dialog_set_offset(chan_num)) {
 694  	if (chan_num == ctrl_shm->trig_chan) {
 695  	    refresh_trigger();
 696  	}
 697  	channel_changed();
 698  	request_display_refresh(1);
 699      }
 700  }
 701  
 702  static gboolean dialog_set_offset(int chan_num)
 703  {
 704      scope_vert_t *vert;
 705      scope_chan_t *chan;
 706      dialog_generic_t dialog;
 707      gchar *title, msg[BUFLEN], *cptr;
 708      struct offset_data data;
 709      GtkWidget *label, *button;
 710      double tmp;
 711  
 712      vert = &(ctrl_usr->vert);
 713      chan = &(ctrl_usr->chan[chan_num - 1]);
 714      title = _("Set Offset");
 715      snprintf(msg, BUFLEN - 1, _("Set the vertical offset\n"
 716  	"for channel %d."), chan_num);
 717      /* create dialog window, disable resizing */
 718      dialog.retval = 0;
 719      dialog.window = gtk_dialog_new();
 720      dialog.app_data = &data;
 721      /* allow user to grow but not shrink the window */
 722      gtk_window_set_policy(GTK_WINDOW(dialog.window), FALSE, TRUE, FALSE);
 723      /* window should appear in center of screen */
 724      gtk_window_set_position(GTK_WINDOW(dialog.window), GTK_WIN_POS_CENTER);
 725      /* set title */
 726      gtk_window_set_title(GTK_WINDOW(dialog.window), title);
 727      /* display message */
 728      label = gtk_label_new(msg);
 729      gtk_misc_set_padding(GTK_MISC(label), 15, 5);
 730      gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog.window)->vbox), label, FALSE,
 731  	TRUE, 0);
 732      /* a separator */
 733      gtk_hseparator_new_in_box(GTK_DIALOG(dialog.window)->vbox, 0);
 734      /* a checkbox: AC coupled */
 735      vert->offset_ac = gtk_check_button_new_with_label(_("AC Coupled"));
 736      gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog.window)->vbox),
 737          vert->offset_ac, FALSE, TRUE, 0);
 738      /* react to changes to the checkbox */
 739      gtk_signal_connect(GTK_OBJECT(vert->offset_ac), "toggled",
 740  	GTK_SIGNAL_FUNC(offset_changed), &data);
 741      /* the entry */
 742      vert->offset_entry = gtk_entry_new();
 743      gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog.window)->vbox),
 744  	vert->offset_entry, FALSE, TRUE, 0);
 745      snprintf(data.buf, BUFLEN, "%f", chan->vert_offset);
 746      gtk_entry_set_text(GTK_ENTRY(vert->offset_entry), data.buf);
 747      gtk_entry_set_max_length(GTK_ENTRY(vert->offset_entry), BUFLEN-1);
 748      /* point at first char */
 749      gtk_entry_set_position(GTK_ENTRY(vert->offset_entry), 0);
 750      /* select all chars, so if the user types the original value goes away */
 751      gtk_entry_select_region(GTK_ENTRY(vert->offset_entry), 0, strlen(data.buf));
 752      /* make it active so user doesn't have to click on it */
 753      gtk_widget_grab_focus(GTK_WIDGET(vert->offset_entry));
 754      gtk_widget_show(vert->offset_entry);
 755      /* capture entry data to the buffer whenever the user types */
 756      gtk_signal_connect(GTK_OBJECT(vert->offset_entry), "changed",
 757  	GTK_SIGNAL_FUNC(offset_changed), data.buf);
 758      /* set up a callback function when the window is destroyed */
 759      gtk_signal_connect(GTK_OBJECT(dialog.window), "destroy",
 760  	GTK_SIGNAL_FUNC(dialog_generic_destroyed), &dialog);
 761      /* make OK and Cancel buttons */
 762      button = gtk_button_new_with_label(_("OK"));
 763      gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog.window)->action_area),
 764  	button, TRUE, TRUE, 4);
 765      gtk_signal_connect(GTK_OBJECT(button), "clicked",
 766  	GTK_SIGNAL_FUNC(dialog_generic_button1), &dialog);
 767      /* hit the "OK" button if the user hits enter */
 768      gtk_signal_connect(GTK_OBJECT(vert->offset_entry), "activate",
 769  	GTK_SIGNAL_FUNC(offset_activated), button);
 770      button = gtk_button_new_with_label(_("Cancel"));
 771      gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog.window)->action_area),
 772  	button, TRUE, TRUE, 4);
 773      gtk_signal_connect(GTK_OBJECT(button), "clicked",
 774  	GTK_SIGNAL_FUNC(dialog_generic_button2), &dialog);
 775      /* make window transient and modal */
 776      gtk_window_set_transient_for(GTK_WINDOW(dialog.window),
 777  	GTK_WINDOW(ctrl_usr->main_win));
 778      gtk_window_set_modal(GTK_WINDOW(dialog.window), TRUE);
 779      gtk_widget_show_all(dialog.window);
 780      gtk_main();
 781      /* we get here when the user makes a selection, hits Cancel, or closes
 782         the window */
 783      if ((dialog.retval == 0) || (dialog.retval == 2)) {
 784  	/* user either closed dialog, or hit cancel */
 785  	return FALSE;
 786      }
 787      tmp = strtod(data.buf, &cptr);
 788      if (cptr == data.buf) {
 789  	return FALSE;
 790      }
 791      set_vert_offset(tmp, data.ac_coupled);
 792      return TRUE;
 793  }
 794  
 795  static void offset_changed(GtkEditable * editable, struct offset_data *data)
 796  {
 797      const char *text;
 798  
 799      /* maybe user hit "ac coupled" button" */
 800      data->ac_coupled =
 801        gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ctrl_usr->vert.offset_ac));
 802      gtk_entry_set_editable(GTK_ENTRY(ctrl_usr->vert.offset_entry),
 803                  !data->ac_coupled);
 804  
 805      /* maybe user typed something, save it in the buffer */
 806      text = gtk_entry_get_text(GTK_ENTRY(ctrl_usr->vert.offset_entry));
 807      strncpy(data->buf, text, BUFLEN);
 808  }
 809  
 810  static void offset_activated(GtkEditable * editable, gchar * button)
 811  {
 812      /* user hit enter, generate a "clicked" event for the OK button */
 813      gtk_button_clicked(GTK_BUTTON(button));
 814  }
 815  
 816  
 817  static void chan_sel_button(GtkWidget * widget, gpointer gdata)
 818  {
 819      long chan_num;
 820      int n, count;
 821      scope_vert_t *vert;
 822      scope_chan_t *chan;
 823      char *title, *msg;
 824  
 825      vert = &(ctrl_usr->vert);
 826      chan_num = (long) gdata;
 827      chan = &(ctrl_usr->chan[chan_num - 1]);
 828  
 829      if (ignore_click != 0) {
 830  	ignore_click = 0;
 831  	return;
 832      }
 833      if (vert->chan_enabled[chan_num - 1] == 0 ) {
 834  	/* channel is disabled, want to enable it */
 835  	if (ctrl_shm->state != IDLE) {
 836  	    /* acquisition in progress, must restart it */
 837              prepare_scope_restart();
 838  	}
 839  	count = 0;
 840  	for (n = 0; n < 16; n++) {
 841  	    if (vert->chan_enabled[n]) {
 842  		count++;
 843  	    }
 844  	}
 845  	if (count >= ctrl_shm->sample_len) {
 846  	    /* max number of channels already enabled */
 847  	    /* force the button to pop back out */
 848  	    ignore_click = 1;
 849  	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
 850  	    title = _("Too many channels");
 851  	    msg = _("You cannot add another channel.\n\n"
 852  		"Either turn off one or more channels, or shorten\n"
 853  		"the record length to allow for more channels");
 854  	    dialog_generic_msg(ctrl_usr->main_win, title, msg, _("OK"), NULL,
 855  		NULL, NULL);
 856  	    return;
 857  	}
 858  	if (chan->name == NULL) {
 859  	    /* need to assign a source */
 860  	    if (dialog_select_source(chan_num) != TRUE) {
 861  		/* user failed to assign a source */
 862  		/* force the button to pop back out */
 863  		ignore_click = 1;
 864  		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
 865  		    FALSE);
 866  		return;
 867  	    }
 868  	}
 869  	vert->chan_enabled[chan_num - 1] = 1;
 870      } else {
 871  	/* channel was already enabled, user wants to select it */
 872  	/* button should stay down, so we force it */
 873  	ignore_click = 1;
 874  	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
 875      }
 876      if (vert->selected != chan_num) {
 877  	/* make chan_num the selected channel */
 878  	vert->selected = chan_num;
 879  	channel_changed();
 880      }
 881  }
 882  
 883  static void channel_off_button(GtkWidget * widget, gpointer gdata)
 884  {
 885      scope_vert_t *vert;
 886      int chan_num;
 887  
 888      vert = &(ctrl_usr->vert);
 889      chan_num = vert->selected;
 890      set_channel_off(chan_num);    
 891  }
 892  
 893  static void change_source_button(GtkWidget * widget, gpointer gdata)
 894  {
 895      int chan_num;
 896  
 897      chan_num = ctrl_usr->vert.selected;
 898      if ((chan_num < 1) || (chan_num > 16)) {
 899  	return;
 900      }
 901      if (ctrl_shm->state != IDLE) {
 902          /* acquisition in progress, must restart it */
 903          prepare_scope_restart();
 904      }
 905      invalidate_channel(chan_num);
 906      dialog_select_source(chan_num);
 907      channel_changed();
 908  }
 909  
 910  static char search_target[HAL_NAME_LEN+1];
 911  static guint32 search_time = 0;
 912  static int search_row = -1;
 913  #define SEARCH_RESET_TIME 1000 /* ms */
 914  
 915  static void selection_made_common(GtkWidget *clist, gint row, dialog_generic_t *dptr) {
 916      gint n, listnum;
 917      gchar *name;
 918      int rv, chan_num;
 919  
 920      scope_vert_t *vert;
 921      /* If we get here, it should be a valid selection */
 922      vert = &(ctrl_usr->vert);
 923      chan_num = *((int *)(dptr->app_data));
 924      /* figure out which notebook tab it was */
 925      listnum = -1;
 926      for (n = 0; n < 3; n++) {
 927  	if (clist == vert->lists[n]) {
 928  	    listnum = n;
 929  	}
 930      }
 931      /* Get the text from the list */
 932      gtk_clist_get_text(GTK_CLIST(clist), row, 0, &name);
 933      /* try to set up the new source */
 934      rv = set_channel_source(chan_num, listnum, name);
 935      if ( rv == 0 ) {
 936  	/* set return value of dialog to indicate success */
 937  	dptr->retval = 1;
 938      } else {
 939  	/* new source invalid, return as if user hit cancel */
 940  	dptr->retval = 2;
 941      }
 942      /* destroy window to cause dialog_generic_destroyed() to be called */
 943      gtk_widget_destroy(dptr->window);
 944      return;
 945  }
 946  
 947  
 948  static gboolean search_for_entry(GtkWidget *widget, GdkEventKey *event, dialog_generic_t *dptr)
 949  {
 950      GtkCList *clist = GTK_CLIST(widget);
 951      int z, wrapped;
 952  
 953      if(event->keyval == GDK_Return) {
 954  	selection_made_common(widget, clist->focus_row, dptr);
 955      }
 956  
 957      if(!isprint(event->string[0])) {
 958  	strcpy(search_target, "");
 959  	search_row = clist->focus_row;
 960  	return 0;
 961      }
 962  
 963      if(event->time - search_time > SEARCH_RESET_TIME) {
 964  	strcpy(search_target, "");
 965  	search_row = clist->focus_row;
 966      }
 967  
 968      search_time = event->time;
 969      if(strcmp(event->string, " ") == 0) {
 970  	char *text;
 971  	search_row = search_row + 1;
 972  	if(!gtk_clist_get_text(clist, search_row, 0, &text))
 973  	    search_row = 0;
 974  	printf(_("next search: %d\n"), search_row);
 975      } else {
 976  	strcat(search_target, event->string);
 977      }
 978      
 979      for(z = search_row, wrapped=0; z != search_row || !wrapped; z ++) {
 980  	char *text;
 981  
 982  	printf(_("search: %d (wrapped=%d)\n"), z, wrapped);
 983  	if(!gtk_clist_get_text(clist, z, 0, &text)) {
 984  	    if(wrapped) break; // wrapped second time (why?)
 985  	    z = 0;
 986  	    wrapped = 1; 
 987  	}
 988  	
 989  	if(strstr(text, search_target)) {
 990  	    double pos = (z+.5) / (clist->rows-1);
 991  	    if(pos > 1) pos = 1;
 992  	    
 993  	    GTK_CLIST_GET_CLASS(clist)->scroll_vertical(clist, GTK_SCROLL_JUMP, pos);
 994  	    gtk_clist_select_row(clist, z, 0);
 995  	    search_row = z;
 996  	    return 1;
 997  	}
 998      }
 999      return 0;
1000  }
1001  
1002  static gboolean change_page(GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, gpointer user_data) {
1003      scope_vert_t *vert;
1004  
1005      vert = &(ctrl_usr->vert);
1006      if(page_num  < 3)
1007  	gtk_widget_grab_focus(GTK_WIDGET(vert->lists[page_num]));
1008      return 0;
1009  }
1010  
1011  static gboolean dialog_select_source(int chan_num)
1012  {
1013      scope_vert_t *vert;
1014      scope_chan_t *chan;
1015      dialog_generic_t dialog;
1016      gchar *title, msg[BUFLEN];
1017      int next, n, initial_page, row, initial_row, max_row;
1018      gchar *tab_label_text[3], *name;
1019      GtkWidget *hbox, *label, *notebk, *button;
1020      GtkAdjustment *adj;
1021      hal_pin_t *pin;
1022      hal_sig_t *sig;
1023      hal_param_t *param;
1024  
1025      vert = &(ctrl_usr->vert);
1026      chan = &(ctrl_usr->chan[chan_num - 1]);
1027      title = _("Select Channel Source");
1028      snprintf(msg, BUFLEN - 1, _("Select a pin, signal, or parameter\n"
1029  	"as the source for channel %d."), chan_num);
1030      /* create dialog window, disable resizing */
1031      dialog.retval = 0;
1032      dialog.window = gtk_dialog_new();
1033      dialog.app_data = &chan_num;
1034      /* set initial height of window */
1035      gtk_widget_set_usize(GTK_WIDGET(dialog.window), -2, 300);
1036      /* allow user to grow but not shrink the window */
1037      gtk_window_set_policy(GTK_WINDOW(dialog.window), FALSE, TRUE, FALSE);
1038      /* window should appear in center of screen */
1039      gtk_window_set_position(GTK_WINDOW(dialog.window), GTK_WIN_POS_CENTER);
1040      /* set title */
1041      gtk_window_set_title(GTK_WINDOW(dialog.window), title);
1042      /* display message */
1043      label = gtk_label_new(msg);
1044      gtk_misc_set_padding(GTK_MISC(label), 15, 5);
1045      gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog.window)->vbox), label, FALSE,
1046  	TRUE, 0);
1047  
1048      /* a separator */
1049      gtk_hseparator_new_in_box(GTK_DIALOG(dialog.window)->vbox, 0);
1050  
1051      /* create a notebook to hold pin, signal, and parameter lists */
1052      notebk = gtk_notebook_new();
1053      /* add the notebook to the dialog */
1054      gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog.window)->vbox), notebk, TRUE,
1055  	TRUE, 0);
1056      /* set overall notebook parameters */
1057      gtk_notebook_set_homogeneous_tabs(GTK_NOTEBOOK(notebk), TRUE);
1058      gtk_signal_connect(GTK_OBJECT(notebk), "switch-page", GTK_SIGNAL_FUNC(change_page), &dialog);
1059      /* text for tab labels */
1060      tab_label_text[0] = _("Pins");
1061      tab_label_text[1] = _("Signals");
1062      tab_label_text[2] = _("Parameters");
1063      /* loop to create three identical tabs */
1064      for (n = 0; n < 3; n++) {
1065  	/* Create a scrolled window to display the list */
1066  	vert->windows[n] = gtk_scrolled_window_new(NULL, NULL);
1067  	vert->adjs[n] = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(vert->windows[n]));
1068  	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vert->windows[n]),
1069  	    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
1070  	gtk_widget_show(vert->windows[n]);
1071  	/* create a list to hold the data */
1072  	vert->lists[n] = gtk_clist_new(1);
1073  	/* set up a callback for when the user selects a line */
1074  	gtk_signal_connect(GTK_OBJECT(vert->lists[n]), "select_row",
1075  	    GTK_SIGNAL_FUNC(selection_made), &dialog);
1076  	gtk_signal_connect(GTK_OBJECT(vert->lists[n]), "key-press-event",
1077  	    GTK_SIGNAL_FUNC(search_for_entry), &dialog);
1078  	/* It isn't necessary to shadow the border, but it looks nice :) */
1079  	gtk_clist_set_shadow_type(GTK_CLIST(vert->lists[n]), GTK_SHADOW_OUT);
1080  	/* set list for single selection only */
1081  	gtk_clist_set_selection_mode(GTK_CLIST(vert->lists[n]),
1082  	    GTK_SELECTION_BROWSE);
1083  	/* put the list into the scrolled window */
1084  	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW
1085  	    (vert->windows[n]), vert->lists[n]);
1086  	/* another way to do it - not sure which is better
1087  	gtk_container_add(GTK_CONTAINER(vert->windows[n]), vert->lists[n]); */
1088  	gtk_widget_show(vert->lists[n]);
1089  	/* create a box for the tab label */
1090  	hbox = gtk_hbox_new(TRUE, 0);
1091  	/* create a label for the page */
1092  	gtk_label_new_in_box(tab_label_text[n], hbox, TRUE, TRUE, 0);
1093  	gtk_widget_show(hbox);
1094  	/* add page to the notebook */
1095  	gtk_notebook_append_page(GTK_NOTEBOOK(notebk), vert->windows[n], hbox);
1096  	/* set tab attributes */
1097  	gtk_notebook_set_tab_label_packing(GTK_NOTEBOOK(notebk), hbox,
1098  	    TRUE, TRUE, GTK_PACK_START);
1099      }
1100      /* determine initial page: pin, signal, or parameter */
1101      if (( chan->data_source_type >= 0 ) && ( chan->data_source_type <= 2 )) {
1102  	initial_page = chan->data_source_type;
1103  	gtk_notebook_set_page(GTK_NOTEBOOK(notebk), initial_page);
1104      } else {
1105  	initial_page = -1;
1106  	gtk_notebook_set_page(GTK_NOTEBOOK(notebk), 0);
1107      }
1108      gtk_widget_show(notebk);
1109  
1110      /* populate the pin, signal, and parameter lists */
1111      gtk_clist_clear(GTK_CLIST(vert->lists[0]));
1112      gtk_clist_clear(GTK_CLIST(vert->lists[1]));
1113      gtk_clist_clear(GTK_CLIST(vert->lists[2]));
1114      rtapi_mutex_get(&(hal_data->mutex));
1115      next = hal_data->pin_list_ptr;
1116      initial_row = -1;
1117      max_row = -1;
1118      while (next != 0) {
1119  	pin = SHMPTR(next);
1120  	name = pin->name;
1121  	row = gtk_clist_append(GTK_CLIST(vert->lists[0]), &name);
1122  	if ( initial_page == 0 ) {
1123  	    if ( strcmp(name, chan->name) == 0 ) {
1124  		initial_row = row;
1125  	    }
1126  	    max_row = row;
1127  	}
1128  	next = pin->next_ptr;
1129      }
1130      next = hal_data->sig_list_ptr;
1131      while (next != 0) {
1132  	sig = SHMPTR(next);
1133  	name = sig->name;
1134  	row = gtk_clist_append(GTK_CLIST(vert->lists[1]), &name);
1135  	if ( initial_page == 1 ) {
1136  	    if ( strcmp(name, chan->name) == 0 ) {
1137  		initial_row = row;
1138  	    }
1139  	    max_row = row;
1140  	}
1141  	next = sig->next_ptr;
1142      }
1143      next = hal_data->param_list_ptr;
1144      while (next != 0) {
1145  	param = SHMPTR(next);
1146  	name = param->name;
1147  	row = gtk_clist_append(GTK_CLIST(vert->lists[2]), &name);
1148  	if ( initial_page == 2 ) {
1149  	    if ( strcmp(name, chan->name) == 0 ) {
1150  		initial_row = row;
1151  	    }
1152  	    max_row = row;
1153  	}
1154  	next = param->next_ptr;
1155      }
1156      rtapi_mutex_give(&(hal_data->mutex));
1157      
1158      if ( initial_row >= 0 ) {
1159  	/* highlight the currently selected name */
1160  	gtk_clist_select_row(GTK_CLIST(vert->lists[initial_page]), initial_row, -1);
1161  	/* set scrolling window to show the highlighted name */
1162  	/* FIXME - I can't seem to get this to work */
1163  	adj = vert->adjs[initial_page];
1164  	adj->value = adj->lower + (adj->upper - adj->lower)*((double)(initial_row)/(double)(max_row+1));
1165  	gtk_adjustment_value_changed(vert->adjs[initial_page]);
1166      }
1167      /* set up a callback function when the window is destroyed */
1168      gtk_signal_connect(GTK_OBJECT(dialog.window), "destroy",
1169  	GTK_SIGNAL_FUNC(dialog_generic_destroyed), &dialog);
1170      /* make Cancel button */
1171      button = gtk_button_new_with_label(_("Cancel"));
1172      gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog.window)->action_area),
1173  	button, TRUE, TRUE, 4);
1174      gtk_signal_connect(GTK_OBJECT(button), "clicked",
1175  	GTK_SIGNAL_FUNC(dialog_generic_button2), &dialog);
1176      /* make window transient and modal */
1177      gtk_window_set_transient_for(GTK_WINDOW(dialog.window),
1178  	GTK_WINDOW(ctrl_usr->main_win));
1179      gtk_window_set_modal(GTK_WINDOW(dialog.window), TRUE);
1180      gtk_widget_show_all(dialog.window);
1181      gtk_main();
1182      /* we get here when the user makes a selection, hits Cancel, or closes
1183         the window */
1184      vert->lists[0] = NULL;
1185      vert->lists[1] = NULL;
1186      vert->lists[2] = NULL;
1187      if ((dialog.retval == 0) || (dialog.retval == 2)) {
1188  	/* user either closed dialog, or hit cancel */
1189  	return FALSE;
1190      }
1191      /* user made a selection */
1192      channel_changed();
1193      return TRUE;
1194  }
1195  /* If we come here, then the user has clicked a row in the list. */
1196  static void selection_made(GtkWidget * clist, gint row, gint column,
1197      GdkEventButton * event, dialog_generic_t * dptr)
1198  {
1199      GdkEventType type;
1200      
1201      if ((event == NULL) || (clist == NULL)) {
1202  	/* We get spurious events when the lists are populated I don't know
1203  	   why.  If either clist or event is null, it's a bad one! */
1204  	return;
1205      }
1206      type = event->type;
1207      if (type != 4) {
1208  	/* We also get bad callbacks if you drag the mouse across the list
1209  	   with the button held down.  They can be distinguished because
1210  	   their event type is 3, not 4. */
1211  	return;
1212      }
1213      selection_made_common(clist, row, dptr);
1214  }
1215  
1216  void channel_changed(void)
1217  {
1218      scope_vert_t *vert;
1219      scope_chan_t *chan;
1220      GtkAdjustment *adj;
1221      gchar *name;
1222      gchar buf1[BUFLEN + 1], buf2[BUFLEN + 1];
1223  
1224      vert = &(ctrl_usr->vert);
1225      if ((vert->selected < 1) || (vert->selected > 16)) {
1226  	gtk_label_set_text_if(vert->scale_label, "----");
1227  	gtk_label_set_text_if(vert->chan_num_label, "--");
1228  	gtk_label_set_text_if(vert->source_name_label, "------");
1229  	request_display_refresh(1);
1230  	return;
1231      }
1232      chan = &(ctrl_usr->chan[vert->selected - 1]);
1233      /* set position slider based on new channel */
1234      gtk_adjustment_set_value(GTK_ADJUSTMENT(vert->pos_adj),
1235  	chan->position * VERT_POS_RESOLUTION);
1236      /* set scale slider based on new channel */
1237      adj = GTK_ADJUSTMENT(vert->scale_adj);
1238      adj->lower = chan->min_index;
1239      adj->upper = chan->max_index;
1240      adj->value = chan->scale_index;
1241      gtk_adjustment_changed(adj);
1242      gtk_adjustment_value_changed(adj);
1243      /* update the channel number and name display */
1244      snprintf(buf1, BUFLEN, "%2d", vert->selected);
1245      name = chan->name;
1246      gtk_label_set_text_if(vert->chan_num_label, buf1);
1247      gtk_label_set_text_if(vert->source_name_label, name);
1248      /* update the offset display */
1249      if (chan->data_type == HAL_BIT) {
1250  	snprintf(buf1, BUFLEN, "----");
1251      } else {
1252          if(chan->ac_offset) {
1253              snprintf(buf1, BUFLEN, "(AC)");
1254          } else {
1255              format_signal_value(buf1, BUFLEN, chan->vert_offset);
1256          }
1257      }
1258      snprintf(buf2, BUFLEN, _("Offset\n%s"), buf1);
1259      gtk_label_set_text_if(vert->offset_label, buf2);
1260      request_display_refresh(1);
1261  }
1262  
1263  void format_scale_value(char *buf, int buflen, double value)
1264  {
1265      char *units;
1266      char symbols[] = "pnum KMGT";
1267  
1268      if (value < 0.9e-12) {
1269  	/* less than pico units, shouldn't happen */
1270  	snprintf(buf, buflen, "tiny");
1271  	return;
1272      }
1273      if (value > 1.1e+12) {
1274  	/* greater than tera-units, shouldn't happen */
1275  	snprintf(buf, buflen, "huge");
1276  	return;
1277      }
1278      units = &(symbols[4]);
1279      while (value < 1.0) {
1280  	value *= 1000.0;
1281  	units--;
1282      }
1283      while (value >= 999.99) {
1284  	value *= 0.001;
1285  	units++;
1286      }
1287      snprintf(buf, buflen, "%0.0f%c/div", value, *units);
1288  }
1289  
1290  static void write_chan_config(FILE *fp, scope_chan_t *chan)
1291  {
1292      if ( chan->data_source_type == 0 ) {
1293  	// pin
1294  	fprintf(fp, "PIN %s\n", chan->name);
1295      } else if ( chan->data_source_type == 1 ) {
1296  	// signal
1297  	fprintf(fp, "SIG %s\n", chan->name);
1298      } else if ( chan->data_source_type == 2 ) {
1299  	// pin
1300  	fprintf(fp, "PARAM %s\n", chan->name);
1301      } else {
1302  	// not configured
1303  	return;
1304      }
1305      fprintf(fp, "VSCALE %d\n", chan->scale_index);
1306      fprintf(fp, "VPOS %f\n", chan->position);
1307      if(chan->ac_offset) {
1308          fprintf(fp, "VAC %e\n", chan->vert_offset);
1309      } else {
1310          fprintf(fp, "VOFF %e\n", chan->vert_offset);
1311      }
1312  }