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.
This commit is contained in:
Kovid Goyal 2013-08-14 18:42:05 +05:30
parent a6adfd12e7
commit 14b40e1559
6 changed files with 110 additions and 2 deletions

View File

@ -423,6 +423,7 @@ class DB(object):
] ]
defs['virtual_libraries'] = {} defs['virtual_libraries'] = {}
defs['virtual_lib_on_startup'] = defs['cs_virtual_lib_on_startup'] = '' defs['virtual_lib_on_startup'] = defs['cs_virtual_lib_on_startup'] = ''
defs['virt_libs_hidden'] = defs['virt_libs_order'] = ()
# Migrate the bool tristate tweak # Migrate the bool tristate tweak
defs['bools_are_tristate'] = \ defs['bools_are_tristate'] = \

View File

@ -118,6 +118,7 @@ defs['cover_grid_color'] = (80, 80, 80)
defs['cover_grid_cache_size'] = 100 defs['cover_grid_cache_size'] = 100
defs['cover_grid_disk_cache_size'] = 2500 defs['cover_grid_disk_cache_size'] = 2500
defs['cover_grid_show_title'] = False defs['cover_grid_show_title'] = False
defs['show_vl_tabs'] = False
del defs del defs
# }}} # }}}

View File

@ -8,9 +8,10 @@ __docformat__ = 'restructuredtext en'
import functools import functools
from PyQt4.Qt import (Qt, QApplication, QStackedWidget, QMenu, QTimer, 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.config import prefs, tweaks
from calibre.utils.icu import sort_key
from calibre.constants import (isosx, __appname__, preferred_encoding, from calibre.constants import (isosx, __appname__, preferred_encoding,
get_version) get_version)
from calibre.gui2 import config, is_widescreen, gprefs 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): # {{{ class LayoutMixin(object): # {{{
def __init__(self): def __init__(self):
self.vl_tabs = VLTabs(self)
self.centralwidget.layout().addWidget(self.vl_tabs)
if config['gui_layout'] == 'narrow': # narrow {{{ if config['gui_layout'] == 'narrow': # narrow {{{
self.book_details = BookDetails(False, self) self.book_details = BookDetails(False, self)

View File

@ -12,7 +12,7 @@ from PyQt4.Qt import (
Qt, QMenu, QPoint, QIcon, QDialog, QGridLayout, QLabel, QLineEdit, QComboBox, Qt, QMenu, QPoint, QIcon, QDialog, QGridLayout, QLabel, QLineEdit, QComboBox,
QDialogButtonBox, QSize, QVBoxLayout, QListWidget, QStringList, QRadioButton) 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.dialogs.confirm_delete import confirm
from calibre.gui2.widgets import ComboBoxWithHelp from calibre.gui2.widgets import ComboBoxWithHelp
from calibre.utils.config_base import tweaks from calibre.utils.config_base import tweaks
@ -334,6 +334,7 @@ class SearchRestrictionMixin(object):
virt_libs = db.prefs.get('virtual_libraries', {}) virt_libs = db.prefs.get('virtual_libraries', {})
virt_libs[name] = search virt_libs[name] = search
db.prefs.set('virtual_libraries', virt_libs) db.prefs.set('virtual_libraries', virt_libs)
self.rebuild_vl_tabs()
def do_create_edit(self, name=None): def do_create_edit(self, name=None):
db = self.library_view.model().db db = self.library_view.model().db
@ -361,6 +362,11 @@ class SearchRestrictionMixin(object):
self.build_virtual_library_list(a, self.remove_vl_triggered) self.build_virtual_library_list(a, self.remove_vl_triggered)
m.addMenu(a) 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() m.addSeparator()
db = self.library_view.model().db db = self.library_view.model().db
@ -402,6 +408,9 @@ class SearchRestrictionMixin(object):
p = QPoint(0, self.virtual_library.height()) p = QPoint(0, self.virtual_library.height())
self.virtual_library_menu.popup(self.virtual_library.mapToGlobal(p)) 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): def apply_virtual_library(self, library=None):
db = self.library_view.model().db db = self.library_view.model().db
virt_libs = db.prefs.get('virtual_libraries', {}) virt_libs = db.prefs.get('virtual_libraries', {})
@ -471,6 +480,7 @@ class SearchRestrictionMixin(object):
db.prefs.set('virtual_libraries', virt_libs) db.prefs.set('virtual_libraries', virt_libs)
if reapply and db.data.get_base_restriction_name() == name: if reapply and db.data.get_base_restriction_name() == name:
self.apply_virtual_library('') self.apply_virtual_library('')
self.rebuild_vl_tabs()
def _trim_restriction_name(self, name): def _trim_restriction_name(self, name):
return name[0:MAX_VIRTUAL_LIBRARY_NAME_LENGTH].strip() return name[0:MAX_VIRTUAL_LIBRARY_NAME_LENGTH].strip()
@ -591,11 +601,13 @@ class SearchRestrictionMixin(object):
'QLabel { border-radius: 6px; background-color: %s }' % 'QLabel { border-radius: 6px; background-color: %s }' %
tweaks['highlight_virtual_library']) tweaks['highlight_virtual_library'])
self.clear_vl.setVisible(True) self.clear_vl.setVisible(True)
self.search_count.setVisible(not gprefs['show_vl_tabs'])
else: # No restriction or not library view else: # No restriction or not library view
t = '' t = ''
self.search_count.setStyleSheet( self.search_count.setStyleSheet(
'QLabel { background-color: transparent; }') 'QLabel { background-color: transparent; }')
self.clear_vl.setVisible(False) self.clear_vl.setVisible(False)
self.search_count.setVisible(False)
self.search_count.setText(t) self.search_count.setText(t)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -341,6 +341,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
######################### Search Restriction ########################## ######################### Search Restriction ##########################
if db.prefs['virtual_lib_on_startup']: if db.prefs['virtual_lib_on_startup']:
self.apply_virtual_library(db.prefs['virtual_lib_on_startup']) self.apply_virtual_library(db.prefs['virtual_lib_on_startup'])
self.rebuild_vl_tabs()
########################### Cover Flow ################################ ########################### Cover Flow ################################
@ -627,6 +628,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.saved_searches_changed(recount=False) # reload the search restrictions combo box self.saved_searches_changed(recount=False) # reload the search restrictions combo box
if db.prefs['virtual_lib_on_startup']: if db.prefs['virtual_lib_on_startup']:
self.apply_virtual_library(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(): for action in self.iactions.values():
action.library_changed(db) action.library_changed(db)
if olddb is not None: if olddb is not None:

View File

@ -235,6 +235,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
] ]
defs['virtual_libraries'] = {} defs['virtual_libraries'] = {}
defs['virtual_lib_on_startup'] = defs['cs_virtual_lib_on_startup'] = '' defs['virtual_lib_on_startup'] = defs['cs_virtual_lib_on_startup'] = ''
defs['virt_libs_hidden'] = defs['virt_libs_order'] = ()
# Migrate the bool tristate tweak # Migrate the bool tristate tweak
defs['bools_are_tristate'] = \ defs['bools_are_tristate'] = \