This commit is contained in:
GRiker 2012-07-13 04:15:34 -06:00
commit 0cebfe4613
146 changed files with 37922 additions and 34300 deletions

View File

@ -19,6 +19,74 @@
# new recipes: # new recipes:
# - title: # - title:
- version: 0.8.60
date: 2012-07-13
new features:
- title: "When searching, allow use of un-accented characters to match accented characters in all fields and all languages (not just authors and English as before)"
description: "The rules for matching un-accented characters are done in a language dependent way. So if your calibre interface language is set to English, n will match both n and ñ, but if it is set to Spanish, it will match only n, as in Spanish ñ is a separate alphabet in Spanish."
type: major
- title: "Content server: Show a best guess for the IP address the content server is currently listening at in the connect/share menu."
tickets: [1024128]
- title: "E-book viewer: Add an option to show a clock in full screen mode."
tickets: [1022086]
- title: "Drivers for Paquito Imaginarium and a few Android phones"
tickets: [1024021,1023613,1023461,1022401]
- title: "HTMLZ Output: Add option to use the book title as the filename for the html file inside the archive"
- title: "Make the list of displayed fields in the book details panel a per library setting"
- title: "Have autocomplete on authors/series/tags/etc. ignore accented characters when finding matches (similar to the changes to search above)"
- title: "Support for retina displays in OS X (I hope)"
tickets: [1022191]
- title: "Remove the dependency on the zip command line tool when developing plugins"
bug fixes:
- title: "Kobo driver: Do not perform write operations on the Kobo database if its version is newer than the latest version the driver supports, for safety"
- title: "KF8 Input: Ignore encoding declarations inside the html markup, as they are sometimes incorrect."
tickets: [1022933]
- title: "Force refresh of cached composite column values when values in the cache are changed"
- title: "Fix a regression that broke calibre --shutdown-running-calibre on windows."
tickets: [1022504]
- title: "Possible workaround for Qt 4.8.2 open file dialog failing on some linux distros."
tickets: [1022019]
- title: "Catalogs: Fix some epubcheck errors when generating catalogs in EPUB format"
- title: "Linux installer: When calling the xdg utilities use system libraries rather than the libraries bundled with calibre"
- title: "Fix numeric sort for composite custom columns that use custom separators"
tickets: [1021814]
- title: "Tag browser: When grouping by first letter, handle languages that have 'letters' made of more than one character. This can be turned off via Preferences->Tweaks"
improved recipes:
- Hola magazine
- Adventure Gamers
- Cosmopolitan UK
- Onda Rock
new recipes:
- title: Empire Magazine
author: Dave Asbury
- title: NZZ Folio
author: Bernd Leinfelder
- title: Warentest
author: asdfdsfksd
- version: 0.8.59 - version: 0.8.59
date: 2012-07-06 date: 2012-07-06

View File

@ -198,7 +198,7 @@ You can insert print statements anywhere in your plugin code, they will be outpu
You can quickly test changes to your plugin by using the following command You can quickly test changes to your plugin by using the following command
line:: line::
calibre -s; calibre-customize -b /path/to/your/plugin/directory; calibre calibre-debug -s; calibre-customize -b /path/to/your/plugin/directory; calibre
This will shutdown a running calibre, wait for the shutdown to complete, then update your plugin in |app| and relaunch |app|. This will shutdown a running calibre, wait for the shutdown to complete, then update your plugin in |app| and relaunch |app|.

View File

@ -1,5 +1,5 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009-2012, Darko Miletic <darko.miletic at gmail.com>'
''' '''
www.adventuregamers.com www.adventuregamers.com
''' '''
@ -14,24 +14,24 @@ class AdventureGamers(BasicNewsRecipe):
publisher = 'Adventure Gamers' publisher = 'Adventure Gamers'
category = 'news, games, adventure, technology' category = 'news, games, adventure, technology'
oldest_article = 10 oldest_article = 10
delay = 10 #delay = 10
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
encoding = 'cp1252' encoding = 'utf8'
remove_javascript = True remove_javascript = True
use_embedded_content = False use_embedded_content = False
INDEX = u'http://www.adventuregamers.com' INDEX = u'http://www.adventuregamers.com'
extra_css = """ extra_css = """
.pageheader_type{font-size: x-large; font-weight: bold; color: #828D74} .pageheader_type{font-size: x-large; font-weight: bold; color: #828D74}
.pageheader_title{font-size: xx-large; color: #394128} .pageheader_title,.page_title{font-size: xx-large; color: #394128}
.pageheader_byline{font-size: small; font-weight: bold; color: #394128} .pageheader_byline{font-size: small; font-weight: bold; color: #394128}
.score_bg {display: inline; width: 100%; margin-bottom: 2em} .score_bg {display: inline; width: 100%; margin-bottom: 2em}
.score_column_1{ padding-left: 10px; font-size: small; width: 50%} .score_column_1{ padding-left: 10px; font-size: small; width: 50%}
.score_column_2{ padding-left: 10px; font-size: small; width: 50%} .score_column_2{ padding-left: 10px; font-size: small; width: 50%}
.score_column_3{ padding-left: 10px; font-size: small; width: 50%} .score_column_3{ padding-left: 10px; font-size: small; width: 50%}
.score_header{font-size: large; color: #50544A} .score_header{font-size: large; color: #50544A}
.bodytext{display: block} img{margin-bottom: 1em;}
body{font-family: Helvetica,Arial,sans-serif} body{font-family: 'Open Sans',Helvetica,Arial,sans-serif}
""" """
conversion_options = { conversion_options = {
@ -41,35 +41,38 @@ class AdventureGamers(BasicNewsRecipe):
, 'language' : language , 'language' : language
} }
keep_only_tags = [ keep_only_tags = [dict(name='div', attrs={'class':'cleft_inn'})]
dict(name='div', attrs={'class':'content_middle'})
]
remove_tags = [ remove_tags = [
dict(name=['object','link','embed','form']) dict(name=['object','link','embed','form','iframe','meta'])
,dict(name='div', attrs={'class':['related-stories','article_leadout','prev','next','both']}) ,dict(name='a', attrs={'href':'http://www.adventuregamers.com/about/scoring'})
,dict(name='a', attrs={'href':'http://www.adventuregamers.com/about/policies'})
] ]
remove_tags_after = [dict(name='div', attrs={'class':'bodytext'})]
remove_tags_after = [dict(name='div', attrs={'class':'toolbar_fat'})]
remove_attributes = ['width','height'] remove_attributes = ['width','height']
feeds = [(u'Articles', u'http://feeds2.feedburner.com/AdventureGamers')] feeds = [(u'Articles', u'http://www.adventuregamers.com/rss/')]
def get_article_url(self, article): def get_article_url(self, article):
return article.get('guid', None) url = BasicNewsRecipe.get_article_url(self, article)
if '/videos/' in url or '/hypeometer/' in url:
return None
return url
def append_page(self, soup, appendtag, position): def append_page(self, soup, appendtag, position):
pager = soup.find('div',attrs={'class':'toolbar_fat_next'}) pager = soup.find('div', attrs={'class':'pagination_big'})
if pager: if pager:
nexturl = self.INDEX + pager.a['href'] nextpage = soup.find('a', attrs={'class':'next-page'})
soup2 = self.index_to_soup(nexturl) if nextpage:
texttag = soup2.find('div', attrs={'class':'bodytext'}) nexturl = nextpage['href']
for it in texttag.findAll(style=True): soup2 = self.index_to_soup(nexturl)
del it['style'] texttag = soup2.find('div', attrs={'class':'bodytext'})
newpos = len(texttag.contents) for it in texttag.findAll(style=True):
self.append_page(soup2,texttag,newpos) del it['style']
texttag.extract() newpos = len(texttag.contents)
appendtag.insert(position,texttag) self.append_page(soup2,texttag,newpos)
texttag.extract()
pager.extract()
appendtag.insert(position,texttag)
def preprocess_html(self, soup): def preprocess_html(self, soup):
@ -78,7 +81,7 @@ class AdventureGamers(BasicNewsRecipe):
for item in soup.findAll('div', attrs={'class':'floatright'}): for item in soup.findAll('div', attrs={'class':'floatright'}):
item.extract() item.extract()
self.append_page(soup, soup.body, 3) self.append_page(soup, soup.body, 3)
pager = soup.find('div',attrs={'class':'toolbar_fat'}) pager = soup.find('div',attrs={'class':'pagination_big'})
if pager: if pager:
pager.extract() pager.extract()
return self.adeify_images(soup) return self.adeify_images(soup)

View File

@ -1,13 +1,13 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
#from calibre import __appname__ import re
from calibre.utils.magick import Image from calibre import browser
class AdvancedUserRecipe1306097511(BasicNewsRecipe): class AdvancedUserRecipe1306097511(BasicNewsRecipe):
title = u'Cosmopolitan UK' title = u'Cosmopolitan UK'
description = 'Fashion, beauty and Gossip for women from COSMOPOLITAN -UK' description = 'Author : D.Asbury : Womens Fashion, beauty and Gossip for women from COSMOPOLITAN -UK'
__author__ = 'Dave Asbury' __author__ = 'Dave Asbury'
#last update 21/12/11 #last update 7/7/12 hopefully get current cover from itunes
# greyscale code by Starson # greyscale code by Starson
cover_url = 'http://www.cosmopolitan.magazine.co.uk/files/4613/2085/8988/Cosmo_Cover3.jpg' cover_url = 'http://www.cosmopolitan.magazine.co.uk/files/4613/2085/8988/Cosmo_Cover3.jpg'
no_stylesheets = True no_stylesheets = True
@ -39,14 +39,19 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
feeds = [ feeds = [
(u'Love & Sex', u'http://www.cosmopolitan.co.uk/love-sex/rss/'), (u'Men', u'http://cosmopolitan.co.uk/men/rss/'), (u'Fashion', u'http://cosmopolitan.co.uk/fashion/rss/'), (u'Hair & Beauty', u'http://cosmopolitan.co.uk/beauty-hair/rss/'), (u'LifeStyle', u'http://cosmopolitan.co.uk/lifestyle/rss/'), (u'Cosmo On Campus', u'http://cosmopolitan.co.uk/campus/rss/'), (u'Celebrity Gossip', u'http://cosmopolitan.co.uk/celebrity-gossip/rss/')] (u'Love & Sex', u'http://www.cosmopolitan.co.uk/love-sex/rss/'), (u'Men', u'http://cosmopolitan.co.uk/men/rss/'), (u'Fashion', u'http://cosmopolitan.co.uk/fashion/rss/'), (u'Hair & Beauty', u'http://cosmopolitan.co.uk/beauty-hair/rss/'), (u'LifeStyle', u'http://cosmopolitan.co.uk/lifestyle/rss/'), (u'Cosmo On Campus', u'http://cosmopolitan.co.uk/campus/rss/'), (u'Celebrity Gossip', u'http://cosmopolitan.co.uk/celebrity-gossip/rss/')]
def postprocess_html(self, soup, first): def get_cover_url(self):
#process all the images soup = self.index_to_soup('http://itunes.apple.com/gb/app/cosmopolitan-uk/id461363572?mt=8')
for tag in soup.findAll(lambda tag: tag.name.lower()=='img' and tag.has_key('src')): # look for the block containing the sun button and url
iurl = tag['src'] cov = soup.find(attrs={'alt' : 'iPhone Screenshot 1'})
img = Image() cov2 = str(cov['src'])
img.open(iurl) br = browser()
if img < 0: br.set_handle_redirect(False)
raise RuntimeError('Out of memory') try:
img.type = "GrayscaleType" br.open_novisit(cov2)
img.save(iurl) cover_url = cov2
return soup except:
cover_url = 'http://www.cosmopolitan.magazine.co.uk/files/4613/2085/8988/Cosmo_Cover3.jpg'
return cover_url

View File

@ -0,0 +1,51 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1341650280(BasicNewsRecipe):
title = u'Empire Magazine'
description = 'Author D.Asbury. Film articles from Empire Mag. '
__author__ = 'Dave Asbury'
# last updated 7/7/12
remove_empty_feeds = True
remove_javascript = True
no_stylesheets = True
#oldest_article = 7
max_articles_per_feed = 20
cover_url = 'http://www.empireonline.com/images/magazine/cover.jpg'
conversion_options = {
'linearize_tables' : True,
}
#auto_cleanup = True
preprocess_regexps = [
(re.compile(r'<a href="http://twitter.com/share.*?</a>', re.IGNORECASE | re.DOTALL), lambda match: ''),
(re.compile(r'<head>.*?<!-- CONTENT: START -->', re.IGNORECASE | re.DOTALL), lambda match: '<head></head><!-- CONTENT: START -->'),
(re.compile(r'<!-- LATEST NEWS HEADLINES: START -->.*?<!-- LATEST NEWS HEADLINES: END -->', re.IGNORECASE | re.DOTALL), lambda match: '<!-- LATEST NEWS HEADLINES: START --><!-- LATEST NEWS HEADLINES: END -->'),
(re.compile(r'<!-- RELATED FUTURE FILMS: START -->.*?<!-- RELATED FUTURE FILMS: END -->', re.IGNORECASE | re.DOTALL), lambda match: '<!-- RELATED FUTURE FILMS: START --><!-- RELATED FUTURE FILMS: END -->'),
(re.compile(r'<!-- CURRENT HIGHLIGHTS: START-->.*?<!-- CURRENT HIGHLIGHTS: END -->', re.IGNORECASE | re.DOTALL), lambda match: '<!-- CURRENT HIGHLIGHTS: START--><!-- CURRENT HIGHLIGHTS: END -->'),
(re.compile(r'<!-- RELATED REVIEWS: START -->.*?<!-- RELATED REVIEWS: END -->', re.IGNORECASE | re.DOTALL), lambda match: '<!-- RELATED REVIEWS: START --><!-- RELATED REVIEWS: END -->'),
(re.compile(r'<!-- RELATED INTERVIEWS -->.*?<!-- RELATED REVIEWS: END -->', re.IGNORECASE | re.DOTALL), lambda match: '<!-- RELATED INTERVIEWS --><!-- RELATED REVIEWS: END -->'),
(re.compile(r'<!-- CONTENT: END -->.*?</body>', re.IGNORECASE | re.DOTALL), lambda match: '<!-- CONTENT: END --></body>'),
(re.compile(r'<!-- STORY: END -->.*?</body>', re.IGNORECASE | re.DOTALL), lambda match: '<!-- STORY: END --></body>'),
(re.compile(r'<!-- RATINGS GUIDE: START-->.*?<!-- RATINGS GUIDE: END-->', re.IGNORECASE | re.DOTALL), lambda match: '<!-- RATINGS GUIDE: START--><!-- RATINGS GUIDE: END-->'),
(re.compile(r'<strong>SUBSCRIBE TO EMPIRE</strong>.*?</tbody>', re.IGNORECASE | re.DOTALL), lambda match: '</tbody>'),
(re.compile(r'<!-- USER REVIEWS: START -->.*?<!-- USER REVIEWS: END -->', re.IGNORECASE | re.DOTALL), lambda match: '<!-- USER REVIEWS: START --><!-- USER REVIEWS: END -->'),
(re.compile(r'Advertisement', re.IGNORECASE | re.DOTALL), lambda match: ''),
(re.compile(r'<a name="haveyoursay".*?now to have your say.', re.IGNORECASE | re.DOTALL), lambda match: ''),
]
keep_only_tags = [
# dict(name='h1'),
# dict(attrs={'class' : 'mediumblack'}),
]
remove_tags = [dict(name='td', attrs={'width':'200', 'valign' : 'top'}),
dict(name='b'),
dict(name='a',attrs={'name' : 'haveyoursay'}),
dict(attrs={'class' : 'newslink'}),
]
feeds = [(u'News', u'http://feed43.com/7338478755673147.xml'),
(u'Recent Features',u'http://feed43.com/4346347750304760.xml'),
(u'Interviews',u'http://feed43.com/3418350077724081.xml'),
(u'Film Reviews',u'http://feed43.com/2643703076510627.xml'),
]

View File

@ -33,6 +33,7 @@ class FazNet(BasicNewsRecipe):
('Technik & Motor', 'http://www.faz.net/aktuell/technik-motor/?rssview=1'), ('Technik & Motor', 'http://www.faz.net/aktuell/technik-motor/?rssview=1'),
('Wissen', 'http://www.faz.net/aktuell/wissen/?rssview=1'), ('Wissen', 'http://www.faz.net/aktuell/wissen/?rssview=1'),
('Reise', 'http://www.faz.net/aktuell/reise/?rssview=1'), ('Reise', 'http://www.faz.net/aktuell/reise/?rssview=1'),
('Beruf & Chance', 'http://www.faz.net/aktuell/beruf-chance/?rssview=1') ('Beruf & Chance', 'http://www.faz.net/aktuell/beruf-chance/?rssview=1'),
('Rhein-Main', 'http://www.faz.net/aktuell/rhein-main/?rssview=1') # AGe add 2012-07-13
] ]

View File

@ -1,38 +1,73 @@
#!/usr/bin/env python #!/usr/bin/env python
__license__ = 'GPL v3' # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__copyright__ = '2010, Brendan Sleight <bms.calibre at barwap.com>' __license__ = 'GPL v3'
__copyright__ = '30 June 2012, desUBIKado'
__author__ = 'desUBIKado'
__description__ = 'Diario de actualidad, moda y belleza'
__version__ = 'v0.01'
__date__ = '30, June 2012'
''' '''
hola.com http://www.hola.com/
''' '''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class Hackaday(BasicNewsRecipe): class hola_es(BasicNewsRecipe):
title = u'Hola' __author__ = 'desUBIKado'
__author__ = 'bmsleight' description = 'Diario de actualidad, moda y belleza'
description = 'diario de actualidad, moda y belleza.' title = u'¡Hola!'
oldest_article = 10 publisher = 'Hola S.L.'
category = 'Spanish celebrities, Entertainment News, Royalty, Daily Variety, Hollywood'
language = 'es'
masthead_url = 'http://imagenes.hola.com/comunes/2008/logo-holacom.gif'
timefmt = '[%a, %d %b, %Y]'
oldest_article = 7
delay = 1
encoding = 'utf-8'
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True
language = 'es'
use_embedded_content = False use_embedded_content = False
remove_empty_feeds = True
keep_only_tags = [ remove_javascript = True
dict(name='div', attrs={'id':'cuerpo'}) no_stylesheets = True
]
feeds = [ feeds = [
(u'Famosos' , u'http://www.hola.com/famosos/rss.xml' ), (u'Famosos' , u'http://www.hola.com/famosos/rss.xml' )
(u'Realeza' , u'http://www.hola.com/realeza/rss.xml' ), ,(u'Realeza' , u'http://www.hola.com/realeza/rss.xml' )
(u'Cine' , u'http://www.hola.com/cine/rss.xml' ), ,(u'Cine' , u'http://www.hola.com/cine/rss.xml' )
(u'Música' , u'http://www.hola.com/musica/rss.xml' ), ,(u'M\xfasica' , u'http://www.hola.com/musica/rss.xml' )
(u'Moda y modelos' , u'http://www.hola.com/moda/portada/rss.xml' ), ,(u'Moda y modelos' , u'http://www.hola.com/moda/portada/rss.xml' )
(u'Belleza y salud', u'http://www.hola.com/belleza/portada/rss.xml' ), ,(u'Belleza y salud', u'http://www.hola.com/belleza/portada/rss.xml' )
(u'Niños' , u'http://www.hola.com/ninos/rss.xml' ), ,(u'Ni\xf1os' , u'http://www.hola.com/ninos/rss.xml' )
(u'Todas las noticias', u'http://int2.hola.com/app/feeds/rss_hola.php'), ]
keep_only_tags = [dict(name='div', attrs={'id':['cuerpo','com']})]
remove_tags = [dict(name='div', attrs={'id':['relacionadas','slide-enlaces-patrocinados','comentarios']}),
dict(name='div', attrs={'class':['slide-enlaces-patricinados-tit','compartir']})
] ]
remove_tags_after = dict(name='div' , attrs={'id':'comentarios'})
# Recuperamos la portada de papel (la imagen 520 tiene mayor resolucion)
def get_cover_url(self):
index = 'http://www.hola.com/abono/ediciondigital/'
soup = self.index_to_soup(index)
for image in soup.findAll('img',src=True):
if image['src'].endswith('portada-revista-hola-520.jpg'):
return 'http://www.hola.com/' + image['src']
return None
def get_article_url(self, article): def get_article_url(self, article):
url = article.get('guid', None) url = article.get('guid', None)
return url return url
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:30px;}
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal; font-style:italic; font-size:18px;}
'''

View File

@ -8,7 +8,7 @@ class NatGeoMag(BasicNewsRecipe):
oldest_article = 31 oldest_article = 31
max_articles_per_feed = 50 max_articles_per_feed = 50
category = 'geography, magazine' category = 'geography, magazine'
language = 'en_US' language = 'en'
publication_type = 'magazine' publication_type = 'magazine'
cover_url = 'http://www.yourlogoresources.com/wp-content/uploads/2011/09/national-geographic-logo.jpg' cover_url = 'http://www.yourlogoresources.com/wp-content/uploads/2011/09/national-geographic-logo.jpg'
use_embedded_content = False use_embedded_content = False

61
recipes/nzz_folio.recipe Normal file
View File

@ -0,0 +1,61 @@
__license__ = 'GPL v3'
__copyright__ = '2012 Bernd Leinfelder <skoll1975@gmail.com>'
'''
www.nzzfolio.ch
'''
from calibre.web.feeds.recipes import BasicNewsRecipe
class Nzzfolio(BasicNewsRecipe):
title = 'NZZ Folio'
__author__ = 'Bernd Leinfelder'
description = 'Aktuelle Artikel des NZZ Folio'
publisher = 'NZZ AG'
category = 'news, politics, nachrichten, Switzerland'
oldest_article = 35
max_articles_per_feed = 100
no_stylesheets = True
encoding = 'utf-8'
use_embedded_content = False
language = 'de'
extra_css = """
body{font-family: Georgia,"Times New Roman",Times,serif }
.artikel h3,.artikel h4,.bildLegende,.question,.autor{font-family: Arial,Verdana,Helvetica,sans-serif}
.bildLegende{font-size: small}
.autor{font-size: 0.9375em; color: #666666}
.quote{font-size: large !important;
font-style: italic;
font-weight: normal !important;
border-bottom: 1px dotted #BFBFBF;
border-top: 1px dotted #BFBFBF;
line-height: 1.25em}
.quelle{color: #666666; font-style: italic; white-space: nowrap}
"""
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
,'linearize_tables' : True
}
remove_attributes=['width','height','lang']
remove_tags_before = dict(id='content')
remove_tags_after = dict(id='content')
remove_tags = [
dict(name=['h2','object','link','base','meta','iframe'])
,dict(id='artikelBar')
,dict(id='foot')
,dict(id='bildLegende')
,dict(name='div',attrs={'class':['box']})
]
feeds = [
(u'NZZ Folio' , u'http://rss.nzzfolio.ch/')
]

View File

@ -1,21 +1,27 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__author__ = 'faber1971'
description = 'Italian rock webzine - v1.01 (6, July 2012)'
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1328535130(BasicNewsRecipe): class AdvancedUserRecipe1328535130(BasicNewsRecipe):
title = u'Onda Rock' title = u'Onda Rock'
__author__ = 'faber1971' __author__ = 'faber1971'
description = 'Italian rock webzine' description = 'Italian rock webzine'
language = 'it' language = 'it'
oldest_article = 15
oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100
auto_cleanup = False auto_cleanup = False
remove_tags = [ remove_tags = [
dict(name='div', attrs={'id':['boxHeader','boxlinks_med','footer','boxinterviste','box_special_med','boxdiscografia_head','path']}), dict(name='div', attrs={'id':['boxHeader','boxlinks_med','footer','boxinterviste','box_special_med','boxdiscografia_head','path','widget','menuarea','headerarea']}),
dict(name='div', attrs={'align':'left'}), dict(name='div', attrs={'align':'left'}),
dict(name='div', attrs={'class':['media','boxarticoli']}),
dict(name='div', attrs={'style':'text-align: center'}), dict(name='div', attrs={'style':'text-align: center'}),
dict(name='table', attrs={'cellpadding':'0'}),
dict(name='span', attrs={'class':'liketext'}),
] ]
no_stylesheets = True no_stylesheets = True
feeds = [(u'Onda Rock', u'http://www.ondarock.it/feed.php')] feeds = [(u'Onda Rock', u'http://www.ondarock.it/feed.php')]
masthead_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/71135_45820579767_4993043_n.jpg' masthead_url = 'http://api.ning.com/files/4ot8ampp*-rYQuwL2NoaHvVqcyu7VMyWyan12a9QMsJUWxk-q5V1-34wnD-Wj9B5qWjc1yPMLGiwQg8hZJxaySeaG2lx8hpV/2009_banner_ondarock.gif'
extra_css = '''
.boxtabscontain_page {border: 1px solid #E0E0E0;clear: both;font-family: "Verdana", "Arial", "Helvetica", sans-serif;font-size: 10px;line-height: 17px;margin: 0px 0px 20px;padding: 10px 10px 10px 40px;position: relative;top: -1px;width: 258px;z-index: 1;}
'''

View File

@ -11,7 +11,7 @@ import subprocess, tempfile, os, time
from setup import Command, installer_name from setup import Command, installer_name
from setup.build_environment import HOST, PROJECT from setup.build_environment import HOST, PROJECT
BASE_RSYNC = ['rsync', '-avz', '--delete'] BASE_RSYNC = ['rsync', '-avz', '--delete', '--force']
EXCLUDES = [] EXCLUDES = []
for x in [ for x in [
'src/calibre/plugins', 'src/calibre/manual', 'src/calibre/trac', 'src/calibre/plugins', 'src/calibre/manual', 'src/calibre/trac',

View File

@ -12,7 +12,7 @@ import sys, os, shutil, platform, subprocess, stat, py_compile, glob, \
from setup import Command, modules, basenames, functions, __version__, \ from setup import Command, modules, basenames, functions, __version__, \
__appname__ __appname__
SITE_PACKAGES = ['IPython', 'PIL', 'dateutil', 'dns', 'PyQt4', 'mechanize', SITE_PACKAGES = ['PIL', 'dateutil', 'dns', 'PyQt4', 'mechanize',
'sip.so', 'BeautifulSoup.py', 'cssutils', 'encutils', 'lxml', 'sip.so', 'BeautifulSoup.py', 'cssutils', 'encutils', 'lxml',
'sipconfig.py', 'xdg', 'dbus', '_dbus_bindings.so', 'dbus_bindings.py', 'sipconfig.py', 'xdg', 'dbus', '_dbus_bindings.so', 'dbus_bindings.py',
'_dbus_glib_bindings.so'] '_dbus_glib_bindings.so']

View File

@ -364,6 +364,7 @@ class Py2App(object):
'application. Visit http://calibre-ebook.com for details.'), 'application. Visit http://calibre-ebook.com for details.'),
CFBundleIconFile='library.icns', CFBundleIconFile='library.icns',
LSMultipleInstancesProhibited=True, LSMultipleInstancesProhibited=True,
NSHighResolutionCapable=True,
LSEnvironment=env LSEnvironment=env
) )
plistlib.writePlist(pl, join(self.contents_dir, 'Info.plist')) plistlib.writePlist(pl, join(self.contents_dir, 'Info.plist'))
@ -385,7 +386,7 @@ class Py2App(object):
@flush @flush
def add_poppler(self): def add_poppler(self):
info('\nAdding poppler') info('\nAdding poppler')
for x in ('libpoppler.25.dylib',): for x in ('libpoppler.26.dylib',):
self.install_dylib(os.path.join(SW, 'lib', x)) self.install_dylib(os.path.join(SW, 'lib', x))
for x in ('pdftohtml', 'pdftoppm', 'pdfinfo'): for x in ('pdftohtml', 'pdftoppm', 'pdfinfo'):
self.install_dylib(os.path.join(SW, 'bin', x), False) self.install_dylib(os.path.join(SW, 'bin', x), False)
@ -482,10 +483,6 @@ class Py2App(object):
shutil.rmtree(tdir) shutil.rmtree(tdir)
shutil.rmtree(os.path.join(self.site_packages, 'calibre', 'plugins')) shutil.rmtree(os.path.join(self.site_packages, 'calibre', 'plugins'))
self.remove_bytecode(join(self.resources_dir, 'Python', 'site-packages')) self.remove_bytecode(join(self.resources_dir, 'Python', 'site-packages'))
# Create dummy IPython README_STARTUP
with open(join(self.site_packages,
'IPython/config/profile/README_STARTUP'), 'wb') as f:
f.write('\n')
@flush @flush
def add_modules_from_dir(self, src): def add_modules_from_dir(self, src):

View File

@ -23,6 +23,7 @@ SW = r'C:\cygwin\home\kovid\sw'
IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.7.6', IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.7.6',
'VisualMagick', 'bin') 'VisualMagick', 'bin')
CRT = r'C:\Microsoft.VC90.CRT' CRT = r'C:\Microsoft.VC90.CRT'
SIGNTOOL = r'C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\signtool.exe'
VERSION = re.sub('[a-z]\d+', '', __version__) VERSION = re.sub('[a-z]\d+', '', __version__)
WINVER = VERSION+'.0' WINVER = VERSION+'.0'

View File

@ -30,7 +30,7 @@ If there are no windows binaries already compiled for the version of python you
Run the following command to install python dependencies:: Run the following command to install python dependencies::
easy_install --always-unzip -U ipython mechanize pyreadline python-dateutil dnspython cssutils clientform pycrypto easy_install --always-unzip -U mechanize pyreadline python-dateutil dnspython cssutils clientform pycrypto
Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly) Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly)
@ -146,7 +146,7 @@ Run bash ./runConfigureICU Cygwin/MSVC
Run make (note that you must have GNU make installed in cygwin) Run make (note that you must have GNU make installed in cygwin)
Optionally run make test Optionally run make check
Libunrar Libunrar
---------- ----------

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = u'calibre' __appname__ = u'calibre'
numeric_version = (0, 8, 59) numeric_version = (0, 8, 60)
__version__ = u'.'.join(map(unicode, numeric_version)) __version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>" __author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -1262,7 +1262,6 @@ class StoreBNStore(StoreBase):
headquarters = 'US' headquarters = 'US'
formats = ['NOOK'] formats = ['NOOK']
affiliate = True
class StoreBeamEBooksDEStore(StoreBase): class StoreBeamEBooksDEStore(StoreBase):
name = 'Beam EBooks DE' name = 'Beam EBooks DE'

View File

@ -81,6 +81,9 @@ class DBPrefs(dict): # {{{
def to_raw(self, val): def to_raw(self, val):
return json.dumps(val, indent=2, default=to_json) return json.dumps(val, indent=2, default=to_json)
def has_setting(self, key):
return key in self
def __getitem__(self, key): def __getitem__(self, key):
try: try:
return dict.__getitem__(self, key) return dict.__getitem__(self, key)
@ -102,6 +105,53 @@ class DBPrefs(dict): # {{{
def set(self, key, val): def set(self, key, val):
self.__setitem__(key, val) self.__setitem__(key, val)
def get_namespaced(self, namespace, key, default=None):
key = u'namespaced:%s:%s'%(namespace, key)
try:
return dict.__getitem__(self, key)
except KeyError:
return default
def set_namespaced(self, namespace, key, val):
if u':' in key: raise KeyError('Colons are not allowed in keys')
if u':' in namespace: raise KeyError('Colons are not allowed in'
' the namespace')
key = u'namespaced:%s:%s'%(namespace, key)
self[key] = val
def write_serialized(self, library_path):
try:
to_filename = os.path.join(library_path, 'metadata_db_prefs_backup.json')
with open(to_filename, "wb") as f:
f.write(json.dumps(self, indent=2, default=to_json))
except:
import traceback
traceback.print_exc()
@classmethod
def read_serialized(cls, library_path, recreate_prefs=False):
try:
from_filename = os.path.join(library_path,
'metadata_db_prefs_backup.json')
with open(from_filename, "rb") as f:
d = json.load(f, object_hook=from_json)
if not recreate_prefs:
return d
cls.clear()
cls.db.conn.execute('DELETE FROM preferences')
for k,v in d.iteritems():
raw = cls.to_raw(v)
cls.db.conn.execute(
'INSERT INTO preferences (key,val) VALUES (?,?)', (k, raw))
cls.db.conn.commit()
cls.clear()
cls.update(d)
return d
except:
import traceback
traceback.print_exc()
raise
return None
# }}} # }}}
# Extra collators {{{ # Extra collators {{{
@ -350,6 +400,23 @@ class DB(object):
defs['gui_restriction'] = defs['cs_restriction'] = '' defs['gui_restriction'] = defs['cs_restriction'] = ''
defs['categories_using_hierarchy'] = [] defs['categories_using_hierarchy'] = []
defs['column_color_rules'] = [] defs['column_color_rules'] = []
defs['grouped_search_make_user_categories'] = []
defs['similar_authors_search_key'] = 'authors'
defs['similar_authors_match_kind'] = 'match_any'
defs['similar_publisher_search_key'] = 'publisher'
defs['similar_publisher_match_kind'] = 'match_any'
defs['similar_tags_search_key'] = 'tags'
defs['similar_tags_match_kind'] = 'match_all'
defs['similar_series_search_key'] = 'series'
defs['similar_series_match_kind'] = 'match_any'
defs['book_display_fields'] = [
('title', False), ('authors', True), ('formats', True),
('series', True), ('identifiers', True), ('tags', True),
('path', True), ('publisher', False), ('rating', False),
('author_sort', False), ('sort', False), ('timestamp', False),
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
('last_modified', False), ('size', False), ('languages', False),
]
# Migrate the bool tristate tweak # Migrate the bool tristate tweak
defs['bools_are_tristate'] = \ defs['bools_are_tristate'] = \

View File

@ -60,6 +60,11 @@ Run an embedded python interpreter.
'editing tools, and then rebuilds the file from the edited HTML. ' 'editing tools, and then rebuilds the file from the edited HTML. '
'Makes no additional changes to the HTML, unlike a full calibre ' 'Makes no additional changes to the HTML, unlike a full calibre '
'conversion).') 'conversion).')
parser.add_option('-s', '--shutdown-running-calibre', default=False,
action='store_true',
help=_('Cause a running calibre instance, if any, to be'
' shutdown. Note that if there are running jobs, they '
'will be silently aborted, so use with care.'))
parser.add_option('--test-build', help='Test binary modules in build', parser.add_option('--test-build', help='Test binary modules in build',
action='store_true', default=False) action='store_true', default=False)
@ -258,6 +263,9 @@ def main(args=sys.argv):
elif opts.test_build: elif opts.test_build:
from calibre.test_build import test from calibre.test_build import test
test() test()
elif opts.shutdown_running_calibre:
from calibre.gui2.main import shutdown_other
shutdown_other()
else: else:
from calibre import ipython from calibre import ipython
ipython() ipython()

View File

@ -10,7 +10,7 @@ import cStringIO
from calibre.devices.usbms.driver import USBMS from calibre.devices.usbms.driver import USBMS
HTC_BCDS = [0x100, 0x0222, 0x0226, 0x227, 0x228] HTC_BCDS = [0x100, 0x0222, 0x0226, 0x227, 0x228, 0x229]
class ANDROID(USBMS): class ANDROID(USBMS):
@ -41,6 +41,7 @@ class ANDROID(USBMS):
0xca9 : HTC_BCDS, 0xca9 : HTC_BCDS,
0xcac : HTC_BCDS, 0xcac : HTC_BCDS,
0xccf : HTC_BCDS, 0xccf : HTC_BCDS,
0xce5 : HTC_BCDS,
0x2910 : HTC_BCDS, 0x2910 : HTC_BCDS,
0xff9 : HTC_BCDS + [0x9999], 0xff9 : HTC_BCDS + [0x9999],
}, },
@ -59,6 +60,7 @@ class ANDROID(USBMS):
0x42d6 : [0x216], 0x42d6 : [0x216],
0x42d7 : [0x216], 0x42d7 : [0x216],
0x42f7 : [0x216], 0x42f7 : [0x216],
0x4365 : [0x216],
}, },
# Freescale # Freescale
0x15a2 : { 0x15a2 : {
@ -209,7 +211,7 @@ class ANDROID(USBMS):
'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', 'SGH-T989'] 'THINKPAD_TABLET', 'SGH-T989', 'YP-G70']
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

@ -413,7 +413,8 @@ class KINDLE2(KINDLE):
if not opts.extra_customization[self.OPT_APNX]: if not opts.extra_customization[self.OPT_APNX]:
return return
if os.path.splitext(filepath.lower())[1] not in ('.azw', '.mobi', '.prc'): if os.path.splitext(filepath.lower())[1] not in ('.azw', '.mobi',
'.prc', '.azw3'):
return return
# Create the sidecar folder if necessary # Create the sidecar folder if necessary

View File

@ -23,10 +23,11 @@ class KOBO(USBMS):
gui_name = 'Kobo Reader' gui_name = 'Kobo Reader'
description = _('Communicate with the Kobo Reader') description = _('Communicate with the Kobo Reader')
author = 'Timothy Legge' author = 'Timothy Legge'
version = (1, 0, 12) version = (1, 0, 13)
dbversion = 0 dbversion = 0
fwversion = 0 fwversion = 0
supported_dbversion = 33
has_kepubs = False has_kepubs = False
supported_platforms = ['windows', 'osx', 'linux'] supported_platforms = ['windows', 'osx', 'linux']
@ -73,6 +74,12 @@ class KOBO(USBMS):
':::'+_('Kobo now shows recommendations on the device. In some case these have ' ':::'+_('Kobo now shows recommendations on the device. In some case these have '
'files but in other cases they are just pointers to the web site to buy. ' 'files but in other cases they are just pointers to the web site to buy. '
'Enable if you wish to see/delete them.'), 'Enable if you wish to see/delete them.'),
_('Attempt to support newer firmware') +
':::'+_('Kobo routinely updates the firmware and the '
'database version. With this option Calibre will attempt '
'to perform full read-write functionality - Here be Dragons!! '
'Enable only if you are comfortable with restoring your kobo '
'to factory defaults and testing software'),
] ]
EXTRA_CUSTOMIZATION_DEFAULT = [ EXTRA_CUSTOMIZATION_DEFAULT = [
@ -81,6 +88,7 @@ class KOBO(USBMS):
True, True,
True, True,
False, False,
False,
False False
] ]
@ -90,6 +98,7 @@ class KOBO(USBMS):
OPT_SHOW_EXPIRED_BOOK_RECORDS = 3 OPT_SHOW_EXPIRED_BOOK_RECORDS = 3
OPT_SHOW_PREVIEWS = 4 OPT_SHOW_PREVIEWS = 4
OPT_SHOW_RECOMMENDATIONS = 5 OPT_SHOW_RECOMMENDATIONS = 5
OPT_SUPPORT_NEWER_FIRMWARE = 6
def initialize(self): def initialize(self):
USBMS.initialize(self) USBMS.initialize(self)
@ -238,15 +247,6 @@ class KOBO(USBMS):
cursor = connection.cursor() cursor = connection.cursor()
#query = 'select count(distinct volumeId) from volume_shortcovers'
#cursor.execute(query)
#for row in (cursor):
# numrows = row[0]
#cursor.close()
# Determine the database version
# 4 - Bluetooth Kobo Rev 2 (1.4)
# 8 - WIFI KOBO Rev 1
cursor.execute('select version from dbversion') cursor.execute('select version from dbversion')
result = cursor.fetchone() result = cursor.fetchone()
self.dbversion = result[0] self.dbversion = result[0]
@ -422,6 +422,9 @@ class KOBO(USBMS):
os.unlink(fpath) os.unlink(fpath)
def delete_books(self, paths, end_session=True): def delete_books(self, paths, end_session=True):
if self.modify_database_check("delete_books") == False:
return
for i, path in enumerate(paths): for i, path in enumerate(paths):
self.report_progress((i+1) / float(len(paths)), _('Removing books from device...')) self.report_progress((i+1) / float(len(paths)), _('Removing books from device...'))
path = self.normalize_path(path) path = self.normalize_path(path)
@ -458,6 +461,9 @@ class KOBO(USBMS):
self.report_progress(1.0, _('Removing books from device...')) self.report_progress(1.0, _('Removing books from device...'))
def remove_books_from_metadata(self, paths, booklists): def remove_books_from_metadata(self, paths, booklists):
if self.modify_datbase_check("remove_books_from_metatata") == False:
return
for i, path in enumerate(paths): for i, path in enumerate(paths):
self.report_progress((i+1) / float(len(paths)), _('Removing books from device metadata listing...')) self.report_progress((i+1) / float(len(paths)), _('Removing books from device metadata listing...'))
for bl in booklists: for bl in booklists:
@ -588,6 +594,34 @@ class KOBO(USBMS):
return path return path
def modify_database_check(self, function):
# Checks to see whether the database version is supported
# and whether the user has chosen to support the firmware version
if self.dbversion > self.supported_dbversion:
# Unsupported database
opts = self.settings()
if not opts.extra_customization[self.OPT_SUPPORT_NEWER_FIRMWARE]:
debug_print('The database has been upgraded past supported version')
debug_print('The database has been upgraded past supported version')
self.report_progress(1.0, _('Removing books from device...'))
from calibre.devices.errors import UserFeedback
raise UserFeedback(_("Kobo database version unsupported - See details"),
_('Your Kobo is running an updated firmware/database version '
'As Calibre has not been updated, database editing is disabled. '
'You can enable support for your Kobo in plugin preferences. '
'Doing so may require you to perform a factory reset. '
'before selecting the "Attempt to support newer firmware" option '
'you should be familiar with restoring your Kobo to factory defaults.'),
UserFeedback.WARN)
return False
else:
# The user chose to edit the database anyway
return True
else:
# Supported database version
return True
def get_file(self, path, *args, **kwargs): def get_file(self, path, *args, **kwargs):
tpath = self.munge_path(path) tpath = self.munge_path(path)
extension = os.path.splitext(tpath)[1] extension = os.path.splitext(tpath)[1]
@ -706,6 +740,9 @@ class KOBO(USBMS):
# debug_print(' Commit: Set FavouritesIndex') # debug_print(' Commit: Set FavouritesIndex')
def update_device_database_collections(self, booklists, collections_attributes, oncard): def update_device_database_collections(self, booklists, collections_attributes, oncard):
if self.modify_database_check("update_device_database_collections") == False:
return
# Only process categories in this list # Only process categories in this list
supportedcategories = { supportedcategories = {
"Im_Reading":1, "Im_Reading":1,

View File

@ -19,9 +19,9 @@ class TECLAST_K3(USBMS):
PRODUCT_ID = [0x3203] PRODUCT_ID = [0x3203]
BCD = [0x0000, 0x0100] BCD = [0x0000, 0x0100]
VENDOR_NAME = ['TECLAST', 'IMAGIN'] VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX']
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5', WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5',
'EREADER'] 'EREADER', 'USB-MSC']
MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card' STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card'

View File

@ -127,12 +127,13 @@ class Device(DeviceConfig, DevicePlugin):
if not prefix: if not prefix:
return 0, 0 return 0, 0
prefix = prefix[:-1] prefix = prefix[:-1]
import win32file import win32file, winerror
try: try:
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
win32file.GetDiskFreeSpace(prefix) win32file.GetDiskFreeSpace(prefix)
except Exception as err: except Exception as err:
if getattr(err, 'args', [None])[0] == 21: # Disk not ready if getattr(err, 'args', [None])[0] == winerror.ERROR_NOT_READY:
# Disk not ready
time.sleep(3) time.sleep(3)
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
win32file.GetDiskFreeSpace(prefix) win32file.GetDiskFreeSpace(prefix)

View File

@ -10,10 +10,15 @@ __docformat__ = 'restructuredtext en'
import re, codecs import re, codecs
ENCODING_PATS = [ ENCODING_PATS = [
# XML declaration
re.compile(r'<\?[^<>]+encoding\s*=\s*[\'"](.*?)[\'"][^<>]*>', re.compile(r'<\?[^<>]+encoding\s*=\s*[\'"](.*?)[\'"][^<>]*>',
re.IGNORECASE), re.IGNORECASE),
# HTML 4 Pragma directive
re.compile(r'''<meta\s+?[^<>]*?content\s*=\s*['"][^'"]*?charset=([-_a-z0-9]+)[^'"]*?['"][^<>]*>''', re.compile(r'''<meta\s+?[^<>]*?content\s*=\s*['"][^'"]*?charset=([-_a-z0-9]+)[^'"]*?['"][^<>]*>''',
re.IGNORECASE), re.IGNORECASE),
# HTML 5 charset
re.compile(r'''<meta\s+charset=['"]([-_a-z0-9]+)['"][^<>]*>''',
re.IGNORECASE),
] ]
ENTITY_PATTERN = re.compile(r'&(\S+?);') ENTITY_PATTERN = re.compile(r'&(\S+?);')

View File

@ -37,6 +37,11 @@ class HTMLZOutput(OutputFormatPlugin):
'external: Use an external CSS file that is linked in the document.\n' 'external: Use an external CSS file that is linked in the document.\n'
'inline: Place the CSS in the head section of the document.' 'inline: Place the CSS in the head section of the document.'
)), )),
OptionRecommendation(name='htmlz_title_filename',
recommended_value=False, level=OptionRecommendation.LOW,
help=_('If set this option causes the file name of the html file'
' inside the htmlz archive to be based on the book title.')
),
]) ])
def convert(self, oeb_book, output_path, input_plugin, opts, log): def convert(self, oeb_book, output_path, input_plugin, opts, log):
@ -44,6 +49,7 @@ class HTMLZOutput(OutputFormatPlugin):
from calibre.ebooks.oeb.base import OEB_IMAGES, SVG_MIME from calibre.ebooks.oeb.base import OEB_IMAGES, SVG_MIME
from calibre.ebooks.metadata.opf2 import OPF, metadata_to_opf from calibre.ebooks.metadata.opf2 import OPF, metadata_to_opf
from calibre.utils.zipfile import ZipFile from calibre.utils.zipfile import ZipFile
from calibre.utils.filenames import ascii_filename
# HTML # HTML
if opts.htmlz_css_type == 'inline': if opts.htmlz_css_type == 'inline':
@ -59,7 +65,10 @@ class HTMLZOutput(OutputFormatPlugin):
htmlizer = OEB2HTMLizer(log) htmlizer = OEB2HTMLizer(log)
html = htmlizer.oeb2html(oeb_book, opts) html = htmlizer.oeb2html(oeb_book, opts)
with open(os.path.join(tdir, u'index.html'), 'wb') as tf: fname = u'index'
if opts.htmlz_title_filename:
fname = ascii_filename(unicode(oeb_book.metadata.title[0]))
with open(os.path.join(tdir, fname+u'.html'), 'wb') as tf:
tf.write(html) tf.write(html)
# CSS # CSS

View File

@ -9,6 +9,8 @@ __docformat__ = 'restructuredtext en'
import re, os import re, os
from calibre.ebooks.chardet import strip_encoding_declarations
def update_internal_links(mobi8_reader): def update_internal_links(mobi8_reader):
# need to update all links that are internal which # need to update all links that are internal which
# are based on positions within the xhtml files **BEFORE** # are based on positions within the xhtml files **BEFORE**
@ -324,6 +326,8 @@ def expand_mobi8_markup(mobi8_reader, resource_map, log):
for i, part in enumerate(parts): for i, part in enumerate(parts):
pi = mobi8_reader.partinfo[i] pi = mobi8_reader.partinfo[i]
with open(os.path.join(pi.type, pi.filename), 'wb') as f: with open(os.path.join(pi.type, pi.filename), 'wb') as f:
part = strip_encoding_declarations(part)
part = part.replace('<head>', '<head><meta charset="UTF-8"/>', 1)
f.write(part.encode('utf-8')) f.write(part.encode('utf-8'))
spine.append(f.name) spine.append(f.name)

View File

@ -89,14 +89,6 @@ gprefs.defaults['tags_browser_partition_method'] = 'first letter'
gprefs.defaults['tags_browser_collapse_at'] = 100 gprefs.defaults['tags_browser_collapse_at'] = 100
gprefs.defaults['tag_browser_dont_collapse'] = [] gprefs.defaults['tag_browser_dont_collapse'] = []
gprefs.defaults['edit_metadata_single_layout'] = 'default' gprefs.defaults['edit_metadata_single_layout'] = 'default'
gprefs.defaults['book_display_fields'] = [
('title', False), ('authors', True), ('formats', True),
('series', True), ('identifiers', True), ('tags', True),
('path', True), ('publisher', False), ('rating', False),
('author_sort', False), ('sort', False), ('timestamp', False),
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
('last_modified', False), ('size', False), ('languages', False),
]
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}' gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
gprefs.defaults['preserve_date_on_ctl'] = True gprefs.defaults['preserve_date_on_ctl'] = True
gprefs.defaults['cb_fullscreen'] = False gprefs.defaults['cb_fullscreen'] = False
@ -581,30 +573,31 @@ class FileDialog(QObject):
if not isinstance(initial_dir, basestring): if not isinstance(initial_dir, basestring):
initial_dir = os.path.expanduser(default_dir) initial_dir = os.path.expanduser(default_dir)
self.selected_files = [] self.selected_files = []
if mode == QFileDialog.AnyFile: with SanitizeLibraryPath():
f = unicode(QFileDialog.getSaveFileName(parent, title, initial_dir, ftext, "")) if mode == QFileDialog.AnyFile:
if f: f = unicode(QFileDialog.getSaveFileName(parent, title, initial_dir, ftext, ""))
self.selected_files.append(f) if f:
elif mode == QFileDialog.ExistingFile: self.selected_files.append(f)
f = unicode(QFileDialog.getOpenFileName(parent, title, initial_dir, ftext, "")) elif mode == QFileDialog.ExistingFile:
if f and os.path.exists(f): f = unicode(QFileDialog.getOpenFileName(parent, title, initial_dir, ftext, ""))
self.selected_files.append(f)
elif mode == QFileDialog.ExistingFiles:
fs = QFileDialog.getOpenFileNames(parent, title, initial_dir, ftext, "")
for f in fs:
f = unicode(f)
if not f: continue
if not os.path.exists(f):
# QFileDialog for some reason quotes spaces
# on linux if there is more than one space in a row
f = unquote(f)
if f and os.path.exists(f): if f and os.path.exists(f):
self.selected_files.append(f) self.selected_files.append(f)
else: elif mode == QFileDialog.ExistingFiles:
opts = QFileDialog.ShowDirsOnly if mode == QFileDialog.Directory else QFileDialog.Option() fs = QFileDialog.getOpenFileNames(parent, title, initial_dir, ftext, "")
f = unicode(QFileDialog.getExistingDirectory(parent, title, initial_dir, opts)) for f in fs:
if os.path.exists(f): f = unicode(f)
self.selected_files.append(f) if not f: continue
if not os.path.exists(f):
# QFileDialog for some reason quotes spaces
# on linux if there is more than one space in a row
f = unquote(f)
if f and os.path.exists(f):
self.selected_files.append(f)
else:
opts = QFileDialog.ShowDirsOnly if mode == QFileDialog.Directory else QFileDialog.Option()
f = unicode(QFileDialog.getExistingDirectory(parent, title, initial_dir, opts))
if os.path.exists(f):
self.selected_files.append(f)
if self.selected_files: if self.selected_files:
self.selected_files = [unicode(q) for q in self.selected_files] self.selected_files = [unicode(q) for q in self.selected_files]
saved_loc = self.selected_files[0] saved_loc = self.selected_files[0]
@ -734,11 +727,11 @@ gui_thread = None
qt_app = None qt_app = None
class Application(QApplication): class Application(QApplication):
def __init__(self, args, force_calibre_style=False): def __init__(self, args, force_calibre_style=False,
override_program_name=None):
self.file_event_hook = None self.file_event_hook = None
if islinux and args[0].endswith(u'calibre'): if override_program_name:
args = list(args) args = [override_program_name] + args[1:]
args[0] += '-gui'
qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args] qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args]
QApplication.__init__(self, qargs) QApplication.__init__(self, qargs)
global gui_thread, qt_app global gui_thread, qt_app
@ -857,16 +850,26 @@ class Application(QApplication):
_store_app = None _store_app = None
class SanitizeLibraryPath(object):
'''Remove the bundled calibre libraries from LD_LIBRARY_PATH on linux. This
is needed to prevent library conflicts when launching external utilities.'''
def __enter__(self):
self.orig = os.environ.get('LD_LIBRARY_PATH', '')
self.changed = False
paths = [x for x in self.orig.split(os.pathsep) if x]
if isfrozen and islinux and paths:
npaths = [x for x in paths if x != sys.frozen_path+'/lib']
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(npaths)
self.changed = True
def __exit__(self, *args):
if self.changed:
os.environ['LD_LIBRARY_PATH'] = self.orig
def open_url(qurl): def open_url(qurl):
paths = os.environ.get('LD_LIBRARY_PATH', with SanitizeLibraryPath():
'').split(os.pathsep) QDesktopServices.openUrl(qurl)
paths = [x for x in paths if x]
if isfrozen and islinux and paths:
npaths = [x for x in paths if x != sys.frozen_path+'/lib']
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(npaths)
QDesktopServices.openUrl(qurl)
if isfrozen and islinux and paths:
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(paths)
def get_current_db(): def get_current_db():
''' '''

View File

@ -74,9 +74,10 @@ class ShareConnMenu(QMenu): # {{{
action=self.toggle_server_action, group=gr) action=self.toggle_server_action, group=gr)
def server_state_changed(self, running): def server_state_changed(self, running):
from calibre.utils.mdns import get_external_ip
text = _('Start Content Server') text = _('Start Content Server')
if running: if running:
text = _('Stop Content Server') text = _('Stop Content Server') + ' [%s]'%get_external_ip()
self.toggle_server_action.setText(text) self.toggle_server_action.setText(text)
def build_email_entries(self, sync_menu): def build_email_entries(self, sync_menu):

View File

@ -84,7 +84,17 @@ def render_html(mi, css, vertical, widget, all_fields=False): # {{{
return ans return ans
def get_field_list(fm, use_defaults=False): def get_field_list(fm, use_defaults=False):
src = gprefs.defaults if use_defaults else gprefs from calibre.gui2.ui import get_gui
db = get_gui().current_db
if use_defaults:
src = db.prefs.defaults
else:
old_val = gprefs.get('book_display_fields', None)
if old_val is not None and not db.prefs.has_setting(
'book_display_fields'):
src = gprefs
else:
src = db.prefs
fieldlist = list(src['book_display_fields']) fieldlist = list(src['book_display_fields'])
names = frozenset([x[0] for x in fieldlist]) names = frozenset([x[0] for x in fieldlist])
for field in fm.displayable_field_keys(): for field in fm.displayable_field_keys():

View File

@ -5,11 +5,15 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
'''
WARNING: The code in this module is deprecated. Use complete2.py instead. This
code remains here for legacy plugin support.
'''
from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt,
QApplication, QCompleter) QApplication, QCompleter)
from calibre.utils.icu import sort_key, lower from calibre.utils.icu import sort_key
from calibre.gui2 import NONE from calibre.gui2 import NONE
from calibre.gui2.widgets import EnComboBox, LineEditECM from calibre.gui2.widgets import EnComboBox, LineEditECM
from calibre.utils.config_base import tweaks from calibre.utils.config_base import tweaks
@ -24,12 +28,11 @@ class CompleteModel(QAbstractListModel):
def set_items(self, items): def set_items(self, items):
items = [unicode(x.strip()) for x in items] items = [unicode(x.strip()) for x in items]
if len(items) < tweaks['completion_change_to_ascii_sorting']: if len(items) < tweaks['completion_change_to_ascii_sorting']:
self.items = sorted(items, key=lambda x: sort_key(x)) self.items = sorted(items, key=sort_key)
self.sorting = QCompleter.UnsortedModel self.sorting = QCompleter.UnsortedModel
else: else:
self.items = sorted(items, key=lambda x:x.lower()) self.items = sorted(items, key=lambda x:x.lower())
self.sorting = QCompleter.CaseInsensitivelySortedModel self.sorting = QCompleter.CaseInsensitivelySortedModel
self.lowered_items = [lower(x) for x in self.items]
self.reset() self.reset()
def rowCount(self, *args): def rowCount(self, *args):

View File

@ -0,0 +1,415 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import weakref
import sip
from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, pyqtSignal, QObject,
QApplication, QListView, QPoint, QModelIndex)
from calibre.utils.icu import sort_key, primary_startswith
from calibre.gui2 import NONE
from calibre.gui2.widgets import EnComboBox, LineEditECM
class CompleteModel(QAbstractListModel): # {{{
def __init__(self, parent=None):
QAbstractListModel.__init__(self, parent)
self.all_items = self.current_items = ()
self.current_prefix = ''
def set_items(self, items):
items = [unicode(x.strip()) for x in items]
items = [x for x in items if x]
items = tuple(sorted(items, key=sort_key))
self.all_items = self.current_items = items
self.reset()
def set_completion_prefix(self, prefix):
old_prefix = self.current_prefix
self.current_prefix = prefix
if prefix == old_prefix:
return
if not prefix:
self.current_items = self.all_items
self.reset()
return
subset = prefix.startswith(old_prefix)
universe = self.current_items if subset else self.all_items
self.current_items = tuple(x for x in universe if primary_startswith(x,
prefix))
self.reset()
def rowCount(self, *args):
return len(self.current_items)
def data(self, index, role):
if role == Qt.DisplayRole:
try:
return self.current_items[index.row()]
except IndexError:
pass
return NONE
def index_for_prefix(self, prefix):
for i, item in enumerate(self.current_items):
if primary_startswith(item, prefix):
return self.index(i)
# }}}
class Completer(QListView): # {{{
item_selected = pyqtSignal(object)
relayout_needed = pyqtSignal()
def __init__(self, completer_widget, max_visible_items=7):
QListView.__init__(self)
self.completer_widget = weakref.ref(completer_widget)
self.setWindowFlags(Qt.Popup)
self.max_visible_items = max_visible_items
self.setEditTriggers(self.NoEditTriggers)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setSelectionBehavior(self.SelectRows)
self.setSelectionMode(self.SingleSelection)
self.setAlternatingRowColors(True)
self.setModel(CompleteModel(self))
self.setMouseTracking(True)
self.entered.connect(self.item_entered)
self.activated.connect(self.item_chosen)
self.pressed.connect(self.item_chosen)
self.installEventFilter(self)
def hide(self):
self.setCurrentIndex(QModelIndex())
QListView.hide(self)
def item_chosen(self, index):
if not self.isVisible(): return
self.hide()
text = self.model().data(index, Qt.DisplayRole)
self.item_selected.emit(unicode(text))
def set_items(self, items):
self.model().set_items(items)
if self.isVisible():
self.relayout_needed.emit()
def set_completion_prefix(self, prefix):
self.model().set_completion_prefix(prefix)
if self.isVisible():
self.relayout_needed.emit()
def item_entered(self, idx):
self.setCurrentIndex(idx)
def next_match(self, previous=False):
c = self.currentIndex()
if c.isValid():
r = c.row()
else:
r = self.model().rowCount() if previous else -1
r = r + (-1 if previous else 1)
index = self.model().index(r % self.model().rowCount())
self.setCurrentIndex(index)
def scroll_to(self, orig):
if orig:
index = self.model().index_for_prefix(orig)
if index is not None and index.isValid():
self.setCurrentIndex(index)
def popup(self, select_first=True):
p = self
m = p.model()
widget = self.completer_widget()
if widget is None:
return
screen = QApplication.desktop().availableGeometry(widget)
h = (p.sizeHintForRow(0) * min(self.max_visible_items, m.rowCount()) +
3) + 3
hsb = p.horizontalScrollBar()
if hsb and hsb.isVisible():
h += hsb.sizeHint().height()
rh = widget.height()
pos = widget.mapToGlobal(QPoint(0, widget.height() - 2))
w = min(widget.width(), screen.width())
if (pos.x() + w) > (screen.x() + screen.width()):
pos.setX(screen.x() + screen.width() - w)
if pos.x() < screen.x():
pos.setX(screen.x())
top = pos.y() - rh - screen.top() + 2
bottom = screen.bottom() - pos.y()
h = max(h, p.minimumHeight())
if h > bottom:
h = min(max(top, bottom), h)
if top > bottom:
pos.setY(pos.y() - h - rh + 2)
p.setGeometry(pos.x(), pos.y(), w, h)
if (select_first and not self.currentIndex().isValid() and
self.model().rowCount() > 0):
self.setCurrentIndex(self.model().index(0))
if not p.isVisible():
p.show()
def eventFilter(self, obj, e):
'Redirect key presses from the popup to the widget'
widget = self.completer_widget()
if widget is None or sip.isdeleted(widget):
return False
etype = e.type()
if obj is not self:
return QObject.eventFilter(self, obj, e)
if etype == e.KeyPress:
key = e.key()
if key == Qt.Key_Escape:
self.hide()
e.accept()
return True
if key == Qt.Key_F4 and e.modifiers() & Qt.AltModifier:
self.hide()
e.accept()
return True
if key in (Qt.Key_Enter, Qt.Key_Return):
if not self.currentIndex().isValid():
self.hide()
e.accept()
return True
return False
if key in (Qt.Key_End, Qt.Key_Home, Qt.Key_Up, Qt.Key_Down,
Qt.Key_PageUp, Qt.Key_PageDown):
# Let the list view handle these keys
return False
if key in (Qt.Key_Tab, Qt.Key_Backtab):
self.next_match(previous=key == Qt.Key_Backtab)
e.accept()
return True
# Send to widget
widget.eat_focus_out = False
widget.keyPressEvent(e)
widget.eat_focus_out = True
if not widget.hasFocus():
# Widget lost focus hide the popup
self.hide()
if e.isAccepted():
return True
elif etype == e.MouseButtonPress:
if not self.underMouse():
self.hide()
e.accept()
return True
return False
# }}}
class LineEdit(QLineEdit, LineEditECM):
'''
A line edit that completes on multiple items separated by a
separator. Use the :meth:`update_items_cache` to set the list of
all possible completions. Separator can be controlled with the
:meth:`set_separator` and :meth:`set_space_before_sep` methods.
A call to self.set_separator(None) will allow this widget to be used
to complete non multiple fields as well.
'''
def __init__(self, parent=None, completer_widget=None):
QLineEdit.__init__(self, parent)
self.sep = ','
self.space_before_sep = False
self.add_separator = True
self.original_cursor_pos = None
completer_widget = (self if completer_widget is None else
completer_widget)
self.mcompleter = Completer(completer_widget)
self.mcompleter.item_selected.connect(self.completion_selected,
type=Qt.QueuedConnection)
self.mcompleter.relayout_needed.connect(self.relayout)
self.mcompleter.setFocusProxy(completer_widget)
self.textEdited.connect(self.text_edited)
self.no_popup = False
# Interface {{{
def update_items_cache(self, complete_items):
self.all_items = complete_items
def set_separator(self, sep):
self.sep = sep
def set_space_before_sep(self, space_before):
self.space_before_sep = space_before
def set_add_separator(self, what):
self.add_separator = bool(what)
@dynamic_property
def all_items(self):
def fget(self):
return self.mcompleter.model().all_items
def fset(self, items):
self.mcompleter.model().set_items(items)
return property(fget=fget, fset=fset)
# }}}
def complete(self, show_all=False, select_first=True):
orig = None
if show_all:
orig = self.mcompleter.model().current_prefix
self.mcompleter.set_completion_prefix('')
if not self.mcompleter.model().current_items:
self.mcompleter.hide()
return
self.mcompleter.popup(select_first=select_first)
self.mcompleter.scroll_to(orig)
def relayout(self):
self.mcompleter.popup()
def text_edited(self, *args):
if self.no_popup: return
self.update_completions()
select_first = len(self.mcompleter.model().current_prefix) > 0
self.complete(select_first=select_first)
def update_completions(self):
' Update the list of completions '
self.original_cursor_pos = cpos = self.cursorPosition()
text = unicode(self.text())
prefix = text[:cpos]
complete_prefix = prefix.lstrip()
if self.sep:
complete_prefix = prefix.split(self.sep)[-1].lstrip()
self.mcompleter.set_completion_prefix(complete_prefix)
def get_completed_text(self, text):
'Get completed text in before and after parts'
if self.sep is None:
return text, ''
else:
cursor_pos = self.original_cursor_pos
if cursor_pos is None:
cursor_pos = self.cursorPosition()
self.original_cursor_pos = None
# Split text
curtext = unicode(self.text())
before_text = curtext[:cursor_pos]
after_text = curtext[cursor_pos:].rstrip()
# Remove the completion prefix from the before text
before_text = self.sep.join(before_text.split(self.sep)[:-1]).rstrip()
if before_text:
# Add the separator to the end of before_text
if self.space_before_sep:
before_text += ' '
before_text += self.sep + ' '
if self.add_separator or after_text:
# Add separator to the end of completed text
if self.space_before_sep:
text = text.rstrip() + ' '
completed_text = text + self.sep + ' '
else:
completed_text = text
return before_text + completed_text, after_text
def completion_selected(self, text):
before_text, after_text = self.get_completed_text(unicode(text))
self.setText(before_text + after_text)
self.setCursorPosition(len(before_text))
class EditWithComplete(EnComboBox):
def __init__(self, *args):
EnComboBox.__init__(self, *args)
self.setLineEdit(LineEdit(self, completer_widget=self))
self.setCompleter(None)
self.eat_focus_out = True
self.installEventFilter(self)
# Interface {{{
def showPopup(self):
self.lineEdit().complete(show_all=True)
def update_items_cache(self, complete_items):
self.lineEdit().update_items_cache(complete_items)
def set_separator(self, sep):
self.lineEdit().set_separator(sep)
def set_space_before_sep(self, space_before):
self.lineEdit().set_space_before_sep(space_before)
def set_add_separator(self, what):
self.lineEdit().set_add_separator(what)
def show_initial_value(self, what):
what = unicode(what) if what else u''
le = self.lineEdit()
self.setEditText(what)
le.selectAll()
@dynamic_property
def all_items(self):
def fget(self): return self.lineEdit().all_items
def fset(self, val): self.lineEdit().all_items = val
return property(fget=fget, fset=fset)
# }}}
def text(self):
return unicode(self.lineEdit().text())
def setText(self, val):
le = self.lineEdit()
le.no_popup = True
le.setText(val)
le.no_popup = False
def setCursorPosition(self, *args):
self.lineEdit().setCursorPosition(*args)
@property
def textChanged(self):
return self.lineEdit().textChanged
def clear(self):
self.lineEdit().clear()
EnComboBox.clear(self)
def eventFilter(self, obj, e):
try:
c = self.lineEdit().mcompleter
except AttributeError:
return False
etype = e.type()
if self.eat_focus_out and self is obj and etype == e.FocusOut:
if c.isVisible():
return True
return EnComboBox.eventFilter(self, obj, e)
if __name__ == '__main__':
from PyQt4.Qt import QDialog, QVBoxLayout
app = QApplication([])
d = QDialog()
d.setLayout(QVBoxLayout())
le = EditWithComplete(d)
d.layout().addWidget(le)
items = ['one', 'otwo', 'othree', 'ooone', 'ootwo',
'oothree', 'a1', 'a2',u'Edgas', u'Èdgar', u'Édgaq', u'Edgar', u'Édgar']
le.update_items_cache(items)
le.show_initial_value('')
d.exec_()

View File

@ -17,7 +17,8 @@ class PluginWidget(Widget, Ui_Form):
ICON = I('mimetypes/html.png') ICON = I('mimetypes/html.png')
def __init__(self, parent, get_option, get_help, db=None, book_id=None): def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, ['htmlz_css_type', 'htmlz_class_style']) Widget.__init__(self, parent, ['htmlz_css_type', 'htmlz_class_style',
'htmlz_title_filename'])
self.db, self.book_id = db, book_id self.db, self.book_id = db, book_id
for x in get_option('htmlz_css_type').option.choices: for x in get_option('htmlz_css_type').option.choices:
self.opt_htmlz_css_type.addItem(x) self.opt_htmlz_css_type.addItem(x)

View File

@ -14,7 +14,7 @@
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="2" column="0"> <item row="3" column="0">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -27,6 +27,9 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="1" column="1">
<widget class="QComboBox" name="opt_htmlz_class_style"/>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@ -51,8 +54,12 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="2" column="0" colspan="2">
<widget class="QComboBox" name="opt_htmlz_class_style"/> <widget class="QCheckBox" name="opt_htmlz_title_filename">
<property name="text">
<string>Use book &amp;title as the filename for the HTML file inside the archive</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>

View File

@ -97,6 +97,9 @@ class MetadataWidget(Widget, Ui_Form):
else: else:
self.cover.setPixmap(QPixmap(I('default_cover.png'))) self.cover.setPixmap(QPixmap(I('default_cover.png')))
self.cover.setToolTip(_('This book has no cover')) self.cover.setToolTip(_('This book has no cover'))
for x in ('author', 'series', 'publisher'):
x = getattr(self, x)
x.lineEdit().deselect()
def set_cover_tooltip(self, pm): def set_cover_tooltip(self, pm):
tt = _('Cover size: %(width)d x %(height)d pixels') % dict( tt = _('Cover size: %(width)d x %(height)d pixels') % dict(

View File

@ -190,7 +190,7 @@
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="4" column="1">
<widget class="MultiCompleteLineEdit" name="tags"> <widget class="EditWithComplete" name="tags">
<property name="toolTip"> <property name="toolTip">
<string>Tags categorize the book. This is particularly useful while searching. &lt;br&gt;&lt;br&gt;They can be any words or phrases, separated by commas.</string> <string>Tags categorize the book. This is particularly useful while searching. &lt;br&gt;&lt;br&gt;They can be any words or phrases, separated by commas.</string>
</property> </property>
@ -213,7 +213,7 @@
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="5" column="1">
<widget class="MultiCompleteComboBox" name="series"> <widget class="EditWithComplete" name="series">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>10</horstretch> <horstretch>10</horstretch>
@ -248,14 +248,14 @@
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="MultiCompleteComboBox" name="publisher"> <widget class="EditWithComplete" name="publisher">
<property name="editable"> <property name="editable">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="MultiCompleteComboBox" name="author"> <widget class="EditWithComplete" name="author">
<property name="editable"> <property name="editable">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -277,14 +277,9 @@
<header>widgets.h</header> <header>widgets.h</header>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>MultiCompleteComboBox</class> <class>EditWithComplete</class>
<extends>QComboBox</extends> <extends>QComboBox</extends>
<header>calibre/gui2/complete.h</header> <header>calibre/gui2/complete2.h</header>
</customwidget>
<customwidget>
<class>MultiCompleteLineEdit</class>
<extends>QLineEdit</extends>
<header>calibre/gui2/complete.h</header>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>ImageView</class> <class>ImageView</class>

View File

@ -13,7 +13,7 @@ from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit,
QPushButton, QMessageBox, QToolButton QPushButton, QMessageBox, QToolButton
from calibre.utils.date import qt_to_dt, now from calibre.utils.date import qt_to_dt, now
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox from calibre.gui2.complete2 import EditWithComplete
from calibre.gui2.comments_editor import Editor as CommentsEditor from calibre.gui2.comments_editor import Editor as CommentsEditor
from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
@ -235,7 +235,7 @@ class MultipleWidget(QWidget):
layout.setSpacing(5) layout.setSpacing(5)
layout.setContentsMargins(0, 0, 0, 0) layout.setContentsMargins(0, 0, 0, 0)
self.tags_box = MultiCompleteLineEdit(parent) self.tags_box = EditWithComplete(parent)
layout.addWidget(self.tags_box, stretch=1000) layout.addWidget(self.tags_box, stretch=1000)
self.editor_button = QToolButton(self) self.editor_button = QToolButton(self)
self.editor_button.setToolTip(_('Open Item Editor')) self.editor_button.setToolTip(_('Open Item Editor'))
@ -293,7 +293,7 @@ class Text(Base):
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
w.get_editor_button().clicked.connect(self.edit) w.get_editor_button().clicked.connect(self.edit)
else: else:
w = MultiCompleteComboBox(parent) w = EditWithComplete(parent)
w.set_separator(None) w.set_separator(None)
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
w.setMinimumContentsLength(25) w.setMinimumContentsLength(25)
@ -363,7 +363,7 @@ class Text(Base):
class Series(Base): class Series(Base):
def setup_ui(self, parent): def setup_ui(self, parent):
w = MultiCompleteComboBox(parent) w = EditWithComplete(parent)
w.set_separator(None) w.set_separator(None)
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
w.setMinimumContentsLength(25) w.setMinimumContentsLength(25)
@ -807,7 +807,7 @@ class BulkDateTime(BulkBase):
class BulkSeries(BulkBase): class BulkSeries(BulkBase):
def setup_ui(self, parent): def setup_ui(self, parent):
self.make_widgets(parent, MultiCompleteComboBox) self.make_widgets(parent, EditWithComplete)
values = self.all_values = list(self.db.all_custom(num=self.col_id)) values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key) values.sort(key=sort_key)
self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon) self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
@ -934,7 +934,7 @@ class RemoveTags(QWidget):
layout.setSpacing(5) layout.setSpacing(5)
layout.setContentsMargins(0, 0, 0, 0) layout.setContentsMargins(0, 0, 0, 0)
self.tags_box = MultiCompleteLineEdit(parent) self.tags_box = EditWithComplete(parent)
self.tags_box.update_items_cache(values) self.tags_box.update_items_cache(values)
layout.addWidget(self.tags_box, stretch=3) layout.addWidget(self.tags_box, stretch=3)
self.checkbox = QCheckBox(_('Remove all tags'), parent) self.checkbox = QCheckBox(_('Remove all tags'), parent)
@ -956,7 +956,7 @@ class BulkText(BulkBase):
values = self.all_values = list(self.db.all_custom(num=self.col_id)) values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key) values.sort(key=sort_key)
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
self.make_widgets(parent, MultiCompleteLineEdit, self.make_widgets(parent, EditWithComplete,
extra_label_text=_('tags to add')) extra_label_text=_('tags to add'))
self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.adding_widget = self.main_widget self.adding_widget = self.main_widget
@ -976,7 +976,7 @@ class BulkText(BulkBase):
self.main_widget.set_add_separator( self.main_widget.set_add_separator(
tweaks['authors_completer_append_separator']) tweaks['authors_completer_append_separator'])
else: else:
self.make_widgets(parent, MultiCompleteComboBox) self.make_widgets(parent, EditWithComplete)
self.main_widget.set_separator(None) self.main_widget.set_separator(None)
self.main_widget.setSizeAdjustPolicy( self.main_widget.setSizeAdjustPolicy(
self.main_widget.AdjustToMinimumContentsLengthWithIcon) self.main_widget.AdjustToMinimumContentsLengthWithIcon)

View File

@ -7,7 +7,7 @@ __license__ = 'GPL v3'
from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \ from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \
QApplication, QSpinBox, QToolButton, QIcon QApplication, QSpinBox, QToolButton, QIcon
from calibre.ebooks.metadata import string_to_authors from calibre.ebooks.metadata import string_to_authors
from calibre.gui2.complete import MultiCompleteComboBox from calibre.gui2.complete2 import EditWithComplete
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
class AddEmptyBookDialog(QDialog): class AddEmptyBookDialog(QDialog):
@ -32,7 +32,7 @@ class AddEmptyBookDialog(QDialog):
self.author_label = QLabel(_('Set the author of the new books to:')) self.author_label = QLabel(_('Set the author of the new books to:'))
self._layout.addWidget(self.author_label, 2, 0, 1, 2) self._layout.addWidget(self.author_label, 2, 0, 1, 2)
self.authors_combo = MultiCompleteComboBox(self) self.authors_combo = EditWithComplete(self)
self.authors_combo.setSizeAdjustPolicy( self.authors_combo.setSizeAdjustPolicy(
self.authors_combo.AdjustToMinimumContentsLengthWithIcon) self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
self.authors_combo.setEditable(True) self.authors_combo.setEditable(True)

View File

@ -76,7 +76,7 @@
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="MultiCompleteComboBox" name="authors"> <widget class="EditWithComplete" name="authors">
<property name="editable"> <property name="editable">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -175,7 +175,7 @@
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="4" column="1">
<widget class="MultiCompleteComboBox" name="publisher"> <widget class="EditWithComplete" name="publisher">
<property name="editable"> <property name="editable">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -195,7 +195,7 @@
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="5" column="1">
<widget class="MultiCompleteLineEdit" name="tags"> <widget class="EditWithComplete" name="tags">
<property name="toolTip"> <property name="toolTip">
<string>Tags categorize the book. This is particularly useful while searching. &lt;br&gt;&lt;br&gt;They can be any words or phrases, separated by commas.</string> <string>Tags categorize the book. This is particularly useful while searching. &lt;br&gt;&lt;br&gt;They can be any words or phrases, separated by commas.</string>
</property> </property>
@ -229,7 +229,7 @@
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="6" column="1">
<widget class="MultiCompleteLineEdit" name="remove_tags"> <widget class="EditWithComplete" name="remove_tags">
<property name="toolTip"> <property name="toolTip">
<string>Comma separated list of tags to remove from the books. </string> <string>Comma separated list of tags to remove from the books. </string>
</property> </property>
@ -262,7 +262,7 @@
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="7" column="1">
<widget class="MultiCompleteComboBox" name="series"> <widget class="EditWithComplete" name="series">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -1181,14 +1181,9 @@ not multiple and the destination field is multiple</string>
<header>widgets.h</header> <header>widgets.h</header>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>MultiCompleteComboBox</class> <class>EditWithComplete</class>
<extends>QComboBox</extends> <extends>QComboBox</extends>
<header>calibre/gui2/complete.h</header> <header>calibre/gui2/complete2.h</header>
</customwidget>
<customwidget>
<class>MultiCompleteLineEdit</class>
<extends>QLineEdit</extends>
<header>calibre/gui2/complete.h</header>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>HistoryLineEdit</class> <class>HistoryLineEdit</class>

View File

@ -237,21 +237,21 @@
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="MultiCompleteComboBox" name="authors_box"> <widget class="EditWithComplete" name="authors_box">
<property name="toolTip"> <property name="toolTip">
<string>Enter an author's name. Only one author can be used.</string> <string>Enter an author's name. Only one author can be used.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="MultiCompleteComboBox" name="series_box"> <widget class="EditWithComplete" name="series_box">
<property name="toolTip"> <property name="toolTip">
<string>Enter a series name, without an index. Only one series name can be used.</string> <string>Enter a series name, without an index. Only one series name can be used.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="4" column="1">
<widget class="MultiCompleteLineEdit" name="tags_box"> <widget class="EditWithComplete" name="tags_box">
<property name="toolTip"> <property name="toolTip">
<string>Enter tags separated by spaces</string> <string>Enter tags separated by spaces</string>
</property> </property>
@ -327,14 +327,9 @@
<header>widgets.h</header> <header>widgets.h</header>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>MultiCompleteLineEdit</class> <class>EditWithComplete</class>
<extends>QLineEdit</extends>
<header>calibre/gui2/complete.h</header>
</customwidget>
<customwidget>
<class>MultiCompleteComboBox</class>
<extends>QComboBox</extends> <extends>QComboBox</extends>
<header>calibre/gui2/complete.h</header> <header>calibre/gui2/complete2.h</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>

View File

@ -7,14 +7,14 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from calibre.gui2.complete import MultiCompleteComboBox from calibre.gui2.complete2 import EditWithComplete
from calibre.utils.localization import lang_map from calibre.utils.localization import lang_map
from calibre.utils.icu import sort_key, lower from calibre.utils.icu import sort_key, lower
class LanguagesEdit(MultiCompleteComboBox): class LanguagesEdit(EditWithComplete):
def __init__(self, parent=None, db=None): def __init__(self, parent=None, db=None):
MultiCompleteComboBox.__init__(self, parent) EditWithComplete.__init__(self, parent)
self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon) self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
self.setMinimumContentsLength(20) self.setMinimumContentsLength(20)

View File

@ -14,7 +14,7 @@ from PyQt4.Qt import (Qt, QApplication, QStyle, QIcon, QDoubleSpinBox,
from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog, rating_font from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog, rating_font
from calibre.constants import iswindows from calibre.constants import iswindows
from calibre.gui2.widgets import EnLineEdit from calibre.gui2.widgets import EnLineEdit
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox from calibre.gui2.complete2 import EditWithComplete
from calibre.utils.date import now, format_date, qt_to_dt from calibre.utils.date import now, format_date, qt_to_dt
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre.utils.formatter import validation_formatter from calibre.utils.formatter import validation_formatter
@ -121,7 +121,7 @@ class TextDelegate(QStyledItemDelegate): # {{{
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
if self.auto_complete_function: if self.auto_complete_function:
editor = MultiCompleteComboBox(parent) editor = EditWithComplete(parent)
editor.set_separator(None) editor.set_separator(None)
complete_items = [i[1] for i in self.auto_complete_function()] complete_items = [i[1] for i in self.auto_complete_function()]
editor.update_items_cache(complete_items) editor.update_items_cache(complete_items)
@ -132,7 +132,7 @@ class TextDelegate(QStyledItemDelegate): # {{{
return editor return editor
def setModelData(self, editor, model, index): def setModelData(self, editor, model, index):
if isinstance(editor, MultiCompleteComboBox): if isinstance(editor, EditWithComplete):
val = editor.lineEdit().text() val = editor.lineEdit().text()
model.setData(index, QVariant(val), Qt.EditRole) model.setData(index, QVariant(val), Qt.EditRole)
else: else:
@ -153,7 +153,7 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
if self.db and hasattr(self.db, self.items_func_name): if self.db and hasattr(self.db, self.items_func_name):
col = index.model().column_map[index.column()] col = index.model().column_map[index.column()]
editor = MultiCompleteComboBox(parent) editor = EditWithComplete(parent)
editor.set_separator(self.sep) editor.set_separator(self.sep)
editor.set_space_before_sep(self.space_before_sep) editor.set_space_before_sep(self.space_before_sep)
if self.sep == '&': if self.sep == '&':
@ -171,7 +171,7 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
return editor return editor
def setModelData(self, editor, model, index): def setModelData(self, editor, model, index):
if isinstance(editor, MultiCompleteComboBox): if isinstance(editor, EditWithComplete):
val = editor.lineEdit().text() val = editor.lineEdit().text()
model.setData(index, QVariant(val), Qt.EditRole) model.setData(index, QVariant(val), Qt.EditRole)
else: else:
@ -244,7 +244,7 @@ class CcTextDelegate(QStyledItemDelegate): # {{{
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
m = index.model() m = index.model()
col = m.column_map[index.column()] col = m.column_map[index.column()]
editor = MultiCompleteLineEdit(parent) editor = EditWithComplete(parent)
editor.set_separator(None) editor.set_separator(None)
complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))), complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))),
key=sort_key) key=sort_key)

View File

@ -125,7 +125,7 @@ class BooksView(QTableView): # {{{
self.last_modified_delegate = DateDelegate(self, self.last_modified_delegate = DateDelegate(self,
tweak_name='gui_last_modified_display_format') tweak_name='gui_last_modified_display_format')
self.languages_delegate = LanguagesDelegate(self) self.languages_delegate = LanguagesDelegate(self)
self.tags_delegate = CompleteDelegate(self, ',', 'all_tags') self.tags_delegate = CompleteDelegate(self, ',', 'all_tag_names')
self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True) self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True)
self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True) self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True)
self.series_delegate = TextDelegate(self) self.series_delegate = TextDelegate(self)

View File

@ -309,7 +309,8 @@ def main(args=sys.argv, logger=None):
return 1 return 1
pid = os.fork() if (islinux or isbsd) else -1 pid = os.fork() if (islinux or isbsd) else -1
if pid <= 0: if pid <= 0:
app = Application(args) override = 'calibre-lrf-viewer' if islinux else None
app = Application(args, override_program_name=override)
app.setWindowIcon(QIcon(I('viewer.png'))) app.setWindowIcon(QIcon(I('viewer.png')))
QCoreApplication.setOrganizationName(ORG_NAME) QCoreApplication.setOrganizationName(ORG_NAME)
QCoreApplication.setApplicationName(APP_UID) QCoreApplication.setApplicationName(APP_UID)

View File

@ -8,7 +8,7 @@ from PyQt4.Qt import (QCoreApplication, QIcon, QObject, QTimer,
QPixmap, QSplashScreen, QApplication) QPixmap, QSplashScreen, QApplication)
from calibre import prints, plugins, force_unicode from calibre import prints, plugins, force_unicode
from calibre.constants import (iswindows, __appname__, isosx, DEBUG, from calibre.constants import (iswindows, __appname__, isosx, DEBUG, islinux,
filesystem_encoding) filesystem_encoding)
from calibre.utils.ipc import gui_socket_address, RC from calibre.utils.ipc import gui_socket_address, RC
from calibre.gui2 import (ORG_NAME, APP_UID, initialize_file_icon_provider, from calibre.gui2 import (ORG_NAME, APP_UID, initialize_file_icon_provider,
@ -58,7 +58,8 @@ def init_qt(args):
prints('Using library at', prefs['library_path']) prints('Using library at', prefs['library_path'])
QCoreApplication.setOrganizationName(ORG_NAME) QCoreApplication.setOrganizationName(ORG_NAME)
QCoreApplication.setApplicationName(APP_UID) QCoreApplication.setApplicationName(APP_UID)
app = Application(args) override = 'calibre-gui' if islinux else None
app = Application(args, override_program_name=override)
actions = tuple(Main.create_application_menubar()) actions = tuple(Main.create_application_menubar())
app.setWindowIcon(QIcon(I('lt.png'))) app.setWindowIcon(QIcon(I('lt.png')))
return app, opts, args, actions return app, opts, args, actions
@ -312,21 +313,39 @@ def cant_start(msg=_('If you are sure it is not running')+', ',
raise SystemExit(1) raise SystemExit(1)
def communicate(opts, args): def build_pipe(print_error=True):
t = RC() t = RC(print_error=print_error)
t.start() t.start()
time.sleep(3) t.join(3.0)
if not t.done: if t.is_alive():
f = os.path.expanduser('~/.calibre_calibre GUI.lock') if iswindows():
cant_start(what=_('try deleting the file')+': '+f) cant_start()
else:
f = os.path.expanduser('~/.calibre_calibre GUI.lock')
cant_start(what=_('try deleting the file')+': '+f)
raise SystemExit(1) raise SystemExit(1)
return t
def shutdown_other(rc=None):
if rc is None:
rc = build_pipe(print_error=False)
if rc.conn is None:
prints(_('No running calibre found'))
return # No running instance found
from calibre.utils.lock import singleinstance
rc.conn.send('shutdown:')
prints(_('Shutdown command sent, waiting for shutdown...'))
for i in xrange(50):
if singleinstance('calibre GUI'):
return
time.sleep(0.1)
prints(_('Failed to shutdown running calibre instance'))
raise SystemExit(1)
def communicate(opts, args):
t = build_pipe()
if opts.shutdown_running_calibre: if opts.shutdown_running_calibre:
t.conn.send('shutdown:') shutdown_other(t)
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])
@ -334,7 +353,6 @@ def communicate(opts, args):
t.conn.close() t.conn.close()
raise SystemExit(0) raise SystemExit(0)
def main(args=sys.argv): def main(args=sys.argv):
gui_debug = None gui_debug = None
if args[0] == '__CALIBRE_GUI_DEBUG__': if args[0] == '__CALIBRE_GUI_DEBUG__':

View File

@ -15,7 +15,6 @@ from PyQt4.Qt import (Qt, QDateTimeEdit, pyqtSignal, QMessageBox,
QPushButton, QSpinBox, QLineEdit, QSizePolicy, QDialogButtonBox, QAction) QPushButton, QSpinBox, QLineEdit, QSizePolicy, QDialogButtonBox, QAction)
from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.config import tweaks, prefs from calibre.utils.config import tweaks, prefs
from calibre.ebooks.metadata import (title_sort, authors_to_string, from calibre.ebooks.metadata import (title_sort, authors_to_string,
@ -23,6 +22,7 @@ from calibre.ebooks.metadata import (title_sort, authors_to_string,
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATETIME, from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATETIME,
choose_files, error_dialog, choose_images) choose_files, error_dialog, choose_images)
from calibre.gui2.complete2 import EditWithComplete
from calibre.utils.date import (local_tz, qt_to_dt, as_local_time, from calibre.utils.date import (local_tz, qt_to_dt, as_local_time,
UNDEFINED_DATE) UNDEFINED_DATE)
from calibre import strftime from calibre import strftime
@ -204,7 +204,7 @@ class TitleSortEdit(TitleEdit):
# }}} # }}}
# Authors {{{ # Authors {{{
class AuthorsEdit(MultiCompleteComboBox): class AuthorsEdit(EditWithComplete):
TOOLTIP = '' TOOLTIP = ''
LABEL = _('&Author(s):') LABEL = _('&Author(s):')
@ -212,7 +212,7 @@ class AuthorsEdit(MultiCompleteComboBox):
def __init__(self, parent, manage_authors): def __init__(self, parent, manage_authors):
self.dialog = parent self.dialog = parent
self.books_to_refresh = set([]) self.books_to_refresh = set([])
MultiCompleteComboBox.__init__(self, parent) EditWithComplete.__init__(self, parent)
self.setToolTip(self.TOOLTIP) self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP)
self.setEditable(True) self.setEditable(True)
@ -443,13 +443,13 @@ class AuthorSortEdit(EnLineEdit):
# }}} # }}}
# Series {{{ # Series {{{
class SeriesEdit(MultiCompleteComboBox): class SeriesEdit(EditWithComplete):
TOOLTIP = _('List of known series. You can add new series.') TOOLTIP = _('List of known series. You can add new series.')
LABEL = _('&Series:') LABEL = _('&Series:')
def __init__(self, parent): def __init__(self, parent):
MultiCompleteComboBox.__init__(self, parent) EditWithComplete.__init__(self, parent)
self.set_separator(None) self.set_separator(None)
self.dialog = parent self.dialog = parent
self.setSizeAdjustPolicy( self.setSizeAdjustPolicy(
@ -1086,14 +1086,14 @@ class RatingEdit(QSpinBox): # {{{
# }}} # }}}
class TagsEdit(MultiCompleteLineEdit): # {{{ class TagsEdit(EditWithComplete): # {{{
LABEL = _('Ta&gs:') LABEL = _('Ta&gs:')
TOOLTIP = '<p>'+_('Tags categorize the book. This is particularly ' TOOLTIP = '<p>'+_('Tags categorize the book. This is particularly '
'useful while searching. <br><br>They can be any words ' 'useful while searching. <br><br>They can be any words '
'or phrases, separated by commas.') 'or phrases, separated by commas.')
def __init__(self, parent): def __init__(self, parent):
MultiCompleteLineEdit.__init__(self, parent) EditWithComplete.__init__(self, parent)
self.books_to_refresh = set([]) self.books_to_refresh = set([])
self.setToolTip(self.TOOLTIP) self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP)
@ -1114,7 +1114,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
tags = db.tags(id_, index_is_id=True) tags = db.tags(id_, index_is_id=True)
tags = tags.split(',') if tags else [] tags = tags.split(',') if tags else []
self.current_val = tags self.current_val = tags
self.all_items = db.all_tags() self.all_items = db.all_tag_names()
self.original_val = self.current_val self.original_val = self.current_val
@property @property
@ -1327,11 +1327,11 @@ class ISBNDialog(QDialog) : # {{{
# }}} # }}}
class PublisherEdit(MultiCompleteComboBox): # {{{ class PublisherEdit(EditWithComplete): # {{{
LABEL = _('&Publisher:') LABEL = _('&Publisher:')
def __init__(self, parent): def __init__(self, parent):
MultiCompleteComboBox.__init__(self, parent) EditWithComplete.__init__(self, parent)
self.set_separator(None) self.set_separator(None)
self.setSizeAdjustPolicy( self.setSizeAdjustPolicy(
self.AdjustToMinimumContentsLengthWithIcon) self.AdjustToMinimumContentsLengthWithIcon)

View File

@ -13,6 +13,7 @@ from PyQt4.Qt import (QWidget, pyqtSignal, QCheckBox, QAbstractSpinBox,
from calibre.customize.ui import preferences_plugins from calibre.customize.ui import preferences_plugins
from calibre.utils.config import ConfigProxy from calibre.utils.config import ConfigProxy
from calibre.gui2.complete2 import EditWithComplete
class AbortCommit(Exception): class AbortCommit(Exception):
pass pass
@ -133,11 +134,15 @@ class Setting(object):
def initialize(self): def initialize(self):
self.gui_obj.blockSignals(True) self.gui_obj.blockSignals(True)
if self.datatype == 'choice': if self.datatype == 'choice':
self.gui_obj.clear() choices = self.choices or []
for x in self.choices: if isinstance(self.gui_obj, EditWithComplete):
if isinstance(x, basestring): self.gui_obj.all_items = choices
x = (x, x) else:
self.gui_obj.addItem(x[0], QVariant(x[1])) self.gui_obj.clear()
for x in choices:
if isinstance(x, basestring):
x = (x, x)
self.gui_obj.addItem(x[0], QVariant(x[1]))
self.set_gui_val(self.get_config_val(default=False)) self.set_gui_val(self.get_config_val(default=False))
self.gui_obj.blockSignals(False) self.gui_obj.blockSignals(False)
self.initial_value = self.get_gui_val() self.initial_value = self.get_gui_val()
@ -171,11 +176,14 @@ class Setting(object):
elif self.datatype == 'string': elif self.datatype == 'string':
self.gui_obj.setText(val if val else '') self.gui_obj.setText(val if val else '')
elif self.datatype == 'choice': elif self.datatype == 'choice':
idx = self.gui_obj.findData(QVariant(val), role=Qt.UserRole, if isinstance(self.gui_obj, EditWithComplete):
flags=self.CHOICES_SEARCH_FLAGS) self.gui_obj.setText(val)
if idx == -1: else:
idx = 0 idx = self.gui_obj.findData(QVariant(val), role=Qt.UserRole,
self.gui_obj.setCurrentIndex(idx) flags=self.CHOICES_SEARCH_FLAGS)
if idx == -1:
idx = 0
self.gui_obj.setCurrentIndex(idx)
def get_gui_val(self): def get_gui_val(self):
if self.datatype == 'bool': if self.datatype == 'bool':
@ -187,9 +195,12 @@ class Setting(object):
if self.empty_string_is_None and not val: if self.empty_string_is_None and not val:
val = None val = None
elif self.datatype == 'choice': elif self.datatype == 'choice':
idx = self.gui_obj.currentIndex() if isinstance(self.gui_obj, EditWithComplete):
if idx < 0: idx = 0 val = unicode(self.gui_obj.text())
val = unicode(self.gui_obj.itemData(idx).toString()) else:
idx = self.gui_obj.currentIndex()
if idx < 0: idx = 0
val = unicode(self.gui_obj.itemData(idx).toString())
return val return val
class CommaSeparatedList(Setting): class CommaSeparatedList(Setting):

View File

@ -75,7 +75,7 @@ class DisplayedFields(QAbstractListModel): # {{{
def commit(self): def commit(self):
if self.changed: if self.changed:
gprefs['book_display_fields'] = self.fields self.db.prefs['book_display_fields'] = self.fields
def move(self, idx, delta): def move(self, idx, delta):
row = idx.row() + delta row = idx.row() + delta

View File

@ -320,7 +320,7 @@ Manage Authors. You can use the values {author} and
</attribute> </attribute>
<layout class="QGridLayout" name="gridLayout_10"> <layout class="QGridLayout" name="gridLayout_10">
<item row="3" column="2" colspan="3"> <item row="3" column="2" colspan="3">
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy"> <widget class="EditWithComplete" name="opt_categories_using_hierarchy">
<property name="toolTip"> <property name="toolTip">
<string>A comma-separated list of categories in which items containing <string>A comma-separated list of categories in which items containing
periods are displayed in the tag browser trees. For example, if periods are displayed in the tag browser trees. For example, if
@ -397,7 +397,7 @@ up into subcategories. If the partition method is set to disable, this value is
</widget> </widget>
</item> </item>
<item row="1" column="3" colspan="2"> <item row="1" column="3" colspan="2">
<widget class="MultiCompleteLineEdit" name="opt_tag_browser_dont_collapse"> <widget class="EditWithComplete" name="opt_tag_browser_dont_collapse">
<property name="toolTip"> <property name="toolTip">
<string>A comma-separated list of categories that are not to <string>A comma-separated list of categories that are not to
be partitioned even if the number of items is larger than be partitioned even if the number of items is larger than
@ -506,9 +506,9 @@ a few top-level elements.</string>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
<class>MultiCompleteLineEdit</class> <class>EditWithComplete</class>
<extends>QLineEdit</extends> <extends>QComboBox</extends>
<header>calibre/gui2/complete.h</header> <header>calibre/gui2/complete2.h</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources> <resources>

View File

@ -98,7 +98,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
db.prefs.set('grouped_search_make_user_categories', []) db.prefs.set('grouped_search_make_user_categories', [])
r('grouped_search_make_user_categories', db.prefs, setting=CommaSeparatedList) r('grouped_search_make_user_categories', db.prefs, setting=CommaSeparatedList)
self.muc_changed = False self.muc_changed = False
self.opt_grouped_search_make_user_categories.editingFinished.connect( self.opt_grouped_search_make_user_categories.lineEdit().editingFinished.connect(
self.muc_box_changed) self.muc_box_changed)
def set_similar_fields(self, initial=False): def set_similar_fields(self, initial=False):
@ -184,6 +184,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.opt_grouped_search_make_user_categories.update_items_cache(terms) self.opt_grouped_search_make_user_categories.update_items_cache(terms)
self.gst_names.blockSignals(True) self.gst_names.blockSignals(True)
self.gst_names.clear() self.gst_names.clear()
print (1111, self.gst_names)
self.gst_names.addItem('', '') self.gst_names.addItem('', '')
for t in terms: for t in terms:
self.gst_names.addItem(t, t) self.gst_names.addItem(t, t)

View File

@ -69,7 +69,7 @@
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="MultiCompleteLineEdit" name="opt_limit_search_columns_to"/> <widget class="EditWithComplete" name="opt_limit_search_columns_to"/>
</item> </item>
<item row="5" column="0" colspan="2"> <item row="5" column="0" colspan="2">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
@ -134,7 +134,7 @@ a search term by changing the value box then pressing Save.</string>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="MultiCompleteLineEdit" name="gst_value"/> <widget class="EditWithComplete" name="gst_value"/>
</item> </item>
<item> <item>
<widget class="QToolButton" name="gst_save_button"> <widget class="QToolButton" name="gst_save_button">
@ -173,7 +173,7 @@ of a search term by changing the value box then pressing Save.</string>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="MultiCompleteLineEdit" name="opt_grouped_search_make_user_categories"> <widget class="EditWithComplete" name="opt_grouped_search_make_user_categories">
<property name="toolTip"> <property name="toolTip">
<string>Enter the names of any grouped search terms you wish <string>Enter the names of any grouped search terms you wish
to be shown as user categories</string> to be shown as user categories</string>
@ -301,9 +301,9 @@ to be shown as user categories</string>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
<class>MultiCompleteLineEdit</class> <class>EditWithComplete</class>
<extends>QLineEdit</extends> <extends>QComboBox</extends>
<header>calibre/gui2/complete.h</header> <header>calibre/gui2/complete2.h</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources> <resources>

View File

@ -6,7 +6,6 @@ __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>' __copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import random
import urllib import urllib
from contextlib import closing from contextlib import closing
@ -24,26 +23,12 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog
class BNStore(BasicStoreConfig, StorePlugin): class BNStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False): def open(self, parent=None, detail_item=None, external=False):
pub_id = 'sHa5EXvYOwA' url = "http://bn.com"
# Use Kovid's affiliate id 30% of the time.
if random.randint(1, 10) in (1, 2, 3):
pub_id = '0dsO3kDu/AU'
murl = 'http://click.linksynergy.com/fs-bin/click?id=%s&offerid=239662.13&type=3&subid=0' % pub_id
if detail_item:
purl = 'http://click.linksynergy.com/fs-bin/click?id=%s&subid=&offerid=239662.%s&type=2&subid=0' % (pub_id, detail_item)
url = purl
else:
purl = None
url = murl
#print(url)
if external or self.config.get('open_external', False): if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(url))) open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
else: else:
d = WebStoreDialog(self.gui, murl, parent, purl) d = WebStoreDialog(self.gui, url, parent, detail_item)
d.setWindowTitle(self.name) d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', '')) d.set_tags(self.config.get('tags', ''))
d.exec_() d.exec_()
@ -60,7 +45,7 @@ class BNStore(BasicStoreConfig, StorePlugin):
if counter <= 0: if counter <= 0:
break break
id = ''.join(data.xpath('.//div[contains(@class, "display-tile-item")]/@data-bn-ean')) id = ''.join(data.xpath('.//div[contains(@class, "image-bounding-box")]/a/@href'))
if not id: if not id:
continue continue

View File

@ -11,7 +11,6 @@ import cPickle, os
from PyQt4.Qt import QDialog, QProgressDialog, QString, QTimer from PyQt4.Qt import QDialog, QProgressDialog, QString, QTimer
from calibre.constants import DEBUG
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2 import warning_dialog, question_dialog from calibre.gui2 import warning_dialog, question_dialog
from calibre.gui2.convert.single import NoSupportedInputFormats from calibre.gui2.convert.single import NoSupportedInputFormats

View File

@ -51,6 +51,8 @@ def config(defaults=None):
help=_('The amount by which to change the font size when clicking' help=_('The amount by which to change the font size when clicking'
' the font larger/smaller buttons. Should be a number between ' ' the font larger/smaller buttons. Should be a number between '
'0 and 1.')) '0 and 1.'))
c.add_opt('fullscreen_clock', default=False, action='store_true',
help=_('Show a clock in fullscreen mode.'))
fonts = c.add_group('FONTS', _('Font options')) fonts = c.add_group('FONTS', _('Font options'))
fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif', fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif',
@ -117,6 +119,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.hyphenate.setVisible(False) self.hyphenate.setVisible(False)
self.hyphenate_default_lang.setVisible(False) self.hyphenate_default_lang.setVisible(False)
self.hyphenate_label.setVisible(False) self.hyphenate_label.setVisible(False)
self.opt_fullscreen_clock.setChecked(opts.fullscreen_clock)
def accept(self, *args): def accept(self, *args):
if self.shortcut_config.is_editing: if self.shortcut_config.is_editing:
@ -148,6 +151,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
str(self.hyphenate_default_lang.itemData(idx).toString())) str(self.hyphenate_default_lang.itemData(idx).toString()))
c.set('line_scrolling_stops_on_pagebreaks', c.set('line_scrolling_stops_on_pagebreaks',
self.opt_line_scrolling_stops_on_pagebreaks.isChecked()) self.opt_line_scrolling_stops_on_pagebreaks.isChecked())
c.set('fullscreen_clock', self.opt_fullscreen_clock.isChecked())
return QDialog.accept(self, *args) return QDialog.accept(self, *args)

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>479</width> <width>839</width>
<height>630</height> <height>630</height>
</rect> </rect>
</property> </property>
@ -167,20 +167,6 @@
</item> </item>
<item> <item>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remember_window_size">
<property name="text">
<string>Remember last used &amp;window size and layout</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remember_current_page">
<property name="text">
<string>Remember the &amp;current page when quitting</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2"> <item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="hyphenate"> <widget class="QCheckBox" name="hyphenate">
<property name="text"> <property name="text">
@ -205,13 +191,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="opt_fit_images">
<property name="text">
<string>&amp;Resize images larger than the viewer window (needs restart)</string>
</property>
</widget>
</item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_11"> <widget class="QLabel" name="label_11">
<property name="text"> <property name="text">
@ -247,13 +226,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="10" column="0" colspan="2">
<widget class="QCheckBox" name="opt_wheel_flips_pages">
<property name="text">
<string>Mouse &amp;wheel flips pages</string>
</property>
</widget>
</item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="QSpinBox" name="max_fs_width"> <widget class="QSpinBox" name="max_fs_width">
<property name="toolTip"> <property name="toolTip">
@ -301,13 +273,48 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="11" column="0" colspan="2"> <item row="7" column="0">
<widget class="QCheckBox" name="opt_fit_images">
<property name="text">
<string>&amp;Resize images larger than the viewer window (needs restart)</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QCheckBox" name="opt_remember_window_size">
<property name="text">
<string>Remember last used &amp;window size and layout</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="opt_wheel_flips_pages">
<property name="text">
<string>Mouse &amp;wheel flips pages</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="opt_remember_current_page">
<property name="text">
<string>Remember the &amp;current page when quitting</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="opt_line_scrolling_stops_on_pagebreaks"> <widget class="QCheckBox" name="opt_line_scrolling_stops_on_pagebreaks">
<property name="text"> <property name="text">
<string>Line &amp;scrolling stops at page breaks</string> <string>Line &amp;scrolling stops at page breaks</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1">
<widget class="QCheckBox" name="opt_fullscreen_clock">
<property name="text">
<string>Show &amp;clock in full screen mode</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

View File

@ -134,6 +134,7 @@ class Document(QWebPage): # {{{
screen_width = QApplication.desktop().screenGeometry().width() screen_width = QApplication.desktop().screenGeometry().width()
# Leave some space for the scrollbar and some border # Leave some space for the scrollbar and some border
self.max_fs_width = min(opts.max_fs_width, screen_width-50) self.max_fs_width = min(opts.max_fs_width, screen_width-50)
self.fullscreen_clock = opts.fullscreen_clock
def fit_images(self): def fit_images(self):
if self.do_fit_images and not self.in_paged_mode: if self.do_fit_images and not self.in_paged_mode:
@ -193,6 +194,14 @@ class Document(QWebPage): # {{{
self.read_anchor_positions(use_cache=False) self.read_anchor_positions(use_cache=False)
self.first_load = False self.first_load = False
def colors(self):
self.javascript('''
bs = getComputedStyle(document.body);
py_bridge.value = [bs.backgroundColor, bs.color]
''')
ans = self.bridge_value
return (ans if isinstance(ans, list) else ['white', 'black'])
def read_anchor_positions(self, use_cache=True): def read_anchor_positions(self, use_cache=True):
self.bridge_value = tuple(self.index_anchors) self.bridge_value = tuple(self.index_anchors)
self.javascript(u''' self.javascript(u'''

View File

@ -6,9 +6,10 @@ from functools import partial
from threading import Thread from threading import Thread
from PyQt4.Qt import (QApplication, Qt, QIcon, QTimer, QByteArray, QSize, from PyQt4.Qt import (QApplication, Qt, QIcon, QTimer, QByteArray, QSize,
QDoubleSpinBox, QLabel, QTextBrowser, QPropertyAnimation, QPainter, QTime, QDoubleSpinBox, QLabel, QTextBrowser, QPropertyAnimation,
QBrush, QColor, pyqtSignal, QUrl, QRegExpValidator, QRegExp, QLineEdit, QPainter, QBrush, QColor, pyqtSignal, QUrl, QRegExpValidator, QRegExp,
QToolButton, QMenu, QInputDialog, QAction, QKeySequence, QModelIndex) QLineEdit, QToolButton, QMenu, QInputDialog, QAction, QKeySequence,
QModelIndex)
from calibre.gui2.viewer.main_ui import Ui_EbookViewer from calibre.gui2.viewer.main_ui import Ui_EbookViewer
from calibre.gui2.viewer.printing import Printing from calibre.gui2.viewer.printing import Printing
@ -288,6 +289,23 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.addAction(self.toggle_toolbar_action) self.addAction(self.toggle_toolbar_action)
self.full_screen_label_anim = QPropertyAnimation( self.full_screen_label_anim = QPropertyAnimation(
self.full_screen_label, 'size') self.full_screen_label, 'size')
self.clock_label = QLabel('99:99', self)
self.clock_label.setVisible(False)
self.clock_label.setFocusPolicy(Qt.NoFocus)
self.clock_label_style = '''
QLabel {
text-align: right;
border-width: 1px;
border-style: solid;
border-radius: 8px;
background-color: %s;
color: %s;
font-family: monospace;
font-size: larger;
padding: 5px;
}'''
self.clock_timer = QTimer(self)
self.clock_timer.timeout.connect(self.update_clock)
self.esc_full_screen_action = a = QAction(self) self.esc_full_screen_action = a = QAction(self)
self.addAction(a) self.addAction(a)
a.setShortcut(Qt.Key_Escape) a.setShortcut(Qt.Key_Escape)
@ -454,9 +472,29 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
a.start() a.start()
QTimer.singleShot(2750, self.full_screen_label.hide) QTimer.singleShot(2750, self.full_screen_label.hide)
self.view.document.switch_to_fullscreen_mode() self.view.document.switch_to_fullscreen_mode()
if self.view.document.fullscreen_clock:
self.show_clock()
def show_clock(self):
self.clock_label.setVisible(True)
self.clock_label.setText('99:99 AA')
self.clock_timer.start(1000)
self.clock_label.setStyleSheet(self.clock_label_style%
tuple(self.view.document.colors()))
self.clock_label.resize(self.clock_label.sizeHint())
sw = QApplication.desktop().screenGeometry(self.view)
self.clock_label.move(sw.width() - self.vertical_scrollbar.width() - 15
- self.clock_label.width(), sw.height() -
self.clock_label.height()-10)
self.update_clock()
def update_clock(self):
self.clock_label.setText(QTime.currentTime().toString('h:mm a'))
def showNormal(self): def showNormal(self):
self.view.document.page_position.save() self.view.document.page_position.save()
self.clock_label.setVisible(False)
self.clock_timer.stop()
self.window_mode_changed = 'normal' self.window_mode_changed = 'normal'
self.esc_full_screen_action.setEnabled(False) self.esc_full_screen_action.setEnabled(False)
self.tool_bar.setVisible(True) self.tool_bar.setVisible(True)
@ -1006,7 +1044,8 @@ def main(args=sys.argv):
except: except:
open_at = None open_at = None
if pid <= 0: if pid <= 0:
app = Application(args) override = 'calibre-ebook-viewer' if islinux else None
app = Application(args, override_program_name=override)
app.setWindowIcon(QIcon(I('viewer.png'))) app.setWindowIcon(QIcon(I('viewer.png')))
QApplication.setOrganizationName(ORG_NAME) QApplication.setOrganizationName(ORG_NAME)
QApplication.setApplicationName(APP_UID) QApplication.setApplicationName(APP_UID)

View File

@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import re, itertools, time, traceback import re, itertools, time, traceback, locale
from itertools import repeat, izip, imap from itertools import repeat, izip, imap
from datetime import timedelta from datetime import timedelta
from threading import Thread from threading import Thread
@ -625,6 +625,8 @@ class ResultCache(SearchQueryParser): # {{{
def get_matches(self, location, query, candidates=None, def get_matches(self, location, query, candidates=None,
allow_recursion=True): allow_recursion=True):
# If candidates is not None, it must not be modified. Changing its
# value will break query optimization in the search parser
matches = set([]) matches = set([])
if candidates is None: if candidates is None:
candidates = self.universal_set() candidates = self.universal_set()
@ -651,10 +653,13 @@ class ResultCache(SearchQueryParser): # {{{
else: else:
invert = False invert = False
for loc in location: for loc in location:
c = candidates.copy()
m = self.get_matches(loc, query, m = self.get_matches(loc, query,
candidates=candidates, allow_recursion=False) candidates=c, allow_recursion=False)
matches |= m matches |= m
candidates -= m c -= m
if len(c) == 0:
break
if invert: if invert:
matches = self.universal_set() - matches matches = self.universal_set() - matches
return matches return matches
@ -669,12 +674,15 @@ class ResultCache(SearchQueryParser): # {{{
if l and l != 'all' and l in self.all_search_locations: if l and l != 'all' and l in self.all_search_locations:
terms.add(l) terms.add(l)
if terms: if terms:
c = candidates.copy()
for l in terms: for l in terms:
try: try:
m = self.get_matches(l, query, m = self.get_matches(l, query,
candidates=candidates, allow_recursion=allow_recursion) candidates=c, allow_recursion=allow_recursion)
matches |= m matches |= m
candidates -= m c -= m
if len(c) == 0:
break
except: except:
pass pass
return matches return matches
@ -751,6 +759,7 @@ class ResultCache(SearchQueryParser): # {{{
for i, loc in enumerate(location): for i, loc in enumerate(location):
location[i] = db_col[loc] location[i] = db_col[loc]
current_candidates = candidates.copy()
for loc in location: # location is now an array of field indices for loc in location: # location is now an array of field indices
if loc == db_col['authors']: if loc == db_col['authors']:
### DB stores authors with commas changed to bars, so change query ### DB stores authors with commas changed to bars, so change query
@ -767,7 +776,7 @@ class ResultCache(SearchQueryParser): # {{{
else: else:
q = query q = query
for id_ in candidates: for id_ in current_candidates:
item = self._data[id_] item = self._data[id_]
if item is None: continue if item is None: continue
@ -814,6 +823,7 @@ class ResultCache(SearchQueryParser): # {{{
if _match(q, vals, matchkind): if _match(q, vals, matchkind):
matches.add(item[0]) matches.add(item[0])
continue continue
current_candidates -= matches
return matches return matches
def search(self, query, return_matches=False): def search(self, query, return_matches=False):
@ -903,7 +913,9 @@ class ResultCache(SearchQueryParser): # {{{
def set(self, row, col, val, row_is_id=False): def set(self, row, col, val, row_is_id=False):
id = row if row_is_id else self._map_filtered[row] id = row if row_is_id else self._map_filtered[row]
self._data[id][col] = val d = self._data[id]
d[col] = val
d.refresh_composites()
def get(self, row, col, row_is_id=False): def get(self, row, col, row_is_id=False):
id = row if row_is_id else self._map_filtered[row] id = row if row_is_id else self._map_filtered[row]
@ -1082,15 +1094,14 @@ class SortKeyGenerator(object):
dt = 'datetime' dt = 'datetime'
elif sb == 'number': elif sb == 'number':
try: try:
val = val.replace(',', '').strip()
p = 1 p = 1
for i, candidate in enumerate( for i, candidate in enumerate(
(' B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')): ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')):
if val.endswith(candidate): if val.endswith(candidate):
p = 1024**(i) p = 1024**(i)
val = val[:-len(candidate)].strip() val = val[:-len(candidate)].strip()
break break
val = float(val) * p val = locale.atof(val) * p
except: except:
val = 0.0 val = 0.0
dt = 'float' dt = 'float'

View File

@ -251,6 +251,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
defs['similar_tags_match_kind'] = 'match_all' defs['similar_tags_match_kind'] = 'match_all'
defs['similar_series_search_key'] = 'series' defs['similar_series_search_key'] = 'series'
defs['similar_series_match_kind'] = 'match_any' defs['similar_series_match_kind'] = 'match_any'
defs['book_display_fields'] = [
('title', False), ('authors', True), ('formats', True),
('series', True), ('identifiers', True), ('tags', True),
('path', True), ('publisher', False), ('rating', False),
('author_sort', False), ('sort', False), ('timestamp', False),
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
('last_modified', False), ('size', False), ('languages', False),
]
# Migrate the bool tristate tweak # Migrate the bool tristate tweak
defs['bools_are_tristate'] = \ defs['bools_are_tristate'] = \
@ -3737,4 +3745,42 @@ books_series_link feeds
'SELECT {0}, count(*) FROM books_{1}_link GROUP BY {0}'.format( 'SELECT {0}, count(*) FROM books_{1}_link GROUP BY {0}'.format(
fm['link_column'], fm['table'])) fm['link_column'], fm['table']))
def all_author_names(self):
ai = self.FIELD_MAP['authors']
ans = set()
for rec in self.data.iterall():
auts = rec[ai]
if auts:
for x in auts.split(','):
ans.add(x.replace('|', ','))
return ans
def all_tag_names(self):
ai = self.FIELD_MAP['tags']
ans = set()
for rec in self.data.iterall():
auts = rec[ai]
if auts:
for x in auts.split(','):
ans.add(x)
return ans
def all_publisher_names(self):
ai = self.FIELD_MAP['publisher']
ans = set()
for rec in self.data.iterall():
auts = rec[ai]
if auts:
ans.add(auts)
return ans
def all_series_names(self):
ai = self.FIELD_MAP['series']
ans = set()
for rec in self.data.iterall():
auts = rec[ai]
if auts:
ans.add(auts)
return ans

View File

@ -34,6 +34,9 @@ class DBPrefs(dict):
def to_raw(self, val): def to_raw(self, val):
return json.dumps(val, indent=2, default=to_json) return json.dumps(val, indent=2, default=to_json)
def has_setting(self, key):
return key in self
def __getitem__(self, key): def __getitem__(self, key):
try: try:
return dict.__getitem__(self, key) return dict.__getitem__(self, key)

View File

@ -5,6 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, os, cPickle, textwrap, stat import sys, os, cPickle, textwrap, stat
from subprocess import check_call from subprocess import check_call
from functools import partial
from calibre import __appname__, prints, guess_type from calibre import __appname__, prints, guess_type
from calibre.constants import islinux, isnetbsd, isbsd from calibre.constants import islinux, isnetbsd, isbsd
@ -346,19 +347,28 @@ class PostInstall:
try: try:
self.info('Setting up desktop integration...') self.info('Setting up desktop integration...')
env = os.environ.copy()
cc = check_call
if getattr(sys, 'frozen_path', False) and 'LD_LIBRARY_PATH' in env:
paths = env.get('LD_LIBRARY_PATH', '').split(os.pathsep)
paths = [x for x in paths if x]
npaths = [x for x in paths if x != sys.frozen_path+'/lib']
env['LD_LIBRARY_PATH'] = os.pathsep.join(npaths)
cc = partial(check_call, env=env)
with TemporaryDirectory() as tdir, CurrentDir(tdir), \ with TemporaryDirectory() as tdir, CurrentDir(tdir), \
PreserveMIMEDefaults(): PreserveMIMEDefaults():
render_img('mimetypes/lrf.png', 'calibre-lrf.png') render_img('mimetypes/lrf.png', 'calibre-lrf.png')
check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True) cc('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True)
self.icon_resources.append(('mimetypes', 'application-lrf', '128')) self.icon_resources.append(('mimetypes', 'application-lrf', '128'))
check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True) cc('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True)
self.icon_resources.append(('mimetypes', 'application-lrs', self.icon_resources.append(('mimetypes', 'application-lrs',
'128')) '128'))
render_img('lt.png', 'calibre-gui.png', width=256, height=256) render_img('lt.png', 'calibre-gui.png', width=256, height=256)
check_call('xdg-icon-resource install --noupdate --size 256 calibre-gui.png calibre-gui', shell=True) cc('xdg-icon-resource install --noupdate --size 256 calibre-gui.png calibre-gui', shell=True)
self.icon_resources.append(('apps', 'calibre-gui', '128')) self.icon_resources.append(('apps', 'calibre-gui', '128'))
render_img('viewer.png', 'calibre-viewer.png') render_img('viewer.png', 'calibre-viewer.png')
check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True) cc('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True)
self.icon_resources.append(('apps', 'calibre-viewer', '128')) self.icon_resources.append(('apps', 'calibre-viewer', '128'))
mimetypes = set([]) mimetypes = set([])
@ -385,14 +395,14 @@ class PostInstall:
'calibre-ebook-viewer.desktop') 'calibre-ebook-viewer.desktop')
for x in des: for x in des:
cmd = ['xdg-desktop-menu', 'install', '--noupdate', './'+x] cmd = ['xdg-desktop-menu', 'install', '--noupdate', './'+x]
check_call(' '.join(cmd), shell=True) cc(' '.join(cmd), shell=True)
self.menu_resources.append(x) self.menu_resources.append(x)
check_call(['xdg-desktop-menu', 'forceupdate']) cc(['xdg-desktop-menu', 'forceupdate'])
f = open('calibre-mimetypes', 'wb') f = open('calibre-mimetypes', 'wb')
f.write(MIME) f.write(MIME)
f.close() f.close()
self.mime_resources.append('calibre-mimetypes') self.mime_resources.append('calibre-mimetypes')
check_call('xdg-mime install ./calibre-mimetypes', shell=True) cc('xdg-mime install ./calibre-mimetypes', shell=True)
except Exception: except Exception:
if self.opts.fatal_errors: if self.opts.fatal_errors:
raise raise

View File

@ -76,15 +76,15 @@ def test_qt():
print ('Qt OK!') print ('Qt OK!')
def test_imaging(): def test_imaging():
from calibre.utils.magick.draw import create_canvas, Image from calibre.ebooks import calibre_cover
im = create_canvas(20, 20, '#ffffff') data = calibre_cover('test', 'ok')
jpg = im.export('jpg') if len(data) > 1000:
Image().load(jpg) print ('ImageMagick OK!')
im.export('png') else:
print ('ImageMagick OK!') raise RuntimeError('ImageMagick choked!')
from PIL import Image from PIL import Image
i = Image.open(cStringIO.StringIO(jpg)) i = Image.open(cStringIO.StringIO(data))
if i.size != (20, 20): if i.size < (20, 20):
raise RuntimeError('PIL choked!') raise RuntimeError('PIL choked!')
print ('PIL OK!') print ('PIL OK!')
@ -94,6 +94,12 @@ def test_unrar():
raise RuntimeError('Failed to load libunrar') raise RuntimeError('Failed to load libunrar')
print ('Unrar OK!') print ('Unrar OK!')
def test_icu():
from calibre.utils.icu import _icu_not_ok
if _icu_not_ok:
raise RuntimeError('ICU module not loaded/valid')
print ('ICU OK!')
def test(): def test():
test_plugins() test_plugins()
test_lxml() test_lxml()
@ -102,6 +108,7 @@ def test():
test_qt() test_qt()
test_imaging() test_imaging()
test_unrar() test_unrar()
test_icu()
if iswindows: if iswindows:
test_win32() test_win32()
test_winutil() test_winutil()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More