From 8c9c5d35e479ef3267e95b62c08c96e4a4588603 Mon Sep 17 00:00:00 2001 From: ldolse Date: Mon, 7 Feb 2011 01:50:17 +0800 Subject: [PATCH 01/15] first pass at abbyy processor --- src/calibre/ebooks/conversion/utils.py | 109 +++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py index c0c2ee8978..e32928fd95 100644 --- a/src/calibre/ebooks/conversion/utils.py +++ b/src/calibre/ebooks/conversion/utils.py @@ -11,6 +11,7 @@ from calibre.ebooks.conversion.preprocess import DocAnalysis, Dehyphenator from calibre.utils.logging import default_log from calibre.utils.wordcount import get_wordcount_obj + class HeuristicProcessor(object): def __init__(self, extra_opts=None, log=None): @@ -38,6 +39,9 @@ class HeuristicProcessor(object): def is_pdftohtml(self, src): return '' in src[:1000] + def is_abbyy(self, src): + return '[^\"]*?);?">)(?P.*?)(?P

)|(?P]*>))', re.IGNORECASE) + empty_paragraph = '\n

\n' + previous_line_bottom_margin = False + self.in_blockquote = False + self.previous_was_paragraph = False + print "detected ABBYY content, running through processor" + html = re.sub(']*>', '', html) + + def check_paragraph(content): + content = re.sub('\s*]*>\s*', '', content) + if re.match('.*[\"\'.!?:]$', content): + #print "detected this as a paragraph" + return True + else: + return False + + def convert_styles(match): + #print "raw styles are: "+match.group('styles') + content = match.group('content') + #print "raw content is: "+match.group('content') + image = match.group('image') + + is_paragraph = False + text_align = '' + text_indent = '' + paragraph_before = '' + paragraph_after = '' + blockquote_open = '\n
\n' + blockquote_close = '
\n' + indented_text = 'text-indent:3%;' + blockquote_open_loop = '' + blockquote_close_loop = '' + debugabby = False + + if image: + debugabby = True + if self.in_blockquote: + self.in_blockquote = False + blockquote_close_loop = blockquote_close + self.previous_was_paragraph = False + return blockquote_close_loop+'\n'+image+'\n' + else: + styles = match.group('styles').split(';') + is_paragraph = check_paragraph(content) + #print "styles for this line are: "+str(styles) + split_styles = [] + for style in styles: + #print "style is: "+str(style) + newstyle = style.split(':') + #print "newstyle is: "+str(newstyle) + split_styles.append(newstyle) + styles = split_styles + for style, setting in styles: + if style == 'text-align' and setting != 'left': + text_align = style+':'+setting+';' + if style == 'text-indent': + setting = int(re.sub('\s*pt\s*', '', setting)) + if 9 < setting < 14: + text_indent = indented_text + else: + text_indent = style+':'+str(setting)+'pt;' + if style == 'padding': + setting = re.sub('pt', '', setting).split(' ') + if int(setting[1]) < 16 and int(setting[3]) < 16: + if self.in_blockquote: + debugabby = True + if is_paragraph: + self.in_blockquote = False + blockquote_close_loop = blockquote_close + if int(setting[3]) > 8 and text_indent == '': + text_indent = indented_text + if int(setting[0]) > 5: + paragraph_before = empty_paragraph + if int(setting[2]) > 5: + paragraph_after = empty_paragraph + elif not self.in_blockquote and self.previous_was_paragraph: + debugabby = True + self.in_blockquote = True + blockquote_open_loop = blockquote_open + if debugabby: + print '\n\n******\n' + print 'padding top is: '+str(setting[0]) + print 'padding right is: '+str(setting[1]) + print 'padding bottom is: '+str(setting[2]) + print 'padding left is: '+str(setting[3]) + + #print "text-align is: "+str(text_align) + print "\n***\nline is:\n "+str(match.group(0))+'\n' + if debugabby: + #print "this line is a paragraph = "+str(is_paragraph)+", previous line was "+str(self.previous_was_paragraph) + print "styles for this line were: "+str(styles) + print 'newline is: \n'+blockquote_open_loop+blockquote_close_loop+paragraph_before+'

'+content+'

'+paragraph_after+'\n\n\n\n\n' + print "is_paragraph is "+str(is_paragraph)+", previous_was_paragraph is "+str(self.previous_was_paragraph) + self.previous_was_paragraph = is_paragraph + print "previous_was_paragraph is now set to "+str(self.previous_was_paragraph)+"\n\n\n" + return blockquote_open_loop+blockquote_close_loop+paragraph_before+'

'+content+'

'+paragraph_after + + html = abbyy_line.sub(convert_styles, html) + return html + def __call__(self, html): self.log.debug("********* Heuristic processing HTML *********") @@ -530,6 +635,10 @@ class HeuristicProcessor(object): self.log.warn("flow is too short, not running heuristics") return html + is_abbyy = self.is_abbyy(html) + if is_abbyy: + html = self.abbyy_processor(html) + # Arrange line feeds and

tags so the line_length and no_markup functions work correctly html = self.arrange_htm_line_endings(html) #self.dump(html, 'after_arrange_line_endings') From 1d8e98122c743cd697c9d580b97d47eed408954a Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 7 Feb 2011 11:54:07 +0000 Subject: [PATCH 02/15] 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 03/15] 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 04/15] 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 05/15] 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 d080ac85d6053af7b472d049ecba70ea3ae4a29c Mon Sep 17 00:00:00 2001 From: ldolse Date: Tue, 8 Feb 2011 13:26:05 +0800 Subject: [PATCH 06/15] included divs in the fix indents option --- src/calibre/ebooks/conversion/utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py index d075390e8e..a87392b54f 100644 --- a/src/calibre/ebooks/conversion/utils.py +++ b/src/calibre/ebooks/conversion/utils.py @@ -77,22 +77,23 @@ class HeuristicProcessor(object): def insert_indent(self, match): pstyle = match.group('formatting') + tag = match.group('tagtype') span = match.group('span') self.found_indents = self.found_indents + 1 if pstyle: - if pstyle.lower().find('style'): + if pstyle.lower().find('style') != -1: pstyle = re.sub(r'"$', '; text-indent:3%"', pstyle) else: pstyle = pstyle+' style="text-indent:3%"' if not span: - return '

' + return '<'+tag+' '+pstyle+'>' else: - return '

'+span + return '<'+tag+' '+pstyle+'>'+span else: if not span: - return '

' + return '<'+tag+' style="text-indent:3%">' else: - return '

'+span + return '<'+tag+' style="text-indent:3%">'+span def no_markup(self, raw, percent): ''' @@ -365,7 +366,7 @@ class HeuristicProcessor(object): return html def fix_nbsp_indents(self, html): - txtindent = re.compile(ur'[^>]*)>\s*(?P(]*>\s*)+)?\s*(\u00a0){2,}', re.IGNORECASE) + txtindent = re.compile(ur'<(?Pp|div)(?P[^>]*)>\s*(?P(]*>\s*)+)?\s*(\u00a0){2,}', re.IGNORECASE) html = txtindent.sub(self.insert_indent, html) if self.found_indents > 1: self.log.debug("replaced "+unicode(self.found_indents)+ " nbsp indents with inline styles") From 8818bccf1d201addecf3a9a596a35260435bae5e Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 8 Feb 2011 12:03:03 +0000 Subject: [PATCH 07/15] 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 08/15] 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 09/15] 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 10/15] 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) From c30e5bcaee6cc469d93977edf558d0435ac60e8a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 8 Feb 2011 09:07:30 -0700 Subject: [PATCH 11/15] Update various French Belgian recipes --- resources/recipes/cinebel_be.recipe | 15 +++++++++++---- resources/recipes/dhnet_be.recipe | 12 ++++++++++-- resources/recipes/lalibre_be.recipe | 21 ++++++++++++++------- resources/recipes/lameuse_be.recipe | 11 ++++++++--- resources/recipes/lavenir_be.recipe | 12 +++++++++--- resources/recipes/lesoir_be.recipe | 5 +++-- 6 files changed, 55 insertions(+), 21 deletions(-) diff --git a/resources/recipes/cinebel_be.recipe b/resources/recipes/cinebel_be.recipe index ec76bfc894..024050eb67 100644 --- a/resources/recipes/cinebel_be.recipe +++ b/resources/recipes/cinebel_be.recipe @@ -1,7 +1,7 @@ #!/usr/bin/env python __license__ = 'GPL v3' -__copyright__ = '2008, Lionel Bergeret ' +__copyright__ = '2008-2011, Lionel Bergeret ' ''' cinebel.be ''' @@ -14,14 +14,14 @@ class Cinebel(BasicNewsRecipe): description = u'Cinema news from Belgium in French' publisher = u'cinebel.be' category = 'news, cinema, movie, Belgium' - oldest_article = 3 - encoding = 'utf8' - language = 'fr_BE' + oldest_article = 15 + language = 'fr' max_articles_per_feed = 20 no_stylesheets = True use_embedded_content = False timefmt = ' [%d %b %Y]' + filterDuplicates = True keep_only_tags = [ dict(name = 'span', attrs = {'class': 'movieMainTitle'}) @@ -35,6 +35,13 @@ class Cinebel(BasicNewsRecipe): ,(u'Top 10' , u'http://www.cinebel.be/Servlets/RssServlet?languageCode=fr&rssType=2' ) ] + def preprocess_html(self, soup): + for alink in soup.findAll('a'): + if alink.has_key('href'): + tstr = "Site officiel: " + alink['href'] + alink.replaceWith(tstr) + return soup + def get_cover_url(self): cover_url = 'http://www.cinebel.be/portal/resources/common/logo_index.gif' return cover_url diff --git a/resources/recipes/dhnet_be.recipe b/resources/recipes/dhnet_be.recipe index ef4d1736e3..d55470a765 100644 --- a/resources/recipes/dhnet_be.recipe +++ b/resources/recipes/dhnet_be.recipe @@ -1,7 +1,7 @@ #!/usr/bin/env python __license__ = 'GPL v3' -__copyright__ = '2008, Lionel Bergeret ' +__copyright__ = '2008-2011, Lionel Bergeret ' ''' dhnet.be ''' @@ -16,7 +16,8 @@ class DHNetBe(BasicNewsRecipe): publisher = u'dhnet.be' category = 'news, Belgium' oldest_article = 3 - language = 'fr_BE' + language = 'fr' + masthead_url = 'http://www.dhnet.be/images/homepage_logo_dh.gif' max_articles_per_feed = 20 no_stylesheets = True @@ -34,6 +35,13 @@ class DHNetBe(BasicNewsRecipe): ,(u'La Une Info' , u'http://www.dhnet.be/rss/dhinfos/' ) ] + def preprocess_html(self, soup): + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup + def get_cover_url(self): cover_url = strftime('http://pdf-online.dhnet.be/pdfonline/image/%Y%m%d/dh_%Y%m%d_nam_infoge_001.pdf.L.jpg') return cover_url diff --git a/resources/recipes/lalibre_be.recipe b/resources/recipes/lalibre_be.recipe index 53e346bf12..a6356be828 100644 --- a/resources/recipes/lalibre_be.recipe +++ b/resources/recipes/lalibre_be.recipe @@ -1,7 +1,7 @@ #!/usr/bin/env python __license__ = 'GPL v3' -__copyright__ = '2008, Lionel Bergeret ' +__copyright__ = '2008-2011, Lionel Bergeret ' ''' lalibre.be ''' @@ -16,18 +16,18 @@ class LaLibre(BasicNewsRecipe): publisher = u'lalibre.be' category = 'news, Belgium' oldest_article = 3 - language = 'fr_BE' + language = 'fr' + masthead_url = 'http://www.lalibre.be/img/logoLaLibre.gif' max_articles_per_feed = 20 no_stylesheets = True use_embedded_content = False timefmt = ' [%d %b %Y]' - keep_only_tags = [ - dict(name = 'div', attrs = {'id': 'articleHat'}) - ,dict(name = 'p', attrs = {'id': 'publicationDate'}) - ,dict(name = 'div', attrs = {'id': 'articleText'}) - ] + remove_tags_before = dict(name = 'div', attrs = {'class': 'extraMainContent'}) + remove_tags_after = dict(name = 'div', attrs = {'id': 'articleText'}) + + remove_tags = [dict(name = 'div', attrs = {'id': 'strongArticleLinks'})] feeds = [ (u'L\'actu' , u'http://www.lalibre.be/rss/?section=10' ) @@ -38,6 +38,13 @@ class LaLibre(BasicNewsRecipe): ,(u'Societe' , u'http://www.lalibre.be/rss/?section=12' ) ] + def preprocess_html(self, soup): + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup + def get_cover_url(self): cover_url = strftime('http://pdf-online.lalibre.be/pdfonline/image/%Y%m%d/llb_%Y%m%d_nam_libre_001.pdf.L.jpg') return cover_url diff --git a/resources/recipes/lameuse_be.recipe b/resources/recipes/lameuse_be.recipe index 03b7f84a5f..7166d01103 100644 --- a/resources/recipes/lameuse_be.recipe +++ b/resources/recipes/lameuse_be.recipe @@ -1,7 +1,7 @@ #!/usr/bin/env python __license__ = 'GPL v3' -__copyright__ = '2008, Lionel Bergeret ' +__copyright__ = '2008-2011, Lionel Bergeret ' ''' lameuse.be ''' @@ -16,8 +16,8 @@ class LaMeuse(BasicNewsRecipe): publisher = u'lameuse.be' category = 'news, Belgium' oldest_article = 3 - encoding = 'utf8' - language = 'fr_BE' + language = 'fr' + masthead_url = 'http://www.lameuse.be/images/SPV3/logo_header_LM.gif' max_articles_per_feed = 20 no_stylesheets = True @@ -32,6 +32,11 @@ class LaMeuse(BasicNewsRecipe): dict(name = 'div', attrs = {'class': 'sb-group'}) ,dict(name = 'div', attrs = {'id': 'share'}) ,dict(name = 'div', attrs = {'id': 'commentaires'}) + ,dict(name = 'ul', attrs = {'class': 'right liensutiles'}) + ,dict(name = 'ul', attrs = {'class': 'bas liensutiles'}) + ,dict(name = 'p', attrs = {'class': 'ariane'}) + ,dict(name = 'div', attrs = {'class': 'inner-bloc'}) + ,dict(name = 'div', attrs = {'class': 'block-01'}) ] feeds = [ diff --git a/resources/recipes/lavenir_be.recipe b/resources/recipes/lavenir_be.recipe index 68be449ae5..4c2c8a00a2 100644 --- a/resources/recipes/lavenir_be.recipe +++ b/resources/recipes/lavenir_be.recipe @@ -1,7 +1,7 @@ #!/usr/bin/env python __license__ = 'GPL v3' -__copyright__ = '2008, Lionel Bergeret ' +__copyright__ = '2008-2011, Lionel Bergeret ' ''' lavenir.net ''' @@ -15,8 +15,7 @@ class LAvenir(BasicNewsRecipe): publisher = u'lavenir.net' category = 'news, Belgium' oldest_article = 3 - encoding = 'utf8' - language = 'fr_BE' + language = 'fr' max_articles_per_feed = 20 no_stylesheets = True @@ -35,6 +34,13 @@ class LAvenir(BasicNewsRecipe): ,(u'Societe' , u'http://www.lavenir.net/rss.aspx?foto=1&intro=1§ion=info&info=12e1a2f4-7e03-4cf1-afec-016869072317' ) ] + def preprocess_html(self, soup): + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup + def get_cover_url(self): cover_url = 'http://www.lavenir.net/extra/Static/journal/Pdf/1/UNE_Nationale.PDF' return cover_url diff --git a/resources/recipes/lesoir_be.recipe b/resources/recipes/lesoir_be.recipe index 6b6891c3b8..64fd2fa65c 100644 --- a/resources/recipes/lesoir_be.recipe +++ b/resources/recipes/lesoir_be.recipe @@ -1,7 +1,7 @@ #!/usr/bin/env python __license__ = 'GPL v3' -__copyright__ = '2008, Lionel Bergeret ' +__copyright__ = '2008-2011, Lionel Bergeret ' ''' lesoir.be ''' @@ -16,7 +16,8 @@ class LeSoirBe(BasicNewsRecipe): publisher = u'lesoir.be' category = 'news, Belgium' oldest_article = 3 - language = 'fr_BE' + language = 'fr' + masthead_url = 'http://pdf.lesoir.be/pdf/images/SOIR//logo.gif' max_articles_per_feed = 20 no_stylesheets = True From 326ebb9bcbececee9cd37797afa9a899df5f63b3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 8 Feb 2011 11:39:14 -0700 Subject: [PATCH 12/15] Turn search as you type off by default --- src/calibre/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py index 88197d423d..a2ceaced68 100644 --- a/src/calibre/utils/config.py +++ b/src/calibre/utils/config.py @@ -733,7 +733,7 @@ def _prefs(): 'prefixes, as for example, Red instead of title:Red, ' 'limit the columns searched to those named below.')) c.add_opt('limit_search_columns_to', - default=['title', 'authors', 'tags', 'series'], + default=['title', 'authors', 'tags', 'series', 'publisher'], help=_('Choose columns to be searched when not using prefixes, ' 'as for example, when searching for Redd instead of ' 'title:Red. Enter a list of search/lookup names ' From ee8ab011ebd70a7ef3df321e714b70abc39cff89 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 8 Feb 2011 20:06:05 +0000 Subject: [PATCH 13/15] Remove spacers from new search preferences dialog groupbox --- src/calibre/gui2/preferences/search.ui | 36 ++++---------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/src/calibre/gui2/preferences/search.ui b/src/calibre/gui2/preferences/search.ui index 69426a3728..360059ce56 100644 --- a/src/calibre/gui2/preferences/search.ui +++ b/src/calibre/gui2/preferences/search.ui @@ -44,14 +44,14 @@ - + &Limit the searched metadata - + &Columns that non-prefixed searches are limited to: @@ -61,7 +61,7 @@ - + @@ -74,32 +74,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -110,8 +84,8 @@ - 20 - 40 + 0 + 0 From 196060f8d27316d35d8ac1a12ce1ab1f1961c041 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 8 Feb 2011 13:27:39 -0700 Subject: [PATCH 14/15] Fix #8873 (Can't get Calibre to recognize Huawei Ideos S7 tablet) --- src/calibre/devices/android/driver.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 11d636791b..e9021461eb 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -62,6 +62,9 @@ class ANDROID(USBMS): # Archos 0x0e79 : { 0x1419: [0x0216], 0x1420 : [0x0216], 0x1422 : [0x0216]}, + # Huawei + 0x45e : { 0x00e1 : [0x007], }, + } EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books'] EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of directories to ' @@ -71,12 +74,13 @@ class ANDROID(USBMS): VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER', 'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX', 'GOOGLE', 'ARCHOS', - 'TELECHIP'] + 'TELECHIP', 'HUAWEI', ] WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE', '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897', 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE', - 'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H'] + 'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H', + 'IDEOS_TABLET'] WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'A70S', 'A101IT'] From bdd690df127cba1f785f6bd3da2c5d649d75a5f6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 8 Feb 2011 15:46:02 -0700 Subject: [PATCH 15/15] Completion: Restore adding of comma at end after completion for tags type fields. Add a tweak to control if an & is added after completion for author type fields --- resources/default_tweaks.py | 5 +- src/calibre/gui2/complete.py | 65 ++++++++++++--------- src/calibre/gui2/convert/metadata.py | 2 + src/calibre/gui2/dialogs/add_empty_book.py | 2 + src/calibre/gui2/dialogs/metadata_bulk.py | 1 + src/calibre/gui2/dialogs/metadata_single.py | 1 + src/calibre/gui2/dialogs/search.py | 2 + src/calibre/gui2/library/delegates.py | 2 + src/calibre/gui2/metadata/basic_widgets.py | 1 + 9 files changed, 50 insertions(+), 31 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 893c8b6b6a..81088da520 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -32,9 +32,10 @@ series_index_auto_increment = 'next' # Should the completion separator be append # to the end of the completed text to -# automatically begin a new completion operation. +# automatically begin a new completion operation +# for authors. # Can be either True or False -completer_append_separator = False +authors_completer_append_separator = False # The algorithm used to copy author to author_sort diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index 58020f924a..2eb97b128d 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -9,7 +9,6 @@ __docformat__ = 'restructuredtext en' from PyQt4.Qt import QLineEdit, QAbstractListModel, Qt, \ QApplication, QCompleter -from calibre.utils.config import tweaks from calibre.utils.icu import sort_key, lower from calibre.gui2 import NONE from calibre.gui2.widgets import EnComboBox @@ -55,6 +54,8 @@ class MultiCompleteLineEdit(QLineEdit): self.sep = ',' self.space_before_sep = False + self.add_separator = True + self.original_cursor_pos = None self._model = CompleteModel(parent=self) self._completer = c = QCompleter(self._model, self) @@ -82,6 +83,9 @@ class MultiCompleteLineEdit(QLineEdit): def set_space_before_sep(self, space_before): self.space_before_sep = space_before + def set_add_separator(self, what): + self.add_separator = bool(what) + # }}} def item_entered(self, idx): @@ -93,7 +97,7 @@ class MultiCompleteLineEdit(QLineEdit): def update_completions(self): ' Update the list of completions ' - cpos = self.cursorPosition() + self.original_cursor_pos = cpos = self.cursorPosition() text = unicode(self.text()) prefix = text[:cpos] self.current_prefix = prefix @@ -103,38 +107,38 @@ class MultiCompleteLineEdit(QLineEdit): self._completer.setCompletionPrefix(complete_prefix) def get_completed_text(self, text): - ''' - Get completed text from current cursor position and the completion - text - ''' + 'Get completed text in before and after parts' if self.sep is None: - return -1, text + return text, '' else: - cursor_pos = self.cursorPosition() - before_text = unicode(self.text())[:cursor_pos] - after_text = unicode(self.text())[cursor_pos:] - prefix_len = len(before_text.split(self.sep)[-1].lstrip()) - if tweaks['completer_append_separator']: - prefix_len = len(before_text.split(self.sep)[-1].lstrip()) - completed_text = before_text[:cursor_pos - prefix_len] + text + self.sep + ' ' + after_text - prefix_len = prefix_len - len(self.sep) - 1 - if prefix_len < 0: - prefix_len = 0 + cursor_pos = self.original_cursor_pos + if cursor_pos is None: + cursor_pos = self.cursorPosition() + self.original_cursor_pos = None + # Split text + curtext = unicode(self.text()) + before_text = curtext[:cursor_pos] + after_text = curtext[cursor_pos:].rstrip() + # Remove the completion prefix from the before text + before_text = self.sep.join(before_text.split(self.sep)[:-1]).rstrip() + if before_text: + # Add the separator to the end of before_text + if self.space_before_sep: + before_text += ' ' + before_text += self.sep + ' ' + if self.add_separator or after_text: + # Add separator to the end of completed text + if self.space_before_sep: + text = text.rstrip() + ' ' + completed_text = text + self.sep + ' ' else: - prefix_len = len(before_text.split(self.sep)[-1].lstrip()) - completed_text = before_text[:cursor_pos - prefix_len] + text + after_text - return prefix_len, completed_text - + completed_text = text + return before_text + completed_text, after_text def completion_selected(self, text): - prefix_len, ctext = self.get_completed_text(unicode(text)) - if self.sep is None: - self.setText(ctext) - self.setCursorPosition(len(ctext)) - else: - cursor_pos = self.cursorPosition() - self.setText(ctext) - self.setCursorPosition(cursor_pos - prefix_len + len(text)) + before_text, after_text = self.get_completed_text(unicode(text)) + self.setText(before_text + after_text) + self.setCursorPosition(len(before_text)) @dynamic_property def all_items(self): @@ -164,6 +168,9 @@ class MultiCompleteComboBox(EnComboBox): def set_space_before_sep(self, space_before): self.lineEdit().set_space_before_sep(space_before) + def set_add_separator(self, what): + self.lineEdit().set_add_separator(what) + if __name__ == '__main__': diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index 81274f25a8..95dd7623c9 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -19,6 +19,7 @@ from calibre.ptempfile import PersistentTemporaryFile from calibre.gui2.convert import Widget from calibre.utils.icu import sort_key from calibre.library.comments import comments_to_html +from calibre.utils.config import tweaks def create_opf_file(db, book_id): mi = db.get_metadata(book_id, index_is_id=True) @@ -108,6 +109,7 @@ class MetadataWidget(Widget, Ui_Form): all_authors.sort(key=lambda x : sort_key(x[1])) self.author.set_separator('&') self.author.set_space_before_sep(True) + self.author.set_add_separator(tweaks['authors_completer_append_separator']) self.author.update_items_cache(self.db.all_author_names()) for i in all_authors: diff --git a/src/calibre/gui2/dialogs/add_empty_book.py b/src/calibre/gui2/dialogs/add_empty_book.py index 9e5fb07308..d4990e14d4 100644 --- a/src/calibre/gui2/dialogs/add_empty_book.py +++ b/src/calibre/gui2/dialogs/add_empty_book.py @@ -9,6 +9,7 @@ from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \ from calibre.ebooks.metadata import authors_to_string, string_to_authors from calibre.utils.icu import sort_key from calibre.gui2.complete import MultiCompleteComboBox +from calibre.utils.config import tweaks class AddEmptyBookDialog(QDialog): @@ -69,6 +70,7 @@ class AddEmptyBookDialog(QDialog): self.authors_combo.set_separator('&') self.authors_combo.set_space_before_sep(True) + self.authors_combo.set_add_separator(tweaks['authors_completer_append_separator']) self.authors_combo.update_items_cache(db.all_author_names()) @property diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index e355144544..9ad61d515b 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -781,6 +781,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.authors.set_separator('&') self.authors.set_space_before_sep(True) + self.authors.set_add_separator(tweaks['authors_completer_append_separator']) self.authors.update_items_cache(self.db.all_author_names()) def initialize_series(self): diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 52d263fe36..d95c905f42 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -735,6 +735,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.authors.set_separator('&') self.authors.set_space_before_sep(True) + self.authors.set_add_separator(tweaks['authors_completer_append_separator']) self.authors.update_items_cache(self.db.all_author_names()) def initialize_series(self): diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index 9c91446f3c..b4976e2657 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -9,6 +9,7 @@ from calibre.gui2.dialogs.search_ui import Ui_Dialog from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH from calibre.gui2 import gprefs from calibre.utils.icu import sort_key +from calibre.utils.config import tweaks box_values = {} @@ -31,6 +32,7 @@ class SearchDialog(QDialog, Ui_Dialog): self.authors_box.setEditText('') self.authors_box.set_separator('&') self.authors_box.set_space_before_sep(True) + self.authors_box.set_add_separator(tweaks['authors_completer_append_separator']) self.authors_box.update_items_cache(db.all_author_names()) all_series = db.all_series() diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index fed2e42470..87da6818eb 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -177,6 +177,8 @@ class CompleteDelegate(QStyledItemDelegate): # {{{ editor = MultiCompleteLineEdit(parent) editor.set_separator(self.sep) editor.set_space_before_sep(self.space_before_sep) + if self.sep == '&': + editor.set_add_separator(tweaks['authors_completer_append_separator']) if not index.model().is_custom_column(col): all_items = getattr(self.db, self.items_func_name)() else: diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index f9058fc333..a135176daf 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -177,6 +177,7 @@ class AuthorsEdit(MultiCompleteComboBox): self.set_separator('&') self.set_space_before_sep(True) + self.set_add_separator(tweaks['authors_completer_append_separator']) self.update_items_cache(db.all_author_names()) au = db.authors(id_, index_is_id=True)