source: orange/Orange/OrangeCanvas/application/settings.py @ 11488:c48186fbb699

Revision 11488:c48186fbb699, 15.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 12 months ago (diff)

Changed the Clicked quick menu trigger to RightClick.

RevLine 
[11250]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 (
[11299]24    Qt, QEventLoop, QAbstractItemModel, QModelIndex
[11250]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
[11256]196        self.__loop = None
197
[11250]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
[11411]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
[11250]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        self.bind(cb_show, "checked", "schemeedit/show-channel-names")
263
264        links.layout().addWidget(cb_show)
265
266        form.addRow(self.tr("Links"), links)
267
268        quickmenu = QWidget(self, objectName="quickmenu-options")
269        quickmenu.setLayout(QVBoxLayout())
270        quickmenu.layout().setContentsMargins(0, 0, 0, 0)
271
272        cb1 = QCheckBox(self.tr("On double click"),
273                        toolTip=self.tr("Open quick menu on a double click "
274                                        "on an empty spot in the canvas"))
275
[11488]276        cb2 = QCheckBox(self.tr("On right click"),
277                        toolTip=self.tr("Open quick menu on a right click "
[11250]278                                        "on an empty spot in the canvas"))
279
280        cb3 = QCheckBox(self.tr("On space key press"),
281                        toolTip=self.tr("On Space key press while the mouse"
282                                        "is hovering over the canvas."))
283
284        cb4 = QCheckBox(self.tr("On any key press"),
285                        toolTip=self.tr("On any key press while the mouse"
286                                        "is hovering over the canvas."))
287
288        self.bind(cb1, "checked", "quickmenu/trigger-on-double-click")
[11488]289        self.bind(cb2, "checked", "quickmenu/trigger-on-right-click")
[11250]290        self.bind(cb3, "checked", "quickmenu/trigger-on-space-key")
[11256]291        self.bind(cb4, "checked", "quickmenu/trigger-on-any-key")
[11250]292
293        quickmenu.layout().addWidget(cb1)
294        quickmenu.layout().addWidget(cb2)
295        quickmenu.layout().addWidget(cb3)
296        quickmenu.layout().addWidget(cb4)
297
298        form.addRow(self.tr("Open quick menu on"), quickmenu)
299
300        startup = QWidget(self, objectName="startup-group")
301        startup.setLayout(QVBoxLayout())
302        startup.layout().setContentsMargins(0, 0, 0, 0)
303
304        cb_splash = QCheckBox(self.tr("Show splash screen"), self,
305                              objectName="show-splash-screen")
306
307        cb_welcome = QCheckBox(self.tr("Show welcome screen"), self,
308                                objectName="show-welcome-screen")
309
310        self.bind(cb_splash, "checked", "startup/show-splash-screen")
311        self.bind(cb_welcome, "checked", "startup/show-welcome-screen")
312
313        startup.layout().addWidget(cb_splash)
314        startup.layout().addWidget(cb_welcome)
315
316        form.addRow(self.tr("On startup"), startup)
317
318        toolbox = QWidget(self, objectName="toolbox-group")
319        toolbox.setLayout(QVBoxLayout())
320        toolbox.layout().setContentsMargins(0, 0, 0, 0)
321
[11416]322        exclusive = QCheckBox(self.tr("Only one tab can be open at a time"))
[11250]323
324        self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive")
325
326        toolbox.layout().addWidget(exclusive)
327
328        form.addRow(self.tr("Tool box"), toolbox)
329        tab.setLayout(form)
330
331        # Output Tab
332        tab = QWidget()
333        self.addTab(tab, self.tr("Output"),
334                    toolTip="Output Redirection")
335
336        form = QFormLayout()
337        box = QWidget(self, objectName="streams")
338        layout = QVBoxLayout()
339        layout.setContentsMargins(0, 0, 0, 0)
340
341        cb1 = QCheckBox(self.tr("Standard output"))
342        cb2 = QCheckBox(self.tr("Standard error"))
343
344        self.bind(cb1, "checked", "output/redirect-stdout")
345        self.bind(cb2, "checked", "output/redirect-stderr")
346
347        layout.addWidget(cb1)
348        layout.addWidget(cb2)
349        box.setLayout(layout)
350
351        form.addRow(self.tr("Redirect output"), box)
352
353        box = QWidget()
354        layout = QVBoxLayout()
355        layout.setContentsMargins(0, 0, 0, 0)
356        combo = QComboBox()
357        combo.addItems([self.tr("Critical"),
358                        self.tr("Error"),
359                        self.tr("Warn"),
360                        self.tr("Info"),
361                        self.tr("Debug")])
362
363        cb = QCheckBox(self.tr("Show output on 'Error'"),
364                       objectName="focus-on-error")
365
366        self.bind(combo, "currentIndex", "logging/level")
367        self.bind(cb, "checked", "output/show-on-error")
368
369        layout.addWidget(combo)
370        layout.addWidget(cb)
371        box.setLayout(layout)
372
373        form.addRow(self.tr("Logging"), box)
374
375        box = QWidget()
376        layout = QVBoxLayout()
377        layout.setContentsMargins(0, 0, 0, 0)
378
379        cb1 = QCheckBox(self.tr("Stay on top"),
380                        objectName="stay-on-top")
381
382        cb2 = QCheckBox(self.tr("Dockable"),
383                        objectName="output-dockable")
384
385        self.bind(cb1, "checked", "output/stay-on-top")
386        self.bind(cb2, "checked", "output/dockable")
387
388        layout.addWidget(cb1)
389        layout.addWidget(cb2)
390        box.setLayout(layout)
391
392        form.addRow(self.tr("Output window"), box)
393
394        box = QWidget()
395        layout = QVBoxLayout()
396        layout.setContentsMargins(0, 0, 0, 0)
397
[11320]398        cb1 = QCheckBox(self.tr("Open in external browser"),
399                        objectName="open-in-external-browser")
400
401        cb2 = QCheckBox(self.tr("Stay on top"),
[11250]402                        objectName="help-stay-on-top")
403
[11320]404        cb3 = QCheckBox(self.tr("Dockable"),
[11250]405                        objectName="help-dockable")
406
[11320]407        self.bind(cb1, "checked", "help/open-in-external-browser")
408        self.bind(cb2, "checked", "help/stay-on-top")
409        self.bind(cb3, "checked", "help/dockable")
[11250]410
411        layout.addWidget(cb1)
412        layout.addWidget(cb2)
[11320]413        layout.addWidget(cb3)
[11250]414        box.setLayout(layout)
415
416        form.addRow(self.tr("Help window"), box)
417
418        tab.setLayout(form)
419
420        # Widget Category
421        tab = QWidget(self, objectName="widget-category")
422        self.addTab(tab, self.tr("Widget Categories"))
423
424    def addTab(self, widget, text, toolTip=None, icon=None):
425        if self.__macUnified:
426            action = QAction(text, self)
427
428            if toolTip:
429                action.setToolTip(toolTip)
430
431            if icon:
432                action.setIcon(toolTip)
433            action.setData(len(self.tab.actions()))
434
435            self.tab.addAction(action)
436
437            self.stack.addWidget(widget)
438        else:
439            i = self.tab.addTab(widget, text)
440
441            if toolTip:
442                self.tab.setTabToolTip(i, toolTip)
443
444            if icon:
445                self.tab.setTabIcon(i, icon)
446
447    def keyPressEvent(self, event):
448        if event.key() == Qt.Key_Escape:
449            self.hide()
450            self.deleteLater()
451
452    def bind(self, source, source_property, key, transformer=None):
453        target = UserDefaultsPropertyBinding(self.__settings, key)
454        source = PropertyBinding(source, source_property)
455        source.set(target.get())
456
457        self._manager.bind(target, source)
458
459    def commit(self):
460        self._manager.commit()
461
462    def revert(self):
463        self._manager.revert()
464
465    def reset(self):
466        for target, source in self._manager.bindings():
467            try:
468                source.reset()
469            except NotImplementedError:
470                # Cannot reset.
471                pass
472            except Exception:
473                log.error("Error reseting %r", source.propertyName,
474                          exc_info=True)
475
476    def exec_(self):
[11256]477        self.__loop = QEventLoop()
[11250]478        self.show()
[11256]479        status = self.__loop.exec_()
480        self.__loop = None
481        return status
[11250]482
483    def showEvent(self, event):
484        sh = self.centralWidget().sizeHint()
485        self.resize(sh)
486        return QMainWindow.showEvent(self, event)
487
[11256]488    def hideEvent(self, event):
489        QMainWindow.hideEvent(self, event)
[11299]490        if self.__loop is not None:
491            self.__loop.exit(0)
492            self.__loop = None
[11256]493
[11250]494    def __macOnToolBarAction(self, action):
495        index, _ = action.data().toInt()
496        self.stack.setCurrentIndex(index)
Note: See TracBrowser for help on using the repository browser.