%PDF- %PDF-
| Direktori : /lib/calibre/calibre/gui2/ |
| Current File : //lib/calibre/calibre/gui2/flow_toolbar.py |
#!/usr/bin/env python3
# License: GPL v3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
from qt.core import (
QPoint, QRect, QSize, QSizePolicy, QStyle, QStyleOption, QStylePainter, Qt,
QToolBar, QToolButton, QWidget, pyqtSignal
)
class Separator(QWidget):
def __init__(self, icon_size, parent=None):
super().__init__(parent)
self.desired_height = icon_size.height() * 0.85
def style_option(self):
opt = QStyleOption()
opt.initFrom(self)
opt.state |= QStyle.StateFlag.State_Horizontal
return opt
def sizeHint(self):
width = self.style().pixelMetric(QStyle.PixelMetric.PM_ToolBarSeparatorExtent, self.style_option(), self)
return QSize(width, int(self.devicePixelRatioF() * self.desired_height))
def paintEvent(self, ev):
p = QStylePainter(self)
p.drawPrimitive(QStyle.PrimitiveElement.PE_IndicatorToolBarSeparator, self.style_option())
class Button(QToolButton):
layout_needed = pyqtSignal()
def __init__(self, action, parent=None):
super().__init__(parent)
self.action = action
self.setAutoRaise(True)
action.changed.connect(self.update_state)
self.update_state()
self.clicked.connect(self.action.trigger)
def update_state(self):
ac = self.action
self.setIcon(ac.icon())
self.setToolTip(ac.toolTip() or self.action.text())
self.setEnabled(ac.isEnabled())
self.setCheckable(ac.isCheckable())
self.setChecked(ac.isChecked())
self.setMenu(ac.menu())
old = self.isVisible()
self.setVisible(ac.isVisible())
if self.isVisible() != old:
self.layout_needed.emit()
def __repr__(self):
return f'Button({self.toolTip()})'
class SingleLineToolBar(QToolBar):
def __init__(self, parent=None, icon_size=18):
super().__init__(parent)
self.setIconSize(QSize(icon_size, icon_size))
def add_action(self, ac, popup_mode=QToolButton.ToolButtonPopupMode.DelayedPopup):
self.addAction(ac)
w = self.widgetForAction(ac)
w.setPopupMode(popup_mode)
def add_separator(self):
self.addSeparator()
class LayoutItem:
def __init__(self, w):
self.widget = w
self.sz = sz = w.sizeHint()
self.width = sz.width()
self.height = sz.height()
class Group:
def __init__(self, parent=None, leading_separator=None):
self.items = []
self.width = self.height = 0
self.parent = parent
self.leading_separator = leading_separator
def __bool__(self):
return bool(self.items)
def smart_spacing(self, horizontal=True):
p = self.parent
if p is None:
return -1
if p.isWidgetType():
which = QStyle.PixelMetric.PM_LayoutHorizontalSpacing if horizontal else QStyle.PixelMetric.PM_LayoutVerticalSpacing
return p.style().pixelMetric(which, None, p)
return p.spacing()
def layout_spacing(self, wid, horizontal=True):
ans = self.smart_spacing(horizontal)
if ans != -1:
return ans
return wid.style().layoutSpacing(
QSizePolicy.ControlType.ToolButton,
QSizePolicy.ControlType.ToolButton,
Qt.Orientation.Horizontal if horizontal else Qt.Orientation.Vertical)
def add_widget(self, w):
item = LayoutItem(w)
self.items.append(item)
hs, vs = self.layout_spacing(w), self.layout_spacing(w, False)
if self.items:
self.width += hs
self.width += item.width
self.height = max(vs + item.height, self.height)
class FlowToolBar(QWidget):
def __init__(self, parent=None, icon_size=18):
super().__init__(parent)
self.icon_size = QSize(icon_size, icon_size)
self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
self.items = []
self.button_map = {}
self.applied_geometry = QRect(0, 0, 0, 0)
def add_action(self, ac, popup_mode=QToolButton.ToolButtonPopupMode.DelayedPopup):
w = Button(ac, self)
w.setPopupMode(popup_mode)
w.setIconSize(self.icon_size)
self.button_map[ac] = w
self.items.append(w)
w.layout_needed.connect(self.updateGeometry)
self.updateGeometry()
def add_separator(self):
self.items.append(Separator(self.icon_size, self))
self.updateGeometry()
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
return self.do_layout(QRect(0, 0, width, 0), apply_geometry=False)
def minimumSize(self):
size = QSize()
for item in self.items:
size = size.expandedTo(item.minimumSize())
return size
sizeHint = minimumSize
def paintEvent(self, ev):
if self.applied_geometry != self.rect():
self.do_layout(self.rect(), apply_geometry=True)
super().paintEvent(ev)
def do_layout(self, rect, apply_geometry=False):
x, y = rect.x(), rect.y()
line_height = 0
def layout_spacing(wid, horizontal=True):
ans = self.smart_spacing(horizontal)
if ans != -1:
return ans
return wid.style().layoutSpacing(
QSizePolicy.ControlType.ToolButton,
QSizePolicy.ControlType.ToolButton,
Qt.Orientation.Horizontal if horizontal else Qt.Orientation.Vertical)
lines, current_line = [], []
gmap = {}
if apply_geometry:
for item in self.items:
if isinstance(item, Separator):
item.setGeometry(0, 0, 0, 0)
def commit_line():
while current_line and isinstance(current_line[-1], Separator):
current_line.pop()
if current_line:
lines.append((line_height, current_line))
groups = []
current_group = Group(self.parent())
for wid in self.items:
if not wid.isVisible() or (not current_group and isinstance(wid, Separator)):
continue
if isinstance(wid, Separator):
groups.append(current_group)
current_group = Group(self.parent(), wid)
else:
current_group.add_widget(wid)
if current_group:
groups.append(current_group)
x = rect.x()
y = 0
line_height = 0
vs = 0
for group in groups:
if current_line and x + group.width >= rect.right():
commit_line()
current_line = []
x = rect.x()
y += group.height
group.leading_separator = None
line_height = 0
if group.leading_separator:
current_line.append(group.leading_separator)
sz = group.leading_separator.sizeHint()
gmap[group.leading_separator] = x, y, sz
x += sz.width() + group.layout_spacing(group.leading_separator)
for item in group.items:
wid = item.widget
if not vs:
vs = group.layout_spacing(wid, False)
if apply_geometry:
gmap[wid] = x, y, item.sz
x += item.width + group.layout_spacing(wid)
current_line.append(wid)
line_height = group.height
commit_line()
if apply_geometry:
self.applied_geometry = rect
for line_height, items in lines:
for wid in items:
x, wy, isz = gmap[wid]
if isz.height() < line_height:
wy += (line_height - isz.height()) // 2
if wid.isVisible():
wid.setGeometry(QRect(QPoint(x, wy), isz))
return y + line_height - rect.y()
def create_flow_toolbar(parent=None, icon_size=18, restrict_to_single_line=False):
if restrict_to_single_line:
return SingleLineToolBar(parent, icon_size)
return FlowToolBar(parent, icon_size)