diff --git a/src/calibre/gui2/dbus_export/demo.py b/src/calibre/gui2/dbus_export/demo.py index d1d5e728e8..7a67a37233 100644 --- a/src/calibre/gui2/dbus_export/demo.py +++ b/src/calibre/gui2/dbus_export/demo.py @@ -7,37 +7,51 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' from PyQt5.Qt import ( - QApplication, QMainWindow, QVBoxLayout, Qt, QKeySequence) + QApplication, QMainWindow, QVBoxLayout, Qt, QKeySequence, QAction, QActionGroup) from calibre.gui2.dbus_export.utils import setup_for_cli_run from calibre.gui2.dbus_export.widgets import factory setup_for_cli_run() +def make_checkable(ac, checked=True): + ac.setCheckable(True), ac.setChecked(checked) + class MainWindow(QMainWindow): + def __init__(self): + QMainWindow.__init__(self) + self.setMinimumWidth(400) + self.setWindowTitle('Demo of DBUS menu exporter and systray integration') + self.statusBar().showMessage(self.windowTitle()) + w = self.centralWidget() + self.l = QVBoxLayout(w) + mb = f.create_window_menubar(self) + self.setMenuBar(mb) + m = mb.addMenu('&One') + s = self.style() + for i, icon in zip(xrange(3), map(s.standardIcon, (s.SP_DialogOkButton, s.SP_DialogCancelButton, s.SP_ArrowUp))): + ac = m.addAction('One - &%d' % (i + 1)) + ac.setShortcut(QKeySequence(Qt.CTRL | (Qt.Key_1 + i), Qt.SHIFT | (Qt.Key_1 + i))) + ac.setIcon(icon) + m.addSeparator() + m.addAction('&Disabled action').setEnabled(False) + ac = m.addAction('A checkable action') + make_checkable(ac) + g = QActionGroup(self) + make_checkable(g.addAction(m.addAction('Exclusive 1'))) + make_checkable(g.addAction(m.addAction('Exclusive 2')), False) + for ac in mb.findChildren(QAction): + ac.triggered.connect(self.action_triggered) + def action_triggered(self, checked=False): - self.statusBar().showMessage('Action triggered: %s' % self.sender().text()) + ac = self.sender() + text = 'Action triggered: %s' % ac.text() + self.statusBar().showMessage(text) app = QApplication([]) f = factory() mw = MainWindow() -mw.setWindowTitle('Demo of DBUS menu exporter and systray integration') -mw.statusBar().showMessage(mw.windowTitle()) -w = mw.centralWidget() -mw.l = l = QVBoxLayout(w) -mb = f.create_window_menubar(mw) -mw.setMenuBar(mb) -m = mb.addMenu('&One') -s = mw.style() -for i, icon in zip(xrange(3), map(s.standardIcon, (s.SP_DialogOkButton, s.SP_DialogCancelButton, s.SP_ArrowUp))): - ac = m.addAction('One - &%d' % (i + 1)) - ac.triggered.connect(mw.action_triggered) - k = getattr(Qt, 'Key_%d' % (i + 1)) - ac.setShortcut(QKeySequence(Qt.CTRL | (Qt.Key_1 + i), Qt.SHIFT | (Qt.Key_1 + i))) - ac.setIcon(icon) -m.addSeparator() -m.addAction('&Disabled action').setEnabled(False) mw.show() print ('DBUS connection unique name:', f.bus.get_unique_name()) app.exec_() diff --git a/src/calibre/gui2/dbus_export/menu.py b/src/calibre/gui2/dbus_export/menu.py index 190aa73d0c..590d57df38 100644 --- a/src/calibre/gui2/dbus_export/menu.py +++ b/src/calibre/gui2/dbus_export/menu.py @@ -11,7 +11,7 @@ __copyright__ = '2014, Kovid Goyal ' import dbus from PyQt5.Qt import ( - QApplication, QMenu, QIcon, QKeySequence, QObject, QEvent, QTimer) + QApplication, QMenu, QIcon, QKeySequence, QObject, QEvent, QTimer, pyqtSignal, Qt) from calibre.utils.dbus_service import Object, BusName, method as dbus_method, dbus_property, signal as dbus_signal from calibre.gui2.dbus_export.utils import ( @@ -66,8 +66,13 @@ def create_properties_for_action(ac, previous=None): class DBusMenu(QObject): + handle_event_signal = pyqtSignal(object, object, object, object) + def __init__(self, object_path, **kw): QObject.__init__(self, kw.get('parent')) + # Unity barfs is the Event DBUS method does not return immediately, so + # handle it asynchronously + self.handle_event_signal.connect(self.handle_event, type=Qt.QueuedConnection) self.dbus_api = DBusMenuAPI(self, object_path, **kw) self.set_status = self.dbus_api.set_status self._next_id = 0 @@ -219,8 +224,9 @@ class DBusMenu(QObject): def handle_event(self, action_id, event, data, timestamp): ac = self.id_to_action(action_id) if event == 'clicked': - # TODO: Handle checkable actions - ac.triggered.emit() + if ac.isCheckable(): + ac.toggle() + ac.triggered.emit(ac.isCheckable() and ac.isChecked()) class DBusMenuAPI(Object): @@ -278,7 +284,7 @@ class DBusMenuAPI(Object): * "closed" Vendor specific events can be added by prefixing them with "x--"''' if self.menu.id_to_action(id) is not None: - self.menu.handle_event(id, eventId, data, timestamp) + self.menu.handle_event_signal.emit(id, eventId, data, timestamp) @dbus_method(IFACE, in_signature='a(isvu)', out_signature='ai') def EventGroup(self, events): @@ -289,7 +295,7 @@ class DBusMenuAPI(Object): missing = dbus.Array(signature='u') for id, eventId, data, timestamp in events: if self.menu.id_to_action(id) is not None: - self.menu.handle_event(id, eventId, data, timestamp) + self.menu.handle_event_signal.emit(id, eventId, data, timestamp) else: missing.append(id) return missing