diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index abdb971a36..c563d7753a 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -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 = [] +#: 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 when using a # public email server like GMX/Hotmail/Gmail. Default is: 5 minutes diff --git a/src/calibre/ebooks/oeb/polish/css.py b/src/calibre/ebooks/oeb/polish/css.py index 3e228a5b68..e6ae0504bc 100644 --- a/src/calibre/ebooks/oeb/polish/css.py +++ b/src/calibre/ebooks/oeb/polish/css.py @@ -7,6 +7,7 @@ __copyright__ = '2014, Kovid Goyal ' from collections import defaultdict from functools import partial +from operator import itemgetter from css_parser.css import CSSRule, CSSStyleDeclaration from css_selectors import parse, SelectorSyntaxError @@ -74,6 +75,37 @@ def merge_identical_selectors(sheet): 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): ''' Remove all unused CSS rules from the book. An unused CSS rule is one that does not match any actual content. diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 141f0a145a..8348476e7a 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -9,15 +9,15 @@ __docformat__ = 'restructuredtext en' import os 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, 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.gui2.complete2 import EditWithComplete 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.utils.config import tweaks from calibre.utils.icu import sort_key @@ -161,32 +161,22 @@ class Bool(Base): self.combobox = QComboBox(parent) l.addWidget(self.combobox) - t = _('Yes') - c = QPushButton(t, parent) - width = c.fontMetrics().boundingRect(t).width() + 7 - c.setMaximumWidth(width) + c = QToolButton(parent) + c.setText(_('Yes')) l.addWidget(c) c.clicked.connect(self.set_to_yes) - t = _('No') - c = QPushButton(t, parent) - width = c.fontMetrics().boundingRect(t).width() + 7 - c.setMaximumWidth(width) + c = QToolButton(parent) + c.setText(_('No')) l.addWidget(c) c.clicked.connect(self.set_to_no) if self.db.new_api.pref('bools_are_tristate'): - t = _('Clear') - c = QPushButton(t, parent) - width = c.fontMetrics().boundingRect(t).width() + 7 - c.setMaximumWidth(width) + c = QToolButton(parent) + c.setText(_('Clear')) l.addWidget(c) c.clicked.connect(self.set_to_cleared) - c = QLabel('', parent) - c.setMaximumWidth(1) - l.addWidget(c, 1) - w = self.combobox items = [_('Yes'), _('No'), _('Undefined')] icons = [I('ok.png'), I('list_remove.png'), I('blank.png')] @@ -326,15 +316,16 @@ class DateTime(Base): dte.setMinimumDateTime(UNDEFINED_QDATETIME) dte.setSpecialValueText(_('Undefined')) l.addWidget(dte) + self.today_button = QToolButton(parent) self.today_button.setText(_('Today')) self.today_button.clicked.connect(dte.set_to_today) l.addWidget(self.today_button) + self.clear_button = QToolButton(parent) self.clear_button.setIcon(QIcon(I('trash.png'))) self.clear_button.clicked.connect(dte.set_to_clear) l.addWidget(self.clear_button) - l.addStretch(1) def setter(self, val): 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 ans = [] column = row = base_row = max_row = 0 - minimum_label = 0 + label_width = 0 for key in cols: if not fm[key]['is_editable']: 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 l = QGridLayout() + l.setHorizontalSpacing(0) if is_comments: layout.addLayout(l, row, column, layout_rows_for_comments, 1) layout.setColumnStretch(column, 100) @@ -790,33 +782,28 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa row += 1 for c in range(0, len(w.widgets), 2): if not is_comments: - w.widgets[c].setWordWrap(True) - ''' - It seems that there is something strange with wordwrapped labels - with some fonts. Apparently one part of QT thinks it is showing - a single line and sizes the line vertically accordingly. Another - part thinks there isn't enough space and wraps the label. The - result is two lines in a single line space, cutting off parts of - the lines. It doesn't happen with every font, nor with every - "long" label. - - This change works around the problem by setting the maximum - display width and telling QT to respect that 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() + # Set the label column width to a fixed size. Elide labels that + # don't fit + wij = w.widgets[c] + if label_width == 0: + font_metrics = wij.fontMetrics() + if bulk: + label_width = (font_metrics.averageCharWidth() * + tweaks['metadata_edit_bulk_cc_label_length']) + else: + label_width = (font_metrics.averageCharWidth() * + tweaks['metadata_edit_single_cc_label_length']) + wij.setMaximumWidth(label_width) if c == 0: - w.widgets[0].setMaximumWidth(label_width) - w.widgets[0].setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) - l.setColumnMinimumWidth(0, minimum_label) - else: - w.widgets[0].setMaximumWidth(max(w.widgets[0].maximumWidth(), label_width)) - w.widgets[c].setBuddy(w.widgets[c+1]) - l.addWidget(w.widgets[c], c, 0) + wij.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) + l.setColumnMinimumWidth(0, label_width) + wij.setAlignment(Qt.AlignRight|Qt.AlignVCenter) + t = unicode_type(wij.text()) + wij.setToolTip(t[1:-1] if t.startswith('&') else t[:-1]) + wij.setText(elided_text(t+' ', font=font_metrics, + width=label_width, pos='right')) + wij.setBuddy(w.widgets[c+1]) + l.addWidget(wij, c, 0) l.addWidget(w.widgets[c+1], c, 1) else: l.addWidget(w.widgets[0], 0, 0, 1, 2) @@ -1306,7 +1293,7 @@ class BulkText(BulkBase): w = RemoveTags(parent, values) w.remove_tags_button.clicked.connect(self.edit_remove) self.widgets.append(QLabel(label_string(self.col_metadata['name'])+': ' + - _('tags to remove'), parent)) + _('tags to remove') + ':', parent)) self.widgets.append(w) self.removing_widget = w self.main_widget.set_separator(',')