3.6. gtkmonitor.g - The Monitor program in GTK

/*--------------------------------------------------------------------
 * File:        gtkmonitor.g
 *
 * Description: Monitors and controls the PID Emulator.
 *
 * Functions:
 *                reset_value
 *                write_data
 *                set_label
 *                table_labeler
 *                toggle_sym
 *                make_scale
 *                make_prog_bar
 *                make_spinner
 *                create_monitor
 *                main
 *------------------------------------------------------------------*/

/* Access common code. */
require("lib/common.g");
  
/*--------------------------------------------------------------------
 * 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, msgno,
                      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));
  adj.signal("value_changed", `send_message(@msgno));
  spinner.show();
  add_exception_function(adjvar, `reset_value(@spinner, @adjvar, nil));
  table.attach_defaults(spinner, left, right, top, bottom);
  spinner;
}

/*--------------------------------------------------------------------
 * 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"));
  button_messages(ambutton, "3.1", "3");
  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, "3.2",
                    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, "3.3",
                    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, "3.4",
                    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, "3.5",
                    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, "3.6",
                    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, "3.7",
                    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",
              `anyos_change_settings(@but1, t, @s1, 4, @s2, .05, @s3, .45,
                                     @s4, .05, @s5, .70, @s6, 1.0, "3.8", nil));
  button_messages(but1, "3.8", "3");

  but2 = gtk_radio_button_new_with_label(list(but1), "Poor");
  box3.pack_start (but2, TRUE, TRUE, 0);
  but2.signal("clicked",
              `anyos_change_settings(@but2, t, @s1, 10, @s2, .05, @s3, .70,
                                     @s4, .05, @s5, .70, @s6, 5.00, "3.9", nil));
  button_messages(but2, "3.9", "3");

  but3 = gtk_radio_button_new_with_label(list(but1,but2), "Oscillating");
  box3.pack_start (but3, TRUE, TRUE, 0);
  but3.signal("clicked",
              `anyos_change_settings(@but3, nil, @s1, 10, @s2, .05,
                                     @s3, 1.37, @s4, .05, @s5, .70,
                                     @s6, .55, "3.10", @ambutton));
  button_messages(but3, "3.10", "3");

  but4 = gtk_radio_button_new_with_label(list(but1,but2,but3),
                                         "Out-of-control");
  box3.pack_start (but4, TRUE, TRUE, 0);
  but4.signal("clicked",
              `anyos_change_settings(@but4, nil, @s1, 10, @s2, 1.0,
                                     @s3, 3.0, @s4, 1.0, @s5, .70,
                                     @s6, .55, "3.11", @ambutton));
  button_messages(but4, "3.11", "3");

  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(300, 5);
  win_monitor;
}


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