mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
a6adfd12e7
commit
14b40e1559
@ -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'] = \
|
||||||
|
@ -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
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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__':
|
||||||
|
@ -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:
|
||||||
|
@ -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'] = \
|
||||||
|
Loading…
x
Reference in New Issue
Block a user