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('?a[^>]*>', '', html)
+
+ def check_paragraph(content):
+ content = re.sub('\s*?span[^>]*>\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
+
+
+
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
-
-
-
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)