7.3. Making Scales and Spin Buttons - make_scale, make_prog_bar, make_spinner

The scale, progress bars and spin buttons are all created in a similar way. The make_scale function returns a scale, based on these parameters:

titleA title string that appears above the scale.
colorA hex number in the format 0xrrggbb. This determines the color inside the trough of the scale.
containerA GtkContainer widget that holds the scale.
adjvarThe variable whose value is shown and/or set by this scale.
lowThe minimum value for the scale.
highThe maximum value for the scale.
stepThe smallest incremental value change permitted by the scale.
pageThe amount the scale changes when a user clicks in the trough.
   /*--------------------------------------------------------------------
    * 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));
     
[Note]

Notice the use of the quote operators ` and @ in the above function calls. In general, the ` operator protects an expression from evaluation, except for sub-expressions that are preceded by the @ operator.

In this case, the ` operator prevents the assignment from taking place when the code is being read, but the @ operator allows the name of the variable representing an adjustment or widget to be evaluated. Note that the @ operator applies to the whole expression it precedes. For example, the expression @adj1.value would evaluate to the value of the .value resource at the time that this code is read, leaving us with that static value. This is not what we want. The expression (@adj1).value evaluates to a variable .value resource for the adjustment, which can change each time a value_changed signal is received.

For data coming from the Cascade DataHub, we use the Gamma function add_exception_function to update the value of the widget.

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

The scalebox gives us a way to pack the scale and its label into another GtkContainer. This is useful, because any change in spacing applied to the items in the container will be applied to the scale and its label as one unit, while their relative spacing remains constant.

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

The make_prog_bar function is quite similar to the make_scale function.

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

This is a work-around used to force the progress bar into a desirable size. This involves making a 5-column homogeneous table, and then packing the bar into the middle three columns.

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

The make_spinner function returns a spin button, based on mostly the same parameters as make_scale. A new parameter is digits, which determines how many digits appear after the decimal point in the numeric display. We need this because we don't want to show fractions of a second for the FREQ spin button.

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