Start work on central widget for main window

This commit is contained in:
Kovid Goyal 2023-12-12 22:06:27 +05:30
parent 5e911aa980
commit fa3e6c9b59
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

268
src/calibre/gui2/central.py Normal file
View 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()