From 1d8e98122c743cd697c9d580b97d47eed408954a Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 7 Feb 2011 11:54:07 +0000
Subject: [PATCH] Add a 'search limit' feature where the user can specify a set
of fields (columns) to search when non-prefixed terms are used
---
src/calibre/gui2/layout.py | 9 ++++++++
src/calibre/gui2/preferences/look_feel.py | 3 +++
src/calibre/gui2/preferences/look_feel.ui | 20 ++++++++++++++++
src/calibre/gui2/search_box.py | 15 ++++++++++--
src/calibre/gui2/tag_view.py | 2 +-
src/calibre/gui2/ui.py | 2 ++
src/calibre/library/caches.py | 28 +++++++++++++++++++----
src/calibre/utils/config.py | 5 ++++
8 files changed, 77 insertions(+), 7 deletions(-)
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