source: orange/orange/OrangeWidgets/plot/owplotgui.py @ 9397:08098271fcf1

Revision 9397:08098271fcf1, 19.8 KB checked in by markotoplak, 2 years ago (diff)

Tried to fix owplot button on Qt 4.6. References #1032.

Line 
1'''
2   
3.. index:: plot
4
5######################################
6GUI elements for plots (``owplotgui``)
7######################################
8
9This module contains functions and classes for creating GUI elements commonly used for plots.
10
11.. autoclass:: OrientedWidget
12    :show-inheritance:
13   
14.. autoclass:: StateButtonContainer
15    :show-inheritance:
16   
17.. autoclass:: OWToolbar
18    :show-inheritance:
19   
20.. autoclass:: OWButton
21    :show-inheritance:
22
23.. autoclass:: OrangeWidgets.plot.OWPlotGUI
24    :members:
25
26'''
27
28import os
29import OWGUI
30
31from owconstants import *
32
33from PyQt4.QtGui import QWidget, QToolButton, QGroupBox, QVBoxLayout, QHBoxLayout, QIcon, QMenu, QAction
34from PyQt4.QtCore import Qt, pyqtSignal, QObject, SIGNAL, SLOT
35
36
37class OrientedWidget(QWidget):
38    '''
39        A simple QWidget with a box layout that matches its ``orientation``.
40    '''
41    def __init__(self, orientation, parent):
42        QWidget.__init__(self, parent)
43        if orientation == Qt.Vertical:
44            self._layout = QVBoxLayout()
45        else:
46            self._layout = QHBoxLayout()
47        self.setLayout(self._layout)
48
49class OWToolbar(OrientedWidget):
50    '''
51        A toolbar is a container that can contain any number of buttons. 
52       
53        :param gui: Used to create containers and buttons
54        :type gui: :obj:`.OWPlotGUI`
55       
56        :param text: The name of this toolbar
57        :type text: str
58       
59        :param orientation: The orientation of this toolbar, either Qt.Vertical or Qt.Horizontal
60        :type tex: int
61       
62        :param buttons: A list of button identifiers to be added to this toolbar
63        :type buttons: list of (int or tuple)
64       
65        :param parent: The toolbar's parent widget
66        :type parent: :obj:`.QWidget`
67    '''
68    def __init__(self, gui, text, orientation, buttons, parent, nomargin = False):
69        OrientedWidget.__init__(self, orientation, parent)
70        self.buttons = {}
71        self.groups = {}
72        i = 0
73        n = len(buttons)
74        while i < n:
75            if buttons[i] == gui.StateButtonsBegin:
76                state_buttons = []
77                for j in range(i+1, n):
78                    if buttons[j] == gui.StateButtonsEnd:
79                        s = gui.state_buttons(orientation, state_buttons, self, nomargin)
80                        self.buttons.update(s.buttons)
81                        self.groups[buttons[i+1]] = s
82                        i = j
83                        break
84                    else:
85                        state_buttons.append(buttons[j])
86            elif buttons[i] == gui.Spacing:
87                self.layout().addSpacing(10)
88            elif type(buttons[i] == int):
89                self.buttons[buttons[i]] = gui.tool_button(buttons[i], self)
90            elif len(buttons[i] == 4):
91                gui.tool_button(buttons[i], self)
92            else:
93                self.buttons[buttons[i][0]] = gui.tool_button(buttons[i], self)
94            i = i + 1
95        self.layout().addStretch()
96
97    def select_state(self, state):
98        #NOTHING = 0
99        #ZOOMING = 1
100        #SELECT = 2
101        #SELECT_POLYGON = 3
102        #PANNING = 4
103        #SELECT_RECTANGLE = SELECT
104        #SELECT_RIGHTCLICK = SELECT
105        state_buttons = {0: 11, 1: 11, 2: 13, 3: 13, 4: 12}
106        self.buttons[state_buttons[state]].click()
107   
108    def select_selection_behaviour(self, selection_behaviour):
109        #SelectionAdd = 21
110        #SelectionRemove = 22
111        #SelectionToggle = 23
112        #SelectionOne = 24
113        self.buttons[13]._actions[21 + selection_behaviour].trigger()
114   
115class StateButtonContainer(OrientedWidget):
116    '''
117        This class can contain any number of checkable buttons, of which only one can be selected at any time.
118   
119        :param gui: Used to create containers and buttons
120        :type gui: :obj:`.OWPlotGUI`
121       
122        :param buttons: A list of button identifiers to be added to this toolbar
123        :type buttons: list of (int or tuple)
124       
125        :param orientation: The orientation of this toolbar, either Qt.Vertical or Qt.Horizontal
126        :type tex: int
127       
128        :param parent: The toolbar's parent widget
129        :type parent: :obj:`.QWidget`
130    '''
131    def __init__(self, gui, orientation, buttons, parent, nomargin = False):
132        OrientedWidget.__init__(self, orientation, parent)
133        self.buttons = {}
134        if nomargin:
135            self.layout().setContentsMargins(0, 0, 0, 0)
136        self._clicked_button = None
137        for i in buttons:
138            b = gui.tool_button(i, self)
139            QObject.connect(b, SIGNAL("triggered(QAction*)"), self.button_clicked)
140            self.buttons[i] = b
141            self.layout().addWidget(b)
142           
143    def button_clicked(self, checked):
144        sender = self.sender()
145        self._clicked_button = sender
146        for button in self.buttons.itervalues():
147            button.setDown(button is sender)
148           
149    def button(self, id):
150        return self.buttons[id]
151       
152    def setEnabled(self, enabled):
153        OrientedWidget.setEnabled(self, enabled)
154        if enabled and self._clicked_button:
155            self._clicked_button.click()
156           
157class OWAction(QAction):
158    '''
159      A :obj:.QAction with convenience methods for calling a callback or setting an attribute of the plot.
160    '''
161    def __init__(self, plot, icon_name=None, attr_name='', attr_value=None, callback=None, parent=None):
162        QAction.__init__(self, parent)
163       
164        if type(callback) == str:
165            callback = getattr(plot, callback, None)
166        if callback:
167            QObject.connect(self, SIGNAL("triggered(bool)"), callback)
168        if attr_name:
169            self._plot = plot
170            self.attr_name = attr_name
171            self.attr_value = attr_value
172            QObject.connect(self, SIGNAL("triggered(bool)"), self.set_attribute)
173        if icon_name:
174            self.setIcon(QIcon(os.path.dirname(__file__) + "/../icons/" + icon_name + '.png'))
175            self.setIconVisibleInMenu(True)
176           
177    def set_attribute(self, clicked):
178        setattr(self._plot, self.attr_name, self.attr_value)
179       
180                   
181class OWButton(QToolButton):
182    '''
183        A custom tool button which signal when its down state changes
184    '''
185    def __init__(self, action=None, parent=None):
186        QToolButton.__init__(self, parent)
187        self.setMinimumSize(30, 30)
188        if action:
189            self.setDefaultAction(action)
190       
191    def setDown(self, down):
192        if self.isDown() != down:
193            self.emit(SIGNAL("downChanged(bool)"), down)
194        QToolButton.setDown(self, down)
195   
196class OWPlotGUI:
197    '''
198        This class contains functions to create common user interface elements (QWidgets)
199        for configuration and interaction with the ``plot``.
200       
201        It provides shorter versions of some methods in :obj:`.OWGUI` that are directly related to an :obj:`.OWPlot` object.
202       
203        Normally, you don't have to construct this class manually. Instead, first create the plot,
204        then use the :attr:`.OWPlot.gui` attribute.
205       
206        Most methods in this class have similar arguments, so they are explaned here in a single place.
207       
208        :param widget: The parent widget which will contain the newly created widget.
209        :type widget: QWidget
210       
211        :param id: If ``id`` is an ``int``, a button is constructed from the default table.
212                   Otherwise, ``id`` must be tuple with 5 or 6 elements. These elements
213                   are explained in the next table.
214        :type id: int or tuple
215       
216        :param ids: A list of widget identifiers
217        :type ids: list of id
218       
219        :param text: The text displayed on the widget
220        :type text: str
221       
222        When using widgets that are specific to your visualization and not included here, you have to provide your
223        own widgets id's. They are a tuple with the following members:
224       
225        :param id: An optional unique identifier for the widget.
226                   This is only needed if you want to retrive this widget using :obj:`.OWToolbar.buttons`.
227        :type id: int or str
228       
229        :param text: The text to be displayed on or next to the widget
230        :type text: str
231       
232        :param attr_name: Name of attribute which will be set when the button is clicked.
233                          If this widget is checkable, its check state will be set
234                          according to the current value of this attribute.
235                          If this parameter is empty or None, no attribute will be read or set.
236        :type attr_name: str
237       
238        :param attr_value: The value that will be assigned to the ``attr_name`` when the button is clicked.
239        :type attr: any
240       
241        :param callback: Function to be called when the button is clicked.
242                         If a string is passed as ``callback``, a method by that name of ``plot`` will be called.
243                         If this parameter is empty or ``None``, no function will be called
244        :type callback: str or function
245       
246        :param icon_name: The filename of the icon for this widget, without the '.png' suffix.
247        :type icon_name: str
248       
249    '''
250    def __init__(self, plot):
251        self._plot = plot
252       
253    Spacing = 0
254       
255    ShowLegend = 2
256    ShowFilledSymbols = 3
257    ShowGridLines = 4
258    PointSize = 5
259    AlphaValue = 6
260   
261    Zoom = 11
262    Pan = 12
263    Select = 13
264   
265    ZoomSelection = 15
266   
267    SelectionAdd = 21
268    SelectionRemove = 22
269    SelectionToggle = 23
270    SelectionOne = 24
271   
272    SendSelection = 31
273    ClearSelection = 32
274    ShufflePoints = 33
275   
276    StateButtonsBegin = 35
277    StateButtonsEnd = 36
278   
279    AnimatePlot = 41
280    AnimatePoints = 42
281    AntialiasPlot = 43
282    AntialiasPoints = 44
283    AntialiasLines = 45
284    DisableAnimationsThreshold = 48
285    AutoAdjustPerformance = 49
286   
287    UserButton = 100
288   
289    default_zoom_select_buttons = [
290        StateButtonsBegin,
291            Zoom,
292            Pan, 
293            Select,
294        StateButtonsEnd,
295        Spacing,
296        SendSelection,
297        ClearSelection
298    ]
299   
300    _buttons = {
301        Zoom : ('Zoom', 'state', ZOOMING, None, 'Dlg_zoom'),
302        Pan : ('Pan', 'state', PANNING, None, 'Dlg_pan_hand'),
303        Select : ('Select', 'state', SELECT, None, 'Dlg_arrow'),
304        SelectionAdd : ('Add to selection', 'selection_behavior', SELECTION_ADD, None, 'Dlg_select_add'),
305        SelectionRemove : ('Remove from selection', 'selection_behavior', SELECTION_REMOVE, None, 'Dlg_select_remove'),
306        SelectionToggle : ('Toggle selection', 'selection_behavior', SELECTION_TOGGLE, None, 'Dlg_select_toggle'),
307        SelectionOne : ('Replace selection', 'selection_behavior', SELECTION_REPLACE, None, 'Dlg_arrow'),
308        SendSelection : ('Send selection', None, None, 'send_selection', 'Dlg_send'),
309        ClearSelection : ('Clear selection', None, None, 'clear_selection', 'Dlg_clear'),
310        ShufflePoints : ('ShufflePoints', None, None, 'shuffle_points', 'Dlg_sort')
311    }
312   
313    _check_boxes = {
314        AnimatePlot : ('Animate plot', 'animate_plot', 'update_animations'),
315        AnimatePoints : ('Animate points', 'animate_points', 'update_animations'),
316        AntialiasPlot : ('Antialias plot', 'antialias_plot', 'update_antialiasing'),
317        AntialiasPoints : ('Antialias points', 'antialias_points', 'update_antialiasing'),
318        AntialiasLines : ('Antialias lines', 'antialias_lines', 'update_antialiasing'),
319        AutoAdjustPerformance : ('Disable effects for large data sets', 'auto_adjust_performance', 'update_performance')
320    }
321    '''
322        The list of built-in buttons. It is a map of
323        id : (name, attr_name, attr_value, callback, icon_name)
324       
325        .. seealso:: :meth:`.tool_button`
326    '''
327
328    def _get_callback(self, name):
329        if type(name) == str:
330            return getattr(self._plot, name, self._plot.replot)
331        else:
332            return name
333       
334    def _check_box(self, widget, value, label, cb_name):
335        '''
336            Adds a :obj:`.QCheckBox` to ``widget``.
337            When the checkbox is toggled, the attribute ``value`` of the plot object is set to the checkbox' check state,
338            and the callback ``cb_name`` is called.
339        '''
340        OWGUI.checkBox(widget, self._plot, value, label, callback=self._get_callback(cb_name))
341       
342    def antialiasing_check_box(self, widget):
343        '''
344            Creates a check box that toggles the Antialiasing of the plot
345        '''
346        self._check_box(widget, 'use_antialiasing', 'Use antialiasing', 'update_antialiasing')
347       
348    def show_legend_check_box(self, widget):
349        '''
350            Creates a check box that shows and hides the plot legend
351        '''
352        self._check_box(widget, 'show_legend', 'Show legend', 'update_legend')
353   
354    def filled_symbols_check_box(self, widget):
355        self._check_box(widget, 'show_filled_symbols', 'Show filled symbols', 'update_filled_symbols')
356       
357    def grid_lines_check_box(self, widget):
358        self._check_box(widget, 'show_grid', 'Show gridlines', 'update_grid')
359   
360    def animations_check_box(self, widget):
361        '''
362            Creates a check box that enabled or disables animations
363        '''
364        self._check_box(widget, 'use_animations', 'Use animations', 'update_animations')
365   
366    def _slider(self, widget, value, label, min_value, max_value, step, cb_name):
367        OWGUI.hSlider(widget, self._plot, value, label=label, minValue=min_value, maxValue=max_value, step=step, callback=self._get_callback(cb_name))
368       
369    def point_size_slider(self, widget):
370        '''
371            Creates a slider that controls point size
372        '''
373        self._slider(widget, 'point_width', "Symbol size:   ", 1, 20, 1, 'update_point_size')
374       
375    def alpha_value_slider(self, widget):
376        '''
377            Creates a slider that controls point transparency
378        '''
379        self._slider(widget, 'alpha_value', "Transparency: ", 0, 255, 10, 'update_alpha_value')
380       
381    def point_properties_box(self, widget):
382        '''
383            Creates a box with controls for common point properties.
384            Currently, these properties are point size and transparency.
385        '''
386        return self.create_box([
387            self.PointSize, 
388            self.AlphaValue
389            ], widget, "Point properties")
390       
391    def plot_settings_box(self, widget):
392        '''
393            Creates a box with controls for common plot settings
394        '''
395        return self.create_box([
396            self.ShowLegend,
397            self.ShowFilledSymbols,
398            self.ShowGridLines,
399            ], widget, "Plot settings")
400       
401    _functions = {
402        ShowLegend : show_legend_check_box,
403        ShowFilledSymbols : filled_symbols_check_box,
404        ShowGridLines : grid_lines_check_box,
405        PointSize : point_size_slider,
406        AlphaValue : alpha_value_slider,
407        }
408       
409    def add_widget(self, id, widget):
410        if id in self._functions:
411            self._functions[id](self, widget)
412        elif id in self._check_boxes:
413            label, attr, cb = self._check_boxes[id]
414            self._check_box(widget, attr, label, cb)
415           
416    def add_widgets(self, ids, widget):
417        for id in ids:
418            self.add_widget(id, widget)
419           
420    def create_box(self, ids, widget, name):
421        '''
422            Creates a :obj:`.QGroupBox` with text ``name`` and adds it to ``widget``.
423            The ``ids`` argument is a list of widget ID's that will be added to this box
424        '''
425        box = OWGUI.widgetBox(widget, name)
426        self.add_widgets(ids, box)
427        return box
428       
429    def _expand_id(self, id):
430        if type(id) == int:
431            name, attr_name, attr_value, callback, icon_name = self._buttons[id]
432        elif len(id) == 4:
433            name, attr_name, attr_value, callback, icon_name = id
434            id = -1
435        else:
436            id, name, attr_name, attr_value, callback, icon_name = id
437        return id, name, attr_name, attr_value, callback, icon_name
438       
439    def tool_button(self, id, widget):
440        '''
441            Creates an :obj:`.OWButton` and adds it to the parent ``widget``.
442        '''
443        id, name, attr_name, attr_value, callback, icon_name = self._expand_id(id)
444        if id == OWPlotGUI.Select:
445            b = self.menu_button(self.Select, [self.SelectionOne, self.SelectionAdd, self.SelectionRemove, self.SelectionToggle], widget)
446        else:
447            b = OWButton(parent=widget)
448            ac = OWAction(self._plot, icon_name, attr_name, attr_value, callback, parent=b)
449            b.setDefaultAction(ac)
450        b.setToolTip(name)
451        if widget.layout() is not None:
452            widget.layout().addWidget(b)
453        return b
454       
455    def menu_button(self, main_action_id, ids, widget):
456        '''
457            Creates an :obj:`.OWButton` with a popup-menu and adds it to the parent ``widget``.
458        '''
459        id, name, attr_name, attr_value, callback, icon_name = self._expand_id(main_action_id)
460        b = OWButton(parent=widget)
461        m = QMenu(b)
462        b.setMenu(m)
463        b._actions = {}
464       
465        QObject.connect(m, SIGNAL("triggered(QAction*)"), b, SLOT("setDefaultAction(QAction*)"))
466
467        if main_action_id:
468            main_action = OWAction(self._plot, icon_name, attr_name, attr_value, callback, parent=b)
469            QObject.connect(m, SIGNAL("triggered(QAction*)"), main_action, SLOT("trigger()"))
470       
471        for id in ids:
472            id, name, attr_name, attr_value, callback, icon_name = self._expand_id(id)
473            a = OWAction(self._plot, icon_name, attr_name, attr_value, callback, parent=m)
474            m.addAction(a)
475            b._actions[id] = a
476           
477        if m.actions():
478            b.setDefaultAction(m.actions()[0])
479        elif main_action_id:
480            b.setDefaultAction(main_action)
481           
482       
483        b.setPopupMode(QToolButton.MenuButtonPopup)
484        b.setMinimumSize(40, 30)
485        return b
486       
487    def state_buttons(self, orientation, buttons, widget, nomargin = False):
488        '''
489            This function creates a set of checkable buttons and connects them so that only one
490            may be checked at a time.
491        '''
492        c = StateButtonContainer(self, orientation, buttons, widget, nomargin)
493        if widget.layout() is not None:
494            widget.layout().addWidget(c)
495        return c
496       
497    def toolbar(self, widget, text, orientation, buttons, nomargin = False):
498        '''
499            Creates an :obj:`.OWToolbar` with the specified ``text``, ``orientation`` and ``buttons`` and adds it to ``widget``.
500           
501            .. seealso:: :obj:`.OWToolbar`
502        '''
503        t = OWToolbar(self, text, orientation, buttons, widget, nomargin)
504        if nomargin:
505            t.layout().setContentsMargins(0, 0, 0, 0)
506        if widget.layout() is not None:
507            widget.layout().addWidget(t)
508        return t
509       
510    def zoom_select_toolbar(self, widget, text = 'Zoom / Select', orientation = Qt.Horizontal, buttons = default_zoom_select_buttons, nomargin = False):
511        t = self.toolbar(widget, text, orientation, buttons, nomargin)
512        t.buttons[self.Select].click()
513        return t   
514       
515    def effects_box(self, widget):
516        b = self.create_box([
517            self.AnimatePlot, 
518            self.AnimatePoints,
519            self.AntialiasPlot,
520        #    self.AntialiasPoints,
521        #    self.AntialiasLines,
522            self.AutoAdjustPerformance,
523            self.DisableAnimationsThreshold], widget, "Visual effects")
524        return b
525       
526    def theme_combo_box(self, widget):
527        c = OWGUI.comboBox(widget, self._plot, "theme_name", "Theme", callback = self._plot.update_theme, sendSelectedValue = 1, valueType = str)
528        c.addItem('Default')
529        c.addItem('Light')
530        c.addItem('Dark')
531        return c
Note: See TracBrowser for help on using the repository browser.