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)