source: orange/docs/extend-widgets/rst/settings.rst @ 11424:ccb4a5fac5e1

Revision 11424:ccb4a5fac5e1, 25.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

More fixes to Widgets Development documentation

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