mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on central widget for main window
This commit is contained in:
parent
5e911aa980
commit
fa3e6c9b59
268
src/calibre/gui2/central.py
Normal file
268
src/calibre/gui2/central.py
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# License: GPLv3 Copyright: 2023, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
from enum import Enum, auto
|
||||||
|
from qt.core import (
|
||||||
|
QDialog, QLabel, QPalette, QPointF, QSize, QSizePolicy, QStyle, QStyleOption,
|
||||||
|
QStylePainter, Qt, QVBoxLayout, QWidget, pyqtSignal
|
||||||
|
)
|
||||||
|
|
||||||
|
from calibre.gui2 import Application, config
|
||||||
|
|
||||||
|
|
||||||
|
class Placeholder(QLabel):
|
||||||
|
backgrounds = 'yellow', 'lightgreen', 'grey', 'cyan', 'magenta'
|
||||||
|
bgcount = 0
|
||||||
|
|
||||||
|
def __init__(self, text, parent=None):
|
||||||
|
super().__init__(text, parent)
|
||||||
|
bg = self.backgrounds[Placeholder.bgcount]
|
||||||
|
Placeholder.bgcount = (Placeholder.bgcount + 1) % len(self.backgrounds)
|
||||||
|
self.setStyleSheet(f'QLabel {{ background: {bg} }}')
|
||||||
|
|
||||||
|
|
||||||
|
class HandleState(Enum):
|
||||||
|
both_visible = auto()
|
||||||
|
only_main_visible = auto()
|
||||||
|
only_side_visible = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class SplitterHandle(QWidget):
|
||||||
|
|
||||||
|
drag_started = pyqtSignal()
|
||||||
|
drag_ended = pyqtSignal()
|
||||||
|
dragged_to = pyqtSignal(QPointF)
|
||||||
|
drag_start = None
|
||||||
|
|
||||||
|
def __init__(self, parent: QWidget=None, orientation: Qt.Orientation = Qt.Orientation.Vertical):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.orientation = orientation
|
||||||
|
if orientation is Qt.Orientation.Vertical:
|
||||||
|
self.setCursor(Qt.CursorShape.SplitHCursor)
|
||||||
|
else:
|
||||||
|
self.setCursor(Qt.CursorShape.SplitVCursor)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> HandleState:
|
||||||
|
p = self.parent()
|
||||||
|
if p is not None:
|
||||||
|
try:
|
||||||
|
return p.handle_state(self)
|
||||||
|
except AttributeError as err:
|
||||||
|
raise Exception(str(err)) from err
|
||||||
|
return HandleState.both_visible
|
||||||
|
|
||||||
|
def mousePressEvent(self, ev):
|
||||||
|
super().mousePressEvent(ev)
|
||||||
|
if ev.button() is Qt.MouseButton.LeftButton:
|
||||||
|
self.drag_start = ev.position()
|
||||||
|
self.drag_started.emit()
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, ev):
|
||||||
|
super().mouseReleaseEvent(ev)
|
||||||
|
if ev.button() is Qt.MouseButton.LeftButton:
|
||||||
|
self.drag_start = None
|
||||||
|
self.drag_started.emit()
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, ev):
|
||||||
|
super().mouseMoveEvent(ev)
|
||||||
|
if self.drag_start is not None:
|
||||||
|
pos = ev.position() - self.drag_start
|
||||||
|
self.dragged_to.emit(self.mapToParent(pos))
|
||||||
|
|
||||||
|
def paintEvent(self, ev):
|
||||||
|
p = QStylePainter(self)
|
||||||
|
opt = QStyleOption()
|
||||||
|
opt.initFrom(self)
|
||||||
|
if self.orientation is Qt.Orientation.Vertical:
|
||||||
|
opt.state |= QStyle.StateFlag.State_Horizontal
|
||||||
|
if self.state is HandleState.only_side_visible:
|
||||||
|
p.fillRect(opt.rect, opt.palette.color(QPalette.ColorRole.Window))
|
||||||
|
else:
|
||||||
|
p.drawControl(QStyle.ControlElement.CE_Splitter, opt)
|
||||||
|
p.end()
|
||||||
|
|
||||||
|
|
||||||
|
class Layout(Enum):
|
||||||
|
|
||||||
|
wide = auto()
|
||||||
|
narrow = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class WideDesires:
|
||||||
|
tag_browser_width = book_details_width = None
|
||||||
|
cover_browser_height = quickview_height = None
|
||||||
|
|
||||||
|
|
||||||
|
class Visibility:
|
||||||
|
tag_browser = book_details = book_list = True
|
||||||
|
cover_browser = quick_view = False
|
||||||
|
|
||||||
|
|
||||||
|
class Central(QWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent=None, initial_layout=''):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||||
|
self.wide_desires = WideDesires()
|
||||||
|
self.is_visible = Visibility()
|
||||||
|
self.layout = Layout.narrow if (initial_layout or config.get('gui_layout')) == 'narrow' else Layout.wide
|
||||||
|
self.tag_browser = Placeholder('tag 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)
|
||||||
|
|
||||||
|
def h(orientation: Qt.Orientation = Qt.Orientation.Vertical):
|
||||||
|
ans = SplitterHandle(self, orientation)
|
||||||
|
ans.dragged_to.connect(self.splitter_handle_dragged)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
self.left_handle = h()
|
||||||
|
self.right_handle = h()
|
||||||
|
self.top_handle = h(Qt.Orientation.Horizontal)
|
||||||
|
self.bottom_handle = h(Qt.Orientation.Horizontal)
|
||||||
|
|
||||||
|
def handle_state(self, handle):
|
||||||
|
if self.layout is Layout.wide:
|
||||||
|
return self.wide_handle_state(handle)
|
||||||
|
return self.narrow_handle_state(handle)
|
||||||
|
|
||||||
|
def splitter_handle_dragged(self, pos):
|
||||||
|
handle = self.sender()
|
||||||
|
if self.layout is Layout.wide:
|
||||||
|
self.wide_move_splitter_handle_to(handle, pos)
|
||||||
|
else:
|
||||||
|
self.narrow_move_splitter_handle_to(handle, pos)
|
||||||
|
|
||||||
|
def refresh_after_config_change(self):
|
||||||
|
self.layout = Layout.narrow if config.get('gui_layout') == 'narrow' else Layout.wide
|
||||||
|
self.relayout()
|
||||||
|
|
||||||
|
def resizeEvent(self, ev):
|
||||||
|
super().resizeEvent(ev)
|
||||||
|
self.relayout()
|
||||||
|
|
||||||
|
def relayout(self):
|
||||||
|
self.tag_browser.setVisible(self.is_visible.tag_browser)
|
||||||
|
self.book_details.setVisible(self.is_visible.book_details)
|
||||||
|
self.cover_browser.setVisible(self.is_visible.cover_browser)
|
||||||
|
self.book_list.setVisible(self.is_visible.book_list)
|
||||||
|
self.quick_view.setVisible(self.is_visible.quick_view)
|
||||||
|
if self.layout is Layout.wide:
|
||||||
|
self.do_wide_layout()
|
||||||
|
else:
|
||||||
|
self.do_narrow_layout()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
# Wide {{{
|
||||||
|
def wide_handle_state(self, handle):
|
||||||
|
if handle is self.left_handle:
|
||||||
|
return HandleState.both_visible if self.is_visible.tag_browser else HandleState.only_main_visible
|
||||||
|
if handle is self.right_handle:
|
||||||
|
return HandleState.both_visible if self.is_visible.book_details else HandleState.only_main_visible
|
||||||
|
if handle is self.top_handle:
|
||||||
|
if self.is_visible.cover_browser:
|
||||||
|
return HandleState.both_visible if self.is_visible.book_list else HandleState.only_side_visible
|
||||||
|
return HandleState.only_main_visible
|
||||||
|
if handle is self.bottom_handle:
|
||||||
|
return HandleState.both_visible if self.is_visible.quick_view else HandleState.only_main_visible
|
||||||
|
|
||||||
|
def do_wide_layout(self):
|
||||||
|
s = self.style()
|
||||||
|
normal_handle_width = int(s.pixelMetric(QStyle.PixelMetric.PM_SplitterWidth, widget=self))
|
||||||
|
available_width = self.width()
|
||||||
|
for h in (self.left_handle, self.right_handle):
|
||||||
|
width = 1
|
||||||
|
hs = h.state
|
||||||
|
if hs is HandleState.both_visible or hs is HandleState.only_side_visible:
|
||||||
|
width = normal_handle_width
|
||||||
|
h.resize(width, self.height())
|
||||||
|
available_width -= width
|
||||||
|
default_width = min(300, (3 * available_width) // 10)
|
||||||
|
tb = self.wide_desires.tag_browser_width or default_width
|
||||||
|
if not self.is_visible.tag_browser:
|
||||||
|
tb = 0
|
||||||
|
bd = self.wide_desires.book_details_width or default_width
|
||||||
|
if not self.is_visible.book_details:
|
||||||
|
bd = 0
|
||||||
|
min_book_list_width = max(200, self.cover_browser.minimumWidth(), available_width // 10)
|
||||||
|
if tb + bd > available_width - min_book_list_width:
|
||||||
|
width_to_share = max(0, available_width - min_book_list_width)
|
||||||
|
tb = int(tb * width_to_share / (tb + bd))
|
||||||
|
bd = width_to_share - tb
|
||||||
|
central_width = available_width - (tb + bd)
|
||||||
|
self.tag_browser.setGeometry(0, 0, tb, self.height())
|
||||||
|
self.left_handle.move(tb, 0)
|
||||||
|
central_x = self.left_handle.x() + self.left_handle.width()
|
||||||
|
self.right_handle.move(tb + central_width + self.left_handle.width(), 0)
|
||||||
|
self.book_details.setGeometry(self.right_handle.x() + self.right_handle.width(), 0, bd, self.height())
|
||||||
|
|
||||||
|
available_height = self.height()
|
||||||
|
for h in (self.top_handle, self.bottom_handle):
|
||||||
|
height = 1
|
||||||
|
hs = h.state
|
||||||
|
if hs is HandleState.both_visible or hs is HandleState.only_side_visible:
|
||||||
|
height = normal_handle_width
|
||||||
|
if h is self.bottom_handle and hs is HandleState.only_main_visible:
|
||||||
|
height = 0
|
||||||
|
h.resize(self.width(), height)
|
||||||
|
available_height -= height
|
||||||
|
|
||||||
|
cb = max(self.cover_browser.minimumHeight(), self.wide_desires.cover_browser_height or (2 * available_height // 5))
|
||||||
|
if not self.is_visible.cover_browser:
|
||||||
|
cb = 0
|
||||||
|
qv = bl = 0
|
||||||
|
if cb >= available_height:
|
||||||
|
cb = available_height
|
||||||
|
else:
|
||||||
|
available_height -= cb
|
||||||
|
min_bl_height = 50
|
||||||
|
if available_height <= min_bl_height:
|
||||||
|
bl = available_height
|
||||||
|
else:
|
||||||
|
qv = min(available_height - min_bl_height, qv)
|
||||||
|
bl = available_height - qv
|
||||||
|
self.cover_browser.setGeometry(central_x, 0, central_width, cb)
|
||||||
|
self.top_handle.move(central_x, cb)
|
||||||
|
self.book_list.setGeometry(central_x, self.top_handle.y() + self.top_handle.height(), central_width, bl)
|
||||||
|
self.bottom_handle.move(central_x, self.book_list.y() + self.book_list.height())
|
||||||
|
self.quick_view.setGeometry(central_x, self.bottom_handle.y() + self.bottom_handle.height(), central_width, qv)
|
||||||
|
|
||||||
|
def wide_move_splitter_handle_to(self, handle: SplitterHandle, pos: QPointF):
|
||||||
|
raise NotImplementedError('TODO: Implement me')
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Narrow {{{
|
||||||
|
def narrow_handle_state(self, handle):
|
||||||
|
raise NotImplementedError('TODO: Implement me')
|
||||||
|
|
||||||
|
def narrow_move_splitter_handle_to(self, handle: SplitterHandle, pos: QPointF):
|
||||||
|
raise NotImplementedError('TODO: Implement me')
|
||||||
|
|
||||||
|
def do_narrow_layout(self):
|
||||||
|
raise NotImplementedError('TODO: Implement me')
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def sizeHint(self):
|
||||||
|
return QSize(800, 600)
|
||||||
|
|
||||||
|
|
||||||
|
def develop():
|
||||||
|
app = Application([])
|
||||||
|
class d(QDialog):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
l = QVBoxLayout(self)
|
||||||
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.central = Central(self)
|
||||||
|
l.addWidget(self.central)
|
||||||
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
|
d = d()
|
||||||
|
d.show()
|
||||||
|
app.exec()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
develop()
|
Loading…
x
Reference in New Issue
Block a user