Workaround for broken handling of menus on OSX in Qt 5

This commit is contained in:
Kovid Goyal 2014-06-15 18:23:53 +05:30
parent 500b05da83
commit 8ea1a6c6cb
4 changed files with 202 additions and 58 deletions

View File

@ -359,6 +359,9 @@ class ChooseLibraryAction(InterfaceAction):
self.gui.location_manager.set_switch_actions(quick_actions, self.gui.location_manager.set_switch_actions(quick_actions,
rename_actions, delete_actions, qs_actions, rename_actions, delete_actions, qs_actions,
self.action_choose) self.action_choose)
# Allow the cloned actions in the OS X global menubar to update
for a in (self.qaction, self.menuless_qaction):
a.changed.emit()
def location_selected(self, loc): def location_selected(self, loc):
enabled = loc == 'library' enabled = loc == 'library'

View File

@ -7,10 +7,11 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import sip
from PyQt5.Qt import (Qt, QAction, QMenu, QMenuBar, QObject, from PyQt5.Qt import (Qt, QAction, QMenu, QMenuBar, QObject,
QToolBar, QToolButton, QSize) QToolBar, QToolButton, QSize, pyqtSignal, QTimer)
from calibre.constants import isosx
from calibre.gui2.throbber import create_donate_widget from calibre.gui2.throbber import create_donate_widget
from calibre.gui2 import gprefs from calibre.gui2 import gprefs
@ -204,61 +205,203 @@ class MenuAction(QAction): # {{{
self.setText(self.clone.text()) self.setText(self.clone.text())
# }}} # }}}
class MenuBar(QMenuBar): # {{{ # MenuBar {{{
def __init__(self, location_manager, parent): if isosx:
QMenuBar.__init__(self, parent) # On OS X we need special handling for the application global menu bar and
self.gui = parent # the context menus, since Qt does not handle dynamic menus or menus in
self.setNativeMenuBar(True) # which the same action occurs in more than one place.
self.location_manager = location_manager class CloneAction(QAction):
self.added_actions = []
self.donate_action = QAction(_('Donate'), self) text_changed = pyqtSignal()
self.donate_menu = QMenu() visibility_changed = pyqtSignal()
self.donate_menu.addAction(self.gui.donate_action)
self.donate_action.setMenu(self.donate_menu)
def update_lm_actions(self): def __init__(self, clone, parent, is_top_level=False, clone_shortcuts=True):
for ac in self.added_actions: QAction.__init__(self, clone.text(), parent)
clone = getattr(ac, 'clone', None) self.is_top_level = is_top_level
if clone is not None and clone in self.location_manager.all_actions: self.clone_shortcuts = clone_shortcuts
ac.setVisible(clone in self.location_manager.available_actions) self.clone = clone
clone.changed.connect(self.clone_changed)
self.clone_changed()
self.triggered.connect(self.do_trigger)
def init_bar(self, actions): def clone_changed(self):
for ac in self.added_actions: otext = self.text()
m = ac.menu() self.setText(self.clone.text())
if m is not None: if otext != self.text:
m.setVisible(False) self.text_changed.emit()
ov = self.isVisible()
self.setVisible(self.clone.isVisible())
if ov != self.isVisible():
self.visibility_changed.emit()
self.setEnabled(self.clone.isEnabled())
self.setCheckable(self.clone.isCheckable())
self.setChecked(self.clone.isChecked())
self.setIcon(self.clone.icon())
if self.clone_shortcuts:
self.setShortcuts(self.clone.shortcuts())
if self.clone.menu() is None:
if not self.is_top_level:
self.setMenu(None)
else:
m = QMenu(self.text(), self.parent())
for ac in QMenu.actions(self.clone.menu()):
if ac.isSeparator():
m.addSeparator()
else:
m.addAction(CloneAction(ac, self.parent(), clone_shortcuts=self.clone_shortcuts))
self.setMenu(m)
self.clear() def do_trigger(self, checked=False):
self.added_actions = [] if not sip.isdeleted(self.clone):
self.clone.trigger()
for what in actions: def populate_menu(m, items, iactions):
for what in items:
if what is None: if what is None:
continue m.addSeparator()
elif what == 'Location Manager': elif what in iactions:
for ac in self.location_manager.all_actions: m.addAction(CloneAction(iactions[what].qaction, m))
ac = self.build_menu(ac)
class MenuBar(QObject):
@property
def native_menubar(self):
return self.gui.native_menubar
def __init__(self, location_manager, parent):
QObject.__init__(self, parent)
self.gui = parent
self.location_manager = location_manager
self.added_actions = []
self.last_actions = []
self.donate_action = QAction(_('Donate'), self)
self.donate_menu = QMenu()
self.donate_menu.addAction(self.gui.donate_action)
self.donate_action.setMenu(self.donate_menu)
self.refresh_timer = t = QTimer(self)
t.setInterval(200), t.setSingleShot(True), t.timeout.connect(self.refresh_bar)
def init_bar(self, actions):
self.last_actions = actions
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
mb = self.native_menubar
for ac in self.added_actions:
mb.removeAction(ac)
if ac is not self.donate_action:
ac.setMenu(None)
ac.deleteLater()
self.added_actions = []
for what in actions:
if what is None:
continue
elif what == 'Location Manager':
for ac in self.location_manager.available_actions:
self.build_menu(ac, ac.isVisible())
elif what == 'Donate':
mb.addAction(self.donate_action)
elif what in self.gui.iactions:
action = self.gui.iactions[what]
self.build_menu(action.qaction)
def build_menu(self, ac, visible=True):
ans = CloneAction(ac, self.native_menubar, is_top_level=True)
ans.setVisible(visible)
if ans.menu() is None:
m = QMenu()
m.addAction(CloneAction(ac, self.native_menubar))
ans.setMenu(m)
# Qt (as of 5.3.0) does not update global menubar entries
# correctly, so we have to rebuild the global menubar.
# Without this the Choose Library action shows the text
# 'Untitled' and the Location Manager items do not work.
ans.text_changed.connect(self.refresh_timer.start)
ans.visibility_changed.connect(self.refresh_timer.start)
self.native_menubar.addAction(ans)
self.added_actions.append(ans)
return ans
def setVisible(self, yes):
pass # no-op on OS X since menu bar is always visible
def update_lm_actions(self):
pass # no-op as this is taken care of by init_bar()
def refresh_bar(self):
self.init_bar(self.last_actions)
else:
def populate_menu(m, items, iactions):
for what in items:
if what is None:
m.addSeparator()
elif what in iactions:
m.addAction(iactions[what].qaction)
class MenuBar(QMenuBar):
def __init__(self, location_manager, parent):
QMenuBar.__init__(self, parent)
parent.setMenuBar(self)
self.gui = parent
self.location_manager = location_manager
self.added_actions = []
self.donate_action = QAction(_('Donate'), self)
self.donate_menu = QMenu()
self.donate_menu.addAction(self.gui.donate_action)
self.donate_action.setMenu(self.donate_menu)
def init_bar(self, actions):
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
self.clear()
self.added_actions = []
for what in actions:
if what is None:
continue
elif what == 'Location Manager':
for ac in self.location_manager.all_actions:
ac = self.build_menu(ac)
self.addAction(ac)
self.added_actions.append(ac)
ac.setVisible(False)
elif what == 'Donate':
self.addAction(self.donate_action)
elif what in self.gui.iactions:
action = self.gui.iactions[what]
ac = self.build_menu(action.qaction)
self.addAction(ac) self.addAction(ac)
self.added_actions.append(ac) self.added_actions.append(ac)
ac.setVisible(False)
elif what == 'Donate':
self.addAction(self.donate_action)
elif what in self.gui.iactions:
action = self.gui.iactions[what]
ac = self.build_menu(action.qaction)
self.addAction(ac)
self.added_actions.append(ac)
def build_menu(self, action): def build_menu(self, action):
m = action.menu() m = action.menu()
ac = MenuAction(action, self) ac = MenuAction(action, self)
if m is None: if m is None:
m = QMenu() m = QMenu()
m.addAction(action) m.addAction(action)
ac.setMenu(m) ac.setMenu(m)
return ac return ac
def update_lm_actions(self):
for ac in self.added_actions:
clone = getattr(ac, 'clone', None)
if clone is not None and clone in self.location_manager.all_actions:
ac.setVisible(clone in self.location_manager.available_actions)
# }}} # }}}
@ -275,7 +418,6 @@ class BarsManager(QObject):
self.child_bars = tuple(bars[2:]) self.child_bars = tuple(bars[2:])
self.menu_bar = MenuBar(self.location_manager, self.parent()) self.menu_bar = MenuBar(self.location_manager, self.parent())
self.parent().setMenuBar(self.menu_bar)
self.apply_settings() self.apply_settings()
self.init_bars() self.init_bars()

View File

@ -70,16 +70,11 @@ class LibraryViewMixin(object): # {{{
self.library_view.model().set_highlight_only(config['highlight_search_matches']) self.library_view.model().set_highlight_only(config['highlight_search_matches'])
def build_context_menus(self): def build_context_menus(self):
from calibre.gui2.bars import populate_menu
lm = QMenu(self) lm = QMenu(self)
def populate_menu(m, items): populate_menu(lm, gprefs['action-layout-context-menu'], self.iactions)
for what in items:
if what is None:
m.addSeparator()
elif what in self.iactions:
m.addAction(self.iactions[what].qaction)
populate_menu(lm, gprefs['action-layout-context-menu'])
dm = QMenu(self) dm = QMenu(self)
populate_menu(dm, gprefs['action-layout-context-menu-device']) populate_menu(dm, gprefs['action-layout-context-menu-device'], self.iactions)
ec = self.iactions['Edit Collections'].qaction ec = self.iactions['Edit Collections'].qaction
self.library_view.set_context_menu(lm, ec) self.library_view.set_context_menu(lm, ec)
for v in (self.memory_view, self.card_a_view, self.card_b_view): for v in (self.memory_view, self.card_a_view, self.card_b_view):
@ -88,7 +83,7 @@ class LibraryViewMixin(object): # {{{
if self.cover_flow is not None: if self.cover_flow is not None:
cm = QMenu(self.cover_flow) cm = QMenu(self.cover_flow)
populate_menu(cm, populate_menu(cm,
gprefs['action-layout-context-menu-cover-browser']) gprefs['action-layout-context-menu-cover-browser'], self.iactions)
self.cover_flow.set_context_menu(cm) self.cover_flow.set_context_menu(cm)
def search_done(self, view, ok): def search_done(self, view, ok):

View File

@ -43,10 +43,10 @@ class GarbageCollector(QObject):
self.threshold = gc.get_threshold() self.threshold = gc.get_threshold()
gc.disable() gc.disable()
self.timer.start(self.INTERVAL) self.timer.start(self.INTERVAL)
#gc.set_debug(gc.DEBUG_SAVEALL) # gc.set_debug(gc.DEBUG_SAVEALL)
def check(self): def check(self):
#return self.debug_cycles() # return self.debug_cycles()
l0, l1, l2 = gc.get_count() l0, l1, l2 = gc.get_count()
if self.debug: if self.debug:
print ('gc_check called:', l0, l1, l2) print ('gc_check called:', l0, l1, l2)
@ -97,6 +97,10 @@ class MainWindow(QMainWindow):
quit_action.setMenuRole(QAction.QuitRole) quit_action.setMenuRole(QAction.QuitRole)
return preferences_action, quit_action return preferences_action, quit_action
@property
def native_menubar(self):
return self.___menu_bar
def __init__(self, opts, parent=None, disable_automatic_gc=False): def __init__(self, opts, parent=None, disable_automatic_gc=False):
QMainWindow.__init__(self, parent) QMainWindow.__init__(self, parent)
if disable_automatic_gc: if disable_automatic_gc: