7.7. Complete program - gtksimple.g

/*--------------------------------------------------------------------
 * File:        gtksimple.g
 *
 * Description: Cogent Tools Demo Tutorial One for GTK.
 *
 * Functions:
 *              start_qnserves
 *              program_startup
 *              reset_value
 *              write_data
 *              set_label
 *              table_labeler
 *              toggle_sym
 *              make_scale
 *              make_prog_bar
 *              make_spinner
 *              change_settings
 *              create_monitor
 *              main
 *------------------------------------------------------------------*/

/********************************************************
 *                     STARTING UP                      *
 ********************************************************/

/*--------------------------------------------------------------------
 * Function:    start_qnserves
 * Returns:     t on success, or nil
 * Description: Used to start qserve or nserve, as specified.  Called by
 *              the program_startup() function.
 *------------------------------------------------------------------*/
function start_qnserves(command, name)
{
  for(i=0; i<=50; i++)
    {
      if ((_os_ == "Linux") || (_os_ == "QNX4"))
        if ((qnx_name_locate(0, name, 0) == nil))
          {
            system(command);
            usleep(10000);
          }
        else
          {
            i = 50;
            nil;
          }
      else
        if (access(string("/dev/", command), 0) == -1)
          {        
            system(command);
            usleep(10000);
          }
        else
          {
            i = 50;
            nil;
          }
    }
}     

/*--------------------------------------------------------------------
 * Function:    program_startup
 * Returns:     t or nil
 * Description: Starts any necessary Cogent software and the main window.
 *              Called by the main() function.
 *------------------------------------------------------------------*/
function program_startup (proc_name, queue_name, win_fn)
{
  local window, tsk;
  
  /* See if qserve and nserve are running. If not, start them. */
  start_qnserves("qserve", "qserve");
  start_qnserves("nserve", "sc/nserve");
  
  /* Start interprocess communication. */
  if (init_ipc(proc_name, queue_name, "toolsdemo") == nil)
    error("Could not initialize IPC.");
  
  /* See if a DataHub is running in the toolsdemo domain.
     If not, start it and a DataHub Viewer. */
  if ((tsk = locate_task ("/dh/toolsdemo", t)) == nil)
      system("datahub -d toolsdemo");
  else
    close_task (tsk);
  
  if ((pid_dhview = fork()) == 0)        
    {
      /* The child process is the DataHub Viewer. */
      exec ("dhview", "-d", "toolsdemo");      
      /* In case the above exec commands fails, the following will be
       * called, and it's virtually guaranteed not to fail.  Thus in
       * any circumstance a child process gets created.*/
      exec("/bin/true");
    }
  princ("The process ID of the DataHub Viewer is: ", pid_dhview, "\n");

  /* Make sure the PID Emulator is not already running. */
  if ((tsk = locate_task ("emul", t)) != nil)
    {
      send(tsk, #exit_program(0));
      close_task (tsk);
    }
  
  /* Start the PID Emulator. */
  if ((pid_emul = fork()) == 0)        
    {
      /* The child process is the PID Emulator. */
      exec ("gamma", "emul");      
      exec("/bin/true");
    }
  princ("The process ID of the PID Emulator is: ", pid_emul, "\n");
  
  /* Kill child processes when this program exits. */
  atexit(`kill(pid_dhview, SIGINT));
  atexit(`kill(pid_emul, SIGINT));
  
  /* Create and register some points with the datahub. */
  SP_001 = register_point (#SP_001);
  PV_001 = register_point (#PV_001);
  MV_001 = register_point (#MV_001);
  AUTO_001 = register_point (#AUTO_001);
  FREQ_001 = register_point (#FREQ_001);
  PID1_Kp = register_point (#PID1_Kp);
  PID1_Ki = register_point (#PID1_Ki);
  PID1_Kd = register_point (#PID1_Kd);
  PROP_001 = register_point (#PROP_001);
  INT_001 = register_point (#INT_001);
  
  /* Create the main window, and put in a destructor function for GTK */
  window = eval(win_fn);
  window.signal ("destroy", #exit_program(0));
}


/********************************************************
 *                     THE MONITOR                      *
 ********************************************************/

TRUE = 1;
FALSE = 0;

/*--------------------------------------------------------------------
 * Function:    reset_value
 * Returns:     t or nil
 * Description: Resets the value of a widget's adjustment.
 *------------------------------------------------------------------*/
function reset_value(widget, newval, adj_id)
{
  local newadj;
  newadj = widget.get_adjustment();
  if (adj_id)
    {
      gtk_signal_handler_block (newadj, adj_id);
      newadj.set_value(100 - newval);
      gtk_signal_handler_unblock (newadj, adj_id);
    }
  else
    newadj.set_value(newval);
}

/*--------------------------------------------------------------------
 * Function:    write_data
 * Returns:     t or nil
 * Description: Reads a widget that contains an adjustment value,
 *              and writes its value to the datahub.
 *------------------------------------------------------------------*/
function write_data(widget, !ptname)
{
  local ptval;
  ptval = widget.get_adjustment();
  write_point(string(ptname), ptval.value);
}

/*--------------------------------------------------------------------
 * Function:    set_label
 * Returns:     t or nil
 * Description: Sets the label of a button according to
 *              the value of a database point.
 *------------------------------------------------------------------*/
function set_label(button, sym, onvalue, offvalue)
{
  if (eval(sym) != 0)
    button.label = onvalue;
  else
    button.label = offvalue;
}

/*--------------------------------------------------------------------
 * Function:    table_labeler
 * Returns:     t or nil
 * Description: Creates a label and puts it into a table.
 *------------------------------------------------------------------*/
function table_labeler(table, txt, left, right, top, bottom)
{
  local label;
  
  label = new(GtkLabel);
  label.set_text(txt);
  table.attach_defaults(label, left, right, top, bottom);
}

/*--------------------------------------------------------------------
 * Function:    toggle_sym
 * Returns:     t or nil
 * Description: Toggles a database point between 0 and 1 when a button
 *              is pressed, and calls set_label() to change the label
 *              accordingly.
 *------------------------------------------------------------------*/
function toggle_sym(button, sym, onvalue, offvalue)
{
  set (sym, eval(sym) == 0 ? 1 : 0);
  write_point (sym, eval(sym));
  set_label(button, eval(sym), onvalue, offvalue);
}

/*--------------------------------------------------------------------
 * Function:    make_scale
 * Returns:     A GtkVScale
 * Description: Makes a scale based on adjustment parameters.  Then makes
 *              a ruler and a label and then packs them together into a
 *              GtkVBox, and packs that box into a container.
 *------------------------------------------------------------------*/
function make_scale(title, color, container, adjvar,
                    low, high, step, page)
{
  local adj, adj2, newvalue, scale, label, scalebox;
  
  /* Two adjustments are necessary right now since the
   * GtkRange.set_inverted() doesn't exist yet, even in C code.
   * We use the first adjusment to handle the actual value, and
   * the second adjustment for the range and ruler, whose values
   * are inverted from the first adjustment.
   */
  adj1 = gtk_adjustment_new (0, 0, 100, step, page, 0.0);
  adj2 = gtk_adjustment_new (0, 0, 100, step, page, 0.0);
  newvalue = eval(adjvar);
  
  if (number_p(newvalue))
    {
      adj1.value = newvalue;
      adj2.value = 100 - newvalue;
    }
  
  scale = new(GtkVScale);
  scale.set_adjustment(adj2);
  scale.set_update_policy(GTK_UPDATE_CONTINUOUS);
  scale.height = 200;
  scale.set_draw_value(FALSE);
  scale.set_color(1, (GTK_STATE_NORMAL &&
                      GTK_STATE_ACTIVE), color);
  
  rule = new(GtkVRuler);
  rule.set_range (100, 0, -1, 200);
  rule.position = - 5;
  rule.set_sensitive(FALSE);
  
  label = new (GtkLabel);
  label.set_text(title);
  label_val = new(GtkLabel);
  label_val.set_text(substr(string((eval(adjvar) * 100) / 100), 0, 6));
  
  /* Implement the adjustments on the value label, ruler, and scale. */
  adj2.signal("value_changed", `(@adj1).set_value(100 - (@adj2).value));
  adj2.signal("value_changed",
              `(@label_val).set_text(substr(string(((@adj1).value * 100) / 100),
                                            0, 6)));
  adj2.signal("value_changed", `(@rule).position = (100 - (@adj2).value));
  adj2_id = adj2.signal("value_changed", `write_point(#SP_001, (@adj1).value));
  
  add_exception_function(#SP_001, `reset_value(@scale, SP_001, @adj2_id));
  
  /* Create a table to facilitate the width and spacing of the widgets. */
  table = gtk_table_new(1, 5, FALSE);
  table.border_width = 5;
  table.set_homogeneous(TRUE);
  table.attach_defaults(scale, 1, 2, 0, 1);  
  table.attach_defaults(rule, 2, 4, 0, 1);
  
  scalebox = new(GtkVBox);
  scalebox.pack_start(label, TRUE, TRUE, 0);
  scalebox.pack_start(table, TRUE, TRUE, 0);
  scalebox.pack_start(label_val, TRUE, TRUE, 0);
  
  container.pack_start(scalebox, TRUE, TRUE, 0);
  scale.show();
  scale;
}

/*--------------------------------------------------------------------
 * Function:    make_prog_bar
 * Returns:     t or nil
 * Description: Makes a progress bar based on adjustment parameters.
 *              It puts the progress bar into a table to facilitate
 *              proper layout.  It also makes a label and packs the
 *              bar and label together into a container.
 *------------------------------------------------------------------*/
function make_prog_bar (title, container, adjvar, color)
{
  local label, table, adj, pbar;
  
  label = new(GtkLabel);
  label.set_text(title);
  container.pack_start(label, TRUE, TRUE, 0);
  
  table = gtk_table_new(1, 5, FALSE);
  table.border_width = 5;
  table.set_homogeneous(TRUE);
  container.pack_start(table, TRUE, TRUE, 0);
  
  adj = gtk_adjustment_new (0, 0, 100, 0, 0, 0.0);
  adj.set_value(eval(adjvar));
  pbar = new(GtkProgressBar);
  pbar.adjustment = adj;
  pbar.set_format_string("%v");
  pbar.set_orientation(GTK_PROGRESS_BOTTOM_TO_TOP);
  pbar.height = 200;
  pbar.set_color (1, 2, color);
  pbar.set_show_text(1);
  add_exception_function(adjvar, `(@pbar).adjustment.set_value(@adjvar));
  table.attach_defaults(pbar, 1, 4, 0, 1);  
}

/*--------------------------------------------------------------------
 * Function:    make_spinner
 * Returns:     A GtkSpinButton
 * Description: Makes a spin button based on adjustment parameters,
 *              and packs it into a table.
 *------------------------------------------------------------------*/
function make_spinner(adjvar, digits, low, high, step, page,
                      table, left, right, top, bottom)
     
{
  local adj, newvalue, spinner;
  
  adj = gtk_adjustment_new (0, low, high, step, page, 0.0);
  
  newvalue = eval(adjvar);
  if (number_p(newvalue))
    adj.value = newvalue;
  
  spinner = new(GtkSpinButton);
  spinner.set_adjustment(adj);
  spinner.set_update_policy(GTK_UPDATE_CONTINUOUS);
  spinner.set_digits(digits);
  spinner.width = 40;
  spinner.set_value(.05);
  adj.signal("value_changed", `write_data(@spinner, @adjvar));
  spinner.show();
  add_exception_function(adjvar, `reset_value(@spinner, @adjvar, nil));
  table.attach_defaults(spinner, left, right, top, bottom);
  spinner;
}

/*--------------------------------------------------------------------
 * Function:    change_settings
 * Returns:     t or nil
 * Description: Changes PID settings in the Monitor, and optionally
 *              unclicks the Auto button if it is on.
 * Note:        In the Demo, this function is a Library function, found
 *              in lib/linux.g.
 *------------------------------------------------------------------*/
function change_settings(button, auto, sp1, v1, sp2, v2, sp3, v3, sp4,
                         v4, sp5, v5, sp6, v6, msgno, autobutton?)
{
  if (button.get_active() == TRUE)
    {
      if (auto == nil)
        {
          if (AUTO_001 != 0)
            autobutton.clicked();
        }
      else
        sp1.set_value(v1);
      sp2.set_value(v2);
      sp3.set_value(v3);
      sp4.set_value(v4);
      sp5.set_value(v5);
      sp6.set_value(v6);
    }
}

/*--------------------------------------------------------------------
 * Function:    create_monitor
 * Returns:     A GtkWindow
 * Description: Creates the Monitor window.
 *------------------------------------------------------------------*/
function create_monitor ()
{
  local win_monitor, main_title, scale, button, label, table;
  local ambutton, frame, box1, box2, box3, onstring, offstring;
  local s1, s2, s3, s4, s5, s6, but1, but2, but3, but4;
  
  win_monitor = new (GtkWindow);
  win_monitor.signal ("destroy", #win_monitor = nil);
  win_monitor.set_color(1, GTK_STATE_NORMAL, 0xdddddd);
  win_monitor.title = "Cogent Tools Demo: Monitor";
  win_monitor.border_width = 5;
  
  main_title = new (GtkLabel);
  main_title.set_text("Cogent Tools Demo - Monitor");
  main_title.height = 25;
  
  frame = new(GtkFrame);
  frame.set_color(1, GTK_STATE_NORMAL, 0xff0000);
  frame.border_width = 5;
  frame.add(main_title);
  
  box1 = new (GtkHBox);
  win_monitor.add (box1);  
  
  box2 = new (GtkVBox);
  box2.spacing = 5;
  box2.border_width = 5;
  box2.pack_start(frame, TRUE, TRUE, 5);
  box1.pack_start (box2, TRUE, TRUE, 0);
  box2.show();
  
  box3 = new(GtkHBox);
  box2.pack_start(box3, TRUE, TRUE, 0);
  
  /********************************************************
   *                   Set point Panel                    *
   ********************************************************/
  box4 = new(GtkVBox);
  box4.set_border_width(5);
  
  evaluation = eval(SP_001);
  scale = make_scale("SP:\nSet point", 0x9da3eb,
                     box4, #SP_001, 0.0, 100.0, 0.1, 5.0);
  
  table = gtk_table_new(2, 2, FALSE);
  table.set_row_spacings(5);
  
  table_labeler(table, "Auto mode ", 0, 1, 0, 1);  
  ambutton = new(GtkButton);
  set_label(ambutton, AUTO_001, "ON", "OFF");
  
  ambutton.signal("clicked",
                  `toggle_sym(@ambutton, #AUTO_001, "ON", "OFF"));
  add_exception_function(#AUTO_001,
                         `set_label(@ambutton, #AUTO_001,"ON", "OFF"));
  table.attach_defaults(ambutton, 1, 2, 0, 1);
  
  table_labeler(table, "Delay", 0, 1, 1, 2);
  
  s1 = make_spinner(#FREQ_001, 0, 1.0, 20.0, 1.0, 1.0,
                    table, 1, 2, 1, 2);
  box4.pack_start(table, TRUE, TRUE, 0);
  
  frame = new(GtkFrame);
  frame.set_color(1, GTK_STATE_NORMAL, 0xaaaaff);
  frame.set_shadow_type(GTK_SHADOW_IN);
  frame.add(box4);
  
  box3.pack_start(frame, TRUE, TRUE, 5);
  
  /********************************************************
   *                Control output Panel                  *
   ********************************************************/
  box4 = new(GtkVBox);
  box4.set_border_width(5);
  
  make_prog_bar("MV:\nControl Output", box4, #MV_001, 0x41be41);
  
  table = gtk_table_new(2, 3, FALSE);
  table.set_row_spacings(5);
  
  table_labeler(table, "Kp: Prop", 0, 1, 0, 1);
  PID1_Kp = register_point (#PID1_Kp);
  s2 = make_spinner(#PID1_Kp, 2, 0.0, 1.0, 0.01, 0.1,
                    table, 1, 2, 0, 1);
  
  table_labeler(table, "  Ki: Int", 0, 1, 1, 2);
  PID1_Ki = register_point (#PID1_Ki);
  s3 = make_spinner(#PID1_Ki, 2, 0.0, 5.0, 0.01, 0.1,
                    table, 1, 2, 1, 2);
  
  table_labeler(table, "Kd: Deriv", 0, 1, 2, 3);
  PID1_Kd = register_point (#PID1_Kd);
  s4 = make_spinner(#PID1_Kd, 2, 0.0, 1.0, 0.01, 0.1,
                    table, 1, 2, 2, 3);
  
  box4.pack_start(table, TRUE, TRUE, 0);
  
  frame = new(GtkFrame);
  frame.set_color(1, GTK_STATE_NORMAL, 0xaaffaa);
  frame.set_shadow_type(GTK_SHADOW_IN);
  frame.add(box4);
  
  box3.pack_start (frame, TRUE, TRUE, 5);
  
  /********************************************************
   *                     Plant Panel                      *
   ********************************************************/
  box4 = new(GtkVBox);
  box4.set_border_width(5);
  
  make_prog_bar("PV:\nProcess Variable", box4, #PV_001, 0xeb0000);
  
  table = gtk_table_new(2, 3, FALSE);
  table.set_row_spacings(5);
  
  table_labeler(table, "PROP", 0, 1, 0, 1);
  PROP_001 = register_point (#PROP_001);
  s5 = make_spinner(#PROP_001, 2, 0.1, 5.0, 0.01, 0.1,
                    table, 1, 2, 0, 1);
  
  table_labeler(table, "  INT", 0, 1, 1, 2);
  INT_001 = register_point (#INT_001);
  s6 = make_spinner(#INT_001, 2, 0.0, 10.0, 0.01, 0.1,
                    table, 1, 2, 1, 2);
  
  table_labeler(table, " ", 0, 1, 2, 3);
  table.set_row_spacing(1, 10);
  
  box4.pack_start(table, TRUE, TRUE, 0);
  
  frame = new(GtkFrame);
  frame.set_color(1, GTK_STATE_NORMAL, 0xff9999);
  frame.set_shadow_type(GTK_SHADOW_IN);
  frame.add(box4);
  
  box3.pack_start (frame, TRUE, TRUE, 5);
  
  /********************************************************
   *        Radio buttons for setting PID Loop            *
   ********************************************************/
  box3 = new (GtkHBox);
  box3.spacing = 0;
  box3.border_width = 5;
  
  label = new(GtkLabel);
  label.set_text("PID Loop: ");
  box3.pack_start (label, TRUE, TRUE, 0);
  
  but1 = gtk_radio_button_new_with_label(nil, "Good");
  box3.pack_start (but1, TRUE, TRUE, 0);
  but1.signal("clicked",
              `change_settings(@but1, t, @s1, 4, @s2, .05, @s3, .45,
                               @s4, .05, @s5, .70, @s6, 1.0, "3.8", nil));
  
  but2 = gtk_radio_button_new_with_label(list(but1), "Poor");
  box3.pack_start (but2, TRUE, TRUE, 0);
  but2.signal("clicked",
              `change_settings(@but2, t, @s1, 10, @s2, .05, @s3, .70,
                               @s4, .05, @s5, .70, @s6, 5.00, "3.9", nil));
  
  but3 = gtk_radio_button_new_with_label(list(but1,but2), "Oscillating");
  box3.pack_start (but3, TRUE, TRUE, 0);
  but3.signal("clicked",
              `change_settings(@but3, nil, @s1, 10, @s2, .05,
                               @s3, 1.37, @s4, .05, @s5, .70,
                               @s6, .55, "3.10", @ambutton));
  
  but4 = gtk_radio_button_new_with_label(list(but1,but2,but3),
                                         "Out-of-control");
  box3.pack_start (but4, TRUE, TRUE, 0);
  but4.signal("clicked",
              `change_settings(@but4, nil, @s1, 10, @s2, 1.0,
                               @s3, 3.0, @s4, 1.0, @s5, .70,
                               @s6, .55, "3.11", @ambutton));
  
  button = new(GtkButton);
  button.label = "Exit";
  button.signal("clicked", `(@win_monitor).destroy());
  button.width = 40;
  box3.pack_start(button, TRUE, TRUE, 0);
  
  box2.pack_start (box3, TRUE, TRUE, 0);
  
  but2.set_active(TRUE);
  win_monitor.show_all();
  win_monitor.reposition(20, 250);
  win_monitor;
}


/*--------------------------------------------------------------------
 * Function:    main
 * Returns:     doesn't return
 * Description: Calls the program_startup() function and loops.
 *------------------------------------------------------------------*/
function main()
{
  program_startup("monitor", "monq", #create_monitor());
  
  /* Loop forever handling events. */
  gtk_main();
}