Edit metadata dialog: Elide the names of custom columns that are longer than a fixed width, instead of using multiple lines. Configurable via Preferences->Tweaks->Edit metadata custom column label length

Merge branch 'master' of https://github.com/cbhaley/calibre into master
This commit is contained in:
Kovid Goyal 2020-10-17 14:25:32 +05:30
commit d8e502507b
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 74 additions and 48 deletions

View File

@ -404,6 +404,13 @@ metadata_single_use_2_cols_for_custom_fields = True
# metadata_edit_custom_column_order = ['#genre', '#mytags', '#etc'] # metadata_edit_custom_column_order = ['#genre', '#mytags', '#etc']
metadata_edit_custom_column_order = [] metadata_edit_custom_column_order = []
#: Edit metadata custom column label length
# Set the length of custom column labels shown in the edit metadata dialogs.
# Labels longer than this length will be elided. The length is computed by
# multiplying the average width of characters in the font by the number below.
metadata_edit_bulk_cc_label_length = 25
metadata_edit_single_cc_label_length = 12
#: The number of seconds to wait before sending emails #: The number of seconds to wait before sending emails
# The number of seconds to wait before sending emails when using a # The number of seconds to wait before sending emails when using a
# public email server like GMX/Hotmail/Gmail. Default is: 5 minutes # public email server like GMX/Hotmail/Gmail. Default is: 5 minutes

View File

@ -7,6 +7,7 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
from collections import defaultdict from collections import defaultdict
from functools import partial from functools import partial
from operator import itemgetter
from css_parser.css import CSSRule, CSSStyleDeclaration from css_parser.css import CSSRule, CSSStyleDeclaration
from css_selectors import parse, SelectorSyntaxError from css_selectors import parse, SelectorSyntaxError
@ -74,6 +75,37 @@ def merge_identical_selectors(sheet):
return len(remove) return len(remove)
def merge_identical_properties(sheet):
' Merge rules having identical properties '
properties_map = defaultdict(list)
def declaration_key(declaration):
items = []
for prop in declaration.getProperties():
val = prop.propertyValue.value
name = prop.name
items.append((name, val))
items.sort(key=itemgetter(0))
return tuple(items)
for rule in sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE):
properties_map[declaration_key(rule.style)].append(rule)
for rule_group in properties_map.values():
if len(rule_group) < 2:
continue
selectors = rule_group[0].selectorList
seen = {s.selectorText for s in selectors}
rules = iter(rule_group)
next(rules)
for rule in rules:
for s in rule.selectorList:
q = s.selectorText
if q not in seen:
seen.add(q)
selectors.append(s)
def remove_unused_css(container, report=None, remove_unused_classes=False, merge_rules=False): def remove_unused_css(container, report=None, remove_unused_classes=False, merge_rules=False):
''' '''
Remove all unused CSS rules from the book. An unused CSS rule is one that does not match any actual content. Remove all unused CSS rules from the book. An unused CSS rule is one that does not match any actual content.

View File

@ -9,15 +9,15 @@ __docformat__ = 'restructuredtext en'
import os import os
from functools import partial from functools import partial
from PyQt5.Qt import (QComboBox, QLabel, QSpinBox, QDoubleSpinBox, from PyQt5.Qt import (Qt, QComboBox, QLabel, QSpinBox, QDoubleSpinBox,
QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout, QUrl, QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout, QUrl,
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, QLineEdit, QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, QLineEdit,
QPushButton, QMessageBox, QToolButton, QPlainTextEdit) QMessageBox, QToolButton, QPlainTextEdit)
from calibre.utils.date import qt_to_dt, now, as_local_time, as_utc, internal_iso_format_string from calibre.utils.date import qt_to_dt, now, as_local_time, as_utc, internal_iso_format_string
from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.complete2 import EditWithComplete
from calibre.gui2.comments_editor import Editor as CommentsEditor from calibre.gui2.comments_editor import Editor as CommentsEditor
from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog, elided_text
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
@ -161,32 +161,22 @@ class Bool(Base):
self.combobox = QComboBox(parent) self.combobox = QComboBox(parent)
l.addWidget(self.combobox) l.addWidget(self.combobox)
t = _('Yes') c = QToolButton(parent)
c = QPushButton(t, parent) c.setText(_('Yes'))
width = c.fontMetrics().boundingRect(t).width() + 7
c.setMaximumWidth(width)
l.addWidget(c) l.addWidget(c)
c.clicked.connect(self.set_to_yes) c.clicked.connect(self.set_to_yes)
t = _('No') c = QToolButton(parent)
c = QPushButton(t, parent) c.setText(_('No'))
width = c.fontMetrics().boundingRect(t).width() + 7
c.setMaximumWidth(width)
l.addWidget(c) l.addWidget(c)
c.clicked.connect(self.set_to_no) c.clicked.connect(self.set_to_no)
if self.db.new_api.pref('bools_are_tristate'): if self.db.new_api.pref('bools_are_tristate'):
t = _('Clear') c = QToolButton(parent)
c = QPushButton(t, parent) c.setText(_('Clear'))
width = c.fontMetrics().boundingRect(t).width() + 7
c.setMaximumWidth(width)
l.addWidget(c) l.addWidget(c)
c.clicked.connect(self.set_to_cleared) c.clicked.connect(self.set_to_cleared)
c = QLabel('', parent)
c.setMaximumWidth(1)
l.addWidget(c, 1)
w = self.combobox w = self.combobox
items = [_('Yes'), _('No'), _('Undefined')] items = [_('Yes'), _('No'), _('Undefined')]
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')] icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
@ -326,15 +316,16 @@ class DateTime(Base):
dte.setMinimumDateTime(UNDEFINED_QDATETIME) dte.setMinimumDateTime(UNDEFINED_QDATETIME)
dte.setSpecialValueText(_('Undefined')) dte.setSpecialValueText(_('Undefined'))
l.addWidget(dte) l.addWidget(dte)
self.today_button = QToolButton(parent) self.today_button = QToolButton(parent)
self.today_button.setText(_('Today')) self.today_button.setText(_('Today'))
self.today_button.clicked.connect(dte.set_to_today) self.today_button.clicked.connect(dte.set_to_today)
l.addWidget(self.today_button) l.addWidget(self.today_button)
self.clear_button = QToolButton(parent) self.clear_button = QToolButton(parent)
self.clear_button.setIcon(QIcon(I('trash.png'))) self.clear_button.setIcon(QIcon(I('trash.png')))
self.clear_button.clicked.connect(dte.set_to_clear) self.clear_button.clicked.connect(dte.set_to_clear)
l.addWidget(self.clear_button) l.addWidget(self.clear_button)
l.addStretch(1)
def setter(self, val): def setter(self, val):
if val is None: if val is None:
@ -755,7 +746,7 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
turnover_point = count + 1000 turnover_point = count + 1000
ans = [] ans = []
column = row = base_row = max_row = 0 column = row = base_row = max_row = 0
minimum_label = 0 label_width = 0
for key in cols: for key in cols:
if not fm[key]['is_editable']: if not fm[key]['is_editable']:
continue # this almost never happens continue # this almost never happens
@ -780,6 +771,7 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
comments_not_in_tweak = 0 comments_not_in_tweak = 0
l = QGridLayout() l = QGridLayout()
l.setHorizontalSpacing(0)
if is_comments: if is_comments:
layout.addLayout(l, row, column, layout_rows_for_comments, 1) layout.addLayout(l, row, column, layout_rows_for_comments, 1)
layout.setColumnStretch(column, 100) layout.setColumnStretch(column, 100)
@ -790,33 +782,28 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
row += 1 row += 1
for c in range(0, len(w.widgets), 2): for c in range(0, len(w.widgets), 2):
if not is_comments: if not is_comments:
w.widgets[c].setWordWrap(True) # Set the label column width to a fixed size. Elide labels that
''' # don't fit
It seems that there is something strange with wordwrapped labels wij = w.widgets[c]
with some fonts. Apparently one part of QT thinks it is showing if label_width == 0:
a single line and sizes the line vertically accordingly. Another font_metrics = wij.fontMetrics()
part thinks there isn't enough space and wraps the label. The if bulk:
result is two lines in a single line space, cutting off parts of label_width = (font_metrics.averageCharWidth() *
the lines. It doesn't happen with every font, nor with every tweaks['metadata_edit_bulk_cc_label_length'])
"long" label. else:
label_width = (font_metrics.averageCharWidth() *
This change works around the problem by setting the maximum tweaks['metadata_edit_single_cc_label_length'])
display width and telling QT to respect that width. wij.setMaximumWidth(label_width)
While here I implemented an arbitrary minimum label length so
that there is a better chance that the field edit boxes line up.
'''
if minimum_label == 0:
minimum_label = w.widgets[c].fontMetrics().boundingRect('smallLabel').width()
label_width = w.widgets[c].fontMetrics().boundingRect(w.widgets[c].text()).width()
if c == 0: if c == 0:
w.widgets[0].setMaximumWidth(label_width) wij.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
w.widgets[0].setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) l.setColumnMinimumWidth(0, label_width)
l.setColumnMinimumWidth(0, minimum_label) wij.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
else: t = unicode_type(wij.text())
w.widgets[0].setMaximumWidth(max(w.widgets[0].maximumWidth(), label_width)) wij.setToolTip(t[1:-1] if t.startswith('&') else t[:-1])
w.widgets[c].setBuddy(w.widgets[c+1]) wij.setText(elided_text(t+' ', font=font_metrics,
l.addWidget(w.widgets[c], c, 0) width=label_width, pos='right'))
wij.setBuddy(w.widgets[c+1])
l.addWidget(wij, c, 0)
l.addWidget(w.widgets[c+1], c, 1) l.addWidget(w.widgets[c+1], c, 1)
else: else:
l.addWidget(w.widgets[0], 0, 0, 1, 2) l.addWidget(w.widgets[0], 0, 0, 1, 2)
@ -1306,7 +1293,7 @@ class BulkText(BulkBase):
w = RemoveTags(parent, values) w = RemoveTags(parent, values)
w.remove_tags_button.clicked.connect(self.edit_remove) w.remove_tags_button.clicked.connect(self.edit_remove)
self.widgets.append(QLabel(label_string(self.col_metadata['name'])+': ' + self.widgets.append(QLabel(label_string(self.col_metadata['name'])+': ' +
_('tags to remove'), parent)) _('tags to remove') + ':', parent))
self.widgets.append(w) self.widgets.append(w)
self.removing_widget = w self.removing_widget = w
self.main_widget.set_separator(',') self.main_widget.set_separator(',')