mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG wip 0.7.33+
This commit is contained in:
commit
167fbe94a4
@ -5,6 +5,7 @@ newscientist.com
|
||||
'''
|
||||
|
||||
import re
|
||||
import urllib
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class NewScientist(BasicNewsRecipe):
|
||||
@ -24,7 +25,7 @@ class NewScientist(BasicNewsRecipe):
|
||||
needs_subscription = 'optional'
|
||||
extra_css = """
|
||||
body{font-family: Arial,sans-serif}
|
||||
img{margin-bottom: 0.8em}
|
||||
img{margin-bottom: 0.8em; display: block}
|
||||
.quotebx{font-size: x-large; font-weight: bold; margin-right: 2em; margin-left: 2em}
|
||||
"""
|
||||
|
||||
@ -42,11 +43,13 @@ class NewScientist(BasicNewsRecipe):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
br.open('http://www.newscientist.com/')
|
||||
if self.username is not None and self.password is not None:
|
||||
br.open('https://www.newscientist.com/user/login?redirectURL=')
|
||||
br.select_form(nr=2)
|
||||
br['loginId' ] = self.username
|
||||
br['password'] = self.password
|
||||
br.submit()
|
||||
br.open('https://www.newscientist.com/user/login')
|
||||
data = urllib.urlencode({ 'source':'form'
|
||||
,'redirectURL':''
|
||||
,'loginId':self.username
|
||||
,'password':self.password
|
||||
})
|
||||
br.open('https://www.newscientist.com/user/login',data)
|
||||
return br
|
||||
|
||||
remove_tags = [
|
||||
@ -55,21 +58,22 @@ class NewScientist(BasicNewsRecipe):
|
||||
,dict(name='p' , attrs={'class':['marker','infotext' ]})
|
||||
,dict(name='meta' , attrs={'name' :'description' })
|
||||
,dict(name='a' , attrs={'rel' :'tag' })
|
||||
,dict(name='ul' , attrs={'class':'markerlist' })
|
||||
,dict(name=['link','base','meta','iframe','object','embed'])
|
||||
]
|
||||
remove_tags_after = dict(attrs={'class':['nbpcopy','comments']})
|
||||
remove_attributes = ['height','width','lang']
|
||||
remove_attributes = ['height','width','lang','onclick']
|
||||
|
||||
feeds = [
|
||||
(u'Latest Headlines' , u'http://feeds.newscientist.com/science-news' )
|
||||
,(u'Magazine' , u'http://www.newscientist.com/feed/magazine' )
|
||||
,(u'Health' , u'http://www.newscientist.com/feed/view?id=2&type=channel' )
|
||||
,(u'Life' , u'http://www.newscientist.com/feed/view?id=3&type=channel' )
|
||||
,(u'Space' , u'http://www.newscientist.com/feed/view?id=6&type=channel' )
|
||||
,(u'Physics and Mathematics' , u'http://www.newscientist.com/feed/view?id=4&type=channel' )
|
||||
,(u'Environment' , u'http://www.newscientist.com/feed/view?id=1&type=channel' )
|
||||
,(u'Science in Society' , u'http://www.newscientist.com/feed/view?id=5&type=channel' )
|
||||
,(u'Tech' , u'http://www.newscientist.com/feed/view?id=7&type=channel' )
|
||||
,(u'Magazine' , u'http://feeds.newscientist.com/magazine' )
|
||||
,(u'Health' , u'http://feeds.newscientist.com/health' )
|
||||
,(u'Life' , u'http://feeds.newscientist.com/life' )
|
||||
,(u'Space' , u'http://feeds.newscientist.com/space' )
|
||||
,(u'Physics and Mathematics' , u'http://feeds.newscientist.com/physics-math' )
|
||||
,(u'Environment' , u'http://feeds.newscientist.com/environment' )
|
||||
,(u'Science in Society' , u'http://feeds.newscientist.com/science-in-society' )
|
||||
,(u'Tech' , u'http://feeds.newscientist.com/tech' )
|
||||
]
|
||||
|
||||
def get_article_url(self, article):
|
||||
@ -79,11 +83,21 @@ class NewScientist(BasicNewsRecipe):
|
||||
return url + '?full=true&print=true'
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
if soup.html.has_key('id'):
|
||||
del soup.html['id']
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
for item in soup.findAll(['quote','quotetext']):
|
||||
item.name='p'
|
||||
for item in soup.findAll(['xref','figref']):
|
||||
tstr = item.string
|
||||
item.replaceWith(tstr)
|
||||
for tg in soup.findAll('a'):
|
||||
if tg.string == 'Home':
|
||||
tg.parent.extract()
|
||||
return self.adeify_images(soup)
|
||||
return self.adeify_images(soup)
|
||||
else:
|
||||
if tg.string is not None:
|
||||
tstr = tg.string
|
||||
tg.replaceWith(tstr)
|
||||
return soup
|
||||
|
||||
|
@ -13,14 +13,16 @@ class Radikal_tr(BasicNewsRecipe):
|
||||
description = 'News from Turkey'
|
||||
publisher = 'radikal'
|
||||
category = 'news, politics, Turkey'
|
||||
oldest_article = 2
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 150
|
||||
no_stylesheets = True
|
||||
encoding = 'cp1254'
|
||||
use_embedded_content = False
|
||||
masthead_url = 'http://www.radikal.com.tr/D/i/1/V2/radikal_logo.jpg'
|
||||
language = 'tr'
|
||||
extra_css = ' @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} .article_description,body{font-family: Arial,Verdana,Helvetica,sans1,sans-serif } '
|
||||
extra_css = """ @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||
.article_description,body{font-family: Arial,Verdana,Helvetica,sans1,sans-serif}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
@ -34,7 +36,13 @@ class Radikal_tr(BasicNewsRecipe):
|
||||
remove_tags_after = dict(attrs={'id':'haberDetayYazi'})
|
||||
|
||||
|
||||
feeds = [(u'Yazarlar', u'http://www.radikal.com.tr/d/rss/RssYazarlar.xml')]
|
||||
feeds = [
|
||||
(u'Yazarlar' , u'http://www.radikal.com.tr/d/rss/RssYazarlar.xml')
|
||||
,(u'Turkiye' , u'http://www.radikal.com.tr/d/rss/Rss_97.xml' )
|
||||
,(u'Politika' , u'http://www.radikal.com.tr/d/rss/Rss_98.xml' )
|
||||
,(u'Dis Haberler', u'http://www.radikal.com.tr/d/rss/Rss_100.xml' )
|
||||
,(u'Ekonomi' , u'http://www.radikal.com.tr/d/rss/Rss_101.xml' )
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
articleid = url.rpartition('ArticleID=')[2]
|
||||
|
@ -457,7 +457,8 @@ from calibre.devices.blackberry.driver import BLACKBERRY
|
||||
from calibre.devices.cybook.driver import CYBOOK, ORIZON
|
||||
from calibre.devices.eb600.driver import EB600, COOL_ER, SHINEBOOK, \
|
||||
POCKETBOOK360, GER2, ITALICA, ECLICTO, DBOOK, INVESBOOK, \
|
||||
BOOQ, ELONEX, POCKETBOOK301, MENTOR, POCKETBOOK602
|
||||
BOOQ, ELONEX, POCKETBOOK301, MENTOR, POCKETBOOK602, \
|
||||
POCKETBOOK701
|
||||
from calibre.devices.iliad.driver import ILIAD
|
||||
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
|
||||
from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI
|
||||
@ -545,9 +546,7 @@ plugins += [
|
||||
JETBOOK_MINI,
|
||||
MIBUK,
|
||||
SHINEBOOK,
|
||||
POCKETBOOK360,
|
||||
POCKETBOOK301,
|
||||
POCKETBOOK602,
|
||||
POCKETBOOK360, POCKETBOOK301, POCKETBOOK602, POCKETBOOK701,
|
||||
KINDLE,
|
||||
KINDLE2,
|
||||
KINDLE_DX,
|
||||
|
@ -246,3 +246,23 @@ class POCKETBOOK602(USBMS):
|
||||
VENDOR_NAME = ''
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['PB602', 'PB902']
|
||||
|
||||
class POCKETBOOK701(USBMS):
|
||||
|
||||
name = 'PocketBook 701 Device Interface'
|
||||
description = _('Communicate with the PocketBook 701')
|
||||
author = _('Kovid Goyal')
|
||||
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
FORMATS = ['epub', 'fb2', 'prc', 'mobi', 'pdf', 'djvu', 'rtf', 'chm',
|
||||
'doc', 'tcr', 'txt']
|
||||
|
||||
EBOOK_DIR_MAIN = 'books'
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
|
||||
VENDOR_ID = [0x18d1]
|
||||
PRODUCT_ID = [0xa004]
|
||||
BCD = [0x0224]
|
||||
|
||||
VENDOR_NAME = 'ANDROID'
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE'
|
||||
|
||||
|
@ -23,16 +23,16 @@ class SNE(USBMS):
|
||||
FORMATS = ['epub', 'pdf', 'txt']
|
||||
|
||||
VENDOR_ID = [0x04e8]
|
||||
PRODUCT_ID = [0x2051, 0x2053]
|
||||
PRODUCT_ID = [0x2051, 0x2053, 0x2054]
|
||||
BCD = [0x0323]
|
||||
|
||||
VENDOR_NAME = 'SAMSUNG'
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'SNE-60'
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['SNE-60', 'E65']
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = 'SNE Main Memory'
|
||||
STORAGE_CARD_VOLUME_LABEL = 'SNE Storage Card'
|
||||
|
||||
EBOOK_DIR_MAIN = 'Books'
|
||||
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'Books'
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
|
||||
|
||||
|
@ -22,6 +22,9 @@ class UnknownFormatError(Exception):
|
||||
class DRMError(ValueError):
|
||||
pass
|
||||
|
||||
class ParserError(ValueError):
|
||||
pass
|
||||
|
||||
BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'htm', 'xhtm',
|
||||
'html', 'xhtml', 'pdf', 'pdb', 'pdr', 'prc', 'mobi', 'azw', 'doc',
|
||||
'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'cbc', 'oebzip',
|
||||
@ -39,6 +42,10 @@ class HTMLRenderer(object):
|
||||
try:
|
||||
if not ok:
|
||||
raise RuntimeError('Rendering of HTML failed.')
|
||||
de = self.page.mainFrame().documentElement()
|
||||
pe = de.findFirst('parsererror')
|
||||
if not pe.isNull():
|
||||
raise ParserError(pe.toPlainText())
|
||||
image = QImage(self.page.viewportSize(), QImage.Format_ARGB32)
|
||||
image.setDotsPerMeterX(96*(100/2.54))
|
||||
image.setDotsPerMeterY(96*(100/2.54))
|
||||
@ -104,7 +111,7 @@ def render_html_svg_workaround(path_to_html, log, width=590, height=750):
|
||||
return data
|
||||
|
||||
|
||||
def render_html(path_to_html, width=590, height=750):
|
||||
def render_html(path_to_html, width=590, height=750, as_xhtml=True):
|
||||
from PyQt4.QtWebKit import QWebPage
|
||||
from PyQt4.Qt import QEventLoop, QPalette, Qt, SIGNAL, QUrl, QSize
|
||||
from calibre.gui2 import is_ok_to_use_qt
|
||||
@ -122,11 +129,18 @@ def render_html(path_to_html, width=590, height=750):
|
||||
renderer = HTMLRenderer(page, loop)
|
||||
page.connect(page, SIGNAL('loadFinished(bool)'), renderer,
|
||||
Qt.QueuedConnection)
|
||||
if as_xhtml:
|
||||
page.mainFrame().setContent(open(path_to_html, 'rb').read(),
|
||||
'application/xhtml+xml', QUrl.fromLocalFile(path_to_html))
|
||||
else:
|
||||
page.mainFrame().load(QUrl.fromLocalFile(path_to_html))
|
||||
loop.exec_()
|
||||
renderer.loop = renderer.page = None
|
||||
del page
|
||||
del loop
|
||||
if isinstance(renderer.exception, ParserError) and as_xhtml:
|
||||
return render_html(path_to_html, width=width, height=height,
|
||||
as_xhtml=False)
|
||||
return renderer
|
||||
|
||||
def check_ebook_format(stream, current_guess):
|
||||
|
@ -17,6 +17,7 @@ pdfreflow, pdfreflow_error = plugins['pdfreflow']
|
||||
def get_metadata(stream, cover=True):
|
||||
if pdfreflow is None:
|
||||
raise RuntimeError(pdfreflow_error)
|
||||
stream.seek(0)
|
||||
raw = stream.read()
|
||||
#isbn = _isbn_pat.search(raw)
|
||||
#if isbn is not None:
|
||||
|
@ -205,7 +205,10 @@ class Stylizer(object):
|
||||
NameError, # thrown on OS X instead of SelectorSyntaxError
|
||||
SelectorSyntaxError):
|
||||
continue
|
||||
try:
|
||||
matches = selector(tree)
|
||||
except etree.XPathEvalError:
|
||||
continue
|
||||
|
||||
if not matches:
|
||||
ntext = capital_sel_pat.sub(lambda m: m.group().lower(), text)
|
||||
|
@ -593,7 +593,6 @@ class DeviceMenu(QMenu): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
class DeviceMixin(object): # {{{
|
||||
|
||||
def __init__(self):
|
||||
|
@ -7,14 +7,14 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>479</width>
|
||||
<height>606</height>
|
||||
<height>591</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Configure Ebook viewer</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/config.png</normaloff>:/images/config.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
@ -85,11 +85,7 @@
|
||||
<item row="2" column="1">
|
||||
<widget class="QFontComboBox" name="mono_family"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>&Default font size:</string>
|
||||
@ -99,7 +95,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="default_font_size">
|
||||
<property name="suffix">
|
||||
<string> px</string>
|
||||
@ -112,7 +108,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Monospace &font size:</string>
|
||||
@ -122,7 +118,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="mono_font_size">
|
||||
<property name="suffix">
|
||||
<string> px</string>
|
||||
@ -135,7 +131,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>S&tandard font:</string>
|
||||
@ -145,7 +141,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="standard_font">
|
||||
<item>
|
||||
<property name="text">
|
||||
@ -164,20 +160,100 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_remember_window_size">
|
||||
<property name="text">
|
||||
<string>Remember last used &window size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_remember_current_page">
|
||||
<property name="text">
|
||||
<string>Remember the &current page when quitting</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="hyphenate">
|
||||
<property name="text">
|
||||
<string>H&yphenate (break line in the middle of large words)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" 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="6" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Default &language for hyphenation:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>hyphenate_default_lang</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_fit_images">
|
||||
<property name="text">
|
||||
<string>&Resize images larger than the viewer window (needs restart)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Page flip &duration:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_page_flip_duration</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QDoubleSpinBox" name="opt_page_flip_duration">
|
||||
<property name="specialValueText">
|
||||
<string>disabled</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> secs</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>3.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.500000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_wheel_flips_pages">
|
||||
<property name="text">
|
||||
<string>Mouse &wheel flips pages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="max_view_width">
|
||||
<property name="suffix">
|
||||
@ -201,54 +277,8 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="hyphenate">
|
||||
<property name="text">
|
||||
<string>H&yphenate (break line in the middle of large words)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" 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="5" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Default &language for hyphenation:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>hyphenate_default_lang</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_fit_images">
|
||||
<property name="text">
|
||||
<string>&Resize images larger than the viewer window (needs restart)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>&User stylesheet</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="1" column="0">
|
||||
<widget class="QPlainTextEdit" name="css"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
@ -268,6 +298,29 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>User &Stylesheet</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string><p>A CSS stylesheet that can be used to control the look and feel of books. For examples, click <a href="http://www.mobileread.com/forums/showthread.php?t=51500">here</a>.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="css"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -276,12 +329,8 @@
|
||||
<tabstop>serif_family</tabstop>
|
||||
<tabstop>sans_family</tabstop>
|
||||
<tabstop>mono_family</tabstop>
|
||||
<tabstop>default_font_size</tabstop>
|
||||
<tabstop>mono_font_size</tabstop>
|
||||
<tabstop>standard_font</tabstop>
|
||||
<tabstop>max_view_width</tabstop>
|
||||
<tabstop>opt_remember_window_size</tabstop>
|
||||
<tabstop>css</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
|
@ -18,6 +18,7 @@ from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
|
||||
from calibre.utils.config import Config, StringConfig
|
||||
from calibre.utils.localization import get_language
|
||||
from calibre.gui2.viewer.config_ui import Ui_Dialog
|
||||
from calibre.gui2.viewer.flip import SlideFlip
|
||||
from calibre.gui2.shortcuts import Shortcuts, ShortcutConfig
|
||||
from calibre.constants import iswindows
|
||||
from calibre import prints, guess_type
|
||||
@ -52,6 +53,11 @@ def config(defaults=None):
|
||||
help=_('Default language for hyphenation rules'))
|
||||
c.add_opt('remember_current_page', default=True,
|
||||
help=_('Save the current position in the document, when quitting'))
|
||||
c.add_opt('wheel_flips_pages', default=False,
|
||||
help=_('Have the mouse wheel turn pages'))
|
||||
c.add_opt('page_flip_duration', default=0.5,
|
||||
help=_('The time, in seconds, for the page flip animation. Default'
|
||||
' is half a second.'))
|
||||
|
||||
fonts = c.add_group('FONTS', _('Font options'))
|
||||
fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif',
|
||||
@ -75,6 +81,8 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
opts = config().parse()
|
||||
self.opt_remember_window_size.setChecked(opts.remember_window_size)
|
||||
self.opt_remember_current_page.setChecked(opts.remember_current_page)
|
||||
self.opt_wheel_flips_pages.setChecked(opts.wheel_flips_pages)
|
||||
self.opt_page_flip_duration.setValue(opts.page_flip_duration)
|
||||
self.serif_family.setCurrentFont(QFont(opts.serif_family))
|
||||
self.sans_family.setCurrentFont(QFont(opts.sans_family))
|
||||
self.mono_family.setCurrentFont(QFont(opts.mono_family))
|
||||
@ -122,6 +130,8 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
c.set('max_view_width', int(self.max_view_width.value()))
|
||||
c.set('hyphenate', self.hyphenate.isChecked())
|
||||
c.set('remember_current_page', self.opt_remember_current_page.isChecked())
|
||||
c.set('wheel_flips_pages', self.opt_wheel_flips_pages.isChecked())
|
||||
c.set('page_flip_duration', self.opt_page_flip_duration.value())
|
||||
idx = self.hyphenate_default_lang.currentIndex()
|
||||
c.set('hyphenate_default_lang',
|
||||
str(self.hyphenate_default_lang.itemData(idx).toString()))
|
||||
@ -197,6 +207,9 @@ class Document(QWebPage):
|
||||
self.hyphenate = opts.hyphenate
|
||||
self.hyphenate_default_lang = opts.hyphenate_default_lang
|
||||
self.do_fit_images = opts.fit_images
|
||||
self.page_flip_duration = opts.page_flip_duration
|
||||
self.enable_page_flip = self.page_flip_duration > 0.1
|
||||
self.wheel_flips_pages = opts.wheel_flips_pages
|
||||
|
||||
def fit_images(self):
|
||||
if self.do_fit_images:
|
||||
@ -453,6 +466,8 @@ class DocumentView(QWebView):
|
||||
|
||||
def __init__(self, *args):
|
||||
QWebView.__init__(self, *args)
|
||||
self.flipper = SlideFlip(self)
|
||||
self.is_auto_repeat_event = False
|
||||
self.debug_javascript = False
|
||||
self.shortcuts = Shortcuts(SHORTCUTS, 'shortcuts/viewer')
|
||||
self.self_closing_pat = re.compile(r'<([a-z1-6]+)\s+([^>]+)/>',
|
||||
@ -693,6 +708,13 @@ class DocumentView(QWebView):
|
||||
self.manager.scrolled(self.document.scroll_fraction)
|
||||
|
||||
self.turn_off_internal_scrollbars()
|
||||
if self.flipper.isVisible():
|
||||
if self.flipper.running:
|
||||
self.flipper.setVisible(False)
|
||||
else:
|
||||
self.flipper(self.current_page_image(),
|
||||
duration=self.document.page_flip_duration)
|
||||
|
||||
|
||||
def turn_off_internal_scrollbars(self):
|
||||
self.document.mainFrame().setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
|
||||
@ -708,12 +730,17 @@ class DocumentView(QWebView):
|
||||
return False
|
||||
return True
|
||||
|
||||
def find_next_blank_line(self, overlap):
|
||||
def current_page_image(self, overlap=-1):
|
||||
if overlap < 0:
|
||||
overlap = self.height()
|
||||
img = QImage(self.width(), overlap, QImage.Format_ARGB32)
|
||||
painter = QPainter(img)
|
||||
# Render a region of width x overlap pixels atthe bottom of the current viewport
|
||||
self.document.mainFrame().render(painter, QRegion(0, 0, self.width(), overlap))
|
||||
painter.end()
|
||||
return img
|
||||
|
||||
def find_next_blank_line(self, overlap):
|
||||
img = self.current_page_image(overlap)
|
||||
for i in range(overlap-1, -1, -1):
|
||||
if self.test_line(img, i):
|
||||
self.scroll_by(y=i, notify=False)
|
||||
@ -721,10 +748,18 @@ class DocumentView(QWebView):
|
||||
self.scroll_by(y=overlap)
|
||||
|
||||
def previous_page(self):
|
||||
if self.flipper.running and not self.is_auto_repeat_event:
|
||||
return
|
||||
if self.loading_url is not None:
|
||||
return
|
||||
epf = self.document.enable_page_flip and not self.is_auto_repeat_event
|
||||
|
||||
delta_y = self.document.window_height - 25
|
||||
if self.document.at_top:
|
||||
if self.manager is not None:
|
||||
self.to_bottom = True
|
||||
if epf:
|
||||
self.flipper.initialize(self.current_page_image(), False)
|
||||
self.manager.previous_document()
|
||||
else:
|
||||
opos = self.document.ypos
|
||||
@ -732,11 +767,23 @@ class DocumentView(QWebView):
|
||||
if upper_limit < 0:
|
||||
upper_limit = 0
|
||||
if upper_limit < opos:
|
||||
if epf:
|
||||
self.flipper.initialize(self.current_page_image(),
|
||||
forwards=False)
|
||||
self.document.scroll_to(self.document.xpos, upper_limit)
|
||||
if epf:
|
||||
self.flipper(self.current_page_image(),
|
||||
duration=self.document.page_flip_duration)
|
||||
if self.manager is not None:
|
||||
self.manager.scrolled(self.scroll_fraction)
|
||||
|
||||
def next_page(self):
|
||||
if self.flipper.running and not self.is_auto_repeat_event:
|
||||
return
|
||||
if self.loading_url is not None:
|
||||
return
|
||||
epf = self.document.enable_page_flip and not self.is_auto_repeat_event
|
||||
|
||||
window_height = self.document.window_height
|
||||
document_height = self.document.height
|
||||
ddelta = document_height - window_height
|
||||
@ -746,6 +793,8 @@ class DocumentView(QWebView):
|
||||
delta_y = window_height - 25
|
||||
if self.document.at_bottom or ddelta <= 0:
|
||||
if self.manager is not None:
|
||||
if epf:
|
||||
self.flipper.initialize(self.current_page_image())
|
||||
self.manager.next_document()
|
||||
elif ddelta < 25:
|
||||
self.scroll_by(y=ddelta)
|
||||
@ -758,6 +807,8 @@ class DocumentView(QWebView):
|
||||
#print 'After set padding=0:', self.document.ypos
|
||||
if opos < oopos:
|
||||
if self.manager is not None:
|
||||
if epf:
|
||||
self.flipper.initialize(self.current_page_image())
|
||||
self.manager.next_document()
|
||||
return
|
||||
lower_limit = opos + delta_y # Max value of top y co-ord after scrolling
|
||||
@ -766,10 +817,14 @@ class DocumentView(QWebView):
|
||||
padding = lower_limit - max_y
|
||||
if padding == window_height:
|
||||
if self.manager is not None:
|
||||
if epf:
|
||||
self.flipper.initialize(self.current_page_image())
|
||||
self.manager.next_document()
|
||||
return
|
||||
#print 'Setting padding to:', lower_limit - max_y
|
||||
self.document.set_bottom_padding(lower_limit - max_y)
|
||||
if epf:
|
||||
self.flipper.initialize(self.current_page_image())
|
||||
#print 'Document height:', self.document.height
|
||||
max_y = self.document.height - window_height
|
||||
lower_limit = min(max_y, lower_limit)
|
||||
@ -780,6 +835,9 @@ class DocumentView(QWebView):
|
||||
#print 'After scroll pos:', self.document.ypos
|
||||
self.find_next_blank_line(window_height - actually_scrolled)
|
||||
#print 'After blank line pos:', self.document.ypos
|
||||
if epf:
|
||||
self.flipper(self.current_page_image(),
|
||||
duration=self.document.page_flip_duration)
|
||||
if self.manager is not None:
|
||||
self.manager.scrolled(self.scroll_fraction)
|
||||
#print 'After all:', self.document.ypos
|
||||
@ -833,6 +891,10 @@ class DocumentView(QWebView):
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if event.delta() < -14:
|
||||
if self.document.wheel_flips_pages:
|
||||
self.next_page()
|
||||
event.accept()
|
||||
return
|
||||
if self.document.at_bottom:
|
||||
self.scroll_by(y=15) # at_bottom can lie on windows
|
||||
if self.manager is not None:
|
||||
@ -840,6 +902,11 @@ class DocumentView(QWebView):
|
||||
event.accept()
|
||||
return
|
||||
elif event.delta() > 14:
|
||||
if self.document.wheel_flips_pages:
|
||||
self.previous_page()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
if self.document.at_top:
|
||||
if self.manager is not None:
|
||||
self.manager.previous_document()
|
||||
@ -862,7 +929,11 @@ class DocumentView(QWebView):
|
||||
key = self.shortcuts.get_match(event)
|
||||
func = self.goto_location_actions.get(key, None)
|
||||
if func is not None:
|
||||
self.is_auto_repeat_event = event.isAutoRepeat()
|
||||
try:
|
||||
func()
|
||||
finally:
|
||||
self.is_auto_repeat_event = False
|
||||
elif key == 'Down':
|
||||
self.scroll_by(y=15)
|
||||
elif key == 'Up':
|
||||
|
116
src/calibre/gui2/viewer/flip.py
Normal file
116
src/calibre/gui2/viewer/flip.py
Normal file
@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import QWidget, QPainter, QPropertyAnimation, QEasingCurve, \
|
||||
QRect, QPixmap, Qt, pyqtProperty
|
||||
|
||||
class SlideFlip(QWidget):
|
||||
|
||||
# API {{{
|
||||
|
||||
# In addition the isVisible() and setVisible() methods must be present
|
||||
|
||||
def __init__(self, parent):
|
||||
QWidget.__init__(self, parent)
|
||||
|
||||
self.setGeometry(0, 0, 1, 1)
|
||||
self._current_width = 0
|
||||
self.before_image = self.after_image = None
|
||||
self.animation = QPropertyAnimation(self, 'current_width', self)
|
||||
self.setVisible(False)
|
||||
self.animation.valueChanged.connect(self.update)
|
||||
self.animation.finished.connect(self.finished)
|
||||
self.flip_forwards = True
|
||||
self.setAttribute(Qt.WA_OpaquePaintEvent)
|
||||
|
||||
@property
|
||||
def running(self):
|
||||
'True iff animation is currently running'
|
||||
return self.animation.state() == self.animation.Running
|
||||
|
||||
def initialize(self, image, forwards=True):
|
||||
'''
|
||||
Initialize the flipper, causes the flipper to show itself displaying
|
||||
the full `image`.
|
||||
|
||||
:param image: The image to display as background
|
||||
:param forwards: If True flipper will flip forwards, otherwise
|
||||
backwards
|
||||
|
||||
'''
|
||||
self.flip_forwards = forwards
|
||||
self.before_image = QPixmap.fromImage(image)
|
||||
self.after_image = None
|
||||
self.setGeometry(0, 0, image.width(), image.height())
|
||||
self.setVisible(True)
|
||||
|
||||
def __call__(self, image, duration=0.5):
|
||||
'''
|
||||
Start the animation. You must have called :meth:`initialize` first.
|
||||
|
||||
:param duration: Animation duration in seconds.
|
||||
|
||||
'''
|
||||
if self.running:
|
||||
return
|
||||
self.after_image = QPixmap.fromImage(image)
|
||||
|
||||
if self.flip_forwards:
|
||||
self.animation.setStartValue(image.width())
|
||||
self.animation.setEndValue(0)
|
||||
t = self.before_image
|
||||
self.before_image = self.after_image
|
||||
self.after_image = t
|
||||
self.animation.setEasingCurve(QEasingCurve(QEasingCurve.InExpo))
|
||||
else:
|
||||
self.animation.setStartValue(0)
|
||||
self.animation.setEndValue(image.width())
|
||||
self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo))
|
||||
|
||||
self.animation.setDuration(duration * 1000)
|
||||
self.animation.start()
|
||||
|
||||
# }}}
|
||||
|
||||
def finished(self):
|
||||
self.setVisible(False)
|
||||
self.before_image = self.after_image = None
|
||||
|
||||
def paintEvent(self, ev):
|
||||
if self.before_image is None:
|
||||
return
|
||||
canvas_size = self.rect()
|
||||
p = QPainter(self)
|
||||
p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
|
||||
|
||||
p.drawPixmap(canvas_size, self.before_image,
|
||||
self.before_image.rect())
|
||||
if self.after_image is not None:
|
||||
width = self._current_width
|
||||
iw = self.after_image.width()
|
||||
sh = min(self.after_image.height(), canvas_size.height())
|
||||
|
||||
if self.flip_forwards:
|
||||
source = QRect(max(0, iw - width), 0, width, sh)
|
||||
else:
|
||||
source = QRect(0, 0, width, sh)
|
||||
|
||||
target = QRect(source)
|
||||
target.moveLeft(0)
|
||||
p.drawPixmap(target, self.after_image, source)
|
||||
|
||||
p.end()
|
||||
|
||||
def set_current_width(self, val):
|
||||
self._current_width = val
|
||||
|
||||
current_width = pyqtProperty('int',
|
||||
fget=lambda self: self._current_width,
|
||||
fset=set_current_width
|
||||
)
|
||||
|
||||
|
@ -5,6 +5,8 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import gc
|
||||
|
||||
## {{{ http://code.activestate.com/recipes/286222/ (r1)
|
||||
import os
|
||||
|
||||
@ -52,4 +54,19 @@ def stacksize(since=0.0):
|
||||
## end of http://code.activestate.com/recipes/286222/ }}}
|
||||
|
||||
|
||||
def gc_histogram():
|
||||
"""Returns per-class counts of existing objects."""
|
||||
result = {}
|
||||
for o in gc.get_objects():
|
||||
t = type(o)
|
||||
count = result.get(t, 0)
|
||||
result[t] = count + 1
|
||||
return result
|
||||
|
||||
def diff_hists(h1, h2):
|
||||
"""Prints differences between two results of gc_histogram()."""
|
||||
for k in h1:
|
||||
if h1[k] != h2[k]:
|
||||
print "%s: %d -> %d (%s%d)" % (
|
||||
k, h1[k], h2[k], h2[k] > h1[k] and "+" or "", h2[k] - h1[k])
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user