Work on plugging the new layout code into the actual GUI

This commit is contained in:
Kovid Goyal 2023-12-22 13:03:14 +05:30
parent 3f446ae4af
commit 4c753c6f63
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
9 changed files with 227 additions and 589 deletions

View File

@ -3,7 +3,7 @@
from enum import Enum from enum import Enum
from functools import partial from functools import partial
from qt.core import QIcon, QToolButton from qt.core import QToolButton
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
@ -33,7 +33,7 @@ class LayoutActions(InterfaceAction):
m.addAction(_('Hide all'), self.hide_all) m.addAction(_('Hide all'), self.hide_all)
for button, name in zip(self.gui.layout_buttons, self.gui.button_order): for button, name in zip(self.gui.layout_buttons, self.gui.button_order):
m.addSeparator() m.addSeparator()
ic = QIcon.ic(button.icname) ic = button.icon()
m.addAction(ic, _('Show {}').format(button.label), partial(self.set_visible, Panel(name), True)) m.addAction(ic, _('Show {}').format(button.label), partial(self.set_visible, Panel(name), True))
m.addAction(ic, _('Hide {}').format(button.label), partial(self.set_visible, Panel(name), False)) m.addAction(ic, _('Hide {}').format(button.label), partial(self.set_visible, Panel(name), False))

View File

@ -10,38 +10,7 @@ from qt.core import QAction, QTimer
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2.dialogs.quickview import Quickview from calibre.gui2.dialogs.quickview import Quickview
from calibre.gui2 import error_dialog, gprefs from calibre.gui2 import error_dialog
from calibre.gui2.widgets import LayoutButton
class QuickviewButton(LayoutButton): # {{{
def __init__(self, gui, quickview_manager):
self.qv = quickview_manager
qaction = quickview_manager.qaction
LayoutButton.__init__(self, 'quickview.png', _('Quickview'),
parent=gui, shortcut=qaction.shortcut().toString())
self.toggled.connect(self.update_state)
self.action_toggle = qaction
self.action_toggle.triggered.connect(self.toggle)
self.action_toggle.changed.connect(self.update_shortcut)
def update_state(self, checked):
if checked:
self.set_state_to_hide()
self.qv._show_quickview()
else:
self.set_state_to_show()
self.qv._hide_quickview()
def save_state(self):
gprefs['quickview visible'] = bool(self.isChecked())
def restore_state(self):
if gprefs.get('quickview visible', False):
self.toggle()
# }}}
current_qv_action_pi = None current_qv_action_pi = None
@ -104,10 +73,13 @@ class ShowQuickviewAction(InterfaceAction):
group=self.action_spec[0]) group=self.action_spec[0])
self.search_action.triggered.connect(self.search_quickview) self.search_action.triggered.connect(self.search_quickview)
self.qv_button = QuickviewButton(self.gui, self) @property
def qv_button(self):
return self.gui.layout_container.quick_view_button
def initialization_complete(self): def initialization_complete(self):
set_quickview_action_plugin(self) set_quickview_action_plugin(self)
self.qv_button.update_shortcut(self.qaction)
def _hide_quickview(self): def _hide_quickview(self):
''' '''

View File

@ -1156,9 +1156,9 @@ class DetailsLayout(QSplitter): # {{{
self.restoreState(s) self.restoreState(s)
self.setOrientation(Qt.Orientation.Vertical if self.vertical else Qt.Orientation.Horizontal) self.setOrientation(Qt.Orientation.Vertical if self.vertical else Qt.Orientation.Horizontal)
def setGeometry(self, r): def setGeometry(self, *a):
super().setGeometry(r) super().setGeometry(*a)
self.do_layout(r) self.do_layout(self.geometry())
def do_splitter_moved(self, *args): def do_splitter_moved(self, *args):
gprefs['book_details_widget_splitter_state'] = bytearray(self.saveState()) gprefs['book_details_widget_splitter_state'] = bytearray(self.saveState())

View File

@ -5,12 +5,12 @@ from copy import copy
from dataclasses import asdict, dataclass, fields from dataclasses import asdict, dataclass, fields
from enum import Enum, auto from enum import Enum, auto
from qt.core import ( from qt.core import (
QDialog, QHBoxLayout, QIcon, QKeySequence, QLabel, QPalette, QPointF, QSize, QAction, QDialog, QHBoxLayout, QIcon, QKeySequence, QLabel, QPalette, QPointF,
QSizePolicy, QStyle, QStyleOption, QStylePainter, Qt, QToolButton, QVBoxLayout, QSize, QSizePolicy, QStyle, QStyleOption, QStylePainter, Qt, QToolButton,
QWidget, pyqtSignal, QVBoxLayout, QWidget, pyqtSignal,
) )
from calibre.gui2 import Application, gprefs from calibre.gui2 import Application, config, gprefs
from calibre.gui2.cover_flow import MIN_SIZE from calibre.gui2.cover_flow import MIN_SIZE
HIDE_THRESHOLD = 10 HIDE_THRESHOLD = 10
@ -30,7 +30,9 @@ class Placeholder(QLabel):
class LayoutButton(QToolButton): class LayoutButton(QToolButton):
def __init__(self, name: str, icon: str, label: str, central: 'Central', shortcut=None): on_action_trigger = pyqtSignal(bool)
def __init__(self, name: str, icon: str, label: str, central: 'CentralContainer', shortcut=None):
super().__init__(central) super().__init__(central)
self.central = central self.central = central
self.label = label self.label = label
@ -39,14 +41,30 @@ class LayoutButton(QToolButton):
self.setIcon(QIcon.ic(icon)) self.setIcon(QIcon.ic(icon))
self.setCheckable(True) self.setCheckable(True)
self.setChecked(self.is_visible) self.setChecked(self.is_visible)
self.toggled.connect(central.layout_button_toggled) self.setCursor(Qt.CursorShape.PointingHandCursor)
if isinstance(central, CentralContainer):
self.toggled.connect(central.layout_button_toggled)
def initialize_with_gui(self, gui):
if self.shortcut is not None:
self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self)
self.action_toggle.changed.connect(self.update_shortcut)
self.action_toggle.triggered.connect(self.toggle_triggered)
gui.addAction(self.action_toggle)
gui.keyboard.register_shortcut(
f'toggle_central_panel_{self.name}', self.action_toggle.text(), default_keys=(self.shortcut,), action=self.action_toggle)
def toggle_triggered(self):
self.toggle()
self.on_action_trigger.emit(self.isChecked())
@property @property
def is_visible(self): def is_visible(self):
return getattr(self.central.is_visible, self.name) return getattr(self.central.is_visible, self.name)
def update_shortcut(self, action_toggle=None): def update_shortcut(self, action_toggle=None):
action_toggle = action_toggle or getattr(self, 'action_toggle', None) if not isinstance(action_toggle, QAction):
action_toggle = getattr(self, 'action_toggle', None)
if action_toggle: if action_toggle:
sc = ', '.join(sc.toString(QKeySequence.SequenceFormat.NativeText) sc = ', '.join(sc.toString(QKeySequence.SequenceFormat.NativeText)
for sc in action_toggle.shortcuts()) for sc in action_toggle.shortcuts())
@ -230,23 +248,31 @@ class Visibility:
setattr(self, f.name, getattr(c, f.name)) setattr(self, f.name, getattr(c, f.name))
class Central(QWidget): class CentralContainer(QWidget):
layout: Layout = Layout.wide layout: Layout = Layout.wide
def __init__(self, parent=None, prefs_name='main_window_central_widget_state'): def __init__(self, parent=None, prefs_name='main_window_central_widget_state', separate_cover_browser=None, for_develop=False):
self.separate_cover_browser = config['separate_cover_flow'] if separate_cover_browser is None else separate_cover_browser
self.prefs_name = prefs_name self.prefs_name = prefs_name
super().__init__(parent) super().__init__(parent)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.wide_desires = WideDesires() self.wide_desires = WideDesires()
self.narrow_desires = NarrowDesires() self.narrow_desires = NarrowDesires()
self.is_visible = Visibility() self.is_visible = Visibility()
self.tag_browser = Placeholder('tag browser', self) if for_develop:
self.book_list = Placeholder('book list', self) self.tag_browser = Placeholder('tag browser', self)
self.cover_browser = Placeholder('cover browser', self) self.book_list = Placeholder('book list', self)
self.cover_browser = Placeholder('cover browser', self)
self.book_details = Placeholder('book details', self)
self.quick_view = Placeholder('quick view', self)
else:
self.tag_browser = QWidget(self)
self.book_list = QWidget(self)
self.cover_browser = QWidget(self)
self.book_details = QWidget(self)
self.quick_view = QWidget(self)
self.cover_browser.setMinimumSize(MIN_SIZE) self.cover_browser.setMinimumSize(MIN_SIZE)
self.book_details = Placeholder('book details', self)
self.quick_view = Placeholder('quick view', self)
self.ignore_button_toggles = False self.ignore_button_toggles = False
self.tag_browser_button = LayoutButton('tag_browser', 'tags.png', _('Tag browser'), self, 'Shift+Alt+T') self.tag_browser_button = LayoutButton('tag_browser', 'tags.png', _('Tag browser'), self, 'Shift+Alt+T')
self.book_details_button = LayoutButton('book_details', 'book.png', _('Book details'), self, 'Shift+Alt+D') self.book_details_button = LayoutButton('book_details', 'book.png', _('Book details'), self, 'Shift+Alt+D')
@ -265,6 +291,28 @@ class Central(QWidget):
self.top_handle = h(Qt.Orientation.Horizontal) self.top_handle = h(Qt.Orientation.Horizontal)
self.bottom_handle = h(Qt.Orientation.Horizontal) self.bottom_handle = h(Qt.Orientation.Horizontal)
def set_widget(self, which, w):
existing = getattr(self, which)
existing.setVisible(False)
existing.setParent(None)
setattr(self, which, w)
w.setParent(self)
def initialize_with_gui(self, gui, book_list_widget):
self.tag_browser_button.initialize_with_gui(gui)
self.book_details_button.initialize_with_gui(gui)
self.cover_browser_button.initialize_with_gui(gui)
self.quick_view_button.initialize_with_gui(gui)
self.set_widget('book_details', gui.book_details)
self.set_widget('tag_browser', gui.tb_widget)
self.set_widget('book_list', book_list_widget)
# cover browser is set in CoverFlowMixin
# Quickview is set in quickview.py code
@property
def is_wide(self):
return self.layout is Layout.wide
def serialized_settings(self): def serialized_settings(self):
return { return {
'layout': self.layout.name, 'layout': self.layout.name,
@ -296,6 +344,7 @@ class Central(QWidget):
before = self.serialized_settings() before = self.serialized_settings()
self.unserialize_settings(gprefs.get(self.prefs_name) or {}) self.unserialize_settings(gprefs.get(self.prefs_name) or {})
if self.serialized_settings() != before: if self.serialized_settings() != before:
self.update_button_states_from_visibility()
self.relayout() self.relayout()
def reset_to_defaults(self): def reset_to_defaults(self):
@ -305,6 +354,7 @@ class Central(QWidget):
self.wide_desires.reset_to_defaults() self.wide_desires.reset_to_defaults()
self.narrow_desires.reset_to_defaults() self.narrow_desires.reset_to_defaults()
if self.serialized_settings() != before: if self.serialized_settings() != before:
self.update_button_states_from_visibility()
self.relayout() self.relayout()
def toggle_panel(self, which): def toggle_panel(self, which):
@ -322,6 +372,16 @@ class Central(QWidget):
setattr(self.is_visible, which, visible) setattr(self.is_visible, which, visible)
self.update_button_states_from_visibility() self.update_button_states_from_visibility()
def show_panel(self, which):
if not getattr(self.is_visible, which):
self.set_visibility_of(which, True)
self.relayout()
def hide_panel(self, which):
if getattr(self.is_visible, which):
self.set_visibility_of(which, False)
self.relayout()
def panel_name_for_handle(self, handle): def panel_name_for_handle(self, handle):
return self.panel_name_for_handle_wide(handle) if self.layout is Layout.wide else self.panel_name_for_handle_narrow(handle) return self.panel_name_for_handle_wide(handle) if self.layout is Layout.wide else self.panel_name_for_handle_narrow(handle)
@ -381,7 +441,7 @@ class Central(QWidget):
def relayout(self): def relayout(self):
self.tag_browser.setVisible(self.is_visible.tag_browser) self.tag_browser.setVisible(self.is_visible.tag_browser)
self.book_details.setVisible(self.is_visible.book_details) self.book_details.setVisible(self.is_visible.book_details)
self.cover_browser.setVisible(self.is_visible.cover_browser) self.cover_browser.setVisible(self.is_visible.cover_browser and not self.separate_cover_browser)
self.book_list.setVisible(self.is_visible.book_list) self.book_list.setVisible(self.is_visible.book_list)
self.quick_view.setVisible(self.is_visible.quick_view) self.quick_view.setVisible(self.is_visible.quick_view)
if self.layout is Layout.wide: if self.layout is Layout.wide:
@ -394,6 +454,12 @@ class Central(QWidget):
self.layout = Layout.narrow if self.layout is Layout.wide else Layout.wide self.layout = Layout.narrow if self.layout is Layout.wide else Layout.wide
self.relayout() self.relayout()
def button_for(self, which):
return getattr(self, which + '_button')
def sizeHint(self):
return QSize(800, 600)
# Wide {{{ # Wide {{{
def wide_handle_state(self, handle): def wide_handle_state(self, handle):
if handle is self.left_handle: if handle is self.left_handle:
@ -401,7 +467,7 @@ class Central(QWidget):
if handle is self.right_handle: if handle is self.right_handle:
return HandleState.both_visible if self.is_visible.book_details else HandleState.only_main_visible return HandleState.both_visible if self.is_visible.book_details else HandleState.only_main_visible
if handle is self.top_handle: if handle is self.top_handle:
if self.is_visible.cover_browser: if self.is_visible.cover_browser and not self.separate_cover_browser:
return HandleState.both_visible if self.is_visible.book_list else HandleState.only_side_visible return HandleState.both_visible if self.is_visible.book_list else HandleState.only_side_visible
return HandleState.only_main_visible return HandleState.only_main_visible
if handle is self.bottom_handle: if handle is self.bottom_handle:
@ -443,13 +509,13 @@ class Central(QWidget):
hs = h.state hs = h.state
if hs is HandleState.both_visible or hs is HandleState.only_side_visible: if hs is HandleState.both_visible or hs is HandleState.only_side_visible:
height = normal_handle_width height = normal_handle_width
if h is self.bottom_handle and hs is HandleState.only_main_visible: if hs is HandleState.only_main_visible and h is self.bottom_handle or (h is self.top_handle and self.separate_cover_browser):
height = 0 height = 0
h.resize(int(central_width), int(height)) h.resize(int(central_width), int(height))
available_height -= height available_height -= height
cb = max(self.cover_browser.minimumHeight(), int(self.wide_desires.cover_browser_height * self.height())) cb = max(self.cover_browser.minimumHeight(), int(self.wide_desires.cover_browser_height * self.height()))
if not self.is_visible.cover_browser: if not self.is_visible.cover_browser or self.separate_cover_browser:
cb = 0 cb = 0
qv = bl = 0 qv = bl = 0
if cb >= available_height: if cb >= available_height:
@ -466,7 +532,7 @@ class Central(QWidget):
bl = available_height - qv bl = available_height - qv
else: else:
bl = available_height bl = available_height
if self.is_visible.cover_browser: if self.is_visible.cover_browser and not self.separate_cover_browser:
self.cover_browser.setGeometry(int(central_x), 0, int(central_width), int(cb)) self.cover_browser.setGeometry(int(central_x), 0, int(central_width), int(cb))
self.top_handle.move(central_x, cb) self.top_handle.move(central_x, cb)
if self.is_visible.book_list: if self.is_visible.book_list:
@ -532,7 +598,7 @@ class Central(QWidget):
if handle is self.left_handle: if handle is self.left_handle:
return HandleState.both_visible if self.is_visible.tag_browser else HandleState.only_main_visible return HandleState.both_visible if self.is_visible.tag_browser else HandleState.only_main_visible
if handle is self.right_handle: if handle is self.right_handle:
if self.is_visible.cover_browser: if self.is_visible.cover_browser and not self.separate_cover_browser:
return HandleState.both_visible if self.is_visible.book_list else HandleState.only_side_visible return HandleState.both_visible if self.is_visible.book_list else HandleState.only_side_visible
return HandleState.only_main_visible return HandleState.only_main_visible
if handle is self.top_handle: if handle is self.top_handle:
@ -569,12 +635,15 @@ class Central(QWidget):
hs = h.state hs = h.state
if hs is HandleState.both_visible or hs is HandleState.only_side_visible: if hs is HandleState.both_visible or hs is HandleState.only_side_visible:
width = normal_handle_width width = normal_handle_width
if h is self.right_handle and self.separate_cover_browser:
width = 0
h.resize(int(width), int(central_height)) h.resize(int(width), int(central_height))
available_width -= width available_width -= width
tb = int(self.narrow_desires.tag_browser_width * self.width()) if self.is_visible.tag_browser else 0 tb = int(self.narrow_desires.tag_browser_width * self.width()) if self.is_visible.tag_browser else 0
cb = max(self.cover_browser.minimumWidth(), int(self.narrow_desires.cover_browser_width * self.width())) if self.is_visible.cover_browser else 0 cb = max(self.cover_browser.minimumWidth(),
int(self.narrow_desires.cover_browser_width * self.width())) if self.is_visible.cover_browser and not self.separate_cover_browser else 0
min_central_width = self.min_central_width_narrow() min_central_width = self.min_central_width_narrow()
if tb + cb > available_width - min_central_width: if tb + cb > max(0, available_width - min_central_width):
width_to_share = max(0, available_width - min_central_width) width_to_share = max(0, available_width - min_central_width)
cb = int(cb * width_to_share / (tb + cb)) cb = int(cb * width_to_share / (tb + cb))
cb = max(self.cover_browser.minimumWidth(), cb) cb = max(self.cover_browser.minimumWidth(), cb)
@ -587,7 +656,7 @@ class Central(QWidget):
self.left_handle.move(tb, 0) self.left_handle.move(tb, 0)
central_x = self.left_handle.x() + self.left_handle.width() central_x = self.left_handle.x() + self.left_handle.width()
self.right_handle.move(tb + central_width + self.left_handle.width(), 0) self.right_handle.move(tb + central_width + self.left_handle.width(), 0)
if self.is_visible.cover_browser: if self.is_visible.cover_browser and not self.separate_cover_browser:
self.cover_browser.setGeometry(int(self.right_handle.x() + self.right_handle.width()), 0, int(cb), int(self.height())) self.cover_browser.setGeometry(int(self.right_handle.x() + self.right_handle.width()), 0, int(cb), int(self.height()))
self.top_handle.resize(int(central_width), int(normal_handle_width if self.is_visible.quick_view else 0)) self.top_handle.resize(int(central_width), int(normal_handle_width if self.is_visible.quick_view else 0))
central_height -= self.top_handle.height() central_height -= self.top_handle.height()
@ -661,9 +730,6 @@ class Central(QWidget):
return {self.left_handle: 'tag_browser', self.right_handle: 'cover_browser', self.top_handle: 'quick_view', self.bottom_handle: 'book_details'}[handle] return {self.left_handle: 'tag_browser', self.right_handle: 'cover_browser', self.top_handle: 'quick_view', self.bottom_handle: 'book_details'}[handle]
# }}} # }}}
def sizeHint(self):
return QSize(800, 600)
# develop {{{ # develop {{{
def develop(): def develop():
@ -675,7 +741,7 @@ def develop():
l.setContentsMargins(0, 0, 0, 0) l.setContentsMargins(0, 0, 0, 0)
h = QHBoxLayout() h = QHBoxLayout()
l.addLayout(h) l.addLayout(h)
self.central = Central(self, prefs_name='develop_central_layout_widget_state') self.central = CentralContainer(self, for_develop=True, prefs_name='develop_central_layout_widget_state', separate_cover_browser=False)
h.addWidget(self.central.tag_browser_button) h.addWidget(self.central.tag_browser_button)
h.addWidget(self.central.book_details_button) h.addWidget(self.central.book_details_button)
h.addWidget(self.central.cover_browser_button) h.addWidget(self.central.cover_browser_button)

View File

@ -353,8 +353,12 @@ class CoverFlowMixin:
disable_cover_browser_refresh = False disable_cover_browser_refresh = False
@property
def cb_button(self):
return self.layout_container.cover_browser_button
def one_auto_scroll(self): def one_auto_scroll(self):
cb_visible = self.cover_flow is not None and self.cb_splitter.button.isChecked() cb_visible = self.cover_flow is not None and self.cb_button.isChecked()
if cb_visible: if cb_visible:
self.cover_flow.one_auto_scroll() self.cover_flow.one_auto_scroll()
else: else:
@ -388,18 +392,17 @@ class CoverFlowMixin:
self.cover_flow.setImages(self.db_images) self.cover_flow.setImages(self.db_images)
self.cover_flow.itemActivated.connect(self.iactions['View'].view_specific_book) self.cover_flow.itemActivated.connect(self.iactions['View'].view_specific_book)
self.update_cover_flow_subtitle_font() self.update_cover_flow_subtitle_font()
button = self.cb_button
if self.separate_cover_browser: if self.separate_cover_browser:
self.separate_cover_browser = True button.clicked.connect(self.toggle_cover_browser)
self.cb_splitter.button.clicked.connect(self.toggle_cover_browser) button.set_state_to_show()
self.cb_splitter.button.set_state_to_show() button.on_action_trigger.connect(self.toggle_cover_browser)
self.cb_splitter.action_toggle.triggered.connect(self.toggle_cover_browser)
self.cover_flow.stop.connect(self.hide_cover_browser) self.cover_flow.stop.connect(self.hide_cover_browser)
self.cover_flow.setVisible(False) self.cover_flow.setVisible(False)
else: else:
self.separate_cover_browser = False self.cover_flow.stop.connect(button.set_state_to_hide)
self.cb_splitter.insertWidget(self.cb_splitter.side_index, self.cover_flow) self.layout_container.set_widget('cover_browser', self.cover_flow)
self.cover_flow.stop.connect(self.cb_splitter.hide_side_pane) button.toggled.connect(self.cover_browser_toggled, type=Qt.ConnectionType.QueuedConnection)
self.cb_splitter.button.toggled.connect(self.cover_browser_toggled, type=Qt.ConnectionType.QueuedConnection)
def update_cover_flow_subtitle_font(self): def update_cover_flow_subtitle_font(self):
db = self.current_db.new_api db = self.current_db.new_api
@ -419,7 +422,7 @@ class CoverFlowMixin:
self.show_cover_browser() self.show_cover_browser()
def cover_browser_toggled(self, *args): def cover_browser_toggled(self, *args):
if self.cb_splitter.button.isChecked(): if self.cb_button.isChecked():
self.cover_browser_shown() self.cover_browser_shown()
else: else:
self.cover_browser_hidden() self.cover_browser_hidden()
@ -447,25 +450,25 @@ class CoverFlowMixin:
def show_cover_browser(self): def show_cover_browser(self):
d = CBDialog(self, self.cover_flow) d = CBDialog(self, self.cover_flow)
d.addAction(self.cb_splitter.action_toggle) d.addAction(self.cb_button.action_toggle)
self.cover_flow.setVisible(True) self.cover_flow.setVisible(True)
self.cover_flow.setFocus(Qt.FocusReason.OtherFocusReason) self.cover_flow.setFocus(Qt.FocusReason.OtherFocusReason)
d.show_fullscreen() if gprefs['cb_fullscreen'] else d.show() d.show_fullscreen() if gprefs['cb_fullscreen'] else d.show()
self.cb_splitter.button.set_state_to_hide() self.cb_button.set_state_to_hide()
d.closed.connect(self.cover_browser_closed) d.closed.connect(self.cover_browser_closed)
self.cb_dialog = d self.cb_dialog = d
self.cb_splitter.button.set_state_to_hide() self.cb_button.set_state_to_hide()
def cover_browser_closed(self, *args): def cover_browser_closed(self, *args):
self.cb_dialog = None self.cb_dialog = None
self.cb_splitter.button.set_state_to_show() self.cb_button.set_state_to_show()
def hide_cover_browser(self, *args): def hide_cover_browser(self, *args):
cbd = getattr(self, 'cb_dialog', None) cbd = getattr(self, 'cb_dialog', None)
if cbd is not None: if cbd is not None:
cbd.accept() cbd.accept()
self.cb_dialog = None self.cb_dialog = None
self.cb_splitter.button.set_state_to_show() self.cb_button.set_state_to_show()
def is_cover_browser_visible(self): def is_cover_browser_visible(self):
try: try:
@ -473,7 +476,7 @@ class CoverFlowMixin:
return self.cover_flow.isVisible() return self.cover_flow.isVisible()
except AttributeError: except AttributeError:
return False # called before init_cover_flow_mixin return False # called before init_cover_flow_mixin
return not self.cb_splitter.is_side_index_hidden return self.cb_button.isChecked()
def refresh_cover_browser(self): def refresh_cover_browser(self):
if self.disable_cover_browser_refresh: if self.disable_cover_browser_refresh:

View File

@ -269,7 +269,7 @@ class Quickview(QDialog, Ui_Quickview):
# Remove the ampersands from the buttons because shortcuts exist. # Remove the ampersands from the buttons because shortcuts exist.
self.lock_qv.setText(_('Lock Quickview contents')) self.lock_qv.setText(_('Lock Quickview contents'))
self.refresh_button.setText(_('Refresh')) self.refresh_button.setText(_('Refresh'))
self.gui.quickview_splitter.add_quickview_dialog(self) self.gui.layout_container.set_widget('quick_view', self)
self.close_button.setVisible(False) self.close_button.setVisible(False)
else: else:
self.dock_button.setToolTip(_('Embed the Quickview panel into the main calibre window')) self.dock_button.setToolTip(_('Embed the Quickview panel into the main calibre window'))
@ -324,7 +324,7 @@ class Quickview(QDialog, Ui_Quickview):
t.start() t.start()
def item_doubleclicked(self, item): def item_doubleclicked(self, item):
tb = self.gui.stack.tb_widget tb = self.gui.tb_widget
tb.set_focus_to_find_box() tb.set_focus_to_find_box()
tb.item_search.lineEdit().setText(self.current_key + ':=' + item.text()) tb.item_search.lineEdit().setText(self.current_key + ':=' + item.text())
tb.do_find() tb.do_find()
@ -456,7 +456,7 @@ class Quickview(QDialog, Ui_Quickview):
def show(self): def show(self):
QDialog.show(self) QDialog.show(self)
if self.is_pane: if self.is_pane:
self.gui.quickview_splitter.show_quickview_widget() self.gui.show_panel('quick_view')
def show_as_pane_changed(self): def show_as_pane_changed(self):
gprefs['quickview_is_pane'] = not gprefs.get('quickview_is_pane', False) gprefs['quickview_is_pane'] = not gprefs.get('quickview_is_pane', False)
@ -707,10 +707,6 @@ class Quickview(QDialog, Ui_Quickview):
def resizeEvent(self, *args): def resizeEvent(self, *args):
QDialog.resizeEvent(self, *args) QDialog.resizeEvent(self, *args)
# Do this if we are resizing for the first time to reset state.
if self.is_pane and self.height() == 0:
self.gui.quickview_splitter.set_sizes()
if self.books_table_column_widths is not None: if self.books_table_column_widths is not None:
for c,w in enumerate(self.books_table_column_widths): for c,w in enumerate(self.books_table_column_widths):
self.books_table.setColumnWidth(c, w) self.books_table.setColumnWidth(c, w)
@ -868,7 +864,7 @@ class Quickview(QDialog, Ui_Quickview):
def _reject(self): def _reject(self):
if self.is_pane: if self.is_pane:
self.gui.quickview_splitter.hide_quickview_widget() self.gui.hide_panel('quick_view')
self.gui.library_view.setFocus(Qt.FocusReason.ActiveWindowFocusReason) self.gui.library_view.setFocus(Qt.FocusReason.ActiveWindowFocusReason)
self._close() self._close()
QDialog.reject(self) QDialog.reject(self)

View File

@ -7,23 +7,21 @@ __docformat__ = 'restructuredtext en'
import functools import functools
from qt.core import ( from qt.core import (
QAction, QApplication, QDialog, QEvent, QIcon, QLabel, QMenu, QPixmap, QSizePolicy, QAction, QApplication, QDialog, QEvent, QIcon, QLabel, QMenu, QPixmap,
QSplitter, QStackedWidget, QStatusBar, QStyle, QStyleOption, QStylePainter, Qt, QStackedWidget, QStatusBar, QStyle, QStyleOption, QStylePainter, Qt, QTabBar,
QTabBar, QTimer, QToolButton, QUrl, QVBoxLayout, QWidget, QTimer, QToolButton, QUrl,
) )
from calibre.constants import get_appname_for_display, get_version, ismacos from calibre.constants import get_appname_for_display, get_version, ismacos
from calibre.customize.ui import find_plugin from calibre.customize.ui import find_plugin
from calibre.gui2 import ( from calibre.gui2 import config, error_dialog, gprefs, open_local_file, open_url
config, error_dialog, gprefs, is_widescreen, open_local_file, open_url,
)
from calibre.gui2.book_details import BookDetails from calibre.gui2.book_details import BookDetails
from calibre.gui2.central import CentralContainer, LayoutButton
from calibre.gui2.layout_menu import LayoutMenu from calibre.gui2.layout_menu import LayoutMenu
from calibre.gui2.library.alternate_views import GridView from calibre.gui2.library.alternate_views import GridView
from calibre.gui2.library.views import BooksView, DeviceBooksView from calibre.gui2.library.views import BooksView, DeviceBooksView
from calibre.gui2.notify import get_notifier from calibre.gui2.notify import get_notifier
from calibre.gui2.tag_browser.ui import TagBrowserWidget from calibre.gui2.tag_browser.ui import TagBrowserWidget
from calibre.gui2.widgets import LayoutButton, Splitter
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.localization import localize_website_link, ngettext from calibre.utils.localization import localize_website_link, ngettext
@ -111,105 +109,6 @@ class LibraryViewMixin: # {{{
# }}} # }}}
class QuickviewSplitter(QSplitter): # {{{
def __init__(self, parent=None, orientation=Qt.Orientation.Vertical, qv_widget=None):
super().__init__(parent=parent, orientation=orientation)
self.splitterMoved.connect(self.splitter_moved)
self.setChildrenCollapsible(False)
self.qv_widget = qv_widget
def splitter_moved(self):
gprefs['quickview_dialog_heights'] = self.sizes()
def resizeEvent(self, *args):
super().resizeEvent(*args)
if self.sizes()[1] != 0:
gprefs['quickview_dialog_heights'] = self.sizes()
def set_sizes(self):
sizes = gprefs.get('quickview_dialog_heights', [])
if len(sizes) == 2:
self.setSizes(sizes)
def add_quickview_dialog(self, qv_dialog):
self.qv_widget.layout().addWidget(qv_dialog)
def show_quickview_widget(self):
self.qv_widget.show()
def hide_quickview_widget(self):
self.qv_widget.hide()
# }}}
class LibraryWidget(Splitter): # {{{
def __init__(self, parent):
orientation = Qt.Orientation.Vertical
if config['gui_layout'] == 'narrow':
orientation = Qt.Orientation.Horizontal if is_widescreen() else Qt.Orientation.Vertical
idx = 0 if orientation == Qt.Orientation.Vertical else 1
size = 300 if orientation == Qt.Orientation.Vertical else 550
Splitter.__init__(self, 'cover_browser_splitter', _('Cover browser'),
'cover_flow.png',
orientation=orientation, parent=parent,
connect_button=not config['separate_cover_flow'],
side_index=idx, initial_side_size=size, initial_show=False,
shortcut='Shift+Alt+B')
quickview_widget = QWidget()
parent.quickview_splitter = QuickviewSplitter(
parent=self, orientation=Qt.Orientation.Vertical, qv_widget=quickview_widget)
parent.library_view = BooksView(parent)
parent.library_view.setObjectName('library_view')
stack = QStackedWidget(self)
av = parent.library_view.alternate_views
parent.pin_container = av.set_stack(stack)
parent.grid_view = GridView(parent)
parent.grid_view.setObjectName('grid_view')
av.add_view('grid', parent.grid_view)
parent.quickview_splitter.addWidget(stack)
l = QVBoxLayout()
l.setContentsMargins(4, 0, 0, 0)
quickview_widget.setLayout(l)
parent.quickview_splitter.addWidget(quickview_widget)
parent.quickview_splitter.hide_quickview_widget()
self.addWidget(parent.quickview_splitter)
# }}}
class Stack(QStackedWidget): # {{{
def __init__(self, parent):
QStackedWidget.__init__(self, parent)
parent.cb_splitter = LibraryWidget(parent)
self.tb_widget = TagBrowserWidget(parent)
parent.tb_splitter = Splitter('tag_browser_splitter',
_('Tag browser'), 'tags.png',
parent=parent, side_index=0, initial_side_size=200,
shortcut='Shift+Alt+T')
parent.tb_splitter.state_changed.connect(
self.tb_widget.set_pane_is_visible, Qt.ConnectionType.QueuedConnection)
parent.tb_splitter.addWidget(self.tb_widget)
parent.tb_splitter.addWidget(parent.cb_splitter)
parent.tb_splitter.setCollapsible(parent.tb_splitter.other_index, False)
self.addWidget(parent.tb_splitter)
for x in ('memory', 'card_a', 'card_b'):
name = x+'_view'
w = DeviceBooksView(parent)
setattr(parent, name, w)
self.addWidget(w)
w.setObjectName(name)
# }}}
class UpdateLabel(QLabel): # {{{ class UpdateLabel(QLabel): # {{{
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -220,7 +119,6 @@ class UpdateLabel(QLabel): # {{{
pass pass
# }}} # }}}
class VersionLabel(QLabel): # {{{ class VersionLabel(QLabel): # {{{
def __init__(self, parent): def __init__(self, parent):
@ -258,7 +156,6 @@ class VersionLabel(QLabel): # {{{
return QLabel.paintEvent(self, ev) return QLabel.paintEvent(self, ev)
# }}} # }}}
class StatusBar(QStatusBar): # {{{ class StatusBar(QStatusBar): # {{{
def __init__(self, parent=None): def __init__(self, parent=None):
@ -327,12 +224,11 @@ class StatusBar(QStatusBar): # {{{
# }}} # }}}
class GridViewButton(LayoutButton): # {{{ class GridViewButton(LayoutButton): # {{{
def __init__(self, gui): def __init__(self, gui):
sc = 'Alt+Shift+G' sc = 'Alt+Shift+G'
LayoutButton.__init__(self, 'grid.png', _('Cover grid'), parent=gui, shortcut=sc) LayoutButton.__init__(self, 'cover_grid', 'grid.png', _('Cover grid'), gui, shortcut=sc)
self.set_state_to_show() self.set_state_to_show()
self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self) self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self)
gui.addAction(self.action_toggle) gui.addAction(self.action_toggle)
@ -342,6 +238,10 @@ class GridViewButton(LayoutButton): # {{{
self.action_toggle.changed.connect(self.update_shortcut) self.action_toggle.changed.connect(self.update_shortcut)
self.toggled.connect(self.update_state) self.toggled.connect(self.update_state)
@property
def is_visible(self):
return self.isChecked()
def update_state(self, checked): def update_state(self, checked):
if checked: if checked:
self.set_state_to_hide() self.set_state_to_hide()
@ -362,7 +262,7 @@ class SearchBarButton(LayoutButton): # {{{
def __init__(self, gui): def __init__(self, gui):
sc = 'Alt+Shift+F' sc = 'Alt+Shift+F'
LayoutButton.__init__(self, 'search.png', _('Search bar'), parent=gui, shortcut=sc) LayoutButton.__init__(self, 'search', 'search.png', _('Search bar'), gui, shortcut=sc)
self.set_state_to_hide() self.set_state_to_hide()
self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self) self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self)
gui.addAction(self.action_toggle) gui.addAction(self.action_toggle)
@ -372,6 +272,10 @@ class SearchBarButton(LayoutButton): # {{{
self.action_toggle.changed.connect(self.update_shortcut) self.action_toggle.changed.connect(self.update_shortcut)
self.toggled.connect(self.update_state) self.toggled.connect(self.update_state)
@property
def is_visible(self):
return self.isChecked()
def update_state(self, checked): def update_state(self, checked):
if checked: if checked:
self.set_state_to_hide() self.set_state_to_hide()
@ -560,7 +464,6 @@ class VLTabs(QTabBar): # {{{
# }}} # }}}
class LayoutMixin: # {{{ class LayoutMixin: # {{{
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -569,35 +472,34 @@ class LayoutMixin: # {{{
def init_layout_mixin(self): def init_layout_mixin(self):
self.vl_tabs = VLTabs(self) self.vl_tabs = VLTabs(self)
self.centralwidget.layout().addWidget(self.vl_tabs) self.centralwidget.layout().addWidget(self.vl_tabs)
self.layout_container = CentralContainer(self)
self.centralwidget.layout().addWidget(self.layout_container)
self.book_details = BookDetails(self.layout_container.is_wide, self)
self.stack = QStackedWidget(self)
self.library_view = BooksView(self)
self.library_view.setObjectName('library_view')
stack = QStackedWidget(self)
self.stack.addWidget(stack)
av = self.library_view.alternate_views
self.pin_container = av.set_stack(stack)
self.grid_view = GridView(self)
self.grid_view.setObjectName('grid_view')
av.add_view('grid', self.grid_view)
self.tb_widget = TagBrowserWidget(self)
self.memory_view = DeviceBooksView(self)
self.stack.addWidget(self.memory_view)
self.memory_view.setObjectName('memory_view')
self.card_a_view = DeviceBooksView(self)
self.stack.addWidget(self.card_a_view)
self.card_a_view.setObjectName('card_a_view')
self.card_b_view = DeviceBooksView(self)
self.stack.addWidget(self.card_b_view)
self.card_b_view.setObjectName('card_b_view')
if config['gui_layout'] == 'narrow': # narrow {{{ if self.layout_container.is_wide:
self.book_details = BookDetails(False, self) self.button_order = 'sb', 'tb', 'cb', 'gv', 'qv', 'bd'
self.stack = Stack(self) else:
self.bd_splitter = Splitter('book_details_splitter', self.button_order = 'sb', 'tb', 'bd', 'gv', 'cb', 'qv'
_('Book details'), 'book.png',
orientation=Qt.Orientation.Vertical, parent=self, side_index=1,
shortcut='Shift+Alt+D')
self.bd_splitter.addWidget(self.stack)
self.bd_splitter.addWidget(self.book_details)
self.bd_splitter.setCollapsible(self.bd_splitter.other_index, False)
self.centralwidget.layout().addWidget(self.bd_splitter)
self.button_order = button_order = ('sb', 'tb', 'bd', 'gv', 'cb', 'qv')
# }}}
else: # wide {{{
self.bd_splitter = Splitter('book_details_splitter',
_('Book details'), 'book.png', initial_side_size=200,
orientation=Qt.Orientation.Horizontal, parent=self, side_index=1,
shortcut='Shift+Alt+D')
self.stack = Stack(self)
self.bd_splitter.addWidget(self.stack)
self.book_details = BookDetails(True, self)
self.bd_splitter.addWidget(self.book_details)
self.bd_splitter.setCollapsible(self.bd_splitter.other_index, False)
self.bd_splitter.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Expanding,
QSizePolicy.Policy.Expanding))
self.centralwidget.layout().addWidget(self.bd_splitter)
self.button_order = button_order = ('sb', 'tb', 'cb', 'gv', 'qv', 'bd')
# }}}
# This must use the base method to find the plugin because it hasn't # This must use the base method to find the plugin because it hasn't
# been fully initialized yet # been fully initialized yet
@ -605,6 +507,9 @@ class LayoutMixin: # {{{
if self.qv and self.qv.actual_plugin_: if self.qv and self.qv.actual_plugin_:
self.qv = self.qv.actual_plugin_ self.qv = self.qv.actual_plugin_
self.layout_container.initialize_with_gui(self, self.stack)
self.layout_container.tag_browser_button.toggled.connect(
self.tb_widget.set_pane_is_visible, Qt.ConnectionType.QueuedConnection)
self.status_bar = StatusBar(self) self.status_bar = StatusBar(self)
stylename = str(self.style().objectName()) stylename = str(self.style().objectName())
self.grid_view_button = GridViewButton(self) self.grid_view_button = GridViewButton(self)
@ -613,40 +518,33 @@ class LayoutMixin: # {{{
self.search_bar_button.toggled.connect(self.toggle_search_bar) self.search_bar_button.toggled.connect(self.toggle_search_bar)
self.layout_buttons = [] self.layout_buttons = []
for x in button_order: for x in self.button_order:
if hasattr(self, x + '_splitter'): if x == 'gv':
button = getattr(self, x + '_splitter').button button = self.grid_view_button
elif x == 'sb':
button = self.search_bar_button
else: else:
if x == 'gv': button = self.layout_container.button_for({
button = self.grid_view_button 'tb': 'tag_browser', 'bd': 'book_details', 'cb': 'cover_browser', 'qv': 'quick_view'
elif x == 'qv': }[x])
if self.qv is None:
continue
button = self.qv.qv_button
else:
button = self.search_bar_button
self.layout_buttons.append(button) self.layout_buttons.append(button)
button.setVisible(False) button.setVisible(gprefs['show_layout_buttons'])
if ismacos and stylename != 'Calibre': if ismacos and stylename != 'Calibre':
button.setStyleSheet(''' button.setStyleSheet('''
QToolButton { background: none; border:none; padding: 0px; } QToolButton { background: none; border:none; padding: 0px; }
QToolButton:checked { background: rgba(0, 0, 0, 25%); } QToolButton:checked { background: rgba(0, 0, 0, 25%); }
''') ''')
self.status_bar.addPermanentWidget(button) self.status_bar.addPermanentWidget(button)
if gprefs['show_layout_buttons']: self.layout_button = b = QToolButton(self)
for b in self.layout_buttons: b.setAutoRaise(True), b.setCursor(Qt.CursorShape.PointingHandCursor)
b.setVisible(True) b.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
self.status_bar.addPermanentWidget(b) b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
else: b.setText(_('Layout')), b.setIcon(QIcon.ic('layout.png'))
self.layout_button = b = QToolButton(self) b.setMenu(LayoutMenu(self))
b.setAutoRaise(True), b.setCursor(Qt.CursorShape.PointingHandCursor) b.setToolTip(_(
b.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) 'Show and hide various parts of the calibre main window'))
b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) self.status_bar.addPermanentWidget(b)
b.setText(_('Layout')), b.setIcon(QIcon.ic('layout.png')) b.setVisible(not gprefs['show_layout_buttons'])
b.setMenu(LayoutMenu(self))
b.setToolTip(_(
'Show and hide various parts of the calibre main window'))
self.status_bar.addPermanentWidget(b)
self.status_bar.addPermanentWidget(self.jobs_button) self.status_bar.addPermanentWidget(self.jobs_button)
self.setStatusBar(self.status_bar) self.setStatusBar(self.status_bar)
self.status_bar.update_label.linkActivated.connect(self.update_link_clicked) self.status_bar.update_label.linkActivated.connect(self.update_link_clicked)
@ -698,6 +596,12 @@ class LayoutMixin: # {{{
self.library_view.currentIndex()) self.library_view.currentIndex())
self.library_view.setFocus(Qt.FocusReason.OtherFocusReason) self.library_view.setFocus(Qt.FocusReason.OtherFocusReason)
def show_panel(self, name):
self.layout_container.show_panel(name)
def hide_panel(self, name):
self.layout_container.hide_panel(name)
def set_search_string_with_append(self, expression, append=''): def set_search_string_with_append(self, expression, append=''):
current = self.search.text().strip() current = self.search.text().strip()
if append: if append:
@ -727,7 +631,7 @@ class LayoutMixin: # {{{
def find_in_tag_browser_triggered(self, field, value): def find_in_tag_browser_triggered(self, field, value):
if field and value: if field and value:
tb = self.stack.tb_widget tb = self.tb_widget
tb.set_focus_to_find_box() tb.set_focus_to_find_box()
tb.item_search.lineEdit().setText(field + ':=' + value) tb.item_search.lineEdit().setText(field + ':=' + value)
tb.do_find() tb.do_find()
@ -812,22 +716,15 @@ class LayoutMixin: # {{{
for x in ('library', 'memory', 'card_a', 'card_b'): for x in ('library', 'memory', 'card_a', 'card_b'):
getattr(self, x+'_view').save_state() getattr(self, x+'_view').save_state()
for x in ('cb', 'tb', 'bd'): self.layout_container.write_settings()
s = getattr(self, x+'_splitter')
s.update_desired_state()
s.save_state()
self.grid_view_button.save_state() self.grid_view_button.save_state()
self.search_bar_button.save_state() self.search_bar_button.save_state()
if self.qv:
self.qv.qv_button.save_state()
def read_layout_settings(self): def read_layout_settings(self):
# View states are restored automatically when set_database is called # View states are restored automatically when set_database is called
for x in ('cb', 'tb', 'bd'): self.layout_container.read_settings()
getattr(self, x+'_splitter').restore_state()
self.grid_view_button.restore_state() self.grid_view_button.restore_state()
self.search_bar_button.restore_state() self.search_bar_button.restore_state()
# Can't do quickview here because the gui isn't totally set up. Do it in ui
def update_status_bar(self, *args): def update_status_bar(self, *args):
v = self.current_view() v = self.current_view()

View File

@ -17,7 +17,6 @@ import sys
import textwrap import textwrap
import time import time
from collections import OrderedDict, deque from collections import OrderedDict, deque
from functools import partial
from io import BytesIO from io import BytesIO
from qt.core import ( from qt.core import (
QAction, QApplication, QDialog, QFont, QIcon, QMenu, QSystemTrayIcon, Qt, QTimer, QAction, QApplication, QDialog, QFont, QIcon, QMenu, QSystemTrayIcon, Qt, QTimer,
@ -56,7 +55,7 @@ from calibre.gui2.search_box import SavedSearchBoxMixin, SearchBoxMixin
from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
from calibre.gui2.tag_browser.ui import TagBrowserMixin from calibre.gui2.tag_browser.ui import TagBrowserMixin
from calibre.gui2.update import UpdateMixin from calibre.gui2.update import UpdateMixin
from calibre.gui2.widgets import ProgressIndicator from calibre.gui2.widgets import ProgressIndicator, BusyCursor
from calibre.library import current_library_name from calibre.library import current_library_name
from calibre.srv.library_broker import GuiLibraryBroker, db_matches from calibre.srv.library_broker import GuiLibraryBroker, db_matches
from calibre.utils.config import dynamic, prefs from calibre.utils.config import dynamic, prefs
@ -398,7 +397,13 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
do_hide_windows = True do_hide_windows = True
show_gui = False show_gui = False
setattr(self, '__systray_minimized', True) setattr(self, '__systray_minimized', True)
QTimer.singleShot(0, partial(self.post_initialize_actions, show_gui, do_hide_windows)) if do_hide_windows:
self.hide_windows()
if show_gui:
timed_print('GUI main window shown')
self.show()
self.layout_container.relayout()
QTimer.singleShot(0, self.post_initialize_actions)
self.read_settings() self.read_settings()
self.finalize_layout() self.finalize_layout()
@ -430,43 +435,12 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.iactions['Connect Share'].check_smartdevice_menus() self.iactions['Connect Share'].check_smartdevice_menus()
QTimer.singleShot(100, self.update_toggle_to_tray_action) QTimer.singleShot(100, self.update_toggle_to_tray_action)
def post_initialize_actions(self, show_gui, do_hide_windows): def post_initialize_actions(self):
# Various post-initialization actions after an event loop tick # Various post-initialization actions after an event loop tick
self.listener.start_listening()
self.start_smartdevice()
# Collect cycles now # Collect cycles now
gc.collect() gc.collect()
self.listener.start_listening()
if do_hide_windows:
self.hide_windows()
if show_gui:
timed_print('GUI main window shown')
self.show()
# Force repaint of the book details splitter because it otherwise ends
# up with the wrong size. I don't know why.
self.bd_splitter.repaint()
# Once the gui is initialized we can restore the quickview state
# The same thing will be true for any action-based operation with a
# layout button. We need to let a book be selected in the book list
# before initializing quickview, so run it after an event loop tick
QTimer.singleShot(0, self.start_quickview)
# Start the smartdevice later so that the network time doesn't affect
# the gui repaint debouncing. Wait 3 seconds before starting to be sure
# that all other initialization (plugins etc) has completed. Yes, 3
# seconds is an arbitrary value and probably too long, but it will do
# until the underlying structure changes to make it unnecessary.
QTimer.singleShot(3000, self.start_smartdevice)
def start_quickview(self):
from calibre.gui2.actions.show_quickview import get_quickview_action_plugin
qv = get_quickview_action_plugin()
if qv:
timed_print('QuickView starting')
qv.qv_button.restore_state()
timed_print('QuickView started')
self.save_layout_state()
self.focus_library_view() self.focus_library_view()
def show_gui_debug_msg(self): def show_gui_debug_msg(self):
@ -498,14 +472,15 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
timed_print('Starting the smartdevice driver') timed_print('Starting the smartdevice driver')
message = None message = None
if self.device_manager.get_option('smartdevice', 'autostart'): if self.device_manager.get_option('smartdevice', 'autostart'):
try: with BusyCursor():
message = self.device_manager.start_plugin('smartdevice') try:
timed_print('Finished starting smartdevice') message = self.device_manager.start_plugin('smartdevice')
except Exception as e: timed_print('Finished starting smartdevice')
message = str(e) except Exception as e:
timed_print(f'Starting smartdevice driver failed: {message}') message = str(e)
import traceback timed_print(f'Starting smartdevice driver failed: {message}')
traceback.print_exc() import traceback
traceback.print_exc()
if message: if message:
if not self.device_manager.is_running('Wireless Devices'): if not self.device_manager.is_running('Wireless Devices'):
error_dialog(self, _('Problem starting the wireless device'), error_dialog(self, _('Problem starting the wireless device'),
@ -1043,9 +1018,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
page = 0 if location == 'library' else 1 if location == 'main' else 2 if location == 'carda' else 3 page = 0 if location == 'library' else 1 if location == 'main' else 2 if location == 'carda' else 3
self.stack.setCurrentIndex(page) self.stack.setCurrentIndex(page)
self.book_details.reset_info() self.book_details.reset_info()
for x in ('tb', 'cb'): self.layout_container.tag_browser_button.setEnabled(location == 'library')
splitter = getattr(self, x+'_splitter') self.layout_container.cover_browser_button.setEnabled(location == 'library')
splitter.button.setEnabled(location == 'library')
for action in self.iactions.values(): for action in self.iactions.values():
action.location_selected(location) action.location_selected(location)
if location == 'library': if location == 'library':
@ -1168,7 +1142,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.save_geometry(gprefs, 'calibre_main_window_geometry') self.save_geometry(gprefs, 'calibre_main_window_geometry')
dynamic.set('sort_history', self.library_view.model().sort_history) dynamic.set('sort_history', self.library_view.model().sort_history)
self.save_layout_state() self.save_layout_state()
self.stack.tb_widget.save_state() self.tb_widget.save_state()
def quit(self, checked=True, restart=False, debug_on_restart=False, def quit(self, checked=True, restart=False, debug_on_restart=False,
confirm_quit=True, no_plugins_on_restart=False): confirm_quit=True, no_plugins_on_restart=False):

View File

@ -6,11 +6,11 @@ Miscellaneous widgets used in the GUI
import os import os
import re import re
from qt.core import ( from qt.core import (
QAction, QApplication, QClipboard, QColor, QComboBox, QCompleter, QCursor, QEvent, QApplication, QClipboard, QColor, QComboBox, QCompleter, QCursor, QEvent, QFont,
QFont, QGraphicsScene, QGraphicsView, QIcon, QKeySequence, QLabel, QLineEdit, QGraphicsScene, QGraphicsView, QIcon, QLabel, QLineEdit, QListWidget,
QListWidget, QListWidgetItem, QMenu, QPageSize, QPainter, QPalette, QPen, QPixmap, QListWidgetItem, QMenu, QPageSize, QPainter, QPalette, QPen, QPixmap, QPrinter,
QPrinter, QRect, QSize, QSplitter, QSplitterHandle, QStringListModel, QRect, QSize, QSplitterHandle, QStringListModel, QSyntaxHighlighter, Qt,
QSyntaxHighlighter, Qt, QTextCharFormat, QTimer, QToolButton, QWidget, pyqtSignal, QTextCharFormat, QWidget, pyqtSignal,
) )
from calibre import fit_image, force_unicode, strftime from calibre import fit_image, force_unicode, strftime
@ -1063,276 +1063,6 @@ class SplitterHandle(QSplitterHandle):
self.double_clicked.emit(self) self.double_clicked.emit(self)
class LayoutButton(QToolButton):
def __init__(self, icon, text, splitter=None, parent=None, shortcut=None):
QToolButton.__init__(self, parent)
self.label = text
self.setIcon(QIcon.ic(icon))
self.setCheckable(True)
self.icname = os.path.basename(icon).rpartition('.')[0]
self.splitter = splitter
if splitter is not None:
splitter.state_changed.connect(self.update_state)
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.shortcut = shortcut or ''
def update_shortcut(self, action_toggle=None):
action_toggle = action_toggle or getattr(self, 'action_toggle', None)
if action_toggle:
sc = ', '.join(sc.toString(QKeySequence.SequenceFormat.NativeText)
for sc in action_toggle.shortcuts())
self.shortcut = sc or ''
self.update_text()
def update_text(self):
t = _('Hide {}') if self.isChecked() else _('Show {}')
t = t.format(self.label)
if self.shortcut:
t += f' [{self.shortcut}]'
self.setText(t), self.setToolTip(t), self.setStatusTip(t)
def set_state_to_show(self, *args):
self.setChecked(False)
self.update_text()
def set_state_to_hide(self, *args):
self.setChecked(True)
self.update_text()
def update_state(self, *args):
if self.splitter.is_side_index_hidden:
self.set_state_to_show()
else:
self.set_state_to_hide()
def mouseReleaseEvent(self, ev):
if ev.button() == Qt.MouseButton.RightButton:
from calibre.gui2.ui import get_gui
gui = get_gui()
if self.icname == 'search':
gui.iactions['Preferences'].do_config(initial_plugin=('Interface', 'Search'), close_after_initial=True)
ev.accept()
return
tab_name = {'book':'book_details', 'grid':'cover_grid', 'cover_flow':'cover_browser',
'tags':'tag_browser', 'quickview':'quickview'}.get(self.icname)
if tab_name:
if gui is not None:
gui.iactions['Preferences'].do_config(initial_plugin=('Interface', 'Look & Feel', tab_name+'_tab'), close_after_initial=True)
ev.accept()
return
return QToolButton.mouseReleaseEvent(self, ev)
class Splitter(QSplitter):
state_changed = pyqtSignal(object)
reapply_sizes = pyqtSignal(object)
def __init__(self, name, label, icon, initial_show=True,
initial_side_size=120, connect_button=True,
orientation=Qt.Orientation.Horizontal, side_index=0, parent=None,
shortcut=None, hide_handle_on_single_panel=True):
super().__init__(parent)
self.reapply_sizes.connect(self.setSizes, type=Qt.ConnectionType.QueuedConnection)
self.hide_handle_on_single_panel = hide_handle_on_single_panel
if hide_handle_on_single_panel:
self.state_changed.connect(self.update_handle_width)
self.original_handle_width = self.handleWidth()
self.resize_timer = QTimer(self)
self.resize_timer.setSingleShot(True)
self.desired_side_size = initial_side_size
self.desired_show = initial_show
self.resize_timer.setInterval(5)
self.resize_timer.timeout.connect(self.do_resize)
self.setOrientation(orientation)
self.side_index = side_index
self._name = name
self.label = label
self.initial_side_size = initial_side_size
self.initial_show = initial_show
self.splitterMoved.connect(self.splitter_moved, type=Qt.ConnectionType.QueuedConnection)
self.button = LayoutButton(icon, label, self, shortcut=shortcut)
if connect_button:
self.button.clicked.connect(self.double_clicked)
if shortcut is not None:
self.action_toggle = QAction(QIcon.ic(icon), _('Toggle') + ' ' + label,
self)
self.action_toggle.changed.connect(self.update_shortcut)
self.action_toggle.triggered.connect(self.toggle_triggered)
if parent is not None:
parent.addAction(self.action_toggle)
if hasattr(parent, 'keyboard'):
parent.keyboard.register_shortcut('splitter %s %s'%(name,
label), str(self.action_toggle.text()),
default_keys=(shortcut,), action=self.action_toggle)
else:
self.action_toggle.setShortcut(shortcut)
else:
self.action_toggle.setShortcut(shortcut)
def update_shortcut(self):
self.button.update_shortcut(self.action_toggle)
def toggle_triggered(self, *args):
self.toggle_side_pane()
def createHandle(self):
return SplitterHandle(self.orientation(), self)
def initialize(self):
for i in range(self.count()):
h = self.handle(i)
if h is not None:
h.splitter_moved()
self.state_changed.emit(not self.is_side_index_hidden)
def splitter_moved(self, *args):
self.desired_side_size = self.side_index_size
self.state_changed.emit(not self.is_side_index_hidden)
def update_handle_width(self, not_one_panel):
self.setHandleWidth(self.original_handle_width if not_one_panel else 0)
@property
def is_side_index_hidden(self):
sizes = list(self.sizes())
try:
return sizes[self.side_index] == 0
except IndexError:
return True
@property
def save_name(self):
ori = 'horizontal' if self.orientation() == Qt.Orientation.Horizontal \
else 'vertical'
return self._name + '_' + ori
def print_sizes(self):
if self.count() > 1:
print(self.save_name, 'side:', self.side_index_size, 'other:', end=' ')
print(list(self.sizes())[self.other_index])
@property
def side_index_size(self):
if self.count() < 2:
return 0
return self.sizes()[self.side_index]
@side_index_size.setter
def side_index_size(self, val):
if self.count() < 2:
return
side_index_hidden = self.is_side_index_hidden
if val == 0 and not side_index_hidden:
self.save_state()
sizes = list(self.sizes())
for i in range(len(sizes)):
sizes[i] = val if i == self.side_index else 10
self.setSizes(sizes)
sizes = list(self.sizes())
total = sum(sizes)
total_needs_adjustment = self.hide_handle_on_single_panel and side_index_hidden
if total_needs_adjustment:
total -= self.original_handle_width
for i in range(len(sizes)):
sizes[i] = val if i == self.side_index else total-val
self.setSizes(sizes)
self.initialize()
if total_needs_adjustment:
# the handle visibility and therefore size distribution will change
# when the event loop ticks
self.reapply_sizes.emit(sizes)
def ignore_child_paints(self, ignore=True):
for widget in self:
if hasattr(widget, 'ignore_paint_events'):
widget.ignore_paint_events = ignore
def do_resize(self, *args):
orig = self.desired_side_size
super().resizeEvent(self._resize_ev)
if orig > 20 and self.desired_show:
c = 0
while abs(self.side_index_size - orig) > 10 and c < 5:
self.apply_state(self.get_state(), save_desired=False)
c += 1
self.ignore_child_paints(False)
def __iter__(self):
for i in range(self.count()):
yield self.widget(i)
def resizeEvent(self, ev):
self.ignore_child_paints()
self._resize_ev = ev
self.resize_timer.start()
def get_state(self):
if self.count() < 2:
return (False, 200)
return (self.desired_show, self.desired_side_size)
def apply_state(self, state, save_desired=True):
if state[0]:
self.side_index_size = state[1]
if save_desired:
self.desired_side_size = self.side_index_size
else:
self.side_index_size = 0
self.desired_show = state[0]
def default_state(self):
return (self.initial_show, self.initial_side_size)
# Public API {{{
def update_desired_state(self):
self.desired_show = not self.is_side_index_hidden
def save_state(self):
if self.count() > 1:
gprefs[self.save_name+'_state'] = self.get_state()
@property
def other_index(self):
return (self.side_index+1)%2
def restore_state(self):
if self.count() > 1:
state = gprefs.get(self.save_name+'_state',
self.default_state())
self.apply_state(state, save_desired=False)
self.desired_side_size = state[1]
def toggle_side_pane(self, hide=None):
if hide is None:
action = 'show' if self.is_side_index_hidden else 'hide'
else:
action = 'hide' if hide else 'show'
getattr(self, action+'_side_pane')()
def show_side_pane(self):
if self.count() < 2 or not self.is_side_index_hidden:
return
if self.desired_side_size == 0:
self.desired_side_size = self.initial_side_size
self.apply_state((True, self.desired_side_size))
def hide_side_pane(self):
if self.count() < 2 or self.is_side_index_hidden:
return
self.apply_state((False, self.desired_side_size))
def double_clicked(self, *args):
self.toggle_side_pane()
# }}}
# }}}
class PaperSizes(QComboBox): # {{{ class PaperSizes(QComboBox): # {{{