Merge from trunk

This commit is contained in:
Charles Haley 2011-02-08 18:41:42 +00:00
commit a3c28f669b
22 changed files with 399 additions and 182 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Lionel Bergeret <lbergeret at gmail.com>'
__copyright__ = '2008-2011, Lionel Bergeret <lbergeret at gmail.com>'
'''
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

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Lionel Bergeret <lbergeret at gmail.com>'
__copyright__ = '2008-2011, Lionel Bergeret <lbergeret at gmail.com>'
'''
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

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Lionel Bergeret <lbergeret at gmail.com>'
__copyright__ = '2008-2011, Lionel Bergeret <lbergeret at gmail.com>'
'''
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

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Lionel Bergeret <lbergeret at gmail.com>'
__copyright__ = '2008-2011, Lionel Bergeret <lbergeret at gmail.com>'
'''
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 = [

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Lionel Bergeret <lbergeret at gmail.com>'
__copyright__ = '2008-2011, Lionel Bergeret <lbergeret at gmail.com>'
'''
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&section=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

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Lionel Bergeret <lbergeret at gmail.com>'
__copyright__ = '2008-2011, Lionel Bergeret <lbergeret at gmail.com>'
'''
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

View File

@ -791,6 +791,17 @@ class Toolbar(PreferencesPlugin):
description = _('Customize the toolbars and context menus, changing which'
' actions are available in each')
class Search(PreferencesPlugin):
name = 'Search'
icon = I('search.png')
gui_name = _('Customize searching')
category = 'Interface'
gui_category = _('Interface')
category_order = 1
name_order = 5
config_widget = 'calibre.gui2.preferences.search'
description = _('Customize the way searching for books works in calibre')
class InputOptions(PreferencesPlugin):
name = 'Input Options'
icon = I('arrow-down.png')
@ -941,7 +952,7 @@ class Misc(PreferencesPlugin):
config_widget = 'calibre.gui2.preferences.misc'
description = _('Miscellaneous advanced configuration')
plugins += [LookAndFeel, Behavior, Columns, Toolbar, InputOptions,
plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
CommonOptions, OutputOptions, Adding, Saving, Sending, Plugboard,
Email, Server, Plugins, Tweaks, Misc, TemplateFunctions]

View File

@ -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):
@ -40,6 +41,9 @@ class HeuristicProcessor(object):
def is_pdftohtml(self, src):
return '<!-- created by calibre\'s pdftohtml -->' in src[:1000]
def is_abbyy(self, src):
return '<meta name="generator" content="ABBYY FineReader' in src[:1000]
def chapter_head(self, match):
from calibre.utils.html2text import html2text
chap = match.group('chap')
@ -518,6 +522,111 @@ class HeuristicProcessor(object):
return scene_break
def abbyy_processor(self, html):
abbyy_line = re.compile('((?P<linestart><p\sstyle="(?P<styles>[^\"]*?);?">)(?P<content>.*?)(?P<lineend></p>)|(?P<image><img[^>]*>))', re.IGNORECASE)
empty_paragraph = '\n<p> </p>\n'
self.in_blockquote = False
self.previous_was_paragraph = False
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<blockquote>\n'
blockquote_close = '</blockquote>\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:
self.log.debug('\n\n******\n')
self.log.debug('padding top is: '+str(setting[0]))
self.log.debug('padding right is:'
+str(setting[1]))
self.log.debug('padding bottom is: ' +
str(setting[2]))
self.log.debug('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)
self.log.debug("styles for this line were:", styles)
self.log.debug('newline is:')
self.log.debug(blockquote_open_loop+blockquote_close_loop+
paragraph_before+'<p style="'+text_indent+text_align+
'">'+content+'</p>'+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+'<p style="'+text_indent+text_align+'">'+content+'</p>'+paragraph_after
html = abbyy_line.sub(convert_styles, html)
return html
def __call__(self, html):
self.log.debug("********* Heuristic processing HTML *********")
@ -532,6 +641,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 </p> 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')

View File

@ -106,9 +106,13 @@ def _config():
'clicked'))
c.add_opt('asked_library_thing_password', default=False,
help='Asked library thing password at least once.')
c.add_opt('search_as_you_type', default=True,
help='Start searching as you type. If this is disabled then search will '
'only take place when the Enter or Return key is pressed.')
c.add_opt('search_as_you_type', default=False,
help=_('Start searching as you type. If this is disabled then search will '
'only take place when the Enter or Return key is pressed.'))
c.add_opt('highlight_search_matches', default=False,
help=_('When searching, show all books with search results '
'highlighted instead of showing only the matches. You can use the '
'N or F3 keys to go to the next match.'))
c.add_opt('save_to_disk_template_history', default=[],
help='Previously used Save to Disk templates')
c.add_opt('send_to_device_template_history', default=[],

View File

@ -28,21 +28,12 @@ class NextMatchAction(InterfaceAction):
self.gui.addAction(self.p_action)
self.p_action.triggered.connect(self.move_backward)
def gui_layout_complete(self):
self.gui.search_options_button.setVisible(True)
def location_selected(self, loc):
self.can_move = loc == 'library'
try:
self.gui.search_options_button.setVisible(self.can_move)
except:
import traceback
traceback.print_exc()
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_options_button.setVisible(self.can_move)
if self.can_move:
self.gui.current_view().move_highlighted_row(forward=True)
@ -50,7 +41,6 @@ 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_options_button.setVisible(self.can_move)
if self.can_move:
self.gui.current_view().move_highlighted_row(forward=False)

View File

@ -33,7 +33,8 @@ class PreferencesAction(InterfaceAction):
x.triggered.connect(self.do_config)
def do_config(self, checked=False, initial_plugin=None):
def do_config(self, checked=False, initial_plugin=None,
close_after_initial=False):
if self.gui.job_manager.has_jobs():
d = error_dialog(self.gui, _('Cannot configure'),
_('Cannot configure while there are running jobs.'))
@ -44,7 +45,8 @@ class PreferencesAction(InterfaceAction):
_('Cannot configure before calibre is restarted.'))
d.exec_()
return
d = Preferences(self.gui, initial_plugin=initial_plugin)
d = Preferences(self.gui, initial_plugin=initial_plugin,
close_after_initial=close_after_initial)
d.show()
d.run_wizard_requested.connect(self.gui.run_wizard,
type=Qt.QueuedConnection)

View File

@ -19,7 +19,6 @@ 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 = []
@ -65,8 +64,7 @@ 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)
self.library_view.model().set_highlight_only(config['highlight_search_matches'])
def build_context_menus(self):
lm = QMenu(self)

View File

@ -7,8 +7,8 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from PyQt4.Qt import QIcon, Qt, QWidget, QToolBar, QSize, QDialogButtonBox, \
pyqtSignal, QToolButton, QMenu, QCheckBox, QDialog, QGridLayout, QFrame, \
from PyQt4.Qt import QIcon, Qt, QWidget, QToolBar, QSize, \
pyqtSignal, QToolButton, QMenu, \
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup
@ -17,7 +17,6 @@ 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
class LocationManager(QObject): # {{{
@ -201,8 +200,7 @@ class SearchBar(QWidget): # {{{
x.setIcon(QIcon(I('config.png')))
x.setObjectName("search_option_button")
l.addWidget(x)
x.setToolTip(_("Change search highlighting and column limit options"))
x.setVisible(False)
x.setToolTip(_("Change the way searching for books works"))
x = parent.saved_search = SavedSearchBox(self)
x.setMaximumSize(QSize(150, 16777215))
@ -229,90 +227,6 @@ 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.setWindowTitle(_('Search options'))
l = QGridLayout()
self.setLayout(l)
x = QLabel('<p>'+_('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.')+'<p>'+_('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)
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('<p>'+_('When searching, show all books with search results '
'highlight instead of showing only the matches.<p> 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 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('<p>'+_('When searching for text without using lookup '
'prefixes, as for example someword instead of title:someword, '
'limit the columns searched to those named in the 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 columns/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 &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, 2)
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()))
# }}}
class Spacer(QWidget): # {{{

View File

@ -46,7 +46,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('disable_tray_notification', config)
r('use_roman_numerals_for_series_number', config)
r('separate_cover_flow', config, restart_required=True)
r('search_as_you_type', config)
r('show_child_bar', gprefs)
choices = [(_('Small'), 'small'), (_('Medium'), 'medium'),
@ -116,7 +115,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
def refresh_gui(self, gui):
gui.search.search_as_you_type(config['search_as_you_type'])
self.update_font_display()
gui.tags_view.reread_collapse_parameters()

View File

@ -124,23 +124,13 @@
</property>
</widget>
</item>
<item row="6" column="0">
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="opt_separate_cover_flow">
<property name="text">
<string>Show cover &amp;browser in a separate window (needs restart)</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="opt_search_as_you_type">
<property name="text">
<string>Search as you type</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<layout class="QHBoxLayout">
<item>

View File

@ -157,11 +157,12 @@ class Preferences(QMainWindow):
run_wizard_requested = pyqtSignal()
def __init__(self, gui, initial_plugin=None):
def __init__(self, gui, initial_plugin=None, close_after_initial=False):
QMainWindow.__init__(self, gui)
self.gui = gui
self.must_restart = False
self.committed = False
self.close_after_initial = close_after_initial
self.resize(900, 720)
nh, nw = min_available_height()-25, available_width()-10
@ -306,7 +307,7 @@ class Preferences(QMainWindow):
def esc(self, *args):
if self.stack.currentIndex() == 1:
self.hide_plugin()
self.cancel()
elif self.stack.currentIndex() == 0:
self.close()
@ -331,11 +332,14 @@ class Preferences(QMainWindow):
show_copy_button=False)
self.showing_widget.refresh_gui(self.gui)
self.hide_plugin()
if must_restart and rc:
if self.close_after_initial or (must_restart and rc):
self.close()
def cancel(self, *args):
if self.close_after_initial:
self.close()
else:
self.hide_plugin()
def restore_defaults(self, *args):

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QApplication
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, \
CommaSeparatedList
from calibre.gui2.preferences.search_ui import Ui_Form
from calibre.gui2 import config
from calibre.utils.config import prefs
class ConfigWidget(ConfigWidgetBase, Ui_Form):
def genesis(self, gui):
self.gui = gui
r = self.register
r('search_as_you_type', config)
r('highlight_search_matches', config)
r('limit_search_columns', prefs)
r('limit_search_columns_to', prefs, setting=CommaSeparatedList)
fl = gui.library_view.model().db.field_metadata.get_search_terms()
self.opt_limit_search_columns_to.update_items_cache(fl)
def refresh_gui(self, gui):
gui.search.search_as_you_type(config['search_as_you_type'])
gui.library_view.model().set_highlight_only(config['highlight_search_matches'])
gui.search.do_search()
if __name__ == '__main__':
app = QApplication([])
test_widget('Interface', 'Search')

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>670</width>
<height>392</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="opt_search_as_you_type">
<property name="text">
<string>Search as you &amp;type</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="opt_highlight_search_matches">
<property name="text">
<string>&amp;Highlight search results instead of restricting the book list to the results</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>What to search by default</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>When you enter a search term without a prefix, by default calibre will search all metadata for matches. For example, entering, &quot;asimov&quot; will search not just authors but title/tags/series/comments/etc. Use these options if you would like to change this behavior.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_limit_search_columns">
<property name="text">
<string>&amp;Limit the searched metadata</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&amp;Columns that non-prefixed searches are limited to:</string>
</property>
<property name="buddy">
<cstring>opt_limit_search_columns_to</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="MultiCompleteLineEdit" name="opt_limit_search_columns_to"/>
</item>
<item row="5" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Note that this option affects all searches, including saved searches and restrictions. Therefore, if you use this option, it is best to ensure that you always use prefixes in your saved searches. For example, use &quot;series:Foundation&quot; rather than just &quot;Foundation&quot; in a saved search</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>MultiCompleteLineEdit</class>
<extends>QLineEdit</extends>
<header>calibre/gui2.complete.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -16,7 +16,6 @@ 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, prefs
from calibre.utils.search_query_parser import saved_searches
from calibre.utils.icu import sort_key
@ -401,24 +400,8 @@ 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]
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_cbox, highlight_cbox = options_box.values()
prefs['search_box_limit_to'] = limit_list
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.iactions['Preferences'].do_config(initial_plugin=('Interface',
'Search'), close_after_initial=True)
def focus_to_library(self):
self.current_view().setFocus(Qt.OtherFocusReason)

View File

@ -483,8 +483,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
action.location_selected(location)
if location == 'library':
self.search_restriction.setEnabled(True)
self.search_options_button.setEnabled(True)
else:
self.search_restriction.setEnabled(False)
self.search_options_button.setEnabled(False)
# Reset the view in case something changed while it was invisible
self.current_view().reset()
self.set_number_of_books_shown()

View File

@ -447,15 +447,15 @@ class ResultCache(SearchQueryParser): # {{{
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(','):
if location == 'all' and prefs['limit_search_columns'] and \
prefs['limit_search_columns_to']:
terms = set([])
for l in prefs['limit_search_columns_to']:
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)
if l and l != 'all' and l in self.all_search_locations:
terms.add(l)
if terms:
for l in terms:
matches |= self.get_matches(l, query,
candidates=candidates, allow_recursion=allow_recursion)
return matches

View File

@ -728,11 +728,17 @@ def _prefs():
c.add_opt('user_categories', default={}, help=_('User-created tag browser categories'))
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='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'))
c.add_opt('limit_search_columns', default=False,
help=_('When searching for text without using lookup '
'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', '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 '
'separated by commas. Only takes effect if you set the option '
'to limit search columns above.'))
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
return c