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