mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
0.8.60
This commit is contained in:
commit
0cebfe4613
@ -19,6 +19,74 @@
|
||||
# new recipes:
|
||||
# - 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
|
||||
date: 2012-07-06
|
||||
|
||||
|
@ -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
|
||||
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|.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
__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
|
||||
'''
|
||||
@ -14,24 +14,24 @@ class AdventureGamers(BasicNewsRecipe):
|
||||
publisher = 'Adventure Gamers'
|
||||
category = 'news, games, adventure, technology'
|
||||
oldest_article = 10
|
||||
delay = 10
|
||||
#delay = 10
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
encoding = 'cp1252'
|
||||
encoding = 'utf8'
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
INDEX = u'http://www.adventuregamers.com'
|
||||
extra_css = """
|
||||
.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}
|
||||
.score_bg {display: inline; width: 100%; margin-bottom: 2em}
|
||||
.score_column_1{ 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_header{font-size: large; color: #50544A}
|
||||
.bodytext{display: block}
|
||||
body{font-family: Helvetica,Arial,sans-serif}
|
||||
img{margin-bottom: 1em;}
|
||||
body{font-family: 'Open Sans',Helvetica,Arial,sans-serif}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
@ -41,27 +41,29 @@ class AdventureGamers(BasicNewsRecipe):
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'content_middle'})
|
||||
]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'cleft_inn'})]
|
||||
remove_tags = [
|
||||
dict(name=['object','link','embed','form'])
|
||||
,dict(name='div', attrs={'class':['related-stories','article_leadout','prev','next','both']})
|
||||
dict(name=['object','link','embed','form','iframe','meta'])
|
||||
,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':'toolbar_fat'})]
|
||||
remove_tags_after = [dict(name='div', attrs={'class':'bodytext'})]
|
||||
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):
|
||||
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):
|
||||
pager = soup.find('div',attrs={'class':'toolbar_fat_next'})
|
||||
pager = soup.find('div', attrs={'class':'pagination_big'})
|
||||
if pager:
|
||||
nexturl = self.INDEX + pager.a['href']
|
||||
nextpage = soup.find('a', attrs={'class':'next-page'})
|
||||
if nextpage:
|
||||
nexturl = nextpage['href']
|
||||
soup2 = self.index_to_soup(nexturl)
|
||||
texttag = soup2.find('div', attrs={'class':'bodytext'})
|
||||
for it in texttag.findAll(style=True):
|
||||
@ -69,6 +71,7 @@ class AdventureGamers(BasicNewsRecipe):
|
||||
newpos = len(texttag.contents)
|
||||
self.append_page(soup2,texttag,newpos)
|
||||
texttag.extract()
|
||||
pager.extract()
|
||||
appendtag.insert(position,texttag)
|
||||
|
||||
|
||||
@ -78,7 +81,7 @@ class AdventureGamers(BasicNewsRecipe):
|
||||
for item in soup.findAll('div', attrs={'class':'floatright'}):
|
||||
item.extract()
|
||||
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:
|
||||
pager.extract()
|
||||
return self.adeify_images(soup)
|
||||
|
@ -1,13 +1,13 @@
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
#from calibre import __appname__
|
||||
from calibre.utils.magick import Image
|
||||
import re
|
||||
from calibre import browser
|
||||
|
||||
class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
||||
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'
|
||||
#last update 21/12/11
|
||||
#last update 7/7/12 hopefully get current cover from itunes
|
||||
# greyscale code by Starson
|
||||
cover_url = 'http://www.cosmopolitan.magazine.co.uk/files/4613/2085/8988/Cosmo_Cover3.jpg'
|
||||
no_stylesheets = True
|
||||
@ -39,14 +39,19 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
||||
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/')]
|
||||
|
||||
def postprocess_html(self, soup, first):
|
||||
#process all the images
|
||||
for tag in soup.findAll(lambda tag: tag.name.lower()=='img' and tag.has_key('src')):
|
||||
iurl = tag['src']
|
||||
img = Image()
|
||||
img.open(iurl)
|
||||
if img < 0:
|
||||
raise RuntimeError('Out of memory')
|
||||
img.type = "GrayscaleType"
|
||||
img.save(iurl)
|
||||
return soup
|
||||
def get_cover_url(self):
|
||||
soup = self.index_to_soup('http://itunes.apple.com/gb/app/cosmopolitan-uk/id461363572?mt=8')
|
||||
# look for the block containing the sun button and url
|
||||
cov = soup.find(attrs={'alt' : 'iPhone Screenshot 1'})
|
||||
cov2 = str(cov['src'])
|
||||
br = browser()
|
||||
br.set_handle_redirect(False)
|
||||
try:
|
||||
br.open_novisit(cov2)
|
||||
cover_url = cov2
|
||||
except:
|
||||
cover_url = 'http://www.cosmopolitan.magazine.co.uk/files/4613/2085/8988/Cosmo_Cover3.jpg'
|
||||
|
||||
return cover_url
|
||||
|
||||
|
||||
|
51
recipes/empire_magazine.recipe
Normal file
51
recipes/empire_magazine.recipe
Normal 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'),
|
||||
]
|
@ -33,6 +33,7 @@ class FazNet(BasicNewsRecipe):
|
||||
('Technik & Motor', 'http://www.faz.net/aktuell/technik-motor/?rssview=1'),
|
||||
('Wissen', 'http://www.faz.net/aktuell/wissen/?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
|
||||
]
|
||||
|
||||
|
@ -1,38 +1,73 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Brendan Sleight <bms.calibre at barwap.com>'
|
||||
__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
|
||||
|
||||
class Hackaday(BasicNewsRecipe):
|
||||
title = u'Hola'
|
||||
__author__ = 'bmsleight'
|
||||
description = 'diario de actualidad, moda y belleza.'
|
||||
oldest_article = 10
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
class hola_es(BasicNewsRecipe):
|
||||
__author__ = 'desUBIKado'
|
||||
description = 'Diario de actualidad, moda y belleza'
|
||||
title = u'¡Hola!'
|
||||
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
|
||||
use_embedded_content = False
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'id':'cuerpo'})
|
||||
]
|
||||
remove_empty_feeds = True
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
feeds = [
|
||||
(u'Famosos' , u'http://www.hola.com/famosos/rss.xml' ),
|
||||
(u'Realeza' , u'http://www.hola.com/realeza/rss.xml' ),
|
||||
(u'Cine' , u'http://www.hola.com/cine/rss.xml' ),
|
||||
(u'Música' , u'http://www.hola.com/musica/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'Niños' , u'http://www.hola.com/ninos/rss.xml' ),
|
||||
(u'Todas las noticias', u'http://int2.hola.com/app/feeds/rss_hola.php'),
|
||||
(u'Famosos' , u'http://www.hola.com/famosos/rss.xml' )
|
||||
,(u'Realeza' , u'http://www.hola.com/realeza/rss.xml' )
|
||||
,(u'Cine' , u'http://www.hola.com/cine/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'Belleza y salud', u'http://www.hola.com/belleza/portada/rss.xml' )
|
||||
,(u'Ni\xf1os' , u'http://www.hola.com/ninos/rss.xml' )
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
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):
|
||||
url = article.get('guid', None)
|
||||
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;}
|
||||
'''
|
||||
|
@ -8,7 +8,7 @@ class NatGeoMag(BasicNewsRecipe):
|
||||
oldest_article = 31
|
||||
max_articles_per_feed = 50
|
||||
category = 'geography, magazine'
|
||||
language = 'en_US'
|
||||
language = 'en'
|
||||
publication_type = 'magazine'
|
||||
cover_url = 'http://www.yourlogoresources.com/wp-content/uploads/2011/09/national-geographic-logo.jpg'
|
||||
use_embedded_content = False
|
||||
|
61
recipes/nzz_folio.recipe
Normal file
61
recipes/nzz_folio.recipe
Normal 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/')
|
||||
]
|
||||
|
@ -1,21 +1,27 @@
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'faber1971'
|
||||
description = 'Italian rock webzine - v1.01 (6, July 2012)'
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1328535130(BasicNewsRecipe):
|
||||
title = u'Onda Rock'
|
||||
__author__ = 'faber1971'
|
||||
description = 'Italian rock webzine'
|
||||
language = 'it'
|
||||
|
||||
|
||||
oldest_article = 7
|
||||
oldest_article = 15
|
||||
max_articles_per_feed = 100
|
||||
auto_cleanup = False
|
||||
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={'class':['media','boxarticoli']}),
|
||||
dict(name='div', attrs={'style':'text-align: center'}),
|
||||
dict(name='table', attrs={'cellpadding':'0'}),
|
||||
dict(name='span', attrs={'class':'liketext'}),
|
||||
]
|
||||
no_stylesheets = True
|
||||
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;}
|
||||
'''
|
||||
|
@ -11,7 +11,7 @@ import subprocess, tempfile, os, time
|
||||
from setup import Command, installer_name
|
||||
from setup.build_environment import HOST, PROJECT
|
||||
|
||||
BASE_RSYNC = ['rsync', '-avz', '--delete']
|
||||
BASE_RSYNC = ['rsync', '-avz', '--delete', '--force']
|
||||
EXCLUDES = []
|
||||
for x in [
|
||||
'src/calibre/plugins', 'src/calibre/manual', 'src/calibre/trac',
|
||||
|
@ -12,7 +12,7 @@ import sys, os, shutil, platform, subprocess, stat, py_compile, glob, \
|
||||
from setup import Command, modules, basenames, functions, __version__, \
|
||||
__appname__
|
||||
|
||||
SITE_PACKAGES = ['IPython', 'PIL', 'dateutil', 'dns', 'PyQt4', 'mechanize',
|
||||
SITE_PACKAGES = ['PIL', 'dateutil', 'dns', 'PyQt4', 'mechanize',
|
||||
'sip.so', 'BeautifulSoup.py', 'cssutils', 'encutils', 'lxml',
|
||||
'sipconfig.py', 'xdg', 'dbus', '_dbus_bindings.so', 'dbus_bindings.py',
|
||||
'_dbus_glib_bindings.so']
|
||||
|
@ -364,6 +364,7 @@ class Py2App(object):
|
||||
'application. Visit http://calibre-ebook.com for details.'),
|
||||
CFBundleIconFile='library.icns',
|
||||
LSMultipleInstancesProhibited=True,
|
||||
NSHighResolutionCapable=True,
|
||||
LSEnvironment=env
|
||||
)
|
||||
plistlib.writePlist(pl, join(self.contents_dir, 'Info.plist'))
|
||||
@ -385,7 +386,7 @@ class Py2App(object):
|
||||
@flush
|
||||
def add_poppler(self):
|
||||
info('\nAdding poppler')
|
||||
for x in ('libpoppler.25.dylib',):
|
||||
for x in ('libpoppler.26.dylib',):
|
||||
self.install_dylib(os.path.join(SW, 'lib', x))
|
||||
for x in ('pdftohtml', 'pdftoppm', 'pdfinfo'):
|
||||
self.install_dylib(os.path.join(SW, 'bin', x), False)
|
||||
@ -482,10 +483,6 @@ class Py2App(object):
|
||||
shutil.rmtree(tdir)
|
||||
shutil.rmtree(os.path.join(self.site_packages, 'calibre', 'plugins'))
|
||||
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
|
||||
def add_modules_from_dir(self, src):
|
||||
|
@ -23,6 +23,7 @@ SW = r'C:\cygwin\home\kovid\sw'
|
||||
IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.7.6',
|
||||
'VisualMagick', 'bin')
|
||||
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__)
|
||||
WINVER = VERSION+'.0'
|
||||
|
@ -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::
|
||||
|
||||
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)
|
||||
|
||||
@ -146,7 +146,7 @@ Run bash ./runConfigureICU Cygwin/MSVC
|
||||
|
||||
Run make (note that you must have GNU make installed in cygwin)
|
||||
|
||||
Optionally run make test
|
||||
Optionally run make check
|
||||
|
||||
Libunrar
|
||||
----------
|
||||
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 8, 59)
|
||||
numeric_version = (0, 8, 60)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
|
@ -1262,7 +1262,6 @@ class StoreBNStore(StoreBase):
|
||||
|
||||
headquarters = 'US'
|
||||
formats = ['NOOK']
|
||||
affiliate = True
|
||||
|
||||
class StoreBeamEBooksDEStore(StoreBase):
|
||||
name = 'Beam EBooks DE'
|
||||
|
@ -81,6 +81,9 @@ class DBPrefs(dict): # {{{
|
||||
def to_raw(self, val):
|
||||
return json.dumps(val, indent=2, default=to_json)
|
||||
|
||||
def has_setting(self, key):
|
||||
return key in self
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return dict.__getitem__(self, key)
|
||||
@ -102,6 +105,53 @@ class DBPrefs(dict): # {{{
|
||||
def set(self, 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 {{{
|
||||
@ -350,6 +400,23 @@ class DB(object):
|
||||
defs['gui_restriction'] = defs['cs_restriction'] = ''
|
||||
defs['categories_using_hierarchy'] = []
|
||||
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
|
||||
defs['bools_are_tristate'] = \
|
||||
|
@ -60,6 +60,11 @@ Run an embedded python interpreter.
|
||||
'editing tools, and then rebuilds the file from the edited HTML. '
|
||||
'Makes no additional changes to the HTML, unlike a full calibre '
|
||||
'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',
|
||||
action='store_true', default=False)
|
||||
@ -258,6 +263,9 @@ def main(args=sys.argv):
|
||||
elif opts.test_build:
|
||||
from calibre.test_build import test
|
||||
test()
|
||||
elif opts.shutdown_running_calibre:
|
||||
from calibre.gui2.main import shutdown_other
|
||||
shutdown_other()
|
||||
else:
|
||||
from calibre import ipython
|
||||
ipython()
|
||||
|
@ -10,7 +10,7 @@ import cStringIO
|
||||
|
||||
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):
|
||||
|
||||
@ -41,6 +41,7 @@ class ANDROID(USBMS):
|
||||
0xca9 : HTC_BCDS,
|
||||
0xcac : HTC_BCDS,
|
||||
0xccf : HTC_BCDS,
|
||||
0xce5 : HTC_BCDS,
|
||||
0x2910 : HTC_BCDS,
|
||||
0xff9 : HTC_BCDS + [0x9999],
|
||||
},
|
||||
@ -59,6 +60,7 @@ class ANDROID(USBMS):
|
||||
0x42d6 : [0x216],
|
||||
0x42d7 : [0x216],
|
||||
0x42f7 : [0x216],
|
||||
0x4365 : [0x216],
|
||||
},
|
||||
# Freescale
|
||||
0x15a2 : {
|
||||
@ -209,7 +211,7 @@ class ANDROID(USBMS):
|
||||
'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW',
|
||||
'KTABLET_PC', 'INGENIC', 'GT-I9001_CARD', 'USB_2.0_DRIVER',
|
||||
'GT-S5830L_CARD', 'UNIVERSE', 'XT875', 'PRO', '.KOBO_VOX',
|
||||
'THINKPAD_TABLET', 'SGH-T989']
|
||||
'THINKPAD_TABLET', 'SGH-T989', 'YP-G70']
|
||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
|
||||
|
@ -413,7 +413,8 @@ class KINDLE2(KINDLE):
|
||||
if not opts.extra_customization[self.OPT_APNX]:
|
||||
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
|
||||
|
||||
# Create the sidecar folder if necessary
|
||||
|
@ -23,10 +23,11 @@ class KOBO(USBMS):
|
||||
gui_name = 'Kobo Reader'
|
||||
description = _('Communicate with the Kobo Reader')
|
||||
author = 'Timothy Legge'
|
||||
version = (1, 0, 12)
|
||||
version = (1, 0, 13)
|
||||
|
||||
dbversion = 0
|
||||
fwversion = 0
|
||||
supported_dbversion = 33
|
||||
has_kepubs = False
|
||||
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
@ -73,6 +74,12 @@ class KOBO(USBMS):
|
||||
':::'+_('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. '
|
||||
'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 = [
|
||||
@ -81,6 +88,7 @@ class KOBO(USBMS):
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False
|
||||
]
|
||||
|
||||
@ -90,6 +98,7 @@ class KOBO(USBMS):
|
||||
OPT_SHOW_EXPIRED_BOOK_RECORDS = 3
|
||||
OPT_SHOW_PREVIEWS = 4
|
||||
OPT_SHOW_RECOMMENDATIONS = 5
|
||||
OPT_SUPPORT_NEWER_FIRMWARE = 6
|
||||
|
||||
def initialize(self):
|
||||
USBMS.initialize(self)
|
||||
@ -238,15 +247,6 @@ class KOBO(USBMS):
|
||||
|
||||
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')
|
||||
result = cursor.fetchone()
|
||||
self.dbversion = result[0]
|
||||
@ -422,6 +422,9 @@ class KOBO(USBMS):
|
||||
os.unlink(fpath)
|
||||
|
||||
def delete_books(self, paths, end_session=True):
|
||||
if self.modify_database_check("delete_books") == False:
|
||||
return
|
||||
|
||||
for i, path in enumerate(paths):
|
||||
self.report_progress((i+1) / float(len(paths)), _('Removing books from device...'))
|
||||
path = self.normalize_path(path)
|
||||
@ -458,6 +461,9 @@ class KOBO(USBMS):
|
||||
self.report_progress(1.0, _('Removing books from device...'))
|
||||
|
||||
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):
|
||||
self.report_progress((i+1) / float(len(paths)), _('Removing books from device metadata listing...'))
|
||||
for bl in booklists:
|
||||
@ -588,6 +594,34 @@ class KOBO(USBMS):
|
||||
|
||||
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):
|
||||
tpath = self.munge_path(path)
|
||||
extension = os.path.splitext(tpath)[1]
|
||||
@ -706,6 +740,9 @@ class KOBO(USBMS):
|
||||
# debug_print(' Commit: Set FavouritesIndex')
|
||||
|
||||
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
|
||||
supportedcategories = {
|
||||
"Im_Reading":1,
|
||||
|
@ -19,9 +19,9 @@ class TECLAST_K3(USBMS):
|
||||
PRODUCT_ID = [0x3203]
|
||||
BCD = [0x0000, 0x0100]
|
||||
|
||||
VENDOR_NAME = ['TECLAST', 'IMAGIN']
|
||||
VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX']
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5',
|
||||
'EREADER']
|
||||
'EREADER', 'USB-MSC']
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory'
|
||||
STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card'
|
||||
|
@ -127,12 +127,13 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
if not prefix:
|
||||
return 0, 0
|
||||
prefix = prefix[:-1]
|
||||
import win32file
|
||||
import win32file, winerror
|
||||
try:
|
||||
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
|
||||
win32file.GetDiskFreeSpace(prefix)
|
||||
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)
|
||||
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
|
||||
win32file.GetDiskFreeSpace(prefix)
|
||||
|
@ -10,10 +10,15 @@ __docformat__ = 'restructuredtext en'
|
||||
import re, codecs
|
||||
|
||||
ENCODING_PATS = [
|
||||
# XML declaration
|
||||
re.compile(r'<\?[^<>]+encoding\s*=\s*[\'"](.*?)[\'"][^<>]*>',
|
||||
re.IGNORECASE),
|
||||
# HTML 4 Pragma directive
|
||||
re.compile(r'''<meta\s+?[^<>]*?content\s*=\s*['"][^'"]*?charset=([-_a-z0-9]+)[^'"]*?['"][^<>]*>''',
|
||||
re.IGNORECASE),
|
||||
# HTML 5 charset
|
||||
re.compile(r'''<meta\s+charset=['"]([-_a-z0-9]+)['"][^<>]*>''',
|
||||
re.IGNORECASE),
|
||||
]
|
||||
ENTITY_PATTERN = re.compile(r'&(\S+?);')
|
||||
|
||||
|
@ -37,6 +37,11 @@ class HTMLZOutput(OutputFormatPlugin):
|
||||
'external: Use an external CSS file that is linked in the document.\n'
|
||||
'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):
|
||||
@ -44,6 +49,7 @@ class HTMLZOutput(OutputFormatPlugin):
|
||||
from calibre.ebooks.oeb.base import OEB_IMAGES, SVG_MIME
|
||||
from calibre.ebooks.metadata.opf2 import OPF, metadata_to_opf
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
|
||||
# HTML
|
||||
if opts.htmlz_css_type == 'inline':
|
||||
@ -59,7 +65,10 @@ class HTMLZOutput(OutputFormatPlugin):
|
||||
htmlizer = OEB2HTMLizer(log)
|
||||
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)
|
||||
|
||||
# CSS
|
||||
|
@ -9,6 +9,8 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import re, os
|
||||
|
||||
from calibre.ebooks.chardet import strip_encoding_declarations
|
||||
|
||||
def update_internal_links(mobi8_reader):
|
||||
# need to update all links that are internal which
|
||||
# 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):
|
||||
pi = mobi8_reader.partinfo[i]
|
||||
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'))
|
||||
spine.append(f.name)
|
||||
|
||||
|
@ -89,14 +89,6 @@ gprefs.defaults['tags_browser_partition_method'] = 'first letter'
|
||||
gprefs.defaults['tags_browser_collapse_at'] = 100
|
||||
gprefs.defaults['tag_browser_dont_collapse'] = []
|
||||
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['preserve_date_on_ctl'] = True
|
||||
gprefs.defaults['cb_fullscreen'] = False
|
||||
@ -581,6 +573,7 @@ class FileDialog(QObject):
|
||||
if not isinstance(initial_dir, basestring):
|
||||
initial_dir = os.path.expanduser(default_dir)
|
||||
self.selected_files = []
|
||||
with SanitizeLibraryPath():
|
||||
if mode == QFileDialog.AnyFile:
|
||||
f = unicode(QFileDialog.getSaveFileName(parent, title, initial_dir, ftext, ""))
|
||||
if f:
|
||||
@ -734,11 +727,11 @@ gui_thread = None
|
||||
qt_app = None
|
||||
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
|
||||
if islinux and args[0].endswith(u'calibre'):
|
||||
args = list(args)
|
||||
args[0] += '-gui'
|
||||
if override_program_name:
|
||||
args = [override_program_name] + args[1:]
|
||||
qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args]
|
||||
QApplication.__init__(self, qargs)
|
||||
global gui_thread, qt_app
|
||||
@ -857,16 +850,26 @@ class Application(QApplication):
|
||||
|
||||
_store_app = None
|
||||
|
||||
def open_url(qurl):
|
||||
paths = os.environ.get('LD_LIBRARY_PATH',
|
||||
'').split(os.pathsep)
|
||||
paths = [x for x in paths if x]
|
||||
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):
|
||||
with SanitizeLibraryPath():
|
||||
QDesktopServices.openUrl(qurl)
|
||||
if isfrozen and islinux and paths:
|
||||
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(paths)
|
||||
|
||||
def get_current_db():
|
||||
'''
|
||||
|
@ -74,9 +74,10 @@ class ShareConnMenu(QMenu): # {{{
|
||||
action=self.toggle_server_action, group=gr)
|
||||
|
||||
def server_state_changed(self, running):
|
||||
from calibre.utils.mdns import get_external_ip
|
||||
text = _('Start Content Server')
|
||||
if running:
|
||||
text = _('Stop Content Server')
|
||||
text = _('Stop Content Server') + ' [%s]'%get_external_ip()
|
||||
self.toggle_server_action.setText(text)
|
||||
|
||||
def build_email_entries(self, sync_menu):
|
||||
|
@ -84,7 +84,17 @@ def render_html(mi, css, vertical, widget, all_fields=False): # {{{
|
||||
return ans
|
||||
|
||||
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'])
|
||||
names = frozenset([x[0] for x in fieldlist])
|
||||
for field in fm.displayable_field_keys():
|
||||
|
@ -5,11 +5,15 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__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,
|
||||
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.widgets import EnComboBox, LineEditECM
|
||||
from calibre.utils.config_base import tweaks
|
||||
@ -24,12 +28,11 @@ class CompleteModel(QAbstractListModel):
|
||||
def set_items(self, items):
|
||||
items = [unicode(x.strip()) for x in items]
|
||||
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
|
||||
else:
|
||||
self.items = sorted(items, key=lambda x:x.lower())
|
||||
self.sorting = QCompleter.CaseInsensitivelySortedModel
|
||||
self.lowered_items = [lower(x) for x in self.items]
|
||||
self.reset()
|
||||
|
||||
def rowCount(self, *args):
|
||||
|
415
src/calibre/gui2/complete2.py
Normal file
415
src/calibre/gui2/complete2.py
Normal 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_()
|
@ -17,7 +17,8 @@ class PluginWidget(Widget, Ui_Form):
|
||||
ICON = I('mimetypes/html.png')
|
||||
|
||||
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
|
||||
for x in get_option('htmlz_css_type').option.choices:
|
||||
self.opt_htmlz_css_type.addItem(x)
|
||||
|
@ -14,7 +14,7 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@ -27,6 +27,9 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="opt_htmlz_class_style"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@ -51,8 +54,12 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="opt_htmlz_class_style"/>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_htmlz_title_filename">
|
||||
<property name="text">
|
||||
<string>Use book &title as the filename for the HTML file inside the archive</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -97,6 +97,9 @@ class MetadataWidget(Widget, Ui_Form):
|
||||
else:
|
||||
self.cover.setPixmap(QPixmap(I('default_cover.png')))
|
||||
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):
|
||||
tt = _('Cover size: %(width)d x %(height)d pixels') % dict(
|
||||
|
@ -190,7 +190,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="MultiCompleteLineEdit" name="tags">
|
||||
<widget class="EditWithComplete" name="tags">
|
||||
<property name="toolTip">
|
||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||
</property>
|
||||
@ -213,7 +213,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="MultiCompleteComboBox" name="series">
|
||||
<widget class="EditWithComplete" name="series">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>10</horstretch>
|
||||
@ -248,14 +248,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="MultiCompleteComboBox" name="publisher">
|
||||
<widget class="EditWithComplete" name="publisher">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="MultiCompleteComboBox" name="author">
|
||||
<widget class="EditWithComplete" name="author">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@ -277,14 +277,9 @@
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MultiCompleteComboBox</class>
|
||||
<class>EditWithComplete</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>calibre/gui2/complete.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MultiCompleteLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>calibre/gui2/complete.h</header>
|
||||
<header>calibre/gui2/complete2.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ImageView</class>
|
||||
|
@ -13,7 +13,7 @@ from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit,
|
||||
QPushButton, QMessageBox, QToolButton
|
||||
|
||||
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 import UNDEFINED_QDATETIME, error_dialog
|
||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||
@ -235,7 +235,7 @@ class MultipleWidget(QWidget):
|
||||
layout.setSpacing(5)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.tags_box = MultiCompleteLineEdit(parent)
|
||||
self.tags_box = EditWithComplete(parent)
|
||||
layout.addWidget(self.tags_box, stretch=1000)
|
||||
self.editor_button = QToolButton(self)
|
||||
self.editor_button.setToolTip(_('Open Item Editor'))
|
||||
@ -293,7 +293,7 @@ class Text(Base):
|
||||
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
w.get_editor_button().clicked.connect(self.edit)
|
||||
else:
|
||||
w = MultiCompleteComboBox(parent)
|
||||
w = EditWithComplete(parent)
|
||||
w.set_separator(None)
|
||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
||||
w.setMinimumContentsLength(25)
|
||||
@ -363,7 +363,7 @@ class Text(Base):
|
||||
class Series(Base):
|
||||
|
||||
def setup_ui(self, parent):
|
||||
w = MultiCompleteComboBox(parent)
|
||||
w = EditWithComplete(parent)
|
||||
w.set_separator(None)
|
||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
||||
w.setMinimumContentsLength(25)
|
||||
@ -807,7 +807,7 @@ class BulkDateTime(BulkBase):
|
||||
class BulkSeries(BulkBase):
|
||||
|
||||
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.sort(key=sort_key)
|
||||
self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
||||
@ -934,7 +934,7 @@ class RemoveTags(QWidget):
|
||||
layout.setSpacing(5)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.tags_box = MultiCompleteLineEdit(parent)
|
||||
self.tags_box = EditWithComplete(parent)
|
||||
self.tags_box.update_items_cache(values)
|
||||
layout.addWidget(self.tags_box, stretch=3)
|
||||
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.sort(key=sort_key)
|
||||
if self.col_metadata['is_multiple']:
|
||||
self.make_widgets(parent, MultiCompleteLineEdit,
|
||||
self.make_widgets(parent, EditWithComplete,
|
||||
extra_label_text=_('tags to add'))
|
||||
self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
self.adding_widget = self.main_widget
|
||||
@ -976,7 +976,7 @@ class BulkText(BulkBase):
|
||||
self.main_widget.set_add_separator(
|
||||
tweaks['authors_completer_append_separator'])
|
||||
else:
|
||||
self.make_widgets(parent, MultiCompleteComboBox)
|
||||
self.make_widgets(parent, EditWithComplete)
|
||||
self.main_widget.set_separator(None)
|
||||
self.main_widget.setSizeAdjustPolicy(
|
||||
self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
||||
|
@ -7,7 +7,7 @@ __license__ = 'GPL v3'
|
||||
from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \
|
||||
QApplication, QSpinBox, QToolButton, QIcon
|
||||
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
|
||||
|
||||
class AddEmptyBookDialog(QDialog):
|
||||
@ -32,7 +32,7 @@ class AddEmptyBookDialog(QDialog):
|
||||
self.author_label = QLabel(_('Set the author of the new books to:'))
|
||||
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.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.authors_combo.setEditable(True)
|
||||
|
@ -76,7 +76,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="MultiCompleteComboBox" name="authors">
|
||||
<widget class="EditWithComplete" name="authors">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@ -175,7 +175,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="MultiCompleteComboBox" name="publisher">
|
||||
<widget class="EditWithComplete" name="publisher">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@ -195,7 +195,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="MultiCompleteLineEdit" name="tags">
|
||||
<widget class="EditWithComplete" name="tags">
|
||||
<property name="toolTip">
|
||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||
</property>
|
||||
@ -229,7 +229,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="MultiCompleteLineEdit" name="remove_tags">
|
||||
<widget class="EditWithComplete" name="remove_tags">
|
||||
<property name="toolTip">
|
||||
<string>Comma separated list of tags to remove from the books. </string>
|
||||
</property>
|
||||
@ -262,7 +262,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="MultiCompleteComboBox" name="series">
|
||||
<widget class="EditWithComplete" name="series">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@ -1181,14 +1181,9 @@ not multiple and the destination field is multiple</string>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MultiCompleteComboBox</class>
|
||||
<class>EditWithComplete</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>calibre/gui2/complete.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MultiCompleteLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>calibre/gui2/complete.h</header>
|
||||
<header>calibre/gui2/complete2.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>HistoryLineEdit</class>
|
||||
|
@ -237,21 +237,21 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="MultiCompleteComboBox" name="authors_box">
|
||||
<widget class="EditWithComplete" name="authors_box">
|
||||
<property name="toolTip">
|
||||
<string>Enter an author's name. Only one author can be used.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="MultiCompleteComboBox" name="series_box">
|
||||
<widget class="EditWithComplete" name="series_box">
|
||||
<property name="toolTip">
|
||||
<string>Enter a series name, without an index. Only one series name can be used.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="MultiCompleteLineEdit" name="tags_box">
|
||||
<widget class="EditWithComplete" name="tags_box">
|
||||
<property name="toolTip">
|
||||
<string>Enter tags separated by spaces</string>
|
||||
</property>
|
||||
@ -327,14 +327,9 @@
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MultiCompleteLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>calibre/gui2/complete.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MultiCompleteComboBox</class>
|
||||
<class>EditWithComplete</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>calibre/gui2/complete.h</header>
|
||||
<header>calibre/gui2/complete2.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
|
@ -7,14 +7,14 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__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.icu import sort_key, lower
|
||||
|
||||
class LanguagesEdit(MultiCompleteComboBox):
|
||||
class LanguagesEdit(EditWithComplete):
|
||||
|
||||
def __init__(self, parent=None, db=None):
|
||||
MultiCompleteComboBox.__init__(self, parent)
|
||||
EditWithComplete.__init__(self, parent)
|
||||
|
||||
self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.setMinimumContentsLength(20)
|
||||
|
@ -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.constants import iswindows
|
||||
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.config import tweaks
|
||||
from calibre.utils.formatter import validation_formatter
|
||||
@ -121,7 +121,7 @@ class TextDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
if self.auto_complete_function:
|
||||
editor = MultiCompleteComboBox(parent)
|
||||
editor = EditWithComplete(parent)
|
||||
editor.set_separator(None)
|
||||
complete_items = [i[1] for i in self.auto_complete_function()]
|
||||
editor.update_items_cache(complete_items)
|
||||
@ -132,7 +132,7 @@ class TextDelegate(QStyledItemDelegate): # {{{
|
||||
return editor
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
if isinstance(editor, MultiCompleteComboBox):
|
||||
if isinstance(editor, EditWithComplete):
|
||||
val = editor.lineEdit().text()
|
||||
model.setData(index, QVariant(val), Qt.EditRole)
|
||||
else:
|
||||
@ -153,7 +153,7 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
|
||||
def createEditor(self, parent, option, index):
|
||||
if self.db and hasattr(self.db, self.items_func_name):
|
||||
col = index.model().column_map[index.column()]
|
||||
editor = MultiCompleteComboBox(parent)
|
||||
editor = EditWithComplete(parent)
|
||||
editor.set_separator(self.sep)
|
||||
editor.set_space_before_sep(self.space_before_sep)
|
||||
if self.sep == '&':
|
||||
@ -171,7 +171,7 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
|
||||
return editor
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
if isinstance(editor, MultiCompleteComboBox):
|
||||
if isinstance(editor, EditWithComplete):
|
||||
val = editor.lineEdit().text()
|
||||
model.setData(index, QVariant(val), Qt.EditRole)
|
||||
else:
|
||||
@ -244,7 +244,7 @@ class CcTextDelegate(QStyledItemDelegate): # {{{
|
||||
def createEditor(self, parent, option, index):
|
||||
m = index.model()
|
||||
col = m.column_map[index.column()]
|
||||
editor = MultiCompleteLineEdit(parent)
|
||||
editor = EditWithComplete(parent)
|
||||
editor.set_separator(None)
|
||||
complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))),
|
||||
key=sort_key)
|
||||
|
@ -125,7 +125,7 @@ class BooksView(QTableView): # {{{
|
||||
self.last_modified_delegate = DateDelegate(self,
|
||||
tweak_name='gui_last_modified_display_format')
|
||||
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.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True)
|
||||
self.series_delegate = TextDelegate(self)
|
||||
|
@ -309,7 +309,8 @@ def main(args=sys.argv, logger=None):
|
||||
return 1
|
||||
pid = os.fork() if (islinux or isbsd) else -1
|
||||
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')))
|
||||
QCoreApplication.setOrganizationName(ORG_NAME)
|
||||
QCoreApplication.setApplicationName(APP_UID)
|
||||
|
@ -8,7 +8,7 @@ from PyQt4.Qt import (QCoreApplication, QIcon, QObject, QTimer,
|
||||
QPixmap, QSplashScreen, QApplication)
|
||||
|
||||
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)
|
||||
from calibre.utils.ipc import gui_socket_address, RC
|
||||
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'])
|
||||
QCoreApplication.setOrganizationName(ORG_NAME)
|
||||
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())
|
||||
app.setWindowIcon(QIcon(I('lt.png')))
|
||||
return app, opts, args, actions
|
||||
@ -312,21 +313,39 @@ def cant_start(msg=_('If you are sure it is not running')+', ',
|
||||
|
||||
raise SystemExit(1)
|
||||
|
||||
def communicate(opts, args):
|
||||
t = RC()
|
||||
def build_pipe(print_error=True):
|
||||
t = RC(print_error=print_error)
|
||||
t.start()
|
||||
time.sleep(3)
|
||||
if not t.done:
|
||||
t.join(3.0)
|
||||
if t.is_alive():
|
||||
if iswindows():
|
||||
cant_start()
|
||||
else:
|
||||
f = os.path.expanduser('~/.calibre_calibre GUI.lock')
|
||||
cant_start(what=_('try deleting the file')+': '+f)
|
||||
raise SystemExit(1)
|
||||
return t
|
||||
|
||||
if opts.shutdown_running_calibre:
|
||||
t.conn.send('shutdown:')
|
||||
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...'))
|
||||
while not singleinstance('calibre GUI'):
|
||||
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:
|
||||
shutdown_other(t)
|
||||
else:
|
||||
if len(args) > 1:
|
||||
args[1] = os.path.abspath(args[1])
|
||||
@ -334,7 +353,6 @@ def communicate(opts, args):
|
||||
t.conn.close()
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
gui_debug = None
|
||||
if args[0] == '__CALIBRE_GUI_DEBUG__':
|
||||
|
@ -15,7 +15,6 @@ from PyQt4.Qt import (Qt, QDateTimeEdit, pyqtSignal, QMessageBox,
|
||||
QPushButton, QSpinBox, QLineEdit, QSizePolicy, QDialogButtonBox, QAction)
|
||||
|
||||
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.config import tweaks, prefs
|
||||
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.gui2 import (file_icon_provider, UNDEFINED_QDATETIME,
|
||||
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,
|
||||
UNDEFINED_DATE)
|
||||
from calibre import strftime
|
||||
@ -204,7 +204,7 @@ class TitleSortEdit(TitleEdit):
|
||||
# }}}
|
||||
|
||||
# Authors {{{
|
||||
class AuthorsEdit(MultiCompleteComboBox):
|
||||
class AuthorsEdit(EditWithComplete):
|
||||
|
||||
TOOLTIP = ''
|
||||
LABEL = _('&Author(s):')
|
||||
@ -212,7 +212,7 @@ class AuthorsEdit(MultiCompleteComboBox):
|
||||
def __init__(self, parent, manage_authors):
|
||||
self.dialog = parent
|
||||
self.books_to_refresh = set([])
|
||||
MultiCompleteComboBox.__init__(self, parent)
|
||||
EditWithComplete.__init__(self, parent)
|
||||
self.setToolTip(self.TOOLTIP)
|
||||
self.setWhatsThis(self.TOOLTIP)
|
||||
self.setEditable(True)
|
||||
@ -443,13 +443,13 @@ class AuthorSortEdit(EnLineEdit):
|
||||
# }}}
|
||||
|
||||
# Series {{{
|
||||
class SeriesEdit(MultiCompleteComboBox):
|
||||
class SeriesEdit(EditWithComplete):
|
||||
|
||||
TOOLTIP = _('List of known series. You can add new series.')
|
||||
LABEL = _('&Series:')
|
||||
|
||||
def __init__(self, parent):
|
||||
MultiCompleteComboBox.__init__(self, parent)
|
||||
EditWithComplete.__init__(self, parent)
|
||||
self.set_separator(None)
|
||||
self.dialog = parent
|
||||
self.setSizeAdjustPolicy(
|
||||
@ -1086,14 +1086,14 @@ class RatingEdit(QSpinBox): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class TagsEdit(MultiCompleteLineEdit): # {{{
|
||||
class TagsEdit(EditWithComplete): # {{{
|
||||
LABEL = _('Ta&gs:')
|
||||
TOOLTIP = '<p>'+_('Tags categorize the book. This is particularly '
|
||||
'useful while searching. <br><br>They can be any words '
|
||||
'or phrases, separated by commas.')
|
||||
|
||||
def __init__(self, parent):
|
||||
MultiCompleteLineEdit.__init__(self, parent)
|
||||
EditWithComplete.__init__(self, parent)
|
||||
self.books_to_refresh = set([])
|
||||
self.setToolTip(self.TOOLTIP)
|
||||
self.setWhatsThis(self.TOOLTIP)
|
||||
@ -1114,7 +1114,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
|
||||
tags = db.tags(id_, index_is_id=True)
|
||||
tags = tags.split(',') if tags else []
|
||||
self.current_val = tags
|
||||
self.all_items = db.all_tags()
|
||||
self.all_items = db.all_tag_names()
|
||||
self.original_val = self.current_val
|
||||
|
||||
@property
|
||||
@ -1327,11 +1327,11 @@ class ISBNDialog(QDialog) : # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class PublisherEdit(MultiCompleteComboBox): # {{{
|
||||
class PublisherEdit(EditWithComplete): # {{{
|
||||
LABEL = _('&Publisher:')
|
||||
|
||||
def __init__(self, parent):
|
||||
MultiCompleteComboBox.__init__(self, parent)
|
||||
EditWithComplete.__init__(self, parent)
|
||||
self.set_separator(None)
|
||||
self.setSizeAdjustPolicy(
|
||||
self.AdjustToMinimumContentsLengthWithIcon)
|
||||
|
@ -13,6 +13,7 @@ from PyQt4.Qt import (QWidget, pyqtSignal, QCheckBox, QAbstractSpinBox,
|
||||
|
||||
from calibre.customize.ui import preferences_plugins
|
||||
from calibre.utils.config import ConfigProxy
|
||||
from calibre.gui2.complete2 import EditWithComplete
|
||||
|
||||
class AbortCommit(Exception):
|
||||
pass
|
||||
@ -133,8 +134,12 @@ class Setting(object):
|
||||
def initialize(self):
|
||||
self.gui_obj.blockSignals(True)
|
||||
if self.datatype == 'choice':
|
||||
choices = self.choices or []
|
||||
if isinstance(self.gui_obj, EditWithComplete):
|
||||
self.gui_obj.all_items = choices
|
||||
else:
|
||||
self.gui_obj.clear()
|
||||
for x in self.choices:
|
||||
for x in choices:
|
||||
if isinstance(x, basestring):
|
||||
x = (x, x)
|
||||
self.gui_obj.addItem(x[0], QVariant(x[1]))
|
||||
@ -171,6 +176,9 @@ class Setting(object):
|
||||
elif self.datatype == 'string':
|
||||
self.gui_obj.setText(val if val else '')
|
||||
elif self.datatype == 'choice':
|
||||
if isinstance(self.gui_obj, EditWithComplete):
|
||||
self.gui_obj.setText(val)
|
||||
else:
|
||||
idx = self.gui_obj.findData(QVariant(val), role=Qt.UserRole,
|
||||
flags=self.CHOICES_SEARCH_FLAGS)
|
||||
if idx == -1:
|
||||
@ -187,6 +195,9 @@ class Setting(object):
|
||||
if self.empty_string_is_None and not val:
|
||||
val = None
|
||||
elif self.datatype == 'choice':
|
||||
if isinstance(self.gui_obj, EditWithComplete):
|
||||
val = unicode(self.gui_obj.text())
|
||||
else:
|
||||
idx = self.gui_obj.currentIndex()
|
||||
if idx < 0: idx = 0
|
||||
val = unicode(self.gui_obj.itemData(idx).toString())
|
||||
|
@ -75,7 +75,7 @@ class DisplayedFields(QAbstractListModel): # {{{
|
||||
|
||||
def commit(self):
|
||||
if self.changed:
|
||||
gprefs['book_display_fields'] = self.fields
|
||||
self.db.prefs['book_display_fields'] = self.fields
|
||||
|
||||
def move(self, idx, delta):
|
||||
row = idx.row() + delta
|
||||
|
@ -320,7 +320,7 @@ Manage Authors. You can use the values {author} and
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_10">
|
||||
<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">
|
||||
<string>A comma-separated list of categories in which items containing
|
||||
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>
|
||||
</item>
|
||||
<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">
|
||||
<string>A comma-separated list of categories that are not to
|
||||
be partitioned even if the number of items is larger than
|
||||
@ -506,9 +506,9 @@ a few top-level elements.</string>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MultiCompleteLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>calibre/gui2/complete.h</header>
|
||||
<class>EditWithComplete</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>calibre/gui2/complete2.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
|
@ -98,7 +98,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
db.prefs.set('grouped_search_make_user_categories', [])
|
||||
r('grouped_search_make_user_categories', db.prefs, setting=CommaSeparatedList)
|
||||
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)
|
||||
|
||||
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.gst_names.blockSignals(True)
|
||||
self.gst_names.clear()
|
||||
print (1111, self.gst_names)
|
||||
self.gst_names.addItem('', '')
|
||||
for t in terms:
|
||||
self.gst_names.addItem(t, t)
|
||||
|
@ -69,7 +69,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<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 row="5" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
@ -134,7 +134,7 @@ a search term by changing the value box then pressing Save.</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="MultiCompleteLineEdit" name="gst_value"/>
|
||||
<widget class="EditWithComplete" name="gst_value"/>
|
||||
</item>
|
||||
<item>
|
||||
<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>
|
||||
</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">
|
||||
<string>Enter the names of any grouped search terms you wish
|
||||
to be shown as user categories</string>
|
||||
@ -301,9 +301,9 @@ to be shown as user categories</string>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MultiCompleteLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>calibre/gui2/complete.h</header>
|
||||
<class>EditWithComplete</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>calibre/gui2/complete2.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
|
@ -6,7 +6,6 @@ __license__ = 'GPL 3'
|
||||
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import random
|
||||
import urllib
|
||||
from contextlib import closing
|
||||
|
||||
@ -24,26 +23,12 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||
class BNStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
pub_id = 'sHa5EXvYOwA'
|
||||
# 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)
|
||||
url = "http://bn.com"
|
||||
|
||||
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:
|
||||
d = WebStoreDialog(self.gui, murl, parent, purl)
|
||||
d = WebStoreDialog(self.gui, url, parent, detail_item)
|
||||
d.setWindowTitle(self.name)
|
||||
d.set_tags(self.config.get('tags', ''))
|
||||
d.exec_()
|
||||
@ -60,7 +45,7 @@ class BNStore(BasicStoreConfig, StorePlugin):
|
||||
if counter <= 0:
|
||||
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:
|
||||
continue
|
||||
|
||||
|
@ -11,7 +11,6 @@ import cPickle, os
|
||||
|
||||
from PyQt4.Qt import QDialog, QProgressDialog, QString, QTimer
|
||||
|
||||
from calibre.constants import DEBUG
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.gui2 import warning_dialog, question_dialog
|
||||
from calibre.gui2.convert.single import NoSupportedInputFormats
|
||||
|
@ -51,6 +51,8 @@ def config(defaults=None):
|
||||
help=_('The amount by which to change the font size when clicking'
|
||||
' the font larger/smaller buttons. Should be a number between '
|
||||
'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('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_default_lang.setVisible(False)
|
||||
self.hyphenate_label.setVisible(False)
|
||||
self.opt_fullscreen_clock.setChecked(opts.fullscreen_clock)
|
||||
|
||||
def accept(self, *args):
|
||||
if self.shortcut_config.is_editing:
|
||||
@ -148,6 +151,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
str(self.hyphenate_default_lang.itemData(idx).toString()))
|
||||
c.set('line_scrolling_stops_on_pagebreaks',
|
||||
self.opt_line_scrolling_stops_on_pagebreaks.isChecked())
|
||||
c.set('fullscreen_clock', self.opt_fullscreen_clock.isChecked())
|
||||
return QDialog.accept(self, *args)
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>479</width>
|
||||
<width>839</width>
|
||||
<height>630</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -167,20 +167,6 @@
|
||||
</item>
|
||||
<item>
|
||||
<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 &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 &current page when quitting</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="hyphenate">
|
||||
<property name="text">
|
||||
@ -205,13 +191,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_fit_images">
|
||||
<property name="text">
|
||||
<string>&Resize images larger than the viewer window (needs restart)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
@ -247,13 +226,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_wheel_flips_pages">
|
||||
<property name="text">
|
||||
<string>Mouse &wheel flips pages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="max_fs_width">
|
||||
<property name="toolTip">
|
||||
@ -301,13 +273,48 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0" colspan="2">
|
||||
<item row="7" column="0">
|
||||
<widget class="QCheckBox" name="opt_fit_images">
|
||||
<property name="text">
|
||||
<string>&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 &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 &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 &current page when quitting</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QCheckBox" name="opt_line_scrolling_stops_on_pagebreaks">
|
||||
<property name="text">
|
||||
<string>Line &scrolling stops at page breaks</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="opt_fullscreen_clock">
|
||||
<property name="text">
|
||||
<string>Show &clock in full screen mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -134,6 +134,7 @@ class Document(QWebPage): # {{{
|
||||
screen_width = QApplication.desktop().screenGeometry().width()
|
||||
# Leave some space for the scrollbar and some border
|
||||
self.max_fs_width = min(opts.max_fs_width, screen_width-50)
|
||||
self.fullscreen_clock = opts.fullscreen_clock
|
||||
|
||||
def fit_images(self):
|
||||
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.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):
|
||||
self.bridge_value = tuple(self.index_anchors)
|
||||
self.javascript(u'''
|
||||
|
@ -6,9 +6,10 @@ from functools import partial
|
||||
from threading import Thread
|
||||
|
||||
from PyQt4.Qt import (QApplication, Qt, QIcon, QTimer, QByteArray, QSize,
|
||||
QDoubleSpinBox, QLabel, QTextBrowser, QPropertyAnimation, QPainter,
|
||||
QBrush, QColor, pyqtSignal, QUrl, QRegExpValidator, QRegExp, QLineEdit,
|
||||
QToolButton, QMenu, QInputDialog, QAction, QKeySequence, QModelIndex)
|
||||
QTime, QDoubleSpinBox, QLabel, QTextBrowser, QPropertyAnimation,
|
||||
QPainter, QBrush, QColor, pyqtSignal, QUrl, QRegExpValidator, QRegExp,
|
||||
QLineEdit, QToolButton, QMenu, QInputDialog, QAction, QKeySequence,
|
||||
QModelIndex)
|
||||
|
||||
from calibre.gui2.viewer.main_ui import Ui_EbookViewer
|
||||
from calibre.gui2.viewer.printing import Printing
|
||||
@ -288,6 +289,23 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.addAction(self.toggle_toolbar_action)
|
||||
self.full_screen_label_anim = QPropertyAnimation(
|
||||
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.addAction(a)
|
||||
a.setShortcut(Qt.Key_Escape)
|
||||
@ -454,9 +472,29 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
a.start()
|
||||
QTimer.singleShot(2750, self.full_screen_label.hide)
|
||||
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):
|
||||
self.view.document.page_position.save()
|
||||
self.clock_label.setVisible(False)
|
||||
self.clock_timer.stop()
|
||||
self.window_mode_changed = 'normal'
|
||||
self.esc_full_screen_action.setEnabled(False)
|
||||
self.tool_bar.setVisible(True)
|
||||
@ -1006,7 +1044,8 @@ def main(args=sys.argv):
|
||||
except:
|
||||
open_at = None
|
||||
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')))
|
||||
QApplication.setOrganizationName(ORG_NAME)
|
||||
QApplication.setApplicationName(APP_UID)
|
||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import re, itertools, time, traceback
|
||||
import re, itertools, time, traceback, locale
|
||||
from itertools import repeat, izip, imap
|
||||
from datetime import timedelta
|
||||
from threading import Thread
|
||||
@ -625,6 +625,8 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
|
||||
def get_matches(self, location, query, candidates=None,
|
||||
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([])
|
||||
if candidates is None:
|
||||
candidates = self.universal_set()
|
||||
@ -651,10 +653,13 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
else:
|
||||
invert = False
|
||||
for loc in location:
|
||||
c = candidates.copy()
|
||||
m = self.get_matches(loc, query,
|
||||
candidates=candidates, allow_recursion=False)
|
||||
candidates=c, allow_recursion=False)
|
||||
matches |= m
|
||||
candidates -= m
|
||||
c -= m
|
||||
if len(c) == 0:
|
||||
break
|
||||
if invert:
|
||||
matches = self.universal_set() - matches
|
||||
return matches
|
||||
@ -669,12 +674,15 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
if l and l != 'all' and l in self.all_search_locations:
|
||||
terms.add(l)
|
||||
if terms:
|
||||
c = candidates.copy()
|
||||
for l in terms:
|
||||
try:
|
||||
m = self.get_matches(l, query,
|
||||
candidates=candidates, allow_recursion=allow_recursion)
|
||||
candidates=c, allow_recursion=allow_recursion)
|
||||
matches |= m
|
||||
candidates -= m
|
||||
c -= m
|
||||
if len(c) == 0:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
return matches
|
||||
@ -751,6 +759,7 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
for i, loc in enumerate(location):
|
||||
location[i] = db_col[loc]
|
||||
|
||||
current_candidates = candidates.copy()
|
||||
for loc in location: # location is now an array of field indices
|
||||
if loc == db_col['authors']:
|
||||
### DB stores authors with commas changed to bars, so change query
|
||||
@ -767,7 +776,7 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
else:
|
||||
q = query
|
||||
|
||||
for id_ in candidates:
|
||||
for id_ in current_candidates:
|
||||
item = self._data[id_]
|
||||
if item is None: continue
|
||||
|
||||
@ -814,6 +823,7 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
if _match(q, vals, matchkind):
|
||||
matches.add(item[0])
|
||||
continue
|
||||
current_candidates -= matches
|
||||
return matches
|
||||
|
||||
def search(self, query, return_matches=False):
|
||||
@ -903,7 +913,9 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
|
||||
def set(self, row, col, val, row_is_id=False):
|
||||
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):
|
||||
id = row if row_is_id else self._map_filtered[row]
|
||||
@ -1082,15 +1094,14 @@ class SortKeyGenerator(object):
|
||||
dt = 'datetime'
|
||||
elif sb == 'number':
|
||||
try:
|
||||
val = val.replace(',', '').strip()
|
||||
p = 1
|
||||
for i, candidate in enumerate(
|
||||
(' B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')):
|
||||
('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')):
|
||||
if val.endswith(candidate):
|
||||
p = 1024**(i)
|
||||
val = val[:-len(candidate)].strip()
|
||||
break
|
||||
val = float(val) * p
|
||||
val = locale.atof(val) * p
|
||||
except:
|
||||
val = 0.0
|
||||
dt = 'float'
|
||||
|
@ -251,6 +251,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
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
|
||||
defs['bools_are_tristate'] = \
|
||||
@ -3737,4 +3745,42 @@ books_series_link feeds
|
||||
'SELECT {0}, count(*) FROM books_{1}_link GROUP BY {0}'.format(
|
||||
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
|
||||
|
||||
|
||||
|
@ -34,6 +34,9 @@ class DBPrefs(dict):
|
||||
def to_raw(self, val):
|
||||
return json.dumps(val, indent=2, default=to_json)
|
||||
|
||||
def has_setting(self, key):
|
||||
return key in self
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return dict.__getitem__(self, key)
|
||||
|
@ -5,6 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import sys, os, cPickle, textwrap, stat
|
||||
from subprocess import check_call
|
||||
from functools import partial
|
||||
|
||||
from calibre import __appname__, prints, guess_type
|
||||
from calibre.constants import islinux, isnetbsd, isbsd
|
||||
@ -346,19 +347,28 @@ class PostInstall:
|
||||
try:
|
||||
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), \
|
||||
PreserveMIMEDefaults():
|
||||
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'))
|
||||
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',
|
||||
'128'))
|
||||
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'))
|
||||
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'))
|
||||
|
||||
mimetypes = set([])
|
||||
@ -385,14 +395,14 @@ class PostInstall:
|
||||
'calibre-ebook-viewer.desktop')
|
||||
for x in des:
|
||||
cmd = ['xdg-desktop-menu', 'install', '--noupdate', './'+x]
|
||||
check_call(' '.join(cmd), shell=True)
|
||||
cc(' '.join(cmd), shell=True)
|
||||
self.menu_resources.append(x)
|
||||
check_call(['xdg-desktop-menu', 'forceupdate'])
|
||||
cc(['xdg-desktop-menu', 'forceupdate'])
|
||||
f = open('calibre-mimetypes', 'wb')
|
||||
f.write(MIME)
|
||||
f.close()
|
||||
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:
|
||||
if self.opts.fatal_errors:
|
||||
raise
|
||||
|
@ -76,15 +76,15 @@ def test_qt():
|
||||
print ('Qt OK!')
|
||||
|
||||
def test_imaging():
|
||||
from calibre.utils.magick.draw import create_canvas, Image
|
||||
im = create_canvas(20, 20, '#ffffff')
|
||||
jpg = im.export('jpg')
|
||||
Image().load(jpg)
|
||||
im.export('png')
|
||||
from calibre.ebooks import calibre_cover
|
||||
data = calibre_cover('test', 'ok')
|
||||
if len(data) > 1000:
|
||||
print ('ImageMagick OK!')
|
||||
else:
|
||||
raise RuntimeError('ImageMagick choked!')
|
||||
from PIL import Image
|
||||
i = Image.open(cStringIO.StringIO(jpg))
|
||||
if i.size != (20, 20):
|
||||
i = Image.open(cStringIO.StringIO(data))
|
||||
if i.size < (20, 20):
|
||||
raise RuntimeError('PIL choked!')
|
||||
print ('PIL OK!')
|
||||
|
||||
@ -94,6 +94,12 @@ def test_unrar():
|
||||
raise RuntimeError('Failed to load libunrar')
|
||||
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():
|
||||
test_plugins()
|
||||
test_lxml()
|
||||
@ -102,6 +108,7 @@ def test():
|
||||
test_qt()
|
||||
test_imaging()
|
||||
test_unrar()
|
||||
test_icu()
|
||||
if iswindows:
|
||||
test_win32()
|
||||
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
Loading…
x
Reference in New Issue
Block a user