9.10. Spin Buttons

The Spin Button widget is generally used to allow the user to select a value from a range of numeric values. It consists of a text entry box with up and down arrow buttons attached to the side. Selecting one of the buttons causes the value to "spin" up and down the range of possible values. The entry box may also be edited directly to enter a specific value.

The Spin Button allows the value to have zero or a number of decimal places and to be incremented/decremented in configurable steps. The action of holding down one of the buttons optionally results in an acceleration of change in the value according to how long it is depressed.

The Spin Button uses an Adjustment object to hold information about the range of values that the spin button can take. This makes for a powerful Spin Button widget.

Recall that an adjustment widget is created with the following function, which illustrates the information that it holds:
 
adjustment = GtkAdjustment(value, lower, upper, step_increment, page_increment,
                           page_size)

These attributes of an Adjustment are used by the Spin Button in the following way:

Additionally, mouse button 3 can be used to jump directly to the upper or lower values when used to select one of the buttons. Lets look at how to create a Spin Button:
 
spin_button = GtkSpinButton(adjustment, climb_rate, digits)

The climb_rate argument take a value between 0.0 and 1.0 and indicates the amount of acceleration that the Spin Button has. The digits argument specifies the number of decimal places to which the value will be displayed.

A Spin Button can be reconfigured after creation using the following method:
 
spin_button.configure(adjustment, climb_rate, digits)

The spin_button argument specifies the Spin Button widget that is to be reconfigured. The other arguments are as specified above.

The adjustment can be set and retrieved independently using the following two methods:
 
spin_button.set_adjustment(adjustment)

adjustment = spin_button.get_adjustment()

The number of decimal places can also be altered using:
 
spin_button.set_digits(digits)

The value that a Spin Button is currently displaying can be changed using the following method:
 
spin_button.set_value(value)

The current value of a Spin Button can be retrieved as either a floating point or integer value with the following methods:
 
value = spin_button.get_value_as_float()

value = spin_button.get_value_as_int()

If you want to alter the value of a Spin Value relative to its current value, then the following method can be used:
 
spin_button.spin(direction, increment)

The direction parameter can take one of the following values:
 
  SPIN_STEP_FORWARD
  SPIN_STEP_BACKWARD
  SPIN_PAGE_FORWARD
  SPIN_PAGE_BACKWARD
  SPIN_HOME
  SPIN_END
  SPIN_USER_DEFINED

This method packs in quite a bit of functionality, which I will attempt to clearly explain. Many of these settings use values from the Adjustment object that is associated with a Spin Button.

SPIN_STEP_FORWARD and SPIN_STEP_BACKWARD change the value of the Spin Button by the amount specified by increment, unless increment is equal to 0, in which case the value is changed by the value of step_increment in theAdjustment.

SPIN_PAGE_FORWARD and SPIN_PAGE_BACKWARD simply alter the value of the Spin Button by increment.

SPIN_HOME sets the value of the Spin Button to the bottom of the Adjustments range.

SPIN_END sets the value of the Spin Button to the top of the Adjustments range.

SPIN_USER_DEFINED simply alters the value of the Spin Button by the specified amount.

We move away from methods for setting and retreving the range attributes of the Spin Button now, and move onto methods that effect the appearance and behaviour of the Spin Button widget itself.

The first of these methods is used to constrain the text box of the Spin Button such that it may only contain a numeric value. This prevents a user from typing anything other than numeric values into the text box of a Spin Button:
 
spin_button.set_numeric(numeric)

numeric is TRUE to constrain the text entry to numeric values or FALSE to unconstrain the text entry.

You can set whether a Spin Button will wrap around between the upper and lower range values with the following method:
 
spin_button.set_wrap(wrap)

The Spin Button will wrap when wrap is set to TRUE.

You can set a Spin Button to round the value to the nearest step_increment, which is set within the Adjustment object used with the Spin Button. This is accomplished with the following method when snap_to_ticks is TRUE:
 
spin_button.set_snap_to_ticks(snap_to_ticks)

The update policy of a Spin Button can be changed with the following method:
 
spin_button.set_update_policy(policy)

The possible values of policy are:
 
UPDATE_ALWAYS
UPDATE_IF_VALID
 
These policies affect the behavior of a Spin Button when parsing inserted text and syncing its value with the values of the Adjustment.

In the case of UPDATE_IF_VALID the Spin Button value only gets changed if the text input is a numeric value that is within the range specified by the Adjustment. Otherwise the text is reset to the current value.

In case of UPDATE_ALWAYS we ignore errors while converting text into a numeric value.

The appearance of the buttons used in a Spin Button can be changed using the following method:
 
spin_button.set_shadow_type(shadow_type)

As usual, the shadow_type can be one of:
 
  SHADOW_IN
  SHADOW_OUT
  SHADOW_ETCHED_IN
  SHADOW_ETCHED_OUT

Finally, you can explicitly request that a Spin Button update itself:
 
spin_button.update()

The spinbutton.py example program illustrates the use of spinbuttons including setting a number of characteristics. Figure 9.10 shows the result of running the example program:

Figure 9.10 Spin Button Example

The spinbutton.py source code is:
 
    1   #!/usr/bin/env python
    2   
    3   # example spinbutton.py
    4   
    5   import gtk
    6   
    7   class SpinButtonExample:
    8       def toggle_snap(self, widget, spin):
    9           spin.set_snap_to_ticks(widget.active)
   10   
   11       def toggle_numeric(self, widget, spin):
   12           spin.set_numeric(widget.active)
   13   
   14       def change_digits(self, widget, spin, spin1):
   15           spin1.set_digits(spin.get_value_as_int())
   16   
   17       def get_value(self, widget, data, spin, spin2, label):
   18           if data == 1:
   19               buf = "%d" % spin.get_value_as_int()
   20           else:
   21               buf = "%0.*f" % (spin2.get_value_as_int(),
   22                                spin.get_value_as_float())
   23           label.set_text(buf)
   24   
   25       def __init__(self):
   26           window = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
   27           window.connect("destroy", gtk.mainquit)
   28           window.set_title("Spin Button")
   29   
   30           main_vbox = gtk.GtkVBox(gtk.FALSE, 5)
   31           main_vbox.set_border_width(10)
   32           window.add(main_vbox)
   33   
   34           frame = gtk.GtkFrame("Not accelerated")
   35           main_vbox.pack_start(frame, gtk.TRUE, gtk.TRUE, 0)
   36     
   37           vbox = gtk.GtkVBox(gtk.FALSE, 0)
   38           vbox.set_border_width(5)
   39           frame.add(vbox)
   40   
   41           # Day, month, year spinners
   42           hbox = gtk.GtkHBox(gtk.FALSE, 0)
   43           vbox.pack_start(hbox, gtk.TRUE, gtk.TRUE, 5)
   44     
   45           vbox2 = gtk.GtkVBox(gtk.FALSE, 0)
   46           hbox.pack_start(vbox2, gtk.TRUE, gtk.TRUE, 5)
   47   
   48           label = gtk.GtkLabel("Day :")
   49           label.set_alignment(0, 0.5)
   50           vbox2.pack_start(label, gtk.FALSE, gtk.TRUE, 0)
   51     
   52           adj = gtk.GtkAdjustment(1.0, 1.0, 31.0, 1.0, 5.0, 0.0)
   53           spinner = gtk.GtkSpinButton(adj, 0, 0)
   54           spinner.set_wrap(gtk.TRUE)
   55           spinner.set_shadow_type(gtk.SHADOW_OUT)
   56           vbox2.pack_start(spinner, gtk.FALSE, gtk.TRUE, 0)
   57     
   58           vbox2 = gtk.GtkVBox(gtk.FALSE, 0)
   59           hbox.pack_start(vbox2, gtk.TRUE, gtk.TRUE, 5)
   60     
   61           label = gtk.GtkLabel("Month :")
   62           label.set_alignment(0, 0.5)
   63           vbox2.pack_start(label, gtk.FALSE, gtk.TRUE, 0)
   64   
   65           adj = gtk.GtkAdjustment(1.0, 1.0, 12.0, 1.0, 5.0, 0.0)
   66           spinner = gtk.GtkSpinButton(adj, 0, 0)
   67           spinner.set_wrap(gtk.TRUE)
   68           spinner.set_shadow_type(gtk.SHADOW_ETCHED_IN)
   69           vbox2.pack_start(spinner, gtk.FALSE, gtk.TRUE, 0)
   70     
   71           vbox2 = gtk.GtkVBox(gtk.FALSE, 0)
   72           hbox.pack_start(vbox2, gtk.TRUE, gtk.TRUE, 5)
   73     
   74           label = gtk.GtkLabel("Year :")
   75           label.set_alignment(0, 0.5)
   76           vbox2.pack_start(label, gtk.FALSE, gtk.TRUE, 0)
   77     
   78           adj = gtk.GtkAdjustment(1998.0, 0.0, 2100.0, 1.0, 100.0, 0.0)
   79           spinner = gtk.GtkSpinButton(adj, 0, 0)
   80           spinner.set_wrap(gtk.FALSE)
   81           spinner.set_shadow_type(gtk.SHADOW_IN)
   82           spinner.set_usize(55, 0)
   83           vbox2.pack_start(spinner, gtk.FALSE, gtk.TRUE, 0)
   84     
   85           frame = gtk.GtkFrame("Accelerated")
   86           main_vbox.pack_start(frame, gtk.TRUE, gtk.TRUE, 0)
   87     
   88           vbox = gtk.GtkVBox(gtk.FALSE, 0)
   89           vbox.set_border_width(5)
   90           frame.add(vbox)
   91     
   92           hbox = gtk.GtkHBox(gtk.FALSE, 0)
   93           vbox.pack_start(hbox, gtk.FALSE, gtk.TRUE, 5)
   94     
   95           vbox2 = gtk.GtkVBox(gtk.FALSE, 0)
   96           hbox.pack_start(vbox2, gtk.TRUE, gtk.TRUE, 5)
   97     
   98           label = gtk.GtkLabel("Value :")
   99           label.set_alignment(0, 0.5)
  100           vbox2.pack_start(label, gtk.FALSE, gtk.TRUE, 0)
  101     
  102           adj = gtk.GtkAdjustment(0.0, -10000.0, 10000.0, 0.5, 100.0, 0.0)
  103           spinner1 = gtk.GtkSpinButton(adj, 1.0, 2)
  104           spinner1.set_wrap(gtk.TRUE)
  105           spinner1.set_usize(100, 0)
  106           vbox2.pack_start(spinner1, gtk.FALSE, gtk.TRUE, 0)
  107     
  108           vbox2 = gtk.GtkVBox(gtk.FALSE, 0)
  109           hbox.pack_start(vbox2, gtk.TRUE, gtk.TRUE, 5)
  110     
  111           label = gtk.GtkLabel("Digits :")
  112           label.set_alignment(0, 0.5)
  113           vbox2.pack_start(label, gtk.FALSE, gtk.TRUE, 0)
  114     
  115           adj = gtk.GtkAdjustment(2, 1, 5, 1, 1, 0)
  116           spinner2 = gtk.GtkSpinButton(adj, 0.0, 0)
  117           spinner2.set_wrap(gtk.TRUE)
  118           adj.connect("value_changed", self.change_digits, spinner2, spinner1)
  119           vbox2.pack_start(spinner2, gtk.FALSE, gtk.TRUE, 0)
  120     
  121           hbox = gtk.GtkHBox(gtk.FALSE, 0)
  122           vbox.pack_start(hbox, gtk.FALSE, gtk.TRUE, 5)
  123   
  124           button = gtk.GtkCheckButton("Snap to 0.5-ticks")
  125           button.connect("clicked", self.toggle_snap, spinner1)
  126           vbox.pack_start(button, gtk.TRUE, gtk.TRUE, 0)
  127           button.set_active(gtk.TRUE)
  128     
  129           button = gtk.GtkCheckButton("Numeric only input mode")
  130           button.connect("clicked", self.toggle_numeric, spinner1)
  131           vbox.pack_start(button, gtk.TRUE, gtk.TRUE, 0)
  132           button.set_active(gtk.TRUE)
  133     
  134           val_label = gtk.GtkLabel("")
  135     
  136           hbox = gtk.GtkHBox(gtk.FALSE, 0)
  137           vbox.pack_start(hbox, gtk.FALSE, gtk.TRUE, 5)
  138           button = gtk.GtkButton("Value as Int")
  139           button.connect("clicked", self.get_value, 1, spinner1, spinner2,
  140                          val_label)
  141           hbox.pack_start(button, gtk.TRUE, gtk.TRUE, 5)
  142     
  143           button = gtk.GtkButton("Value as Float")
  144           button.connect("clicked", self.get_value, 2, spinner1, spinner2,
  145                          val_label)
  146           hbox.pack_start(button, gtk.TRUE, gtk.TRUE, 5)
  147     
  148           vbox.pack_start(val_label, gtk.TRUE, gtk.TRUE, 0)
  149           val_label.set_text("0")
  150     
  151           hbox = gtk.GtkHBox(gtk.FALSE, 0)
  152           main_vbox.pack_start(hbox, gtk.FALSE, gtk.TRUE, 0)
  153     
  154           button = gtk.GtkButton("Close")
  155           button.connect("clicked", gtk.mainquit)
  156           hbox.pack_start(button, gtk.TRUE, gtk.TRUE, 5)
  157           window.show_all()
  158   
  159   def main():
  160       gtk.mainloop()
  161       return 0
  162   
  163   if __name__ == "__main__":
  164       SpinButtonExample()
  165       main()