mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
merge from trunk
This commit is contained in:
commit
cc84c6f11b
@ -2,7 +2,7 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__author__ = 'Luis Hernandez'
|
__author__ = 'Luis Hernandez'
|
||||||
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
|
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
|
||||||
description = 'Periódico gratuito en español - v0.5 - 25 Jan 2011'
|
description = 'Periódico gratuito en español - v0.8 - 27 Jan 2011'
|
||||||
|
|
||||||
'''
|
'''
|
||||||
www.20minutos.es
|
www.20minutos.es
|
||||||
@ -15,8 +15,8 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
|||||||
title = u'20 Minutos'
|
title = u'20 Minutos'
|
||||||
publisher = u'Grupo 20 Minutos'
|
publisher = u'Grupo 20 Minutos'
|
||||||
|
|
||||||
__author__ = u'Luis Hernández'
|
__author__ = 'Luis Hernández'
|
||||||
description = u'Periódico gratuito en español'
|
description = 'Periódico gratuito en español'
|
||||||
cover_url = 'http://estaticos.20minutos.es/mmedia/especiales/corporativo/css/img/logotipos_grupo20minutos.gif'
|
cover_url = 'http://estaticos.20minutos.es/mmedia/especiales/corporativo/css/img/logotipos_grupo20minutos.gif'
|
||||||
|
|
||||||
oldest_article = 5
|
oldest_article = 5
|
||||||
@ -30,8 +30,9 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
|||||||
language = 'es'
|
language = 'es'
|
||||||
timefmt = '[%a, %d %b, %Y]'
|
timefmt = '[%a, %d %b, %Y]'
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'id':['content']})
|
keep_only_tags = [
|
||||||
,dict(name='div', attrs={'class':['boxed','description','lead','article-content']})
|
dict(name='div', attrs={'id':['content','vinetas',]})
|
||||||
|
,dict(name='div', attrs={'class':['boxed','description','lead','article-content','cuerpo estirar']})
|
||||||
,dict(name='span', attrs={'class':['photo-bar']})
|
,dict(name='span', attrs={'class':['photo-bar']})
|
||||||
,dict(name='ul', attrs={'class':['article-author']})
|
,dict(name='ul', attrs={'class':['article-author']})
|
||||||
]
|
]
|
||||||
@ -42,9 +43,11 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
|||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name='ol', attrs={'class':['navigation',]})
|
dict(name='ol', attrs={'class':['navigation',]})
|
||||||
,dict(name='span', attrs={'class':['action']})
|
,dict(name='span', attrs={'class':['action']})
|
||||||
,dict(name='div', attrs={'class':['twitter comments-list hidden','related-news','col']})
|
,dict(name='div', attrs={'class':['twitter comments-list hidden','related-news','col','photo-gallery','calendario','article-comment','postto estirar','otras_vinetas estirar','kment','user-actions']})
|
||||||
,dict(name='div', attrs={'id':['twitter-destacados']})
|
,dict(name='div', attrs={'id':['twitter-destacados','eco-tabs','inner','vineta_calendario','vinetistas clearfix','otras_vinetas estirar','MIN1','main','SUP1','INT']})
|
||||||
,dict(name='ul', attrs={'class':['article-user-actions','stripped-list']})
|
,dict(name='ul', attrs={'class':['article-user-actions','stripped-list']})
|
||||||
|
,dict(name='ul', attrs={'id':['site-links']})
|
||||||
|
,dict(name='li', attrs={'class':['puntuacion','enviar','compartir']})
|
||||||
]
|
]
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
@ -62,6 +65,6 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
|||||||
,(u'Empleo' , u'http://www.20minutos.es/rss/empleo/')
|
,(u'Empleo' , u'http://www.20minutos.es/rss/empleo/')
|
||||||
,(u'Cine' , u'http://www.20minutos.es/rss/cine/')
|
,(u'Cine' , u'http://www.20minutos.es/rss/cine/')
|
||||||
,(u'Musica' , u'http://www.20minutos.es/rss/musica/')
|
,(u'Musica' , u'http://www.20minutos.es/rss/musica/')
|
||||||
|
,(u'Vinetas' , u'http://www.20minutos.es/rss/vinetas/')
|
||||||
,(u'Comunidad20' , u'http://www.20minutos.es/rss/zona20/')
|
,(u'Comunidad20' , u'http://www.20minutos.es/rss/zona20/')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ class CanWestPaper(BasicNewsRecipe):
|
|||||||
|
|
||||||
language = 'en_CA'
|
language = 'en_CA'
|
||||||
__author__ = 'Nick Redding'
|
__author__ = 'Nick Redding'
|
||||||
|
encoding = 'latin1'
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
timefmt = ' [%b %d]'
|
timefmt = ' [%b %d]'
|
||||||
extra_css = '''
|
extra_css = '''
|
||||||
@ -97,6 +98,8 @@ class CanWestPaper(BasicNewsRecipe):
|
|||||||
atag = h1tag.find('a',href=True)
|
atag = h1tag.find('a',href=True)
|
||||||
if not atag:
|
if not atag:
|
||||||
continue
|
continue
|
||||||
|
url = atag['href']
|
||||||
|
if not url.startswith('http:'):
|
||||||
url = self.url_prefix+'/news/todays-paper/'+atag['href']
|
url = self.url_prefix+'/news/todays-paper/'+atag['href']
|
||||||
#self.log("Section %s" % key)
|
#self.log("Section %s" % key)
|
||||||
#self.log("url %s" % url)
|
#self.log("url %s" % url)
|
||||||
|
11
resources/recipes/capes_n_babes.recipe
Normal file
11
resources/recipes/capes_n_babes.recipe
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class CapesnBabesRecipe(BasicNewsRecipe):
|
||||||
|
title = u'Capes n Babes'
|
||||||
|
language = 'en'
|
||||||
|
description = 'The Capes n Babes comic Blog'
|
||||||
|
__author__ = 'skyhawker'
|
||||||
|
oldest_article = 31
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
use_embedded_content = True
|
||||||
|
feeds = [(u'Capes & Babes', u'feed://www.capesnbabes.com/feed/')]
|
45
resources/recipes/dbb.recipe
Normal file
45
resources/recipes/dbb.recipe
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# -*- coding: utf-8
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__author__ = 'Luis Hernandez'
|
||||||
|
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
|
||||||
|
|
||||||
|
'''
|
||||||
|
http://www.filmica.com/david_bravo/
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Blog de David Bravo'
|
||||||
|
publisher = u'Filmica'
|
||||||
|
|
||||||
|
__author__ = 'Luis Hernández'
|
||||||
|
description = 'blog sobre leyes, p2p y copyright'
|
||||||
|
cover_url = 'http://www.elpais.es/edigitales/image.php?foto=par/portada/1551.jpg'
|
||||||
|
|
||||||
|
oldest_article = 365
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
|
||||||
|
remove_javascript = True
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
|
||||||
|
encoding = 'ISO-8859-1'
|
||||||
|
language = 'es'
|
||||||
|
timefmt = '[%a, %d %b, %Y]'
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'class':['blog','date','blogbody','comments-head','comments-body']})
|
||||||
|
,dict(name='span', attrs={'class':['comments-post']})
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags_before = dict(name='div' , attrs={'id':['bitacoras']})
|
||||||
|
remove_tags_after = dict(name='div' , attrs={'id':['comments-body']})
|
||||||
|
|
||||||
|
extra_css = ' p{text-align: justify; font-size: 100%} body{ text-align: left; font-family: serif; font-size: 100% } h2{ font-family: sans-serif; font-size:75%; font-weight: 800; text-align: justify } h3{ font-family: sans-serif; font-size:150%; font-weight: 600; text-align: left } img{margin-bottom: 0.4em} '
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [(u'Blog', u'http://www.filmica.com/david_bravo/index.rdf')]
|
74
resources/recipes/la_nueva.recipe
Normal file
74
resources/recipes/la_nueva.recipe
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__author__ = 'Luis Hernandez'
|
||||||
|
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
|
||||||
|
description = 'Diario independiente de Asturias - v1.0 - 27 Jan 2011'
|
||||||
|
|
||||||
|
'''
|
||||||
|
www.lne.es
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'La Nueva España'
|
||||||
|
publisher = u'Editorial Prensa Iberica'
|
||||||
|
|
||||||
|
__author__ = 'Luis Hernandez'
|
||||||
|
description = 'Diario independiente de Asturias'
|
||||||
|
cover_url = 'http://estaticos00.lne.es//elementosWeb/mediaweb/images/iconos/logo2.jpg'
|
||||||
|
|
||||||
|
oldest_article = 3
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
|
||||||
|
remove_javascript = True
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
|
||||||
|
encoding = 'ISO-8859-1'
|
||||||
|
language = 'es'
|
||||||
|
timefmt = '[%a, %d %b, %Y]'
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'class':['noticia_titular','subtitulo','noticiadd2','noticia_texto']})
|
||||||
|
,dict(name='div', attrs={'id':['noticia_texto']})
|
||||||
|
]
|
||||||
|
|
||||||
|
extra_css = ' p{text-align: justify; font-size: 100%} body{ text-align: left; font-family: serif; font-size: 100% } h1{ font-family: sans-serif; font-size:150%; font-weight: 600; text-align: justify; } h2{ font-family: sans-serif; font-size:120%; font-weight: 500; text-align: justify } '
|
||||||
|
|
||||||
|
|
||||||
|
remove_tags_before = dict(name='div' , attrs={'class':['contenedor']})
|
||||||
|
remove_tags_after = dict(name='div' , attrs={'class':['fin_noticia']})
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'class':['epigrafe','antetitulo','bloqueclear','bloqueclear_video','cuadro_multimedia','cintillo2','editor_documentos','noticiadd','noticiadd3','noticiainterior','fin_noticia']})
|
||||||
|
,dict(name='div', attrs={'id':['evotos']})
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Al minuto' , u'http://www.lne.es/elementosInt/rss/AlMinuto')
|
||||||
|
,(u'General' , u'http://www.lne.es/elementosInt/rss/55')
|
||||||
|
,(u'Nacional' , u'http://www.lne.es/elementosInt/rss/43')
|
||||||
|
,(u'Internacional' , u'http://www.lne.es/elementosInt/rss/44')
|
||||||
|
,(u'Economia' , u'http://www.lne.es/elementosInt/rss/45')
|
||||||
|
,(u'Deportes' , u'http://www.lne.es/elementosInt/rss/47')
|
||||||
|
,(u'Campeones' , u'http://www.lne.es/elementosInt/rss/65')
|
||||||
|
,(u'Sociedad' , u'http://www.lne.es/elementosInt/rss/46')
|
||||||
|
,(u'Sucesos' , u'http://www.lne.es/elementosInt/rss/48')
|
||||||
|
,(u'Galeria' , u'http://www.lne.es/elementosInt/rss/51')
|
||||||
|
,(u'Cultura' , u'http://www.lne.es/elementosInt/rss/66')
|
||||||
|
,(u'Motor' , u'http://www.lne.es/elementosInt/rss/62')
|
||||||
|
,(u'Opinion' , u'http://www.lne.es/elementosInt/rss/52')
|
||||||
|
,(u'Asturias' , u'http://www.lne.es/elementosInt/rss/42')
|
||||||
|
,(u'Oviedo' , u'http://www.lne.es/elementosInt/rss/31')
|
||||||
|
,(u'Gijon' , u'http://www.lne.es/elementosInt/rss/35')
|
||||||
|
,(u'Aviles' , u'http://www.lne.es/elementosInt/rss/36')
|
||||||
|
,(u'Nalon' , u'http://www.lne.es/elementosInt/rss/37')
|
||||||
|
,(u'Cuencas' , u'http://www.lne.es/elementosInt/rss/38')
|
||||||
|
,(u'Caudal' , u'http://www.lne.es/elementosInt/rss/39')
|
||||||
|
,(u'Oriente' , u'http://www.lne.es/elementosInt/rss/40')
|
||||||
|
,(u'Occidente' , u'http://www.lne.es/elementosInt/rss/41')
|
||||||
|
,(u'Mar y Campo' , u'http://www.lne.es/elementosInt/rss/63')
|
||||||
|
,(u'Ultima' , u'http://www.lne.es/elementosInt/rss/50')
|
||||||
|
]
|
@ -1,9 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__author__ = 'Luis Hernandez'
|
||||||
|
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
|
||||||
|
description = 'Diario local de Talavera de la Reina - v1.2 - 27 Jan 2011'
|
||||||
|
|
||||||
|
'''
|
||||||
|
http://www.latribunadetalavera.es/
|
||||||
|
'''
|
||||||
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||||
|
|
||||||
title = u'La Tribuna de Talavera'
|
title = u'La Tribuna de Talavera'
|
||||||
|
publisher = u'Grupo PROMECAL'
|
||||||
|
|
||||||
__author__ = 'Luis Hernández'
|
__author__ = 'Luis Hernández'
|
||||||
description = 'Diario de Talavera de la Reina'
|
description = 'Diario local de Talavera de la Reina'
|
||||||
cover_url = 'http://www.latribunadetalavera.es/entorno/mancheta.gif'
|
cover_url = 'http://www.latribunadetalavera.es/entorno/mancheta.gif'
|
||||||
|
|
||||||
oldest_article = 5
|
oldest_article = 5
|
||||||
@ -17,7 +30,8 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
|||||||
language = 'es'
|
language = 'es'
|
||||||
timefmt = '[%a, %d %b, %Y]'
|
timefmt = '[%a, %d %b, %Y]'
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'id':['articulo']})
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'id':['articulo']})
|
||||||
,dict(name='div', attrs={'class':['foto']})
|
,dict(name='div', attrs={'class':['foto']})
|
||||||
,dict(name='p', attrs={'id':['texto']})
|
,dict(name='p', attrs={'id':['texto']})
|
||||||
]
|
]
|
||||||
@ -25,5 +39,13 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
|||||||
remove_tags_before = dict(name='div' , attrs={'class':['comparte']})
|
remove_tags_before = dict(name='div' , attrs={'class':['comparte']})
|
||||||
remove_tags_after = dict(name='div' , attrs={'id':['relacionadas']})
|
remove_tags_after = dict(name='div' , attrs={'id':['relacionadas']})
|
||||||
|
|
||||||
|
extra_css = ' p{text-align: justify; font-size: 100%} body{ text-align: left; font-family: serif; font-size: 100% } h1{ font-family: sans-serif; font-size:150%; font-weight: 700; text-align: justify; } h2{ font-family: sans-serif; font-size:120%; font-weight: 600; text-align: justify } h3{ font-family: sans-serif; font-size:60%; font-weight: 600; text-align: left } h4{ font-family: sans-serif; font-size:80%; font-weight: 600; text-align: left } h5{ font-family: sans-serif; font-size:70%; font-weight: 600; text-align: left }img{margin-bottom: 0.4em} '
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for alink in soup.findAll('a'):
|
||||||
|
if alink.string is not None:
|
||||||
|
tstr = alink.string
|
||||||
|
alink.replaceWith(tstr)
|
||||||
|
return soup
|
||||||
|
|
||||||
feeds = [(u'Portada', u'http://www.latribunadetalavera.es/rss.html')]
|
feeds = [(u'Portada', u'http://www.latribunadetalavera.es/rss.html')]
|
||||||
|
40
resources/recipes/leduc.recipe
Normal file
40
resources/recipes/leduc.recipe
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1292550626(BasicNewsRecipe):
|
||||||
|
title = 'Leduc - Wetaskiwin Pipestone Flyer'
|
||||||
|
__author__ = 'Brian Hahn'
|
||||||
|
description = 'News from Alberta, Canada'
|
||||||
|
oldest_article = 56
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
#delay = 1
|
||||||
|
use_embedded_content = False
|
||||||
|
publisher = 'Pipestone Publishing'
|
||||||
|
category = 'News, Alberta, Canada'
|
||||||
|
language = 'en_CA'
|
||||||
|
encoding = 'iso-8859-1'
|
||||||
|
cover_url = 'http://www.pipestoneflyer.ca/images/calibre-cover.jpg'
|
||||||
|
remove_tags_before = dict(id='ContentPanel')
|
||||||
|
remove_tags_after = dict(id='ContentPanel')
|
||||||
|
remove_tags = [dict(name='div', attrs={'id':'StoryNav'}),dict(name='div', attrs={'id':'BottomAds'}),dict(name='div', attrs={'id':'MoreStoryLinks'})]
|
||||||
|
extra_css = 'img { margin:5px }'
|
||||||
|
feeds = [
|
||||||
|
('Feature', 'http://www.pipestoneflyer.ca/Feature.rss'),
|
||||||
|
('Editors Desk', 'http://www.pipestoneflyer.ca/Editor%27s%20Desk.rss'),
|
||||||
|
('Letters', 'http://www.pipestoneflyer.ca/Letters.rss'),
|
||||||
|
('A Loco Viewpoint', 'http://www.pipestoneflyer.ca/A%20Loco%20Viewpoint.rss'),
|
||||||
|
('Lifes Doorway', 'http://www.pipestoneflyer.ca/Life%27s%20Doorway.rss'),
|
||||||
|
('From the Otherside', 'http://www.pipestoneflyer.ca/From%20the%20Otherside.rss'),
|
||||||
|
('Opinion', 'http://www.pipestoneflyer.ca/Opinion.rss'),
|
||||||
|
('Community', 'http://www.pipestoneflyer.ca/Community.rss'),
|
||||||
|
('Sports', 'http://www.pipestoneflyer.ca/Sports.rss'),
|
||||||
|
('Chambers', 'http://www.pipestoneflyer.ca/Chambers.rss'),
|
||||||
|
('Government', 'http://www.pipestoneflyer.ca/Government.rss'),
|
||||||
|
('Environment', 'http://www.pipestoneflyer.ca/Environment.rss'),
|
||||||
|
('Health', 'http://www.pipestoneflyer.ca/Health.rss'),
|
||||||
|
('Funnies', 'http://www.pipestoneflyer.ca/Funnies.rss'),
|
||||||
|
('Faith', 'http://www.pipestoneflyer.ca/Faith.rss'),
|
||||||
|
('News and Views', 'http://www.pipestoneflyer.ca/News%20and%20Views.rss'),
|
||||||
|
('Obituaries', 'http://www.pipestoneflyer.ca/Obituaries.rss'),
|
||||||
|
('Police Blotter', 'http://www.pipestoneflyer.ca/Police%20Blotter.rss'),
|
||||||
|
]
|
15
resources/recipes/spin_magazine.recipe
Normal file
15
resources/recipes/spin_magazine.recipe
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1296179411(BasicNewsRecipe):
|
||||||
|
title = u'SPIN Magzine'
|
||||||
|
__author__ = 'Quistopher'
|
||||||
|
language = 'en'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Daily Noise Blog | SPIN.com', u'http://www.spin.com/blog/feed'),
|
||||||
|
(u'It Happened Last Night | SPIN.com', u'http://www.spin.com/it-happened-last-night/feed'),
|
||||||
|
(u'Album Reviews | SPIN.com', u'http://www.spin.com/album-reviews/feed')
|
||||||
|
|
||||||
|
]
|
@ -495,6 +495,22 @@ class SonyReader900Output(SonyReaderOutput):
|
|||||||
screen_size = (600, 999)
|
screen_size = (600, 999)
|
||||||
comic_screen_size = screen_size
|
comic_screen_size = screen_size
|
||||||
|
|
||||||
|
class GenericEink(SonyReaderOutput):
|
||||||
|
|
||||||
|
name = 'Generic e-ink'
|
||||||
|
short_name = 'generic_eink'
|
||||||
|
description = _('Suitable for use with any e-ink device')
|
||||||
|
epub_periodical_format = None
|
||||||
|
|
||||||
|
class GenericEinkLarge(GenericEink):
|
||||||
|
|
||||||
|
name = 'Generic e-ink large'
|
||||||
|
short_name = 'generic_eink_large'
|
||||||
|
description = _('Suitable for use with any large screen e-ink device')
|
||||||
|
|
||||||
|
screen_size = (600, 999)
|
||||||
|
comic_screen_size = screen_size
|
||||||
|
|
||||||
class JetBook5Output(OutputProfile):
|
class JetBook5Output(OutputProfile):
|
||||||
|
|
||||||
name = 'JetBook 5-inch'
|
name = 'JetBook 5-inch'
|
||||||
@ -719,6 +735,6 @@ output_profiles = [OutputProfile, SonyReaderOutput, SonyReader300Output,
|
|||||||
iPadOutput, KoboReaderOutput, TabletOutput, SamsungGalaxy,
|
iPadOutput, KoboReaderOutput, TabletOutput, SamsungGalaxy,
|
||||||
SonyReaderLandscapeOutput, KindleDXOutput, IlliadOutput,
|
SonyReaderLandscapeOutput, KindleDXOutput, IlliadOutput,
|
||||||
IRexDR1000Output, IRexDR800Output, JetBook5Output, NookOutput,
|
IRexDR1000Output, IRexDR800Output, JetBook5Output, NookOutput,
|
||||||
BambookOutput, NookColorOutput]
|
BambookOutput, NookColorOutput, GenericEink, GenericEinkLarge]
|
||||||
|
|
||||||
output_profiles.sort(cmp=lambda x,y:cmp(x.name.lower(), y.name.lower()))
|
output_profiles.sort(cmp=lambda x,y:cmp(x.name.lower(), y.name.lower()))
|
||||||
|
@ -36,6 +36,7 @@ def author_to_author_sort(author):
|
|||||||
return author
|
return author
|
||||||
author = _bracket_pat.sub('', author).strip()
|
author = _bracket_pat.sub('', author).strip()
|
||||||
tokens = author.split()
|
tokens = author.split()
|
||||||
|
if tokens and tokens[-1] not in ('Inc.', 'Inc'):
|
||||||
tokens = tokens[-1:] + tokens[:-1]
|
tokens = tokens[-1:] + tokens[:-1]
|
||||||
if len(tokens) > 1 and method != 'nocomma':
|
if len(tokens) > 1 and method != 'nocomma':
|
||||||
tokens[0] += ','
|
tokens[0] += ','
|
||||||
|
@ -143,7 +143,9 @@ class PML_HTMLizer(object):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.state = {}
|
self.state = {}
|
||||||
self.toc = TOC()
|
# toc consists of a tuple
|
||||||
|
# (level, (href, id, text))
|
||||||
|
self.toc = []
|
||||||
self.file_name = ''
|
self.file_name = ''
|
||||||
|
|
||||||
def prepare_pml(self, pml):
|
def prepare_pml(self, pml):
|
||||||
@ -494,7 +496,7 @@ class PML_HTMLizer(object):
|
|||||||
output = []
|
output = []
|
||||||
|
|
||||||
self.state = {}
|
self.state = {}
|
||||||
self.toc = TOC()
|
self.toc = []
|
||||||
self.file_name = file_name
|
self.file_name = file_name
|
||||||
|
|
||||||
indent_state = {'t': False, 'T': False}
|
indent_state = {'t': False, 'T': False}
|
||||||
@ -542,6 +544,7 @@ class PML_HTMLizer(object):
|
|||||||
# inside of ="" so we don't have do special processing
|
# inside of ="" so we don't have do special processing
|
||||||
# for C.
|
# for C.
|
||||||
t = ''
|
t = ''
|
||||||
|
level = 0
|
||||||
if c in 'XC':
|
if c in 'XC':
|
||||||
level = line.read(1)
|
level = line.read(1)
|
||||||
id = 'pml_toc-%s' % len(self.toc)
|
id = 'pml_toc-%s' % len(self.toc)
|
||||||
@ -553,7 +556,7 @@ class PML_HTMLizer(object):
|
|||||||
if not value or value == '':
|
if not value or value == '':
|
||||||
text = t
|
text = t
|
||||||
else:
|
else:
|
||||||
self.toc.add_item(os.path.basename(self.file_name), id, value)
|
self.toc.append((level, (os.path.basename(self.file_name), id, value)))
|
||||||
text = '%s<span id="%s"></span>' % (t, id)
|
text = '%s<span id="%s"></span>' % (t, id)
|
||||||
elif c == 'm':
|
elif c == 'm':
|
||||||
empty = False
|
empty = False
|
||||||
@ -624,7 +627,72 @@ class PML_HTMLizer(object):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
def get_toc(self):
|
def get_toc(self):
|
||||||
return self.toc
|
'''
|
||||||
|
Toc can have up to 5 levels, 0 - 4 inclusive.
|
||||||
|
|
||||||
|
This function will add items to their appropriate
|
||||||
|
depth in the TOC tree. If the specified depth is
|
||||||
|
invalid (item would not have a valid parent) add
|
||||||
|
it to the next valid level above the specified
|
||||||
|
level.
|
||||||
|
'''
|
||||||
|
# Base toc object all items will be added to.
|
||||||
|
n_toc = TOC()
|
||||||
|
# Used to track nodes in the toc so we can add
|
||||||
|
# sub items to the appropriate place in tree.
|
||||||
|
t_l0 = None
|
||||||
|
t_l1 = None
|
||||||
|
t_l2 = None
|
||||||
|
t_l3 = None
|
||||||
|
|
||||||
|
for level, (href, id, text) in self.toc:
|
||||||
|
if level == u'0':
|
||||||
|
t_l0 = n_toc.add_item(href, id, text)
|
||||||
|
t_l1 = None
|
||||||
|
t_l2 = None
|
||||||
|
t_l3 = None
|
||||||
|
elif level == u'1':
|
||||||
|
if t_l0 == None:
|
||||||
|
t_l0 = n_toc
|
||||||
|
t_l1 = t_l0.add_item(href, id, text)
|
||||||
|
t_l2 = None
|
||||||
|
t_l3 = None
|
||||||
|
elif level == u'2':
|
||||||
|
if t_l1 == None:
|
||||||
|
if t_l0 == None:
|
||||||
|
t_l1 = n_toc
|
||||||
|
else:
|
||||||
|
t_l1 = t_l0
|
||||||
|
t_l2 = t_l1.add_item(href, id, text)
|
||||||
|
t_l3 = None
|
||||||
|
elif level == u'3':
|
||||||
|
if t_l2 == None:
|
||||||
|
if t_l1 == None:
|
||||||
|
if t_l0 == None:
|
||||||
|
t_l2 = n_toc
|
||||||
|
else:
|
||||||
|
t_l2 = t_l0
|
||||||
|
else:
|
||||||
|
t_l2 = t_l1
|
||||||
|
t_l3 = t_l2.add_item(href, id, text)
|
||||||
|
# Level 4.
|
||||||
|
# Anything above 4 is invalid but we will count
|
||||||
|
# it as level 4.
|
||||||
|
else:
|
||||||
|
if t_l3 == None:
|
||||||
|
if t_l2 == None:
|
||||||
|
if t_l1 == None:
|
||||||
|
if t_l0 == None:
|
||||||
|
t_l3 = n_toc
|
||||||
|
else:
|
||||||
|
t_l3 = t_l0
|
||||||
|
else:
|
||||||
|
t_l3 = t_l1
|
||||||
|
else:
|
||||||
|
t_l3 = t_l2
|
||||||
|
t_l3.add_item(href, id, text)
|
||||||
|
|
||||||
|
return n_toc
|
||||||
|
|
||||||
|
|
||||||
def pml_to_html(pml):
|
def pml_to_html(pml):
|
||||||
|
@ -83,7 +83,6 @@ class TXTInput(InputFormatPlugin):
|
|||||||
setattr(options, 'markup_chapter_headings', True)
|
setattr(options, 'markup_chapter_headings', True)
|
||||||
setattr(options, 'italicize_common_cases', True)
|
setattr(options, 'italicize_common_cases', True)
|
||||||
setattr(options, 'fix_indents', True)
|
setattr(options, 'fix_indents', True)
|
||||||
setattr(options, 'preserve_spaces', True)
|
|
||||||
setattr(options, 'delete_blank_paragraphs', True)
|
setattr(options, 'delete_blank_paragraphs', True)
|
||||||
setattr(options, 'format_scene_breaks', True)
|
setattr(options, 'format_scene_breaks', True)
|
||||||
setattr(options, 'dehyphenate', True)
|
setattr(options, 'dehyphenate', True)
|
||||||
|
@ -20,9 +20,13 @@ HTML_TEMPLATE = u'<html><head><meta http-equiv="Content-Type" content="text/html
|
|||||||
def clean_txt(txt):
|
def clean_txt(txt):
|
||||||
if isbytestring(txt):
|
if isbytestring(txt):
|
||||||
txt = txt.decode('utf-8', 'replace')
|
txt = txt.decode('utf-8', 'replace')
|
||||||
# Strip whitespace from the beginning and end of the line. Also replace
|
# Strip whitespace from the end of the line. Also replace
|
||||||
# all line breaks with \n.
|
# all line breaks with \n.
|
||||||
txt = '\n'.join([line.strip() for line in txt.splitlines()])
|
txt = '\n'.join([line.rstrip() for line in txt.splitlines()])
|
||||||
|
|
||||||
|
# Replace whitespace at the beginning of the list with
|
||||||
|
txt = re.sub('(?m)(?P<space>[ ]+)', lambda mo: ' ' * mo.groups('space').count(' '), txt)
|
||||||
|
txt = re.sub('(?m)(?P<space>[\t]+)', lambda mo: ' ' * 4 * mo.groups('space').count('\t'), txt)
|
||||||
|
|
||||||
# Condense redundant spaces
|
# Condense redundant spaces
|
||||||
txt = re.sub('[ ]{2,}', ' ', txt)
|
txt = re.sub('[ ]{2,}', ' ', txt)
|
||||||
@ -31,7 +35,7 @@ def clean_txt(txt):
|
|||||||
txt = re.sub('^\s+(?=.)', '', txt)
|
txt = re.sub('^\s+(?=.)', '', txt)
|
||||||
txt = re.sub('(?<=.)\s+$', '', txt)
|
txt = re.sub('(?<=.)\s+$', '', txt)
|
||||||
# Remove excessive line breaks.
|
# Remove excessive line breaks.
|
||||||
txt = re.sub('\n{3,}', '\n\n', txt)
|
txt = re.sub('\n{5,}', '\n\n\n\n', txt)
|
||||||
#remove ASCII invalid chars : 0 to 8 and 11-14 to 24
|
#remove ASCII invalid chars : 0 to 8 and 11-14 to 24
|
||||||
txt = clean_ascii_chars(txt)
|
txt = clean_ascii_chars(txt)
|
||||||
|
|
||||||
@ -59,10 +63,16 @@ def convert_basic(txt, title='', epub_split_size_kb=0):
|
|||||||
txt = split_txt(txt, epub_split_size_kb)
|
txt = split_txt(txt, epub_split_size_kb)
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
|
blank_count = 0
|
||||||
# Split into paragraphs based on having a blank line between text.
|
# Split into paragraphs based on having a blank line between text.
|
||||||
for line in txt.split('\n\n'):
|
for line in txt.split('\n'):
|
||||||
if line.strip():
|
if line.strip():
|
||||||
|
blank_count = 0
|
||||||
lines.append(u'<p>%s</p>' % prepare_string_for_xml(line.replace('\n', ' ')))
|
lines.append(u'<p>%s</p>' % prepare_string_for_xml(line.replace('\n', ' ')))
|
||||||
|
else:
|
||||||
|
blank_count += 1
|
||||||
|
if blank_count == 2:
|
||||||
|
lines.append(u'<p> </p>')
|
||||||
|
|
||||||
return HTML_TEMPLATE % (title, u'\n'.join(lines))
|
return HTML_TEMPLATE % (title, u'\n'.join(lines))
|
||||||
|
|
||||||
@ -85,7 +95,7 @@ def normalize_line_endings(txt):
|
|||||||
return txt
|
return txt
|
||||||
|
|
||||||
def separate_paragraphs_single_line(txt):
|
def separate_paragraphs_single_line(txt):
|
||||||
txt = re.sub(u'(?<=.)\n(?=.)', '\n\n', txt)
|
txt = txt.replace('\n', '\n\n')
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
def separate_paragraphs_print_formatted(txt):
|
def separate_paragraphs_print_formatted(txt):
|
||||||
@ -93,7 +103,7 @@ def separate_paragraphs_print_formatted(txt):
|
|||||||
return txt
|
return txt
|
||||||
|
|
||||||
def preserve_spaces(txt):
|
def preserve_spaces(txt):
|
||||||
txt = txt.replace(' ', ' ')
|
txt = re.sub('(?P<space>[ ]{2,})', lambda mo: ' ' + (' ' * (len(mo.group('space')) - 1)), txt)
|
||||||
txt = txt.replace('\t', ' ')
|
txt = txt.replace('\t', ' ')
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
|
@ -100,6 +100,9 @@ class AddAction(InterfaceAction):
|
|||||||
mi = MetaInformation(_('Unknown'), dlg.selected_authors)
|
mi = MetaInformation(_('Unknown'), dlg.selected_authors)
|
||||||
self.gui.library_view.model().db.import_book(mi, [])
|
self.gui.library_view.model().db.import_book(mi, [])
|
||||||
self.gui.library_view.model().books_added(num)
|
self.gui.library_view.model().books_added(num)
|
||||||
|
if hasattr(self.gui, 'db_images'):
|
||||||
|
self.gui.db_images.reset()
|
||||||
|
self.gui.tags_view.recount()
|
||||||
|
|
||||||
def add_isbns(self, books, add_tags=[]):
|
def add_isbns(self, books, add_tags=[]):
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
|
@ -17,7 +17,7 @@ from calibre.gui2.actions import InterfaceAction
|
|||||||
class GenerateCatalogAction(InterfaceAction):
|
class GenerateCatalogAction(InterfaceAction):
|
||||||
|
|
||||||
name = 'Generate Catalog'
|
name = 'Generate Catalog'
|
||||||
action_spec = (_('Create catalog of books in your calibre library'), None, None, None)
|
action_spec = (_('Create a catalog of the books in your calibre library'), None, None, None)
|
||||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
||||||
|
|
||||||
def generate_catalog(self):
|
def generate_catalog(self):
|
||||||
|
@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import os, shutil
|
import os, shutil
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QMenu, Qt, QInputDialog, QThread, pyqtSignal, QProgressDialog
|
from PyQt4.Qt import QMenu, Qt, QInputDialog
|
||||||
|
|
||||||
from calibre import isbytestring
|
from calibre import isbytestring
|
||||||
from calibre.constants import filesystem_encoding
|
from calibre.constants import filesystem_encoding
|
||||||
@ -16,7 +16,7 @@ from calibre.utils.config import prefs
|
|||||||
from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
|
from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
|
||||||
question_dialog, info_dialog
|
question_dialog, info_dialog
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
from calibre.gui2.dialogs.check_library import CheckLibraryDialog
|
from calibre.gui2.dialogs.check_library import CheckLibraryDialog, DBCheck
|
||||||
|
|
||||||
class LibraryUsageStats(object): # {{{
|
class LibraryUsageStats(object): # {{{
|
||||||
|
|
||||||
@ -76,76 +76,6 @@ class LibraryUsageStats(object): # {{{
|
|||||||
self.write_stats()
|
self.write_stats()
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Check Integrity {{{
|
|
||||||
|
|
||||||
class VacThread(QThread):
|
|
||||||
|
|
||||||
check_done = pyqtSignal(object, object)
|
|
||||||
callback = pyqtSignal(object, object)
|
|
||||||
|
|
||||||
def __init__(self, parent, db):
|
|
||||||
QThread.__init__(self, parent)
|
|
||||||
self.db = db
|
|
||||||
self._parent = parent
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
err = bad = None
|
|
||||||
try:
|
|
||||||
bad = self.db.check_integrity(self.callbackf)
|
|
||||||
except:
|
|
||||||
import traceback
|
|
||||||
err = traceback.format_exc()
|
|
||||||
self.check_done.emit(bad, err)
|
|
||||||
|
|
||||||
def callbackf(self, progress, msg):
|
|
||||||
self.callback.emit(progress, msg)
|
|
||||||
|
|
||||||
|
|
||||||
class CheckIntegrity(QProgressDialog):
|
|
||||||
|
|
||||||
def __init__(self, db, parent=None):
|
|
||||||
QProgressDialog.__init__(self, parent)
|
|
||||||
self.db = db
|
|
||||||
self.setCancelButton(None)
|
|
||||||
self.setMinimum(0)
|
|
||||||
self.setMaximum(100)
|
|
||||||
self.setWindowTitle(_('Checking database integrity'))
|
|
||||||
self.setAutoReset(False)
|
|
||||||
self.setValue(0)
|
|
||||||
|
|
||||||
self.vthread = VacThread(self, db)
|
|
||||||
self.vthread.check_done.connect(self.check_done,
|
|
||||||
type=Qt.QueuedConnection)
|
|
||||||
self.vthread.callback.connect(self.callback, type=Qt.QueuedConnection)
|
|
||||||
self.vthread.start()
|
|
||||||
|
|
||||||
def callback(self, progress, msg):
|
|
||||||
self.setLabelText(msg)
|
|
||||||
self.setValue(int(100*progress))
|
|
||||||
|
|
||||||
def check_done(self, bad, err):
|
|
||||||
if err:
|
|
||||||
error_dialog(self, _('Error'),
|
|
||||||
_('Failed to check database integrity'),
|
|
||||||
det_msg=err, show=True)
|
|
||||||
elif bad:
|
|
||||||
titles = [self.db.title(x, index_is_id=True) for x in bad]
|
|
||||||
det_msg = '\n'.join(titles)
|
|
||||||
warning_dialog(self, _('Some inconsistencies found'),
|
|
||||||
_('The following books had formats or covers listed in the '
|
|
||||||
'database that are not actually available. '
|
|
||||||
'The entries for the formats/covers have been removed. '
|
|
||||||
'You should check them manually. This can '
|
|
||||||
'happen if you manipulate the files in the '
|
|
||||||
'library folder directly.'), det_msg=det_msg, show=True)
|
|
||||||
else:
|
|
||||||
info_dialog(self, _('No errors found'),
|
|
||||||
_('The integrity check completed with no uncorrectable errors found.'),
|
|
||||||
show=True)
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class ChooseLibraryAction(InterfaceAction):
|
class ChooseLibraryAction(InterfaceAction):
|
||||||
|
|
||||||
name = 'Choose Library'
|
name = 'Choose Library'
|
||||||
@ -209,14 +139,6 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
None, None), attr='action_check_library')
|
None, None), attr='action_check_library')
|
||||||
ac.triggered.connect(self.check_library, type=Qt.QueuedConnection)
|
ac.triggered.connect(self.check_library, type=Qt.QueuedConnection)
|
||||||
self.maintenance_menu.addAction(ac)
|
self.maintenance_menu.addAction(ac)
|
||||||
ac = self.create_action(spec=(_('Check database integrity'), 'lt.png',
|
|
||||||
None, None), attr='action_check_database')
|
|
||||||
ac.triggered.connect(self.check_database, type=Qt.QueuedConnection)
|
|
||||||
self.maintenance_menu.addAction(ac)
|
|
||||||
ac = self.create_action(spec=(_('Recover database'), 'lt.png',
|
|
||||||
None, None), attr='action_restore_database')
|
|
||||||
ac.triggered.connect(self.restore_database, type=Qt.QueuedConnection)
|
|
||||||
self.maintenance_menu.addAction(ac)
|
|
||||||
self.choose_menu.addMenu(self.maintenance_menu)
|
self.choose_menu.addMenu(self.maintenance_menu)
|
||||||
|
|
||||||
def pick_random(self, *args):
|
def pick_random(self, *args):
|
||||||
@ -346,28 +268,35 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
'rate of approximately 1 book every three seconds.'), show=True)
|
'rate of approximately 1 book every three seconds.'), show=True)
|
||||||
|
|
||||||
def check_library(self):
|
def check_library(self):
|
||||||
db = self.gui.library_view.model().db
|
self.gui.library_view.save_state()
|
||||||
d = CheckLibraryDialog(self.gui.parent(), db)
|
|
||||||
d.exec_()
|
|
||||||
|
|
||||||
def check_database(self, *args):
|
|
||||||
m = self.gui.library_view.model()
|
m = self.gui.library_view.model()
|
||||||
m.stop_metadata_backup()
|
m.stop_metadata_backup()
|
||||||
try:
|
db = m.db
|
||||||
d = CheckIntegrity(m.db, self.gui)
|
db.prefs.disable_setting = True
|
||||||
d.exec_()
|
|
||||||
finally:
|
|
||||||
m.start_metadata_backup()
|
|
||||||
|
|
||||||
def restore_database(self):
|
d = DBCheck(self.gui, db)
|
||||||
info_dialog(self.gui, _('Recover database'), '<p>'+
|
d.start()
|
||||||
_(
|
try:
|
||||||
'This command rebuilds your calibre database from the information '
|
d.conn.close()
|
||||||
'stored by calibre in the OPF files.<p>'
|
except:
|
||||||
'This function is not currently available in the GUI. You can '
|
pass
|
||||||
'recover your database using the \'calibredb restore_database\' '
|
d.break_cycles()
|
||||||
'command line function.'
|
self.gui.library_moved(db.library_path, call_close=not
|
||||||
), show=True)
|
d.closed_orig_conn)
|
||||||
|
if d.rejected:
|
||||||
|
return
|
||||||
|
if d.error is None:
|
||||||
|
if not question_dialog(self.gui, _('Success'),
|
||||||
|
_('Found no errors in your calibre library database.'
|
||||||
|
' Do you want calibre to check if the files in your '
|
||||||
|
' library match the information in the database?')):
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
return error_dialog(self.gui, _('Failed'),
|
||||||
|
_('Database integrity check failed, click Show details'
|
||||||
|
' for details.'), show=True, det_msg=d.error[1])
|
||||||
|
d = CheckLibraryDialog(self.gui, m.db)
|
||||||
|
d.exec_()
|
||||||
|
|
||||||
def switch_requested(self, location):
|
def switch_requested(self, location):
|
||||||
if not self.change_library_allowed():
|
if not self.change_library_allowed():
|
||||||
|
@ -31,7 +31,7 @@ class ConvertAction(InterfaceAction):
|
|||||||
partial(self.convert_ebook, False, bulk=True))
|
partial(self.convert_ebook, False, bulk=True))
|
||||||
cm.addSeparator()
|
cm.addSeparator()
|
||||||
ac = cm.addAction(
|
ac = cm.addAction(
|
||||||
_('Create catalog of books in your calibre library'))
|
_('Create a catalog of the books in your calibre library'))
|
||||||
ac.triggered.connect(self.gui.iactions['Generate Catalog'].generate_catalog)
|
ac.triggered.connect(self.gui.iactions['Generate Catalog'].generate_catalog)
|
||||||
self.qaction.setMenu(cm)
|
self.qaction.setMenu(cm)
|
||||||
self.qaction.triggered.connect(self.convert_ebook)
|
self.qaction.triggered.connect(self.convert_ebook)
|
||||||
|
@ -19,7 +19,9 @@ class PluginWidget(QWidget, Ui_Form):
|
|||||||
('bib_entry', 0), #mixed
|
('bib_entry', 0), #mixed
|
||||||
('bibfile_enc', 0), #utf-8
|
('bibfile_enc', 0), #utf-8
|
||||||
('bibfile_enctag', 0), #strict
|
('bibfile_enctag', 0), #strict
|
||||||
('impcit', True) ]
|
('impcit', True),
|
||||||
|
('addfiles', False),
|
||||||
|
]
|
||||||
|
|
||||||
sync_enabled = False
|
sync_enabled = False
|
||||||
formats = set(['bib'])
|
formats = set(['bib'])
|
||||||
@ -49,7 +51,7 @@ class PluginWidget(QWidget, Ui_Form):
|
|||||||
opt_value = gprefs.get(self.name + '_' + opt[0], opt[1])
|
opt_value = gprefs.get(self.name + '_' + opt[0], opt[1])
|
||||||
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
||||||
getattr(self, opt[0]).setCurrentIndex(opt_value)
|
getattr(self, opt[0]).setCurrentIndex(opt_value)
|
||||||
elif opt[0] == 'impcit' :
|
elif opt[0] in ['impcit', 'addfiles'] :
|
||||||
getattr(self, opt[0]).setChecked(opt_value)
|
getattr(self, opt[0]).setChecked(opt_value)
|
||||||
else:
|
else:
|
||||||
getattr(self, opt[0]).setText(opt_value)
|
getattr(self, opt[0]).setText(opt_value)
|
||||||
@ -76,7 +78,7 @@ class PluginWidget(QWidget, Ui_Form):
|
|||||||
for opt in self.OPTION_FIELDS:
|
for opt in self.OPTION_FIELDS:
|
||||||
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
||||||
opt_value = getattr(self,opt[0]).currentIndex()
|
opt_value = getattr(self,opt[0]).currentIndex()
|
||||||
elif opt[0] == 'impcit' :
|
elif opt[0] in ['impcit', 'addfiles'] :
|
||||||
opt_value = getattr(self, opt[0]).isChecked()
|
opt_value = getattr(self, opt[0]).isChecked()
|
||||||
else :
|
else :
|
||||||
opt_value = unicode(getattr(self, opt[0]).text())
|
opt_value = unicode(getattr(self, opt[0]).text())
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
</item>
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1" rowspan="12">
|
<item row="1" column="1" rowspan="11">
|
||||||
<widget class="QListWidget" name="db_fields">
|
<widget class="QListWidget" name="db_fields">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||||
@ -141,6 +141,13 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="0">
|
<item row="8" column="0">
|
||||||
|
<widget class="QCheckBox" name="addfiles">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add files path with formats?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="0">
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Expression to form the BibTeX citation tag:</string>
|
<string>Expression to form the BibTeX citation tag:</string>
|
||||||
|
@ -151,12 +151,27 @@ class DateEdit(QDateEdit):
|
|||||||
def set_to_today(self):
|
def set_to_today(self):
|
||||||
self.setDate(now())
|
self.setDate(now())
|
||||||
|
|
||||||
|
def set_to_clear(self):
|
||||||
|
self.setDate(UNDEFINED_QDATE)
|
||||||
|
|
||||||
class DateTime(Base):
|
class DateTime(Base):
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
cm = self.col_metadata
|
cm = self.col_metadata
|
||||||
self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(parent),
|
self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(parent)]
|
||||||
QLabel(''), QPushButton(_('Set \'%s\' to today')%cm['name'], parent)]
|
self.widgets.append(QLabel(''))
|
||||||
|
w = QWidget(parent)
|
||||||
|
self.widgets.append(w)
|
||||||
|
l = QHBoxLayout()
|
||||||
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
|
w.setLayout(l)
|
||||||
|
l.addStretch(1)
|
||||||
|
self.today_button = QPushButton(_('Set \'%s\' to today')%cm['name'], parent)
|
||||||
|
l.addWidget(self.today_button)
|
||||||
|
self.clear_button = QPushButton(_('Clear \'%s\'')%cm['name'], parent)
|
||||||
|
l.addWidget(self.clear_button)
|
||||||
|
l.addStretch(2)
|
||||||
|
|
||||||
w = self.widgets[1]
|
w = self.widgets[1]
|
||||||
format = cm['display'].get('date_format','')
|
format = cm['display'].get('date_format','')
|
||||||
if not format:
|
if not format:
|
||||||
@ -165,7 +180,8 @@ class DateTime(Base):
|
|||||||
w.setCalendarPopup(True)
|
w.setCalendarPopup(True)
|
||||||
w.setMinimumDate(UNDEFINED_QDATE)
|
w.setMinimumDate(UNDEFINED_QDATE)
|
||||||
w.setSpecialValueText(_('Undefined'))
|
w.setSpecialValueText(_('Undefined'))
|
||||||
self.widgets[3].clicked.connect(w.set_to_today)
|
self.today_button.clicked.connect(w.set_to_today)
|
||||||
|
self.clear_button.clicked.connect(w.set_to_clear)
|
||||||
|
|
||||||
def setter(self, val):
|
def setter(self, val):
|
||||||
if val is None:
|
if val is None:
|
||||||
@ -470,11 +486,48 @@ class BulkBase(Base):
|
|||||||
self.setter(val)
|
self.setter(val)
|
||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
|
if not self.a_c_checkbox.isChecked():
|
||||||
|
return
|
||||||
val = self.gui_val
|
val = self.gui_val
|
||||||
val = self.normalize_ui_val(val)
|
val = self.normalize_ui_val(val)
|
||||||
if val != self.initial_val:
|
if val != self.initial_val:
|
||||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
||||||
|
|
||||||
|
def make_widgets(self, parent, main_widget_class, extra_label_text=''):
|
||||||
|
w = QWidget(parent)
|
||||||
|
self.widgets = [QLabel('&'+self.col_metadata['name']+':', w), w]
|
||||||
|
l = QHBoxLayout()
|
||||||
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
|
w.setLayout(l)
|
||||||
|
self.main_widget = main_widget_class(w)
|
||||||
|
l.addWidget(self.main_widget)
|
||||||
|
l.setStretchFactor(self.main_widget, 10)
|
||||||
|
self.a_c_checkbox = QCheckBox( _('Apply changes'), w)
|
||||||
|
l.addWidget(self.a_c_checkbox)
|
||||||
|
self.ignore_change_signals = True
|
||||||
|
|
||||||
|
# connect to the various changed signals so we can auto-update the
|
||||||
|
# apply changes checkbox
|
||||||
|
if hasattr(self.main_widget, 'editTextChanged'):
|
||||||
|
# editable combobox widgets
|
||||||
|
self.main_widget.editTextChanged.connect(self.a_c_checkbox_changed)
|
||||||
|
if hasattr(self.main_widget, 'textChanged'):
|
||||||
|
# lineEdit widgets
|
||||||
|
self.main_widget.textChanged.connect(self.a_c_checkbox_changed)
|
||||||
|
if hasattr(self.main_widget, 'currentIndexChanged'):
|
||||||
|
# combobox widgets
|
||||||
|
self.main_widget.currentIndexChanged[int].connect(self.a_c_checkbox_changed)
|
||||||
|
if hasattr(self.main_widget, 'valueChanged'):
|
||||||
|
# spinbox widgets
|
||||||
|
self.main_widget.valueChanged.connect(self.a_c_checkbox_changed)
|
||||||
|
if hasattr(self.main_widget, 'dateChanged'):
|
||||||
|
# dateEdit widgets
|
||||||
|
self.main_widget.dateChanged.connect(self.a_c_checkbox_changed)
|
||||||
|
|
||||||
|
def a_c_checkbox_changed(self):
|
||||||
|
if not self.ignore_change_signals:
|
||||||
|
self.a_c_checkbox.setChecked(True)
|
||||||
|
|
||||||
class BulkBool(BulkBase, Bool):
|
class BulkBool(BulkBase, Bool):
|
||||||
|
|
||||||
def get_initial_value(self, book_ids):
|
def get_initial_value(self, book_ids):
|
||||||
@ -484,58 +537,144 @@ class BulkBool(BulkBase, Bool):
|
|||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
||||||
val = False
|
val = False
|
||||||
if value is not None and value != val:
|
if value is not None and value != val:
|
||||||
return 'nochange'
|
return None
|
||||||
value = val
|
value = val
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
|
self.make_widgets(parent, QComboBox)
|
||||||
QComboBox(parent)]
|
items = [_('Yes'), _('No'), _('Undefined')]
|
||||||
w = self.widgets[1]
|
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
||||||
items = [_('Yes'), _('No'), _('Undefined'), _('Do not change')]
|
self.main_widget.blockSignals(True)
|
||||||
icons = [I('ok.png'), I('list_remove.png'), I('blank.png'), I('blank.png')]
|
|
||||||
for icon, text in zip(icons, items):
|
for icon, text in zip(icons, items):
|
||||||
w.addItem(QIcon(icon), text)
|
self.main_widget.addItem(QIcon(icon), text)
|
||||||
|
self.main_widget.blockSignals(False)
|
||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
val = self.widgets[1].currentIndex()
|
val = self.main_widget.currentIndex()
|
||||||
return {3: 'nochange', 2: None, 1: False, 0: True}[val]
|
return {2: None, 1: False, 0: True}[val]
|
||||||
|
|
||||||
def setter(self, val):
|
def setter(self, val):
|
||||||
val = {'nochange': 3, None: 2, False: 1, True: 0}[val]
|
val = {None: 2, False: 1, True: 0}[val]
|
||||||
self.widgets[1].setCurrentIndex(val)
|
self.main_widget.setCurrentIndex(val)
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
|
if not self.a_c_checkbox.isChecked():
|
||||||
|
return
|
||||||
val = self.gui_val
|
val = self.gui_val
|
||||||
val = self.normalize_ui_val(val)
|
val = self.normalize_ui_val(val)
|
||||||
if val != self.initial_val and val != 'nochange':
|
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
||||||
val = False
|
val = False
|
||||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
||||||
|
|
||||||
class BulkInt(BulkBase, Int):
|
class BulkInt(BulkBase):
|
||||||
pass
|
|
||||||
|
|
||||||
class BulkFloat(BulkBase, Float):
|
def setup_ui(self, parent):
|
||||||
pass
|
self.make_widgets(parent, QSpinBox)
|
||||||
|
self.main_widget.setRange(-100, sys.maxint)
|
||||||
|
self.main_widget.setSpecialValueText(_('Undefined'))
|
||||||
|
self.main_widget.setSingleStep(1)
|
||||||
|
|
||||||
class BulkRating(BulkBase, Rating):
|
def setter(self, val):
|
||||||
pass
|
if val is None:
|
||||||
|
val = self.main_widget.minimum()
|
||||||
|
else:
|
||||||
|
val = int(val)
|
||||||
|
self.main_widget.setValue(val)
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
class BulkDateTime(BulkBase, DateTime):
|
def getter(self):
|
||||||
pass
|
val = self.main_widget.value()
|
||||||
|
if val == self.main_widget.minimum():
|
||||||
|
val = None
|
||||||
|
return val
|
||||||
|
|
||||||
|
class BulkFloat(BulkInt):
|
||||||
|
|
||||||
|
def setup_ui(self, parent):
|
||||||
|
self.make_widgets(parent, QDoubleSpinBox)
|
||||||
|
self.main_widget.setRange(-100., float(sys.maxint))
|
||||||
|
self.main_widget.setDecimals(2)
|
||||||
|
self.main_widget.setSpecialValueText(_('Undefined'))
|
||||||
|
self.main_widget.setSingleStep(1)
|
||||||
|
|
||||||
|
class BulkRating(BulkBase):
|
||||||
|
|
||||||
|
def setup_ui(self, parent):
|
||||||
|
self.make_widgets(parent, QSpinBox)
|
||||||
|
self.main_widget.setRange(0, 5)
|
||||||
|
self.main_widget.setSuffix(' '+_('star(s)'))
|
||||||
|
self.main_widget.setSpecialValueText(_('Unrated'))
|
||||||
|
self.main_widget.setSingleStep(1)
|
||||||
|
|
||||||
|
def setter(self, val):
|
||||||
|
if val is None:
|
||||||
|
val = 0
|
||||||
|
self.main_widget.setValue(int(round(val/2.)))
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
|
def getter(self):
|
||||||
|
val = self.main_widget.value()
|
||||||
|
if val == 0:
|
||||||
|
val = None
|
||||||
|
else:
|
||||||
|
val *= 2
|
||||||
|
return val
|
||||||
|
|
||||||
|
class BulkDateTime(BulkBase):
|
||||||
|
|
||||||
|
def setup_ui(self, parent):
|
||||||
|
cm = self.col_metadata
|
||||||
|
self.make_widgets(parent, DateEdit)
|
||||||
|
self.widgets.append(QLabel(''))
|
||||||
|
w = QWidget(parent)
|
||||||
|
self.widgets.append(w)
|
||||||
|
l = QHBoxLayout()
|
||||||
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
|
w.setLayout(l)
|
||||||
|
l.addStretch(1)
|
||||||
|
self.today_button = QPushButton(_('Set \'%s\' to today')%cm['name'], parent)
|
||||||
|
l.addWidget(self.today_button)
|
||||||
|
self.clear_button = QPushButton(_('Clear \'%s\'')%cm['name'], parent)
|
||||||
|
l.addWidget(self.clear_button)
|
||||||
|
l.addStretch(2)
|
||||||
|
|
||||||
|
w = self.main_widget
|
||||||
|
format = cm['display'].get('date_format','')
|
||||||
|
if not format:
|
||||||
|
format = 'dd MMM yyyy'
|
||||||
|
w.setDisplayFormat(format)
|
||||||
|
w.setCalendarPopup(True)
|
||||||
|
w.setMinimumDate(UNDEFINED_QDATE)
|
||||||
|
w.setSpecialValueText(_('Undefined'))
|
||||||
|
self.today_button.clicked.connect(w.set_to_today)
|
||||||
|
self.clear_button.clicked.connect(w.set_to_clear)
|
||||||
|
|
||||||
|
def setter(self, val):
|
||||||
|
if val is None:
|
||||||
|
val = self.main_widget.minimumDate()
|
||||||
|
else:
|
||||||
|
val = QDate(val.year, val.month, val.day)
|
||||||
|
self.main_widget.setDate(val)
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
|
def getter(self):
|
||||||
|
val = self.main_widget.date()
|
||||||
|
if val == UNDEFINED_QDATE:
|
||||||
|
val = None
|
||||||
|
else:
|
||||||
|
val = qt_to_dt(val)
|
||||||
|
return val
|
||||||
|
|
||||||
class BulkSeries(BulkBase):
|
class BulkSeries(BulkBase):
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
|
self.make_widgets(parent, EnComboBox)
|
||||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(key=sort_key)
|
values.sort(key=sort_key)
|
||||||
w = EnComboBox(parent)
|
self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
||||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
self.main_widget.setMinimumContentsLength(25)
|
||||||
w.setMinimumContentsLength(25)
|
|
||||||
self.name_widget = w
|
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
|
|
||||||
|
|
||||||
self.widgets.append(QLabel('', parent))
|
self.widgets.append(QLabel('', parent))
|
||||||
w = QWidget(parent)
|
w = QWidget(parent)
|
||||||
layout = QHBoxLayout(w)
|
layout = QHBoxLayout(w)
|
||||||
@ -555,15 +694,24 @@ class BulkSeries(BulkBase):
|
|||||||
layout.addWidget(self.series_start_number)
|
layout.addWidget(self.series_start_number)
|
||||||
layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
||||||
self.widgets.append(w)
|
self.widgets.append(w)
|
||||||
|
self.idx_widget.stateChanged.connect(self.check_changed_checkbox)
|
||||||
|
self.force_number.stateChanged.connect(self.check_changed_checkbox)
|
||||||
|
self.series_start_number.valueChanged.connect(self.check_changed_checkbox)
|
||||||
|
self.remove_series.stateChanged.connect(self.check_changed_checkbox)
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
|
def check_changed_checkbox(self):
|
||||||
|
self.a_c_checkbox.setChecked(True)
|
||||||
|
|
||||||
def initialize(self, book_id):
|
def initialize(self, book_id):
|
||||||
self.idx_widget.setChecked(False)
|
self.idx_widget.setChecked(False)
|
||||||
for c in self.all_values:
|
for c in self.all_values:
|
||||||
self.name_widget.addItem(c)
|
self.main_widget.addItem(c)
|
||||||
self.name_widget.setEditText('')
|
self.main_widget.setEditText('')
|
||||||
|
self.a_c_checkbox.setChecked(False)
|
||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
n = unicode(self.name_widget.currentText()).strip()
|
n = unicode(self.main_widget.currentText()).strip()
|
||||||
i = self.idx_widget.checkState()
|
i = self.idx_widget.checkState()
|
||||||
f = self.force_number.checkState()
|
f = self.force_number.checkState()
|
||||||
s = self.series_start_number.value()
|
s = self.series_start_number.value()
|
||||||
@ -571,6 +719,8 @@ class BulkSeries(BulkBase):
|
|||||||
return n, i, f, s, r
|
return n, i, f, s, r
|
||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
|
if not self.a_c_checkbox.isChecked():
|
||||||
|
return
|
||||||
val, update_indices, force_start, at_value, clear = self.gui_val
|
val, update_indices, force_start, at_value, clear = self.gui_val
|
||||||
val = None if clear else self.normalize_ui_val(val)
|
val = None if clear else self.normalize_ui_val(val)
|
||||||
if clear or val != '':
|
if clear or val != '':
|
||||||
@ -598,9 +748,9 @@ class BulkEnumeration(BulkBase, Enumeration):
|
|||||||
|
|
||||||
def get_initial_value(self, book_ids):
|
def get_initial_value(self, book_ids):
|
||||||
value = None
|
value = None
|
||||||
ret_value = None
|
first = True
|
||||||
dialog_shown = False
|
dialog_shown = False
|
||||||
for i,book_id in enumerate(book_ids):
|
for book_id in book_ids:
|
||||||
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
|
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
|
||||||
if val and val not in self.col_metadata['display']['enum_values']:
|
if val and val not in self.col_metadata['display']['enum_values']:
|
||||||
if not dialog_shown:
|
if not dialog_shown:
|
||||||
@ -610,44 +760,32 @@ class BulkEnumeration(BulkBase, Enumeration):
|
|||||||
self.col_metadata['name']),
|
self.col_metadata['name']),
|
||||||
show=True, show_copy_button=False)
|
show=True, show_copy_button=False)
|
||||||
dialog_shown = True
|
dialog_shown = True
|
||||||
ret_value = ' nochange '
|
if first:
|
||||||
elif (value is not None and value != val) or (val and i != 0):
|
|
||||||
ret_value = ' nochange '
|
|
||||||
value = val
|
value = val
|
||||||
if ret_value is None:
|
first = False
|
||||||
|
elif value != val:
|
||||||
|
value = None
|
||||||
|
if not value:
|
||||||
|
self.ignore_change_signals = False
|
||||||
return value
|
return value
|
||||||
return ret_value
|
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
self.parent = parent
|
self.make_widgets(parent, QComboBox)
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
|
|
||||||
QComboBox(parent)]
|
|
||||||
w = self.widgets[1]
|
|
||||||
vals = self.col_metadata['display']['enum_values']
|
vals = self.col_metadata['display']['enum_values']
|
||||||
w.addItem('Do Not Change')
|
self.main_widget.blockSignals(True)
|
||||||
w.addItem('')
|
self.main_widget.addItem('')
|
||||||
for v in vals:
|
self.main_widget.addItems(vals)
|
||||||
w.addItem(v)
|
self.main_widget.blockSignals(False)
|
||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
if self.widgets[1].currentIndex() == 0:
|
return unicode(self.main_widget.currentText())
|
||||||
return ' nochange '
|
|
||||||
return unicode(self.widgets[1].currentText())
|
|
||||||
|
|
||||||
def setter(self, val):
|
def setter(self, val):
|
||||||
if val == ' nochange ':
|
|
||||||
self.widgets[1].setCurrentIndex(0)
|
|
||||||
else:
|
|
||||||
if val is None:
|
if val is None:
|
||||||
self.widgets[1].setCurrentIndex(1)
|
self.main_widget.setCurrentIndex(0)
|
||||||
else:
|
else:
|
||||||
self.widgets[1].setCurrentIndex(self.widgets[1].findText(val))
|
self.main_widget.setCurrentIndex(self.main_widget.findText(val))
|
||||||
|
self.ignore_change_signals = False
|
||||||
def commit(self, book_ids, notify=False):
|
|
||||||
val = self.gui_val
|
|
||||||
val = self.normalize_ui_val(val)
|
|
||||||
if val != self.initial_val and val != ' nochange ':
|
|
||||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
|
||||||
|
|
||||||
class RemoveTags(QWidget):
|
class RemoveTags(QWidget):
|
||||||
|
|
||||||
@ -658,11 +796,10 @@ class RemoveTags(QWidget):
|
|||||||
layout.setContentsMargins(0, 0, 0, 0)
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
self.tags_box = CompleteLineEdit(parent, values)
|
self.tags_box = CompleteLineEdit(parent, values)
|
||||||
layout.addWidget(self.tags_box, stretch = 1)
|
layout.addWidget(self.tags_box, stretch=3)
|
||||||
# self.tags_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
|
||||||
|
|
||||||
self.checkbox = QCheckBox(_('Remove all tags'), parent)
|
self.checkbox = QCheckBox(_('Remove all tags'), parent)
|
||||||
layout.addWidget(self.checkbox)
|
layout.addWidget(self.checkbox)
|
||||||
|
layout.addStretch(1)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
self.connect(self.checkbox, SIGNAL('stateChanged(int)'), self.box_touched)
|
self.connect(self.checkbox, SIGNAL('stateChanged(int)'), self.box_touched)
|
||||||
|
|
||||||
@ -679,39 +816,45 @@ class BulkText(BulkBase):
|
|||||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(key=sort_key)
|
values.sort(key=sort_key)
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
w = CompleteLineEdit(parent, values)
|
self.make_widgets(parent, CompleteLineEdit,
|
||||||
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
extra_label_text=_('tags to add'))
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+': ' +
|
self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||||
_('tags to add'), parent), w]
|
self.adding_widget = self.main_widget
|
||||||
self.adding_widget = w
|
|
||||||
|
|
||||||
w = RemoveTags(parent, values)
|
w = RemoveTags(parent, values)
|
||||||
self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' +
|
self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' +
|
||||||
_('tags to remove'), parent))
|
_('tags to remove'), parent))
|
||||||
self.widgets.append(w)
|
self.widgets.append(w)
|
||||||
self.removing_widget = w
|
self.removing_widget = w
|
||||||
|
w.tags_box.textChanged.connect(self.a_c_checkbox_changed)
|
||||||
|
w.checkbox.stateChanged.connect(self.a_c_checkbox_changed)
|
||||||
else:
|
else:
|
||||||
w = EnComboBox(parent)
|
self.make_widgets(parent, EnComboBox)
|
||||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
self.main_widget.setSizeAdjustPolicy(
|
||||||
w.setMinimumContentsLength(25)
|
self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
|
self.main_widget.setMinimumContentsLength(25)
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
def initialize(self, book_ids):
|
def initialize(self, book_ids):
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
self.widgets[1].update_items_cache(self.all_values)
|
self.main_widget.update_items_cache(self.all_values)
|
||||||
else:
|
else:
|
||||||
val = self.get_initial_value(book_ids)
|
val = self.get_initial_value(book_ids)
|
||||||
self.initial_val = val = self.normalize_db_val(val)
|
self.initial_val = val = self.normalize_db_val(val)
|
||||||
idx = None
|
idx = None
|
||||||
|
self.main_widget.blockSignals(True)
|
||||||
for i, c in enumerate(self.all_values):
|
for i, c in enumerate(self.all_values):
|
||||||
if c == val:
|
if c == val:
|
||||||
idx = i
|
idx = i
|
||||||
self.widgets[1].addItem(c)
|
self.main_widget.addItem(c)
|
||||||
self.widgets[1].setEditText('')
|
self.main_widget.setEditText('')
|
||||||
if idx is not None:
|
if idx is not None:
|
||||||
self.widgets[1].setCurrentIndex(idx)
|
self.main_widget.setCurrentIndex(idx)
|
||||||
|
self.main_widget.blockSignals(False)
|
||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
|
if not self.a_c_checkbox.isChecked():
|
||||||
|
return
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
remove_all, adding, rtext = self.gui_val
|
remove_all, adding, rtext = self.gui_val
|
||||||
remove = set()
|
remove = set()
|
||||||
@ -740,7 +883,7 @@ class BulkText(BulkBase):
|
|||||||
unicode(self.adding_widget.text()), \
|
unicode(self.adding_widget.text()), \
|
||||||
unicode(self.removing_widget.tags_box.text())
|
unicode(self.removing_widget.tags_box.text())
|
||||||
|
|
||||||
val = unicode(self.widgets[1].currentText()).strip()
|
val = unicode(self.main_widget.currentText()).strip()
|
||||||
if not val:
|
if not val:
|
||||||
val = None
|
val = None
|
||||||
return val
|
return val
|
||||||
|
@ -8,15 +8,12 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import os, sys
|
import os, sys
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
|
||||||
from PyQt4.Qt import QDialog, SIGNAL
|
|
||||||
|
|
||||||
from calibre.customize.ui import config
|
from calibre.customize.ui import config
|
||||||
from calibre.gui2.dialogs.catalog_ui import Ui_Dialog
|
from calibre.gui2.dialogs.catalog_ui import Ui_Dialog
|
||||||
from calibre.gui2 import dynamic
|
from calibre.gui2 import dynamic, ResizableDialog
|
||||||
from calibre.customize.ui import catalog_plugins
|
from calibre.customize.ui import catalog_plugins
|
||||||
|
|
||||||
class Catalog(QDialog, Ui_Dialog):
|
class Catalog(ResizableDialog, Ui_Dialog):
|
||||||
''' Catalog Dialog builder'''
|
''' Catalog Dialog builder'''
|
||||||
|
|
||||||
def __init__(self, parent, dbspec, ids, db):
|
def __init__(self, parent, dbspec, ids, db):
|
||||||
@ -24,10 +21,8 @@ class Catalog(QDialog, Ui_Dialog):
|
|||||||
from calibre import prints as info
|
from calibre import prints as info
|
||||||
from PyQt4.uic import compileUi
|
from PyQt4.uic import compileUi
|
||||||
|
|
||||||
QDialog.__init__(self, parent)
|
ResizableDialog.__init__(self, parent)
|
||||||
|
|
||||||
# Run the dialog setup generated from catalog.ui
|
|
||||||
self.setupUi(self)
|
|
||||||
self.dbspec, self.ids = dbspec, ids
|
self.dbspec, self.ids = dbspec, ids
|
||||||
|
|
||||||
# Display the number of books we've been passed
|
# Display the number of books we've been passed
|
||||||
@ -120,11 +115,13 @@ class Catalog(QDialog, Ui_Dialog):
|
|||||||
self.sync.setChecked(dynamic.get('catalog_sync_to_device', True))
|
self.sync.setChecked(dynamic.get('catalog_sync_to_device', True))
|
||||||
|
|
||||||
self.format.currentIndexChanged.connect(self.show_plugin_tab)
|
self.format.currentIndexChanged.connect(self.show_plugin_tab)
|
||||||
self.connect(self.buttonBox.button(QtGui.QDialogButtonBox.Apply),
|
self.buttonBox.button(self.buttonBox.Apply).clicked.connect(self.apply)
|
||||||
SIGNAL("clicked()"),
|
|
||||||
self.apply)
|
|
||||||
self.show_plugin_tab(None)
|
self.show_plugin_tab(None)
|
||||||
|
|
||||||
|
geom = dynamic.get('catalog_window_geom', None)
|
||||||
|
if geom is not None:
|
||||||
|
self.restoreGeometry(bytes(geom))
|
||||||
|
|
||||||
def show_plugin_tab(self, idx):
|
def show_plugin_tab(self, idx):
|
||||||
cf = unicode(self.format.currentText()).lower()
|
cf = unicode(self.format.currentText()).lower()
|
||||||
while self.tabs.count() > 1:
|
while self.tabs.count() > 1:
|
||||||
@ -157,8 +154,9 @@ class Catalog(QDialog, Ui_Dialog):
|
|||||||
dynamic.set('catalog_last_used_title', self.catalog_title)
|
dynamic.set('catalog_last_used_title', self.catalog_title)
|
||||||
self.catalog_sync = bool(self.sync.isChecked())
|
self.catalog_sync = bool(self.sync.isChecked())
|
||||||
dynamic.set('catalog_sync_to_device', self.catalog_sync)
|
dynamic.set('catalog_sync_to_device', self.catalog_sync)
|
||||||
|
dynamic.set('catalog_window_geom', bytearray(self.saveGeometry()))
|
||||||
|
|
||||||
def apply(self):
|
def apply(self, *args):
|
||||||
# Store current values without building catalog
|
# Store current values without building catalog
|
||||||
self.save_catalog_settings()
|
self.save_catalog_settings()
|
||||||
if self.tabs.count() > 1:
|
if self.tabs.count() > 1:
|
||||||
@ -166,4 +164,9 @@ class Catalog(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
self.save_catalog_settings()
|
self.save_catalog_settings()
|
||||||
return QDialog.accept(self)
|
return ResizableDialog.accept(self)
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
dynamic.set('catalog_window_geom', bytearray(self.saveGeometry()))
|
||||||
|
ResizableDialog.reject(self)
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<string>Generate catalog</string>
|
<string>Generate catalog</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
@ -31,7 +31,38 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="1" column="0" colspan="2">
|
<item row="1" column="0" colspan="2">
|
||||||
|
<widget class="QScrollArea" name="scrollArea">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>666</width>
|
||||||
|
<height>599</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
<widget class="QTabWidget" name="tabs">
|
<widget class="QTabWidget" name="tabs">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
@ -82,13 +113,6 @@
|
|||||||
<item row="1" column="2">
|
<item row="1" column="2">
|
||||||
<widget class="QLineEdit" name="title"/>
|
<widget class="QLineEdit" name="title"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QCheckBox" name="sync">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Send catalog to device automatically</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -96,30 +120,31 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
<width>20</width>
|
<width>0</width>
|
||||||
<height>299</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QCheckBox" name="sync">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Send catalog to device automatically</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
</layout>
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
</widget>
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../../../work/calibre/resources/images.qrc"/>
|
<include location="../../../../resources/images.qrc"/>
|
||||||
</resources>
|
</resources>
|
||||||
<connections>
|
<connections>
|
||||||
<connection>
|
<connection>
|
||||||
|
@ -3,16 +3,132 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
import os
|
import os, shutil
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \
|
from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \
|
||||||
QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \
|
QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \
|
||||||
QLineEdit, Qt
|
QLineEdit, Qt, QProgressBar, QSize, QTimer
|
||||||
|
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
from calibre.library.check_library import CheckLibrary, CHECKS
|
from calibre.library.check_library import CheckLibrary, CHECKS
|
||||||
from calibre.library.database2 import delete_file, delete_tree
|
from calibre.library.database2 import delete_file, delete_tree
|
||||||
from calibre import prints
|
from calibre import prints, as_unicode
|
||||||
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
|
from calibre.library.sqlite import DBThread, OperationalError
|
||||||
|
|
||||||
|
class DBCheck(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent, db):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.l = QVBoxLayout()
|
||||||
|
self.setLayout(self.l)
|
||||||
|
self.l1 = QLabel(_('Checking database integrity')+'...')
|
||||||
|
self.setWindowTitle(_('Checking database integrity'))
|
||||||
|
self.l.addWidget(self.l1)
|
||||||
|
self.pb = QProgressBar(self)
|
||||||
|
self.l.addWidget(self.pb)
|
||||||
|
self.pb.setMaximum(0)
|
||||||
|
self.pb.setMinimum(0)
|
||||||
|
self.msg = QLabel('')
|
||||||
|
self.l.addWidget(self.msg)
|
||||||
|
self.msg.setWordWrap(True)
|
||||||
|
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
|
||||||
|
self.l.addWidget(self.bb)
|
||||||
|
self.bb.rejected.connect(self.reject)
|
||||||
|
self.resize(self.sizeHint() + QSize(100, 50))
|
||||||
|
self.error = None
|
||||||
|
self.db = db
|
||||||
|
self.closed_orig_conn = False
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.user_version = self.db.user_version
|
||||||
|
self.rejected = False
|
||||||
|
self.db.clean()
|
||||||
|
self.db.conn.close()
|
||||||
|
self.closed_orig_conn = True
|
||||||
|
t = DBThread(self.db.dbpath, False)
|
||||||
|
t.connect()
|
||||||
|
self.conn = t.conn
|
||||||
|
self.dump = self.conn.iterdump()
|
||||||
|
self.statements = []
|
||||||
|
self.count = 0
|
||||||
|
self.msg.setText(_('Dumping database to SQL'))
|
||||||
|
# Give the backup thread time to stop
|
||||||
|
QTimer.singleShot(2000, self.do_one_dump)
|
||||||
|
self.exec_()
|
||||||
|
|
||||||
|
def do_one_dump(self):
|
||||||
|
if self.rejected:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
self.statements.append(self.dump.next())
|
||||||
|
self.count += 1
|
||||||
|
except StopIteration:
|
||||||
|
self.start_load()
|
||||||
|
return
|
||||||
|
QTimer.singleShot(0, self.do_one_dump)
|
||||||
|
except Exception, e:
|
||||||
|
import traceback
|
||||||
|
self.error = (as_unicode(e), traceback.format_exc())
|
||||||
|
self.reject()
|
||||||
|
|
||||||
|
def start_load(self):
|
||||||
|
self.conn.close()
|
||||||
|
self.pb.setMaximum(self.count)
|
||||||
|
self.pb.setValue(0)
|
||||||
|
self.msg.setText(_('Loading database from SQL'))
|
||||||
|
self.db.conn.close()
|
||||||
|
self.ndbpath = PersistentTemporaryFile('.db')
|
||||||
|
self.ndbpath.close()
|
||||||
|
self.ndbpath = self.ndbpath.name
|
||||||
|
t = DBThread(self.ndbpath, False)
|
||||||
|
t.connect()
|
||||||
|
self.conn = t.conn
|
||||||
|
self.conn.execute('create temporary table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)')
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
QTimer.singleShot(0, self.do_one_load)
|
||||||
|
|
||||||
|
def do_one_load(self):
|
||||||
|
if self.rejected:
|
||||||
|
return
|
||||||
|
if self.count > 0:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
self.conn.execute(self.statements.pop(0))
|
||||||
|
except OperationalError:
|
||||||
|
if self.count > 1:
|
||||||
|
# The last statement in the dump could be an extra
|
||||||
|
# commit, so ignore it.
|
||||||
|
raise
|
||||||
|
self.pb.setValue(self.pb.value() + 1)
|
||||||
|
self.count -= 1
|
||||||
|
QTimer.singleShot(0, self.do_one_load)
|
||||||
|
except Exception, e:
|
||||||
|
import traceback
|
||||||
|
self.error = (as_unicode(e), traceback.format_exc())
|
||||||
|
self.reject()
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.replace_db()
|
||||||
|
|
||||||
|
def replace_db(self):
|
||||||
|
self.conn.commit()
|
||||||
|
self.conn.execute('pragma user_version=%d'%int(self.user_version))
|
||||||
|
self.conn.commit()
|
||||||
|
self.conn.close()
|
||||||
|
shutil.copyfile(self.ndbpath, self.db.dbpath)
|
||||||
|
self.db = None
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def break_cycles(self):
|
||||||
|
self.statements = self.unpickler = self.db = self.conn = None
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
self.rejected = True
|
||||||
|
QDialog.reject(self)
|
||||||
|
|
||||||
|
|
||||||
class Item(QTreeWidgetItem):
|
class Item(QTreeWidgetItem):
|
||||||
pass
|
pass
|
||||||
|
@ -39,6 +39,12 @@ class MessageBox(QDialog, Ui_Dialog):
|
|||||||
self.det_msg.setPlainText(det_msg)
|
self.det_msg.setPlainText(det_msg)
|
||||||
self.det_msg.setVisible(False)
|
self.det_msg.setVisible(False)
|
||||||
|
|
||||||
|
if show_copy_button:
|
||||||
|
self.ctc_button = self.bb.addButton(_('&Copy to clipboard'),
|
||||||
|
self.bb.ActionRole)
|
||||||
|
self.ctc_button.clicked.connect(self.copy_to_clipboard)
|
||||||
|
|
||||||
|
|
||||||
if det_msg:
|
if det_msg:
|
||||||
self.show_det_msg = _('Show &details')
|
self.show_det_msg = _('Show &details')
|
||||||
self.hide_det_msg = _('Hide &details')
|
self.hide_det_msg = _('Hide &details')
|
||||||
@ -47,11 +53,6 @@ class MessageBox(QDialog, Ui_Dialog):
|
|||||||
self.det_msg_toggle.setToolTip(
|
self.det_msg_toggle.setToolTip(
|
||||||
_('Show detailed information about this error'))
|
_('Show detailed information about this error'))
|
||||||
|
|
||||||
if show_copy_button:
|
|
||||||
self.ctc_button = self.bb.addButton(_('&Copy to clipboard'),
|
|
||||||
self.bb.ActionRole)
|
|
||||||
self.ctc_button.clicked.connect(self.copy_to_clipboard)
|
|
||||||
|
|
||||||
|
|
||||||
self.copy_action = QAction(self)
|
self.copy_action = QAction(self)
|
||||||
self.addAction(self.copy_action)
|
self.addAction(self.copy_action)
|
||||||
|
@ -208,6 +208,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
from calibre.gui2 import config
|
from calibre.gui2 import config
|
||||||
title = unicode(self.title.text()).strip()
|
title = unicode(self.title.text()).strip()
|
||||||
author = unicode(self.authors.text()).strip()
|
author = unicode(self.authors.text()).strip()
|
||||||
|
if author.endswith('&'):
|
||||||
|
author = author[:-1]
|
||||||
if not title or not author:
|
if not title or not author:
|
||||||
return error_dialog(self, _('Specify title and author'),
|
return error_dialog(self, _('Specify title and author'),
|
||||||
_('You must specify a title and author before generating '
|
_('You must specify a title and author before generating '
|
||||||
|
@ -259,14 +259,14 @@ class Scheduler(QObject):
|
|||||||
if self.oldest > 0:
|
if self.oldest > 0:
|
||||||
delta = timedelta(days=self.oldest)
|
delta = timedelta(days=self.oldest)
|
||||||
try:
|
try:
|
||||||
ids = self.recipe_model.db.tags_older_than(_('News'), delta)
|
ids = list(self.recipe_model.db.tags_older_than(_('News'),
|
||||||
|
delta))
|
||||||
except:
|
except:
|
||||||
# Should never happen
|
# Should never happen
|
||||||
ids = []
|
ids = []
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
if ids:
|
if ids:
|
||||||
ids = list(ids)
|
|
||||||
if ids:
|
if ids:
|
||||||
self.delete_old_news.emit(ids)
|
self.delete_old_news.emit(ids)
|
||||||
QTimer.singleShot(60 * 60 * 1000, self.oldest_check)
|
QTimer.singleShot(60 * 60 * 1000, self.oldest_check)
|
||||||
|
@ -87,7 +87,7 @@ class MainWindow(QMainWindow):
|
|||||||
fe = sio.getvalue()
|
fe = sio.getvalue()
|
||||||
prints(fe, file=sys.stderr)
|
prints(fe, file=sys.stderr)
|
||||||
msg = '<b>%s</b>:'%type.__name__ + unicode(str(value), 'utf8', 'replace')
|
msg = '<b>%s</b>:'%type.__name__ + unicode(str(value), 'utf8', 'replace')
|
||||||
error_dialog(self, _('ERROR: Unhandled exception'), msg, det_msg=fe,
|
error_dialog(self, _('Unhandled exception'), msg, det_msg=fe,
|
||||||
show=True)
|
show=True)
|
||||||
except BaseException:
|
except BaseException:
|
||||||
pass
|
pass
|
||||||
|
@ -64,6 +64,8 @@ class TagDelegate(QItemDelegate): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_minus': 2}
|
||||||
|
|
||||||
class TagsView(QTreeView): # {{{
|
class TagsView(QTreeView): # {{{
|
||||||
|
|
||||||
refresh_required = pyqtSignal()
|
refresh_required = pyqtSignal()
|
||||||
@ -177,9 +179,16 @@ class TagsView(QTreeView): # {{{
|
|||||||
return joiner.join(tokens)
|
return joiner.join(tokens)
|
||||||
|
|
||||||
def toggle(self, index):
|
def toggle(self, index):
|
||||||
|
self._toggle(index, None)
|
||||||
|
|
||||||
|
def _toggle(self, index, set_to):
|
||||||
|
'''
|
||||||
|
set_to: if None, advance the state. Otherwise must be one of the values
|
||||||
|
in TAG_SEARCH_STATES
|
||||||
|
'''
|
||||||
modifiers = int(QApplication.keyboardModifiers())
|
modifiers = int(QApplication.keyboardModifiers())
|
||||||
exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
|
exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
|
||||||
if self._model.toggle(index, exclusive):
|
if self._model.toggle(index, exclusive, set_to=set_to):
|
||||||
self.tags_marked.emit(self.search_string)
|
self.tags_marked.emit(self.search_string)
|
||||||
|
|
||||||
def conditional_clear(self, search_string):
|
def conditional_clear(self, search_string):
|
||||||
@ -187,7 +196,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
def context_menu_handler(self, action=None, category=None,
|
def context_menu_handler(self, action=None, category=None,
|
||||||
key=None, index=None, negate=None):
|
key=None, index=None, search_state=None):
|
||||||
if not action:
|
if not action:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@ -201,11 +210,10 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.user_category_edit.emit(category)
|
self.user_category_edit.emit(category)
|
||||||
return
|
return
|
||||||
if action == 'search':
|
if action == 'search':
|
||||||
self.tags_marked.emit(('not ' if negate else '') +
|
self._toggle(index, set_to=search_state)
|
||||||
category + ':"=' + key + '"')
|
|
||||||
return
|
return
|
||||||
if action == 'search_category':
|
if action == 'search_category':
|
||||||
self.tags_marked.emit(category + ':' + str(not negate))
|
self.tags_marked.emit(key + ':' + search_state)
|
||||||
return
|
return
|
||||||
if action == 'manage_searches':
|
if action == 'manage_searches':
|
||||||
self.saved_search_edit.emit(category)
|
self.saved_search_edit.emit(category)
|
||||||
@ -270,20 +278,16 @@ class TagsView(QTreeView): # {{{
|
|||||||
partial(self.context_menu_handler,
|
partial(self.context_menu_handler,
|
||||||
action='edit_author_sort', index=tag_id))
|
action='edit_author_sort', index=tag_id))
|
||||||
# Add the search for value items
|
# Add the search for value items
|
||||||
n = tag_name
|
|
||||||
c = category
|
|
||||||
if self.db.field_metadata[key]['datatype'] == 'rating':
|
|
||||||
n = str(len(tag_name))
|
|
||||||
elif self.db.field_metadata[key]['kind'] in ['user', 'search']:
|
|
||||||
c = tag_item.tag.category
|
|
||||||
self.context_menu.addAction(self.search_icon,
|
self.context_menu.addAction(self.search_icon,
|
||||||
_('Search for %s')%tag_name,
|
_('Search for %s')%tag_name,
|
||||||
partial(self.context_menu_handler, action='search',
|
partial(self.context_menu_handler, action='search',
|
||||||
category=c, key=n, negate=False))
|
search_state=TAG_SEARCH_STATES['mark_plus'],
|
||||||
|
index=index))
|
||||||
self.context_menu.addAction(self.search_icon,
|
self.context_menu.addAction(self.search_icon,
|
||||||
_('Search for everything but %s')%tag_name,
|
_('Search for everything but %s')%tag_name,
|
||||||
partial(self.context_menu_handler, action='search',
|
partial(self.context_menu_handler, action='search',
|
||||||
category=c, key=n, negate=True))
|
search_state=TAG_SEARCH_STATES['mark_minus'],
|
||||||
|
index=index))
|
||||||
self.context_menu.addSeparator()
|
self.context_menu.addSeparator()
|
||||||
# Hide/Show/Restore categories
|
# Hide/Show/Restore categories
|
||||||
self.context_menu.addAction(_('Hide category %s') % category,
|
self.context_menu.addAction(_('Hide category %s') % category,
|
||||||
@ -299,11 +303,11 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.context_menu.addAction(self.search_icon,
|
self.context_menu.addAction(self.search_icon,
|
||||||
_('Search for books in category %s')%category,
|
_('Search for books in category %s')%category,
|
||||||
partial(self.context_menu_handler, action='search_category',
|
partial(self.context_menu_handler, action='search_category',
|
||||||
category=key, negate=False))
|
key=key, search_state='true'))
|
||||||
self.context_menu.addAction(self.search_icon,
|
self.context_menu.addAction(self.search_icon,
|
||||||
_('Search for books not in category %s')%category,
|
_('Search for books not in category %s')%category,
|
||||||
partial(self.context_menu_handler, action='search_category',
|
partial(self.context_menu_handler, action='search_category',
|
||||||
category=key, negate=True))
|
key=key, search_state='false'))
|
||||||
# Offer specific editors for tags/series/publishers/saved searches
|
# Offer specific editors for tags/series/publishers/saved searches
|
||||||
self.context_menu.addSeparator()
|
self.context_menu.addSeparator()
|
||||||
if key in ['tags', 'publisher', 'series'] or \
|
if key in ['tags', 'publisher', 'series'] or \
|
||||||
@ -528,9 +532,15 @@ class TagTreeItem(object): # {{{
|
|||||||
return QVariant(self.tooltip)
|
return QVariant(self.tooltip)
|
||||||
return NONE
|
return NONE
|
||||||
|
|
||||||
def toggle(self):
|
def toggle(self, set_to=None):
|
||||||
|
'''
|
||||||
|
set_to: None => advance the state, otherwise a value from TAG_SEARCH_STATES
|
||||||
|
'''
|
||||||
if self.type == self.TAG:
|
if self.type == self.TAG:
|
||||||
|
if set_to is None:
|
||||||
self.tag.state = (self.tag.state + 1)%3
|
self.tag.state = (self.tag.state + 1)%3
|
||||||
|
else:
|
||||||
|
self.tag.state = set_to
|
||||||
|
|
||||||
def child_tags(self):
|
def child_tags(self):
|
||||||
res = []
|
res = []
|
||||||
@ -1014,11 +1024,15 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
def clear_state(self):
|
def clear_state(self):
|
||||||
self.reset_all_states()
|
self.reset_all_states()
|
||||||
|
|
||||||
def toggle(self, index, exclusive):
|
def toggle(self, index, exclusive, set_to=None):
|
||||||
|
'''
|
||||||
|
exclusive: clear all states before applying this one
|
||||||
|
set_to: None => advance the state, otherwise a value from TAG_SEARCH_STATES
|
||||||
|
'''
|
||||||
if not index.isValid(): return False
|
if not index.isValid(): return False
|
||||||
item = index.internalPointer()
|
item = index.internalPointer()
|
||||||
if item.type == TagTreeItem.TAG:
|
if item.type == TagTreeItem.TAG:
|
||||||
item.toggle()
|
item.toggle(set_to=set_to)
|
||||||
if exclusive:
|
if exclusive:
|
||||||
self.reset_all_states(except_=item.tag)
|
self.reset_all_states(except_=item.tag)
|
||||||
self.dataChanged.emit(index, index)
|
self.dataChanged.emit(index, index)
|
||||||
@ -1040,8 +1054,9 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
category_item = self.root_item.children[row_index]
|
category_item = self.root_item.children[row_index]
|
||||||
for tag_item in category_item.child_tags():
|
for tag_item in category_item.child_tags():
|
||||||
tag = tag_item.tag
|
tag = tag_item.tag
|
||||||
if tag.state > 0:
|
if tag.state != TAG_SEARCH_STATES['clear']:
|
||||||
prefix = ' not ' if tag.state == 2 else ''
|
prefix = ' not ' if tag.state == TAG_SEARCH_STATES['mark_minus'] \
|
||||||
|
else ''
|
||||||
category = key if key != 'news' else 'tag'
|
category = key if key != 'news' else 'tag'
|
||||||
if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating
|
if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating
|
||||||
ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
|
ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
|
||||||
|
@ -408,7 +408,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
def booklists(self):
|
def booklists(self):
|
||||||
return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db
|
return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db
|
||||||
|
|
||||||
def library_moved(self, newloc, copy_structure=False):
|
def library_moved(self, newloc, copy_structure=False, call_close=True):
|
||||||
if newloc is None: return
|
if newloc is None: return
|
||||||
default_prefs = None
|
default_prefs = None
|
||||||
try:
|
try:
|
||||||
@ -441,6 +441,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
self.apply_named_search_restriction(db.prefs['gui_restriction'])
|
self.apply_named_search_restriction(db.prefs['gui_restriction'])
|
||||||
if olddb is not None:
|
if olddb is not None:
|
||||||
try:
|
try:
|
||||||
|
if call_close:
|
||||||
olddb.conn.close()
|
olddb.conn.close()
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -33,10 +33,10 @@ from calibre.gui2.dialogs.progress import ProgressDialog
|
|||||||
|
|
||||||
class Device(object):
|
class Device(object):
|
||||||
|
|
||||||
output_profile = 'default'
|
output_profile = 'generic_eink'
|
||||||
output_format = 'EPUB'
|
output_format = 'EPUB'
|
||||||
name = 'Default'
|
name = 'Generic e-ink device'
|
||||||
manufacturer = 'Default'
|
manufacturer = 'Generic'
|
||||||
id = 'default'
|
id = 'default'
|
||||||
supports_color = False
|
supports_color = False
|
||||||
|
|
||||||
@ -63,6 +63,18 @@ class Device(object):
|
|||||||
recs['dont_grayscale'] = True
|
recs['dont_grayscale'] = True
|
||||||
save_defaults('comic_input', recs)
|
save_defaults('comic_input', recs)
|
||||||
|
|
||||||
|
class Smartphone(Device):
|
||||||
|
|
||||||
|
id = 'smartphone'
|
||||||
|
name = 'Smartphone'
|
||||||
|
supports_color = True
|
||||||
|
|
||||||
|
class Tablet(Device):
|
||||||
|
|
||||||
|
id = 'tablet'
|
||||||
|
name = 'iPad like tablet'
|
||||||
|
output_profile = 'tablet'
|
||||||
|
supports_color = True
|
||||||
|
|
||||||
class Kindle(Device):
|
class Kindle(Device):
|
||||||
|
|
||||||
@ -206,12 +218,21 @@ class iPhone(Device):
|
|||||||
|
|
||||||
class Android(Device):
|
class Android(Device):
|
||||||
|
|
||||||
name = 'Adroid phone + WordPlayer/Aldiko'
|
name = 'Android phone'
|
||||||
output_format = 'EPUB'
|
output_format = 'EPUB'
|
||||||
manufacturer = 'Android'
|
manufacturer = 'Android'
|
||||||
id = 'android'
|
id = 'android'
|
||||||
supports_color = True
|
supports_color = True
|
||||||
|
|
||||||
|
class AndroidTablet(Device):
|
||||||
|
|
||||||
|
name = 'Android tablet'
|
||||||
|
output_format = 'EPUB'
|
||||||
|
manufacturer = 'Android'
|
||||||
|
id = 'android_tablet'
|
||||||
|
supports_color = True
|
||||||
|
output_profile = 'tablet'
|
||||||
|
|
||||||
class HanlinV3(Device):
|
class HanlinV3(Device):
|
||||||
|
|
||||||
name = 'Hanlin V3'
|
name = 'Hanlin V3'
|
||||||
@ -268,9 +289,9 @@ def get_manufacturers():
|
|||||||
mans = set([])
|
mans = set([])
|
||||||
for x in get_devices():
|
for x in get_devices():
|
||||||
mans.add(x.manufacturer)
|
mans.add(x.manufacturer)
|
||||||
if 'Default' in mans:
|
if Device.manufacturer in mans:
|
||||||
mans.remove('Default')
|
mans.remove(Device.manufacturer)
|
||||||
return ['Default'] + sorted(mans)
|
return [Device.manufacturer] + sorted(mans)
|
||||||
|
|
||||||
def get_devices_of(manufacturer):
|
def get_devices_of(manufacturer):
|
||||||
ans = [d for d in get_devices() if d.manufacturer == manufacturer]
|
ans = [d for d in get_devices() if d.manufacturer == manufacturer]
|
||||||
@ -402,22 +423,6 @@ class StanzaPage(QWizardPage, StanzaUI):
|
|||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
class WordPlayerPage(StanzaPage):
|
|
||||||
|
|
||||||
ID = 6
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
StanzaPage.__init__(self)
|
|
||||||
self.label.setText('<p>'+_('If you use the WordPlayer e-book app on '
|
|
||||||
'your Android phone, you can access your calibre book collection '
|
|
||||||
'directly on the device. To do this you have to turn on the '
|
|
||||||
'content server.'))
|
|
||||||
self.instructions.setText('<p>'+_('Remember to leave calibre running '
|
|
||||||
'as the server only runs as long as calibre is running.')+'<br><br>'
|
|
||||||
+ _('You have to add the URL http://myhostname:8080 as your '
|
|
||||||
'calibre library in WordPlayer. Here myhostname should be the fully '
|
|
||||||
'qualified hostname or the IP address of the computer calibre is running on.'))
|
|
||||||
|
|
||||||
|
|
||||||
class DevicePage(QWizardPage, DeviceUI):
|
class DevicePage(QWizardPage, DeviceUI):
|
||||||
|
|
||||||
@ -430,6 +435,8 @@ class DevicePage(QWizardPage, DeviceUI):
|
|||||||
self.registerField("device", self.device_view)
|
self.registerField("device", self.device_view)
|
||||||
|
|
||||||
def initializePage(self):
|
def initializePage(self):
|
||||||
|
self.label.setText(_('Choose you e-book device. If your device is'
|
||||||
|
' not in the list, choose a "%s" device.')%Device.manufacturer)
|
||||||
self.man_model = ManufacturerModel()
|
self.man_model = ManufacturerModel()
|
||||||
self.manufacturer_view.setModel(self.man_model)
|
self.manufacturer_view.setModel(self.man_model)
|
||||||
previous = dynamic.get('welcome_wizard_device', False)
|
previous = dynamic.get('welcome_wizard_device', False)
|
||||||
@ -477,8 +484,6 @@ class DevicePage(QWizardPage, DeviceUI):
|
|||||||
return KindlePage.ID
|
return KindlePage.ID
|
||||||
if dev is iPhone:
|
if dev is iPhone:
|
||||||
return StanzaPage.ID
|
return StanzaPage.ID
|
||||||
if dev is Android:
|
|
||||||
return WordPlayerPage.ID
|
|
||||||
return FinishPage.ID
|
return FinishPage.ID
|
||||||
|
|
||||||
class MoveMonitor(QObject):
|
class MoveMonitor(QObject):
|
||||||
@ -753,13 +758,11 @@ class Wizard(QWizard):
|
|||||||
self.set_finish_text()
|
self.set_finish_text()
|
||||||
self.kindle_page = KindlePage()
|
self.kindle_page = KindlePage()
|
||||||
self.stanza_page = StanzaPage()
|
self.stanza_page = StanzaPage()
|
||||||
self.word_player_page = WordPlayerPage()
|
|
||||||
self.setPage(self.library_page.ID, self.library_page)
|
self.setPage(self.library_page.ID, self.library_page)
|
||||||
self.setPage(self.device_page.ID, self.device_page)
|
self.setPage(self.device_page.ID, self.device_page)
|
||||||
self.setPage(self.finish_page.ID, self.finish_page)
|
self.setPage(self.finish_page.ID, self.finish_page)
|
||||||
self.setPage(self.kindle_page.ID, self.kindle_page)
|
self.setPage(self.kindle_page.ID, self.kindle_page)
|
||||||
self.setPage(self.stanza_page.ID, self.stanza_page)
|
self.setPage(self.stanza_page.ID, self.stanza_page)
|
||||||
self.setPage(self.word_player_page.ID, self.word_player_page)
|
|
||||||
|
|
||||||
self.device_extra_page = None
|
self.device_extra_page = None
|
||||||
nh, nw = min_available_height()-75, available_width()-30
|
nh, nw = min_available_height()-75, available_width()-30
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
<item row="0" column="0" colspan="2">
|
<item row="0" column="0" colspan="2">
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Choose your book reader. This will set the conversion options to produce books optimized for your device.</string>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -49,7 +49,7 @@ class MetadataBackup(Thread): # {{{
|
|||||||
def run(self):
|
def run(self):
|
||||||
while self.keep_running:
|
while self.keep_running:
|
||||||
try:
|
try:
|
||||||
time.sleep(2) # Limit to two per second
|
time.sleep(2) # Limit to one book per two seconds
|
||||||
(id_, sequence) = self.db.get_a_dirtied_book()
|
(id_, sequence) = self.db.get_a_dirtied_book()
|
||||||
if id_ is None:
|
if id_ is None:
|
||||||
continue
|
continue
|
||||||
|
@ -24,10 +24,9 @@ from calibre.utils.logging import default_log as log
|
|||||||
from calibre.utils.zipfile import ZipFile, ZipInfo
|
from calibre.utils.zipfile import ZipFile, ZipInfo
|
||||||
from calibre.utils.magick.draw import thumbnail
|
from calibre.utils.magick.draw import thumbnail
|
||||||
|
|
||||||
FIELDS = ['all', 'author_sort', 'authors', 'comments',
|
FIELDS = ['all', 'title', 'author_sort', 'authors', 'comments',
|
||||||
'cover', 'formats', 'id', 'isbn', 'ondevice', 'pubdate', 'publisher', 'rating',
|
'cover', 'formats','id', 'isbn', 'ondevice', 'pubdate', 'publisher',
|
||||||
'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
|
'rating', 'series_index', 'series', 'size', 'tags', 'timestamp', 'uuid']
|
||||||
'uuid']
|
|
||||||
|
|
||||||
#Allowed fields for template
|
#Allowed fields for template
|
||||||
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
|
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
|
||||||
@ -252,6 +251,15 @@ class BIBTEX(CatalogPlugin): # {{{
|
|||||||
"Default: '%default'\n"
|
"Default: '%default'\n"
|
||||||
"Applies to: BIBTEX output format")),
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
|
Option('--add-files-path',
|
||||||
|
default = 'True',
|
||||||
|
dest = 'addfiles',
|
||||||
|
action = None,
|
||||||
|
help = _('Create a file entry if formats is selected for BibTeX entries.\n'
|
||||||
|
'Boolean value: True, False\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
Option('--citation-template',
|
Option('--citation-template',
|
||||||
default = '{authors}{id}',
|
default = '{authors}{id}',
|
||||||
dest = 'bib_cit',
|
dest = 'bib_cit',
|
||||||
@ -298,7 +306,7 @@ class BIBTEX(CatalogPlugin): # {{{
|
|||||||
from calibre.utils.bibtex import BibTeX
|
from calibre.utils.bibtex import BibTeX
|
||||||
|
|
||||||
def create_bibtex_entry(entry, fields, mode, template_citation,
|
def create_bibtex_entry(entry, fields, mode, template_citation,
|
||||||
bibtexdict, citation_bibtex = True):
|
bibtexdict, citation_bibtex=True, calibre_files=True):
|
||||||
|
|
||||||
#Bibtex doesn't like UTF-8 but keep unicode until writing
|
#Bibtex doesn't like UTF-8 but keep unicode until writing
|
||||||
#Define starting chain or if book valid strict and not book return a Fail string
|
#Define starting chain or if book valid strict and not book return a Fail string
|
||||||
@ -360,8 +368,13 @@ class BIBTEX(CatalogPlugin): # {{{
|
|||||||
bibtex_entry.append(u'isbn = "%s"' % re.sub(u'[\D]', u'', item))
|
bibtex_entry.append(u'isbn = "%s"' % re.sub(u'[\D]', u'', item))
|
||||||
|
|
||||||
elif field == 'formats' :
|
elif field == 'formats' :
|
||||||
item = u', '.join([format.rpartition('.')[2].lower() for format in item])
|
#Add file path if format is selected
|
||||||
bibtex_entry.append(u'formats = "%s"' % item)
|
formats = [format.rpartition('.')[2].lower() for format in item]
|
||||||
|
bibtex_entry.append(u'formats = "%s"' % u', '.join(formats))
|
||||||
|
if calibre_files:
|
||||||
|
files = [u':%s:%s' % (format, format.rpartition('.')[2].upper())\
|
||||||
|
for format in item]
|
||||||
|
bibtex_entry.append(u'files = "%s"' % u', '.join(files))
|
||||||
|
|
||||||
elif field == 'series_index' :
|
elif field == 'series_index' :
|
||||||
bibtex_entry.append(u'volume = "%s"' % int(item))
|
bibtex_entry.append(u'volume = "%s"' % int(item))
|
||||||
@ -511,15 +524,26 @@ class BIBTEX(CatalogPlugin): # {{{
|
|||||||
else :
|
else :
|
||||||
citation_bibtex= opts.impcit
|
citation_bibtex= opts.impcit
|
||||||
|
|
||||||
|
#Check add file entry and go to default in case of bad CLI
|
||||||
|
if isinstance(opts.addfiles, (StringType, UnicodeType)) :
|
||||||
|
if opts.addfiles == 'False' :
|
||||||
|
addfiles_bibtex = False
|
||||||
|
elif opts.addfiles == 'True' :
|
||||||
|
addfiles_bibtex = True
|
||||||
|
else :
|
||||||
|
log(" WARNING: incorrect --add-files-path, revert to default")
|
||||||
|
addfiles_bibtex= True
|
||||||
|
else :
|
||||||
|
addfiles_bibtex = opts.addfiles
|
||||||
|
|
||||||
#Preprocess for error and light correction
|
#Preprocess for error and light correction
|
||||||
template_citation = preprocess_template(opts.bib_cit)
|
template_citation = preprocess_template(opts.bib_cit)
|
||||||
|
|
||||||
#Open output and write entries
|
#Open output and write entries
|
||||||
outfile = codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag)
|
with codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag)\
|
||||||
|
as outfile:
|
||||||
#File header
|
#File header
|
||||||
nb_entries = len(data)
|
nb_entries = len(data)
|
||||||
|
|
||||||
#check in book strict if all is ok else throw a warning into log
|
#check in book strict if all is ok else throw a warning into log
|
||||||
if bib_entry == 'book' :
|
if bib_entry == 'book' :
|
||||||
nb_books = len(filter(check_entry_book_valid, data))
|
nb_books = len(filter(check_entry_book_valid, data))
|
||||||
@ -533,9 +557,7 @@ class BIBTEX(CatalogPlugin): # {{{
|
|||||||
|
|
||||||
for entry in data:
|
for entry in data:
|
||||||
outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
|
outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
|
||||||
bibtexc, citation_bibtex))
|
bibtexc, citation_bibtex, addfiles_bibtex))
|
||||||
|
|
||||||
outfile.close()
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class EPUB_MOBI(CatalogPlugin):
|
class EPUB_MOBI(CatalogPlugin):
|
||||||
|
@ -21,7 +21,7 @@ from calibre.library.field_metadata import FieldMetadata, TagsIcons
|
|||||||
from calibre.library.schema_upgrades import SchemaUpgrade
|
from calibre.library.schema_upgrades import SchemaUpgrade
|
||||||
from calibre.library.caches import ResultCache
|
from calibre.library.caches import ResultCache
|
||||||
from calibre.library.custom_columns import CustomColumns
|
from calibre.library.custom_columns import CustomColumns
|
||||||
from calibre.library.sqlite import connect, IntegrityError, DBThread
|
from calibre.library.sqlite import connect, IntegrityError
|
||||||
from calibre.library.prefs import DBPrefs
|
from calibre.library.prefs import DBPrefs
|
||||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string
|
from calibre.ebooks.metadata import string_to_authors, authors_to_string
|
||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
@ -827,7 +827,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
try:
|
try:
|
||||||
book_ids = self.data.parse(query)
|
book_ids = self.data.parse(query)
|
||||||
except:
|
except:
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return identical_book_ids
|
return identical_book_ids
|
||||||
for book_id in book_ids:
|
for book_id in book_ids:
|
||||||
@ -2797,82 +2796,3 @@ books_series_link feeds
|
|||||||
yield id, title, script
|
yield id, title, script
|
||||||
|
|
||||||
|
|
||||||
def check_integrity(self, callback):
|
|
||||||
callback(0., _('Checking SQL integrity...'))
|
|
||||||
self.clean()
|
|
||||||
user_version = self.user_version
|
|
||||||
sql = '\n'.join(self.conn.dump())
|
|
||||||
self.conn.close()
|
|
||||||
dest = self.dbpath+'.tmp'
|
|
||||||
if os.path.exists(dest):
|
|
||||||
os.remove(dest)
|
|
||||||
conn = None
|
|
||||||
try:
|
|
||||||
ndb = DBThread(dest, None)
|
|
||||||
ndb.connect()
|
|
||||||
conn = ndb.conn
|
|
||||||
conn.execute('create table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)')
|
|
||||||
conn.commit()
|
|
||||||
conn.executescript(sql)
|
|
||||||
conn.commit()
|
|
||||||
conn.execute('pragma user_version=%d'%user_version)
|
|
||||||
conn.commit()
|
|
||||||
conn.execute('drop table temp_sequence')
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
except:
|
|
||||||
if conn is not None:
|
|
||||||
try:
|
|
||||||
conn.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if os.path.exists(dest):
|
|
||||||
os.remove(dest)
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
shutil.copyfile(dest, self.dbpath)
|
|
||||||
self.connect()
|
|
||||||
self.initialize_dynamic()
|
|
||||||
self.refresh()
|
|
||||||
if os.path.exists(dest):
|
|
||||||
os.remove(dest)
|
|
||||||
callback(0.1, _('Checking for missing files.'))
|
|
||||||
bad = {}
|
|
||||||
us = self.data.universal_set()
|
|
||||||
total = float(len(us))
|
|
||||||
for i, id in enumerate(us):
|
|
||||||
formats = self.data.get(id, self.FIELD_MAP['formats'], row_is_id=True)
|
|
||||||
if not formats:
|
|
||||||
formats = []
|
|
||||||
else:
|
|
||||||
formats = [x.lower() for x in formats.split(',')]
|
|
||||||
actual_formats = self.formats(id, index_is_id=True)
|
|
||||||
if not actual_formats:
|
|
||||||
actual_formats = []
|
|
||||||
else:
|
|
||||||
actual_formats = [x.lower() for x in actual_formats.split(',')]
|
|
||||||
|
|
||||||
for fmt in formats:
|
|
||||||
if fmt in actual_formats:
|
|
||||||
continue
|
|
||||||
if id not in bad:
|
|
||||||
bad[id] = []
|
|
||||||
bad[id].append(fmt)
|
|
||||||
has_cover = self.data.get(id, self.FIELD_MAP['cover'],
|
|
||||||
row_is_id=True)
|
|
||||||
if has_cover and self.cover(id, index_is_id=True, as_path=True) is None:
|
|
||||||
if id not in bad:
|
|
||||||
bad[id] = []
|
|
||||||
bad[id].append('COVER')
|
|
||||||
callback(0.1+0.9*(1+i)/total, _('Checked id') + ' %d'%id)
|
|
||||||
|
|
||||||
for id in bad:
|
|
||||||
for fmt in bad[id]:
|
|
||||||
if fmt != 'COVER':
|
|
||||||
self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, fmt.upper()))
|
|
||||||
else:
|
|
||||||
self.conn.execute('UPDATE books SET has_cover=0 WHERE id=?', (id,))
|
|
||||||
self.conn.commit()
|
|
||||||
self.refresh_ids(list(bad.keys()))
|
|
||||||
|
|
||||||
return bad
|
|
||||||
|
@ -17,6 +17,7 @@ class DBPrefs(dict):
|
|||||||
dict.__init__(self)
|
dict.__init__(self)
|
||||||
self.db = db
|
self.db = db
|
||||||
self.defaults = {}
|
self.defaults = {}
|
||||||
|
self.disable_setting = False
|
||||||
for key, val in self.db.conn.get('SELECT key,val FROM preferences'):
|
for key, val in self.db.conn.get('SELECT key,val FROM preferences'):
|
||||||
try:
|
try:
|
||||||
val = self.raw_to_object(val)
|
val = self.raw_to_object(val)
|
||||||
@ -45,6 +46,8 @@ class DBPrefs(dict):
|
|||||||
self.db.conn.commit()
|
self.db.conn.commit()
|
||||||
|
|
||||||
def __setitem__(self, key, val):
|
def __setitem__(self, key, val):
|
||||||
|
if self.disable_setting:
|
||||||
|
return
|
||||||
raw = self.to_raw(val)
|
raw = self.to_raw(val)
|
||||||
self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
|
self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
|
||||||
self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key,
|
self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user