Source code for pyqtcmd.uicommand
#!/usr/bin/env python3
"""
Integration of commands with UI elements
"""
try:
from PyQt5 import QtWidgets
QAction = QtWidgets.QAction
except ModuleNotFoundError: # pragma: no cover
from PyQt6 import QtGui
QAction = QtGui.QAction
[docs]class UICommand(QAction):
"""
An UICommand is the intermediate between UI elements and the
underlying Commands. A Command encapsulates a single behavior; an
UICommand has a longer life time since it's associated to a UI
element. Its role is to instantiate and run the right Command when
the user interacts with the element. It also has an
enabled/disabled state that you can manage by overriding the
should_be_enabled() method.
"""
def __init__(self, parent=None, *, text=None, icon=None, shortcut=None, tip=None):
super().__init__(parent)
self.triggered.connect(self.do)
self._check()
self.history().changed.connect(self._check)
if text is not None:
self.setText(text) # pragma: no cover
if icon is not None:
self.setIcon(icon) # pragma: no cover
if shortcut is not None:
self.setShortcut(shortcut) # pragma: no cover
if tip is not None:
self.setToolTip(tip)
self.setStatusTip(tip)
[docs] def history(self):
"""Override this to return the History object"""
raise NotImplementedError # pragma: no cover
[docs] def add_signal_check(self, signal):
"""
Call this, passing it a signal that could change the UIAction
state (enabled/disabled, etc) when it's fired. The signal's
signature does not matter.
"""
signal.connect(self._check)
[docs] def should_be_enabled(self): # pylint: disable=R0201
"""Return False from this method if the underlying UI element should be disabled."""
return True
[docs] def do(self):
"""
Subclasses should override this to do whatever is necessary
(for instance show a dialog for choosing a file name, etc) and
then instantiate the right Command class and run() it.
"""
raise NotImplementedError # pragma: no cover
def _check(self, *unused_):
self.setEnabled(self.should_be_enabled())
[docs]class UndoUICommandMixin:
"""
Undo UICommand. This is a mixin so you can inherit from your concrete UICommand subclass.
"""
def __init__(self, *args, text=None, **kwargs):
self.__original_text = text
super().__init__(*args, text=text, **kwargs)
def do(self): # pylint: disable=C0111
self.history().undo()
def should_be_enabled(self): # pylint: disable=C0111
return self.history().can_undo()
def _check(self, *unused_):
self.setText(self.history().undo_label() or self.__original_text)
super()._check()
[docs]class RedoUICommandMixin:
"""
Redo UICommand. This is a mixin so you can inherit from your concrete UICommand subclass.
"""
def __init__(self, *args, text=None, **kwargs):
self.__original_text = text
super().__init__(*args, text=text, **kwargs)
def do(self): # pylint: disable=C0111
self.history().redo()
def should_be_enabled(self): # pylint: disable=C0111
return self.history().can_redo()
def _check(self, *unused_):
self.setText(self.history().redo_label() or self.__original_text)
super()._check()
[docs]class NeedsSelectionUICommandMixin:
"""
A mixin for UICommand. The constructor takes a `container` keyword
argument. This must be an object which implements a
`selectionChanged` signal (of any signature) and a `selection()`
method, which should return the selection as an object with a
__len__.
"""
def __init__(self, parent=None, *, container, **kwargs):
self._container = container
super().__init__(parent, **kwargs)
self.add_signal_check(container.selectionChanged)
[docs] def container(self):
"""
Accessor for the underlying container
"""
return self._container
[docs] def selection(self):
"""
Returns the current selection. If you want to implement more
drastic limitations than 'selection is not empty' you can
override this to filter the actual underlying selection. The
action will be enabled iff this returns a non-empty iterable.
"""
return self.container().selection()
def should_be_enabled(self): # pylint: disable=C0111
return len(self.selection()) != 0 and super().should_be_enabled()