Updates in Apple driver supporting static iTunes glue code for appscript

This commit is contained in:
GRiker 2012-06-27 08:50:30 -06:00
commit 39380521c1
48 changed files with 1161 additions and 360 deletions

View File

@ -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.

View File

@ -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

View File

@ -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
View 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')]

View 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')]

View 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'),
]

View File

@ -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
@ -35,47 +35,39 @@ class OGlobo(BasicNewsRecipe):
body{font-family:Arial,Helvetica,sans-serif;font-size:x-small;} body{font-family:Arial,Helvetica,sans-serif;font-size:x-small;}
h3{font-size:large; color:#082963; font-weight:bold;} h3{font-size:large; color:#082963; font-weight:bold;}
#ident{color:#0179B4; font-size:xx-small;} #ident{color:#0179B4; font-size:xx-small;}
p{color:#000000;font-weight:normal;} p{color:#000000;font-weight:normal;}
.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
View 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -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()}});
} }

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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"

View 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', [])]

View File

@ -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)

View File

@ -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)$')

View File

@ -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'))

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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?

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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">

View File

@ -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>

View File

@ -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" >

View File

@ -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()):

View File

@ -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>

View File

@ -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)

View File

@ -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])

View File

@ -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)

View File

@ -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:

View File

@ -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 = (

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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()

View File

@ -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)

View File

@ -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 {{{

View File

@ -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:

View File

@ -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):

View File

@ -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)