7.2. The Monitor Window - create_monitor

The Monitor window is divided into three framed panels. The first panel contains a scale, while the other two contain progress bars, and each panel contains a few buttons and label. Below the panels is a row of radio buttons.

[Note]

This part of the tutorial assumes a basic working knowledge of Gamma/GTK. For a more detailed tutorial on topics covered here, please refer to the Building Applications tutorial and the Example Code appendix of the Gamma/GTK manual.

Each of the three colored bars in the panels correspond to one of the three PID Emulator values in the datahub. The scale on the left, SP, is used in two ways—to display values and to set values. The two progress bars (MV and PV) are for display only. The Kp, Ki, Kd, INT and PROP spin buttons and labels to their left are arranged using GtkTable widgets; and three levels of GtkBox widgets are used to achieve the correct layout.

The first part of the function builds the main window and title area, and creates the top-level boxes.

   /*--------------------------------------------------------------------
    * 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);
     

We put in an extra GtkHBox here for future use, so that a trend graph can be added on the right.

     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);
     

The next three main parts of the function build the three panels that contain the spin buttons and scale or progress bar. Each panel is different, but they are created in a similar way. This first one will serve as an example.

     /********************************************************
      *                   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);
     

The scale is built using the make_scale function. Its functionality is added by calling the Gamma function add_exception_function, which in turn calls the program's reset_value function whenever a datahub point changes its value.

To get consistent spacing, sizing, and vertical alignment among all the elements at the bottom of the window, we use a table. Each row in the table is similar, containing a label made using our table_labeler function, and a button.

     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);
     

The Automatic mode button (ambutton) has a toggling label: On and Off. We use the set_label function to set the label according to the value of AUTO_001 in the datahub. Then there are two ways that the button label should be able to change. Clicking on the button itself calls the toggle_sym function, which changes the value of AUTO_001 in the datahub, and then changes the label with the set_label function. But the label should also change when the value of AUTO_001 gets changed in the datahub by some other program. For that case, we use Gamma's add_exception_function to activate the set_label function directly.

The next button (s1) is a spin button, and we create it using our make_spinner function. We give it a callback with the add_exception_function, add it to the table, and pack the table in the box. All the other parameter spin buttons are created in the same way.

     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);
     

All this gets put inside a frame, to create a "control panel" appearance. The Controller Panel and Plant Panel are created in essentially the same way.

     /********************************************************
      *                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);
     

The bottom part of the window has four radio buttons and an exit button packed into a GtkHBox. We use a GtkHBox instead of a GtkHButtonBox because it gives more consistent spacing in this case. The code for each radio button is similar, each one calling the change_settings function when clicked. We set the Poor button active, to start out with the most visible PID response.

     /********************************************************
      *        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();
   }