3.10. gtkhistory.g - The History program in GTK

/*--------------------------------------------------------------------
 * File:         gtkhistory.g
 *
 * Description:  Demonstrates the Cascade Historian
 *
 * Functions and methods by category
 *
 * Interpolator: InterpolatorSettings.change_int
 *               InterpolatorSettings.assign_values
 *               InterpolatorSettings.get_data
 * Query:        allow_entry_values
 *               countdown
 *               record_data
 *               reset_deadbands
 *               plot_prep
 *               send_query
 *               write_minmax
 *               file_add
 *               show_data
 * Deadbanding:  db_options
 *               db_labels
 *               create_dbtype_win
 * Window:       make_toggle_button
 *               qry_radio_but
 *               qry_entry
 *               create_history
 * Main:         main
 *------------------------------------------------------------------*/

/********************************************************
 *               GENERAL UTILITY Code                   *
 ********************************************************/

/* Get access to the library of common functions. */
require("lib/common.g");
  
/* Keep track of all child processes that this process has started. */
Children := nil;

/********************************************************
 *                  INTERPOLATOR Code                   *
 ********************************************************/

/*--------------------------------------------------------------------
 * Method:      InterpolatorSettings.change_int
 * Returns:     t or nil
 * Description: Responding to a click on a "Choose an Interpolator"
 *              radio button, this method calls the allow_entry_values() 
 *              function on the active button.  The callback is set up 
 *              in qry_radio_but().
 *------------------------------------------------------------------*/
method InterpolatorSettings.change_int(button, str, w1, w2, w3, e1, e2, e3)
{
  if (button.switched_on())
    {
      .fn = str;
      allow_entry_values(w1, w2, w3, e1, e2, e3);
    }
}

/*--------------------------------------------------------------------
 * Method:      InterpolatorSettings.assign_values
 * Returns:     t or nil
 * Description: Assigns the values from the interpolator entry widgets
 *              to the appropriate instance variables of the class.
 *              Called by send_query().
 *------------------------------------------------------------------*/
method InterpolatorSettings.assign_values(e1, e2, e3, e4, e5, e6)
{
  .y_history = assign_history(.dbflag, .y_history, e1.get_text());
  .start = number(e2.get_text());
  .duration = number(e3.get_text());
  .x_history =  assign_history(.dbflag, .x_history, e4.get_text());
  .interval = number(e5.get_text());
  .max_gap = number(e6.get_text());
}

/*--------------------------------------------------------------------
 * Method:      InterpolatorSettings.get_data
 * Returns:     a list of two lists
 * Description: Sends the query and retrieves the data.  Activated by 
 *              the send_query() function.
 *------------------------------------------------------------------*/
method InterpolatorSettings.get_data(e1, e2, e3, e4, e5, e6, e7)
{
  local tsk, intpl, buf_length, alldata, count, dur, data, outfile;
  local xdata = list();
  local ydata = list();
  local midnite = find_midnite();

  try
    {
      /* Make sure there are valid values in the entry widgets.*/
      if (undefined_p(.start) || (!number_p(.start)))
        {
          .start = 0;
          e2.set_text("0");
        }
      if (undefined_p(.duration) || (!number_p(.duration))
          || (.duration <= 0))
        {
          .duration = 10;
          e3.set_text("10");
        }
      if (undefined_p(.interval) || (!number_p(.interval))
          || (.interval < .01))
        {
          .interval = .01;
          e5.set_text("0.01");
        }
      
      if ((tsk = locate_task("demohistdb", nil)) != nil)
        {
          alldata = list();
          count = 0;      
          intpl = self.set_interpolator(tsk);
          buf_length = caddr(send(tsk, `bufferIdLength(@intpl)));
          if(!((.fn == "TimeSetup") || (.fn == "RelSetup")))
            e7.set_text(string(buf_length));
          
          while (count < buf_length)
            {
              if((buf_length - count) > 100)
                dur = 100;
              else
                dur = buf_length - count;
              data = send(tsk, `bufferIdDataAscii(@intpl, @count, @dur));
              data = cddr(cdr(data));
              alldata = append(alldata, data);
              count = count + 100;
            }
          
          if(.dbflag == "NONE")
            outfile = open("/tmp/cogentdemo/hsoutput.dat", "w");
          if(.dbflag == "DB")
            outfile = open("/tmp/cogentdemo/hsoutput2.dat", "w");
          if(.dbflag == "DBP")
            outfile = open("/tmp/cogentdemo/hsoutput3.dat", "w");
          if (outfile)
            if (buf_length == 0)
              {
                writec(outfile, "# Sorry, no data matched your query.");
                writec(outfile, string("\n", midnite, " 0",
                                       "\n", (midnite + 1), " 0"));
                close(outfile);
              }
            else
              {
                with d in alldata do
                  writec(outfile, string(car(d), " ", cadr(d), "\n"));
                close(outfile);
              }
          
          while (alldata != nil)
            {
              if(.fn == "TimeSetup" || 
                 .fn == "NoInterpolator" || .fn == "Periodic")
                xdata = cons(caar(alldata) - midnite, xdata);
              else
                xdata = cons(caar(alldata), xdata);
              ydata = cons(cadar(alldata), ydata);
              alldata = cdr(alldata);
            }
          xdata = reverse(xdata);
          ydata = reverse(ydata);
          send(tsk, `bufferIdDestroy(@intpl)); 
        }
      list(xdata, ydata);
    }
  catch
    list(nil, nil);
}

/********************************************************
 *                      QUERY Code                      *
 ********************************************************/

/*--------------------------------------------------------------------
 * Function:    allow_entry_values
 * Returns:     t or nil
 * Description: Sets the three optional parameter entry widgets
 *              (X history, Time Interval, and Max. gap) sensitive or
 *              non-sensitive.  Called by qry_radio_but() and
 *              InterpolatorSettings.change_int().  The possible values
 *              for e1 - e3 are 0 (FALSE) or 1 (TRUE).
 *------------------------------------------------------------------*/
function allow_entry_values(w1, w2, w3, e1, e2, e3)
{
  w1.set_sensitive(e1);
  w2.set_sensitive(e2);
  w3.set_sensitive(e3);
}

/*--------------------------------------------------------------------
 * Function:    countdown
 * Returns:     t or nil
 * Description: Called by record_data() to do the time count-down.
 *------------------------------------------------------------------*/
function countdown(txt_wgt, time)
{
  txt_wgt.set_point(42);
  txt_wgt.forward_delete(txt_wgt.get_length() - 42);
  txt_wgt.insert(FONT, nil, nil, string(time - clock()), -1);
}

/* Assign a global variable for recording time, and a font. */
RECORD_TIME = 5;
FONT = gdk_font_load ("-adobe-courier-medium-r-normal--*-120-*-*-*-*-*-*");

/*--------------------------------------------------------------------
 * Function:    record_data
 * Returns:     t or nil
 * Description: Records data for the specified time, then sends a query.
 * Arguments:   button:       The Record button
 *              iset:         An InterpolatorSettings instance
 *              e1 - e6:      The six Interpolator options entry widgets
 *              e7:           The "Number of points from last query" display
 *              but_db1 - 3:  The three Deadband options buttons
 *              b1 - b4:      The four Interpolator options buttons
 *              b6:           The Update display button
 *------------------------------------------------------------------*/
function record_data(button, iset, but_db1, but_db2, but_db3, text,
                     e1, e2, e3, e4, e5, e6, e7, b1, b2, b3, b4, b6)
{
  if (button.switched_on())
    {
      local time = clock() + RECORD_TIME;
      text.backward_delete(text.get_length());
      text.insert(FONT, nil, nil,
                  (string("Recording,please wait.\nSeconds remaining: ", RECORD_TIME)),
                  -1);
      numtimer = every (1, `countdown(@text, @time));
      
      e3.set_text(string(RECORD_TIME + 1));
      iset.set_defaults(button, e2);
      send_hs_command(button, "enable", e1.get_text(), nil);
      
      after(RECORD_TIME, `(@button).set_active(FALSE));
      after((RECORD_TIME + .1), `(send_hs_command(@button, "enable",
                                                  (@e1).get_text(), nil)));
      after((RECORD_TIME + .1), `(cancel(numtimer)));
      after((RECORD_TIME + .15),
            `(reset_deadbands(nil, @iset, @but_db1, @but_db2, @but_db3, @text,
                              @e1, @e2, @e3, @e4, @e5, @e6, @e7, @b1,
                              @b2, @b3, @b4, @button, @b6)));
    }
}

/*--------------------------------------------------------------------
 * Function:    reset_deadbands
 * Returns:     t or nil
 * Description: Clears all traces and sets the Full data set (no deadband)
 *              option.  Called when non-deadband buttons are activated.
 *------------------------------------------------------------------*/
function reset_deadbands(button, iset, but1, but2, but3, text, e1, e2, 
                         e3, e4, e5, e6, e7, b1, b2, b3, b4, b5, b6)
{
  but2.set_active(FALSE);
  but3.set_active(FALSE);
  but1.set_active(TRUE);
  if(is_file("/tmp/cogentdemo/hsoutput2.dat"))
      unlink("/tmp/cogentdemo/hsoutput2.dat");
  if(is_file("/tmp/cogentdemo/hsoutput3.dat"))
      unlink("/tmp/cogentdemo/hsoutput3.dat");
  plot_prep(but1, iset, but1, but2, but3, text, e1, e2, 
            e3, e4, e5, e6, e7, b1, b2, b3, b4, b5, b6);
}

/*--------------------------------------------------------------------
 * Function:    plot_prep
 * Returns:     t or nil
 * Description: Writes to the plhistsetup.dat file the relevant values
 *              for the time of midnight, x-axis label, and deadband
 *              status.
 *------------------------------------------------------------------*/
function plot_prep (button, iset, but_db1, but_db2, but_db3, text,
                    e1, e2, e3, e4, e5, e6, e7, b1, b2, b3, b4, b5, b6)
{
  local fp, histtime, line;

  /* Write the time. */
  fp = open("/tmp/cogentdemo/plhistsetup.dat", "w", nil);
  histtime = find_midnite();
  line = string("histtime = ", histtime, "\n");
  writec(fp, line);

  /* Set up the X-axis label. */
  if ((iset.fn == "NoInterpolator") || (iset.fn == "Periodic"))
    line = "xlab = 1\n";
  else line = "xlab = 2\n";
  writec(fp, line);

  /* Set the deadband flag in the InterpolatorSettings instance. */
  if(button.switched_on() && button == but_db1)
    iset.dbflag = "NONE";
  if(button.switched_on() && button == but_db2)
    iset.dbflag = "DB";
  if(button.switched_on() && button == but_db3)
    iset.dbflag = "DBP";

  /* Write the on or off value for each deadband. */
  if (but_db1.switched_on())
    line = "db1 = 1\n";
  else line = "db1 = 0\n";
  writec(fp, line);

  if (but_db2.switched_on())
    line = "db2 = 1\n";
  else line = "db2 = 0\n";
  writec(fp, line);

  if (but_db3.switched_on())
    line = "db3 = 1\n";
  else line = "db3 = 0\n";
  writec(fp, line);

  close(fp);

  send_query(button, iset, text, e1, e2, e3, e4,
             e5, e6, e7, b1, b2, b3, b4, b5, b6);
}

/*--------------------------------------------------------------------
 * Function:    send_query
 * Returns:     t or nil
 * Description: Calls the necessary functions to send a query to the
 *              Historian, and handle the data returned.  It is activated
 *              by the "Record" button popping out, or any radio button.
 *              It sends two queries, the first to calculate the minimum
 *              and maximum values for use in creating plots, and the
 *              second to get the actual data.
 * Arguments:   button:       The button that activated the function
 *              iset:         An InterpolatorSettings instance
 *              e1 - e6:      The six Interpolator options entry widgets
 *              e7:           The "Number of points from last query" display
 *              b1 - b4:      The four Interpolator options buttons
 *              b5:           The Record button
 *              b6:           The Update display button
 *------------------------------------------------------------------*/
function send_query(button, iset, text, e1, e2, e3, e4,
                    e5, e6, e7, b1, b2, b3, b4, b5, b6)
{
  local fnholder, datalist, minmaxlist;
  if ((button == nil) || (button == b6) || (button.switched_on()))
    {
      if ((button == b1 || button == b2 ||
           button == b3 || button == b4 ||
           button == b5 || button == b6))
        iset.dbflag = "NONE";
      
      iset.assign_values(e1, e2, e3, e4, e5, e6);
      
      datalist = list();
      fnholder = iset.fn;

      /* Assign a name for the setup query. */
      if ((iset.fn == "NoInterpolator") || (iset.fn == "Periodic"))
        iset.fn = "TimeSetup";
      else
        iset.fn = "RelSetup";

      /* Make the setup query and assign minimum and maximum values. */
      datalist = iset.get_data(e1, e2, e3, e4, e5, e6, e7);
      if (car(datalist) && cadr(datalist))
        minmaxlist = list(min_max(car(datalist)), min_max(cadr(datalist)));
      else
        minmaxlist = nil;

      /* Make the actual query. */
      iset.fn = fnholder;
      datalist = iset.get_data(e1, e2, e3, e4, e5, e6, e7);
      show_data(text, iset, button, datalist, minmaxlist);
    }
  else
    {
      write_minmax(iset.misc_info);
    }
}

/*--------------------------------------------------------------------
 * Function:    write_minmax
 * Returns:     t or nil
 * Description: Writes a string to the plhistsetup.dat file containing
 *              four lines, each line assigning a value for an X or Y axis
 *              minimum or maximum variable, which are used by gnuplot.
 *------------------------------------------------------------------*/
function write_minmax(minmax_string)
{
  local fp = open("/tmp/cogentdemo/plhistsetup.dat", "a", nil);
  writec(fp, minmax_string);
  close(fp);
}

/*--------------------------------------------------------------------
 * Function:    file_add
 * Returns:     t or nil
 * Description: Adds content from a file previously opened for reading
 *              (inptr) to a file previously opened for writing (outptr).
 *              Adds extra newlines, which gnuplot uses to mark the beginning
 *              of a new index file.  Called only by show_data().
 *------------------------------------------------------------------*/
function file_add(inptr, outptr)
{
  local line;
  while(line != _eof_)
    {
      line = read_line(inptr);
      if (line != "Unexpected end of file")
        writec(outptr, string(line, "\n"));
    }
  writec(outptr, "\n\n");
  close(inptr);
}

/*--------------------------------------------------------------------
 * Function:    show_data
 * Returns:     t or nil
 * Description: Shows interpolated query results from the Cascade Historian
 *              in the text widget.  Sets up a single file (hsoutputpl.dat)
 *              that contains the content of all existing hsoutput files.
 *              This is necessary because gnuplot must read from a single file.
 *              Uses file_add() to do the actual reading and writing of the files.
 *------------------------------------------------------------------*/
function show_data(txt_wgt, int_set, button, datalist, minmaxlist)
{
  local start_time, tsk, infile, line, string_list, line_ret;
  local title_string, x_list, y_list;
  local f1, f2, f3, f4;

  x_list = car(datalist);
  y_list = cadr(datalist);
  midnite = find_midnite();
  
  if (int_set.fn == "NoInterpolator"  || int_set.fn == "Periodic")
    {
      x_is_time = t;
      title_string = "Query results: \n     Time             Y History\n";
    }
  else
    {
      x_is_time = nil;
      title_string = "Query results: \nX-hist / Y-hist\n";
    }

//  midnite = find_midnite();
  txt_wgt.freeze();
  txt_wgt.backward_delete(txt_wgt.get_length());
  txt_wgt.insert(FONT, nil, nil, title_string, -1);
  
  if(int_set.dbflag == "NONE")
    infile = open("/tmp/cogentdemo/hsoutput.dat", "r");
  if(int_set.dbflag == "DB")
    infile = open("/tmp/cogentdemo/hsoutput2.dat", "r");
  if(int_set.dbflag == "DBP")
    infile = open("/tmp/cogentdemo/hsoutput3.dat", "r");
  if (infile)
    {
      line = nil;
      while(line != _eof_)
        {
          line = read_line(infile);
          if (line != "Unexpected end of file")
            {
              if ((strstr(line,"#") != -1))
                {
                  txt_wgt.insert(FONT, nil, nil, line, -1);
                  read_line(infile);
                  read_line(infile);
                }
              else
                {
                  string_list = string_split(line, " ", 2);
                  if (int_set.fn == "NoInterpolator"
                      || int_set.fn == "Periodic")
                    line_ret = string(number(car(string_list)) - midnite,
                                      " ", cadr(string_list),"\n");
                  else
                    line_ret = string(number(car(string_list)),
                                      " ", cadr(string_list),"\n");
                  txt_wgt.insert(FONT, nil, nil, line_ret, -1);
                }
            }
        }
      close(infile);
      
      if(x_list && y_list && minmaxlist != nil)
        {
          if (x_is_time)
            {
              x_min = caar(minmaxlist);
              x_max = cadar(minmaxlist);
            }        
          else
            {
              x_min = floor(caar(minmaxlist));
              x_max = ceil(cadar(minmaxlist));
            }          
          y_min = floor(caadr(minmaxlist));
          y_max = ceil(car(cdadr(minmaxlist))) + 1;
          int_set.misc_info = string("x_min = ", x_min, "\n",
                                     "x_max = ", x_max, "\n",
                                     "y_min = ", y_min, "\n",
                                     "y_max = ", y_max, "\n");
          write_minmax(int_set.misc_info);
        }
      else
        {
          txt_wgt.backward_delete(txt_wgt.get_point());
          txt_wgt.insert(FONT, nil, nil,
                         string("Sorry, there was an error retrieving\n",
                                "data from the Cascade Historian."), -1);
        }
    }
  txt_wgt.thaw();

  if(is_file("/tmp/cogentdemo/hsoutput.dat"))
    f1 = open("/tmp/cogentdemo/hsoutput.dat", "r");
  if(is_file("/tmp/cogentdemo/hsoutput2.dat"))
    f2 = open("/tmp/cogentdemo/hsoutput2.dat", "r");
  if(is_file("/tmp/cogentdemo/hsoutput3.dat"))
    f3 = open("/tmp/cogentdemo/hsoutput3.dat", "r");
  f4 = open("/tmp/cogentdemo/hsoutputpl.dat", "w");

  if(f1)
    {
      file_add(f1, f4);
      if(f2)
        file_add(f2, f4);
      else
        /* Put in a spacer line so gnuplot starts a new plot. */
        writec(f4, "999 999\n\n\n");
      if(f3)
        file_add(f3, f4);
    }
  
  close(f4);
}

/********************************************************
 *               DEADBANDING Code                       *
 ********************************************************/

/*--------------------------------------------------------------------
 * Function:    db_options
 * Returns:     t or nil
 * Description: Creates a GtkEntry widget and puts it into a table.
 *              The widget calls the update_value() function when it
 *              gets changed.
 *------------------------------------------------------------------*/
function db_options(tbl, p_array, left, right)
{
  local entry;

  for (i=0; i<=3; i++)
    {
      entry = new(GtkEntry);
      entry.width = 40;
      entry.set_text(string(p_array[i]));
      entry.signal("changed", `(@p_array)[@i] = number((@entry).get_text()));
      tbl.attach_defaults(entry, left, right, i + 2, i + 3);
    }
}

/*--------------------------------------------------------------------
 * Function:    db_labels
 * Returns:     t or nil
 * Description: Creates labels and puts them in a table.
 *------------------------------------------------------------------*/
function db_labels(tbl, title, left, right, top, bottom)
{
  local label;
  
  label = new(GtkLabel);
  label.set_text(title);
  tbl.attach_defaults(label, left, right, top, bottom);
}

/*--------------------------------------------------------------------
 * Function:    create_dbtype_win
 * Returns:     A GtkWindow, or a destroyed instance
 * Description: Creates the Deadband Type window.
 *------------------------------------------------------------------*/
function create_dbtype_win (!point_list, button)
{
  local db1, db2, db3, vbox, table, pa1, pa2, pa3;
  local label, separator, box2, abut, cbut, okbut;

  if (((undefined_p(win_db) || (win_db == nil))
       && (button.get_active() == TRUE)))
    {
      db1 = car(point_list);
      db2 = cadr(point_list);

      win_db = new (GtkWindow);
      send_message("6.50");
      win_db.signal ("destroy", #win_db = nil);
      win_db.title = "Deadband Type";
      win_db.set_uposition(20, 440);
      win_db.set_color(1, GTK_STATE_NORMAL, 0xdddddd);
      win_db.width = 500;

      table = gtk_table_new(7, 4, FALSE);
      table.border_width = 15;
      table.set_col_spacings(15);
      table.set_row_spacings(3);
      table.set_row_spacing(0, 10);
      table.set_row_spacing(3, 10);
      table.set_row_spacing(5, 10);

      db_labels(table, "Set up deadband types", 0, 4, 0, 1);
      db_labels(table, "MV", 1, 2, 1, 2);
      db_labels(table, "PV", 2, 3, 1, 2);

      db_labels(table, "Absolute:", 0, 1, 2, 3);
      db_labels(table, "Percent:", 0, 1, 3, 4);
      db_labels(table, "Time Limit:", 0, 1, 4, 5);
      db_labels(table, "Count Limit:", 0, 1, 5, 6);

      db_labels(table, string("Deadband range is set to this ",
                              "value.                  "), 3, 4, 2, 3);
      db_labels(table, string("Range set to percent of last logged ",
                              "value.          "), 3, 4, 3, 4);
      db_labels(table, string("The maximum time period to deadband.",
                              "               "), 3, 4, 4, 5);
      db_labels(table, string("The max number of received values to",
                              " deadband."), 3, 4, 5, 6);
      db_labels(table, string("Note: These settings don't take effect",
                              " until the next record session."), 0, 4, 6, 7);

      pa1 = array(db1.absolute, db1.percent,
                  db1.timelimit, db1.countlimit);

      pa2 = array(db2.absolute, db2.percent,
                  db2.timelimit, db2.countlimit);

      db_options(table, pa1, 1, 2);
      db_options(table, pa2, 2, 3);

      vbox = new(GtkVBox);
      win_db.add(vbox);
      vbox.pack_start(table, TRUE, TRUE, 0);

      separator = new(GtkHSeparator);
      vbox.pack_start(separator, TRUE, TRUE, 0);

      box2 = new(GtkHButtonBox);
      box2.border_width = 10;
      box2.set_layout(GTK_BUTTONBOX_SPREAD);
      vbox.pack_start(box2, TRUE, TRUE, 0);

      cbut = new(GtkButton);
      cbut.label = "Cancel";
      cbut.signal("clicked", `(@button).set_active(FALSE));
      cbut.signal("clicked", `(@win_db).destroy());
      button_messages (cbut, "6.55", "6.50");
      box2.pack_start(cbut, TRUE, TRUE, 0);
      cbut.show();
  
      okbut = new(GtkButton);
      okbut.label = "OK";
      okbut.signal("clicked", `(@db1).set_parms(@okbut, @pa1));
      okbut.signal("clicked", `(@db2).set_parms(@okbut, @pa2));
      okbut.signal("clicked", `(@button).set_active(FALSE));
      okbut.signal("clicked", `(@win_db).destroy());
      button_messages (okbut, "6.56", "6.50");
      box2.pack_start(okbut, TRUE, TRUE, 0);
      okbut.show();

      win_db.signal("destroy", `(@button).set_active(FALSE));
      win_db.show_all();
      win_db;
    }
  else
      if(instance_p(win_db))
        win_db.destroy();
}


/********************************************************
 *               HISTORY WINDOW Code                    *
 ********************************************************/

/*--------------------------------------------------------------------
 * Function:    make_toggle_button
 * Returns:     A GtkToggleButton
 * Description: Creates toggle buttons.
 *------------------------------------------------------------------*/
function make_toggle_button(lbl, color)
{
  local button;
  
  button = new(GtkToggleButton);
  button.label = lbl;
  button.set_color(1, GTK_STATE_NORMAL, color);
  button.set_color(1, GTK_STATE_PRELIGHT, (color + 0x111111));
  button.set_color(1, GTK_STATE_ACTIVE, (color - 0x111111));
  button;
}

/*--------------------------------------------------------------------
 * Function:    qry_radio_but
 * Returns:     A GtkRadioButton
 * Description: Creates the "Choose an Interpolator" radio buttons and
 *              puts them into a table.  The last 6 parameters simply
 *              get passed along to the allow_entry_values() function
 *              and the .change_int() method.
 *------------------------------------------------------------------*/
function qry_radio_but(tbl, txt, butlist, int_set, msg,
                       top, bottom, w1, w2, w3, e1, e2, e3)
{
  local button;
  button = gtk_radio_button_new_with_label(butlist, txt);
  if (txt == "NoInterpolator")
    {
      button.set_active(TRUE);
      allow_entry_values(w1, w2, w3, e1, e2, e3);      
    }
  button.signal("clicked", `((@int_set).change_int(@button, @txt, @w1, @w2,
                                                    @w3, @e1, @e2, @e3)));
  button_messages (button, msg, "6");
  tbl.attach_defaults(button, 0, 1, top, bottom);
  button;
}

/*--------------------------------------------------------------------
 * Function:    qry_entry
 * Returns:     A GtkEntry or GtkCombo widget, as specified
 * Description: Creates a GtkEntry or GtkCombo widget, sets defaut values,
 *              and puts it into a table.
 *------------------------------------------------------------------*/
function qry_entry(tbl, txt, value, top, bottom)
{
  local box, label, entry, cbitems, cmb, ent_but = nil;

  box = new(GtkHBox);
  label = new(GtkLabel);
  label.set_text(txt);
  label.width = 80;
  box.pack_start(label, TRUE, TRUE, 0);

  if ((strstr(txt, "history")) == -1)
    {
      entry = new(GtkEntry);
      entry.width = 75;
      if (value == nil)
        entry.set_text("");
      else
        entry.set_text(string(value));
      box.pack_start(entry, TRUE, TRUE, 0);
    }
  else
    {
      cbitems = list("MV_001", "PV_001");
      cmb = new(GtkCombo);
      cmb.width = 75;
      cmb.use_arrows = 0;
      cmb.set_popdown_strings(cbitems);
      entry = car(cmb.children());
      ent_but = cadr(cmb.children());
      entry.set_editable(FALSE);
      if (value == nil)
        entry.set_text("");
      else
        entry.set_text(string(value));
      box.pack_start(cmb, TRUE, TRUE, 0);
    }
  tbl.attach_defaults(box, 1, 2, top, bottom);
  entry;
}

/*--------------------------------------------------------------------
 * Function:    create_history
 * Returns:     A GtkWindow
 * Description: Creates the History window.
 *------------------------------------------------------------------*/
function create_history ()
{
  local history_win, title, frame, box1, box2, box3, table;
  local label, label1, label2, but_int1, but_int2, but_int3, but_int4;
  local recbut, entry, qubut, dbsbut, text, but_db1, but_db2, but_db3;
  local spinner, adj, xbut, iset, ent_yhs, ent_str, ent_dur;
  local ent_xhs, ent_int, ent_max, ent_res, dbmv_set, dbpv_set, db_list;
  local dbsbut, plbut, swin, buttons;
  
  history_win = new (GtkWindow);
  send_message("6");
  history_win.signal ("destroy", #history_win = nil);
  history_win.title = "Cogent Tools Demo: History";
  history_win.set_color(1, GTK_STATE_NORMAL, 0xdddddd);
  history_win.border_width = 10;
  history_win.width = 380;

  /* Title frame. */
  
  box1 = new(GtkVBox);
  title = new (GtkLabel);
  title.set_text("Cogent Tools Demo - History");
  title.set_color(1, GTK_STATE_NORMAL, 0xff0000);
  title.height = 25;

  frame = new(GtkFrame);
  frame.set_color(1, GTK_STATE_NORMAL, 0xff0000);
  frame.add(title);
  frame.border_width = 5;
  box1.pack_start(frame, TRUE, TRUE, 0);
  history_win.add (box1);

  /* Record Values and Exit buttons. */
  
  table = gtk_table_new(5, 1, FALSE);
  table.border_width = 5;
  table.set_homogeneous(FALSE);

  recbut = make_toggle_button("Record", 0xddccee);
  recbut.signal("clicked", `send_hs_command(@recbut, "enable", nil, nil));
  button_messages (recbut, "6.2", "6");

  label1 = new(GtkLabel);
  label1.set_text("data for");

  adj = gtk_adjustment_new (0, 1, 200, 1, 5, 0.0);
  spinner = new(GtkSpinButton);
  spinner.set_adjustment(adj);
  spinner.set_value(RECORD_TIME);
  adj.signal("value_changed", `(RECORD_TIME = (@adj).value));
  
  label2 = new(GtkLabel);
  label2.set_text("seconds.           ");

  xbut = new(GtkButton);
  xbut.label = "Exit";
  xbut.signal("clicked", `(@history_win).destroy());

  iset = new(InterpolatorSettings);

  table.attach_defaults(recbut, 0, 1, 0, 1);  
  table.attach_defaults(label1, 1, 2, 0, 1);  
  table.attach_defaults(spinner, 2, 3, 0, 1);
  table.attach_defaults(label2, 3, 4, 0, 1);
  table.attach_defaults(xbut, 4, 5, 0, 1);  
  box1.pack_start(table, TRUE, TRUE, 5);
  
  text = new(GtkText);
  text.set_editable(FALSE);

  /* Interpolator options. */

  frame = new(GtkFrame);
  frame.border_width = 5;
  frame.set_color(1, GTK_STATE_NORMAL, 0xddccee);
  frame.set_label(" Interpolator options: ");
  frame.set_label_align(.05, 1);
  frame.set_shadow_type(GTK_SHADOW_IN);
  box1.pack_start(frame, TRUE, TRUE, 0);
  
  table = gtk_table_new(2, 6, FALSE);
  table.border_width = 10;
  table.set_col_spacing(0, 20);
  table.set_row_spacings(3);
  frame.add(table);
  
  ent_yhs = qry_entry(table, "Y history:", iset.y_history, 0, 1);
  ent_str = qry_entry(table, "Start:", iset.start, 1, 2);
  ent_dur = qry_entry(table, "Duration:", iset.duration, 2, 3);
  ent_xhs = qry_entry(table, "X history:", iset.x_history, 3, 4);
  ent_int = qry_entry(table, "Time Interval:", iset.interval, 4, 5);
  ent_max = qry_entry(table, "Max. gap:", iset.max_gap, 5, 6);
  
  but_int1 = qry_radio_but(table, "NoInterpolator", nil, iset, 
                           "6.31", 0, 1, ent_xhs, ent_int, ent_max,
                           0, 0, 0);
  but_int2 = qry_radio_but(table, "Periodic", list(but_int1), iset, 
                           "6.32", 1, 2, ent_xhs, ent_int, ent_max,
                           0, 1, 1);
  but_int3 = qry_radio_but(table, "Relative", list(but_int1, but_int2), 
                           iset, "6.33", 2, 3, ent_xhs, ent_int,
                           ent_max, 1, 0, 0);
  but_int4 = qry_radio_but(table, "FixedRelative", 
                           list(but_int1, but_int2, but_int3), iset, "6.34",
                           3, 4, ent_xhs, ent_int, ent_max, 1, 1, 0);
    
  ent_res = new(GtkEntry);
  ent_res.width = 45;
  
  qubut = new(GtkButton);
  qubut.label = "Update display";
  qubut.set_color(1, GTK_STATE_NORMAL, 0xddccee);
  qubut.set_color(1, GTK_STATE_PRELIGHT, 0xeeddff);
  button_messages (qubut, "6.3", "6");
  table.attach_defaults(qubut, 0, 1, 5, 6);
  
  /* Deadband options. */
  dbmv_set = new(DeadbandSettings);
  dbmv_set.history = "MV_001";

  dbpv_set = new(DeadbandSettings);
  dbpv_set.history = "PV_001";

  db_list = list(dbmv_set, dbpv_set);

  box2 = new(GtkHBox);
  box1.pack_start(box2, TRUE, TRUE, 0);

  frame = new(GtkFrame);
  frame.border_width = 5;
  frame.set_color(1, GTK_STATE_NORMAL, 0xddccee);
  frame.set_label(" Deadband options: ");
  frame.set_label_align(.05, 1);
  frame.set_shadow_type(GTK_SHADOW_IN);
  box2.pack_start(frame, TRUE, TRUE, 0);
  
  table = gtk_table_new(1, 4, FALSE);
  table.border_width = 10;
  table.set_row_spacings(3);
  frame.add(table);
  
  but_db1 = gtk_check_button_new_with_label ("Full data set (no deadband)");
  but_db2 = gtk_check_button_new_with_label ("Deadband on, prior value off");
  but_db3 = gtk_check_button_new_with_label ("Deadband on, prior value on");
  but_db1.signal("enter", `send_message("6.51"));
  but_db2.signal("enter", `send_message("6.52"));
  but_db3.signal("enter", `send_message("6.53"));

  dbsbut = make_toggle_button("Set up deadband types", 0xddccee);
  dbsbut.signal("toggled", `create_dbtype_win(@db_list, @dbsbut));
  dbsbut.signal("enter", `send_message("6.5"));

  table.attach_defaults(but_db1,  0, 1, 0, 1);
  table.attach_defaults(but_db2,  0, 1, 1, 2);
  table.attach_defaults(but_db3,  0, 1, 2, 3);
  table.attach_defaults(dbsbut,  0, 1, 3, 4);

  /* Plot and text display. */
  box3 = new(GtkVBox);
  box2.pack_start(box3, TRUE, TRUE, 30);
  
  local button;
  plbut = new(GtkToggleButton);
  plbut.height = 20;
  plbut.label = "Plot Data";
  plbut.set_color(1, GTK_STATE_NORMAL, 0xccccb0);
  plbut.set_color(1, GTK_STATE_PRELIGHT, 0xddddbb);
  plbut.set_color(1, GTK_STATE_ACTIVE, 0xbbbb99);
  plbut.signal("toggled", `start_stop(@plbut, "gnuplot", "plothist1", 
                                      "-background", "gray", "-geometry",
                                      "500x400-10-30", "plhist.cfg", nil, nil, nil));
  plbut.signal("toggled", `anygui_move_window(@plbut, @history_win,
                                              380, 45, 100, 45));
  button_messages (plbut, "6.4", "6");

  label = new(GtkLabel);
  label.set_text("Number\nof points\nfrom last\nquery:");

  box3.pack_end(ent_res, FALSE, FALSE, 5);
  box3.pack_end(label, TRUE, TRUE, 5);
  box3.pack_end(plbut, TRUE, TRUE, 5);
  
  swin = new(GtkScrolledWindow);
  swin.set_policy(GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
  swin.border_width = 5;
  swin.height = 200;
  swin.width = 400;
  swin.add(text);
  box1.pack_start(swin, TRUE, TRUE, 0);
  
  recbut.signal("clicked",
                `record_data(@recbut, @iset, @but_db1, @but_db2, @but_db3, @text,
                             @ent_yhs, @ent_str, @ent_dur, @ent_xhs,
                             @ent_int, @ent_max, @ent_res,
                              @but_int1, @but_int2,
                              @but_int3, @but_int4, @qubut));

  /* Callbacks for non-deadband buttons to send a query. */
  buttons = list(but_int1, but_int2, but_int3, but_int4, qubut);
  with b in buttons do
  {
    b.signal("clicked",
             `reset_deadbands(@b, @iset, @but_db1, @but_db2, @but_db3, @text,
                              @ent_yhs, @ent_str, @ent_dur, @ent_xhs,
                              @ent_int, @ent_max, @ent_res,
                              @but_int1, @but_int2, @but_int3,
                              @but_int4, @recbut, @qubut));
  }
  
  /* Callbacks for deadband buttons to prepare for and send a query. */
  buttons = list(but_db1, but_db2, but_db3);
  with b in buttons do
  {
    b.signal("clicked",
             `plot_prep(@b, @iset, @but_db1, @but_db2, @but_db3, @text,
                        @ent_yhs, @ent_str, @ent_dur, @ent_xhs,
                        @ent_int, @ent_max, @ent_res,
                        @but_int1, @but_int2, @but_int3,
                        @but_int4, @recbut, @qubut));
  }

  /* Display version info and start-up message.*/
  ret = display_hs_info(text);
  
  history_win.show_all();
  history_win.reposition(380, 45);
  history_win;
}

/********************************************************
 *                 MAIN FUNCTION Code                   *
 ********************************************************/

/*--------------------------------------------------------------------
 * Function:    main
 * Returns:     doesn't return
 * Description: Calls the common.g program_startup() function and loops.
 *------------------------------------------------------------------*/
function main()
{
  program_startup("history", "historyq", #create_history(), "6");
  
  atexit(#stop_processes());

  /* Loop forever handling events. */
  gtk_main ();
}