def run(self): """ Reimplemented from `QRunnable.run` """ self.eventLoop = QEventLoop() self.eventLoop.processEvents() # Move the task to the current thread so it's events, signals, slots # are triggered from this thread. assert self.task.thread() is _TaskDepotThread.instance() QMetaObject.invokeMethod( self.task.thread(), "transfer", Qt.BlockingQueuedConnection, Q_ARG(object, self.task), Q_ARG(object, QThread.currentThread()), ) self.eventLoop.processEvents() # Schedule task.run from the event loop. self.task.start() # Quit the loop and exit when task finishes or is cancelled. self.task.finished.connect(self.eventLoop.quit) self.task.cancelled.connect(self.eventLoop.quit) self.eventLoop.exec_()
def exec_(self): self.__loop = QEventLoop() self.show() status = self.__loop.exec_() self.__loop = None refresh_proxies() return status
def __init__(self, boundsig, **kwargs): super(QSignalSpy, self).__init__(**kwargs) from AnyQt.QtCore import QEventLoop, QTimer self.__boundsig = boundsig self.__boundsig.connect(lambda *args: self.__record(*args)) self.__recorded = [] # type: List[List[Any]] self.__loop = QEventLoop() self.__timer = QTimer(self, singleShot=True) self.__timer.timeout.connect(self.__loop.quit)
def exec_(self, pos=None): self.popup(pos) self.__loop = QEventLoop() self.__action = None self.__loop.exec_() self.__loop = None if self.__action is not None: action = self.__action else: action = None return action
def exec_(self, pos=None): # type: (Optional[QPoint]) -> Optional[QAction] self.popup(pos) self.__loop = QEventLoop() self.__action = None self.__loop.exec_() self.__loop = None if self.__action is not None: action = self.__action else: action = None return action
def test_one(): ref = SimpleNamespace() # hold single reference to Emitter obj ref.obj = Emitter() # enqueue 200 meta call events to the obj for i in range(200): ref.obj.schedule_emit() # event signaling the event loop is about to be entered event = threading.Event() def clear_obj(ref=ref): # wait for main thread to signal it is about to enter the event loop event.wait() del ref.obj # clear the last/single ref to obj executor.submit(clear_obj) loop = QEventLoop() QTimer.singleShot(0, loop.quit) # bytecode optimizations, reduce the time between event.set and # exec to minimum set = event.set exec = loop.exec set() # signal/unblock the worker; exec() # enter event loop to process the queued meta calls
def run(self): """ Reimplemented from `QRunnable.run` """ self.eventLoop = QEventLoop() self.eventLoop.processEvents() # Move the task to the current thread so it's events, signals, slots # are triggered from this thread. assert self.task.thread() is _TaskDepotThread.instance() QMetaObject.invokeMethod( self.task.thread(), "transfer", Qt.BlockingQueuedConnection, Q_ARG(object, self.task), Q_ARG(object, QThread.currentThread()) ) self.eventLoop.processEvents() # Schedule task.run from the event loop. self.task.start() # Quit the loop and exit when task finishes or is cancelled. self.task.finished.connect(self.eventLoop.quit) self.task.cancelled.connect(self.eventLoop.quit) self.eventLoop.exec_()
def read_message_queue(self): """ Update board queue and retrieve most recent messages """ try: recent_history = self.session.messages_history[self. _history_index:] for message in recent_history: if self._stop: return if not isinstance(message, self._events_2_draw): continue if isinstance(message, StateOccurrence): if message.state_name not in self._states_names.keys(): self._states_names[message.state_name] = len( self._states_names) if not (math.isnan(message.start_timestamp) or math.isnan(message.end_timestamp)): bpod_start = int(round(message.start_timestamp * 1000)) bpod_end = int(round(message.end_timestamp * 1000)) self.__add_event( bpod_start, bpod_end, self._states_names[message.state_name], message.state_name) elif isinstance(message, SessionInfo): if message.content == 'SESSION-STARTED': self._session_start_timestamp = message.pc_timestamp elif isinstance(message, EventOccurrence): ts = None # in case we don't find any valid timestamp, don't add the event to the timeline # check which timestamp will be used. host timestamp wins over pc timestamp if message.pc_timestamp is not None: ts = self.timediff_ms(message.pc_timestamp, self._session_start_timestamp) # proceed if we have a valid timestamp if ts is not None: # create a new event slot in the timeline in case current message is entirely new if message.event_name not in self._states_names.keys(): self._states_names[message.event_name] = len( self._states_names) # add a time delta to ts so the event can be shown in the timeline self.__add_event( ts - 10, ts + 10, self._states_names[message.event_name], message.event_name) self._history_index += 1 QEventLoop() except RunSetupError as err: logger.error(str(err), exc_info=True) self._timer.stop()
class QSignalSpy(QObject): """ QSignalSpy(boundsignal) """ def __init__(self, boundsig, **kwargs): super(QSignalSpy, self).__init__(**kwargs) from AnyQt.QtCore import QEventLoop, QTimer self.__boundsig = boundsig self.__boundsig.connect(lambda *args: self.__record(*args)) self.__recorded = [] # type: List[List[Any]] self.__loop = QEventLoop() self.__timer = QTimer(self, singleShot=True) self.__timer.timeout.connect(self.__loop.quit) def __record(self, *args): self.__recorded.append(list(args)) if self.__loop.isRunning(): self.__loop.quit() def signal(self): return _QByteArray(self.__boundsig.signal[1:].encode("latin-1")) def isValid(self): return True def wait(self, timeout=5000): count = len(self) self.__timer.stop() self.__timer.setInterval(timeout) self.__timer.start() self.__loop.exec_() self.__timer.stop() return len(self) != count def __getitem__(self, index): return self.__recorded[index] def __setitem__(self, index, value): self.__recorded.__setitem__(index, value) def __delitem__(self, index): self.__recorded.__delitem__(index) def __len__(self): return len(self.__recorded)
def __init__(self, object, etype, **kwargs): super().__init__(**kwargs) if not isinstance(object, QObject): raise TypeError self.__object = object try: len(etype) except TypeError: etypes = {etype} else: etypes = set(etype) self.__etypes = etypes self.__record = [] self.__loop = QEventLoop() self.__timer = QTimer(self, singleShot=True) self.__timer.timeout.connect(self.__loop.quit) self.__object.installEventFilter(self)
def test_run_query(self): w = AddonManagerDialog() query_res = [ addons._QueryResult("uber-pkg", None), addons._QueryResult( "unter-pkg", Installable("unter-pkg", "0.0.0", "", "", "", [])) ] def query(names): return query_res with patch.object(QMessageBox, "exec_", return_value=QMessageBox.Cancel), \ patch.object(addons, "query_pypi", query): f = w.runQueryAndAddResults(["uber-pkg", "unter-pkg"], ) loop = QEventLoop() f.add_done_callback(qinvoke(lambda f: loop.quit(), loop)) loop.exec() items = w.items() self.assertEqual(items, [Available(query_res[1].installable)])
def exec_(self, pos=None, searchText=""): # type: (Optional[QPoint], str) -> Optional[QAction] """ Execute the menu at position `pos` (in global screen coordinates). Return the triggered :class:`QAction` or `None` if no action was triggered. 'Search' text field is initialized with `searchText` if provided. """ self.popup(pos, searchText) self.setFocus(Qt.PopupFocusReason) self.__triggeredAction = None self.__loop = QEventLoop() self.__loop.exec_() self.__loop.deleteLater() self.__loop = None action = self.__triggeredAction self.__triggeredAction = None return action
def test_py_owned(self): class Obj(QObject, PyOwned): pass executor = ThreadPoolExecutor() ref = SimpleNamespace(obj=Obj()) wref = weakref.ref(ref.obj) event = threading.Event() event.clear() def clear_ref(): del ref.obj event.set() executor.submit(clear_ref) event.wait() self.assertIsNotNone(wref()) self.assertIn(wref(), PyOwned._PyOwned__delete_later_set) loop = QEventLoop() QTimer.singleShot(0, loop.quit) loop.exec() self.assertIsNone(wref())
def exec_(self, pos=None, searchText=""): """ Execute the menu at position `pos` (in global screen coordinates). Return the triggered :class:`QAction` or `None` if no action was triggered. 'Search' text field is initialized with `searchText` if provided. """ self.popup(pos, searchText) self.setFocus(Qt.PopupFocusReason) self.__triggeredAction = None self.__loop = QEventLoop() self.__loop.exec_() self.__loop.deleteLater() self.__loop = None action = self.__triggeredAction self.__triggeredAction = None return action
def __init__(self, boundsig, **kwargs): super(QSignalSpy, self).__init__(**kwargs) from AnyQt.QtCore import QEventLoop, QTimer self.__boundsig = boundsig self.__recorded = recorded = [] # type: List[List[Any]] self.__loop = loop = QEventLoop() self.__timer = QTimer(self, singleShot=True) self.__timer.timeout.connect(self.__loop.quit) def record(*args): # Record the emitted arguments and quit the loop if running. # NOTE: not capturing self from parent scope recorded.append(list(args)) if loop.isRunning(): loop.quit() # Need to keep reference at least for PyQt4 4.11.4, sip 4.16.9 on # python 3.4 (if the signal is emitted during gc collection, and # the boundsignal is a QObject.destroyed signal). self.__record = record boundsig.connect(record)
class _TaskRunnable(QRunnable): """ A QRunnable for running a :class:`Task` by a :class:`ThreadExecutor`. """ def __init__(self, future, task, args, kwargs): QRunnable.__init__(self) self.future = future self.task = task self.args = args self.kwargs = kwargs self.eventLoop = None def run(self): """ Reimplemented from `QRunnable.run` """ self.eventLoop = QEventLoop() self.eventLoop.processEvents() # Move the task to the current thread so it's events, signals, slots # are triggered from this thread. assert self.task.thread() is _TaskDepotThread.instance() QMetaObject.invokeMethod( self.task.thread(), "transfer", Qt.BlockingQueuedConnection, Q_ARG(object, self.task), Q_ARG(object, QThread.currentThread()), ) self.eventLoop.processEvents() # Schedule task.run from the event loop. self.task.start() # Quit the loop and exit when task finishes or is cancelled. self.task.finished.connect(self.eventLoop.quit) self.task.cancelled.connect(self.eventLoop.quit) self.eventLoop.exec_()
class _TaskRunnable(QRunnable): """ A QRunnable for running a :class:`Task` by a :class:`ThreadExecutor`. """ def __init__(self, future, task, args, kwargs): QRunnable.__init__(self) self.future = future self.task = task self.args = args self.kwargs = kwargs self.eventLoop = None def run(self): """ Reimplemented from `QRunnable.run` """ self.eventLoop = QEventLoop() self.eventLoop.processEvents() # Move the task to the current thread so it's events, signals, slots # are triggered from this thread. assert self.task.thread() is _TaskDepotThread.instance() QMetaObject.invokeMethod( self.task.thread(), "transfer", Qt.BlockingQueuedConnection, Q_ARG(object, self.task), Q_ARG(object, QThread.currentThread()) ) self.eventLoop.processEvents() # Schedule task.run from the event loop. self.task.start() # Quit the loop and exit when task finishes or is cancelled. self.task.finished.connect(self.eventLoop.quit) self.task.cancelled.connect(self.eventLoop.quit) self.eventLoop.exec_()
class UserSettingsDialog(QMainWindow): """ A User Settings/Defaults dialog. """ MAC_UNIFIED = True def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.setWindowFlags(Qt.Dialog) self.setWindowModality(Qt.ApplicationModal) self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) self.__macUnified = sys.platform == "darwin" and self.MAC_UNIFIED self._manager = BindingManager(self, submitPolicy=BindingManager.AutoSubmit) self.__loop = None self.__settings = config.settings() self.__setupUi() def __setupUi(self): """Set up the UI. """ if self.__macUnified: self.tab = QToolBar( floatable=False, movable=False, allowedAreas=Qt.TopToolBarArea, ) self.addToolBar(Qt.TopToolBarArea, self.tab) self.setUnifiedTitleAndToolBarOnMac(True) # This does not seem to work self.setWindowFlags(self.windowFlags() & \ ~Qt.MacWindowToolBarButtonHint) self.tab.actionTriggered[QAction].connect( self.__macOnToolBarAction) central = QStackedWidget() central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) else: self.tab = central = QTabWidget(self) # Add a close button to the bottom of the dialog # (to satisfy GNOME 3 which shows the dialog without a title bar). container = container_widget_helper() container.layout().addWidget(central) buttonbox = QDialogButtonBox(QDialogButtonBox.Close) buttonbox.rejected.connect(self.close) container.layout().addWidget(buttonbox) self.setCentralWidget(container) self.stack = central # General Tab tab = QWidget() self.addTab(tab, self.tr("General"), toolTip=self.tr("General Options")) form = FormLayout() tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) nodes = QWidget(self, objectName="nodes") nodes.setLayout(QVBoxLayout()) nodes.layout().setContentsMargins(0, 0, 0, 0) cb_anim = QCheckBox(self.tr("Enable node animations"), objectName="enable-node-animations", toolTip=self.tr( "Enable shadow and ping animations for nodes " "in the workflow.")) self.bind(cb_anim, "checked", "schemeedit/enable-node-animations") nodes.layout().addWidget(cb_anim) form.addRow(self.tr("Nodes"), nodes) links = QWidget(self, objectName="links") links.setLayout(QVBoxLayout()) links.layout().setContentsMargins(0, 0, 0, 0) cb_show = QCheckBox(self.tr("Show channel names between widgets"), objectName="show-channel-names", toolTip=self.tr( "Show source and sink channel names " "over the links.")) self.bind(cb_show, "checked", "schemeedit/show-channel-names") links.layout().addWidget(cb_show) form.addRow(self.tr("Links"), links) quickmenu = QWidget(self, objectName="quickmenu-options") quickmenu.setLayout(QVBoxLayout()) quickmenu.layout().setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("Open on double click"), toolTip=self.tr("Open quick menu on a double click " "on an empty spot in the canvas")) cb2 = QCheckBox(self.tr("Open on right click"), toolTip=self.tr("Open quick menu on a right click " "on an empty spot in the canvas")) cb3 = QCheckBox(self.tr("Open on space key press"), toolTip=self.tr( "Open quick menu on Space key press " "while the mouse is hovering over the canvas.")) cb4 = QCheckBox(self.tr("Open on any key press"), toolTip=self.tr( "Open quick menu on any key press " "while the mouse is hovering over the canvas.")) cb5 = QCheckBox(self.tr("Show categories"), toolTip=self.tr( "In addition to searching, allow filtering " "by categories.")) self.bind(cb1, "checked", "quickmenu/trigger-on-double-click") self.bind(cb2, "checked", "quickmenu/trigger-on-right-click") self.bind(cb3, "checked", "quickmenu/trigger-on-space-key") self.bind(cb4, "checked", "quickmenu/trigger-on-any-key") self.bind(cb5, "checked", "quickmenu/show-categories") quickmenu.layout().addWidget(cb1) quickmenu.layout().addWidget(cb2) quickmenu.layout().addWidget(cb3) quickmenu.layout().addWidget(cb4) quickmenu.layout().addWidget(cb5) form.addRow(self.tr("Quick menu"), quickmenu) startup = QWidget(self, objectName="startup-group") startup.setLayout(QVBoxLayout()) startup.layout().setContentsMargins(0, 0, 0, 0) cb_splash = QCheckBox(self.tr("Show splash screen"), self, objectName="show-splash-screen") cb_welcome = QCheckBox(self.tr("Show welcome screen"), self, objectName="show-welcome-screen") cb_crash = QCheckBox(self.tr("Load crashed scratch workflows"), self, objectName="load-crashed-workflows") self.bind(cb_splash, "checked", "startup/show-splash-screen") self.bind(cb_welcome, "checked", "startup/show-welcome-screen") self.bind(cb_crash, "checked", "startup/load-crashed-workflows") startup.layout().addWidget(cb_splash) startup.layout().addWidget(cb_welcome) startup.layout().addWidget(cb_crash) form.addRow(self.tr("On startup"), startup) toolbox = QWidget(self, objectName="toolbox-group") toolbox.setLayout(QVBoxLayout()) toolbox.layout().setContentsMargins(0, 0, 0, 0) exclusive = QCheckBox(self.tr("Only one tab can be open at a time")) self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive") toolbox.layout().addWidget(exclusive) form.addRow(self.tr("Tool box"), toolbox) tab.setLayout(form) # Style tab tab = StyleConfigWidget() self.addTab(tab, self.tr("&Style"), toolTip="Application style") self.bind(tab, "selectedStyle_", "application-style/style-name") self.bind(tab, "selectedPalette_", "application-style/palette") # Output Tab tab = QWidget() self.addTab(tab, self.tr("Output"), toolTip="Output Redirection") form = FormLayout() combo = QComboBox() combo.addItems([ self.tr("Critical"), self.tr("Error"), self.tr("Warn"), self.tr("Info"), self.tr("Debug") ]) self.bind(combo, "currentIndex", "logging/level") form.addRow(self.tr("Logging"), combo) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("Open in external browser"), objectName="open-in-external-browser") self.bind(cb1, "checked", "help/open-in-external-browser") layout.addWidget(cb1) box.setLayout(layout) form.addRow(self.tr("Help window"), box) tab.setLayout(form) # Categories Tab tab = QWidget() layout = QVBoxLayout() view = QListView(editTriggers=QListView.NoEditTriggers) from .. import registry reg = registry.global_registry() model = QStandardItemModel() settings = QSettings() for cat in reg.categories(): item = QStandardItem() item.setText(cat.name) item.setCheckable(True) visible, _ = category_state(cat, settings) item.setCheckState(Qt.Checked if visible else Qt.Unchecked) model.appendRow([item]) view.setModel(model) layout.addWidget(view) tab.setLayout(layout) model.itemChanged.connect(lambda item: save_category_state( reg.category(str(item.text())), _State(item.checkState() == Qt.Checked, -1), settings)) self.addTab(tab, "Categories") # Add-ons Tab tab = QWidget() self.addTab(tab, self.tr("Add-ons"), toolTip="Settings related to add-on installation") form = FormLayout() conda = QWidget(self, objectName="conda-group") conda.setLayout(QVBoxLayout()) conda.layout().setContentsMargins(0, 0, 0, 0) cb_conda_install = QCheckBox(self.tr("Install add-ons with conda"), self, objectName="allow-conda") self.bind(cb_conda_install, "checked", "add-ons/allow-conda") conda.layout().addWidget(cb_conda_install) form.addRow(self.tr("Conda"), conda) form.addRow(self.tr("Pip"), QLabel("Pip install arguments:")) line_edit_pip = QLineEdit() self.bind(line_edit_pip, "text", "add-ons/pip-install-arguments") form.addRow("", line_edit_pip) tab.setLayout(form) # Network Tab tab = QWidget() self.addTab(tab, self.tr("Network"), toolTip="Settings related to networking") form = FormLayout() line_edit_http_proxy = QLineEdit() self.bind(line_edit_http_proxy, "text", "network/http-proxy") form.addRow("HTTP proxy:", line_edit_http_proxy) line_edit_https_proxy = QLineEdit() self.bind(line_edit_https_proxy, "text", "network/https-proxy") form.addRow("HTTPS proxy:", line_edit_https_proxy) tab.setLayout(form) if self.__macUnified: # Need some sensible size otherwise mac unified toolbar 'takes' # the space that should be used for layout of the contents self.adjustSize() def addTab(self, widget, text, toolTip=None, icon=None): if self.__macUnified: action = QAction(text, self) if toolTip: action.setToolTip(toolTip) if icon: action.setIcon(toolTip) action.setData(len(self.tab.actions())) self.tab.addAction(action) self.stack.addWidget(widget) else: i = self.tab.addTab(widget, text) if toolTip: self.tab.setTabToolTip(i, toolTip) if icon: self.tab.setTabIcon(i, icon) def setCurrentIndex(self, index: int): if self.__macUnified: self.stack.setCurrentIndex(index) else: self.tab.setCurrentIndex(index) def widget(self, index): if self.__macUnified: return self.stack.widget(index) else: return self.tab.widget(index) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.hide() self.deleteLater() def bind(self, source, source_property, key, transformer=None): target = UserDefaultsPropertyBinding(self.__settings, key) source = PropertyBinding(source, source_property) source.set(target.get()) self._manager.bind(target, source) def commit(self): self._manager.commit() def revert(self): self._manager.revert() def reset(self): for target, source in self._manager.bindings(): try: source.reset() except NotImplementedError: # Cannot reset. pass except Exception: log.error("Error reseting %r", source.propertyName, exc_info=True) def exec_(self): self.__loop = QEventLoop() self.show() status = self.__loop.exec_() self.__loop = None refresh_proxies() return status def hideEvent(self, event): super().hideEvent(event) if self.__loop is not None: self.__loop.exit(0) self.__loop = None def __macOnToolBarAction(self, action): index = action.data() self.stack.setCurrentIndex(index)
class UserSettingsDialog(QMainWindow): """ A User Settings/Defaults dialog. """ MAC_UNIFIED = True def __init__(self, parent=None, **kwargs): QMainWindow.__init__(self, parent, **kwargs) self.setWindowFlags(Qt.Dialog) self.setWindowModality(Qt.ApplicationModal) self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) self.__macUnified = sys.platform == "darwin" and self.MAC_UNIFIED self._manager = BindingManager(self, submitPolicy=BindingManager.AutoSubmit) self.__loop = None self.__settings = config.settings() self.__setupUi() def __setupUi(self): """Set up the UI. """ if self.__macUnified: self.tab = QToolBar() self.addToolBar(Qt.TopToolBarArea, self.tab) self.setUnifiedTitleAndToolBarOnMac(True) # This does not seem to work self.setWindowFlags(self.windowFlags() & \ ~Qt.MacWindowToolBarButtonHint) self.tab.actionTriggered[QAction].connect( self.__macOnToolBarAction) central = QStackedWidget() central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) else: self.tab = central = QTabWidget(self) self.stack = central self.setCentralWidget(central) # General Tab tab = QWidget() self.addTab(tab, self.tr("通用"), toolTip=self.tr("通用选项")) form = QFormLayout() tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) nodes = QWidget(self, objectName="nodes") nodes.setLayout(QVBoxLayout()) nodes.layout().setContentsMargins(0, 0, 0, 0) cb_anim = QCheckBox(self.tr("启用节点动画"), objectName="enable-node-animations", toolTip=self.tr("启用数据挖掘流程中节点的阴影和动画")) self.bind(cb_anim, "checked", "schemeedit/enable-node-animations") nodes.layout().addWidget(cb_anim) form.addRow(self.tr("节点"), nodes) links = QWidget(self, objectName="links") links.setLayout(QVBoxLayout()) links.layout().setContentsMargins(0, 0, 0, 0) cb_show = QCheckBox(self.tr("显示部件之间的通道名称"), objectName="show-channel-names", toolTip=self.tr("在链接上显示来源和接收的通道名称。")) self.bind(cb_show, "checked", "schemeedit/show-channel-names") links.layout().addWidget(cb_show) form.addRow(self.tr("连接"), links) quickmenu = QWidget(self, objectName="quickmenu-options") quickmenu.setLayout(QVBoxLayout()) quickmenu.layout().setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("双击"), toolTip=self.tr("双击画布空白处打开快捷菜单")) cb2 = QCheckBox(self.tr("右击"), toolTip=self.tr("右击画布空白处打开快捷菜单")) cb3 = QCheckBox(self.tr("按空格键"), toolTip=self.tr("鼠标停在画布上,按空格键打开快捷菜单")) cb4 = QCheckBox(self.tr("按任意键"), toolTip=self.tr("鼠标停在画布上,按任意键打开快捷菜单")) self.bind(cb1, "checked", "quickmenu/trigger-on-double-click") self.bind(cb2, "checked", "quickmenu/trigger-on-right-click") self.bind(cb3, "checked", "quickmenu/trigger-on-space-key") self.bind(cb4, "checked", "quickmenu/trigger-on-any-key") quickmenu.layout().addWidget(cb1) quickmenu.layout().addWidget(cb2) quickmenu.layout().addWidget(cb3) quickmenu.layout().addWidget(cb4) form.addRow(self.tr("打开快捷菜单"), quickmenu) startup = QWidget(self, objectName="startup-group") startup.setLayout(QVBoxLayout()) startup.layout().setContentsMargins(0, 0, 0, 0) cb_splash = QCheckBox(self.tr("显示初始屏幕"), self, objectName="show-splash-screen") cb_welcome = QCheckBox(self.tr("显示欢迎屏幕"), self, objectName="show-welcome-screen") cb_updates = QCheckBox(self.tr("检查更新"), self, objectName="check-updates") self.bind(cb_splash, "checked", "startup/show-splash-screen") self.bind(cb_welcome, "checked", "startup/show-welcome-screen") self.bind(cb_updates, "checked", "startup/check-updates") startup.layout().addWidget(cb_splash) startup.layout().addWidget(cb_welcome) startup.layout().addWidget(cb_updates) form.addRow(self.tr("启动"), startup) toolbox = QWidget(self, objectName="toolbox-group") toolbox.setLayout(QVBoxLayout()) toolbox.layout().setContentsMargins(0, 0, 0, 0) exclusive = QCheckBox(self.tr("一次只能打开一个选项卡 ")) self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive") toolbox.layout().addWidget(exclusive) form.addRow(self.tr("工具箱"), toolbox) tab.setLayout(form) # Output Tab tab = QWidget() self.addTab(tab, self.tr("输出"), toolTip="输出重定向") form = QFormLayout() box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) combo = QComboBox() combo.addItems([ self.tr("Critical"), self.tr("Error"), self.tr("Warn"), self.tr("Info"), self.tr("Debug") ]) self.bind(combo, "currentIndex", "logging/level") layout.addWidget(combo) box.setLayout(layout) form.addRow(self.tr("日志"), box) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("用外部浏览器打开"), objectName="open-in-external-browser") self.bind(cb1, "checked", "help/open-in-external-browser") layout.addWidget(cb1) box.setLayout(layout) form.addRow(self.tr("帮助"), box) tab.setLayout(form) # Error Reporting Tab tab = QWidget() self.addTab(tab, self.tr("错误报告"), toolTip="错误报告相关的设置") form = QFormLayout() line_edit_mid = QLineEdit() self.bind(line_edit_mid, "text", "error-reporting/machine-id") form.addRow("Machine ID:", line_edit_mid) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox( self.tr(""), toolTip=self.tr( "Share anonymous usage statistics to improve Orange")) self.bind(cb1, "checked", "error-reporting/send-statistics") layout.addWidget(cb1) box.setLayout(layout) form.addRow(self.tr("共享匿名统计信息"), box) tab.setLayout(form) # Add-ons Tab tab = QWidget() self.addTab(tab, self.tr("附加"), toolTip="附加组件安装相关的设置") form = QFormLayout() conda = QWidget(self, objectName="conda-group") conda.setLayout(QVBoxLayout()) conda.layout().setContentsMargins(0, 0, 0, 0) cb_conda_install = QCheckBox(self.tr("Conda安装附加组件"), self, objectName="allow-conda") self.bind(cb_conda_install, "checked", "add-ons/allow-conda") conda.layout().addWidget(cb_conda_install) form.addRow(self.tr("Conda"), conda) form.addRow(self.tr("Pip"), QLabel("Pip安装参数:")) line_edit_pip = QLineEdit() self.bind(line_edit_pip, "text", "add-ons/pip-install-arguments") form.addRow("", line_edit_pip) tab.setLayout(form) # Network Tab tab = QWidget() self.addTab(tab, self.tr("网络"), toolTip="网络相关的设置") form = QFormLayout() line_edit_http_proxy = QLineEdit() self.bind(line_edit_http_proxy, "text", "network/http-proxy") form.addRow("HTTP代理:", line_edit_http_proxy) line_edit_https_proxy = QLineEdit() self.bind(line_edit_https_proxy, "text", "network/https-proxy") form.addRow("HTTPS代理:", line_edit_https_proxy) tab.setLayout(form) if self.__macUnified: # Need some sensible size otherwise mac unified toolbar 'takes' # the space that should be used for layout of the contents self.adjustSize() def addTab(self, widget, text, toolTip=None, icon=None): if self.__macUnified: action = QAction(text, self) if toolTip: action.setToolTip(toolTip) if icon: action.setIcon(toolTip) action.setData(len(self.tab.actions())) self.tab.addAction(action) self.stack.addWidget(widget) else: i = self.tab.addTab(widget, text) if toolTip: self.tab.setTabToolTip(i, toolTip) if icon: self.tab.setTabIcon(i, icon) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.hide() self.deleteLater() def bind(self, source, source_property, key, transformer=None): target = UserDefaultsPropertyBinding(self.__settings, key) source = PropertyBinding(source, source_property) source.set(target.get()) self._manager.bind(target, source) def commit(self): self._manager.commit() def revert(self): self._manager.revert() def reset(self): for target, source in self._manager.bindings(): try: source.reset() except NotImplementedError: # Cannot reset. pass except Exception: log.error("Error reseting %r", source.propertyName, exc_info=True) def exec_(self): self.__loop = QEventLoop() self.show() status = self.__loop.exec_() self.__loop = None refresh_proxies() return status def hideEvent(self, event): QMainWindow.hideEvent(self, event) if self.__loop is not None: self.__loop.exit(0) self.__loop = None def __macOnToolBarAction(self, action): self.stack.setCurrentIndex(action.data())
def exec_(self): self.__loop = QEventLoop() self.show() status = self.__loop.exec_() self.__loop = None return status
class EventSpy(QObject): """ A testing utility class (similar to QSignalSpy) to record events delivered to a QObject instance. Note ---- Only event types can be recorded (as QEvent instances are deleted on delivery). Note ---- Can only be used with a QCoreApplication running. Parameters ---------- object : QObject An object whose events need to be recorded. etype : Union[QEvent.Type, Sequence[QEvent.Type] A event type (or types) that should be recorded """ def __init__(self, object, etype, **kwargs): super().__init__(**kwargs) if not isinstance(object, QObject): raise TypeError self.__object = object try: len(etype) except TypeError: etypes = {etype} else: etypes = set(etype) self.__etypes = etypes self.__record = [] self.__loop = QEventLoop() self.__timer = QTimer(self, singleShot=True) self.__timer.timeout.connect(self.__loop.quit) self.__object.installEventFilter(self) def wait(self, timeout=5000): """ Start an event loop that runs until a spied event or a timeout occurred. Parameters ---------- timeout : int Timeout in milliseconds. Returns ------- res : bool True if the event occurred and False otherwise. Example ------- >>> app = QCoreApplication.instance() or QCoreApplication([]) >>> obj = QObject() >>> spy = EventSpy(obj, QEvent.User) >>> app.postEvent(obj, QEvent(QEvent.User)) >>> spy.wait() True >>> print(spy.events()) [1000] """ count = len(self.__record) self.__timer.stop() self.__timer.setInterval(timeout) self.__timer.start() self.__loop.exec_() self.__timer.stop() return len(self.__record) != count def eventFilter(self, reciever, event): if reciever is self.__object and event.type() in self.__etypes: self.__record.append(event.type()) if self.__loop.isRunning(): self.__loop.quit() return super().eventFilter(reciever, event) def events(self): """ Return a list of all (listened to) event types that occurred. Returns ------- events : List[QEvent.Type] """ return list(self.__record)
class UserSettingsDialog(QMainWindow): """ A User Settings/Defaults dialog. """ MAC_UNIFIED = True def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.setWindowFlags(Qt.Dialog) self.setWindowModality(Qt.ApplicationModal) self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) self.__macUnified = sys.platform == "darwin" and self.MAC_UNIFIED self._manager = BindingManager(self, submitPolicy=BindingManager.AutoSubmit) self.__loop = None self.__settings = config.settings() self.__setupUi() def __setupUi(self): """Set up the UI. """ if self.__macUnified: self.tab = QToolBar( floatable=False, movable=False, allowedAreas=Qt.TopToolBarArea, ) self.addToolBar(Qt.TopToolBarArea, self.tab) self.setUnifiedTitleAndToolBarOnMac(True) # This does not seem to work self.setWindowFlags(self.windowFlags() & \ ~Qt.MacWindowToolBarButtonHint) self.tab.actionTriggered[QAction].connect( self.__macOnToolBarAction ) central = QStackedWidget() central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) else: self.tab = central = QTabWidget(self) # Add a close button to the bottom of the dialog # (to satisfy GNOME 3 which shows the dialog without a title bar). container = container_widget_helper() container.layout().addWidget(central) buttonbox = QDialogButtonBox(QDialogButtonBox.Close) buttonbox.rejected.connect(self.close) container.layout().addWidget(buttonbox) self.setCentralWidget(container) self.stack = central # General Tab tab = QWidget() self.addTab(tab, self.tr("常规"), toolTip=self.tr("常规选项")) form = FormLayout() tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) nodes = QWidget(self, objectName="nodes") nodes.setLayout(QVBoxLayout()) nodes.layout().setContentsMargins(0, 0, 0, 0) cb_anim = QCheckBox( self.tr("启用节点动画"), objectName="enable-node-animations", toolTip=self.tr("为工作流中的节点启用阴影和ping动画。") ) self.bind(cb_anim, "checked", "schemeedit/enable-node-animations") nodes.layout().addWidget(cb_anim) form.addRow(self.tr("结点"), nodes) links = QWidget(self, objectName="links") links.setLayout(QVBoxLayout()) links.layout().setContentsMargins(0, 0, 0, 0) cb_show = QCheckBox( self.tr("在窗口小部件之间显示通道名称"), objectName="show-channel-names", toolTip=self.tr("在链接上显示源和接收器通道名称。") ) self.bind(cb_show, "checked", "schemeedit/show-channel-names") links.layout().addWidget(cb_show) form.addRow(self.tr("链接"), links) quickmenu = QWidget(self, objectName="quickmenu-options") quickmenu.setLayout(QVBoxLayout()) quickmenu.layout().setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("双击时打开"), toolTip=self.tr("双击画布中的空白位置打开快捷菜单")) cb2 = QCheckBox(self.tr("单击鼠标右键时打开"), toolTip=self.tr("右键单击画布中的空白处打开快捷菜单")) cb3 = QCheckBox(self.tr("按空格键时打开"), toolTip=self.tr("当鼠标悬停在画布上时,按空格键。")) cb4 = QCheckBox(self.tr("按任意按键时打开"), toolTip=self.tr("当鼠标悬停在画布上时,按任意键。")) cb5 = QCheckBox(self.tr("显示分类"), toolTip=self.tr("In addition to searching, allow filtering " "by categories.")) self.bind(cb1, "checked", "quickmenu/trigger-on-double-click") self.bind(cb2, "checked", "quickmenu/trigger-on-right-click") self.bind(cb3, "checked", "quickmenu/trigger-on-space-key") self.bind(cb4, "checked", "quickmenu/trigger-on-any-key") self.bind(cb5, "checked", "quickmenu/show-categories") quickmenu.layout().addWidget(cb1) quickmenu.layout().addWidget(cb2) quickmenu.layout().addWidget(cb3) quickmenu.layout().addWidget(cb4) quickmenu.layout().addWidget(cb5) form.addRow(self.tr("快捷菜单"), quickmenu) startup = QWidget(self, objectName="startup-group") startup.setLayout(QVBoxLayout()) startup.layout().setContentsMargins(0, 0, 0, 0) cb_splash = QCheckBox(self.tr("显示启动画面"), self, objectName="show-splash-screen") cb_welcome = QCheckBox(self.tr("显示欢迎界面"), self, objectName="show-welcome-screen") cb_crash = QCheckBox(self.tr("加载崩溃的工作流"), self, objectName="load-crashed-workflows") self.bind(cb_splash, "checked", "startup/show-splash-screen") self.bind(cb_welcome, "checked", "startup/show-welcome-screen") self.bind(cb_crash, "checked", "startup/load-crashed-workflows") startup.layout().addWidget(cb_splash) startup.layout().addWidget(cb_welcome) startup.layout().addWidget(cb_crash) form.addRow(self.tr("启动时"), startup) toolbox = QWidget(self, objectName="toolbox-group") toolbox.setLayout(QVBoxLayout()) toolbox.layout().setContentsMargins(0, 0, 0, 0) exclusive = QCheckBox(self.tr("一次只能打开一个选项卡")) self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive") toolbox.layout().addWidget(exclusive) form.addRow(self.tr("工具箱"), toolbox) tab.setLayout(form) # Style tab tab = StyleConfigWidget() self.addTab(tab, self.tr("&Style"), toolTip="Application style") self.bind(tab, "selectedStyle_", "application-style/style-name") self.bind(tab, "selectedPalette_", "application-style/palette") # Output Tab tab = QWidget() self.addTab(tab, self.tr("输出"), toolTip="输出重定向") form = FormLayout() combo = QComboBox() combo.addItems([self.tr("关键"), self.tr("错误"), self.tr("警告"), self.tr("信息"), self.tr("调试")]) self.bind(combo, "currentIndex", "logging/level") form.addRow(self.tr("记录"), combo) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("在外部浏览器中打开"), objectName="open-in-external-browser") self.bind(cb1, "checked", "help/open-in-external-browser") layout.addWidget(cb1) box.setLayout(layout) form.addRow(self.tr("帮助窗口"), box) tab.setLayout(form) # Categories Tab tab = QWidget() layout = QVBoxLayout() view = QListView( editTriggers=QListView.NoEditTriggers ) from .. import registry reg = registry.global_registry() model = QStandardItemModel() settings = QSettings() for cat in reg.categories(): item = QStandardItem() item.setText(cat.name) item.setCheckable(True) visible, _ = category_state(cat, settings) item.setCheckState(Qt.Checked if visible else Qt.Unchecked) model.appendRow([item]) view.setModel(model) layout.addWidget(view) tab.setLayout(layout) model.itemChanged.connect( lambda item: save_category_state( reg.category(str(item.text())), _State(item.checkState() == Qt.Checked, -1), settings ) ) self.addTab(tab, "类别") # Add-ons Tab tab = QWidget() self.addTab(tab, self.tr("插件"), toolTip="与插件安装相关的设置") form = FormLayout() conda = QWidget(self, objectName="conda-group") conda.setLayout(QVBoxLayout()) conda.layout().setContentsMargins(0, 0, 0, 0) mirror_install = QCheckBox(self.tr("使用国内镜像安装"), self, objectName="allow-conda") self.bind(mirror_install, "checked", "add-ons/allow-conda") conda.layout().addWidget(mirror_install) form.addRow(self.tr("镜像"), conda) form.addRow(self.tr("Pip"), QLabel("Pip 安装参数:")) line_edit_pip = QLineEdit() # line_edit_pip.setText('-i https://mirrors.aliyun.com/pypi/simple') self.bind(line_edit_pip, "text", "add-ons/pip-install-arguments") form.addRow("", line_edit_pip) tab.setLayout(form) # Network Tab tab = QWidget() self.addTab(tab, self.tr("网络"), toolTip="与网络相关的设置") form = FormLayout() line_edit_http_proxy = QLineEdit() self.bind(line_edit_http_proxy, "text", "network/http-proxy") form.addRow("HTTP 代理:", line_edit_http_proxy) line_edit_https_proxy = QLineEdit() self.bind(line_edit_https_proxy, "text", "network/https-proxy") form.addRow("HTTPS 代理:", line_edit_https_proxy) tab.setLayout(form) if self.__macUnified: # Need some sensible size otherwise mac unified toolbar 'takes' # the space that should be used for layout of the contents self.adjustSize() def addTab(self, widget, text, toolTip=None, icon=None): if self.__macUnified: action = QAction(text, self) if toolTip: action.setToolTip(toolTip) if icon: action.setIcon(toolTip) action.setData(len(self.tab.actions())) self.tab.addAction(action) self.stack.addWidget(widget) else: i = self.tab.addTab(widget, text) if toolTip: self.tab.setTabToolTip(i, toolTip) if icon: self.tab.setTabIcon(i, icon) def setCurrentIndex(self, index: int): if self.__macUnified: self.stack.setCurrentIndex(index) else: self.tab.setCurrentIndex(index) def widget(self, index): if self.__macUnified: return self.stack.widget(index) else: return self.tab.widget(index) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.hide() self.deleteLater() def bind(self, source, source_property, key, transformer=None): target = UserDefaultsPropertyBinding(self.__settings, key) source = PropertyBinding(source, source_property) source.set(target.get()) self._manager.bind(target, source) def commit(self): self._manager.commit() def revert(self): self._manager.revert() def reset(self): for target, source in self._manager.bindings(): try: source.reset() except NotImplementedError: # Cannot reset. pass except Exception: log.error("Error reseting %r", source.propertyName, exc_info=True) def exec_(self): self.__loop = QEventLoop() self.show() status = self.__loop.exec_() self.__loop = None refresh_proxies() return status def hideEvent(self, event): super().hideEvent(event) if self.__loop is not None: self.__loop.exit(0) self.__loop = None def __macOnToolBarAction(self, action): index = action.data() self.stack.setCurrentIndex(index)
class CategoryPopupMenu(FramelessWindow): """ A menu popup from which nodes can be dragged or clicked/activated. """ triggered = Signal(QAction) hovered = Signal(QAction) def __init__(self, parent=None, **kwargs): # type: (Optional[QWidget], Any) -> None super().__init__(parent, **kwargs) self.setWindowFlags(self.windowFlags() | Qt.Popup) layout = QVBoxLayout() layout.setContentsMargins(6, 6, 6, 6) self.__menu = MenuPage() self.__menu.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE) if sys.platform == "darwin": self.__menu.view().setAttribute(Qt.WA_MacShowFocusRect, False) self.__menu.triggered.connect(self.__onTriggered) self.__menu.hovered.connect(self.hovered) self.__dragListener = ItemViewDragStartEventListener(self) self.__dragListener.dragStarted.connect(self.__onDragStarted) self.__menu.view().viewport().installEventFilter(self.__dragListener) self.__menu.view().installEventFilter(self) layout.addWidget(self.__menu) self.setLayout(layout) self.__action = None # type: Optional[QAction] self.__loop = None # type: Optional[QEventLoop] def setCategoryItem(self, item): """ Set the category root item (:class:`QStandardItem`). """ warnings.warn( "setCategoryItem is deprecated. Use the more general 'setModel'" "and setRootIndex", DeprecationWarning, stacklevel=2 ) model = item.model() self.__menu.setModel(model) self.__menu.setRootIndex(item.index()) def setModel(self, model): # type: (QAbstractItemModel) -> None """ Set the model. Parameters ---------- model : QAbstractItemModel """ self.__menu.setModel(model) def setRootIndex(self, index): # type: (QModelIndex) -> None """ Set the root index in `model`. Parameters ---------- index : QModelIndex """ self.__menu.setRootIndex(index) def setActionRole(self, role): # type: (Qt.ItemDataRole) -> None """ Set the action role in model. This is an item role in `model` that returns a QAction for the item. Parameters ---------- role : Qt.ItemDataRole """ self.__menu.setActionRole(role) def popup(self, pos=None): # type: (Optional[QPoint]) -> None """ Show the popup at `pos`. Parameters ---------- pos : Optional[QPoint] The position in global screen coordinates """ if pos is None: pos = self.pos() self.adjustSize() geom = widget_popup_geometry(pos, self) self.setGeometry(geom) self.show() self.__menu.view().setFocus() def exec_(self, pos=None): # type: (Optional[QPoint]) -> Optional[QAction] self.popup(pos) self.__loop = QEventLoop() self.__action = None self.__loop.exec_() self.__loop = None if self.__action is not None: action = self.__action else: action = None return action def hideEvent(self, event): # type: (QHideEvent) -> None if self.__loop is not None: self.__loop.exit(0) super().hideEvent(event) def __onTriggered(self, action): # type: (QAction) -> None self.__action = action self.triggered.emit(action) self.hide() if self.__loop: self.__loop.exit(0) def __onDragStarted(self, index): # type: (QModelIndex) -> None desc = index.data(QtWidgetRegistry.WIDGET_DESC_ROLE) icon = index.data(Qt.DecorationRole) drag_data = QMimeData() drag_data.setData( "application/vnv.orange-canvas.registry.qualified-name", desc.qualified_name.encode('utf-8') ) drag = QDrag(self) drag.setPixmap(icon.pixmap(38)) drag.setMimeData(drag_data) # TODO: Should animate (accept) hide. self.hide() # When a drag is started and the menu hidden the item's tool tip # can still show for a short time UNDER the cursor preventing a # drop. viewport = self.__menu.view().viewport() filter = ToolTipEventFilter() viewport.installEventFilter(filter) drag.exec_(Qt.CopyAction) viewport.removeEventFilter(filter) def eventFilter(self, obj, event): if isinstance(obj, QTreeView) and event.type() == QEvent.KeyPress: key = event.key() if key in [Qt.Key_Return, Qt.Key_Enter]: curr = obj.currentIndex() if curr.isValid(): obj.activated.emit(curr) return True return super().eventFilter(obj, event)
class CategoryPopupMenu(FramelessWindow): triggered = Signal(QAction) hovered = Signal(QAction) def __init__(self, parent=None, **kwargs): FramelessWindow.__init__(self, parent, **kwargs) self.setWindowFlags(self.windowFlags() | Qt.Popup) layout = QVBoxLayout() layout.setContentsMargins(6, 6, 6, 6) self.__menu = MenuPage() self.__menu.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE) if sys.platform == "darwin": self.__menu.view().setAttribute(Qt.WA_MacShowFocusRect, False) self.__menu.triggered.connect(self.__onTriggered) self.__menu.hovered.connect(self.hovered) self.__dragListener = ItemViewDragStartEventListener(self) self.__dragListener.dragStarted.connect(self.__onDragStarted) self.__menu.view().viewport().installEventFilter(self.__dragListener) layout.addWidget(self.__menu) self.setLayout(layout) self.__action = None self.__loop = None self.__item = None def setCategoryItem(self, item): """ Set the category root item (:class:`QStandardItem`). """ self.__item = item model = item.model() self.__menu.setModel(model) self.__menu.setRootIndex(item.index()) def popup(self, pos=None): if pos is None: pos = self.pos() self.adjustSize() geom = widget_popup_geometry(pos, self) self.setGeometry(geom) self.show() def exec_(self, pos=None): self.popup(pos) self.__loop = QEventLoop() self.__action = None self.__loop.exec_() self.__loop = None if self.__action is not None: action = self.__action else: action = None return action def hideEvent(self, event): if self.__loop is not None: self.__loop.exit(0) return FramelessWindow.hideEvent(self, event) def __onTriggered(self, action): self.__action = action self.triggered.emit(action) self.hide() if self.__loop: self.__loop.exit(0) def __onDragStarted(self, index): desc = qunwrap(index.data(QtWidgetRegistry.WIDGET_DESC_ROLE)) icon = qunwrap(index.data(Qt.DecorationRole)) drag_data = QMimeData() drag_data.setData( "application/vnv.orange-canvas.registry.qualified-name", desc.qualified_name.encode('utf-8')) drag = QDrag(self) drag.setPixmap(icon.pixmap(38)) drag.setMimeData(drag_data) # TODO: Should animate (accept) hide. self.hide() # When a drag is started and the menu hidden the item's tool tip # can still show for a short time UNDER the cursor preventing a # drop. viewport = self.__menu.view().viewport() filter = ToolTipEventFilter() viewport.installEventFilter(filter) drag.exec_(Qt.CopyAction) viewport.removeEventFilter(filter)
class UserSettingsDialog(QMainWindow): """ A User Settings/Defaults dialog. """ MAC_UNIFIED = True def __init__(self, parent=None, **kwargs): QMainWindow.__init__(self, parent, **kwargs) self.setWindowFlags(Qt.Dialog) self.setWindowModality(Qt.ApplicationModal) self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) self.__macUnified = sys.platform == "darwin" and self.MAC_UNIFIED self._manager = BindingManager(self, submitPolicy=BindingManager.AutoSubmit) self.__loop = None self.__settings = config.settings() self.__setupUi() def __setupUi(self): """Set up the UI. """ if self.__macUnified: self.tab = QToolBar() self.addToolBar(Qt.TopToolBarArea, self.tab) self.setUnifiedTitleAndToolBarOnMac(True) # This does not seem to work self.setWindowFlags(self.windowFlags() & \ ~Qt.MacWindowToolBarButtonHint) self.tab.actionTriggered[QAction].connect( self.__macOnToolBarAction ) central = QStackedWidget() central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) else: self.tab = central = QTabWidget(self) self.stack = central self.setCentralWidget(central) # General Tab tab = QWidget() self.addTab(tab, self.tr("General"), toolTip=self.tr("General Options")) form = QFormLayout() tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) nodes = QWidget(self, objectName="nodes") nodes.setLayout(QVBoxLayout()) nodes.layout().setContentsMargins(0, 0, 0, 0) cb_anim = QCheckBox( self.tr("Enable node animations"), objectName="enable-node-animations", toolTip=self.tr("Enable shadow and ping animations for nodes " "in the workflow.") ) self.bind(cb_anim, "checked", "schemeedit/enable-node-animations") nodes.layout().addWidget(cb_anim) form.addRow(self.tr("Nodes"), nodes) links = QWidget(self, objectName="links") links.setLayout(QVBoxLayout()) links.layout().setContentsMargins(0, 0, 0, 0) cb_show = QCheckBox( self.tr("Show channel names between widgets"), objectName="show-channel-names", toolTip=self.tr("Show source and sink channel names " "over the links.") ) self.bind(cb_show, "checked", "schemeedit/show-channel-names") links.layout().addWidget(cb_show) form.addRow(self.tr("Links"), links) quickmenu = QWidget(self, objectName="quickmenu-options") quickmenu.setLayout(QVBoxLayout()) quickmenu.layout().setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("On double click"), toolTip=self.tr("Open quick menu on a double click " "on an empty spot in the canvas")) cb2 = QCheckBox(self.tr("On right click"), toolTip=self.tr("Open quick menu on a right click " "on an empty spot in the canvas")) cb3 = QCheckBox(self.tr("On space key press"), toolTip=self.tr("On Space key press while the mouse" "is hovering over the canvas.")) cb4 = QCheckBox(self.tr("On any key press"), toolTip=self.tr("On any key press while the mouse" "is hovering over the canvas.")) self.bind(cb1, "checked", "quickmenu/trigger-on-double-click") self.bind(cb2, "checked", "quickmenu/trigger-on-right-click") self.bind(cb3, "checked", "quickmenu/trigger-on-space-key") self.bind(cb4, "checked", "quickmenu/trigger-on-any-key") quickmenu.layout().addWidget(cb1) quickmenu.layout().addWidget(cb2) quickmenu.layout().addWidget(cb3) quickmenu.layout().addWidget(cb4) form.addRow(self.tr("Open quick menu on"), quickmenu) startup = QWidget(self, objectName="startup-group") startup.setLayout(QVBoxLayout()) startup.layout().setContentsMargins(0, 0, 0, 0) cb_splash = QCheckBox(self.tr("Show splash screen"), self, objectName="show-splash-screen") cb_welcome = QCheckBox(self.tr("Show welcome screen"), self, objectName="show-welcome-screen") cb_updates = QCheckBox(self.tr("Check for updates"), self, objectName="check-updates") self.bind(cb_splash, "checked", "startup/show-splash-screen") self.bind(cb_welcome, "checked", "startup/show-welcome-screen") self.bind(cb_updates, "checked", "startup/check-updates") startup.layout().addWidget(cb_splash) startup.layout().addWidget(cb_welcome) startup.layout().addWidget(cb_updates) form.addRow(self.tr("On startup"), startup) toolbox = QWidget(self, objectName="toolbox-group") toolbox.setLayout(QVBoxLayout()) toolbox.layout().setContentsMargins(0, 0, 0, 0) exclusive = QCheckBox(self.tr("Only one tab can be open at a time")) self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive") toolbox.layout().addWidget(exclusive) form.addRow(self.tr("Tool box"), toolbox) tab.setLayout(form) # Output Tab tab = QWidget() self.addTab(tab, self.tr("Output"), toolTip="Output Redirection") form = QFormLayout() box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) combo = QComboBox() combo.addItems([self.tr("Critical"), self.tr("Error"), self.tr("Warn"), self.tr("Info"), self.tr("Debug")]) self.bind(combo, "currentIndex", "logging/level") layout.addWidget(combo) box.setLayout(layout) form.addRow(self.tr("Logging"), box) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("Open in external browser"), objectName="open-in-external-browser") self.bind(cb1, "checked", "help/open-in-external-browser") layout.addWidget(cb1) box.setLayout(layout) form.addRow(self.tr("Help window"), box) tab.setLayout(form) # Error Reporting Tab tab = QWidget() self.addTab(tab, self.tr("Error Reporting"), toolTip="Settings related to error reporting") form = QFormLayout() line_edit_mid = QLineEdit() self.bind(line_edit_mid, "text", "error-reporting/machine-id") form.addRow("Machine ID:", line_edit_mid) tab.setLayout(form) # Add-ons Tab tab = QWidget() self.addTab(tab, self.tr("Add-ons"), toolTip="Settings related to add-on installation") form = QFormLayout() conda = QWidget(self, objectName="conda-group") conda.setLayout(QVBoxLayout()) conda.layout().setContentsMargins(0, 0, 0, 0) cb_conda_install = QCheckBox(self.tr("Install add-ons with conda"), self, objectName="allow-conda-experimental") self.bind(cb_conda_install, "checked", "add-ons/allow-conda-experimental") conda.layout().addWidget(cb_conda_install) form.addRow(self.tr("Conda"), conda) tab.setLayout(form) if self.__macUnified: # Need some sensible size otherwise mac unified toolbar 'takes' # the space that should be used for layout of the contents self.adjustSize() def addTab(self, widget, text, toolTip=None, icon=None): if self.__macUnified: action = QAction(text, self) if toolTip: action.setToolTip(toolTip) if icon: action.setIcon(toolTip) action.setData(len(self.tab.actions())) self.tab.addAction(action) self.stack.addWidget(widget) else: i = self.tab.addTab(widget, text) if toolTip: self.tab.setTabToolTip(i, toolTip) if icon: self.tab.setTabIcon(i, icon) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.hide() self.deleteLater() def bind(self, source, source_property, key, transformer=None): target = UserDefaultsPropertyBinding(self.__settings, key) source = PropertyBinding(source, source_property) source.set(target.get()) self._manager.bind(target, source) def commit(self): self._manager.commit() def revert(self): self._manager.revert() def reset(self): for target, source in self._manager.bindings(): try: source.reset() except NotImplementedError: # Cannot reset. pass except Exception: log.error("Error reseting %r", source.propertyName, exc_info=True) def exec_(self): self.__loop = QEventLoop() self.show() status = self.__loop.exec_() self.__loop = None return status def hideEvent(self, event): QMainWindow.hideEvent(self, event) if self.__loop is not None: self.__loop.exit(0) self.__loop = None def __macOnToolBarAction(self, action): self.stack.setCurrentIndex(action.data())
class QuickMenu(FramelessWindow): """ A quick menu popup for the widgets. The widgets are set using :func:`QuickMenu.setModel` which must be a model as returned by :func:`QtWidgetRegistry.model` """ #: An action has been triggered in the menu. triggered = Signal(QAction) #: An action has been hovered in the menu hovered = Signal(QAction) def __init__(self, parent=None, **kwargs): # type: (Optional[QWidget], Any) -> None super().__init__(parent, **kwargs) self.setWindowFlags(Qt.Popup) self.__filterFunc = None # type: Optional[FilterFunc] self.__sortingFunc = None # type: Optional[Callable[[Any, Any], bool]] self.setLayout(QVBoxLayout(self)) self.layout().setContentsMargins(6, 6, 6, 6) self.__search = SearchWidget(self, objectName="search-line") self.__search.setPlaceholderText( self.tr("Search for widget or select from the list.")) self.layout().addWidget(self.__search) self.__frame = QFrame(self, objectName="menu-frame") layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) self.__frame.setLayout(layout) self.layout().addWidget(self.__frame) self.__pages = PagedMenu(self, objectName="paged-menu") self.__pages.currentChanged.connect(self.setCurrentIndex) self.__pages.triggered.connect(self.triggered) self.__pages.hovered.connect(self.hovered) self.__frame.layout().addWidget(self.__pages) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) self.__suggestPage = SuggestMenuPage(self, objectName="suggest-page") self.__suggestPage.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE) self.__suggestPage.setIcon(icon_loader().get("icons/Search.svg")) if sys.platform == "darwin": view = self.__suggestPage.view() view.verticalScrollBar().setAttribute(Qt.WA_MacMiniSize, True) # Don't show the focus frame because it expands into the tab bar. view.setAttribute(Qt.WA_MacShowFocusRect, False) i = self.addPage(self.tr("Quick Search"), self.__suggestPage) button = self.__pages.tabButton(i) button.setObjectName("search-tab-button") button.setStyleSheet("TabButton {\n" " qproperty-flat_: false;\n" " border: none;" "}\n") self.__search.textEdited.connect(self.__on_textEdited) self.__navigator = ItemViewKeyNavigator(self) self.__navigator.setView(self.__suggestPage.view()) self.__search.installEventFilter(self.__navigator) self.__grip = WindowSizeGrip(self) # type: Optional[WindowSizeGrip] self.__grip.raise_() self.__loop = None # type: Optional[QEventLoop] self.__model = None # type: Optional[QAbstractItemModel] self.setModel(QStandardItemModel()) self.__triggeredAction = None # type: Optional[QAction] def setSizeGripEnabled(self, enabled): # type: (bool) -> None """ Enable the resizing of the menu with a size grip in a bottom right corner (enabled by default). """ if bool(enabled) != bool(self.__grip): if self.__grip: self.__grip.deleteLater() self.__grip = None else: self.__grip = WindowSizeGrip(self) self.__grip.raise_() def sizeGripEnabled(self): # type: () -> bool """ Is the size grip enabled. """ return bool(self.__grip) def addPage(self, name, page): # type: (str, MenuPage) -> int """ Add the `page` (:class:`MenuPage`) with `name` and return it's index. The `page.icon()` will be used as the icon in the tab bar. """ return self.insertPage(self.__pages.count(), name, page) def insertPage(self, index, name, page): # type: (int, str, MenuPage) -> int icon = page.icon() tip = name if page.toolTip(): tip = page.toolTip() index = self.__pages.insertPage(index, page, name, icon, tip) # Route the page's signals page.triggered.connect(self.__onTriggered) page.hovered.connect(self.hovered) # Install event filter to intercept key presses. page.view().installEventFilter(self) return index def createPage(self, index): # type: (QModelIndex) -> MenuPage """ Create a new page based on the contents of an index (:class:`QModeIndex`) item. """ page = MenuPage(self) page.setModel(index.model()) page.setRootIndex(index) view = page.view() if sys.platform == "darwin": view.verticalScrollBar().setAttribute(Qt.WA_MacMiniSize, True) # Don't show the focus frame because it expands into the tab # bar at the top. view.setAttribute(Qt.WA_MacShowFocusRect, False) name = str(index.data(Qt.DisplayRole)) page.setTitle(name) icon = index.data(Qt.DecorationRole) if isinstance(icon, QIcon): page.setIcon(icon) page.setToolTip(index.data(Qt.ToolTipRole)) return page def __clear(self): # type: () -> None for i in range(self.__pages.count() - 1, 0, -1): self.__pages.removePage(i) def setModel(self, model): # type: (QAbstractItemModel) -> None """ Set the model containing the actions. """ if self.__model is not None: self.__model.dataChanged.disconnect(self.__on_dataChanged) self.__model.rowsInserted.disconnect(self.__on_rowsInserted) self.__model.rowsRemoved.disconnect(self.__on_rowsRemoved) self.__clear() for i in range(model.rowCount()): index = model.index(i, 0) self.__insertPage(i + 1, index) self.__model = model self.__suggestPage.setModel(model) if model is not None: model.dataChanged.connect(self.__on_dataChanged) model.rowsInserted.connect(self.__on_rowsInserted) model.rowsRemoved.connect(self.__on_rowsRemoved) def __on_dataChanged(self, topLeft, bottomRight): # type: (QModelIndex, QModelIndex) -> None parent = topLeft.parent() # Only handle top level item (categories). if not parent.isValid(): for row in range(topLeft.row(), bottomRight.row() + 1): index = topLeft.sibling(row, 0) # Note: the tab buttons are offest by 1 (to accommodate # the Suggest Page). button = self.__pages.tabButton(row + 1) brush = as_qbrush(index.data(QtWidgetRegistry.BACKGROUND_ROLE)) if brush is not None: base_color = brush.color() button.setStyleSheet( TAB_BUTTON_STYLE_TEMPLATE % (create_css_gradient(base_color), create_css_gradient(base_color.darker(120)))) def __on_rowsInserted(self, parent, start, end): # type: (QModelIndex, int, int) -> None # Only handle top level item (categories). assert self.__model is not None if not parent.isValid(): for row in range(start, end + 1): index = self.__model.index(row, 0) self.__insertPage(row + 1, index) def __on_rowsRemoved(self, parent, start, end): # type: (QModelIndex, int, int) -> None # Only handle top level item (categories). if not parent.isValid(): for row in range(end, start - 1, -1): self.__removePage(row + 1) def __insertPage(self, row, index): # type: (int, QModelIndex) -> None page = self.createPage(index) page.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE) i = self.insertPage(row, page.title(), page) brush = as_qbrush(index.data(QtWidgetRegistry.BACKGROUND_ROLE)) if brush is not None: base_color = brush.color() button = self.__pages.tabButton(i) button.setStyleSheet(TAB_BUTTON_STYLE_TEMPLATE % (create_css_gradient(base_color), create_css_gradient(base_color.darker(120)))) def __removePage(self, row): # type: (int) -> None page = self.__pages.page(row) page.triggered.disconnect(self.__onTriggered) page.hovered.disconnect(self.hovered) page.view().removeEventFilter(self) self.__pages.removePage(row) def setSortingFunc(self, func): # type: (Callable[[Any, Any], bool]) -> None """ Set a sorting function in the suggest (search) menu. """ if self.__sortingFunc != func: self.__sortingFunc = func for i in range(0, self.__pages.count()): page = self.__pages.page(i) if isinstance(page, SuggestMenuPage): page.setSortingFunc(func) def setFilterFunc(self, func): # type: (Optional[FilterFunc]) -> None """ Set a filter function. """ if func != self.__filterFunc: self.__filterFunc = func for i in range(0, self.__pages.count()): self.__pages.page(i).setFilterFunc(func) def popup(self, pos=None, searchText=""): # type: (Optional[QPoint], str) -> None """ Popup the menu at `pos` (in screen coordinates). 'Search' text field is initialized with `searchText` if provided. """ if pos is None: pos = QPoint() self.__clearCurrentItems() self.__search.setText(searchText) patt = QRegExp(r"(^|\W)" + searchText) patt.setCaseSensitivity(Qt.CaseInsensitive) self.__suggestPage.setFilterRegExp(patt) UsageStatistics.set_last_search_query(searchText) self.ensurePolished() if self.testAttribute(Qt.WA_Resized) and self.sizeGripEnabled(): size = self.size() else: size = self.sizeHint() desktop = QApplication.desktop() screen_geom = desktop.availableGeometry(pos) # Adjust the size to fit inside the screen. if size.height() > screen_geom.height(): size.setHeight(screen_geom.height()) if size.width() > screen_geom.width(): size.setWidth(screen_geom.width()) geom = QRect(pos, size) if geom.top() < screen_geom.top(): geom.setTop(screen_geom.top()) if geom.left() < screen_geom.left(): geom.setLeft(screen_geom.left()) bottom_margin = screen_geom.bottom() - geom.bottom() right_margin = screen_geom.right() - geom.right() if bottom_margin < 0: # Falls over the bottom of the screen, move it up. geom.translate(0, bottom_margin) # TODO: right to left locale if right_margin < 0: # Falls over the right screen edge, move the menu to the # other side of pos. geom.translate(-size.width(), 0) self.setGeometry(geom) self.show() self.setFocusProxy(self.__search) def exec_(self, pos=None, searchText=""): # type: (Optional[QPoint], str) -> Optional[QAction] """ Execute the menu at position `pos` (in global screen coordinates). Return the triggered :class:`QAction` or `None` if no action was triggered. 'Search' text field is initialized with `searchText` if provided. """ self.popup(pos, searchText) self.setFocus(Qt.PopupFocusReason) self.__triggeredAction = None self.__loop = QEventLoop() self.__loop.exec_() self.__loop.deleteLater() self.__loop = None action = self.__triggeredAction self.__triggeredAction = None return action def hideEvent(self, event): """ Reimplemented from :class:`QWidget` """ super().hideEvent(event) if self.__loop: self.__loop.exit() def setCurrentPage(self, page): # type: (MenuPage) -> None """ Set the current shown page to `page`. """ self.__pages.setCurrentPage(page) def setCurrentIndex(self, index): # type: (int) -> None """ Set the current page index. """ self.__pages.setCurrentIndex(index) def __clearCurrentItems(self): # type: () -> None """ Clear any selected (or current) items in all the menus. """ for i in range(self.__pages.count()): self.__pages.page(i).view().selectionModel().clear() def __onTriggered(self, action): # type: (QAction) -> None """ Re-emit the action from the page. """ self.__triggeredAction = action # Hide and exit the event loop if necessary. self.hide() self.triggered.emit(action) def __on_textEdited(self, text): # type: (str) -> None patt = QRegExp(r"(^|\W)" + text) patt.setCaseSensitivity(Qt.CaseInsensitive) self.__suggestPage.setFilterRegExp(patt) self.__pages.setCurrentPage(self.__suggestPage) self.__selectFirstIndex() UsageStatistics.set_last_search_query(text) def __selectFirstIndex(self): # type: () -> None view = self.__pages.currentPage().view() model = view.model() index = model.index(0, 0) view.setCurrentIndex(index) def triggerSearch(self): # type: () -> None """ Trigger action search. This changes to current page to the 'Suggest' page and sets the keyboard focus to the search line edit. """ self.__pages.setCurrentPage(self.__suggestPage) self.__search.setFocus(Qt.ShortcutFocusReason) # Make sure that the first enabled item is set current. self.__suggestPage.ensureCurrent() def keyPressEvent(self, event): if event.text(): # Ignore modifiers, ... self.__search.setFocus(Qt.ShortcutFocusReason) self.setCurrentIndex(0) self.__search.keyPressEvent(event) super().keyPressEvent(event) event.accept() def event(self, event): if event.type() == QEvent.ShortcutOverride: log.debug("Overriding shortcuts") event.accept() return True return super().event(event) def eventFilter(self, obj, event): if isinstance(obj, QTreeView): etype = event.type() if etype == QEvent.KeyPress: # ignore modifiers non printable characters, Enter, ... if event.text() and event.key() not in \ [Qt.Key_Enter, Qt.Key_Return]: self.__search.setFocus(Qt.ShortcutFocusReason) self.setCurrentIndex(0) self.__search.keyPressEvent(event) return True return super().eventFilter(obj, event)
class UserSettingsDialog(QMainWindow): """ A User Settings/Defaults dialog. """ MAC_UNIFIED = True def __init__(self, parent=None, **kwargs): QMainWindow.__init__(self, parent, **kwargs) self.setWindowFlags(Qt.Dialog) self.setWindowModality(Qt.ApplicationModal) self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) self.__macUnified = sys.platform == "darwin" and self.MAC_UNIFIED self._manager = BindingManager(self, submitPolicy=BindingManager.AutoSubmit) self.__loop = None self.__settings = config.settings() self.__setupUi() def __setupUi(self): """Set up the UI. """ if self.__macUnified: self.tab = QToolBar() self.addToolBar(Qt.TopToolBarArea, self.tab) self.setUnifiedTitleAndToolBarOnMac(True) # This does not seem to work self.setWindowFlags(self.windowFlags() & \ ~Qt.MacWindowToolBarButtonHint) self.tab.actionTriggered[QAction].connect( self.__macOnToolBarAction) central = QStackedWidget() central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) else: self.tab = central = QTabWidget(self) self.stack = central self.setCentralWidget(central) # General Tab tab = QWidget() self.addTab(tab, self.tr("General"), toolTip=self.tr("General Options")) form = QFormLayout() tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) nodes = QWidget(self, objectName="nodes") nodes.setLayout(QVBoxLayout()) nodes.layout().setContentsMargins(0, 0, 0, 0) cb_anim = QCheckBox(self.tr("Enable node animations"), objectName="enable-node-animations", toolTip=self.tr( "Enable shadow and ping animations for nodes " "in the workflow.")) self.bind(cb_anim, "checked", "schemeedit/enable-node-animations") nodes.layout().addWidget(cb_anim) form.addRow(self.tr("Nodes"), nodes) links = QWidget(self, objectName="links") links.setLayout(QVBoxLayout()) links.layout().setContentsMargins(0, 0, 0, 0) cb_show = QCheckBox(self.tr("Show channel names between widgets"), objectName="show-channel-names", toolTip=self.tr( "Show source and sink channel names " "over the links.")) self.bind(cb_show, "checked", "schemeedit/show-channel-names") links.layout().addWidget(cb_show) form.addRow(self.tr("Links"), links) quickmenu = QWidget(self, objectName="quickmenu-options") quickmenu.setLayout(QVBoxLayout()) quickmenu.layout().setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("On double click"), toolTip=self.tr("Open quick menu on a double click " "on an empty spot in the canvas")) cb2 = QCheckBox(self.tr("On right click"), toolTip=self.tr("Open quick menu on a right click " "on an empty spot in the canvas")) cb3 = QCheckBox(self.tr("On space key press"), toolTip=self.tr("On Space key press while the mouse" "is hovering over the canvas.")) cb4 = QCheckBox(self.tr("On any key press"), toolTip=self.tr("On any key press while the mouse" "is hovering over the canvas.")) self.bind(cb1, "checked", "quickmenu/trigger-on-double-click") self.bind(cb2, "checked", "quickmenu/trigger-on-right-click") self.bind(cb3, "checked", "quickmenu/trigger-on-space-key") self.bind(cb4, "checked", "quickmenu/trigger-on-any-key") quickmenu.layout().addWidget(cb1) quickmenu.layout().addWidget(cb2) quickmenu.layout().addWidget(cb3) quickmenu.layout().addWidget(cb4) form.addRow(self.tr("Open quick menu on"), quickmenu) startup = QWidget(self, objectName="startup-group") startup.setLayout(QVBoxLayout()) startup.layout().setContentsMargins(0, 0, 0, 0) cb_splash = QCheckBox(self.tr("Show splash screen"), self, objectName="show-splash-screen") cb_welcome = QCheckBox(self.tr("Show welcome screen"), self, objectName="show-welcome-screen") cb_updates = QCheckBox(self.tr("Check for updates"), self, objectName="check-updates") self.bind(cb_splash, "checked", "startup/show-splash-screen") self.bind(cb_welcome, "checked", "startup/show-welcome-screen") self.bind(cb_updates, "checked", "startup/check-updates") startup.layout().addWidget(cb_splash) startup.layout().addWidget(cb_welcome) startup.layout().addWidget(cb_updates) form.addRow(self.tr("On startup"), startup) toolbox = QWidget(self, objectName="toolbox-group") toolbox.setLayout(QVBoxLayout()) toolbox.layout().setContentsMargins(0, 0, 0, 0) exclusive = QCheckBox(self.tr("Only one tab can be open at a time")) self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive") toolbox.layout().addWidget(exclusive) form.addRow(self.tr("Tool box"), toolbox) tab.setLayout(form) # Output Tab tab = QWidget() self.addTab(tab, self.tr("Output"), toolTip="Output Redirection") form = QFormLayout() box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) combo = QComboBox() combo.addItems([ self.tr("Critical"), self.tr("Error"), self.tr("Warn"), self.tr("Info"), self.tr("Debug") ]) self.bind(combo, "currentIndex", "logging/level") layout.addWidget(combo) box.setLayout(layout) form.addRow(self.tr("Logging"), box) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("Open in external browser"), objectName="open-in-external-browser") self.bind(cb1, "checked", "help/open-in-external-browser") layout.addWidget(cb1) box.setLayout(layout) form.addRow(self.tr("Help window"), box) tab.setLayout(form) # Error Reporting Tab tab = QWidget() self.addTab(tab, self.tr("Error Reporting"), toolTip="Settings related to error reporting") form = QFormLayout() line_edit_mid = QLineEdit() self.bind(line_edit_mid, "text", "error-reporting/machine-id") form.addRow("Machine ID:", line_edit_mid) tab.setLayout(form) if self.__macUnified: # Need some sensible size otherwise mac unified toolbar 'takes' # the space that should be used for layout of the contents self.adjustSize() def addTab(self, widget, text, toolTip=None, icon=None): if self.__macUnified: action = QAction(text, self) if toolTip: action.setToolTip(toolTip) if icon: action.setIcon(toolTip) action.setData(len(self.tab.actions())) self.tab.addAction(action) self.stack.addWidget(widget) else: i = self.tab.addTab(widget, text) if toolTip: self.tab.setTabToolTip(i, toolTip) if icon: self.tab.setTabIcon(i, icon) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.hide() self.deleteLater() def bind(self, source, source_property, key, transformer=None): target = UserDefaultsPropertyBinding(self.__settings, key) source = PropertyBinding(source, source_property) source.set(target.get()) self._manager.bind(target, source) def commit(self): self._manager.commit() def revert(self): self._manager.revert() def reset(self): for target, source in self._manager.bindings(): try: source.reset() except NotImplementedError: # Cannot reset. pass except Exception: log.error("Error reseting %r", source.propertyName, exc_info=True) def exec_(self): self.__loop = QEventLoop() self.show() status = self.__loop.exec_() self.__loop = None return status def hideEvent(self, event): QMainWindow.hideEvent(self, event) if self.__loop is not None: self.__loop.exit(0) self.__loop = None def __macOnToolBarAction(self, action): self.stack.setCurrentIndex(action.data())
class QuickMenu(FramelessWindow): """ A quick menu popup for the widgets. The widgets are set using :func:`QuickMenu.setModel` which must be a model as returned by :func:`QtWidgetRegistry.model` """ #: An action has been triggered in the menu. triggered = Signal(QAction) #: An action has been hovered in the menu hovered = Signal(QAction) def __init__(self, parent=None, **kwargs): FramelessWindow.__init__(self, parent, **kwargs) self.setWindowFlags(Qt.Popup) self.__filterFunc = None self.__sortingFunc = None self.__setupUi() self.__loop = None self.__model = QStandardItemModel() self.__triggeredAction = None def __setupUi(self): self.setLayout(QVBoxLayout(self)) self.layout().setContentsMargins(6, 6, 6, 6) self.__search = SearchWidget(self, objectName="search-line") self.__search.setPlaceholderText( self.tr("Search for widget or select from the list.") ) self.layout().addWidget(self.__search) self.__frame = QFrame(self, objectName="menu-frame") layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) self.__frame.setLayout(layout) self.layout().addWidget(self.__frame) self.__pages = PagedMenu(self, objectName="paged-menu") self.__pages.currentChanged.connect(self.setCurrentIndex) self.__pages.triggered.connect(self.triggered) self.__pages.hovered.connect(self.hovered) self.__frame.layout().addWidget(self.__pages) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) self.__suggestPage = SuggestMenuPage(self, objectName="suggest-page") self.__suggestPage.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE) self.__suggestPage.setIcon(icon_loader().get("icons/Search.svg")) if sys.platform == "darwin": view = self.__suggestPage.view() view.verticalScrollBar().setAttribute(Qt.WA_MacMiniSize, True) # Don't show the focus frame because it expands into the tab bar. view.setAttribute(Qt.WA_MacShowFocusRect, False) i = self.addPage(self.tr("Quick Search"), self.__suggestPage) button = self.__pages.tabButton(i) button.setObjectName("search-tab-button") button.setStyleSheet( "TabButton {\n" " qproperty-flat_: false;\n" " border: none;" "}\n") self.__search.textEdited.connect(self.__on_textEdited) self.__navigator = ItemViewKeyNavigator(self) self.__navigator.setView(self.__suggestPage.view()) self.__search.installEventFilter(self.__navigator) self.__grip = WindowSizeGrip(self) self.__grip.raise_() def setSizeGripEnabled(self, enabled): """ Enable the resizing of the menu with a size grip in a bottom right corner (enabled by default). """ if bool(enabled) != bool(self.__grip): if self.__grip: self.__grip.deleteLater() self.__grip = None else: self.__grip = WindowSizeGrip(self) self.__grip.raise_() def sizeGripEnabled(self): """ Is the size grip enabled. """ return bool(self.__grip) def addPage(self, name, page): """ Add the `page` (:class:`MenuPage`) with `name` and return it's index. The `page.icon()` will be used as the icon in the tab bar. """ icon = page.icon() tip = name if page.toolTip(): tip = page.toolTip() index = self.__pages.addPage(page, name, icon, tip) # Route the page's signals page.triggered.connect(self.__onTriggered) page.hovered.connect(self.hovered) # Install event filter to intercept key presses. page.view().installEventFilter(self) return index def createPage(self, index): """ Create a new page based on the contents of an index (:class:`QModeIndex`) item. """ page = MenuPage(self) page.setModel(index.model()) page.setRootIndex(index) view = page.view() if sys.platform == "darwin": view.verticalScrollBar().setAttribute(Qt.WA_MacMiniSize, True) # Don't show the focus frame because it expands into the tab # bar at the top. view.setAttribute(Qt.WA_MacShowFocusRect, False) name = str(index.data(Qt.DisplayRole)) page.setTitle(name) icon = index.data(Qt.DecorationRole) if isinstance(icon, QIcon): page.setIcon(icon) page.setToolTip(index.data(Qt.ToolTipRole)) return page def setModel(self, model): """ Set the model containing the actions. """ root = model.invisibleRootItem() for i in range(root.rowCount()): item = root.child(i) index = item.index() page = self.createPage(index) page.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE) i = self.addPage(page.title(), page) brush = index.data(QtWidgetRegistry.BACKGROUND_ROLE) if isinstance(brush, QBrush): base_color = brush.color() button = self.__pages.tabButton(i) button.setStyleSheet( TAB_BUTTON_STYLE_TEMPLATE % (create_css_gradient(base_color), create_css_gradient(base_color.darker(120))) ) self.__model = model self.__suggestPage.setModel(model) def setSortingFunc(self, func): """ Set a sorting function in the suggest (search) menu. """ if self.__sortingFunc != func: self.__sortingFunc = func for i in range(0, self.__pages.count()): if isinstance(self.__pages.page(i), SuggestMenuPage): self.__pages.page(i).setSortingFunc(func) def setFilterFunc(self, func): """ Set a filter function. """ if func != self.__filterFunc: self.__filterFunc = func for i in range(0, self.__pages.count()): self.__pages.page(i).setFilterFunc(func) def popup(self, pos=None, searchText=""): """ Popup the menu at `pos` (in screen coordinates). 'Search' text field is initialized with `searchText` if provided. """ if pos is None: pos = QPoint() self.__clearCurrentItems() self.__search.setText(searchText) patt = QRegExp("(^|\W)"+searchText) patt.setCaseSensitivity(False) self.__suggestPage.setFilterRegExp(patt) UsageStatistics.set_last_search_query(searchText) self.ensurePolished() if self.testAttribute(Qt.WA_Resized) and self.sizeGripEnabled(): size = self.size() else: size = self.sizeHint() desktop = QApplication.desktop() screen_geom = desktop.availableGeometry(pos) # Adjust the size to fit inside the screen. if size.height() > screen_geom.height(): size.setHeight(screen_geom.height()) if size.width() > screen_geom.width(): size.setWidth(screen_geom.width()) geom = QRect(pos, size) if geom.top() < screen_geom.top(): geom.setTop(screen_geom.top()) if geom.left() < screen_geom.left(): geom.setLeft(screen_geom.left()) bottom_margin = screen_geom.bottom() - geom.bottom() right_margin = screen_geom.right() - geom.right() if bottom_margin < 0: # Falls over the bottom of the screen, move it up. geom.translate(0, bottom_margin) # TODO: right to left locale if right_margin < 0: # Falls over the right screen edge, move the menu to the # other side of pos. geom.translate(-size.width(), 0) self.setGeometry(geom) self.show() self.setFocusProxy(self.__search) def exec_(self, pos=None, searchText=""): """ Execute the menu at position `pos` (in global screen coordinates). Return the triggered :class:`QAction` or `None` if no action was triggered. 'Search' text field is initialized with `searchText` if provided. """ self.popup(pos, searchText) self.setFocus(Qt.PopupFocusReason) self.__triggeredAction = None self.__loop = QEventLoop() self.__loop.exec_() self.__loop.deleteLater() self.__loop = None action = self.__triggeredAction self.__triggeredAction = None return action def hideEvent(self, event): """ Reimplemented from :class:`QWidget` """ FramelessWindow.hideEvent(self, event) if self.__loop: self.__loop.exit() def setCurrentPage(self, page): """ Set the current shown page to `page`. """ self.__pages.setCurrentPage(page) def setCurrentIndex(self, index): """ Set the current page index. """ self.__pages.setCurrentIndex(index) def __clearCurrentItems(self): """ Clear any selected (or current) items in all the menus. """ for i in range(self.__pages.count()): self.__pages.page(i).view().selectionModel().clear() def __onTriggered(self, action): """ Re-emit the action from the page. """ self.__triggeredAction = action # Hide and exit the event loop if necessary. self.hide() self.triggered.emit(action) def __on_textEdited(self, text): patt = QRegExp("(^|\W)" + text) patt.setCaseSensitivity(False) self.__suggestPage.setFilterRegExp(patt) self.__pages.setCurrentPage(self.__suggestPage) self.__selectFirstIndex() UsageStatistics.set_last_search_query(text) def __selectFirstIndex(self): view = self.__pages.currentPage().view() model = view.model() index = model.index(0, 0) view.setCurrentIndex(index) def triggerSearch(self): """ Trigger action search. This changes to current page to the 'Suggest' page and sets the keyboard focus to the search line edit. """ self.__pages.setCurrentPage(self.__suggestPage) self.__search.setFocus(Qt.ShortcutFocusReason) # Make sure that the first enabled item is set current. self.__suggestPage.ensureCurrent() def keyPressEvent(self, event): if event.text(): # Ignore modifiers, ... self.__search.setFocus(Qt.ShortcutFocusReason) self.setCurrentIndex(0) self.__search.keyPressEvent(event) FramelessWindow.keyPressEvent(self, event) event.accept() def event(self, event): if event.type() == QEvent.ShortcutOverride: log.debug("Overriding shortcuts") event.accept() return True return FramelessWindow.event(self, event) def eventFilter(self, obj, event): if isinstance(obj, QTreeView): etype = event.type() if etype == QEvent.KeyPress: # ignore modifiers non printable characters, Enter, ... if event.text() and event.key() not in \ [Qt.Key_Enter, Qt.Key_Return]: self.__search.setFocus(Qt.ShortcutFocusReason) self.setCurrentIndex(0) self.__search.keyPressEvent(event) return True return FramelessWindow.eventFilter(self, obj, event)
class CategoryPopupMenu(FramelessWindow): triggered = Signal(QAction) hovered = Signal(QAction) def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.setWindowFlags(self.windowFlags() | Qt.Popup) layout = QVBoxLayout() layout.setContentsMargins(6, 6, 6, 6) self.__menu = MenuPage() self.__menu.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE) if sys.platform == "darwin": self.__menu.view().setAttribute(Qt.WA_MacShowFocusRect, False) self.__menu.triggered.connect(self.__onTriggered) self.__menu.hovered.connect(self.hovered) self.__dragListener = ItemViewDragStartEventListener(self) self.__dragListener.dragStarted.connect(self.__onDragStarted) self.__menu.view().viewport().installEventFilter(self.__dragListener) layout.addWidget(self.__menu) self.setLayout(layout) self.__action = None self.__loop = None self.__item = None def setCategoryItem(self, item): """ Set the category root item (:class:`QStandardItem`). """ self.__item = item model = item.model() self.__menu.setModel(model) self.__menu.setRootIndex(item.index()) def popup(self, pos=None): if pos is None: pos = self.pos() self.adjustSize() geom = widget_popup_geometry(pos, self) self.setGeometry(geom) self.show() def exec_(self, pos=None): self.popup(pos) self.__loop = QEventLoop() self.__action = None self.__loop.exec_() self.__loop = None if self.__action is not None: action = self.__action else: action = None return action def hideEvent(self, event): if self.__loop is not None: self.__loop.exit(0) return super().hideEvent(event) def __onTriggered(self, action): self.__action = action self.triggered.emit(action) self.hide() if self.__loop: self.__loop.exit(0) def __onDragStarted(self, index): desc = index.data(QtWidgetRegistry.WIDGET_DESC_ROLE) icon = index.data(Qt.DecorationRole) drag_data = QMimeData() drag_data.setData( "application/vnv.orange-canvas.registry.qualified-name", desc.qualified_name.encode('utf-8') ) drag = QDrag(self) drag.setPixmap(icon.pixmap(38)) drag.setMimeData(drag_data) # TODO: Should animate (accept) hide. self.hide() # When a drag is started and the menu hidden the item's tool tip # can still show for a short time UNDER the cursor preventing a # drop. viewport = self.__menu.view().viewport() filter = ToolTipEventFilter() viewport.installEventFilter(filter) drag.exec_(Qt.CopyAction) viewport.removeEventFilter(filter)
class UserSettingsDialog(QMainWindow): """ A User Settings/Defaults dialog. """ MAC_UNIFIED = True def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.setWindowFlags(Qt.Dialog) self.setWindowModality(Qt.ApplicationModal) self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) self.__macUnified = sys.platform == "darwin" and self.MAC_UNIFIED self._manager = BindingManager(self, submitPolicy=BindingManager.AutoSubmit) self.__loop = None self.__settings = config.settings() self.__setupUi() def __setupUi(self): """Set up the UI. """ if self.__macUnified: self.tab = QToolBar( floatable=False, movable=False, allowedAreas=Qt.TopToolBarArea, ) self.addToolBar(Qt.TopToolBarArea, self.tab) self.setUnifiedTitleAndToolBarOnMac(True) # This does not seem to work self.setWindowFlags(self.windowFlags() & \ ~Qt.MacWindowToolBarButtonHint) self.tab.actionTriggered[QAction].connect( self.__macOnToolBarAction ) central = QStackedWidget() central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) else: self.tab = central = QTabWidget(self) # Add a close button to the bottom of the dialog # (to satisfy GNOME 3 which shows the dialog without a title bar). container = container_widget_helper() container.layout().addWidget(central) buttonbox = QDialogButtonBox(QDialogButtonBox.Close) buttonbox.rejected.connect(self.close) container.layout().addWidget(buttonbox) self.setCentralWidget(container) self.stack = central # General Tab tab = QWidget() self.addTab(tab, self.tr("General"), toolTip=self.tr("General Options")) form = QFormLayout() tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) nodes = QWidget(self, objectName="nodes") nodes.setLayout(QVBoxLayout()) nodes.layout().setContentsMargins(0, 0, 0, 0) cb_anim = QCheckBox( self.tr("Enable node animations"), objectName="enable-node-animations", toolTip=self.tr("Enable shadow and ping animations for nodes " "in the workflow.") ) self.bind(cb_anim, "checked", "schemeedit/enable-node-animations") nodes.layout().addWidget(cb_anim) form.addRow(self.tr("Nodes"), nodes) links = QWidget(self, objectName="links") links.setLayout(QVBoxLayout()) links.layout().setContentsMargins(0, 0, 0, 0) cb_show = QCheckBox( self.tr("Show channel names between widgets"), objectName="show-channel-names", toolTip=self.tr("Show source and sink channel names " "over the links.") ) self.bind(cb_show, "checked", "schemeedit/show-channel-names") links.layout().addWidget(cb_show) form.addRow(self.tr("Links"), links) quickmenu = QWidget(self, objectName="quickmenu-options") quickmenu.setLayout(QVBoxLayout()) quickmenu.layout().setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("On double click"), toolTip=self.tr("Open quick menu on a double click " "on an empty spot in the canvas")) cb2 = QCheckBox(self.tr("On right click"), toolTip=self.tr("Open quick menu on a right click " "on an empty spot in the canvas")) cb3 = QCheckBox(self.tr("On space key press"), toolTip=self.tr("On Space key press while the mouse" "is hovering over the canvas.")) cb4 = QCheckBox(self.tr("On any key press"), toolTip=self.tr("On any key press while the mouse" "is hovering over the canvas.")) self.bind(cb1, "checked", "quickmenu/trigger-on-double-click") self.bind(cb2, "checked", "quickmenu/trigger-on-right-click") self.bind(cb3, "checked", "quickmenu/trigger-on-space-key") self.bind(cb4, "checked", "quickmenu/trigger-on-any-key") quickmenu.layout().addWidget(cb1) quickmenu.layout().addWidget(cb2) quickmenu.layout().addWidget(cb3) quickmenu.layout().addWidget(cb4) form.addRow(self.tr("Open quick menu on"), quickmenu) startup = QWidget(self, objectName="startup-group") startup.setLayout(QVBoxLayout()) startup.layout().setContentsMargins(0, 0, 0, 0) cb_splash = QCheckBox(self.tr("Show splash screen"), self, objectName="show-splash-screen") cb_welcome = QCheckBox(self.tr("Show welcome screen"), self, objectName="show-welcome-screen") self.bind(cb_splash, "checked", "startup/show-splash-screen") self.bind(cb_welcome, "checked", "startup/show-welcome-screen") startup.layout().addWidget(cb_splash) startup.layout().addWidget(cb_welcome) form.addRow(self.tr("On startup"), startup) toolbox = QWidget(self, objectName="toolbox-group") toolbox.setLayout(QVBoxLayout()) toolbox.layout().setContentsMargins(0, 0, 0, 0) exclusive = QCheckBox(self.tr("Only one tab can be open at a time")) self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive") toolbox.layout().addWidget(exclusive) form.addRow(self.tr("Tool box"), toolbox) tab.setLayout(form) # Output Tab tab = QWidget() self.addTab(tab, self.tr("Output"), toolTip="Output Redirection") form = QFormLayout() box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) combo = QComboBox() combo.addItems([self.tr("Critical"), self.tr("Error"), self.tr("Warn"), self.tr("Info"), self.tr("Debug")]) self.bind(combo, "currentIndex", "logging/level") layout.addWidget(combo) box.setLayout(layout) form.addRow(self.tr("Logging"), box) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("Open in external browser"), objectName="open-in-external-browser") self.bind(cb1, "checked", "help/open-in-external-browser") layout.addWidget(cb1) box.setLayout(layout) form.addRow(self.tr("Help window"), box) tab.setLayout(form) # Categories Tab tab = QWidget() layout = QVBoxLayout() view = QListView( editTriggers=QListView.NoEditTriggers ) from .. import registry reg = registry.global_registry() model = QStandardItemModel() settings = QSettings() for cat in reg.categories(): item = QStandardItem() item.setText(cat.name) item.setCheckable(True) visible, _ = category_state(cat, settings) item.setCheckState(Qt.Checked if visible else Qt.Unchecked) model.appendRow([item]) view.setModel(model) layout.addWidget(view) tab.setLayout(layout) model.itemChanged.connect( lambda item: save_category_state( reg.category(str(item.text())), _State(item.checkState() == Qt.Checked, -1), settings ) ) self.addTab(tab, "Categories") # Add-ons Tab tab = QWidget() self.addTab(tab, self.tr("Add-ons"), toolTip="Settings related to add-on installation") form = QFormLayout() conda = QWidget(self, objectName="conda-group") conda.setLayout(QVBoxLayout()) conda.layout().setContentsMargins(0, 0, 0, 0) cb_conda_install = QCheckBox(self.tr("Install add-ons with conda"), self, objectName="allow-conda-experimental") self.bind(cb_conda_install, "checked", "add-ons/allow-conda-experimental") conda.layout().addWidget(cb_conda_install) form.addRow(self.tr("Conda"), conda) form.addRow(self.tr("Pip"), QLabel("Pip install arguments:")) line_edit_pip = QLineEdit() self.bind(line_edit_pip, "text", "add-ons/pip-install-arguments") form.addRow("", line_edit_pip) tab.setLayout(form) # Network Tab tab = QWidget() self.addTab(tab, self.tr("Network"), toolTip="Settings related to networking") form = QFormLayout() line_edit_http_proxy = QLineEdit() self.bind(line_edit_http_proxy, "text", "network/http-proxy") form.addRow("HTTP proxy:", line_edit_http_proxy) line_edit_https_proxy = QLineEdit() self.bind(line_edit_https_proxy, "text", "network/https-proxy") form.addRow("HTTPS proxy:", line_edit_https_proxy) tab.setLayout(form) if self.__macUnified: # Need some sensible size otherwise mac unified toolbar 'takes' # the space that should be used for layout of the contents self.adjustSize() def addTab(self, widget, text, toolTip=None, icon=None): if self.__macUnified: action = QAction(text, self) if toolTip: action.setToolTip(toolTip) if icon: action.setIcon(toolTip) action.setData(len(self.tab.actions())) self.tab.addAction(action) self.stack.addWidget(widget) else: i = self.tab.addTab(widget, text) if toolTip: self.tab.setTabToolTip(i, toolTip) if icon: self.tab.setTabIcon(i, icon) def widget(self, index): if self.__macUnified: return self.stack.widget(index) else: return self.tab.widget(index) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.hide() self.deleteLater() def bind(self, source, source_property, key, transformer=None): target = UserDefaultsPropertyBinding(self.__settings, key) source = PropertyBinding(source, source_property) source.set(target.get()) self._manager.bind(target, source) def commit(self): self._manager.commit() def revert(self): self._manager.revert() def reset(self): for target, source in self._manager.bindings(): try: source.reset() except NotImplementedError: # Cannot reset. pass except Exception: log.error("Error reseting %r", source.propertyName, exc_info=True) def exec_(self): self.__loop = QEventLoop() self.show() status = self.__loop.exec_() self.__loop = None refresh_proxies() return status def hideEvent(self, event): super().hideEvent(event) if self.__loop is not None: self.__loop.exit(0) self.__loop = None def __macOnToolBarAction(self, action): index = action.data() self.stack.setCurrentIndex(index)