source: orange/Orange/OrangeCanvas/application/settings.py @ 11411:f1d5470c8031

Revision 11411:f1d5470c8031, 15.8 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Added ping and shadow animations for node items.

Line 
1"""
2User settings/preference dialog
3===============================
4
5"""
6import sys
7import logging
8
9from .. import config
10from ..utils.settings import SettingChangedEvent
11from ..utils import toPyObject
12
13from ..utils.propertybindings import (
14    AbstractBoundProperty, PropertyBinding, BindingManager
15)
16
17from PyQt4.QtGui import (
18    QWidget, QMainWindow, QComboBox, QCheckBox, QListView, QTabWidget,
19    QToolBar, QAction, QStackedWidget, QVBoxLayout, QHBoxLayout,
20    QFormLayout, QStandardItemModel, QSizePolicy
21)
22
23from PyQt4.QtCore import (
24    Qt, QEventLoop, QAbstractItemModel, QModelIndex
25)
26
27log = logging.getLogger(__name__)
28
29
30class UserDefaultsPropertyBinding(AbstractBoundProperty):
31    """
32    A Property binding for a setting in a
33    :class:`Orange.OrangeCanvas.utility.settings.Settings` instance.
34
35    """
36    def __init__(self, obj, propertyName, parent=None):
37        AbstractBoundProperty.__init__(self, obj, propertyName, parent)
38
39        obj.installEventFilter(self)
40
41    def get(self):
42        return self.obj.get(self.propertyName)
43
44    def set(self, value):
45        self.obj[self.propertyName] = value
46
47    def eventFilter(self, obj, event):
48        if event.type() == SettingChangedEvent.SettingChanged and \
49                event.key() == self.propertyName:
50            self.notifyChanged()
51
52        return AbstractBoundProperty.eventFilter(self, obj, event)
53
54
55class UserSettingsModel(QAbstractItemModel):
56    """
57    An Item Model for user settings presenting a list of
58    key, setting value entries along with it's status and type.
59
60    """
61    def __init__(self, parent=None, settings=None):
62        QAbstractItemModel.__init__(self, parent)
63
64        self.__settings = settings
65        self.__headers = ["Name", "Status", "Type", "Value"]
66
67    def setSettings(self, settings):
68        if self.__settings != settings:
69            self.__settings = settings
70            self.reset()
71
72    def settings(self):
73        return self.__settings
74
75    def rowCount(self, parent=QModelIndex()):
76        if parent.isValid():
77            return 0
78        elif self.__settings:
79            return len(self.__settings)
80        else:
81            return 0
82
83    def columnCount(self, parent=QModelIndex()):
84        if parent.isValid():
85            return 0
86        else:
87            return len(self.__headers)
88
89    def parent(self, index):
90        return QModelIndex()
91
92    def index(self, row, column=0, parent=QModelIndex()):
93        if parent.isValid() or \
94                column < 0 or column >= self.columnCount() or \
95                row < 0 or row >= self.rowCount():
96            return QModelIndex()
97
98        return self.createIndex(row, column, row)
99
100    def headerData(self, section, orientation, role=Qt.DisplayRole):
101        if section >= 0 and section < 4 and orientation == Qt.Horizontal:
102            if role == Qt.DisplayRole:
103                return self.__headers[section]
104
105        return QAbstractItemModel.headerData(self, section, orientation, role)
106
107    def data(self, index, role=Qt.DisplayRole):
108        if self._valid(index):
109            key = self._keyFromIndex(index)
110            column = index.column()
111            if role == Qt.DisplayRole:
112                if column == 0:
113                    return key
114                elif column == 1:
115                    default = self.__settings.isdefault(key)
116                    return "Default" if default else "User"
117                elif column == 2:
118                    return type(self.__settings.get(key)).__name__
119                elif column == 3:
120                    return self.__settings.get(key)
121                return self
122
123        return None
124
125    def flags(self, index):
126        if self._valid(index):
127            flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
128            if index.column() == 3:
129                return Qt.ItemIsEditable | flags
130            else:
131                return flags
132        return Qt.NoItemFlags
133
134    def setData(self, index, value, role=Qt.EditRole):
135        if self._valid(index) and index.column() == 3:
136            key = self._keyFromIndex(index)
137            value = toPyObject(value)
138            try:
139                self.__settings[key] = value
140            except (TypeError, ValueError) as ex:
141                log.error("Failed to set value (%r) for key %r", value, key,
142                          exc_info=True)
143            else:
144                self.dataChanged.emit(index, index)
145                return True
146
147        return False
148
149    def _valid(self, index):
150        row = index.row()
151        return row >= 0 and row < self.rowCount()
152
153    def _keyFromIndex(self, index):
154        row = index.row()
155        return self.__settings.keys()[row]
156
157
158def container_widget_helper(orientation=Qt.Vertical, spacing=None, margin=0):
159    widget = QWidget()
160    if orientation == Qt.Vertical:
161        layout = QVBoxLayout()
162        widget.setSizePolicy(QSizePolicy.Fixed,
163                             QSizePolicy.MinimumExpanding)
164    else:
165        layout = QHBoxLayout()
166
167    if spacing is not None:
168        layout.setSpacing(spacing)
169
170    if margin is not None:
171        layout.setContentsMargins(0, 0, 0, 0)
172
173    widget.setLayout(layout)
174
175    return widget
176
177
178class UserSettingsDialog(QMainWindow):
179    """
180    A User Settings/Defaults dialog.
181
182    """
183    MAC_UNIFIED = True
184
185    def __init__(self, parent=None, **kwargs):
186        QMainWindow.__init__(self, parent, **kwargs)
187        self.setWindowFlags(Qt.Dialog)
188        self.setWindowModality(Qt.ApplicationModal)
189
190        self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize)
191
192        self.__macUnified = sys.platform == "darwin" and self.MAC_UNIFIED
193        self._manager = BindingManager(self,
194                                       submitPolicy=BindingManager.AutoSubmit)
195
196        self.__loop = None
197
198        self.__settings = config.settings()
199        self.__setupUi()
200
201    def __setupUi(self):
202        """Set up the UI.
203        """
204        if self.__macUnified:
205            self.tab = QToolBar()
206
207            self.addToolBar(Qt.TopToolBarArea, self.tab)
208            self.setUnifiedTitleAndToolBarOnMac(True)
209
210            # This does not seem to work
211            self.setWindowFlags(self.windowFlags() & \
212                                ~Qt.MacWindowToolBarButtonHint)
213
214            self.tab.actionTriggered[QAction].connect(
215                self.__macOnToolBarAction
216            )
217
218            central = QStackedWidget()
219
220            central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
221        else:
222            self.tab = central = QTabWidget(self)
223
224        self.stack = central
225
226        self.setCentralWidget(central)
227
228        # General Tab
229        tab = QWidget()
230        self.addTab(tab, self.tr("General"),
231                    toolTip=self.tr("General Options"))
232
233        form = QFormLayout()
234        tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
235
236        nodes = QWidget(self, objectName="nodes")
237        nodes.setLayout(QVBoxLayout())
238        nodes.layout().setContentsMargins(0, 0, 0, 0)
239
240        cb_anim = QCheckBox(
241            self.tr("Enable node animations"),
242            objectName="enable-node-animations",
243            toolTip=self.tr("Enable shadow and ping animations for node "
244                            "items in the scheme.")
245        )
246        self.bind(cb_anim, "checked", "schemeedit/enable-node-animations")
247        nodes.layout().addWidget(cb_anim)
248
249        form.addRow(self.tr("Nodes"), nodes)
250
251        links = QWidget(self, objectName="links")
252        links.setLayout(QVBoxLayout())
253        links.layout().setContentsMargins(0, 0, 0, 0)
254
255        cb_show = QCheckBox(
256            self.tr("Show channel names between widgets"),
257            objectName="show-channel-names",
258            toolTip=self.tr("Show source and sink channel names "
259                            "over the links.")
260        )
261
262        cb_state = QCheckBox(
263            self.tr("Show link state"),
264            objectName="show-link-state",
265            toolTip=self.tr("Show link state")
266        )
267
268        self.bind(cb_show, "checked", "schemeedit/show-channel-names")
269        self.bind(cb_state, "checked", "schemeedit/show-link-state")
270
271        links.layout().addWidget(cb_show)
272        links.layout().addWidget(cb_state)
273
274        form.addRow(self.tr("Links"), links)
275
276        quickmenu = QWidget(self, objectName="quickmenu-options")
277        quickmenu.setLayout(QVBoxLayout())
278        quickmenu.layout().setContentsMargins(0, 0, 0, 0)
279
280        cb1 = QCheckBox(self.tr("On double click"),
281                        toolTip=self.tr("Open quick menu on a double click "
282                                        "on an empty spot in the canvas"))
283
284        cb2 = QCheckBox(self.tr("On left click"),
285                        toolTip=self.tr("Open quick menu on a left click "
286                                        "on an empty spot in the canvas"))
287
288        cb3 = QCheckBox(self.tr("On space key press"),
289                        toolTip=self.tr("On Space key press while the mouse"
290                                        "is hovering over the canvas."))
291
292        cb4 = QCheckBox(self.tr("On any key press"),
293                        toolTip=self.tr("On any key press while the mouse"
294                                        "is hovering over the canvas."))
295
296        self.bind(cb1, "checked", "quickmenu/trigger-on-double-click")
297        self.bind(cb2, "checked", "quickmenu/trigger-on-left-click")
298        self.bind(cb3, "checked", "quickmenu/trigger-on-space-key")
299        self.bind(cb4, "checked", "quickmenu/trigger-on-any-key")
300
301        quickmenu.layout().addWidget(cb1)
302        quickmenu.layout().addWidget(cb2)
303        quickmenu.layout().addWidget(cb3)
304        quickmenu.layout().addWidget(cb4)
305
306        form.addRow(self.tr("Open quick menu on"), quickmenu)
307
308        startup = QWidget(self, objectName="startup-group")
309        startup.setLayout(QVBoxLayout())
310        startup.layout().setContentsMargins(0, 0, 0, 0)
311
312        cb_splash = QCheckBox(self.tr("Show splash screen"), self,
313                              objectName="show-splash-screen")
314
315        cb_welcome = QCheckBox(self.tr("Show welcome screen"), self,
316                                objectName="show-welcome-screen")
317
318        self.bind(cb_splash, "checked", "startup/show-splash-screen")
319        self.bind(cb_welcome, "checked", "startup/show-welcome-screen")
320
321        startup.layout().addWidget(cb_splash)
322        startup.layout().addWidget(cb_welcome)
323
324        form.addRow(self.tr("On startup"), startup)
325
326        toolbox = QWidget(self, objectName="toolbox-group")
327        toolbox.setLayout(QVBoxLayout())
328        toolbox.layout().setContentsMargins(0, 0, 0, 0)
329
330        exclusive = QCheckBox(self.tr("Exclusive"),
331                              toolTip="Only one tab can be opened at a time.")
332
333        floatable = QCheckBox(self.tr("Floatable"),
334                              toolTip="Toolbox can be undocked.")
335
336        self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive")
337        self.bind(floatable, "checked", "mainwindow/toolbox-dock-floatable")
338
339        toolbox.layout().addWidget(exclusive)
340        toolbox.layout().addWidget(floatable)
341
342        form.addRow(self.tr("Tool box"), toolbox)
343        tab.setLayout(form)
344
345        # Output Tab
346        tab = QWidget()
347        self.addTab(tab, self.tr("Output"),
348                    toolTip="Output Redirection")
349
350        form = QFormLayout()
351        box = QWidget(self, objectName="streams")
352        layout = QVBoxLayout()
353        layout.setContentsMargins(0, 0, 0, 0)
354
355        cb1 = QCheckBox(self.tr("Standard output"))
356        cb2 = QCheckBox(self.tr("Standard error"))
357
358        self.bind(cb1, "checked", "output/redirect-stdout")
359        self.bind(cb2, "checked", "output/redirect-stderr")
360
361        layout.addWidget(cb1)
362        layout.addWidget(cb2)
363        box.setLayout(layout)
364
365        form.addRow(self.tr("Redirect output"), box)
366
367        box = QWidget()
368        layout = QVBoxLayout()
369        layout.setContentsMargins(0, 0, 0, 0)
370        combo = QComboBox()
371        combo.addItems([self.tr("Critical"),
372                        self.tr("Error"),
373                        self.tr("Warn"),
374                        self.tr("Info"),
375                        self.tr("Debug")])
376
377        cb = QCheckBox(self.tr("Show output on 'Error'"),
378                       objectName="focus-on-error")
379
380        self.bind(combo, "currentIndex", "logging/level")
381        self.bind(cb, "checked", "output/show-on-error")
382
383        layout.addWidget(combo)
384        layout.addWidget(cb)
385        box.setLayout(layout)
386
387        form.addRow(self.tr("Logging"), box)
388
389        box = QWidget()
390        layout = QVBoxLayout()
391        layout.setContentsMargins(0, 0, 0, 0)
392
393        cb1 = QCheckBox(self.tr("Stay on top"),
394                        objectName="stay-on-top")
395
396        cb2 = QCheckBox(self.tr("Dockable"),
397                        objectName="output-dockable")
398
399        self.bind(cb1, "checked", "output/stay-on-top")
400        self.bind(cb2, "checked", "output/dockable")
401
402        layout.addWidget(cb1)
403        layout.addWidget(cb2)
404        box.setLayout(layout)
405
406        form.addRow(self.tr("Output window"), box)
407
408        box = QWidget()
409        layout = QVBoxLayout()
410        layout.setContentsMargins(0, 0, 0, 0)
411
412        cb1 = QCheckBox(self.tr("Open in external browser"),
413                        objectName="open-in-external-browser")
414
415        cb2 = QCheckBox(self.tr("Stay on top"),
416                        objectName="help-stay-on-top")
417
418        cb3 = QCheckBox(self.tr("Dockable"),
419                        objectName="help-dockable")
420
421        self.bind(cb1, "checked", "help/open-in-external-browser")
422        self.bind(cb2, "checked", "help/stay-on-top")
423        self.bind(cb3, "checked", "help/dockable")
424
425        layout.addWidget(cb1)
426        layout.addWidget(cb2)
427        layout.addWidget(cb3)
428        box.setLayout(layout)
429
430        form.addRow(self.tr("Help window"), box)
431
432        tab.setLayout(form)
433
434        # Widget Category
435        tab = QWidget(self, objectName="widget-category")
436        self.addTab(tab, self.tr("Widget Categories"))
437
438    def addTab(self, widget, text, toolTip=None, icon=None):
439        if self.__macUnified:
440            action = QAction(text, self)
441
442            if toolTip:
443                action.setToolTip(toolTip)
444
445            if icon:
446                action.setIcon(toolTip)
447            action.setData(len(self.tab.actions()))
448
449            self.tab.addAction(action)
450
451            self.stack.addWidget(widget)
452        else:
453            i = self.tab.addTab(widget, text)
454
455            if toolTip:
456                self.tab.setTabToolTip(i, toolTip)
457
458            if icon:
459                self.tab.setTabIcon(i, icon)
460
461    def keyPressEvent(self, event):
462        if event.key() == Qt.Key_Escape:
463            self.hide()
464            self.deleteLater()
465
466    def bind(self, source, source_property, key, transformer=None):
467        target = UserDefaultsPropertyBinding(self.__settings, key)
468        source = PropertyBinding(source, source_property)
469        source.set(target.get())
470
471        self._manager.bind(target, source)
472
473    def commit(self):
474        self._manager.commit()
475
476    def revert(self):
477        self._manager.revert()
478
479    def reset(self):
480        for target, source in self._manager.bindings():
481            try:
482                source.reset()
483            except NotImplementedError:
484                # Cannot reset.
485                pass
486            except Exception:
487                log.error("Error reseting %r", source.propertyName,
488                          exc_info=True)
489
490    def exec_(self):
491        self.__loop = QEventLoop()
492        self.show()
493        status = self.__loop.exec_()
494        self.__loop = None
495        return status
496
497    def showEvent(self, event):
498        sh = self.centralWidget().sizeHint()
499        self.resize(sh)
500        return QMainWindow.showEvent(self, event)
501
502    def hideEvent(self, event):
503        QMainWindow.hideEvent(self, event)
504        if self.__loop is not None:
505            self.__loop.exit(0)
506            self.__loop = None
507
508    def __macOnToolBarAction(self, action):
509        index, _ = action.data().toInt()
510        self.stack.setCurrentIndex(index)
Note: See TracBrowser for help on using the repository browser.