3.11. phhistory.g - The History program in Photon

/*--------------------------------------------------------------------
 * File:        phhistory.g
 *
 * Description: Demonstrates the Cascade Historian
 *
 * Classes:     HistoryWindow
 *              DeadbandType
 *
 * Functions and methods by category:
 *
 * Int & Query: InterpolatorSettings.assign_values
 *              InterpolatorSettings.get_data
 *              send_query
 *              countdown
 *              record_data
 *              show_data
 *              query_refresh
 * Deadbanding: reset_deadbands
 *              db_prep_query
 *              DeadbandType.constructor
 *              db_options
 *              create_dbtype_win
 * Plotting:    make_CwGraph_points
 *              make_graph
 * Window:      HistoryWindow.constructor
 *              PtText.set_sensitive
 *              PtComboBox.set_sensitive
 *              HistoryWindow.allow_entry_values
 *              HistoryWindow.change_int
 *              create_history
 * Main         main
 *------------------------------------------------------------------*/

/* Get access to the library of common functions. */
require("lib/common.g");
require_lisp("Profile");
anyver_loadlibs();

/* Keep track of all child processes that this process has started.*/
Children := nil;

/********************************************************
 *           INTERPOLATOR and QUERY Code                *
 ********************************************************/

/*--------------------------------------------------------------------
 * 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.
 *------------------------------------------------------------------*/
method InterpolatorSettings.assign_values(e1, e2, e3, e4, e5, e6)
{
  .y_history = assign_history(.dbflag, .y_history, e1.text_string);
  .start = number(e2.text_string);
  .duration = number(e3.text_string);
  .x_history = assign_history(.dbflag, .x_history, e4.text_string);
  .interval = number(e5.text_string);
  .max_gap = number(e6.text_string);
}

/*--------------------------------------------------------------------
 * Method:      InterpolatorSettings.get_data
 * Returns:     a list of x-data and y-data, as strings
 * Description: Sends a query to the Historian and receives the data.
 *              Activated by the send_query function.  
 *------------------------------------------------------------------*/
method InterpolatorSettings.get_data()
{
  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;
          hw.StartEnt.text_string = "0";
        }
      if (undefined_p(.duration) || (!number_p(.duration))
          || (.duration <= 0))
        {
          .duration = 10;
          hw.DurEnt.text_string = "10";
        }
      if (undefined_p(.interval) || (!number_p(.interval))
          || (.interval < .01))
        {
          .interval = .01;
          hw.IntEnt.text_string = "0.01";
        }
      
      if ((tsk = locate_task("demohistdb", nil)) != nil)
        {
          intpl = self.set_interpolator(tsk);
          
          buf_length = caddr(send(tsk, `bufferIdLength(intpl)));
          if(!((.fn == "TimeSetup") || (.fn == "RelSetup")))
            hw.NumPts.text_string = string(buf_length);
          
          alldata = list();
          count = 0;
          
          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 (outfile = open("/tmp/cogentdemo/hsoutput.dat", "w"))
            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);
}

/*--------------------------------------------------------------------
 * Function:    send_query
 * Returns:     t or nil
 * Description: Calls the necessary functions to send a query to the
 *              Historian, and handles the data returned.  It is activated
 *              by the "Record" button popping out, or any radio button.
 *------------------------------------------------------------------*/
function send_query(iset, text, parent, ent1, ent2,
                    ent3, ent4, ent5, ent6, button)
{
  local fnholder, datalist, minmaxlist;
  
  if ((button == hw.NoIntBut || button == hw.PeriodBut ||
       button == hw.RelBut || button  == hw.FixRelBut ||
       button == hw.DisplayBut || button  == hw.RecBut))
    iset.dbflag = "NONE";
  
  iset.assign_values(ent1, ent2, ent3, ent4, ent5, ent6);
  
  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();
  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();
  show_data(text, iset, parent, button, datalist, minmaxlist);
}

/*--------------------------------------------------------------------
 * Function:    countdown
 * Returns:     an integer
 * Description: Called by record_data to facilitate the time count-down.
 *              This functionality had to be separated out from the callback.
 *------------------------------------------------------------------*/
function countdown(starttime)
{
  starttime - clock();
}

RECORD_TIME = 5;

/*--------------------------------------------------------------------
 * Function:    record_data
 * Returns:     an integer (timer number), or nil
 * Description: Records data for the specified time, then sends a query.
 *------------------------------------------------------------------*/
function record_data(but1, but2, iset, text, window, e1, e2, e3, e4, e5, e6)
{
  if (but1.switched_on())
    {
      local time = clock() + RECORD_TIME;
      text.text_string = string("Recording,please wait.\nSeconds remaining: ",
                                RECORD_TIME);
      numtimer = every (1, `((@text).text_string = string(substr((@text).text_string, 0, 42),
                                                          string((countdown(@time))))));
      e3.text_string = string(RECORD_TIME + 1);
      iset.set_defaults(but1, e2);
      send_hs_command(but1, "enable", e1.text_string, nil);
      after(RECORD_TIME, `((@but1).flags = cons(Pt_SET, nil)));
      after((RECORD_TIME + .1), `(send_hs_command(@but1, "enable", (@e1).text_string, nil)));
      after((RECORD_TIME + .1), `(cancel(numtimer)));
      after((RECORD_TIME + .15), `(query_refresh(nil, nil)));
      after((RECORD_TIME + .25), `PtFlush());
    }
}

/*--------------------------------------------------------------------
 * Function:    show_data
 * Returns:     t or nil
 * Description: Shows interpolated query results from the Cascade Historian
 *              in the text widget and plots the results in the CwGraph widget.
 *------------------------------------------------------------------*/
function show_data(txt_wgt, int_set, parent, button, datalist, minmaxlist)
{
  local start_time, infile, line, string_list, line_ret;
  local x_is_time, x_min, x_max, y_min, y_max, x_list, y_list;
  local outstring = open_string ("");
  
  x_list = car(datalist);
  y_list = cadr(datalist);
  txt_wgt.text_string = "";
  start_time = find_midnite();
  
  if (int_set.fn == "NoInterpolator"  || int_set.fn == "Periodic")
    {
      x_is_time = t;
      txt_wgt.text_string = "Query results: \n     Time         Y History\n";
    }
  else
    {
      x_is_time = nil;
      txt_wgt.text_string = "Query results: \nX-hist / Y-hist\n";
    }
  
  infile = open("/tmp/cogentdemo/hsoutput.dat", "r");
  if (infile)
    {
      line = nil;
      while(line != _eof_)
        {
          line = read_line(infile);
          line_ret = "";
          if (line != "Unexpected end of file")
            {
              if ((strstr(line,"#") != -1))
                {
                  writec (outstring, line);
                  read_line(infile);
                  read_line(infile);
                }
              else
                {
                  string_list = string_split(line, " ", 2);
                  if (x_is_time)
                    {
                      line_ret = string(number(car(string_list)) - start_time,
                                        " ", cadr(string_list),"\n");
                    }
                  else
                    {
                      line_ret = string(number(car(string_list)),
                                        "   ", cadr(string_list),"\n");
                    }
                  writec (outstring, line_ret);
                }
            }
        }
      
      txt_wgt.text_string = string(txt_wgt.text_string, string_file_buffer(outstring));
      
      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;
          
          PtSetParentWidget(parent);
          
          make_graph(x_min, x_max, y_min, y_max,
                            x_list, y_list, int_set.dbflag);
        }          
      else
        txt_wgt.text_string = string("Sorry, there was an error retrieving\n",
                                     "data from the Cascade Historian.");
      close(infile);
    }
}

/*--------------------------------------------------------------------
 * Function:    query_refresh
 * Returns:     t or nil
 * Description: Sets the interpolator, resets the deadband, and sends a
 *              query.  Used by callbacks on the four interpolator buttons,
 *              the Update display button, and a timer on the Record button.
 *------------------------------------------------------------------*/
function query_refresh(button, title_string)
{
  if (title_string)
    hw.change_int(button, title_string);
  
  reset_deadbands();
  send_query(hw.int_set, hw.Text, hw.PtHist, hw.YHistEnt,
             hw.StartEnt, hw.DurEnt, hw.XHistEnt, hw.IntEnt,
             hw.GapEnt, button);
}

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

/*--------------------------------------------------------------------
 * Function:    reset_deadbands
 * Returns:     an integer
 * Description: Clears all traces and sets the Full data set (no deadband)
 *              option.  Called when non-deadband buttons are activated.
 *------------------------------------------------------------------*/
function reset_deadbands()
{
  hw.DBBut.flags = cons(Pt_SET, nil);
  hw.DBPBut.flags = cons(Pt_SET, nil);
  
  if(!undefined_p(GraphWidget))
    {
      CwGraphClearTrace (GraphWidget, 0);
      CwGraphClearTrace (GraphWidget, 1);
      CwGraphClearTrace (GraphWidget, 2);
    }
  hw.DBNoneBut.flags = Pt_SET; 
}

/*--------------------------------------------------------------------
 * Function:    db_prep_query
 * Returns:     t or nil
 * Description: Sends a query when a Deadband button is toggled on, and
 *              erases the corresponding graph trace when a Deadband button
 *              is untoggled.
 *------------------------------------------------------------------*/
function db_prep_query(button)
{
  if ((button.flags & Pt_SET) != 0)
    send_query(hw.int_set, hw.Text, hw.PtHist, hw.YHistEnt,
               hw.StartEnt, hw.DurEnt, hw.XHistEnt, hw.IntEnt,
               hw.GapEnt, button);
  else
    {
      if(!undefined_p(GraphWidget))
        {
          if(button == hw.DBNoneBut)
            CwGraphClearTrace (GraphWidget, 0);
          else if(button == hw.DBBut)
            CwGraphClearTrace (GraphWidget, 1);
          else if(button == hw.DBPBut)
            CwGraphClearTrace (GraphWidget, 2);
        }
    }        
}

/********************************************************
 *          DEADBAND TYPE WINDOW Code                   *
 ********************************************************/

/*--------------------------------------------------------------------
 * Class:       DeadbandType
 * Description: Creates the Deadband Type window using PhabAttachWidgets.
 *------------------------------------------------------------------*/
class DeadbandType
{
  window;
}

PhabAttachWidgets (DeadbandType, anyos_assign("deadband_widgetfile"));

/*--------------------------------------------------------------------
 * Method:      DeadbandType.constructor
 * Returns:     the root widget of a widget hierarchy
 * Description: Instantiates a DeadbandType window.
 *------------------------------------------------------------------*/
method DeadbandType.constructor ()
{
  .window = PhabRoot(.PhabInstantiate (t));
}

/*--------------------------------------------------------------------
 * Function:    db_options
 * Returns:     a PtText widget
 * Description: Creates a PtText widget, gives it a position, and gives
 *              it a callback to update the corresponding value in the
 *              parameter array.
 *------------------------------------------------------------------*/
function db_options (p_array, index, col, row)
{
  local option;
  local cols = [86, 148];
  local rows = [55, 86, 127, 157];
  
  option = new(PtText);
  option.SetDim(40, 20);
  option.fill_color = 0xffffee;
  option.border_width = 1;
  option.text_string = (string(p_array[index]));
  option.SetPos(cols[col], rows[row]);
  PtAttachCallback(option, Pt_CB_TEXT_CHANGED,
                   `((@p_array)[@index] = number((@option).text_string)));
  option;
}

/*--------------------------------------------------------------------
 * Function:    create_dbtype_win
 * Returns:     a Deadband Type window, or a destroyed instance
 * Description: Creates the Deadband Type window.
 *------------------------------------------------------------------*/
function create_dbtype_win (!point_list, button)
{
  local db1, db2, pa1, pa2;
  if ((button.flags & Pt_SET) != 0)
    {
      if (undefined_p(db) || db.window == nil)
        {
          db = new (DeadbandType);
          db.DeadBandWindow.SetPos(150, 300);
          attach_msg(db.DeadBandWindow, "6.54", "6");
          
          db1 = car(point_list);
          db2 = cadr(point_list);
          
          pa1 = array(db1.absolute, db1.percent, db1.timelimit, db1.countlimit);
          pa2 = array(db2.absolute, db2.percent, db2.timelimit, db2.countlimit);
          
          db_options(pa1, 0, 0, 0);
          db_options(pa1, 1, 0, 1);
          db_options(pa1, 2, 0, 2);
          db_options(pa1, 3, 0, 3);
          
          db_options(pa2, 0, 1, 0);
          db_options(pa2, 1, 1, 1);
          db_options(pa2, 2, 1, 2);
          db_options(pa2, 3, 1, 3);
          
          attach_msg(db.CancelBut, "6.55", "6.5");
          attach_msg(db.OKBut, "6.56", "6.5");
          
          PtAttachCallback(db.CancelBut, Pt_CB_ACTIVATE,
                           `((@button).flags = cons(Pt_SET, nil)));
          PtAttachCallback(db.CancelBut, Pt_CB_ACTIVATE,
                           `PtDestroyWidget((@db).window));                        
          
          PtAttachCallback(db.OKBut, Pt_CB_ACTIVATE,
                           `(@db1).set_parms((@db).OKBut, @pa1));
          PtAttachCallback(db.OKBut, Pt_CB_ACTIVATE,
                           `(@db2).set_parms((@db).OKBut, @pa2));
          PtAttachCallback(db.OKBut, Pt_CB_ACTIVATE,
                           `((@button).flags = cons(Pt_SET, nil)));
          PtAttachCallback(db.OKBut, Pt_CB_ACTIVATE,
                           `PtDestroyWidget((@db).window));
          
          PtAttachCallback(db.window, Pt_CB_DESTROYED,
                           `((@button).flags = cons(Pt_SET, nil)));
          PtAttachCallback(db.window, Pt_CB_DESTROYED,
                           `((@db).window = nil));
        }
      if(db)
        PtRealizeWidget (db.window);
      db;
    }        
  else
    PtDestroyWidget (db.window);
}

/********************************************************
 *                     PLOTTING                         *
 ********************************************************/

/*--------------------------------------------------------------------
 * Function:    make_CwGraph_points
 * Returns:     a list
 * Description: Applied to the x and y arguments of the CwGraphAddXYPoints()
 *              function, which are lists. This function destructively
 *              modifies those lists, adding extra values that, when plotted
 *              by CwGraph, render as small, diamond-shaped points. The xval
 *              parameter must be set to t for the x-coordinate, and to nil
 *              for the y coordinate.
 *------------------------------------------------------------------*/
function make_CwGraph_points (ptlist, ptsize, xval)
{
  parray = list_to_array(ptlist);
  i = 0;
  if(xval)
    {
      /* Add x-values to the trace. */ 
      while (i < length(parray))
        {        
          insert(parray, i+1, parray[i] + (ptsize));
          insert(parray, i+2, parray[i]);
          insert(parray, i+3, parray[i] - (ptsize));
          insert(parray, i+4, parray[i]);
          insert(parray, i+5, parray[i] + (ptsize));
          insert(parray, i+6, parray[i]);
          i = i + 7;
        }
    }
  else
    {
      /* Add y-values to the trace. */ 
      while (i < length(parray))
        {        
          insert(parray, i+1, parray[i]);
          insert(parray, i+2, parray[i] - (ptsize));
          insert(parray, i+3, parray[i]);
          insert(parray, i+4, parray[i] + (ptsize));
          insert(parray, i+5, parray[i]);
          insert(parray, i+6, parray[i]);
          i = i + 7;
        }
    }
  ret = array_to_list(parray);
}

/*--------------------------------------------------------------------
 * Function:    make_graph
 * Returns:     a CwGraph widget
 * Description: Sets up a CwGraph widget to plot the query results.
 *------------------------------------------------------------------*/
function make_graph (xmin, xmax, ymin, ymax, xlist, ylist, dband)
{
  local grf;

  /* Set a size for the points created by make_CwGraph_points. */
  local xdist = (xmax - xmin) * .007;
  local ydist = (ymax - ymin) * .007;
  
  if (undefined_p(GraphWidget) || !GraphWidget)
    {
      /* Make a new graph, if one doesn't already exist. */
      PtSetParentWidget (hw.GraphPane);
      GraphWidget = new(CwGraph);
      
      GraphWidget.SetArea(0, 0, hw.GraphPane.dim.w, hw.GraphPane.dim.h);
      GraphWidget.fill_color = 0xffffe5;
      GraphWidget.bot_border_color = 0x000000;
      GraphWidget.graph_y_label_left = "Y history";
      GraphWidget.graph_x_font = anyos_assign("graph_font");
      GraphWidget.graph_y_font_left = anyos_assign("graph_font");
      GraphWidget.ClearBit(#graph_flags, Cw_GRAPH_Y_AXIS_RIGHT);
      GraphWidget.ClearBit(#graph_flags, Cw_GRAPH_Y_AXIS_LABEL_RIGHT);
      GraphWidget.ClearBit(#graph_flags, Cw_GRAPH_Y_AXIS_TITLE_RIGHT);
      GraphWidget.ClearBit(#graph_flags, Cw_GRAPH_Y_MAJOR_TICK_RIGHT);
      GraphWidget.ClearBit(#graph_flags, Cw_GRAPH_Y_MINOR_TICK_RIGHT);
      
      PtAttachCallback (hw.GraphPane, Pt_CB_RESIZE,
                        `GraphWidget.SetArea(0, 0, hw.GraphPane.dim.w,
                                             hw.GraphPane.dim.h));
    }

  /* Change the tick size and scale of the existing graph for a new data plot. */
  grf = GraphWidget;
  
  grf.graph_x_major_tick = (xmax - xmin) / 5;
  grf.graph_x_minor_tick = (xmax - xmin) / 25;
  grf.graph_y_major_tick_left = (ymax - ymin) / 5;
  grf.graph_y_minor_tick_left = (ymax - ymin) / 25;
  grf.graph_xmin = xmin;
  grf.graph_xmax = xmax;
  grf.graph_ymin_left = ymin;
  grf.graph_ymax_left = ymax;
  grf.graph_traces = 3;
  
  if (xmax > 1000)
    grf.graph_x_label = "Time (number of seconds after midnight)";
  else
    grf.graph_x_label = "X history";

  /* Assign trace limits and colors for the new data plot. */
  CwGraphSetYLimits(grf, 0, ymin, ymax);
  CwGraphSetYLimits(grf, 1, ymin, ymax);
  CwGraphSetYLimits(grf, 2, ymin, ymax);
  
  CwGraphSetTraceColor(grf, 0, 0xdd00aa);
  CwGraphSetTraceColor(grf, 1, 0x0000aa);
  CwGraphSetTraceColor(grf, 2, 0x00aa00);

  /* Create the traces for the new data plot. */
  if(dband == "NONE")
    CwGraphAddXYPoints(grf, 0, make_CwGraph_points(xlist, xdist, t),
                       make_CwGraph_points(ylist, ydist, nil));
  else if(dband == "DB")
    CwGraphAddXYPoints(grf, 1, make_CwGraph_points(xlist, xdist, t),
                       make_CwGraph_points(ylist, ydist, nil));
  else if(dband == "DBP")
    CwGraphAddXYPoints(grf, 2, make_CwGraph_points(xlist, xdist, t),
                       make_CwGraph_points(ylist, ydist, nil));
  
  PtRealizeWidget(grf);
}

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

/*--------------------------------------------------------------------
 * Class:       HistoryWindow
 * Description: Creates the History window using PhabAttachWidgets.
 *------------------------------------------------------------------*/
class HistoryWindow
{
  window;
  int_set;
}

PhabAttachWidgets (HistoryWindow, anyos_assign("history_widgetfile"));

/*--------------------------------------------------------------------
 * Method:      HistoryWindow.constructor
 * Returns:     the root widget of a widget hierarchy
 * Description: Instantiates a HistoryWindow.
 *------------------------------------------------------------------*/
method HistoryWindow.constructor ()
{
  .window = PhabRoot(.PhabInstantiate (t));
}

/*--------------------------------------------------------------------
 * Method:      PtText.set_sensitive
 * Returns:     an integer
 * Description: Changes the sensitivity of a PtText widget.  Called by
 *              HistoryWindow.allow_entry_values.
 *------------------------------------------------------------------*/
method PtText.set_sensitive (val)
{
  if (val != 0)
    {
      self.text_flags = Pt_EDITABLE;
      self.fill_color = 0xffffe5;
    }
  else
    {
      self.text_flags = cons(Pt_EDITABLE, nil);
      self.fill_color = 0xd0d0d0;
    }
}

/*--------------------------------------------------------------------
 * Method:      PtComboBox.set_sensitive
 * Returns:     an integer
 * Description: Changes the fill color of a PtComboBox widget.  Called 
 *              by HistoryWindow.allow_entry_values.
 *------------------------------------------------------------------*/
method PtComboBox.set_sensitive (val)
{
  if (val != 0)
    {
      self.list_flags = cons (Pt_LIST_INACTIVE, nil);
      self.fill_color = 0xffffe5;
    }
  else
    {
      self.list_flags = Pt_LIST_INACTIVE;
      self.fill_color = 0xd0d0d0;
    }
}

/*--------------------------------------------------------------------
 * Method:      HistoryWindow.allow_entry_values
 * Returns:     t or nil
 * Description: Sets the three specialized parameter entry widgets sensitive
 *              or non-sensitive.  Called by HistoryWindow.change_int().
 *------------------------------------------------------------------*/
method HistoryWindow.allow_entry_values(e1, e2, e3)
{
  .XHistEnt.set_sensitive(e1);
  .IntEnt.set_sensitive(e2);
  .GapEnt.set_sensitive(e3);
}

/*--------------------------------------------------------------------
 * Method:      HistoryWindow.change_int
 * Returns:     t
 * Description: Responding to a click on a "Choose an Interpolator" radio
 *              button, this method calls the allow_entry_values() function
 *              on the active button.  
 *------------------------------------------------------------------*/
method HistoryWindow.change_int(button, str)
{
  
  if (button.switched_on())
    {
      .int_set.fn = str;
      switch (str)
        {
        case "NoInterpolator":
          .allow_entry_values(0, 0, 0);
        case "Periodic":
          .allow_entry_values(0, 1, 1);
        case "Relative":
          .allow_entry_values(1, 0, 0);
        case "FixedRelative":
          .allow_entry_values(1, 1, 0);
        }
    }
  /* To allow for a slow processor */
  usleep(10000);
}

/*--------------------------------------------------------------------
 * Function:    create_history
 * Returns:     doesn't return
 * Description: Creates the History window.
 *------------------------------------------------------------------*/
function create_history ()
{
  local buttons, version_string;
  local        hw = new (HistoryWindow);
  
  hw.GraphPane.anchor_flags = Pt_LEFT_ANCHORED_LEFT |
    Pt_RIGHT_ANCHORED_RIGHT | Pt_BOTTOM_ANCHORED_BOTTOM |
      Pt_TOP_ANCHORED_TOP;  
  hw.int_set = new(InterpolatorSettings);
  hw.change_int(hw.NoIntBut, "NoInterpolator");
  hw.SecsEnt.numeric_value = RECORD_TIME;
  hw.YHistEnt.text_string = hw.int_set.y_history;
  hw.StartEnt.text_string = hw.int_set.start;
  hw.DurEnt.text_string = hw.int_set.duration;
  hw.XHistEnt.text_string = hw.int_set.x_history;
  hw.IntEnt.text_string = hw.int_set.interval;
  hw.GapEnt.text_string = hw.int_set.max_gap;
  hw.YHistEnt.items = array ("MV_001", "PV_001");
  hw.XHistEnt.items = array ("PV_001", "MV_001");

  /*Attach message callbacks to buttons and entry widgets.*/
  attach_msg(hw.PtHist, "6", "1");
  attach_msg(hw.RecBut, "6.2", "6");
  attach_msg(hw.DisplayBut, "6.3", "6");
  attach_msg(hw.NoIntBut, "6.31", "6");
  attach_msg(hw.PeriodBut, "6.32", "6");
  attach_msg(hw.RelBut, "6.33", "6");
  attach_msg(hw.FixRelBut, "6.34", "6");
  attach_msg(hw.YHistEnt, "6.35", "6");
  attach_msg(hw.StartEnt, "6.36", "6");
  attach_msg(hw.DurEnt, "6.37", "6");
  attach_msg(hw.XHistEnt, "6.35", "6");
  attach_msg(hw.IntEnt, "6.38", "6");
  attach_msg(hw.GapEnt, "6.39", "6");
  attach_msg(hw.SetupBut, "6.5", "6");
  attach_msg(hw.DBNoneBut, "6.51", "6");
  attach_msg(hw.DBBut, "6.52", "6");
  attach_msg(hw.DBPBut, "6.53", "6");

  /* Display version info and start-up message.*/
  display_hs_info(hw.Text);

  /* Callback for the PtComboBox entry, number of seconds to record.*/
  PtAttachCallback(hw.SecsEnt, Pt_CB_NUMERIC_CHANGED,
                   `(RECORD_TIME = (@hw).SecsEnt.numeric_value));
  
  /* Callbacks for non-deadband buttons to send a query.*/
  PtAttachCallback(hw.NoIntBut, Pt_CB_ACTIVATE,
                   `query_refresh(@hw.NoIntBut, "NoInterpolator"));
  PtAttachCallback(hw.PeriodBut, Pt_CB_ACTIVATE,
                   `query_refresh(@hw.PeriodBut, "Periodic"));
  PtAttachCallback(hw.RelBut, Pt_CB_ACTIVATE,
                   `query_refresh(@hw.RelBut, "Relative"));
  PtAttachCallback(hw.FixRelBut, Pt_CB_ACTIVATE,
                   `query_refresh(@hw.FixRelBut, "FixedRelative"));
  PtAttachCallback(hw.DisplayBut, Pt_CB_ACTIVATE,
                   `query_refresh(@hw.DisplayBut, nil));

  /* A callback to record data, which includes sending a query. */
  PtAttachCallback(hw.RecBut, Pt_CB_ACTIVATE,
                   `(record_data((@hw).RecBut, (@hw).DisplayBut,
                                 (@hw).int_set, (@hw).Text,
                                 (@hw).PtHist, (@hw).YHistEnt,
                                 (@hw).StartEnt, (@hw).DurEnt,
                                 (@hw).XHistEnt, (@hw).IntEnt,
                                 (@hw).GapEnt)));
  
  /* Callbacks to set the deadband flag.*/
  PtAttachCallback(hw.DBNoneBut, Pt_CB_ACTIVATE, `((@hw).int_set.dbflag = "NONE"));
  PtAttachCallback(hw.DBBut, Pt_CB_ACTIVATE, `((@hw).int_set.dbflag = "DB"));
  PtAttachCallback(hw.DBPBut, Pt_CB_ACTIVATE, `((@hw).int_set.dbflag = "DBP"));
  
  /* A callback for deadband buttons to prepare for and send a query.*/
  buttons = list(hw.DBNoneBut, hw.DBBut, hw.DBPBut);
  with b in buttons do
    {
      PtAttachCallback(b, Pt_CB_ACTIVATE, `db_prep_query(@b));
    }
  
  /* Deadband setup.*/
  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);
  
  PtAttachCallback(hw.SetupBut, Pt_CB_ACTIVATE,
                   `create_dbtype_win(@db_list, (@hw).SetupBut));
  
  PtAttachCallback(hw.ExitBut, Pt_CB_ACTIVATE, #exit_program(-1));
  
  hw.PtHist.SetPos(320, 250);
  PtRealizeWidget (hw.window);
  send_message("nsnames");
  hw;
  
  /* Loop forever handling events. */
  while(t) next_event(); 
  
}

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

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