OWGUI: Library of Common GUI Controls

Orange Widgets wrap Orange's classes in an easy to use interactive graphical interface. As such, much of their code is about the interface, event control and maintaining the state of the GUI controls.

In the spirit of constructive laziness, we wrote a library using which a single line of code can construct a check box, line edit or a combo, make it being synchronized with a Python object's attribute (which, by the way, gets automatically saved and retrieved when the widgets is closed and reopened), attaches a callback function to the control, make it disable or enable other controls...


Common Attributes

Many functions that construct controls share one or more common arguments, usually in the same order. These are described here. Descriptions of individual controls will only list their specific arguments, while the arguments which are presented here will only be described in cases where they have a different meaning.

widget (required)
Widget on which control will be drawn - can be widget's controlArea or another box.
master (required)
Object which includes an attribute that are used to store control's state, most often the object which called the function that initialized the control.
value (required)
String with the name of the master's attribute that synchronizes with the state of the control (and vice-versa - when this attribute is changed, the control changes as well). This attribute should usually be also included the master's settingsList, so that it is automatically saved and retrieved.
box (default: None)
Indicates if there should be a box that is drawn around the control. If box is None, no box is drawn; if it is a string, it is also used as box's name. If box is any other true value (such as True :), an unlabeled box is drawn.
callback (default: None)
A function to be called when the state of the control is changed. Can include a single function, or a list of functions that will be called in the order provided. If callback function changes the value of the controlled attribute (the one given as the value argument described above) it may trigger a cycle; a simple trick to avoid this is shown in the description of listBox function.
tooltip (default: None)
A string that is displayed in a tooltip that appears when mouse is over the control.
label (default: None)
A string that is displayed as control's label.
labelWidth (default: None)
Sets the label's width. This is useful for aligning the controls.
orientation (default: "vertical")
When label is used, determines the relative placement of the label and the control. Label can be above the control, "vertical", or in the same line with control, "horizontal". Instead of "vertical" and "horizontal" you can also use True and False or 1 and 0, respectively. (Remember this as "vertical" being the usual order of controls in the widgets, so vertical is "true".)
disabled (default: False)
Tells whether the control be disabled upon the initialization.
addSpace (default: False)
If true, a space of 8 pixels is added after the widget by calling OWGUI.separator. addSpace can also be an integer specifying the height of the added space.

Controls

This section describes the OWGUI wrappers for controls like check boxes, buttons and similar. All the important Qt's controls can be constructed through this functions. You should always use them instead of calling Qt directly, not only because they are convenient, but also because they set up a lot of things that happen in behind.

Check Box

Check box, a wrapper around QCheckBox, adding a label, box, tooltip, callback and synchronization with the designated widget's attribute.

checkBox(widget, master, value, label[, box, tooltip, callback, disabled, labelWidth, disables])

disables (default: [])
If the check box needs to disable some other controls they can be given in list disables, e.g. disables=[someOtherCheckBox, someLineEdit]. If the other control should be disabled when the checkbox is checked, do it like this: disables=[someOtherCheckBox, (-1, someLineEdit)] - now someOtherCheckBox will be enabled when this check box is checked, while someLineEdit will be enabled when the check box is unchecked.
labelWidth (default: None)
labelWidth can be used to align this widget with others.
Show Example Download example (gui_check.py)

In the following code we define two check boxes. The second one disables (enables) the box with the spin box, depending on the state of the checkbox.

self.spinval = 10 self.chkA = 1 self.chkB = 0 self.dx = 15 box = OWGUI.widgetBox(self.controlArea, "Settings") gridbox = OWGUI.widgetBox(self.controlArea, "Grid Opions") gridbox.setEnabled(self.chkB) OWGUI.checkBox(box, self, "chkA", "Verbose") OWGUI.checkBox(box, self, "chkB", "Display Grid", disables=[gridbox]) OWGUI.spin(gridbox, self, "dx", 10, 20)

Line Edit

Edit box, a wrapper around QLineEdit.

lineEdit(widget, master, value[, label, labelWidth, orientation, box, tooltip, callback, valueType, validator, controlWidth])

valueType (default: str)
A type into which the value is cast.
validator (default: None)
A standard Qt validator that can be associated with the control.
Show Example Download example (gui_lineedit.py)
self.val1 = "Enter text ..." self.val2 = "Some more text ..." self.valF = 10.2 OWGUI.lineEdit(self.controlArea, self, "val1", box="Text Entry") box = OWGUI.widgetBox(self.controlArea, "Options (with lineEdit)") OWGUI.lineEdit(box, self, "val2", label="Name:", orientation="horizontal", labelWidth=40) OWGUI.lineEdit(box, self, "valF", label="Float:", orientation="horizontal", labelWidth=40, valueType=float)

Button

A wrapper around QPushButton, just to be able to define a button and its callback in a single line.

button(widget, master, label[, callback, disabled, tooltip])

Radio Buttons

OWGUI can create an individual radio button or a box of radio buttons or an individual radio button.

An individual radio button is created by radioButton.

radioButton(widget, master, value, label[, box, tooltip, callback, addSpace])

The function provides the usual capabilities of OWGUI controls. It is though your responsibility to put it in something like a QVButtonGroup.

A box of radio buttons is created by function radioButtonsInBox.

radioButtonsInBox(widget, master, value, btnLabels[, box, tooltips, callback)

value (required)
Synchronized with the index of the selected radio button.
btnLabels (required)
A list with labels for radio buttons. Labels can be strings or pixmaps.
tooltips (default: None)
A list of tooltips, one for each button.
Show Example Download example (gui_radiobuttons.py)
self.method = 0 OWGUI.radioButtonsInBox(self.controlArea, self, "method", box = "Probability estimation", btnLabels = ["Relative", "Laplace", "m-estimate"], tooltips = ["Relative frequency of the event", "Laplace-corrected estimate", "M-estimate of probability"])

Combo Box

A wrapper around QComboBox.

comboBox(widget, master, value[, box, label, labelWidth, orientation, items, tooltip, callback, sendSelectedValue, valueType, control2attributeDict, emptyString])

items (default: [])
A list of combo box's items. Unlike most OWGUI, items have one Orange-specific quirk: its element can be either a string, in which case it is used as a label, or a tuple, where the first element is a label name and the last is the attribute type which is used to create an icon. Most attribute lists in Orange Widgets are constructed this way.
sendSelectedValue (default: 0)
If false, attribute value will be assigned the index of the selected item. Otherwise, it is assigned the currently selected item's label.
control2attributeDict (default: {})
A dictionary for translating the item's label into value. It is used only is sendSelectedValue is true, and even then a label is translated only if an item with such a key is found in the dictionary; otherwise, label is written to value as it is.
emptyString (default: "")
Tells which combo box's item corresponds to an empty value. This is typically used when combo box's labels are attribute names and an item "(none)", which allows user to select no attribute. If we give emptyString="(none)", value will be an empty string when the user selects "(none)". This is equivalent to specifying control2attributeDict = {"(none)": ""} (and is actually implemented like that), but far more convenient.
valueType (default: str or unicode)
A function through which the currently selected item's label is converted prior to looking into control2attributeDict. Needed to convert Qt's QString.
Show Example Download example (gui_combobox.py)

We shall create a widget with two combo boxes: one will contain a simple choice of three colors, while the other will let the user select an attribute or no attribute (none).

self.chosenColor = 1 self.chosenAttribute = "" box = OWGUI.widgetBox(self.controlArea, "Color & Attribute") OWGUI.comboBox(box, self, "chosenColor", label="Color: ", items=["Red", "Green", "Blue"]) self.attrCombo = OWGUI.comboBox(box, self, "chosenAttribute", label="Attribute: ", sendSelectedValue = 1, valueType=str)

The widget's snapshot on the right is a cheat: the above code leaves the bottom list box empty. To be able to fill it later on, when the widget gets some data, we stored the control (self.attrCombo).

Say that the widget received an example table and stored it in self.data. This is how we fill out the combo.

self.attrCombo.clear() self.attrCombo.insertItem("(none)") icons = OWGUI.getAttributeIcons() for attr in self.data.domain: self.attrCombo.insertItem(icons[attr.varType], attr.name) self.chosenAttribute = self.data.domain[0].name

The first item is "(none)". When the user selects it, the value of chosenAttribute is set to "", since we specified emptyString="(none)" when initializing the combo. Next we insert items using Qt QComboBox's function insertItem, where the first argument is a QPixmap (an icon for the corresponding attribute type) and the second is an attribute name. Finally, we set self.chosenAttribute (and with that the selection in the combo box) to the first attribute. (For simplicity, we here left out testing whether the data contains any attribute.)

List Box

This control, which might be the most complicated control in OWGUI, is a sophisticated wrapper around QListBox. It's complexity arises from synchronization.

listBox(widget, master, value, labels[, box, tooltip, callback, selectionMode])

value (required)
The name of master's attribute containing indices of all selected values.
labels (required)
The name of master's attribute containing the list box's labels. Similar to items in combo box, list labels have one Orange-specific quirk: its element can be either a string, in which case it is used as a label, or a tuple, where the first element is a label name and the second can be either an icon on an integer, representing the attribute type which is used to create an icon. Most attribute lists in Orange Widgets are constructed this way.
selectionMode (default: QListWidget.SingleSelection)
Tells whether the user can select a single item (QListWidget.SingleSelection), multiple items (QListWidget.MultiSelection, QListWidget.ExtendedSelection) or nothing (QListWidget.NoSelection).

value is automatically cast to OWGUI.ControlledList (this is needed because the list should report any changes to the control, the list box; OWGUI.ControlledList is like an ordinary Python list except that it triggers synchronization with the list box at every change).

labels is only partially synchronized with the list box: if a new list is assigning to labels attribute, the list will change. If elements of the existing list are changed or added, the list box won't budge. You should never change the list, but always assign a new list (or reassign the same after it's changed). If the labels are stored in self.listLabels and you write self.listLabels[1]="a new label", the list box won't change. To trigger the synchronization, you should continue by self.listLabels = self.listLabels. This may seem awkward, but by our experience a list of selected items is seldom changed changed "per-item", so we were too lazy to write the annoyingly complex backward callbacks.

Show Example Download example (gui_listbox.py) Download example (gui_listbox_attr.py)

In first example, we will set up to list boxes, one with single and one with multiple selection. To see what is going on, we provide two labels that show the values of the corresponding attributes. Besides that, if all items in the second list box are chosen, the value of the first list box will change to red and the user will be prevented to change it from red until she deselects some of the items in the second list.

self.colors = ["Red", "Green", "Blue"] self.chosenColor = [2] self.numbers = ["One", "Two", "Three", "Four"] self.chosenNumbers = [0, 2, 3] OWGUI.listBox(self.controlArea, self, "chosenColor", "colors", box="Color", callback=self.checkAll) OWGUI.listBox(self.controlArea, self, "chosenNumbers", "numbers", box="Number", selectionMode=QListWidget.MultiSelection, callback=self.checkAll) box = OWGUI.widgetBox(self.controlArea, "Debug info") OWGUI.label(box, self, "Color: %(chosenColor)s") OWGUI.label(box, self, "Numbers: %(chosenNumbers)s", labelWidth=100)

Selecting the red color and not allowing any other color but red when the entire second list is selected is ensured by function checkAll which is used as a callback for both list box. An important point not to be overlooked here is self.chosenColor != [0] in condition here. If omitted, setting self.chosenColor would trigger a change in the list box selection, change in the list box selection causes the callback function self.chosenColor to be called again and ... you see where this leads.

def checkAll(self): if len(self.chosenNumbers) == len(self.numbers) and self.chosenColor != [0]: self.chosenColor = [0]

The second example shows a construction of an attribute list. If a discrete attribute is selected, the second list box will let the user select one or more of its values.

self.attributes = [] self.chosenAttribute = [] self.values = [] self.chosenValues = [] OWGUI.listBox(self.controlArea, self, "chosenAttribute", "attributes", box="Attributes", callback=self.setValues) OWGUI.separator(self.controlArea) OWGUI.listBox(self.controlArea, self, "chosenValues", "values", box="Values", selectionMode=QListWidget.MultiSelection) # The following assignments usually don't take place in __init__ # but later on, when the widget receives some data self.data = orange.ExampleTable(r"..\datasets\horse-colic.tab") self.attributes = [(attr.name, attr.varType) for attr in self.data.domain] self.chosenAttribute = [0]

We avoided the temptation of writing self.attributes = self.chosenAttribute = self.values = self.chosenValues = []. This would assign all attributes the same empty list and would, through self.chosenAttributes and self.chosenValues keep the selection in two lists synchronized (try it, it looks funny).

The second list box is filled every time the selection in the first is changed. This is done through the callback setValues.

def setValues(self): attrIndex = self.chosenAttribute[0] attr = self.data.domain[attrIndex] if attr.varType == orange.VarTypes.Discrete: self.values = attr.values else: self.values = [] self.chosenValues = []

How does the list fill when the widget gets the data? In the above code, self.chosenAttribute = [0] changes the selection in the first lists and causes the callback, setValues to be called. That's how.

Spin

Spin control, a wrapper around QSpinBox.

spin(widget, master, value, min, max[, step, box, label, labelWidth, orientation, tooltip, callback, controlWidth])

min, max, step=1
Minimal and maximal value, and step.
Show Example Download example (gui_spin.py)

Following is an excerpt of the code in the initialization part of the widget. It defines three spin boxes, where the second and third invoke the callback which sets the text in the info box.

self.spinval = 10 OWGUI.spin(self.controlArea, self, "spinval", 0, 100, box="Value A") box = OWGUI.widgetBox(self.controlArea, "Options") self.alpha = 30 self.beta = 4 OWGUI.spin(box, self, "alpha", 0, 100, label="Alpha:", labelWidth=60, orientation="horizontal", callback=self.setInfo) OWGUI.spin(box, self, "beta", -10, 10, label="Beta:", labelWidth=60, orientation="horizontal", callback=self.setInfo) box = OWGUI.widgetBox(self.controlArea, "Info") self.info = OWGUI.widgetLabel(box, "") self.setInfo()

The callback for setting the info box is simple. Notice that OWGUI associates each control's state with some value, which is updated automatically on any change of the control.

def setInfo(self): self.info.setText("Alpha=%d, Beta=%d" % (self.alpha, self.beta))

By the way, such functions are needed only when updating the label requires more than simply inserting some attributes' values. This code becomes redundant if we use OWGUI.label instead of QLabel.

Slider

A wrapper around QSlider that allows user setting a numerical value between the given bounds.

hSlider(widget, master, value[, box, minValue, maxValue, step, callback, labelFormat, ticks, divideFactor])

minValue (default: 0), maxValue (default: 10), step (default: 1)
Minimal and maximal value for the spin control, and its step.
ticks (default: 0)
If non-zero, it gives the interval between two ticks. The ticks will appear below the groove.
labelFormat (default: " %d")
Defines the look of the label on the righthand side of the slider. It has to contain one format character (like %d in the default), but can contain other text as well.
divideFactor (default: 1.0)
The value printed in the label is divided by divideFactor.

For an example of usage, see the second example in the description of labels.

Check Box with Spin

Check box with spin, or, essentially, a wrapper around OWGUI.checkBox and OWGUI.spin.

checkWithSpin(widget, master, label, min, max, checked, value[, posttext, step, tooltip, checkCallback, spinCallback, labelWidth])

min, max, step (required)
Minimal and maximal value for the spin control, and its step.
checked (required)
Master's attribute that is synchronized with the state of the check box.
value (required)
The attribute that is synchronized with the spin.
posttext (default: None)
Text which appears on the right-hand side of the control.
checkCallback (default: None), spinCallback (default: None)
Function that are called when the state of the check box or spin changes.
Show Example Download example (gui_checkspin.py)
self.val = 20 self.chk = 1 OWGUI.checkWithSpin(self.controlArea, self, "Prunning, m=", 0, 100, "chk", "val", posttext = "%")

Labels

There are two functions for constructing labels. The first is a simple wrapper around QLabel which differs only in allowing to specify a fixed width without needing an extra line. Note that unlike most other OWGUI widgets, this one does not have the argument master.

widgetLabel(widget, label[, labelWidth])

The second is a label which can synchronize with values of master widget's attributes.

label(widget, master, label[, labelWidth])

label
label is a format string following Python's syntax (see the corresponding Python documentation): the label's content is rendered as label % master.__dict__.
Show Example Download example (gui_label.py)

The following code, taken from OWPurgeDomain, construct four labels that change whenever the value of self.removedAttrs, self.reducedAttrs, self.resortedAttrs or self.classAttr changes in the code.

box3 = OWGUI.widgetBox(self.controlArea, 'Statistics') OWGUI.label(box3, self, "Removed attributes: %(removedAttrs)s") OWGUI.label(box3, self, "Reduced attributes: %(reducedAttrs)s") OWGUI.label(box3, self, "Resorted attributes: %(resortedAttrs)s") OWGUI.label(box3, self, "Class attribute: %(classAttr)s")

Each label can contain any number of attributes, which can be strings (as in %(name)s) or of any type supported by Python (e.g. %(name)5.3f), like in the toy example below.

self.val = 5 self.line = "a parrot" OWGUI.spin(self.controlArea, self, "val", 1, 10, label="Value") OWGUI.lineEdit(self.controlArea, self, "line", label="Line: ", orientation=0) OWGUI.label(self.controlArea, self, "Value is %(val)i and line edit contains %(line)s")

Utilities

Widget box

widgetBox(widget, box=None, orientation='vertical', addSpace=False)
Creates a box in which other widgets can be put. If box is given and not false, the box will be framed. If box is a string, it will be used for the box name (don't capitalize each word; spaces in front or after the string will be trimmed and replaced with a single space). Argument orientation can be "vertical" or "horizontal" (or True and False, or 1 and 0, respectively).

Idented box

indentedBox(widget, sep=20)
Creates an indented box. Widgets which are subsequently put into that box will be arranged vertically and aligned with an indentation of sep.
Show Example Download example (gui_indentedBox.py)

The first box in this example widget contains three check boxes: the first enables and disables the other two, which are for this reason indented. Note that we are disabling both check boxes at once by disabling the indented box.

The second box contains only two check boxes, the first disabling the second. Note the shortcut we used - the indented check box is created on the fly.

box = OWGUI.widgetBox(self.controlArea, "Redundant values") remRedCB = OWGUI.checkBox(box, self, "removeRedundant", "Remove redundant values") iBox = OWGUI.indentedBox(box) OWGUI.checkBox(iBox, self, "removeContinuous", "Reduce continuous attributes") OWGUI.checkBox(iBox, self, "removeClasses", "Reduce class attribute") remRedCB.disables.append(iBox) box = OWGUI.widgetBox(self.controlArea, "Noise") noiseCB = OWGUI.checkBox(box, self, "addNoise", "Add noise to data") OWGUI.checkBox(OWGUI.indentedBox(box), self, "classOnly", "Add noise to class only")

Inserting Space between Widgets

Most widgets look better if we insert some vertical space between the controls or groups of controls. A few functions have an optional argument addSpace by which we can request such space to be added. For other occasions, we can use the following two functions.

separator(widget, width=0, height=8)

Function separator inserts a fixed amount of space into widget. Although the caller can specify the amount, leaving the default will help the widgets having uniform look.

rubber(widget[, orientation="vertical"])

Similar to separator, except that the size is (1, 1) and that it expands in the specified direction if the widget is expanded. Most widgets should have rubber somewhere in their controlArea.

Attribute Icons

getAttributeIcons()

Returns a dictionary with attribute types (orange.VarTypes.Discrete, orange.VarTypes.Continuous, orange.VarTypes.String, -1) as keys and colored pixmaps as values. The dictionary can be used in list and combo boxes showing attributes for easier distinguishing between attributes of different types.

Send automatically / Send

Many widgets have a "Send" button (perhaps named "Apply", "Commit"...) accompanied with a check box "Send automatically", having the same effect as if the user pressed the button after each change. A well behaved widget cares to:

Programming this into every widget is annoying and error-prone; at the time when the function described here was written, not many widgets actually did this properly.

setStopper(master, sendButton, stopCheckbox, changedFlag, callback)

sendButton
The button that will be disabled when the check box is checked.
stopCheckbox
Check box that decides whether the changes are sent/commited/applied automatically.
changedFlag
The name of the master's attribute which tells whether there is a change which has not been sent/applied yet.
callback
The function that sends the data or applies the changes. This is typically the function which is also used as the sendButton's callback.

setStopper is a trivial three lines long function which connects a few signals. Its true importance is in enforcing the correct procedure for implementing such button-check box combinations. Make sure to carefully observe and follow the example provided below.

Show Example Download example (gui_stopper.py)

In this somewhat lengthy example we shall write a widget for a new learning method XLearner, which will feature a few radio buttons and check boxes, and an "apply box", with an Apply button and a check box for automatic application of each change.

Except for two import statements and the construction of learner, which typically takes one line, this is a complete source. This is how much code a typical learner-wrapping widget takes.

class OWXLearner(OWWidget): settingsList = ["method", "maxi", "cheat", "autoApply"] def __init__(self, parent=None): OWWidget.__init__(self, parent, title='X Learner') self.method= 0 self.maxi = 1 self.cheat = 0 self.autoApply = True self.settingsChanged = False OWGUI.radioButtonsInBox(self.controlArea, self, "method", ["Vanishing", "Disappearing", "Invisibilisation"], box="Minimization technique", callback = self.applyIf) OWGUI.separator(self.controlArea) box = OWGUI.widgetBox(self.controlArea, "Settings") OWGUI.checkBox(box, self, "maxi", "Post-maximize", callback = self.applyIf) OWGUI.checkBox(box, self, "cheat", "Quasi-cheating", callback = self.applyIf) OWGUI.separator(self.controlArea) box = OWGUI.widgetBox(self.controlArea, "Apply") applyButton = OWGUI.button(box, self, "Apply", callback = self.apply) autoApplyCB = OWGUI.checkBox(box, self, "autoApply", "Apply automatically") OWGUI.setStopper(self, applyButton, autoApplyCB, "settingsChanged", self.apply) self.adjustSize() def applyIf(self): if self.autoApply: self.apply() else: self.settingsChanged = True def apply(self): # Constructs and sends the learner; here we shall show only a message box QMessageBox.information(self, "Applied", "New settings applied") self.settingsChanged = False

First note the two apply functions. The first, applyIf is a conditional apply; if automatic application is enabled, the "true" apply function is called, elsewhere we just set the flag that settings have been changed since the last application. The other apply function, apply would construct a learner and send the signal to widget's outputs, while we just show a message box. In either case, we have to clear the flag that settings have been changed.

All controls that set the learner's properties specify applyIf as the callback function. If other code needs to be executed when the control is changed, this is OK, but at the end it needs to call applyIf. Apply button naturally calls apply function, which unconditionally applies the changes.

The callback function that setStopper attaches to the check box "Apply automatically" does this and which is called at each change of the check box, verifies whether the check box has been just checked and whether any settings have been changed (settingsChanged). If so, apply is called. Otherwise, it does nothing. (Button Apply gets disabled, of course, but this happens using another, more general OWGUI's mechanism.)