This commit is contained in:
GRiker 2012-07-20 04:54:14 -06:00
commit c21ca60dc9
122 changed files with 48917 additions and 40460 deletions

View File

@ -19,6 +19,54 @@
# new recipes: # new recipes:
# - title: # - title:
- version: 0.8.61
date: 2012-07-20
new features:
- title: "E-book viewer: Add a paged mode that splits up the text into pages, like in a paper book instead of presenting it as a single column. To activate click the button with the yellow scroll icon in the top right corner."
type: major
description: "In paged mode, the ebook viewer no longer cuts off the last line of text at the bottom of the screen, and it respects CSS page-break directives. You can also set page margins and control the number of pages displayed on screen by clicking the Preferences button in the viewer and going to 'Text layout in paged mode'."
- title: "Digitally sign the calibre OS X and windows builds"
- title: "Get Books: Add Mills and Boon UK"
- title: "Various minor improvements to the Bulk metadata edit dialog"
tickets: [1025825, 1025838, 1025628]
- title: "Fix various regression in the auto-complete functionality for authors/series/tags etc introduced in 0.8.60"
- title: "Drivers for various new Android devices"
tickets: [1024934]
- title: "MOBI: Add support for the new language EXTH header field in MOBI files generated by kindlegen 2.5"
bug fixes:
- title: "KF8 Output: Fix calibre produced KF8 files not showing the 'Use publisher font' option on the Kindle Touch when they have embedded fonts"
- title: "Txt/fb2/rtf/pml/rb output: Fix non-visibile element's tail text (which should be visible) is being ignored when it shouldn't."
tickets: [1026541]
- title: "Book details panel: When displaying a link to amazon, use a country specific name like amazon.fr instead of using amazon.com for all countries"
- title: "Conversion: When splitting on page breaks, ignore page-breaks with values of auto and inherit. "
tickets: [1018875]
- title: "Metadata jacket: Specify foreground in addition to the background color for the title banner so that it remain readable if the user tries to monkey with the CSS in the viewer."
- title: "PDF Output: Fix rendering of cover as first age of PDF (ignore margins so that the image covers the entire page)"
- title: "Linux binaries: Bundle libglib to avoid incompatibilities with glib on various distros."
tickets: [1022019]
- title: "Fix find_identical_books() choking on books with too many authors"
improved recipes:
- Toronto Star
- American Prospect
- faz.net
- version: 0.8.60 - version: 0.8.60
date: 2012-07-13 date: 2012-07-13

View File

@ -12,7 +12,11 @@ class AmericanProspect(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
remove_javascript = True remove_javascript = True
keep_only_tags = [dict(name='div', attrs={'class':'pad_10L10R'})] #keep_only_tags = [dict(name='div', attrs={'class':'pad_10L10R'})]
remove_tags = [dict(name='form'), dict(name='div', attrs={'class':['bkt_caption','sharebox noprint','badgebox']})] #remove_tags = [dict(name='form'), dict(name='div', attrs={'class':['bkt_caption','sharebox noprint','badgebox']})]
use_embedded_content = False
no_stylesheets = True
auto_cleanup = True
feeds = [(u'Articles', u'feed://www.prospect.org/articles_rss.jsp')] feeds = [(u'Articles', u'feed://www.prospect.org/articles_rss.jsp')]

View File

@ -6,16 +6,15 @@ from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1325006965(BasicNewsRecipe): class AdvancedUserRecipe1325006965(BasicNewsRecipe):
title = u'The Sun UK' title = u'The Sun UK'
description = 'A Recipe for The Sun tabloid UK' description = 'Articles from The Sun tabloid UK'
__author__ = 'Dave Asbury' __author__ = 'Dave Asbury'
# last updated 29/4/12 # last updated 15/7/12
language = 'en_GB' language = 'en_GB'
oldest_article = 1 oldest_article = 1
max_articles_per_feed = 15 max_articles_per_feed = 15
remove_empty_feeds = True remove_empty_feeds = True
no_stylesheets = True no_stylesheets = True
#auto_cleanup = True
#articles_are_obfuscated = True
masthead_url = 'http://www.thesun.co.uk/sol/img/global/Sun-logo.gif' masthead_url = 'http://www.thesun.co.uk/sol/img/global/Sun-logo.gif'
encoding = 'UTF-8' encoding = 'UTF-8'
@ -34,7 +33,7 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
keep_only_tags = [ keep_only_tags = [
dict(name='h1'),dict(name='h2',attrs={'class' : 'medium centered'}), dict(name='h1'),dict(name='h2',attrs={'class' : ['large','large centered','medium centered','medium']}),dict(name='h3'),
dict(name='div',attrs={'class' : 'text-center'}), dict(name='div',attrs={'class' : 'text-center'}),
dict(name='div',attrs={'id' : 'bodyText'}) dict(name='div',attrs={'id' : 'bodyText'})
# dict(name='p') # dict(name='p')
@ -72,22 +71,18 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
cov2 = str(cov) cov2 = str(cov)
cov2=cov2[27:-18] cov2=cov2[27:-18]
#cov2 now is pic url, now go back to original function #cov2 now is pic url, now go back to original function
br = browser() br = browser()
br.set_handle_redirect(False) br.set_handle_redirect(False)
try: try:
br.open_novisit(cov2) br.open_novisit(cov2)
cover_url = cov2 cover_url = cov2
except: except:
cover_url = random.choice(( cover_url = random.choice([
'http://img.thesun.co.uk/multimedia/archive/00905/errorpage6_677961a_905507a.jpg' 'http://img.thesun.co.uk/multimedia/archive/00905/errorpage6_677961a_905507a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage7_677962a_905505a.jpg' ,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage7_677962a_905505a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage5_677960a_905512a.jpg' ,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage5_677960a_905512a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage2_677957a_905502a.jpg' ,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage2_677957a_905502a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage3_677958a_905503a.jpg' ,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage3_677958a_905503a.jpg'
)) ])
return cover_url return cover_url

View File

@ -16,6 +16,8 @@ class TheTorontoStar(BasicNewsRecipe):
language = 'en_CA' language = 'en_CA'
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
#auto_cleanup = True
#auto_cleanup_keep = '//div[@class="topsContent topsContentActive"]'
use_embedded_content = False use_embedded_content = False
delay = 2 delay = 2
publisher = 'The Toronto Star' publisher = 'The Toronto Star'
@ -28,18 +30,18 @@ class TheTorontoStar(BasicNewsRecipe):
,'publisher' : publisher ,'publisher' : publisher
} }
keep_only_tags = [dict(name='div', attrs={'class':'ts-article'})] #keep_only_tags = [dict(name='div', attrs={'class':'ts-article'})]
remove_tags_before = dict(name='div',attrs={'id':'ts-article_header'}) #remove_tags_before = dict(name='div',attrs={'id':'ts-article_header'})
feeds = [ feeds = [
(u'News' , u'http://www.thestar.com/rss/82672?' ) (u'News' , u'http://www.thestar.com/rss/?categories=293' )
,(u'Opinion' , u'http://www.thestar.com/rss/82863?' ) ,(u'Opinion' , u'http://www.thestar.com/rss/?categories=303' )
,(u'Business' , u'http://www.thestar.com/rss/82796?' ) ,(u'Business' , u'http://www.thestar.com/rss/?categories=294' )
,(u'Sports' , u'http://www.thestar.com/rss/82758?' ) ,(u'Sports' , u'http://www.thestar.com/rss/?categories=295' )
,(u'Entertainment', u'http://www.thestar.com/rss/117741?' ) ,(u'Entertainment', u'http://www.toronto.com/rss?categories=6298' )
,(u'Living' , u'http://www.thestar.com/rss/82839?' ) ,(u'Living' , u'http://www.thestar.com/rss/?categories=297' )
,(u'Travel' , u'http://www.thestar.com/rss/82858?' ) ,(u'Travel' , u'http://www.thestar.com/rss/list/1042246?' )
,(u'Science' , u'http://www.thestar.com/rss/82848?') ,(u'Science' , u'http://www.thestar.com/rss?categories=6481')
] ]
def print_version(self, url): def print_version(self, url):
@ -47,3 +49,4 @@ class TheTorontoStar(BasicNewsRecipe):
artid = artl.rpartition('/')[2] artid = artl.rpartition('/')[2]
return 'http://www.thestar.com/printarticle/' + artid return 'http://www.thestar.com/printarticle/' + artid

Binary file not shown.

View File

@ -516,3 +516,11 @@ default_tweak_format = None
# enable_multicharacters_in_tag_browser = False # enable_multicharacters_in_tag_browser = False
enable_multicharacters_in_tag_browser = True enable_multicharacters_in_tag_browser = True
#: Do not preselect a completion when editing authors/tags/series/etc.
# This means that you can make changes and press Enter and your changes will
# not be overwritten by a matching completion. However, if you wish to use the
# completions you will now have to press Tab to select one before pressing
# Enter. Which technique you prefer will depend on the state of metadata in
# your library and your personal editing style.
preselect_first_completion = False

BIN
resources/images/scroll.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -27,6 +27,7 @@
*/ */
.cbj_banner { .cbj_banner {
background: #eee; background: #eee;
color: black;
border: thin solid black; border: thin solid black;
margin: 1em; margin: 1em;
padding: 1em; padding: 1em;

View File

@ -23,6 +23,7 @@ MAGICK_PREFIX = '/usr'
binary_includes = [ binary_includes = [
'/usr/bin/pdftohtml', '/usr/bin/pdftohtml',
'/usr/bin/pdfinfo', '/usr/bin/pdfinfo',
'/usr/lib/libglib-2.0.so.0',
'/usr/bin/pdftoppm', '/usr/bin/pdftoppm',
'/usr/lib/libwmflite-0.2.so.7', '/usr/lib/libwmflite-0.2.so.7',
'/usr/lib/liblcms.so.1', '/usr/lib/liblcms.so.1',

View File

@ -624,8 +624,9 @@ class Py2App(object):
if os.path.exists(dmg): if os.path.exists(dmg):
os.unlink(dmg) os.unlink(dmg)
tdir = tempfile.mkdtemp() tdir = tempfile.mkdtemp()
shutil.copytree(d, os.path.join(tdir, os.path.basename(d)), appdir = os.path.join(tdir, os.path.basename(d))
symlinks=True) shutil.copytree(d, appdir, symlinks=True)
subprocess.check_call(['/Users/kovid/sign.sh', appdir])
os.symlink('/Applications', os.path.join(tdir, 'Applications')) os.symlink('/Applications', os.path.join(tdir, 'Applications'))
subprocess.check_call(['/usr/bin/hdiutil', 'create', '-srcfolder', tdir, subprocess.check_call(['/usr/bin/hdiutil', 'create', '-srcfolder', tdir,
'-volname', volname, '-format', format, dmg]) '-volname', volname, '-format', format, dmg])

View File

@ -6,11 +6,13 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os, shutil, subprocess import os, shutil, subprocess, re
from setup import Command, __appname__, __version__ from setup import Command, __appname__, __version__
from setup.installer import VMInstaller from setup.installer import VMInstaller
SIGNTOOL = r'C:\cygwin\home\kovid\sign.bat'
class Win(Command): class Win(Command):
description = 'Build windows binary installers' description = 'Build windows binary installers'
@ -34,10 +36,20 @@ class Win32(VMInstaller):
INSTALLER_EXT = 'msi' INSTALLER_EXT = 'msi'
SHUTDOWN_CMD = ['shutdown.exe', '-s', '-f', '-t', '0'] SHUTDOWN_CMD = ['shutdown.exe', '-s', '-f', '-t', '0']
def sign_msi(self):
print ('Signing .msi ...')
raw = open(self.VM).read()
vmx = re.search(r'''launch_vmware\(['"](.+?)['"]''', raw).group(1)
subprocess.check_call(['vmrun', '-T', 'ws', '-gu', 'kovid', '-gp',
"et tu brutus", 'runProgramInGuest', vmx, 'cmd.exe', '/C',
r'C:\cygwin\home\kovid\sign.bat'])
def download_installer(self): def download_installer(self):
installer = self.installer() installer = self.installer()
if os.path.exists('build/winfrozen'): if os.path.exists('build/winfrozen'):
shutil.rmtree('build/winfrozen') shutil.rmtree('build/winfrozen')
self.sign_msi()
subprocess.check_call(('scp', subprocess.check_call(('scp',
'xp_build:build/%s/%s'%(__appname__, installer), 'dist')) 'xp_build:build/%s/%s'%(__appname__, installer), 'dist'))
if not os.path.exists(installer): if not os.path.exists(installer):

View File

@ -23,7 +23,6 @@ SW = r'C:\cygwin\home\kovid\sw'
IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.7.6', IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.7.6',
'VisualMagick', 'bin') 'VisualMagick', 'bin')
CRT = r'C:\Microsoft.VC90.CRT' CRT = r'C:\Microsoft.VC90.CRT'
SIGNTOOL = r'C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\signtool.exe'
VERSION = re.sub('[a-z]\d+', '', __version__) VERSION = re.sub('[a-z]\d+', '', __version__)
WINVER = VERSION+'.0' WINVER = VERSION+'.0'

View File

@ -4,7 +4,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__ = u'calibre' __appname__ = u'calibre'
numeric_version = (0, 8, 60) numeric_version = (0, 8, 61)
__version__ = u'.'.join(map(unicode, numeric_version)) __version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>" __author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -1483,6 +1483,16 @@ class StoreManyBooksStore(StoreBase):
headquarters = 'US' headquarters = 'US'
formats = ['EPUB', 'FB2', 'JAR', 'LIT', 'LRF', 'MOBI', 'PDB', 'PDF', 'RB', 'RTF', 'TCR', 'TXT', 'ZIP'] formats = ['EPUB', 'FB2', 'JAR', 'LIT', 'LRF', 'MOBI', 'PDB', 'PDF', 'RB', 'RTF', 'TCR', 'TXT', 'ZIP']
class StoreMillsBoonUKStore(StoreBase):
name = 'Mills and Boon UK'
author = 'Charles Haley'
description = u'"Bring Romance to Life" "[A] hallmark for romantic fiction, recognised around the world."'
actual_plugin = 'calibre.gui2.store.stores.mills_boon_uk_plugin:MillsBoonUKStore'
headquarters = 'UK'
formats = ['EPUB']
affiliate = True
class StoreMobileReadStore(StoreBase): class StoreMobileReadStore(StoreBase):
name = 'MobileRead' name = 'MobileRead'
description = u'Ebooks handcrafted with the utmost care.' description = u'Ebooks handcrafted with the utmost care.'
@ -1646,6 +1656,7 @@ plugins += [
StoreLibreDEStore, StoreLibreDEStore,
StoreLitResStore, StoreLitResStore,
StoreManyBooksStore, StoreManyBooksStore,
StoreMillsBoonUKStore,
StoreMobileReadStore, StoreMobileReadStore,
StoreNextoStore, StoreNextoStore,
StoreOpenBooksStore, StoreOpenBooksStore,

View File

@ -194,7 +194,7 @@ class ANDROID(USBMS):
'GENERIC-', 'ZTE', 'MID', 'QUALCOMM', 'PANDIGIT', 'HYSTON', 'GENERIC-', 'ZTE', 'MID', 'QUALCOMM', 'PANDIGIT', 'HYSTON',
'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC', 'LENOVO', 'ROCKCHIP', 'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC', 'LENOVO', 'ROCKCHIP',
'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD', 'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD',
'PMP5097C'] 'PMP5097C', 'MASS', 'NOVO7']
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE', WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897', '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID',
@ -211,7 +211,8 @@ class ANDROID(USBMS):
'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW', 'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW',
'KTABLET_PC', 'INGENIC', 'GT-I9001_CARD', 'USB_2.0_DRIVER', 'KTABLET_PC', 'INGENIC', 'GT-I9001_CARD', 'USB_2.0_DRIVER',
'GT-S5830L_CARD', 'UNIVERSE', 'XT875', 'PRO', '.KOBO_VOX', 'GT-S5830L_CARD', 'UNIVERSE', 'XT875', 'PRO', '.KOBO_VOX',
'THINKPAD_TABLET', 'SGH-T989', 'YP-G70'] 'THINKPAD_TABLET', 'SGH-T989', 'YP-G70', 'STORAGE_DEVICE',
'ADVANCED']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD', 'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',

View File

@ -601,17 +601,23 @@ class KOBO(USBMS):
# Unsupported database # Unsupported database
opts = self.settings() opts = self.settings()
if not opts.extra_customization[self.OPT_SUPPORT_NEWER_FIRMWARE]: if not opts.extra_customization[self.OPT_SUPPORT_NEWER_FIRMWARE]:
debug_print('The database has been upgraded past supported version')
debug_print('The database has been upgraded past supported version') debug_print('The database has been upgraded past supported version')
self.report_progress(1.0, _('Removing books from device...')) self.report_progress(1.0, _('Removing books from device...'))
from calibre.devices.errors import UserFeedback from calibre.devices.errors import UserFeedback
raise UserFeedback(_("Kobo database version unsupported - See details"), raise UserFeedback(_("Kobo database version unsupported - See details"),
_('Your Kobo is running an updated firmware/database version. ' _('Your Kobo is running an updated firmware/database version.'
'As Calibre has not been updated, database editing is disabled. ' ' As calibre does not know about this updated firmware,'
'You can enable support for your Kobo in plugin preferences. ' ' database editing is disabled, to prevent corruption.'
'Doing so may require you to perform a factory reset. ' ' You can still send books to your Kobo with calibre, '
'Before selecting the "Attempt to support newer firmware" option ' ' but deleting books and managing collections is disabled.'
'you should be familiar with restoring your Kobo to factory defaults.'), ' If you are willing to experiment and know how to reset'
' your Kobo to Factory defaults, you can override this'
' check by right clicking the device icon in calibre and'
' selecting "Configure this device" and then the '
' "Attempt to support newer firmware" option.'
' Doing so may require you to perform a factory reset of'
' your Kobo.'
),
UserFeedback.WARN) UserFeedback.WARN)
return False return False

View File

@ -352,13 +352,21 @@ class FB2MLizer(object):
@return: List of string representing the XHTML converted to FB2 markup. @return: List of string representing the XHTML converted to FB2 markup.
''' '''
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
elem = elem_tree
# Ensure what we are converting is not a string and that the fist tag is part of the XHTML namespace. # Ensure what we are converting is not a string and that the fist tag is part of the XHTML namespace.
if not isinstance(elem_tree.tag, basestring) or namespace(elem_tree.tag) != XHTML_NS: if not isinstance(elem_tree.tag, basestring) or namespace(elem_tree.tag) != XHTML_NS:
p = elem.getparent()
if p is not None and isinstance(p.tag, basestring) and namespace(p.tag) == XHTML_NS \
and elem.tail:
return [elem.tail]
return [] return []
style = stylizer.style(elem_tree) style = stylizer.style(elem_tree)
if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') or style['visibility'] == 'hidden': if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \
or style['visibility'] == 'hidden':
if hasattr(elem, 'tail') and elem.tail:
return [elem.tail]
return [] return []
# FB2 generated output. # FB2 generated output.

View File

@ -573,8 +573,13 @@ class Amazon(Source):
else: else:
url = 'http://www.amazon.%s/dp/%s'%(domain, asin) url = 'http://www.amazon.%s/dp/%s'%(domain, asin)
if url: if url:
idtype = 'amazon' if self.domain == 'com' else 'amazon_'+self.domain idtype = 'amazon' if domain == 'com' else 'amazon_'+domain
return (idtype, asin, url) return (idtype, asin, url)
def get_book_url_name(self, idtype, idval, url):
if idtype == 'amazon':
return self.name
return 'A' + idtype.replace('_', '.')[1:]
# }}} # }}}
@property @property

View File

@ -443,6 +443,12 @@ class Source(Plugin):
''' '''
return None return None
def get_book_url_name(self, idtype, idval, url):
'''
Return a human readable name from the return value of get_book_url().
'''
return self.name
def get_cached_cover_url(self, identifiers): def get_cached_cover_url(self, identifiers):
''' '''
Return cached cover URL for the book identified by Return cached cover URL for the book identified by

View File

@ -517,7 +517,7 @@ def urls_from_identifiers(identifiers): # {{{
for plugin in all_metadata_plugins(): for plugin in all_metadata_plugins():
try: try:
id_type, id_val, url = plugin.get_book_url(identifiers) id_type, id_val, url = plugin.get_book_url(identifiers)
ans.append((plugin.name, id_type, id_val, url)) ans.append((plugin.get_book_url_name(id_type, id_val, url), id_type, id_val, url))
except: except:
pass pass
isbn = identifiers.get('isbn', None) isbn = identifiers.get('isbn', None)

View File

@ -144,9 +144,9 @@ class EXTHRecord(object):
118 : 'retailprice', 118 : 'retailprice',
119 : 'retailpricecurrency', 119 : 'retailpricecurrency',
121 : 'KF8 header section index', 121 : 'KF8 header section index',
125 : 'KF8 unknown count', 125 : 'KF8 resources (images/fonts) count',
129 : 'KF8 thumbnail URI', 129 : 'KF8 cover URI',
131 : 'KF8 resources (images/fonts) count', 131 : 'KF8 unknown count',
201 : 'coveroffset', 201 : 'coveroffset',
202 : 'thumboffset', 202 : 'thumboffset',
203 : 'hasfakecover', 203 : 'hasfakecover',

View File

@ -29,9 +29,8 @@ EXTH_CODES = {
'versionnumber': 114, 'versionnumber': 114,
'startreading': 116, 'startreading': 116,
'kf8_header_index': 121, 'kf8_header_index': 121,
'kf8_unknown_count': 125, 'num_of_resources': 125,
'kf8_thumbnail_uri': 129, 'kf8_unknown_count': 131,
'num_of_resources': 131,
'coveroffset': 201, 'coveroffset': 201,
'thumboffset': 202, 'thumboffset': 202,
'hasfakecover': 203, 'hasfakecover': 203,
@ -160,10 +159,7 @@ def build_exth(metadata, prefer_author_sort=False, is_periodical=False,
if thumbnail_offset is not None: if thumbnail_offset is not None:
exth.write(pack(b'>III', EXTH_CODES['thumboffset'], 12, exth.write(pack(b'>III', EXTH_CODES['thumboffset'], 12,
thumbnail_offset)) thumbnail_offset))
cover_uri_str = bytes('kindle:embed:%04X' %(thumbnail_offset)) nrecs += 1
exth.write(pack(b'>II', EXTH_CODES['kf8_thumbnail_uri'], len(cover_uri_str) + 8))
exth.write(cover_uri_str)
nrecs += 2
if start_offset is not None: if start_offset is not None:
try: try:

View File

@ -26,16 +26,54 @@ class PagedDisplay
this.current_margin_side = 0 this.current_margin_side = 0
this.is_full_screen_layout = false this.is_full_screen_layout = false
this.max_col_width = -1 this.max_col_width = -1
this.document_margins = null
this.use_document_margins = false
read_document_margins: () ->
# Read page margins from the document. First checks for an @page rule.
# If that is not found, side margins are set to the side margins of the
# body element.
if this.document_margins is null
this.document_margins = {left:null, top:null, right:null, bottom:null}
tmp = document.createElement('div')
tmp.style.visibility = 'hidden'
tmp.style.position = 'absolute'
document.body.appendChild(tmp)
for sheet in document.styleSheets
for rule in sheet.rules
if rule.type == CSSRule.PAGE_RULE
for prop in ['left', 'top', 'bottom', 'right']
val = rule.style.getPropertyValue('margin-'+prop)
if val
tmp.style.height = val
pxval = parseInt(window.getComputedStyle(tmp).height)
if not isNaN(pxval)
this.document_margins[prop] = pxval
document.body.removeChild(tmp)
if this.document_margins.left is null
val = parseInt(window.getComputedStyle(document.body).marginLeft)
if not isNaN(val)
this.document_margins.left = val
if this.document_margins.right is null
val = parseInt(window.getComputedStyle(document.body).marginRight)
if not isNaN(val)
this.document_margins.right = val
set_geometry: (cols_per_screen=1, margin_top=20, margin_side=40, margin_bottom=20) -> set_geometry: (cols_per_screen=1, margin_top=20, margin_side=40, margin_bottom=20) ->
this.cols_per_screen = cols_per_screen
if this.use_document_margins and this.document_margins != null
this.margin_top = this.document_margins.top or margin_top
this.margin_bottom = this.document_margins.bottom or margin_bottom
this.margin_side = this.document_margins.left or this.document_margins.right or margin_side
else
this.margin_top = margin_top this.margin_top = margin_top
this.margin_side = margin_side this.margin_side = margin_side
this.margin_bottom = margin_bottom this.margin_bottom = margin_bottom
this.cols_per_screen = cols_per_screen
layout: () -> layout: () ->
# start_time = new Date().getTime() # start_time = new Date().getTime()
body_style = window.getComputedStyle(document.body) body_style = window.getComputedStyle(document.body)
bs = document.body.style
# When laying body out in columns, webkit bleeds the top margin of the # When laying body out in columns, webkit bleeds the top margin of the
# first block element out above the columns, leading to an extra top # first block element out above the columns, leading to an extra top
# margin for the page. We compensate for that here. Computing the # margin for the page. We compensate for that here. Computing the
@ -43,8 +81,10 @@ class PagedDisplay
# it before the column layout is applied. # it before the column layout is applied.
first_layout = false first_layout = false
if not this.in_paged_mode if not this.in_paged_mode
document.body.style.marginTop = '0px' bs.setProperty('margin-top', '0px')
extra_margin = document.body.getBoundingClientRect().top extra_margin = document.body.getBoundingClientRect().top
if extra_margin <= this.margin_top
extra_margin = 0
margin_top = (this.margin_top - extra_margin) + 'px' margin_top = (this.margin_top - extra_margin) + 'px'
# Check if the current document is a full screen layout like # Check if the current document is a full screen layout like
# cover, if so we treat it specially. # cover, if so we treat it specially.
@ -78,7 +118,6 @@ class PagedDisplay
this.screen_width = this.page_width * this.cols_per_screen this.screen_width = this.page_width * this.cols_per_screen
fgcolor = body_style.getPropertyValue('color') fgcolor = body_style.getPropertyValue('color')
bs = document.body.style
bs.setProperty('-webkit-column-gap', (2*sm)+'px') bs.setProperty('-webkit-column-gap', (2*sm)+'px')
bs.setProperty('-webkit-column-width', col_width+'px') bs.setProperty('-webkit-column-width', col_width+'px')
@ -101,7 +140,7 @@ class PagedDisplay
# Convert page-breaks to column-breaks # Convert page-breaks to column-breaks
for sheet in document.styleSheets for sheet in document.styleSheets
for rule in sheet.rules for rule in sheet.rules
if rule.type == 1 # CSSStyleRule if rule.type == CSSRule.STYLE_RULE
for prop in ['page-break-before', 'page-break-after', 'page-break-inside'] for prop in ['page-break-before', 'page-break-after', 'page-break-inside']
val = rule.style.getPropertyValue(prop) val = rule.style.getPropertyValue(prop)
if val if val
@ -144,6 +183,19 @@ class PagedDisplay
img.style.setProperty('max-width', max_width+'px') img.style.setProperty('max-width', max_width+'px')
calibre_utils.store(img, 'width-limited', true) calibre_utils.store(img, 'width-limited', true)
check_top_margin: () ->
# This is needed to handle the case when a descendant of body specifies
# a top margin as a percentage, which messes up the top margin
# calculations above
tm = document.body.getBoundingClientRect().top
if tm != this.margin_top
document.body.style.setProperty('margin-top', '0px')
tm = document.body.getBoundingClientRect().top
if tm <= this.margin_top
tm = 0
m = this.margin_top - tm
document.body.style.setProperty('margin-top', m+'px')
scroll_to_pos: (frac) -> scroll_to_pos: (frac) ->
# Scroll to the position represented by frac (number between 0 and 1) # Scroll to the position represented by frac (number between 0 and 1)
xpos = Math.floor(document.body.scrollWidth * frac) xpos = Math.floor(document.body.scrollWidth * frac)
@ -348,4 +400,3 @@ if window?
# TODO: # TODO:
# Highlight on jump_to_anchor # Highlight on jump_to_anchor
# Handle document specified margins and allow them to be overridden

View File

@ -82,7 +82,7 @@ class Split(object):
after = getattr(rule.style.getPropertyCSSValue( after = getattr(rule.style.getPropertyCSSValue(
'page-break-after'), 'cssText', '').strip().lower() 'page-break-after'), 'cssText', '').strip().lower()
try: try:
if before and before != 'avoid': if before and before not in {'avoid', 'auto', 'inherit'}:
self.page_break_selectors.add((CSSSelector(rule.selectorText), self.page_break_selectors.add((CSSSelector(rule.selectorText),
True)) True))
if self.remove_css_pagebreaks: if self.remove_css_pagebreaks:
@ -90,7 +90,7 @@ class Split(object):
except: except:
pass pass
try: try:
if after and after != 'avoid': if after and after not in {'avoid', 'auto', 'inherit'}:
self.page_break_selectors.add((CSSSelector(rule.selectorText), self.page_break_selectors.add((CSSSelector(rule.selectorText),
False)) False))
if self.remove_css_pagebreaks: if self.remove_css_pagebreaks:

View File

@ -202,6 +202,7 @@ class PDFWriter(QObject): # {{{
paged_display.set_geometry(1, 0, 0, 0); paged_display.set_geometry(1, 0, 0, 0);
paged_display.layout(); paged_display.layout();
paged_display.fit_images(); paged_display.fit_images();
paged_display.check_top_margin();
''') ''')
mf = self.view.page().mainFrame() mf = self.view.page().mainFrame()
while True: while True:
@ -223,7 +224,8 @@ class PDFWriter(QObject): # {{{
if self.cover_data is None: if self.cover_data is None:
return return
item_path = os.path.join(self.tmp_path, 'cover.pdf') item_path = os.path.join(self.tmp_path, 'cover.pdf')
printer = get_pdf_printer(self.opts, output_file_name=item_path) printer = get_pdf_printer(self.opts, output_file_name=item_path,
for_comic=True)
self.combine_queue.insert(0, item_path) self.combine_queue.insert(0, item_path)
p = QPixmap() p = QPixmap()
p.loadFromData(self.cover_data) p.loadFromData(self.cover_data)

View File

@ -220,8 +220,11 @@ class PMLMLizer(object):
def dump_text(self, elem, stylizer, page, tag_stack=[]): def dump_text(self, elem, stylizer, page, tag_stack=[]):
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
if not isinstance(elem.tag, basestring) \ if not isinstance(elem.tag, basestring) or namespace(elem.tag) != XHTML_NS:
or namespace(elem.tag) != XHTML_NS: p = elem.getparent()
if p is not None and isinstance(p.tag, basestring) and namespace(p.tag) == XHTML_NS \
and elem.tail:
return [elem.tail]
return [] return []
text = [] text = []
@ -230,6 +233,8 @@ class PMLMLizer(object):
if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \ if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \
or style['visibility'] == 'hidden': or style['visibility'] == 'hidden':
if hasattr(elem, 'tail') and elem.tail:
return [elem.tail]
return [] return []
tag = barename(elem.tag) tag = barename(elem.tag)

View File

@ -142,8 +142,11 @@ class RBMLizer(object):
def dump_text(self, elem, stylizer, page, tag_stack=[]): def dump_text(self, elem, stylizer, page, tag_stack=[]):
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
if not isinstance(elem.tag, basestring) \ if not isinstance(elem.tag, basestring) or namespace(elem.tag) != XHTML_NS:
or namespace(elem.tag) != XHTML_NS: p = elem.getparent()
if p is not None and isinstance(p.tag, basestring) and namespace(p.tag) == XHTML_NS \
and elem.tail:
return [elem.tail]
return [u''] return [u'']
text = [u''] text = [u'']
@ -151,6 +154,8 @@ class RBMLizer(object):
if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \ if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \
or style['visibility'] == 'hidden': or style['visibility'] == 'hidden':
if hasattr(elem, 'tail') and elem.tail:
return [elem.tail]
return [u''] return [u'']
tag = barename(elem.tag) tag = barename(elem.tag)

View File

@ -229,6 +229,8 @@ class RTFMLizer(object):
if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \ if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \
or style['visibility'] == 'hidden': or style['visibility'] == 'hidden':
if hasattr(elem, 'tail') and elem.tail:
return elem.tail
return u'' return u''
tag = barename(elem.tag) tag = barename(elem.tag)

View File

@ -212,6 +212,10 @@ class SNBMLizer(object):
if not isinstance(elem.tag, basestring) \ if not isinstance(elem.tag, basestring) \
or namespace(elem.tag) != XHTML_NS: or namespace(elem.tag) != XHTML_NS:
p = elem.getparent()
if p is not None and isinstance(p.tag, basestring) and namespace(p.tag) == XHTML_NS \
and elem.tail:
return [elem.tail]
return [''] return ['']
@ -225,6 +229,8 @@ class SNBMLizer(object):
if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \ if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \
or style['visibility'] == 'hidden': or style['visibility'] == 'hidden':
if hasattr(elem, 'tail') and elem.tail:
return [elem.tail]
return [''] return ['']
tag = barename(elem.tag) tag = barename(elem.tag)

View File

@ -127,6 +127,8 @@ class MarkdownMLizer(OEB2HTML):
# Ignore anything that is set to not be displayed. # Ignore anything that is set to not be displayed.
if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \ if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \
or style['visibility'] == 'hidden': or style['visibility'] == 'hidden':
if hasattr(elem, 'tail') and elem.tail:
return [elem.tail]
return [''] return ['']
# Soft scene breaks. # Soft scene breaks.

View File

@ -241,6 +241,8 @@ class TextileMLizer(OEB2HTML):
# Ignore anything that is set to not be displayed. # Ignore anything that is set to not be displayed.
if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \ if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \
or style['visibility'] == 'hidden': or style['visibility'] == 'hidden':
if hasattr(elem, 'tail') and elem.tail:
return [elem.tail]
return [''] return ['']
# Soft scene breaks. # Soft scene breaks.

View File

@ -200,6 +200,8 @@ class TXTMLizer(object):
if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \ if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \
or style['visibility'] == 'hidden': or style['visibility'] == 'hidden':
if hasattr(elem, 'tail') and elem.tail:
return [elem.tail]
return [''] return ['']
tag = barename(elem.tag) tag = barename(elem.tag)

View File

@ -16,6 +16,7 @@ from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, pyqtSignal, QObject,
from calibre.utils.icu import sort_key, primary_startswith from calibre.utils.icu import sort_key, primary_startswith
from calibre.gui2 import NONE from calibre.gui2 import NONE
from calibre.gui2.widgets import EnComboBox, LineEditECM from calibre.gui2.widgets import EnComboBox, LineEditECM
from calibre.utils.config import tweaks
class CompleteModel(QAbstractListModel): # {{{ class CompleteModel(QAbstractListModel): # {{{
@ -157,8 +158,8 @@ class Completer(QListView): # {{{
p.setGeometry(pos.x(), pos.y(), w, h) p.setGeometry(pos.x(), pos.y(), w, h)
if (select_first and not self.currentIndex().isValid() and if (tweaks['preselect_first_completion'] and select_first and not
self.model().rowCount() > 0): self.currentIndex().isValid() and self.model().rowCount() > 0):
self.setCurrentIndex(self.model().index(0)) self.setCurrentIndex(self.model().index(0))
if not p.isVisible(): if not p.isVisible():
@ -184,17 +185,19 @@ class Completer(QListView): # {{{
e.accept() e.accept()
return True return True
if key in (Qt.Key_Enter, Qt.Key_Return): if key in (Qt.Key_Enter, Qt.Key_Return):
if not self.currentIndex().isValid(): # We handle this explicitly because on OS X activated() is
# not emitted on pressing Enter.
idx = self.currentIndex()
if idx.isValid():
self.item_chosen(idx)
self.hide() self.hide()
e.accept() e.accept()
return True return True
return False if key in (Qt.Key_PageUp, Qt.Key_PageDown):
if key in (Qt.Key_End, Qt.Key_Home, Qt.Key_Up, Qt.Key_Down,
Qt.Key_PageUp, Qt.Key_PageDown):
# Let the list view handle these keys # Let the list view handle these keys
return False return False
if key in (Qt.Key_Tab, Qt.Key_Backtab): if key in (Qt.Key_Up, Qt.Key_Down):
self.next_match(previous=key == Qt.Key_Backtab) self.next_match(previous=key == Qt.Key_Up)
e.accept() e.accept()
return True return True
# Send to widget # Send to widget
@ -211,6 +214,8 @@ class Completer(QListView): # {{{
self.hide() self.hide()
e.accept() e.accept()
return True return True
elif etype == e.ShortcutOverride:
QApplication.sendEvent(widget, e)
return False return False
# }}} # }}}

View File

@ -934,6 +934,11 @@ class DeviceMixin(object): # {{{
fmt = None fmt = None
if specific: if specific:
if (not self.device_connected or not self.device_manager or
self.device_manager.device is None):
error_dialog(self, _('No device'),
_('No device connected'), show=True)
return
formats = [] formats = []
aval_out_formats = available_output_formats() aval_out_formats = available_output_formats()
format_count = {} format_count = {}

View File

@ -370,6 +370,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
geom = gprefs.get('bulk_metadata_window_geometry', None) geom = gprefs.get('bulk_metadata_window_geometry', None)
if geom is not None: if geom is not None:
self.restoreGeometry(bytes(geom)) self.restoreGeometry(bytes(geom))
ct = gprefs.get('bulk_metadata_window_tab', 0)
self.central_widget.setCurrentIndex(ct)
self.languages.init_langs(self.db) self.languages.init_langs(self.db)
self.languages.setEditText('') self.languages.setEditText('')
self.authors.setFocus(Qt.OtherFocusReason) self.authors.setFocus(Qt.OtherFocusReason)
@ -378,6 +380,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
def save_state(self, *args): def save_state(self, *args):
gprefs['bulk_metadata_window_geometry'] = \ gprefs['bulk_metadata_window_geometry'] = \
bytearray(self.saveGeometry()) bytearray(self.saveGeometry())
gprefs['bulk_metadata_window_tab'] = self.central_widget.currentIndex()
def do_apply_pubdate(self, *args): def do_apply_pubdate(self, *args):
self.apply_pubdate.setChecked(True) self.apply_pubdate.setChecked(True)
@ -410,7 +413,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
(fm[f]['datatype'] in ['text', 'series', 'enumeration', 'comments'] (fm[f]['datatype'] in ['text', 'series', 'enumeration', 'comments']
and fm[f].get('search_terms', None) and fm[f].get('search_terms', None)
and f not in ['formats', 'ondevice']) or and f not in ['formats', 'ondevice']) or
(fm[f]['datatype'] in ['int', 'float', 'bool'] and (fm[f]['datatype'] in ['int', 'float', 'bool', 'datetime'] and
f not in ['id'])): f not in ['id'])):
self.all_fields.append(f) self.all_fields.append(f)
self.writable_fields.append(f) self.writable_fields.append(f)
@ -527,7 +530,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.queries = JSONConfig("search_replace_queries") self.queries = JSONConfig("search_replace_queries")
self.query_field.addItem("") self.query_field.addItem("")
self.query_field.addItems(sorted([q for q in self.queries], key=sort_key)) self.query_field_values = sorted([q for q in self.queries], key=sort_key)
self.query_field.addItems(self.query_field_values)
self.query_field.currentIndexChanged[str].connect(self.s_r_query_change) self.query_field.currentIndexChanged[str].connect(self.s_r_query_change)
self.query_field.setCurrentIndex(0) self.query_field.setCurrentIndex(0)
self.search_field.setCurrentIndex(0) self.search_field.setCurrentIndex(0)
@ -1030,11 +1034,16 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.queries.commit() self.queries.commit()
def s_r_save_query(self, *args): def s_r_save_query(self, *args):
name, ok = QInputDialog.getText(self, _('Save search/replace'), dex = self.query_field_values.index(self.saved_search_name)
_('Search/replace name:')) name = ''
while not name:
name, ok = QInputDialog.getItem(self, _('Save search/replace'),
_('Search/replace name:'), self.query_field_values, dex, True)
if not ok: if not ok:
return return
if not name:
error_dialog(self, _("Save search/replace"),
_("You must provide a name."), show=True)
new = True new = True
name = unicode(name) name = unicode(name)
if name in self.queries.keys(): if name in self.queries.keys():
@ -1069,7 +1078,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.query_field.blockSignals(True) self.query_field.blockSignals(True)
self.query_field.clear() self.query_field.clear()
self.query_field.addItem('') self.query_field.addItem('')
self.query_field.addItems(sorted([q for q in self.queries], key=sort_key)) self.query_field_values = sorted([q for q in self.queries], key=sort_key)
self.query_field.addItems(self.query_field_values)
self.query_field.blockSignals(False) self.query_field.blockSignals(False)
self.query_field.setCurrentIndex(self.query_field.findText(name)) self.query_field.setCurrentIndex(self.query_field.findText(name))
@ -1081,6 +1091,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
if item is None: if item is None:
self.s_r_reset_query_fields() self.s_r_reset_query_fields()
return return
self.saved_search_name = item_name
def set_text(attr, key): def set_text(attr, key):
try: try:

View File

@ -667,7 +667,7 @@ Future conversion of these books will use the default settings.</string>
<string>Load searc&amp;h/replace:</string> <string>Load searc&amp;h/replace:</string>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>search_field</cstring> <cstring>query_field</cstring>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import urllib2
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class MillsBoonUKStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://www.awin1.com/awclick.php?mid=1150&id=120917'
detail_url = 'http://www.awin1.com/cread.php?awinmid=1150&awinaffid=120917&clickref=&p='
if external or self.config.get('open_external', False):
if detail_item:
url = detail_url + detail_item
open_url(QUrl(url_slash_cleaner(url)))
else:
detail_url = None
if detail_item:
detail_url = url + detail_item
d = WebStoreDialog(self.gui, url, parent, detail_url)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
base_url = 'http://millsandboon.co.uk'
url = base_url + '/pages/searchres.htm?search=true&booktypesearch=ebook&first=yes&inputsearch=' + urllib2.quote(query)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//div[@class="catProdDiv"]'):
if counter <= 0:
break
id_ = ''.join(data.xpath('.//div[@class="catProdImage"]/div/a/@href')).strip()
id_ = base_url + id_[2:]
if not id_:
continue
cover_url = ''.join(data.xpath('.//div[@class="catProdImage"]/div/a/img/@src'))
cover_url = base_url + cover_url[2:]
title = ''.join(data.xpath('.//div[@class="catProdImage"]/div/a/img/@alt')).strip()
title = title[23:]
author = ''.join(data.xpath('.//div[@class="catProdDetails"]/div[@class="catProdDetails-top"]/p[1]/a/text()'))
price = ''.join(data.xpath('.//span[@class="priceBold"]/text()'))
format_ = ''.join(data.xpath('.//p[@class="doc-meta-format"]/span[last()]/text()'))
drm = SearchResult.DRM_LOCKED
counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.title = title.strip()
s.author = author.strip()
s.price = price
s.detail_item = id_
s.drm = drm
s.formats = format_
yield s

View File

@ -53,6 +53,11 @@ def config(defaults=None):
'0 and 1.')) '0 and 1.'))
c.add_opt('fullscreen_clock', default=False, action='store_true', c.add_opt('fullscreen_clock', default=False, action='store_true',
help=_('Show a clock in fullscreen mode.')) help=_('Show a clock in fullscreen mode.'))
c.add_opt('cols_per_screen', default=1)
c.add_opt('use_book_margins', default=False, action='store_true')
c.add_opt('top_margin', default=20)
c.add_opt('side_margin', default=40)
c.add_opt('bottom_margin', default=20)
fonts = c.add_group('FONTS', _('Font options')) fonts = c.add_group('FONTS', _('Font options'))
fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif', fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif',
@ -120,6 +125,11 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.hyphenate_default_lang.setVisible(False) self.hyphenate_default_lang.setVisible(False)
self.hyphenate_label.setVisible(False) self.hyphenate_label.setVisible(False)
self.opt_fullscreen_clock.setChecked(opts.fullscreen_clock) self.opt_fullscreen_clock.setChecked(opts.fullscreen_clock)
self.opt_cols_per_screen.setValue(opts.cols_per_screen)
self.opt_override_book_margins.setChecked(not opts.use_book_margins)
for x in ('top', 'bottom', 'side'):
getattr(self, 'opt_%s_margin'%x).setValue(getattr(opts,
x+'_margin'))
def accept(self, *args): def accept(self, *args):
if self.shortcut_config.is_editing: if self.shortcut_config.is_editing:
@ -152,6 +162,11 @@ class ConfigDialog(QDialog, Ui_Dialog):
c.set('line_scrolling_stops_on_pagebreaks', c.set('line_scrolling_stops_on_pagebreaks',
self.opt_line_scrolling_stops_on_pagebreaks.isChecked()) self.opt_line_scrolling_stops_on_pagebreaks.isChecked())
c.set('fullscreen_clock', self.opt_fullscreen_clock.isChecked()) c.set('fullscreen_clock', self.opt_fullscreen_clock.isChecked())
c.set('cols_per_screen', int(self.opt_cols_per_screen.value()))
c.set('use_book_margins', not
self.opt_override_book_margins.isChecked())
for x in ('top', 'bottom', 'side'):
c.set(x+'_margin', int(getattr(self, 'opt_%s_margin'%x).value()))
return QDialog.accept(self, *args) return QDialog.accept(self, *args)

View File

@ -18,16 +18,6 @@
<normaloff>:/images/config.png</normaloff>:/images/config.png</iconset> <normaloff>:/images/config.png</normaloff>:/images/config.png</iconset>
</property> </property>
<layout class="QGridLayout" name="gridLayout_4"> <layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QTabWidget" name="tabs"> <widget class="QTabWidget" name="tabs">
<property name="currentIndex"> <property name="currentIndex">
@ -39,13 +29,42 @@
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QToolBox" name="toolBox">
<property name="title"> <property name="styleSheet">
<string>&amp;Font options</string> <string notr="true">QToolBox::tab {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #E1E1E1, stop: 0.4 #DDDDDD,
stop: 0.5 #D8D8D8, stop: 1.0 #D3D3D3);
border-radius: 5px;
color: black;
font-weight: bold;
}
QToolBox::tab:selected {
font-style: italic;
}
QToolBox::tab:hover {
color: red;
font-style: italic;
}</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <property name="currentIndex">
<item row="0" column="0"> <number>0</number>
<layout class="QGridLayout" name="gridLayout"> </property>
<widget class="QWidget" name="page">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>811</width>
<height>384</height>
</rect>
</property>
<attribute name="label">
<string>&amp;Font options</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@ -160,38 +179,221 @@
</item> </item>
</widget> </widget>
</item> </item>
</layout> <item row="6" column="0">
</item> <widget class="QLabel" name="label_12">
</layout>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="hyphenate">
<property name="text"> <property name="text">
<string>H&amp;yphenate (break line in the middle of large words)</string> <string>Font &amp;magnification step size:</string>
</property>
<property name="buddy">
<cstring>opt_font_mag_step</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="6" column="1">
<widget class="QComboBox" name="hyphenate_default_lang"> <widget class="QSpinBox" name="opt_font_mag_step">
<property name="toolTip"> <property name="toolTip">
<string>The default language to use for hyphenation rules. If the book does not specify a language, this will be used.</string> <string>The amount by which the font size is increased/decreased
when you click the font size larger/smaller buttons</string>
</property>
<property name="suffix">
<string>%</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="0"> </layout>
<widget class="QLabel" name="hyphenate_label"> </widget>
<widget class="QWidget" name="page_5">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>811</width>
<height>384</height>
</rect>
</property>
<attribute name="label">
<string>Text &amp;layout in paged mode</string>
</attribute>
<layout class="QFormLayout" name="formLayout_5">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_8">
<property name="text"> <property name="text">
<string>Default &amp;language for hyphenation:</string> <string>&lt;p&gt;These options only apply in &quot;paged&quot; mode, where the text is broken up into pages, as in a paper book. To get into this mode, use the button with the yellow scroll icon in the top right corner of the viewer window.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>The number of &amp;pages of text to show on screen </string>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>hyphenate_default_lang</cstring> <cstring>opt_cols_per_screen</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="opt_cols_per_screen">
<property name="suffix">
<string> page(s)</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>5</number>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_override_book_margins">
<property name="text">
<string>&amp;Override the page margin settings specified in the book</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>&amp;Top margin</string>
</property>
<property name="buddy">
<cstring>opt_top_margin</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="opt_top_margin">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>1000</number>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>&amp;Side margin</string>
</property>
<property name="buddy">
<cstring>opt_side_margin</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="opt_side_margin">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>&amp;Bottom margin</string>
</property>
<property name="buddy">
<cstring>opt_bottom_margin</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="opt_bottom_margin">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>811</width>
<height>384</height>
</rect>
</property>
<attribute name="label">
<string>F&amp;ull screen options</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Maximum text width in &amp;fullscreen:</string>
</property>
<property name="buddy">
<cstring>max_fs_width</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="max_fs_width">
<property name="toolTip">
<string>Set the maximum width that the book's text and pictures will take when in fullscreen mode. This allows you to read the book text without it becoming too wide.</string>
</property>
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="opt_fullscreen_clock">
<property name="text">
<string>Show &amp;clock in full screen mode</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>811</width>
<height>384</height>
</rect>
</property>
<attribute name="label">
<string>Page Fl&amp;ip options</string>
</attribute>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_11"> <widget class="QLabel" name="label_11">
<property name="text"> <property name="text">
<string>Page flip &amp;duration:</string> <string>Page flip &amp;duration:</string>
@ -201,7 +403,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="0" column="1">
<widget class="QDoubleSpinBox" name="opt_page_flip_duration"> <widget class="QDoubleSpinBox" name="opt_page_flip_duration">
<property name="specialValueText"> <property name="specialValueText">
<string>disabled</string> <string>disabled</string>
@ -226,96 +428,83 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="1" column="0" colspan="2">
<widget class="QSpinBox" name="max_fs_width">
<property name="toolTip">
<string>Set the maximum width that the book's text and pictures will take when in fullscreen mode. This allows you to read the book text without it becoming too wide.</string>
</property>
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Maximum text width in &amp;fullscreen:</string>
</property>
<property name="buddy">
<cstring>max_fs_width</cstring>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Font &amp;magnification step size:</string>
</property>
<property name="buddy">
<cstring>opt_font_mag_step</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="opt_font_mag_step">
<property name="toolTip">
<string>The amount by which the font size is increased/decreased
when you click the font size larger/smaller buttons</string>
</property>
<property name="suffix">
<string>%</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="opt_fit_images">
<property name="text">
<string>&amp;Resize images larger than the viewer window (needs restart)</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QCheckBox" name="opt_remember_window_size">
<property name="text">
<string>Remember last used &amp;window size and layout</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="opt_wheel_flips_pages"> <widget class="QCheckBox" name="opt_wheel_flips_pages">
<property name="text"> <property name="text">
<string>Mouse &amp;wheel flips pages</string> <string>Mouse &amp;wheel flips pages</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="1"> <item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remember_current_page">
<property name="text">
<string>Remember the &amp;current page when quitting</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="opt_line_scrolling_stops_on_pagebreaks"> <widget class="QCheckBox" name="opt_line_scrolling_stops_on_pagebreaks">
<property name="text"> <property name="text">
<string>Line &amp;scrolling stops at page breaks</string> <string>Line &amp;scrolling stops at page breaks</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1"> <item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_fullscreen_clock"> <widget class="QCheckBox" name="opt_fit_images">
<property name="text"> <property name="text">
<string>Show &amp;clock in full screen mode</string> <string>&amp;Resize images larger than the viewer window (needs restart)</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget>
<widget class="QWidget" name="page_4">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>811</width>
<height>384</height>
</rect>
</property>
<attribute name="label">
<string>&amp;Miscellaneous options</string>
</attribute>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="hyphenate">
<property name="text">
<string>H&amp;yphenate (break line in the middle of large words)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="hyphenate_label">
<property name="text">
<string>Default &amp;language for hyphenation:</string>
</property>
<property name="buddy">
<cstring>hyphenate_default_lang</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="hyphenate_default_lang">
<property name="toolTip">
<string>The default language to use for hyphenation rules. If the book does not specify a language, this will be used.</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remember_window_size">
<property name="text">
<string>Remember last used &amp;window size and layout</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remember_current_page">
<property name="text">
<string>Remember the &amp;current page when quitting</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -361,14 +550,19 @@
</widget> </widget>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>serif_family</tabstop>
<tabstop>sans_family</tabstop>
<tabstop>mono_family</tabstop>
<tabstop>max_fs_width</tabstop>
<tabstop>opt_remember_window_size</tabstop>
<tabstop>buttonBox</tabstop> <tabstop>buttonBox</tabstop>
</tabstops> </tabstops>
<resources> <resources>
@ -382,8 +576,8 @@
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>252</x> <x>258</x>
<y>569</y> <y>623</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>157</x> <x>157</x>
@ -398,8 +592,8 @@
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>320</x> <x>326</x>
<y>569</y> <y>623</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>286</x> <x>286</x>
@ -414,12 +608,60 @@
<slot>setEnabled(bool)</slot> <slot>setEnabled(bool)</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>83</x> <x>89</x>
<y>279</y> <y>226</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>349</x> <x>332</x>
<y>312</y> <y>259</y>
</hint>
</hints>
</connection>
<connection>
<sender>opt_override_book_margins</sender>
<signal>toggled(bool)</signal>
<receiver>opt_top_margin</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>137</x>
<y>189</y>
</hint>
<hint type="destinationlabel">
<x>367</x>
<y>218</y>
</hint>
</hints>
</connection>
<connection>
<sender>opt_override_book_margins</sender>
<signal>toggled(bool)</signal>
<receiver>opt_side_margin</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>71</x>
<y>193</y>
</hint>
<hint type="destinationlabel">
<x>347</x>
<y>253</y>
</hint>
</hints>
</connection>
<connection>
<sender>opt_override_book_margins</sender>
<signal>toggled(bool)</signal>
<receiver>opt_bottom_margin</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>513</x>
<y>196</y>
</hint>
<hint type="destinationlabel">
<x>371</x>
<y>281</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>

View File

@ -22,7 +22,6 @@ from calibre.gui2.viewer.javascript import JavaScriptLoader
from calibre.gui2.viewer.position import PagePosition from calibre.gui2.viewer.position import PagePosition
from calibre.gui2.viewer.config import config, ConfigDialog from calibre.gui2.viewer.config import config, ConfigDialog
from calibre.ebooks.oeb.display.webview import load_html from calibre.ebooks.oeb.display.webview import load_html
from calibre.utils.config import tweaks
from calibre.constants import isxp from calibre.constants import isxp
# }}} # }}}
@ -60,7 +59,7 @@ class Document(QWebPage): # {{{
def __init__(self, shortcuts, parent=None, debug_javascript=False): def __init__(self, shortcuts, parent=None, debug_javascript=False):
QWebPage.__init__(self, parent) QWebPage.__init__(self, parent)
self.setObjectName("py_bridge") self.setObjectName("py_bridge")
self.in_paged_mode = tweaks.get('viewer_test_paged_mode', False) self.in_paged_mode = False
# Use this to pass arbitrary JSON encodable objects between python and # Use this to pass arbitrary JSON encodable objects between python and
# javascript. In python get/set the value as: self.bridge_value. In # javascript. In python get/set the value as: self.bridge_value. In
# javascript, get/set the value as: py_bridge.value # javascript, get/set the value as: py_bridge.value
@ -135,6 +134,10 @@ class Document(QWebPage): # {{{
# Leave some space for the scrollbar and some border # Leave some space for the scrollbar and some border
self.max_fs_width = min(opts.max_fs_width, screen_width-50) self.max_fs_width = min(opts.max_fs_width, screen_width-50)
self.fullscreen_clock = opts.fullscreen_clock self.fullscreen_clock = opts.fullscreen_clock
self.use_book_margins = opts.use_book_margins
self.cols_per_screen = opts.cols_per_screen
self.side_margin = opts.side_margin
self.top_margin, self.bottom_margin = opts.top_margin, opts.bottom_margin
def fit_images(self): def fit_images(self):
if self.do_fit_images and not self.in_paged_mode: if self.do_fit_images and not self.in_paged_mode:
@ -180,6 +183,7 @@ class Document(QWebPage): # {{{
fset=_pass_json_value_setter) fset=_pass_json_value_setter)
def after_load(self): def after_load(self):
self.javascript('window.paged_display.read_document_margins()')
self.set_bottom_padding(0) self.set_bottom_padding(0)
self.fit_images() self.fit_images()
self.init_hyphenate() self.init_hyphenate()
@ -216,6 +220,14 @@ class Document(QWebPage): # {{{
def switch_to_paged_mode(self, onresize=False): def switch_to_paged_mode(self, onresize=False):
if onresize and not self.loaded_javascript: if onresize and not self.loaded_javascript:
return return
self.javascript('''
window.paged_display.use_document_margins = %s;
window.paged_display.set_geometry(%d, %d, %d, %d);
'''%(
('true' if self.use_book_margins else 'false'),
self.cols_per_screen, self.top_margin, self.side_margin,
self.bottom_margin
))
side_margin = self.javascript('window.paged_display.layout()', typ=int) side_margin = self.javascript('window.paged_display.layout()', typ=int)
# Setup the contents size to ensure that there is a right most margin. # Setup the contents size to ensure that there is a right most margin.
# Without this webkit renders the final column with no margin, as the # Without this webkit renders the final column with no margin, as the
@ -229,6 +241,7 @@ class Document(QWebPage): # {{{
sz.setWidth(scroll_width+side_margin) sz.setWidth(scroll_width+side_margin)
self.setPreferredContentsSize(sz) self.setPreferredContentsSize(sz)
self.javascript('window.paged_display.fit_images()') self.javascript('window.paged_display.fit_images()')
self.javascript('window.paged_display.check_top_margin()')
@property @property
def column_boundaries(self): def column_boundaries(self):
@ -647,6 +660,7 @@ class DocumentView(QWebView): # {{{
def load_path(self, path, pos=0.0): def load_path(self, path, pos=0.0):
self.initial_pos = pos self.initial_pos = pos
self.last_loaded_path = path
def callback(lu): def callback(lu):
self.loading_url = lu self.loading_url = lu
@ -654,7 +668,7 @@ class DocumentView(QWebView): # {{{
self.manager.load_started() self.manager.load_started()
load_html(path, self, codec=getattr(path, 'encoding', 'utf-8'), mime_type=getattr(path, load_html(path, self, codec=getattr(path, 'encoding', 'utf-8'), mime_type=getattr(path,
'mime_type', None), pre_load_callback=callback) 'mime_type', 'text/html'), pre_load_callback=callback)
entries = set() entries = set()
for ie in getattr(path, 'index_entries', []): for ie in getattr(path, 'index_entries', []):
if ie.start_anchor: if ie.start_anchor:

View File

@ -152,6 +152,10 @@ class RecentAction(QAction):
class EbookViewer(MainWindow, Ui_EbookViewer): class EbookViewer(MainWindow, Ui_EbookViewer):
STATE_VERSION = 1 STATE_VERSION = 1
FLOW_MODE_TT = _('Switch to paged mode - where the text is broken up '
'into pages like a paper book')
PAGED_MODE_TT = _('Switch to flow mode - where the text is not broken up '
'into pages')
def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None): def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None):
MainWindow.__init__(self, None) MainWindow.__init__(self, None)
@ -168,6 +172,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.pending_anchor = None self.pending_anchor = None
self.pending_reference = None self.pending_reference = None
self.pending_bookmark = None self.pending_bookmark = None
self.pending_restore = False
self.existing_bookmarks= [] self.existing_bookmarks= []
self.selected_text = None self.selected_text = None
self.read_settings() self.read_settings()
@ -339,6 +344,22 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.addAction(action) self.addAction(action)
self.restore_state() self.restore_state()
self.action_toggle_paged_mode.toggled[bool].connect(self.toggle_paged_mode)
def toggle_paged_mode(self, checked, at_start=False):
in_paged_mode = not self.action_toggle_paged_mode.isChecked()
self.view.document.in_paged_mode = in_paged_mode
self.action_toggle_paged_mode.setToolTip(self.FLOW_MODE_TT if
self.action_toggle_paged_mode.isChecked() else
self.PAGED_MODE_TT)
if at_start: return
self.reload()
def reload(self):
if hasattr(self, 'current_index') and self.current_index > -1:
self.view.document.page_position.save(overwrite=False)
self.pending_restore = True
self.load_path(self.view.last_loaded_path)
def set_toc_visible(self, yes): def set_toc_visible(self, yes):
self.toc.setVisible(yes) self.toc.setVisible(yes)
@ -394,6 +415,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
vprefs.set('viewer_splitter_state', vprefs.set('viewer_splitter_state',
bytearray(self.splitter.saveState())) bytearray(self.splitter.saveState()))
vprefs['multiplier'] = self.view.multiplier vprefs['multiplier'] = self.view.multiplier
vprefs['in_paged_mode1'] = not self.action_toggle_paged_mode.isChecked()
def restore_state(self): def restore_state(self):
state = vprefs.get('viewer_toolbar_state', None) state = vprefs.get('viewer_toolbar_state', None)
@ -410,6 +432,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
# specific location, ensure they are visible. # specific location, ensure they are visible.
self.tool_bar.setVisible(True) self.tool_bar.setVisible(True)
self.tool_bar2.setVisible(True) self.tool_bar2.setVisible(True)
self.action_toggle_paged_mode.setChecked(not vprefs.get('in_paged_mode1',
False))
self.toggle_paged_mode(self.action_toggle_paged_mode.isChecked(),
at_start=True)
def lookup(self, word): def lookup(self, word):
self.dictionary_view.setHtml('<html><body><p>'+ \ self.dictionary_view.setHtml('<html><body><p>'+ \
@ -716,6 +742,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
if self.pending_bookmark is not None: if self.pending_bookmark is not None:
self.goto_bookmark(self.pending_bookmark) self.goto_bookmark(self.pending_bookmark)
self.pending_bookmark = None self.pending_bookmark = None
if self.pending_restore:
self.view.document.page_position.restore()
return self.current_index return self.current_index
def goto_next_section(self): def goto_next_section(self):

View File

@ -143,6 +143,7 @@
</attribute> </attribute>
<addaction name="action_find_next"/> <addaction name="action_find_next"/>
<addaction name="action_find_previous"/> <addaction name="action_find_previous"/>
<addaction name="action_toggle_paged_mode"/>
</widget> </widget>
<action name="action_back"> <action name="action_back">
<property name="icon"> <property name="icon">
@ -309,6 +310,18 @@
<string>Shift+F3</string> <string>Shift+F3</string>
</property> </property>
</action> </action>
<action name="action_toggle_paged_mode">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/scroll.png</normaloff>:/images/scroll.png</iconset>
</property>
<property name="text">
<string>Toggle Paged mode</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View File

@ -50,7 +50,8 @@ class PagePosition(object):
def __exit__(self, *args): def __exit__(self, *args):
self.restore() self.restore()
def save(self): def save(self, overwrite=True):
if not overwrite and self._cpos is not None: return
self._cpos = self.current_pos self._cpos = self.current_pos
def restore(self): def restore(self):

View File

@ -71,6 +71,7 @@ class Printing(QObject):
paged_display.set_geometry(1, 0, 0, 0); paged_display.set_geometry(1, 0, 0, 0);
paged_display.layout(); paged_display.layout();
paged_display.fit_images(); paged_display.fit_images();
paged_display.check_top_margin();
''') ''')
while True: while True:

View File

@ -1096,8 +1096,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
identical_book_ids = set([]) identical_book_ids = set([])
if mi.authors: if mi.authors:
try: try:
quathors = mi.authors[:10] # Too many authors causes parsing of
# the search expression to fail
query = u' and '.join([u'author:"=%s"'%(a.replace('"', '')) for a in query = u' and '.join([u'author:"=%s"'%(a.replace('"', '')) for a in
mi.authors]) quathors])
qauthors = mi.authors[10:]
except ValueError: except ValueError:
return identical_book_ids return identical_book_ids
try: try:
@ -1105,6 +1108,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
except: except:
traceback.print_exc() traceback.print_exc()
return identical_book_ids return identical_book_ids
if qauthors and book_ids:
matches = set()
qauthors = {lower(x) for x in qauthors}
for book_id in book_ids:
aut = self.authors(book_id, index_is_id=True)
if aut:
aut = {lower(x.replace('|', ',')) for x in
aut.split(',')}
if aut.issuperset(qauthors):
matches.add(book_id)
book_ids = matches
for book_id in book_ids: for book_id in book_ids:
fbook_title = self.title(book_id, index_is_id=True) fbook_title = self.title(book_id, index_is_id=True)
fbook_title = fuzzy_title(fbook_title) fbook_title = fuzzy_title(fbook_title)
@ -2351,6 +2366,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# This can repeat what was done above in rare cases. Let it. # This can repeat what was done above in rare cases. Let it.
ss = self.author_sort_from_book(id, index_is_id=True) ss = self.author_sort_from_book(id, index_is_id=True)
self._update_author_in_cache(id, ss, final_authors) self._update_author_in_cache(id, ss, final_authors)
self.clean_standard_field('authors', commit=True)
return books_to_refresh return books_to_refresh
def set_authors(self, id, authors, notify=True, commit=True, def set_authors(self, id, authors, notify=True, commit=True,

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

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

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

Some files were not shown because too many files have changed in this diff Show More