mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -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::
|
||||
|
||||
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.
|
||||
It relies on the freely available zip command line tool.
|
||||
|
@ -1,5 +1,5 @@
|
||||
__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
|
||||
'''
|
||||
@ -7,7 +7,7 @@ abc.com.py
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class ABC_py(BasicNewsRecipe):
|
||||
title = 'ABC digital'
|
||||
title = 'ABC Color'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Noticias de Paraguay y el resto del mundo'
|
||||
publisher = 'ABC'
|
||||
@ -15,12 +15,16 @@ class ABC_py(BasicNewsRecipe):
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'cp1252'
|
||||
encoding = 'utf8'
|
||||
use_embedded_content = False
|
||||
language = 'es_PY'
|
||||
remove_empty_feeds = True
|
||||
masthead_url = 'http://www.abc.com.py/plantillas/img/abc-logo.png'
|
||||
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 = {
|
||||
'comment' : description
|
||||
@ -29,21 +33,19 @@ class ABC_py(BasicNewsRecipe):
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
remove_tags = [dict(name=['form','iframe','embed','object','link','base','table']),dict(attrs={'class':'toolbox'})]
|
||||
remove_tags_after = dict(attrs={'class':'date'})
|
||||
keep_only_tags = [dict(attrs={'class':'zcontent'})]
|
||||
remove_tags = [
|
||||
dict(name=['form','iframe','embed','object','link','base','table']),
|
||||
dict(attrs={'class':['es-carousel-wrapper']}),
|
||||
dict(attrs={'id':['tools','article-banner-1']})
|
||||
]
|
||||
keep_only_tags = [dict(attrs={'id':'article'})]
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Ultimo momento' , u'http://www.abc.com.py/ultimo-momento.xml' )
|
||||
,(u'Nacionales' , u'http://www.abc.com.py/nacionales.xml' )
|
||||
,(u'Internacionales' , u'http://www.abc.com.py/internacionales.xml' )
|
||||
,(u'Deportes' , u'http://www.abc.com.py/deportes.xml' )
|
||||
,(u'Espectaculos' , u'http://www.abc.com.py/espectaculos.xml' )
|
||||
,(u'Ciencia y Tecnologia', u'http://www.abc.com.py/ciencia-y-tecnologia.xml')
|
||||
(u'Ultimo momento', u'http://www.abc.com.py/rss.xml' )
|
||||
,(u'Nacionales' , u'http://www.abc.com.py/nacionales/rss.xml' )
|
||||
,(u'Mundo' , u'http://www.abc.com.py/internacionales/rss.xml')
|
||||
,(u'Deportes' , u'http://www.abc.com.py/deportes/rss.xml' )
|
||||
,(u'Espectaculos' , u'http://www.abc.com.py/espectaculos/rss.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__ = 'claviola'
|
||||
__author__ = 'Carlos Laviola'
|
||||
|
||||
# Specify English as the language of the RSS feeds (ISO-639 code).
|
||||
language = 'en_GB'
|
||||
language = 'pt_BR'
|
||||
|
||||
# Set tags.
|
||||
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'),
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -10,11 +10,11 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class OGlobo(BasicNewsRecipe):
|
||||
title = 'O Globo'
|
||||
__author__ = 'Darko Miletic and Sujata Raman'
|
||||
__author__ = 'Darko Miletic and Carlos Laviola'
|
||||
description = 'News from Brasil'
|
||||
publisher = 'O Globo'
|
||||
category = 'news, politics, Brasil'
|
||||
oldest_article = 2
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
@ -39,43 +39,35 @@ class OGlobo(BasicNewsRecipe):
|
||||
.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 = [
|
||||
dict(name='script')
|
||||
,dict(name='object')
|
||||
,dict(name='form')
|
||||
,dict(name='div', attrs={'id':['linksPatGoogle','rdpm','cor','com','env','rcm_st','coment',]})
|
||||
,dict(name='div', attrs={'class':'box-zap-anu2'})
|
||||
,dict(name='a', attrs={'class':'assine'})
|
||||
,dict(name='link')
|
||||
,dict(name='div', attrs={'id':'header'})
|
||||
,dict(name='p', attrs={'id':'info-date-press'})
|
||||
]
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Todos os canais', u'http://oglobo.globo.com/rss/plantao.xml')
|
||||
,(u'Ciencia', u'http://oglobo.globo.com/rss/plantaociencia.xml')
|
||||
,(u'Educacao', u'http://oglobo.globo.com/rss/plantaoeducacao.xml')
|
||||
,(u'Opiniao', u'http://oglobo.globo.com/rss/plantaoopiniao.xml')
|
||||
,(u'Sao Paulo', u'http://oglobo.globo.com/rss/plantaosaopaulo.xml')
|
||||
,(u'Viagem', u'http://oglobo.globo.com/rss/plantaoviagem.xml')
|
||||
,(u'Cultura', u'http://oglobo.globo.com/rss/plantaocultura.xml')
|
||||
,(u'Esportes', u'http://oglobo.globo.com/rss/plantaoesportes.xml')
|
||||
,(u'Mundo', u'http://oglobo.globo.com/rss/plantaomundo.xml')
|
||||
,(u'Pais', u'http://oglobo.globo.com/rss/plantaopais.xml')
|
||||
,(u'Rio', u'http://oglobo.globo.com/rss/plantaorio.xml')
|
||||
,(u'Saude', u'http://oglobo.globo.com/rss/plantaosaude.xml')
|
||||
,(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')
|
||||
(u'Todos os canais', u'http://oglobo.globo.com/rss.xml?completo=true')
|
||||
,(u'Ciencia', u'http://oglobo.globo.com/rss.xml?secao=ciencia&completo=true')
|
||||
,(u'Educacao', u'http://oglobo.globo.com/rss.xml?secao=educacao&completo=true')
|
||||
,(u'Opiniao', u'http://oglobo.globo.com/rss.xml?secao=opiniao&completo=true')
|
||||
,(u'Cultura', u'http://oglobo.globo.com/rss.xml?secao=cultura&completo=true')
|
||||
,(u'Esportes', u'http://oglobo.globo.com/rss.xml?secao=esportes&completo=true')
|
||||
,(u'Mundo', u'http://oglobo.globo.com/rss.xml?secao=mundo&completo=true')
|
||||
,(u'Pais', u'http://oglobo.globo.com/rss.xml?secao=pais&completo=true')
|
||||
,(u'Rio', u'http://oglobo.globo.com/rss.xml?secao=rio&completo=true')
|
||||
,(u'Saude', u'http://oglobo.globo.com/rss.xml?secao=saude&completo=true')
|
||||
,(u'Economia', u'http://oglobo.globo.com/rss.xml?secao=economia&completo=true')
|
||||
,(u'Tecnologia', u'http://oglobo.globo.com/rss.xml?secao=tecnologia&completo=true')
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?service=print'
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
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,7 +60,12 @@ function goto_reference(ref) {
|
||||
if (num < 0) {alert("Invalid reference: "+ref); return;}
|
||||
var p = $("p");
|
||||
if (num >= p.length) {alert("Reference not found: "+ref); return;}
|
||||
$.scrollTo($(p[num]), 1000,
|
||||
var dest = $(p[num]);
|
||||
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',
|
||||
\'src/qtcurve/common', 'src/qtcurve',
|
||||
\'/usr/include/ImageMagick',
|
||||
\]
|
||||
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs
|
||||
|
||||
|
@ -115,7 +115,7 @@ PyQt4
|
||||
|
||||
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 install
|
||||
|
||||
|
@ -90,6 +90,7 @@ class ANDROID(USBMS):
|
||||
0x4e22 : [0x0100, 0x226, 0x227, 0x231],
|
||||
0xb058 : [0x0222, 0x226, 0x227],
|
||||
0x0ff9 : [0x0226],
|
||||
0xc91 : HTC_BCDS,
|
||||
0xdddd : [0x216],
|
||||
},
|
||||
|
||||
@ -165,7 +166,10 @@ class ANDROID(USBMS):
|
||||
0x2237: { 0x2208 : [0x0226] },
|
||||
|
||||
# Lenovo
|
||||
0x17ef : { 0x7421 : [0x0216] },
|
||||
0x17ef : {
|
||||
0x7421 : [0x0216],
|
||||
0x741b : [0x9999],
|
||||
},
|
||||
|
||||
# Pantech
|
||||
0x10a9 : { 0x6050 : [0x227] },
|
||||
@ -203,7 +207,8 @@ class ANDROID(USBMS):
|
||||
'GT-I9003_CARD', 'XT912', 'FILE-CD_GADGET', 'RK29_SDK', 'MB855',
|
||||
'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW',
|
||||
'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',
|
||||
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
|
||||
|
@ -2393,8 +2393,8 @@ class ITUNES(DriverBase):
|
||||
foo = self.iTunes.name()
|
||||
except:
|
||||
# Try static binding
|
||||
import iTunes_glue
|
||||
self.iTunes = appscript.app('iTunes',terms=iTunes_glue)
|
||||
import itunes
|
||||
self.iTunes = appscript.app('iTunes',terms=itunes)
|
||||
try:
|
||||
foo = self.iTunes.name()
|
||||
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>'
|
||||
__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
|
||||
|
||||
def node_mountpoint(node):
|
||||
@ -23,13 +19,20 @@ def node_mountpoint(node):
|
||||
return de_mangle(line[1])
|
||||
return None
|
||||
|
||||
class NoUDisks1(Exception):
|
||||
pass
|
||||
|
||||
class UDisks(object):
|
||||
|
||||
def __init__(self):
|
||||
self.bus = dbus.SystemBus()
|
||||
try:
|
||||
self.main = dbus.Interface(self.bus.get_object('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):
|
||||
devpath = self.main.FindDeviceByDeviceFile(device_node_path)
|
||||
@ -67,6 +70,7 @@ class UDisks2(object):
|
||||
|
||||
BLOCK = 'org.freedesktop.UDisks2.Block'
|
||||
FILESYSTEM = 'org.freedesktop.UDisks2.Filesystem'
|
||||
DRIVE = 'org.freedesktop.UDisks2.Drive'
|
||||
|
||||
def __init__(self):
|
||||
self.bus = dbus.SystemBus()
|
||||
@ -131,6 +135,21 @@ class UDisks2(object):
|
||||
raise
|
||||
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):
|
||||
if ver is None:
|
||||
try:
|
||||
@ -140,7 +159,6 @@ def get_udisks(ver=None):
|
||||
return u
|
||||
return UDisks2() if ver == 2 else UDisks()
|
||||
|
||||
|
||||
def mount(node_path):
|
||||
u = UDisks()
|
||||
u.mount(node_path)
|
||||
|
@ -187,7 +187,9 @@ def calibre_cover(title, author_string, series_string=None,
|
||||
lines.append(TextLine(series_string, author_size))
|
||||
if logo_path is None:
|
||||
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)$')
|
||||
|
||||
|
@ -687,7 +687,11 @@ class Amazon(Source):
|
||||
return True
|
||||
|
||||
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)
|
||||
if title_ok(title):
|
||||
matches.append(a.get('href'))
|
||||
|
@ -154,10 +154,11 @@ class ISBNDB(Source):
|
||||
total_results = int(bl.get('total_results'))
|
||||
shown_results = int(bl.get('shown_results'))
|
||||
for bd in bl.xpath('.//BookData'):
|
||||
isbn = check_isbn(bd.get('isbn13', bd.get('isbn', None)))
|
||||
if not isbn:
|
||||
isbn = check_isbn(bd.get('isbn', None))
|
||||
isbn13 = check_isbn(bd.get('isbn13', None))
|
||||
if not isbn and not isbn13:
|
||||
continue
|
||||
if orig_isbn and isbn != orig_isbn:
|
||||
if orig_isbn and orig_isbn not in {isbn, isbn13}:
|
||||
continue
|
||||
title = tostring(bd.find('Title'))
|
||||
if not title:
|
||||
@ -173,10 +174,6 @@ class ISBNDB(Source):
|
||||
if not authors:
|
||||
continue
|
||||
comments = tostring(bd.find('Summary'))
|
||||
if not comments:
|
||||
# Require comments, since without them the result is useless
|
||||
# anyway
|
||||
continue
|
||||
id_ = (title, tuple(authors))
|
||||
if id_ in seen:
|
||||
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 tests: List of 2-tuples. Each two tuple is of the form (args,
|
||||
@ -246,6 +247,7 @@ def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
||||
None]
|
||||
if not good:
|
||||
prints('Failed to find', plugin.test_fields(possibles[0]))
|
||||
if fail_missing_meta:
|
||||
raise SystemExit(1)
|
||||
|
||||
if results[0] is not possibles[0]:
|
||||
@ -263,9 +265,10 @@ def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
||||
results.append(rq.get_nowait())
|
||||
except Empty:
|
||||
break
|
||||
if not results:
|
||||
if not results and fail_missing_meta:
|
||||
prints('Cover download failed')
|
||||
raise SystemExit(1)
|
||||
elif results:
|
||||
cdata = results[0]
|
||||
cover = os.path.join(tdir, plugin.name.replace(' ',
|
||||
'')+'-%s-cover.jpg'%sanitize_file_name2(mi.title.replace(' ',
|
||||
|
@ -111,7 +111,7 @@ class Skeleton(object):
|
||||
self.chunks = chunks
|
||||
|
||||
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_insert_positions()
|
||||
@ -127,7 +127,7 @@ class Skeleton(object):
|
||||
self.metrics = {}
|
||||
for tag in root.xpath('//*[@aid]'):
|
||||
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
|
||||
end_length = len(raw.rpartition(b'<')[-1]) + 1
|
||||
self.metrics[tag.get('aid')] = Metric(start_length, end_length)
|
||||
|
@ -469,6 +469,8 @@ class DirContainer(object):
|
||||
return f.write(data)
|
||||
|
||||
def exists(self, path):
|
||||
if not path:
|
||||
return False
|
||||
try:
|
||||
path = os.path.join(self.rootdir, self._unquote(path))
|
||||
except ValueError: #Happens if path contains quoted special chars
|
||||
|
@ -6,20 +6,34 @@
|
||||
Released under the GPLv3 License
|
||||
###
|
||||
|
||||
body_height = () ->
|
||||
db = document.body
|
||||
dde = document.documentElement
|
||||
if db? and dde?
|
||||
return Math.max(db.scrollHeight, dde.scrollHeight, db.offsetHeight,
|
||||
dde.offsetHeight, db.clientHeight, dde.clientHeight)
|
||||
return 0
|
||||
window_scroll_pos = (win=window) -> # {{{
|
||||
if typeof(win.pageXOffset) == 'number'
|
||||
x = win.pageXOffset
|
||||
y = win.pageYOffset
|
||||
else # IE < 9
|
||||
if document.body and ( document.body.scrollLeft or document.body.scrollTop )
|
||||
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) ->
|
||||
ans = elem.offsetTop
|
||||
while elem.offsetParent
|
||||
elem = elem.offsetParent
|
||||
ans += elem.offsetTop
|
||||
return ans
|
||||
viewport_to_document = (x, y, doc=window?.document) -> # {{{
|
||||
until doc == window.document
|
||||
# We are in a frame
|
||||
frame = doc.defaultView.frameElement
|
||||
rect = frame.getBoundingClientRect()
|
||||
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
|
||||
###
|
||||
@ -33,7 +47,7 @@ class BookIndexing
|
||||
|
||||
constructor: () ->
|
||||
this.cache = {}
|
||||
this.body_height_at_last_check = null
|
||||
this.last_check = [null, null]
|
||||
|
||||
cache_valid: (anchors) ->
|
||||
for a in anchors
|
||||
@ -45,7 +59,9 @@ class BookIndexing
|
||||
return true
|
||||
|
||||
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
|
||||
|
||||
ans = {}
|
||||
@ -56,19 +72,24 @@ class BookIndexing
|
||||
try
|
||||
result = document.evaluate(
|
||||
".//*[local-name() = 'a' and @name='#{ anchor }']",
|
||||
document.body, null,
|
||||
body, null,
|
||||
XPathResult.FIRST_ORDERED_NODE_TYPE, null)
|
||||
elem = result.singleNodeValue
|
||||
catch error
|
||||
# The anchor had a ' or other invalid char
|
||||
elem = null
|
||||
if elem == null
|
||||
pos = body_height() + 10000
|
||||
pos = [body.scrollWidth+1000, body.scrollHeight+1000]
|
||||
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
|
||||
|
||||
this.cache = ans
|
||||
this.body_height_at_last_check = body_height()
|
||||
this.last_check = [body.scrollWidth, body.scrollHeight]
|
||||
return ans
|
||||
|
||||
if window?
|
||||
|
@ -50,14 +50,12 @@ absleft = (elem) -> # {{{
|
||||
# }}}
|
||||
|
||||
class PagedDisplay
|
||||
###
|
||||
This class is a namespace to expose functions via the
|
||||
window.paged_display object. The most important functions are:
|
||||
|
||||
set_geometry(): sets the parameters used to layout text in paged mode
|
||||
|
||||
layout(): causes the currently loaded document to be laid out in columns.
|
||||
###
|
||||
# This class is a namespace to expose functions via the
|
||||
# window.paged_display object. The most important functions are:
|
||||
#
|
||||
# set_geometry(): sets the parameters used to layout text in paged mode
|
||||
#
|
||||
# layout(): causes the currently loaded document to be laid out in columns.
|
||||
|
||||
constructor: () ->
|
||||
if not this instanceof arguments.callee
|
||||
@ -163,7 +161,7 @@ class PagedDisplay
|
||||
xpos = Math.floor(document.body.scrollWidth * frac)
|
||||
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
|
||||
# the viewport
|
||||
if typeof(xpos) != 'number'
|
||||
@ -172,13 +170,48 @@ class PagedDisplay
|
||||
if this.is_full_screen_layout
|
||||
window.scrollTo(0, 0)
|
||||
return
|
||||
pos = 0
|
||||
until (pos <= xpos < pos + this.page_width)
|
||||
pos += this.page_width
|
||||
pos = Math.floor(xpos/this.page_width) * this.page_width
|
||||
limit = document.body.scrollWidth - this.screen_width
|
||||
pos = limit if pos > limit
|
||||
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) ->
|
||||
# The current scroll position as a fraction between 0 and 1
|
||||
limit = document.body.scrollWidth - window.innerWidth
|
||||
@ -192,10 +225,7 @@ class PagedDisplay
|
||||
if this.is_full_screen_layout
|
||||
return 0
|
||||
x = window.pageXOffset + Math.max(10, this.current_margin_side)
|
||||
edge = Math.floor(x/this.page_width) * this.page_width
|
||||
while edge < x
|
||||
edge += this.page_width
|
||||
return edge - this.page_width
|
||||
return Math.floor(x/this.page_width) * this.page_width
|
||||
|
||||
next_screen_location: () ->
|
||||
# The position to scroll to for the next screen (which could contain
|
||||
@ -329,7 +359,6 @@ if window?
|
||||
window.paged_display = new PagedDisplay()
|
||||
|
||||
# TODO:
|
||||
# Go to reference positions
|
||||
# Indexing
|
||||
# Resizing of images
|
||||
# Full screen mode
|
||||
# Highlight on jump_to_anchor
|
||||
|
@ -105,14 +105,14 @@ class UniqueFilenames(object): # {{{
|
||||
base, ext = posixpath.splitext(item.href)
|
||||
nhref = base + suffix + ext
|
||||
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,
|
||||
fallback=item.fallback)
|
||||
self.seen_filenames.add(posixpath.basename(nhref))
|
||||
self.rename_map[item.href] = nhref
|
||||
if item.spine_position is not None:
|
||||
oeb.spine.insert(item.spine_position, nitem, item.linear)
|
||||
oeb.spine.remove(item)
|
||||
oeb.manifest.remove(item)
|
||||
if spine_pos is not None:
|
||||
oeb.spine.insert(spine_pos, nitem, item.linear)
|
||||
else:
|
||||
self.seen_filenames.add(fname)
|
||||
|
||||
|
@ -248,6 +248,18 @@ def available_width():
|
||||
desktop = QCoreApplication.instance().desktop()
|
||||
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
|
||||
|
||||
def is_widescreen():
|
||||
@ -791,7 +803,18 @@ class Application(QApplication):
|
||||
font.setStretch(s)
|
||||
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()
|
||||
else:
|
||||
st = self.style()
|
||||
|
@ -15,7 +15,7 @@
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
||||
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
|
@ -54,7 +54,7 @@
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
||||
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -14,7 +14,7 @@
|
||||
</property>
|
||||
<property name="windowIcon" >
|
||||
<iconset resource="../../../../resources/images.qrc" >
|
||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
||||
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="0" column="0" >
|
||||
|
@ -5,7 +5,7 @@ from PyQt4.Qt import (Qt, QDialog, QTableWidgetItem, QIcon, QByteArray,
|
||||
QString, QSize)
|
||||
|
||||
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
|
||||
|
||||
class NameTableWidgetItem(QTableWidgetItem):
|
||||
@ -149,6 +149,9 @@ class TagListEditor(QDialog, Ui_TagListEditor):
|
||||
self.table.itemChanged.connect(self.finish_editing)
|
||||
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:
|
||||
geom = gprefs.get('tag_list_editor_dialog_geometry', None)
|
||||
if geom is not None:
|
||||
@ -158,6 +161,26 @@ class TagListEditor(QDialog, Ui_TagListEditor):
|
||||
except:
|
||||
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):
|
||||
self.table_column_widths = []
|
||||
for c in range(0, self.table.columnCount()):
|
||||
|
@ -18,11 +18,28 @@
|
||||
<normaloff>:/images/chapters.png</normaloff>:/images/chapters.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout">
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<widget class="HistoryLineEdit" name="search_box">
|
||||
<property name="toolTip">
|
||||
<string>Search for an item in the Tag column</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="search_button">
|
||||
<property name="text">
|
||||
<string>Find</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Copy the selected color name to the clipboard</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QToolButton" name="delete_button">
|
||||
@ -69,7 +86,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QTableWidget" name="table">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
@ -82,11 +99,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -101,6 +114,13 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>HistoryLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
@ -255,7 +255,7 @@ class MainWindowMixin(object): # {{{
|
||||
|
||||
def __init__(self, db):
|
||||
self.setObjectName('MainWindow')
|
||||
self.setWindowIcon(QIcon(I('library.png')))
|
||||
self.setWindowIcon(QIcon(I('lt.png')))
|
||||
self.setWindowTitle(__appname__)
|
||||
|
||||
self.setContextMenuPolicy(Qt.NoContextMenu)
|
||||
|
@ -60,7 +60,7 @@ def init_qt(args):
|
||||
QCoreApplication.setApplicationName(APP_UID)
|
||||
app = Application(args)
|
||||
actions = tuple(Main.create_application_menubar())
|
||||
app.setWindowIcon(QIcon(I('library.png')))
|
||||
app.setWindowIcon(QIcon(I('lt.png')))
|
||||
return app, opts, args, actions
|
||||
|
||||
|
||||
@ -323,6 +323,10 @@ def communicate(opts, args):
|
||||
|
||||
if opts.shutdown_running_calibre:
|
||||
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:
|
||||
if len(args) > 1:
|
||||
args[1] = os.path.abspath(args[1])
|
||||
|
@ -228,7 +228,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
self.default_thumbnail = None
|
||||
self.tb_wrapper = textwrap.TextWrapper(width=40)
|
||||
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.tooltip_requested.connect(
|
||||
self.job_manager.show_tooltip)
|
||||
|
@ -202,19 +202,31 @@ class Document(QWebPage): # {{{
|
||||
if not isinstance(self.anchor_positions, dict):
|
||||
# Some weird javascript error happened
|
||||
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):
|
||||
if onresize and not self.loaded_javascript:
|
||||
return
|
||||
side_margin = self.javascript('window.paged_display.layout()', typ=int)
|
||||
# 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
|
||||
# columns extend beyond the boundaries (and margin) of body
|
||||
mf = self.mainFrame()
|
||||
sz = mf.contentsSize()
|
||||
if sz.width() > self.window_width:
|
||||
sz.setWidth(sz.width()+side_margin)
|
||||
scroll_width = self.javascript('document.body.scrollWidth', int)
|
||||
# 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)
|
||||
|
||||
@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):
|
||||
if self.in_paged_mode:
|
||||
self.setPreferredContentsSize(QSize())
|
||||
@ -294,6 +306,7 @@ class Document(QWebPage): # {{{
|
||||
self.mainFrame().setScrollPosition(QPoint(x, y))
|
||||
|
||||
def jump_to_anchor(self, anchor):
|
||||
if not self.loaded_javascript: return
|
||||
self.javascript('window.paged_display.jump_to_anchor("%s")'%anchor)
|
||||
|
||||
def element_ypos(self, elem):
|
||||
@ -352,7 +365,7 @@ class Document(QWebPage): # {{{
|
||||
except ZeroDivisionError:
|
||||
return 0.
|
||||
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)
|
||||
else:
|
||||
npos = val * (self.height - self.window_height)
|
||||
@ -555,6 +568,18 @@ class DocumentView(QWebView): # {{{
|
||||
return (self.document.ypos, self.document.ypos +
|
||||
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):
|
||||
link, text = unicode(link), unicode(text)
|
||||
if link:
|
||||
|
@ -138,7 +138,9 @@ class Reference(QLineEdit):
|
||||
self.editingFinished.connect(self.editing_finished)
|
||||
|
||||
def editing_finished(self):
|
||||
self.goto.emit(unicode(self.text()))
|
||||
text = unicode(self.text())
|
||||
self.setText('')
|
||||
self.goto.emit(text)
|
||||
|
||||
class RecentAction(QAction):
|
||||
|
||||
@ -681,7 +683,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
if hasattr(self, 'current_index'):
|
||||
entry = self.toc_model.next_entry(self.current_index,
|
||||
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:
|
||||
self.pending_goto_next_section = (
|
||||
self.toc_model.currently_viewed_entry, entry, False)
|
||||
@ -691,7 +693,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
if hasattr(self, 'current_index'):
|
||||
entry = self.toc_model.next_entry(self.current_index,
|
||||
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:
|
||||
self.pending_goto_next_section = (
|
||||
self.toc_model.currently_viewed_entry, entry, True)
|
||||
@ -703,7 +706,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
if anchor_positions is None:
|
||||
anchor_positions = self.view.document.read_anchor_positions()
|
||||
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:
|
||||
self.toc.scrollTo(items[-1].index())
|
||||
if pgns is not None:
|
||||
@ -712,7 +716,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
if pgns[0] is self.toc_model.currently_viewed_entry:
|
||||
entry = self.toc_model.next_entry(self.current_index,
|
||||
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])
|
||||
if entry is not None:
|
||||
self.pending_goto_next_section = (
|
||||
|
@ -93,9 +93,19 @@ class TOCItem(QStandardItem):
|
||||
def type(cls):
|
||||
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
|
||||
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
|
||||
# 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
|
||||
@ -103,6 +113,9 @@ class TOCItem(QStandardItem):
|
||||
# be larger than 25, but that's a decent compromise. Also we dont want
|
||||
# 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:
|
||||
# The position at which this anchor is present in the document
|
||||
start_pos = anchor_map.get(self.start_anchor, 0)
|
||||
@ -115,7 +128,7 @@ class TOCItem(QStandardItem):
|
||||
# ancestors of this entry.
|
||||
psp = [anchor_map.get(x, 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 >= 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
|
||||
# of the window +1
|
||||
end_pos = min(psp) if psp else (bottom+1 if self.ends_at >=
|
||||
@ -141,6 +154,51 @@ class TOCItem(QStandardItem):
|
||||
if changed:
|
||||
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):
|
||||
return 'TOC Item: %s %s#%s'%(self.title, self.abspath, self.fragment)
|
||||
|
||||
@ -183,20 +241,26 @@ class TOC(QStandardItemModel):
|
||||
self.currently_viewed_entry = t
|
||||
return items_being_viewed
|
||||
|
||||
def next_entry(self, spine_pos, anchor_map, scroll_pos, backwards=False,
|
||||
current_entry=None):
|
||||
def next_entry(self, spine_pos, anchor_map, viewport_rect, in_paged_mode,
|
||||
backwards=False, current_entry=None):
|
||||
current_entry = (self.currently_viewed_entry if current_entry is None
|
||||
else current_entry)
|
||||
if current_entry is None: return
|
||||
items = reversed(self.all_items) if backwards else self.all_items
|
||||
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:
|
||||
if found:
|
||||
start_pos = anchor_map.get(item.start_anchor, 0)
|
||||
if backwards and item.is_being_viewed and start_pos >= top:
|
||||
# Going to this item will either not move the scroll
|
||||
# position or cause to to *increase* instead of descresing
|
||||
if backwards and item.is_being_viewed and start_pos >= start:
|
||||
# This item will not cause any scrolling
|
||||
continue
|
||||
if item.starts_at != spine_pos or item.start_anchor:
|
||||
return item
|
||||
|
@ -6,14 +6,12 @@ Miscellaneous widgets used in the GUI
|
||||
import re, traceback, os
|
||||
|
||||
from PyQt4.Qt import (QIcon, QFont, QLabel, QListWidget, QAction,
|
||||
QListWidgetItem, QTextCharFormat, QApplication,
|
||||
QSyntaxHighlighter, QCursor, QColor, QWidget,
|
||||
QPixmap, QSplitterHandle, QToolButton,
|
||||
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal,
|
||||
QRegExp, QSettings, QSize, QSplitter,
|
||||
QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene,
|
||||
QMenu, QStringListModel, QCompleter, QStringList,
|
||||
QTimer, QRect, QFontDatabase, QGraphicsView)
|
||||
QListWidgetItem, QTextCharFormat, QApplication, QSyntaxHighlighter,
|
||||
QCursor, QColor, QWidget, QPixmap, QSplitterHandle, QToolButton,
|
||||
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, QRegExp, QSize,
|
||||
QSplitter, QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene, QMenu,
|
||||
QStringListModel, QCompleter, QStringList, QTimer, QRect,
|
||||
QFontDatabase, QGraphicsView, QByteArray)
|
||||
|
||||
from calibre.constants import iswindows
|
||||
from calibre.gui2 import (NONE, error_dialog, pixmap_to_data, gprefs,
|
||||
@ -803,69 +801,29 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
|
||||
@classmethod
|
||||
def loadConfig(cls):
|
||||
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"):
|
||||
Config["%swidth" % name] = settings.value("%swidth" % name,
|
||||
QVariant(QApplication.desktop() \
|
||||
.availableGeometry().width() / 2)).toInt()[0]
|
||||
Config["%sheight" % name] = settings.value("%sheight" % name,
|
||||
QVariant(QApplication.desktop() \
|
||||
.availableGeometry().height() / 2)).toInt()[0]
|
||||
Config["%sy" % name] = settings.value("%sy" % name,
|
||||
QVariant(0)).toInt()[0]
|
||||
Config["toolbars"] = settings.value("toolbars").toByteArray()
|
||||
Config["splitter"] = settings.value("splitter").toByteArray()
|
||||
Config["shellx"] = settings.value("shellx", QVariant(0)).toInt()[0]
|
||||
Config["windowx"] = settings.value("windowx", QVariant(QApplication \
|
||||
.desktop().availableGeometry().width() / 2)).toInt()[0]
|
||||
Config["remembergeometry"] = settings.value("remembergeometry",
|
||||
QVariant(True)).toBool()
|
||||
Config["startwithshell"] = settings.value("startwithshell",
|
||||
QVariant(True)).toBool()
|
||||
Config["showwindowinfo"] = settings.value("showwindowinfo",
|
||||
QVariant(True)).toBool()
|
||||
setDefaultString("shellstartup", """\
|
||||
from __future__ import division
|
||||
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]
|
||||
Config["%swidth" % name] = QVariant(QApplication.desktop().availableGeometry().width() / 2).toInt()[0]
|
||||
Config["%sheight" % name] = QVariant(QApplication.desktop().availableGeometry().height() / 2).toInt()[0]
|
||||
Config["%sy" % name] = QVariant(0).toInt()[0]
|
||||
Config["toolbars"] = QByteArray(b'')
|
||||
Config["splitter"] = QByteArray(b'')
|
||||
Config["shellx"] = QVariant(0).toInt()[0]
|
||||
Config["windowx"] = QVariant(QApplication.desktop().availableGeometry().width() / 2).toInt()[0]
|
||||
Config["remembergeometry"] = QVariant(True).toBool()
|
||||
Config["startwithshell"] = QVariant(True).toBool()
|
||||
Config["showwindowinfo"] = QVariant(True).toBool()
|
||||
Config["backupsuffix"] = QVariant(".bak").toString()
|
||||
Config["cwd"] = QVariant(".").toString()
|
||||
Config["tooltipsize"] = QVariant(150).toInt()[0]
|
||||
Config["maxlinestoscan"] = QVariant(5000).toInt()[0]
|
||||
Config["pythondocpath"] = QVariant("http://docs.python.org").toString()
|
||||
Config["autohidefinddialog"] = QVariant(True).toBool()
|
||||
Config["findcasesensitive"] = QVariant(False).toBool()
|
||||
Config["findwholewords"] = QVariant(False).toBool()
|
||||
Config["tabwidth"] = QVariant(4).toInt()[0]
|
||||
Config["fontfamily"] = QVariant("monospace").toString()
|
||||
Config["fontsize"] = QVariant(10).toInt()[0]
|
||||
for name, color, bold, italic in (
|
||||
("normal", "#000000", False, False),
|
||||
("keyword", "#000080", True, False),
|
||||
@ -877,12 +835,9 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
|
||||
("number", "#924900", False, False),
|
||||
("error", "#FF0000", False, False),
|
||||
("pyqt", "#50621A", False, False)):
|
||||
Config["%sfontcolor" % name] = settings.value(
|
||||
"%sfontcolor" % name, QVariant(color)).toString()
|
||||
Config["%sfontbold" % name] = settings.value(
|
||||
"%sfontbold" % name, QVariant(bold)).toBool()
|
||||
Config["%sfontitalic" % name] = settings.value(
|
||||
"%sfontitalic" % name, QVariant(italic)).toBool()
|
||||
Config["%sfontcolor" % name] = QVariant(color).toString()
|
||||
Config["%sfontbold" % name] = QVariant(bold).toBool()
|
||||
Config["%sfontitalic" % name] = QVariant(italic).toBool()
|
||||
|
||||
|
||||
@classmethod
|
||||
|
@ -6,13 +6,22 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
import os, errno
|
||||
from threading import Thread
|
||||
|
||||
from calibre.constants import iswindows, get_windows_username
|
||||
|
||||
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():
|
||||
global ADDRESS
|
||||
if ADDRESS is None:
|
||||
|
@ -15,6 +15,7 @@ from functools import partial
|
||||
|
||||
from calibre import as_unicode, prints
|
||||
from calibre.constants import iswindows, DEBUG
|
||||
from calibre.utils.ipc import eintr_retry_call
|
||||
|
||||
def _encode(msg):
|
||||
raw = cPickle.dumps(msg, -1)
|
||||
@ -68,7 +69,7 @@ class Writer(Thread):
|
||||
break
|
||||
try:
|
||||
self.data_written = True
|
||||
self.conn.send_bytes(x)
|
||||
eintr_retry_call(self.conn.send_bytes, x)
|
||||
except Exception as e:
|
||||
self.resultq.put(as_unicode(e))
|
||||
else:
|
||||
@ -112,7 +113,7 @@ class Server(Thread):
|
||||
def run(self):
|
||||
while self.keep_going:
|
||||
try:
|
||||
conn = self.listener.accept()
|
||||
conn = eintr_retry_call(self.listener.accept)
|
||||
self.handle_client(conn)
|
||||
except:
|
||||
pass
|
||||
@ -125,7 +126,7 @@ class Server(Thread):
|
||||
def _handle_client(self, conn):
|
||||
while True:
|
||||
try:
|
||||
func_name, args, kwargs = conn.recv()
|
||||
func_name, args, kwargs = eintr_retry_call(conn.recv)
|
||||
except EOFError:
|
||||
try:
|
||||
conn.close()
|
||||
@ -156,8 +157,8 @@ class Server(Thread):
|
||||
import traceback
|
||||
# Try to tell the client process what error happened
|
||||
try:
|
||||
conn.send_bytes(_encode(('failed', (unicode(e),
|
||||
as_unicode(traceback.format_exc())))))
|
||||
eintr_retry_call(conn.send_bytes, (_encode(('failed', (unicode(e),
|
||||
as_unicode(traceback.format_exc()))))))
|
||||
except:
|
||||
pass
|
||||
raise
|
||||
|
@ -14,6 +14,7 @@ from multiprocessing.connection import Listener, arbitrary_address
|
||||
from collections import deque
|
||||
from binascii import hexlify
|
||||
|
||||
from calibre.utils.ipc import eintr_retry_call
|
||||
from calibre.utils.ipc.launch import Worker
|
||||
from calibre.utils.ipc.worker import PARALLEL_FUNCS
|
||||
from calibre import detect_ncpus as cpu_count
|
||||
@ -38,7 +39,7 @@ class ConnectedWorker(Thread):
|
||||
|
||||
def start_job(self, job):
|
||||
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:
|
||||
self.start()
|
||||
else:
|
||||
@ -48,7 +49,7 @@ class ConnectedWorker(Thread):
|
||||
def run(self):
|
||||
while True:
|
||||
try:
|
||||
x = self.conn.recv()
|
||||
x = eintr_retry_call(self.conn.recv)
|
||||
self.notifications.put(x)
|
||||
except BaseException:
|
||||
break
|
||||
@ -129,12 +130,7 @@ class Server(Thread):
|
||||
'CALIBRE_WORKER_KEY' : hexlify(self.auth_key),
|
||||
'CALIBRE_WORKER_RESULT' : hexlify(rfile.encode('utf-8')),
|
||||
}
|
||||
for i in range(2):
|
||||
# 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):
|
||||
raise CriticalError('Failed to launch worker process:\n'+cw)
|
||||
if DEBUG:
|
||||
@ -146,7 +142,7 @@ class Server(Thread):
|
||||
|
||||
try:
|
||||
w(redirect_output=redirect_output)
|
||||
conn = self.listener.accept()
|
||||
conn = eintr_retry_call(self.listener.accept)
|
||||
if conn is None:
|
||||
raise Exception('Failed to launch worker process')
|
||||
except BaseException:
|
||||
|
@ -14,6 +14,7 @@ from threading import Thread
|
||||
from contextlib import closing
|
||||
|
||||
from calibre.constants import iswindows
|
||||
from calibre.utils.ipc import eintr_retry_call
|
||||
from calibre.utils.ipc.launch import Worker
|
||||
|
||||
class WorkerError(Exception):
|
||||
@ -35,30 +36,18 @@ class ConnectedWorker(Thread):
|
||||
|
||||
def run(self):
|
||||
conn = tb = None
|
||||
for i in range(2):
|
||||
# On OS X an EINTR can interrupt the accept() call
|
||||
try:
|
||||
conn = self.listener.accept()
|
||||
break
|
||||
conn = eintr_retry_call(self.listener.accept)
|
||||
except:
|
||||
tb = traceback.format_exc()
|
||||
pass
|
||||
if conn is None:
|
||||
self.tb = tb
|
||||
return
|
||||
self.accepted = True
|
||||
with closing(conn):
|
||||
try:
|
||||
try:
|
||||
conn.send(self.args)
|
||||
except:
|
||||
# Maybe an EINTR
|
||||
conn.send(self.args)
|
||||
try:
|
||||
self.res = conn.recv()
|
||||
except:
|
||||
# Maybe an EINTR
|
||||
self.res = conn.recv()
|
||||
eintr_retry_call(conn.send, self.args)
|
||||
self.res = eintr_retry_call(conn.recv)
|
||||
except:
|
||||
self.tb = traceback.format_exc()
|
||||
|
||||
@ -202,11 +191,7 @@ def main():
|
||||
address = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS']))
|
||||
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
|
||||
with closing(Client(address, authkey=key)) as conn:
|
||||
try:
|
||||
args = conn.recv()
|
||||
except:
|
||||
# Maybe EINTR
|
||||
args = conn.recv()
|
||||
args = eintr_retry_call(conn.recv)
|
||||
try:
|
||||
mod, func, args, kwargs, module_is_source_code = args
|
||||
if module_is_source_code:
|
||||
|
@ -16,6 +16,7 @@ from zipimport import ZipImportError
|
||||
|
||||
from calibre import prints
|
||||
from calibre.constants import iswindows, isosx
|
||||
from calibre.utils.ipc import eintr_retry_call
|
||||
|
||||
PARALLEL_FUNCS = {
|
||||
'lrfviewer' :
|
||||
@ -75,7 +76,7 @@ class Progress(Thread):
|
||||
if x is None:
|
||||
break
|
||||
try:
|
||||
self.conn.send(x)
|
||||
eintr_retry_call(self.conn.send, x)
|
||||
except:
|
||||
break
|
||||
|
||||
@ -178,7 +179,7 @@ def main():
|
||||
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
|
||||
resultf = unhexlify(os.environ['CALIBRE_WORKER_RESULT']).decode('utf-8')
|
||||
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:
|
||||
prints(desc)
|
||||
sys.stdout.flush()
|
||||
|
@ -245,12 +245,18 @@ class TextLine(object):
|
||||
|
||||
|
||||
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
|
||||
the specified output_format.
|
||||
'''
|
||||
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
|
||||
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:
|
||||
foot_font = P('fonts/liberation/LiberationMono-Regular.ttf')
|
||||
vanity = create_text_arc(__appname__ + ' ' + __version__, 24,
|
||||
font=foot_font)
|
||||
font=foot_font, bgcolor='#00000000')
|
||||
lwidth, lheight = vanity.size
|
||||
left = int(max(0, (width - lwidth)/2.))
|
||||
top = height - lheight - 10
|
||||
@ -279,6 +285,9 @@ def create_cover_page(top_lines, logo_path, width=590, height=750,
|
||||
logo.size = (lwidth, lheight)
|
||||
left = int(max(0, (width - lwidth)/2.))
|
||||
top = bottom+10
|
||||
extra = int((available[1] - lheight)/2.0)
|
||||
if extra > 0:
|
||||
top += extra
|
||||
canvas.compose(logo, left, top)
|
||||
|
||||
return canvas.export(output_format)
|
||||
|
@ -495,6 +495,7 @@ typedef struct {
|
||||
// Method declarations {{{
|
||||
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_texture(magick_Image *self, PyObject *args, PyObject *kwargs);
|
||||
// }}}
|
||||
|
||||
static void
|
||||
@ -926,7 +927,6 @@ magick_Image_flip(magick_Image *self, PyObject *args, PyObject *kwargs) {
|
||||
}
|
||||
// }}}
|
||||
|
||||
|
||||
// Image.set_page {{{
|
||||
|
||||
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 {{{
|
||||
static PyMethodDef magick_Image_methods[] = {
|
||||
{"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)"
|
||||
},
|
||||
|
||||
{"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(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 {{{
|
||||
|
@ -77,7 +77,8 @@ class BasicNewsRecipe(Recipe):
|
||||
delay = 0
|
||||
|
||||
#: 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'
|
||||
|
||||
#: Number of simultaneous downloads. Set to 1 if the server is picky.
|
||||
@ -1264,6 +1265,7 @@ class BasicNewsRecipe(Recipe):
|
||||
mi = MetaInformation(title, [__appname__])
|
||||
mi.publisher = __appname__
|
||||
mi.author_sort = __appname__
|
||||
if self.publication_type:
|
||||
mi.publication_type = 'periodical:'+self.publication_type+':'+self.short_title()
|
||||
mi.timestamp = nowf()
|
||||
article_titles, aseen = [], set()
|
||||
|
@ -12,6 +12,7 @@ from urllib import url2pathname, quote
|
||||
from httplib import responses
|
||||
from PIL import Image
|
||||
from cStringIO import StringIO
|
||||
from base64 import b64decode
|
||||
|
||||
from calibre import browser, relpath, unicode_path
|
||||
from calibre.constants import filesystem_encoding, iswindows
|
||||
@ -346,6 +347,13 @@ class RecursiveFetcher(object):
|
||||
c = 0
|
||||
for tag in soup.findAll(lambda tag: tag.name.lower()=='img' and tag.has_key('src')):
|
||||
iurl = tag['src']
|
||||
if iurl.startswith('data:image/'):
|
||||
try:
|
||||
data = b64decode(iurl.partition(',')[-1])
|
||||
except:
|
||||
self.log.exception('Failed to decode embedded image')
|
||||
continue
|
||||
else:
|
||||
if callable(self.image_url_processor):
|
||||
iurl = self.image_url_processor(baseurl, iurl)
|
||||
if not urlparse.urlsplit(iurl).scheme:
|
||||
|
@ -1075,9 +1075,7 @@ void Style::init(bool initial)
|
||||
#endif
|
||||
}
|
||||
|
||||
opts.contrast=QSettings(QLatin1String("Trolltech")).value("/Qt/KDE/contrast", DEFAULT_CONTRAST).toInt();
|
||||
if(opts.contrast<0 || opts.contrast>10)
|
||||
opts.contrast=DEFAULT_CONTRAST;
|
||||
opts.contrast=DEFAULT_CONTRAST; //Changed by Kovid
|
||||
|
||||
shadeColors(QApplication::palette().color(QPalette::Active, QPalette::Highlight), itsHighlightCols);
|
||||
shadeColors(QApplication::palette().color(QPalette::Active, QPalette::Background), itsBackgroundCols);
|
||||
@ -1522,7 +1520,7 @@ void Style::polish(QApplication *app)
|
||||
|
||||
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);
|
||||
|
||||
if(contrast<0 || contrast>10)
|
||||
|
Loading…
x
Reference in New Issue
Block a user