mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
babe65c41a
@ -52,6 +52,17 @@ p.formats {
|
|||||||
text-indent: 0.0in;
|
text-indent: 0.0in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Minimize widows and orphans by logically grouping chunks
|
||||||
|
* Some reports of problems with Sony (ADE) ereaders
|
||||||
|
* ADE: page-break-inside:avoid;
|
||||||
|
* iBooks: display:inline-block;
|
||||||
|
* width:100%;
|
||||||
|
*/
|
||||||
|
div.author_logical_group {
|
||||||
|
page-break-inside:avoid;
|
||||||
|
}
|
||||||
|
|
||||||
div.description > p:first-child {
|
div.description > p:first-child {
|
||||||
margin: 0 0 0 0;
|
margin: 0 0 0 0;
|
||||||
text-indent: 0em;
|
text-indent: 0em;
|
||||||
@ -62,27 +73,19 @@ div.description {
|
|||||||
text-indent: 1em;
|
text-indent: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
div.initial_letter {
|
||||||
* Attempt to minimize widows and orphans by logically grouping chunks
|
page-break-before:always;
|
||||||
* Recommend enabling for iPad
|
|
||||||
* Some reports of problems with Sony ereaders, presumably ADE engines
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
div.logical_group {
|
|
||||||
display:inline-block;
|
|
||||||
width:100%;
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
p.date_index {
|
p.author_title_letter_index {
|
||||||
font-size:x-large;
|
font-size:x-large;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
margin-top:1em;
|
margin-top:0px;
|
||||||
margin-bottom:0px;
|
margin-bottom:0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.letter_index {
|
p.date_index {
|
||||||
font-size:x-large;
|
font-size:x-large;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
@ -99,6 +102,14 @@ p.series {
|
|||||||
text-indent:-2em;
|
text-indent:-2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.series_letter_index {
|
||||||
|
font-size:x-large;
|
||||||
|
text-align:center;
|
||||||
|
font-weight:bold;
|
||||||
|
margin-top:1em;
|
||||||
|
margin-bottom:0px;
|
||||||
|
}
|
||||||
|
|
||||||
p.read_book {
|
p.read_book {
|
||||||
text-align:left;
|
text-align:left;
|
||||||
margin-top:0px;
|
margin-top:0px;
|
||||||
|
@ -2,24 +2,23 @@
|
|||||||
__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 = 'Diario local de Talavera de la Reina - v1.2 - 27 Jan 2011'
|
__version__ = 'v1.0'
|
||||||
|
__date__ = '01 Feb 2011'
|
||||||
|
|
||||||
'''
|
'''
|
||||||
http://www.latribunadetalavera.es/
|
http://www.promecal.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'
|
||||||
publisher = u'Grupo PROMECAL'
|
publisher = u'Grupo PROMECAL'
|
||||||
|
|
||||||
__author__ = 'Luis Hernández'
|
__author__ = 'Luis Hernández'
|
||||||
description = 'Diario local de Talavera de la Reina'
|
description = 'Varios diarios locales del grupo PROMECAL'
|
||||||
cover_url = 'http://www.latribunadetalavera.es/entorno/mancheta.gif'
|
|
||||||
|
|
||||||
oldest_article = 5
|
oldest_article = 3
|
||||||
max_articles_per_feed = 50
|
max_articles_per_feed = 50
|
||||||
|
|
||||||
remove_javascript = True
|
remove_javascript = True
|
||||||
@ -27,7 +26,7 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
|||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
|
|
||||||
encoding = 'utf-8'
|
encoding = 'utf-8'
|
||||||
language = 'es'
|
language = 'es_ES'
|
||||||
timefmt = '[%a, %d %b, %Y]'
|
timefmt = '[%a, %d %b, %Y]'
|
||||||
|
|
||||||
keep_only_tags = [
|
keep_only_tags = [
|
||||||
@ -39,7 +38,20 @@ 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} '
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'id':['relacionadas']})
|
||||||
|
,dict(name='h3')
|
||||||
|
,dict(name='h5')
|
||||||
|
]
|
||||||
|
|
||||||
|
extra_css = """
|
||||||
|
p{text-align: justify; font-size: 100%}
|
||||||
|
body{text-align: left; font-family: serif; font-size: 100%}
|
||||||
|
h1{font-family: sans; font-size:150%; font-weight: bold; text-align: justify;}
|
||||||
|
h2{font-family: sans-serif; font-size:85%; font-style: italic; text-align: justify;}
|
||||||
|
h4{font-family: sans; font-size:75%; font-weight: bold; text-align: center;}
|
||||||
|
img{margin-bottom: 0.4em}
|
||||||
|
"""
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
for alink in soup.findAll('a'):
|
for alink in soup.findAll('a'):
|
||||||
@ -48,4 +60,15 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
|||||||
alink.replaceWith(tstr)
|
alink.replaceWith(tstr)
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
feeds = [(u'Portada', u'http://www.latribunadetalavera.es/rss.html')]
|
|
||||||
|
feeds = [
|
||||||
|
(u'Albacete', u'http://www.latribunadealbacete.es/rss.html')
|
||||||
|
,(u'Avila', u'http://www.diariodeavila.es/rss.html')
|
||||||
|
,(u'Burgos', u'http://www.diariodeburgos.es/rss.html')
|
||||||
|
,(u'Ciudad Real', u'http://www.latribunadeciudadreal.es/rss.html')
|
||||||
|
,(u'Palencia', u'http://www.diariopalentino.es/rss.html')
|
||||||
|
,(u'Puertollano', u'http://www.latribunadepuertollano.es/rss.html')
|
||||||
|
,(u'Talavera de la Reina', u'http://www.latribunadetalavera.es/rss.html')
|
||||||
|
,(u'Toledo', u'http://www.latribunadetoledo.es/rss.html')
|
||||||
|
,(u'Valladolid', u'http://www.eldiadevalladolid.com/rss.html')
|
||||||
|
]
|
||||||
|
@ -15,12 +15,26 @@ class LeTemps(BasicNewsRecipe):
|
|||||||
oldest_article = 7
|
oldest_article = 7
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
__author__ = 'Sujata Raman'
|
__author__ = 'Sujata Raman'
|
||||||
|
description = 'French news. Needs a subscription from http://www.letemps.ch'
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
remove_javascript = True
|
remove_javascript = True
|
||||||
recursions = 1
|
recursions = 1
|
||||||
encoding = 'UTF-8'
|
encoding = 'UTF-8'
|
||||||
match_regexps = [r'http://www.letemps.ch/Page/Uuid/[-0-9a-f]+\|[1-9]']
|
match_regexps = [r'http://www.letemps.ch/Page/Uuid/[-0-9a-f]+\|[1-9]']
|
||||||
language = 'fr'
|
language = 'fr'
|
||||||
|
needs_subscription = True
|
||||||
|
|
||||||
|
def get_browser(self):
|
||||||
|
br = BasicNewsRecipe.get_browser(self)
|
||||||
|
br.open('http://www.letemps.ch/login')
|
||||||
|
br['username'] = self.username
|
||||||
|
br['password'] = self.password
|
||||||
|
raw = br.submit().read()
|
||||||
|
if '>Login' in raw:
|
||||||
|
raise ValueError('Failed to login to letemp.ch. Check '
|
||||||
|
'your username and password')
|
||||||
|
return br
|
||||||
|
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'id':'content'}),
|
keep_only_tags = [dict(name='div', attrs={'id':'content'}),
|
||||||
dict(name='div', attrs={'class':'story'})
|
dict(name='div', attrs={'class':'story'})
|
||||||
|
@ -13,15 +13,12 @@ class MSNSankeiNewsProduct(BasicNewsRecipe):
|
|||||||
description = 'Products release from Japan'
|
description = 'Products release from Japan'
|
||||||
oldest_article = 7
|
oldest_article = 7
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
encoding = 'Shift_JIS'
|
encoding = 'utf-8'
|
||||||
language = 'ja'
|
language = 'ja'
|
||||||
cover_url = 'http://sankei.jp.msn.com/images/common/sankeShinbunLogo.jpg'
|
cover_url = 'http://sankei.jp.msn.com/images/common/sankeShinbunLogo.jpg'
|
||||||
masthead_url = 'http://sankei.jp.msn.com/images/common/sankeiNewsLogo.gif'
|
masthead_url = 'http://sankei.jp.msn.com/images/common/sankeiNewsLogo.gif'
|
||||||
|
|
||||||
feeds = [(u'\u65b0\u5546\u54c1', u'http://sankei.jp.msn.com/rss/news/release.xml')]
|
feeds = [(u'\u65b0\u5546\u54c1', u'http://sankei.jp.msn.com/rss/news/release.xml')]
|
||||||
|
|
||||||
remove_tags_before = dict(id="__r_article_title__")
|
remove_tags_before = dict(id="NewsTitle")
|
||||||
remove_tags_after = dict(id="ajax_release_news")
|
remove_tags_after = dict(id="RelatedTitle")
|
||||||
remove_tags = [{'class':"parent chromeCustom6G"},
|
|
||||||
dict(id="RelatedImg")
|
|
||||||
]
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2009-2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
|
|
||||||
'''
|
'''
|
||||||
theonion.com
|
theonion.com
|
||||||
@ -15,26 +13,39 @@ class TheOnion(BasicNewsRecipe):
|
|||||||
description = "America's finest news source"
|
description = "America's finest news source"
|
||||||
oldest_article = 2
|
oldest_article = 2
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
publisher = u'Onion, Inc.'
|
publisher = 'Onion, Inc.'
|
||||||
category = u'humor, news, USA'
|
category = 'humor, news, USA'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
|
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
encoding = 'utf-8'
|
encoding = 'utf-8'
|
||||||
remove_javascript = True
|
publication_type = 'newsportal'
|
||||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
masthead_url = 'http://o.onionstatic.com/img/headers/onion_190.png'
|
||||||
|
extra_css = """
|
||||||
|
body{font-family: Helvetica,Arial,sans-serif}
|
||||||
|
.section_title{color: gray; text-transform: uppercase}
|
||||||
|
.title{font-family: Georgia,serif}
|
||||||
|
.meta{color: gray; display: inline}
|
||||||
|
.has_caption{display: block}
|
||||||
|
.caption{font-size: x-small; color: gray; margin-bottom: 0.8em}
|
||||||
|
"""
|
||||||
|
|
||||||
html2lrf_options = [
|
conversion_options = {
|
||||||
'--comment' , description
|
'comment' : description
|
||||||
, '--category' , category
|
, 'tags' : category
|
||||||
, '--publisher' , publisher
|
, 'publisher': publisher
|
||||||
]
|
, 'language' : language
|
||||||
|
}
|
||||||
keep_only_tags = [dict(name='div', attrs={'id':'main'})]
|
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='h2', attrs={'class':['section_title','title']})
|
||||||
|
,dict(attrs={'class':['main_image','meta','article_photo_lead','article_body']})
|
||||||
|
,dict(attrs={'id':['entries']})
|
||||||
|
]
|
||||||
|
remove_attributes=['lang','rel']
|
||||||
|
remove_tags_after = dict(attrs={'class':['article_body','feature_content']})
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name=['object','link','iframe','base'])
|
dict(name=['object','link','iframe','base','meta'])
|
||||||
,dict(name='div', attrs={'class':['toolbar_side','graphical_feature','toolbar_bottom']})
|
,dict(name='div', attrs={'class':['toolbar_side','graphical_feature','toolbar_bottom']})
|
||||||
,dict(name='div', attrs={'id':['recent_slider','sidebar','pagination','related_media']})
|
,dict(name='div', attrs={'id':['recent_slider','sidebar','pagination','related_media']})
|
||||||
]
|
]
|
||||||
@ -44,3 +55,28 @@ class TheOnion(BasicNewsRecipe):
|
|||||||
(u'Daily' , u'http://feeds.theonion.com/theonion/daily' )
|
(u'Daily' , u'http://feeds.theonion.com/theonion/daily' )
|
||||||
,(u'Sports' , u'http://feeds.theonion.com/theonion/sports' )
|
,(u'Sports' , u'http://feeds.theonion.com/theonion/sports' )
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_article_url(self, article):
|
||||||
|
artl = BasicNewsRecipe.get_article_url(self, article)
|
||||||
|
if artl.startswith('http://www.theonion.com/audio/'):
|
||||||
|
artl = None
|
||||||
|
return artl
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for item in soup.findAll(style=True):
|
||||||
|
del item['style']
|
||||||
|
for item in soup.findAll('a'):
|
||||||
|
limg = item.find('img')
|
||||||
|
if item.string is not None:
|
||||||
|
str = item.string
|
||||||
|
item.replaceWith(str)
|
||||||
|
else:
|
||||||
|
if limg:
|
||||||
|
item.name = 'div'
|
||||||
|
item.attrs = []
|
||||||
|
if not limg.has_key('alt'):
|
||||||
|
limg['alt'] = 'image'
|
||||||
|
else:
|
||||||
|
str = self.tag_to_string(item)
|
||||||
|
item.replaceWith(str)
|
||||||
|
return soup
|
||||||
|
@ -89,21 +89,21 @@ class NOOK_COLOR(NOOK):
|
|||||||
BCD = [0x216]
|
BCD = [0x216]
|
||||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_DISK'
|
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_DISK'
|
||||||
|
|
||||||
EBOOK_DIR_MAIN = 'My Files/Books'
|
EBOOK_DIR_MAIN = 'My Files'
|
||||||
|
|
||||||
'''
|
|
||||||
def create_upload_path(self, path, mdata, fname, create_dirs=True):
|
def create_upload_path(self, path, mdata, fname, create_dirs=True):
|
||||||
filepath = NOOK.create_upload_path(self, path, mdata, fname,
|
filepath = NOOK.create_upload_path(self, path, mdata, fname,
|
||||||
create_dirs=create_dirs)
|
create_dirs=False)
|
||||||
edm = self.EBOOK_DIR_MAIN.replace('/', os.sep)
|
edm = self.EBOOK_DIR_MAIN
|
||||||
npath = os.path.join(edm, _('News')) + os.sep
|
subdir = 'Books'
|
||||||
if npath in filepath:
|
if mdata.tags:
|
||||||
filepath = filepath.replace(npath, os.sep.join('My Files',
|
if _('News') in mdata.tags:
|
||||||
'Magazines')+os.sep)
|
subdir = 'Magazines'
|
||||||
filedir = os.path.dirname(filepath)
|
filepath = filepath.replace(os.sep+edm+os.sep,
|
||||||
if create_dirs and not os.path.exists(filedir):
|
os.sep+edm+os.sep+subdir+os.sep)
|
||||||
os.makedirs(filedir)
|
filedir = os.path.dirname(filepath)
|
||||||
|
if create_dirs and not os.path.exists(filedir):
|
||||||
|
os.makedirs(filedir)
|
||||||
|
|
||||||
return filepath
|
return filepath
|
||||||
'''
|
|
||||||
|
|
||||||
|
@ -490,11 +490,12 @@ class HeuristicProcessor(object):
|
|||||||
applied to wrapping divs. This is because many ebook devices don't support margin:auto
|
applied to wrapping divs. This is because many ebook devices don't support margin:auto
|
||||||
All other html is converted to text.
|
All other html is converted to text.
|
||||||
'''
|
'''
|
||||||
hr_open = '<div id="scenebreak" style="margin-left: 45%; margin-right: 45%; margin-top:1.5em; margin-bottom:1.5em">'
|
hr_open = '<div id="scenebreak" style="margin-left: 45%; margin-right: 45%; margin-top:1.5em; margin-bottom:1.5em; page-break-before:avoid">'
|
||||||
if re.findall('(<|>)', replacement_break):
|
if re.findall('(<|>)', replacement_break):
|
||||||
if re.match('^<hr', replacement_break):
|
if re.match('^<hr', replacement_break):
|
||||||
if replacement_break.find('width') != -1:
|
if replacement_break.find('width') != -1:
|
||||||
width = int(re.sub('.*?width(:|=)(?P<wnum>\d+).*', '\g<wnum>', replacement_break))
|
width = int(re.sub('.*?width(:|=)(?P<wnum>\d+).*', '\g<wnum>', replacement_break))
|
||||||
|
replacement_break = re.sub('(?i)(width=\d+\%?|width:\s*\d+(\%|px|pt|em)?;?)', '', replacement_break)
|
||||||
divpercent = (100 - width) / 2
|
divpercent = (100 - width) / 2
|
||||||
hr_open = re.sub('45', str(divpercent), hr_open)
|
hr_open = re.sub('45', str(divpercent), hr_open)
|
||||||
scene_break = hr_open+replacement_break+'</div>'
|
scene_break = hr_open+replacement_break+'</div>'
|
||||||
@ -642,7 +643,7 @@ class HeuristicProcessor(object):
|
|||||||
# or 'hard' scene breaks are replaced, depending on which is in use
|
# or 'hard' scene breaks are replaced, depending on which is in use
|
||||||
# Otherwise separator lines are centered, use a bit larger margin in this case
|
# Otherwise separator lines are centered, use a bit larger margin in this case
|
||||||
replacement_break = getattr(self.extra_opts, 'replace_scene_breaks', None)
|
replacement_break = getattr(self.extra_opts, 'replace_scene_breaks', None)
|
||||||
if replacement_break is not None:
|
if replacement_break:
|
||||||
replacement_break = self.markup_user_break(replacement_break)
|
replacement_break = self.markup_user_break(replacement_break)
|
||||||
if len(scene_break.findall(html)) >= 1:
|
if len(scene_break.findall(html)) >= 1:
|
||||||
html = scene_break.sub(replacement_break, html)
|
html = scene_break.sub(replacement_break, html)
|
||||||
|
@ -633,7 +633,7 @@ class Style(object):
|
|||||||
def lineHeight(self):
|
def lineHeight(self):
|
||||||
if self._lineHeight is None:
|
if self._lineHeight is None:
|
||||||
result = None
|
result = None
|
||||||
#parent = self._getparent()
|
parent = self._get_parent()
|
||||||
if 'line-height' in self._style:
|
if 'line-height' in self._style:
|
||||||
lineh = self._style['line-height']
|
lineh = self._style['line-height']
|
||||||
if lineh == 'normal':
|
if lineh == 'normal':
|
||||||
@ -642,9 +642,9 @@ class Style(object):
|
|||||||
result = float(lineh) * self.fontSize
|
result = float(lineh) * self.fontSize
|
||||||
except ValueError:
|
except ValueError:
|
||||||
result = self._unit_convert(lineh, base=self.fontSize)
|
result = self._unit_convert(lineh, base=self.fontSize)
|
||||||
#elif parent is not None:
|
elif parent is not None:
|
||||||
# # TODO: proper inheritance
|
# TODO: proper inheritance
|
||||||
# result = parent.lineHeight
|
result = parent.lineHeight
|
||||||
else:
|
else:
|
||||||
result = 1.2 * self.fontSize
|
result = 1.2 * self.fontSize
|
||||||
self._lineHeight = result
|
self._lineHeight = result
|
||||||
|
@ -74,23 +74,29 @@ class ShareConnMenu(QMenu): # {{{
|
|||||||
opts = email_config().parse()
|
opts = email_config().parse()
|
||||||
if opts.accounts:
|
if opts.accounts:
|
||||||
self.email_to_menu = QMenu(_('Email to')+'...', self)
|
self.email_to_menu = QMenu(_('Email to')+'...', self)
|
||||||
|
ac = self.addMenu(self.email_to_menu)
|
||||||
|
self.email_actions.append(ac)
|
||||||
|
self.email_to_and_delete_menu = QMenu(
|
||||||
|
_('Email to and delete from library')+'...', self)
|
||||||
keys = sorted(opts.accounts.keys())
|
keys = sorted(opts.accounts.keys())
|
||||||
for account in keys:
|
for account in keys:
|
||||||
formats, auto, default = opts.accounts[account]
|
formats, auto, default = opts.accounts[account]
|
||||||
dest = 'mail:'+account+';'+formats
|
dest = 'mail:'+account+';'+formats
|
||||||
action1 = DeviceAction(dest, False, False, I('mail.png'),
|
action1 = DeviceAction(dest, False, False, I('mail.png'),
|
||||||
_('Email to')+' '+account)
|
account)
|
||||||
action2 = DeviceAction(dest, True, False, I('mail.png'),
|
action2 = DeviceAction(dest, True, False, I('mail.png'),
|
||||||
_('Email to')+' '+account+ _(' and delete from library'))
|
account + ' ' + _('(delete from library)'))
|
||||||
map(self.email_to_menu.addAction, (action1, action2))
|
self.email_to_menu.addAction(action1)
|
||||||
|
self.email_to_and_delete_menu.addAction(action2)
|
||||||
map(self.memory.append, (action1, action2))
|
map(self.memory.append, (action1, action2))
|
||||||
if default:
|
if default:
|
||||||
map(self.addAction, (action1, action2))
|
ac = DeviceAction(dest, False, False,
|
||||||
map(self.email_actions.append, (action1, action2))
|
I('mail.png'), _('Email to') + ' ' +account)
|
||||||
self.email_to_menu.addSeparator()
|
self.addAction(ac)
|
||||||
|
self.email_actions.append(ac)
|
||||||
action1.a_s.connect(sync_menu.action_triggered)
|
action1.a_s.connect(sync_menu.action_triggered)
|
||||||
action2.a_s.connect(sync_menu.action_triggered)
|
action2.a_s.connect(sync_menu.action_triggered)
|
||||||
ac = self.addMenu(self.email_to_menu)
|
ac = self.addMenu(self.email_to_and_delete_menu)
|
||||||
self.email_actions.append(ac)
|
self.email_actions.append(ac)
|
||||||
else:
|
else:
|
||||||
ac = self.addAction(_('Setup email based sharing of books'))
|
ac = self.addAction(_('Setup email based sharing of books'))
|
||||||
|
@ -160,6 +160,7 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
break
|
break
|
||||||
|
|
||||||
changed.add(d.id)
|
changed.add(d.id)
|
||||||
|
self.gui.library_view.model().refresh_ids(list(d.books_to_refresh))
|
||||||
if d.row_delta == 0:
|
if d.row_delta == 0:
|
||||||
break
|
break
|
||||||
current_row += d.row_delta
|
current_row += d.row_delta
|
||||||
|
@ -64,8 +64,6 @@ class CompleteWindow(QListView): # {{{
|
|||||||
|
|
||||||
def do_selected(self, idx=None):
|
def do_selected(self, idx=None):
|
||||||
idx = self.currentIndex() if idx is None else idx
|
idx = self.currentIndex() if idx is None else idx
|
||||||
if not idx.isValid() and self.model().rowCount() > 0:
|
|
||||||
idx = self.model().index(0)
|
|
||||||
if idx.isValid():
|
if idx.isValid():
|
||||||
data = unicode(self.model().data(idx, Qt.DisplayRole))
|
data = unicode(self.model().data(idx, Qt.DisplayRole))
|
||||||
self.completion_selected.emit(data)
|
self.completion_selected.emit(data)
|
||||||
@ -81,6 +79,9 @@ class CompleteWindow(QListView): # {{{
|
|||||||
self.hide()
|
self.hide()
|
||||||
return True
|
return True
|
||||||
elif key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab):
|
elif key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab):
|
||||||
|
if key == Qt.Key_Tab and not self.currentIndex().isValid():
|
||||||
|
if self.model().rowCount() > 0:
|
||||||
|
self.setCurrentIndex(self.model().index(0))
|
||||||
self.do_selected()
|
self.do_selected()
|
||||||
return True
|
return True
|
||||||
elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp,
|
elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp,
|
||||||
@ -175,9 +176,9 @@ class MultiCompleteLineEdit(QLineEdit):
|
|||||||
|
|
||||||
self._model = CompleteModel(parent=self)
|
self._model = CompleteModel(parent=self)
|
||||||
self.complete_window = CompleteWindow(self, self._model)
|
self.complete_window = CompleteWindow(self, self._model)
|
||||||
self.textChanged.connect(self.text_changed)
|
self.textEdited.connect(self.text_edited)
|
||||||
self.cursorPositionChanged.connect(self.cursor_position_changed)
|
|
||||||
self.complete_window.completion_selected.connect(self.completion_selected)
|
self.complete_window.completion_selected.connect(self.completion_selected)
|
||||||
|
self.installEventFilter(self)
|
||||||
|
|
||||||
# Interface {{{
|
# Interface {{{
|
||||||
def update_items_cache(self, complete_items):
|
def update_items_cache(self, complete_items):
|
||||||
@ -197,15 +198,17 @@ class MultiCompleteLineEdit(QLineEdit):
|
|||||||
return True # Filter this event since the cw is visible
|
return True # Filter this event since the cw is visible
|
||||||
return QLineEdit.eventFilter(self, o, e)
|
return QLineEdit.eventFilter(self, o, e)
|
||||||
|
|
||||||
|
def hide_completion_window(self):
|
||||||
|
self.complete_window.hide()
|
||||||
|
|
||||||
def text_changed(self, *args):
|
|
||||||
self.update_completions()
|
|
||||||
|
|
||||||
def cursor_position_changed(self, *args):
|
def text_edited(self, *args):
|
||||||
self.update_completions()
|
self.update_completions()
|
||||||
|
|
||||||
def update_completions(self):
|
def update_completions(self):
|
||||||
' Update the list of completions '
|
' Update the list of completions '
|
||||||
|
if not self.complete_window.isVisible() and not self.hasFocus():
|
||||||
|
return
|
||||||
cpos = self.cursorPosition()
|
cpos = self.cursorPosition()
|
||||||
text = unicode(self.text())
|
text = unicode(self.text())
|
||||||
prefix = text[:cpos]
|
prefix = text[:cpos]
|
||||||
@ -223,7 +226,7 @@ class MultiCompleteLineEdit(QLineEdit):
|
|||||||
text
|
text
|
||||||
'''
|
'''
|
||||||
if self.sep is None:
|
if self.sep is None:
|
||||||
return text
|
return -1, text
|
||||||
else:
|
else:
|
||||||
cursor_pos = self.cursorPosition()
|
cursor_pos = self.cursorPosition()
|
||||||
before_text = unicode(self.text())[:cursor_pos]
|
before_text = unicode(self.text())[:cursor_pos]
|
||||||
@ -232,24 +235,18 @@ class MultiCompleteLineEdit(QLineEdit):
|
|||||||
if len(after_parts) < 3 and not after_parts[-1].strip():
|
if len(after_parts) < 3 and not after_parts[-1].strip():
|
||||||
after_text = u''
|
after_text = u''
|
||||||
prefix_len = len(before_text.split(self.sep)[-1].lstrip())
|
prefix_len = len(before_text.split(self.sep)[-1].lstrip())
|
||||||
if self.space_before_sep:
|
return prefix_len, \
|
||||||
complete_text_pat = '%s%s %s %s'
|
before_text[:cursor_pos - prefix_len] + text + after_text
|
||||||
len_extra = 3
|
|
||||||
else:
|
|
||||||
complete_text_pat = '%s%s%s %s'
|
|
||||||
len_extra = 2
|
|
||||||
return prefix_len, len_extra, complete_text_pat % (
|
|
||||||
before_text[:cursor_pos - prefix_len], text, self.sep, after_text)
|
|
||||||
|
|
||||||
def completion_selected(self, text):
|
def completion_selected(self, text):
|
||||||
prefix_len, len_extra, ctext = self.get_completed_text(text)
|
prefix_len, ctext = self.get_completed_text(text)
|
||||||
if self.sep is None:
|
if self.sep is None:
|
||||||
self.setText(ctext)
|
self.setText(ctext)
|
||||||
self.setCursorPosition(len(ctext))
|
self.setCursorPosition(len(ctext))
|
||||||
else:
|
else:
|
||||||
cursor_pos = self.cursorPosition()
|
cursor_pos = self.cursorPosition()
|
||||||
self.setText(ctext)
|
self.setText(ctext)
|
||||||
self.setCursorPosition(cursor_pos - prefix_len + len(text) + len_extra)
|
self.setCursorPosition(cursor_pos - prefix_len + len(text))
|
||||||
|
|
||||||
def update_complete_window(self, matches):
|
def update_complete_window(self, matches):
|
||||||
self._model.update_matches(matches)
|
self._model.update_matches(matches)
|
||||||
@ -334,6 +331,11 @@ class MultiCompleteComboBox(EnComboBox):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
EnComboBox.__init__(self, *args)
|
EnComboBox.__init__(self, *args)
|
||||||
self.setLineEdit(MultiCompleteLineEdit(self))
|
self.setLineEdit(MultiCompleteLineEdit(self))
|
||||||
|
# Needed to allow changing the case of an existing item
|
||||||
|
# otherwise on focus out, the text is changed to the
|
||||||
|
# item that matches case insensitively
|
||||||
|
c = self.lineEdit().completer()
|
||||||
|
c.setCaseSensitivity(Qt.CaseSensitive)
|
||||||
|
|
||||||
def update_items_cache(self, complete_items):
|
def update_items_cache(self, complete_items):
|
||||||
self.lineEdit().update_items_cache(complete_items)
|
self.lineEdit().update_items_cache(complete_items)
|
||||||
|
@ -27,8 +27,8 @@ class HeuristicsWidget(Widget, Ui_Form):
|
|||||||
'dehyphenate', 'renumber_headings']
|
'dehyphenate', 'renumber_headings']
|
||||||
)
|
)
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
self.rssb_defaults = [u'', u'<hr />', u'* * *', u'• • •', u'✦ ✦ ✦',
|
self.rssb_defaults = [u'', u'<hr />', u'∗ ∗ ∗', u'• • •', u'♦ ♦ ♦',
|
||||||
u'✮ ✮ ✮', u'☆ ☆ ☆', u'❂ ❂ ❂', u'✣ ✣ ✣', u'❖ ❖ ❖', u'☼ ☼ ☼', u'✠ ✠ ✠']
|
u'† †', u'‡ ‡ ‡', u'∞ ∞ ∞', u'¤ ¤ ¤', u'§']
|
||||||
self.initialize_options(get_option, get_help, db, book_id)
|
self.initialize_options(get_option, get_help, db, book_id)
|
||||||
|
|
||||||
self.load_histories()
|
self.load_histories()
|
||||||
|
@ -70,9 +70,6 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
def initialize_metadata_options(self):
|
def initialize_metadata_options(self):
|
||||||
self.initialize_combos()
|
self.initialize_combos()
|
||||||
self.author.editTextChanged.connect(self.deduce_author_sort)
|
self.author.editTextChanged.connect(self.deduce_author_sort)
|
||||||
self.author.set_separator('&')
|
|
||||||
self.author.set_space_before_sep(True)
|
|
||||||
self.author.update_items_cache(self.db.all_author_names())
|
|
||||||
|
|
||||||
mi = self.db.get_metadata(self.book_id, index_is_id=True)
|
mi = self.db.get_metadata(self.book_id, index_is_id=True)
|
||||||
self.title.setText(mi.title)
|
self.title.setText(mi.title)
|
||||||
@ -109,6 +106,9 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
def initalize_authors(self):
|
def initalize_authors(self):
|
||||||
all_authors = self.db.all_authors()
|
all_authors = self.db.all_authors()
|
||||||
all_authors.sort(key=lambda x : sort_key(x[1]))
|
all_authors.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.author.set_separator('&')
|
||||||
|
self.author.set_space_before_sep(True)
|
||||||
|
self.author.update_items_cache(self.db.all_author_names())
|
||||||
|
|
||||||
for i in all_authors:
|
for i in all_authors:
|
||||||
id, name = i
|
id, name = i
|
||||||
@ -124,6 +124,8 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
def initialize_series(self):
|
def initialize_series(self):
|
||||||
all_series = self.db.all_series()
|
all_series = self.db.all_series()
|
||||||
all_series.sort(key=lambda x : sort_key(x[1]))
|
all_series.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.series.set_separator(None)
|
||||||
|
self.series.update_items_cache([x[1] for x in all_series])
|
||||||
|
|
||||||
for i in all_series:
|
for i in all_series:
|
||||||
id, name = i
|
id, name = i
|
||||||
@ -133,6 +135,8 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
def initialize_publisher(self):
|
def initialize_publisher(self):
|
||||||
all_publishers = self.db.all_publishers()
|
all_publishers = self.db.all_publishers()
|
||||||
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.publisher.set_separator(None)
|
||||||
|
self.publisher.update_items_cache([x[1] for x in all_publishers])
|
||||||
|
|
||||||
for i in all_publishers:
|
for i in all_publishers:
|
||||||
id, name = i
|
id, name = i
|
||||||
|
@ -190,7 +190,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="CompleteLineEdit" name="tags">
|
<widget class="MultiCompleteLineEdit" name="tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -213,7 +213,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="1">
|
<item row="5" column="1">
|
||||||
<widget class="EnComboBox" name="series">
|
<widget class="MultiCompleteComboBox" name="series">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
<horstretch>10</horstretch>
|
<horstretch>10</horstretch>
|
||||||
@ -248,14 +248,14 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="3" column="1">
|
||||||
<widget class="EnComboBox" name="publisher">
|
<widget class="MultiCompleteComboBox" name="publisher">
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="CompleteComboBox" name="author">
|
<widget class="MultiCompleteComboBox" name="author">
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -277,19 +277,14 @@
|
|||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>EnComboBox</class>
|
<class>MultiCompleteComboBox</class>
|
||||||
<extends>QComboBox</extends>
|
<extends>QComboBox</extends>
|
||||||
<header>widgets.h</header>
|
<header>calibre/gui2/complete.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>CompleteComboBox</class>
|
<class>MultiCompleteLineEdit</class>
|
||||||
<extends>QComboBox</extends>
|
|
||||||
<header>widgets.h</header>
|
|
||||||
</customwidget>
|
|
||||||
<customwidget>
|
|
||||||
<class>CompleteLineEdit</class>
|
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>calibre/gui2/complete.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>ImageView</class>
|
<class>ImageView</class>
|
||||||
|
@ -14,7 +14,7 @@ from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
|
|||||||
QPushButton
|
QPushButton
|
||||||
|
|
||||||
from calibre.utils.date import qt_to_dt, now
|
from calibre.utils.date import qt_to_dt, now
|
||||||
from calibre.gui2.widgets import CompleteLineEdit, EnComboBox
|
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
|
||||||
from calibre.gui2.comments_editor import Editor as CommentsEditor
|
from calibre.gui2.comments_editor import Editor as CommentsEditor
|
||||||
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
@ -44,8 +44,10 @@ class Base(object):
|
|||||||
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(book_id, val, num=self.col_id, notify=notify,
|
return self.db.set_custom(book_id, val, num=self.col_id,
|
||||||
commit=False)
|
notify=notify, commit=False, allow_case_change=True)
|
||||||
|
else:
|
||||||
|
return set()
|
||||||
|
|
||||||
def normalize_db_val(self, val):
|
def normalize_db_val(self, val):
|
||||||
return val
|
return val
|
||||||
@ -228,10 +230,12 @@ class Text(Base):
|
|||||||
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)
|
w = MultiCompleteLineEdit(parent)
|
||||||
|
w.update_items_cache(values)
|
||||||
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||||
else:
|
else:
|
||||||
w = EnComboBox(parent)
|
w = MultiCompleteComboBox(parent)
|
||||||
|
w.set_separator(None)
|
||||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
||||||
w.setMinimumContentsLength(25)
|
w.setMinimumContentsLength(25)
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
|
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
|
||||||
@ -240,9 +244,10 @@ class Text(Base):
|
|||||||
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)
|
||||||
self.initial_val = val
|
self.initial_val = val
|
||||||
val = self.normalize_db_val(val)
|
val = self.normalize_db_val(val)
|
||||||
|
self.widgets[1].update_items_cache(self.all_values)
|
||||||
|
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
self.setter(val)
|
self.setter(val)
|
||||||
self.widgets[1].update_items_cache(self.all_values)
|
|
||||||
else:
|
else:
|
||||||
idx = None
|
idx = None
|
||||||
for i, c in enumerate(self.all_values):
|
for i, c in enumerate(self.all_values):
|
||||||
@ -276,7 +281,7 @@ class Series(Base):
|
|||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
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)
|
w = MultiCompleteComboBox(parent)
|
||||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
||||||
w.setMinimumContentsLength(25)
|
w.setMinimumContentsLength(25)
|
||||||
self.name_widget = w
|
self.name_widget = w
|
||||||
@ -305,6 +310,7 @@ class Series(Base):
|
|||||||
if c == val:
|
if c == val:
|
||||||
idx = i
|
idx = i
|
||||||
self.name_widget.addItem(c)
|
self.name_widget.addItem(c)
|
||||||
|
self.name_widget.update_items_cache(self.all_values)
|
||||||
self.name_widget.setEditText('')
|
self.name_widget.setEditText('')
|
||||||
if idx is not None:
|
if idx is not None:
|
||||||
self.widgets[1].setCurrentIndex(idx)
|
self.widgets[1].setCurrentIndex(idx)
|
||||||
@ -326,8 +332,10 @@ class Series(Base):
|
|||||||
num=self.col_id)
|
num=self.col_id)
|
||||||
else:
|
else:
|
||||||
s_index = None
|
s_index = None
|
||||||
self.db.set_custom(book_id, val, extra=s_index,
|
return self.db.set_custom(book_id, val, extra=s_index, num=self.col_id,
|
||||||
num=self.col_id, notify=notify, commit=False)
|
notify=notify, commit=False, allow_case_change=True)
|
||||||
|
else:
|
||||||
|
return set()
|
||||||
|
|
||||||
class Enumeration(Base):
|
class Enumeration(Base):
|
||||||
|
|
||||||
@ -670,7 +678,7 @@ class BulkDateTime(BulkBase):
|
|||||||
class BulkSeries(BulkBase):
|
class BulkSeries(BulkBase):
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
self.make_widgets(parent, EnComboBox)
|
self.make_widgets(parent, MultiCompleteComboBox)
|
||||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(key=sort_key)
|
values.sort(key=sort_key)
|
||||||
self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
||||||
@ -705,6 +713,8 @@ class BulkSeries(BulkBase):
|
|||||||
|
|
||||||
def initialize(self, book_id):
|
def initialize(self, book_id):
|
||||||
self.idx_widget.setChecked(False)
|
self.idx_widget.setChecked(False)
|
||||||
|
self.main_widget.set_separator(None)
|
||||||
|
self.main_widget.update_items_cache(self.all_values)
|
||||||
for c in self.all_values:
|
for c in self.all_values:
|
||||||
self.main_widget.addItem(c)
|
self.main_widget.addItem(c)
|
||||||
self.main_widget.setEditText('')
|
self.main_widget.setEditText('')
|
||||||
@ -795,7 +805,8 @@ class RemoveTags(QWidget):
|
|||||||
layout.setSpacing(5)
|
layout.setSpacing(5)
|
||||||
layout.setContentsMargins(0, 0, 0, 0)
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
self.tags_box = CompleteLineEdit(parent, values)
|
self.tags_box = MultiCompleteLineEdit(parent)
|
||||||
|
self.tags_box.update_items_cache(values)
|
||||||
layout.addWidget(self.tags_box, stretch=3)
|
layout.addWidget(self.tags_box, stretch=3)
|
||||||
self.checkbox = QCheckBox(_('Remove all tags'), parent)
|
self.checkbox = QCheckBox(_('Remove all tags'), parent)
|
||||||
layout.addWidget(self.checkbox)
|
layout.addWidget(self.checkbox)
|
||||||
@ -816,7 +827,7 @@ class BulkText(BulkBase):
|
|||||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(key=sort_key)
|
values.sort(key=sort_key)
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
self.make_widgets(parent, CompleteLineEdit,
|
self.make_widgets(parent, MultiCompleteLineEdit,
|
||||||
extra_label_text=_('tags to add'))
|
extra_label_text=_('tags to add'))
|
||||||
self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||||
self.adding_widget = self.main_widget
|
self.adding_widget = self.main_widget
|
||||||
@ -829,16 +840,16 @@ class BulkText(BulkBase):
|
|||||||
w.tags_box.textChanged.connect(self.a_c_checkbox_changed)
|
w.tags_box.textChanged.connect(self.a_c_checkbox_changed)
|
||||||
w.checkbox.stateChanged.connect(self.a_c_checkbox_changed)
|
w.checkbox.stateChanged.connect(self.a_c_checkbox_changed)
|
||||||
else:
|
else:
|
||||||
self.make_widgets(parent, EnComboBox)
|
self.make_widgets(parent, MultiCompleteComboBox)
|
||||||
|
self.main_widget.set_separator(None)
|
||||||
self.main_widget.setSizeAdjustPolicy(
|
self.main_widget.setSizeAdjustPolicy(
|
||||||
self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
||||||
self.main_widget.setMinimumContentsLength(25)
|
self.main_widget.setMinimumContentsLength(25)
|
||||||
self.ignore_change_signals = False
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
def initialize(self, book_ids):
|
def initialize(self, book_ids):
|
||||||
if self.col_metadata['is_multiple']:
|
self.main_widget.update_items_cache(self.all_values)
|
||||||
self.main_widget.update_items_cache(self.all_values)
|
if not self.col_metadata['is_multiple']:
|
||||||
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
|
||||||
|
@ -7,8 +7,8 @@ __license__ = 'GPL v3'
|
|||||||
from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \
|
from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \
|
||||||
QApplication, QSpinBox, QToolButton, QIcon
|
QApplication, QSpinBox, QToolButton, QIcon
|
||||||
from calibre.ebooks.metadata import authors_to_string, string_to_authors
|
from calibre.ebooks.metadata import authors_to_string, string_to_authors
|
||||||
from calibre.gui2.widgets import CompleteComboBox
|
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
|
from calibre.gui2.complete import MultiCompleteComboBox
|
||||||
|
|
||||||
class AddEmptyBookDialog(QDialog):
|
class AddEmptyBookDialog(QDialog):
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class AddEmptyBookDialog(QDialog):
|
|||||||
self.author_label = QLabel(_('Set the author of the new books to:'))
|
self.author_label = QLabel(_('Set the author of the new books to:'))
|
||||||
self._layout.addWidget(self.author_label, 2, 0, 1, 2)
|
self._layout.addWidget(self.author_label, 2, 0, 1, 2)
|
||||||
|
|
||||||
self.authors_combo = CompleteComboBox(self)
|
self.authors_combo = MultiCompleteComboBox(self)
|
||||||
self.authors_combo.setSizeAdjustPolicy(
|
self.authors_combo.setSizeAdjustPolicy(
|
||||||
self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
|
self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
|
||||||
self.authors_combo.setEditable(True)
|
self.authors_combo.setEditable(True)
|
||||||
|
@ -11,7 +11,7 @@ from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
|
|||||||
|
|
||||||
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
||||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string
|
from calibre.ebooks.metadata import string_to_authors, authors_to_string, title_sort
|
||||||
from calibre.ebooks.metadata.book.base import composite_formatter
|
from calibre.ebooks.metadata.book.base import composite_formatter
|
||||||
from calibre.ebooks.metadata.meta import get_metadata
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
||||||
@ -134,7 +134,7 @@ class MyBlockingBusy(QDialog): # {{{
|
|||||||
do_autonumber, do_remove_format, remove_format, do_swap_ta, \
|
do_autonumber, do_remove_format, remove_format, do_swap_ta, \
|
||||||
do_remove_conv, do_auto_author, series, do_series_restart, \
|
do_remove_conv, do_auto_author, series, do_series_restart, \
|
||||||
series_start_value, do_title_case, cover_action, clear_series, \
|
series_start_value, do_title_case, cover_action, clear_series, \
|
||||||
pubdate, adddate = self.args
|
pubdate, adddate, do_title_sort = self.args
|
||||||
|
|
||||||
|
|
||||||
# first loop: do author and title. These will commit at the end of each
|
# first loop: do author and title. These will commit at the end of each
|
||||||
@ -159,6 +159,9 @@ class MyBlockingBusy(QDialog): # {{{
|
|||||||
if do_title_case and not title_set:
|
if do_title_case and not title_set:
|
||||||
title = self.db.title(id, index_is_id=True)
|
title = self.db.title(id, index_is_id=True)
|
||||||
self.db.set_title(id, titlecase(title), notify=False)
|
self.db.set_title(id, titlecase(title), notify=False)
|
||||||
|
if do_title_sort:
|
||||||
|
title = self.db.title(id, index_is_id=True)
|
||||||
|
self.db.set_title_sort(id, title_sort(title), notify=False)
|
||||||
if au:
|
if au:
|
||||||
self.db.set_authors(id, string_to_authors(au), notify=False)
|
self.db.set_authors(id, string_to_authors(au), notify=False)
|
||||||
if cover_action == 'remove':
|
if cover_action == 'remove':
|
||||||
@ -360,11 +363,11 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
if (f in ['author_sort'] or
|
if (f in ['author_sort'] or
|
||||||
(fm[f]['datatype'] in ['text', 'series', 'enumeration']
|
(fm[f]['datatype'] in ['text', 'series', 'enumeration']
|
||||||
and fm[f].get('search_terms', None)
|
and fm[f].get('search_terms', None)
|
||||||
and f not in ['formats', 'ondevice', 'sort']) or
|
and f not in ['formats', 'ondevice']) or
|
||||||
fm[f]['datatype'] in ['int', 'float', 'bool'] ):
|
fm[f]['datatype'] in ['int', 'float', 'bool'] ):
|
||||||
self.all_fields.append(f)
|
self.all_fields.append(f)
|
||||||
self.writable_fields.append(f)
|
self.writable_fields.append(f)
|
||||||
if f in ['sort'] or fm[f]['datatype'] == 'composite':
|
if fm[f]['datatype'] == 'composite':
|
||||||
self.all_fields.append(f)
|
self.all_fields.append(f)
|
||||||
self.all_fields.sort()
|
self.all_fields.sort()
|
||||||
self.all_fields.insert(1, '{template}')
|
self.all_fields.insert(1, '{template}')
|
||||||
@ -437,7 +440,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
self.replace_func.addItems(sorted(self.s_r_functions.keys()))
|
self.replace_func.addItems(sorted(self.s_r_functions.keys()))
|
||||||
self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed)
|
self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed)
|
||||||
self.search_field.currentIndexChanged[int].connect(self.s_r_search_field_changed)
|
self.search_field.currentIndexChanged[int].connect(self.s_r_search_field_changed)
|
||||||
self.destination_field.currentIndexChanged[str].connect(self.s_r_destination_field_changed)
|
self.destination_field.currentIndexChanged[int].connect(self.s_r_destination_field_changed)
|
||||||
|
|
||||||
self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results)
|
self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results)
|
||||||
self.replace_func.currentIndexChanged[str].connect(self.s_r_paint_results)
|
self.replace_func.currentIndexChanged[str].connect(self.s_r_paint_results)
|
||||||
@ -469,6 +472,16 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
self.query_field.currentIndexChanged[str].connect(self.s_r_query_change)
|
self.query_field.currentIndexChanged[str].connect(self.s_r_query_change)
|
||||||
self.query_field.setCurrentIndex(0)
|
self.query_field.setCurrentIndex(0)
|
||||||
|
|
||||||
|
def s_r_sf_itemdata(self, idx):
|
||||||
|
if idx is None:
|
||||||
|
idx = self.search_field.currentIndex()
|
||||||
|
return unicode(self.search_field.itemData(idx).toString())
|
||||||
|
|
||||||
|
def s_r_df_itemdata(self, idx):
|
||||||
|
if idx is None:
|
||||||
|
idx = self.destination_field.currentIndex()
|
||||||
|
return unicode(self.destination_field.itemData(idx).toString())
|
||||||
|
|
||||||
def s_r_get_field(self, mi, field):
|
def s_r_get_field(self, mi, field):
|
||||||
if field:
|
if field:
|
||||||
if field == '{template}':
|
if field == '{template}':
|
||||||
@ -508,7 +521,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
for i in range(0, self.s_r_number_of_books):
|
for i in range(0, self.s_r_number_of_books):
|
||||||
w = getattr(self, 'book_%d_text'%(i+1))
|
w = getattr(self, 'book_%d_text'%(i+1))
|
||||||
mi = self.db.get_metadata(self.ids[i], index_is_id=True)
|
mi = self.db.get_metadata(self.ids[i], index_is_id=True)
|
||||||
src = unicode(self.search_field.currentText())
|
src = self.s_r_sf_itemdata(idx)
|
||||||
t = self.s_r_get_field(mi, src)
|
t = self.s_r_get_field(mi, src)
|
||||||
if len(t) > 1:
|
if len(t) > 1:
|
||||||
t = t[self.starting_from.value()-1:
|
t = t[self.starting_from.value()-1:
|
||||||
@ -518,13 +531,13 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
if self.search_mode.currentIndex() == 0:
|
if self.search_mode.currentIndex() == 0:
|
||||||
self.destination_field.setCurrentIndex(idx)
|
self.destination_field.setCurrentIndex(idx)
|
||||||
else:
|
else:
|
||||||
self.s_r_destination_field_changed(self.destination_field.currentText())
|
self.s_r_destination_field_changed(self.destination_field.currentIndex())
|
||||||
self.s_r_paint_results(None)
|
self.s_r_paint_results(None)
|
||||||
|
|
||||||
def s_r_destination_field_changed(self, txt):
|
def s_r_destination_field_changed(self, idx):
|
||||||
txt = unicode(txt)
|
txt = self.s_r_df_itemdata(idx)
|
||||||
if not txt:
|
if not txt:
|
||||||
txt = unicode(self.search_field.currentText())
|
txt = self.s_r_sf_itemdata(None)
|
||||||
if txt and txt in self.writable_fields:
|
if txt and txt in self.writable_fields:
|
||||||
self.destination_field_fm = self.db.metadata_for_field(txt)
|
self.destination_field_fm = self.db.metadata_for_field(txt)
|
||||||
self.s_r_paint_results(None)
|
self.s_r_paint_results(None)
|
||||||
@ -533,8 +546,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
self.search_field.clear()
|
self.search_field.clear()
|
||||||
self.destination_field.clear()
|
self.destination_field.clear()
|
||||||
if val == 0:
|
if val == 0:
|
||||||
self.search_field.addItems(self.writable_fields)
|
for f in self.writable_fields:
|
||||||
self.destination_field.addItems(self.writable_fields)
|
self.search_field.addItem(f if f != 'sort' else 'title_sort', f)
|
||||||
|
self.destination_field.addItem(f if f != 'sort' else 'title_sort', f)
|
||||||
self.destination_field.setCurrentIndex(0)
|
self.destination_field.setCurrentIndex(0)
|
||||||
self.destination_field.setVisible(False)
|
self.destination_field.setVisible(False)
|
||||||
self.destination_field_label.setVisible(False)
|
self.destination_field_label.setVisible(False)
|
||||||
@ -544,8 +558,14 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
self.comma_separated.setVisible(False)
|
self.comma_separated.setVisible(False)
|
||||||
self.s_r_heading.setText('<p>'+self.main_heading + self.character_heading)
|
self.s_r_heading.setText('<p>'+self.main_heading + self.character_heading)
|
||||||
else:
|
else:
|
||||||
self.search_field.addItems(self.all_fields)
|
self.search_field.blockSignals(True)
|
||||||
self.destination_field.addItems(self.writable_fields)
|
self.destination_field.blockSignals(True)
|
||||||
|
for f in self.all_fields:
|
||||||
|
self.search_field.addItem(f if f != 'sort' else 'title_sort', f)
|
||||||
|
for f in self.writable_fields:
|
||||||
|
self.destination_field.addItem(f if f != 'sort' else 'title_sort', f)
|
||||||
|
self.search_field.blockSignals(False)
|
||||||
|
self.destination_field.blockSignals(False)
|
||||||
self.destination_field.setVisible(True)
|
self.destination_field.setVisible(True)
|
||||||
self.destination_field_label.setVisible(True)
|
self.destination_field_label.setVisible(True)
|
||||||
self.replace_mode.setVisible(True)
|
self.replace_mode.setVisible(True)
|
||||||
@ -575,7 +595,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
return rfunc(rtext)
|
return rfunc(rtext)
|
||||||
|
|
||||||
def s_r_do_regexp(self, mi):
|
def s_r_do_regexp(self, mi):
|
||||||
src_field = unicode(self.search_field.currentText())
|
src_field = self.s_r_sf_itemdata(None)
|
||||||
src = self.s_r_get_field(mi, src_field)
|
src = self.s_r_get_field(mi, src_field)
|
||||||
result = []
|
result = []
|
||||||
rfunc = self.s_r_functions[unicode(self.replace_func.currentText())]
|
rfunc = self.s_r_functions[unicode(self.replace_func.currentText())]
|
||||||
@ -587,10 +607,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def s_r_do_destination(self, mi, val):
|
def s_r_do_destination(self, mi, val):
|
||||||
src = unicode(self.search_field.currentText())
|
src = self.s_r_sf_itemdata(None)
|
||||||
if src == '':
|
if src == '':
|
||||||
return ''
|
return ''
|
||||||
dest = unicode(self.destination_field.currentText())
|
dest = self.s_r_df_itemdata(None)
|
||||||
if dest == '':
|
if dest == '':
|
||||||
if self.db.metadata_for_field(src)['datatype'] == 'composite':
|
if self.db.metadata_for_field(src)['datatype'] == 'composite':
|
||||||
raise Exception(_('You must specify a destination when source is a composite field'))
|
raise Exception(_('You must specify a destination when source is a composite field'))
|
||||||
@ -680,10 +700,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
break
|
break
|
||||||
|
|
||||||
def do_search_replace(self, id):
|
def do_search_replace(self, id):
|
||||||
source = unicode(self.search_field.currentText())
|
source = self.s_r_sf_itemdata(None)
|
||||||
if not source or not self.s_r_obj:
|
if not source or not self.s_r_obj:
|
||||||
return
|
return
|
||||||
dest = unicode(self.destination_field.currentText())
|
dest = self.s_r_df_itemdata(None)
|
||||||
if not dest:
|
if not dest:
|
||||||
dest = source
|
dest = source
|
||||||
dfm = self.db.field_metadata[dest]
|
dfm = self.db.field_metadata[dest]
|
||||||
@ -717,6 +737,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
else:
|
else:
|
||||||
if dest == 'comments':
|
if dest == 'comments':
|
||||||
setter = self.db.set_comment
|
setter = self.db.set_comment
|
||||||
|
elif dest == 'sort':
|
||||||
|
setter = self.db.set_title_sort
|
||||||
else:
|
else:
|
||||||
setter = getattr(self.db, 'set_'+dest)
|
setter = getattr(self.db, 'set_'+dest)
|
||||||
if dest in ['title', 'authors']:
|
if dest in ['title', 'authors']:
|
||||||
@ -764,6 +786,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
def initialize_series(self):
|
def initialize_series(self):
|
||||||
all_series = self.db.all_series()
|
all_series = self.db.all_series()
|
||||||
all_series.sort(key=lambda x : sort_key(x[1]))
|
all_series.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.series.set_separator(None)
|
||||||
|
self.series.update_items_cache([x[1] for x in all_series])
|
||||||
|
|
||||||
for i in all_series:
|
for i in all_series:
|
||||||
id, name = i
|
id, name = i
|
||||||
@ -773,6 +797,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
def initialize_publisher(self):
|
def initialize_publisher(self):
|
||||||
all_publishers = self.db.all_publishers()
|
all_publishers = self.db.all_publishers()
|
||||||
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.publisher.set_separator(None)
|
||||||
|
self.publisher.update_items_cache([x[1] for x in all_publishers])
|
||||||
|
|
||||||
for i in all_publishers:
|
for i in all_publishers:
|
||||||
id, name = i
|
id, name = i
|
||||||
@ -840,6 +866,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
do_remove_conv = self.remove_conversion_settings.isChecked()
|
do_remove_conv = self.remove_conversion_settings.isChecked()
|
||||||
do_auto_author = self.auto_author_sort.isChecked()
|
do_auto_author = self.auto_author_sort.isChecked()
|
||||||
do_title_case = self.change_title_to_title_case.isChecked()
|
do_title_case = self.change_title_to_title_case.isChecked()
|
||||||
|
do_title_sort = self.update_title_sort.isChecked()
|
||||||
pubdate = adddate = None
|
pubdate = adddate = None
|
||||||
if self.apply_pubdate.isChecked():
|
if self.apply_pubdate.isChecked():
|
||||||
pubdate = qt_to_dt(self.pubdate.date())
|
pubdate = qt_to_dt(self.pubdate.date())
|
||||||
@ -858,7 +885,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
do_autonumber, do_remove_format, remove_format, do_swap_ta,
|
do_autonumber, do_remove_format, remove_format, do_swap_ta,
|
||||||
do_remove_conv, do_auto_author, series, do_series_restart,
|
do_remove_conv, do_auto_author, series, do_series_restart,
|
||||||
series_start_value, do_title_case, cover_action, clear_series,
|
series_start_value, do_title_case, cover_action, clear_series,
|
||||||
pubdate, adddate)
|
pubdate, adddate, do_title_sort)
|
||||||
|
|
||||||
bb = MyBlockingBusy(_('Applying changes to %d books.\nPhase {0} {1}%%.')
|
bb = MyBlockingBusy(_('Applying changes to %d books.\nPhase {0} {1}%%.')
|
||||||
%len(self.ids), args, self.db, self.ids,
|
%len(self.ids), args, self.db, self.ids,
|
||||||
|
@ -76,7 +76,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="CompleteComboBox" name="authors">
|
<widget class="MultiCompleteComboBox" name="authors">
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -175,7 +175,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="EnComboBox" name="publisher">
|
<widget class="MultiCompleteComboBox" name="publisher">
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -195,7 +195,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="1">
|
<item row="5" column="1">
|
||||||
<widget class="CompleteLineEdit" name="tags">
|
<widget class="MultiCompleteLineEdit" name="tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -229,7 +229,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="1">
|
<item row="6" column="1">
|
||||||
<widget class="CompleteLineEdit" name="remove_tags">
|
<widget class="MultiCompleteLineEdit" name="remove_tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Comma separated list of tags to remove from the books. </string>
|
<string>Comma separated list of tags to remove from the books. </string>
|
||||||
</property>
|
</property>
|
||||||
@ -262,7 +262,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="1">
|
<item row="7" column="1">
|
||||||
<widget class="EnComboBox" name="series">
|
<widget class="MultiCompleteComboBox" name="series">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
@ -489,6 +489,16 @@ title and author are swapped before the title case is set</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="update_title_sort">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Update title sort based on the current title. This will be applied only after other changes to title.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Update &title sort</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -1072,19 +1082,14 @@ not multiple and the destination field is multiple</string>
|
|||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>EnComboBox</class>
|
<class>MultiCompleteComboBox</class>
|
||||||
<extends>QComboBox</extends>
|
<extends>QComboBox</extends>
|
||||||
<header>widgets.h</header>
|
<header>calibre/gui2/complete.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>CompleteComboBox</class>
|
<class>MultiCompleteLineEdit</class>
|
||||||
<extends>QComboBox</extends>
|
|
||||||
<header>widgets.h</header>
|
|
||||||
</customwidget>
|
|
||||||
<customwidget>
|
|
||||||
<class>CompleteLineEdit</class>
|
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>calibre/gui2/complete.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>HistoryLineEdit</class>
|
<class>HistoryLineEdit</class>
|
||||||
|
@ -622,6 +622,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
|
|
||||||
self.original_author = unicode(self.authors.text()).strip()
|
self.original_author = unicode(self.authors.text()).strip()
|
||||||
self.original_title = unicode(self.title.text()).strip()
|
self.original_title = unicode(self.title.text()).strip()
|
||||||
|
self.books_to_refresh = set()
|
||||||
|
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
@ -739,6 +740,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
|
self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
|
||||||
all_series = self.db.all_series()
|
all_series = self.db.all_series()
|
||||||
all_series.sort(key=lambda x : sort_key(x[1]))
|
all_series.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.series.set_separator(None)
|
||||||
|
self.series.update_items_cache([x[1] for x in all_series])
|
||||||
series_id = self.db.series_id(self.row)
|
series_id = self.db.series_id(self.row)
|
||||||
idx, c = None, 0
|
idx, c = None, 0
|
||||||
for i in all_series:
|
for i in all_series:
|
||||||
@ -756,6 +759,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
def initialize_publisher(self):
|
def initialize_publisher(self):
|
||||||
all_publishers = self.db.all_publishers()
|
all_publishers = self.db.all_publishers()
|
||||||
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.publisher.set_separator(None)
|
||||||
|
self.publisher.update_items_cache([x[1] for x in all_publishers])
|
||||||
publisher_id = self.db.publisher_id(self.row)
|
publisher_id = self.db.publisher_id(self.row)
|
||||||
idx, c = None, 0
|
idx, c = None, 0
|
||||||
for i in all_publishers:
|
for i in all_publishers:
|
||||||
@ -775,7 +780,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
_('You have changed the tags. In order to use the tags'
|
_('You have changed the tags. In order to use the tags'
|
||||||
' editor, you must either discard or apply these '
|
' editor, you must either discard or apply these '
|
||||||
'changes. Apply changes?'), show_copy_button=False):
|
'changes. Apply changes?'), show_copy_button=False):
|
||||||
self.apply_tags(commit=True, notify=True)
|
self.books_to_refresh |= self.apply_tags(commit=True, notify=True,
|
||||||
|
allow_case_change=True)
|
||||||
self.original_tags = unicode(self.tags.text())
|
self.original_tags = unicode(self.tags.text())
|
||||||
else:
|
else:
|
||||||
self.tags.setText(self.original_tags)
|
self.tags.setText(self.original_tags)
|
||||||
@ -882,9 +888,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
break
|
break
|
||||||
|
|
||||||
def apply_tags(self, commit=False, notify=False):
|
def apply_tags(self, commit=False, notify=False):
|
||||||
self.db.set_tags(self.id, [x.strip() for x in
|
return self.db.set_tags(self.id, [x.strip() for x in
|
||||||
unicode(self.tags.text()).split(',')],
|
unicode(self.tags.text()).split(',')],
|
||||||
notify=notify, commit=commit)
|
notify=notify, commit=commit, allow_case_change=True)
|
||||||
|
|
||||||
def next_triggered(self, row_delta, *args):
|
def next_triggered(self, row_delta, *args):
|
||||||
self.row_delta = row_delta
|
self.row_delta = row_delta
|
||||||
@ -903,7 +909,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.db.set_title_sort(self.id, ts, notify=False, commit=False)
|
self.db.set_title_sort(self.id, ts, notify=False, commit=False)
|
||||||
au = unicode(self.authors.text()).strip()
|
au = unicode(self.authors.text()).strip()
|
||||||
if au and au != self.original_author:
|
if au and au != self.original_author:
|
||||||
self.db.set_authors(self.id, string_to_authors(au), notify=False)
|
self.books_to_refresh |= self.db.set_authors(self.id,
|
||||||
|
string_to_authors(au),
|
||||||
|
notify=False,
|
||||||
|
allow_case_change=True)
|
||||||
aus = unicode(self.author_sort.text()).strip()
|
aus = unicode(self.author_sort.text()).strip()
|
||||||
if aus:
|
if aus:
|
||||||
self.db.set_author_sort(self.id, aus, notify=False, commit=False)
|
self.db.set_author_sort(self.id, aus, notify=False, commit=False)
|
||||||
@ -913,13 +922,13 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
notify=False, commit=False)
|
notify=False, commit=False)
|
||||||
self.db.set_rating(self.id, 2*self.rating.value(), notify=False,
|
self.db.set_rating(self.id, 2*self.rating.value(), notify=False,
|
||||||
commit=False)
|
commit=False)
|
||||||
self.apply_tags()
|
self.books_to_refresh |= self.apply_tags()
|
||||||
self.db.set_publisher(self.id,
|
self.books_to_refresh |= self.db.set_publisher(self.id,
|
||||||
unicode(self.publisher.currentText()).strip(),
|
unicode(self.publisher.currentText()).strip(),
|
||||||
notify=False, commit=False)
|
notify=False, commit=False, allow_case_change=True)
|
||||||
self.db.set_series(self.id,
|
self.books_to_refresh |= self.db.set_series(self.id,
|
||||||
unicode(self.series.currentText()).strip(), notify=False,
|
unicode(self.series.currentText()).strip(), notify=False,
|
||||||
commit=False)
|
commit=False, allow_case_change=True)
|
||||||
self.db.set_series_index(self.id, self.series_index.value(),
|
self.db.set_series_index(self.id, self.series_index.value(),
|
||||||
notify=False, commit=False)
|
notify=False, commit=False)
|
||||||
self.db.set_comment(self.id,
|
self.db.set_comment(self.id,
|
||||||
@ -940,7 +949,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
else:
|
else:
|
||||||
self.db.remove_cover(self.id)
|
self.db.remove_cover(self.id)
|
||||||
for w in getattr(self, 'custom_column_widgets', []):
|
for w in getattr(self, 'custom_column_widgets', []):
|
||||||
w.commit(self.id)
|
self.books_to_refresh |= w.commit(self.id)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
except IOError, err:
|
except IOError, err:
|
||||||
if err.errno == 13: # Permission denied
|
if err.errno == 13: # Permission denied
|
||||||
|
@ -240,7 +240,7 @@ Using this button to create author sort will change author sort from red to gree
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="CompleteComboBox" name="authors">
|
<widget class="MultiCompleteComboBox" name="authors">
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -313,7 +313,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="1" colspan="2">
|
<item row="5" column="1" colspan="2">
|
||||||
<widget class="EnComboBox" name="publisher">
|
<widget class="MultiCompleteComboBox" name="publisher">
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -335,7 +335,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<item row="6" column="1">
|
<item row="6" column="1">
|
||||||
<layout class="QHBoxLayout" name="_2">
|
<layout class="QHBoxLayout" name="_2">
|
||||||
<item>
|
<item>
|
||||||
<widget class="CompleteLineEdit" name="tags">
|
<widget class="MultiCompleteLineEdit" name="tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -379,7 +379,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<number>5</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="EnComboBox" name="series">
|
<widget class="MultiCompleteComboBox" name="series">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>List of known series. You can add new series.</string>
|
<string>List of known series. You can add new series.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -837,19 +837,14 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>EnComboBox</class>
|
<class>MultiCompleteLineEdit</class>
|
||||||
<extends>QComboBox</extends>
|
|
||||||
<header>widgets.h</header>
|
|
||||||
</customwidget>
|
|
||||||
<customwidget>
|
|
||||||
<class>CompleteLineEdit</class>
|
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>calibre/gui2/complete.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>CompleteComboBox</class>
|
<class>MultiCompleteComboBox</class>
|
||||||
<extends>QComboBox</extends>
|
<extends>QComboBox</extends>
|
||||||
<header>widgets.h</header>
|
<header>calibre/gui2/complete.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>FormatList</class>
|
<class>FormatList</class>
|
||||||
|
@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
import re, copy
|
import re, copy
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog, QDialogButtonBox, QCompleter, Qt
|
from PyQt4.Qt import QDialog, QDialogButtonBox
|
||||||
|
|
||||||
from calibre.gui2.dialogs.search_ui import Ui_Dialog
|
from calibre.gui2.dialogs.search_ui import Ui_Dialog
|
||||||
from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH
|
from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH
|
||||||
@ -29,20 +29,18 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
name = name.strip().replace('|', ',')
|
name = name.strip().replace('|', ',')
|
||||||
self.authors_box.addItem(name)
|
self.authors_box.addItem(name)
|
||||||
self.authors_box.setEditText('')
|
self.authors_box.setEditText('')
|
||||||
self.authors_box.completer().setCompletionMode(QCompleter.PopupCompletion)
|
|
||||||
self.authors_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
|
|
||||||
self.authors_box.set_separator('&')
|
self.authors_box.set_separator('&')
|
||||||
self.authors_box.set_space_before_sep(True)
|
self.authors_box.set_space_before_sep(True)
|
||||||
self.authors_box.update_items_cache(db.all_author_names())
|
self.authors_box.update_items_cache(db.all_author_names())
|
||||||
|
|
||||||
all_series = db.all_series()
|
all_series = db.all_series()
|
||||||
all_series.sort(key=lambda x : sort_key(x[1]))
|
all_series.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.series_box.set_separator(None)
|
||||||
|
self.series_box.update_items_cache([x[1] for x in all_series])
|
||||||
for i in all_series:
|
for i in all_series:
|
||||||
id, name = i
|
id, name = i
|
||||||
self.series_box.addItem(name)
|
self.series_box.addItem(name)
|
||||||
self.series_box.setEditText('')
|
self.series_box.setEditText('')
|
||||||
self.series_box.completer().setCompletionMode(QCompleter.PopupCompletion)
|
|
||||||
self.series_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
|
|
||||||
|
|
||||||
all_tags = db.all_tags()
|
all_tags = db.all_tags()
|
||||||
self.tags_box.update_items_cache(all_tags)
|
self.tags_box.update_items_cache(all_tags)
|
||||||
|
@ -265,21 +265,21 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="CompleteComboBox" name="authors_box">
|
<widget class="MultiCompleteComboBox" name="authors_box">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Enter an author's name. Only one author can be used.</string>
|
<string>Enter an author's name. Only one author can be used.</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="3" column="1">
|
||||||
<widget class="EnComboBox" name="series_box">
|
<widget class="MultiCompleteComboBox" name="series_box">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Enter a series name, without an index. Only one series name can be used.</string>
|
<string>Enter a series name, without an index. Only one series name can be used.</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="CompleteLineEdit" name="tags_box">
|
<widget class="MultiCompleteLineEdit" name="tags_box">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Enter tags separated by spaces</string>
|
<string>Enter tags separated by spaces</string>
|
||||||
</property>
|
</property>
|
||||||
@ -355,19 +355,14 @@
|
|||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>EnComboBox</class>
|
<class>MultiCompleteLineEdit</class>
|
||||||
<extends>QComboBox</extends>
|
|
||||||
<header>widgets.h</header>
|
|
||||||
</customwidget>
|
|
||||||
<customwidget>
|
|
||||||
<class>CompleteLineEdit</class>
|
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>calibre/gui2/complete.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>CompleteComboBox</class>
|
<class>MultiCompleteComboBox</class>
|
||||||
<extends>QComboBox</extends>
|
<extends>QComboBox</extends>
|
||||||
<header>widgets.h</header>
|
<header>calibre/gui2/complete.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
|
@ -12,11 +12,11 @@ from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \
|
|||||||
QPainterPath, QLinearGradient, QBrush, \
|
QPainterPath, QLinearGradient, QBrush, \
|
||||||
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
|
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
|
||||||
QIcon, QDoubleSpinBox, QVariant, QSpinBox, \
|
QIcon, QDoubleSpinBox, QVariant, QSpinBox, \
|
||||||
QStyledItemDelegate, QCompleter, \
|
QStyledItemDelegate, QComboBox, QTextDocument
|
||||||
QComboBox, QTextDocument
|
|
||||||
|
|
||||||
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
||||||
from calibre.gui2.widgets import EnLineEdit, CompleteLineEdit
|
from calibre.gui2.widgets import EnLineEdit
|
||||||
|
from calibre.gui2.complete import MultiCompleteLineEdit
|
||||||
from calibre.utils.date import now, format_date
|
from calibre.utils.date import now, format_date
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
from calibre.utils.formatter import validation_formatter
|
from calibre.utils.formatter import validation_formatter
|
||||||
@ -151,38 +151,15 @@ class TextDelegate(QStyledItemDelegate): # {{{
|
|||||||
self.auto_complete_function = f
|
self.auto_complete_function = f
|
||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
def createEditor(self, parent, option, index):
|
||||||
editor = EnLineEdit(parent)
|
|
||||||
if self.auto_complete_function:
|
if self.auto_complete_function:
|
||||||
|
editor = MultiCompleteLineEdit(parent)
|
||||||
|
editor.set_separator(None)
|
||||||
complete_items = [i[1] for i in self.auto_complete_function()]
|
complete_items = [i[1] for i in self.auto_complete_function()]
|
||||||
completer = QCompleter(complete_items, self)
|
editor.update_items_cache(complete_items)
|
||||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
|
||||||
completer.setCompletionMode(QCompleter.PopupCompletion)
|
|
||||||
editor.setCompleter(completer)
|
|
||||||
return editor
|
|
||||||
#}}}
|
|
||||||
|
|
||||||
class TagsDelegate(QStyledItemDelegate): # {{{
|
|
||||||
def __init__(self, parent):
|
|
||||||
QStyledItemDelegate.__init__(self, parent)
|
|
||||||
self.db = None
|
|
||||||
|
|
||||||
def set_database(self, db):
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
|
||||||
if self.db:
|
|
||||||
col = index.model().column_map[index.column()]
|
|
||||||
if not index.model().is_custom_column(col):
|
|
||||||
editor = CompleteLineEdit(parent, self.db.all_tags())
|
|
||||||
else:
|
|
||||||
editor = CompleteLineEdit(parent,
|
|
||||||
sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
|
|
||||||
key=sort_key))
|
|
||||||
return editor
|
|
||||||
else:
|
else:
|
||||||
editor = EnLineEdit(parent)
|
editor = EnLineEdit(parent)
|
||||||
return editor
|
return editor
|
||||||
# }}}
|
#}}}
|
||||||
|
|
||||||
class CompleteDelegate(QStyledItemDelegate): # {{{
|
class CompleteDelegate(QStyledItemDelegate): # {{{
|
||||||
def __init__(self, parent, sep, items_func_name, space_before_sep=False):
|
def __init__(self, parent, sep, items_func_name, space_before_sep=False):
|
||||||
@ -197,13 +174,15 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
|
|||||||
def createEditor(self, parent, option, index):
|
def createEditor(self, parent, option, index):
|
||||||
if self.db and hasattr(self.db, self.items_func_name):
|
if self.db and hasattr(self.db, self.items_func_name):
|
||||||
col = index.model().column_map[index.column()]
|
col = index.model().column_map[index.column()]
|
||||||
|
editor = MultiCompleteLineEdit(parent)
|
||||||
|
editor.set_separator(self.sep)
|
||||||
|
editor.set_space_before_sep(self.space_before_sep)
|
||||||
if not index.model().is_custom_column(col):
|
if not index.model().is_custom_column(col):
|
||||||
editor = CompleteLineEdit(parent, getattr(self.db, self.items_func_name)(),
|
all_items = getattr(self.db, self.items_func_name)()
|
||||||
self.sep, self.space_before_sep)
|
|
||||||
else:
|
else:
|
||||||
editor = CompleteLineEdit(parent,
|
all_items = list(self.db.all_custom(
|
||||||
sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
|
label=self.db.field_metadata.key_to_label(col)))
|
||||||
key=sort_key), self.sep, self.space_before_sep)
|
editor.update_items_cache(all_items)
|
||||||
else:
|
else:
|
||||||
editor = EnLineEdit(parent)
|
editor = EnLineEdit(parent)
|
||||||
return editor
|
return editor
|
||||||
@ -273,13 +252,11 @@ class CcTextDelegate(QStyledItemDelegate): # {{{
|
|||||||
editor.setRange(-100., float(sys.maxint))
|
editor.setRange(-100., float(sys.maxint))
|
||||||
editor.setDecimals(2)
|
editor.setDecimals(2)
|
||||||
else:
|
else:
|
||||||
editor = EnLineEdit(parent)
|
editor = MultiCompleteLineEdit(parent)
|
||||||
|
editor.set_separator(None)
|
||||||
complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))),
|
complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))),
|
||||||
key=sort_key)
|
key=sort_key)
|
||||||
completer = QCompleter(complete_items, self)
|
editor.update_items_cache(complete_items)
|
||||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
|
||||||
completer.setCompletionMode(QCompleter.PopupCompletion)
|
|
||||||
editor.setCompleter(completer)
|
|
||||||
return editor
|
return editor
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -800,9 +800,10 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
id = self.db.id(row)
|
id = self.db.id(row)
|
||||||
self.db.set_custom(id, val, extra=s_index,
|
books_to_refresh = set([id])
|
||||||
|
books_to_refresh |= self.db.set_custom(id, val, extra=s_index,
|
||||||
label=label, num=None, append=False, notify=True)
|
label=label, num=None, append=False, notify=True)
|
||||||
self.refresh_ids([id], current_row=row)
|
self.refresh_ids(list(books_to_refresh), current_row=row)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setData(self, index, value, role):
|
def setData(self, index, value, role):
|
||||||
@ -819,6 +820,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
value.toDate() if column in ('timestamp', 'pubdate') else \
|
value.toDate() if column in ('timestamp', 'pubdate') else \
|
||||||
unicode(value.toString())
|
unicode(value.toString())
|
||||||
id = self.db.id(row)
|
id = self.db.id(row)
|
||||||
|
books_to_refresh = set([id])
|
||||||
if column == 'rating':
|
if column == 'rating':
|
||||||
val = 0 if val < 0 else 5 if val > 5 else val
|
val = 0 if val < 0 else 5 if val > 5 else val
|
||||||
val *= 2
|
val *= 2
|
||||||
@ -826,7 +828,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
elif column == 'series':
|
elif column == 'series':
|
||||||
val = val.strip()
|
val = val.strip()
|
||||||
if not val:
|
if not val:
|
||||||
self.db.set_series(id, val)
|
books_to_refresh |= self.db.set_series(id, val,
|
||||||
|
allow_case_change=True)
|
||||||
self.db.set_series_index(id, 1.0)
|
self.db.set_series_index(id, 1.0)
|
||||||
else:
|
else:
|
||||||
pat = re.compile(r'\[([.0-9]+)\]')
|
pat = re.compile(r'\[([.0-9]+)\]')
|
||||||
@ -840,7 +843,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
if ni != 1:
|
if ni != 1:
|
||||||
self.db.set_series_index(id, ni)
|
self.db.set_series_index(id, ni)
|
||||||
if val:
|
if val:
|
||||||
self.db.set_series(id, val)
|
books_to_refresh |= self.db.set_series(id, val,
|
||||||
|
allow_case_change=True)
|
||||||
elif column == 'timestamp':
|
elif column == 'timestamp':
|
||||||
if val.isNull() or not val.isValid():
|
if val.isNull() or not val.isValid():
|
||||||
return False
|
return False
|
||||||
@ -850,8 +854,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
return False
|
return False
|
||||||
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
|
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
|
||||||
else:
|
else:
|
||||||
self.db.set(row, column, val)
|
books_to_refresh |= self.db.set(row, column, val,
|
||||||
self.refresh_ids([id], row)
|
allow_case_change=True)
|
||||||
|
self.refresh_ids(list(books_to_refresh), row)
|
||||||
self.dataChanged.emit(index, index)
|
self.dataChanged.emit(index, index)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from PyQt4.Qt import Qt, QDateEdit, QDate, \
|
|||||||
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \
|
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \
|
||||||
QPushButton, QSpinBox, QLineEdit
|
QPushButton, QSpinBox, QLineEdit
|
||||||
|
|
||||||
from calibre.gui2.widgets import EnLineEdit, EnComboBox, FormatList, ImageView
|
from calibre.gui2.widgets import EnLineEdit, FormatList, ImageView
|
||||||
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
|
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
from calibre.utils.config import tweaks, prefs
|
from calibre.utils.config import tweaks, prefs
|
||||||
@ -156,6 +156,7 @@ class AuthorsEdit(MultiCompleteComboBox):
|
|||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
self.dialog = parent
|
self.dialog = parent
|
||||||
|
self.books_to_refresh = set([])
|
||||||
MultiCompleteComboBox.__init__(self, parent)
|
MultiCompleteComboBox.__init__(self, parent)
|
||||||
self.setToolTip(self.TOOLTIP)
|
self.setToolTip(self.TOOLTIP)
|
||||||
self.setWhatsThis(self.TOOLTIP)
|
self.setWhatsThis(self.TOOLTIP)
|
||||||
@ -166,6 +167,7 @@ class AuthorsEdit(MultiCompleteComboBox):
|
|||||||
return _('Unknown')
|
return _('Unknown')
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
def initialize(self, db, id_):
|
||||||
|
self.books_to_refresh = set([])
|
||||||
all_authors = db.all_authors()
|
all_authors = db.all_authors()
|
||||||
all_authors.sort(key=lambda x : sort_key(x[1]))
|
all_authors.sort(key=lambda x : sort_key(x[1]))
|
||||||
for i in all_authors:
|
for i in all_authors:
|
||||||
@ -185,7 +187,8 @@ class AuthorsEdit(MultiCompleteComboBox):
|
|||||||
|
|
||||||
def commit(self, db, id_):
|
def commit(self, db, id_):
|
||||||
authors = self.current_val
|
authors = self.current_val
|
||||||
db.set_authors(id_, authors, notify=False)
|
self.books_to_refresh |= db.set_authors(id_, authors, notify=False,
|
||||||
|
allow_case_change=True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
@ -283,19 +286,21 @@ class AuthorSortEdit(EnLineEdit):
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Series {{{
|
# Series {{{
|
||||||
class SeriesEdit(EnComboBox):
|
class SeriesEdit(MultiCompleteComboBox):
|
||||||
|
|
||||||
TOOLTIP = _('List of known series. You can add new series.')
|
TOOLTIP = _('List of known series. You can add new series.')
|
||||||
LABEL = _('&Series:')
|
LABEL = _('&Series:')
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
EnComboBox.__init__(self, parent)
|
MultiCompleteComboBox.__init__(self, parent)
|
||||||
|
self.set_separator(None)
|
||||||
self.dialog = parent
|
self.dialog = parent
|
||||||
self.setSizeAdjustPolicy(
|
self.setSizeAdjustPolicy(
|
||||||
self.AdjustToMinimumContentsLengthWithIcon)
|
self.AdjustToMinimumContentsLengthWithIcon)
|
||||||
self.setToolTip(self.TOOLTIP)
|
self.setToolTip(self.TOOLTIP)
|
||||||
self.setWhatsThis(self.TOOLTIP)
|
self.setWhatsThis(self.TOOLTIP)
|
||||||
self.setEditable(True)
|
self.setEditable(True)
|
||||||
|
self.books_to_refresh = set([])
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
def current_val(self):
|
def current_val(self):
|
||||||
@ -312,8 +317,10 @@ class SeriesEdit(EnComboBox):
|
|||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
def initialize(self, db, id_):
|
||||||
|
self.books_to_refresh = set([])
|
||||||
all_series = db.all_series()
|
all_series = db.all_series()
|
||||||
all_series.sort(key=lambda x : sort_key(x[1]))
|
all_series.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.update_items_cache([x[1] for x in all_series])
|
||||||
series_id = db.series_id(id_, index_is_id=True)
|
series_id = db.series_id(id_, index_is_id=True)
|
||||||
idx, c = None, 0
|
idx, c = None, 0
|
||||||
for i in all_series:
|
for i in all_series:
|
||||||
@ -330,7 +337,8 @@ class SeriesEdit(EnComboBox):
|
|||||||
|
|
||||||
def commit(self, db, id_):
|
def commit(self, db, id_):
|
||||||
series = self.current_val
|
series = self.current_val
|
||||||
db.set_series(id_, series, notify=False, commit=True)
|
self.books_to_refresh |= db.set_series(id_, series, notify=False,
|
||||||
|
commit=True, allow_case_change=True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class SeriesIndexEdit(QDoubleSpinBox):
|
class SeriesIndexEdit(QDoubleSpinBox):
|
||||||
@ -822,6 +830,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
|
|||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
MultiCompleteLineEdit.__init__(self, parent)
|
MultiCompleteLineEdit.__init__(self, parent)
|
||||||
|
self.books_to_refresh = set([])
|
||||||
self.setToolTip(self.TOOLTIP)
|
self.setToolTip(self.TOOLTIP)
|
||||||
self.setWhatsThis(self.TOOLTIP)
|
self.setWhatsThis(self.TOOLTIP)
|
||||||
|
|
||||||
@ -836,6 +845,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
|
|||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
def initialize(self, db, id_):
|
||||||
|
self.books_to_refresh = set([])
|
||||||
tags = db.tags(id_, index_is_id=True)
|
tags = db.tags(id_, index_is_id=True)
|
||||||
tags = tags.split(',') if tags else []
|
tags = tags.split(',') if tags else []
|
||||||
self.current_val = tags
|
self.current_val = tags
|
||||||
@ -864,7 +874,9 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
|
|||||||
|
|
||||||
|
|
||||||
def commit(self, db, id_):
|
def commit(self, db, id_):
|
||||||
db.set_tags(id_, self.current_val, notify=False, commit=False)
|
self.books_to_refresh |= db.set_tags(
|
||||||
|
id_, self.current_val, notify=False, commit=False,
|
||||||
|
allow_case_change=True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
@ -910,13 +922,15 @@ class ISBNEdit(QLineEdit): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class PublisherEdit(EnComboBox): # {{{
|
class PublisherEdit(MultiCompleteComboBox): # {{{
|
||||||
LABEL = _('&Publisher:')
|
LABEL = _('&Publisher:')
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
EnComboBox.__init__(self, parent)
|
MultiCompleteComboBox.__init__(self, parent)
|
||||||
|
self.set_separator(None)
|
||||||
self.setSizeAdjustPolicy(
|
self.setSizeAdjustPolicy(
|
||||||
self.AdjustToMinimumContentsLengthWithIcon)
|
self.AdjustToMinimumContentsLengthWithIcon)
|
||||||
|
self.books_to_refresh = set([])
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
def current_val(self):
|
def current_val(self):
|
||||||
@ -933,8 +947,10 @@ class PublisherEdit(EnComboBox): # {{{
|
|||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
def initialize(self, db, id_):
|
||||||
|
self.books_to_refresh = set([])
|
||||||
all_publishers = db.all_publishers()
|
all_publishers = db.all_publishers()
|
||||||
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.update_items_cache([x[1] for x in all_publishers])
|
||||||
publisher_id = db.publisher_id(id_, index_is_id=True)
|
publisher_id = db.publisher_id(id_, index_is_id=True)
|
||||||
idx, c = None, 0
|
idx, c = None, 0
|
||||||
for i in all_publishers:
|
for i in all_publishers:
|
||||||
@ -949,7 +965,8 @@ class PublisherEdit(EnComboBox): # {{{
|
|||||||
self.setCurrentIndex(idx)
|
self.setCurrentIndex(idx)
|
||||||
|
|
||||||
def commit(self, db, id_):
|
def commit(self, db, id_):
|
||||||
db.set_publisher(id_, self.current_val, notify=False, commit=False)
|
self.books_to_refresh |= db.set_publisher(id_, self.current_val,
|
||||||
|
notify=False, commit=False, allow_case_change=True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -31,6 +31,8 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
def __init__(self, db, parent=None):
|
def __init__(self, db, parent=None):
|
||||||
self.db = db
|
self.db = db
|
||||||
self.changed = set([])
|
self.changed = set([])
|
||||||
|
self.books_to_refresh = set([])
|
||||||
|
self.rows_to_refresh = set([])
|
||||||
ResizableDialog.__init__(self, parent)
|
ResizableDialog.__init__(self, parent)
|
||||||
|
|
||||||
def setupUi(self, *args): # {{{
|
def setupUi(self, *args): # {{{
|
||||||
@ -192,6 +194,7 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
|
|
||||||
def __call__(self, id_):
|
def __call__(self, id_):
|
||||||
self.book_id = id_
|
self.book_id = id_
|
||||||
|
self.books_to_refresh = set([])
|
||||||
for widget in self.basic_metadata_widgets:
|
for widget in self.basic_metadata_widgets:
|
||||||
widget.initialize(self.db, id_)
|
widget.initialize(self.db, id_)
|
||||||
for widget in self.custom_metadata_widgets:
|
for widget in self.custom_metadata_widgets:
|
||||||
@ -295,6 +298,8 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
try:
|
try:
|
||||||
if not widget.commit(self.db, self.book_id):
|
if not widget.commit(self.db, self.book_id):
|
||||||
return False
|
return False
|
||||||
|
self.books_to_refresh |= getattr(widget, 'books_to_refresh',
|
||||||
|
set([]))
|
||||||
except IOError, err:
|
except IOError, err:
|
||||||
if err.errno == 13: # Permission denied
|
if err.errno == 13: # Permission denied
|
||||||
import traceback
|
import traceback
|
||||||
@ -306,9 +311,13 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
return False
|
return False
|
||||||
raise
|
raise
|
||||||
for widget in getattr(self, 'custom_metadata_widgets', []):
|
for widget in getattr(self, 'custom_metadata_widgets', []):
|
||||||
widget.commit(self.book_id)
|
self.books_to_refresh |= widget.commit(self.book_id)
|
||||||
|
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
rows = self.db.refresh_ids(list(self.books_to_refresh))
|
||||||
|
if rows:
|
||||||
|
self.rows_to_refresh |= set(rows)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
@ -330,12 +339,14 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
self.current_row = current_row
|
self.current_row = current_row
|
||||||
if view_slot is not None:
|
if view_slot is not None:
|
||||||
self.view_format.connect(view_slot)
|
self.view_format.connect(view_slot)
|
||||||
self.do_one()
|
self.do_one(apply_changes=False)
|
||||||
ret = self.exec_()
|
ret = self.exec_()
|
||||||
self.break_cycles()
|
self.break_cycles()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def do_one(self, delta=0):
|
def do_one(self, delta=0, apply_changes=True):
|
||||||
|
if apply_changes:
|
||||||
|
self.apply_changes()
|
||||||
self.current_row += delta
|
self.current_row += delta
|
||||||
prev = next_ = None
|
prev = next_ = None
|
||||||
if self.current_row > 0:
|
if self.current_row > 0:
|
||||||
@ -353,6 +364,7 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
self.prev_button.setVisible(prev is not None)
|
self.prev_button.setVisible(prev is not None)
|
||||||
self(self.db.id(self.row_list[self.current_row]))
|
self(self.db.id(self.row_list[self.current_row]))
|
||||||
|
|
||||||
|
|
||||||
def break_cycles(self):
|
def break_cycles(self):
|
||||||
# Break any reference cycles that could prevent python
|
# Break any reference cycles that could prevent python
|
||||||
# from garbage collecting this dialog
|
# from garbage collecting this dialog
|
||||||
@ -618,7 +630,7 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{
|
|||||||
def edit_metadata(db, row_list, current_row, parent=None, view_slot=None):
|
def edit_metadata(db, row_list, current_row, parent=None, view_slot=None):
|
||||||
d = MetadataSingleDialog(db, parent)
|
d = MetadataSingleDialog(db, parent)
|
||||||
d.start(row_list, current_row, view_slot=view_slot)
|
d.start(row_list, current_row, view_slot=view_slot)
|
||||||
return d.changed
|
return d.changed, d.rows_to_refresh
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from PyQt4.Qt import QApplication
|
from PyQt4.Qt import QApplication
|
||||||
|
@ -420,7 +420,8 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
return candidates - res
|
return candidates - res
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_matches(self, location, query, allow_recursion=True, candidates=None):
|
def get_matches(self, location, query, candidates=None,
|
||||||
|
allow_recursion=True):
|
||||||
matches = set([])
|
matches = set([])
|
||||||
if candidates is None:
|
if candidates is None:
|
||||||
candidates = self.universal_set()
|
candidates = self.universal_set()
|
||||||
@ -434,8 +435,8 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
if isinstance(location, list):
|
if isinstance(location, list):
|
||||||
if allow_recursion:
|
if allow_recursion:
|
||||||
for loc in location:
|
for loc in location:
|
||||||
matches |= self.get_matches(loc, query, candidates,
|
matches |= self.get_matches(loc, query,
|
||||||
allow_recursion=False)
|
candidates=candidates, allow_recursion=False)
|
||||||
return matches
|
return matches
|
||||||
raise ParseException(query, len(query), 'Recursive query group detected', self)
|
raise ParseException(query, len(query), 'Recursive query group detected', self)
|
||||||
|
|
||||||
|
@ -1841,8 +1841,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
body.insert(btc,pTag)
|
body.insert(btc,pTag)
|
||||||
btc += 1
|
btc += 1
|
||||||
|
|
||||||
# <p class="letter_index">
|
|
||||||
# <p class="book_title">
|
|
||||||
divTag = Tag(soup, "div")
|
divTag = Tag(soup, "div")
|
||||||
dtc = 0
|
dtc = 0
|
||||||
current_letter = ""
|
current_letter = ""
|
||||||
@ -1870,11 +1868,12 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
divTag.insert(dtc, divRunningTag)
|
divTag.insert(dtc, divRunningTag)
|
||||||
dtc += 1
|
dtc += 1
|
||||||
divRunningTag = Tag(soup, 'div')
|
divRunningTag = Tag(soup, 'div')
|
||||||
divRunningTag['class'] = "logical_group"
|
if dtc > 0:
|
||||||
|
divRunningTag['class'] = "initial_letter"
|
||||||
drtc = 0
|
drtc = 0
|
||||||
current_letter = self.letter_or_symbol(book['title_sort'][0])
|
current_letter = self.letter_or_symbol(book['title_sort'][0])
|
||||||
pIndexTag = Tag(soup, "p")
|
pIndexTag = Tag(soup, "p")
|
||||||
pIndexTag['class'] = "letter_index"
|
pIndexTag['class'] = "author_title_letter_index"
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['name'] = "%s" % self.letter_or_symbol(book['title_sort'][0])
|
aTag['name'] = "%s" % self.letter_or_symbol(book['title_sort'][0])
|
||||||
pIndexTag.insert(0,aTag)
|
pIndexTag.insert(0,aTag)
|
||||||
@ -1982,8 +1981,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
body.insert(btc, aTag)
|
body.insert(btc, aTag)
|
||||||
btc += 1
|
btc += 1
|
||||||
|
|
||||||
# <p class="letter_index">
|
|
||||||
# <p class="author_index">
|
|
||||||
divTag = Tag(soup, "div")
|
divTag = Tag(soup, "div")
|
||||||
dtc = 0
|
dtc = 0
|
||||||
divOpeningTag = None
|
divOpeningTag = None
|
||||||
@ -2017,10 +2014,11 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
current_letter = self.letter_or_symbol(book['author_sort'][0].upper())
|
current_letter = self.letter_or_symbol(book['author_sort'][0].upper())
|
||||||
author_count = 0
|
author_count = 0
|
||||||
divOpeningTag = Tag(soup, 'div')
|
divOpeningTag = Tag(soup, 'div')
|
||||||
divOpeningTag['class'] = "logical_group"
|
if dtc > 0:
|
||||||
|
divOpeningTag['class'] = "initial_letter"
|
||||||
dotc = 0
|
dotc = 0
|
||||||
pIndexTag = Tag(soup, "p")
|
pIndexTag = Tag(soup, "p")
|
||||||
pIndexTag['class'] = "letter_index"
|
pIndexTag['class'] = "author_title_letter_index"
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['name'] = "%sauthors" % self.letter_or_symbol(current_letter)
|
aTag['name'] = "%sauthors" % self.letter_or_symbol(current_letter)
|
||||||
pIndexTag.insert(0,aTag)
|
pIndexTag.insert(0,aTag)
|
||||||
@ -2032,16 +2030,21 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
# Start a new author
|
# Start a new author
|
||||||
current_author = book['author']
|
current_author = book['author']
|
||||||
author_count += 1
|
author_count += 1
|
||||||
if author_count == 2:
|
if author_count >= 2:
|
||||||
# Add divOpeningTag to divTag, kill divOpeningTag
|
# Add divOpeningTag to divTag, kill divOpeningTag
|
||||||
divTag.insert(dtc, divOpeningTag)
|
if divOpeningTag:
|
||||||
dtc += 1
|
divTag.insert(dtc, divOpeningTag)
|
||||||
divOpeningTag = None
|
dtc += 1
|
||||||
dotc = 0
|
divOpeningTag = None
|
||||||
|
dotc = 0
|
||||||
|
|
||||||
|
# Create a divRunningTag for the next author
|
||||||
|
if author_count > 2:
|
||||||
|
divTag.insert(dtc, divRunningTag)
|
||||||
|
dtc += 1
|
||||||
|
|
||||||
# Create a divRunningTag for the rest of the authors in this letter
|
|
||||||
divRunningTag = Tag(soup, 'div')
|
divRunningTag = Tag(soup, 'div')
|
||||||
divRunningTag['class'] = "logical_group"
|
divRunningTag['class'] = "author_logical_group"
|
||||||
drtc = 0
|
drtc = 0
|
||||||
|
|
||||||
non_series_books = 0
|
non_series_books = 0
|
||||||
@ -2373,8 +2376,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
body.insert(btc,pTag)
|
body.insert(btc,pTag)
|
||||||
btc += 1
|
btc += 1
|
||||||
|
|
||||||
# <p class="letter_index">
|
|
||||||
# <p class="author_index">
|
|
||||||
divTag = Tag(soup, "div")
|
divTag = Tag(soup, "div")
|
||||||
dtc = 0
|
dtc = 0
|
||||||
|
|
||||||
@ -2558,8 +2559,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
body.insert(btc, aTag)
|
body.insert(btc, aTag)
|
||||||
btc += 1
|
btc += 1
|
||||||
|
|
||||||
# <p class="letter_index">
|
|
||||||
# <p class="author_index">
|
|
||||||
divTag = Tag(soup, "div")
|
divTag = Tag(soup, "div")
|
||||||
dtc = 0
|
dtc = 0
|
||||||
|
|
||||||
@ -2661,8 +2660,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
body.insert(btc, aTag)
|
body.insert(btc, aTag)
|
||||||
btc += 1
|
btc += 1
|
||||||
|
|
||||||
# <p class="letter_index">
|
|
||||||
# <p class="author_index">
|
|
||||||
divTag = Tag(soup, "div")
|
divTag = Tag(soup, "div")
|
||||||
dtc = 0
|
dtc = 0
|
||||||
current_letter = ""
|
current_letter = ""
|
||||||
@ -2677,7 +2674,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
# Start a new letter with Index letter
|
# Start a new letter with Index letter
|
||||||
current_letter = self.letter_or_symbol(sort_title[0].upper())
|
current_letter = self.letter_or_symbol(sort_title[0].upper())
|
||||||
pIndexTag = Tag(soup, "p")
|
pIndexTag = Tag(soup, "p")
|
||||||
pIndexTag['class'] = "letter_index"
|
pIndexTag['class'] = "series_letter_index"
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['name'] = "%s_series" % self.letter_or_symbol(current_letter)
|
aTag['name'] = "%s_series" % self.letter_or_symbol(current_letter)
|
||||||
pIndexTag.insert(0,aTag)
|
pIndexTag.insert(0,aTag)
|
||||||
|
@ -440,22 +440,24 @@ class CustomColumns(object):
|
|||||||
self.dirtied(ids, commit=False)
|
self.dirtied(ids, commit=False)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
def set_custom(self, id, val, label=None, num=None,
|
def set_custom(self, id, val, label=None, num=None, append=False,
|
||||||
append=False, notify=True, extra=None, commit=True):
|
notify=True, extra=None, commit=True, allow_case_change=False):
|
||||||
self._set_custom(id, val, label=label, num=num, append=append,
|
rv = self._set_custom(id, val, label=label, num=num, append=append,
|
||||||
notify=notify, extra=extra)
|
notify=notify, extra=extra,
|
||||||
|
allow_case_change=allow_case_change)
|
||||||
self.dirtied([id], commit=False)
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
return rv
|
||||||
|
|
||||||
def _set_custom(self, id_, val, label=None, num=None,
|
def _set_custom(self, id_, val, label=None, num=None, append=False,
|
||||||
append=False, notify=True, extra=None):
|
notify=True, extra=None, allow_case_change=False):
|
||||||
if label is not None:
|
if label is not None:
|
||||||
data = self.custom_column_label_map[label]
|
data = self.custom_column_label_map[label]
|
||||||
if num is not None:
|
if num is not None:
|
||||||
data = self.custom_column_num_map[num]
|
data = self.custom_column_num_map[num]
|
||||||
if data['datatype'] == 'composite':
|
if data['datatype'] == 'composite':
|
||||||
return None
|
return set([])
|
||||||
if not data['editable']:
|
if not data['editable']:
|
||||||
raise ValueError('Column %r is not editable'%data['label'])
|
raise ValueError('Column %r is not editable'%data['label'])
|
||||||
table, lt = self.custom_table_names(data['num'])
|
table, lt = self.custom_table_names(data['num'])
|
||||||
@ -466,10 +468,11 @@ class CustomColumns(object):
|
|||||||
if data['datatype'] == 'series' and extra is None:
|
if data['datatype'] == 'series' and extra is None:
|
||||||
(val, extra) = self._get_series_values(val)
|
(val, extra) = self._get_series_values(val)
|
||||||
|
|
||||||
|
books_to_refresh = set([])
|
||||||
if data['normalized']:
|
if data['normalized']:
|
||||||
if data['datatype'] == 'enumeration' and (
|
if data['datatype'] == 'enumeration' and (
|
||||||
val and val not in data['display']['enum_values']):
|
val and val not in data['display']['enum_values']):
|
||||||
return None
|
return books_to_refresh
|
||||||
if not append or not data['is_multiple']:
|
if not append or not data['is_multiple']:
|
||||||
self.conn.execute('DELETE FROM %s WHERE book=?'%lt, (id_,))
|
self.conn.execute('DELETE FROM %s WHERE book=?'%lt, (id_,))
|
||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
@ -483,6 +486,7 @@ class CustomColumns(object):
|
|||||||
for x in set(set_val) - set(existing):
|
for x in set(set_val) - set(existing):
|
||||||
if x is None:
|
if x is None:
|
||||||
continue
|
continue
|
||||||
|
case_change = False
|
||||||
existing = list(self.all_custom(num=data['num']))
|
existing = list(self.all_custom(num=data['num']))
|
||||||
lx = [t.lower() if hasattr(t, 'lower') else t for t in existing]
|
lx = [t.lower() if hasattr(t, 'lower') else t for t in existing]
|
||||||
try:
|
try:
|
||||||
@ -492,13 +496,14 @@ class CustomColumns(object):
|
|||||||
if idx > -1:
|
if idx > -1:
|
||||||
ex = existing[idx]
|
ex = existing[idx]
|
||||||
xid = self.conn.get(
|
xid = self.conn.get(
|
||||||
'SELECT id FROM %s WHERE value=?'%table, (ex,), all=False)
|
'SELECT id FROM %s WHERE value=?'%table, (ex,), all=False)
|
||||||
if ex != x:
|
if allow_case_change and ex != x:
|
||||||
|
case_change = True
|
||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
'UPDATE %s SET value=? WHERE id=?'%table, (x, xid))
|
'UPDATE %s SET value=? WHERE id=?'%table, (x, xid))
|
||||||
else:
|
else:
|
||||||
xid = self.conn.execute(
|
xid = self.conn.execute(
|
||||||
'INSERT INTO %s(value) VALUES(?)'%table, (x,)).lastrowid
|
'INSERT INTO %s(value) VALUES(?)'%table, (x,)).lastrowid
|
||||||
if not self.conn.get(
|
if not self.conn.get(
|
||||||
'SELECT book FROM %s WHERE book=? AND value=?'%lt,
|
'SELECT book FROM %s WHERE book=? AND value=?'%lt,
|
||||||
(id_, xid), all=False):
|
(id_, xid), all=False):
|
||||||
@ -512,6 +517,10 @@ class CustomColumns(object):
|
|||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
'''INSERT INTO %s(book, value)
|
'''INSERT INTO %s(book, value)
|
||||||
VALUES (?,?)'''%lt, (id_, xid))
|
VALUES (?,?)'''%lt, (id_, xid))
|
||||||
|
if case_change:
|
||||||
|
bks = self.conn.get('SELECT book FROM %s WHERE value=?'%lt,
|
||||||
|
(xid,))
|
||||||
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
nval = self.conn.get(
|
nval = self.conn.get(
|
||||||
'SELECT custom_%s FROM meta2 WHERE id=?'%data['num'],
|
'SELECT custom_%s FROM meta2 WHERE id=?'%data['num'],
|
||||||
(id_,), all=False)
|
(id_,), all=False)
|
||||||
@ -530,7 +539,7 @@ class CustomColumns(object):
|
|||||||
row_is_id=True)
|
row_is_id=True)
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id_])
|
self.notify('metadata', [id_])
|
||||||
return nval
|
return books_to_refresh
|
||||||
|
|
||||||
def clean_custom(self):
|
def clean_custom(self):
|
||||||
st = ('DELETE FROM {table} WHERE (SELECT COUNT(id) FROM {lt} WHERE'
|
st = ('DELETE FROM {table} WHERE (SELECT COUNT(id) FROM {lt} WHERE'
|
||||||
|
@ -1479,29 +1479,34 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
return float(tweaks['series_index_auto_increment'])
|
return float(tweaks['series_index_auto_increment'])
|
||||||
return 1.0
|
return 1.0
|
||||||
|
|
||||||
def set(self, row, column, val):
|
def set(self, row, column, val, allow_case_change=False):
|
||||||
'''
|
'''
|
||||||
Convenience method for setting the title, authors, publisher or rating
|
Convenience method for setting the title, authors, publisher or rating
|
||||||
'''
|
'''
|
||||||
id = self.data[row][0]
|
id = self.data[row][0]
|
||||||
col = {'title':1, 'authors':2, 'publisher':3, 'rating':4, 'tags':7}[column]
|
col = {'title':1, 'authors':2, 'publisher':3, 'rating':4, 'tags':7}[column]
|
||||||
|
|
||||||
|
books_to_refresh = set()
|
||||||
self.data.set(row, col, val)
|
self.data.set(row, col, val)
|
||||||
if column == 'authors':
|
if column == 'authors':
|
||||||
val = string_to_authors(val)
|
val = string_to_authors(val)
|
||||||
self.set_authors(id, val, notify=False)
|
books_to_refresh |= self.set_authors(id, val, notify=False,
|
||||||
|
allow_case_change=allow_case_change)
|
||||||
elif column == 'title':
|
elif column == 'title':
|
||||||
self.set_title(id, val, notify=False)
|
self.set_title(id, val, notify=False)
|
||||||
elif column == 'publisher':
|
elif column == 'publisher':
|
||||||
self.set_publisher(id, val, notify=False)
|
books_to_refresh |= self.set_publisher(id, val, notify=False,
|
||||||
|
allow_case_change=allow_case_change)
|
||||||
elif column == 'rating':
|
elif column == 'rating':
|
||||||
self.set_rating(id, val, notify=False)
|
self.set_rating(id, val, notify=False)
|
||||||
elif column == 'tags':
|
elif column == 'tags':
|
||||||
self.set_tags(id, [x.strip() for x in val.split(',') if x.strip()],
|
books_to_refresh |= \
|
||||||
append=False, notify=False)
|
self.set_tags(id, [x.strip() for x in val.split(',') if x.strip()],
|
||||||
|
append=False, notify=False, allow_case_change=allow_case_change)
|
||||||
self.data.refresh_ids(self, [id])
|
self.data.refresh_ids(self, [id])
|
||||||
self.set_path(id, True)
|
self.set_path(id, True)
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
return books_to_refresh
|
||||||
|
|
||||||
def set_metadata(self, id, mi, ignore_errors=False,
|
def set_metadata(self, id, mi, ignore_errors=False,
|
||||||
set_title=True, set_authors=True, commit=True):
|
set_title=True, set_authors=True, commit=True):
|
||||||
@ -1627,54 +1632,73 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
result.append(r)
|
result.append(r)
|
||||||
return ' & '.join(result).replace('|', ',')
|
return ' & '.join(result).replace('|', ',')
|
||||||
|
|
||||||
def _set_authors(self, id, authors):
|
def _set_authors(self, id, authors, allow_case_change=False):
|
||||||
if not authors:
|
if not authors:
|
||||||
authors = [_('Unknown')]
|
authors = [_('Unknown')]
|
||||||
self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,))
|
self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,))
|
||||||
|
books_to_refresh = set([])
|
||||||
|
final_authors = []
|
||||||
for a in authors:
|
for a in authors:
|
||||||
|
case_change = False
|
||||||
if not a:
|
if not a:
|
||||||
continue
|
continue
|
||||||
a = a.strip().replace(',', '|')
|
a = a.strip().replace(',', '|')
|
||||||
if not isinstance(a, unicode):
|
if not isinstance(a, unicode):
|
||||||
a = a.decode(preferred_encoding, 'replace')
|
a = a.decode(preferred_encoding, 'replace')
|
||||||
author = self.conn.get('SELECT id from authors WHERE name=?', (a,), all=False)
|
aus = self.conn.get('SELECT id, name FROM authors WHERE name=?', (a,))
|
||||||
if author:
|
if aus:
|
||||||
aid = author
|
aid, name = aus[0]
|
||||||
# Handle change of case
|
# Handle change of case
|
||||||
self.conn.execute('UPDATE authors SET name=? WHERE id=?', (a, aid))
|
if name != a:
|
||||||
|
if allow_case_change:
|
||||||
|
self.conn.execute('''UPDATE authors
|
||||||
|
SET name=? WHERE id=?''', (a, aid))
|
||||||
|
case_change = True
|
||||||
|
else:
|
||||||
|
a = name
|
||||||
else:
|
else:
|
||||||
aid = self.conn.execute('INSERT INTO authors(name) VALUES (?)', (a,)).lastrowid
|
aid = self.conn.execute('''INSERT INTO authors(name)
|
||||||
|
VALUES (?)''', (a,)).lastrowid
|
||||||
|
final_authors.append(a.replace('|', ','))
|
||||||
try:
|
try:
|
||||||
self.conn.execute('INSERT INTO books_authors_link(book, author) VALUES (?,?)',
|
self.conn.execute('''INSERT INTO books_authors_link(book, author)
|
||||||
(id, aid))
|
VALUES (?,?)''', (id, aid))
|
||||||
except IntegrityError: # Sometimes books specify the same author twice in their metadata
|
except IntegrityError: # Sometimes books specify the same author twice in their metadata
|
||||||
pass
|
pass
|
||||||
|
if case_change:
|
||||||
|
bks = self.conn.get('''SELECT book FROM books_authors_link
|
||||||
|
WHERE author=?''', (aid,))
|
||||||
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
ss = self.author_sort_from_book(id, index_is_id=True)
|
ss = self.author_sort_from_book(id, index_is_id=True)
|
||||||
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
||||||
(ss, id))
|
(ss, id))
|
||||||
self.data.set(id, self.FIELD_MAP['authors'],
|
self.data.set(id, self.FIELD_MAP['authors'],
|
||||||
','.join([a.replace(',', '|') for a in authors]),
|
','.join([a.replace(',', '|') for a in final_authors]),
|
||||||
row_is_id=True)
|
row_is_id=True)
|
||||||
self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True)
|
||||||
aum = self.authors_with_sort_strings(id, index_is_id=True)
|
aum = self.authors_with_sort_strings(id, index_is_id=True)
|
||||||
self.data.set(id, self.FIELD_MAP['au_map'],
|
self.data.set(id, self.FIELD_MAP['au_map'],
|
||||||
':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]),
|
':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]),
|
||||||
row_is_id=True)
|
row_is_id=True)
|
||||||
|
return books_to_refresh
|
||||||
|
|
||||||
def set_authors(self, id, authors, notify=True, commit=True):
|
def set_authors(self, id, authors, notify=True, commit=True,
|
||||||
|
allow_case_change=False):
|
||||||
'''
|
'''
|
||||||
Note that even if commit is False, the db will still be committed to
|
Note that even if commit is False, the db will still be committed to
|
||||||
because this causes the location of files to change
|
because this causes the location of files to change
|
||||||
|
|
||||||
:param authors: A list of authors.
|
:param authors: A list of authors.
|
||||||
'''
|
'''
|
||||||
self._set_authors(id, authors)
|
books_to_refresh = self._set_authors(id, authors,
|
||||||
|
allow_case_change=allow_case_change)
|
||||||
self.dirtied([id], commit=False)
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.set_path(id, index_is_id=True)
|
self.set_path(id, index_is_id=True)
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
return books_to_refresh
|
||||||
|
|
||||||
def set_title_sort(self, id, title_sort_, notify=True, commit=True):
|
def set_title_sort(self, id, title_sort_, notify=True, commit=True):
|
||||||
if not title_sort_:
|
if not title_sort_:
|
||||||
@ -1697,10 +1721,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
title = title.decode(preferred_encoding, 'replace')
|
title = title.decode(preferred_encoding, 'replace')
|
||||||
self.conn.execute('UPDATE books SET title=? WHERE id=?', (title, id))
|
self.conn.execute('UPDATE books SET title=? WHERE id=?', (title, id))
|
||||||
self.data.set(id, self.FIELD_MAP['title'], title, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['title'], title, row_is_id=True)
|
||||||
if tweaks['title_series_sorting'] == 'library_order':
|
ts = self.conn.get('SELECT sort FROM books WHERE id=?', (id,),
|
||||||
self.data.set(id, self.FIELD_MAP['sort'], title_sort(title), row_is_id=True)
|
all=False)
|
||||||
else:
|
if ts:
|
||||||
self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['sort'], ts, row_is_id=True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_title(self, id, title, notify=True, commit=True):
|
def set_title(self, id, title, notify=True, commit=True):
|
||||||
@ -1738,24 +1762,44 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
|
||||||
|
|
||||||
def set_publisher(self, id, publisher, notify=True, commit=True):
|
def set_publisher(self, id, publisher, notify=True, commit=True,
|
||||||
|
allow_case_change=False):
|
||||||
self.conn.execute('DELETE FROM books_publishers_link WHERE book=?',(id,))
|
self.conn.execute('DELETE FROM books_publishers_link WHERE book=?',(id,))
|
||||||
self.conn.execute('DELETE FROM publishers WHERE (SELECT COUNT(id) FROM books_publishers_link WHERE publisher=publishers.id) < 1')
|
self.conn.execute('''DELETE FROM publishers WHERE (SELECT COUNT(id)
|
||||||
|
FROM books_publishers_link
|
||||||
|
WHERE publisher=publishers.id) < 1''')
|
||||||
|
books_to_refresh = set()
|
||||||
if publisher:
|
if publisher:
|
||||||
|
case_change = False
|
||||||
if not isinstance(publisher, unicode):
|
if not isinstance(publisher, unicode):
|
||||||
publisher = publisher.decode(preferred_encoding, 'replace')
|
publisher = publisher.decode(preferred_encoding, 'replace')
|
||||||
pub = self.conn.get('SELECT id from publishers WHERE name=?', (publisher,), all=False)
|
pubx = self.conn.get('''SELECT id,name from publishers
|
||||||
if pub:
|
WHERE name=?''', (publisher,))
|
||||||
aid = pub
|
if pubx:
|
||||||
|
aid, cur_name = pubx[0]
|
||||||
|
if publisher != cur_name:
|
||||||
|
if allow_case_change:
|
||||||
|
self.conn.execute('''UPDATE publishers SET name=?
|
||||||
|
WHERE id=?''', (publisher, aid))
|
||||||
|
case_change = True
|
||||||
|
else:
|
||||||
|
publisher = cur_name
|
||||||
else:
|
else:
|
||||||
aid = self.conn.execute('INSERT INTO publishers(name) VALUES (?)', (publisher,)).lastrowid
|
aid = self.conn.execute('''INSERT INTO publishers(name)
|
||||||
self.conn.execute('INSERT INTO books_publishers_link(book, publisher) VALUES (?,?)', (id, aid))
|
VALUES (?)''', (publisher,)).lastrowid
|
||||||
self.dirtied([id], commit=False)
|
self.conn.execute('''INSERT INTO books_publishers_link(book, publisher)
|
||||||
if commit:
|
VALUES (?,?)''', (id, aid))
|
||||||
self.conn.commit()
|
if case_change:
|
||||||
self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True)
|
bks = self.conn.get('''SELECT book FROM books_publishers_link
|
||||||
if notify:
|
WHERE publisher=?''', (aid,))
|
||||||
self.notify('metadata', [id])
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
|
if commit:
|
||||||
|
self.conn.commit()
|
||||||
|
self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True)
|
||||||
|
if notify:
|
||||||
|
self.notify('metadata', [id])
|
||||||
|
return books_to_refresh
|
||||||
|
|
||||||
def set_uuid(self, id, uuid, notify=True, commit=True):
|
def set_uuid(self, id, uuid, notify=True, commit=True):
|
||||||
if uuid:
|
if uuid:
|
||||||
@ -2119,17 +2163,21 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
def commit(self):
|
def commit(self):
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
def set_tags(self, id, tags, append=False, notify=True, commit=True):
|
def set_tags(self, id, tags, append=False, notify=True, commit=True,
|
||||||
|
allow_case_change=False):
|
||||||
'''
|
'''
|
||||||
@param tags: list of strings
|
@param tags: list of strings
|
||||||
@param append: If True existing tags are not removed
|
@param append: If True existing tags are not removed
|
||||||
'''
|
'''
|
||||||
if not append:
|
if not append:
|
||||||
self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,))
|
self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,))
|
||||||
self.conn.execute('DELETE FROM tags WHERE (SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) < 1')
|
self.conn.execute('''DELETE FROM tags WHERE (SELECT COUNT(id)
|
||||||
|
FROM books_tags_link WHERE tag=tags.id) < 1''')
|
||||||
otags = self.get_tags(id)
|
otags = self.get_tags(id)
|
||||||
tags = self.cleanup_tags(tags)
|
tags = self.cleanup_tags(tags)
|
||||||
|
books_to_refresh = set([])
|
||||||
for tag in (set(tags)-otags):
|
for tag in (set(tags)-otags):
|
||||||
|
case_changed = False
|
||||||
tag = tag.strip()
|
tag = tag.strip()
|
||||||
if not tag:
|
if not tag:
|
||||||
continue
|
continue
|
||||||
@ -2144,15 +2192,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if idx > -1:
|
if idx > -1:
|
||||||
etag = existing_tags[idx]
|
etag = existing_tags[idx]
|
||||||
tid = self.conn.get('SELECT id FROM tags WHERE name=?', (etag,), all=False)
|
tid = self.conn.get('SELECT id FROM tags WHERE name=?', (etag,), all=False)
|
||||||
if etag != tag:
|
if allow_case_change and etag != tag:
|
||||||
self.conn.execute('UPDATE tags SET name=? WHERE id=?', (tag, tid))
|
self.conn.execute('UPDATE tags SET name=? WHERE id=?', (tag, tid))
|
||||||
|
case_changed = True
|
||||||
else:
|
else:
|
||||||
tid = self.conn.execute('INSERT INTO tags(name) VALUES(?)', (tag,)).lastrowid
|
tid = self.conn.execute('INSERT INTO tags(name) VALUES(?)', (tag,)).lastrowid
|
||||||
|
|
||||||
if not self.conn.get('SELECT book FROM books_tags_link WHERE book=? AND tag=?',
|
if not self.conn.get('''SELECT book FROM books_tags_link
|
||||||
(id, tid), all=False):
|
WHERE book=? AND tag=?''', (id, tid), all=False):
|
||||||
self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)',
|
self.conn.execute('''INSERT INTO books_tags_link(book, tag)
|
||||||
(id, tid))
|
VALUES (?,?)''', (id, tid))
|
||||||
|
if case_changed:
|
||||||
|
bks = self.conn.get('SELECT book FROM books_tags_link WHERE tag=?',
|
||||||
|
(tid,))
|
||||||
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
self.dirtied([id], commit=False)
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
@ -2160,12 +2213,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.data.set(id, self.FIELD_MAP['tags'], tags, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['tags'], tags, row_is_id=True)
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
return books_to_refresh
|
||||||
|
|
||||||
def unapply_tags(self, book_id, tags, notify=True):
|
def unapply_tags(self, book_id, tags, notify=True):
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
id = self.conn.get('SELECT id FROM tags WHERE name=?', (tag,), all=False)
|
id = self.conn.get('SELECT id FROM tags WHERE name=?', (tag,), all=False)
|
||||||
if id:
|
if id:
|
||||||
self.conn.execute('DELETE FROM books_tags_link WHERE tag=? AND book=?', (id, book_id))
|
self.conn.execute('''DELETE FROM books_tags_link
|
||||||
|
WHERE tag=? AND book=?''', (id, book_id))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.refresh_ids(self, [book_id])
|
self.data.refresh_ids(self, [book_id])
|
||||||
if notify:
|
if notify:
|
||||||
@ -2209,31 +2264,44 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
pass
|
pass
|
||||||
return (val, None)
|
return (val, None)
|
||||||
|
|
||||||
def set_series(self, id, series, notify=True, commit=True):
|
def set_series(self, id, series, notify=True, commit=True, allow_case_change=True):
|
||||||
self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,))
|
self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,))
|
||||||
self.conn.execute('''DELETE FROM series
|
self.conn.execute('''DELETE FROM series
|
||||||
WHERE (SELECT COUNT(id) FROM books_series_link
|
WHERE (SELECT COUNT(id) FROM books_series_link
|
||||||
WHERE series=series.id) < 1''')
|
WHERE series=series.id) < 1''')
|
||||||
(series, idx) = self._get_series_values(series)
|
(series, idx) = self._get_series_values(series)
|
||||||
|
books_to_refresh = set([])
|
||||||
if series:
|
if series:
|
||||||
|
case_change = False
|
||||||
if not isinstance(series, unicode):
|
if not isinstance(series, unicode):
|
||||||
series = series.decode(preferred_encoding, 'replace')
|
series = series.decode(preferred_encoding, 'replace')
|
||||||
series = series.strip()
|
series = series.strip()
|
||||||
series = u' '.join(series.split())
|
series = u' '.join(series.split())
|
||||||
s = self.conn.get('SELECT id from series WHERE name=?', (series,), all=False)
|
sx = self.conn.get('SELECT id,name from series WHERE name=?', (series,))
|
||||||
if s:
|
if sx:
|
||||||
aid = s
|
aid, cur_name = sx[0]
|
||||||
|
if cur_name != series:
|
||||||
|
if allow_case_change:
|
||||||
|
self.conn.execute('UPDATE series SET name=? WHERE id=?', (series, aid))
|
||||||
|
case_change = True
|
||||||
|
else:
|
||||||
|
series = cur_name
|
||||||
else:
|
else:
|
||||||
aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid
|
aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid
|
||||||
self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid))
|
self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid))
|
||||||
if idx:
|
if idx:
|
||||||
self.set_series_index(id, idx, notify=notify, commit=commit)
|
self.set_series_index(id, idx, notify=notify, commit=commit)
|
||||||
|
if case_change:
|
||||||
|
bks = self.conn.get('SELECT book FROM books_series_link WHERE series=?',
|
||||||
|
(aid,))
|
||||||
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
self.dirtied([id], commit=False)
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['series'], series, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['series'], series, row_is_id=True)
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
return books_to_refresh
|
||||||
|
|
||||||
def set_series_index(self, id, idx, notify=True, commit=True):
|
def set_series_index(self, id, idx, notify=True, commit=True):
|
||||||
if idx is None:
|
if idx is None:
|
||||||
|
@ -316,9 +316,19 @@ remove all non-breaking-space entities, or may include false positive matches re
|
|||||||
|
|
||||||
:guilabel:`Replace scene breaks`
|
:guilabel:`Replace scene breaks`
|
||||||
If this option is configured then |app| will replace scene break markers it finds with the replacement text specified by the
|
If this option is configured then |app| will replace scene break markers it finds with the replacement text specified by the
|
||||||
user. In general you should avoid using html tags, |app| will discard any tags and use pre-defined markup. <hr />
|
user. Please note that some ornamental characters may not be supported across all reading devices.
|
||||||
tags, i.e. horizontal rules, are an exception. These can optionally be specified with styles, if you choose to add your own
|
|
||||||
style be sure to include the 'width' setting, otherwise the style information will be discarded.
|
In general you should avoid using html tags, |app| will discard any tags and use pre-defined markup. <hr />
|
||||||
|
tags, i.e. horizontal rules, and <img> tags are exceptions. Horizontal rules can optionally be specified with styles, if you
|
||||||
|
choose to add your own style be sure to include the 'width' setting, otherwise the style information will be discarded. Image
|
||||||
|
tags can used, but |app| does not provide the ability to add the image during conversion, this must be done after the fact using
|
||||||
|
the 'Tweak Epub' feature, or Sigil.
|
||||||
|
|
||||||
|
Example image tag (place the image within an 'Images' folder inside the epub after conversion):
|
||||||
|
<img style="width:10%" src="../Images/scenebreak.png" />
|
||||||
|
|
||||||
|
Example horizontal rule with styles:
|
||||||
|
<hr style="width:20%;padding-top: 1px;border-top: 2px ridge black;border-bottom: 2px groove black;"/>
|
||||||
|
|
||||||
:guilabel:`Remove unnecessary hyphens`
|
:guilabel:`Remove unnecessary hyphens`
|
||||||
|app| will analyze all hyphenated content in the document when this option is enabled. The document itself is used
|
|app| will analyze all hyphenated content in the document when this option is enabled. The document itself is used
|
||||||
|
@ -186,7 +186,7 @@ class BuiltinTemplate(BuiltinFormatterFunction):
|
|||||||
|
|
||||||
def evaluate(self, formatter, kwargs, mi, locals, template):
|
def evaluate(self, formatter, kwargs, mi, locals, template):
|
||||||
template = template.replace('[[', '{').replace(']]', '}')
|
template = template.replace('[[', '{').replace(']]', '}')
|
||||||
return formatter.safe_format(template, kwargs, 'TEMPLATE', mi)
|
return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)
|
||||||
|
|
||||||
class BuiltinEval(BuiltinFormatterFunction):
|
class BuiltinEval(BuiltinFormatterFunction):
|
||||||
name = 'eval'
|
name = 'eval'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user