From 1f5e9d0ac5f1712fd1da7c55e71170ced1897256 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 25 Sep 2024 12:26:26 +0100 Subject: [PATCH 1/2] For column icons and grid view emblems, support icons for both light and dark themes. --- src/calibre/gui2/library/alternate_views.py | 11 ++++++-- src/calibre/gui2/library/models.py | 11 ++++++-- src/calibre/gui2/preferences/coloring.py | 31 +++++++++++++++++++-- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/library/alternate_views.py b/src/calibre/gui2/library/alternate_views.py index 8160cda63a..bfa511a9be 100644 --- a/src/calibre/gui2/library/alternate_views.py +++ b/src/calibre/gui2/library/alternate_views.py @@ -60,7 +60,7 @@ from qt.core import ( from calibre import fit_image, human_readable, prepare_string_for_xml from calibre.constants import DEBUG, config_dir, islinux from calibre.ebooks.metadata import fmt_sidx, rating_to_stars -from calibre.gui2 import clip_border_radius, config, empty_index, gprefs, rating_font +from calibre.gui2 import clip_border_radius, config, empty_index, gprefs, rating_font, is_dark_theme from calibre.gui2.dnd import path_from_qurl from calibre.gui2.gestures import GestureManager from calibre.gui2.library.caches import CoverCache, ThumbnailCache @@ -554,7 +554,14 @@ class CoverDelegate(QStyledItemDelegate): elif name == ':ondevice': ans = QIcon.ic('ok.png').pixmap(sz, sz) elif name: - pmap = QIcon(os.path.join(config_dir, 'cc_icons', name)).pixmap(sz, sz) + pmap = None + if is_dark_theme(): + n,ext = os.path.splitext(name) + d = os.path.join(config_dir, 'cc_icons', n + '-dark' + ext) + if os.path.exists(d): + pmap = QIcon(d).pixmap(sz, sz) + if pmap is None: + pmap = QIcon(os.path.join(config_dir, 'cc_icons', name)).pixmap(sz, sz) if not pmap.isNull(): ans = pmap cache[name] = ans diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 0aacc88c89..ac35a1649a 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -23,7 +23,7 @@ from calibre.db.search import CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH, _match from calibre.db.utils import force_to_bool from calibre.ebooks.metadata import authors_to_string, fmt_sidx, string_to_authors from calibre.ebooks.metadata.book.formatter import SafeFormat -from calibre.gui2 import error_dialog, simple_excepthook +from calibre.gui2 import error_dialog, simple_excepthook, is_dark_theme from calibre.gui2.library import DEFAULT_SORT from calibre.library.coloring import color_row_key from calibre.library.save_to_disk import find_plugboard @@ -132,8 +132,15 @@ class ColumnIcon: # {{{ total_width = 0 rh = max(2, self.model.row_height - 4) dim = int(self.dpr * rh) + icon_dir = os.path.join(config_dir, 'cc_icons') for icon in icons: - d = os.path.join(config_dir, 'cc_icons', icon) + d = None + if is_dark_theme(): + root,ext = os.path.splitext(icon) + d = os.path.join(icon_dir, root + '-dark' + ext) + d = d if os.path.exists(d) else None + if d is None: + d = os.path.join(icon_dir, icon) if (os.path.exists(d)): bm = QPixmap(d) scaled, nw, nh = fit_image(bm.width(), bm.height(), bm.width(), dim) diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index fd7a9cb32d..ec6df983fa 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -21,6 +21,7 @@ from qt.core import ( QDoubleValidator, QFrame, QGridLayout, + QHBoxLayout, QIcon, QIntValidator, QItemSelection, @@ -47,7 +48,8 @@ from qt.core import ( from calibre import as_unicode, prepare_string_for_xml, sanitize_file_name from calibre.constants import config_dir -from calibre.gui2 import choose_files, choose_save_file, error_dialog, gprefs, open_local_file, pixmap_to_data, question_dialog +from calibre.gui2 import (choose_files, choose_save_file, error_dialog, gprefs, info_dialog, + open_local_file, pixmap_to_data, question_dialog) from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.gui2.metadata.single_download import RichTextDelegate from calibre.gui2.preferences import ListViewWithMoveByKeyPress @@ -512,11 +514,19 @@ class RuleEditor(QDialog): # {{{ ' blanking all of its boxes')) l.addWidget(l6, 8, 0, 1, 8) + bbl = QHBoxLayout() self.bb = bb = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok|QDialogButtonBox.StandardButton.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) - l.addWidget(bb, 9, 0, 1, 8) + if self.rule_kind in ('emblem', 'icon'): + theme_button = QPushButton(_('Using icons in light/dark themes')) + theme_button.setIcon(QIcon.ic('help.png')) + theme_button.clicked.connect(self.show_theme_help) + bbl.addWidget(theme_button) + bbl.addStretch(10) + bbl.addWidget(bb) + l.addLayout(bbl, 9, 0, 1, 8) if self.rule_kind != 'color': self.remove_button = b = bb.addButton(_('&Remove icons'), QDialogButtonBox.ButtonRole.ActionRole) b.setIcon(QIcon.ic('minus.png')) @@ -555,6 +565,23 @@ class RuleEditor(QDialog): # {{{ self.resize(self.sizeHint()) + def show_theme_help(self): + msg = '

'+ _( + 'You can use different icons in light and dark themes. To do this, ' + 'add two icons to the icon list. The light theme icon will have ' + 'the "normal" name, for example "ok.png". The dark theme icon must ' + 'have the same name with "-dark" appended. For example, if the light ' + 'theme icon is named "ok.png" then the dark theme icon must be named ' + '"ok-dark.png".' + '

' + 'When defining a rule, always use the "normal" name. The -dark icon will be ' + 'automatically substituted for the normal icon when a dark theme is ' + 'being used.' + '

' + 'Remember to add both the light and dark theme icons to the list of icons.' + ) + '

' + info_dialog(self, _('Using icons in light/dark themes'), msg, show=True) + def multiple_box_clicked(self): self.update_filename_box() self.update_icon_filenames_in_box() From 5941de87a530efbe52bc866e8b86db18c057c338 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 25 Sep 2024 16:16:39 +0100 Subject: [PATCH 2/2] For icon rules and emblems, use the standard theme names for the icons. Generalize the naming so 1 or both of the icons can have themed names. --- src/calibre/gui2/library/alternate_views.py | 9 ++++---- src/calibre/gui2/library/models.py | 24 ++++++++++++++------- src/calibre/gui2/preferences/coloring.py | 23 +++++++++++++------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/calibre/gui2/library/alternate_views.py b/src/calibre/gui2/library/alternate_views.py index bfa511a9be..6bb0d50c60 100644 --- a/src/calibre/gui2/library/alternate_views.py +++ b/src/calibre/gui2/library/alternate_views.py @@ -64,6 +64,7 @@ from calibre.gui2 import clip_border_radius, config, empty_index, gprefs, rating from calibre.gui2.dnd import path_from_qurl from calibre.gui2.gestures import GestureManager from calibre.gui2.library.caches import CoverCache, ThumbnailCache +from calibre.gui2.library.models import themed_icon_name from calibre.gui2.pin_columns import PinContainer from calibre.utils import join_with_timeout from calibre.utils.config import prefs, tweaks @@ -555,11 +556,9 @@ class CoverDelegate(QStyledItemDelegate): ans = QIcon.ic('ok.png').pixmap(sz, sz) elif name: pmap = None - if is_dark_theme(): - n,ext = os.path.splitext(name) - d = os.path.join(config_dir, 'cc_icons', n + '-dark' + ext) - if os.path.exists(d): - pmap = QIcon(d).pixmap(sz, sz) + d = themed_icon_name(os.path.join(config_dir, 'cc_icons'), name) + if d is not None: + pmap = QIcon(d).pixmap(sz, sz) if pmap is None: pmap = QIcon(os.path.join(config_dir, 'cc_icons', name)).pixmap(sz, sz) if not pmap.isNull(): diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index ac35a1649a..7cb22a0af7 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -93,6 +93,20 @@ class ColumnColor: # {{{ # }}} +def themed_icon_name(icon_dir, icon_name): + root,ext = os.path.splitext(icon_name) + # Remove any theme from the icon name + root = root.removesuffix('-for-dark-theme').removesuffix('-for-light-theme') + # Check if the correct themed icon exists. + theme_suffix = '-for-dark-theme' if is_dark_theme() else '-for-light-theme' + d = os.path.join(icon_dir, root + theme_suffix + ext) + if os.path.exists(d): + return d + # No themed icon exists. Try the original name + d = os.path.join(icon_dir, icon_name) + return d if os.path.exists(d) else None + + class ColumnIcon: # {{{ def __init__(self, formatter, model): @@ -134,14 +148,8 @@ class ColumnIcon: # {{{ dim = int(self.dpr * rh) icon_dir = os.path.join(config_dir, 'cc_icons') for icon in icons: - d = None - if is_dark_theme(): - root,ext = os.path.splitext(icon) - d = os.path.join(icon_dir, root + '-dark' + ext) - d = d if os.path.exists(d) else None - if d is None: - d = os.path.join(icon_dir, icon) - if (os.path.exists(d)): + d = themed_icon_name(icon_dir, icon) + if d is not None: bm = QPixmap(d) scaled, nw, nh = fit_image(bm.width(), bm.height(), bm.width(), dim) bm = bm.scaled(int(nw), int(nh), aspectRatioMode=Qt.AspectRatioMode.IgnoreAspectRatio, diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index ec6df983fa..8bcb4df120 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -568,15 +568,22 @@ class RuleEditor(QDialog): # {{{ def show_theme_help(self): msg = '

'+ _( 'You can use different icons in light and dark themes. To do this, ' - 'add two icons to the icon list. The light theme icon will have ' - 'the "normal" name, for example "ok.png". The dark theme icon must ' - 'have the same name with "-dark" appended. For example, if the light ' - 'theme icon is named "ok.png" then the dark theme icon must be named ' - '"ok-dark.png".' + 'add two icons to the icon list. One of the icons must have either the ' + '"plain" name, for example "ok.png", or the themed name, for example ' + '"ok-for-light-theme.png". The other icon must have a themed name with ' + 'the same prefix, for example "ok-for-dark-theme.png". ' '

' - 'When defining a rule, always use the "normal" name. The -dark icon will be ' - 'automatically substituted for the normal icon when a dark theme is ' - 'being used.' + 'Example: if the light theme icon is named "ok.png" then the dark ' + 'theme icon must be named "ok-for-dark-theme.png". If the light ' + 'theme icon is named "ok-for-light-theme.png" then the dark theme ' + 'icon must be named either ok.png or "ok-for-dark-theme.png".' + '

' + 'When defining a rule, use either of the icon names. The correct ' + 'icon for the theme will automatically be used, if it exists.' + '

' + 'You are not required to change existing rules to use theming. Decide ' + 'the theme where the existing icon should be used then add the ' + 'other icon with the correct themed name. ' '

' 'Remember to add both the light and dark theme icons to the list of icons.' ) + '

'