mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
Updates in Apple driver supporting static iTunes glue code for appscript
This commit is contained in:
commit
39380521c1
@ -195,7 +195,7 @@ It can get tiresome to keep re-adding a plugin to calibre to test small changes.
|
|||||||
|
|
||||||
Once you've located the zip file of your plugin you can then directly update it with your changes instead of re-adding it each time. To do so from the command line, in the directory that contains your plugin source code, use::
|
Once you've located the zip file of your plugin you can then directly update it with your changes instead of re-adding it each time. To do so from the command line, in the directory that contains your plugin source code, use::
|
||||||
|
|
||||||
calibre -s; sleep 4s; zip -R /path/to/plugin/zip/file.zip *; calibre
|
calibre -s; zip -R /path/to/plugin/zip/file.zip *; calibre
|
||||||
|
|
||||||
This will shutdown a running calibre. Wait for the shutdown to complete, then update your plugin files and relaunch calibre.
|
This will shutdown a running calibre. Wait for the shutdown to complete, then update your plugin files and relaunch calibre.
|
||||||
It relies on the freely available zip command line tool.
|
It relies on the freely available zip command line tool.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2010-2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
abc.com.py
|
abc.com.py
|
||||||
'''
|
'''
|
||||||
@ -7,7 +7,7 @@ abc.com.py
|
|||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class ABC_py(BasicNewsRecipe):
|
class ABC_py(BasicNewsRecipe):
|
||||||
title = 'ABC digital'
|
title = 'ABC Color'
|
||||||
__author__ = 'Darko Miletic'
|
__author__ = 'Darko Miletic'
|
||||||
description = 'Noticias de Paraguay y el resto del mundo'
|
description = 'Noticias de Paraguay y el resto del mundo'
|
||||||
publisher = 'ABC'
|
publisher = 'ABC'
|
||||||
@ -15,12 +15,16 @@ class ABC_py(BasicNewsRecipe):
|
|||||||
oldest_article = 2
|
oldest_article = 2
|
||||||
max_articles_per_feed = 200
|
max_articles_per_feed = 200
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
encoding = 'cp1252'
|
encoding = 'utf8'
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
language = 'es_PY'
|
language = 'es_PY'
|
||||||
remove_empty_feeds = True
|
remove_empty_feeds = True
|
||||||
|
masthead_url = 'http://www.abc.com.py/plantillas/img/abc-logo.png'
|
||||||
publication_type = 'newspaper'
|
publication_type = 'newspaper'
|
||||||
extra_css = ' body{font-family: Arial,Helvetica,sans-serif } img{margin-bottom: 0.4em} '
|
extra_css = """
|
||||||
|
body{font-family: UnitSlabProMedium,"Times New Roman",serif }
|
||||||
|
img{margin-bottom: 0.4em; display: block;}
|
||||||
|
"""
|
||||||
|
|
||||||
conversion_options = {
|
conversion_options = {
|
||||||
'comment' : description
|
'comment' : description
|
||||||
@ -29,21 +33,19 @@ class ABC_py(BasicNewsRecipe):
|
|||||||
, 'language' : language
|
, 'language' : language
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_tags = [dict(name=['form','iframe','embed','object','link','base','table']),dict(attrs={'class':'toolbox'})]
|
remove_tags = [
|
||||||
remove_tags_after = dict(attrs={'class':'date'})
|
dict(name=['form','iframe','embed','object','link','base','table']),
|
||||||
keep_only_tags = [dict(attrs={'class':'zcontent'})]
|
dict(attrs={'class':['es-carousel-wrapper']}),
|
||||||
|
dict(attrs={'id':['tools','article-banner-1']})
|
||||||
|
]
|
||||||
|
keep_only_tags = [dict(attrs={'id':'article'})]
|
||||||
|
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
(u'Ultimo momento' , u'http://www.abc.com.py/ultimo-momento.xml' )
|
(u'Ultimo momento', u'http://www.abc.com.py/rss.xml' )
|
||||||
,(u'Nacionales' , u'http://www.abc.com.py/nacionales.xml' )
|
,(u'Nacionales' , u'http://www.abc.com.py/nacionales/rss.xml' )
|
||||||
,(u'Internacionales' , u'http://www.abc.com.py/internacionales.xml' )
|
,(u'Mundo' , u'http://www.abc.com.py/internacionales/rss.xml')
|
||||||
,(u'Deportes' , u'http://www.abc.com.py/deportes.xml' )
|
,(u'Deportes' , u'http://www.abc.com.py/deportes/rss.xml' )
|
||||||
,(u'Espectaculos' , u'http://www.abc.com.py/espectaculos.xml' )
|
,(u'Espectaculos' , u'http://www.abc.com.py/espectaculos/rss.xml' )
|
||||||
,(u'Ciencia y Tecnologia', u'http://www.abc.com.py/ciencia-y-tecnologia.xml')
|
,(u'TecnoCiencia' , u'http://www.abc.com.py/ciencia/rss.xml' )
|
||||||
]
|
]
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
|
||||||
for item in soup.findAll(style=True):
|
|
||||||
del item['style']
|
|
||||||
return soup
|
|
||||||
|
@ -147,10 +147,9 @@ class BBCBrasilRecipe(BasicNewsRecipe):
|
|||||||
|
|
||||||
|
|
||||||
# Author of this recipe.
|
# Author of this recipe.
|
||||||
__author__ = 'claviola'
|
__author__ = 'Carlos Laviola'
|
||||||
|
|
||||||
# Specify English as the language of the RSS feeds (ISO-639 code).
|
language = 'pt_BR'
|
||||||
language = 'en_GB'
|
|
||||||
|
|
||||||
# Set tags.
|
# Set tags.
|
||||||
tags = 'news, sport, blog'
|
tags = 'news, sport, blog'
|
||||||
|
12
recipes/ct24.recipe
Normal file
12
recipes/ct24.recipe
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1339974788(BasicNewsRecipe):
|
||||||
|
title = u'\u010cT24'
|
||||||
|
oldest_article = 1
|
||||||
|
language = 'cs'
|
||||||
|
__author__ = 'zoidozoido'
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
auto_cleanup = True
|
||||||
|
|
||||||
|
feeds = [(u'Hlavn\xed zpr\xe1vy', u'http://www.ceskatelevize.cz/ct24/rss/hlavni-zpravy/'), (u'Dom\xe1c\xed', u'http://www.ceskatelevize.cz/ct24/rss/domaci/'), (u'Sv\u011bt', u'http://www.ceskatelevize.cz/ct24/rss/svet/'), (u'Regiony', u'http://www.ceskatelevize.cz/ct24/rss/regiony/'), (u'Kultura', u'http://www.ceskatelevize.cz/ct24/rss/kultura/'), (u'Ekonomika', u'http://www.ceskatelevize.cz/ct24/rss/ekonomika/'), (u'Sport - hlavn\xed zpr\xe1vy', u'http://www.ceskatelevize.cz/ct4/rss/hlavni-zpravy/'), (u'OH 2012', u'http://www.ceskatelevize.cz/ct4/rss/oh-2012/')]
|
||||||
|
remove_tags = [dict(name='img')]
|
56
recipes/marketing_sensoriale.recipe
Normal file
56
recipes/marketing_sensoriale.recipe
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from calibre.utils.ipc.simple_worker import fork_job
|
||||||
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
|
|
||||||
|
js_fetcher = '''
|
||||||
|
|
||||||
|
import calibre.web.jsbrowser.browser as jsbrowser
|
||||||
|
|
||||||
|
def grab(url):
|
||||||
|
browser = jsbrowser.Browser()
|
||||||
|
#10 second timeout
|
||||||
|
browser.visit(url, 10)
|
||||||
|
browser.run_for_a_time(10)
|
||||||
|
html = browser.html
|
||||||
|
browser.close()
|
||||||
|
return html
|
||||||
|
|
||||||
|
'''
|
||||||
|
class MarketingSensoriale(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Marketing sensoriale'
|
||||||
|
__author__ = 'NotTaken'
|
||||||
|
description = 'Marketing Sensoriale, il Blog'
|
||||||
|
category = 'Blog'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 200
|
||||||
|
no_stylesheets = True
|
||||||
|
encoding = 'utf8'
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'it'
|
||||||
|
remove_empty_feeds = True
|
||||||
|
recursions = 0
|
||||||
|
requires_version = (0, 8, 58)
|
||||||
|
auto_cleanup = False
|
||||||
|
simultaneous_downloads = 1
|
||||||
|
articles_are_obfuscated = True
|
||||||
|
|
||||||
|
remove_tags_after = [dict(name='div', attrs={'class':['article-footer']})]
|
||||||
|
|
||||||
|
|
||||||
|
def get_article_url(self, article):
|
||||||
|
return article.get('feedburner_origlink', None)
|
||||||
|
|
||||||
|
def get_obfuscated_article(self, url):
|
||||||
|
result = fork_job(js_fetcher, 'grab', (url,), module_is_source_code=True)
|
||||||
|
|
||||||
|
html = result['result']
|
||||||
|
if isinstance(html, type(u'')):
|
||||||
|
html = html.encode('utf-8')
|
||||||
|
pt = PersistentTemporaryFile('.html')
|
||||||
|
pt.write(html)
|
||||||
|
pt.close()
|
||||||
|
return pt.name
|
||||||
|
|
||||||
|
feeds = [(u'Marketing sensoriale', u'http://feeds.feedburner.com/MarketingSensoriale?format=xml')]
|
||||||
|
|
90
recipes/new_statesman.recipe
Normal file
90
recipes/new_statesman.recipe
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
'''
|
||||||
|
newstatesman.com
|
||||||
|
'''
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class NewStatesman(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = 'New Statesman'
|
||||||
|
language = 'en_GB'
|
||||||
|
__author__ = "NotTaken"
|
||||||
|
description = "Britain's Current Affairs & Politics Magazine (bi-weekly)"
|
||||||
|
oldest_article = 4.0
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_empty_feeds = True
|
||||||
|
|
||||||
|
keep_only_tags = [dict(attrs={'class' : 'node'})]
|
||||||
|
|
||||||
|
remove_tags_after = [
|
||||||
|
dict(attrs={'class' : lambda x: x and 'content123' in x})
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(attrs={'class' : lambda x: x and 'links_bookmark' in x})
|
||||||
|
]
|
||||||
|
|
||||||
|
extra_css = '''
|
||||||
|
.title-main {font-size: x-large;}
|
||||||
|
h2 { font-size: small; }
|
||||||
|
h1 { font-size: medium; }
|
||||||
|
.field-field-nodeimage-title {
|
||||||
|
font-size: small;
|
||||||
|
color: #3C3C3C;
|
||||||
|
}
|
||||||
|
.link_col {
|
||||||
|
font-size: x-small;
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
processed_urls = []
|
||||||
|
|
||||||
|
def populate_article_metadata(self, article, soup, first):
|
||||||
|
if first and hasattr(self, 'add_toc_thumbnail'):
|
||||||
|
pic = soup.find('img')
|
||||||
|
if pic is not None:
|
||||||
|
self.add_toc_thumbnail(article,pic['src'])
|
||||||
|
|
||||||
|
def get_article_url(self, article):
|
||||||
|
url = BasicNewsRecipe.get_article_url(self,article)
|
||||||
|
|
||||||
|
if url in self.processed_urls:
|
||||||
|
self.log('skipping duplicate article: %s' %article.title )
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.processed_urls.append(url)
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Politics',
|
||||||
|
u'http://www.newstatesman.com/politics.rss'),
|
||||||
|
(u'Business',
|
||||||
|
u'http://www.newstatesman.com/business.rss'),
|
||||||
|
(u'Economics',
|
||||||
|
u'http://www.newstatesman.com/economics.rss'),
|
||||||
|
(u'Culture',
|
||||||
|
u'http://www.newstatesman.com/culture.rss'),
|
||||||
|
(u'Media',
|
||||||
|
u'http://www.newstatesman.com/media.rss'),
|
||||||
|
(u'Books',
|
||||||
|
u'http://www.newstatesman.com/taxonomy/term/feed/27'),
|
||||||
|
(u'Life & Society',
|
||||||
|
u'http://www.newstatesman.com/taxonomyfeed/11'),
|
||||||
|
(u'World Affairs',
|
||||||
|
u'http://www.newstatesman.com/world-affairs.rss'),
|
||||||
|
(u'Sci-Tech',
|
||||||
|
u'http://www.newstatesman.com/feeds/topics/sci-tech.rss'),
|
||||||
|
(u'Others',
|
||||||
|
u'http://www.newstatesman.com/feeds_allsite/site_feed.php'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
@ -10,11 +10,11 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
|||||||
|
|
||||||
class OGlobo(BasicNewsRecipe):
|
class OGlobo(BasicNewsRecipe):
|
||||||
title = 'O Globo'
|
title = 'O Globo'
|
||||||
__author__ = 'Darko Miletic and Sujata Raman'
|
__author__ = 'Darko Miletic and Carlos Laviola'
|
||||||
description = 'News from Brasil'
|
description = 'News from Brasil'
|
||||||
publisher = 'O Globo'
|
publisher = 'O Globo'
|
||||||
category = 'news, politics, Brasil'
|
category = 'news, politics, Brasil'
|
||||||
oldest_article = 2
|
oldest_article = 7
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
@ -39,43 +39,35 @@ class OGlobo(BasicNewsRecipe):
|
|||||||
.commentario p{color:#007BB5; font-style:italic;}
|
.commentario p{color:#007BB5; font-style:italic;}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'id':'ltintb'}),
|
|
||||||
dict(name='a', attrs={'class':['img imgLoader','img ftr imgLoader']}),]
|
|
||||||
|
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name='script')
|
dict(name='script')
|
||||||
,dict(name='object')
|
|
||||||
,dict(name='form')
|
,dict(name='form')
|
||||||
,dict(name='div', attrs={'id':['linksPatGoogle','rdpm','cor','com','env','rcm_st','coment',]})
|
,dict(name='div', attrs={'id':'header'})
|
||||||
,dict(name='div', attrs={'class':'box-zap-anu2'})
|
,dict(name='p', attrs={'id':'info-date-press'})
|
||||||
,dict(name='a', attrs={'class':'assine'})
|
|
||||||
,dict(name='link')
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
(u'Todos os canais', u'http://oglobo.globo.com/rss/plantao.xml')
|
(u'Todos os canais', u'http://oglobo.globo.com/rss.xml?completo=true')
|
||||||
,(u'Ciencia', u'http://oglobo.globo.com/rss/plantaociencia.xml')
|
,(u'Ciencia', u'http://oglobo.globo.com/rss.xml?secao=ciencia&completo=true')
|
||||||
,(u'Educacao', u'http://oglobo.globo.com/rss/plantaoeducacao.xml')
|
,(u'Educacao', u'http://oglobo.globo.com/rss.xml?secao=educacao&completo=true')
|
||||||
,(u'Opiniao', u'http://oglobo.globo.com/rss/plantaoopiniao.xml')
|
,(u'Opiniao', u'http://oglobo.globo.com/rss.xml?secao=opiniao&completo=true')
|
||||||
,(u'Sao Paulo', u'http://oglobo.globo.com/rss/plantaosaopaulo.xml')
|
,(u'Cultura', u'http://oglobo.globo.com/rss.xml?secao=cultura&completo=true')
|
||||||
,(u'Viagem', u'http://oglobo.globo.com/rss/plantaoviagem.xml')
|
,(u'Esportes', u'http://oglobo.globo.com/rss.xml?secao=esportes&completo=true')
|
||||||
,(u'Cultura', u'http://oglobo.globo.com/rss/plantaocultura.xml')
|
,(u'Mundo', u'http://oglobo.globo.com/rss.xml?secao=mundo&completo=true')
|
||||||
,(u'Esportes', u'http://oglobo.globo.com/rss/plantaoesportes.xml')
|
,(u'Pais', u'http://oglobo.globo.com/rss.xml?secao=pais&completo=true')
|
||||||
,(u'Mundo', u'http://oglobo.globo.com/rss/plantaomundo.xml')
|
,(u'Rio', u'http://oglobo.globo.com/rss.xml?secao=rio&completo=true')
|
||||||
,(u'Pais', u'http://oglobo.globo.com/rss/plantaopais.xml')
|
,(u'Saude', u'http://oglobo.globo.com/rss.xml?secao=saude&completo=true')
|
||||||
,(u'Rio', u'http://oglobo.globo.com/rss/plantaorio.xml')
|
,(u'Economia', u'http://oglobo.globo.com/rss.xml?secao=economia&completo=true')
|
||||||
,(u'Saude', u'http://oglobo.globo.com/rss/plantaosaude.xml')
|
,(u'Tecnologia', u'http://oglobo.globo.com/rss.xml?secao=tecnologia&completo=true')
|
||||||
,(u'Viver Melhor', u'http://oglobo.globo.com/rss/plantaovivermelhor.xml')
|
|
||||||
,(u'Economia', u'http://oglobo.globo.com/rss/plantaoeconomia.xml')
|
|
||||||
,(u'Tecnologia', u'http://oglobo.globo.com/rss/plantaotecnologia.xml')
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url + '?service=print'
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
for item in soup.findAll(style=True):
|
for item in soup.findAll(style=True):
|
||||||
del item['style']
|
del item['style']
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
language = 'pt'
|
language = 'pt_BR'
|
||||||
|
|
||||||
|
114
recipes/smilezilla.recipe
Normal file
114
recipes/smilezilla.recipe
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||||
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
|
|
||||||
|
class SmileZilla(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = 'SmileZilla'
|
||||||
|
language = 'en'
|
||||||
|
__author__ = "Will"
|
||||||
|
JOKES_INDEX = 'http://www.smilezilla.com/joke.do'
|
||||||
|
STORIES_INDEX = 'http://www.smilezilla.com/story.do'
|
||||||
|
description = 'Daily Jokes and funny stoires'
|
||||||
|
oldest_article = 1
|
||||||
|
remove_tags = [
|
||||||
|
]
|
||||||
|
keep_only_tags = []
|
||||||
|
no_stylesheets = True
|
||||||
|
simultaneous_downloads = 1
|
||||||
|
articles_are_obfuscated = True
|
||||||
|
encoding = 'utf-8'
|
||||||
|
|
||||||
|
remove_tags = [dict(name='table')]
|
||||||
|
|
||||||
|
counter = {JOKES_INDEX: 0, STORIES_INDEX: 0 }
|
||||||
|
cache = {}
|
||||||
|
|
||||||
|
def cached_fetch(self, url):
|
||||||
|
cache = self.cache
|
||||||
|
|
||||||
|
if url in cache:
|
||||||
|
f = open(cache[url])
|
||||||
|
html = f.read()
|
||||||
|
f.close()
|
||||||
|
return BeautifulSoup(html, fromEncoding=self.encoding)
|
||||||
|
|
||||||
|
br = BasicNewsRecipe.get_browser()
|
||||||
|
response = br.open(url)
|
||||||
|
html = response.read()
|
||||||
|
soup = BeautifulSoup(html, fromEncoding=self.encoding)
|
||||||
|
for img in soup.findAll('img',src=True):
|
||||||
|
if img['src'].startswith('/'):
|
||||||
|
img['src'] = 'http://www.smilezilla.com' + img['src']
|
||||||
|
pt = PersistentTemporaryFile('.html')
|
||||||
|
pt.write(str(soup.html).encode(self.encoding))
|
||||||
|
pt.close()
|
||||||
|
cache[url] = pt.name
|
||||||
|
return soup
|
||||||
|
|
||||||
|
def _get_entry(self,soup):
|
||||||
|
return soup.find('form', attrs={'name':'contentForm'})
|
||||||
|
|
||||||
|
def _get_section_title(self, soup):
|
||||||
|
title_div = soup.find('div', attrs={'class':'title'})
|
||||||
|
return self.tag_to_string(title_div).strip()
|
||||||
|
|
||||||
|
def parse_index(self):
|
||||||
|
articles = []
|
||||||
|
|
||||||
|
soup = self.cached_fetch(self.JOKES_INDEX)
|
||||||
|
jokes_entry = self._get_entry(soup)
|
||||||
|
section_title = self._get_section_title(soup)
|
||||||
|
todays_jokes = []
|
||||||
|
for hr in enumerate(jokes_entry.findAll('hr')):
|
||||||
|
title = 'Joke ' + str(hr[0] + 1)
|
||||||
|
url = self.JOKES_INDEX
|
||||||
|
todays_jokes.append({'title':title, 'url':url,
|
||||||
|
'description':'', 'date':''})
|
||||||
|
articles.append((section_title,todays_jokes))
|
||||||
|
|
||||||
|
soup = self.cached_fetch(self.STORIES_INDEX)
|
||||||
|
entry = self._get_entry(soup)
|
||||||
|
section_title = self._get_section_title(soup)
|
||||||
|
|
||||||
|
todays_stories = []
|
||||||
|
for hr in enumerate(entry.findAll('hr')):
|
||||||
|
title = 'Story ' + str(hr[0] + 1)
|
||||||
|
current = hr[1]
|
||||||
|
while True:
|
||||||
|
current = current.findPrevious()
|
||||||
|
if current is None:
|
||||||
|
break
|
||||||
|
elif current.name == 'hr':
|
||||||
|
break
|
||||||
|
elif current.name == 'b':
|
||||||
|
title = title + ': ' + self.tag_to_string(current)
|
||||||
|
break
|
||||||
|
url = self.STORIES_INDEX
|
||||||
|
todays_stories.append({'title':title, 'url':url,
|
||||||
|
'description':'', 'date':''})
|
||||||
|
articles.append((section_title,todays_stories))
|
||||||
|
|
||||||
|
|
||||||
|
return articles
|
||||||
|
|
||||||
|
def get_obfuscated_article(self, url):
|
||||||
|
return self.cache[url]
|
||||||
|
|
||||||
|
|
||||||
|
def preprocess_raw_html(self,raw_html, url):
|
||||||
|
url = self.JOKES_INDEX if (self.cache[self.JOKES_INDEX] in url) else self.STORIES_INDEX
|
||||||
|
count = self.counter[url] +1
|
||||||
|
self.counter[url] = count
|
||||||
|
soup = self.index_to_soup(raw_html)
|
||||||
|
entry = self._get_entry(soup)
|
||||||
|
soup2 = BeautifulSoup('<html><head></head><body></body></html>')
|
||||||
|
body = soup2.find('body')
|
||||||
|
entries = str(entry).split('<hr />')
|
||||||
|
body.insert(0,entries[count -1])
|
||||||
|
|
||||||
|
return str(soup2)
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
resources/images/cover_texture.png
Normal file
BIN
resources/images/cover_texture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -60,8 +60,13 @@ function goto_reference(ref) {
|
|||||||
if (num < 0) {alert("Invalid reference: "+ref); return;}
|
if (num < 0) {alert("Invalid reference: "+ref); return;}
|
||||||
var p = $("p");
|
var p = $("p");
|
||||||
if (num >= p.length) {alert("Reference not found: "+ref); return;}
|
if (num >= p.length) {alert("Reference not found: "+ref); return;}
|
||||||
$.scrollTo($(p[num]), 1000,
|
var dest = $(p[num]);
|
||||||
{onAfter:function(){window.py_bridge.animated_scroll_done()}});
|
if (window.paged_display.in_paged_mode) {
|
||||||
|
var xpos = dest.offset().left;
|
||||||
|
window.paged_display.scroll_to_xpos(xpos, true, true, 1000);
|
||||||
|
} else
|
||||||
|
$.scrollTo(dest, 1000,
|
||||||
|
{onAfter:function(){window.py_bridge.animated_scroll_done()}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ let g:syntastic_cpp_include_dirs = [
|
|||||||
\'/usr/include/qt4/QtGui',
|
\'/usr/include/qt4/QtGui',
|
||||||
\'/usr/include/qt4',
|
\'/usr/include/qt4',
|
||||||
\'src/qtcurve/common', 'src/qtcurve',
|
\'src/qtcurve/common', 'src/qtcurve',
|
||||||
|
\'/usr/include/ImageMagick',
|
||||||
\]
|
\]
|
||||||
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs
|
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ PyQt4
|
|||||||
|
|
||||||
Compiling instructions::
|
Compiling instructions::
|
||||||
|
|
||||||
python configure.py -c -j5 -e QtCore -e QtGui -e QtSvg -e QtNetwork -e QtWebKit -e QtXmlPatterns --verbose
|
python configure.py -c -j5 -e QtCore -e QtGui -e QtSvg -e QtNetwork -e QtWebKit -e QtXmlPatterns --verbose --confirm-license
|
||||||
nmake
|
nmake
|
||||||
nmake install
|
nmake install
|
||||||
|
|
||||||
|
@ -90,6 +90,7 @@ class ANDROID(USBMS):
|
|||||||
0x4e22 : [0x0100, 0x226, 0x227, 0x231],
|
0x4e22 : [0x0100, 0x226, 0x227, 0x231],
|
||||||
0xb058 : [0x0222, 0x226, 0x227],
|
0xb058 : [0x0222, 0x226, 0x227],
|
||||||
0x0ff9 : [0x0226],
|
0x0ff9 : [0x0226],
|
||||||
|
0xc91 : HTC_BCDS,
|
||||||
0xdddd : [0x216],
|
0xdddd : [0x216],
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -165,7 +166,10 @@ class ANDROID(USBMS):
|
|||||||
0x2237: { 0x2208 : [0x0226] },
|
0x2237: { 0x2208 : [0x0226] },
|
||||||
|
|
||||||
# Lenovo
|
# Lenovo
|
||||||
0x17ef : { 0x7421 : [0x0216] },
|
0x17ef : {
|
||||||
|
0x7421 : [0x0216],
|
||||||
|
0x741b : [0x9999],
|
||||||
|
},
|
||||||
|
|
||||||
# Pantech
|
# Pantech
|
||||||
0x10a9 : { 0x6050 : [0x227] },
|
0x10a9 : { 0x6050 : [0x227] },
|
||||||
@ -203,7 +207,8 @@ class ANDROID(USBMS):
|
|||||||
'GT-I9003_CARD', 'XT912', 'FILE-CD_GADGET', 'RK29_SDK', 'MB855',
|
'GT-I9003_CARD', 'XT912', 'FILE-CD_GADGET', 'RK29_SDK', 'MB855',
|
||||||
'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']
|
||||||
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',
|
||||||
|
@ -2393,8 +2393,8 @@ class ITUNES(DriverBase):
|
|||||||
foo = self.iTunes.name()
|
foo = self.iTunes.name()
|
||||||
except:
|
except:
|
||||||
# Try static binding
|
# Try static binding
|
||||||
import iTunes_glue
|
import itunes
|
||||||
self.iTunes = appscript.app('iTunes',terms=iTunes_glue)
|
self.iTunes = appscript.app('iTunes',terms=itunes)
|
||||||
try:
|
try:
|
||||||
foo = self.iTunes.name()
|
foo = self.iTunes.name()
|
||||||
as_binding = "static"
|
as_binding = "static"
|
||||||
|
280
src/calibre/devices/apple/itunes.py
Normal file
280
src/calibre/devices/apple/itunes.py
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
version = 1.1
|
||||||
|
path = '/Applications/iTunes.app'
|
||||||
|
|
||||||
|
classes = \
|
||||||
|
[('print_settings', 'pset'),
|
||||||
|
('application', 'capp'),
|
||||||
|
('artwork', 'cArt'),
|
||||||
|
('audio_CD_playlist', 'cCDP'),
|
||||||
|
('audio_CD_track', 'cCDT'),
|
||||||
|
('browser_window', 'cBrW'),
|
||||||
|
('device_playlist', 'cDvP'),
|
||||||
|
('device_track', 'cDvT'),
|
||||||
|
('encoder', 'cEnc'),
|
||||||
|
('EQ_preset', 'cEQP'),
|
||||||
|
('EQ_window', 'cEQW'),
|
||||||
|
('file_track', 'cFlT'),
|
||||||
|
('folder_playlist', 'cFoP'),
|
||||||
|
('item', 'cobj'),
|
||||||
|
('library_playlist', 'cLiP'),
|
||||||
|
('playlist', 'cPly'),
|
||||||
|
('playlist_window', 'cPlW'),
|
||||||
|
('radio_tuner_playlist', 'cRTP'),
|
||||||
|
('shared_track', 'cShT'),
|
||||||
|
('source', 'cSrc'),
|
||||||
|
('track', 'cTrk'),
|
||||||
|
('URL_track', 'cURT'),
|
||||||
|
('user_playlist', 'cUsP'),
|
||||||
|
('visual', 'cVis'),
|
||||||
|
('window', 'cwin')]
|
||||||
|
|
||||||
|
enums = \
|
||||||
|
[('track_listing', 'kTrk'),
|
||||||
|
('album_listing', 'kAlb'),
|
||||||
|
('cd_insert', 'kCDi'),
|
||||||
|
('standard', 'lwst'),
|
||||||
|
('detailed', 'lwdt'),
|
||||||
|
('stopped', 'kPSS'),
|
||||||
|
('playing', 'kPSP'),
|
||||||
|
('paused', 'kPSp'),
|
||||||
|
('fast_forwarding', 'kPSF'),
|
||||||
|
('rewinding', 'kPSR'),
|
||||||
|
('off', 'kRpO'),
|
||||||
|
('one', 'kRp1'),
|
||||||
|
('all', 'kAll'),
|
||||||
|
('small', 'kVSS'),
|
||||||
|
('medium', 'kVSM'),
|
||||||
|
('large', 'kVSL'),
|
||||||
|
('library', 'kLib'),
|
||||||
|
('iPod', 'kPod'),
|
||||||
|
('audio_CD', 'kACD'),
|
||||||
|
('MP3_CD', 'kMCD'),
|
||||||
|
('device', 'kDev'),
|
||||||
|
('radio_tuner', 'kTun'),
|
||||||
|
('shared_library', 'kShd'),
|
||||||
|
('unknown', 'kUnk'),
|
||||||
|
('albums', 'kSrL'),
|
||||||
|
('artists', 'kSrR'),
|
||||||
|
('composers', 'kSrC'),
|
||||||
|
('displayed', 'kSrV'),
|
||||||
|
('songs', 'kSrS'),
|
||||||
|
('none', 'kNon'),
|
||||||
|
('Books', 'kSpA'),
|
||||||
|
('folder', 'kSpF'),
|
||||||
|
('Genius', 'kSpG'),
|
||||||
|
('iTunes_U', 'kSpU'),
|
||||||
|
('Library', 'kSpL'),
|
||||||
|
('Movies', 'kSpI'),
|
||||||
|
('Music', 'kSpZ'),
|
||||||
|
('Party_Shuffle', 'kSpS'),
|
||||||
|
('Podcasts', 'kSpP'),
|
||||||
|
('Purchased_Music', 'kSpM'),
|
||||||
|
('TV_Shows', 'kSpT'),
|
||||||
|
('movie', 'kVdM'),
|
||||||
|
('music_video', 'kVdV'),
|
||||||
|
('TV_show', 'kVdT'),
|
||||||
|
('user', 'kRtU'),
|
||||||
|
('computed', 'kRtC')]
|
||||||
|
|
||||||
|
properties = \
|
||||||
|
[('copies', 'lwcp'),
|
||||||
|
('collating', 'lwcl'),
|
||||||
|
('starting_page', 'lwfp'),
|
||||||
|
('ending_page', 'lwlp'),
|
||||||
|
('pages_across', 'lwla'),
|
||||||
|
('pages_down', 'lwld'),
|
||||||
|
('error_handling', 'lweh'),
|
||||||
|
('requested_print_time', 'lwqt'),
|
||||||
|
('printer_features', 'lwpf'),
|
||||||
|
('fax_number', 'faxn'),
|
||||||
|
('target_printer', 'trpr'),
|
||||||
|
('current_encoder', 'pEnc'),
|
||||||
|
('current_EQ_preset', 'pEQP'),
|
||||||
|
('current_playlist', 'pPla'),
|
||||||
|
('current_stream_title', 'pStT'),
|
||||||
|
('current_stream_URL', 'pStU'),
|
||||||
|
('current_track', 'pTrk'),
|
||||||
|
('current_visual', 'pVis'),
|
||||||
|
('EQ_enabled', 'pEQ '),
|
||||||
|
('fixed_indexing', 'pFix'),
|
||||||
|
('frontmost', 'pisf'),
|
||||||
|
('full_screen', 'pFSc'),
|
||||||
|
('name', 'pnam'),
|
||||||
|
('mute', 'pMut'),
|
||||||
|
('player_position', 'pPos'),
|
||||||
|
('player_state', 'pPlS'),
|
||||||
|
('selection', 'sele'),
|
||||||
|
('sound_volume', 'pVol'),
|
||||||
|
('version', 'vers'),
|
||||||
|
('visuals_enabled', 'pVsE'),
|
||||||
|
('visual_size', 'pVSz'),
|
||||||
|
('data', 'pPCT'),
|
||||||
|
('description', 'pDes'),
|
||||||
|
('downloaded', 'pDlA'),
|
||||||
|
('format', 'pFmt'),
|
||||||
|
('kind', 'pKnd'),
|
||||||
|
('raw_data', 'pRaw'),
|
||||||
|
('artist', 'pArt'),
|
||||||
|
('compilation', 'pAnt'),
|
||||||
|
('composer', 'pCmp'),
|
||||||
|
('disc_count', 'pDsC'),
|
||||||
|
('disc_number', 'pDsN'),
|
||||||
|
('genre', 'pGen'),
|
||||||
|
('year', 'pYr '),
|
||||||
|
('location', 'pLoc'),
|
||||||
|
('minimized', 'pMin'),
|
||||||
|
('view', 'pPly'),
|
||||||
|
('band_1', 'pEQ1'),
|
||||||
|
('band_2', 'pEQ2'),
|
||||||
|
('band_3', 'pEQ3'),
|
||||||
|
('band_4', 'pEQ4'),
|
||||||
|
('band_5', 'pEQ5'),
|
||||||
|
('band_6', 'pEQ6'),
|
||||||
|
('band_7', 'pEQ7'),
|
||||||
|
('band_8', 'pEQ8'),
|
||||||
|
('band_9', 'pEQ9'),
|
||||||
|
('band_10', 'pEQ0'),
|
||||||
|
('modifiable', 'pMod'),
|
||||||
|
('preamp', 'pEQA'),
|
||||||
|
('update_tracks', 'pUTC'),
|
||||||
|
('container', 'ctnr'),
|
||||||
|
('id', 'ID '),
|
||||||
|
('index', 'pidx'),
|
||||||
|
('persistent_ID', 'pPIS'),
|
||||||
|
('duration', 'pDur'),
|
||||||
|
('parent', 'pPlP'),
|
||||||
|
('shuffle', 'pShf'),
|
||||||
|
('size', 'pSiz'),
|
||||||
|
('song_repeat', 'pRpt'),
|
||||||
|
('special_kind', 'pSpK'),
|
||||||
|
('time', 'pTim'),
|
||||||
|
('visible', 'pvis'),
|
||||||
|
('capacity', 'capa'),
|
||||||
|
('free_space', 'frsp'),
|
||||||
|
('album', 'pAlb'),
|
||||||
|
('album_artist', 'pAlA'),
|
||||||
|
('album_rating', 'pAlR'),
|
||||||
|
('album_rating_kind', 'pARk'),
|
||||||
|
('bit_rate', 'pBRt'),
|
||||||
|
('bookmark', 'pBkt'),
|
||||||
|
('bookmarkable', 'pBkm'),
|
||||||
|
('bpm', 'pBPM'),
|
||||||
|
('category', 'pCat'),
|
||||||
|
('comment', 'pCmt'),
|
||||||
|
('database_ID', 'pDID'),
|
||||||
|
('date_added', 'pAdd'),
|
||||||
|
('enabled', 'enbl'),
|
||||||
|
('episode_ID', 'pEpD'),
|
||||||
|
('episode_number', 'pEpN'),
|
||||||
|
('EQ', 'pEQp'),
|
||||||
|
('finish', 'pStp'),
|
||||||
|
('gapless', 'pGpl'),
|
||||||
|
('grouping', 'pGrp'),
|
||||||
|
('long_description', 'pLds'),
|
||||||
|
('lyrics', 'pLyr'),
|
||||||
|
('modification_date', 'asmo'),
|
||||||
|
('played_count', 'pPlC'),
|
||||||
|
('played_date', 'pPlD'),
|
||||||
|
('podcast', 'pTPc'),
|
||||||
|
('rating', 'pRte'),
|
||||||
|
('rating_kind', 'pRtk'),
|
||||||
|
('release_date', 'pRlD'),
|
||||||
|
('sample_rate', 'pSRt'),
|
||||||
|
('season_number', 'pSeN'),
|
||||||
|
('shufflable', 'pSfa'),
|
||||||
|
('skipped_count', 'pSkC'),
|
||||||
|
('skipped_date', 'pSkD'),
|
||||||
|
('show', 'pShw'),
|
||||||
|
('sort_album', 'pSAl'),
|
||||||
|
('sort_artist', 'pSAr'),
|
||||||
|
('sort_album_artist', 'pSAA'),
|
||||||
|
('sort_name', 'pSNm'),
|
||||||
|
('sort_composer', 'pSCm'),
|
||||||
|
('sort_show', 'pSSN'),
|
||||||
|
('start', 'pStr'),
|
||||||
|
('track_count', 'pTrC'),
|
||||||
|
('track_number', 'pTrN'),
|
||||||
|
('unplayed', 'pUnp'),
|
||||||
|
('video_kind', 'pVdK'),
|
||||||
|
('volume_adjustment', 'pAdj'),
|
||||||
|
('address', 'pURL'),
|
||||||
|
('shared', 'pShr'),
|
||||||
|
('smart', 'pSmt'),
|
||||||
|
('bounds', 'pbnd'),
|
||||||
|
('closeable', 'hclb'),
|
||||||
|
('collapseable', 'pWSh'),
|
||||||
|
('collapsed', 'wshd'),
|
||||||
|
('position', 'ppos'),
|
||||||
|
('resizable', 'prsz'),
|
||||||
|
('zoomable', 'iszm'),
|
||||||
|
('zoomed', 'pzum')]
|
||||||
|
|
||||||
|
elements = \
|
||||||
|
[('artworks', 'cArt'),
|
||||||
|
('audio_CD_playlists', 'cCDP'),
|
||||||
|
('audio_CD_tracks', 'cCDT'),
|
||||||
|
('browser_windows', 'cBrW'),
|
||||||
|
('device_playlists', 'cDvP'),
|
||||||
|
('device_tracks', 'cDvT'),
|
||||||
|
('encoders', 'cEnc'),
|
||||||
|
('EQ_presets', 'cEQP'),
|
||||||
|
('EQ_windows', 'cEQW'),
|
||||||
|
('file_tracks', 'cFlT'),
|
||||||
|
('folder_playlists', 'cFoP'),
|
||||||
|
('items', 'cobj'),
|
||||||
|
('library_playlists', 'cLiP'),
|
||||||
|
('playlists', 'cPly'),
|
||||||
|
('playlist_windows', 'cPlW'),
|
||||||
|
('radio_tuner_playlists', 'cRTP'),
|
||||||
|
('shared_tracks', 'cShT'),
|
||||||
|
('sources', 'cSrc'),
|
||||||
|
('tracks', 'cTrk'),
|
||||||
|
('URL_tracks', 'cURT'),
|
||||||
|
('user_playlists', 'cUsP'),
|
||||||
|
('visuals', 'cVis'),
|
||||||
|
('windows', 'cwin'),
|
||||||
|
('application', 'capp'),
|
||||||
|
('print_settings', 'pset')]
|
||||||
|
|
||||||
|
commands = \
|
||||||
|
[('set', 'coresetd', [('to', 'data')]),
|
||||||
|
('exists', 'coredoex', []),
|
||||||
|
('move', 'coremove', [('to', 'insh')]),
|
||||||
|
('subscribe', 'hookpSub', []),
|
||||||
|
('playpause', 'hookPlPs', []),
|
||||||
|
('download', 'hookDwnl', []),
|
||||||
|
('close', 'coreclos', []),
|
||||||
|
('open', 'aevtodoc', []),
|
||||||
|
('open_location', 'GURLGURL', []),
|
||||||
|
('quit', 'aevtquit', []),
|
||||||
|
('pause', 'hookPaus', []),
|
||||||
|
('make',
|
||||||
|
'corecrel',
|
||||||
|
[('new', 'kocl'), ('at', 'insh'), ('with_properties', 'prdt')]),
|
||||||
|
('duplicate', 'coreclon', [('to', 'insh')]),
|
||||||
|
('print_',
|
||||||
|
'aevtpdoc',
|
||||||
|
[('print_dialog', 'pdlg'),
|
||||||
|
('with_properties', 'prdt'),
|
||||||
|
('kind', 'pKnd'),
|
||||||
|
('theme', 'pThm')]),
|
||||||
|
('add', 'hookAdd ', [('to', 'insh')]),
|
||||||
|
('rewind', 'hookRwnd', []),
|
||||||
|
('play', 'hookPlay', [('once', 'POne')]),
|
||||||
|
('run', 'aevtoapp', []),
|
||||||
|
('resume', 'hookResu', []),
|
||||||
|
('updatePodcast', 'hookUpd1', []),
|
||||||
|
('next_track', 'hookNext', []),
|
||||||
|
('stop', 'hookStop', []),
|
||||||
|
('search', 'hookSrch', [('for_', 'pTrm'), ('only', 'pAre')]),
|
||||||
|
('updateAllPodcasts', 'hookUpdp', []),
|
||||||
|
('update', 'hookUpdt', []),
|
||||||
|
('previous_track', 'hookPrev', []),
|
||||||
|
('fast_forward', 'hookFast', []),
|
||||||
|
('count', 'corecnte', [('each', 'kocl')]),
|
||||||
|
('reveal', 'hookRevl', []),
|
||||||
|
('convert', 'hookConv', []),
|
||||||
|
('eject', 'hookEjct', []),
|
||||||
|
('back_track', 'hookBack', []),
|
||||||
|
('refresh', 'hookRfrs', []),
|
||||||
|
('delete', 'coredelo', [])]
|
@ -5,10 +5,6 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
# First repeat after me: Linux desktop infrastructure is designed by a
|
|
||||||
# committee of rabid monkeys on crack. They would not know a decent desktop if
|
|
||||||
# it was driving the rabid monkey extermination truck that runs them over.
|
|
||||||
|
|
||||||
import os, dbus, re
|
import os, dbus, re
|
||||||
|
|
||||||
def node_mountpoint(node):
|
def node_mountpoint(node):
|
||||||
@ -23,13 +19,20 @@ def node_mountpoint(node):
|
|||||||
return de_mangle(line[1])
|
return de_mangle(line[1])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
class NoUDisks1(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class UDisks(object):
|
class UDisks(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.bus = dbus.SystemBus()
|
self.bus = dbus.SystemBus()
|
||||||
self.main = dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
|
try:
|
||||||
|
self.main = dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
|
||||||
'/org/freedesktop/UDisks'), 'org.freedesktop.UDisks')
|
'/org/freedesktop/UDisks'), 'org.freedesktop.UDisks')
|
||||||
|
except dbus.exceptions.DBusException as e:
|
||||||
|
if getattr(e, '_dbus_error_name', None) == 'org.freedesktop.DBus.Error.ServiceUnknown':
|
||||||
|
raise NoUDisks1()
|
||||||
|
raise
|
||||||
|
|
||||||
def device(self, device_node_path):
|
def device(self, device_node_path):
|
||||||
devpath = self.main.FindDeviceByDeviceFile(device_node_path)
|
devpath = self.main.FindDeviceByDeviceFile(device_node_path)
|
||||||
@ -67,6 +70,7 @@ class UDisks2(object):
|
|||||||
|
|
||||||
BLOCK = 'org.freedesktop.UDisks2.Block'
|
BLOCK = 'org.freedesktop.UDisks2.Block'
|
||||||
FILESYSTEM = 'org.freedesktop.UDisks2.Filesystem'
|
FILESYSTEM = 'org.freedesktop.UDisks2.Filesystem'
|
||||||
|
DRIVE = 'org.freedesktop.UDisks2.Drive'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.bus = dbus.SystemBus()
|
self.bus = dbus.SystemBus()
|
||||||
@ -131,6 +135,21 @@ class UDisks2(object):
|
|||||||
raise
|
raise
|
||||||
return mp
|
return mp
|
||||||
|
|
||||||
|
def unmount(self, device_node_path):
|
||||||
|
d = self.device(device_node_path)
|
||||||
|
d.Unmount({'force':True, 'auth.no_user_interaction':True},
|
||||||
|
dbus_interface=self.FILESYSTEM)
|
||||||
|
|
||||||
|
def drive_for_device(self, device):
|
||||||
|
drive = device.Get(self.BLOCK, 'Drive',
|
||||||
|
dbus_interface='org.freedesktop.DBus.Properties')
|
||||||
|
return self.bus.get_object('org.freedesktop.UDisks2', drive)
|
||||||
|
|
||||||
|
def eject(self, device_node_path):
|
||||||
|
drive = self.drive_for_device(self.device(device_node_path))
|
||||||
|
drive.Eject({'auth.no_user_interaction':True},
|
||||||
|
dbus_interface=self.DRIVE)
|
||||||
|
|
||||||
def get_udisks(ver=None):
|
def get_udisks(ver=None):
|
||||||
if ver is None:
|
if ver is None:
|
||||||
try:
|
try:
|
||||||
@ -140,7 +159,6 @@ def get_udisks(ver=None):
|
|||||||
return u
|
return u
|
||||||
return UDisks2() if ver == 2 else UDisks()
|
return UDisks2() if ver == 2 else UDisks()
|
||||||
|
|
||||||
|
|
||||||
def mount(node_path):
|
def mount(node_path):
|
||||||
u = UDisks()
|
u = UDisks()
|
||||||
u.mount(node_path)
|
u.mount(node_path)
|
||||||
|
@ -187,7 +187,9 @@ def calibre_cover(title, author_string, series_string=None,
|
|||||||
lines.append(TextLine(series_string, author_size))
|
lines.append(TextLine(series_string, author_size))
|
||||||
if logo_path is None:
|
if logo_path is None:
|
||||||
logo_path = I('library.png')
|
logo_path = I('library.png')
|
||||||
return create_cover_page(lines, logo_path, output_format='jpg')
|
return create_cover_page(lines, logo_path, output_format='jpg',
|
||||||
|
texture_opacity=0.3, texture_data=I('cover_texture.png',
|
||||||
|
data=True))
|
||||||
|
|
||||||
UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$')
|
UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$')
|
||||||
|
|
||||||
|
@ -687,7 +687,11 @@ class Amazon(Source):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
for div in root.xpath(r'//div[starts-with(@id, "result_")]'):
|
for div in root.xpath(r'//div[starts-with(@id, "result_")]'):
|
||||||
for a in div.xpath(r'descendant::a[@class="title" and @href]'):
|
links = div.xpath(r'descendant::a[@class="title" and @href]')
|
||||||
|
if not links:
|
||||||
|
# New amazon markup
|
||||||
|
links = div.xpath('descendant::h3/a[@href]')
|
||||||
|
for a in links:
|
||||||
title = tostring(a, method='text', encoding=unicode)
|
title = tostring(a, method='text', encoding=unicode)
|
||||||
if title_ok(title):
|
if title_ok(title):
|
||||||
matches.append(a.get('href'))
|
matches.append(a.get('href'))
|
||||||
|
@ -154,10 +154,11 @@ class ISBNDB(Source):
|
|||||||
total_results = int(bl.get('total_results'))
|
total_results = int(bl.get('total_results'))
|
||||||
shown_results = int(bl.get('shown_results'))
|
shown_results = int(bl.get('shown_results'))
|
||||||
for bd in bl.xpath('.//BookData'):
|
for bd in bl.xpath('.//BookData'):
|
||||||
isbn = check_isbn(bd.get('isbn13', bd.get('isbn', None)))
|
isbn = check_isbn(bd.get('isbn', None))
|
||||||
if not isbn:
|
isbn13 = check_isbn(bd.get('isbn13', None))
|
||||||
|
if not isbn and not isbn13:
|
||||||
continue
|
continue
|
||||||
if orig_isbn and isbn != orig_isbn:
|
if orig_isbn and orig_isbn not in {isbn, isbn13}:
|
||||||
continue
|
continue
|
||||||
title = tostring(bd.find('Title'))
|
title = tostring(bd.find('Title'))
|
||||||
if not title:
|
if not title:
|
||||||
@ -173,10 +174,6 @@ class ISBNDB(Source):
|
|||||||
if not authors:
|
if not authors:
|
||||||
continue
|
continue
|
||||||
comments = tostring(bd.find('Summary'))
|
comments = tostring(bd.find('Summary'))
|
||||||
if not comments:
|
|
||||||
# Require comments, since without them the result is useless
|
|
||||||
# anyway
|
|
||||||
continue
|
|
||||||
id_ = (title, tuple(authors))
|
id_ = (title, tuple(authors))
|
||||||
if id_ in seen:
|
if id_ in seen:
|
||||||
continue
|
continue
|
||||||
|
@ -167,7 +167,8 @@ def test_identify(tests): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None,
|
||||||
|
fail_missing_meta=True): # {{{
|
||||||
'''
|
'''
|
||||||
:param name: Plugin name
|
:param name: Plugin name
|
||||||
:param tests: List of 2-tuples. Each two tuple is of the form (args,
|
:param tests: List of 2-tuples. Each two tuple is of the form (args,
|
||||||
@ -246,7 +247,8 @@ def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
|||||||
None]
|
None]
|
||||||
if not good:
|
if not good:
|
||||||
prints('Failed to find', plugin.test_fields(possibles[0]))
|
prints('Failed to find', plugin.test_fields(possibles[0]))
|
||||||
raise SystemExit(1)
|
if fail_missing_meta:
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
if results[0] is not possibles[0]:
|
if results[0] is not possibles[0]:
|
||||||
prints('Most relevant result failed the tests')
|
prints('Most relevant result failed the tests')
|
||||||
@ -263,21 +265,22 @@ def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
|||||||
results.append(rq.get_nowait())
|
results.append(rq.get_nowait())
|
||||||
except Empty:
|
except Empty:
|
||||||
break
|
break
|
||||||
if not results:
|
if not results and fail_missing_meta:
|
||||||
prints('Cover download failed')
|
prints('Cover download failed')
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
cdata = results[0]
|
elif results:
|
||||||
cover = os.path.join(tdir, plugin.name.replace(' ',
|
cdata = results[0]
|
||||||
'')+'-%s-cover.jpg'%sanitize_file_name2(mi.title.replace(' ',
|
cover = os.path.join(tdir, plugin.name.replace(' ',
|
||||||
'_')))
|
'')+'-%s-cover.jpg'%sanitize_file_name2(mi.title.replace(' ',
|
||||||
with open(cover, 'wb') as f:
|
'_')))
|
||||||
f.write(cdata[-1])
|
with open(cover, 'wb') as f:
|
||||||
|
f.write(cdata[-1])
|
||||||
|
|
||||||
prints('Cover downloaded to:', cover)
|
prints('Cover downloaded to:', cover)
|
||||||
|
|
||||||
if len(cdata[-1]) < 10240:
|
if len(cdata[-1]) < 10240:
|
||||||
prints('Downloaded cover too small')
|
prints('Downloaded cover too small')
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
prints('Average time per query', sum(times)/len(times))
|
prints('Average time per query', sum(times)/len(times))
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ class Skeleton(object):
|
|||||||
self.chunks = chunks
|
self.chunks = chunks
|
||||||
|
|
||||||
self.skeleton = self.render(root)
|
self.skeleton = self.render(root)
|
||||||
self.body_offset = self.skeleton.find('<body')
|
self.body_offset = self.skeleton.find(b'<body')
|
||||||
self.calculate_metrics(root)
|
self.calculate_metrics(root)
|
||||||
|
|
||||||
self.calculate_insert_positions()
|
self.calculate_insert_positions()
|
||||||
@ -127,7 +127,7 @@ class Skeleton(object):
|
|||||||
self.metrics = {}
|
self.metrics = {}
|
||||||
for tag in root.xpath('//*[@aid]'):
|
for tag in root.xpath('//*[@aid]'):
|
||||||
text = (tag.text or '').encode('utf-8')
|
text = (tag.text or '').encode('utf-8')
|
||||||
raw = tostring(tag, with_tail=True)
|
raw = close_self_closing_tags(tostring(tag, with_tail=True))
|
||||||
start_length = len(raw.partition(b'>')[0]) + len(text) + 1
|
start_length = len(raw.partition(b'>')[0]) + len(text) + 1
|
||||||
end_length = len(raw.rpartition(b'<')[-1]) + 1
|
end_length = len(raw.rpartition(b'<')[-1]) + 1
|
||||||
self.metrics[tag.get('aid')] = Metric(start_length, end_length)
|
self.metrics[tag.get('aid')] = Metric(start_length, end_length)
|
||||||
|
@ -469,6 +469,8 @@ class DirContainer(object):
|
|||||||
return f.write(data)
|
return f.write(data)
|
||||||
|
|
||||||
def exists(self, path):
|
def exists(self, path):
|
||||||
|
if not path:
|
||||||
|
return False
|
||||||
try:
|
try:
|
||||||
path = os.path.join(self.rootdir, self._unquote(path))
|
path = os.path.join(self.rootdir, self._unquote(path))
|
||||||
except ValueError: #Happens if path contains quoted special chars
|
except ValueError: #Happens if path contains quoted special chars
|
||||||
|
@ -6,20 +6,34 @@
|
|||||||
Released under the GPLv3 License
|
Released under the GPLv3 License
|
||||||
###
|
###
|
||||||
|
|
||||||
body_height = () ->
|
window_scroll_pos = (win=window) -> # {{{
|
||||||
db = document.body
|
if typeof(win.pageXOffset) == 'number'
|
||||||
dde = document.documentElement
|
x = win.pageXOffset
|
||||||
if db? and dde?
|
y = win.pageYOffset
|
||||||
return Math.max(db.scrollHeight, dde.scrollHeight, db.offsetHeight,
|
else # IE < 9
|
||||||
dde.offsetHeight, db.clientHeight, dde.clientHeight)
|
if document.body and ( document.body.scrollLeft or document.body.scrollTop )
|
||||||
return 0
|
x = document.body.scrollLeft
|
||||||
|
y = document.body.scrollTop
|
||||||
|
else if document.documentElement and ( document.documentElement.scrollLeft or document.documentElement.scrollTop)
|
||||||
|
y = document.documentElement.scrollTop
|
||||||
|
x = document.documentElement.scrollLeft
|
||||||
|
return [x, y]
|
||||||
|
# }}}
|
||||||
|
|
||||||
abstop = (elem) ->
|
viewport_to_document = (x, y, doc=window?.document) -> # {{{
|
||||||
ans = elem.offsetTop
|
until doc == window.document
|
||||||
while elem.offsetParent
|
# We are in a frame
|
||||||
elem = elem.offsetParent
|
frame = doc.defaultView.frameElement
|
||||||
ans += elem.offsetTop
|
rect = frame.getBoundingClientRect()
|
||||||
return ans
|
x += rect.left
|
||||||
|
y += rect.top
|
||||||
|
doc = frame.ownerDocument
|
||||||
|
win = doc.defaultView
|
||||||
|
[wx, wy] = window_scroll_pos(win)
|
||||||
|
x += wx
|
||||||
|
y += wy
|
||||||
|
return [x, y]
|
||||||
|
# }}}
|
||||||
|
|
||||||
class BookIndexing
|
class BookIndexing
|
||||||
###
|
###
|
||||||
@ -33,7 +47,7 @@ class BookIndexing
|
|||||||
|
|
||||||
constructor: () ->
|
constructor: () ->
|
||||||
this.cache = {}
|
this.cache = {}
|
||||||
this.body_height_at_last_check = null
|
this.last_check = [null, null]
|
||||||
|
|
||||||
cache_valid: (anchors) ->
|
cache_valid: (anchors) ->
|
||||||
for a in anchors
|
for a in anchors
|
||||||
@ -45,7 +59,9 @@ class BookIndexing
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
anchor_positions: (anchors, use_cache=false) ->
|
anchor_positions: (anchors, use_cache=false) ->
|
||||||
if use_cache and body_height() == this.body_height_at_last_check and this.cache_valid(anchors)
|
body = document.body
|
||||||
|
doc_constant = body.scrollHeight == this.last_check[1] and body.scrollWidth == this.last_check[0]
|
||||||
|
if use_cache and doc_constant and this.cache_valid(anchors)
|
||||||
return this.cache
|
return this.cache
|
||||||
|
|
||||||
ans = {}
|
ans = {}
|
||||||
@ -56,19 +72,24 @@ class BookIndexing
|
|||||||
try
|
try
|
||||||
result = document.evaluate(
|
result = document.evaluate(
|
||||||
".//*[local-name() = 'a' and @name='#{ anchor }']",
|
".//*[local-name() = 'a' and @name='#{ anchor }']",
|
||||||
document.body, null,
|
body, null,
|
||||||
XPathResult.FIRST_ORDERED_NODE_TYPE, null)
|
XPathResult.FIRST_ORDERED_NODE_TYPE, null)
|
||||||
elem = result.singleNodeValue
|
elem = result.singleNodeValue
|
||||||
catch error
|
catch error
|
||||||
# The anchor had a ' or other invalid char
|
# The anchor had a ' or other invalid char
|
||||||
elem = null
|
elem = null
|
||||||
if elem == null
|
if elem == null
|
||||||
pos = body_height() + 10000
|
pos = [body.scrollWidth+1000, body.scrollHeight+1000]
|
||||||
else
|
else
|
||||||
pos = abstop(elem)
|
br = elem.getBoundingClientRect()
|
||||||
|
pos = viewport_to_document(br.left, br.top, elem.ownerDocument)
|
||||||
|
|
||||||
|
if window.paged_display?.in_paged_mode
|
||||||
|
pos[0] = window.paged_display.column_at(pos[0])
|
||||||
ans[anchor] = pos
|
ans[anchor] = pos
|
||||||
|
|
||||||
this.cache = ans
|
this.cache = ans
|
||||||
this.body_height_at_last_check = body_height()
|
this.last_check = [body.scrollWidth, body.scrollHeight]
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
if window?
|
if window?
|
||||||
|
@ -50,14 +50,12 @@ absleft = (elem) -> # {{{
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class PagedDisplay
|
class PagedDisplay
|
||||||
###
|
# This class is a namespace to expose functions via the
|
||||||
This class is a namespace to expose functions via the
|
# window.paged_display object. The most important functions are:
|
||||||
window.paged_display object. The most important functions are:
|
#
|
||||||
|
# set_geometry(): sets the parameters used to layout text in paged mode
|
||||||
set_geometry(): sets the parameters used to layout text in paged mode
|
#
|
||||||
|
# layout(): causes the currently loaded document to be laid out in columns.
|
||||||
layout(): causes the currently loaded document to be laid out in columns.
|
|
||||||
###
|
|
||||||
|
|
||||||
constructor: () ->
|
constructor: () ->
|
||||||
if not this instanceof arguments.callee
|
if not this instanceof arguments.callee
|
||||||
@ -163,7 +161,7 @@ class PagedDisplay
|
|||||||
xpos = Math.floor(document.body.scrollWidth * frac)
|
xpos = Math.floor(document.body.scrollWidth * frac)
|
||||||
this.scroll_to_xpos(xpos)
|
this.scroll_to_xpos(xpos)
|
||||||
|
|
||||||
scroll_to_xpos: (xpos) ->
|
scroll_to_xpos: (xpos, animated=false, notify=false, duration=1000) ->
|
||||||
# Scroll so that the column containing xpos is the left most column in
|
# Scroll so that the column containing xpos is the left most column in
|
||||||
# the viewport
|
# the viewport
|
||||||
if typeof(xpos) != 'number'
|
if typeof(xpos) != 'number'
|
||||||
@ -172,12 +170,47 @@ class PagedDisplay
|
|||||||
if this.is_full_screen_layout
|
if this.is_full_screen_layout
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
return
|
return
|
||||||
pos = 0
|
pos = Math.floor(xpos/this.page_width) * this.page_width
|
||||||
until (pos <= xpos < pos + this.page_width)
|
|
||||||
pos += this.page_width
|
|
||||||
limit = document.body.scrollWidth - this.screen_width
|
limit = document.body.scrollWidth - this.screen_width
|
||||||
pos = limit if pos > limit
|
pos = limit if pos > limit
|
||||||
window.scrollTo(pos, 0)
|
if animated
|
||||||
|
this.animated_scroll(pos, duration=1000, notify=notify)
|
||||||
|
else
|
||||||
|
window.scrollTo(pos, 0)
|
||||||
|
|
||||||
|
column_at: (xpos) ->
|
||||||
|
# Return the number of the column that contains xpos
|
||||||
|
return Math.floor(xpos/this.page_width)
|
||||||
|
|
||||||
|
column_boundaries: () ->
|
||||||
|
# Return the column numbers at the left edge and after the right edge
|
||||||
|
# of the viewport
|
||||||
|
l = this.column_at(window.pageXOffset + 10)
|
||||||
|
return [l, l + this.cols_per_screen]
|
||||||
|
|
||||||
|
animated_scroll: (pos, duration=1000, notify=true) ->
|
||||||
|
# Scroll the window to X-position pos in an animated fashion over
|
||||||
|
# duration milliseconds. If notify is true, py_bridge.animated_scroll_done is
|
||||||
|
# called.
|
||||||
|
delta = pos - window.pageXOffset
|
||||||
|
interval = 50
|
||||||
|
steps = Math.floor(duration/interval)
|
||||||
|
step_size = Math.floor(delta/steps)
|
||||||
|
this.current_scroll_animation = {target:pos, step_size:step_size, interval:interval, notify:notify, fn: () =>
|
||||||
|
a = this.current_scroll_animation
|
||||||
|
npos = window.pageXOffset + a.step_size
|
||||||
|
completed = false
|
||||||
|
if Math.abs(npos - a.target) < Math.abs(a.step_size)
|
||||||
|
completed = true
|
||||||
|
npos = a.target
|
||||||
|
window.scrollTo(npos, 0)
|
||||||
|
if completed
|
||||||
|
if notify
|
||||||
|
window.py_bridge.animated_scroll_done()
|
||||||
|
else
|
||||||
|
setTimeout(a.fn, a.interval)
|
||||||
|
}
|
||||||
|
this.current_scroll_animation.fn()
|
||||||
|
|
||||||
current_pos: (frac) ->
|
current_pos: (frac) ->
|
||||||
# The current scroll position as a fraction between 0 and 1
|
# The current scroll position as a fraction between 0 and 1
|
||||||
@ -192,10 +225,7 @@ class PagedDisplay
|
|||||||
if this.is_full_screen_layout
|
if this.is_full_screen_layout
|
||||||
return 0
|
return 0
|
||||||
x = window.pageXOffset + Math.max(10, this.current_margin_side)
|
x = window.pageXOffset + Math.max(10, this.current_margin_side)
|
||||||
edge = Math.floor(x/this.page_width) * this.page_width
|
return Math.floor(x/this.page_width) * this.page_width
|
||||||
while edge < x
|
|
||||||
edge += this.page_width
|
|
||||||
return edge - this.page_width
|
|
||||||
|
|
||||||
next_screen_location: () ->
|
next_screen_location: () ->
|
||||||
# The position to scroll to for the next screen (which could contain
|
# The position to scroll to for the next screen (which could contain
|
||||||
@ -329,7 +359,6 @@ if window?
|
|||||||
window.paged_display = new PagedDisplay()
|
window.paged_display = new PagedDisplay()
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# Go to reference positions
|
|
||||||
# Indexing
|
|
||||||
# Resizing of images
|
# Resizing of images
|
||||||
# Full screen mode
|
# Full screen mode
|
||||||
|
# Highlight on jump_to_anchor
|
||||||
|
@ -105,14 +105,14 @@ class UniqueFilenames(object): # {{{
|
|||||||
base, ext = posixpath.splitext(item.href)
|
base, ext = posixpath.splitext(item.href)
|
||||||
nhref = base + suffix + ext
|
nhref = base + suffix + ext
|
||||||
nhref = oeb.manifest.generate(href=nhref)[1]
|
nhref = oeb.manifest.generate(href=nhref)[1]
|
||||||
|
spine_pos = item.spine_position
|
||||||
|
oeb.manifest.remove(item)
|
||||||
nitem = oeb.manifest.add(item.id, nhref, item.media_type, data=data,
|
nitem = oeb.manifest.add(item.id, nhref, item.media_type, data=data,
|
||||||
fallback=item.fallback)
|
fallback=item.fallback)
|
||||||
self.seen_filenames.add(posixpath.basename(nhref))
|
self.seen_filenames.add(posixpath.basename(nhref))
|
||||||
self.rename_map[item.href] = nhref
|
self.rename_map[item.href] = nhref
|
||||||
if item.spine_position is not None:
|
if spine_pos is not None:
|
||||||
oeb.spine.insert(item.spine_position, nitem, item.linear)
|
oeb.spine.insert(spine_pos, nitem, item.linear)
|
||||||
oeb.spine.remove(item)
|
|
||||||
oeb.manifest.remove(item)
|
|
||||||
else:
|
else:
|
||||||
self.seen_filenames.add(fname)
|
self.seen_filenames.add(fname)
|
||||||
|
|
||||||
|
@ -248,6 +248,18 @@ def available_width():
|
|||||||
desktop = QCoreApplication.instance().desktop()
|
desktop = QCoreApplication.instance().desktop()
|
||||||
return desktop.availableGeometry().width()
|
return desktop.availableGeometry().width()
|
||||||
|
|
||||||
|
def get_windows_color_depth():
|
||||||
|
import win32gui, win32con, win32print
|
||||||
|
hwin = win32gui.GetDesktopWindow()
|
||||||
|
hwindc = win32gui.GetWindowDC(hwin)
|
||||||
|
ans = win32print.GetDeviceCaps(hwindc, win32con.BITSPIXEL)
|
||||||
|
win32gui.ReleaseDC(hwin, hwindc)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def get_screen_dpi():
|
||||||
|
d = QApplication.desktop()
|
||||||
|
return (d.logicalDpiX(), d.logicalDpiY())
|
||||||
|
|
||||||
_is_widescreen = None
|
_is_widescreen = None
|
||||||
|
|
||||||
def is_widescreen():
|
def is_widescreen():
|
||||||
@ -791,7 +803,18 @@ class Application(QApplication):
|
|||||||
font.setStretch(s)
|
font.setStretch(s)
|
||||||
QApplication.setFont(font)
|
QApplication.setFont(font)
|
||||||
|
|
||||||
if force_calibre_style or gprefs['ui_style'] != 'system':
|
depth_ok = True
|
||||||
|
if iswindows:
|
||||||
|
# There are some people that still run 16 bit winxp installs. The
|
||||||
|
# new style does not render well on 16bit machines.
|
||||||
|
try:
|
||||||
|
depth_ok = get_windows_color_depth() >= 32
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
if force_calibre_style or (depth_ok and gprefs['ui_style'] !=
|
||||||
|
'system'):
|
||||||
self.load_calibre_style()
|
self.load_calibre_style()
|
||||||
else:
|
else:
|
||||||
st = self.style()
|
st = self.style()
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="windowIcon" >
|
<property name="windowIcon" >
|
||||||
<iconset resource="../../../../resources/images.qrc" >
|
<iconset resource="../../../../resources/images.qrc" >
|
||||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" >
|
<layout class="QGridLayout" >
|
||||||
<item row="0" column="0" >
|
<item row="0" column="0" >
|
||||||
|
@ -5,7 +5,7 @@ from PyQt4.Qt import (Qt, QDialog, QTableWidgetItem, QIcon, QByteArray,
|
|||||||
QString, QSize)
|
QString, QSize)
|
||||||
|
|
||||||
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
|
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
|
||||||
from calibre.gui2 import question_dialog, error_dialog, gprefs
|
from calibre.gui2 import question_dialog, error_dialog, info_dialog, gprefs
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
|
|
||||||
class NameTableWidgetItem(QTableWidgetItem):
|
class NameTableWidgetItem(QTableWidgetItem):
|
||||||
@ -149,6 +149,9 @@ class TagListEditor(QDialog, Ui_TagListEditor):
|
|||||||
self.table.itemChanged.connect(self.finish_editing)
|
self.table.itemChanged.connect(self.finish_editing)
|
||||||
self.buttonBox.accepted.connect(self.accepted)
|
self.buttonBox.accepted.connect(self.accepted)
|
||||||
|
|
||||||
|
self.search_box.initialize('tag_list_search_box_' + cat_name)
|
||||||
|
self.search_button.clicked.connect(self.search_clicked)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
geom = gprefs.get('tag_list_editor_dialog_geometry', None)
|
geom = gprefs.get('tag_list_editor_dialog_geometry', None)
|
||||||
if geom is not None:
|
if geom is not None:
|
||||||
@ -158,6 +161,26 @@ class TagListEditor(QDialog, Ui_TagListEditor):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def search_clicked(self):
|
||||||
|
search_for = icu_lower(unicode(self.search_box.text()))
|
||||||
|
if not search_for:
|
||||||
|
error_dialog(self, _('Find'), _('You must enter some text to search for'),
|
||||||
|
show=True, show_copy_button=False)
|
||||||
|
return
|
||||||
|
row = self.table.currentRow()
|
||||||
|
if row < 0:
|
||||||
|
row = 0
|
||||||
|
rows = self.table.rowCount()
|
||||||
|
for i in range(0, rows):
|
||||||
|
row += 1
|
||||||
|
if row >= rows:
|
||||||
|
row = 0
|
||||||
|
item = self.table.item(row, 0)
|
||||||
|
if search_for in icu_lower(unicode(item.text())):
|
||||||
|
self.table.setCurrentItem(item)
|
||||||
|
return
|
||||||
|
info_dialog(self, _('Find'), _('No tag found'), show=True, show_copy_button=False)
|
||||||
|
|
||||||
def table_column_resized(self, col, old, new):
|
def table_column_resized(self, col, old, new):
|
||||||
self.table_column_widths = []
|
self.table_column_widths = []
|
||||||
for c in range(0, self.table.columnCount()):
|
for c in range(0, self.table.columnCount()):
|
||||||
|
@ -18,75 +18,88 @@
|
|||||||
<normaloff>:/images/chapters.png</normaloff>:/images/chapters.png</iconset>
|
<normaloff>:/images/chapters.png</normaloff>:/images/chapters.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout">
|
<layout class="QGridLayout">
|
||||||
<item row="0" column="0">
|
<item row="0" column="1">
|
||||||
<layout class="QVBoxLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout">
|
<widget class="HistoryLineEdit" name="search_box">
|
||||||
<item>
|
<property name="toolTip">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<string>Search for an item in the Tag column</string>
|
||||||
<item>
|
</property>
|
||||||
<widget class="QToolButton" name="delete_button">
|
</widget>
|
||||||
<property name="toolTip">
|
</item>
|
||||||
<string>Delete item from database. This will unapply the item from all books and then remove it from the database.</string>
|
<item>
|
||||||
</property>
|
<widget class="QToolButton" name="search_button">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>...</string>
|
<string>Find</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="toolTip">
|
||||||
<iconset>
|
<string>Copy the selected color name to the clipboard</string>
|
||||||
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
</property>
|
||||||
</property>
|
</widget>
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>32</width>
|
|
||||||
<height>32</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="rename_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Rename the item in every book where it is used.</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset>
|
|
||||||
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>32</width>
|
|
||||||
<height>32</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+S</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QTableWidget" name="table">
|
|
||||||
<property name="alternatingRowColors">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="selectionMode">
|
|
||||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
|
||||||
</property>
|
|
||||||
<property name="selectionBehavior">
|
|
||||||
<enum>QAbstractItemView::SelectRows</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0" colspan="2">
|
<item row="1" column="0">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="delete_button">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Delete item from database. This will unapply the item from all books and then remove it from the database.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="rename_button">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Rename the item in every book where it is used.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+S</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QTableWidget" name="table">
|
||||||
|
<property name="alternatingRowColors">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0" colspan="2">
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
@ -101,6 +114,13 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>HistoryLineEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections>
|
<connections>
|
||||||
<connection>
|
<connection>
|
||||||
|
@ -255,7 +255,7 @@ class MainWindowMixin(object): # {{{
|
|||||||
|
|
||||||
def __init__(self, db):
|
def __init__(self, db):
|
||||||
self.setObjectName('MainWindow')
|
self.setObjectName('MainWindow')
|
||||||
self.setWindowIcon(QIcon(I('library.png')))
|
self.setWindowIcon(QIcon(I('lt.png')))
|
||||||
self.setWindowTitle(__appname__)
|
self.setWindowTitle(__appname__)
|
||||||
|
|
||||||
self.setContextMenuPolicy(Qt.NoContextMenu)
|
self.setContextMenuPolicy(Qt.NoContextMenu)
|
||||||
|
@ -60,7 +60,7 @@ def init_qt(args):
|
|||||||
QCoreApplication.setApplicationName(APP_UID)
|
QCoreApplication.setApplicationName(APP_UID)
|
||||||
app = Application(args)
|
app = Application(args)
|
||||||
actions = tuple(Main.create_application_menubar())
|
actions = tuple(Main.create_application_menubar())
|
||||||
app.setWindowIcon(QIcon(I('library.png')))
|
app.setWindowIcon(QIcon(I('lt.png')))
|
||||||
return app, opts, args, actions
|
return app, opts, args, actions
|
||||||
|
|
||||||
|
|
||||||
@ -323,6 +323,10 @@ def communicate(opts, args):
|
|||||||
|
|
||||||
if opts.shutdown_running_calibre:
|
if opts.shutdown_running_calibre:
|
||||||
t.conn.send('shutdown:')
|
t.conn.send('shutdown:')
|
||||||
|
from calibre.utils.lock import singleinstance
|
||||||
|
prints(_('Shutdown command sent, waiting for shutdown...'))
|
||||||
|
while not singleinstance('calibre GUI'):
|
||||||
|
time.sleep(0.1)
|
||||||
else:
|
else:
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
args[1] = os.path.abspath(args[1])
|
args[1] = os.path.abspath(args[1])
|
||||||
|
@ -228,7 +228,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
self.default_thumbnail = None
|
self.default_thumbnail = None
|
||||||
self.tb_wrapper = textwrap.TextWrapper(width=40)
|
self.tb_wrapper = textwrap.TextWrapper(width=40)
|
||||||
self.viewers = collections.deque()
|
self.viewers = collections.deque()
|
||||||
self.system_tray_icon = SystemTrayIcon(QIcon(I('library.png')), self)
|
self.system_tray_icon = SystemTrayIcon(QIcon(I('lt.png')), self)
|
||||||
self.system_tray_icon.setToolTip('calibre')
|
self.system_tray_icon.setToolTip('calibre')
|
||||||
self.system_tray_icon.tooltip_requested.connect(
|
self.system_tray_icon.tooltip_requested.connect(
|
||||||
self.job_manager.show_tooltip)
|
self.job_manager.show_tooltip)
|
||||||
|
@ -202,19 +202,31 @@ class Document(QWebPage): # {{{
|
|||||||
if not isinstance(self.anchor_positions, dict):
|
if not isinstance(self.anchor_positions, dict):
|
||||||
# Some weird javascript error happened
|
# Some weird javascript error happened
|
||||||
self.anchor_positions = {}
|
self.anchor_positions = {}
|
||||||
return self.anchor_positions
|
return {k:tuple(v) for k, v in self.anchor_positions.iteritems()}
|
||||||
|
|
||||||
def switch_to_paged_mode(self, onresize=False):
|
def switch_to_paged_mode(self, onresize=False):
|
||||||
|
if onresize and not self.loaded_javascript:
|
||||||
|
return
|
||||||
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
|
||||||
# columns extend beyond the boundaries (and margin) of body
|
# columns extend beyond the boundaries (and margin) of body
|
||||||
mf = self.mainFrame()
|
mf = self.mainFrame()
|
||||||
sz = mf.contentsSize()
|
sz = mf.contentsSize()
|
||||||
if sz.width() > self.window_width:
|
scroll_width = self.javascript('document.body.scrollWidth', int)
|
||||||
sz.setWidth(sz.width()+side_margin)
|
# At this point sz.width() is not reliable, presumably because Qt
|
||||||
|
# has not yet been updated
|
||||||
|
if scroll_width > self.window_width:
|
||||||
|
sz.setWidth(scroll_width+side_margin)
|
||||||
self.setPreferredContentsSize(sz)
|
self.setPreferredContentsSize(sz)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def column_boundaries(self):
|
||||||
|
if not self.loaded_javascript:
|
||||||
|
return (0, 1)
|
||||||
|
self.javascript(u'py_bridge.value = paged_display.column_boundaries()')
|
||||||
|
return tuple(self.bridge_value)
|
||||||
|
|
||||||
def after_resize(self):
|
def after_resize(self):
|
||||||
if self.in_paged_mode:
|
if self.in_paged_mode:
|
||||||
self.setPreferredContentsSize(QSize())
|
self.setPreferredContentsSize(QSize())
|
||||||
@ -294,6 +306,7 @@ class Document(QWebPage): # {{{
|
|||||||
self.mainFrame().setScrollPosition(QPoint(x, y))
|
self.mainFrame().setScrollPosition(QPoint(x, y))
|
||||||
|
|
||||||
def jump_to_anchor(self, anchor):
|
def jump_to_anchor(self, anchor):
|
||||||
|
if not self.loaded_javascript: return
|
||||||
self.javascript('window.paged_display.jump_to_anchor("%s")'%anchor)
|
self.javascript('window.paged_display.jump_to_anchor("%s")'%anchor)
|
||||||
|
|
||||||
def element_ypos(self, elem):
|
def element_ypos(self, elem):
|
||||||
@ -352,7 +365,7 @@ class Document(QWebPage): # {{{
|
|||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
return 0.
|
return 0.
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
if self.in_paged_mode:
|
if self.in_paged_mode and self.loaded_javascript:
|
||||||
self.javascript('paged_display.scroll_to_pos(%f)'%val)
|
self.javascript('paged_display.scroll_to_pos(%f)'%val)
|
||||||
else:
|
else:
|
||||||
npos = val * (self.height - self.window_height)
|
npos = val * (self.height - self.window_height)
|
||||||
@ -555,6 +568,18 @@ class DocumentView(QWebView): # {{{
|
|||||||
return (self.document.ypos, self.document.ypos +
|
return (self.document.ypos, self.document.ypos +
|
||||||
self.document.window_height)
|
self.document.window_height)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def viewport_rect(self):
|
||||||
|
# (left, top, right, bottom) of the viewport in document co-ordinates
|
||||||
|
# When in paged mode, left and right are the numbers of the columns
|
||||||
|
# at the left edge and *after* the right edge of the viewport
|
||||||
|
d = self.document
|
||||||
|
if d.in_paged_mode:
|
||||||
|
l, r = d.column_boundaries
|
||||||
|
else:
|
||||||
|
l, r = d.xpos, d.xpos + d.window_width
|
||||||
|
return (l, d.ypos, r, d.ypos + d.window_height)
|
||||||
|
|
||||||
def link_hovered(self, link, text, context):
|
def link_hovered(self, link, text, context):
|
||||||
link, text = unicode(link), unicode(text)
|
link, text = unicode(link), unicode(text)
|
||||||
if link:
|
if link:
|
||||||
|
@ -138,7 +138,9 @@ class Reference(QLineEdit):
|
|||||||
self.editingFinished.connect(self.editing_finished)
|
self.editingFinished.connect(self.editing_finished)
|
||||||
|
|
||||||
def editing_finished(self):
|
def editing_finished(self):
|
||||||
self.goto.emit(unicode(self.text()))
|
text = unicode(self.text())
|
||||||
|
self.setText('')
|
||||||
|
self.goto.emit(text)
|
||||||
|
|
||||||
class RecentAction(QAction):
|
class RecentAction(QAction):
|
||||||
|
|
||||||
@ -681,7 +683,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
if hasattr(self, 'current_index'):
|
if hasattr(self, 'current_index'):
|
||||||
entry = self.toc_model.next_entry(self.current_index,
|
entry = self.toc_model.next_entry(self.current_index,
|
||||||
self.view.document.read_anchor_positions(),
|
self.view.document.read_anchor_positions(),
|
||||||
self.view.scroll_pos)
|
self.view.viewport_rect, self.view.document.in_paged_mode)
|
||||||
if entry is not None:
|
if entry is not None:
|
||||||
self.pending_goto_next_section = (
|
self.pending_goto_next_section = (
|
||||||
self.toc_model.currently_viewed_entry, entry, False)
|
self.toc_model.currently_viewed_entry, entry, False)
|
||||||
@ -691,7 +693,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
if hasattr(self, 'current_index'):
|
if hasattr(self, 'current_index'):
|
||||||
entry = self.toc_model.next_entry(self.current_index,
|
entry = self.toc_model.next_entry(self.current_index,
|
||||||
self.view.document.read_anchor_positions(),
|
self.view.document.read_anchor_positions(),
|
||||||
self.view.scroll_pos, backwards=True)
|
self.view.viewport_rect, self.view.document.in_paged_mode,
|
||||||
|
backwards=True)
|
||||||
if entry is not None:
|
if entry is not None:
|
||||||
self.pending_goto_next_section = (
|
self.pending_goto_next_section = (
|
||||||
self.toc_model.currently_viewed_entry, entry, True)
|
self.toc_model.currently_viewed_entry, entry, True)
|
||||||
@ -703,7 +706,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
if anchor_positions is None:
|
if anchor_positions is None:
|
||||||
anchor_positions = self.view.document.read_anchor_positions()
|
anchor_positions = self.view.document.read_anchor_positions()
|
||||||
items = self.toc_model.update_indexing_state(self.current_index,
|
items = self.toc_model.update_indexing_state(self.current_index,
|
||||||
self.view.scroll_pos, anchor_positions)
|
self.view.viewport_rect, anchor_positions,
|
||||||
|
self.view.document.in_paged_mode)
|
||||||
if items:
|
if items:
|
||||||
self.toc.scrollTo(items[-1].index())
|
self.toc.scrollTo(items[-1].index())
|
||||||
if pgns is not None:
|
if pgns is not None:
|
||||||
@ -712,7 +716,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
if pgns[0] is self.toc_model.currently_viewed_entry:
|
if pgns[0] is self.toc_model.currently_viewed_entry:
|
||||||
entry = self.toc_model.next_entry(self.current_index,
|
entry = self.toc_model.next_entry(self.current_index,
|
||||||
self.view.document.read_anchor_positions(),
|
self.view.document.read_anchor_positions(),
|
||||||
self.view.scroll_pos,
|
self.view.viewport_rect,
|
||||||
|
self.view.document.in_paged_mode,
|
||||||
backwards=pgns[2], current_entry=pgns[1])
|
backwards=pgns[2], current_entry=pgns[1])
|
||||||
if entry is not None:
|
if entry is not None:
|
||||||
self.pending_goto_next_section = (
|
self.pending_goto_next_section = (
|
||||||
|
@ -93,9 +93,19 @@ class TOCItem(QStandardItem):
|
|||||||
def type(cls):
|
def type(cls):
|
||||||
return QStandardItem.UserType+10
|
return QStandardItem.UserType+10
|
||||||
|
|
||||||
def update_indexing_state(self, spine_index, scroll_pos, anchor_map):
|
def update_indexing_state(self, spine_index, viewport_rect, anchor_map,
|
||||||
|
in_paged_mode):
|
||||||
|
if in_paged_mode:
|
||||||
|
self.update_indexing_state_paged(spine_index, viewport_rect,
|
||||||
|
anchor_map)
|
||||||
|
else:
|
||||||
|
self.update_indexing_state_unpaged(spine_index, viewport_rect,
|
||||||
|
anchor_map)
|
||||||
|
|
||||||
|
def update_indexing_state_unpaged(self, spine_index, viewport_rect,
|
||||||
|
anchor_map):
|
||||||
is_being_viewed = False
|
is_being_viewed = False
|
||||||
top, bottom = scroll_pos
|
top, bottom = viewport_rect[1], viewport_rect[3]
|
||||||
# We use bottom-25 in the checks below to account for the case where
|
# We use bottom-25 in the checks below to account for the case where
|
||||||
# the next entry has some invisible margin that just overlaps with the
|
# the next entry has some invisible margin that just overlaps with the
|
||||||
# bottom of the screen. In this case it will appear to the user that
|
# bottom of the screen. In this case it will appear to the user that
|
||||||
@ -103,6 +113,9 @@ class TOCItem(QStandardItem):
|
|||||||
# be larger than 25, but that's a decent compromise. Also we dont want
|
# be larger than 25, but that's a decent compromise. Also we dont want
|
||||||
# to count a partial line as being visible.
|
# to count a partial line as being visible.
|
||||||
|
|
||||||
|
# We only care about y position
|
||||||
|
anchor_map = {k:v[1] for k, v in anchor_map.iteritems()}
|
||||||
|
|
||||||
if spine_index >= self.starts_at and spine_index <= self.ends_at:
|
if spine_index >= self.starts_at and spine_index <= self.ends_at:
|
||||||
# The position at which this anchor is present in the document
|
# The position at which this anchor is present in the document
|
||||||
start_pos = anchor_map.get(self.start_anchor, 0)
|
start_pos = anchor_map.get(self.start_anchor, 0)
|
||||||
@ -115,7 +128,7 @@ class TOCItem(QStandardItem):
|
|||||||
# ancestors of this entry.
|
# ancestors of this entry.
|
||||||
psp = [anchor_map.get(x, 0) for x in self.possible_end_anchors]
|
psp = [anchor_map.get(x, 0) for x in self.possible_end_anchors]
|
||||||
psp = [x for x in psp if x >= start_pos]
|
psp = [x for x in psp if x >= start_pos]
|
||||||
# The end position. The first anchor whose pos is >= self.start_pos
|
# The end position. The first anchor whose pos is >= start_pos
|
||||||
# or if the end is not in this spine item, we set it to the bottom
|
# or if the end is not in this spine item, we set it to the bottom
|
||||||
# of the window +1
|
# of the window +1
|
||||||
end_pos = min(psp) if psp else (bottom+1 if self.ends_at >=
|
end_pos = min(psp) if psp else (bottom+1 if self.ends_at >=
|
||||||
@ -141,6 +154,51 @@ class TOCItem(QStandardItem):
|
|||||||
if changed:
|
if changed:
|
||||||
self.setFont(self.bold_font if is_being_viewed else self.normal_font)
|
self.setFont(self.bold_font if is_being_viewed else self.normal_font)
|
||||||
|
|
||||||
|
def update_indexing_state_paged(self, spine_index, viewport_rect,
|
||||||
|
anchor_map):
|
||||||
|
is_being_viewed = False
|
||||||
|
|
||||||
|
left, right = viewport_rect[0], viewport_rect[2]
|
||||||
|
left, right = (left, 0), (right, -1)
|
||||||
|
|
||||||
|
if spine_index >= self.starts_at and spine_index <= self.ends_at:
|
||||||
|
# The position at which this anchor is present in the document
|
||||||
|
start_pos = anchor_map.get(self.start_anchor, (0, 0))
|
||||||
|
psp = []
|
||||||
|
if self.ends_at == spine_index:
|
||||||
|
# Anchors that could possibly indicate the start of the next
|
||||||
|
# section and therefore the end of this section.
|
||||||
|
# self.possible_end_anchors is a set of anchors belonging to
|
||||||
|
# toc entries with depth <= self.depth that are also not
|
||||||
|
# ancestors of this entry.
|
||||||
|
psp = [anchor_map.get(x, (0, 0)) for x in self.possible_end_anchors]
|
||||||
|
psp = [x for x in psp if x >= start_pos]
|
||||||
|
# The end position. The first anchor whose pos is >= start_pos
|
||||||
|
# or if the end is not in this spine item, we set it to the column
|
||||||
|
# after the right edge of the viewport
|
||||||
|
end_pos = min(psp) if psp else (right if self.ends_at >=
|
||||||
|
spine_index else (0, 0))
|
||||||
|
if spine_index > self.starts_at and spine_index < self.ends_at:
|
||||||
|
# The entire spine item is contained in this entry
|
||||||
|
is_being_viewed = True
|
||||||
|
elif (spine_index == self.starts_at and right > start_pos and
|
||||||
|
# This spine item contains the start
|
||||||
|
# The start position is before the end of the viewport
|
||||||
|
(spine_index != self.ends_at or left < end_pos)):
|
||||||
|
# The end position is after the start of the viewport
|
||||||
|
is_being_viewed = True
|
||||||
|
elif (spine_index == self.ends_at and left < end_pos and
|
||||||
|
# This spine item contains the end
|
||||||
|
# The end position is after the start of the viewport
|
||||||
|
(spine_index != self.starts_at or right > start_pos)):
|
||||||
|
# The start position is before the end of the viewport
|
||||||
|
is_being_viewed = True
|
||||||
|
|
||||||
|
changed = is_being_viewed != self.is_being_viewed
|
||||||
|
self.is_being_viewed = is_being_viewed
|
||||||
|
if changed:
|
||||||
|
self.setFont(self.bold_font if is_being_viewed else self.normal_font)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'TOC Item: %s %s#%s'%(self.title, self.abspath, self.fragment)
|
return 'TOC Item: %s %s#%s'%(self.title, self.abspath, self.fragment)
|
||||||
|
|
||||||
@ -183,20 +241,26 @@ class TOC(QStandardItemModel):
|
|||||||
self.currently_viewed_entry = t
|
self.currently_viewed_entry = t
|
||||||
return items_being_viewed
|
return items_being_viewed
|
||||||
|
|
||||||
def next_entry(self, spine_pos, anchor_map, scroll_pos, backwards=False,
|
def next_entry(self, spine_pos, anchor_map, viewport_rect, in_paged_mode,
|
||||||
current_entry=None):
|
backwards=False, current_entry=None):
|
||||||
current_entry = (self.currently_viewed_entry if current_entry is None
|
current_entry = (self.currently_viewed_entry if current_entry is None
|
||||||
else current_entry)
|
else current_entry)
|
||||||
if current_entry is None: return
|
if current_entry is None: return
|
||||||
items = reversed(self.all_items) if backwards else self.all_items
|
items = reversed(self.all_items) if backwards else self.all_items
|
||||||
found = False
|
found = False
|
||||||
top = scroll_pos[0]
|
|
||||||
|
if in_paged_mode:
|
||||||
|
start = viewport_rect[0]
|
||||||
|
anchor_map = {k:v[0] for k, v in anchor_map.iteritems()}
|
||||||
|
else:
|
||||||
|
start = viewport_rect[1]
|
||||||
|
anchor_map = {k:v[1] for k, v in anchor_map.iteritems()}
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
if found:
|
if found:
|
||||||
start_pos = anchor_map.get(item.start_anchor, 0)
|
start_pos = anchor_map.get(item.start_anchor, 0)
|
||||||
if backwards and item.is_being_viewed and start_pos >= top:
|
if backwards and item.is_being_viewed and start_pos >= start:
|
||||||
# Going to this item will either not move the scroll
|
# This item will not cause any scrolling
|
||||||
# position or cause to to *increase* instead of descresing
|
|
||||||
continue
|
continue
|
||||||
if item.starts_at != spine_pos or item.start_anchor:
|
if item.starts_at != spine_pos or item.start_anchor:
|
||||||
return item
|
return item
|
||||||
|
@ -6,14 +6,12 @@ Miscellaneous widgets used in the GUI
|
|||||||
import re, traceback, os
|
import re, traceback, os
|
||||||
|
|
||||||
from PyQt4.Qt import (QIcon, QFont, QLabel, QListWidget, QAction,
|
from PyQt4.Qt import (QIcon, QFont, QLabel, QListWidget, QAction,
|
||||||
QListWidgetItem, QTextCharFormat, QApplication,
|
QListWidgetItem, QTextCharFormat, QApplication, QSyntaxHighlighter,
|
||||||
QSyntaxHighlighter, QCursor, QColor, QWidget,
|
QCursor, QColor, QWidget, QPixmap, QSplitterHandle, QToolButton,
|
||||||
QPixmap, QSplitterHandle, QToolButton,
|
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, QRegExp, QSize,
|
||||||
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal,
|
QSplitter, QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene, QMenu,
|
||||||
QRegExp, QSettings, QSize, QSplitter,
|
QStringListModel, QCompleter, QStringList, QTimer, QRect,
|
||||||
QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene,
|
QFontDatabase, QGraphicsView, QByteArray)
|
||||||
QMenu, QStringListModel, QCompleter, QStringList,
|
|
||||||
QTimer, QRect, QFontDatabase, QGraphicsView)
|
|
||||||
|
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
from calibre.gui2 import (NONE, error_dialog, pixmap_to_data, gprefs,
|
from calibre.gui2 import (NONE, error_dialog, pixmap_to_data, gprefs,
|
||||||
@ -803,69 +801,29 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
|
|||||||
@classmethod
|
@classmethod
|
||||||
def loadConfig(cls):
|
def loadConfig(cls):
|
||||||
Config = cls.Config
|
Config = cls.Config
|
||||||
settings = QSettings()
|
|
||||||
def setDefaultString(name, default):
|
|
||||||
value = settings.value(name).toString()
|
|
||||||
if value.isEmpty():
|
|
||||||
value = default
|
|
||||||
Config[name] = value
|
|
||||||
|
|
||||||
for name in ("window", "shell"):
|
for name in ("window", "shell"):
|
||||||
Config["%swidth" % name] = settings.value("%swidth" % name,
|
Config["%swidth" % name] = QVariant(QApplication.desktop().availableGeometry().width() / 2).toInt()[0]
|
||||||
QVariant(QApplication.desktop() \
|
Config["%sheight" % name] = QVariant(QApplication.desktop().availableGeometry().height() / 2).toInt()[0]
|
||||||
.availableGeometry().width() / 2)).toInt()[0]
|
Config["%sy" % name] = QVariant(0).toInt()[0]
|
||||||
Config["%sheight" % name] = settings.value("%sheight" % name,
|
Config["toolbars"] = QByteArray(b'')
|
||||||
QVariant(QApplication.desktop() \
|
Config["splitter"] = QByteArray(b'')
|
||||||
.availableGeometry().height() / 2)).toInt()[0]
|
Config["shellx"] = QVariant(0).toInt()[0]
|
||||||
Config["%sy" % name] = settings.value("%sy" % name,
|
Config["windowx"] = QVariant(QApplication.desktop().availableGeometry().width() / 2).toInt()[0]
|
||||||
QVariant(0)).toInt()[0]
|
Config["remembergeometry"] = QVariant(True).toBool()
|
||||||
Config["toolbars"] = settings.value("toolbars").toByteArray()
|
Config["startwithshell"] = QVariant(True).toBool()
|
||||||
Config["splitter"] = settings.value("splitter").toByteArray()
|
Config["showwindowinfo"] = QVariant(True).toBool()
|
||||||
Config["shellx"] = settings.value("shellx", QVariant(0)).toInt()[0]
|
Config["backupsuffix"] = QVariant(".bak").toString()
|
||||||
Config["windowx"] = settings.value("windowx", QVariant(QApplication \
|
Config["cwd"] = QVariant(".").toString()
|
||||||
.desktop().availableGeometry().width() / 2)).toInt()[0]
|
Config["tooltipsize"] = QVariant(150).toInt()[0]
|
||||||
Config["remembergeometry"] = settings.value("remembergeometry",
|
Config["maxlinestoscan"] = QVariant(5000).toInt()[0]
|
||||||
QVariant(True)).toBool()
|
Config["pythondocpath"] = QVariant("http://docs.python.org").toString()
|
||||||
Config["startwithshell"] = settings.value("startwithshell",
|
Config["autohidefinddialog"] = QVariant(True).toBool()
|
||||||
QVariant(True)).toBool()
|
Config["findcasesensitive"] = QVariant(False).toBool()
|
||||||
Config["showwindowinfo"] = settings.value("showwindowinfo",
|
Config["findwholewords"] = QVariant(False).toBool()
|
||||||
QVariant(True)).toBool()
|
Config["tabwidth"] = QVariant(4).toInt()[0]
|
||||||
setDefaultString("shellstartup", """\
|
Config["fontfamily"] = QVariant("monospace").toString()
|
||||||
from __future__ import division
|
Config["fontsize"] = QVariant(10).toInt()[0]
|
||||||
import codecs
|
|
||||||
import sys
|
|
||||||
sys.stdin = codecs.getreader("UTF8")(sys.stdin)
|
|
||||||
sys.stdout = codecs.getwriter("UTF8")(sys.stdout)""")
|
|
||||||
setDefaultString("newfile", """\
|
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
|
|
||||||
import sys
|
|
||||||
""")
|
|
||||||
Config["backupsuffix"] = settings.value("backupsuffix",
|
|
||||||
QVariant(".bak")).toString()
|
|
||||||
setDefaultString("beforeinput", "#>>>")
|
|
||||||
setDefaultString("beforeoutput", "#---")
|
|
||||||
Config["cwd"] = settings.value("cwd", QVariant(".")).toString()
|
|
||||||
Config["tooltipsize"] = settings.value("tooltipsize",
|
|
||||||
QVariant(150)).toInt()[0]
|
|
||||||
Config["maxlinestoscan"] = settings.value("maxlinestoscan",
|
|
||||||
QVariant(5000)).toInt()[0]
|
|
||||||
Config["pythondocpath"] = settings.value("pythondocpath",
|
|
||||||
QVariant("http://docs.python.org")).toString()
|
|
||||||
Config["autohidefinddialog"] = settings.value("autohidefinddialog",
|
|
||||||
QVariant(True)).toBool()
|
|
||||||
Config["findcasesensitive"] = settings.value("findcasesensitive",
|
|
||||||
QVariant(False)).toBool()
|
|
||||||
Config["findwholewords"] = settings.value("findwholewords",
|
|
||||||
QVariant(False)).toBool()
|
|
||||||
Config["tabwidth"] = settings.value("tabwidth",
|
|
||||||
QVariant(4)).toInt()[0]
|
|
||||||
Config["fontfamily"] = settings.value("fontfamily",
|
|
||||||
QVariant("monospace")).toString()
|
|
||||||
Config["fontsize"] = settings.value("fontsize",
|
|
||||||
QVariant(10)).toInt()[0]
|
|
||||||
for name, color, bold, italic in (
|
for name, color, bold, italic in (
|
||||||
("normal", "#000000", False, False),
|
("normal", "#000000", False, False),
|
||||||
("keyword", "#000080", True, False),
|
("keyword", "#000080", True, False),
|
||||||
@ -877,12 +835,9 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
|
|||||||
("number", "#924900", False, False),
|
("number", "#924900", False, False),
|
||||||
("error", "#FF0000", False, False),
|
("error", "#FF0000", False, False),
|
||||||
("pyqt", "#50621A", False, False)):
|
("pyqt", "#50621A", False, False)):
|
||||||
Config["%sfontcolor" % name] = settings.value(
|
Config["%sfontcolor" % name] = QVariant(color).toString()
|
||||||
"%sfontcolor" % name, QVariant(color)).toString()
|
Config["%sfontbold" % name] = QVariant(bold).toBool()
|
||||||
Config["%sfontbold" % name] = settings.value(
|
Config["%sfontitalic" % name] = QVariant(italic).toBool()
|
||||||
"%sfontbold" % name, QVariant(bold)).toBool()
|
|
||||||
Config["%sfontitalic" % name] = settings.value(
|
|
||||||
"%sfontitalic" % name, QVariant(italic)).toBool()
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -6,13 +6,22 @@ __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
|
import os, errno
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from calibre.constants import iswindows, get_windows_username
|
from calibre.constants import iswindows, get_windows_username
|
||||||
|
|
||||||
ADDRESS = None
|
ADDRESS = None
|
||||||
|
|
||||||
|
def eintr_retry_call(func, *args, **kwargs):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except EnvironmentError as e:
|
||||||
|
if getattr(e, 'errno', None) == errno.EINTR:
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
|
||||||
def gui_socket_address():
|
def gui_socket_address():
|
||||||
global ADDRESS
|
global ADDRESS
|
||||||
if ADDRESS is None:
|
if ADDRESS is None:
|
||||||
|
@ -15,6 +15,7 @@ from functools import partial
|
|||||||
|
|
||||||
from calibre import as_unicode, prints
|
from calibre import as_unicode, prints
|
||||||
from calibre.constants import iswindows, DEBUG
|
from calibre.constants import iswindows, DEBUG
|
||||||
|
from calibre.utils.ipc import eintr_retry_call
|
||||||
|
|
||||||
def _encode(msg):
|
def _encode(msg):
|
||||||
raw = cPickle.dumps(msg, -1)
|
raw = cPickle.dumps(msg, -1)
|
||||||
@ -68,7 +69,7 @@ class Writer(Thread):
|
|||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
self.data_written = True
|
self.data_written = True
|
||||||
self.conn.send_bytes(x)
|
eintr_retry_call(self.conn.send_bytes, x)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.resultq.put(as_unicode(e))
|
self.resultq.put(as_unicode(e))
|
||||||
else:
|
else:
|
||||||
@ -112,7 +113,7 @@ class Server(Thread):
|
|||||||
def run(self):
|
def run(self):
|
||||||
while self.keep_going:
|
while self.keep_going:
|
||||||
try:
|
try:
|
||||||
conn = self.listener.accept()
|
conn = eintr_retry_call(self.listener.accept)
|
||||||
self.handle_client(conn)
|
self.handle_client(conn)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@ -125,7 +126,7 @@ class Server(Thread):
|
|||||||
def _handle_client(self, conn):
|
def _handle_client(self, conn):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
func_name, args, kwargs = conn.recv()
|
func_name, args, kwargs = eintr_retry_call(conn.recv)
|
||||||
except EOFError:
|
except EOFError:
|
||||||
try:
|
try:
|
||||||
conn.close()
|
conn.close()
|
||||||
@ -156,8 +157,8 @@ class Server(Thread):
|
|||||||
import traceback
|
import traceback
|
||||||
# Try to tell the client process what error happened
|
# Try to tell the client process what error happened
|
||||||
try:
|
try:
|
||||||
conn.send_bytes(_encode(('failed', (unicode(e),
|
eintr_retry_call(conn.send_bytes, (_encode(('failed', (unicode(e),
|
||||||
as_unicode(traceback.format_exc())))))
|
as_unicode(traceback.format_exc()))))))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
raise
|
raise
|
||||||
|
@ -14,6 +14,7 @@ from multiprocessing.connection import Listener, arbitrary_address
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
|
|
||||||
|
from calibre.utils.ipc import eintr_retry_call
|
||||||
from calibre.utils.ipc.launch import Worker
|
from calibre.utils.ipc.launch import Worker
|
||||||
from calibre.utils.ipc.worker import PARALLEL_FUNCS
|
from calibre.utils.ipc.worker import PARALLEL_FUNCS
|
||||||
from calibre import detect_ncpus as cpu_count
|
from calibre import detect_ncpus as cpu_count
|
||||||
@ -38,7 +39,7 @@ class ConnectedWorker(Thread):
|
|||||||
|
|
||||||
def start_job(self, job):
|
def start_job(self, job):
|
||||||
notification = PARALLEL_FUNCS[job.name][-1] is not None
|
notification = PARALLEL_FUNCS[job.name][-1] is not None
|
||||||
self.conn.send((job.name, job.args, job.kwargs, job.description))
|
eintr_retry_call(self.conn.send, (job.name, job.args, job.kwargs, job.description))
|
||||||
if notification:
|
if notification:
|
||||||
self.start()
|
self.start()
|
||||||
else:
|
else:
|
||||||
@ -48,7 +49,7 @@ class ConnectedWorker(Thread):
|
|||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
x = self.conn.recv()
|
x = eintr_retry_call(self.conn.recv)
|
||||||
self.notifications.put(x)
|
self.notifications.put(x)
|
||||||
except BaseException:
|
except BaseException:
|
||||||
break
|
break
|
||||||
@ -129,12 +130,7 @@ class Server(Thread):
|
|||||||
'CALIBRE_WORKER_KEY' : hexlify(self.auth_key),
|
'CALIBRE_WORKER_KEY' : hexlify(self.auth_key),
|
||||||
'CALIBRE_WORKER_RESULT' : hexlify(rfile.encode('utf-8')),
|
'CALIBRE_WORKER_RESULT' : hexlify(rfile.encode('utf-8')),
|
||||||
}
|
}
|
||||||
for i in range(2):
|
cw = self.do_launch(env, gui, redirect_output, rfile)
|
||||||
# Try launch twice as occasionally on OS X
|
|
||||||
# Listener.accept fails with EINTR
|
|
||||||
cw = self.do_launch(env, gui, redirect_output, rfile)
|
|
||||||
if isinstance(cw, ConnectedWorker):
|
|
||||||
break
|
|
||||||
if isinstance(cw, basestring):
|
if isinstance(cw, basestring):
|
||||||
raise CriticalError('Failed to launch worker process:\n'+cw)
|
raise CriticalError('Failed to launch worker process:\n'+cw)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -146,7 +142,7 @@ class Server(Thread):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
w(redirect_output=redirect_output)
|
w(redirect_output=redirect_output)
|
||||||
conn = self.listener.accept()
|
conn = eintr_retry_call(self.listener.accept)
|
||||||
if conn is None:
|
if conn is None:
|
||||||
raise Exception('Failed to launch worker process')
|
raise Exception('Failed to launch worker process')
|
||||||
except BaseException:
|
except BaseException:
|
||||||
|
@ -14,6 +14,7 @@ from threading import Thread
|
|||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
|
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
|
from calibre.utils.ipc import eintr_retry_call
|
||||||
from calibre.utils.ipc.launch import Worker
|
from calibre.utils.ipc.launch import Worker
|
||||||
|
|
||||||
class WorkerError(Exception):
|
class WorkerError(Exception):
|
||||||
@ -35,30 +36,18 @@ class ConnectedWorker(Thread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
conn = tb = None
|
conn = tb = None
|
||||||
for i in range(2):
|
try:
|
||||||
# On OS X an EINTR can interrupt the accept() call
|
conn = eintr_retry_call(self.listener.accept)
|
||||||
try:
|
except:
|
||||||
conn = self.listener.accept()
|
tb = traceback.format_exc()
|
||||||
break
|
|
||||||
except:
|
|
||||||
tb = traceback.format_exc()
|
|
||||||
pass
|
|
||||||
if conn is None:
|
if conn is None:
|
||||||
self.tb = tb
|
self.tb = tb
|
||||||
return
|
return
|
||||||
self.accepted = True
|
self.accepted = True
|
||||||
with closing(conn):
|
with closing(conn):
|
||||||
try:
|
try:
|
||||||
try:
|
eintr_retry_call(conn.send, self.args)
|
||||||
conn.send(self.args)
|
self.res = eintr_retry_call(conn.recv)
|
||||||
except:
|
|
||||||
# Maybe an EINTR
|
|
||||||
conn.send(self.args)
|
|
||||||
try:
|
|
||||||
self.res = conn.recv()
|
|
||||||
except:
|
|
||||||
# Maybe an EINTR
|
|
||||||
self.res = conn.recv()
|
|
||||||
except:
|
except:
|
||||||
self.tb = traceback.format_exc()
|
self.tb = traceback.format_exc()
|
||||||
|
|
||||||
@ -202,11 +191,7 @@ def main():
|
|||||||
address = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS']))
|
address = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS']))
|
||||||
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
|
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
|
||||||
with closing(Client(address, authkey=key)) as conn:
|
with closing(Client(address, authkey=key)) as conn:
|
||||||
try:
|
args = eintr_retry_call(conn.recv)
|
||||||
args = conn.recv()
|
|
||||||
except:
|
|
||||||
# Maybe EINTR
|
|
||||||
args = conn.recv()
|
|
||||||
try:
|
try:
|
||||||
mod, func, args, kwargs, module_is_source_code = args
|
mod, func, args, kwargs, module_is_source_code = args
|
||||||
if module_is_source_code:
|
if module_is_source_code:
|
||||||
|
@ -16,6 +16,7 @@ from zipimport import ZipImportError
|
|||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.constants import iswindows, isosx
|
from calibre.constants import iswindows, isosx
|
||||||
|
from calibre.utils.ipc import eintr_retry_call
|
||||||
|
|
||||||
PARALLEL_FUNCS = {
|
PARALLEL_FUNCS = {
|
||||||
'lrfviewer' :
|
'lrfviewer' :
|
||||||
@ -75,7 +76,7 @@ class Progress(Thread):
|
|||||||
if x is None:
|
if x is None:
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
self.conn.send(x)
|
eintr_retry_call(self.conn.send, x)
|
||||||
except:
|
except:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -178,7 +179,7 @@ def main():
|
|||||||
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
|
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
|
||||||
resultf = unhexlify(os.environ['CALIBRE_WORKER_RESULT']).decode('utf-8')
|
resultf = unhexlify(os.environ['CALIBRE_WORKER_RESULT']).decode('utf-8')
|
||||||
with closing(Client(address, authkey=key)) as conn:
|
with closing(Client(address, authkey=key)) as conn:
|
||||||
name, args, kwargs, desc = conn.recv()
|
name, args, kwargs, desc = eintr_retry_call(conn.recv)
|
||||||
if desc:
|
if desc:
|
||||||
prints(desc)
|
prints(desc)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
@ -245,12 +245,18 @@ class TextLine(object):
|
|||||||
|
|
||||||
|
|
||||||
def create_cover_page(top_lines, logo_path, width=590, height=750,
|
def create_cover_page(top_lines, logo_path, width=590, height=750,
|
||||||
bgcolor='#ffffff', output_format='jpg'):
|
bgcolor='#ffffff', output_format='jpg', texture_data=None,
|
||||||
|
texture_opacity=1.0):
|
||||||
'''
|
'''
|
||||||
Create the standard calibre cover page and return it as a byte string in
|
Create the standard calibre cover page and return it as a byte string in
|
||||||
the specified output_format.
|
the specified output_format.
|
||||||
'''
|
'''
|
||||||
canvas = create_canvas(width, height, bgcolor)
|
canvas = create_canvas(width, height, bgcolor)
|
||||||
|
if texture_data and hasattr(canvas, 'texture'):
|
||||||
|
texture = Image()
|
||||||
|
texture.load(texture_data)
|
||||||
|
texture.set_opacity(texture_opacity)
|
||||||
|
canvas.texture(texture)
|
||||||
|
|
||||||
bottom = 10
|
bottom = 10
|
||||||
for line in top_lines:
|
for line in top_lines:
|
||||||
@ -263,7 +269,7 @@ def create_cover_page(top_lines, logo_path, width=590, height=750,
|
|||||||
if not foot_font:
|
if not foot_font:
|
||||||
foot_font = P('fonts/liberation/LiberationMono-Regular.ttf')
|
foot_font = P('fonts/liberation/LiberationMono-Regular.ttf')
|
||||||
vanity = create_text_arc(__appname__ + ' ' + __version__, 24,
|
vanity = create_text_arc(__appname__ + ' ' + __version__, 24,
|
||||||
font=foot_font)
|
font=foot_font, bgcolor='#00000000')
|
||||||
lwidth, lheight = vanity.size
|
lwidth, lheight = vanity.size
|
||||||
left = int(max(0, (width - lwidth)/2.))
|
left = int(max(0, (width - lwidth)/2.))
|
||||||
top = height - lheight - 10
|
top = height - lheight - 10
|
||||||
@ -279,6 +285,9 @@ def create_cover_page(top_lines, logo_path, width=590, height=750,
|
|||||||
logo.size = (lwidth, lheight)
|
logo.size = (lwidth, lheight)
|
||||||
left = int(max(0, (width - lwidth)/2.))
|
left = int(max(0, (width - lwidth)/2.))
|
||||||
top = bottom+10
|
top = bottom+10
|
||||||
|
extra = int((available[1] - lheight)/2.0)
|
||||||
|
if extra > 0:
|
||||||
|
top += extra
|
||||||
canvas.compose(logo, left, top)
|
canvas.compose(logo, left, top)
|
||||||
|
|
||||||
return canvas.export(output_format)
|
return canvas.export(output_format)
|
||||||
|
@ -495,6 +495,7 @@ typedef struct {
|
|||||||
// Method declarations {{{
|
// Method declarations {{{
|
||||||
static PyObject* magick_Image_compose(magick_Image *self, PyObject *args, PyObject *kwargs);
|
static PyObject* magick_Image_compose(magick_Image *self, PyObject *args, PyObject *kwargs);
|
||||||
static PyObject* magick_Image_copy(magick_Image *self, PyObject *args, PyObject *kwargs);
|
static PyObject* magick_Image_copy(magick_Image *self, PyObject *args, PyObject *kwargs);
|
||||||
|
static PyObject* magick_Image_texture(magick_Image *self, PyObject *args, PyObject *kwargs);
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -926,7 +927,6 @@ magick_Image_flip(magick_Image *self, PyObject *args, PyObject *kwargs) {
|
|||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
|
||||||
// Image.set_page {{{
|
// Image.set_page {{{
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
@ -1114,6 +1114,22 @@ magick_Image_destroy(magick_Image *self, PyObject *args, PyObject *kwargs) {
|
|||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
// Image.set_opacity {{{
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
magick_Image_set_opacity(magick_Image *self, PyObject *args, PyObject *kwargs) {
|
||||||
|
double opacity;
|
||||||
|
NULL_CHECK(NULL)
|
||||||
|
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "d", &opacity)) return NULL;
|
||||||
|
|
||||||
|
if (!MagickSetImageOpacity(self->wand, opacity)) return magick_set_exception(self->wand);
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
// Image attr list {{{
|
// Image attr list {{{
|
||||||
static PyMethodDef magick_Image_methods[] = {
|
static PyMethodDef magick_Image_methods[] = {
|
||||||
{"destroy", (PyCFunction)magick_Image_destroy, METH_VARARGS,
|
{"destroy", (PyCFunction)magick_Image_destroy, METH_VARARGS,
|
||||||
@ -1145,6 +1161,14 @@ static PyMethodDef magick_Image_methods[] = {
|
|||||||
"compose(img, left, top, op) \n\n Compose img using operation op at (left, top)"
|
"compose(img, left, top, op) \n\n Compose img using operation op at (left, top)"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{"texture", (PyCFunction)magick_Image_texture, METH_VARARGS,
|
||||||
|
"texture(img)) \n\n Repeatedly tile img across and down the canvas."
|
||||||
|
},
|
||||||
|
|
||||||
|
{"set_opacity", (PyCFunction)magick_Image_set_opacity, METH_VARARGS,
|
||||||
|
"set_opacity(opacity)) \n\n Set the opacity of this image (between 0.0 - transparent and 1.0 - opaque)"
|
||||||
|
},
|
||||||
|
|
||||||
{"copy", (PyCFunction)magick_Image_copy, METH_VARARGS,
|
{"copy", (PyCFunction)magick_Image_copy, METH_VARARGS,
|
||||||
"copy(img) \n\n Copy img to self."
|
"copy(img) \n\n Copy img to self."
|
||||||
},
|
},
|
||||||
@ -1335,6 +1359,23 @@ magick_Image_copy(magick_Image *self, PyObject *args, PyObject *kwargs)
|
|||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
// Image.texture {{{
|
||||||
|
static PyObject *
|
||||||
|
magick_Image_texture(magick_Image *self, PyObject *args, PyObject *kwargs) {
|
||||||
|
PyObject *img;
|
||||||
|
magick_Image *texture;
|
||||||
|
|
||||||
|
NULL_CHECK(NULL)
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "O!", &magick_ImageType, &img)) return NULL;
|
||||||
|
texture = (magick_Image*)img;
|
||||||
|
if (!IsMagickWand(texture->wand)) {PyErr_SetString(PyExc_TypeError, "Not a valid ImageMagick wand"); return NULL;}
|
||||||
|
|
||||||
|
self->wand = MagickTextureImage(self->wand, texture->wand);
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
// Module functions {{{
|
// Module functions {{{
|
||||||
|
@ -77,7 +77,8 @@ class BasicNewsRecipe(Recipe):
|
|||||||
delay = 0
|
delay = 0
|
||||||
|
|
||||||
#: Publication type
|
#: Publication type
|
||||||
#: Set to newspaper, magazine or blog
|
#: Set to newspaper, magazine or blog. If set to None, no publication type
|
||||||
|
#: metadata will be written to the opf file.
|
||||||
publication_type = 'unknown'
|
publication_type = 'unknown'
|
||||||
|
|
||||||
#: Number of simultaneous downloads. Set to 1 if the server is picky.
|
#: Number of simultaneous downloads. Set to 1 if the server is picky.
|
||||||
@ -1264,7 +1265,8 @@ class BasicNewsRecipe(Recipe):
|
|||||||
mi = MetaInformation(title, [__appname__])
|
mi = MetaInformation(title, [__appname__])
|
||||||
mi.publisher = __appname__
|
mi.publisher = __appname__
|
||||||
mi.author_sort = __appname__
|
mi.author_sort = __appname__
|
||||||
mi.publication_type = 'periodical:'+self.publication_type+':'+self.short_title()
|
if self.publication_type:
|
||||||
|
mi.publication_type = 'periodical:'+self.publication_type+':'+self.short_title()
|
||||||
mi.timestamp = nowf()
|
mi.timestamp = nowf()
|
||||||
article_titles, aseen = [], set()
|
article_titles, aseen = [], set()
|
||||||
for f in feeds:
|
for f in feeds:
|
||||||
|
@ -12,6 +12,7 @@ from urllib import url2pathname, quote
|
|||||||
from httplib import responses
|
from httplib import responses
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
|
from base64 import b64decode
|
||||||
|
|
||||||
from calibre import browser, relpath, unicode_path
|
from calibre import browser, relpath, unicode_path
|
||||||
from calibre.constants import filesystem_encoding, iswindows
|
from calibre.constants import filesystem_encoding, iswindows
|
||||||
@ -346,22 +347,29 @@ class RecursiveFetcher(object):
|
|||||||
c = 0
|
c = 0
|
||||||
for tag in soup.findAll(lambda tag: tag.name.lower()=='img' and tag.has_key('src')):
|
for tag in soup.findAll(lambda tag: tag.name.lower()=='img' and tag.has_key('src')):
|
||||||
iurl = tag['src']
|
iurl = tag['src']
|
||||||
if callable(self.image_url_processor):
|
if iurl.startswith('data:image/'):
|
||||||
iurl = self.image_url_processor(baseurl, iurl)
|
try:
|
||||||
if not urlparse.urlsplit(iurl).scheme:
|
data = b64decode(iurl.partition(',')[-1])
|
||||||
iurl = urlparse.urljoin(baseurl, iurl, False)
|
except:
|
||||||
with self.imagemap_lock:
|
self.log.exception('Failed to decode embedded image')
|
||||||
if self.imagemap.has_key(iurl):
|
|
||||||
tag['src'] = self.imagemap[iurl]
|
|
||||||
continue
|
continue
|
||||||
try:
|
else:
|
||||||
data = self.fetch_url(iurl)
|
if callable(self.image_url_processor):
|
||||||
if data == 'GIF89a\x01':
|
iurl = self.image_url_processor(baseurl, iurl)
|
||||||
# Skip empty GIF files as PIL errors on them anyway
|
if not urlparse.urlsplit(iurl).scheme:
|
||||||
|
iurl = urlparse.urljoin(baseurl, iurl, False)
|
||||||
|
with self.imagemap_lock:
|
||||||
|
if self.imagemap.has_key(iurl):
|
||||||
|
tag['src'] = self.imagemap[iurl]
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
data = self.fetch_url(iurl)
|
||||||
|
if data == 'GIF89a\x01':
|
||||||
|
# Skip empty GIF files as PIL errors on them anyway
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
self.log.exception('Could not fetch image ', iurl)
|
||||||
continue
|
continue
|
||||||
except Exception:
|
|
||||||
self.log.exception('Could not fetch image ', iurl)
|
|
||||||
continue
|
|
||||||
c += 1
|
c += 1
|
||||||
fname = ascii_filename('img'+str(c))
|
fname = ascii_filename('img'+str(c))
|
||||||
if isinstance(fname, unicode):
|
if isinstance(fname, unicode):
|
||||||
|
@ -1075,9 +1075,7 @@ void Style::init(bool initial)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.contrast=QSettings(QLatin1String("Trolltech")).value("/Qt/KDE/contrast", DEFAULT_CONTRAST).toInt();
|
opts.contrast=DEFAULT_CONTRAST; //Changed by Kovid
|
||||||
if(opts.contrast<0 || opts.contrast>10)
|
|
||||||
opts.contrast=DEFAULT_CONTRAST;
|
|
||||||
|
|
||||||
shadeColors(QApplication::palette().color(QPalette::Active, QPalette::Highlight), itsHighlightCols);
|
shadeColors(QApplication::palette().color(QPalette::Active, QPalette::Highlight), itsHighlightCols);
|
||||||
shadeColors(QApplication::palette().color(QPalette::Active, QPalette::Background), itsBackgroundCols);
|
shadeColors(QApplication::palette().color(QPalette::Active, QPalette::Background), itsBackgroundCols);
|
||||||
@ -1522,7 +1520,7 @@ void Style::polish(QApplication *app)
|
|||||||
|
|
||||||
void Style::polish(QPalette &palette)
|
void Style::polish(QPalette &palette)
|
||||||
{
|
{
|
||||||
int contrast(QSettings(QLatin1String("Trolltech")).value("/Qt/KDE/contrast", DEFAULT_CONTRAST).toInt());
|
int contrast = DEFAULT_CONTRAST; // Changed by Kovid
|
||||||
bool newContrast(false);
|
bool newContrast(false);
|
||||||
|
|
||||||
if(contrast<0 || contrast>10)
|
if(contrast<0 || contrast>10)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user