diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 4ee96eb52b..5b7cc484fe 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -9,6 +9,7 @@ import signal import sys import threading from contextlib import contextmanager +from functools import lru_cache from qt.core import ( QT_VERSION, QApplication, QBuffer, QByteArray, QColor, QDateTime, QDesktopServices, QDialog, QDialogButtonBox, QEvent, QFileDialog, @@ -1126,7 +1127,19 @@ class Application(QApplication): QTimer.singleShot(1000, lambda: QApplication.instance().setAttribute(Qt.ApplicationAttribute.AA_SetPalette, False)) self.ignore_palette_changes = False + @lru_cache(maxsize=256) + def cached_qimage(self, name): + return self.cached_qpixmap(name).toImage() + + @lru_cache(maxsize=256) + def cached_qpixmap(self, name): + ic = QIcon.ic(name) + pmap = ic.pixmap(ic.availableSizes()[0]) + return pmap + def on_palette_change(self): + self.cached_qimage.cache_clear() + self.cached_qpixmap.cache_clear() self.is_dark_theme = is_dark_theme() self.setProperty('is_dark_theme', self.is_dark_theme) if self.using_calibre_style: diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index 15056d4b3f..665c9b91c5 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -126,9 +126,9 @@ class ShareConnMenu(QMenu): # {{{ subject = opts.subjects.get(account, '') alias = opts.aliases.get(account, '') dest = 'mail:'+account+';'+formats+';'+subject - action1 = DeviceAction(dest, False, False, I('mail.png'), + action1 = DeviceAction(dest, False, False, 'mail.png', alias or account) - action2 = DeviceAction(dest, True, False, I('mail.png'), + action2 = DeviceAction(dest, True, False, 'mail.png', (alias or account) + ' ' + _('(delete from library)')) self.email_to_menu.addAction(action1) self.email_to_and_delete_menu.addAction(action2) @@ -136,22 +136,22 @@ class ShareConnMenu(QMenu): # {{{ self.memory.append(action2) if default: ac = DeviceAction(dest, False, False, - I('mail.png'), _('Email to') + ' ' +(alias or + 'mail.png', _('Email to') + ' ' +(alias or account)) self.addAction(ac) self.email_actions.append(ac) ac.a_s.connect(sync_menu.action_triggered) action1.a_s.connect(sync_menu.action_triggered) action2.a_s.connect(sync_menu.action_triggered) - action1 = DeviceAction('choosemail:', False, False, I('mail.png'), + action1 = DeviceAction('choosemail:', False, False, 'mail.png', _('Select recipients')) - action2 = DeviceAction('choosemail:', True, False, I('mail.png'), + action2 = DeviceAction('choosemail:', True, False, 'mail.png', _('Select recipients') + ' ' + _('(delete from library)')) self.email_to_menu.addAction(action1) self.email_to_and_delete_menu.addAction(action2) self.memory.append(action1) self.memory.append(action2) - tac1 = DeviceAction('choosemail:', False, False, I('mail.png'), + tac1 = DeviceAction('choosemail:', False, False, 'mail.png', _('Email to selected recipients...')) self.addAction(tac1) tac1.a_s.connect(sync_menu.action_triggered) diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index bc604052ca..af8e178626 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -379,7 +379,7 @@ class EditMetadataAction(InterfaceAction): intro_msg=_('The downloaded metadata is on the left and the original metadata' ' is on the right. If a downloaded value is blank or unknown,' ' the original value is used.'), - action_button=(_('&View book'), I('view.png'), self.gui.iactions['View'].view_historical), + action_button=(_('&View book'), 'view.png', self.gui.iactions['View'].view_historical), db=db ) if d.exec() == QDialog.DialogCode.Accepted: diff --git a/src/calibre/gui2/actions/store.py b/src/calibre/gui2/actions/store.py index c9f6279bde..69ea2ffc8b 100644 --- a/src/calibre/gui2/actions/store.py +++ b/src/calibre/gui2/actions/store.py @@ -4,7 +4,7 @@ __docformat__ = 'restructuredtext en' from functools import partial -from qt.core import QIcon, QSize +from qt.core import QIcon from calibre.gui2 import error_dialog from calibre.gui2.actions import InterfaceAction @@ -37,8 +37,7 @@ class StoreAction(InterfaceAction): def load_menu(self): self.store_list_menu.clear() - icon = QIcon() - icon.addFile(I('donate.png'), QSize(16, 16)) + icon = QIcon.ic('donate.png') for n, p in sorted(self.gui.istores.items(), key=lambda x: x[0].lower()): if p.base_plugin.affiliate: self.store_list_menu.addAction(icon, n, diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 56a22f5a0a..2750e7f1d7 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -516,7 +516,7 @@ class CoverView(QWidget): # {{{ QSizePolicy.Policy.Expanding if vertical else QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) - self.default_pixmap = QPixmap(I('default_cover.png')) + self.default_pixmap = QApplication.instance().cached_qpixmap('default_cover.png') self.pixmap = self.default_pixmap self.pwidth = self.pheight = None self.data = {} diff --git a/src/calibre/gui2/convert/debug.py b/src/calibre/gui2/convert/debug.py index aa3efc4d8e..e61e93bd2f 100644 --- a/src/calibre/gui2/convert/debug.py +++ b/src/calibre/gui2/convert/debug.py @@ -16,7 +16,7 @@ from calibre.ebooks.conversion.config import OPTIONS class DebugWidget(Widget, Ui_Form): TITLE = _('Debug') - ICON = I('debug.png') + ICON = 'debug.png' HELP = _('Debug the conversion process.') COMMIT_NAME = 'debug' diff --git a/src/calibre/gui2/convert/look_and_feel.py b/src/calibre/gui2/convert/look_and_feel.py index 4c7e01627e..0fcd6e6d3e 100644 --- a/src/calibre/gui2/convert/look_and_feel.py +++ b/src/calibre/gui2/convert/look_and_feel.py @@ -18,7 +18,7 @@ from polyglot.builtins import iteritems class LookAndFeelWidget(Widget, Ui_Form): TITLE = _('Look & feel') - ICON = I('lookfeel.png') + ICON = 'lookfeel.png' HELP = _('Control the look and feel of the output.') COMMIT_NAME = 'look_and_feel' diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index 447b0a0e56..71b82a14d6 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' import os, re, errno -from qt.core import QPixmap +from qt.core import QPixmap, QApplication from calibre.gui2 import choose_images, error_dialog from calibre.gui2.convert.metadata_ui import Ui_Form @@ -49,7 +49,7 @@ def create_cover_file(db, book_id): class MetadataWidget(Widget, Ui_Form): TITLE = _('Metadata') - ICON = I('dialog_information.png') + ICON = 'dialog_information.png' HELP = _('Set the metadata. The output file will contain as much of this ' 'metadata as possible.') COMMIT_NAME = 'metadata' @@ -111,8 +111,7 @@ class MetadataWidget(Widget, Ui_Form): self.cover_data = cover self.set_cover_tooltip(pm) else: - pm = QPixmap(I('default_cover.png')) - pm.setDevicePixelRatio(getattr(self, 'devicePixelRatioF', self.devicePixelRatio)()) + pm = QApplication.instance().cached_qpixmap('default_cover.png') self.cover.setPixmap(pm) self.cover.setToolTip(_('This book has no cover')) for x in ('author', 'series', 'publisher'): diff --git a/src/calibre/gui2/convert/structure_detection.py b/src/calibre/gui2/convert/structure_detection.py index f1826faaf0..56a1901fe4 100644 --- a/src/calibre/gui2/convert/structure_detection.py +++ b/src/calibre/gui2/convert/structure_detection.py @@ -14,7 +14,7 @@ from calibre.ebooks.conversion.config import OPTIONS class StructureDetectionWidget(Widget, Ui_Form): TITLE = _('Structure\ndetection') - ICON = I('chapters.png') + ICON = 'chapters.png' HELP = _('Fine tune the detection of chapter headings and ' 'other document structure.') COMMIT_NAME = 'structure_detection' diff --git a/src/calibre/gui2/convert/toc.py b/src/calibre/gui2/convert/toc.py index e7677f075f..719f6b9d02 100644 --- a/src/calibre/gui2/convert/toc.py +++ b/src/calibre/gui2/convert/toc.py @@ -16,7 +16,7 @@ from calibre.ebooks.conversion.config import OPTIONS class TOCWidget(Widget, Ui_Form): TITLE = _('Table of\nContents') - ICON = I('toc.png') + ICON = 'toc.png' HELP = _('Control the creation/conversion of the Table of Contents.') COMMIT_NAME = 'toc' diff --git a/src/calibre/gui2/dialogs/confirm_delete_location.py b/src/calibre/gui2/dialogs/confirm_delete_location.py index 58b9f49df6..98111edebb 100644 --- a/src/calibre/gui2/dialogs/confirm_delete_location.py +++ b/src/calibre/gui2/dialogs/confirm_delete_location.py @@ -7,7 +7,7 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' \ __docformat__ = 'restructuredtext en' from calibre.gui2.dialogs.confirm_delete_location_ui import Ui_Dialog -from qt.core import QDialog, Qt, QPixmap, QIcon +from qt.core import QDialog, Qt, QIcon class Dialog(QDialog, Ui_Dialog): @@ -42,8 +42,9 @@ class Dialog(QDialog, Ui_Dialog): def confirm_location(msg, name, parent=None, pixmap='dialog_warning.png'): d = Dialog(msg, name, parent) - d.label.setPixmap(QPixmap(I(pixmap))) - d.setWindowIcon(QIcon.ic(pixmap)) + ic = QIcon.ic(pixmap) + d.label.setPixmap(ic.pixmap(ic.availableSizes()[0])) + d.setWindowIcon(ic) d.resize(d.sizeHint()) ret = d.exec() d.break_cycles() diff --git a/src/calibre/gui2/dialogs/plugin_updater.py b/src/calibre/gui2/dialogs/plugin_updater.py index 780cce6b79..aa82ee213a 100644 --- a/src/calibre/gui2/dialogs/plugin_updater.py +++ b/src/calibre/gui2/dialogs/plugin_updater.py @@ -11,8 +11,7 @@ import traceback from qt.core import ( QAbstractItemView, QAbstractTableModel, QAction, QApplication, QBrush, QComboBox, QDialog, QDialogButtonBox, QFont, QFrame, QHBoxLayout, QIcon, QLabel, QLineEdit, - QModelIndex, QPixmap, QSize, QSortFilterProxyModel, Qt, QTableView, QUrl, - QVBoxLayout + QModelIndex, QSize, QSortFilterProxyModel, Qt, QTableView, QUrl, QVBoxLayout ) from calibre import prints @@ -129,13 +128,12 @@ class ImageTitleLayout(QHBoxLayout): title_font = QFont() title_font.setPointSize(16) title_image_label = QLabel(parent) - pixmap = QPixmap() - pixmap.load(I(icon_name)) - if pixmap is None: + ic = QIcon.ic(icon_name) + if ic.isNull(): error_dialog(parent, _('Restart required'), _('You must restart calibre before using this plugin!'), show=True) else: - title_image_label.setPixmap(pixmap) + title_image_label.setPixmap(ic.pixmap(32, 32)) title_image_label.setMaximumSize(32, 32) title_image_label.setScaledContents(True) self.addWidget(title_image_label) @@ -387,7 +385,7 @@ class DisplayPluginModel(QAbstractTableModel): icon_name = 'plugin_new_valid.png' else: icon_name = 'plugin_new_invalid.png' - return QIcon(I('plugins/' + icon_name)) + return QIcon.ic('plugins/' + icon_name) def _get_status_tooltip(self, display_plugin): if display_plugin.is_deprecated: diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 52db152397..0e95fe8161 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -153,7 +153,7 @@ class LibraryWidget(Splitter): # {{{ idx = 0 if orientation == Qt.Orientation.Vertical else 1 size = 300 if orientation == Qt.Orientation.Vertical else 550 Splitter.__init__(self, 'cover_browser_splitter', _('Cover browser'), - I('cover_flow.png'), + 'cover_flow.png', orientation=orientation, parent=parent, connect_button=not config['separate_cover_flow'], side_index=idx, initial_side_size=size, initial_show=False, diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 1577539f71..1b1e8af84f 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -47,8 +47,6 @@ TIME_FMT = '%d %b %Y' ALIGNMENT_MAP = {'left': Qt.AlignmentFlag.AlignLeft, 'right': Qt.AlignmentFlag.AlignRight, 'center': Qt.AlignmentFlag.AlignHCenter} -_default_image = None - def render_pin(color='green', save_to=None): svg = P('pin-template.svg', data=True).replace(b'fill:#f39509', ('fill:' + color).encode('utf-8')) @@ -61,13 +59,6 @@ def render_pin(color='green', save_to=None): return pm -def default_image(): - global _default_image - if _default_image is None: - _default_image = QImage(I('default_cover.png')) - return _default_image - - def group_numbers(numbers): for k, g in groupby(enumerate(sorted(numbers)), lambda i_x:i_x[0] - i_x[1]): first = None @@ -224,7 +215,6 @@ class BooksModel(QAbstractTableModel): # {{{ self.editable_cols = ['title', 'authors', 'rating', 'publisher', 'tags', 'series', 'timestamp', 'pubdate', 'languages'] - self.default_image = default_image() self.sorted_on = DEFAULT_SORT self.sort_history = [self.sorted_on] self.last_search = '' # The last search performed on this model @@ -276,6 +266,10 @@ class BooksModel(QAbstractTableModel): # {{{ self.marked_text_icons[label] = color, ans return ans + @property + def default_image(self): + return QApplication.instance().cached_qimage('default_cover.png') + def _clear_caches(self): self.color_cache = defaultdict(dict) self.icon_cache = defaultdict(dict) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 367ca93feb..288d36e3ec 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -132,7 +132,7 @@ def init_qt(args): # Ancient broken VNC servers cannot handle icons of size greater than 256 # https://www.mobileread.com/forums/showthread.php?t=278447 ic = 'lt.png' if is_x11 else 'library.png' - app.setWindowIcon(QIcon(I(ic, allow_user_override=False))) + app.setWindowIcon(QIcon.ic(ic)) return app, opts, args diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 82d645ac8e..9f90268ae8 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -1285,7 +1285,7 @@ class Cover(ImageView): # {{{ if cdata: pm.loadFromData(cdata) if pm.isNull(): - pm = QPixmap(I('default_cover.png')) + pm = QApplication.instance().cached_qpixmap('default_cover.png') else: self._cdata = cdata pm.setDevicePixelRatio(getattr(self, 'devicePixelRatioF', self.devicePixelRatio)()) diff --git a/src/calibre/gui2/metadata/diff.py b/src/calibre/gui2/metadata/diff.py index b5e9487bd3..4baece3486 100644 --- a/src/calibre/gui2/metadata/diff.py +++ b/src/calibre/gui2/metadata/diff.py @@ -318,7 +318,8 @@ class CoverView(QWidget): self.field = field self.metadata = metadata self.pixmap = None - self.blank = QPixmap(I('blank.png')) + ic = QIcon.ic('blank.png') + self.blank = ic.pixmap(ic.availableSizes()[0]) self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.MinimumExpanding) self.sizePolicy().setHeightForWidth(True) @@ -665,7 +666,7 @@ class CompareMany(QDialog): self.addAction(ac) if action_button is not None: self.acb = b = bb.addButton(action_button[0], QDialogButtonBox.ButtonRole.ActionRole) - b.setIcon(QIcon(action_button[1])) + b.setIcon(QIcon.ic(action_button[1])) self.action_button_action = action_button[2] b.clicked.connect(self.action_button_clicked) self.nb = b = bb.addButton(_('&Next') if self.total > 1 else _('&OK'), QDialogButtonBox.ButtonRole.ActionRole) diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index 32f4d1bc20..2d941cdb12 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -622,7 +622,8 @@ class CoversModel(QAbstractListModel): # {{{ QAbstractListModel.__init__(self, parent) if current_cover is None: - current_cover = QPixmap(I('default_cover.png')) + ic = QIcon.ic('default_cover.png') + current_cover = ic.pixmap(ic.availableSizes()[0]) current_cover.setDevicePixelRatio(QApplication.instance().devicePixelRatio()) self.blank = QIcon.ic('blank.png').pixmap(*CoverDelegate.ICON_SIZE) diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index e17a4eedc4..0a84eb9ee2 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -1097,7 +1097,7 @@ class Splitter(QSplitter): self.button.clicked.connect(self.double_clicked) if shortcut is not None: - self.action_toggle = QAction(QIcon(icon), _('Toggle') + ' ' + label, + self.action_toggle = QAction(QIcon.ic(icon), _('Toggle') + ' ' + label, self) self.action_toggle.changed.connect(self.update_shortcut) self.action_toggle.triggered.connect(self.toggle_triggered)