diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index fa6d4edd62..dc19ca433e 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -12,7 +12,7 @@ from PyQt5.Qt import (Qt, QApplication, QStackedWidget, QMenu, QTimer, QVBoxLayout, QWidget, QSplitter, QToolButton, QIcon, QPainter, QStyleOption) from calibre.utils.config import prefs -from calibre.utils.icu import sort_key, primary_sort_key +from calibre.utils.icu import sort_key from calibre.constants import (isosx, __appname__, preferred_encoding, get_version) from calibre.gui2 import config, is_widescreen, gprefs, error_dialog, open_url @@ -22,6 +22,7 @@ from calibre.gui2.widgets import Splitter, LayoutButton from calibre.gui2.tag_browser.ui import TagBrowserWidget from calibre.gui2.book_details import BookDetails from calibre.gui2.notify import get_notifier +from calibre.gui2.layout_menu import LayoutMenu _keep_refs = [] @@ -591,7 +592,7 @@ class LayoutMixin(object): # {{{ b.setPopupMode(b.InstantPopup) b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) b.setText(_('Layout')), b.setIcon(QIcon(I('config.png'))) - b.setMenu(QMenu()), b.menu().aboutToShow.connect(self.populate_layout_menu) + b.setMenu(LayoutMenu(self)) b.setToolTip(_( 'Show and hide various parts of the calibre main window')) self.status_bar.addPermanentWidget(b) @@ -599,13 +600,6 @@ class LayoutMixin(object): # {{{ self.setStatusBar(self.status_bar) self.status_bar.update_label.linkActivated.connect(self.update_link_clicked) - def populate_layout_menu(self): - m = self.layout_button.menu() - m.clear() - buttons = sorted(self.layout_buttons, key=lambda b:primary_sort_key(b.label)) - for b in buttons: - m.addAction(b.icon(), b.text(), b.click) - def finalize_layout(self): self.status_bar.initialize(self.system_tray_icon) self.book_details.show_book_info.connect(self.iactions['Show Book Details'].show_book_info) diff --git a/src/calibre/gui2/layout_menu.py b/src/calibre/gui2/layout_menu.py new file mode 100644 index 0000000000..0992a5fdf6 --- /dev/null +++ b/src/calibre/gui2/layout_menu.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python2 +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2017, Kovid Goyal + +from __future__ import absolute_import, division, print_function, unicode_literals + +from PyQt5.Qt import ( + QFontMetrics, QHBoxLayout, QIcon, QMenu, QPainter, QPushButton, QSize, + QSizePolicy, Qt, QWidget, QStyleOption, QStyle) + +from calibre.utils.icu import primary_sort_key + +ICON_SZ = 64 + + +class LayoutItem(QWidget): + + def __init__(self, button, parent=None): + QWidget.__init__(self, parent) + self.mouse_over = False + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.button = button + self.text = button.label + self.setCursor(Qt.PointingHandCursor) + self.fm = QFontMetrics(self.font()) + self._bi = self._di = None + + @property + def bright_icon(self): + if self._bi is None: + self._bi = self.button.icon().pixmap(ICON_SZ, ICON_SZ) + return self._bi + + @property + def dull_icon(self): + if self._di is None: + self._di = self.button.icon().pixmap(ICON_SZ, ICON_SZ, mode=QIcon.Disabled) + return self._di + + def event(self, ev): + m = None + et = ev.type() + if et == ev.Enter: + m = True + elif et == ev.Leave: + m = False + if m is not None and m != self.mouse_over: + self.mouse_over = m + self.update() + return QWidget.event(self, ev) + + def sizeHint(self): + br = self.fm.boundingRect(self.text) + w = max(br.width(), ICON_SZ) + 10 + h = 2 * self.fm.lineSpacing() + ICON_SZ + 8 + return QSize(w, h) + + def paintEvent(self, ev): + shown = self.button.isChecked() + ls = self.fm.lineSpacing() + painter = QPainter(self) + if self.mouse_over: + tool = QStyleOption() + tool.rect = self.rect() + tool.state = QStyle.State_Raised | QStyle.State_Active | QStyle.State_MouseOver + s = self.style() + s.drawPrimitive(QStyle.PE_PanelButtonTool, tool, painter, self) + painter.drawText( + 0, 0, + self.width(), + ls, Qt.AlignCenter | Qt.TextSingleLine, self.text) + text = _('Hide') if shown else _('Show') + f = self.font() + f.setBold(True) + painter.setFont(f) + painter.drawText( + 0, self.height() - ls, + self.width(), + ls, Qt.AlignCenter | Qt.TextSingleLine, text) + x = (self.width() - ICON_SZ) // 2 + y = ls + (self.height() - ICON_SZ - 2 * ls) // 2 + pmap = self.bright_icon if shown else self.dull_icon + painter.drawPixmap(x, y, pmap) + painter.end() + + +class LayoutMenu(QMenu): + + def __init__(self, parent=None): + QMenu.__init__(self, parent) + self.l = l = QHBoxLayout(self) + l.setSpacing(20) + self.items = [] + if parent is None: + buttons = [ + QPushButton(QIcon(I(i + '.png')), i, self) + for i in 'search tags cover_flow grid book'.split()] + for b in buttons: + b.setVisible(False), b.setCheckable(True), b.setChecked(b.text() in 'tags grid') + b.label = b.text().capitalize() + else: + buttons = sorted( + parent.layout_buttons, key=lambda b: primary_sort_key(b.label)) + for b in buttons: + self.items.append(LayoutItem(b, self)) + l.addWidget(self.items[-1]) + self.current_item = None + + def sizeHint(self): + return QWidget.sizeHint(self) + + def paintEvent(self, ev): + return QWidget.paintEvent(self, ev) + + def item_for_ev(self, ev): + for item in self.items: + if item.geometry().contains(ev.pos()): + return item + + def mousePressEvent(self, ev): + if ev.button() != Qt.LeftButton: + ev.ignore() + return + if (ev.pos().isNull() and not ev.screenPos().isNull()) or not self.rect().contains(ev.pos()): + self.hide() + self.current_item = self.item_for_ev(ev) + if self.current_item is not None: + ev.accept() + else: + ev.ignore() + + def mouseReleaseEvent(self, ev): + if ev.button() != Qt.LeftButton: + ev.ignore() + return + item = self.item_for_ev(ev) + if item is not None and item is self.current_item: + ev.accept() + self.hide() + item.button.click() + + +if __name__ == '__main__': + from calibre.gui2 import Application + app = Application([]) + w = LayoutMenu() + w.show() + w.exec_()