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,
rename_actions, delete_actions, qs_actions,
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):
enabled = loc == 'library'

View File

@ -7,10 +7,11 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sip
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 import gprefs
@ -204,61 +205,203 @@ class MenuAction(QAction): # {{{
self.setText(self.clone.text())
# }}}
class MenuBar(QMenuBar): # {{{
# MenuBar {{{
def __init__(self, location_manager, parent):
QMenuBar.__init__(self, parent)
self.gui = parent
self.setNativeMenuBar(True)
if isosx:
# On OS X we need special handling for the application global menu bar and
# the context menus, since Qt does not handle dynamic menus or menus in
# which the same action occurs in more than one place.
self.location_manager = location_manager
self.added_actions = []
class CloneAction(QAction):
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)
text_changed = pyqtSignal()
visibility_changed = pyqtSignal()
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)
def __init__(self, clone, parent, is_top_level=False, clone_shortcuts=True):
QAction.__init__(self, clone.text(), parent)
self.is_top_level = is_top_level
self.clone_shortcuts = clone_shortcuts
self.clone = clone
clone.changed.connect(self.clone_changed)
self.clone_changed()
self.triggered.connect(self.do_trigger)
def init_bar(self, actions):
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
def clone_changed(self):
otext = self.text()
self.setText(self.clone.text())
if otext != self.text:
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()
self.added_actions = []
def do_trigger(self, checked=False):
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:
continue
elif what == 'Location Manager':
for ac in self.location_manager.all_actions:
ac = self.build_menu(ac)
m.addSeparator()
elif what in iactions:
m.addAction(CloneAction(iactions[what].qaction, m))
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.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):
m = action.menu()
ac = MenuAction(action, self)
if m is None:
m = QMenu()
m.addAction(action)
ac.setMenu(m)
return ac
def build_menu(self, action):
m = action.menu()
ac = MenuAction(action, self)
if m is None:
m = QMenu()
m.addAction(action)
ac.setMenu(m)
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.menu_bar = MenuBar(self.location_manager, self.parent())
self.parent().setMenuBar(self.menu_bar)
self.apply_settings()
self.init_bars()

View File

@ -70,16 +70,11 @@ class LibraryViewMixin(object): # {{{
self.library_view.model().set_highlight_only(config['highlight_search_matches'])
def build_context_menus(self):
from calibre.gui2.bars import populate_menu
lm = QMenu(self)
def populate_menu(m, items):
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'])
populate_menu(lm, gprefs['action-layout-context-menu'], self.iactions)
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
self.library_view.set_context_menu(lm, ec)
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:
cm = QMenu(self.cover_flow)
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)
def search_done(self, view, ok):

View File

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