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

View File

@ -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
# }}}

View File

@ -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)

View File

@ -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__':

View File

@ -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:

View File

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