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'] = \