Basic Widgets
The basic widgets are the leaf elements of every UI: the label that displays text, the entry that takes one line of input, the button that triggers an action, the checkbox and radio that take a choice, the scale and spin that take a number, and the progress bar that shows state. Each has a small, fixed vocabulary of properties (the text, the sensitivity, the focus) and a small set of signals (clicked, activate, toggled), and together they cover the majority of what a typical dialog or form is made of. This chapter introduces each with the properties and signals it actually uses, and ends with a form that composes them - the running example's settings dialog.
Labels, Entries, and Text
import gtk
# the label - displays text, takes no input:
label = gtk.Label("Name:")
label.set_xalign(0.0) # left-align within its space
# the entry - single-line text input:
entry = gtk.Entry()
entry.set_text("default text")
entry.set_max_length(60)
entry.set_visibility(False) # password mode: dots
entry.set_placeholder_text("your name") # 2.24 placeholder
# the buffer: the entry's text is an object, not a string field
text = entry.get_text()
entry.set_text("changed")
entry.connect("activate", on_enter) # Enter key in the entry
# the multi-line text widget - the document the editor uses
# (covered in depth later; here just the shape):
textview = gtk.TextView()
buf = textview.get_buffer()
buf.set_text("first line\nsecond line")
print(buf.get_text(*buf.get_bounds()))
The text widgets split by input. The Label displays - it has the text, the alignment, and the markup (set_markup for styled text), and no input state. The Entry takes one line: its text is a buffer accessed through get_text/set_text, and its notable properties are the max length, the visibility toggle (the password mode), and the placeholder (2.24). The entry's activate signal fires on the Enter key, which is the form's submit trigger. The TextView is the multi-line document widget - it owns a TextBuffer that holds the document, addressed by iterators (the get_bounds, the selection, the cursor position), and it is the editor's center in the later chapter. The pattern across all three: the displayed content is a property or a buffer object, not a string field on the widget, and reading or writing the text goes through that object.
Buttons, Checkboxes, Radios
import gtk
# the button - the action trigger:
button = gtk.Button(label="Save")
button.connect("clicked", on_save)
button.set_sensitive(False) # disabled: greyed, no signals
button.set_can_focus(True)
button.set_relief(gtk.RELIEF_NONE) # flat, for toolbar-style use
# the checkbox (CheckButton) - a boolean toggle:
check = gtk.CheckButton(label="Wrap long lines")
check.set_active(True)
print(check.get_active()) # True/False
check.connect("toggled", on_toggle)
# the toggle button - a button that stays pressed:
toggle = gtk.ToggleButton(label="Bold")
toggle.connect("toggled", on_bold)
# the radio buttons - mutually exclusive, grouped:
r1 = gtk.RadioButton(None, "Light")
r2 = gtk.RadioButton(r1, "Dark") # grouped with r1 (same group)
r3 = gtk.RadioButton(r1, "System")
# only ONE of the group can be active at a time
The choice and action widgets. The Button is the action trigger - its clicked signal is the handler, and its sensitive property is the enable/disable (an insensitive button is greyed and emits nothing). The CheckButton is the boolean - set_active/get_active for the state, the toggled signal when it changes. The ToggleButton is a button that holds its pressed state (the formatting buttons in an editor). The radio buttons are the mutually-exclusive choice: the first is created with None as its group, and each subsequent one passes the first (or any member) to join the group, so exactly one of the group is active at a time and the toggled signal fires on the newly-active member. The group is the data structure the exclusivity lives in - two sets of radios in one dialog are two groups, each with its own "one active" rule.
Scales, Spin Buttons, Progress
import gtk
# the scale - a slider over a range:
scale = gtk.HScale()
scale.set_range(0, 100) # the value range
scale.set_value(40)
scale.set_increments(1, 10) # step, page
scale.connect("value-changed", on_scale)
# the spin button - a numeric entry with up/down:
spin = gtk.SpinButton.new(0, 1000, 1)
spin.set_value(25)
spin.set_digits(0)
spin.connect("value-changed", on_spin)
print(spin.get_value()) # a float
# the progress bar - shows a fraction, takes no input:
bar = gtk.ProgressBar()
bar.set_fraction(0.35) # 0.0 to 1.0
bar.set_text("35%")
bar.pulse() # the indeterminate mode
# sensitivity across all of them: the disable is the property
spin.set_sensitive(False)
The numeric and state widgets. The Scale is the slider - a HScale/VScale over a range with a current value, the value-changed signal on every move. The SpinButton is the numeric entry with the up/down steppers - a range, a step, a value that is a float (the integer fields use set_digits(0)), and the same value-changed signal. The ProgressBar is the state display - a set_fraction from 0.0 to 1.0 for the determinate case, or pulse() for the indeterminate "working" mode. All of them share the sensitive property (the disable), and the pattern is the one the whole chapter holds: each widget exposes its value through a getter/setter pair and its change through a single signal, so reading state is a method call and reacting to change is a connection - the same two operations for every widget in the toolkit.
Composing a Form
import gtk
def build_settings_dialog():
dialog = gtk.Dialog("Settings")
dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
dialog.add_button("Save", gtk.RESPONSE_OK)
box = gtk.VBox(spacing=8)
box.set_border_width(12)
dialog.vbox.pack_start(box, True, True, 0)
# the fields - each is the basic widget plus its label:
name = gtk.Entry()
wrap = gtk.CheckButton(label="Wrap long lines")
wrap.set_active(True)
indent = gtk.SpinButton.new(1, 12, 1)
indent.set_value(4)
box.pack_start(gtk.Label("Name:"), False, False, 0)
box.pack_start(name, False, False, 0)
box.pack_start(wrap, False, False, 0)
box.pack_start(gtk.Label("Indent width:"), False, False, 0)
box.pack_start(indent, False, False, 0)
def on_save(dialog, response):
print(name.get_text(), wrap.get_active(),
int(indent.get_value()))
dialog.destroy()
dialog.connect("response", on_save)
dialog.show_all()
# the form is the composition: labels + inputs in a container,
# the dialog's response buttons collect the state, and the
# "response" signal is where the read of every field happens
The form is the composition of the basics: each field is its widget (the entry, the check, the spin) in a container with its label, and the gtk.Dialog provides the frame plus the response buttons (Cancel, Save) whose response signal carries the pressed button's id. The read of every field happens in the one place - the response handler reads name.get_text(), wrap.get_active(), indent.get_value(), and acts on the collected state, which is the form's whole logic in one function. The pattern is the one the dialogs chapter expands: the dialog is a window with a button box, the content is the form, and the response signal is the collection point - the basic widgets supply the fields, the container arranges them, and the dialog's response completes the interaction.
Summary
The basic widgets are the leaf vocabulary: the Label (display, with markup), the Entry (single-line input, the buffer, the activate signal, the placeholder), the TextView (the multi-line document with its TextBuffer), the Button (the clicked action, the sensitive disable), the CheckButton and ToggleButton (the active state, the toggled signal), the grouped radio buttons (one active per group), the Scale and SpinButton (the numeric value and the value-changed signal), and the ProgressBar (the fraction or the pulse). Every widget shares the same two operations - a getter/setter for its value and a signal for its change - which is the uniformity the rest of the toolkit builds on, and the form composition (fields in a container, the dialog's response as the collection point) is the pattern that assembles them into a working UI.