source: orange/Orange/OrangeCanvas/application/settings.py @ 11250:3b499a088cfc

Revision 11250:3b499a088cfc, 14.8 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Added user settings/preferences dialog.

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, QSettings,
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.__settings = config.settings()
197        self.__setupUi()
198
199    def __setupUi(self):
200        """Set up the UI.
201        """
202        if self.__macUnified:
203            self.tab = QToolBar()
204
205            self.addToolBar(Qt.TopToolBarArea, self.tab)
206            self.setUnifiedTitleAndToolBarOnMac(True)
207
208            # This does not seem to work
209            self.setWindowFlags(self.windowFlags() & \
210                                ~Qt.MacWindowToolBarButtonHint)
211
212            self.tab.actionTriggered[QAction].connect(
213                self.__macOnToolBarAction
214            )
215
216            central = QStackedWidget()
217
218            central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
219        else:
220            self.tab = central = QTabWidget(self)
221
222        self.stack = central
223
224        self.setCentralWidget(central)
225
226        # General Tab
227        tab = QWidget()
228        self.addTab(tab, self.tr("General"),
229                    toolTip=self.tr("General Options"))
230
231        form = QFormLayout()
232        tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
233
234        links = QWidget(self, objectName="links")
235        links.setLayout(QVBoxLayout())
236        links.layout().setContentsMargins(0, 0, 0, 0)
237
238        cb_show = QCheckBox(
239            self.tr("Show channel names between widgets"),
240            objectName="show-channel-names",
241            toolTip=self.tr("Show source and sink channel names "
242                            "over the links.")
243        )
244
245        cb_state = QCheckBox(
246            self.tr("Show link state"),
247            objectName="show-link-state",
248            toolTip=self.tr("Show link state")
249        )
250
251        self.bind(cb_show, "checked", "schemeedit/show-channel-names")
252        self.bind(cb_state, "checked", "schemeedit/show-link-state")
253
254        links.layout().addWidget(cb_show)
255        links.layout().addWidget(cb_state)
256
257        form.addRow(self.tr("Links"), links)
258
259        quickmenu = QWidget(self, objectName="quickmenu-options")
260        quickmenu.setLayout(QVBoxLayout())
261        quickmenu.layout().setContentsMargins(0, 0, 0, 0)
262
263        cb1 = QCheckBox(self.tr("On double click"),
264                        toolTip=self.tr("Open quick menu on a double click "
265                                        "on an empty spot in the canvas"))
266
267        cb2 = QCheckBox(self.tr("On left click"),
268                        toolTip=self.tr("Open quick menu on a left click "
269                                        "on an empty spot in the canvas"))
270
271        cb3 = QCheckBox(self.tr("On space key press"),
272                        toolTip=self.tr("On Space key press while the mouse"
273                                        "is hovering over the canvas."))
274
275        cb4 = QCheckBox(self.tr("On any key press"),
276                        toolTip=self.tr("On any key press while the mouse"
277                                        "is hovering over the canvas."))
278
279        self.bind(cb1, "checked", "quickmenu/trigger-on-double-click")
280        self.bind(cb2, "checked", "quickmenu/trigger-on-left-click")
281        self.bind(cb3, "checked", "quickmenu/trigger-on-space-key")
282        self.bind(cb3, "checked", "quickmenu/trigger-on-any-key")
283
284        quickmenu.layout().addWidget(cb1)
285        quickmenu.layout().addWidget(cb2)
286        quickmenu.layout().addWidget(cb3)
287        quickmenu.layout().addWidget(cb4)
288
289        form.addRow(self.tr("Open quick menu on"), quickmenu)
290
291        startup = QWidget(self, objectName="startup-group")
292        startup.setLayout(QVBoxLayout())
293        startup.layout().setContentsMargins(0, 0, 0, 0)
294
295        cb_splash = QCheckBox(self.tr("Show splash screen"), self,
296                              objectName="show-splash-screen")
297
298        cb_welcome = QCheckBox(self.tr("Show welcome screen"), self,
299                                objectName="show-welcome-screen")
300
301        self.bind(cb_splash, "checked", "startup/show-splash-screen")
302        self.bind(cb_welcome, "checked", "startup/show-welcome-screen")
303
304        startup.layout().addWidget(cb_splash)
305        startup.layout().addWidget(cb_welcome)
306
307        form.addRow(self.tr("On startup"), startup)
308
309        toolbox = QWidget(self, objectName="toolbox-group")
310        toolbox.setLayout(QVBoxLayout())
311        toolbox.layout().setContentsMargins(0, 0, 0, 0)
312
313        exclusive = QCheckBox(self.tr("Exclusive"),
314                              toolTip="Only one tab can be opened at a time.")
315
316        floatable = QCheckBox(self.tr("Floatable"),
317                              toolTip="Toolbox can be undocked.")
318
319        self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive")
320        self.bind(floatable, "checked", "mainwindow/toolbox-dock-floatable")
321
322        toolbox.layout().addWidget(exclusive)
323        toolbox.layout().addWidget(floatable)
324
325        form.addRow(self.tr("Tool box"), toolbox)
326        tab.setLayout(form)
327
328        # Output Tab
329        tab = QWidget()
330        self.addTab(tab, self.tr("Output"),
331                    toolTip="Output Redirection")
332
333        form = QFormLayout()
334        box = QWidget(self, objectName="streams")
335        layout = QVBoxLayout()
336        layout.setContentsMargins(0, 0, 0, 0)
337
338        cb1 = QCheckBox(self.tr("Standard output"))
339        cb2 = QCheckBox(self.tr("Standard error"))
340
341        self.bind(cb1, "checked", "output/redirect-stdout")
342        self.bind(cb2, "checked", "output/redirect-stderr")
343
344        layout.addWidget(cb1)
345        layout.addWidget(cb2)
346        box.setLayout(layout)
347
348        form.addRow(self.tr("Redirect output"), box)
349
350        box = QWidget()
351        layout = QVBoxLayout()
352        layout.setContentsMargins(0, 0, 0, 0)
353        combo = QComboBox()
354        combo.addItems([self.tr("Critical"),
355                        self.tr("Error"),
356                        self.tr("Warn"),
357                        self.tr("Info"),
358                        self.tr("Debug")])
359
360        cb = QCheckBox(self.tr("Show output on 'Error'"),
361                       objectName="focus-on-error")
362
363        self.bind(combo, "currentIndex", "logging/level")
364        self.bind(cb, "checked", "output/show-on-error")
365
366        layout.addWidget(combo)
367        layout.addWidget(cb)
368        box.setLayout(layout)
369
370        form.addRow(self.tr("Logging"), box)
371
372        box = QWidget()
373        layout = QVBoxLayout()
374        layout.setContentsMargins(0, 0, 0, 0)
375
376        cb1 = QCheckBox(self.tr("Stay on top"),
377                        objectName="stay-on-top")
378
379        cb2 = QCheckBox(self.tr("Dockable"),
380                        objectName="output-dockable")
381
382        self.bind(cb1, "checked", "output/stay-on-top")
383        self.bind(cb2, "checked", "output/dockable")
384
385        layout.addWidget(cb1)
386        layout.addWidget(cb2)
387        box.setLayout(layout)
388
389        form.addRow(self.tr("Output window"), box)
390
391        box = QWidget()
392        layout = QVBoxLayout()
393        layout.setContentsMargins(0, 0, 0, 0)
394
395        cb1 = QCheckBox(self.tr("Stay on top"),
396                        objectName="help-stay-on-top")
397
398        cb2 = QCheckBox(self.tr("Dockable"),
399                        objectName="help-dockable")
400
401        self.bind(cb1, "checked", "help/stay-on-top")
402        self.bind(cb2, "checked", "help/dockable")
403
404        layout.addWidget(cb1)
405        layout.addWidget(cb2)
406        box.setLayout(layout)
407
408        form.addRow(self.tr("Help window"), box)
409
410        tab.setLayout(form)
411
412        # Widget Category
413        tab = QWidget(self, objectName="widget-category")
414        self.addTab(tab, self.tr("Widget Categories"))
415
416    def addTab(self, widget, text, toolTip=None, icon=None):
417        if self.__macUnified:
418            action = QAction(text, self)
419
420            if toolTip:
421                action.setToolTip(toolTip)
422
423            if icon:
424                action.setIcon(toolTip)
425            action.setData(len(self.tab.actions()))
426
427            self.tab.addAction(action)
428
429            self.stack.addWidget(widget)
430        else:
431            i = self.tab.addTab(widget, text)
432
433            if toolTip:
434                self.tab.setTabToolTip(i, toolTip)
435
436            if icon:
437                self.tab.setTabIcon(i, icon)
438
439    def keyPressEvent(self, event):
440        if event.key() == Qt.Key_Escape:
441            self.hide()
442            self.deleteLater()
443
444    def bind(self, source, source_property, key, transformer=None):
445        target = UserDefaultsPropertyBinding(self.__settings, key)
446        source = PropertyBinding(source, source_property)
447        source.set(target.get())
448
449        self._manager.bind(target, source)
450
451    def commit(self):
452        self._manager.commit()
453
454    def revert(self):
455        self._manager.revert()
456
457    def reset(self):
458        for target, source in self._manager.bindings():
459            try:
460                source.reset()
461            except NotImplementedError:
462                # Cannot reset.
463                pass
464            except Exception:
465                log.error("Error reseting %r", source.propertyName,
466                          exc_info=True)
467
468    def exec_(self):
469        loop = QEventLoop()
470        self.show()
471        status = loop.exec_()
472
473    def showEvent(self, event):
474        sh = self.centralWidget().sizeHint()
475        self.resize(sh)
476        return QMainWindow.showEvent(self, event)
477
478    def __macOnToolBarAction(self, action):
479        index, _ = action.data().toInt()
480        self.stack.setCurrentIndex(index)
Note: See TracBrowser for help on using the repository browser.