KG + GwR revisions

This commit is contained in:
GRiker 2010-06-07 06:44:08 -06:00
commit 045a893d20
42 changed files with 41226 additions and 30593 deletions

View File

@ -4,6 +4,38 @@
# for important features/bug fixes. # for important features/bug fixes.
# Also, each release can have new and improved recipes. # Also, each release can have new and improved recipes.
- version: 0.7.1
date: 2010-06-04
new features:
- title: "Content server: Add option to control category groupiong in OPDS feeds"
- title: "Make the book details pane occupy the full lower part of the window"
- title: "Add true and false searches for date based columns"
tickets: [5717]
bug fixes:
- title: "iPad driver: Various bug fixes."
- title: "SONY driver: Fix Launcher partition being detected as storage card in linux"
- title: "Fix news downloading breaking on windows systems with local encoding other than UTF-8."
- title: "SONY driver: Fix problem caused by null titles"
- title: "Make the new splash screen not always stay on top"
tickets: [5700]
- title: "When setting an image with transparent pixels as the book cover, overlay it on a white background first. Fixes transparent covers getting random backgrounds."
- title: "Content server: Fix stanza integration when entering the server URL my hand"
improved recipes:
- Gizmodo
- Vreme
- version: 0.7.0 - version: 0.7.0
date: 2010-06-04 date: 2010-06-04

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

View File

@ -0,0 +1,25 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1275798572(BasicNewsRecipe):
title = u'CBC Canada'
publisher = 'www.cbc.ca'
language = 'en_CA'
__author__ = 'rty'
category = 'news'
oldest_article = 4
max_articles_per_feed = 100
remove_javascript = True
use_embedded_content = False
no_stylesheets = True
language = 'en'
masthead_url = 'http://www.cbc.ca/includes/gfx/cbcnews_logo_09.gif'
cover_url = 'http://img692.imageshack.us/img692/2814/cbc.png'
keep_only_tags = [dict(name='div', attrs={'id':['storyhead','storybody']})]
remove_tags_after = dict(id=['socialtools'])
feeds = [(u'Top Stories', u'http://rss.cbc.ca/lineup/topstories.xml'),
(u'World', u'http://rss.cbc.ca/lineup/world.xml'),
(u'National', u'http://rss.cbc.ca/lineup/canada.xml'),
(u'Manitoba', u'http://rss.cbc.ca/lineup/canada-manitoba.xml'),
(u'Politics', u'http://rss.cbc.ca/lineup/politics.xml'),
(u'Tech & Science', u'http://rss.cbc.ca/lineup/technology.xml'),
(u'Books', u'http://rss.cbc.ca/lineup/arts-books.xml')]

View File

@ -0,0 +1,57 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
haaretz.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Haaretz_en(BasicNewsRecipe):
title = 'Haaretz in English'
__author__ = 'Darko Miletic'
description = 'Haaretz.com, the online edition of Haaretz Newspaper in Israel, and analysis from Israel and the Middle East. Haaretz.com provides extensive and in-depth coverage of Israel, the Jewish World and the Middle East, including defense, diplomacy, the Arab-Israeli conflict, the peace process, Israeli politics, Jerusalem affairs, international relations, Iran, Iraq, Syria, Lebanon, the Palestinian Authority, the West Bank and the Gaza Strip, the Israeli business world and Jewish life in Israel and the Diaspora. '
publisher = 'haaretz.com'
category = 'news, politics, Israel'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'cp1252'
use_embedded_content = False
language = 'en_IL'
publication_type = 'newspaper'
remove_empty_feeds = True
masthead_url = 'http://www.haaretz.com/images/logos/logoGrey.gif'
extra_css = ' body{font-family: Verdana,Arial,Helvetica,sans-serif } '
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_tags = [dict(name='div', attrs={'class':['rightcol']}),dict(name='table')]
remove_tags_before = dict(name='h1')
remove_tags_after = dict(attrs={'id':'innerArticle'})
keep_only_tags = [dict(attrs={'id':'content'})]
feeds = [
(u'Opinion' , u'http://www.haaretz.com/cmlink/opinion-rss-1.209234?localLinksEnabled=false' )
,(u'Defense and diplomacy' , u'http://www.haaretz.com/cmlink/defense-and-diplomacy-rss-1.208894?localLinksEnabled=false')
,(u'National' , u'http://www.haaretz.com/cmlink/national-rss-1.208896?localLinksEnabled=false' )
,(u'International' , u'http://www.haaretz.com/cmlink/international-rss-1.208898?localLinksEnabled=false' )
,(u'Jewish World' , u'http://www.haaretz.com/cmlink/jewish-world-rss-1.209085?localLinksEnabled=false' )
,(u'Business' , u'http://www.haaretz.com/cmlink/business-print-rss-1.264904?localLinksEnabled=false' )
,(u'Real Estate' , u'http://www.haaretz.com/cmlink/real-estate-print-rss-1.264977?localLinksEnabled=false' )
,(u'Features' , u'http://www.haaretz.com/cmlink/features-print-rss-1.264912?localLinksEnabled=false' )
,(u'Arts and leisure' , u'http://www.haaretz.com/cmlink/arts-and-leisure-rss-1.286090?localLinksEnabled=false' )
,(u'Books' , u'http://www.haaretz.com/cmlink/books-rss-1.264947?localLinksEnabled=false' )
,(u'Food and Wine' , u'http://www.haaretz.com/cmlink/food-and-wine-print-rss-1.265034?localLinksEnabled=false' )
,(u'Sports' , u'http://www.haaretz.com/cmlink/sports-rss-1.286092?localLinksEnabled=false' )
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -41,6 +41,8 @@ mimetypes.add_type('application/vnd.palm', '.pdb')
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi') mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
mimetypes.add_type('application/x-mobipocket-ebook', '.prc') mimetypes.add_type('application/x-mobipocket-ebook', '.prc')
mimetypes.add_type('application/x-mobipocket-ebook', '.azw') mimetypes.add_type('application/x-mobipocket-ebook', '.azw')
mimetypes.add_type('application/x-cbz', '.cbz')
mimetypes.add_type('application/x-cbr', '.cbr')
mimetypes.add_type('image/wmf', '.wmf') mimetypes.add_type('image/wmf', '.wmf')
guess_type = mimetypes.guess_type guess_type = mimetypes.guess_type
import cssutils import cssutils

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.7.0' __version__ = '0.7.1'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re import re

View File

@ -1163,7 +1163,7 @@ class ITUNES(DevicePlugin):
hits = lib_books.Search(cached_book['author'],SearchField.index('Artists')) hits = lib_books.Search(cached_book['author'],SearchField.index('Artists'))
if hits: if hits:
for hit in hits: for hit in hits:
self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) #self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist))
if hit.Name == cached_book['title']: if hit.Name == cached_book['title']:
self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist)) self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist))
return hit return hit
@ -1359,7 +1359,6 @@ class ITUNES(DevicePlugin):
if DEBUG: if DEBUG:
self.log.info('ITUNES._get_library_books():\n No Books playlist') self.log.info('ITUNES._get_library_books():\n No Books playlist')
elif iswindows: elif iswindows:
lib = None lib = None
try: try:
@ -1466,7 +1465,7 @@ class ITUNES(DevicePlugin):
cmd = "defaults read com.apple.itunes NSNavLastRootDirectory" cmd = "defaults read com.apple.itunes NSNavLastRootDirectory"
proc = subprocess.Popen( cmd, shell=True, cwd=os.curdir, stdout=subprocess.PIPE) proc = subprocess.Popen( cmd, shell=True, cwd=os.curdir, stdout=subprocess.PIPE)
retcode = proc.wait() retcode = proc.wait()
media_dir = proc.communicate()[0].strip() media_dir = os.path.abspath(proc.communicate()[0].strip())
if os.path.exists(media_dir): if os.path.exists(media_dir):
self.iTunes_media = media_dir self.iTunes_media = media_dir
else: else:
@ -1493,12 +1492,13 @@ class ITUNES(DevicePlugin):
soup = BeautifulSoup(xml.read().decode('utf-8')) soup = BeautifulSoup(xml.read().decode('utf-8'))
mf = soup.find('key',text="Music Folder").parent mf = soup.find('key',text="Music Folder").parent
string = mf.findNext('string').renderContents() string = mf.findNext('string').renderContents()
media_dir = string[len('file://localhost/'):].replace('%20',' ') media_dir = os.path.abspath(string[len('file://localhost/'):].replace('%20',' '))
if os.path.exists(media_dir): if os.path.exists(media_dir):
self.iTunes_media = media_dir self.iTunes_media = media_dir
else: else:
self.log.error(" could not extract valid iTunes.media_dir from %s" % self.iTunes.LibraryXMLPath) self.log.error(" could not extract valid iTunes.media_dir from %s" % self.iTunes.LibraryXMLPath)
self.log.error(" %s" % string.parent.prettify()) self.log.error(" %s" % string.parent.prettify())
self.log.error(" '%s' not found" % media_dir)
if DEBUG: if DEBUG:
self.log.info( " [%s - %s (%s), driver version %d.%d.%d]" % self.log.info( " [%s - %s (%s), driver version %d.%d.%d]" %
@ -1514,13 +1514,7 @@ class ITUNES(DevicePlugin):
''' '''
if isosx: if isosx:
storage_path = os.path.split(cached_book['lib_book'].location().path) storage_path = os.path.split(cached_book['lib_book'].location().path)
presumptive_path = os.path.join(self.iTunes_media, if cached_book['lib_book'].location().path.startswith(self.iTunes_media):
'iTunes Music',
cached_book['author'][0],
cached_book['title'],
storage_path[1])
if os.path.exists(presumptive_path):
title_storage_path = storage_path[0] title_storage_path = storage_path[0]
if DEBUG: if DEBUG:
self.log.info("ITUNES._remove_from_iTunes():") self.log.info("ITUNES._remove_from_iTunes():")
@ -1545,7 +1539,7 @@ class ITUNES(DevicePlugin):
self.log.info(" author_storage_path not empty (%d objects):" % len(author_files)) self.log.info(" author_storage_path not empty (%d objects):" % len(author_files))
self.log.info(" %s" % '\n'.join(author_files)) self.log.info(" %s" % '\n'.join(author_files))
else: else:
self.log.info(" '%s' not found in iTunes storage, no files deleted" % presumptive_path) self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title'])
self.iTunes.delete(cached_book['lib_book']) self.iTunes.delete(cached_book['lib_book'])
@ -1560,15 +1554,7 @@ class ITUNES(DevicePlugin):
if book: if book:
path = book.Location path = book.Location
storage_path = os.path.split(book.Location) storage_path = os.path.split(book.Location)
# This assumes that 'Books' will be the storage subdirectory in if book.Location.startswith(self.iTunes_media):
# all versions of Windows. The XML file doesn't offer any deeper information
# than the media directory.
presumptive_path = os.path.join(self.iTunes_media,
'Books',
cached_book['author'],
storage_path[1])
if os.path.exists(presumptive_path):
if DEBUG: if DEBUG:
self.log.info("ITUNES._remove_from_iTunes():") self.log.info("ITUNES._remove_from_iTunes():")
self.log.info(" removing '%s' at %s" % self.log.info(" removing '%s' at %s" %
@ -1585,7 +1571,7 @@ class ITUNES(DevicePlugin):
# Delete from iTunes database # Delete from iTunes database
else: else:
self.log.info(" '%s' not in iTunes storage, no files deleted" % presumptive_path) self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title'])
book.Delete() book.Delete()

View File

@ -445,6 +445,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.username.setText(opts.username) self.username.setText(opts.username)
self.password.setText(opts.password if opts.password else '') self.password.setText(opts.password if opts.password else '')
self.opt_max_opds_items.setValue(opts.max_opds_items) self.opt_max_opds_items.setValue(opts.max_opds_items)
self.opt_max_opds_ungrouped_items.setValue(opts.max_opds_ungrouped_items)
self.auto_launch.setChecked(config['autolaunch_server']) self.auto_launch.setChecked(config['autolaunch_server'])
self.systray_icon.setChecked(config['systray_icon']) self.systray_icon.setChecked(config['systray_icon'])
self.sync_news.setChecked(config['upload_news_to_device']) self.sync_news.setChecked(config['upload_news_to_device'])
@ -848,6 +849,8 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
sc.set('port', self.port.value()) sc.set('port', self.port.value())
sc.set('max_cover', mcs) sc.set('max_cover', mcs)
sc.set('max_opds_items', self.opt_max_opds_items.value()) sc.set('max_opds_items', self.opt_max_opds_items.value())
sc.set('max_opds_ungrouped_items',
self.opt_max_opds_ungrouped_items.value())
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked() config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
config['upload_news_to_device'] = self.sync_news.isChecked() config['upload_news_to_device'] = self.sync_news.isChecked()
config['search_as_you_type'] = self.search_as_you_type.isChecked() config['search_as_you_type'] = self.search_as_you_type.isChecked()

View File

@ -892,6 +892,26 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1">
<widget class="QSpinBox" name="opt_max_opds_ungrouped_items">
<property name="minimum">
<number>25</number>
</property>
<property name="maximum">
<number>1000000</number>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Max. OPDS &amp;ungrouped items:</string>
</property>
<property name="buddy">
<cstring>opt_max_opds_ungrouped_items</cstring>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>

View File

@ -213,7 +213,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.endInsertRows() self.endInsertRows()
self.count_changed() self.count_changed()
def search(self, text, refinement, reset=True): def search(self, text, reset=True):
try: try:
self.db.search(text) self.db.search(text)
except ParseException: except ParseException:
@ -224,9 +224,10 @@ class BooksModel(QAbstractTableModel): # {{{
self.clear_caches() self.clear_caches()
self.reset() self.reset()
if self.last_search: if self.last_search:
# Do not issue search done for the null search. It is used to clear
# the search and count records for restrictions
self.searched.emit(True) self.searched.emit(True)
def sort(self, col, order, reset=True): def sort(self, col, order, reset=True):
if not self.db: if not self.db:
return return
@ -257,7 +258,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.sort(col, self.sorted_on[1], reset=reset) self.sort(col, self.sorted_on[1], reset=reset)
def research(self, reset=True): def research(self, reset=True):
self.search(self.last_search, False, reset=reset) self.search(self.last_search, reset=reset)
def columnCount(self, parent): def columnCount(self, parent):
if parent and parent.isValid(): if parent and parent.isValid():
@ -730,6 +731,8 @@ class BooksModel(QAbstractTableModel): # {{{
def set_search_restriction(self, s): def set_search_restriction(self, s):
self.db.data.set_search_restriction(s) self.db.data.set_search_restriction(s)
self.search('')
return self.rowCount(None)
# }}} # }}}
@ -874,7 +877,7 @@ class DeviceBooksModel(BooksModel): # {{{
return flags return flags
def search(self, text, refinement, reset=True): def search(self, text, reset=True):
if not text or not text.strip(): if not text or not text.strip():
self.map = list(range(len(self.db))) self.map = list(range(len(self.db)))
else: else:
@ -1086,7 +1089,6 @@ class DeviceBooksModel(BooksModel): # {{{
idx = self.map[row] idx = self.map[row]
if cname == 'title' : if cname == 'title' :
self.db[idx].title = val self.db[idx].title = val
self.db[idx].title_sorter = val
elif cname == 'authors': elif cname == 'authors':
self.db[idx].authors = string_to_authors(val) self.db[idx].authors = string_to_authors(val)
elif cname == 'collections': elif cname == 'collections':

View File

@ -437,10 +437,6 @@ class BooksView(QTableView): # {{{
self._search_done = search_done self._search_done = search_done
self._model.searched.connect(self.search_done) self._model.searched.connect(self.search_done)
def connect_to_restriction_set(self, tv):
# must be synchronous (not queued)
tv.restriction_set.connect(self._model.set_search_restriction)
def connect_to_book_display(self, bd): def connect_to_book_display(self, bd):
self._model.new_bookdisplay_data.connect(bd) self._model.new_bookdisplay_data.connect(bd)

View File

@ -152,7 +152,7 @@ class Main(MainWindow, Ui_MainWindow):
self.stack.setCurrentIndex(1) self.stack.setCurrentIndex(1)
self.renderer.start() self.renderer.start()
def find(self, search, refinement): def find(self, search):
self.last_search = search self.last_search = search
try: try:
self.document.search(search) self.document.search(search)

View File

@ -57,7 +57,7 @@ class SearchBox2(QComboBox):
INTERVAL = 1500 #: Time to wait before emitting search signal INTERVAL = 1500 #: Time to wait before emitting search signal
MAX_COUNT = 25 MAX_COUNT = 25
search = pyqtSignal(object, object) search = pyqtSignal(object)
def __init__(self, parent=None): def __init__(self, parent=None):
QComboBox.__init__(self, parent) QComboBox.__init__(self, parent)
@ -97,8 +97,12 @@ class SearchBox2(QComboBox):
self.help_state = False self.help_state = False
def clear_to_help(self): def clear_to_help(self):
self.search.emit('')
self._in_a_search = False self._in_a_search = False
self.setEditText(self.help_text) self.setEditText(self.help_text)
if self.timer is not None: # Turn off any timers that got started in setEditText
self.killTimer(self.timer)
self.timer = None
self.line_edit.home(False) self.line_edit.home(False)
self.help_state = True self.help_state = True
self.line_edit.setStyleSheet( self.line_edit.setStyleSheet(
@ -111,7 +115,6 @@ class SearchBox2(QComboBox):
def clear(self): def clear(self):
self.clear_to_help() self.clear_to_help()
self.search.emit('', False)
def search_done(self, ok): def search_done(self, ok):
if not unicode(self.currentText()).strip(): if not unicode(self.currentText()).strip():
@ -155,9 +158,8 @@ class SearchBox2(QComboBox):
if not text or text == self.help_text: if not text or text == self.help_text:
return self.clear() return self.clear()
self.help_state = False self.help_state = False
refinement = text.startswith(self.prev_search) and ':' not in text
self.prev_search = text self.prev_search = text
self.search.emit(text, refinement) self.search.emit(text)
idx = self.findText(text, Qt.MatchFixedString) idx = self.findText(text, Qt.MatchFixedString)
self.block_signals(True) self.block_signals(True)
@ -187,12 +189,15 @@ class SearchBox2(QComboBox):
self.set_search_string(joiner.join(tags)) self.set_search_string(joiner.join(tags))
def set_search_string(self, txt): def set_search_string(self, txt):
if not txt:
self.clear_to_help()
return
self.normalize_state() self.normalize_state()
self.setEditText(txt) self.setEditText(txt)
if self.timer is not None: # Turn off any timers that got started in setEditText if self.timer is not None: # Turn off any timers that got started in setEditText
self.killTimer(self.timer) self.killTimer(self.timer)
self.timer = None self.timer = None
self.search.emit(txt, False) self.search.emit(txt)
self.line_edit.end(False) self.line_edit.end(False)
self.initial_state = False self.initial_state = False

View File

@ -22,7 +22,6 @@ from calibre.gui2 import error_dialog
class TagsView(QTreeView): # {{{ class TagsView(QTreeView): # {{{
refresh_required = pyqtSignal() refresh_required = pyqtSignal()
restriction_set = pyqtSignal(object)
tags_marked = pyqtSignal(object, object) tags_marked = pyqtSignal(object, object)
user_category_edit = pyqtSignal(object) user_category_edit = pyqtSignal(object)
tag_list_edit = pyqtSignal(object, object) tag_list_edit = pyqtSignal(object, object)
@ -37,24 +36,23 @@ class TagsView(QTreeView): # {{{
self.setIconSize(QSize(30, 30)) self.setIconSize(QSize(30, 30))
self.tag_match = None self.tag_match = None
def set_database(self, db, tag_match, popularity, restriction): def set_database(self, db, tag_match, popularity):
self.hidden_categories = config['tag_browser_hidden_categories'] self.hidden_categories = config['tag_browser_hidden_categories']
self._model = TagsModel(db, parent=self, self._model = TagsModel(db, parent=self,
hidden_categories=self.hidden_categories) hidden_categories=self.hidden_categories,
search_restriction=None)
self.popularity = popularity self.popularity = popularity
self.restriction = restriction
self.tag_match = tag_match self.tag_match = tag_match
self.db = db self.db = db
self.search_restriction = None
self.setModel(self._model) self.setModel(self._model)
self.setContextMenuPolicy(Qt.CustomContextMenu) self.setContextMenuPolicy(Qt.CustomContextMenu)
self.clicked.connect(self.toggle) self.clicked.connect(self.toggle)
self.customContextMenuRequested.connect(self.show_context_menu) self.customContextMenuRequested.connect(self.show_context_menu)
self.popularity.setChecked(config['sort_by_popularity']) self.popularity.setChecked(config['sort_by_popularity'])
self.popularity.stateChanged.connect(self.sort_changed) self.popularity.stateChanged.connect(self.sort_changed)
self.restriction.activated[str].connect(self.search_restriction_set)
self.refresh_required.connect(self.recount, type=Qt.QueuedConnection) self.refresh_required.connect(self.recount, type=Qt.QueuedConnection)
db.add_listener(self.database_changed) db.add_listener(self.database_changed)
self.saved_searches_changed(recount=False)
def database_changed(self, event, ids): def database_changed(self, event, ids):
self.refresh_required.emit() self.refresh_required.emit()
@ -68,16 +66,12 @@ class TagsView(QTreeView): # {{{
self.model().refresh() self.model().refresh()
# self.search_restriction_set() # self.search_restriction_set()
def search_restriction_set(self, s): def set_search_restriction(self, s):
self.clear() if s:
if len(s) == 0: self.search_restriction = s
self.search_restriction = ''
else: else:
self.search_restriction = 'search:"%s"' % unicode(s).strip() self.search_restriction = None
self.model().set_search_restriction(self.search_restriction) self.set_new_model()
self.restriction_set.emit(self.search_restriction)
self.recount() # Must happen after the emission of the restriction_set signal
self.tags_marked.emit(self._model.tokens(), self.match_all)
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
# Swallow everything except leftButton so context menus work correctly # Swallow everything except leftButton so context menus work correctly
@ -187,21 +181,8 @@ class TagsView(QTreeView): # {{{
return True return True
def clear(self): def clear(self):
self.model().clear_state() if self.model():
self.model().clear_state()
def saved_searches_changed(self, recount=True):
p = prefs['saved_searches'].keys()
p.sort()
t = self.restriction.currentText()
self.restriction.clear() # rebuild the restrictions combobox using current saved searches
self.restriction.addItem('')
for s in p:
self.restriction.addItem(s)
if t in p: # redo the current restriction, if there was one
self.restriction.setCurrentIndex(self.restriction.findText(t))
self.search_restriction_set(t)
if recount:
self.recount()
def recount(self, *args): def recount(self, *args):
ci = self.currentIndex() ci = self.currentIndex()
@ -223,7 +204,8 @@ class TagsView(QTreeView): # {{{
# model. Reason: it is much easier than reconstructing the browser tree. # model. Reason: it is much easier than reconstructing the browser tree.
def set_new_model(self): def set_new_model(self):
self._model = TagsModel(self.db, parent=self, self._model = TagsModel(self.db, parent=self,
hidden_categories=self.hidden_categories) hidden_categories=self.hidden_categories,
search_restriction=self.search_restriction)
self.setModel(self._model) self.setModel(self._model)
# }}} # }}}
@ -311,7 +293,7 @@ class TagTreeItem(object): # {{{
class TagsModel(QAbstractItemModel): # {{{ class TagsModel(QAbstractItemModel): # {{{
def __init__(self, db, parent, hidden_categories=None): def __init__(self, db, parent, hidden_categories=None, search_restriction=None):
QAbstractItemModel.__init__(self, parent) QAbstractItemModel.__init__(self, parent)
# must do this here because 'QPixmap: Must construct a QApplication # must do this here because 'QPixmap: Must construct a QApplication
@ -333,8 +315,7 @@ class TagsModel(QAbstractItemModel): # {{{
self.db = db self.db = db
self.tags_view = parent self.tags_view = parent
self.hidden_categories = hidden_categories self.hidden_categories = hidden_categories
self.search_restriction = '' self.search_restriction = search_restriction
self.ignore_next_search = 0
# Reconstruct the user categories, putting them into metadata # Reconstruct the user categories, putting them into metadata
tb_cats = self.db.field_metadata tb_cats = self.db.field_metadata
@ -370,9 +351,10 @@ class TagsModel(QAbstractItemModel): # {{{
self.row_map = [] self.row_map = []
self.categories = [] self.categories = []
if len(self.search_restriction): if self.search_restriction:
data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map, data = self.db.get_categories(sort_on_count=sort,
ids=self.db.search(self.search_restriction, return_matches=True)) icon_map=self.category_icon_map,
ids=self.db.search('', return_matches=True))
else: else:
data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map) data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map)
@ -385,7 +367,6 @@ class TagsModel(QAbstractItemModel): # {{{
self.category_items[category] = set([tag.name for tag in data[category]]) self.category_items[category] = set([tag.name for tag in data[category]])
self.row_map.append(category) self.row_map.append(category)
self.categories.append(tb_categories[category]['name']) self.categories.append(tb_categories[category]['name'])
return data return data
def refresh(self): def refresh(self):
@ -544,12 +525,6 @@ class TagsModel(QAbstractItemModel): # {{{
def clear_state(self): def clear_state(self):
self.reset_all_states() self.reset_all_states()
def reinit(self, *args, **kwargs):
if self.ignore_next_search == 0:
self.reset_all_states()
else:
self.ignore_next_search -= 1
def toggle(self, index, exclusive): def toggle(self, index, exclusive):
if not index.isValid(): return False if not index.isValid(): return False
item = index.internalPointer() item = index.internalPointer()
@ -557,7 +532,6 @@ class TagsModel(QAbstractItemModel): # {{{
item.toggle() item.toggle()
if exclusive: if exclusive:
self.reset_all_states(except_=item.tag) self.reset_all_states(except_=item.tag)
self.ignore_next_search = 2
self.dataChanged.emit(index, index) self.dataChanged.emit(index, index)
return True return True
return False return False

View File

@ -160,9 +160,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.restriction_in_effect = False self.restriction_in_effect = False
self.search.initialize('main_search_history', colorize=True, self.search.initialize('main_search_history', colorize=True,
help_text=_('Search (For Advanced Search click the button to the left)')) help_text=_('Search (For Advanced Search click the button to the left)'))
self.connect(self.clear_button, SIGNAL('clicked()'), self.search_clear) self.connect(self.clear_button, SIGNAL('clicked()'), self.search.clear)
self.connect(self.clear_button, SIGNAL('clicked()'), self.saved_search.clear_to_help) self.connect(self.clear_button, SIGNAL('clicked()'), self.saved_search.clear_to_help)
self.search_clear() self.search.clear()
self.saved_search.initialize(saved_searches, self.search, colorize=True, self.saved_search.initialize(saved_searches, self.search, colorize=True,
help_text=_('Saved Searches')) help_text=_('Saved Searches'))
@ -226,14 +226,14 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.connect(self.quit_action, SIGNAL('triggered(bool)'), self.quit) self.connect(self.quit_action, SIGNAL('triggered(bool)'), self.quit)
self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate) self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate)
self.connect(self.restore_action, SIGNAL('triggered()'), self.connect(self.restore_action, SIGNAL('triggered()'),
self.show_windows) self.show_windows)
self.connect(self.action_show_book_details, self.connect(self.action_show_book_details,
SIGNAL('triggered(bool)'), self.show_book_info) SIGNAL('triggered(bool)'), self.show_book_info)
self.connect(self.action_restart, SIGNAL('triggered()'), self.connect(self.action_restart, SIGNAL('triggered()'),
self.restart) self.restart)
self.connect(self.system_tray_icon, self.connect(self.system_tray_icon,
SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
self.system_tray_icon_activated) self.system_tray_icon_activated)
self.tool_bar.contextMenuEvent = self.no_op self.tool_bar.contextMenuEvent = self.no_op
####################### Start spare job server ######################## ####################### Start spare job server ########################
@ -521,8 +521,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search_done)), self.search_done)),
('connect_to_book_display', ('connect_to_book_display',
(self.status_bar.book_info.show_data,)), (self.status_bar.book_info.show_data,)),
('connect_to_restriction_set',
(self.tags_view,)),
]: ]:
for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view): for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view):
getattr(view, func)(*args) getattr(view, func)(*args)
@ -545,24 +543,22 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.cover_cache.start() self.cover_cache.start()
self.library_view.model().cover_cache = self.cover_cache self.library_view.model().cover_cache = self.cover_cache
self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_user_categories_edit) self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_user_categories_edit)
self.tags_view.set_database(db, self.tag_match, self.popularity, self.search_restriction) self.search_restriction.activated[str].connect(self.apply_search_restriction)
self.tags_view.set_database(db, self.tag_match, self.popularity)
self.tags_view.tags_marked.connect(self.search.search_from_tags) self.tags_view.tags_marked.connect(self.search.search_from_tags)
for x in (self.saved_search.clear_to_help, self.mark_restriction_set):
self.tags_view.restriction_set.connect(x)
self.tags_view.tags_marked.connect(self.saved_search.clear_to_help) self.tags_view.tags_marked.connect(self.saved_search.clear_to_help)
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit) self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
self.tags_view.user_category_edit.connect(self.do_user_categories_edit) self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit) self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed) self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help) self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
self.search.search.connect(self.tags_view.model().reinit)
for x in (self.location_view.count_changed, self.tags_view.recount, for x in (self.location_view.count_changed, self.tags_view.recount,
self.restriction_count_changed): self.restriction_count_changed):
self.library_view.model().count_changed_signal.connect(x) self.library_view.model().count_changed_signal.connect(x)
self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared) self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared)
self.connect(self.saved_search, SIGNAL('changed()'), self.connect(self.saved_search, SIGNAL('changed()'), self.saved_searches_changed)
self.tags_view.saved_searches_changed, Qt.QueuedConnection) self.saved_searches_changed()
if not gprefs.get('quick_start_guide_added', False): if not gprefs.get('quick_start_guide_added', False):
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
mi = MetaInformation(_('Calibre Quick Start Guide'), ['John Schember']) mi = MetaInformation(_('Calibre Quick Start Guide'), ['John Schember'])
@ -585,7 +581,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon) self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon)
self.search_restriction.setMinimumContentsLength(10) self.search_restriction.setMinimumContentsLength(10)
########################### Cover Flow ################################ ########################### Cover Flow ################################
self.cover_flow = None self.cover_flow = None
if CoverFlow is not None: if CoverFlow is not None:
@ -625,7 +620,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.sidebar.job_done, Qt.QueuedConnection) self.sidebar.job_done, Qt.QueuedConnection)
if config['autolaunch_server']: if config['autolaunch_server']:
from calibre.library.server.main import start_threaded_server from calibre.library.server.main import start_threaded_server
from calibre.library.server import server_config from calibre.library.server import server_config
@ -683,7 +677,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
d = SavedSearchEditor(self, search) d = SavedSearchEditor(self, search)
d.exec_() d.exec_()
if d.result() == d.Accepted: if d.result() == d.Accepted:
self.tags_view.saved_searches_changed(recount=True) self.saved_searches_changed()
self.saved_search.clear_to_help() self.saved_search.clear_to_help()
def resizeEvent(self, ev): def resizeEvent(self, ev):
@ -842,19 +836,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
sm.select(idx, sm.ClearAndSelect|sm.Rows) sm.select(idx, sm.ClearAndSelect|sm.Rows)
self.library_view.setCurrentIndex(idx) self.library_view.setCurrentIndex(idx)
''' '''
Handling of the count of books in a restricted view requires that Restrictions.
we capture the count after the initial restriction search. To so this, Adding and deleting books creates a complexity. When added, they are
we require that the restriction_set signal be issued before the search signal, displayed regardless of whether they match a search restriction. However, if
so that when the search_done happens and the count is displayed, they do not, they are removed at the next search. The counts must take this
we can grab the count. This works because the search box is cleared
when a restriction is set, so that first search will find all books.
Adding and deleting books creates another complexity. When added, they are
displayed regardless of whether they match the restriction. However, if they
do not, they are removed at the next search. The counts must take this
behavior into effect. behavior into effect.
''' '''
@ -862,15 +848,25 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.restriction_count_of_books_in_view += c - self.restriction_count_of_books_in_library self.restriction_count_of_books_in_view += c - self.restriction_count_of_books_in_library
self.restriction_count_of_books_in_library = c self.restriction_count_of_books_in_library = c
if self.restriction_in_effect: if self.restriction_in_effect:
self.set_number_of_books_shown(compute_count=False) self.set_number_of_books_shown()
def mark_restriction_set(self, r): def apply_search_restriction(self, r):
self.restriction_in_effect = False if r is None or not r else True r = unicode(r)
if r is not None and r != '':
self.restriction_in_effect = True
restriction = "search:%s"%(r)
else:
self.restriction_in_effect = False
restriction = ''
self.restriction_count_of_books_in_view = \
self.library_view.model().set_search_restriction(restriction)
self.search.clear_to_help()
self.saved_search.clear_to_help()
self.tags_view.set_search_restriction(restriction)
self.set_number_of_books_shown()
def set_number_of_books_shown(self, compute_count): def set_number_of_books_shown(self):
if self.current_view() == self.library_view and self.restriction_in_effect: if self.current_view() == self.library_view and self.restriction_in_effect:
if compute_count:
self.restriction_count_of_books_in_view = self.current_view().row_count()
t = _("({0} of {1})").format(self.current_view().row_count(), t = _("({0} of {1})").format(self.current_view().row_count(),
self.restriction_count_of_books_in_view) self.restriction_count_of_books_in_view)
self.search_count.setStyleSheet('QLabel { border-radius: 8px; background-color: yellow; }') self.search_count.setStyleSheet('QLabel { border-radius: 8px; background-color: yellow; }')
@ -884,18 +880,31 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search_count.setText(t) self.search_count.setText(t)
def search_box_cleared(self): def search_box_cleared(self):
self.set_number_of_books_shown(compute_count=True)
self.tags_view.clear() self.tags_view.clear()
self.saved_search.clear_to_help() self.saved_search.clear_to_help()
self.set_number_of_books_shown()
def search_clear(self):
self.set_number_of_books_shown(compute_count=True)
self.search.clear()
def search_done(self, view, ok): def search_done(self, view, ok):
if view is self.current_view(): if view is self.current_view():
self.search.search_done(ok) self.search.search_done(ok)
self.set_number_of_books_shown(compute_count=False) self.set_number_of_books_shown()
def saved_searches_changed(self):
p = prefs['saved_searches'].keys()
p.sort()
t = unicode(self.search_restriction.currentText())
self.search_restriction.clear() # rebuild the restrictions combobox using current saved searches
self.search_restriction.addItem('')
self.tags_view.recount()
for s in p:
self.search_restriction.addItem(s)
if t:
if t in p: # redo the current restriction, if there was one
self.search_restriction.setCurrentIndex(self.search_restriction.findText(t))
# self.tags_view.set_search_restriction(t)
else:
self.search_restriction.setCurrentIndex(0)
self.apply_search_restriction('')
def sync_cf_to_listview(self, current, previous): def sync_cf_to_listview(self, current, previous):
if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \
@ -2304,14 +2313,17 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
def library_moved(self, newloc): def library_moved(self, newloc):
if newloc is None: return if newloc is None: return
db = LibraryDatabase2(newloc) db = LibraryDatabase2(newloc)
self.library_path = newloc
self.book_on_device(None, reset=True) self.book_on_device(None, reset=True)
db.set_book_on_device_func(self.book_on_device) db.set_book_on_device_func(self.book_on_device)
self.library_view.set_database(db) self.library_view.set_database(db)
self.tags_view.set_database(db, self.tag_match, self.popularity)
self.library_view.model().set_book_on_device_func(self.book_on_device) self.library_view.model().set_book_on_device_func(self.book_on_device)
self.status_bar.clearMessage() self.status_bar.clearMessage()
self.search.clear_to_help() self.search.clear_to_help()
self.status_bar.reset_info() self.status_bar.reset_info()
self.library_view.model().count_changed() self.library_view.model().count_changed()
prefs['library_path'] = self.library_path
############################################################################ ############################################################################
@ -2358,7 +2370,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search_restriction.setEnabled(False) self.search_restriction.setEnabled(False)
for action in list(self.delete_menu.actions())[1:]: for action in list(self.delete_menu.actions())[1:]:
action.setEnabled(False) action.setEnabled(False)
self.set_number_of_books_shown(compute_count=False) self.set_number_of_books_shown()
def device_job_exception(self, job): def device_job_exception(self, job):

View File

@ -424,7 +424,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.set_bookmarks(self.iterator.bookmarks) self.set_bookmarks(self.iterator.bookmarks)
def find(self, text, refinement, repeat=False, backwards=False): def find(self, text, repeat=False, backwards=False):
if not text: if not text:
self.view.search('') self.view.search('')
return self.search.search_done(False) return self.search.search_done(False)

View File

@ -241,6 +241,24 @@ class ResultCache(SearchQueryParser):
matches = set([]) matches = set([])
if len(query) < 2: if len(query) < 2:
return matches return matches
if location == 'date':
location = 'timestamp'
loc = self.field_metadata[location]['rec_index']
if query == 'false':
for item in self._data:
if item is None: continue
if item[loc] is None or item[loc] == UNDEFINED_DATE:
matches.add(item[0])
return matches
if query == 'true':
for item in self._data:
if item is None: continue
if item[loc] is not None and item[loc] != UNDEFINED_DATE:
matches.add(item[0])
return matches
relop = None relop = None
for k in self.date_search_relops.keys(): for k in self.date_search_relops.keys():
if query.startswith(k): if query.startswith(k):
@ -249,10 +267,6 @@ class ResultCache(SearchQueryParser):
if relop is None: if relop is None:
(p, relop) = self.date_search_relops['='] (p, relop) = self.date_search_relops['=']
if location == 'date':
location = 'timestamp'
loc = self.field_metadata[location]['rec_index']
if query == _('today'): if query == _('today'):
qd = now() qd = now()
field_count = 3 field_count = 3
@ -301,7 +315,7 @@ class ResultCache(SearchQueryParser):
if query == 'false': if query == 'false':
query = '0' query = '0'
elif query == 'true': elif query == 'true':
query = '>0' query = '!=0'
relop = None relop = None
for k in self.numeric_search_relops.keys(): for k in self.numeric_search_relops.keys():
if query.startswith(k): if query.startswith(k):

View File

@ -38,6 +38,12 @@ def server_config(defaults=None):
c.add_opt('max_opds_items', ['--max-opds-items'], default=30, c.add_opt('max_opds_items', ['--max-opds-items'], default=30,
help=_('The maximum number of matches to return per OPDS query. ' help=_('The maximum number of matches to return per OPDS query. '
'This affects Stanza, WordPlayer, etc. integration.')) 'This affects Stanza, WordPlayer, etc. integration.'))
c.add_opt('max_opds_ungrouped_items', ['--max-opds-ungrouped-items'],
default=100,
help=_('Group items in categories such as author/tags '
'by first letter when there are more than this number '
'of items. Default: %default. Set to a large number '
'to disable grouping.'))
return c return c
def main(): def main():

View File

@ -445,7 +445,7 @@ class OPDSServer(object):
id_ = 'calibre-category-feed:'+which id_ = 'calibre-category-feed:'+which
MAX_ITEMS = 50 MAX_ITEMS = self.opts.max_opds_ungrouped_items
if len(items) <= MAX_ITEMS: if len(items) <= MAX_ITEMS:
max_items = self.opts.max_opds_items max_items = self.opts.max_opds_items
@ -459,8 +459,6 @@ class OPDSServer(object):
self.text, self.count = text, count self.text, self.count = text, count
starts = set([x.name[0] for x in items]) starts = set([x.name[0] for x in items])
if len(starts) > MAX_ITEMS:
starts = set([x.name[:2] for x in items])
category_groups = OrderedDict() category_groups = OrderedDict()
for x in sorted(starts, cmp=lambda x,y:cmp(x.lower(), y.lower())): for x in sorted(starts, cmp=lambda x,y:cmp(x.lower(), y.lower())):
category_groups[x] = len([y for y in items if category_groups[x] = len([y for y in items if

View File

@ -8,16 +8,25 @@ Customizing |app|
================================== ==================================
|app| has a highly modular design. Various parts of it can be customized. You can learn how to create |app| has a highly modular design. Various parts of it can be customized. You can learn how to create
*recipes* to add new sources of online content to |app| in the Section :ref:`news`. Here, you will learn how to *recipes* to add new sources of online content to |app| in the Section :ref:`news`. Here, you will learn,
use *plugins* to customize and control various aspects of |app|'s behavior. first, how to use environment variables and *tweaks* to customize |app|'s behavior and then how to
use *plugins* to add funtionality to |app|.
Theer are different kinds of plugins, corresponding to different aspects of |app|. As more and more aspects of |app|
are modularized, new plugin types will be added.
.. contents:: .. contents::
:depth: 2 :depth: 2
:local: :local:
Environment variables
-----------------------
* ``CALIBRE_CONFIG_DIRECTORY``
* ``CALIBRE_OVERRIDE_DATABASE_PATH``
* ``CALIBRE_DEVELOP_FROM``
* ``CALIBRE_OVERRIDE_LANG``
* ``SYSFS_PATH``
* ``http_proxy``
A Hello World plugin A Hello World plugin
------------------------ ------------------------

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -171,8 +171,7 @@ def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
border_color) border_color)
compose_image(canvas, img, left, top) compose_image(canvas, img, left, top)
p.DestroyMagickWand(img) p.DestroyMagickWand(img)
with open(path_to_image, 'wb') as f: p.MagickWriteImage(canvas,path_to_image)
p.MagickWriteImage(canvas, f)
p.DestroyMagickWand(canvas) p.DestroyMagickWand(canvas)
def create_cover_page(top_lines, logo_path, width=590, height=750, def create_cover_page(top_lines, logo_path, width=590, height=750,

View File

@ -24,6 +24,7 @@ from calibre.ebooks.metadata import MetaInformation
from calibre.web.feeds import feed_from_xml, templates, feeds_from_index, Feed from calibre.web.feeds import feed_from_xml, templates, feeds_from_index, Feed
from calibre.web.fetch.simple import option_parser as web2disk_option_parser from calibre.web.fetch.simple import option_parser as web2disk_option_parser
from calibre.web.fetch.simple import RecursiveFetcher from calibre.web.fetch.simple import RecursiveFetcher
from calibre.utils.magick_draw import add_borders_to_image
from calibre.utils.threadpool import WorkRequest, ThreadPool, NoResultsPending from calibre.utils.threadpool import WorkRequest, ThreadPool, NoResultsPending
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.date import now as nowf from calibre.utils.date import now as nowf
@ -283,6 +284,15 @@ class BasicNewsRecipe(Recipe):
#: Override this in your recipe to provide a url to use as a masthead. #: Override this in your recipe to provide a url to use as a masthead.
masthead_url = None masthead_url = None
#: By default, the cover image returned by get_cover_url() will be used as
#: the cover for the periodical. Overriding this in your recipe instructs
#: calibre to render the downloaded cover into a frame whose width and height
#: are expressed as a percentage of the downloaded cover.
#: cover_margins = (10,15,'white') pads the cover with a white margin
#: 10px on the left and right, 15px on the top and bottom.
#: Colors name defined at http://www.imagemagick.org/script/color.php
cover_margins = (0,0,'white')
#: Set to a non empty string to disable this recipe #: Set to a non empty string to disable this recipe
#: The string will be used as the disabled message #: The string will be used as the disabled message
recipe_disabled = None recipe_disabled = None
@ -974,6 +984,11 @@ class BasicNewsRecipe(Recipe):
self.report_progress(1, _('Downloading cover from %s')%cu) self.report_progress(1, _('Downloading cover from %s')%cu)
with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r): with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r):
cfile.write(r.read()) cfile.write(r.read())
if self.cover_margins[0] or self.cover_margins[1]:
add_borders_to_image(cpath,
left=self.cover_margins[0],right=self.cover_margins[0],
top=self.cover_margins[1],bottom=self.cover_margins[1],
border_color=self.cover_margins[2])
if ext.lower() == 'pdf': if ext.lower() == 'pdf':
from calibre.ebooks.metadata.pdf import get_metadata from calibre.ebooks.metadata.pdf import get_metadata
stream = open(cpath, 'rb') stream = open(cpath, 'rb')

View File

@ -241,7 +241,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
results.add(urn) results.add(urn)
return results return results
def search(self, query, refinement): def search(self, query):
try: try:
results = self.parse(unicode(query)) results = self.parse(unicode(query))
if not results: if not results: