Add a 'search limit' feature where the user can specify a set of fields (columns) to search when non-prefixed terms are used

This commit is contained in:
Charles Haley 2011-02-07 11:54:07 +00:00
parent b8b6c83a1d
commit 1d8e98122c
8 changed files with 77 additions and 7 deletions

View File

@ -202,6 +202,15 @@ class SearchBar(QWidget): # {{{
l.addWidget(x) l.addWidget(x)
x.setVisible(False) x.setVisible(False)
x = parent.search_limit_to = QCheckBox()
x.setText(_('&Limit'))
x.setToolTip('<p>'+_('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 = parent.saved_search = SavedSearchBox(self)
x.setMaximumSize(QSize(150, 16777215)) x.setMaximumSize(QSize(150, 16777215))
x.setMinimumContentsLength(15) x.setMinimumContentsLength(15)

View File

@ -62,6 +62,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('tags_browser_partition_method', gprefs, choices=choices) r('tags_browser_partition_method', gprefs, choices=choices)
r('tags_browser_collapse_at', gprefs) r('tags_browser_collapse_at', gprefs)
r('search_box_limit_to', prefs)
self.current_font = None self.current_font = None
self.change_font_button.clicked.connect(self.change_font) 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']) gui.search.search_as_you_type(config['search_as_you_type'])
self.update_font_display() self.update_font_display()
gui.tags_view.reread_collapse_parameters() gui.tags_view.reread_collapse_parameters()
gui.search_limit_to.setEnabled(bool(prefs['search_box_limit_to']))
if __name__ == '__main__': if __name__ == '__main__':
app = QApplication([]) app = QApplication([])

View File

@ -200,6 +200,26 @@ up into sub-categories. If the partition method is set to disable, this value is
</item> </item>
</layout> </layout>
</item> </item>
<item row="8" column="0">
<widget class="QLabel" name="label_81">
<property name="text">
<string>Limit non-&amp;prefixed searches to columns:</string>
</property>
<property name="buddy">
<cstring>opt_search_box_limit_to</cstring>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="opt_search_box_limit_to">
<property name="toolTip">
<string>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.</string>
</property>
</widget>
</item>
<item row="15" column="0" colspan="2"> <item row="15" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2"> <widget class="QGroupBox" name="groupBox_2">
<property name="title"> <property name="title">

View File

@ -16,7 +16,7 @@ from calibre.gui2 import config
from calibre.gui2.dialogs.confirm_delete import confirm 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.config import dynamic from calibre.utils.config import dynamic, prefs
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 from calibre.utils.icu import sort_key
@ -271,7 +271,7 @@ class SavedSearchBox(QComboBox): # {{{
def initialize(self, _search_box, colorize=False, help_text=_('Search')): def initialize(self, _search_box, colorize=False, help_text=_('Search')):
self.search_box = _search_box self.search_box = _search_box
try: try:
self.line_edit.setPlaceholderText(help_text) self.line_edit.setPlaceholderText(help_text)
except: except:
# Using Qt < 4.7 # Using Qt < 4.7
pass pass
@ -379,6 +379,12 @@ class SearchBoxMixin(object): # {{{
self.search_highlight_only.stateChanged.connect(self.highlight_only_changed) self.search_highlight_only.stateChanged.connect(self.highlight_only_changed)
self.search_highlight_only.setChecked( self.search_highlight_only.setChecked(
dynamic.get('search_highlight_only', False)) 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): def focus_search_box(self, *args):
self.search.setFocus(Qt.OtherFocusReason) self.search.setFocus(Qt.OtherFocusReason)
@ -410,6 +416,11 @@ class SearchBoxMixin(object): # {{{
self.current_view().model().set_highlight_only(toWhat) self.current_view().model().set_highlight_only(toWhat)
self.focus_to_library() 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): # {{{ class SavedSearchBoxMixin(object): # {{{

View File

@ -1214,7 +1214,7 @@ class TagBrowserMixin(object): # {{{
db.field_metadata.remove_user_categories() db.field_metadata.remove_user_categories()
for k in d.categories: for k in d.categories:
db.field_metadata.add_user_category('@' + k, k) 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.set_new_model()
self.tags_view.recount() self.tags_view.recount()

View File

@ -482,8 +482,10 @@ 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_limit_to.setVisible(True)
self.search_restriction.setEnabled(True) self.search_restriction.setEnabled(True)
else: else:
self.search_limit_to.setVisible(False)
self.search_restriction.setEnabled(False) self.search_restriction.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()

View File

@ -11,7 +11,7 @@ from itertools import repeat
from datetime import timedelta from datetime import timedelta
from threading import Thread 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.date import parse_date, now, UNDEFINED_DATE
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
@ -182,15 +182,16 @@ class ResultCache(SearchQueryParser): # {{{
self.first_sort = True self.first_sort = True
self.search_restriction = '' self.search_restriction = ''
self.field_metadata = field_metadata self.field_metadata = field_metadata
all_search_locations = field_metadata.get_search_terms() self.all_search_locations = field_metadata.get_search_terms()
SearchQueryParser.__init__(self, all_search_locations, optimize=True) SearchQueryParser.__init__(self, self.all_search_locations, optimize=True)
self.build_date_relop_dict() self.build_date_relop_dict()
self.build_numeric_relop_dict() self.build_numeric_relop_dict()
def break_cycles(self): def break_cycles(self):
self._data = self.field_metadata = self.FIELD_MAP = \ self._data = self.field_metadata = self.FIELD_MAP = \
self.numeric_search_relops = self.date_search_relops = \ 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): def __getitem__(self, row):
@ -218,6 +219,10 @@ class ResultCache(SearchQueryParser): # {{{
def universal_set(self): def universal_set(self):
return set([i[0] for i in self._data if i is not None]) 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): def build_date_relop_dict(self):
''' '''
Because the database dates have time in them, we can't use direct 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 # get metadata key associated with the search term. Eliminates
# dealing with plurals and other aliases # dealing with plurals and other aliases
location = self.field_metadata.search_term_to_field_key(icu_lower(location.strip())) location = self.field_metadata.search_term_to_field_key(icu_lower(location.strip()))
# grouped search terms
if isinstance(location, list): if isinstance(location, list):
if allow_recursion: if allow_recursion:
for loc in location: for loc in location:
@ -440,6 +446,20 @@ class ResultCache(SearchQueryParser): # {{{
return matches return matches
raise ParseException(query, len(query), 'Recursive query group detected', self) 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: if location in self.field_metadata:
fm = self.field_metadata[location] fm = self.field_metadata[location]
# take care of dates special case # take care of dates special case

View File

@ -729,6 +729,11 @@ def _prefs():
c.add_opt('manage_device_metadata', default='manual', c.add_opt('manage_device_metadata', default='manual',
help=_('How and when calibre updates metadata on the device.')) 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.') c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
return c return c