source: orange/docs/extend-widgets/rst/settings.rst @ 11881:99bec0d8a70d

Revision 11881:99bec0d8a70d, 21.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 5 weeks ago (diff)

More fixes to widget development manual code snippets.

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