The tag browser notes/links icon stuff.

Clicking the "buttons" works because of how mouse tracking works. The user must move the mouse over/into the current item line before clicking a button, which causes that line to be painted. When the line is painted the horizontal positions of the buttons on the line are recorded. If/when a click happens, the click X point is checked to see if it is in the ranges defined by the horizontal positions.
This commit is contained in:
Charles Haley 2023-12-17 13:41:20 +00:00
parent 96211be30e
commit 91a46f99a9
6 changed files with 202 additions and 66 deletions

View File

@ -423,6 +423,8 @@ def create_defs():
defs['tb_search_order'] = {'0': 1, '1': 2, '2': 3, '3': 4, '4': 0}
defs['search_tool_bar_shows_text'] = True
defs['allow_keyboard_search_in_library_views'] = True
defs['show_links_in_tag_brouser'] = False
defs['show_notes_in_tag_brouser'] = False
def migrate_tweak(tweak_name, pref_name):
# If the tweak has been changed then leave the tweak in the file so

View File

@ -646,6 +646,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('language', prefs, choices=choices, restart_required=True, setting=LanguageSetting)
r('show_avg_rating', config)
r('show_links_in_tag_brouser', gprefs)
r('show_notes_in_tag_brouser', gprefs)
r('disable_animations', config)
r('systray_icon', config, restart_required=True)
r('show_splash_screen', gprefs)

View File

@ -1290,24 +1290,42 @@ structure and you want to use the same column order for each one.&lt;/p&gt;</str
</item>
</layout>
</item>
<item row="5" column="0">
<item row="5" column="0" colspan="3">
<layout class="QGridLayout" name="gridlayout_22">
<item row="0" column="0">
<widget class="QCheckBox" name="opt_show_avg_rating">
<property name="text">
<string>Show &amp;average ratings</string>
</property>
<property name="toolTip">
<string>Show the average rating per item indication in the Tag browser</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="2">
<item row="0" column="1">
<widget class="QCheckBox" name="opt_show_links_in_tag_brouser">
<property name="text">
<string>Show &amp;links icons</string>
</property>
<property name="toolTip">
<string>Show an icon if the item has an attached link</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QCheckBox" name="opt_tag_browser_show_tooltips">
<property name="text">
<string>Show &amp;tooltips</string>
</property>
</widget>
</item>
<item row="6" column="0">
<item row="1" column="0">
<widget class="QCheckBox" name="opt_tag_browser_show_counts">
<property name="toolTip">
<string>&lt;p&gt;Show counts for items in the Tag browser. Such as the number of books
@ -1319,14 +1337,27 @@ see the counts by hovering your mouse over any item.&lt;/p&gt;</string>
</property>
</widget>
</item>
<item row="6" column="2">
<item row="1" column="1">
<widget class="QCheckBox" name="opt_show_notes_in_tag_brouser">
<property name="text">
<string>Show &amp;notes icons</string>
</property>
<property name="toolTip">
<string>Show an icon if the item has an attached note</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QCheckBox" name="opt_tag_browser_old_look">
<property name="text">
<string>Use &amp;alternating row colors</string>
</property>
</widget>
</item>
<item row="7" column="0">
<item row="2" column="0">
<widget class="QCheckBox" name="opt_tag_browser_hide_empty_categories">
<property name="toolTip">
<string>&lt;p&gt;When checked, calibre will automatically hide any category
@ -1339,7 +1370,7 @@ box will cause these empty categories to be hidden.&lt;/p&gt;</string>
</property>
</widget>
</item>
<item row="7" column="2">
<item row="2" column="2">
<widget class="QCheckBox" name="opt_tag_browser_always_autocollapse">
<property name="toolTip">
<string>&lt;p&gt;When checked, Find in the Tag browser will show all items
@ -1351,6 +1382,8 @@ also checked then only categories containing a matched item will be shown.&lt;/p
</property>
</widget>
</item>
</layout>
</item>
<item row="8" column="0" colspan="3">
<layout class="QVBoxLayout" name="verticalLayout">
<item>

View File

@ -235,10 +235,16 @@ class TagTreeItem: # {{{
ar = self.average_rating
if ar:
tt.append(_('Average rating for books in this category: %.1f') % ar)
elif self.type == self.TAG and ar is not None:
elif self.type == self.TAG:
if ar is not None:
tt.append(_('Books in this category are unrated'))
if self.type == self.TAG and tag.category != 'search':
if tag.category != 'search':
tt.append(_('Number of books: %s') % self.item_count)
from calibre.gui2.ui import get_gui
db = get_gui().current_db.new_api
link = db.get_link_map(tag.category).get(tag.original_name)
if link:
tt.append(_('Link: %s') % link)
return '\n'.join(tt)
return None
if role == DRAG_IMAGE_ROLE:
@ -365,6 +371,7 @@ class TagsModel(QAbstractItemModel): # {{{
self._build_in_progress = False
self.reread_collapse_model({}, rebuild=False)
self.show_error_after_event_loop_tick_signal.connect(self.on_show_error_after_event_loop_tick, type=Qt.ConnectionType.QueuedConnection)
self.reset_notes_and_link_maps()
@property
def gui_parent(self):
@ -441,6 +448,43 @@ class TagsModel(QAbstractItemModel): # {{{
self._run_rebuild()
self.endResetModel()
def _cached_notes_map(self, category):
if self.notes_map is None:
self.notes_map = {}
if category not in self.notes_map:
try:
self.notes_map[category] = (self.db.new_api.get_all_items_that_have_notes(category),
self.db.new_api.get_item_name_map(category))
except:
self.notes_map[category] = (frozenset(), {})
return self.notes_map[category]
def _cached_link_map(self, category):
if self.link_map is None:
self.link_map = {}
if category not in self.link_map:
try:
self.link_map[category] = self.db.new_api.get_link_map(category)
except Exception:
self.link_map[category] = {}
return self.link_map[category]
def category_has_notes(self, category):
return len(self._cached_notes_map(category)[0]) > 0
def item_has_note(self, category, item_name):
notes_map, item_id_map = self._cached_notes_map(category)
return item_id_map.get(item_name) in notes_map
def category_has_links(self, category):
return len(self._cached_link_map(category)) > 0
def item_has_link(self, category, item_name):
return item_name in self._cached_link_map(category)
def reset_notes_and_link_maps(self):
self.link_map = self.notes_map = None
def rebuild_node_tree(self, state_map={}):
if self._build_in_progress:
print('Tag browser build already in progress')
@ -455,6 +499,7 @@ class TagsModel(QAbstractItemModel): # {{{
self._build_in_progress = False
def _run_rebuild(self, state_map={}):
self.reset_notes_and_link_maps()
for node in itervalues(self.node_map):
node.break_cycles()
del node # Clear reference to node in the current frame

View File

@ -514,6 +514,7 @@ class TagBrowserMixin: # {{{
m = self.library_view.model()
ids = [m.id(r) for r in rows]
self.tags_view.model().reset_notes_and_link_maps()
m.refresh(reset=False)
m.research()
self.library_view.select_rows(ids)

View File

@ -22,7 +22,7 @@ from calibre.constants import config_dir
from calibre.ebooks.metadata import rating_to_stars
from calibre.gui2 import (
FunctionDispatcher, choose_files, config, empty_index, gprefs, pixmap_to_data,
question_dialog, rating_font,
question_dialog, rating_font, safe_open_url,
)
from calibre.gui2.dialogs.edit_category_notes import EditNoteDialog
from calibre.gui2.complete2 import EditWithComplete
@ -43,6 +43,9 @@ class TagDelegate(QStyledItemDelegate): # {{{
self.rating_pat = re.compile(r'[%s]' % rating_to_stars(3, True))
self.rating_font = QFont(rating_font())
self.tags_view = tags_view
self.links_icon = QIcon.ic('external-link.png')
self.notes_icon = QIcon.ic('notes.png')
self.blank_icon = QIcon()
def draw_average_rating(self, item, style, painter, option, widget):
rating = item.average_rating
@ -86,6 +89,30 @@ class TagDelegate(QStyledItemDelegate): # {{{
hover = option.state & QStyle.StateFlag.State_MouseOver
is_search = (True if item.type == TagTreeItem.TAG and
item.tag.category == 'search' else False)
show_notes = gprefs['show_notes_in_tag_brouser']
show_links = gprefs['show_links_in_tag_brouser']
if item.type == TagTreeItem.TAG:
category = item.tag.category
name = item.tag.original_name
m = self.tags_view._model
if show_notes and m.category_has_notes(category):
icon = self.notes_icon if m.item_has_note(category, name) else self.blank_icon
width = int(tr.height()/2)
r = QRect(tr)
r.setRight(r.right() - 1), r.setLeft(r.right() - width - 4)
self.tags_view.current_note_button_position = (r.left(), r.left()+r.width())
icon.paint(painter, r, option.decorationAlignment, QIcon.Mode.Normal, QIcon.State.On)
tr.setRight(r.left() - 1)
if show_links and m.category_has_links(category):
icon = self.links_icon if m.item_has_link(category, name) else self.blank_icon
width = int(tr.height()/2)
r = QRect(tr)
r.setRight(r.right() - 1), r.setLeft(r.right() - width - 4)
self.tags_view.current_link_button_position = (r.left(), r.left()+r.width())
icon.paint(painter, r, option.decorationAlignment, QIcon.Mode.Normal, QIcon.State.On)
tr.setRight(r.left() - 1)
if not is_search and (hover or gprefs['tag_browser_show_counts']):
count = str(index.data(COUNT_ROLE))
width = painter.fontMetrics().boundingRect(count).width()
@ -202,6 +229,8 @@ class TagsView(QTreeView): # {{{
self.setTabKeyNavigation(True)
self.setAnimated(True)
self.setHeaderHidden(True)
self.current_note_button_position = (-1, -1)
self.current_link_button_position = (-1, -1)
self.setItemDelegate(TagDelegate(tags_view=self))
self.made_connections = False
self.setAcceptDrops(True)
@ -428,10 +457,29 @@ class TagsView(QTreeView): # {{{
except:
pass
def number_in_range(self, val, range_tuple):
return range_tuple[0] <= val <= range_tuple[1]
def mousePressEvent(self, event):
if event.buttons() & Qt.MouseButton.LeftButton:
# Only remember a possible drag start if the item is drag enabled
dex = self.indexAt(event.pos())
t = self._model.data(dex, Qt.UserRole)
if t.type == TagTreeItem.TAG:
db = self._model.db.new_api
tag = t.tag
x = event.pos().x()
if self.number_in_range(x, self.current_note_button_position):
from calibre.gui2.dialogs.show_category_note import ShowNoteDialog
item_id = db.get_item_id(tag.category, tag.original_name)
if db.notes_for(tag.category, item_id):
ShowNoteDialog(tag.category, item_id, db, parent=self).show()
return
elif self.number_in_range(x, self.current_link_button_position):
link = db.get_link_map(tag.category).get(tag.original_name)
if link:
safe_open_url(link)
return
if self._model.flags(dex) & Qt.ItemFlag.ItemIsDragEnabled:
self.possible_drag_start = event.pos()
else:
@ -811,6 +859,19 @@ class TagsView(QTreeView): # {{{
self.context_menu.addAction(_('Edit link for %s')%display_name(tag),
partial(self.context_menu_handler,
action='edit_author_link', index=tag.id)).setIcon(QIcon.ic('insert-link.png'))
elif self.db.new_api.has_link_map(key):
self.context_menu.addAction(_('Edit link for %s')%display_name(tag),
partial(self.context_menu_handler, action='open_editor',
category=tag.original_name if tag else None,
key=key))
if self.db.new_api.field_supports_notes(key):
item_id = self.db.new_api.get_item_id(tag.category, tag.original_name)
has_note = self._model.item_has_note(key, tag.original_name) #bool(self.db.new_api.notes_for(tag.category, item_id))
self.context_menu.addAction(self.edit_metadata_icon,
(_('Edit note for %s') if has_note else _('Create note for %s'))%display_name(tag),
partial(self.context_menu_handler, action='edit_note',
index=index, extra=item_id, category=tag.category))
# is_editable is also overloaded to mean 'can be added
# to a User category'
@ -848,14 +909,6 @@ class TagsView(QTreeView): # {{{
m.addAction(self.minus_icon,
_('Remove %s from selected books') % display_name(tag),
partial(self.context_menu_handler, action='remove_tag', index=index))
item_id = self.db.new_api.get_item_id(tag.category, tag.original_name)
has_note = bool(self.db.new_api.notes_for(tag.category, item_id))
self.context_menu.addAction(self.edit_metadata_icon,
(_('Edit note for %s') if has_note else _('Create note for %s'))%display_name(tag),
partial(self.context_menu_handler, action='edit_note',
index=index, extra=item_id, category=tag.category))
elif key == 'search' and tag.is_searchable:
self.context_menu.addAction(self.rename_icon,
_('Rename %s')%display_name(tag),