diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py
index e03b0680be..8d853deaa9 100644
--- a/resources/default_tweaks.py
+++ b/resources/default_tweaks.py
@@ -90,4 +90,18 @@ save_template_title_series_sorting = 'library_order'
# Examples:
# auto_connect_to_folder = 'C:\\Users\\someone\\Desktop\\testlib'
# auto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library'
-auto_connect_to_folder = ''
\ No newline at end of file
+auto_connect_to_folder = ''
+
+
+# Create search terms to apply a query across several built-in search terms.
+# Syntax: {'new term':['existing term 1', 'term 2', ...], 'new':['old'...] ...}
+# Example: create the term 'myseries' that when used as myseries:foo would
+# search all of the search categories 'series', '#myseries', and '#myseries2':
+# grouped_search_terms={'myseries':['series','#myseries', '#myseries2']}
+# Example: two search terms 'a' and 'b' both that search 'tags' and '#mytags':
+# grouped_search_terms={'a':['tags','#mytags'], 'b':['tags','#mytags']}
+# Note: You cannot create a search term that is a duplicate of an existing term.
+# Such duplicates will be silently ignored. Also note that search terms ignore
+# case. 'MySearch' and 'mysearch' are the same term.
+grouped_search_terms = {}
+
diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py
index 3988c9663d..ff2bcd47c8 100644
--- a/src/calibre/gui2/actions/copy_to_library.py
+++ b/src/calibre/gui2/actions/copy_to_library.py
@@ -66,7 +66,8 @@ class Worker(Thread):
for identical_book in identical_book_list:
self.add_formats(identical_book, paths, newdb, replace=False)
if not added:
- newdb.import_book(mi, paths, notify=False, import_hooks=False)
+ newdb.import_book(mi, paths, notify=False, import_hooks=False,
+ apply_import_tags=False)
co = self.db.conversion_options(x, 'PIPE')
if co is not None:
newdb.set_conversion_options(x, 'PIPE', co)
diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py
index 1f04c50e19..091b98dc86 100644
--- a/src/calibre/gui2/dialogs/config/__init__.py
+++ b/src/calibre/gui2/dialogs/config/__init__.py
@@ -457,6 +457,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.priority.setCurrentIndex(p)
self.priority.setVisible(iswindows)
self.priority_label.setVisible(iswindows)
+ self.new_book_tags.setText(', '.join(prefs['new_book_tags']))
self._plugin_model = PluginModel()
self.plugin_view.setModel(self._plugin_model)
self.plugin_view.setStyleSheet(
@@ -906,6 +907,9 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
config['disable_tray_notification'] = not self.systray_notifications.isChecked()
p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()]
prefs['worker_process_priority'] = p
+ nbt = [x.strip() for x in
+ unicode(self.new_book_tags.text()).strip().split(',')]
+ prefs['new_book_tags'] = [x for x in nbt if x]
prefs['output_format'] = unicode(self.output_format.currentText()).upper()
config['cover_flow_queue_length'] = self.cover_browse.value()
prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString())
diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui
index 988960615d..5a2bf805da 100644
--- a/src/calibre/gui2/dialogs/config/config.ui
+++ b/src/calibre/gui2/dialogs/config/config.ui
@@ -136,7 +136,7 @@
-
-
-
+
-
Default network &timeout:
@@ -146,7 +146,7 @@
- -
+
-
Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)
@@ -165,10 +165,10 @@
- -
+
-
- -
+
-
Choose &language (requires restart):
@@ -178,7 +178,7 @@
- -
+
-
-
@@ -197,7 +197,7 @@
- -
+
-
Job &priority:
@@ -207,7 +207,7 @@
- -
+
-
Preferred &output format:
@@ -217,9 +217,26 @@
- -
+
-
+ -
+
+
+ Tags to apply when adding a book:
+
+
+ new_book_tags
+
+
+
+ -
+
+
+ A comma-separated list of tags that will be applied to books added to the library
+
+
+
-
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 89008735fe..e442007c9a 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -236,8 +236,8 @@ class BooksModel(QAbstractTableModel): # {{{
def search(self, text, reset=True):
try:
self.db.search(text)
- except ParseException:
- self.searched.emit(False)
+ except ParseException as e:
+ self.searched.emit(e.msg)
return
self.last_search = text
if reset:
diff --git a/src/calibre/gui2/preferences/behavior.ui b/src/calibre/gui2/preferences/behavior.ui
index 09d83f29de..c6c07a1220 100644
--- a/src/calibre/gui2/preferences/behavior.ui
+++ b/src/calibre/gui2/preferences/behavior.ui
@@ -164,6 +164,20 @@
+ -
+
+
+ A comma-separated list of tags that will be applied to books added to the library
+
+
+
+ -
+
+
+ Tags to apply when adding a book:
+
+
+
-
diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py
index 8986346dbc..d8f59b8db7 100644
--- a/src/calibre/gui2/search_box.py
+++ b/src/calibre/gui2/search_box.py
@@ -90,6 +90,7 @@ class SearchBox2(QComboBox):
self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
self.setMinimumContentsLength(25)
self._in_a_search = False
+ self.tool_tip_text = self.toolTip()
def initialize(self, opt_name, colorize=False, help_text=_('Search')):
self.as_you_type = config['search_as_you_type']
@@ -100,6 +101,7 @@ class SearchBox2(QComboBox):
self.clear_to_help()
def normalize_state(self):
+ self.setToolTip(self.tool_tip_text)
if self.help_state:
self.setEditText('')
self.line_edit.setStyleSheet(
@@ -112,6 +114,7 @@ class SearchBox2(QComboBox):
self.normal_background)
def clear_to_help(self):
+ self.setToolTip(self.tool_tip_text)
if self.help_state:
return
self.help_state = True
@@ -131,6 +134,9 @@ class SearchBox2(QComboBox):
self.clear_to_help()
def search_done(self, ok):
+ if isinstance(ok, basestring):
+ self.setToolTip(ok)
+ ok = False
if not unicode(self.currentText()).strip():
return self.clear_to_help()
self._in_a_search = ok
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index ca66d28ddb..b9c1211c7f 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -319,12 +319,18 @@ class ResultCache(SearchQueryParser):
matches.add(item[0])
return matches
- def get_matches(self, location, query):
+ def get_matches(self, location, query, allow_recursion=True):
matches = set([])
if query and query.strip():
# get metadata key associated with the search term. Eliminates
# dealing with plurals and other aliases
location = self.field_metadata.search_term_to_key(location.lower().strip())
+ if isinstance(location, list):
+ if allow_recursion:
+ for loc in location:
+ matches |= self.get_matches(loc, query, allow_recursion=False)
+ return matches
+ raise ParseException(query, len(query), 'Recursive query group detected', self)
# take care of dates special case
if location in self.field_metadata and \
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 44c5cfcabb..192a45cccb 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -297,6 +297,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if len(saved_searches().names()):
tb_cats.add_search_category(label='search', name=_('Searches'))
+ gst = tweaks['grouped_search_terms']
+ for t in gst:
+ try:
+ self.field_metadata._add_search_terms_to_map(gst[t], [t])
+ except ValueError:
+ traceback.print_exc()
+
self.book_on_device_func = None
self.data = ResultCache(self.FIELD_MAP, self.field_metadata)
self.search = self.data.search
@@ -1718,7 +1725,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
path = path_or_stream
return run_plugins_on_import(path, format)
+ def _add_newbook_tag(self, mi):
+ tags = prefs['new_book_tags']
+ if tags:
+ for tag in [t.strip() for t in tags]:
+ if tag:
+ if mi.tags is None:
+ mi.tags = [tag]
+ else:
+ mi.tags.append(tag)
+
def create_book_entry(self, mi, cover=None, add_duplicates=True):
+ self._add_newbook_tag(mi)
if not add_duplicates and self.has_book(mi):
return None
series_index = 1.0 if mi.series_index is None else mi.series_index
@@ -1757,6 +1775,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
ids = []
for path in paths:
mi = metadata.next()
+ self._add_newbook_tag(mi)
format = formats.next()
if not add_duplicates and self.has_book(mi):
duplicates.append((path, format, mi))
@@ -1795,8 +1814,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return (paths, formats, metadata), len(ids)
return None, len(ids)
- def import_book(self, mi, formats, notify=True, import_hooks=True):
+ def import_book(self, mi, formats, notify=True, import_hooks=True,
+ apply_import_tags=True):
series_index = 1.0 if mi.series_index is None else mi.series_index
+ if apply_import_tags:
+ self._add_newbook_tag(mi)
if not mi.title:
mi.title = _('Unknown')
if not mi.authors:
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index 2a79c3a75b..e28b6d422a 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -475,9 +475,7 @@ class FieldMetadata(dict):
# ])
def get_search_terms(self):
- s_keys = []
- for v in self._tb_cats.itervalues():
- map((lambda x:s_keys.append(x)), v['search_terms'])
+ s_keys = sorted(self._search_term_map.keys())
for v in self.search_items:
s_keys.append(v)
# if set(s_keys) != self.DEFAULT_LOCATIONS:
@@ -488,6 +486,9 @@ class FieldMetadata(dict):
def _add_search_terms_to_map(self, key, terms):
if terms is not None:
for t in terms:
+ t = t.lower()
+ if t in self._search_term_map:
+ raise ValueError('Attempt to add duplicate search term "%s"'%t)
self._search_term_map[t] = key
def search_term_to_key(self, term):
diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py
index a890d653b6..695ece3c48 100644
--- a/src/calibre/utils/config.py
+++ b/src/calibre/utils/config.py
@@ -717,6 +717,7 @@ def _prefs():
c.add_opt('add_formats_to_existing', default=False,
help=_('Add new formats to existing book records'))
c.add_opt('installation_uuid', default=None, help='Installation UUID')
+ c.add_opt('new_book_tags', default=[], help=_('Tags to apply to books added to the library'))
# these are here instead of the gui preferences because calibredb and
# calibre server can execute searches