def __init__(self, position=None, size=None, name=None, subtitle=None, **kwargs): # HACK to ensure that closeEvent is called only twice (seems like a # Qt bug). self._closed = False if not QApplication.instance(): # pragma: no cover raise RuntimeError("A Qt application must be created.") super(GUI, self).__init__() QMetaObject.connectSlotsByName(self) self.setDockOptions(QMainWindow.AllowTabbedDocks | QMainWindow.AllowNestedDocks | QMainWindow.AnimatedDocks) self._set_name(name, subtitle) self._set_pos_size(position, size) # Registered functions. self._registered = {} # Mapping {name: menuBar}. self._menus = {} # We can derive from EventEmitter because of a conflict with connect. self._event = EventEmitter() # Status bar. self._lock_status = False self._status_bar = QStatusBar() self.setStatusBar(self._status_bar) # List of attached Actions instances. self.actions = [] # Default actions. self._set_default_actions() # Create and attach snippets. self.snippets = Snippets(self) # Create the state. self.state = GUIState(self.name, **kwargs) @self.connect_ def on_show(): logger.debug("Load the geometry state.") gs = self.state.get('geometry_state', None) self.restore_geometry_state(gs) @self.connect_ def on_close(): logger.debug("Save the geometry state.") gs = self.save_geometry_state() self.state['geometry_state'] = gs # Save the state to disk when closing the GUI. self.state.save()
class DockWidget(QDockWidget): """A QDockWidget that can emit events.""" def __init__(self, *args, **kwargs): super(DockWidget, self).__init__(*args, **kwargs) self._event = EventEmitter() def emit(self, *args, **kwargs): return self._event.emit(*args, **kwargs) def connect_(self, *args, **kwargs): self._event.connect(*args, **kwargs) def closeEvent(self, e): """Qt slot when the window is closed.""" self.emit('close_widget') super(DockWidget, self).closeEvent(e)
def __init__(self, *args, **kwargs): super(DockWidget, self).__init__(*args, **kwargs) self._event = EventEmitter()
class GUI(QMainWindow): """A Qt main window holding docking Qt or VisPy widgets. `GUI` derives from `QMainWindow`. Events ------ close show add_view close_view Note ---- Use `connect_()`, not `connect()`, because of a name conflict with Qt. """ def __init__(self, position=None, size=None, name=None, subtitle=None, **kwargs): # HACK to ensure that closeEvent is called only twice (seems like a # Qt bug). self._closed = False if not QApplication.instance(): # pragma: no cover raise RuntimeError("A Qt application must be created.") super(GUI, self).__init__() QMetaObject.connectSlotsByName(self) self.setDockOptions(QMainWindow.AllowTabbedDocks | QMainWindow.AllowNestedDocks | QMainWindow.AnimatedDocks) self._set_name(name, subtitle) self._set_pos_size(position, size) # Registered functions. self._registered = {} # Mapping {name: menuBar}. self._menus = {} # We can derive from EventEmitter because of a conflict with connect. self._event = EventEmitter() # Status bar. self._lock_status = False self._status_bar = QStatusBar() self.setStatusBar(self._status_bar) # List of attached Actions instances. self.actions = [] # Default actions. self._set_default_actions() # Create and attach snippets. self.snippets = Snippets(self) # Create the state. self.state = GUIState(self.name, **kwargs) @self.connect_ def on_show(): logger.debug("Load the geometry state.") gs = self.state.get('geometry_state', None) self.restore_geometry_state(gs) @self.connect_ def on_close(): logger.debug("Save the geometry state.") gs = self.save_geometry_state() self.state['geometry_state'] = gs # Save the state to disk when closing the GUI. self.state.save() def _set_name(self, name, subtitle): if name is None: name = self.__class__.__name__ title = name if not subtitle else name + ' - ' + subtitle self.setWindowTitle(title) self.setObjectName(name) # Set the name in the GUI. self.name = name def _set_pos_size(self, position, size): if position is not None: self.move(position[0], position[1]) if size is not None: self.resize(QSize(size[0], size[1])) def _set_default_actions(self): self.default_actions = Actions(self, name='Default', menu='&File') @self.default_actions.add(shortcut=('HelpContents', 'h')) def show_all_shortcuts(): """Show the shortcuts of all actions.""" for actions in self.actions: actions.show_shortcuts() @self.default_actions.add(shortcut='ctrl+q') def exit(): """Close the GUI.""" self.close() # Events # ------------------------------------------------------------------------- def emit(self, *args, **kwargs): return self._event.emit(*args, **kwargs) def connect_(self, *args, **kwargs): self._event.connect(*args, **kwargs) def unconnect_(self, *args, **kwargs): self._event.unconnect(*args, **kwargs) def closeEvent(self, e): """Qt slot when the window is closed.""" if self._closed: return self._closed = True res = self.emit('close') # Discard the close event if False is returned by one of the callback # functions. if False in res: # pragma: no cover e.ignore() return super(GUI, self).closeEvent(e) def show(self): """Show the window.""" self.emit('show') super(GUI, self).show() # Views # ------------------------------------------------------------------------- def _get_view_index(self, view): """Index of a view in the GUI: 0 for the first view of a given class, 1 for the next, and so on.""" name = view.__class__.__name__ return len(self.list_views(name)) def add_view(self, view, name=None, position=None, closable=False, floatable=True, floating=None): """Add a widget to the main window.""" # Set the name in the view. view.view_index = self._get_view_index(view) # The view name is `<class_name><view_index>`, e.g. `MyView0`. view.name = name or view.__class__.__name__ + str(view.view_index) # Get the Qt canvas for VisPy and matplotlib views. widget = _try_get_vispy_canvas(view) widget = _try_get_matplotlib_canvas(widget) dock_widget = _create_dock_widget( widget, view.name, closable=closable, floatable=floatable, ) self.addDockWidget(_get_dock_position(position), dock_widget) if floating is not None: dock_widget.setFloating(floating) dock_widget.view = view # Emit the close_view event when the dock widget is closed. @dock_widget.connect_ def on_close_widget(): self.emit('close_view', view) dock_widget.show() self.emit('add_view', view) logger.log(5, "Add %s to GUI.", view.name) return dock_widget def list_views(self, name='', is_visible=True): """List all views which name start with a given string.""" children = self.findChildren(QWidget) return [ child.view for child in children if isinstance(child, QDockWidget) and child.view.name.startswith( name) and (child.isVisible() if is_visible else True) and child.width() >= 10 and child.height() >= 10 ] def view_count(self): """Return the number of opened views.""" views = self.list_views() counts = defaultdict(lambda: 0) for view in views: counts[view.name] += 1 return dict(counts) # Menu bar # ------------------------------------------------------------------------- def get_menu(self, name): """Return or create a menu.""" if name not in self._menus: self._menus[name] = self.menuBar().addMenu(name) return self._menus[name] def dialog(self, message): """Show a message in a dialog box.""" box = QMessageBox() box.setText(message) return box # Status bar # ------------------------------------------------------------------------- @property def status_message(self): """The message in the status bar.""" return str(self._status_bar.currentMessage()) @status_message.setter def status_message(self, value): if self._lock_status: return self._status_bar.showMessage(str(value)) def lock_status(self): self._lock_status = True def unlock_status(self): self._lock_status = False # State # ------------------------------------------------------------------------- def save_geometry_state(self): """Return picklable geometry and state of the window and docks. This function can be called in `on_close()`. """ return { 'geometry': self.saveGeometry(), 'state': self.saveState(), } def restore_geometry_state(self, gs): """Restore the position of the main window and the docks. The gui widgets need to be recreated first. This function can be called in `on_show()`. """ if not gs: return if gs.get('geometry', None): self.restoreGeometry((gs['geometry'])) if gs.get('state', None): self.restoreState((gs['state']))
def __init__(self, position=None, size=None, name=None, subtitle=None, **kwargs ): # HACK to ensure that closeEvent is called only twice (seems like a # Qt bug). self._closed = False if not QApplication.instance(): # pragma: no cover raise RuntimeError("A Qt application must be created.") super(GUI, self).__init__() QMetaObject.connectSlotsByName(self) self.setDockOptions(QMainWindow.AllowTabbedDocks | QMainWindow.AllowNestedDocks | QMainWindow.AnimatedDocks ) self._set_name(name, subtitle) position = position or (200, 200) size = size or (800, 600) self._set_pos_size(position, size) # Registered functions. self._registered = {} # Mapping {name: menuBar}. self._menus = {} # We can derive from EventEmitter because of a conflict with connect. self._event = EventEmitter() # Status bar. self._lock_status = False self._status_bar = QStatusBar() self.setStatusBar(self._status_bar) # List of attached Actions instances. self.actions = [] # Default actions. self._set_default_actions() # Create and attach snippets. self.snippets = Snippets(self) # Create the state. self.state = GUIState(self.name, **kwargs) @self.connect_ def on_show(): logger.debug("Load the geometry state.") gs = self.state.get('geometry_state', None) self.restore_geometry_state(gs) @self.connect_ def on_close(): logger.debug("Save the geometry state.") gs = self.save_geometry_state() self.state['geometry_state'] = gs # Save the state to disk when closing the GUI. self.state.save()
class GUI(QMainWindow): """A Qt main window holding docking Qt or VisPy widgets. `GUI` derives from `QMainWindow`. Events ------ close show add_view close_view Note ---- Use `connect_()`, not `connect()`, because of a name conflict with Qt. """ def __init__(self, position=None, size=None, name=None, subtitle=None, **kwargs ): # HACK to ensure that closeEvent is called only twice (seems like a # Qt bug). self._closed = False if not QApplication.instance(): # pragma: no cover raise RuntimeError("A Qt application must be created.") super(GUI, self).__init__() QMetaObject.connectSlotsByName(self) self.setDockOptions(QMainWindow.AllowTabbedDocks | QMainWindow.AllowNestedDocks | QMainWindow.AnimatedDocks ) self._set_name(name, subtitle) position = position or (200, 200) size = size or (800, 600) self._set_pos_size(position, size) # Registered functions. self._registered = {} # Mapping {name: menuBar}. self._menus = {} # We can derive from EventEmitter because of a conflict with connect. self._event = EventEmitter() # Status bar. self._lock_status = False self._status_bar = QStatusBar() self.setStatusBar(self._status_bar) # List of attached Actions instances. self.actions = [] # Default actions. self._set_default_actions() # Create and attach snippets. self.snippets = Snippets(self) # Create the state. self.state = GUIState(self.name, **kwargs) @self.connect_ def on_show(): logger.debug("Load the geometry state.") gs = self.state.get('geometry_state', None) self.restore_geometry_state(gs) @self.connect_ def on_close(): logger.debug("Save the geometry state.") gs = self.save_geometry_state() self.state['geometry_state'] = gs # Save the state to disk when closing the GUI. self.state.save() def _set_name(self, name, subtitle): if name is None: name = self.__class__.__name__ title = name if not subtitle else name + ' - ' + subtitle self.setWindowTitle(title) self.setObjectName(name) # Set the name in the GUI. self.name = name def _set_pos_size(self, position, size): if position is not None: self.move(position[0], position[1]) if size is not None: self.resize(QSize(size[0], size[1])) def _set_default_actions(self): self.default_actions = Actions(self, name='Default', menu='&File') @self.default_actions.add(shortcut=('HelpContents', 'h')) def show_all_shortcuts(): """Show the shortcuts of all actions.""" for actions in self.actions: actions.show_shortcuts() @self.default_actions.add(shortcut='?') def about(): # pragma: no cover """Display an about dialog.""" from phy import __version_git__ msg = "phy {} v{}".format(self.name, __version_git__) try: from phycontrib import __version__ msg += "\nphycontrib v{}".format(__version__) except ImportError: pass QMessageBox.about(self, "About", msg) @self.default_actions.add(shortcut='ctrl+q') def exit(): """Close the GUI.""" self.close() self.default_actions.separator() # Events # ------------------------------------------------------------------------- def emit(self, *args, **kwargs): return self._event.emit(*args, **kwargs) def connect_(self, *args, **kwargs): self._event.connect(*args, **kwargs) def unconnect_(self, *args, **kwargs): self._event.unconnect(*args, **kwargs) def closeEvent(self, e): """Qt slot when the window is closed.""" if self._closed: return res = self.emit('close') # Discard the close event if False is returned by one of the callback # functions. if False in res: # pragma: no cover e.ignore() return super(GUI, self).closeEvent(e) self._closed = True def show(self): """Show the window.""" self.emit('show') super(GUI, self).show() # Views # ------------------------------------------------------------------------- def _get_view_index(self, view): """Index of a view in the GUI: 0 for the first view of a given class, 1 for the next, and so on.""" name = view.__class__.__name__ return len(self.list_views(name)) def add_view(self, view, name=None, position=None, closable=False, floatable=True, floating=None): """Add a widget to the main window.""" # Set the name in the view. view.view_index = self._get_view_index(view) # The view name is `<class_name><view_index>`, e.g. `MyView0`. view.name = name or view.__class__.__name__ + str(view.view_index) # Get the Qt canvas for VisPy and matplotlib views. widget = _try_get_vispy_canvas(view) widget = _try_get_matplotlib_canvas(widget) dock_widget = _create_dock_widget(widget, view.name, closable=closable, floatable=floatable, ) self.addDockWidget(_get_dock_position(position), dock_widget) if floating is not None: dock_widget.setFloating(floating) dock_widget.view = view # Emit the close_view event when the dock widget is closed. @dock_widget.connect_ def on_close_widget(): self.emit('close_view', view) dock_widget.show() self.emit('add_view', view) logger.log(5, "Add %s to GUI.", view.name) return dock_widget def list_views(self, name='', is_visible=True): """List all views which name start with a given string.""" children = self.findChildren(QWidget) return [child.view for child in children if isinstance(child, QDockWidget) and child.view.name.startswith(name) and (child.isVisible() if is_visible else True) and child.width() >= 10 and child.height() >= 10 ] def get_view(self, name, is_visible=True): """Return a view from its name.""" views = self.list_views(name, is_visible=is_visible) return views[0] if views else None def view_count(self): """Return the number of opened views.""" views = self.list_views() counts = defaultdict(lambda: 0) for view in views: counts[view.name] += 1 return dict(counts) # Menu bar # ------------------------------------------------------------------------- def get_menu(self, name): """Return or create a menu.""" if name not in self._menus: self._menus[name] = self.menuBar().addMenu(name) return self._menus[name] def dialog(self, message): """Show a message in a dialog box.""" box = QMessageBox() box.setText(message) return box # Status bar # ------------------------------------------------------------------------- @property def status_message(self): """The message in the status bar.""" return str(self._status_bar.currentMessage()) @status_message.setter def status_message(self, value): if self._lock_status: return self._status_bar.showMessage(str(value)) def lock_status(self): self._lock_status = True def unlock_status(self): self._lock_status = False # State # ------------------------------------------------------------------------- def save_geometry_state(self): """Return picklable geometry and state of the window and docks. This function can be called in `on_close()`. """ return { 'geometry': self.saveGeometry(), 'state': self.saveState(), } def restore_geometry_state(self, gs): """Restore the position of the main window and the docks. The gui widgets need to be recreated first. This function can be called in `on_show()`. """ if not gs: return if gs.get('geometry', None): self.restoreGeometry((gs['geometry'])) if gs.get('state', None): self.restoreState((gs['state']))