From 14b40e15590df94266ec6a08a5f3d1c4e7a574b9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 14 Aug 2013 18:42:05 +0530 Subject: [PATCH] Virtual libraries as tabs above book list Add an option to display virtual libraries as tabs above the book list. Convenient to quickly switch between virtual libraries. To enable, click the Virtual library button and select 'Show virtual libraries as tabs'. You can re-arrange the tabs by drag and drop and close tabs you do not want. Right click on the tabs to restore closed tabs. --- src/calibre/db/backend.py | 1 + src/calibre/gui2/__init__.py | 1 + src/calibre/gui2/init.py | 93 +++++++++++++++++++- src/calibre/gui2/search_restriction_mixin.py | 14 ++- src/calibre/gui2/ui.py | 2 + src/calibre/library/database2.py | 1 + 6 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index f977bbff49..9a13f4fd21 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -423,6 +423,7 @@ class DB(object): ] defs['virtual_libraries'] = {} defs['virtual_lib_on_startup'] = defs['cs_virtual_lib_on_startup'] = '' + defs['virt_libs_hidden'] = defs['virt_libs_order'] = () # Migrate the bool tristate tweak defs['bools_are_tristate'] = \ diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index fc1364c20e..5b8d2142a4 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -118,6 +118,7 @@ defs['cover_grid_color'] = (80, 80, 80) defs['cover_grid_cache_size'] = 100 defs['cover_grid_disk_cache_size'] = 2500 defs['cover_grid_show_title'] = False +defs['show_vl_tabs'] = False del defs # }}} diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 14910d749a..7feb4c730d 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -8,9 +8,10 @@ __docformat__ = 'restructuredtext en' import functools from PyQt4.Qt import (Qt, QApplication, QStackedWidget, QMenu, QTimer, - QSize, QSizePolicy, QStatusBar, QLabel, QFont, QAction) + QSize, QSizePolicy, QStatusBar, QLabel, QFont, QAction, QTabBar) from calibre.utils.config import prefs, tweaks +from calibre.utils.icu import sort_key from calibre.constants import (isosx, __appname__, preferred_encoding, get_version) from calibre.gui2 import config, is_widescreen, gprefs @@ -278,9 +279,99 @@ class GridViewButton(LayoutButton): # {{{ # }}} +class VLTabs(QTabBar): # {{{ + + def __init__(self, parent): + QTabBar.__init__(self, parent) + self.setDocumentMode(True) + self.setDrawBase(False) + self.setMovable(True) + self.setTabsClosable(True) + self.gui = parent + self.currentChanged.connect(self.tab_changed) + self.tabMoved.connect(self.tab_moved, type=Qt.QueuedConnection) + self.tabCloseRequested.connect(self.tab_close) + self.setStyleSheet('QTabBar::tab:selected { font-weight: bold } QTabBar::tab { text-align: center }') + self.setVisible(gprefs['show_vl_tabs']) + + def enable_bar(self): + gprefs['show_vl_tabs'] = True + self.setVisible(True) + + def disable_bar(self): + gprefs['show_vl_tabs'] = False + self.setVisible(False) + + def tab_changed(self, idx): + vl = unicode(self.tabData(idx).toString()).strip() or None + self.gui.apply_virtual_library(vl) + + def tab_moved(self, from_, to): + self.current_db.prefs['virt_libs_order'] = [unicode(self.tabData(i).toString()) for i in range(self.count())] + + def tab_close(self, index): + vl = unicode(self.tabData(index).toString()) + if vl: # Dont allow closing the All Books tab + self.current_db.prefs['virt_libs_hidden'] = list( + self.current_db.prefs['virt_libs_hidden']) + [vl] + self.removeTab(index) + + @property + def current_db(self): + return self.gui.current_db + + def rebuild(self): + self.currentChanged.disconnect(self.tab_changed) + db = self.current_db + virt_libs = frozenset(db.prefs.get('virtual_libraries', {})) + hidden = frozenset(db.prefs['virt_libs_hidden']) + if hidden - virt_libs: + db.prefs['virt_libs_hidden'] = list(hidden.intersection(virt_libs)) + order = db.prefs['virt_libs_order'] + while self.count(): + self.removeTab(0) + current_lib = db.data.get_base_restriction_name() + current_idx = all_idx = None + virt_libs = (set(virt_libs) - hidden) | {''} + order = {x:i for i, x in enumerate(order)} + for i, vl in enumerate(sorted(virt_libs, key=lambda x:(order.get(x, 0), sort_key(x)))): + self.addTab(vl or _('All books')) + self.setTabData(i, vl) + if vl == current_lib: + current_idx = i + if not vl: + all_idx = i + self.setCurrentIndex(all_idx if current_idx is None else current_idx) + self.currentChanged.connect(self.tab_changed) + self.tabButton(all_idx, self.RightSide).setVisible(False) + + def contextMenuEvent(self, ev): + m = QMenu(self) + m.addAction(_('Sort alphabetically'), self.sort_alphabetically) + hidden = self.current_db.prefs['virt_libs_hidden'] + if hidden: + s = m._s = m.addMenu(_('Restore hidden tabs')) + for x in hidden: + s.addAction(x, partial(self.restore, x)) + m.addAction(_('Hide virtual library tabs'), self.disable_bar) + m.exec_(ev.globalPos()) + + def sort_alphabetically(self): + self.current_db.prefs['virt_libs_order'] = () + self.rebuild() + + def restore(self, x): + h = self.current_db.prefs['virt_libs_hidden'] + self.current_db.prefs['virt_libs_hidden'] = list(set(h) - {x}) + self.rebuild() + +# }}} + class LayoutMixin(object): # {{{ def __init__(self): + self.vl_tabs = VLTabs(self) + self.centralwidget.layout().addWidget(self.vl_tabs) if config['gui_layout'] == 'narrow': # narrow {{{ self.book_details = BookDetails(False, self) diff --git a/src/calibre/gui2/search_restriction_mixin.py b/src/calibre/gui2/search_restriction_mixin.py index e8f7cd51ae..2d8ff8474c 100644 --- a/src/calibre/gui2/search_restriction_mixin.py +++ b/src/calibre/gui2/search_restriction_mixin.py @@ -12,7 +12,7 @@ from PyQt4.Qt import ( Qt, QMenu, QPoint, QIcon, QDialog, QGridLayout, QLabel, QLineEdit, QComboBox, QDialogButtonBox, QSize, QVBoxLayout, QListWidget, QStringList, QRadioButton) -from calibre.gui2 import error_dialog, question_dialog +from calibre.gui2 import error_dialog, question_dialog, gprefs from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.widgets import ComboBoxWithHelp from calibre.utils.config_base import tweaks @@ -334,6 +334,7 @@ class SearchRestrictionMixin(object): virt_libs = db.prefs.get('virtual_libraries', {}) virt_libs[name] = search db.prefs.set('virtual_libraries', virt_libs) + self.rebuild_vl_tabs() def do_create_edit(self, name=None): db = self.library_view.model().db @@ -361,6 +362,11 @@ class SearchRestrictionMixin(object): self.build_virtual_library_list(a, self.remove_vl_triggered) m.addMenu(a) + if gprefs['show_vl_tabs']: + m.addAction(_('Hide virtual library tabs'), self.vl_tabs.disable_bar) + else: + m.addAction(_('Show virtual libraries as tabs'), self.vl_tabs.enable_bar) + m.addSeparator() db = self.library_view.model().db @@ -402,6 +408,9 @@ class SearchRestrictionMixin(object): p = QPoint(0, self.virtual_library.height()) self.virtual_library_menu.popup(self.virtual_library.mapToGlobal(p)) + def rebuild_vl_tabs(self): + self.vl_tabs.rebuild() + def apply_virtual_library(self, library=None): db = self.library_view.model().db virt_libs = db.prefs.get('virtual_libraries', {}) @@ -471,6 +480,7 @@ class SearchRestrictionMixin(object): db.prefs.set('virtual_libraries', virt_libs) if reapply and db.data.get_base_restriction_name() == name: self.apply_virtual_library('') + self.rebuild_vl_tabs() def _trim_restriction_name(self, name): return name[0:MAX_VIRTUAL_LIBRARY_NAME_LENGTH].strip() @@ -591,11 +601,13 @@ class SearchRestrictionMixin(object): 'QLabel { border-radius: 6px; background-color: %s }' % tweaks['highlight_virtual_library']) self.clear_vl.setVisible(True) + self.search_count.setVisible(not gprefs['show_vl_tabs']) else: # No restriction or not library view t = '' self.search_count.setStyleSheet( 'QLabel { background-color: transparent; }') self.clear_vl.setVisible(False) + self.search_count.setVisible(False) self.search_count.setText(t) if __name__ == '__main__': diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 196de7cb6a..4cec88cf05 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -341,6 +341,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ ######################### Search Restriction ########################## if db.prefs['virtual_lib_on_startup']: self.apply_virtual_library(db.prefs['virtual_lib_on_startup']) + self.rebuild_vl_tabs() ########################### Cover Flow ################################ @@ -627,6 +628,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.saved_searches_changed(recount=False) # reload the search restrictions combo box if db.prefs['virtual_lib_on_startup']: self.apply_virtual_library(db.prefs['virtual_lib_on_startup']) + self.rebuild_vl_tabs() for action in self.iactions.values(): action.library_changed(db) if olddb is not None: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 847b223b4d..248f958288 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -235,6 +235,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): ] defs['virtual_libraries'] = {} defs['virtual_lib_on_startup'] = defs['cs_virtual_lib_on_startup'] = '' + defs['virt_libs_hidden'] = defs['virt_libs_order'] = () # Migrate the bool tristate tweak defs['bools_are_tristate'] = \