diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index c1d9498075..d3d51066a1 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -202,6 +202,15 @@ class SearchBar(QWidget): # {{{ l.addWidget(x) x.setVisible(False) + x = parent.search_limit_to = QCheckBox() + x.setText(_('&Limit')) + x.setToolTip('

'+_('When searching for text without using lookup ' + 'prefixes, as for example someword instead of title:someword, ' + 'limit the columns searched to those named in the option ' + 'Preferences -> Look and Feel -> Limit non-prefixed searches to columns.')) + x.setVisible(False) + l.addWidget(x) + x = parent.saved_search = SavedSearchBox(self) x.setMaximumSize(QSize(150, 16777215)) x.setMinimumContentsLength(15) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 37ed90cc61..e7bc172dfd 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -62,6 +62,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('tags_browser_partition_method', gprefs, choices=choices) r('tags_browser_collapse_at', gprefs) + r('search_box_limit_to', prefs) + self.current_font = None self.change_font_button.clicked.connect(self.change_font) @@ -119,6 +121,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): gui.search.search_as_you_type(config['search_as_you_type']) self.update_font_display() gui.tags_view.reread_collapse_parameters() + gui.search_limit_to.setEnabled(bool(prefs['search_box_limit_to'])) if __name__ == '__main__': app = QApplication([]) diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 2223167068..248941515c 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -200,6 +200,26 @@ up into sub-categories. If the partition method is set to disable, this value is + + + + Limit non-&prefixed searches to columns: + + + opt_search_box_limit_to + + + + + + + Choose columns to be searched when not using prefixes, as for +example when searching for someword instead of title:someword. +Enter a list of search/lookup names separated by commas. You +must check the 'Limit' box on the GUI for this option to take effect. + + + diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index e4073a01c9..1c94038125 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -16,7 +16,7 @@ from calibre.gui2 import config from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor from calibre.gui2.dialogs.search import SearchDialog -from calibre.utils.config import dynamic +from calibre.utils.config import dynamic, prefs from calibre.utils.search_query_parser import saved_searches from calibre.utils.icu import sort_key @@ -271,7 +271,7 @@ class SavedSearchBox(QComboBox): # {{{ def initialize(self, _search_box, colorize=False, help_text=_('Search')): self.search_box = _search_box try: - self.line_edit.setPlaceholderText(help_text) + self.line_edit.setPlaceholderText(help_text) except: # Using Qt < 4.7 pass @@ -379,6 +379,12 @@ class SearchBoxMixin(object): # {{{ self.search_highlight_only.stateChanged.connect(self.highlight_only_changed) self.search_highlight_only.setChecked( dynamic.get('search_highlight_only', False)) + self.search_limit_to.stateChanged.connect(self.search_limit_to_changed) + self.search_limit_to.setVisible(True) + chk = dynamic.get('use_search_box_limit', False) + self.search_limit_to.setChecked(chk) + prefs['use_search_box_limit'] = chk + self.search_limit_to.setEnabled(bool(prefs['search_box_limit_to'])) def focus_search_box(self, *args): self.search.setFocus(Qt.OtherFocusReason) @@ -410,6 +416,11 @@ class SearchBoxMixin(object): # {{{ self.current_view().model().set_highlight_only(toWhat) self.focus_to_library() + def search_limit_to_changed(self, toWhat): + dynamic.set('use_search_box_limit', toWhat) + prefs['use_search_box_limit'] = toWhat + self.search.do_search() + # }}} class SavedSearchBoxMixin(object): # {{{ diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index fd3530d333..79199c6881 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -1214,7 +1214,7 @@ class TagBrowserMixin(object): # {{{ db.field_metadata.remove_user_categories() for k in d.categories: db.field_metadata.add_user_category('@' + k, k) - db.data.sqp_change_locations(db.field_metadata.get_search_terms()) + db.data.change_search_locations(db.field_metadata.get_search_terms()) self.tags_view.set_new_model() self.tags_view.recount() diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 907dd577b8..70d0d387a5 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -482,8 +482,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ for action in self.iactions.values(): action.location_selected(location) if location == 'library': + self.search_limit_to.setVisible(True) self.search_restriction.setEnabled(True) else: + self.search_limit_to.setVisible(False) self.search_restriction.setEnabled(False) # Reset the view in case something changed while it was invisible self.current_view().reset() diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index e818e6a3c0..fb68a0164a 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -11,7 +11,7 @@ from itertools import repeat from datetime import timedelta from threading import Thread -from calibre.utils.config import tweaks +from calibre.utils.config import tweaks, prefs from calibre.utils.date import parse_date, now, UNDEFINED_DATE from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.pyparsing import ParseException @@ -182,15 +182,16 @@ class ResultCache(SearchQueryParser): # {{{ self.first_sort = True self.search_restriction = '' self.field_metadata = field_metadata - all_search_locations = field_metadata.get_search_terms() - SearchQueryParser.__init__(self, all_search_locations, optimize=True) + self.all_search_locations = field_metadata.get_search_terms() + SearchQueryParser.__init__(self, self.all_search_locations, optimize=True) self.build_date_relop_dict() self.build_numeric_relop_dict() def break_cycles(self): self._data = self.field_metadata = self.FIELD_MAP = \ self.numeric_search_relops = self.date_search_relops = \ - self.db_prefs = None + self.db_prefs = self.all_search_locations = None + self.sqp_change_locations([]) def __getitem__(self, row): @@ -218,6 +219,10 @@ class ResultCache(SearchQueryParser): # {{{ def universal_set(self): return set([i[0] for i in self._data if i is not None]) + def change_search_locations(self, locations): + self.sqp_change_locations(locations) + self.all_search_locations = locations + def build_date_relop_dict(self): ''' Because the database dates have time in them, we can't use direct @@ -432,6 +437,7 @@ class ResultCache(SearchQueryParser): # {{{ # get metadata key associated with the search term. Eliminates # dealing with plurals and other aliases location = self.field_metadata.search_term_to_field_key(icu_lower(location.strip())) + # grouped search terms if isinstance(location, list): if allow_recursion: for loc in location: @@ -440,6 +446,20 @@ class ResultCache(SearchQueryParser): # {{{ return matches raise ParseException(query, len(query), 'Recursive query group detected', self) + # apply the limit if appropriate + if location == 'all' and prefs['use_search_box_limit'] and \ + prefs['search_box_limit_to']: + for l in prefs['search_box_limit_to'].split(','): + l = icu_lower(l.strip()) + if not l or l == 'all': + continue + if l not in self.all_search_locations: + raise ParseException(l, len(l), + 'Unknown field "%s" in search column limit'%l, self) + matches |= self.get_matches(l, query, + candidates=candidates, allow_recursion=allow_recursion) + return matches + if location in self.field_metadata: fm = self.field_metadata[location] # take care of dates special case diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py index 11c58f7769..976864ebd3 100644 --- a/src/calibre/utils/config.py +++ b/src/calibre/utils/config.py @@ -729,6 +729,11 @@ def _prefs(): c.add_opt('manage_device_metadata', default='manual', help=_('How and when calibre updates metadata on the device.')) + c.add_opt('search_box_limit_to', default='', + help=_('Comma-separated list of fields to search when no prefix')) + c.add_opt('use_search_box_limit', default=False, + help=_('Set to true to apply the search box limit')) + c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.') return c