From 2ac8fcb927c360ac841640a60fe7bedfa1630ca4 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 16 Dec 2024 12:39:09 +0000 Subject: [PATCH] Add an option to expand the tag browser tree to show the item that was current when the library was closed. The implementation provides an API for plugins to get and set the expansion state. See https://www.mobileread.com/forums/showthread.php?t=362240 --- src/calibre/gui2/__init__.py | 1 + src/calibre/gui2/preferences/look_feel.py | 1 + src/calibre/gui2/preferences/look_feel.ui | 12 ++++++++++ src/calibre/gui2/tag_browser/ui.py | 22 ++++++++++++++++-- src/calibre/gui2/tag_browser/view.py | 28 +++++++++++++++++++++++ src/calibre/gui2/ui.py | 17 +++++++++++++- 6 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 3e32c6b58f..9ff3d3a95f 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -411,6 +411,7 @@ def create_defs(): defs['tag_browser_old_look'] = False defs['tag_browser_hide_empty_categories'] = False defs['tag_browser_always_autocollapse'] = False + defs['tag_browser_restore_tree_expansion'] = False defs['tag_browser_allow_keyboard_focus'] = False defs['book_list_tooltips'] = True defs['show_layout_buttons'] = False diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 4e17ca4ab2..aa2bac5254 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -636,6 +636,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('tag_browser_old_look', gprefs) r('tag_browser_hide_empty_categories', gprefs) r('tag_browser_always_autocollapse', gprefs) + r('tag_browser_restore_tree_expansion', gprefs) r('tag_browser_show_tooltips', gprefs) r('tag_browser_allow_keyboard_focus', gprefs) r('bd_show_cover', gprefs) diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index d2d24ce76d..5801c552de 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -1426,6 +1426,18 @@ also checked then only categories containing a matched item will be shown.</p + + + + <p>When checked, when a library is opened the Tag browser +will expand the tree so that the last used item is visible. An item is "used" when +it is expanded, collapsed, or clicked.</p> + + + Expand tr&ee to show last used item + + + diff --git a/src/calibre/gui2/tag_browser/ui.py b/src/calibre/gui2/tag_browser/ui.py index 4de70a6701..2216471635 100644 --- a/src/calibre/gui2/tag_browser/ui.py +++ b/src/calibre/gui2/tag_browser/ui.py @@ -947,8 +947,26 @@ class TagBrowserWidget(QFrame): # {{{ self.tags_view.model().prefs['tag_browser_hide_empty_categories'] ^= True self.tags_view.recount_with_position_based_index() - def save_state(self): - gprefs.set('tag browser search box visible', self.toggle_search_button.isChecked()) + def save_state(self, gprefs_local=None): + if gprefs_local is None: + gprefs_local = gprefs + gprefs_local.set('tag browser search box visible', self.toggle_search_button.isChecked()) + + def restore_expansion_state(self, state): + ''' + Expands the tag browser tree so that the node specified in state is + visible. Use get_expansion_state() to get the state. The intent is that + a plugin could restore the state in the library_changed() method. + ''' + if state is not None: + self.tags_view.restore_expansion(state) + + def get_expansion_state(self): + ''' + Returns the currently expanded node in the tag browser as a string + suitable for restoring using restore_expansion_state. + ''' + return self.tags_view.current_expansion def toggle_item(self): self.tags_view.toggle_current_index() diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index 5d6d421795..1271509da3 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -289,6 +289,7 @@ class TagsView(QTreeView): # {{{ self.setDropIndicatorShown(True) self.setAutoExpandDelay(500) self.pane_is_visible = False + self.current_expansion = None self.search_icon = QIcon.ic('search.png') self.search_copy_icon = QIcon.ic("search_copy_saved.png") self.user_category_icon = QIcon.ic('tb_folder.png') @@ -426,6 +427,7 @@ class TagsView(QTreeView): # {{{ self.refresh_signal_processed = True db.add_listener(self.database_changed) self.expanded.connect(self.item_expanded) + self.collapsed.connect(self.item_collapsed) self.collapsed.connect(self.collapse_node_and_children) db.data.add_marked_listener(self.marked_change_listener) @@ -1434,6 +1436,7 @@ class TagsView(QTreeView): # {{{ ci = self.currentIndex() if not ci.isValid(): ci = self.indexAt(QPoint(10, 10)) + item_is_expanded = True if ci.isValid() and self.isExpanded(ci) else False use_pos = self._model.use_position_based_index_on_next_recount self._model.use_position_based_index_on_next_recount = False if use_pos: @@ -1454,6 +1457,10 @@ class TagsView(QTreeView): # {{{ index = self._model.index_for_named_path(path) if index.isValid(): self.show_item_at_index(index) + if not item_is_expanded: + # show_item_at_index() will expand the target node. + # Collapse it if it wasn't expanded before the recount. + self.collapse(index) self.blockSignals(False) def show_item_at_path(self, path, box=False, @@ -1490,5 +1497,26 @@ class TagsView(QTreeView): # {{{ Called by the expanded signal ''' self.setCurrentIndex(idx) + self.current_expansion = (self.isExpanded(idx), self._model.named_path_for_index(idx)) + + def item_collapsed(self, idx): + ''' + Called by the collapsed signal + ''' + self.current_expansion = (self.isExpanded(idx), self._model.named_path_for_index(idx)) + + def currentChanged(self, idx, prev_idx): + self.current_expansion = (self.isExpanded(idx), self._model.named_path_for_index(idx)) + super().currentChanged(idx, prev_idx) + + def restore_expansion(self, expansion): + self.current_expansion = None + if expansion is not None and len(expansion) == 2: + idx = self._model.index_for_named_path(expansion[1]) + if idx.isValid(): + self.show_item_at_index(idx) + if not expansion[0]: + self.collapse(idx) + # }}} diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index b2b520f70e..4f3394b91f 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -983,6 +983,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ return else: return + self._save_tb_state(gprefs) for action in self.iactions.values(): try: action.library_about_to_change(olddb, db) @@ -1009,6 +1010,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if db.new_api.pref('virtual_lib_on_startup'): self.apply_virtual_library(db.new_api.pref('virtual_lib_on_startup')) self.rebuild_vl_tabs() + self._restore_tb_expansion_state() # Do this before plugins library_changed() for action in self.iactions.values(): try: action.library_changed(db) @@ -1167,16 +1169,29 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ _('Failed')+': '+str(job.description), det_msg=job.details, retry_func=retry_func) + def _save_tb_state(self, gprefs): + self.tb_widget.save_state(gprefs) + if gprefs['tag_browser_restore_tree_expansion']: + tv_saved_expansions = gprefs.get('tags_view_saved_expansions', {}) + tv_saved_expansions.update({self.current_db.library_id: self.tb_widget.get_expansion_state()}) + gprefs['tags_view_saved_expansions'] = tv_saved_expansions + + def _restore_tb_expansion_state(self): + if gprefs['tag_browser_restore_tree_expansion']: + tv_saved_expansions = gprefs.get('tags_view_saved_expansions', {}) + self.tb_widget.restore_expansion_state(tv_saved_expansions.get(self.current_db.library_id)) + def read_settings(self): self.restore_geometry(gprefs, 'calibre_main_window_geometry', get_legacy_saved_geometry=lambda: config['main_window_geometry']) self.read_layout_settings() + self._restore_tb_expansion_state() def write_settings(self): with gprefs: # Only write to gprefs once self.save_geometry(gprefs, 'calibre_main_window_geometry') dynamic.set('sort_history', self.library_view.model().sort_history) self.save_layout_state() - self.tb_widget.save_state() + self._save_tb_state(gprefs) def restart(self): self.quit(restart=True)