source: orange/docs/extend-widgets/rst/settings.rst @ 11593:6edc44eb9655

Revision 11593:6edc44eb9655, 22.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 10 months ago (diff)

Updated Widget development tutorial.

Line 
1#####################
2Settings and Controls
3#####################
4
5In the :doc:`previous section <basics>` of our tutorial we
6have just built a simple sampling widget. Let us now make this widget
7a bit more useful, by allowing a user to set the proportion of data
8instances to be retained in the sample. Say we want to design a widget
9that looks something like this:
10
11.. image:: dataSamplerBWidget.png
12
13What we added is an Options box, with a spin entry box to set the
14sample size, and a check box and button to commit (send out) any
15change we made in setting. If the check box with "Commit data on
16selection change" is checked, than any change in the sample size will
17make the widget send out the sampled data set. If data sets are large
18(say of several thousands or more) instances, we may want to send out
19the sample data only after we are done setting the sample size, hence
20we left the commit check box unchecked and press "Commit" when we are
21ready for it.
22
23This is a very simple interface, but there is something more to
24it. We want the settings (the sample size and the state of the commit
25button) to be saved. That is, any setting we made, after closing our
26widget (or after going out of Orange application that includes this
27widget, or after closing Orange Canvas), we want to save so that the
28next time we open the widget the settings is there as we have left
29it. There is some complication to it, as widget can be part of an
30application, or part of some schema in the Canvas, and we would like
31to have the settings application- or schema-specific.
32
33****************
34Widgets Settings
35****************
36
37Luckily, since we use the base class :obj:`OWWidget`, the settings
38will be handled just fine. We only need to tell which variables we
39will use for the settings. For Python inspired readers: these
40variables can store any complex object, as long as it is
41picklable. In our widget, we will use two settings variables, and we
42declare this just after the widget class definition.
43
44::
45
46    class OWDataSamplerB(OWWidget):
47        settingsList = ['proportion', 'commitOnChange']
48
49Any setting has to be initialized, and then we need to call
50:obj:`loadSettings()` to override defaults in case we have used
51the widget before and the settings have been saved::
52
53    self.proportion = 50
54    self.commitOnChange = 0
55    self.loadSettings()
56
57Now anything we do with the two variables (:obj:`self.proportion` and
58:obj:`self.commitOnChange`) will be saved upon exiting our
59widget. In our widget, we won't be setting these variables directly,
60but will instead use them in conjunction with GUI controls.
61
62******************
63Controls and OWGUI
64******************
65
66Now we could tell you how to put different Qt controls on the
67widgets and write callback functions that set our settings
68appropriately. This is what we have done before we got bored with it,
69since the GUI part spanned over much of the widget's code. Instead, we
70wrote a library called OWGUI (I never liked the name, but could never
71come up with something better). With this library, the GUI definition
72part of the options box is a bit dense but rather very short::
73
74    box = OWGUI.widgetBox(self.controlArea, "Info")
75    self.infoa = OWGUI.widgetLabel(box, 'No data on input yet, waiting to get something.')
76    self.infob = OWGUI.widgetLabel(box, '')
77
78    OWGUI.separator(self.controlArea)
79    self.optionsBox = OWGUI.widgetBox(self.controlArea, "Options")
80    OWGUI.spin(self.optionsBox, self, 'proportion', min=10, max=90, step=10,
81               label='Sample Size [%]:', callback=[self.selection, self.checkCommit])
82    OWGUI.checkBox(self.optionsBox, self, 'commitOnChange', 'Commit data on selection change')
83    OWGUI.button(self.optionsBox, self, "Commit", callback=self.commit)
84    self.optionsBox.setDisabled(1)
85
86We are already familiar with the first part - the Info group
87box. To make widget nicer, we put a separator between this and Options
88box. After defining the option box, here is our first serious OWGUI
89control. Called a :obj:`spin`, we give it place where it is
90drawn (:obj:`self.optionsBox`), and we give it the widget object
91(:obj:`self`) so that it knows where the settings and some other
92variables of our widget are.
93
94Next, we tell the spin box to be
95associated with a variable called :obj:`proportion`. This simply
96means that any change in the value the spin box holds will be directly
97translated to a change of the variable
98:obj:`self.proportion`. No need for a callback! But there's
99more: any change in variable :obj:`self.proportion` will be
100reflected in the look of this GUI control. Say if there would be a
101line :obj:`self.proportion = 70` in your code, our spin box
102control would get updated as well. (I must admit I do not know if you
103appreciate this feature, but trust me, it may really help prototyping
104widgets with some more complex GUI.
105
106The rest of the OWGUI spin box call gives some parameters for the
107control (minimum and maximum value and the step size), tells about the
108label which will be placed on the top, and tells it which functions to
109call when the value in the spin box is changed. We need the first
110callback to make a data sample and report in the Info box what is the
111size of the sample, and a second callback to check if we can send this
112data out. In OWGUI, callbacks are either references to functions, or a
113list with references, just like in our case.
114
115With all of the above, the parameters for the call of
116:obj:`OWGUI.checkBox` should be clear as well. Notice that this
117and a call to :obj:`OWGUI.spin` do not need a parameter which
118would tell the control the value for initialization: upon construction,
119both controls will be set to the value that is pertained in the
120associated setting variable.
121
122That's it. Notice though that we have, as a default, disabled all
123the controls in the Options box. This is because at the start of the
124widget, there is no data to sample from. But this also means that when
125process the input tokens, we should take care for enabling and
126disabling. The data processing and token sending part of our widget
127now is::
128
129    def data(self, dataset):
130        if dataset:
131            self.dataset = dataset
132            self.infoa.setText('%d instances in input data set' % len(dataset))
133            self.optionsBox.setDisabled(0)
134            self.selection()
135            self.commit()
136        else:
137            self.send("Sampled Data", None)
138            self.optionsBox.setDisabled(1)
139            self.infoa.setText('No data on input yet, waiting to get something.')
140            self.infob.setText('')
141
142    def selection(self):
143        indices = orange.MakeRandomIndices2(p0=self.proportion / 100.)
144        ind = indices(self.dataset)
145        self.sample = self.dataset.select(ind, 0)
146        self.infob.setText('%d sampled instances' % len(self.sample))
147
148    def commit(self):
149        self.send("Sampled Data", self.sample)
150
151    def checkCommit(self):
152        if self.commitOnChange:
153            self.commit()
154
155You can now also inspect the :download:`complete code <OWDataSamplerB.py>`
156of this widget. To distinguish it with a widget we have developed in the
157previous section, we have designed a special
158:download:`icon <DataSamplerB.svg>` for it. If you wish to test is
159widget in the Orange Canvas, put its code in the Test directory we
160have created for the previous widget, update the Canvas registry, and
161try it out using a schema with a File and Data Table widget.
162
163.. image:: schemawithdatasamplerB.png
164
165
166Well-behaved widgets remember their settings - the state of their
167checkboxes and radio-buttons, the text in their line edits, the
168selections in their combo boxes and similar. These settings are even
169maintained across sessions. This document describes the Orange's
170methods that take care of that.
171
172Orange doesn't really save the state of the controls but instead
173saves the value of the corresponding attributes. For a check box there
174should be a corresponding widget's attribute recording the check box's
175state so that when the user changes a check box, the attribute changes
176and vice-versa. You can create such a link manually, or you can use
177the :doc:`OWGUI <owgui>` module instead; for instance, for a check
178box, use :func:`OWGUI.checkBox`.
179
180The settings fall into two groups. Some of them do not depend on
181the data, while other are context-dependent. For the first to be saved
182properly, you only need to list them in the :obj:`settingsList`
183in the widget definition, as already described.
184
185
186.. module:: OWContexts
187
188**************************
189Context dependent settings
190**************************
191
192Context dependent settings usually depend upon the attributes that
193are present in the data set domain. For instance, the scatter plot
194widget contains settings that specify the attributes for x and y axis,
195and the settings that define the color, shape and size of the examples
196in the graph. An even more complicated case is the widget for data
197selection with which one can select the examples based on values of
198certain attributes. Before applying the saved settings, these widgets
199needs to check their compliance with the domain of the actual data
200set. To be truly useful, context dependent settings needs to save a
201setting configuration for each particular data set used. That is, when
202given a particular data set, it has to select the saved settings that
203is applicable and matches best currently used data set.
204
205Saving, loading and matching contexts is taken care of by context
206handlers. Currently, there are only two classes of context handlers
207implemented. The first one is the abstract :class:`ContextHandler`
208and the second one is :class:`DomainContextHandler` in which the
209context is defined by the data set domain and where the settings
210contain attribute names. The latter should cover most of your needs,
211while for more complicated widgets you will need to derive a new
212classes from it. There may even be some cases in which the context is
213not defined by the domain, in which case the
214:class:`ContextHandler` will be used as a base for your new
215handler.
216
217Contexts need to be declared, opened and closed. Opening and
218closing usually takes place (in the opposite order) in the function
219that handles the data signal. This is how it looks in the scatter plot
220(the code is somewhat simplified for clarity).
221
222::
223
224    def cdata(self, data, clearResults = 1):
225        self.closeContext()
226
227        exData = self.data
228        self.data = data
229        self.graph.setData(data)
230        self.graph.insideColors = None
231        self.graph.clusterClosure = None
232
233        self.initAttrValues()
234
235        self.openContext("", data)
236
237        self.updateGraph()
238        self.sendSelections()
239
240In general, the function should go like this.
241
242* Do any clean-up you need, but without clearing any of the settings that need
243  to be saved. Scatter plot needs none.
244* Call :obj:`self.closeContext()`; this ensures that all the context dependent
245  settings (e.g. attribute names from the list boxes) are remembered.
246* Get the data (or whatever you do) and set the controls to some defaults as
247  if there were no context retrieving mechanism. Scatter plot does it by
248  calling :obj:`initAttrValues()` which assigns the first two attributes to
249  the x and y axis and the class attribute to the color. At this phase, you
250  shouldn't call any functions that depend on the settings, such as drawing
251  the graph.
252* Call :obj:`self.openContext` (more about the arguments later). This will
253  search for a suitable context and assign the controls new values if one is
254  found. If there is no saved context that can be used, a new context is
255  created and filled with the default values that were assigned at the previous
256  point.
257* Finally, adjust the widget according to the retrieved controls. Scatter plot
258  now plots the graph by calling :obj:`updateGraph`.
259
260
261:obj:`closeContext` has an argument, the name of the context. If omitted
262(like above), the default name (:obj:`""`) is used. When opening the context,
263we give the name and some arguments on which the context depends. In case of
264:obj:`DomainContextHandler`, which scatter plot uses, we can give it a domain
265or any object that has a field :obj:`domain` containing a domain. Whether a
266saved context can be reused is judged upon the presence of attributes in the
267domain.
268
269If the widget is constructed appropriately (that is, if it strictly uses OWGUI
270controls instead of the Qt's), no other administration is needed to switch the
271context.
272
273Except for declaring the context settings, that is. Scatter plot has this just
274below the :obj:`settingsList` ::
275
276    contextHandlers = {"": DomainContextHandler("",
277      [("attrX", DomainContextHandler.Required),
278       ("attrY", DomainContextHandler.Required),
279       ("attrLabel", DomainContextHandler.Optional),
280       ("attrShape", DomainContextHandler.Optional),
281       ("attrSize", DomainContextHandler.Optional)])}
282
283:obj:`contextHandlers` is a dictionary whose keys are contexts' names. Each
284widget can have multiple contexts; for an unrealistic example, consider a
285scatter plot which gets two data sets and uses one attribute from the first
286for the x axis, and an attribute from the other for y. Since we won't see this
287often, the default name for a context is an empty string.
288
289The values in the dictionary are context handlers. Scatter plot declares that
290it has a DomainContextHandler with name "" (sorry for the repetition) with
291attributes "attrX", "attrY", "attrLabel", "attrShape" and "attrSize". The
292first two are required, while the other three are optional.
293
294*********************************
295Using :obj:`DomainContextHandler`
296*********************************
297
298What we said above is not exactly true. :obj:`DomainContextHandler.Required`
299is the default flag, so :obj:`("attrX", DomainContextHandler.Required)` can
300be replaced by simply :obj:`"attrX"`. And the latter three have the
301same flags, so they can be grouped into :obj:`(["attrLabel",
302"attrShape", "attrSize"], DomainContextHandler.Optional)`. So
303what scatter plot really says is::
304
305    contextHandlers = {"": DomainContextHandler("", [
306       "attrX", "attrY",
307       (["attrLabel", "attrShape", "attrSize"], DomainContextHandler.Optional)])}
308
309What do ``Optional`` and ``Required`` mean? Say that you used the
310scatter plot on the data with attributes A, B, C and D; A and B are
311used for the x and y axis and D defined the colors of examples. Now
312you load a new data with attributes A, B, E, and F. The same context
313can be used - A and B will again be shown on x and y axis and the
314default (the one set by :obj:`self.initAttrValues`) will be used
315for the color since the attribute D is missing in the new data. Now
316comes the third data set, which only has attributes A, D and E. The
317context now can't be reused since the attribute used for the
318*required* :obj:`attrY` (the y axis) is missing.
319
320OK, now it is time to be a bit formal. As said,
321:obj:`contextHandlers` is a dictionary and the values in it need
322to be context handlers derived from the abstract class
323:obj:`ContextHandler`. The way it is declared of course depends
324upon its constructor, so the above applies only to the usual
325:obj:`DomainContextHandler`.
326
327:class:`DomainContextHandler`'s constructor has the following arguments
328
329`contextName`
330   The name of the context; it should consist of letters and digits (it is
331   used as a part of a variable name). In case the widget has multiple
332   contexts, they should have unique names. In most cases there will be only
333   one context, so you can leave it empty.
334
335`fields`
336   The names of the attributes to be saved and the corresponding flags. They
337   are described in more details below.
338
339`cloneIfImperfect`
340   States that when the context doesn't match perfectly, that is, unless the
341   domain is exactly the same as the domain from which the context was
342   originally created, :obj:`openContext` shouldn't reuse a context but create
343   a copy of the best matching context instead. Default is :obj:`True`.
344
345`loadImperfect`
346   tells whether the contexts that do not match perfectly (see above) should
347   be used or not. Default is :obj:`True`.
348
349`findImperfect`
350   Tells whether imperfect contexts match at all or not (this flag is
351   somewhat confused with :obj:`loadImperfect`, but it may come useful some
352   day). Default is :obj:`True` again.
353
354`syncWithGlobal`
355   Tells whether instances of this widget should have a shared list of
356   contexts (default). The alternative is that each keeps its own list;
357   each individual list is merged with the global when the widget is deleted
358   from the canvas (or when the canvas is closed). This setting only applies
359   to canvas, while in saved applications widgets always have separate settings
360   lists.
361
362`maxAttributesToPickle`
363   To keep the size of the context file small, settings for domains exceeding
364   a certain number of attributes are not pickled. Default is 100, but you can
365   increase (or decrease this) if you need to.
366
367
368The truly interesting argument is :obj:`fields`. It roughly corresponds to the
369:obj:`settingsList` in that each element specifies one widget attribute to be
370saved. The elements of :obj:`fields` can be strings, tuples and/or instances of
371:obj:`ContextField` (whatever you give, it gets automatically converted to the
372latter). When given as tuples, they should consist of two elements, the field
373name (just like in :obj:`settingsList`) and a flag. Here are the possible flags:
374
375* :obj:`DomainContextHandler.Optional`,
376  :obj:`DomainContextHandler.SelectedRequired` and
377  :obj:`DomainContextHandler.Required` state whether the attribute is optional
378  or required, as explained above. Default is :obj:`Required`.
379  :obj:`DomainContextHandler.SelectedRequired` is applicable only if the
380  control is a list box, where it means that the attributes that are selected
381  are required while the other attributes from the list are not.
382
383* :obj:`DomainContextHandler.NotAttribute` the setting is not an attribute
384  name. You can essentially make a check box context dependent, but we very
385  strongly dissuade from this since it can really confuse the user if some
386  check boxes change with the data while most do not.
387
388* :obj:`DomainContextHandler.List` tells that the attribute corresponds to a
389  list box.
390
391
392Flags can be combined, so to specify a list in which all attributes
393are required, you would give :obj:`DomainContextHandler.List +
394DomainContextHandler.Required`. Since this combination is
395common, :obj:`DomainContextHandler.RequiredList` can be used
396instead.
397
398There are two shortcuts. The default flag is
399:obj:`DomainContextHandler.Required`. If your attribute is like
400this (as most are), you can give only its name instead of a
401tuple. This is how :obj:`"attrX"` and :obj:`"attrY"` are
402given in the scatter plot. If there are multiple attributes with the
403same flags, you can specify them with a tuple in which the first
404element is not a string but a list of strings. We have seen this trick
405in the scatter plot, too.
406
407But the tuples are actually a shortcut for instances of
408:obj:`ContextField`. When you say :obj:`"attrX"` this is actually
409:obj:`ContextField("attrX", DomainContextHandler.Required)`
410
411
412*****************************
413Defining New Context Handlers
414*****************************
415
416Avoid it if you can. If you can't, here's the list of the methods you may need
417to implement. You may want to copy as much from the :obj:`DomainContextHandler`
418as you can.
419
420
421:obj:`__init__`
422   Has the same arguments as the :obj:`DomainContextHandler`'s, except for the
423   :obj:`fields`.
424
425:obj:`newContext()`
426   Creates and returns a new context. In :obj:`ContextHandler` it returns an
427   instance of :obj:`Context`; you probably won't need to change this.
428
429:obj:`openContext(widget, *args)`
430   The method is given a widget and some additional arguments based on which
431   the contexts are compared. In case of :obj:`DomainContextHandler` this is
432   a domain. There can be one or more such arguments. Note that the method
433   :obj:`openContext` which we talked about above is a method of
434   :obj:`OWBaseWidget`, while here we describe a method of context handlers.
435   Actually, :obj:`OWBaseWidget.openContext(self,contextName, *args)` calls
436   the context handler's, passing it's :obj:`self` and :obj:`*args`.
437
438   It needs to find a matching context and copy its settings to the widget or
439   construct a new context and copy the settings from the widget. Also, when an
440   old context is reused, it should be moved to the beginning of the list.
441   :obj:`ContextHandler` already defines this method, which should usually
442   suffice. :obj:`DomainContextHandler` adds very little to it.
443
444:obj:`closeContext`
445   Copies the settings from the widget by calling :obj:`settingsFromWidget`.
446   You probably won't need to overwrite it.
447
448:obj:`match`
449   The method is called by :obj:`openContext` to find a matching context.
450   Given an existing context and the arguments that were given to
451   :obj:`openContext` (for instance, a domain), it should decide whether the
452   context matches or not. If it returns 2, it is a perfect match (e.g.
453   domains are the same). If it returns 0, the context is not applicable
454   (e.g. some of the required attributes are missing). In case it returns a
455   number between 0 and 1 (excluding 0), the higher the number the better the
456   match. :obj:`openContext` will use the best matching context (or the
457   perfect one, if found).
458
459:obj:`settingsToWidget` / :obj:`settingsFromWidget`
460   Copy the settings to and from the widget.
461
462:obj:`fastSave`
463   This function is called by the widget's :obj:`__setattr__` each time any
464   widget's variable is changed to immediately synchronize the context with
465   the state of the widget. The method is really needed only when
466   :obj:`syncWithGlobal` is set. When the context is closed,
467   :obj:`closeContext` will save the settings anyway.
468
469:obj:`cloneContext`
470   Given an existing context, it prepares and returns a copy. The method is
471   optional; :obj:`copy.deepcopy` can be used instead.
472
473
474***************************
475Saving and loading settings
476***************************
477
478Settings can be saved in two different places. Orange Canvas save
479settings in .ini files in its application data directory. Each widget type has
480a separate file; for instance, the scatter plot's settings are saved in
481:obj:`ScatterPlot.ini`. Saved schemas and applications save
482settings in .sav files; the .sav file is placed in the same directory
483as the schema or application, has the same name (except for the
484extension) and contains the settings for all widgets in the
485schema/application.
486
487Saving and loading is done automatically by canvas or the
488application. In a very rare case you need it to run these operations
489manually, the functions involved are :obj:`loadSettings(self, file=None)`,
490:obj:`saveSettings(self, file=None)`, :obj:`loadSettingsStr(self, str)`,
491:obj:`saveSettingsStr(self)`. The first two load and save from
492the file; if not given, the default name (widget's name +
493:obj:`.ini`) is used. They are called by the canvas, never by a
494schema or an application. The last two load and save from a string and
495are used by schemas and applications. All the functions are defined as
496methods of :obj:`OWBaseWidget`, which all other widgets are
497derived from.
Note: See TracBrowser for help on using the repository browser.