In the previous section of our tutorial we have just built a simple sampling widget. Let us now make this widget a bit more useful, by allowing a user to set the proportion of data instances to be retained in the sample. Say we want to design a widget that looks something like this:
What we added is an Options box, with a spin entry box to set the sample size, and a check box and button to commit (send out) any change we made in setting. If the check box with "Commit data on selection change" is checked, than any change in the sample size will make the widget send out the sampled data set. If data sets are large (say of several thousands or more) instances, we may want to send out the sample data only after we are done setting the sample size, hence we left the commit check box unchecked and press "Commit" when we are ready for it.
This is a very simple interface, but there is something more to it. We want the settings (the sample size and the state of the commit button) to be saved. That is, any setting we made, after closing our widget (or after going out of Orange application that includes this widget, or after closing Orange Canvas), we want to save so that the next time we open the widget the settings is there as we have left it. There is some complication to it, as widget can be part of an application, or part of some schema in the Canvas, and we would like to have the settings application- or schema-specific.
Luckily, since we use the base class OWWidget
, the settings
will be handled just fine. We only need to tell which variables we
will use for the settings. For Python inspired readers: these
variables can store any complex object, as long as it is
picklable. In our widget, we will use two settings variables, and we
declare this just after the widget class definition.
Any setting has to be initialized, and then we need to call
loadSettings()
to override defaults in case we have used
the widget before and the settings have been saved:
Now anything we do with the two variables (self.proportion
and
self.commitOnChange
) will be saved upon exiting our
widget. In our widget, we won't be setting these variables directly,
but will instead use them in conjunction with GUI controls.
Now we could tell you how to put different Qt controls on the widgets and write callback functions that set our settings appropriately. This is what we have done before we got bored with it, since the GUI part spanned over much of the widget's code. Instead, we wrote a library called OWGUI (I never liked the name, but could never come up with something better). With this library, the GUI definition part of the options box is a bit dense but rather very short:
We are already familiar with the first part - the Info group
box. To make widget nicer, we put a separator between this and Options
box. After defining the option box, here is our first serious OWGUI
control. Called a spin
, we give it place where it is
drawn (self.optionsBox
), and we give it the widget object
(self
) so that it knows where the settings and some other
variables of our widget are.
Next, we tell the spin box to be
associated with a variable called proportion
. This simply
means that any change in the value the spin box holds will be directly
translated to a change of the variable
self.proportion
. No need for a callback! But there's
more: any change in variable self.proportion
will be
reflected in the look of this GUI control. Say if there would be a
line self.proportion = 70
in your code, our spin box
control would get updated as well. (I must admit I do not know if you
appreciate this feature, but trust me, it may really help prototyping
widgets with some more complex GUI.
The rest of the OWGUI spin box call gives some parameters for the control (minimum and maximum value and the step size), tells about the label which will be placed on the top, and tells it which functions to call when the value in the spin box is changed. We need the first callback to make a data sample and report in the Info box what is the size of the sample, and a second callback to check if we can send this data out. In OWGUI, callbacks are either references to functions, or a list with references, just like in our case.
With all of the above, the parameters for the call of
OWGUI.checkBox
should be clear as well. Notice that this
and a call to OWGUI.spin
do not need a parameter which
would tell the control the value for initialization: upon construction,
both controls will be set to the value that is pertained in the
associated setting variable.
That's it. Notice though that we have, as a default, disabled all the controls in the Options box. This is because at the start of the widget, there is no data to sample from. But this also means that when process the input tokens, we should take care for enabling and disabling. The data processing and token sending part of our widget now is:
You can now also inspect the complete code of this widget. To distinguish it with a widget we have developed in the previous section, we have designed a special icon for it. If you wish to test is widget in the Orange Canvas, put its code in the Test directory we have created for the previous widget, update the Canvas registry, and try it out using a schema with a File and Data Table widget.