From d693cb46fe36a95a36c8c8a0c7a10315d4220834 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 15 Apr 2022 10:30:14 +0530 Subject: [PATCH] Use randomnly colored pins for text marks --- resources/images/marked-text.png | Bin 4875 -> 0 bytes resources/pin-template.svg | 177 +++++++++++++++++++++++++ src/calibre/db/view.py | 4 + src/calibre/gui2/actions/mark_books.py | 2 +- src/calibre/gui2/library/models.py | 41 +++++- 5 files changed, 220 insertions(+), 4 deletions(-) delete mode 100644 resources/images/marked-text.png create mode 100644 resources/pin-template.svg diff --git a/resources/images/marked-text.png b/resources/images/marked-text.png deleted file mode 100644 index d60fc1f38410055d0eeac061743abb03c9283510..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4875 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 diff --git a/resources/pin-template.svg b/resources/pin-template.svg new file mode 100644 index 0000000000..0a950c6f92 --- /dev/null +++ b/resources/pin-template.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Kovid Goyal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/calibre/db/view.py b/src/calibre/db/view.py index fd1287c15b..18ad1c4eb9 100644 --- a/src/calibre/db/view.py +++ b/src/calibre/db/view.py @@ -93,6 +93,7 @@ class View: 'au_map': self.get_author_data, 'ondevice': self.get_ondevice, 'marked': self.get_marked, + 'all_marked_labels': self.all_marked_labels, 'series_sort':self.get_series_sort, }.get(col, self._get) if isinstance(col, numbers.Integral): @@ -221,6 +222,9 @@ class View: id_ = idx if index_is_id else self.index_to_id(idx) return self.marked_ids.get(id_, default_value) + def all_marked_labels(self): + return set(self.marked_ids.values()) - {'true'} + def get_author_data(self, idx, index_is_id=True, default_value=None): id_ = idx if index_is_id else self.index_to_id(idx) with self.cache.safe_read_lock: diff --git a/src/calibre/gui2/actions/mark_books.py b/src/calibre/gui2/actions/mark_books.py index fc7de5f0d2..7fd9aa6330 100644 --- a/src/calibre/gui2/actions/mark_books.py +++ b/src/calibre/gui2/actions/mark_books.py @@ -122,7 +122,7 @@ class MarkBooksAction(InterfaceAction): self.menu = m = self.qaction.menu() m.aboutToShow.connect(self.about_to_show_menu) ma = partial(self.create_menu_action, m) - self.show_marked_action = a = ma('mark_with_text', _('Mark books with text label'), icon='marked-text.png') + self.show_marked_action = a = ma('mark_with_text', _('Mark books with text label'), icon='marked.png') a.triggered.connect(self.mark_with_text) self.show_marked_action = a = ma('show-marked', _('Show marked books'), icon='search.png', shortcut='Shift+Ctrl+M') a.triggered.connect(self.show_marked) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 325f11926c..561c8180bd 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -23,7 +23,7 @@ from calibre import ( fit_image, force_unicode, human_readable, isbytestring, prepare_string_for_xml, strftime ) -from calibre.constants import DEBUG, config_dir, filesystem_encoding +from calibre.constants import DEBUG, config_dir, dark_link_color, filesystem_encoding from calibre.db.search import CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH, _match from calibre.ebooks.metadata import authors_to_string, fmt_sidx, string_to_authors from calibre.ebooks.metadata.book.formatter import SafeFormat @@ -52,6 +52,17 @@ ALIGNMENT_MAP = {'left': Qt.AlignmentFlag.AlignLeft, 'right': Qt.AlignmentFlag.A _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')) + pm = QPixmap() + dpr = QApplication.instance().devicePixelRatio() + pm.setDevicePixelRatio(dpr) + pm.loadFromData(svg, 'svg') + if save_to: + pm.save(save_to) + return pm + + def default_image(): global _default_image if _default_image is None: @@ -232,7 +243,6 @@ class BooksModel(QAbstractTableModel): # {{{ # remember that the cover grid view needs a larger version of the icon, # anyway) self.marked_icon = QIcon(I('marked.png')) - self.marked_text_icon = QIcon(I('marked-text.png')) self.bool_blank_icon_as_icon = QIcon(self.bool_blank_icon) self.row_decoration = None self.device_connected = False @@ -241,8 +251,33 @@ class BooksModel(QAbstractTableModel): # {{{ self.current_highlighted_idx = None self.highlight_only = False self.row_height = 0 + self.marked_text_icons = {} self.read_config() + def marked_text_icon_for(self, label): + import random + ans = self.marked_text_icons.get(label) + if ans is not None: + return ans[1] + used_labels = self.db.data.all_marked_labels() + for qlabel in tuple(self.marked_text_icons): + if qlabel not in used_labels: + del self.marked_text_icons[qlabel] + used_colors = {x[0] for x in self.marked_text_icons.values()} + if QApplication.instance().is_dark_theme: + all_colors = {dark_link_color, 'lightgreen', 'red', 'maroon', 'cyan', 'pink'} + else: + all_colors = {'blue', 'green', 'red', 'maroon', 'cyan', 'pink'} + for c in all_colors - used_colors: + color = c + break + else: + color = random.choice(sorted(all_colors)) + pm = render_pin(color) + ans = QIcon(pm) + self.marked_text_icons[label] = color, ans + return ans + def _clear_caches(self): self.color_cache = defaultdict(dict) self.icon_cache = defaultdict(dict) @@ -1075,7 +1110,7 @@ class BooksModel(QAbstractTableModel): # {{{ try: m = self.db.data.get_marked(self.db.data.index_to_id(section)) if m: - i = self.marked_icon if m == 'true' else self.marked_text_icon + i = self.marked_icon if m == 'true' else self.marked_text_icon_for(m) else: i = self.row_decoration return i