mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG + GwR revisions
This commit is contained in:
commit
045a893d20
@ -4,6 +4,38 @@
|
||||
# for important features/bug fixes.
|
||||
# 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
|
||||
date: 2010-06-04
|
||||
|
||||
|
BIN
resources/images/news/haaretz_en.png
Normal file
BIN
resources/images/news/haaretz_en.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 712 B |
25
resources/recipes/cbc_canada.recipe
Normal file
25
resources/recipes/cbc_canada.recipe
Normal 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')]
|
57
resources/recipes/haaretz_en.recipe
Normal file
57
resources/recipes/haaretz_en.recipe
Normal 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
|
@ -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', '.prc')
|
||||
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')
|
||||
guess_type = mimetypes.guess_type
|
||||
import cssutils
|
||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.7.0'
|
||||
__version__ = '0.7.1'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
import re
|
||||
|
@ -1163,7 +1163,7 @@ class ITUNES(DevicePlugin):
|
||||
hits = lib_books.Search(cached_book['author'],SearchField.index('Artists'))
|
||||
if 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']:
|
||||
self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist))
|
||||
return hit
|
||||
@ -1359,7 +1359,6 @@ class ITUNES(DevicePlugin):
|
||||
if DEBUG:
|
||||
self.log.info('ITUNES._get_library_books():\n No Books playlist')
|
||||
|
||||
|
||||
elif iswindows:
|
||||
lib = None
|
||||
try:
|
||||
@ -1466,7 +1465,7 @@ class ITUNES(DevicePlugin):
|
||||
cmd = "defaults read com.apple.itunes NSNavLastRootDirectory"
|
||||
proc = subprocess.Popen( cmd, shell=True, cwd=os.curdir, stdout=subprocess.PIPE)
|
||||
retcode = proc.wait()
|
||||
media_dir = proc.communicate()[0].strip()
|
||||
media_dir = os.path.abspath(proc.communicate()[0].strip())
|
||||
if os.path.exists(media_dir):
|
||||
self.iTunes_media = media_dir
|
||||
else:
|
||||
@ -1493,12 +1492,13 @@ class ITUNES(DevicePlugin):
|
||||
soup = BeautifulSoup(xml.read().decode('utf-8'))
|
||||
mf = soup.find('key',text="Music Folder").parent
|
||||
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):
|
||||
self.iTunes_media = media_dir
|
||||
else:
|
||||
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' not found" % media_dir)
|
||||
|
||||
if DEBUG:
|
||||
self.log.info( " [%s - %s (%s), driver version %d.%d.%d]" %
|
||||
@ -1514,13 +1514,7 @@ class ITUNES(DevicePlugin):
|
||||
'''
|
||||
if isosx:
|
||||
storage_path = os.path.split(cached_book['lib_book'].location().path)
|
||||
presumptive_path = os.path.join(self.iTunes_media,
|
||||
'iTunes Music',
|
||||
cached_book['author'][0],
|
||||
cached_book['title'],
|
||||
storage_path[1])
|
||||
|
||||
if os.path.exists(presumptive_path):
|
||||
if cached_book['lib_book'].location().path.startswith(self.iTunes_media):
|
||||
title_storage_path = storage_path[0]
|
||||
if DEBUG:
|
||||
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(" %s" % '\n'.join(author_files))
|
||||
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'])
|
||||
|
||||
@ -1560,15 +1554,7 @@ class ITUNES(DevicePlugin):
|
||||
if book:
|
||||
path = book.Location
|
||||
storage_path = os.path.split(book.Location)
|
||||
# This assumes that 'Books' will be the storage subdirectory in
|
||||
# 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 book.Location.startswith(self.iTunes_media):
|
||||
if DEBUG:
|
||||
self.log.info("ITUNES._remove_from_iTunes():")
|
||||
self.log.info(" removing '%s' at %s" %
|
||||
@ -1585,7 +1571,7 @@ class ITUNES(DevicePlugin):
|
||||
|
||||
# Delete from iTunes database
|
||||
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()
|
||||
|
||||
|
@ -445,6 +445,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
self.username.setText(opts.username)
|
||||
self.password.setText(opts.password if opts.password else '')
|
||||
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.systray_icon.setChecked(config['systray_icon'])
|
||||
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('max_cover', mcs)
|
||||
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['upload_news_to_device'] = self.sync_news.isChecked()
|
||||
config['search_as_you_type'] = self.search_as_you_type.isChecked()
|
||||
|
@ -892,6 +892,26 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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 &ungrouped items:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_max_opds_ungrouped_items</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -213,7 +213,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
self.endInsertRows()
|
||||
self.count_changed()
|
||||
|
||||
def search(self, text, refinement, reset=True):
|
||||
def search(self, text, reset=True):
|
||||
try:
|
||||
self.db.search(text)
|
||||
except ParseException:
|
||||
@ -224,9 +224,10 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
self.clear_caches()
|
||||
self.reset()
|
||||
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)
|
||||
|
||||
|
||||
def sort(self, col, order, reset=True):
|
||||
if not self.db:
|
||||
return
|
||||
@ -257,7 +258,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
self.sort(col, self.sorted_on[1], reset=reset)
|
||||
|
||||
def research(self, reset=True):
|
||||
self.search(self.last_search, False, reset=reset)
|
||||
self.search(self.last_search, reset=reset)
|
||||
|
||||
def columnCount(self, parent):
|
||||
if parent and parent.isValid():
|
||||
@ -730,6 +731,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
|
||||
def set_search_restriction(self, s):
|
||||
self.db.data.set_search_restriction(s)
|
||||
self.search('')
|
||||
return self.rowCount(None)
|
||||
|
||||
# }}}
|
||||
|
||||
@ -874,7 +877,7 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
return flags
|
||||
|
||||
|
||||
def search(self, text, refinement, reset=True):
|
||||
def search(self, text, reset=True):
|
||||
if not text or not text.strip():
|
||||
self.map = list(range(len(self.db)))
|
||||
else:
|
||||
@ -1086,7 +1089,6 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
idx = self.map[row]
|
||||
if cname == 'title' :
|
||||
self.db[idx].title = val
|
||||
self.db[idx].title_sorter = val
|
||||
elif cname == 'authors':
|
||||
self.db[idx].authors = string_to_authors(val)
|
||||
elif cname == 'collections':
|
||||
|
@ -437,10 +437,6 @@ class BooksView(QTableView): # {{{
|
||||
self._search_done = 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):
|
||||
self._model.new_bookdisplay_data.connect(bd)
|
||||
|
||||
|
@ -152,7 +152,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.stack.setCurrentIndex(1)
|
||||
self.renderer.start()
|
||||
|
||||
def find(self, search, refinement):
|
||||
def find(self, search):
|
||||
self.last_search = search
|
||||
try:
|
||||
self.document.search(search)
|
||||
|
@ -57,7 +57,7 @@ class SearchBox2(QComboBox):
|
||||
INTERVAL = 1500 #: Time to wait before emitting search signal
|
||||
MAX_COUNT = 25
|
||||
|
||||
search = pyqtSignal(object, object)
|
||||
search = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QComboBox.__init__(self, parent)
|
||||
@ -97,8 +97,12 @@ class SearchBox2(QComboBox):
|
||||
self.help_state = False
|
||||
|
||||
def clear_to_help(self):
|
||||
self.search.emit('')
|
||||
self._in_a_search = False
|
||||
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.help_state = True
|
||||
self.line_edit.setStyleSheet(
|
||||
@ -111,7 +115,6 @@ class SearchBox2(QComboBox):
|
||||
|
||||
def clear(self):
|
||||
self.clear_to_help()
|
||||
self.search.emit('', False)
|
||||
|
||||
def search_done(self, ok):
|
||||
if not unicode(self.currentText()).strip():
|
||||
@ -155,9 +158,8 @@ class SearchBox2(QComboBox):
|
||||
if not text or text == self.help_text:
|
||||
return self.clear()
|
||||
self.help_state = False
|
||||
refinement = text.startswith(self.prev_search) and ':' not in text
|
||||
self.prev_search = text
|
||||
self.search.emit(text, refinement)
|
||||
self.search.emit(text)
|
||||
|
||||
idx = self.findText(text, Qt.MatchFixedString)
|
||||
self.block_signals(True)
|
||||
@ -187,12 +189,15 @@ class SearchBox2(QComboBox):
|
||||
self.set_search_string(joiner.join(tags))
|
||||
|
||||
def set_search_string(self, txt):
|
||||
if not txt:
|
||||
self.clear_to_help()
|
||||
return
|
||||
self.normalize_state()
|
||||
self.setEditText(txt)
|
||||
if self.timer is not None: # Turn off any timers that got started in setEditText
|
||||
self.killTimer(self.timer)
|
||||
self.timer = None
|
||||
self.search.emit(txt, False)
|
||||
self.search.emit(txt)
|
||||
self.line_edit.end(False)
|
||||
self.initial_state = False
|
||||
|
||||
|
@ -22,7 +22,6 @@ from calibre.gui2 import error_dialog
|
||||
class TagsView(QTreeView): # {{{
|
||||
|
||||
refresh_required = pyqtSignal()
|
||||
restriction_set = pyqtSignal(object)
|
||||
tags_marked = pyqtSignal(object, object)
|
||||
user_category_edit = pyqtSignal(object)
|
||||
tag_list_edit = pyqtSignal(object, object)
|
||||
@ -37,24 +36,23 @@ class TagsView(QTreeView): # {{{
|
||||
self.setIconSize(QSize(30, 30))
|
||||
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._model = TagsModel(db, parent=self,
|
||||
hidden_categories=self.hidden_categories)
|
||||
hidden_categories=self.hidden_categories,
|
||||
search_restriction=None)
|
||||
self.popularity = popularity
|
||||
self.restriction = restriction
|
||||
self.tag_match = tag_match
|
||||
self.db = db
|
||||
self.search_restriction = None
|
||||
self.setModel(self._model)
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.clicked.connect(self.toggle)
|
||||
self.customContextMenuRequested.connect(self.show_context_menu)
|
||||
self.popularity.setChecked(config['sort_by_popularity'])
|
||||
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)
|
||||
db.add_listener(self.database_changed)
|
||||
self.saved_searches_changed(recount=False)
|
||||
|
||||
def database_changed(self, event, ids):
|
||||
self.refresh_required.emit()
|
||||
@ -68,16 +66,12 @@ class TagsView(QTreeView): # {{{
|
||||
self.model().refresh()
|
||||
# self.search_restriction_set()
|
||||
|
||||
def search_restriction_set(self, s):
|
||||
self.clear()
|
||||
if len(s) == 0:
|
||||
self.search_restriction = ''
|
||||
def set_search_restriction(self, s):
|
||||
if s:
|
||||
self.search_restriction = s
|
||||
else:
|
||||
self.search_restriction = 'search:"%s"' % unicode(s).strip()
|
||||
self.model().set_search_restriction(self.search_restriction)
|
||||
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)
|
||||
self.search_restriction = None
|
||||
self.set_new_model()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
# Swallow everything except leftButton so context menus work correctly
|
||||
@ -187,21 +181,8 @@ class TagsView(QTreeView): # {{{
|
||||
return True
|
||||
|
||||
def clear(self):
|
||||
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()
|
||||
if self.model():
|
||||
self.model().clear_state()
|
||||
|
||||
def recount(self, *args):
|
||||
ci = self.currentIndex()
|
||||
@ -223,7 +204,8 @@ class TagsView(QTreeView): # {{{
|
||||
# model. Reason: it is much easier than reconstructing the browser tree.
|
||||
def set_new_model(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)
|
||||
# }}}
|
||||
|
||||
@ -311,7 +293,7 @@ class TagTreeItem(object): # {{{
|
||||
|
||||
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)
|
||||
|
||||
# must do this here because 'QPixmap: Must construct a QApplication
|
||||
@ -333,8 +315,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
self.db = db
|
||||
self.tags_view = parent
|
||||
self.hidden_categories = hidden_categories
|
||||
self.search_restriction = ''
|
||||
self.ignore_next_search = 0
|
||||
self.search_restriction = search_restriction
|
||||
|
||||
# Reconstruct the user categories, putting them into metadata
|
||||
tb_cats = self.db.field_metadata
|
||||
@ -370,9 +351,10 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
self.row_map = []
|
||||
self.categories = []
|
||||
|
||||
if len(self.search_restriction):
|
||||
data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map,
|
||||
ids=self.db.search(self.search_restriction, return_matches=True))
|
||||
if self.search_restriction:
|
||||
data = self.db.get_categories(sort_on_count=sort,
|
||||
icon_map=self.category_icon_map,
|
||||
ids=self.db.search('', return_matches=True))
|
||||
else:
|
||||
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.row_map.append(category)
|
||||
self.categories.append(tb_categories[category]['name'])
|
||||
|
||||
return data
|
||||
|
||||
def refresh(self):
|
||||
@ -544,12 +525,6 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
def clear_state(self):
|
||||
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):
|
||||
if not index.isValid(): return False
|
||||
item = index.internalPointer()
|
||||
@ -557,7 +532,6 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
item.toggle()
|
||||
if exclusive:
|
||||
self.reset_all_states(except_=item.tag)
|
||||
self.ignore_next_search = 2
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
return False
|
||||
|
@ -160,9 +160,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.restriction_in_effect = False
|
||||
self.search.initialize('main_search_history', colorize=True,
|
||||
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.search_clear()
|
||||
self.search.clear()
|
||||
|
||||
self.saved_search.initialize(saved_searches, self.search, colorize=True,
|
||||
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.donate_action, SIGNAL('triggered(bool)'), self.donate)
|
||||
self.connect(self.restore_action, SIGNAL('triggered()'),
|
||||
self.show_windows)
|
||||
self.show_windows)
|
||||
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.restart)
|
||||
self.connect(self.system_tray_icon,
|
||||
SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
|
||||
self.system_tray_icon_activated)
|
||||
SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
|
||||
self.system_tray_icon_activated)
|
||||
self.tool_bar.contextMenuEvent = self.no_op
|
||||
|
||||
####################### Start spare job server ########################
|
||||
@ -521,8 +521,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.search_done)),
|
||||
('connect_to_book_display',
|
||||
(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):
|
||||
getattr(view, func)(*args)
|
||||
@ -545,24 +543,22 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.cover_cache.start()
|
||||
self.library_view.model().cover_cache = self.cover_cache
|
||||
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)
|
||||
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.tag_list_edit.connect(self.do_tags_list_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.tag_item_renamed.connect(self.do_tag_item_renamed)
|
||||
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,
|
||||
self.restriction_count_changed):
|
||||
self.library_view.model().count_changed_signal.connect(x)
|
||||
|
||||
self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared)
|
||||
self.connect(self.saved_search, SIGNAL('changed()'),
|
||||
self.tags_view.saved_searches_changed, Qt.QueuedConnection)
|
||||
self.connect(self.saved_search, SIGNAL('changed()'), self.saved_searches_changed)
|
||||
self.saved_searches_changed()
|
||||
if not gprefs.get('quick_start_guide_added', False):
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
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.setMinimumContentsLength(10)
|
||||
|
||||
|
||||
########################### Cover Flow ################################
|
||||
self.cover_flow = None
|
||||
if CoverFlow is not None:
|
||||
@ -625,7 +620,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.sidebar.job_done, Qt.QueuedConnection)
|
||||
|
||||
|
||||
|
||||
if config['autolaunch_server']:
|
||||
from calibre.library.server.main import start_threaded_server
|
||||
from calibre.library.server import server_config
|
||||
@ -683,7 +677,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
d = SavedSearchEditor(self, search)
|
||||
d.exec_()
|
||||
if d.result() == d.Accepted:
|
||||
self.tags_view.saved_searches_changed(recount=True)
|
||||
self.saved_searches_changed()
|
||||
self.saved_search.clear_to_help()
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
@ -842,19 +836,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
sm.select(idx, sm.ClearAndSelect|sm.Rows)
|
||||
self.library_view.setCurrentIndex(idx)
|
||||
|
||||
|
||||
|
||||
'''
|
||||
Handling of the count of books in a restricted view requires that
|
||||
we capture the count after the initial restriction search. To so this,
|
||||
we require that the restriction_set signal be issued before the search signal,
|
||||
so that when the search_done happens and the count is displayed,
|
||||
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
|
||||
Restrictions.
|
||||
Adding and deleting books creates a complexity. When added, they are
|
||||
displayed regardless of whether they match a search restriction. However, if
|
||||
they do not, they are removed at the next search. The counts must take this
|
||||
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_library = c
|
||||
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):
|
||||
self.restriction_in_effect = False if r is None or not r else True
|
||||
def apply_search_restriction(self, r):
|
||||
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 compute_count:
|
||||
self.restriction_count_of_books_in_view = self.current_view().row_count()
|
||||
t = _("({0} of {1})").format(self.current_view().row_count(),
|
||||
self.restriction_count_of_books_in_view)
|
||||
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)
|
||||
|
||||
def search_box_cleared(self):
|
||||
self.set_number_of_books_shown(compute_count=True)
|
||||
self.tags_view.clear()
|
||||
self.saved_search.clear_to_help()
|
||||
|
||||
def search_clear(self):
|
||||
self.set_number_of_books_shown(compute_count=True)
|
||||
self.search.clear()
|
||||
self.set_number_of_books_shown()
|
||||
|
||||
def search_done(self, view, ok):
|
||||
if view is self.current_view():
|
||||
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):
|
||||
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):
|
||||
if newloc is None: return
|
||||
db = LibraryDatabase2(newloc)
|
||||
self.library_path = newloc
|
||||
self.book_on_device(None, reset=True)
|
||||
db.set_book_on_device_func(self.book_on_device)
|
||||
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.status_bar.clearMessage()
|
||||
self.search.clear_to_help()
|
||||
self.status_bar.reset_info()
|
||||
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)
|
||||
for action in list(self.delete_menu.actions())[1:]:
|
||||
action.setEnabled(False)
|
||||
self.set_number_of_books_shown(compute_count=False)
|
||||
self.set_number_of_books_shown()
|
||||
|
||||
|
||||
def device_job_exception(self, job):
|
||||
|
@ -424,7 +424,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
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:
|
||||
self.view.search('')
|
||||
return self.search.search_done(False)
|
||||
|
@ -241,6 +241,24 @@ class ResultCache(SearchQueryParser):
|
||||
matches = set([])
|
||||
if len(query) < 2:
|
||||
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
|
||||
for k in self.date_search_relops.keys():
|
||||
if query.startswith(k):
|
||||
@ -249,10 +267,6 @@ class ResultCache(SearchQueryParser):
|
||||
if relop is None:
|
||||
(p, relop) = self.date_search_relops['=']
|
||||
|
||||
if location == 'date':
|
||||
location = 'timestamp'
|
||||
loc = self.field_metadata[location]['rec_index']
|
||||
|
||||
if query == _('today'):
|
||||
qd = now()
|
||||
field_count = 3
|
||||
@ -301,7 +315,7 @@ class ResultCache(SearchQueryParser):
|
||||
if query == 'false':
|
||||
query = '0'
|
||||
elif query == 'true':
|
||||
query = '>0'
|
||||
query = '!=0'
|
||||
relop = None
|
||||
for k in self.numeric_search_relops.keys():
|
||||
if query.startswith(k):
|
||||
|
@ -38,6 +38,12 @@ def server_config(defaults=None):
|
||||
c.add_opt('max_opds_items', ['--max-opds-items'], default=30,
|
||||
help=_('The maximum number of matches to return per OPDS query. '
|
||||
'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
|
||||
|
||||
def main():
|
||||
|
@ -445,7 +445,7 @@ class OPDSServer(object):
|
||||
|
||||
id_ = 'calibre-category-feed:'+which
|
||||
|
||||
MAX_ITEMS = 50
|
||||
MAX_ITEMS = self.opts.max_opds_ungrouped_items
|
||||
|
||||
if len(items) <= MAX_ITEMS:
|
||||
max_items = self.opts.max_opds_items
|
||||
@ -459,8 +459,6 @@ class OPDSServer(object):
|
||||
self.text, self.count = text, count
|
||||
|
||||
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()
|
||||
for x in sorted(starts, cmp=lambda x,y:cmp(x.lower(), y.lower())):
|
||||
category_groups[x] = len([y for y in items if
|
||||
|
@ -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
|
||||
*recipes* to add new sources of online content to |app| in the Section :ref:`news`. Here, you will learn how to
|
||||
use *plugins* to customize and control various aspects of |app|'s behavior.
|
||||
|
||||
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.
|
||||
*recipes* to add new sources of online content to |app| in the Section :ref:`news`. Here, you will learn,
|
||||
first, how to use environment variables and *tweaks* to customize |app|'s behavior and then how to
|
||||
use *plugins* to add funtionality to |app|.
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
:local:
|
||||
|
||||
Environment variables
|
||||
-----------------------
|
||||
|
||||
* ``CALIBRE_CONFIG_DIRECTORY``
|
||||
* ``CALIBRE_OVERRIDE_DATABASE_PATH``
|
||||
* ``CALIBRE_DEVELOP_FROM``
|
||||
* ``CALIBRE_OVERRIDE_LANG``
|
||||
* ``SYSFS_PATH``
|
||||
* ``http_proxy``
|
||||
|
||||
|
||||
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
@ -171,8 +171,7 @@ def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
|
||||
border_color)
|
||||
compose_image(canvas, img, left, top)
|
||||
p.DestroyMagickWand(img)
|
||||
with open(path_to_image, 'wb') as f:
|
||||
p.MagickWriteImage(canvas, f)
|
||||
p.MagickWriteImage(canvas,path_to_image)
|
||||
p.DestroyMagickWand(canvas)
|
||||
|
||||
def create_cover_page(top_lines, logo_path, width=590, height=750,
|
||||
|
@ -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.fetch.simple import option_parser as web2disk_option_parser
|
||||
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.ptempfile import PersistentTemporaryFile
|
||||
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.
|
||||
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
|
||||
#: The string will be used as the disabled message
|
||||
recipe_disabled = None
|
||||
@ -974,6 +984,11 @@ class BasicNewsRecipe(Recipe):
|
||||
self.report_progress(1, _('Downloading cover from %s')%cu)
|
||||
with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r):
|
||||
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':
|
||||
from calibre.ebooks.metadata.pdf import get_metadata
|
||||
stream = open(cpath, 'rb')
|
||||
|
@ -241,7 +241,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
results.add(urn)
|
||||
return results
|
||||
|
||||
def search(self, query, refinement):
|
||||
def search(self, query):
|
||||
try:
|
||||
results = self.parse(unicode(query))
|
||||
if not results:
|
||||
|
Loading…
x
Reference in New Issue
Block a user