Well-behaved widgets remember their settings - the state of their checkboxes and radio-buttons, the text in their line edits, the selections in their combo boxes and similar. These settings are even maintained across sessions. This document describes the Orange's methods that take care of that.
Orange doesn't really save the state of the controls but instead
saves the value of the corresponding attributes. For a check box there
should be a corresponding widget's attribute recording the check box's
state so that when the user changes a check box, the attribute changes
and vice-versa. Although you can create such a link manually, you
should always use the module OWGUI instead;
for instance, for a check box, use OWGUI.checkBox
and not
simply the Qt's QCheckBox
.
The settings fall into two groups. Some of them do not depend on
the data, while other are context-dependent. For the first to be saved
properly, you only need to list them in the settingsList
in the widget definition, as already described elsewhere.
Context dependent settings usually depend upon the attributes that are present in the data set domain. For instance, the scatter plot widget contains settings that specify the attributes for x and y axis, and the settings that define the color, shape and size of the examples in the graph. An even more complicated case is the widget for data selection with which one can select the examples based on values of certain attributes. Before applying the saved settings, these widgets needs to check their compliance with the domain of the actual data set. To be truly useful, context dependent settings needs to save a setting configuration for each particular data set used. That is, when given a particular data set, it has to select the saved settings that is applicable and matches best currently used data set.
Saving, loading and matching contexts is taken care of by context
handlers. Currently, there are only two classes of context handlers
implemented. The first one is the abstract ContextHandler
and the second one is DomainContextHandler
in which the
context is defined by the data set domain and where the settings
contain attribute names. The latter should cover most of your needs,
while for more complicated widgets you will need to derive a new
classes from it. There may even be some cases in which the context is
not defined by the domain, in which case the
ContextHandler
will be used as a base for your new
handler.
Contexts need to be declared, opened and closed. Opening and closing usually takes place (in the opposite order) in the function that handles the data signal. This is how it looks in the scatter plot (the code is somewhat simplified for clarity).
In general, the function should go like this.
self.closeContext
; this ensures that all the context dependent settings (e.g. attribute names from the list boxes) are remembered.initAttrValues()
which assigns the first two attributes to the x and y axis and the class attribute to the color. At this phase, you shouldn't call any functions that depend on the settings, such as drawing the graph.self.openContext
(more about the arguments later). This will search for a suitable context and assign the controls new values if one is found. If there is no saved context that can be used, a new context is created and filled with the default values that were assigned at the previous point.updateGraph
.closeContext
has an argument, the name of the context. If omitted (like above), the default name (""
) is used. When opening the context, we give the name and some arguments on which the context depends. In case of DomainContextHandler
, which scatter plot uses, we can give it a domain or any object that has a field domain
containing a domain. Whether a saved context can be reused is judged upon the presence of attributes in the domain.
If the widget is constructed appropriately (that is, if it strictly uses OWGUI controls instead of the Qt's), no other administration is needed to switch the context.
Except for declaring the context settings, that is. Scatter plot has this just below the settingsList
:
contextHandlers
is a dictionary whose keys are contexts' names. Each widget can have multiple contexts; for an unrealistic example, consider a scatter plot which gets two data sets and uses one attribute from the first for the x axis, and an attribute from the other for y. Since we won't see this often, the default name for a context is an empty string.
The values in the dictionary are context handlers. Scatter plot declares that it has a DomainContextHandler with name "" (sorry for the repetition) with attributes "attrX", "attrY", "attrLabel", "attrShape" and "attrSize". The first two are required, while the other three are optional.
DomainContextHandler
What we said above is not exactly
true. DomainContextHandler.Required
is the default flag,
so ("attrX", DomainContextHandler.Required)
can be
replaced by simply "attrX"
. And the latter three have the
same flags, so they can be grouped into (["attrLabel",
"attrShape", "attrSize"], DomainContextHandler.Optional)
. So
what scatter plot really says is
What do "optional" and "required" mean? Say that you used the
scatter plot on the data with attributes A, B, C and D; A and B are
used for the x and y axis and D defined the colors of examples. Now
you load a new data with attributes A, B, E, and F. The same context
can be used - A and B will again be shown on x and y axis and the
default (the one set by self.initAttrValues
) will be used
for the color since the attribute D is missing in the new data. Now
comes the third data set, which only has attributes A, D and E. The
context now can't be reused since the attribute used for the
required attrY
(the y axis) is missing.
OK, now it is time to be a bit formal. As said,
contextHandlers
is a dictionary and the values in it need
to be context handlers derived from the abstract class
ContextHandler
. The way it is declared of course depends
upon its constructor, so the above applies only to the usual
DomainContextHandler
.
DomainContextHandler's constructor has the following arguments
openContext
shouldn't reuse a context but create a copy of the best matching context instead. Default is True
.True
.loadImperfect
, but it may come useful some day. Default is True
again.The truly interesting argument is fields
. It roughly corresponds to the settingsList
in that each element specifies one widget attribute to be saved. The elements of fields
can be strings, tuples and/or instances of ContextField
(whatever you give, it gets automatically converted to the latter). When given as tuples, they should consist of two elements, the field name (just like in settingsList
) and a flag. Here are the possible flags:
DomainContextHandler.Optional
, DomainContextHandler.SelectedRequired
and DomainContextHandler.Required
state whether the attribute is optional or required, as explained above. Default is Required
. DomainContextHandler.SelectedRequired
is applicable only if the control is a list box, where it means that the attributes that are selected are required while the other attributes from the list are not.DomainContextHandler.NotAttribute
the setting is not an attribute name. You can essentially make a check box context dependent, but we very strongly dissuade from this since it can really confuse the user if some check boxes change with the data while most do not.DomainContextHandler.List
tells that the attribute corresponds to a list box.Flags can be combined, so to specify a list in which all attributes
are required, you would give DomainContextHandler.List +
DomainContextHandler.Required
. Since this combination is
common, DomainContextHandler.RequiredList
can be used
instead.
There are two shortcuts. The default flag is
DomainContextHandler.Required
. If your attribute is like
this (as most are), you can give only its name instead of a
tuple. This is how "attrX"
and "attrY"
are
given in the scatter plot. If there are multiple attributes with the
same flags, you can specify them with a tuple in which the first
element is not a string but a list of strings. We have seen this trick
in the scatter plot, too.
But the tuples are actually a shortcut for instances of
ContextField
. When you say "attrX"
this is
actually ContextField("attrX",
DomainContextHandler.Required)
(you should appreciate the
shortcurt, right?). But see this monster from widget "Select
Attributes" (file OWDataDomain.py):
ContextField
's constructor gets the name and flags and a list of arguments that are written directly into the object instance. To follow the example, recall what Select Attributes looks like: it allows you to select a subset of attributes, the class attribute and the meta attributes that you want to use; the attributes in the corresponding three list boxes are stored in the widget's variables chosenAttributes
, classAttribute
and metaAttributes
respectively. When the user selects some attributes in any of these boxes, the selection is stored in selectedChosen
, selectedClass
and inputAttributes
.
The above definition tells that the context needs to store the contents of the three list boxes by specifying the corresponding variables; the list of attributes is given as the name of the field and the list of selected attributes is in the optional named attribute selected
. By reservoir
we told the context handler that the attributes are taken from inputAttributes
. So, when a context is retrieved, all the attributes that are not in any of the three list boxes are put into inputAttributes
.
Why the mess? Couldn't we just store inputAttributes
as the fourth list box? Imagine that the user first loads the data with attributes A, B, C, D, E and F, puts A, B, C in chosen and D in class. E and F are left in inputAttributes
. Now she loads another data which has attributes A, B, C, D, E, and G. The contexts should match (the new data has all the attributes we need), but inputAttributes
should now contain E and G, not E and F, since F doesn't exist any more, while G needs to be made available.
You can use ContextField
(instead of tuples and strings) for declaring any fields, but you will usually need them only for lists or, maybe, some complicated future controls.
Avoid it if you can. If you can't, here's the list of the methods you may need to implement. You may want to copy as much from the DomainContextHandler
as you can.
DomainContextHandler
's, except for the fields
.ContextHandler
is returns an instance of Context
; you probably won't need to change this.DomainContextHandler
this is a domain. There can be one or more such arguments. Note that the method openContext
which we talked about above is a method of OWBaseWidget
, while here we describe a method of context handlers. Actually, OWBaseWidget(self, contextName, *args)
calls the context handler's, passing it's self
and *args
.
It needs to find a matching context and copy its settings to the widget or construct a new context and copy the settings from the widget. Also, when an old context is reused, it should be moved to the beginning of the list. ContextHandler
already defines this method, which should usually suffice. DomainContextHandler
adds very little to it.settingsFromWidget
. You probably won't need to overwrite it.openContext
to find a matching context. Given an existing context and the arguments that were given to openContext
(for instance, a domain), it should decide whether the context matches or not. If it returns 2, it is a perfect match (e.g. domains are the same). If it returns 0, the context is not applicable (e.g. some of the required attributes are missing). In case it returns a number between 0 and 1 (excluding 0), the higher the number the better the match. openContext
will use the best matching context (or the perfect one, if found).__setattr__
each time any widget's variable is changed to immediately synchronize the context with the state of the widget. The method is really needed only when syncWithGlobal
is set. When the context is closed, closeContext
will save the settings anyway.copy.deepcopy
can be used instead.Settings can be saved in two different places. Orange Canvas save
settings in .ini files in directory
Orange/OrangeWidgets/widgetSettings. Each widget type has its separate
file; for instance, the scatter plot's settings are saved in
ScatterPlot.ini
. Saved schemas and applications save
settings in .sav files; the .sav file is placed in the same directory
as the schema or application, has the same name (except for the
extension) and contains the settings for all widgets in the
schema/application.
Saving and loading is done automatically by canvas or the
application. In a very rare case you need it to run these operations
manually, the functions involved are loadSettings(self, file =
None)
, saveSettings(self, file = None)
,
loadSettingsStr(self, str)
,
saveSettingsStr(self)
. The first two load and save from
the file; if not given, the default name (widget's name +
.ini
) is used. They are called by the canvas, never by a
schema or an application. The last two load and save from a string and
are used by schemas and applications. All the functions are defined as
methods of OWBaseWidget
, which all other widgets are
derived from.