From 1d8e98122c743cd697c9d580b97d47eed408954a Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 7 Feb 2011 11:54:07 +0000 Subject: [PATCH 1/8] 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 From 5d14ffaec595a9b66c28117c3d44e8d4d5722f03 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 7 Feb 2011 17:28:31 +0000 Subject: [PATCH 2/8] Harmonize QString vs unicode --- src/calibre/gui2/dialogs/tag_list_editor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index 5e35a236e4..ef279e78c7 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -1,8 +1,8 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -from PyQt4.QtCore import SIGNAL, Qt -from PyQt4.QtGui import QDialog, QListWidgetItem, QListWidget +from PyQt4.QtCore import Qt, QString +from PyQt4.QtGui import QDialog, QListWidgetItem from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor from calibre.gui2 import question_dialog, error_dialog @@ -11,9 +11,9 @@ class ListWidgetItem(QListWidgetItem): def __init__(self, txt): QListWidgetItem.__init__(self, txt) - self.initial_value = txt - self.current_value = txt - self.previous_value = txt + self.initial_value = QString(txt) + self.current_value = QString(txt) + self.previous_value = QString(txt) def data(self, role): if role == Qt.DisplayRole: @@ -86,7 +86,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): return if item.text() != item.initial_text(): id_ = item.data(Qt.UserRole).toInt()[0] - self.to_rename[id_] = item.text() + self.to_rename[id_] = unicode(item.text()) def rename_tag(self): item = self.available_tags.currentItem() From 0a89f2e69a2b1b096dfffa02314e9cd2c18ad162 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 7 Feb 2011 19:02:37 +0000 Subject: [PATCH 3/8] A few changes to the author_sort faq --- src/calibre/manual/faq.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 9c02ace0e8..cdae20ea3b 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -327,12 +327,14 @@ Now coming to author name sorting: * Authors in the Tag Browser are sorted by the sort value for the **authors**. Remember that this is different from the Author sort field for a book. * By default, this sort algorithm assumes that the author name is in ``First name Last name`` format and generates a ``Last name, First name`` sort value. * You can change this algorithm by going to Preferences->Tweaks and setting the :guilabel:`author_sort_copy_method` tweak. - * You can force |app| to recalculate the author sort values for every author by right clicking on any author and selecting :guilabel:`Manage authors` - * You can force |app| to recalculate the author sort values for all books by using the bulk metadata edit dialog (select all books and click edit metadata) - * When recalculating the author sort values for books, |app| uses the author sort values for each individual author. + * You can force |app| to recalculate the author sort values for every author by right clicking on any author and selecting :guilabel:`Manage authors`, then pushing the `Recalculate all author sort values` button. Do this after you have set the author_sort_copy_method tweak to what you want. + * You can force |app| to recalculate the author sort values for all books by using the bulk metadata edit dialog (select all books and click edit metadata, check the `Automatically set author sort` checkbox, then press OK.) + * When recalculating the author sort values for books, |app| uses the author sort values for each individual author. Therefore, ensure that the individual author sort values are correct before recalculating the books' author sort values. * You can control whether the Tag Browser display authors using their names or their sort values by setting the :guilabel:`categories_use_field_for_author_name` tweak in Preferences->Tweaks -With all this flexibility, it is possible to have |app| manage your author names however you like. For example, one common request is to have |app| display author names LN, FN. To do this first set the ``author_sort_copy_method`` to ``copy``. Then change all author names to LN, FN via the Manage authors dialog. Then have |app| recalculate author sort values as described above. +With all this flexibility, it is possible to have |app| manage your author names however you like. For example, one common request is to have |app| display author names LN, FN. To do this first set the ``author_sort_copy_method`` to ``copy``. Then change all author names to LN, FN via the Manage authors dialog. Then have |app| recalculate author sort values for both authors and books as described above. + +Note that you can set an individual author's sort value to whatever you want using :guilabel:`Manage authors`. This is useful when dealing with names that |app| will not get right, such as complex multi-part names like Miguel de Cervantes Saavedra or when dealing with Asian names like Sun Tzu. Why doesn't |app| let me store books in my own directory structure? From 708b1a1769323d85da41b7666fa71a1520cc9f25 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 7 Feb 2011 20:41:20 +0000 Subject: [PATCH 4/8] Add completion to the search limit entry box --- src/calibre/gui2/preferences/look_feel.py | 3 +++ src/calibre/gui2/preferences/look_feel.ui | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index e7bc172dfd..86d450567c 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -63,6 +63,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('tags_browser_collapse_at', gprefs) r('search_box_limit_to', prefs) + self.opt_search_box_limit_to.set_separator(',') + self.opt_search_box_limit_to.update_items_cache( + self.gui.library_view.model().db.field_metadata.get_search_terms()) self.current_font = None self.change_font_button.clicked.connect(self.change_font) diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 248941515c..4bd514101b 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -211,7 +211,7 @@ up into sub-categories. If the partition method is set to disable, this value is - + Choose columns to be searched when not using prefixes, as for example when searching for someword instead of title:someword. @@ -305,6 +305,13 @@ must check the 'Limit' box on the GUI for this option to take effect. + + + MultiCompleteLineEdit + QLineEdit +

calibre.gui2.complete.h
+ + From 8818bccf1d201addecf3a9a596a35260435bae5e Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 8 Feb 2011 12:03:03 +0000 Subject: [PATCH 5/8] Intermediate commit before cleaning up after adding search options box --- src/calibre/gui2/actions/next_match.py | 8 +- src/calibre/gui2/layout.py | 108 ++++++++++++++++++---- src/calibre/gui2/library/models.py | 2 - src/calibre/gui2/preferences/look_feel.py | 6 -- src/calibre/gui2/preferences/look_feel.ui | 20 ---- src/calibre/gui2/search_box.py | 50 ++++++++-- src/calibre/gui2/ui.py | 2 - src/calibre/utils/config.py | 2 +- 8 files changed, 134 insertions(+), 64 deletions(-) diff --git a/src/calibre/gui2/actions/next_match.py b/src/calibre/gui2/actions/next_match.py index 79de6a2d9b..b88aa0dd59 100644 --- a/src/calibre/gui2/actions/next_match.py +++ b/src/calibre/gui2/actions/next_match.py @@ -29,12 +29,12 @@ class NextMatchAction(InterfaceAction): self.p_action.triggered.connect(self.move_backward) def gui_layout_complete(self): - self.gui.search_highlight_only.setVisible(True) + self.gui.search_options_button.setVisible(True) def location_selected(self, loc): self.can_move = loc == 'library' try: - self.gui.search_highlight_only.setVisible(self.can_move) + self.gui.search_options_button.setVisible(self.can_move) except: import traceback traceback.print_exc() @@ -42,7 +42,7 @@ class NextMatchAction(InterfaceAction): def move_forward(self): if self.can_move is None: self.can_move = self.gui.current_view() is self.gui.library_view - self.gui.search_highlight_only.setVisible(self.can_move) + self.gui.search_options_button.setVisible(self.can_move) if self.can_move: self.gui.current_view().move_highlighted_row(forward=True) @@ -50,7 +50,7 @@ class NextMatchAction(InterfaceAction): def move_backward(self): if self.can_move is None: self.can_move = self.gui.current_view() is self.gui.library_view - self.gui.search_highlight_only.setVisible(self.can_move) + self.gui.search_options_button.setVisible(self.can_move) if self.can_move: self.gui.current_view().move_highlighted_row(forward=False) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index d3d51066a1..9ef8a546eb 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -7,8 +7,8 @@ __docformat__ = 'restructuredtext en' from functools import partial -from PyQt4.Qt import QIcon, Qt, QWidget, QToolBar, QSize, \ - pyqtSignal, QToolButton, QMenu, QCheckBox, \ +from PyQt4.Qt import QIcon, Qt, QWidget, QToolBar, QSize, QDialogButtonBox, \ + pyqtSignal, QToolButton, QMenu, QCheckBox, QDialog, QGridLayout, \ QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup @@ -17,7 +17,9 @@ from calibre.gui2.search_box import SearchBox2, SavedSearchBox from calibre.gui2.throbber import ThrobbingButton from calibre.gui2 import gprefs from calibre.gui2.widgets import ComboBoxWithHelp +from calibre.gui2.complete import MultiCompleteLineEdit from calibre import human_readable +from calibre.utils.config import prefs class LocationManager(QObject): # {{{ @@ -149,6 +151,8 @@ class SearchBar(QWidget): # {{{ def __init__(self, parent): QWidget.__init__(self, parent) + self.parent = parent + self._layout = l = QHBoxLayout() self.setLayout(self._layout) self._layout.setContentsMargins(0,5,0,0) @@ -156,9 +160,10 @@ class SearchBar(QWidget): # {{{ x = ComboBoxWithHelp(self) x.setMaximumSize(QSize(150, 16777215)) x.setObjectName("search_restriction") - x.setToolTip(_("Books display will be restricted to those matching the selected saved search")) - l.addWidget(x) + x.setToolTip(_('Books display will be restricted to those matching the ' + 'selected saved search')) parent.search_restriction = x + l.addWidget(x) x = QLabel(self) x.setObjectName("search_count") @@ -175,7 +180,8 @@ class SearchBar(QWidget): # {{{ x = parent.search = SearchBox2(self) x.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) x.setObjectName("search") - x.setToolTip(_("

Search the list of books by title, author, publisher, tags, comments, etc.

Words separated by spaces are ANDed")) + x.setToolTip(_("

Search the list of books by title, author, publisher, " + "tags, comments, etc.

Words separated by spaces are ANDed")) l.addWidget(x) self.search_button = QToolButton() @@ -194,23 +200,13 @@ class SearchBar(QWidget): # {{{ l.addWidget(x) x.setToolTip(_("Reset Quick Search")) - x = parent.search_highlight_only = QCheckBox() - x.setText(_('&Highlight')) - x.setToolTip('

'+_('When searching, highlight matched books, instead ' - 'of restricting the book list to the matches.

You can use the ' - 'N or F3 keys to go to the next match.')) + x = parent.search_options_button = QToolButton(self) + x.setIcon(QIcon(I('config.png'))) + x.setObjectName("search_option_button") l.addWidget(x) + x.setToolTip(_("Change search highlighting and field limit options")) 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) @@ -236,6 +232,80 @@ class SearchBar(QWidget): # {{{ x.setToolTip(_("Delete current saved search")) +class SearchOptions(QDialog): + + def __init__(self, parent, limit_to_fields, limit_field_list, + limit_cbox, highlight_cbox): + QDialog.__init__(self, parent=parent) +# self.search_limit_possible_fields = [] +# self.search_limit_cbox_value = False +# self.search_highlight_cbox_value = False +# self.search_limit_list = '' +# self = self.search_popup = QDialog(self.parent) + self.setWindowTitle(_('Search options')) + l = QGridLayout() + self.setLayout(l) + + x = QLabel(_(' '), parent=self) + x.setBuddy(parent.search_restriction) + l.addWidget(x, 1, 0, 1, 1) + + x = self.search_highlight_only = QCheckBox(self) + x.setToolTip('

'+_('When searching, highlight matched books, instead ' + 'of restricting the book list to the matches.

You can use the ' + 'N or F3 keys to go to the next match.')) + x.setChecked(highlight_cbox) + l.addWidget(x, 2, 1, 1, 1) + x = QLabel(_('Check this box if you want to see all books with search ' + 'results &highlighted'), parent=self) + x.setBuddy(self.search_highlight_only) + l.addWidget(x, 2, 0, 1, 1) + + x = self.search_limit_checkbox = QCheckBox(self) + 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.setChecked(limit_cbox) + l.addWidget(x, 3, 1, 1, 1) + x = QLabel(_('Check this box if you want non-prefixed searches to be ' + '&limited to certain fields/lookup names'), parent=self) + x.setBuddy(self.search_limit_checkbox) + l.addWidget(x, 3, 0, 1, 1) + + x = self.search_box_limit_to = MultiCompleteLineEdit(parent=self) + x.setToolTip(_('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 ' + 'above for this option to take effect.')) + x.setMinimumWidth(200) + x.set_separator(',') + x.update_items_cache(limit_field_list) + x.setText(limit_to_fields) + l.addWidget(x, 4, 1, 1, 1) + x = QLabel(_('Enter the list of fields that non-prefixed searches ' + 'are &limited to'), parent=self) + x.setBuddy(self.search_box_limit_to) + l.addWidget(x, 4, 0, 1, 1) + + buttons = QDialogButtonBox() + buttons.addButton(QDialogButtonBox.Ok) + buttons.addButton(QDialogButtonBox.Cancel) + l.addWidget(buttons, 5, 0, 1, 1) + buttons.accepted.connect(self.search_options_accepted) + buttons.rejected.connect(self.search_options_rejected) + + def search_options_accepted(self): + QDialog.accept(self) + + def search_options_rejected(self): + QDialog.reject(self) + + def values(self): + return (unicode(self.search_box_limit_to.text()), + bool(self.search_limit_checkbox.checkState()), + bool(self.search_highlight_only.checkState())) # }}} diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 2f8a747c39..48668d3376 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -238,8 +238,6 @@ class BooksModel(QAbstractTableModel): # {{{ def set_highlight_only(self, toWhat): self.highlight_only = toWhat - if self.last_search: - self.research() def get_current_highlighted_id(self): if len(self.ids_to_highlight) == 0 or self.current_highlighted_idx is None: diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 86d450567c..37ed90cc61 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -62,11 +62,6 @@ 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.opt_search_box_limit_to.set_separator(',') - self.opt_search_box_limit_to.update_items_cache( - self.gui.library_view.model().db.field_metadata.get_search_terms()) - self.current_font = None self.change_font_button.clicked.connect(self.change_font) @@ -124,7 +119,6 @@ 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 4bd514101b..2c9c2cc089 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -200,26 +200,6 @@ 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 1c94038125..827b549afb 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -376,15 +376,10 @@ class SearchBoxMixin(object): # {{{ unicode(self.search.toolTip()))) self.advanced_search_button.setStatusTip(self.advanced_search_button.toolTip()) self.clear_button.setStatusTip(self.clear_button.toolTip()) - 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'])) + + self.search_options_button.clicked.connect(self.search_options_button_clicked) + prefs['use_search_box_limit'] = dynamic.get('use_search_box_limit', False) + highlight_cbox=dynamic.get('search_highlight_only', False) def focus_search_box(self, *args): self.search.setFocus(Qt.OtherFocusReason) @@ -408,6 +403,40 @@ class SearchBoxMixin(object): # {{{ self.search.do_search() self.focus_to_library() + def search_options_button_clicked(self): + fm = self.library_view.model().db.field_metadata + ll = fm.get_search_terms() + ll = [l for l in ll if not l.startswith('@') and l not in fm.search_items] + print ll + + from calibre.gui2.layout import SearchOptions + options_box = SearchOptions(self, + limit_to_fields=prefs['search_box_limit_to'], + limit_field_list=ll, + limit_cbox=dynamic.get('use_search_box_limit', False), + highlight_cbox=dynamic.get('search_highlight_only', False)) + r = options_box.exec_() + if r: + limit_list, limit_cb, highlight_cb = options_box.values() + print limit_list, limit_cb, highlight_cb + prefs['search_box_limit_to'] = limit_list + dynamic.set('use_search_box_limit', limit_cb) + prefs['use_search_box_limit'] = limit_cb + dynamic.set('search_highlight_only', highlight_cb) + self.current_view().model().set_highlight_only(highlight_cb) + self.search.do_search() + +# self.search_highlight_only.stateChanged.connect(self.highlight_only_changed) +# self.search_highlight_only.setChecked( +# dynamic.get('search_highlight_only', False)) +# self.search_limit_checkbox.stateChanged.connect(self.search_limit_checkbox_changed) +# self.search_limit_checkbox.setVisible(True) +# chk = dynamic.get('use_search_box_limit', False) +# self.search_limit_checkbox.setChecked(chk) +# prefs['use_search_box_limit'] = chk +# self.search_limit_checkbox.setEnabled(bool(prefs['search_box_limit_to'])) + + def focus_to_library(self): self.current_view().setFocus(Qt.OtherFocusReason) @@ -416,7 +445,8 @@ class SearchBoxMixin(object): # {{{ self.current_view().model().set_highlight_only(toWhat) self.focus_to_library() - def search_limit_to_changed(self, toWhat): + def search_limit_checkbox_changed(self, toWhat): + toWhat = bool(toWhat) dynamic.set('use_search_box_limit', toWhat) prefs['use_search_box_limit'] = toWhat self.search.do_search() diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 70d0d387a5..907dd577b8 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -482,10 +482,8 @@ 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/utils/config.py b/src/calibre/utils/config.py index 976864ebd3..0ccc949260 100644 --- a/src/calibre/utils/config.py +++ b/src/calibre/utils/config.py @@ -729,7 +729,7 @@ 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='', + c.add_opt('search_box_limit_to', default='title, authors, series', 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')) From cc181a9d0bb5adde566d28f9f891cf654d48acca Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 8 Feb 2011 12:44:52 +0000 Subject: [PATCH 6/8] Move some initialization to the appropriate mixin and clean up the use of the preference --- src/calibre/gui2/init.py | 3 ++ src/calibre/gui2/layout.py | 40 +++++++++--------- src/calibre/gui2/preferences/look_feel.ui | 7 ---- src/calibre/gui2/search_box.py | 50 +++++------------------ 4 files changed, 35 insertions(+), 65 deletions(-) diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index ebd670c8fa..bfa009b2da 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -19,6 +19,7 @@ from calibre.gui2.widgets import Splitter from calibre.gui2.tag_view import TagBrowserWidget from calibre.gui2.book_details import BookDetails from calibre.gui2.notify import get_notifier +from calibre.utils.config import dynamic _keep_refs = [] @@ -64,6 +65,8 @@ class LibraryViewMixin(object): # {{{ view.verticalHeader().sectionDoubleClicked.connect(self.iactions['View'].view_specific_book) self.build_context_menus() + highlight_cbox=dynamic.get('search_highlight_only', False) + self.library_view.model().set_highlight_only(highlight_cbox) def build_context_menus(self): lm = QMenu(self) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 9ef8a546eb..e6f796da06 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' from functools import partial from PyQt4.Qt import QIcon, Qt, QWidget, QToolBar, QSize, QDialogButtonBox, \ - pyqtSignal, QToolButton, QMenu, QCheckBox, QDialog, QGridLayout, \ + pyqtSignal, QToolButton, QMenu, QCheckBox, QDialog, QGridLayout, QFrame, \ QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup @@ -19,7 +19,6 @@ from calibre.gui2 import gprefs from calibre.gui2.widgets import ComboBoxWithHelp from calibre.gui2.complete import MultiCompleteLineEdit from calibre import human_readable -from calibre.utils.config import prefs class LocationManager(QObject): # {{{ @@ -151,8 +150,6 @@ class SearchBar(QWidget): # {{{ def __init__(self, parent): QWidget.__init__(self, parent) - self.parent = parent - self._layout = l = QHBoxLayout() self.setLayout(self._layout) self._layout.setContentsMargins(0,5,0,0) @@ -162,8 +159,8 @@ class SearchBar(QWidget): # {{{ x.setObjectName("search_restriction") x.setToolTip(_('Books display will be restricted to those matching the ' 'selected saved search')) - parent.search_restriction = x l.addWidget(x) + parent.search_restriction = x x = QLabel(self) x.setObjectName("search_count") @@ -237,21 +234,26 @@ class SearchOptions(QDialog): def __init__(self, parent, limit_to_fields, limit_field_list, limit_cbox, highlight_cbox): QDialog.__init__(self, parent=parent) -# self.search_limit_possible_fields = [] -# self.search_limit_cbox_value = False -# self.search_highlight_cbox_value = False -# self.search_limit_list = '' -# self = self.search_popup = QDialog(self.parent) self.setWindowTitle(_('Search options')) l = QGridLayout() self.setLayout(l) - x = QLabel(_(' '), parent=self) - x.setBuddy(parent.search_restriction) - l.addWidget(x, 1, 0, 1, 1) + x = QLabel(_('Use this box to change search options related to how ' + 'results are displayed and which fields are searched. ' + 'Changes will be remembered across calibre restarts. ' + 'When you press OK, the last search will be redone using ' + 'the new option values.'), + parent=self) + x.setWordWrap(True) + l.addWidget(x, 0, 0, 1, 2) + + line = QFrame(self) + line.setFrameShape(QFrame.HLine) + line.setFrameShadow(QFrame.Sunken) + l.addWidget(line, 1, 0, 1, 2) x = self.search_highlight_only = QCheckBox(self) - x.setToolTip('

'+_('When searching, highlight matched books, instead ' + x.setToolTip('

'+_('When searching, highlight matched books instead ' 'of restricting the book list to the matches.

You can use the ' 'N or F3 keys to go to the next match.')) x.setChecked(highlight_cbox) @@ -268,8 +270,8 @@ class SearchOptions(QDialog): 'Preferences -> Look and Feel -> Limit non-prefixed searches to columns.')) x.setChecked(limit_cbox) l.addWidget(x, 3, 1, 1, 1) - x = QLabel(_('Check this box if you want non-prefixed searches to be ' - '&limited to certain fields/lookup names'), parent=self) + x = QLabel(_('Check this box if you want non-&prefixed searches to be ' + 'limited to certain fields/lookup names'), parent=self) x.setBuddy(self.search_limit_checkbox) l.addWidget(x, 3, 0, 1, 1) @@ -284,15 +286,15 @@ class SearchOptions(QDialog): x.update_items_cache(limit_field_list) x.setText(limit_to_fields) l.addWidget(x, 4, 1, 1, 1) - x = QLabel(_('Enter the list of fields that non-prefixed searches ' - 'are &limited to'), parent=self) + x = QLabel(_('Enter the list of &columns that non-prefixed searches ' + 'are limited to'), parent=self) x.setBuddy(self.search_box_limit_to) l.addWidget(x, 4, 0, 1, 1) buttons = QDialogButtonBox() buttons.addButton(QDialogButtonBox.Ok) buttons.addButton(QDialogButtonBox.Cancel) - l.addWidget(buttons, 5, 0, 1, 1) + l.addWidget(buttons, 5, 0, 1, 2) buttons.accepted.connect(self.search_options_accepted) buttons.rejected.connect(self.search_options_rejected) diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 2c9c2cc089..2223167068 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -285,13 +285,6 @@ up into sub-categories. If the partition method is set to disable, this value is - - - MultiCompleteLineEdit - QLineEdit -

calibre.gui2.complete.h
- - diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 827b549afb..b9344a7782 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -376,10 +376,7 @@ class SearchBoxMixin(object): # {{{ unicode(self.search.toolTip()))) self.advanced_search_button.setStatusTip(self.advanced_search_button.toolTip()) self.clear_button.setStatusTip(self.clear_button.toolTip()) - self.search_options_button.clicked.connect(self.search_options_button_clicked) - prefs['use_search_box_limit'] = dynamic.get('use_search_box_limit', False) - highlight_cbox=dynamic.get('search_highlight_only', False) def focus_search_box(self, *args): self.search.setFocus(Qt.OtherFocusReason) @@ -404,53 +401,28 @@ class SearchBoxMixin(object): # {{{ self.focus_to_library() def search_options_button_clicked(self): + from calibre.gui2.layout import SearchOptions + fm = self.library_view.model().db.field_metadata ll = fm.get_search_terms() ll = [l for l in ll if not l.startswith('@') and l not in fm.search_items] - print ll - - from calibre.gui2.layout import SearchOptions - options_box = SearchOptions(self, - limit_to_fields=prefs['search_box_limit_to'], - limit_field_list=ll, - limit_cbox=dynamic.get('use_search_box_limit', False), - highlight_cbox=dynamic.get('search_highlight_only', False)) + options_box = SearchOptions(parent=self, + limit_to_fields=prefs['search_box_limit_to'], + limit_field_list=ll, + limit_cbox=prefs['use_search_box_limit'], + highlight_cbox=dynamic.get('search_highlight_only', False)) r = options_box.exec_() if r: - limit_list, limit_cb, highlight_cb = options_box.values() - print limit_list, limit_cb, highlight_cb + limit_list, limit_cbox, highlight_cbox = options_box.values() prefs['search_box_limit_to'] = limit_list - dynamic.set('use_search_box_limit', limit_cb) - prefs['use_search_box_limit'] = limit_cb - dynamic.set('search_highlight_only', highlight_cb) - self.current_view().model().set_highlight_only(highlight_cb) + prefs['use_search_box_limit'] = limit_cbox + dynamic.set('search_highlight_only', highlight_cbox) + self.current_view().model().set_highlight_only(highlight_cbox) self.search.do_search() -# self.search_highlight_only.stateChanged.connect(self.highlight_only_changed) -# self.search_highlight_only.setChecked( -# dynamic.get('search_highlight_only', False)) -# self.search_limit_checkbox.stateChanged.connect(self.search_limit_checkbox_changed) -# self.search_limit_checkbox.setVisible(True) -# chk = dynamic.get('use_search_box_limit', False) -# self.search_limit_checkbox.setChecked(chk) -# prefs['use_search_box_limit'] = chk -# self.search_limit_checkbox.setEnabled(bool(prefs['search_box_limit_to'])) - - def focus_to_library(self): self.current_view().setFocus(Qt.OtherFocusReason) - def highlight_only_changed(self, toWhat): - dynamic.set('search_highlight_only', toWhat) - self.current_view().model().set_highlight_only(toWhat) - self.focus_to_library() - - def search_limit_checkbox_changed(self, toWhat): - toWhat = bool(toWhat) - dynamic.set('use_search_box_limit', toWhat) - prefs['use_search_box_limit'] = toWhat - self.search.do_search() - # }}} class SavedSearchBoxMixin(object): # {{{ From 900ff9b93ac0e0673b8a4728e5629a831610564d Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 8 Feb 2011 12:51:00 +0000 Subject: [PATCH 7/8] Minor changes to tooltips, etc --- src/calibre/gui2/layout.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index e6f796da06..efac3e1232 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -201,7 +201,7 @@ class SearchBar(QWidget): # {{{ x.setIcon(QIcon(I('config.png'))) x.setObjectName("search_option_button") l.addWidget(x) - x.setToolTip(_("Change search highlighting and field limit options")) + x.setToolTip(_("Change search highlighting and column limit options")) x.setVisible(False) x = parent.saved_search = SavedSearchBox(self) @@ -239,7 +239,7 @@ class SearchOptions(QDialog): self.setLayout(l) x = QLabel(_('Use this box to change search options related to how ' - 'results are displayed and which fields are searched. ' + 'results are displayed and which columns are searched. ' 'Changes will be remembered across calibre restarts. ' 'When you press OK, the last search will be redone using ' 'the new option values.'), @@ -253,25 +253,25 @@ class SearchOptions(QDialog): l.addWidget(line, 1, 0, 1, 2) x = self.search_highlight_only = QCheckBox(self) - x.setToolTip('

'+_('When searching, highlight matched books instead ' - 'of restricting the book list to the matches.

You can use the ' + x.setToolTip('

'+_('When searching, show all books with search results ' + 'highlight instead of showing only the matches.

You can use the ' 'N or F3 keys to go to the next match.')) x.setChecked(highlight_cbox) l.addWidget(x, 2, 1, 1, 1) x = QLabel(_('Check this box if you want to see all books with search ' - 'results &highlighted'), parent=self) + 'results &highlighted instead of only the matched books'), + parent=self) x.setBuddy(self.search_highlight_only) l.addWidget(x, 2, 0, 1, 1) x = self.search_limit_checkbox = QCheckBox(self) 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.')) + 'limit the columns searched to those named in the text box below.')) x.setChecked(limit_cbox) l.addWidget(x, 3, 1, 1, 1) x = QLabel(_('Check this box if you want non-&prefixed searches to be ' - 'limited to certain fields/lookup names'), parent=self) + 'limited to certain columns/lookup names'), parent=self) x.setBuddy(self.search_limit_checkbox) l.addWidget(x, 3, 0, 1, 1) From 9b9eeb5265a4ac4c8da1c1bd5649d1f93e85753f Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 8 Feb 2011 13:07:35 +0000 Subject: [PATCH 8/8] Add some text about limits interacting with saved searches --- src/calibre/gui2/layout.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index efac3e1232..6ad5551de7 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -238,11 +238,15 @@ class SearchOptions(QDialog): l = QGridLayout() self.setLayout(l) - x = QLabel(_('Use this box to change search options related to how ' + x = QLabel('

'+_('Use this box to change search options related to how ' 'results are displayed and which columns are searched. ' 'Changes will be remembered across calibre restarts. ' 'When you press OK, the last search will be redone using ' - 'the new option values.'), + 'the new option values.')+'

'+_('Note: the limit option ' + 'below affects all searches, including saved searches ' + 'and, by extension, search restrictions. For this reason ' + 'it is usually better to use prefixes in saved searches, ' + 'for example series:someword instead of simply someword.'), parent=self) x.setWordWrap(True) l.addWidget(x, 0, 0, 1, 2)