mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
A better multiline toolbar for the comments editor
This commit is contained in:
parent
796034a8ab
commit
b604611a3a
@ -12,12 +12,11 @@ from html5_parser import parse
|
||||
from lxml import html
|
||||
from qt.core import (
|
||||
QAction, QApplication, QBrush, QByteArray, QCheckBox, QColor, QColorDialog,
|
||||
QDialog, QDialogButtonBox, QFont, QFontInfo, QFontMetrics, QFormLayout,
|
||||
QHBoxLayout, QIcon, QKeySequence, QLabel, QLineEdit, QMenu, QPalette,
|
||||
QPlainTextEdit, QPushButton, QSize, QSyntaxHighlighter, Qt, QTabWidget,
|
||||
QTextBlockFormat, QTextCharFormat, QTextCursor, QTextEdit, QTextFormat,
|
||||
QTextListFormat, QToolBar, QToolButton, QUrl, QVBoxLayout, QWidget, pyqtSignal,
|
||||
pyqtSlot
|
||||
QDialog, QDialogButtonBox, QFont, QFontInfo, QFontMetrics, QFormLayout, QIcon,
|
||||
QKeySequence, QLabel, QLineEdit, QMenu, QPalette, QPlainTextEdit, QPushButton,
|
||||
QSize, QSyntaxHighlighter, Qt, QTabWidget, QTextBlockFormat, QTextCharFormat,
|
||||
QTextCursor, QTextEdit, QTextFormat, QTextListFormat, QToolButton, QUrl,
|
||||
QVBoxLayout, QWidget, pyqtSignal, pyqtSlot
|
||||
)
|
||||
|
||||
from calibre import xml_replace_entities
|
||||
@ -26,8 +25,9 @@ from calibre.gui2 import (
|
||||
NO_URL_FORMATTING, choose_files, error_dialog, gprefs, is_dark_theme
|
||||
)
|
||||
from calibre.gui2.book_details import css
|
||||
from calibre.gui2.flow_toolbar import create_flow_toolbar
|
||||
from calibre.gui2.widgets import LineEditECM
|
||||
from calibre.gui2.widgets2 import to_plain_text, FlowLayout
|
||||
from calibre.gui2.widgets2 import to_plain_text
|
||||
from calibre.utils.cleantext import clean_xml_chars
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre.utils.imghdr import what
|
||||
@ -1069,12 +1069,7 @@ class Editor(QWidget): # {{{
|
||||
def __init__(self, parent=None, one_line_toolbar=False, toolbar_prefs_name=None):
|
||||
QWidget.__init__(self, parent)
|
||||
self.toolbar_prefs_name = toolbar_prefs_name or self.toolbar_prefs_name
|
||||
self.toolbar1 = QToolBar(self)
|
||||
self.toolbar2 = QToolBar(self)
|
||||
self.toolbar3 = QToolBar(self)
|
||||
for i in range(1, 4):
|
||||
t = getattr(self, 'toolbar%d'%i)
|
||||
t.setIconSize(QSize(18, 18))
|
||||
self.toolbar = create_flow_toolbar(self, restrict_to_single_line=one_line_toolbar, icon_size=18)
|
||||
self.editor = EditorWidget(self)
|
||||
self.editor.data_changed.connect(self.data_changed)
|
||||
self.set_base_url = self.editor.set_base_url
|
||||
@ -1090,15 +1085,8 @@ class Editor(QWidget): # {{{
|
||||
self.wyswyg.layout = l = QVBoxLayout(self.wyswyg)
|
||||
self.setLayout(self._layout)
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
if one_line_toolbar:
|
||||
tb = QHBoxLayout()
|
||||
else:
|
||||
tb = FlowLayout()
|
||||
tb.setContentsMargins(0, 0, 0, 0)
|
||||
l.addLayout(tb)
|
||||
tb.addWidget(self.toolbar1)
|
||||
tb.addWidget(self.toolbar2)
|
||||
tb.addWidget(self.toolbar3)
|
||||
|
||||
l.addWidget(self.toolbar)
|
||||
l.addWidget(self.editor)
|
||||
self._layout.addWidget(self.tabs)
|
||||
self.tabs.addTab(self.wyswyg, _('&Normal view'))
|
||||
@ -1111,53 +1099,46 @@ class Editor(QWidget): # {{{
|
||||
if hidden:
|
||||
self.hide_toolbars()
|
||||
|
||||
# toolbar1 {{{
|
||||
self.toolbar1.addAction(self.editor.action_undo)
|
||||
self.toolbar1.addAction(self.editor.action_redo)
|
||||
self.toolbar1.addAction(self.editor.action_select_all)
|
||||
self.toolbar1.addAction(self.editor.action_remove_format)
|
||||
self.toolbar1.addAction(self.editor.action_clear)
|
||||
self.toolbar1.addSeparator()
|
||||
self.toolbar.add_action(self.editor.action_undo)
|
||||
self.toolbar.add_action(self.editor.action_redo)
|
||||
self.toolbar.add_action(self.editor.action_select_all)
|
||||
self.toolbar.add_action(self.editor.action_remove_format)
|
||||
self.toolbar.add_action(self.editor.action_clear)
|
||||
self.toolbar.add_separator()
|
||||
|
||||
for x in ('copy', 'cut', 'paste'):
|
||||
ac = getattr(self.editor, 'action_'+x)
|
||||
self.toolbar1.addAction(ac)
|
||||
self.toolbar.add_action(ac)
|
||||
|
||||
self.toolbar1.addSeparator()
|
||||
self.toolbar1.addAction(self.editor.action_background)
|
||||
# }}}
|
||||
self.toolbar.add_separator()
|
||||
self.toolbar.add_action(self.editor.action_background)
|
||||
self.toolbar.add_separator()
|
||||
|
||||
# toolbar2 {{{
|
||||
for x in ('', 'un'):
|
||||
ac = getattr(self.editor, 'action_%sordered_list'%x)
|
||||
self.toolbar2.addAction(ac)
|
||||
self.toolbar2.addSeparator()
|
||||
self.toolbar.add_action(ac)
|
||||
self.toolbar.add_separator()
|
||||
for x in ('superscript', 'subscript', 'indent', 'outdent'):
|
||||
self.toolbar2.addAction(getattr(self.editor, 'action_' + x))
|
||||
self.toolbar.add_action(getattr(self.editor, 'action_' + x))
|
||||
if x in ('subscript', 'outdent'):
|
||||
self.toolbar2.addSeparator()
|
||||
self.toolbar.add_separator()
|
||||
|
||||
self.toolbar2.addAction(self.editor.action_block_style)
|
||||
w = self.toolbar2.widgetForAction(self.editor.action_block_style)
|
||||
if hasattr(w, 'setPopupMode'):
|
||||
w.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||
self.toolbar2.addAction(self.editor.action_insert_link)
|
||||
self.toolbar2.addAction(self.editor.action_insert_hr)
|
||||
# }}}
|
||||
self.toolbar.add_action(self.editor.action_block_style, popup_mode=QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||
self.toolbar.add_action(self.editor.action_insert_link)
|
||||
self.toolbar.add_action(self.editor.action_insert_hr)
|
||||
self.toolbar.add_separator()
|
||||
|
||||
# toolbar3 {{{
|
||||
for x in ('bold', 'italic', 'underline', 'strikethrough'):
|
||||
ac = getattr(self.editor, 'action_'+x)
|
||||
self.toolbar3.addAction(ac)
|
||||
self.toolbar.add_action(ac)
|
||||
self.addAction(ac)
|
||||
self.toolbar3.addSeparator()
|
||||
self.toolbar.add_separator()
|
||||
|
||||
for x in ('left', 'center', 'right', 'justified'):
|
||||
ac = getattr(self.editor, 'action_align_'+x)
|
||||
self.toolbar3.addAction(ac)
|
||||
self.toolbar3.addSeparator()
|
||||
self.toolbar3.addAction(self.editor.action_color)
|
||||
# }}}
|
||||
self.toolbar.add_action(ac)
|
||||
self.toolbar.add_separator()
|
||||
self.toolbar.add_action(self.editor.action_color)
|
||||
|
||||
self.code_edit.textChanged.connect(self.code_dirtied)
|
||||
self.editor.data_changed.connect(self.wyswyg_dirtied)
|
||||
@ -1201,14 +1182,10 @@ class Editor(QWidget): # {{{
|
||||
self.source_dirty = True
|
||||
|
||||
def hide_toolbars(self):
|
||||
self.toolbar1.setVisible(False)
|
||||
self.toolbar2.setVisible(False)
|
||||
self.toolbar3.setVisible(False)
|
||||
self.toolbar.setVisible(False)
|
||||
|
||||
def show_toolbars(self):
|
||||
self.toolbar1.setVisible(True)
|
||||
self.toolbar2.setVisible(True)
|
||||
self.toolbar3.setVisible(True)
|
||||
self.toolbar.setVisible(True)
|
||||
|
||||
def toggle_toolbars(self):
|
||||
visible = self.toolbars_visible
|
||||
@ -1218,7 +1195,7 @@ class Editor(QWidget): # {{{
|
||||
|
||||
@property
|
||||
def toolbars_visible(self):
|
||||
return self.toolbar1.isVisible() or self.toolbar2.isVisible() or self.toolbar3.isVisible()
|
||||
return self.toolbar.isVisible()
|
||||
|
||||
@toolbars_visible.setter
|
||||
def toolbars_visible(self, val):
|
||||
@ -1243,8 +1220,9 @@ class Editor(QWidget): # {{{
|
||||
if __name__ == '__main__':
|
||||
from calibre.gui2 import Application
|
||||
app = Application([])
|
||||
w = Editor()
|
||||
w = Editor(one_line_toolbar=False)
|
||||
w.resize(800, 600)
|
||||
w.setWindowFlag(Qt.WindowType.Dialog)
|
||||
w.show()
|
||||
w.html = '''<h1>Test Heading</h1><blockquote>Test blockquote</blockquote><p><span style="background-color: rgb(0, 255, 255); ">He hadn't
|
||||
set <u>out</u> to have an <em>affair</em>, <span style="font-style:italic; background-color:red">
|
||||
|
186
src/calibre/gui2/flow_toolbar.py
Normal file
186
src/calibre/gui2/flow_toolbar.py
Normal file
@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from qt.core import (
|
||||
QPainter, QPoint, QRect, QSize, QSizePolicy, QStyle, QStyleOption, 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 = QPainter(self)
|
||||
self.style().drawPrimitive(QStyle.PrimitiveElement.PE_IndicatorToolBarSeparator, self.style_option(), p, self)
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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 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 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 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 = {}
|
||||
for i, wid in enumerate(self.items):
|
||||
prev_wid = self.items[i - 1] if i else None
|
||||
if isinstance(wid, Button) and not wid.isVisible():
|
||||
continue
|
||||
isz = wid.sizeHint()
|
||||
hs, vs = layout_spacing(wid), layout_spacing(wid, False)
|
||||
|
||||
next_x = x + isz.width() + hs
|
||||
wid.setVisible(True)
|
||||
if isinstance(wid, Separator) and isinstance(prev_wid, Separator):
|
||||
wid.setVisible(False)
|
||||
if next_x - hs > rect.right() and line_height > 0:
|
||||
if isinstance(prev_wid, Separator):
|
||||
prev_wid.setVisible(False)
|
||||
if isinstance(wid, Separator):
|
||||
wid.setVisible(False)
|
||||
continue
|
||||
x = rect.x()
|
||||
y = y + line_height + vs
|
||||
next_x = x + isz.width() + hs
|
||||
lines.append((line_height, current_line))
|
||||
current_line = []
|
||||
line_height = 0
|
||||
if apply_geometry:
|
||||
gmap[wid] = x, y, isz
|
||||
x = next_x
|
||||
line_height = max(line_height, isz.height())
|
||||
current_line.append((wid, isz.height()))
|
||||
|
||||
lines.append((line_height, current_line))
|
||||
|
||||
if apply_geometry:
|
||||
self.applied_geometry = rect
|
||||
for line_height, items in lines:
|
||||
for wid, item_height in items:
|
||||
x, wy, isz = gmap[wid]
|
||||
if item_height < line_height:
|
||||
wy += (line_height - item_height) // 2
|
||||
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)
|
Loading…
x
Reference in New Issue
Block a user