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

View File

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

View File

@ -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
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'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
@ -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
@ -35,47 +35,39 @@ class OGlobo(BasicNewsRecipe):
body{font-family:Arial,Helvetica,sans-serif;font-size:x-small;}
h3{font-size:large; color:#082963; font-weight:bold;}
#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;}
'''
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
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;}
var p = $("p");
if (num >= p.length) {alert("Reference not found: "+ref); return;}
$.scrollTo($(p[num]), 1000,
{onAfter:function(){window.py_bridge.animated_scroll_done()}});
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()}});
}

View File

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

View File

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

View File

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

View File

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

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>'
__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()
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')
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)

View File

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

View File

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

View File

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

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 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]
if not good:
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]:
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())
except Empty:
break
if not results:
if not results and fail_missing_meta:
prints('Cover download failed')
raise SystemExit(1)
cdata = results[0]
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])
elif results:
cdata = results[0]
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])
prints('Cover downloaded to:', cover)
prints('Cover downloaded to:', cover)
if len(cdata[-1]) < 10240:
prints('Downloaded cover too small')
raise SystemExit(1)
if len(cdata[-1]) < 10240:
prints('Downloaded cover too small')
raise SystemExit(1)
prints('Average time per query', sum(times)/len(times))

View File

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

View File

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

View File

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

View File

@ -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,12 +170,47 @@ 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
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) ->
# The current scroll position as a fraction between 0 and 1
@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,75 +18,88 @@
<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">
<item>
<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>
<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>
<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" 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">
<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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
cw = self.do_launch(env, gui, redirect_output, rfile)
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:

View File

@ -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
except:
tb = traceback.format_exc()
pass
try:
conn = eintr_retry_call(self.listener.accept)
except:
tb = traceback.format_exc()
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:

View File

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

View File

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

View File

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

View File

@ -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,7 +1265,8 @@ class BasicNewsRecipe(Recipe):
mi = MetaInformation(title, [__appname__])
mi.publisher = __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()
article_titles, aseen = [], set()
for f in feeds:

View File

@ -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,22 +347,29 @@ 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 callable(self.image_url_processor):
iurl = self.image_url_processor(baseurl, iurl)
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]
if iurl.startswith('data:image/'):
try:
data = b64decode(iurl.partition(',')[-1])
except:
self.log.exception('Failed to decode embedded image')
continue
try:
data = self.fetch_url(iurl)
if data == 'GIF89a\x01':
# Skip empty GIF files as PIL errors on them anyway
else:
if callable(self.image_url_processor):
iurl = self.image_url_processor(baseurl, iurl)
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
except Exception:
self.log.exception('Could not fetch image ', iurl)
continue
c += 1
fname = ascii_filename('img'+str(c))
if isinstance(fname, unicode):

View File

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