mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Virtual Libraries
This commit is contained in:
commit
a8d9d760ff
@ -422,6 +422,8 @@ class DB(object):
|
|||||||
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
|
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
|
||||||
('last_modified', False), ('size', False), ('languages', False),
|
('last_modified', False), ('size', False), ('languages', False),
|
||||||
]
|
]
|
||||||
|
defs['virtual_libraries'] = {}
|
||||||
|
defs['virtual_lib_on_startup'] = defs['cs_virtual_lib_on_startup'] = ''
|
||||||
|
|
||||||
# Migrate the bool tristate tweak
|
# Migrate the bool tristate tweak
|
||||||
defs['bools_are_tristate'] = \
|
defs['bools_are_tristate'] = \
|
||||||
@ -470,6 +472,24 @@ class DB(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# migrate the gui_restriction preference to a virtual library
|
||||||
|
gr_pref = self.prefs.get('gui_restriction', None)
|
||||||
|
if gr_pref:
|
||||||
|
virt_libs = self.prefs.get('virtual_libraries', {})
|
||||||
|
virt_libs[gr_pref] = 'search:"' + gr_pref + '"'
|
||||||
|
self.prefs['virtual_libraries'] = virt_libs
|
||||||
|
self.prefs['gui_restriction'] = ''
|
||||||
|
self.prefs['virtual_lib_on_startup'] = gr_pref
|
||||||
|
|
||||||
|
# migrate the cs_restriction preference to a virtual library
|
||||||
|
gr_pref = self.prefs.get('cs_restriction', None)
|
||||||
|
if gr_pref:
|
||||||
|
virt_libs = self.prefs.get('virtual_libraries', {})
|
||||||
|
virt_libs[gr_pref] = 'search:"' + gr_pref + '"'
|
||||||
|
self.prefs['virtual_libraries'] = virt_libs
|
||||||
|
self.prefs['cs_restriction'] = ''
|
||||||
|
self.prefs['cs_virtual_lib_on_startup'] = gr_pref
|
||||||
|
|
||||||
# Rename any user categories with names that differ only in case
|
# Rename any user categories with names that differ only in case
|
||||||
user_cats = self.prefs.get('user_categories', [])
|
user_cats = self.prefs.get('user_categories', [])
|
||||||
catmap = {}
|
catmap = {}
|
||||||
|
@ -49,7 +49,8 @@ class View(object):
|
|||||||
self.cache = cache
|
self.cache = cache
|
||||||
self.marked_ids = {}
|
self.marked_ids = {}
|
||||||
self.search_restriction_book_count = 0
|
self.search_restriction_book_count = 0
|
||||||
self.search_restriction = ''
|
self.search_restriction = self.base_restriction = ''
|
||||||
|
self.search_restriction_name = self.base_restriction_name = ''
|
||||||
self._field_getters = {}
|
self._field_getters = {}
|
||||||
for col, idx in cache.backend.FIELD_MAP.iteritems():
|
for col, idx in cache.backend.FIELD_MAP.iteritems():
|
||||||
if isinstance(col, int):
|
if isinstance(col, int):
|
||||||
@ -168,8 +169,19 @@ class View(object):
|
|||||||
return ans
|
return ans
|
||||||
self._map_filtered = tuple(ans)
|
self._map_filtered = tuple(ans)
|
||||||
|
|
||||||
|
def _build_restriction_string(self, restriction):
|
||||||
|
if self.base_restriction:
|
||||||
|
if restriction:
|
||||||
|
return u'(%s) and (%s)' % (self.base_restriction, restriction)
|
||||||
|
else:
|
||||||
|
return self.base_restriction
|
||||||
|
else:
|
||||||
|
return restriction
|
||||||
|
|
||||||
def search_getting_ids(self, query, search_restriction,
|
def search_getting_ids(self, query, search_restriction,
|
||||||
set_restriction_count=False):
|
set_restriction_count=False, use_virtual_library=True):
|
||||||
|
if use_virtual_library:
|
||||||
|
search_restriction = self._build_restriction_string(search_restriction)
|
||||||
q = ''
|
q = ''
|
||||||
if not query or not query.strip():
|
if not query or not query.strip():
|
||||||
q = search_restriction
|
q = search_restriction
|
||||||
@ -188,11 +200,32 @@ class View(object):
|
|||||||
self.search_restriction_book_count = len(rv)
|
self.search_restriction_book_count = len(rv)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
def get_search_restriction(self):
|
||||||
|
return self.search_restriction
|
||||||
|
|
||||||
def set_search_restriction(self, s):
|
def set_search_restriction(self, s):
|
||||||
self.search_restriction = s
|
self.search_restriction = s
|
||||||
|
|
||||||
|
def get_base_restriction(self):
|
||||||
|
return self.base_restriction
|
||||||
|
|
||||||
|
def set_base_restriction(self, s):
|
||||||
|
self.base_restriction = s
|
||||||
|
|
||||||
|
def get_base_restriction_name(self):
|
||||||
|
return self.base_restriction_name
|
||||||
|
|
||||||
|
def set_base_restriction_name(self, s):
|
||||||
|
self.base_restriction_name = s
|
||||||
|
|
||||||
|
def get_search_restriction_name(self):
|
||||||
|
return self.search_restriction_name
|
||||||
|
|
||||||
|
def set_search_restriction_name(self, s):
|
||||||
|
self.search_restriction_name = s
|
||||||
|
|
||||||
def search_restriction_applied(self):
|
def search_restriction_applied(self):
|
||||||
return bool(self.search_restriction)
|
return bool(self.search_restriction) or bool(self.base_restriction)
|
||||||
|
|
||||||
def get_search_restriction_book_count(self):
|
def get_search_restriction_book_count(self):
|
||||||
return self.search_restriction_book_count
|
return self.search_restriction_book_count
|
||||||
|
@ -16,7 +16,6 @@ from calibre.constants import __appname__
|
|||||||
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
||||||
from calibre.gui2.throbber import ThrobbingButton
|
from calibre.gui2.throbber import ThrobbingButton
|
||||||
from calibre.gui2.bars import BarsManager
|
from calibre.gui2.bars import BarsManager
|
||||||
from calibre.gui2.widgets import ComboBoxWithHelp
|
|
||||||
from calibre.utils.config_base import tweaks
|
from calibre.utils.config_base import tweaks
|
||||||
from calibre import human_readable
|
from calibre import human_readable
|
||||||
|
|
||||||
@ -173,11 +172,13 @@ class SearchBar(QWidget): # {{{
|
|||||||
self.setLayout(self._layout)
|
self.setLayout(self._layout)
|
||||||
self._layout.setContentsMargins(0,5,0,0)
|
self._layout.setContentsMargins(0,5,0,0)
|
||||||
|
|
||||||
x = ComboBoxWithHelp(self)
|
x = QToolButton(self)
|
||||||
x.setMaximumSize(QSize(150, 16777215))
|
x.setText(_('Virtual Library'))
|
||||||
x.setObjectName("search_restriction")
|
x.setIcon(QIcon(I('lt.png')))
|
||||||
|
x.setObjectName("virtual_library")
|
||||||
|
x.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
|
||||||
l.addWidget(x)
|
l.addWidget(x)
|
||||||
parent.search_restriction = x
|
parent.virtual_library = x
|
||||||
|
|
||||||
x = QLabel(self)
|
x = QLabel(self)
|
||||||
x.setObjectName("search_count")
|
x.setObjectName("search_count")
|
||||||
|
@ -14,7 +14,6 @@ from calibre.gui2.preferences.behavior_ui import Ui_Form
|
|||||||
from calibre.gui2 import config, info_dialog, dynamic, gprefs
|
from calibre.gui2 import config, info_dialog, dynamic, gprefs
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
from calibre.customize.ui import available_output_formats, all_input_formats
|
from calibre.customize.ui import available_output_formats, all_input_formats
|
||||||
from calibre.utils.search_query_parser import saved_searches
|
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
from calibre.ebooks.oeb.iterator import is_supported
|
from calibre.ebooks.oeb.iterator import is_supported
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
@ -48,9 +47,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
choices = [(x.upper(), x) for x in output_formats]
|
choices = [(x.upper(), x) for x in output_formats]
|
||||||
r('output_format', prefs, choices=choices, setting=OutputFormatSetting)
|
r('output_format', prefs, choices=choices, setting=OutputFormatSetting)
|
||||||
|
|
||||||
restrictions = sorted(saved_searches().names(), key=sort_key)
|
restrictions = sorted(db.prefs['virtual_libraries'].iterkeys(), key=sort_key)
|
||||||
choices = [('', '')] + [(x, x) for x in restrictions]
|
choices = [('', '')] + [(x, x) for x in restrictions]
|
||||||
r('gui_restriction', db.prefs, choices=choices)
|
# check that the virtual library still exists
|
||||||
|
vls = db.prefs['virtual_lib_on_startup']
|
||||||
|
if vls and vls not in restrictions:
|
||||||
|
db.prefs['virtual_lib_on_startup'] = ''
|
||||||
|
r('virtual_lib_on_startup', db.prefs, choices=choices)
|
||||||
self.reset_confirmation_button.clicked.connect(self.reset_confirmation_dialogs)
|
self.reset_confirmation_button.clicked.connect(self.reset_confirmation_dialogs)
|
||||||
|
|
||||||
self.input_up_button.clicked.connect(self.up_input)
|
self.input_up_button.clicked.connect(self.up_input)
|
||||||
|
@ -147,15 +147,15 @@ If not checked, the values can be Yes or No.</string>
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_170">
|
<widget class="QLabel" name="label_170">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Restriction to apply when the current library is opened:</string>
|
<string>Virtual library to apply when the current library is opened:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>opt_gui_restriction</cstring>
|
<cstring>opt_virtual_lib_on_startup</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QComboBox" name="opt_gui_restriction">
|
<widget class="QComboBox" name="opt_virtual_lib_on_startup">
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>250</width>
|
<width>250</width>
|
||||||
@ -163,7 +163,7 @@ If not checked, the values can be Yes or No.</string>
|
|||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Apply this restriction on calibre startup if the current library is being used. Also applied when switching to this library. Note that this setting is per library. </string>
|
<string>Use this virtual library on calibre startup if the current library is being used. Also applied when switching to this library. Note that this setting is per library. </string>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeAdjustPolicy">
|
<property name="sizeAdjustPolicy">
|
||||||
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
|
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
|
||||||
|
@ -12,7 +12,6 @@ from PyQt4.Qt import Qt, QUrl, QDialog, QSize, QVBoxLayout, QLabel, \
|
|||||||
|
|
||||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
||||||
from calibre.gui2.preferences.server_ui import Ui_Form
|
from calibre.gui2.preferences.server_ui import Ui_Form
|
||||||
from calibre.utils.search_query_parser import saved_searches
|
|
||||||
from calibre.library.server import server_config
|
from calibre.library.server import server_config
|
||||||
from calibre.utils.config import ConfigProxy
|
from calibre.utils.config import ConfigProxy
|
||||||
from calibre.gui2 import error_dialog, config, open_url, warning_dialog, \
|
from calibre.gui2 import error_dialog, config, open_url, warning_dialog, \
|
||||||
@ -44,13 +43,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
else self.opt_password.Password))
|
else self.opt_password.Password))
|
||||||
self.opt_password.setEchoMode(self.opt_password.Password)
|
self.opt_password.setEchoMode(self.opt_password.Password)
|
||||||
|
|
||||||
restrictions = sorted(saved_searches().names(), key=sort_key)
|
restrictions = sorted(db.prefs['virtual_libraries'].iterkeys(), key=sort_key)
|
||||||
# verify that the current restriction still exists. If not, clear it.
|
|
||||||
csr = db.prefs.get('cs_restriction', None)
|
|
||||||
if csr and csr not in restrictions:
|
|
||||||
db.prefs.set('cs_restriction', '')
|
|
||||||
choices = [('', '')] + [(x, x) for x in restrictions]
|
choices = [('', '')] + [(x, x) for x in restrictions]
|
||||||
r('cs_restriction', db.prefs, choices=choices)
|
# check that the virtual library still exists
|
||||||
|
vls = db.prefs['cs_virtual_lib_on_startup']
|
||||||
|
if vls and vls not in restrictions:
|
||||||
|
db.prefs['cs_virtual_lib_on_startup'] = ''
|
||||||
|
r('cs_virtual_lib_on_startup', db.prefs, choices=choices)
|
||||||
|
|
||||||
self.start_button.setEnabled(not getattr(self.server, 'is_running', False))
|
self.start_button.setEnabled(not getattr(self.server, 'is_running', False))
|
||||||
self.test_button.setEnabled(not self.start_button.isEnabled())
|
self.test_button.setEnabled(not self.start_button.isEnabled())
|
||||||
|
@ -139,14 +139,14 @@
|
|||||||
<item row="7" column="0">
|
<item row="7" column="0">
|
||||||
<widget class="QLabel" name="label_164">
|
<widget class="QLabel" name="label_164">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Restriction (saved search) to apply:</string>
|
<string>Virtual library to apply:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="1" colspan="2">
|
<item row="7" column="1" colspan="2">
|
||||||
<widget class="QComboBox" name="opt_cs_restriction">
|
<widget class="QComboBox" name="opt_cs_virtual_lib_on_startup">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>This restriction (based on a saved search) will restrict the books the content server makes available to those matching the search. This setting is per library (i.e. you can have a different restriction per library).</string>
|
<string>Setting a virtual library will restrict the books the content server makes available to those in the library. This setting is per library (i.e. you can have a different value per library).</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeAdjustPolicy">
|
<property name="sizeAdjustPolicy">
|
||||||
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
|
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
|
||||||
|
@ -19,7 +19,6 @@ from calibre.gui2.dialogs.confirm_delete import confirm
|
|||||||
from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor
|
from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor
|
||||||
from calibre.gui2.dialogs.search import SearchDialog
|
from calibre.gui2.dialogs.search import SearchDialog
|
||||||
from calibre.utils.search_query_parser import saved_searches
|
from calibre.utils.search_query_parser import saved_searches
|
||||||
from calibre.utils.icu import sort_key
|
|
||||||
|
|
||||||
class SearchLineEdit(QLineEdit): # {{{
|
class SearchLineEdit(QLineEdit): # {{{
|
||||||
key_pressed = pyqtSignal(object)
|
key_pressed = pyqtSignal(object)
|
||||||
@ -332,6 +331,10 @@ class SavedSearchBox(QComboBox): # {{{
|
|||||||
name = unicode(self.currentText())
|
name = unicode(self.currentText())
|
||||||
if not name.strip():
|
if not name.strip():
|
||||||
name = unicode(self.search_box.text()).replace('"', '')
|
name = unicode(self.search_box.text()).replace('"', '')
|
||||||
|
if not (name and self.search_box.text()):
|
||||||
|
error_dialog(self, _('Create saved search'),
|
||||||
|
_('There is no search to save'), show=True)
|
||||||
|
return
|
||||||
saved_searches().delete(name)
|
saved_searches().delete(name)
|
||||||
saved_searches().add(name, unicode(self.search_box.text()))
|
saved_searches().add(name, unicode(self.search_box.text()))
|
||||||
# now go through an initialization cycle to ensure that the combobox has
|
# now go through an initialization cycle to ensure that the combobox has
|
||||||
@ -339,7 +342,7 @@ class SavedSearchBox(QComboBox): # {{{
|
|||||||
# references the new search instead of the text in the search.
|
# references the new search instead of the text in the search.
|
||||||
self.clear()
|
self.clear()
|
||||||
self.setCurrentIndex(self.findText(name))
|
self.setCurrentIndex(self.findText(name))
|
||||||
self.saved_search_selected (name)
|
self.saved_search_selected(name)
|
||||||
self.changed.emit()
|
self.changed.emit()
|
||||||
|
|
||||||
def delete_current_search(self):
|
def delete_current_search(self):
|
||||||
@ -361,8 +364,8 @@ class SavedSearchBox(QComboBox): # {{{
|
|||||||
self.changed.emit()
|
self.changed.emit()
|
||||||
|
|
||||||
# SIGNALed from the main UI
|
# SIGNALed from the main UI
|
||||||
def copy_search_button_clicked (self):
|
def copy_search_button_clicked(self):
|
||||||
idx = self.currentIndex();
|
idx = self.currentIndex()
|
||||||
if idx < 0:
|
if idx < 0:
|
||||||
return
|
return
|
||||||
self.search_box.set_search_string(saved_searches().lookup(unicode(self.currentText())))
|
self.search_box.set_search_string(saved_searches().lookup(unicode(self.currentText())))
|
||||||
@ -452,7 +455,7 @@ class SavedSearchBoxMixin(object): # {{{
|
|||||||
self.saved_search.save_search_button_clicked)
|
self.saved_search.save_search_button_clicked)
|
||||||
self.copy_search_button.clicked.connect(
|
self.copy_search_button.clicked.connect(
|
||||||
self.saved_search.copy_search_button_clicked)
|
self.saved_search.copy_search_button_clicked)
|
||||||
self.saved_searches_changed()
|
# self.saved_searches_changed()
|
||||||
self.saved_search.initialize(self.search, colorize=True,
|
self.saved_search.initialize(self.search, colorize=True,
|
||||||
help_text=_('Saved Searches'))
|
help_text=_('Saved Searches'))
|
||||||
self.saved_search.setToolTip(
|
self.saved_search.setToolTip(
|
||||||
@ -479,17 +482,9 @@ class SavedSearchBoxMixin(object): # {{{
|
|||||||
partial(self.do_saved_search_edit, None))
|
partial(self.do_saved_search_edit, None))
|
||||||
|
|
||||||
def saved_searches_changed(self, set_restriction=None, recount=True):
|
def saved_searches_changed(self, set_restriction=None, recount=True):
|
||||||
p = sorted(saved_searches().names(), key=sort_key)
|
self.build_search_restriction_list()
|
||||||
if set_restriction is None:
|
|
||||||
set_restriction = unicode(self.search_restriction.currentText())
|
|
||||||
# rebuild the restrictions combobox using current saved searches
|
|
||||||
self.search_restriction.clear()
|
|
||||||
self.search_restriction.addItem('')
|
|
||||||
self.search_restriction.addItem(_('*Current search'))
|
|
||||||
if recount:
|
if recount:
|
||||||
self.tags_view.recount()
|
self.tags_view.recount()
|
||||||
for s in p:
|
|
||||||
self.search_restriction.addItem(s)
|
|
||||||
if set_restriction: # redo the search restriction if there was one
|
if set_restriction: # redo the search restriction if there was one
|
||||||
self.apply_named_search_restriction(set_restriction)
|
self.apply_named_search_restriction(set_restriction)
|
||||||
|
|
||||||
|
@ -4,23 +4,422 @@ Created on 10 Jun 2010
|
|||||||
@author: charles
|
@author: charles
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from PyQt4.Qt import Qt
|
from functools import partial
|
||||||
|
|
||||||
|
from PyQt4.Qt import (
|
||||||
|
Qt, QMenu, QPoint, QIcon, QDialog, QGridLayout, QLabel, QLineEdit,
|
||||||
|
QDialogButtonBox, QSize, QVBoxLayout, QListWidget, QStringList, QCheckBox)
|
||||||
|
|
||||||
|
from calibre.gui2 import error_dialog, question_dialog
|
||||||
|
from calibre.gui2.widgets import ComboBoxWithHelp
|
||||||
|
from calibre.utils.icu import sort_key
|
||||||
|
from calibre.utils.pyparsing import ParseException
|
||||||
|
from calibre.utils.search_query_parser import saved_searches
|
||||||
|
|
||||||
|
class SelectNames(QDialog): # {{{
|
||||||
|
|
||||||
|
def __init__(self, names, txt, parent=None):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.l = l = QVBoxLayout(self)
|
||||||
|
self.setLayout(l)
|
||||||
|
|
||||||
|
self.la = la = QLabel(_('Create a Virtual Library based on %s') % txt)
|
||||||
|
l.addWidget(la)
|
||||||
|
|
||||||
|
self._names = QListWidget(self)
|
||||||
|
self._names.addItems(QStringList(sorted(names, key=sort_key)))
|
||||||
|
self._names.setSelectionMode(self._names.ExtendedSelection)
|
||||||
|
l.addWidget(self._names)
|
||||||
|
|
||||||
|
self._and = QCheckBox(_('Match all selected %s names')%txt)
|
||||||
|
l.addWidget(self._and)
|
||||||
|
|
||||||
|
self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
|
self.bb.accepted.connect(self.accept)
|
||||||
|
self.bb.rejected.connect(self.reject)
|
||||||
|
l.addWidget(self.bb)
|
||||||
|
|
||||||
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def names(self):
|
||||||
|
for item in self._names.selectedItems():
|
||||||
|
yield unicode(item.data(Qt.DisplayRole).toString())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def match_type(self):
|
||||||
|
return ' and ' if self._and.isChecked() else ' or '
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
MAX_VIRTUAL_LIBRARY_NAME_LENGTH = 40
|
||||||
|
|
||||||
|
def _build_full_search_string(gui):
|
||||||
|
search_templates = (
|
||||||
|
'',
|
||||||
|
'{cl}',
|
||||||
|
'{cr}',
|
||||||
|
'(({cl}) and ({cr}))',
|
||||||
|
'{sb}',
|
||||||
|
'(({cl}) and ({sb}))',
|
||||||
|
'(({cr}) and ({sb}))',
|
||||||
|
'(({cl}) and ({cr}) and ({sb}))'
|
||||||
|
)
|
||||||
|
|
||||||
|
sb = gui.search.current_text
|
||||||
|
db = gui.current_db
|
||||||
|
cr = db.data.get_search_restriction()
|
||||||
|
cl = db.data.get_base_restriction()
|
||||||
|
dex = 0
|
||||||
|
if sb:
|
||||||
|
dex += 4
|
||||||
|
if cr:
|
||||||
|
dex += 2
|
||||||
|
if cl:
|
||||||
|
dex += 1
|
||||||
|
template = search_templates[dex]
|
||||||
|
return template.format(cl=cl, cr=cr, sb=sb).strip()
|
||||||
|
|
||||||
|
class CreateVirtualLibrary(QDialog): # {{{
|
||||||
|
|
||||||
|
def __init__(self, gui, existing_names, editing=None):
|
||||||
|
QDialog.__init__(self, gui)
|
||||||
|
|
||||||
|
self.gui = gui
|
||||||
|
self.existing_names = existing_names
|
||||||
|
|
||||||
|
if editing:
|
||||||
|
self.setWindowTitle(_('Edit virtual library'))
|
||||||
|
else:
|
||||||
|
self.setWindowTitle(_('Create virtual library'))
|
||||||
|
self.setWindowIcon(QIcon(I('lt.png')))
|
||||||
|
|
||||||
|
gl = QGridLayout()
|
||||||
|
self.setLayout(gl)
|
||||||
|
self.la1 = la1 = QLabel(_('Virtual library &name:'))
|
||||||
|
gl.addWidget(la1, 0, 0)
|
||||||
|
self.vl_name = QLineEdit()
|
||||||
|
self.vl_name.setMaxLength(MAX_VIRTUAL_LIBRARY_NAME_LENGTH)
|
||||||
|
la1.setBuddy(self.vl_name)
|
||||||
|
gl.addWidget(self.vl_name, 0, 1)
|
||||||
|
self.editing = editing
|
||||||
|
if editing:
|
||||||
|
self.vl_name.setText(editing)
|
||||||
|
|
||||||
|
self.la2 = la2 = QLabel(_('&Search expression:'))
|
||||||
|
gl.addWidget(la2, 1, 0)
|
||||||
|
self.vl_text = QLineEdit()
|
||||||
|
la2.setBuddy(self.vl_text)
|
||||||
|
gl.addWidget(self.vl_text, 1, 1)
|
||||||
|
self.vl_text.setText(_build_full_search_string(self.gui))
|
||||||
|
|
||||||
|
self.sl = sl = QLabel('<p>'+_('Create a virtual library based on: ')+
|
||||||
|
('<a href="author.{0}">{0}</a>, '
|
||||||
|
'<a href="tag.{1}">{1}</a>, '
|
||||||
|
'<a href="publisher.{2}">{2}</a>, '
|
||||||
|
'<a href="series.{3}">{3}</a>.').format(_('Authors'), _('Tags'), _('Publishers'), _('Series')))
|
||||||
|
sl.setWordWrap(True)
|
||||||
|
sl.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
|
||||||
|
sl.linkActivated.connect(self.link_activated)
|
||||||
|
gl.addWidget(sl, 2, 0, 1, 2)
|
||||||
|
|
||||||
|
self.hl = hl = QLabel(_('''
|
||||||
|
<h2>Virtual Libraries</h2>
|
||||||
|
|
||||||
|
<p>Using <i>virtual libraries</i> you can restrict calibre to only show
|
||||||
|
you books that match a search. When a virtual library is in effect, calibre
|
||||||
|
behaves as though the library contains only the matched books. The Tag Browser
|
||||||
|
display only the tags/authors/series/etc. that belong to the matched books and any searches
|
||||||
|
you do will only search within the books in the virtual library. This
|
||||||
|
is a good way to partition your large library into smaller and easier to work with subsets.</p>
|
||||||
|
|
||||||
|
<p>For example you can use a Virtual Library to only show you books with the Tag <i>"Unread"</i>
|
||||||
|
or only books by <i>"My Favorite Author"</i> or only books in a particular series.</p>
|
||||||
|
'''))
|
||||||
|
hl.setWordWrap(True)
|
||||||
|
hl.setFrameStyle(hl.StyledPanel)
|
||||||
|
gl.addWidget(hl, 0, 3, 4, 1)
|
||||||
|
|
||||||
|
bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
|
bb.accepted.connect(self.accept)
|
||||||
|
bb.rejected.connect(self.reject)
|
||||||
|
gl.addWidget(bb, 4, 0, 1, 0)
|
||||||
|
|
||||||
|
if editing:
|
||||||
|
db = self.gui.current_db
|
||||||
|
virt_libs = db.prefs.get('virtual_libraries', {})
|
||||||
|
self.vl_text.setText(virt_libs.get(editing, ''))
|
||||||
|
|
||||||
|
self.resize(self.sizeHint()+QSize(150, 25))
|
||||||
|
|
||||||
|
def link_activated(self, url):
|
||||||
|
db = self.gui.current_db
|
||||||
|
f, txt = unicode(url).partition('.')[0::2]
|
||||||
|
names = getattr(db, 'all_%s_names'%f)()
|
||||||
|
d = SelectNames(names, txt, parent=self)
|
||||||
|
if d.exec_() == d.Accepted:
|
||||||
|
prefix = f+'s' if f in {'tag', 'author'} else f
|
||||||
|
search = ['%s:"=%s"'%(prefix, x.replace('"', '\\"')) for x in d.names]
|
||||||
|
if search:
|
||||||
|
self.vl_name.setText(d.names.next())
|
||||||
|
self.vl_text.setText(d.match_type.join(search))
|
||||||
|
self.vl_text.setCursorPosition(0)
|
||||||
|
self.vl_name.setCursorPosition(0)
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
n = unicode(self.vl_name.text()).strip()
|
||||||
|
if not n:
|
||||||
|
error_dialog(self.gui, _('No name'),
|
||||||
|
_('You must provide a name for the new virtual library'),
|
||||||
|
show=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if n.startswith('*'):
|
||||||
|
error_dialog(self.gui, _('Invalid name'),
|
||||||
|
_('A virtual library name cannot begin with "*"'),
|
||||||
|
show=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if n in self.existing_names and n != self.editing:
|
||||||
|
if question_dialog(self.gui, _('Name already in use'),
|
||||||
|
_('That name is already in use. Do you want to replace it '
|
||||||
|
'with the new search?'),
|
||||||
|
default_yes=False) == self.Rejected:
|
||||||
|
return
|
||||||
|
|
||||||
|
v = unicode(self.vl_text.text()).strip()
|
||||||
|
if not v:
|
||||||
|
error_dialog(self.gui, _('No search string'),
|
||||||
|
_('You must provide a search to define the new virtual library'),
|
||||||
|
show=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = self.gui.library_view.model().db
|
||||||
|
recs = db.data.search_getting_ids('', v, use_virtual_library=False)
|
||||||
|
except ParseException as e:
|
||||||
|
error_dialog(self.gui, _('Invalid search'),
|
||||||
|
_('The search in the search box is not valid'),
|
||||||
|
det_msg=e.msg, show=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not recs and not question_dialog(
|
||||||
|
self.gui, _('Search found no books'),
|
||||||
|
_('The search found no books, so the virtual library '
|
||||||
|
'will be empty. Do you really want to use that search?'),
|
||||||
|
default_yes=False):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.library_name = n
|
||||||
|
self.library_search = v
|
||||||
|
QDialog.accept(self)
|
||||||
|
# }}}
|
||||||
|
|
||||||
class SearchRestrictionMixin(object):
|
class SearchRestrictionMixin(object):
|
||||||
|
|
||||||
|
no_restriction = _('<None>')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.search_restriction.initialize(help_text=_('Restrict to'))
|
self.checked = QIcon(I('ok.png'))
|
||||||
self.search_restriction.activated[int].connect(self.apply_search_restriction)
|
self.empty = QIcon()
|
||||||
self.library_view.model().count_changed_signal.connect(self.set_number_of_books_shown)
|
self.search_based_vl_name = None
|
||||||
self.search_restriction.setSizeAdjustPolicy(
|
self.search_based_vl = None
|
||||||
self.search_restriction.AdjustToMinimumContentsLengthWithIcon)
|
|
||||||
self.search_restriction.setMinimumContentsLength(10)
|
self.virtual_library_menu = QMenu()
|
||||||
self.search_restriction.setStatusTip(self.search_restriction.toolTip())
|
|
||||||
|
self.virtual_library.clicked.connect(self.virtual_library_clicked)
|
||||||
|
|
||||||
|
self.virtual_library_tooltip = \
|
||||||
|
_('Books display will show only those books matching the search')
|
||||||
|
self.virtual_library.setToolTip(self.virtual_library_tooltip)
|
||||||
|
|
||||||
|
self.search_restriction = ComboBoxWithHelp(self)
|
||||||
|
self.search_restriction.setVisible(False)
|
||||||
self.search_count.setText(_("(all books)"))
|
self.search_count.setText(_("(all books)"))
|
||||||
self.search_restriction_tooltip = \
|
self.ar_menu = QMenu(_('Additional restriction'))
|
||||||
_('Books display will be restricted to those matching a '
|
|
||||||
'selected saved search')
|
def add_virtual_library(self, db, name, search):
|
||||||
self.search_restriction.setToolTip(self.search_restriction_tooltip)
|
virt_libs = db.prefs.get('virtual_libraries', {})
|
||||||
|
virt_libs[name] = search
|
||||||
|
db.prefs.set('virtual_libraries', virt_libs)
|
||||||
|
|
||||||
|
def do_create_edit(self, editing=None):
|
||||||
|
db = self.library_view.model().db
|
||||||
|
virt_libs = db.prefs.get('virtual_libraries', {})
|
||||||
|
cd = CreateVirtualLibrary(self, virt_libs.keys(), editing=editing)
|
||||||
|
if cd.exec_() == cd.Accepted:
|
||||||
|
if editing:
|
||||||
|
self._remove_vl(editing, reapply=False)
|
||||||
|
self.add_virtual_library(db, cd.library_name, cd.library_search)
|
||||||
|
self.apply_virtual_library(cd.library_name)
|
||||||
|
|
||||||
|
def virtual_library_clicked(self):
|
||||||
|
m = self.virtual_library_menu
|
||||||
|
m.clear()
|
||||||
|
|
||||||
|
a = m.addAction(_('Create Virtual Library'))
|
||||||
|
a.triggered.connect(partial(self.do_create_edit, editing=None))
|
||||||
|
|
||||||
|
self.edit_menu = a = QMenu()
|
||||||
|
a.setTitle(_('Edit Virtual Library'))
|
||||||
|
a.aboutToShow.connect(partial(self.build_virtual_library_list, remove=False))
|
||||||
|
m.addMenu(a)
|
||||||
|
|
||||||
|
self.rm_menu = a = QMenu()
|
||||||
|
a.setTitle(_('Remove Virtual Library'))
|
||||||
|
a.aboutToShow.connect(partial(self.build_virtual_library_list, remove=True))
|
||||||
|
m.addMenu(a)
|
||||||
|
|
||||||
|
m.addSeparator()
|
||||||
|
|
||||||
|
db = self.library_view.model().db
|
||||||
|
|
||||||
|
a = self.ar_menu
|
||||||
|
a.clear()
|
||||||
|
a.setIcon(self.checked if db.data.get_search_restriction_name() else self.empty)
|
||||||
|
a.aboutToShow.connect(self.build_search_restriction_list)
|
||||||
|
m.addMenu(a)
|
||||||
|
|
||||||
|
m.addSeparator()
|
||||||
|
|
||||||
|
current_lib = db.data.get_base_restriction_name()
|
||||||
|
|
||||||
|
if current_lib == '':
|
||||||
|
a = m.addAction(self.checked, self.no_restriction)
|
||||||
|
else:
|
||||||
|
a = m.addAction(self.empty, self.no_restriction)
|
||||||
|
a.triggered.connect(partial(self.apply_virtual_library, library=''))
|
||||||
|
|
||||||
|
a = m.addAction(self.empty, _('*current search'))
|
||||||
|
a.triggered.connect(partial(self.apply_virtual_library, library='*'))
|
||||||
|
|
||||||
|
if self.search_based_vl_name:
|
||||||
|
a = m.addAction(
|
||||||
|
self.checked if db.data.get_base_restriction_name().startswith('*')
|
||||||
|
else self.empty,
|
||||||
|
self.search_based_vl_name)
|
||||||
|
a.triggered.connect(partial(self.apply_virtual_library,
|
||||||
|
library=self.search_based_vl_name))
|
||||||
|
|
||||||
|
virt_libs = db.prefs.get('virtual_libraries', {})
|
||||||
|
for vl in sorted(virt_libs.keys(), key=sort_key):
|
||||||
|
a = m.addAction(self.checked if vl == current_lib else self.empty, vl)
|
||||||
|
a.triggered.connect(partial(self.apply_virtual_library, library=vl))
|
||||||
|
|
||||||
|
p = QPoint(0, self.virtual_library.height())
|
||||||
|
self.virtual_library_menu.popup(self.virtual_library.mapToGlobal(p))
|
||||||
|
|
||||||
|
def apply_virtual_library(self, library=None):
|
||||||
|
db = self.library_view.model().db
|
||||||
|
virt_libs = db.prefs.get('virtual_libraries', {})
|
||||||
|
if not library:
|
||||||
|
db.data.set_base_restriction('')
|
||||||
|
db.data.set_base_restriction_name('')
|
||||||
|
elif library == '*':
|
||||||
|
if not self.search.current_text:
|
||||||
|
error_dialog(self, _('No search'),
|
||||||
|
_('There is no current search to use'), show=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
txt = _build_full_search_string(self)
|
||||||
|
try:
|
||||||
|
db.data.search_getting_ids('', txt, use_virtual_library=False)
|
||||||
|
except ParseException as e:
|
||||||
|
error_dialog(self, _('Invalid search'),
|
||||||
|
_('The search in the search box is not valid'),
|
||||||
|
det_msg=e.msg, show=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.search_based_vl = txt
|
||||||
|
db.data.set_base_restriction(txt)
|
||||||
|
self.search_based_vl_name = self._trim_restriction_name('*' + txt)
|
||||||
|
db.data.set_base_restriction_name(self.search_based_vl_name)
|
||||||
|
elif library == self.search_based_vl_name:
|
||||||
|
db.data.set_base_restriction(self.search_based_vl)
|
||||||
|
db.data.set_base_restriction_name(self.search_based_vl_name)
|
||||||
|
elif library in virt_libs:
|
||||||
|
db.data.set_base_restriction(virt_libs[library])
|
||||||
|
db.data.set_base_restriction_name(library)
|
||||||
|
self._apply_search_restriction(db.data.get_search_restriction(),
|
||||||
|
db.data.get_search_restriction_name())
|
||||||
|
|
||||||
|
def build_virtual_library_list(self, remove=False):
|
||||||
|
db = self.library_view.model().db
|
||||||
|
virt_libs = db.prefs.get('virtual_libraries', {})
|
||||||
|
if remove:
|
||||||
|
m = self.rm_menu
|
||||||
|
else:
|
||||||
|
m = self.edit_menu
|
||||||
|
m.clear()
|
||||||
|
|
||||||
|
def add_action(name, search):
|
||||||
|
a = m.addAction(name)
|
||||||
|
if remove:
|
||||||
|
a.triggered.connect(partial(self.remove_vl_triggered, name=name))
|
||||||
|
else:
|
||||||
|
a.triggered.connect(partial(self.do_create_edit, editing=name))
|
||||||
|
|
||||||
|
for n in sorted(virt_libs.keys(), key=sort_key):
|
||||||
|
add_action(n, virt_libs[n])
|
||||||
|
|
||||||
|
def remove_vl_triggered(self, name=None):
|
||||||
|
if not question_dialog(self, _('Are you sure?'),
|
||||||
|
_('Are you sure you want to remove '
|
||||||
|
'the virtual library {0}').format(name),
|
||||||
|
default_yes=False):
|
||||||
|
return
|
||||||
|
self._remove_vl(name, reapply=True)
|
||||||
|
|
||||||
|
def _remove_vl(self, name, reapply=True):
|
||||||
|
db = self.library_view.model().db
|
||||||
|
virt_libs = db.prefs.get('virtual_libraries', {})
|
||||||
|
virt_libs.pop(name, None)
|
||||||
|
db.prefs.set('virtual_libraries', virt_libs)
|
||||||
|
if reapply and db.data.get_base_restriction_name() == name:
|
||||||
|
self.apply_virtual_library('')
|
||||||
|
|
||||||
|
def _trim_restriction_name(self, name):
|
||||||
|
return name[0:MAX_VIRTUAL_LIBRARY_NAME_LENGTH].strip()
|
||||||
|
|
||||||
|
def build_search_restriction_list(self):
|
||||||
|
m = self.ar_menu
|
||||||
|
m.clear()
|
||||||
|
|
||||||
|
current_restriction_text = None
|
||||||
|
|
||||||
|
if self.search_restriction.count() > 1:
|
||||||
|
txt = unicode(self.search_restriction.itemText(2))
|
||||||
|
if txt.startswith('*'):
|
||||||
|
current_restriction_text = txt
|
||||||
|
self.search_restriction.clear()
|
||||||
|
|
||||||
|
current_restriction = self.library_view.model().db.data.get_search_restriction_name()
|
||||||
|
m.setIcon(self.checked if current_restriction else self.empty)
|
||||||
|
|
||||||
|
def add_action(txt, index):
|
||||||
|
self.search_restriction.addItem(txt)
|
||||||
|
txt = self._trim_restriction_name(txt)
|
||||||
|
if txt == current_restriction:
|
||||||
|
a = m.addAction(self.checked, txt if txt else self.no_restriction)
|
||||||
|
else:
|
||||||
|
a = m.addAction(self.empty, txt if txt else self.no_restriction)
|
||||||
|
a.triggered.connect(partial(self.search_restriction_triggered,
|
||||||
|
action=a, index=index))
|
||||||
|
|
||||||
|
add_action('', 0)
|
||||||
|
add_action(_('*current search'), 1)
|
||||||
|
dex = 2
|
||||||
|
if current_restriction_text:
|
||||||
|
add_action(current_restriction_text, 2)
|
||||||
|
dex += 1
|
||||||
|
|
||||||
|
for n in sorted(saved_searches().names(), key=sort_key):
|
||||||
|
add_action(n, dex)
|
||||||
|
dex += 1
|
||||||
|
|
||||||
|
def search_restriction_triggered(self, action=None, index=None):
|
||||||
|
self.search_restriction.setCurrentIndex(index)
|
||||||
|
self.apply_search_restriction(index)
|
||||||
|
|
||||||
def apply_named_search_restriction(self, name):
|
def apply_named_search_restriction(self, name):
|
||||||
if not name:
|
if not name:
|
||||||
@ -29,7 +428,6 @@ class SearchRestrictionMixin(object):
|
|||||||
r = self.search_restriction.findText(name)
|
r = self.search_restriction.findText(name)
|
||||||
if r < 0:
|
if r < 0:
|
||||||
r = 0
|
r = 0
|
||||||
if r != self.search_restriction.currentIndex():
|
|
||||||
self.search_restriction.setCurrentIndex(r)
|
self.search_restriction.setCurrentIndex(r)
|
||||||
self.apply_search_restriction(r)
|
self.apply_search_restriction(r)
|
||||||
|
|
||||||
@ -37,7 +435,7 @@ class SearchRestrictionMixin(object):
|
|||||||
search = unicode(search)
|
search = unicode(search)
|
||||||
if not search:
|
if not search:
|
||||||
self.search_restriction.setCurrentIndex(0)
|
self.search_restriction.setCurrentIndex(0)
|
||||||
self._apply_search_restriction('')
|
self._apply_search_restriction('', '')
|
||||||
else:
|
else:
|
||||||
s = '*' + search
|
s = '*' + search
|
||||||
if self.search_restriction.count() > 1:
|
if self.search_restriction.count() > 1:
|
||||||
@ -49,10 +447,7 @@ class SearchRestrictionMixin(object):
|
|||||||
else:
|
else:
|
||||||
self.search_restriction.insertItem(2, s)
|
self.search_restriction.insertItem(2, s)
|
||||||
self.search_restriction.setCurrentIndex(2)
|
self.search_restriction.setCurrentIndex(2)
|
||||||
self.search_restriction.setToolTip('<p>' +
|
self._apply_search_restriction(search, self._trim_restriction_name(s))
|
||||||
self.search_restriction_tooltip +
|
|
||||||
_(' or the search ') + "'" + search + "'</p>")
|
|
||||||
self._apply_search_restriction(search)
|
|
||||||
|
|
||||||
def apply_search_restriction(self, i):
|
def apply_search_restriction(self, i):
|
||||||
if i == 1:
|
if i == 1:
|
||||||
@ -66,18 +461,20 @@ class SearchRestrictionMixin(object):
|
|||||||
restriction = 'search:"%s"'%(r)
|
restriction = 'search:"%s"'%(r)
|
||||||
else:
|
else:
|
||||||
restriction = ''
|
restriction = ''
|
||||||
self._apply_search_restriction(restriction)
|
self._apply_search_restriction(restriction, r)
|
||||||
|
|
||||||
def _apply_search_restriction(self, restriction):
|
def _apply_search_restriction(self, restriction, name):
|
||||||
self.saved_search.clear()
|
self.saved_search.clear()
|
||||||
# The order below is important. Set the restriction, force a '' search
|
# The order below is important. Set the restriction, force a '' search
|
||||||
# to apply it, reset the tag browser to take it into account, then set
|
# to apply it, reset the tag browser to take it into account, then set
|
||||||
# the book count.
|
# the book count.
|
||||||
self.library_view.model().db.data.set_search_restriction(restriction)
|
self.library_view.model().db.data.set_search_restriction(restriction)
|
||||||
|
self.library_view.model().db.data.set_search_restriction_name(name)
|
||||||
self.search.clear(emit_search=True)
|
self.search.clear(emit_search=True)
|
||||||
self.tags_view.set_search_restriction(restriction)
|
self.tags_view.recount()
|
||||||
self.set_number_of_books_shown()
|
self.set_number_of_books_shown()
|
||||||
self.current_view().setFocus(Qt.OtherFocusReason)
|
self.current_view().setFocus(Qt.OtherFocusReason)
|
||||||
|
self.set_window_title()
|
||||||
|
|
||||||
def set_number_of_books_shown(self):
|
def set_number_of_books_shown(self):
|
||||||
db = self.library_view.model().db
|
db = self.library_view.model().db
|
||||||
@ -86,8 +483,8 @@ class SearchRestrictionMixin(object):
|
|||||||
rows = self.current_view().row_count()
|
rows = self.current_view().row_count()
|
||||||
rbc = max(rows, db.data.get_search_restriction_book_count())
|
rbc = max(rows, db.data.get_search_restriction_book_count())
|
||||||
t = _("({0} of {1})").format(rows, rbc)
|
t = _("({0} of {1})").format(rows, rbc)
|
||||||
self.search_count.setStyleSheet \
|
self.search_count.setStyleSheet(
|
||||||
('QLabel { border-radius: 8px; background-color: yellow; }')
|
'QLabel { border-radius: 8px; background-color: yellow; }')
|
||||||
else: # No restriction or not library view
|
else: # No restriction or not library view
|
||||||
if not self.search.in_a_search():
|
if not self.search.in_a_search():
|
||||||
t = _("(all books)")
|
t = _("(all books)")
|
||||||
@ -96,3 +493,14 @@ class SearchRestrictionMixin(object):
|
|||||||
self.search_count.setStyleSheet(
|
self.search_count.setStyleSheet(
|
||||||
'QLabel { background-color: transparent; }')
|
'QLabel { background-color: transparent; }')
|
||||||
self.search_count.setText(t)
|
self.search_count.setText(t)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from calibre.gui2 import Application
|
||||||
|
from calibre.gui2.preferences import init_gui
|
||||||
|
app = Application([])
|
||||||
|
app
|
||||||
|
gui = init_gui()
|
||||||
|
d = CreateVirtualLibrary(gui, [])
|
||||||
|
d.exec_()
|
||||||
|
|
||||||
|
|
||||||
|
@ -264,13 +264,8 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
if rebuild:
|
if rebuild:
|
||||||
self.rebuild_node_tree(state_map)
|
self.rebuild_node_tree(state_map)
|
||||||
|
|
||||||
def set_search_restriction(self, s):
|
|
||||||
self.search_restriction = s
|
|
||||||
self.rebuild_node_tree()
|
|
||||||
|
|
||||||
def set_database(self, db):
|
def set_database(self, db):
|
||||||
self.beginResetModel()
|
self.beginResetModel()
|
||||||
self.search_restriction = None
|
|
||||||
hidden_cats = db.prefs.get('tag_browser_hidden_categories', None)
|
hidden_cats = db.prefs.get('tag_browser_hidden_categories', None)
|
||||||
# migrate from config to db prefs
|
# migrate from config to db prefs
|
||||||
if hidden_cats is None:
|
if hidden_cats is None:
|
||||||
@ -848,7 +843,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
self.categories = {}
|
self.categories = {}
|
||||||
|
|
||||||
# Get the categories
|
# Get the categories
|
||||||
if self.search_restriction:
|
if self.db.data.get_base_restriction() or self.db.data.get_search_restriction():
|
||||||
try:
|
try:
|
||||||
data = self.db.get_categories(sort=sort,
|
data = self.db.get_categories(sort=sort,
|
||||||
icon_map=self.category_icon_map,
|
icon_map=self.category_icon_map,
|
||||||
|
@ -232,10 +232,6 @@ class TagsView(QTreeView): # {{{
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_search_restriction(self, s):
|
|
||||||
s = s if s else None
|
|
||||||
self._model.set_search_restriction(s)
|
|
||||||
|
|
||||||
def mouseMoveEvent(self, event):
|
def mouseMoveEvent(self, event):
|
||||||
dex = self.indexAt(event.pos())
|
dex = self.indexAt(event.pos())
|
||||||
if self.in_drag_drop or not dex.isValid():
|
if self.in_drag_drop or not dex.isValid():
|
||||||
|
@ -15,7 +15,7 @@ from threading import Thread
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from PyQt4.Qt import (Qt, SIGNAL, QTimer, QHelpEvent, QAction,
|
from PyQt4.Qt import (Qt, SIGNAL, QTimer, QHelpEvent, QAction,
|
||||||
QMenu, QIcon, pyqtSignal, QUrl,
|
QMenu, QIcon, pyqtSignal, QUrl, QFont,
|
||||||
QDialog, QSystemTrayIcon, QApplication)
|
QDialog, QSystemTrayIcon, QApplication)
|
||||||
|
|
||||||
from calibre import prints, force_unicode
|
from calibre import prints, force_unicode
|
||||||
@ -187,7 +187,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
else:
|
else:
|
||||||
stmap[st.name] = st
|
stmap[st.name] = st
|
||||||
|
|
||||||
|
|
||||||
def initialize(self, library_path, db, listener, actions, show_gui=True):
|
def initialize(self, library_path, db, listener, actions, show_gui=True):
|
||||||
opts = self.opts
|
opts = self.opts
|
||||||
self.preferences_action, self.quit_action = actions
|
self.preferences_action, self.quit_action = actions
|
||||||
@ -279,6 +278,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
UpdateMixin.__init__(self, opts)
|
UpdateMixin.__init__(self, opts)
|
||||||
|
|
||||||
####################### Search boxes ########################
|
####################### Search boxes ########################
|
||||||
|
SearchRestrictionMixin.__init__(self)
|
||||||
SavedSearchBoxMixin.__init__(self)
|
SavedSearchBoxMixin.__init__(self)
|
||||||
SearchBoxMixin.__init__(self)
|
SearchBoxMixin.__init__(self)
|
||||||
|
|
||||||
@ -313,9 +313,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
TagBrowserMixin.__init__(self, db)
|
TagBrowserMixin.__init__(self, db)
|
||||||
|
|
||||||
######################### Search Restriction ##########################
|
######################### Search Restriction ##########################
|
||||||
SearchRestrictionMixin.__init__(self)
|
if db.prefs['virtual_lib_on_startup']:
|
||||||
if db.prefs['gui_restriction']:
|
self.apply_virtual_library(db.prefs['virtual_lib_on_startup'])
|
||||||
self.apply_named_search_restriction(db.prefs['gui_restriction'])
|
|
||||||
|
|
||||||
########################### Cover Flow ################################
|
########################### Cover Flow ################################
|
||||||
|
|
||||||
@ -339,7 +338,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
if config['autolaunch_server']:
|
if config['autolaunch_server']:
|
||||||
self.start_content_server()
|
self.start_content_server()
|
||||||
|
|
||||||
|
|
||||||
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
|
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
|
||||||
|
|
||||||
self.read_settings()
|
self.read_settings()
|
||||||
@ -494,7 +492,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
path = os.path.abspath(argv[1])
|
path = os.path.abspath(argv[1])
|
||||||
if os.access(path, os.R_OK):
|
if os.access(path, os.R_OK):
|
||||||
self.iactions['Add Books'].add_filesystem_book(path)
|
self.iactions['Add Books'].add_filesystem_book(path)
|
||||||
self.setWindowState(self.windowState() & \
|
self.setWindowState(self.windowState() &
|
||||||
~Qt.WindowMinimized|Qt.WindowActive)
|
~Qt.WindowMinimized|Qt.WindowActive)
|
||||||
self.show_windows()
|
self.show_windows()
|
||||||
self.raise_()
|
self.raise_()
|
||||||
@ -526,7 +524,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
|
|
||||||
def library_moved(self, newloc, copy_structure=False, call_close=True,
|
def library_moved(self, newloc, copy_structure=False, call_close=True,
|
||||||
allow_rebuild=False):
|
allow_rebuild=False):
|
||||||
if newloc is None: return
|
if newloc is None:
|
||||||
|
return
|
||||||
default_prefs = None
|
default_prefs = None
|
||||||
try:
|
try:
|
||||||
olddb = self.library_view.model().db
|
olddb = self.library_view.model().db
|
||||||
@ -537,7 +536,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
try:
|
try:
|
||||||
db = LibraryDatabase2(newloc, default_prefs=default_prefs)
|
db = LibraryDatabase2(newloc, default_prefs=default_prefs)
|
||||||
except (DatabaseException, sqlite.Error):
|
except (DatabaseException, sqlite.Error):
|
||||||
if not allow_rebuild: raise
|
if not allow_rebuild:
|
||||||
|
raise
|
||||||
import traceback
|
import traceback
|
||||||
repair = question_dialog(self, _('Corrupted database'),
|
repair = question_dialog(self, _('Corrupted database'),
|
||||||
_('The library database at %s appears to be corrupted. Do '
|
_('The library database at %s appears to be corrupted. Do '
|
||||||
@ -596,9 +596,19 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
# interface later
|
# interface later
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
|
|
||||||
def set_window_title(self):
|
def set_window_title(self):
|
||||||
self.setWindowTitle(__appname__ + u' - || %s ||'%self.iactions['Choose Library'].library_name())
|
db = self.current_db
|
||||||
|
restrictions = [x for x in (db.data.get_base_restriction_name(),
|
||||||
|
db.data.get_search_restriction_name()) if x]
|
||||||
|
restrictions = ' :: '.join(restrictions)
|
||||||
|
font = QFont()
|
||||||
|
if restrictions:
|
||||||
|
restrictions = ' :: ' + restrictions
|
||||||
|
font.setBold(True)
|
||||||
|
self.virtual_library.setFont(font)
|
||||||
|
title = u'{0} - || {1}{2} ||'.format(
|
||||||
|
__appname__, self.iactions['Choose Library'].library_name(), restrictions)
|
||||||
|
self.setWindowTitle(title)
|
||||||
|
|
||||||
def location_selected(self, location):
|
def location_selected(self, location):
|
||||||
'''
|
'''
|
||||||
@ -613,17 +623,15 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
for action in self.iactions.values():
|
for action in self.iactions.values():
|
||||||
action.location_selected(location)
|
action.location_selected(location)
|
||||||
if location == 'library':
|
if location == 'library':
|
||||||
self.search_restriction.setEnabled(True)
|
self.virtual_library_menu.setEnabled(True)
|
||||||
self.highlight_only_button.setEnabled(True)
|
self.highlight_only_button.setEnabled(True)
|
||||||
else:
|
else:
|
||||||
self.search_restriction.setEnabled(False)
|
self.virtual_library_menu.setEnabled(False)
|
||||||
self.highlight_only_button.setEnabled(False)
|
self.highlight_only_button.setEnabled(False)
|
||||||
# Reset the view in case something changed while it was invisible
|
# Reset the view in case something changed while it was invisible
|
||||||
self.current_view().reset()
|
self.current_view().reset()
|
||||||
self.set_number_of_books_shown()
|
self.set_number_of_books_shown()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def job_exception(self, job, dialog_title=_('Conversion Error')):
|
def job_exception(self, job, dialog_title=_('Conversion Error')):
|
||||||
if not hasattr(self, '_modeless_dialogs'):
|
if not hasattr(self, '_modeless_dialogs'):
|
||||||
self._modeless_dialogs = []
|
self._modeless_dialogs = []
|
||||||
@ -748,7 +756,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def shutdown(self, write_settings=True):
|
def shutdown(self, write_settings=True):
|
||||||
try:
|
try:
|
||||||
db = self.library_view.model().db
|
db = self.library_view.model().db
|
||||||
@ -808,13 +815,11 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
pass
|
pass
|
||||||
QApplication.instance().quit()
|
QApplication.instance().quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
self.write_settings()
|
self.write_settings()
|
||||||
if self.system_tray_icon.isVisible():
|
if self.system_tray_icon.isVisible():
|
||||||
if not dynamic['systray_msg'] and not isosx:
|
if not dynamic['systray_msg'] and not isosx:
|
||||||
info_dialog(self, 'calibre', 'calibre '+ \
|
info_dialog(self, 'calibre', 'calibre '+
|
||||||
_('will keep running in the system tray. To close it, '
|
_('will keep running in the system tray. To close it, '
|
||||||
'choose <b>Quit</b> in the context menu of the '
|
'choose <b>Quit</b> in the context menu of the '
|
||||||
'system tray.'), show_copy_button=False).exec_()
|
'system tray.'), show_copy_button=False).exec_()
|
||||||
|
@ -209,7 +209,8 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
self._data = []
|
self._data = []
|
||||||
self._map = self._map_filtered = []
|
self._map = self._map_filtered = []
|
||||||
self.first_sort = True
|
self.first_sort = True
|
||||||
self.search_restriction = ''
|
self.search_restriction = self.base_restriction = ''
|
||||||
|
self.base_restriction_name = self.search_restriction_name = ''
|
||||||
self.search_restriction_book_count = 0
|
self.search_restriction_book_count = 0
|
||||||
self.marked_ids_dict = {}
|
self.marked_ids_dict = {}
|
||||||
self.field_metadata = field_metadata
|
self.field_metadata = field_metadata
|
||||||
@ -825,8 +826,19 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
return ans
|
return ans
|
||||||
self._map_filtered = ans
|
self._map_filtered = ans
|
||||||
|
|
||||||
|
def _build_restriction_string(self, restriction):
|
||||||
|
if self.base_restriction:
|
||||||
|
if restriction:
|
||||||
|
return u'(%s) and (%s)' % (self.base_restriction, restriction)
|
||||||
|
else:
|
||||||
|
return self.base_restriction
|
||||||
|
else:
|
||||||
|
return restriction
|
||||||
|
|
||||||
def search_getting_ids(self, query, search_restriction,
|
def search_getting_ids(self, query, search_restriction,
|
||||||
set_restriction_count=False):
|
set_restriction_count=False, use_virtual_library=True):
|
||||||
|
if use_virtual_library:
|
||||||
|
search_restriction = self._build_restriction_string(search_restriction)
|
||||||
q = ''
|
q = ''
|
||||||
if not query or not query.strip():
|
if not query or not query.strip():
|
||||||
q = search_restriction
|
q = search_restriction
|
||||||
@ -847,11 +859,32 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
self.search_restriction_book_count = len(rv)
|
self.search_restriction_book_count = len(rv)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
def get_search_restriction(self):
|
||||||
|
return self.search_restriction
|
||||||
|
|
||||||
def set_search_restriction(self, s):
|
def set_search_restriction(self, s):
|
||||||
self.search_restriction = s
|
self.search_restriction = s
|
||||||
|
|
||||||
|
def get_base_restriction(self):
|
||||||
|
return self.base_restriction
|
||||||
|
|
||||||
|
def set_base_restriction(self, s):
|
||||||
|
self.base_restriction = s
|
||||||
|
|
||||||
|
def get_base_restriction_name(self):
|
||||||
|
return self.base_restriction_name
|
||||||
|
|
||||||
|
def set_base_restriction_name(self, s):
|
||||||
|
self.base_restriction_name = s
|
||||||
|
|
||||||
|
def get_search_restriction_name(self):
|
||||||
|
return self.search_restriction_name
|
||||||
|
|
||||||
|
def set_search_restriction_name(self, s):
|
||||||
|
self.search_restriction_name = s
|
||||||
|
|
||||||
def search_restriction_applied(self):
|
def search_restriction_applied(self):
|
||||||
return bool(self.search_restriction)
|
return bool(self.search_restriction) or bool((self.base_restriction))
|
||||||
|
|
||||||
def get_search_restriction_book_count(self):
|
def get_search_restriction_book_count(self):
|
||||||
return self.search_restriction_book_count
|
return self.search_restriction_book_count
|
||||||
@ -1002,7 +1035,7 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
if field is not None:
|
if field is not None:
|
||||||
self.sort(field, ascending)
|
self.sort(field, ascending)
|
||||||
self._map_filtered = list(self._map)
|
self._map_filtered = list(self._map)
|
||||||
if self.search_restriction:
|
if self.search_restriction or self.base_restriction:
|
||||||
self.search('', return_matches=False)
|
self.search('', return_matches=False)
|
||||||
|
|
||||||
# Sorting functions {{{
|
# Sorting functions {{{
|
||||||
|
@ -229,6 +229,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
|
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
|
||||||
('last_modified', False), ('size', False), ('languages', False),
|
('last_modified', False), ('size', False), ('languages', False),
|
||||||
]
|
]
|
||||||
|
defs['virtual_libraries'] = {}
|
||||||
|
defs['virtual_lib_on_startup'] = defs['cs_virtual_lib_on_startup'] = ''
|
||||||
|
|
||||||
# Migrate the bool tristate tweak
|
# Migrate the bool tristate tweak
|
||||||
defs['bools_are_tristate'] = \
|
defs['bools_are_tristate'] = \
|
||||||
@ -279,6 +281,24 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# migrate the gui_restriction preference to a virtual library
|
||||||
|
gr_pref = self.prefs.get('gui_restriction', None)
|
||||||
|
if gr_pref:
|
||||||
|
virt_libs = self.prefs.get('virtual_libraries', {})
|
||||||
|
virt_libs[gr_pref] = 'search:"' + gr_pref + '"'
|
||||||
|
self.prefs['virtual_libraries'] = virt_libs
|
||||||
|
self.prefs['gui_restriction'] = ''
|
||||||
|
self.prefs['virtual_lib_on_startup'] = gr_pref
|
||||||
|
|
||||||
|
# migrate the cs_restriction preference to a virtual library
|
||||||
|
gr_pref = self.prefs.get('cs_restriction', None)
|
||||||
|
if gr_pref:
|
||||||
|
virt_libs = self.prefs.get('virtual_libraries', {})
|
||||||
|
virt_libs[gr_pref] = 'search:"' + gr_pref + '"'
|
||||||
|
self.prefs['virtual_libraries'] = virt_libs
|
||||||
|
self.prefs['cs_restriction'] = ''
|
||||||
|
self.prefs['cs_virtual_lib_on_startup'] = gr_pref
|
||||||
|
|
||||||
# Rename any user categories with names that differ only in case
|
# Rename any user categories with names that differ only in case
|
||||||
user_cats = self.prefs.get('user_categories', [])
|
user_cats = self.prefs.get('user_categories', [])
|
||||||
catmap = {}
|
catmap = {}
|
||||||
|
@ -205,26 +205,32 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
|
|
||||||
def set_database(self, db):
|
def set_database(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
virt_libs = db.prefs.get('virtual_libraries', {})
|
||||||
sr = getattr(self.opts, 'restriction', None)
|
sr = getattr(self.opts, 'restriction', None)
|
||||||
sr = db.prefs.get('cs_restriction', '') if sr is None else sr
|
if sr:
|
||||||
self.set_search_restriction(sr)
|
if sr in virt_libs:
|
||||||
|
sr = virt_libs[sr]
|
||||||
|
elif sr not in saved_searches().names():
|
||||||
|
prints('WARNING: Content server: search restriction ',
|
||||||
|
sr, ' does not exist')
|
||||||
|
sr = ''
|
||||||
|
else:
|
||||||
|
sr = 'search:"%s"'%sr
|
||||||
|
else:
|
||||||
|
sr = db.prefs.get('cs_virtual_lib_on_startup', '')
|
||||||
|
if sr:
|
||||||
|
if sr not in virt_libs:
|
||||||
|
prints('WARNING: Content server: virtual library ',
|
||||||
|
sr, ' does not exist')
|
||||||
|
sr = ''
|
||||||
|
else:
|
||||||
|
sr = virt_libs[sr]
|
||||||
|
self.search_restriction = sr
|
||||||
|
self.reset_caches()
|
||||||
|
|
||||||
def graceful(self):
|
def graceful(self):
|
||||||
cherrypy.engine.graceful()
|
cherrypy.engine.graceful()
|
||||||
|
|
||||||
def set_search_restriction(self, restriction):
|
|
||||||
self.search_restriction_name = restriction
|
|
||||||
if restriction:
|
|
||||||
if restriction not in saved_searches().names():
|
|
||||||
prints('WARNING: Content server: search restriction ',
|
|
||||||
restriction, ' does not exist')
|
|
||||||
self.search_restriction = ''
|
|
||||||
else:
|
|
||||||
self.search_restriction = 'search:"%s"'%restriction
|
|
||||||
else:
|
|
||||||
self.search_restriction = ''
|
|
||||||
self.reset_caches()
|
|
||||||
|
|
||||||
def setup_loggers(self):
|
def setup_loggers(self):
|
||||||
access_file = log_access_file
|
access_file = log_access_file
|
||||||
error_file = log_error_file
|
error_file = log_error_file
|
||||||
|
@ -145,10 +145,7 @@ def render_rating(rating, url_prefix, container='span', prefix=None): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def get_category_items(category, items, restriction, datatype, prefix): # {{{
|
def get_category_items(category, items, datatype, prefix): # {{{
|
||||||
|
|
||||||
if category == 'search':
|
|
||||||
items = [x for x in items if x.name != restriction]
|
|
||||||
|
|
||||||
def item(i):
|
def item(i):
|
||||||
templ = (u'<div title="{4}" class="category-item">'
|
templ = (u'<div title="{4}" class="category-item">'
|
||||||
@ -489,8 +486,7 @@ class BrowseServer(object):
|
|||||||
if not cats and len(items) == 1:
|
if not cats and len(items) == 1:
|
||||||
# Only one item in category, go directly to book list
|
# Only one item in category, go directly to book list
|
||||||
html = get_category_items(category, items,
|
html = get_category_items(category, items,
|
||||||
self.search_restriction_name, datatype,
|
datatype, self.opts.url_prefix)
|
||||||
self.opts.url_prefix)
|
|
||||||
href = re.search(r'<a href="([^"]+)"', html)
|
href = re.search(r'<a href="([^"]+)"', html)
|
||||||
if href is not None:
|
if href is not None:
|
||||||
raise cherrypy.HTTPRedirect(href.group(1))
|
raise cherrypy.HTTPRedirect(href.group(1))
|
||||||
@ -498,8 +494,7 @@ class BrowseServer(object):
|
|||||||
if len(items) <= self.opts.max_opds_ungrouped_items:
|
if len(items) <= self.opts.max_opds_ungrouped_items:
|
||||||
script = 'false'
|
script = 'false'
|
||||||
items = get_category_items(category, items,
|
items = get_category_items(category, items,
|
||||||
self.search_restriction_name, datatype,
|
datatype, self.opts.url_prefix)
|
||||||
self.opts.url_prefix)
|
|
||||||
else:
|
else:
|
||||||
getter = lambda x: unicode(getattr(x, 'sort', x.name))
|
getter = lambda x: unicode(getattr(x, 'sort', x.name))
|
||||||
starts = set([])
|
starts = set([])
|
||||||
@ -588,8 +583,7 @@ class BrowseServer(object):
|
|||||||
|
|
||||||
sort = self.browse_sort_categories(entries, sort)
|
sort = self.browse_sort_categories(entries, sort)
|
||||||
entries = get_category_items(category, entries,
|
entries = get_category_items(category, entries,
|
||||||
self.search_restriction_name, datatype,
|
datatype, self.opts.url_prefix)
|
||||||
self.opts.url_prefix)
|
|
||||||
return json.dumps(entries, ensure_ascii=True)
|
return json.dumps(entries, ensure_ascii=True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,10 +55,11 @@ The OPDS interface is advertised via BonJour automatically.
|
|||||||
help=_('Write process PID to the specified file'))
|
help=_('Write process PID to the specified file'))
|
||||||
parser.add_option('--daemonize', default=False, action='store_true',
|
parser.add_option('--daemonize', default=False, action='store_true',
|
||||||
help='Run process in background as a daemon. No effect on windows.')
|
help='Run process in background as a daemon. No effect on windows.')
|
||||||
parser.add_option('--restriction', default=None,
|
parser.add_option('--restriction', '--virtual-library', default=None,
|
||||||
help=_('Specifies a restriction to be used for this invocation. '
|
help=_('Specifies a virtual library to be used for this invocation. '
|
||||||
'This option overrides any per-library settings specified'
|
'This option overrides any per-library settings specified'
|
||||||
' in the GUI'))
|
' in the GUI. For compatibility, if the value is not a '
|
||||||
|
'virtual library but is a saved search, that saved search is used.'))
|
||||||
parser.add_option('--auto-reload', default=False, action='store_true',
|
parser.add_option('--auto-reload', default=False, action='store_true',
|
||||||
help=_('Auto reload server when source code changes. May not'
|
help=_('Auto reload server when source code changes. May not'
|
||||||
' work in all environments.'))
|
' work in all environments.'))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user