From 268a213659287ebd4aabb7481f8b3b0c1dab1cd2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 25 Apr 2020 10:16:57 +0530 Subject: [PATCH] macOS: Fix keyboard shortcuts for select all, copy and paste not working in file dialogs. Fixes #1874499 [Select-All Broken in Import Dialogue](https://bugs.launchpad.net/calibre/+bug/1874499) --- src/calibre/gui2/bars.py | 61 ++++++++++++++++++++++++----- src/calibre/gui2/qt_file_dialogs.py | 30 ++++++++++---- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/src/calibre/gui2/bars.py b/src/calibre/gui2/bars.py index 61e358ac6f..6c1459c944 100644 --- a/src/calibre/gui2/bars.py +++ b/src/calibre/gui2/bars.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' from functools import partial from PyQt5.Qt import ( - Qt, QAction, QMenu, QObject, QToolBar, QToolButton, QSize, pyqtSignal, + Qt, QAction, QMenu, QObject, QToolBar, QToolButton, QSize, pyqtSignal, QKeySequence, QTimer, QPropertyAnimation, QEasingCurve, pyqtProperty, QPainter, QWidget) try: from PyQt5 import sip @@ -423,12 +423,17 @@ if isosx: @property def native_menubar(self): - return self.gui.native_menubar + mb = self.gui.native_menubar + if mb.parent() is None: + # Without this the menubar does not update correctly with Qt >= + # 5.6. See the last couple of lines in updateMenuBarImmediately + # in qcocoamenubar.mm + mb.setParent(self.gui) + return mb def __init__(self, location_manager, parent): QObject.__init__(self, parent) self.gui = parent - self.location_manager = location_manager self.added_actions = [] self.last_actions = [] @@ -440,14 +445,30 @@ if isosx: self.refresh_timer = t = QTimer(self) t.setInterval(200), t.setSingleShot(True), t.timeout.connect(self.refresh_bar) - def init_bar(self, actions): + def adapt_for_dialog(self, enter): + + def ac(text, key, role=QAction.TextHeuristicRole): + ans = QAction(text, self) + ans.setMenuRole(role) + ans.setShortcut(QKeySequence(key)) + self.edit_menu.addAction(ans) + return ans + mb = self.native_menubar - if mb.parent() is None: - # Without this the menubar does not update correctly with Qt >= - # 5.6. See the last couple of lines in updateMenuBarImmediately - # in qcocoamenubar.mm - mb.setParent(self.gui) - self.last_actions = actions + if enter: + self.clear_bar(mb) + self.edit_menu = QMenu() + self.edit_action = QAction(_('Edit'), self) + self.edit_action.setMenu(self.edit_menu) + ac(_('Copy'), QKeySequence.Copy), + ac(_('Paste'), QKeySequence.Paste), + ac(_('Select all'), QKeySequence.SelectAll), + mb.addAction(self.edit_action) + self.added_actions = [self.edit_action] + else: + self.refresh_bar() + + def clear_bar(self, mb): for ac in self.added_actions: m = ac.menu() if m is not None: @@ -460,6 +481,11 @@ if isosx: ac.deleteLater() self.added_actions = [] + def init_bar(self, actions): + mb = self.native_menubar + self.last_actions = actions + self.clear_bar(mb) + for what in actions: if what is None: continue @@ -585,6 +611,20 @@ else: # }}} +class AdaptMenuBarForDialog(object): + + def __init__(self, menu_bar): + self.menu_bar = menu_bar + + def __enter__(self): + if isosx and self.menu_bar.is_native_menubar: + self.menu_bar.adapt_for_dialog(True) + + def __exit__(self, *a): + if isosx and self.menu_bar.is_native_menubar: + self.menu_bar.adapt_for_dialog(False) + + class BarsManager(QObject): def __init__(self, donate_action, location_manager, parent): @@ -598,6 +638,7 @@ class BarsManager(QObject): self.menu_bar = MenuBar(self.location_manager, self.parent()) is_native_menubar = self.menu_bar.is_native_menubar + self.adapt_menu_bar_for_dialog = AdaptMenuBarForDialog(self.menu_bar) self.menubar_fallback = native_menubar_defaults['action-layout-menubar'] if is_native_menubar else () self.menubar_device_fallback = native_menubar_defaults['action-layout-menubar-device'] if is_native_menubar else () diff --git a/src/calibre/gui2/qt_file_dialogs.py b/src/calibre/gui2/qt_file_dialogs.py index 4740cc1a06..03149567a6 100644 --- a/src/calibre/gui2/qt_file_dialogs.py +++ b/src/calibre/gui2/qt_file_dialogs.py @@ -24,6 +24,15 @@ def select_initial_dir(q): return os.path.expanduser(u'~') +class Dummy(object): + + def __enter__(self): + pass + + def __exit__(self, *a): + pass + + class FileDialog(QObject): def __init__(self, title=_('Choose Files'), @@ -38,6 +47,9 @@ class FileDialog(QObject): combine_file_and_saved_dir=False ): from calibre.gui2 import dynamic, sanitize_env_vars + from calibre.gui2.ui import get_gui + gui = get_gui() + adapt_menubar = gui.bars_manager.adapt_menu_bar_for_dialog if gui is not None else Dummy() QObject.__init__(self) ftext = '' if filters: @@ -82,18 +94,21 @@ class FileDialog(QObject): if not use_native_dialog: opts |= QFileDialog.DontUseNativeDialog if mode == QFileDialog.AnyFile: - f = QFileDialog.getSaveFileName(parent, title, - initial_dir, ftext, "", opts) + with adapt_menubar: + f = QFileDialog.getSaveFileName(parent, title, + initial_dir, ftext, "", opts) if f and f[0]: self.selected_files.append(f[0]) elif mode == QFileDialog.ExistingFile: - f = QFileDialog.getOpenFileName(parent, title, - initial_dir, ftext, "", opts) + with adapt_menubar: + f = QFileDialog.getOpenFileName(parent, title, + initial_dir, ftext, "", opts) if f and f[0] and os.path.exists(f[0]): self.selected_files.append(f[0]) elif mode == QFileDialog.ExistingFiles: - fs = QFileDialog.getOpenFileNames(parent, title, initial_dir, - ftext, "", opts) + with adapt_menubar: + fs = QFileDialog.getOpenFileNames(parent, title, initial_dir, + ftext, "", opts) if fs and fs[0]: for f in fs[0]: f = unicode_type(f) @@ -108,7 +123,8 @@ class FileDialog(QObject): else: if mode == QFileDialog.Directory: opts |= QFileDialog.ShowDirsOnly - f = unicode_type(QFileDialog.getExistingDirectory(parent, title, initial_dir, opts)) + with adapt_menubar: + f = unicode_type(QFileDialog.getExistingDirectory(parent, title, initial_dir, opts)) if os.path.exists(f): self.selected_files.append(f) if self.selected_files: