mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
KG/chaley updates
This commit is contained in:
commit
16fc28a1ed
23
resources/recipes/ajiajin.recipe
Normal file
23
resources/recipes/ajiajin.recipe
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Hiroshi Miura <miurahr@linux.com>'
|
||||||
|
'''
|
||||||
|
ajiajin.com/blog
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AjiajinBlog(BasicNewsRecipe):
|
||||||
|
title = u'Ajiajin blog'
|
||||||
|
__author__ = 'Hiroshi Miura'
|
||||||
|
oldest_article = 5
|
||||||
|
publication_type = 'blog'
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
description = 'The next generation internet trends in Japan and Asia'
|
||||||
|
publisher = ''
|
||||||
|
category = 'internet, asia, japan'
|
||||||
|
language = 'en'
|
||||||
|
encoding = 'utf-8'
|
||||||
|
|
||||||
|
feeds = [(u'blog', u'http://feeds.feedburner.com/Asiajin')]
|
||||||
|
|
||||||
|
|
37
resources/recipes/chouchoublog.recipe
Normal file
37
resources/recipes/chouchoublog.recipe
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Hiroshi Miura <miurahr@linux.com>'
|
||||||
|
'''
|
||||||
|
http://ameblo.jp/
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class SakuraBlog(BasicNewsRecipe):
|
||||||
|
title = u'chou chou blog'
|
||||||
|
__author__ = 'Hiroshi Miura'
|
||||||
|
oldest_article = 4
|
||||||
|
publication_type = 'blog'
|
||||||
|
max_articles_per_feed = 20
|
||||||
|
description = 'Japanese popular dog blog'
|
||||||
|
publisher = ''
|
||||||
|
category = 'dog, pet, japan'
|
||||||
|
language = 'ja'
|
||||||
|
encoding = 'utf-8'
|
||||||
|
use_embedded_content = True
|
||||||
|
|
||||||
|
feeds = [(u'blog', u'http://feedblog.ameba.jp/rss/ameblo/chouchou1218/rss20.xml')]
|
||||||
|
|
||||||
|
def parse_feeds(self):
|
||||||
|
feeds = BasicNewsRecipe.parse_feeds(self)
|
||||||
|
for curfeed in feeds:
|
||||||
|
delList = []
|
||||||
|
for a,curarticle in enumerate(curfeed.articles):
|
||||||
|
if re.search(r'rssad.jp', curarticle.url):
|
||||||
|
delList.append(curarticle)
|
||||||
|
if len(delList)>0:
|
||||||
|
for d in delList:
|
||||||
|
index = curfeed.articles.index(d)
|
||||||
|
curfeed.articles[index:index+1] = []
|
||||||
|
return feeds
|
||||||
|
|
@ -3,15 +3,16 @@ __copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
|||||||
'''
|
'''
|
||||||
http://www.dilbert.com
|
http://www.dilbert.com
|
||||||
'''
|
'''
|
||||||
import re
|
|
||||||
|
|
||||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
import re
|
||||||
|
|
||||||
class DosisDiarias(BasicNewsRecipe):
|
class DilbertBig(BasicNewsRecipe):
|
||||||
title = 'Dilbert'
|
title = 'Dilbert'
|
||||||
__author__ = 'Darko Miletic'
|
__author__ = 'Darko Miletic and Starson17'
|
||||||
description = 'Dilbert'
|
description = 'Dilbert'
|
||||||
oldest_article = 5
|
reverse_article_order = True
|
||||||
|
oldest_article = 15
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
use_embedded_content = True
|
use_embedded_content = True
|
||||||
@ -29,20 +30,23 @@ class DosisDiarias(BasicNewsRecipe):
|
|||||||
|
|
||||||
feeds = [(u'Dilbert', u'http://feeds.dilbert.com/DilbertDailyStrip' )]
|
feeds = [(u'Dilbert', u'http://feeds.dilbert.com/DilbertDailyStrip' )]
|
||||||
|
|
||||||
preprocess_regexps = [
|
|
||||||
(re.compile('strip\..*\.gif', re.DOTALL|re.IGNORECASE),
|
|
||||||
lambda match: 'strip.zoom.gif')
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_article_url(self, article):
|
def get_article_url(self, article):
|
||||||
return article.get('feedburner_origlink', None)
|
return article.get('feedburner_origlink', None)
|
||||||
|
|
||||||
|
preprocess_regexps = [
|
||||||
|
(re.compile('strip\..*\.gif', re.DOTALL|re.IGNORECASE), lambda match: 'strip.zoom.gif')
|
||||||
|
]
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
for tag in soup.findAll(name='a'):
|
for tag in soup.findAll(name='a'):
|
||||||
if tag['href'].find('http://feedads') >= 0:
|
if tag['href'].find('http://feedads') >= 0:
|
||||||
tag.extract()
|
tag.extract()
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
|
extra_css = '''
|
||||||
|
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||||
|
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||||
|
img {max-width:100%; min-width:100%;}
|
||||||
|
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||||
|
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||||
|
'''
|
||||||
|
31
resources/recipes/kahokushinpo.recipe
Normal file
31
resources/recipes/kahokushinpo.recipe
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Hiroshi Miura <miurahr@linux.com>'
|
||||||
|
'''
|
||||||
|
www.kahoku.co.jp
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
|
||||||
|
class KahokuShinpoNews(BasicNewsRecipe):
|
||||||
|
title = u'\u6cb3\u5317\u65b0\u5831'
|
||||||
|
__author__ = 'Hiroshi Miura'
|
||||||
|
oldest_article = 2
|
||||||
|
max_articles_per_feed = 20
|
||||||
|
description = 'Tohoku regional news paper in Japan'
|
||||||
|
publisher = 'Kahoku Shinpo Sha'
|
||||||
|
category = 'news, japan'
|
||||||
|
language = 'ja'
|
||||||
|
encoding = 'Shift_JIS'
|
||||||
|
no_stylesheets = True
|
||||||
|
|
||||||
|
feeds = [(u'news', u'http://www.kahoku.co.jp/rss/index_thk.xml')]
|
||||||
|
|
||||||
|
keep_only_tags = [ dict(id="page_title"),
|
||||||
|
dict(id="news_detail"),
|
||||||
|
dict(id="bt_title"),
|
||||||
|
{'class':"photoLeft"},
|
||||||
|
dict(id="bt_body")
|
||||||
|
]
|
||||||
|
remove_tags = [ {'class':"button"}]
|
||||||
|
|
38
resources/recipes/nationalgeographic.recipe
Normal file
38
resources/recipes/nationalgeographic.recipe
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Hiroshi Miura <miurahr@linux.com>'
|
||||||
|
'''
|
||||||
|
nationalgeographic.com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
import re
|
||||||
|
|
||||||
|
class NationalGeographicNews(BasicNewsRecipe):
|
||||||
|
title = u'National Geographic News'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
remove_javascript = True
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
|
||||||
|
feeds = [(u'news', u'http://feeds.nationalgeographic.com/ng/News/News_Main')]
|
||||||
|
|
||||||
|
remove_tags_before = dict(id='page_head')
|
||||||
|
remove_tags_after = [dict(id='social_buttons'),{'class':'aside'}]
|
||||||
|
remove_tags = [
|
||||||
|
{'class':'hidden'}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
def parse_feeds(self):
|
||||||
|
feeds = BasicNewsRecipe.parse_feeds(self)
|
||||||
|
for curfeed in feeds:
|
||||||
|
delList = []
|
||||||
|
for a,curarticle in enumerate(curfeed.articles):
|
||||||
|
if re.search(r'ads\.pheedo\.com', curarticle.url):
|
||||||
|
delList.append(curarticle)
|
||||||
|
if len(delList)>0:
|
||||||
|
for d in delList:
|
||||||
|
index = curfeed.articles.index(d)
|
||||||
|
curfeed.articles[index:index+1] = []
|
||||||
|
return feeds
|
20
resources/recipes/nationalgeographicjp.recipe
Normal file
20
resources/recipes/nationalgeographicjp.recipe
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Hiroshi Miura <miurahr@linux.com>'
|
||||||
|
'''
|
||||||
|
nationalgeographic.co.jp
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
import re
|
||||||
|
|
||||||
|
class NationalGeoJp(BasicNewsRecipe):
|
||||||
|
title = u'\u30ca\u30b7\u30e7\u30ca\u30eb\u30fb\u30b8\u30aa\u30b0\u30e9\u30d5\u30a3\u30c3\u30af\u30cb\u30e5\u30fc\u30b9'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
|
||||||
|
feeds = [(u'news', u'http://www.nationalgeographic.co.jp/news/rss.php')]
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return re.sub(r'news_article.php','news_printer_friendly.php', url)
|
||||||
|
|
@ -10,8 +10,8 @@ import mechanize
|
|||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
|
|
||||||
|
|
||||||
class NikkeiNet_sub_life(BasicNewsRecipe):
|
class NikkeiNet_sub_shakai(BasicNewsRecipe):
|
||||||
title = u'\u65e5\u7d4c\u65b0\u805e\u96fb\u5b50\u7248(\u751f\u6d3b)'
|
title = u'\u65e5\u7d4c\u65b0\u805e\u96fb\u5b50\u7248(Social)'
|
||||||
__author__ = 'Hiroshi Miura'
|
__author__ = 'Hiroshi Miura'
|
||||||
description = 'News and current market affairs from Japan'
|
description = 'News and current market affairs from Japan'
|
||||||
cover_url = 'http://parts.nikkei.com/parts/ds/images/common/logo_r1.svg'
|
cover_url = 'http://parts.nikkei.com/parts/ds/images/common/logo_r1.svg'
|
||||||
|
58
resources/recipes/paperli_topic.recipe
Normal file
58
resources/recipes/paperli_topic.recipe
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Hiroshi Miura <miurahr@linux.com>'
|
||||||
|
'''
|
||||||
|
paperli
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from calibre import strftime
|
||||||
|
|
||||||
|
class paperli_topics(BasicNewsRecipe):
|
||||||
|
# Customize this recipe and change paperli_tag and title below to
|
||||||
|
# download news on your favorite tag
|
||||||
|
paperli_tag = 'climate'
|
||||||
|
title = u'The #climate Daily - paperli'
|
||||||
|
#-------------------------------------------------------------
|
||||||
|
__author__ = 'Hiroshi Miura'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
description = 'paper.li page about '+ paperli_tag
|
||||||
|
publisher = 'paper.li'
|
||||||
|
category = 'paper.li'
|
||||||
|
language = 'en'
|
||||||
|
encoding = 'utf-8'
|
||||||
|
remove_javascript = True
|
||||||
|
masthead_title = u'The '+ paperli_tag +' Daily'
|
||||||
|
timefmt = '[%y/%m/%d]'
|
||||||
|
base_url = 'http://paper.li'
|
||||||
|
index = base_url+'/tag/'+paperli_tag
|
||||||
|
|
||||||
|
|
||||||
|
def parse_index(self):
|
||||||
|
# get topics
|
||||||
|
topics = []
|
||||||
|
soup = self.index_to_soup(self.index)
|
||||||
|
topics_lists = soup.find('div',attrs={'class':'paper-nav-bottom'})
|
||||||
|
for item in topics_lists.findAll('li', attrs={'class':""}):
|
||||||
|
itema = item.find('a',href=True)
|
||||||
|
topics.append({'title': itema.string, 'url': itema['href']})
|
||||||
|
|
||||||
|
#get feeds
|
||||||
|
feeds = []
|
||||||
|
for topic in topics:
|
||||||
|
newsarticles = []
|
||||||
|
soup = self.index_to_soup(''.join([self.base_url, topic['url'] ]))
|
||||||
|
topstories = soup.findAll('div',attrs={'class':'yui-u'})
|
||||||
|
for itt in topstories:
|
||||||
|
itema = itt.find('a',href=True,attrs={'class':'ts'})
|
||||||
|
if itema is not None:
|
||||||
|
itemd = itt.find('div',text=True, attrs={'class':'text'})
|
||||||
|
newsarticles.append({
|
||||||
|
'title' :itema.string
|
||||||
|
,'date' :strftime(self.timefmt)
|
||||||
|
,'url' :itema['href']
|
||||||
|
,'description':itemd.string
|
||||||
|
})
|
||||||
|
feeds.append((topic['title'], newsarticles))
|
||||||
|
return feeds
|
||||||
|
|
42
resources/recipes/science_based_medicine.recipe
Normal file
42
resources/recipes/science_based_medicine.recipe
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import re
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from calibre.ebooks.BeautifulSoup import Tag
|
||||||
|
|
||||||
|
class SBM(BasicNewsRecipe):
|
||||||
|
title = 'Science Based Medicine'
|
||||||
|
__author__ = 'BuzzKill'
|
||||||
|
description = 'Exploring issues and controversies in the relationship between science and medicine'
|
||||||
|
oldest_article = 5
|
||||||
|
max_articles_per_feed = 15
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
encoding = 'utf-8'
|
||||||
|
publisher = 'SBM'
|
||||||
|
category = 'science, sbm, ebm, blog, pseudoscience'
|
||||||
|
language = 'en'
|
||||||
|
|
||||||
|
lang = 'en-US'
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comment' : description
|
||||||
|
, 'tags' : category
|
||||||
|
, 'publisher' : publisher
|
||||||
|
, 'language' : lang
|
||||||
|
, 'pretty_print' : True
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='a', attrs={'title':re.compile(r'Posts by.*', re.DOTALL|re.IGNORECASE)}),
|
||||||
|
dict(name='div', attrs={'class':'entry'})
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [(u'Science Based Medicine', u'http://www.sciencebasedmedicine.org/?feed=rss2')]
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
mtag = Tag(soup,'meta',[('http-equiv','Content-Type'),('context','text/html; charset=utf-8')])
|
||||||
|
soup.head.insert(0,mtag)
|
||||||
|
soup.html['lang'] = self.lang
|
||||||
|
return self.adeify_images(soup)
|
||||||
|
|
36
resources/recipes/uninohimitu.recipe
Normal file
36
resources/recipes/uninohimitu.recipe
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Hiroshi Miura <miurahr@linux.com>'
|
||||||
|
'''
|
||||||
|
http://ameblo.jp/sauta19/
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class UniNoHimituKichiBlog(BasicNewsRecipe):
|
||||||
|
title = u'Uni secret base'
|
||||||
|
__author__ = 'Hiroshi Miura'
|
||||||
|
oldest_article = 2
|
||||||
|
publication_type = 'blog'
|
||||||
|
max_articles_per_feed = 20
|
||||||
|
description = 'Japanese famous Cat blog'
|
||||||
|
publisher = ''
|
||||||
|
category = 'cat, pet, japan'
|
||||||
|
language = 'ja'
|
||||||
|
encoding = 'utf-8'
|
||||||
|
|
||||||
|
feeds = [(u'blog', u'http://feedblog.ameba.jp/rss/ameblo/sauta19/rss20.xml')]
|
||||||
|
|
||||||
|
def parse_feeds(self):
|
||||||
|
feeds = BasicNewsRecipe.parse_feeds(self)
|
||||||
|
for curfeed in feeds:
|
||||||
|
delList = []
|
||||||
|
for a,curarticle in enumerate(curfeed.articles):
|
||||||
|
if re.search(r'rssad.jp', curarticle.url):
|
||||||
|
delList.append(curarticle)
|
||||||
|
if len(delList)>0:
|
||||||
|
for d in delList:
|
||||||
|
index = curfeed.articles.index(d)
|
||||||
|
curfeed.articles[index:index+1] = []
|
||||||
|
return feeds
|
||||||
|
|
@ -60,8 +60,8 @@ class ZeitDe(BasicNewsRecipe):
|
|||||||
for tag in soup.findAll(name=['ul','li']):
|
for tag in soup.findAll(name=['ul','li']):
|
||||||
tag.name = 'div'
|
tag.name = 'div'
|
||||||
|
|
||||||
soup.html['xml:lang'] = self.lang
|
soup.html['xml:lang'] = self.language.replace('_', '-')
|
||||||
soup.html['lang'] = self.lang
|
soup.html['lang'] = self.language.replace('_', '-')
|
||||||
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=' + self.encoding + '">'
|
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=' + self.encoding + '">'
|
||||||
soup.head.insert(0,mtag)
|
soup.head.insert(0,mtag)
|
||||||
return soup
|
return soup
|
||||||
|
@ -36,6 +36,11 @@ class UserFeedback(DeviceError):
|
|||||||
self.details = details
|
self.details = details
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
|
||||||
|
class OpenFeedback(DeviceError):
|
||||||
|
def __init__(self, msg):
|
||||||
|
self.feedback_msg = msg
|
||||||
|
DeviceError.__init__(self, msg)
|
||||||
|
|
||||||
class DeviceBusy(ProtocolError):
|
class DeviceBusy(ProtocolError):
|
||||||
""" Raised when device is busy """
|
""" Raised when device is busy """
|
||||||
def __init__(self, uerr=""):
|
def __init__(self, uerr=""):
|
||||||
|
@ -216,6 +216,9 @@ class DevicePlugin(Plugin):
|
|||||||
an implementation of
|
an implementation of
|
||||||
this function that should serve as a good example for USB Mass storage
|
this function that should serve as a good example for USB Mass storage
|
||||||
devices.
|
devices.
|
||||||
|
|
||||||
|
This method can raise an OpenFeedback exception to display a message to
|
||||||
|
the user.
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ def add_pipeline_options(parser, plumber):
|
|||||||
[
|
[
|
||||||
'base_font_size', 'disable_font_rescaling',
|
'base_font_size', 'disable_font_rescaling',
|
||||||
'font_size_mapping',
|
'font_size_mapping',
|
||||||
'line_height',
|
'line_height', 'minimum_line_height',
|
||||||
'linearize_tables',
|
'linearize_tables',
|
||||||
'extra_css', 'smarten_punctuation',
|
'extra_css', 'smarten_punctuation',
|
||||||
'margin_top', 'margin_left', 'margin_right',
|
'margin_top', 'margin_left', 'margin_right',
|
||||||
|
@ -160,12 +160,29 @@ OptionRecommendation(name='disable_font_rescaling',
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
OptionRecommendation(name='minimum_line_height',
|
||||||
|
recommended_value=120.0, level=OptionRecommendation.LOW,
|
||||||
|
help=_(
|
||||||
|
'The minimum line height, as a percentage of the element\'s '
|
||||||
|
'calculated font size. calibre will ensure that every element '
|
||||||
|
'has a line height of at least this setting, irrespective of '
|
||||||
|
'what the input document specifies. Set to zero to disable. '
|
||||||
|
'Default is 120%. Use this setting in preference to '
|
||||||
|
'the direct line height specification, unless you know what '
|
||||||
|
'you are doing. For example, you can achieve "double spaced" '
|
||||||
|
'text by setting this to 240.'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
OptionRecommendation(name='line_height',
|
OptionRecommendation(name='line_height',
|
||||||
recommended_value=0, level=OptionRecommendation.LOW,
|
recommended_value=0, level=OptionRecommendation.LOW,
|
||||||
help=_('The line height in pts. Controls spacing between consecutive '
|
help=_(
|
||||||
'lines of text. By default no line height manipulation is '
|
'The line height in pts. Controls spacing between consecutive '
|
||||||
'performed.'
|
'lines of text. Only applies to elements that do not define '
|
||||||
|
'their own line height. In most cases, the minimum line height '
|
||||||
|
'option is more useful. '
|
||||||
|
'By default no line height manipulation is performed.'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -73,6 +73,10 @@ class FB2MLizer(object):
|
|||||||
text = re.sub(r'(?miu)<p>\s*</p>', '', text)
|
text = re.sub(r'(?miu)<p>\s*</p>', '', text)
|
||||||
text = re.sub(r'(?miu)\s+</p>', '</p>', text)
|
text = re.sub(r'(?miu)\s+</p>', '</p>', text)
|
||||||
text = re.sub(r'(?miu)</p><p>', '</p>\n\n<p>', text)
|
text = re.sub(r'(?miu)</p><p>', '</p>\n\n<p>', text)
|
||||||
|
|
||||||
|
if self.opts.insert_blank_line:
|
||||||
|
text = re.sub(r'(?miu)</p>', '</p><empty-line />', text)
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def fb2_header(self):
|
def fb2_header(self):
|
||||||
@ -293,6 +297,18 @@ class FB2MLizer(object):
|
|||||||
s_out, s_tags = self.handle_simple_tag('emphasis', tag_stack+tags)
|
s_out, s_tags = self.handle_simple_tag('emphasis', tag_stack+tags)
|
||||||
fb2_out += s_out
|
fb2_out += s_out
|
||||||
tags += s_tags
|
tags += s_tags
|
||||||
|
elif tag in ('del', 'strike'):
|
||||||
|
s_out, s_tags = self.handle_simple_tag('strikethrough', tag_stack+tags)
|
||||||
|
fb2_out += s_out
|
||||||
|
tags += s_tags
|
||||||
|
elif tag == 'sub':
|
||||||
|
s_out, s_tags = self.handle_simple_tag('sub', tag_stack+tags)
|
||||||
|
fb2_out += s_out
|
||||||
|
tags += s_tags
|
||||||
|
elif tag == 'sup':
|
||||||
|
s_out, s_tags = self.handle_simple_tag('sup', tag_stack+tags)
|
||||||
|
fb2_out += s_out
|
||||||
|
tags += s_tags
|
||||||
|
|
||||||
# Processes style information.
|
# Processes style information.
|
||||||
if style['font-style'] == 'italic':
|
if style['font-style'] == 'italic':
|
||||||
@ -303,6 +319,10 @@ class FB2MLizer(object):
|
|||||||
s_out, s_tags = self.handle_simple_tag('strong', tag_stack+tags)
|
s_out, s_tags = self.handle_simple_tag('strong', tag_stack+tags)
|
||||||
fb2_out += s_out
|
fb2_out += s_out
|
||||||
tags += s_tags
|
tags += s_tags
|
||||||
|
elif style['text-decoration'] == 'line-through':
|
||||||
|
s_out, s_tags = self.handle_simple_tag('strikethrough', tag_stack+tags)
|
||||||
|
fb2_out += s_out
|
||||||
|
tags += s_tags
|
||||||
|
|
||||||
# Process element text.
|
# Process element text.
|
||||||
if hasattr(elem_tree, 'text') and elem_tree.text:
|
if hasattr(elem_tree, 'text') and elem_tree.text:
|
||||||
|
@ -314,6 +314,8 @@ class HTMLInput(InputFormatPlugin):
|
|||||||
rewrite_links, urlnormalize, urldefrag, BINARY_MIME, OEB_STYLES, \
|
rewrite_links, urlnormalize, urldefrag, BINARY_MIME, OEB_STYLES, \
|
||||||
xpath
|
xpath
|
||||||
from calibre import guess_type
|
from calibre import guess_type
|
||||||
|
from calibre.ebooks.oeb.transforms.metadata import \
|
||||||
|
meta_info_to_oeb_metadata
|
||||||
import cssutils
|
import cssutils
|
||||||
self.OEB_STYLES = OEB_STYLES
|
self.OEB_STYLES = OEB_STYLES
|
||||||
oeb = create_oebbook(log, None, opts, self,
|
oeb = create_oebbook(log, None, opts, self,
|
||||||
@ -321,15 +323,7 @@ class HTMLInput(InputFormatPlugin):
|
|||||||
self.oeb = oeb
|
self.oeb = oeb
|
||||||
|
|
||||||
metadata = oeb.metadata
|
metadata = oeb.metadata
|
||||||
if mi.title:
|
meta_info_to_oeb_metadata(mi, metadata, log)
|
||||||
metadata.add('title', mi.title)
|
|
||||||
if mi.authors:
|
|
||||||
for a in mi.authors:
|
|
||||||
metadata.add('creator', a, attrib={'role':'aut'})
|
|
||||||
if mi.publisher:
|
|
||||||
metadata.add('publisher', mi.publisher)
|
|
||||||
if mi.isbn:
|
|
||||||
metadata.add('identifier', mi.isbn, attrib={'scheme':'ISBN'})
|
|
||||||
if not metadata.language:
|
if not metadata.language:
|
||||||
oeb.logger.warn(u'Language not specified')
|
oeb.logger.warn(u'Language not specified')
|
||||||
metadata.add('language', get_lang().replace('_', '-'))
|
metadata.add('language', get_lang().replace('_', '-'))
|
||||||
|
@ -170,7 +170,27 @@ def get_metadata_(src, encoding=None):
|
|||||||
if match:
|
if match:
|
||||||
series = match.group(1)
|
series = match.group(1)
|
||||||
if series:
|
if series:
|
||||||
|
pat = re.compile(r'\[([.0-9]+)\]')
|
||||||
|
match = pat.search(series)
|
||||||
|
series_index = None
|
||||||
|
if match is not None:
|
||||||
|
try:
|
||||||
|
series_index = float(match.group(1))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
series = series.replace(match.group(), '').strip()
|
||||||
|
|
||||||
mi.series = ent_pat.sub(entity_to_unicode, series)
|
mi.series = ent_pat.sub(entity_to_unicode, series)
|
||||||
|
if series_index is None:
|
||||||
|
pat = get_meta_regexp_("Seriesnumber")
|
||||||
|
match = pat.search(src)
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
series_index = float(match.group(1))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if series_index is not None:
|
||||||
|
mi.series_index = series_index
|
||||||
|
|
||||||
# RATING
|
# RATING
|
||||||
rating = None
|
rating = None
|
||||||
|
@ -184,7 +184,7 @@ class MobiMLizer(object):
|
|||||||
para.attrib['value'] = str(istates[-2].list_num)
|
para.attrib['value'] = str(istates[-2].list_num)
|
||||||
elif tag in NESTABLE_TAGS and istate.rendered:
|
elif tag in NESTABLE_TAGS and istate.rendered:
|
||||||
para = wrapper = bstate.nested[-1]
|
para = wrapper = bstate.nested[-1]
|
||||||
elif left > 0 and indent >= 0:
|
elif not self.opts.mobi_ignore_margins and left > 0 and indent >= 0:
|
||||||
ems = self.profile.mobi_ems_per_blockquote
|
ems = self.profile.mobi_ems_per_blockquote
|
||||||
para = wrapper = etree.SubElement(parent, XHTML('blockquote'))
|
para = wrapper = etree.SubElement(parent, XHTML('blockquote'))
|
||||||
para = wrapper
|
para = wrapper
|
||||||
|
@ -39,6 +39,12 @@ class MOBIOutput(OutputFormatPlugin):
|
|||||||
OptionRecommendation(name='personal_doc', recommended_value='[PDOC]',
|
OptionRecommendation(name='personal_doc', recommended_value='[PDOC]',
|
||||||
help=_('Tag marking book to be filed with Personal Docs')
|
help=_('Tag marking book to be filed with Personal Docs')
|
||||||
),
|
),
|
||||||
|
OptionRecommendation(name='mobi_ignore_margins',
|
||||||
|
recommended_value=False,
|
||||||
|
help=_('Ignore margins in the input document. If False, then '
|
||||||
|
'the MOBI output plugin will try to convert margins specified'
|
||||||
|
' in the input document, otherwise it will ignore them.')
|
||||||
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
def check_for_periodical(self):
|
def check_for_periodical(self):
|
||||||
|
@ -633,12 +633,12 @@ class Style(object):
|
|||||||
parent = self._getparent()
|
parent = self._getparent()
|
||||||
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':
|
||||||
|
lineh = '1.2'
|
||||||
try:
|
try:
|
||||||
float(lineh)
|
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)
|
||||||
else:
|
|
||||||
result = float(lineh) * self.fontSize
|
|
||||||
elif parent is not None:
|
elif parent is not None:
|
||||||
# TODO: proper inheritance
|
# TODO: proper inheritance
|
||||||
result = parent.lineHeight
|
result = parent.lineHeight
|
||||||
|
@ -245,6 +245,8 @@ class CSSFlattener(object):
|
|||||||
del node.attrib['bgcolor']
|
del node.attrib['bgcolor']
|
||||||
if cssdict.get('font-weight', '').lower() == 'medium':
|
if cssdict.get('font-weight', '').lower() == 'medium':
|
||||||
cssdict['font-weight'] = 'normal' # ADE chokes on font-weight medium
|
cssdict['font-weight'] = 'normal' # ADE chokes on font-weight medium
|
||||||
|
|
||||||
|
fsize = font_size
|
||||||
if not self.context.disable_font_rescaling:
|
if not self.context.disable_font_rescaling:
|
||||||
_sbase = self.sbase if self.sbase is not None else \
|
_sbase = self.sbase if self.sbase is not None else \
|
||||||
self.context.source.fbase
|
self.context.source.fbase
|
||||||
@ -258,6 +260,14 @@ class CSSFlattener(object):
|
|||||||
fsize = self.fmap[font_size]
|
fsize = self.fmap[font_size]
|
||||||
cssdict['font-size'] = "%0.5fem" % (fsize / psize)
|
cssdict['font-size'] = "%0.5fem" % (fsize / psize)
|
||||||
psize = fsize
|
psize = fsize
|
||||||
|
|
||||||
|
try:
|
||||||
|
minlh = self.context.minimum_line_height / 100.
|
||||||
|
if style['line-height'] < minlh * fsize:
|
||||||
|
cssdict['line-height'] = str(minlh)
|
||||||
|
except:
|
||||||
|
self.oeb.logger.exception('Failed to set minimum line-height')
|
||||||
|
|
||||||
if cssdict:
|
if cssdict:
|
||||||
if self.lineh and self.fbase and tag != 'body':
|
if self.lineh and self.fbase and tag != 'body':
|
||||||
self.clean_edges(cssdict, style, psize)
|
self.clean_edges(cssdict, style, psize)
|
||||||
@ -290,6 +300,7 @@ class CSSFlattener(object):
|
|||||||
lineh = self.lineh / psize
|
lineh = self.lineh / psize
|
||||||
cssdict['line-height'] = "%0.5fem" % lineh
|
cssdict['line-height'] = "%0.5fem" % lineh
|
||||||
|
|
||||||
|
|
||||||
if (self.context.remove_paragraph_spacing or
|
if (self.context.remove_paragraph_spacing or
|
||||||
self.context.insert_blank_line) and tag in ('p', 'div'):
|
self.context.insert_blank_line) and tag in ('p', 'div'):
|
||||||
if item_id != 'calibre_jacket' or self.context.output_profile.name == 'Kindle':
|
if item_id != 'calibre_jacket' or self.context.output_profile.name == 'Kindle':
|
||||||
|
@ -77,10 +77,14 @@ class TXTInput(InputFormatPlugin):
|
|||||||
base = os.getcwdu()
|
base = os.getcwdu()
|
||||||
if hasattr(stream, 'name'):
|
if hasattr(stream, 'name'):
|
||||||
base = os.path.dirname(stream.name)
|
base = os.path.dirname(stream.name)
|
||||||
htmlfile = open(os.path.join(base, 'temp_calibre_txt_input_to_html.html'),
|
fname = os.path.join(base, 'index.html')
|
||||||
'wb')
|
c = 0
|
||||||
|
while os.path.exists(fname):
|
||||||
|
c += 1
|
||||||
|
fname = 'index%d.html'%c
|
||||||
|
htmlfile = open(fname, 'wb')
|
||||||
|
with htmlfile:
|
||||||
htmlfile.write(html.encode('utf-8'))
|
htmlfile.write(html.encode('utf-8'))
|
||||||
htmlfile.close()
|
|
||||||
cwd = os.getcwdu()
|
cwd = os.getcwdu()
|
||||||
odi = options.debug_pipeline
|
odi = options.debug_pipeline
|
||||||
options.debug_pipeline = None
|
options.debug_pipeline = None
|
||||||
|
@ -9,7 +9,7 @@ from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
|
|||||||
QByteArray, QTranslator, QCoreApplication, QThread, \
|
QByteArray, QTranslator, QCoreApplication, QThread, \
|
||||||
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, \
|
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, \
|
||||||
QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
||||||
QIcon, QApplication, QDialog, QPushButton, QUrl
|
QIcon, QApplication, QDialog, QPushButton, QUrl, QFont
|
||||||
|
|
||||||
ORG_NAME = 'KovidsBrain'
|
ORG_NAME = 'KovidsBrain'
|
||||||
APP_UID = 'libprs500'
|
APP_UID = 'libprs500'
|
||||||
@ -52,6 +52,7 @@ gprefs.defaults['show_splash_screen'] = True
|
|||||||
gprefs.defaults['toolbar_icon_size'] = 'medium'
|
gprefs.defaults['toolbar_icon_size'] = 'medium'
|
||||||
gprefs.defaults['toolbar_text'] = 'auto'
|
gprefs.defaults['toolbar_text'] = 'auto'
|
||||||
gprefs.defaults['show_child_bar'] = False
|
gprefs.defaults['show_child_bar'] = False
|
||||||
|
gprefs.defaults['font'] = None
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@ -613,6 +614,10 @@ class Application(QApplication):
|
|||||||
qt_app = self
|
qt_app = self
|
||||||
self._file_open_paths = []
|
self._file_open_paths = []
|
||||||
self._file_open_lock = RLock()
|
self._file_open_lock = RLock()
|
||||||
|
self.original_font = QFont(QApplication.font())
|
||||||
|
fi = gprefs['font']
|
||||||
|
if fi is not None:
|
||||||
|
QApplication.setFont(QFont(*fi))
|
||||||
|
|
||||||
def _send_file_open_events(self):
|
def _send_file_open_events(self):
|
||||||
with self._file_open_lock:
|
with self._file_open_lock:
|
||||||
|
@ -154,15 +154,17 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
d.view_format.connect(lambda
|
d.view_format.connect(lambda
|
||||||
fmt:self.gui.iactions['View'].view_format(row_list[current_row],
|
fmt:self.gui.iactions['View'].view_format(row_list[current_row],
|
||||||
fmt))
|
fmt))
|
||||||
if d.exec_() != d.Accepted:
|
ret = d.exec_()
|
||||||
d.view_format.disconnect()
|
d.break_cycles()
|
||||||
|
if ret != d.Accepted:
|
||||||
break
|
break
|
||||||
d.view_format.disconnect()
|
|
||||||
changed.add(d.id)
|
changed.add(d.id)
|
||||||
if d.row_delta == 0:
|
if d.row_delta == 0:
|
||||||
break
|
break
|
||||||
current_row += d.row_delta
|
current_row += d.row_delta
|
||||||
|
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
self.gui.library_view.model().refresh_ids(list(changed))
|
self.gui.library_view.model().refresh_ids(list(changed))
|
||||||
current = self.gui.library_view.currentIndex()
|
current = self.gui.library_view.currentIndex()
|
||||||
|
@ -10,7 +10,7 @@ import textwrap
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QWidget, QSpinBox, QDoubleSpinBox, QLineEdit, QTextEdit, \
|
from PyQt4.Qt import QWidget, QSpinBox, QDoubleSpinBox, QLineEdit, QTextEdit, \
|
||||||
QCheckBox, QComboBox, Qt, QIcon, pyqtSignal
|
QCheckBox, QComboBox, Qt, QIcon, pyqtSignal, QLabel
|
||||||
|
|
||||||
from calibre.customize.conversion import OptionRecommendation
|
from calibre.customize.conversion import OptionRecommendation
|
||||||
from calibre.ebooks.conversion.config import load_defaults, \
|
from calibre.ebooks.conversion.config import load_defaults, \
|
||||||
@ -81,6 +81,21 @@ class Widget(QWidget):
|
|||||||
self.apply_recommendations(defaults)
|
self.apply_recommendations(defaults)
|
||||||
self.setup_help(get_help)
|
self.setup_help(get_help)
|
||||||
|
|
||||||
|
def process_child(child):
|
||||||
|
for g in child.children():
|
||||||
|
if isinstance(g, QLabel):
|
||||||
|
buddy = g.buddy()
|
||||||
|
if buddy is not None and hasattr(buddy, '_help'):
|
||||||
|
g._help = buddy._help
|
||||||
|
htext = unicode(buddy.toolTip()).strip()
|
||||||
|
g.setToolTip(htext)
|
||||||
|
g.setWhatsThis(htext)
|
||||||
|
g.__class__.enterEvent = lambda obj, event: self.set_help(getattr(obj, '_help', obj.toolTip()))
|
||||||
|
else:
|
||||||
|
process_child(g)
|
||||||
|
process_child(self)
|
||||||
|
|
||||||
|
|
||||||
def restore_defaults(self, get_option):
|
def restore_defaults(self, get_option):
|
||||||
defaults = GuiRecommendations()
|
defaults = GuiRecommendations()
|
||||||
defaults.merge_recommendations(get_option, OptionRecommendation.LOW,
|
defaults.merge_recommendations(get_option, OptionRecommendation.LOW,
|
||||||
|
@ -21,7 +21,7 @@ class LookAndFeelWidget(Widget, Ui_Form):
|
|||||||
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
||||||
Widget.__init__(self, parent,
|
Widget.__init__(self, parent,
|
||||||
['change_justification', 'extra_css', 'base_font_size',
|
['change_justification', 'extra_css', 'base_font_size',
|
||||||
'font_size_mapping', 'line_height',
|
'font_size_mapping', 'line_height', 'minimum_line_height',
|
||||||
'linearize_tables', 'smarten_punctuation',
|
'linearize_tables', 'smarten_punctuation',
|
||||||
'disable_font_rescaling', 'insert_blank_line',
|
'disable_font_rescaling', 'insert_blank_line',
|
||||||
'remove_paragraph_spacing', 'remove_paragraph_spacing_indent_size','input_encoding',
|
'remove_paragraph_spacing', 'remove_paragraph_spacing_indent_size','input_encoding',
|
||||||
|
@ -97,7 +97,7 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="4" column="0">
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Line &height:</string>
|
<string>Line &height:</string>
|
||||||
@ -107,7 +107,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1" colspan="2">
|
<item row="4" column="1" colspan="2">
|
||||||
<widget class="QDoubleSpinBox" name="opt_line_height">
|
<widget class="QDoubleSpinBox" name="opt_line_height">
|
||||||
<property name="suffix">
|
<property name="suffix">
|
||||||
<string> pt</string>
|
<string> pt</string>
|
||||||
@ -117,7 +117,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="5" column="0">
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Input character &encoding:</string>
|
<string>Input character &encoding:</string>
|
||||||
@ -127,17 +127,17 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1" colspan="3">
|
<item row="5" column="1" colspan="3">
|
||||||
<widget class="QLineEdit" name="opt_input_encoding"/>
|
<widget class="QLineEdit" name="opt_input_encoding"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" colspan="2">
|
<item row="6" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="opt_remove_paragraph_spacing">
|
<widget class="QCheckBox" name="opt_remove_paragraph_spacing">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Remove &spacing between paragraphs</string>
|
<string>Remove &spacing between paragraphs</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="2" colspan="2">
|
<item row="6" column="2" colspan="2">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_4">
|
<widget class="QLabel" name="label_4">
|
||||||
@ -164,21 +164,21 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0">
|
<item row="7" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Text justification:</string>
|
<string>Text justification:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="0">
|
<item row="8" column="0">
|
||||||
<widget class="QCheckBox" name="opt_linearize_tables">
|
<widget class="QCheckBox" name="opt_linearize_tables">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Linearize tables</string>
|
<string>&Linearize tables</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="10" column="0" colspan="4">
|
<item row="11" column="0" colspan="4">
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Extra &CSS</string>
|
<string>Extra &CSS</string>
|
||||||
@ -190,37 +190,60 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="2" colspan="2">
|
<item row="7" column="2" colspan="2">
|
||||||
<widget class="QComboBox" name="opt_change_justification"/>
|
<widget class="QComboBox" name="opt_change_justification"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="1" colspan="3">
|
<item row="8" column="1" colspan="3">
|
||||||
<widget class="QCheckBox" name="opt_asciiize">
|
<widget class="QCheckBox" name="opt_asciiize">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Transliterate unicode characters to ASCII</string>
|
<string>&Transliterate unicode characters to ASCII</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="0">
|
<item row="9" column="0">
|
||||||
<widget class="QCheckBox" name="opt_insert_blank_line">
|
<widget class="QCheckBox" name="opt_insert_blank_line">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Insert &blank line</string>
|
<string>Insert &blank line</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="1" colspan="2">
|
<item row="9" column="1" colspan="2">
|
||||||
<widget class="QCheckBox" name="opt_keep_ligatures">
|
<widget class="QCheckBox" name="opt_keep_ligatures">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Keep &ligatures</string>
|
<string>Keep &ligatures</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="9" column="0">
|
<item row="10" column="0">
|
||||||
<widget class="QCheckBox" name="opt_smarten_punctuation">
|
<widget class="QCheckBox" name="opt_smarten_punctuation">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Smarten &punctuation</string>
|
<string>Smarten &punctuation</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Minimum &line height:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_minimum_line_height</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1" colspan="2">
|
||||||
|
<widget class="QDoubleSpinBox" name="opt_minimum_line_height">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> %</string>
|
||||||
|
</property>
|
||||||
|
<property name="decimals">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>900.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
|
@ -25,6 +25,7 @@ class PluginWidget(Widget, Ui_Form):
|
|||||||
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
||||||
Widget.__init__(self, parent,
|
Widget.__init__(self, parent,
|
||||||
['prefer_author_sort', 'rescale_images', 'toc_title',
|
['prefer_author_sort', 'rescale_images', 'toc_title',
|
||||||
|
'mobi_ignore_margins',
|
||||||
'dont_compress', 'no_inline_toc', 'masthead_font','personal_doc']
|
'dont_compress', 'no_inline_toc', 'masthead_font','personal_doc']
|
||||||
)
|
)
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" colspan="2">
|
<item row="6" column="0" colspan="2">
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Kindle options</string>
|
<string>Kindle options</string>
|
||||||
@ -101,7 +101,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0">
|
<item row="7" column="0">
|
||||||
<spacer name="verticalSpacer_2">
|
<spacer name="verticalSpacer_2">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -114,6 +114,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QCheckBox" name="opt_mobi_ignore_margins">
|
||||||
|
<property name="text">
|
||||||
|
<string>Ignore &margins</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -12,7 +12,7 @@ from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, \
|
|||||||
from calibre.customize.ui import available_input_formats, available_output_formats, \
|
from calibre.customize.ui import available_input_formats, available_output_formats, \
|
||||||
device_plugins
|
device_plugins
|
||||||
from calibre.devices.interface import DevicePlugin
|
from calibre.devices.interface import DevicePlugin
|
||||||
from calibre.devices.errors import UserFeedback
|
from calibre.devices.errors import UserFeedback, OpenFeedback
|
||||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||||
from calibre.utils.ipc.job import BaseJob
|
from calibre.utils.ipc.job import BaseJob
|
||||||
from calibre.devices.scanner import DeviceScanner
|
from calibre.devices.scanner import DeviceScanner
|
||||||
@ -122,7 +122,8 @@ def device_name_for_plugboards(device_class):
|
|||||||
|
|
||||||
class DeviceManager(Thread): # {{{
|
class DeviceManager(Thread): # {{{
|
||||||
|
|
||||||
def __init__(self, connected_slot, job_manager, open_feedback_slot, sleep_time=2):
|
def __init__(self, connected_slot, job_manager, open_feedback_slot,
|
||||||
|
open_feedback_msg, sleep_time=2):
|
||||||
'''
|
'''
|
||||||
:sleep_time: Time to sleep between device probes in secs
|
:sleep_time: Time to sleep between device probes in secs
|
||||||
'''
|
'''
|
||||||
@ -143,6 +144,7 @@ class DeviceManager(Thread): # {{{
|
|||||||
self.ejected_devices = set([])
|
self.ejected_devices = set([])
|
||||||
self.mount_connection_requests = Queue.Queue(0)
|
self.mount_connection_requests = Queue.Queue(0)
|
||||||
self.open_feedback_slot = open_feedback_slot
|
self.open_feedback_slot = open_feedback_slot
|
||||||
|
self.open_feedback_msg = open_feedback_msg
|
||||||
|
|
||||||
def report_progress(self, *args):
|
def report_progress(self, *args):
|
||||||
pass
|
pass
|
||||||
@ -163,6 +165,9 @@ class DeviceManager(Thread): # {{{
|
|||||||
dev.reset(detected_device=detected_device,
|
dev.reset(detected_device=detected_device,
|
||||||
report_progress=self.report_progress)
|
report_progress=self.report_progress)
|
||||||
dev.open()
|
dev.open()
|
||||||
|
except OpenFeedback, e:
|
||||||
|
self.open_feedback_msg(dev.get_gui_name(), e.feedback_msg)
|
||||||
|
continue
|
||||||
except:
|
except:
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
if DEBUG or tb not in self.reported_errors:
|
if DEBUG or tb not in self.reported_errors:
|
||||||
@ -594,11 +599,16 @@ class DeviceMixin(object): # {{{
|
|||||||
_('Error communicating with device'), ' ')
|
_('Error communicating with device'), ' ')
|
||||||
self.device_error_dialog.setModal(Qt.NonModal)
|
self.device_error_dialog.setModal(Qt.NonModal)
|
||||||
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
|
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
|
||||||
self.job_manager, Dispatcher(self.status_bar.show_message))
|
self.job_manager, Dispatcher(self.status_bar.show_message),
|
||||||
|
Dispatcher(self.show_open_feedback))
|
||||||
self.device_manager.start()
|
self.device_manager.start()
|
||||||
if tweaks['auto_connect_to_folder']:
|
if tweaks['auto_connect_to_folder']:
|
||||||
self.connect_to_folder_named(tweaks['auto_connect_to_folder'])
|
self.connect_to_folder_named(tweaks['auto_connect_to_folder'])
|
||||||
|
|
||||||
|
def show_open_feedback(self, devname, msg):
|
||||||
|
self.__of_dev_mem__ = d = info_dialog(self, devname, msg)
|
||||||
|
d.show()
|
||||||
|
|
||||||
def auto_convert_question(self, msg, autos):
|
def auto_convert_question(self, msg, autos):
|
||||||
autos = u'\n'.join(map(unicode, map(force_unicode, autos)))
|
autos = u'\n'.join(map(unicode, map(force_unicode, autos)))
|
||||||
return self.ask_a_yes_no_question(
|
return self.ask_a_yes_no_question(
|
||||||
|
@ -102,7 +102,7 @@ class MyBlockingBusy(QDialog):
|
|||||||
remove_all, remove, add, au, aus, do_aus, rating, pub, do_series, \
|
remove_all, remove, add, au, aus, do_aus, rating, pub, do_series, \
|
||||||
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, clear_series = self.args
|
series_start_value, do_title_case, cover_action, clear_series = 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
|
||||||
@ -129,6 +129,23 @@ class MyBlockingBusy(QDialog):
|
|||||||
self.db.set_title(id, titlecase(title), notify=False)
|
self.db.set_title(id, titlecase(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':
|
||||||
|
self.db.remove_cover(id)
|
||||||
|
elif cover_action == 'generate':
|
||||||
|
from calibre.ebooks import calibre_cover
|
||||||
|
from calibre.ebooks.metadata import fmt_sidx
|
||||||
|
from calibre.gui2 import config
|
||||||
|
mi = self.db.get_metadata(id, index_is_id=True)
|
||||||
|
series_string = None
|
||||||
|
if mi.series:
|
||||||
|
series_string = _('Book %s of %s')%(
|
||||||
|
fmt_sidx(mi.series_index,
|
||||||
|
use_roman=config['use_roman_numerals_for_series_number']),
|
||||||
|
mi.series)
|
||||||
|
|
||||||
|
cdata = calibre_cover(mi.title, mi.format_field('authors')[-1],
|
||||||
|
series_string=series_string)
|
||||||
|
self.db.set_cover(id, cdata)
|
||||||
elif self.current_phase == 2:
|
elif self.current_phase == 2:
|
||||||
# All of these just affect the DB, so we can tolerate a total rollback
|
# All of these just affect the DB, so we can tolerate a total rollback
|
||||||
if do_auto_author:
|
if do_auto_author:
|
||||||
@ -678,11 +695,16 @@ class MetadataBulkDialog(QDialog, 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()
|
||||||
|
cover_action = None
|
||||||
|
if self.cover_remove.isChecked():
|
||||||
|
cover_action = 'remove'
|
||||||
|
elif self.cover_generate.isChecked():
|
||||||
|
cover_action = 'generate'
|
||||||
|
|
||||||
args = (remove_all, remove, add, au, aus, do_aus, rating, pub, do_series,
|
args = (remove_all, remove, add, au, aus, do_aus, rating, pub, do_series,
|
||||||
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, clear_series)
|
series_start_value, do_title_case, cover_action, clear_series)
|
||||||
|
|
||||||
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,
|
||||||
|
@ -381,7 +381,7 @@ Future conversion of these books will use the default settings.</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="15" column="0" colspan="3">
|
<item row="14" column="0" colspan="3">
|
||||||
<spacer name="verticalSpacer_2">
|
<spacer name="verticalSpacer_2">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -394,6 +394,39 @@ Future conversion of these books will use the default settings.</string>
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="13" column="0" colspan="3">
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Change &cover</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="cover_no_change">
|
||||||
|
<property name="text">
|
||||||
|
<string>&No change</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="cover_remove">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Remove cover</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="cover_generate">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Generate default cover</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="tab">
|
<widget class="QWidget" name="tab">
|
||||||
|
@ -240,37 +240,39 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.cover_fetcher = CoverFetcher(None, None, isbn,
|
self.cover_fetcher = CoverFetcher(None, None, isbn,
|
||||||
self.timeout, title, author)
|
self.timeout, title, author)
|
||||||
self.cover_fetcher.start()
|
self.cover_fetcher.start()
|
||||||
self._hangcheck = QTimer(self)
|
|
||||||
self._hangcheck.timeout.connect(self.hangcheck,
|
|
||||||
type=Qt.QueuedConnection)
|
|
||||||
self.cf_start_time = time.time()
|
self.cf_start_time = time.time()
|
||||||
self.pi.start(_('Downloading cover...'))
|
self.pi.start(_('Downloading cover...'))
|
||||||
self._hangcheck.start(100)
|
QTimer.singleShot(100, self.hangcheck)
|
||||||
|
|
||||||
def hangcheck(self):
|
def hangcheck(self):
|
||||||
if self.cover_fetcher.is_alive() and \
|
cf = self.cover_fetcher
|
||||||
time.time()-self.cf_start_time < self.COVER_FETCH_TIMEOUT:
|
if cf is None:
|
||||||
|
# Called after dialog closed
|
||||||
|
return
|
||||||
|
|
||||||
|
if cf.is_alive() and \
|
||||||
|
time.time()-self.cf_start_time < self.COVER_FETCH_TIMEOUT:
|
||||||
|
QTimer.singleShot(100, self.hangcheck)
|
||||||
return
|
return
|
||||||
|
|
||||||
self._hangcheck.stop()
|
|
||||||
try:
|
try:
|
||||||
if self.cover_fetcher.is_alive():
|
if cf.is_alive():
|
||||||
error_dialog(self, _('Cannot fetch cover'),
|
error_dialog(self, _('Cannot fetch cover'),
|
||||||
_('<b>Could not fetch cover.</b><br/>')+
|
_('<b>Could not fetch cover.</b><br/>')+
|
||||||
_('The download timed out.')).exec_()
|
_('The download timed out.')).exec_()
|
||||||
return
|
return
|
||||||
if self.cover_fetcher.needs_isbn:
|
if cf.needs_isbn:
|
||||||
error_dialog(self, _('Cannot fetch cover'),
|
error_dialog(self, _('Cannot fetch cover'),
|
||||||
_('Could not find cover for this book. Try '
|
_('Could not find cover for this book. Try '
|
||||||
'specifying the ISBN first.')).exec_()
|
'specifying the ISBN first.')).exec_()
|
||||||
return
|
return
|
||||||
if self.cover_fetcher.exception is not None:
|
if cf.exception is not None:
|
||||||
err = self.cover_fetcher.exception
|
err = cf.exception
|
||||||
error_dialog(self, _('Cannot fetch cover'),
|
error_dialog(self, _('Cannot fetch cover'),
|
||||||
_('<b>Could not fetch cover.</b><br/>')+unicode(err)).exec_()
|
_('<b>Could not fetch cover.</b><br/>')+unicode(err)).exec_()
|
||||||
return
|
return
|
||||||
if self.cover_fetcher.errors and self.cover_fetcher.cover_data is None:
|
if cf.errors and cf.cover_data is None:
|
||||||
details = u'\n\n'.join([e[-1] + ': ' + e[1] for e in self.cover_fetcher.errors])
|
details = u'\n\n'.join([e[-1] + ': ' + e[1] for e in cf.errors])
|
||||||
error_dialog(self, _('Cannot fetch cover'),
|
error_dialog(self, _('Cannot fetch cover'),
|
||||||
_('<b>Could not fetch cover.</b><br/>') +
|
_('<b>Could not fetch cover.</b><br/>') +
|
||||||
_('For the error message from each cover source, '
|
_('For the error message from each cover source, '
|
||||||
@ -278,7 +280,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
pix = QPixmap()
|
pix = QPixmap()
|
||||||
pix.loadFromData(self.cover_fetcher.cover_data)
|
pix.loadFromData(cf.cover_data)
|
||||||
if pix.isNull():
|
if pix.isNull():
|
||||||
error_dialog(self, _('Bad cover'),
|
error_dialog(self, _('Bad cover'),
|
||||||
_('The cover is not a valid picture')).exec_()
|
_('The cover is not a valid picture')).exec_()
|
||||||
@ -287,10 +289,11 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.update_cover_tooltip()
|
self.update_cover_tooltip()
|
||||||
self.cover_changed = True
|
self.cover_changed = True
|
||||||
self.cpixmap = pix
|
self.cpixmap = pix
|
||||||
self.cover_data = self.cover_fetcher.cover_data
|
self.cover_data = cf.cover_data
|
||||||
finally:
|
finally:
|
||||||
self.fetch_cover_button.setEnabled(True)
|
self.fetch_cover_button.setEnabled(True)
|
||||||
self.unsetCursor()
|
self.unsetCursor()
|
||||||
|
if self.pi is not None:
|
||||||
self.pi.stop()
|
self.pi.stop()
|
||||||
|
|
||||||
|
|
||||||
@ -438,8 +441,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
def __init__(self, window, row, db, prev=None,
|
def __init__(self, window, row, db, prev=None,
|
||||||
next_=None):
|
next_=None):
|
||||||
ResizableDialog.__init__(self, window)
|
ResizableDialog.__init__(self, window)
|
||||||
|
self.cover_fetcher = None
|
||||||
self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter)
|
self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter)
|
||||||
self.cancel_all = False
|
|
||||||
base = unicode(self.author_sort.toolTip())
|
base = unicode(self.author_sort.toolTip())
|
||||||
self.ok_aus_tooltip = '<p>' + textwrap.fill(base+'<br><br>'+
|
self.ok_aus_tooltip = '<p>' + textwrap.fill(base+'<br><br>'+
|
||||||
_(' The green color indicates that the current '
|
_(' The green color indicates that the current '
|
||||||
@ -570,7 +573,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.enable_series_index)
|
QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.enable_series_index)
|
||||||
self.series.lineEdit().editingFinished.connect(self.increment_series_index)
|
self.series.lineEdit().editingFinished.connect(self.increment_series_index)
|
||||||
|
|
||||||
self.show()
|
|
||||||
pm = QPixmap()
|
pm = QPixmap()
|
||||||
if cover:
|
if cover:
|
||||||
pm.loadFromData(cover)
|
pm.loadFromData(cover)
|
||||||
@ -590,6 +592,8 @@ 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.show()
|
||||||
|
|
||||||
def create_custom_column_editors(self):
|
def create_custom_column_editors(self):
|
||||||
w = self.central_widget.widget(1)
|
w = self.central_widget.widget(1)
|
||||||
layout = w.layout()
|
layout = w.layout()
|
||||||
@ -828,10 +832,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
cf = getattr(self, 'cover_fetcher', None)
|
|
||||||
if cf is not None and hasattr(cf, 'terminate'):
|
|
||||||
cf.terminate()
|
|
||||||
cf.wait()
|
|
||||||
try:
|
try:
|
||||||
if self.formats_changed:
|
if self.formats_changed:
|
||||||
self.sync_formats()
|
self.sync_formats()
|
||||||
@ -888,14 +888,12 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
show=True)
|
show=True)
|
||||||
raise
|
raise
|
||||||
self.save_state()
|
self.save_state()
|
||||||
|
self.cover_fetcher = None
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
def reject(self, *args):
|
def reject(self, *args):
|
||||||
cf = getattr(self, 'cover_fetcher', None)
|
|
||||||
if cf is not None and hasattr(cf, 'terminate'):
|
|
||||||
cf.terminate()
|
|
||||||
cf.wait()
|
|
||||||
self.save_state()
|
self.save_state()
|
||||||
|
self.cover_fetcher = None
|
||||||
QDialog.reject(self, *args)
|
QDialog.reject(self, *args)
|
||||||
|
|
||||||
def read_state(self):
|
def read_state(self):
|
||||||
@ -910,3 +908,48 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
dynamic.set('metasingle_window_geometry', bytes(self.saveGeometry()))
|
dynamic.set('metasingle_window_geometry', bytes(self.saveGeometry()))
|
||||||
dynamic.set('metasingle_splitter_state',
|
dynamic.set('metasingle_splitter_state',
|
||||||
bytes(self.splitter.saveState()))
|
bytes(self.splitter.saveState()))
|
||||||
|
|
||||||
|
def break_cycles(self):
|
||||||
|
# Break any reference cycles that could prevent python
|
||||||
|
# from garbage collecting this dialog
|
||||||
|
def disconnect(signal):
|
||||||
|
try:
|
||||||
|
signal.disconnect()
|
||||||
|
except:
|
||||||
|
pass # Fails if view format was never connected
|
||||||
|
disconnect(self.view_format)
|
||||||
|
for b in ('next_button', 'prev_button'):
|
||||||
|
x = getattr(self, b, None)
|
||||||
|
if x is not None:
|
||||||
|
disconnect(x.clicked)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from calibre.library import db
|
||||||
|
from PyQt4.Qt import QApplication
|
||||||
|
from calibre.utils.mem import memory
|
||||||
|
import gc
|
||||||
|
|
||||||
|
|
||||||
|
app = QApplication([])
|
||||||
|
db = db()
|
||||||
|
|
||||||
|
# Initialize all Qt Objects once
|
||||||
|
d = MetadataSingleDialog(None, 4, db)
|
||||||
|
d.break_cycles()
|
||||||
|
d.reject()
|
||||||
|
del d
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
gc.collect()
|
||||||
|
before = memory()
|
||||||
|
|
||||||
|
d = MetadataSingleDialog(None, 4, db)
|
||||||
|
d.reject()
|
||||||
|
d.break_cycles()
|
||||||
|
del d
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
gc.collect()
|
||||||
|
print 'Used memory:', memory(before)/1024.**2, 'MB'
|
||||||
|
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ class TagCategories(QDialog, Ui_TagCategories):
|
|||||||
index = self.all_items[node.data(Qt.UserRole).toPyObject()].index
|
index = self.all_items[node.data(Qt.UserRole).toPyObject()].index
|
||||||
if index not in self.applied_items:
|
if index not in self.applied_items:
|
||||||
self.applied_items.append(index)
|
self.applied_items.append(index)
|
||||||
self.applied_items.sort(key=lambda x:sort_key(self.all_items[x]))
|
self.applied_items.sort(key=lambda x:sort_key(self.all_items[x].name))
|
||||||
self.display_filtered_categories(None)
|
self.display_filtered_categories(None)
|
||||||
|
|
||||||
def unapply_tags(self, node=None):
|
def unapply_tags(self, node=None):
|
||||||
|
@ -105,9 +105,13 @@ class TagListEditor(QDialog, Ui_TagListEditor):
|
|||||||
if not question_dialog(self, _('Are your sure?'),
|
if not question_dialog(self, _('Are your sure?'),
|
||||||
'<p>'+_('Are you certain you want to delete the following items?')+'<br>'+ct):
|
'<p>'+_('Are you certain you want to delete the following items?')+'<br>'+ct):
|
||||||
return
|
return
|
||||||
|
row = self.available_tags.row(deletes[0])
|
||||||
for item in deletes:
|
for item in deletes:
|
||||||
(id,ign) = item.data(Qt.UserRole).toInt()
|
(id,ign) = item.data(Qt.UserRole).toInt()
|
||||||
self.to_delete.append(id)
|
self.to_delete.append(id)
|
||||||
self.available_tags.takeItem(self.available_tags.row(item))
|
self.available_tags.takeItem(self.available_tags.row(item))
|
||||||
|
|
||||||
|
if row >= self.available_tags.count():
|
||||||
|
row = self.available_tags.count() - 1
|
||||||
|
if row >= 0:
|
||||||
|
self.available_tags.scrollToItem(self.available_tags.item(row))
|
||||||
|
@ -123,6 +123,8 @@ class Stack(QStackedWidget): # {{{
|
|||||||
_('Tag Browser'), I('tags.png'),
|
_('Tag Browser'), I('tags.png'),
|
||||||
parent=parent, side_index=0, initial_side_size=200,
|
parent=parent, side_index=0, initial_side_size=200,
|
||||||
shortcut=_('Shift+Alt+T'))
|
shortcut=_('Shift+Alt+T'))
|
||||||
|
parent.tb_splitter.state_changed.connect(
|
||||||
|
self.tb_widget.set_pane_is_visible, Qt.QueuedConnection)
|
||||||
parent.tb_splitter.addWidget(self.tb_widget)
|
parent.tb_splitter.addWidget(self.tb_widget)
|
||||||
parent.tb_splitter.addWidget(parent.cb_splitter)
|
parent.tb_splitter.addWidget(parent.cb_splitter)
|
||||||
parent.tb_splitter.setCollapsible(parent.tb_splitter.other_index, False)
|
parent.tb_splitter.setCollapsible(parent.tb_splitter.other_index, False)
|
||||||
|
@ -5,10 +5,11 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import QApplication, QFont, QFontInfo, QFontDialog
|
||||||
|
|
||||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
||||||
from calibre.gui2.preferences.look_feel_ui import Ui_Form
|
from calibre.gui2.preferences.look_feel_ui import Ui_Form
|
||||||
from calibre.gui2 import config, gprefs
|
from calibre.gui2 import config, gprefs, qt_app
|
||||||
from calibre.utils.localization import available_translations, \
|
from calibre.utils.localization import available_translations, \
|
||||||
get_language, get_lang
|
get_language, get_lang
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
@ -56,12 +57,64 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
(_('Never'), 'never')]
|
(_('Never'), 'never')]
|
||||||
r('toolbar_text', gprefs, choices=choices)
|
r('toolbar_text', gprefs, choices=choices)
|
||||||
|
|
||||||
|
self.current_font = None
|
||||||
|
self.change_font_button.clicked.connect(self.change_font)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
ConfigWidgetBase.initialize(self)
|
||||||
|
self.current_font = gprefs['font']
|
||||||
|
self.update_font_display()
|
||||||
|
|
||||||
|
def restore_defaults(self):
|
||||||
|
ConfigWidgetBase.restore_defaults(self)
|
||||||
|
ofont = self.current_font
|
||||||
|
self.current_font = None
|
||||||
|
if ofont is not None:
|
||||||
|
self.changed_signal.emit()
|
||||||
|
self.update_font_display()
|
||||||
|
|
||||||
|
def build_font_obj(self):
|
||||||
|
font_info = self.current_font
|
||||||
|
if font_info is not None:
|
||||||
|
font = QFont(*font_info)
|
||||||
|
else:
|
||||||
|
font = qt_app.original_font
|
||||||
|
return font
|
||||||
|
|
||||||
|
def update_font_display(self):
|
||||||
|
font = self.build_font_obj()
|
||||||
|
fi = QFontInfo(font)
|
||||||
|
name = unicode(fi.family())
|
||||||
|
|
||||||
|
self.font_display.setFont(font)
|
||||||
|
self.font_display.setText(_('Current font:') + ' ' + name +
|
||||||
|
' [%dpt]'%fi.pointSize())
|
||||||
|
|
||||||
|
def change_font(self, *args):
|
||||||
|
fd = QFontDialog(self.build_font_obj(), self)
|
||||||
|
if fd.exec_() == fd.Accepted:
|
||||||
|
font = fd.selectedFont()
|
||||||
|
fi = QFontInfo(font)
|
||||||
|
self.current_font = (unicode(fi.family()), fi.pointSize(),
|
||||||
|
fi.weight(), fi.italic())
|
||||||
|
self.update_font_display()
|
||||||
|
self.changed_signal.emit()
|
||||||
|
|
||||||
|
def commit(self, *args):
|
||||||
|
rr = ConfigWidgetBase.commit(self, *args)
|
||||||
|
if self.current_font != gprefs['font']:
|
||||||
|
gprefs['font'] = self.current_font
|
||||||
|
QApplication.setFont(self.font_display.font())
|
||||||
|
rr = True
|
||||||
|
return rr
|
||||||
|
|
||||||
|
|
||||||
def refresh_gui(self, gui):
|
def refresh_gui(self, gui):
|
||||||
gui.search.search_as_you_type(config['search_as_you_type'])
|
gui.search.search_as_you_type(config['search_as_you_type'])
|
||||||
|
self.update_font_display()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from PyQt4.Qt import QApplication
|
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
test_widget('Interface', 'Look & Feel')
|
test_widget('Interface', 'Look & Feel')
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="0" colspan="2">
|
<item row="9" column="0" colspan="2">
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -196,6 +196,20 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<widget class="QLineEdit" name="font_display">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="1">
|
||||||
|
<widget class="QPushButton" name="change_font_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>Change &font (needs restart)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -251,10 +251,28 @@ class Preferences(QMainWindow):
|
|||||||
self.close()
|
self.close()
|
||||||
self.run_wizard_requested.emit()
|
self.run_wizard_requested.emit()
|
||||||
|
|
||||||
|
def set_tooltips_for_labels(self):
|
||||||
|
|
||||||
|
def process_child(child):
|
||||||
|
for g in child.children():
|
||||||
|
if isinstance(g, QLabel):
|
||||||
|
buddy = g.buddy()
|
||||||
|
if buddy is not None and hasattr(buddy, 'toolTip'):
|
||||||
|
htext = unicode(buddy.toolTip()).strip()
|
||||||
|
etext = unicode(g.toolTip()).strip()
|
||||||
|
if htext and not etext:
|
||||||
|
g.setToolTip(htext)
|
||||||
|
g.setWhatsThis(htext)
|
||||||
|
else:
|
||||||
|
process_child(g)
|
||||||
|
|
||||||
|
process_child(self.showing_widget)
|
||||||
|
|
||||||
def show_plugin(self, plugin):
|
def show_plugin(self, plugin):
|
||||||
self.showing_widget = plugin.create_widget(self.scroll_area)
|
self.showing_widget = plugin.create_widget(self.scroll_area)
|
||||||
self.showing_widget.genesis(self.gui)
|
self.showing_widget.genesis(self.gui)
|
||||||
self.showing_widget.initialize()
|
self.showing_widget.initialize()
|
||||||
|
self.set_tooltips_for_labels()
|
||||||
self.scroll_area.setWidget(self.showing_widget)
|
self.scroll_area.setWidget(self.showing_widget)
|
||||||
self.stack.setCurrentIndex(1)
|
self.stack.setCurrentIndex(1)
|
||||||
self.showing_widget.show()
|
self.showing_widget.show()
|
||||||
|
@ -87,6 +87,13 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.setDragDropMode(self.DropOnly)
|
self.setDragDropMode(self.DropOnly)
|
||||||
self.setDropIndicatorShown(True)
|
self.setDropIndicatorShown(True)
|
||||||
self.setAutoExpandDelay(500)
|
self.setAutoExpandDelay(500)
|
||||||
|
self.pane_is_visible = False
|
||||||
|
|
||||||
|
def set_pane_is_visible(self, to_what):
|
||||||
|
pv = self.pane_is_visible
|
||||||
|
self.pane_is_visible = to_what
|
||||||
|
if to_what and not pv:
|
||||||
|
self.recount()
|
||||||
|
|
||||||
def set_database(self, db, tag_match, sort_by):
|
def set_database(self, db, tag_match, sort_by):
|
||||||
self.hidden_categories = config['tag_browser_hidden_categories']
|
self.hidden_categories = config['tag_browser_hidden_categories']
|
||||||
@ -94,6 +101,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
hidden_categories=self.hidden_categories,
|
hidden_categories=self.hidden_categories,
|
||||||
search_restriction=None,
|
search_restriction=None,
|
||||||
drag_drop_finished=self.drag_drop_finished)
|
drag_drop_finished=self.drag_drop_finished)
|
||||||
|
self.pane_is_visible = True # because TagsModel.init did a recount
|
||||||
self.sort_by = sort_by
|
self.sort_by = sort_by
|
||||||
self.tag_match = tag_match
|
self.tag_match = tag_match
|
||||||
self.db = db
|
self.db = db
|
||||||
@ -300,7 +308,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
return self.isExpanded(idx)
|
return self.isExpanded(idx)
|
||||||
|
|
||||||
def recount(self, *args):
|
def recount(self, *args):
|
||||||
if self.disable_recounting:
|
if self.disable_recounting or not self.pane_is_visible:
|
||||||
return
|
return
|
||||||
self.refresh_signal_processed = True
|
self.refresh_signal_processed = True
|
||||||
ci = self.currentIndex()
|
ci = self.currentIndex()
|
||||||
@ -969,6 +977,7 @@ class TagBrowserWidget(QWidget): # {{{
|
|||||||
self._layout.setContentsMargins(0,0,0,0)
|
self._layout.setContentsMargins(0,0,0,0)
|
||||||
|
|
||||||
parent.tags_view = TagsView(parent)
|
parent.tags_view = TagsView(parent)
|
||||||
|
self.tags_view = parent.tags_view
|
||||||
self._layout.addWidget(parent.tags_view)
|
self._layout.addWidget(parent.tags_view)
|
||||||
|
|
||||||
parent.sort_by = QComboBox(parent)
|
parent.sort_by = QComboBox(parent)
|
||||||
@ -998,6 +1007,9 @@ class TagBrowserWidget(QWidget): # {{{
|
|||||||
_('Add your own categories to the Tag Browser'))
|
_('Add your own categories to the Tag Browser'))
|
||||||
parent.edit_categories.setStatusTip(parent.edit_categories.toolTip())
|
parent.edit_categories.setStatusTip(parent.edit_categories.toolTip())
|
||||||
|
|
||||||
|
def set_pane_is_visible(self, to_what):
|
||||||
|
self.tags_view.set_pane_is_visible(to_what)
|
||||||
|
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -234,6 +234,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
|
|
||||||
######################### Search Restriction ##########################
|
######################### Search Restriction ##########################
|
||||||
SearchRestrictionMixin.__init__(self)
|
SearchRestrictionMixin.__init__(self)
|
||||||
|
if db.prefs['gui_restriction']:
|
||||||
self.apply_named_search_restriction(db.prefs['gui_restriction'])
|
self.apply_named_search_restriction(db.prefs['gui_restriction'])
|
||||||
|
|
||||||
########################### Cover Flow ################################
|
########################### Cover Flow ################################
|
||||||
|
@ -19,12 +19,15 @@ def generate_test_db(library_path,
|
|||||||
max_tags=10
|
max_tags=10
|
||||||
):
|
):
|
||||||
import random, string, os, sys, time
|
import random, string, os, sys, time
|
||||||
|
from calibre.constants import preferred_encoding
|
||||||
|
|
||||||
if not os.path.exists(library_path):
|
if not os.path.exists(library_path):
|
||||||
os.makedirs(library_path)
|
os.makedirs(library_path)
|
||||||
|
|
||||||
|
letters = string.letters.decode(preferred_encoding)
|
||||||
|
|
||||||
def randstr(length):
|
def randstr(length):
|
||||||
return ''.join(random.choice(string.letters) for i in
|
return ''.join(random.choice(letters) for i in
|
||||||
xrange(length))
|
xrange(length))
|
||||||
|
|
||||||
all_tags = [randstr(tag_length) for j in xrange(num_of_tags)]
|
all_tags = [randstr(tag_length) for j in xrange(num_of_tags)]
|
||||||
|
@ -10,11 +10,10 @@ import os, sys, shutil, cStringIO, glob, time, functools, traceback, re
|
|||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from math import floor
|
from math import floor
|
||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
from operator import itemgetter
|
|
||||||
|
|
||||||
from PyQt4.QtGui import QImage
|
from PyQt4.QtGui import QImage
|
||||||
|
|
||||||
|
from calibre import prints
|
||||||
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
||||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||||
from calibre.library.database import LibraryDatabase
|
from calibre.library.database import LibraryDatabase
|
||||||
@ -1039,25 +1038,142 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
tn=field['table'], col=field['link_column']), (id_,))
|
tn=field['table'], col=field['link_column']), (id_,))
|
||||||
return set(x[0] for x in ans)
|
return set(x[0] for x in ans)
|
||||||
|
|
||||||
|
########## data structures for get_categories
|
||||||
|
|
||||||
CATEGORY_SORTS = ('name', 'popularity', 'rating')
|
CATEGORY_SORTS = ('name', 'popularity', 'rating')
|
||||||
|
|
||||||
def get_categories(self, sort='name', ids=None, icon_map=None):
|
class TCat_Tag(object):
|
||||||
self.books_list_filter.change([] if not ids else ids)
|
|
||||||
|
|
||||||
categories = {}
|
def __init__(self, name, sort):
|
||||||
|
self.n = name
|
||||||
|
self.s = sort
|
||||||
|
self.c = 0
|
||||||
|
self.rt = 0
|
||||||
|
self.rc = 0
|
||||||
|
self.id = None
|
||||||
|
|
||||||
|
def set_all(self, c, rt, rc, id):
|
||||||
|
self.c = c
|
||||||
|
self.rt = rt
|
||||||
|
self.rc = rc
|
||||||
|
self.id = id
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return unicode(self)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return 'n=%s s=%s c=%d rt=%d rc=%d id=%s'%\
|
||||||
|
(self.n, self.s, self.c, self.rt, self.rc, self.id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_categories(self, sort='name', ids=None, icon_map=None):
|
||||||
|
#start = last = time.clock()
|
||||||
if icon_map is not None and type(icon_map) != TagsIcons:
|
if icon_map is not None and type(icon_map) != TagsIcons:
|
||||||
raise TypeError('icon_map passed to get_categories must be of type TagIcons')
|
raise TypeError('icon_map passed to get_categories must be of type TagIcons')
|
||||||
|
if sort not in self.CATEGORY_SORTS:
|
||||||
|
raise ValueError('sort ' + sort + ' not a valid value')
|
||||||
|
|
||||||
|
self.books_list_filter.change([] if not ids else ids)
|
||||||
|
id_filter = None if not ids else frozenset(ids)
|
||||||
|
|
||||||
tb_cats = self.field_metadata
|
tb_cats = self.field_metadata
|
||||||
#### First, build the standard and custom-column categories ####
|
tcategories = {}
|
||||||
|
tids = {}
|
||||||
|
md = []
|
||||||
|
|
||||||
|
# First, build the maps. We need a category->items map and an
|
||||||
|
# item -> (item_id, sort_val) map to use in the books loop
|
||||||
for category in tb_cats.keys():
|
for category in tb_cats.keys():
|
||||||
cat = tb_cats[category]
|
cat = tb_cats[category]
|
||||||
if not cat['is_category'] or cat['kind'] in ['user', 'search']:
|
if not cat['is_category'] or cat['kind'] in ['user', 'search'] \
|
||||||
|
or category in ['news', 'formats']:
|
||||||
continue
|
continue
|
||||||
|
# Get the ids for the item values
|
||||||
|
if not cat['is_custom']:
|
||||||
|
funcs = {
|
||||||
|
'authors' : self.get_authors_with_ids,
|
||||||
|
'series' : self.get_series_with_ids,
|
||||||
|
'publisher': self.get_publishers_with_ids,
|
||||||
|
'tags' : self.get_tags_with_ids,
|
||||||
|
'rating' : self.get_ratings_with_ids,
|
||||||
|
}
|
||||||
|
func = funcs.get(category, None)
|
||||||
|
if func:
|
||||||
|
list = func()
|
||||||
|
else:
|
||||||
|
raise ValueError(category + ' has no get with ids function')
|
||||||
|
else:
|
||||||
|
list = self.get_custom_items_with_ids(label=cat['label'])
|
||||||
|
tids[category] = {}
|
||||||
|
if category == 'authors':
|
||||||
|
for l in list:
|
||||||
|
(id, val, sort_val) = (l[0], l[1], l[2])
|
||||||
|
tids[category][val] = (id, sort_val)
|
||||||
|
else:
|
||||||
|
for l in list:
|
||||||
|
(id, val) = (l[0], l[1])
|
||||||
|
tids[category][val] = (id, val)
|
||||||
|
# add an empty category to the category map
|
||||||
|
tcategories[category] = {}
|
||||||
|
# create a list of category/field_index for the books scan to use.
|
||||||
|
# This saves iterating through field_metadata for each book
|
||||||
|
md.append((category, cat['rec_index'], cat['is_multiple']))
|
||||||
|
|
||||||
|
#print 'end phase "collection":', time.clock() - last, 'seconds'
|
||||||
|
#last = time.clock()
|
||||||
|
|
||||||
|
# Now scan every book looking for category items.
|
||||||
|
# Code below is duplicated because it shaves off 10% of the loop time
|
||||||
|
id_dex = self.FIELD_MAP['id']
|
||||||
|
rating_dex = self.FIELD_MAP['rating']
|
||||||
|
tag_class = LibraryDatabase2.TCat_Tag
|
||||||
|
for book in self.data.iterall():
|
||||||
|
if id_filter and book[id_dex] not in id_filter:
|
||||||
|
continue
|
||||||
|
rating = book[rating_dex]
|
||||||
|
# We kept track of all possible category field_map positions above
|
||||||
|
for (cat, dex, mult) in md:
|
||||||
|
if book[dex] is None:
|
||||||
|
continue
|
||||||
|
if not mult:
|
||||||
|
val = book[dex]
|
||||||
|
try:
|
||||||
|
(item_id, sort_val) = tids[cat][val] # let exceptions fly
|
||||||
|
item = tcategories[cat].get(val, None)
|
||||||
|
if not item:
|
||||||
|
item = tag_class(val, sort_val)
|
||||||
|
tcategories[cat][val] = item
|
||||||
|
item.c += 1
|
||||||
|
item.id = item_id
|
||||||
|
if rating > 0:
|
||||||
|
item.rt += rating
|
||||||
|
item.rc += 1
|
||||||
|
except:
|
||||||
|
prints('get_categories: item', val, 'is not in', cat, 'list!')
|
||||||
|
else:
|
||||||
|
vals = book[dex].split(mult)
|
||||||
|
for val in vals:
|
||||||
|
try:
|
||||||
|
(item_id, sort_val) = tids[cat][val] # let exceptions fly
|
||||||
|
item = tcategories[cat].get(val, None)
|
||||||
|
if not item:
|
||||||
|
item = tag_class(val, sort_val)
|
||||||
|
tcategories[cat][val] = item
|
||||||
|
item.c += 1
|
||||||
|
item.id = item_id
|
||||||
|
if rating > 0:
|
||||||
|
item.rt += rating
|
||||||
|
item.rc += 1
|
||||||
|
except:
|
||||||
|
prints('get_categories: item', val, 'is not in', cat, 'list!')
|
||||||
|
|
||||||
|
#print 'end phase "books":', time.clock() - last, 'seconds'
|
||||||
|
#last = time.clock()
|
||||||
|
|
||||||
|
# Now do news
|
||||||
|
tcategories['news'] = {}
|
||||||
|
cat = tb_cats['news']
|
||||||
tn = cat['table']
|
tn = cat['table']
|
||||||
categories[category] = [] #reserve the position in the ordered list
|
|
||||||
if tn is None: # Nothing to do for the moment
|
|
||||||
continue
|
|
||||||
cn = cat['column']
|
cn = cat['column']
|
||||||
if ids is None:
|
if ids is None:
|
||||||
query = '''SELECT id, {0}, count, avg_rating, sort
|
query = '''SELECT id, {0}, count, avg_rating, sort
|
||||||
@ -1065,17 +1181,32 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
else:
|
else:
|
||||||
query = '''SELECT id, {0}, count, avg_rating, sort
|
query = '''SELECT id, {0}, count, avg_rating, sort
|
||||||
FROM tag_browser_filtered_{1}'''.format(cn, tn)
|
FROM tag_browser_filtered_{1}'''.format(cn, tn)
|
||||||
if sort == 'popularity':
|
# results will be sorted later
|
||||||
query += ' ORDER BY count DESC, sort ASC'
|
|
||||||
elif sort == 'name':
|
|
||||||
query += ' ORDER BY sort COLLATE icucollate'
|
|
||||||
else:
|
|
||||||
query += ' ORDER BY avg_rating DESC, sort ASC'
|
|
||||||
data = self.conn.get(query)
|
data = self.conn.get(query)
|
||||||
|
for r in data:
|
||||||
|
item = LibraryDatabase2.TCat_Tag(r[1], r[1])
|
||||||
|
item.set_all(c=r[2], rt=r[2]*r[3], rc=r[2], id=r[0])
|
||||||
|
tcategories['news'][r[1]] = item
|
||||||
|
|
||||||
|
#print 'end phase "news":', time.clock() - last, 'seconds'
|
||||||
|
#last = time.clock()
|
||||||
|
|
||||||
|
# Build the real category list by iterating over the temporary copy
|
||||||
|
# and building the Tag instances.
|
||||||
|
categories = {}
|
||||||
|
tag_class = Tag
|
||||||
|
for category in tb_cats.keys():
|
||||||
|
if category not in tcategories:
|
||||||
|
continue
|
||||||
|
cat = tb_cats[category]
|
||||||
|
|
||||||
|
# prepare the place where we will put the array of Tags
|
||||||
|
categories[category] = []
|
||||||
|
|
||||||
# icon_map is not None if get_categories is to store an icon and
|
# icon_map is not None if get_categories is to store an icon and
|
||||||
# possibly a tooltip in the tag structure.
|
# possibly a tooltip in the tag structure.
|
||||||
icon, tooltip = None, ''
|
icon = None
|
||||||
|
tooltip = ''
|
||||||
label = tb_cats.key_to_label(category)
|
label = tb_cats.key_to_label(category)
|
||||||
if icon_map:
|
if icon_map:
|
||||||
if not tb_cats.is_custom_field(category):
|
if not tb_cats.is_custom_field(category):
|
||||||
@ -1087,23 +1218,46 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
tooltip = self.custom_column_label_map[label]['name']
|
tooltip = self.custom_column_label_map[label]['name']
|
||||||
|
|
||||||
datatype = cat['datatype']
|
datatype = cat['datatype']
|
||||||
avgr = itemgetter(3)
|
avgr = lambda x: 0.0 if x.rc == 0 else x.rt/x.rc
|
||||||
item_not_zero_func = lambda x: x[2] > 0
|
# Duplicate the build of items below to avoid using a lambda func
|
||||||
|
# in the main Tag loop. Saves a few %
|
||||||
if datatype == 'rating':
|
if datatype == 'rating':
|
||||||
# eliminate the zero ratings line as well as count == 0
|
|
||||||
item_not_zero_func = (lambda x: x[1] > 0 and x[2] > 0)
|
|
||||||
formatter = (lambda x:u'\u2605'*int(x/2))
|
formatter = (lambda x:u'\u2605'*int(x/2))
|
||||||
avgr = itemgetter(1)
|
avgr = lambda x : x.n
|
||||||
|
# eliminate the zero ratings line as well as count == 0
|
||||||
|
items = [v for v in tcategories[category].values() if v.c > 0 and v.n != 0]
|
||||||
elif category == 'authors':
|
elif category == 'authors':
|
||||||
# Clean up the authors strings to human-readable form
|
# Clean up the authors strings to human-readable form
|
||||||
formatter = (lambda x: x.replace('|', ','))
|
formatter = (lambda x: x.replace('|', ','))
|
||||||
|
items = [v for v in tcategories[category].values() if v.c > 0]
|
||||||
else:
|
else:
|
||||||
formatter = (lambda x:unicode(x))
|
formatter = (lambda x:unicode(x))
|
||||||
|
items = [v for v in tcategories[category].values() if v.c > 0]
|
||||||
|
|
||||||
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
|
# sort the list
|
||||||
avg=avgr(r), sort=r[4], icon=icon,
|
if sort == 'name':
|
||||||
|
def get_sort_key(x):
|
||||||
|
sk = x.s
|
||||||
|
if isinstance(sk, unicode):
|
||||||
|
sk = sort_key(sk)
|
||||||
|
return sk
|
||||||
|
kf = get_sort_key
|
||||||
|
reverse=False
|
||||||
|
elif sort == 'popularity':
|
||||||
|
kf = lambda x: x.c
|
||||||
|
reverse=True
|
||||||
|
else:
|
||||||
|
kf = avgr
|
||||||
|
reverse=True
|
||||||
|
items.sort(key=kf, reverse=reverse)
|
||||||
|
|
||||||
|
categories[category] = [tag_class(formatter(r.n), count=r.c, id=r.id,
|
||||||
|
avg=avgr(r), sort=r.s, icon=icon,
|
||||||
tooltip=tooltip, category=category)
|
tooltip=tooltip, category=category)
|
||||||
for r in data if item_not_zero_func(r)]
|
for r in items]
|
||||||
|
|
||||||
|
#print 'end phase "tags list":', time.clock() - last, 'seconds'
|
||||||
|
#last = time.clock()
|
||||||
|
|
||||||
# Needed for legacy databases that have multiple ratings that
|
# Needed for legacy databases that have multiple ratings that
|
||||||
# map to n stars
|
# map to n stars
|
||||||
@ -1189,8 +1343,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
icon_map['search'] = icon_map['search']
|
icon_map['search'] = icon_map['search']
|
||||||
categories['search'] = items
|
categories['search'] = items
|
||||||
|
|
||||||
|
#print 'last phase ran in:', time.clock() - last, 'seconds'
|
||||||
|
#print 'get_categories ran in:', time.clock() - start, 'seconds'
|
||||||
|
|
||||||
return categories
|
return categories
|
||||||
|
|
||||||
|
############# End get_categories
|
||||||
|
|
||||||
def tags_older_than(self, tag, delta):
|
def tags_older_than(self, tag, delta):
|
||||||
tag = tag.lower().strip()
|
tag = tag.lower().strip()
|
||||||
now = nowf()
|
now = nowf()
|
||||||
@ -1486,6 +1645,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
# Note: we generally do not need to refresh_ids because library_view will
|
# Note: we generally do not need to refresh_ids because library_view will
|
||||||
# refresh everything.
|
# refresh everything.
|
||||||
|
|
||||||
|
def get_ratings_with_ids(self):
|
||||||
|
result = self.conn.get('SELECT id,rating FROM ratings')
|
||||||
|
if not result:
|
||||||
|
return []
|
||||||
|
return result
|
||||||
|
|
||||||
def dirty_books_referencing(self, field, id, commit=True):
|
def dirty_books_referencing(self, field, id, commit=True):
|
||||||
# Get the list of books to dirty -- all books that reference the item
|
# Get the list of books to dirty -- all books that reference the item
|
||||||
table = self.field_metadata[field]['table']
|
table = self.field_metadata[field]['table']
|
||||||
|
@ -119,10 +119,8 @@ class SafeFormat(TemplateFormatter):
|
|||||||
try:
|
try:
|
||||||
b = self.book.get_user_metadata(key, False)
|
b = self.book.get_user_metadata(key, False)
|
||||||
except:
|
except:
|
||||||
if DEBUG:
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
b = None
|
b = None
|
||||||
|
|
||||||
if b is not None and b['datatype'] == 'composite':
|
if b is not None and b['datatype'] == 'composite':
|
||||||
if key in self.composite_values:
|
if key in self.composite_values:
|
||||||
return self.composite_values[key]
|
return self.composite_values[key]
|
||||||
@ -135,7 +133,6 @@ class SafeFormat(TemplateFormatter):
|
|||||||
return val.replace('/', '_').replace('\\', '_')
|
return val.replace('/', '_').replace('\\', '_')
|
||||||
return ''
|
return ''
|
||||||
except:
|
except:
|
||||||
if DEBUG:
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return key
|
return key
|
||||||
|
|
||||||
@ -155,6 +152,8 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
|
|||||||
format_args['tags'] = mi.format_tags()
|
format_args['tags'] = mi.format_tags()
|
||||||
if format_args['tags'].startswith('/'):
|
if format_args['tags'].startswith('/'):
|
||||||
format_args['tags'] = format_args['tags'][1:]
|
format_args['tags'] = format_args['tags'][1:]
|
||||||
|
else:
|
||||||
|
format_args['tags'] = ''
|
||||||
if mi.series:
|
if mi.series:
|
||||||
format_args['series'] = tsfmt(mi.series)
|
format_args['series'] = tsfmt(mi.series)
|
||||||
if mi.series_index is not None:
|
if mi.series_index is not None:
|
||||||
@ -254,6 +253,7 @@ def do_save_book_to_disk(id_, mi, cover, plugboards,
|
|||||||
if not os.path.exists(dirpath):
|
if not os.path.exists(dirpath):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
ocover = mi.cover
|
||||||
if opts.save_cover and cover and os.access(cover, os.R_OK):
|
if opts.save_cover and cover and os.access(cover, os.R_OK):
|
||||||
with open(base_path+'.jpg', 'wb') as f:
|
with open(base_path+'.jpg', 'wb') as f:
|
||||||
with open(cover, 'rb') as s:
|
with open(cover, 'rb') as s:
|
||||||
@ -267,6 +267,8 @@ def do_save_book_to_disk(id_, mi, cover, plugboards,
|
|||||||
with open(base_path+'.opf', 'wb') as f:
|
with open(base_path+'.opf', 'wb') as f:
|
||||||
f.write(opf)
|
f.write(opf)
|
||||||
|
|
||||||
|
mi.cover = ocover
|
||||||
|
|
||||||
written = False
|
written = False
|
||||||
for fmt in formats:
|
for fmt in formats:
|
||||||
global plugboard_save_to_disk_value, plugboard_any_format_value
|
global plugboard_save_to_disk_value, plugboard_any_format_value
|
||||||
|
55
src/calibre/utils/mem.py
Normal file
55
src/calibre/utils/mem.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
## {{{ http://code.activestate.com/recipes/286222/ (r1)
|
||||||
|
import os
|
||||||
|
|
||||||
|
_proc_status = '/proc/%d/status' % os.getpid()
|
||||||
|
|
||||||
|
_scale = {'kB': 1024.0, 'mB': 1024.0*1024.0,
|
||||||
|
'KB': 1024.0, 'MB': 1024.0*1024.0}
|
||||||
|
|
||||||
|
def _VmB(VmKey):
|
||||||
|
'''Private.
|
||||||
|
'''
|
||||||
|
global _proc_status, _scale
|
||||||
|
# get pseudo file /proc/<pid>/status
|
||||||
|
try:
|
||||||
|
t = open(_proc_status)
|
||||||
|
v = t.read()
|
||||||
|
t.close()
|
||||||
|
except:
|
||||||
|
return 0.0 # non-Linux?
|
||||||
|
# get VmKey line e.g. 'VmRSS: 9999 kB\n ...'
|
||||||
|
i = v.index(VmKey)
|
||||||
|
v = v[i:].split(None, 3) # whitespace
|
||||||
|
if len(v) < 3:
|
||||||
|
return 0.0 # invalid format?
|
||||||
|
# convert Vm value to bytes
|
||||||
|
return float(v[1]) * _scale[v[2]]
|
||||||
|
|
||||||
|
|
||||||
|
def memory(since=0.0):
|
||||||
|
'''Return memory usage in bytes.
|
||||||
|
'''
|
||||||
|
return _VmB('VmSize:') - since
|
||||||
|
|
||||||
|
|
||||||
|
def resident(since=0.0):
|
||||||
|
'''Return resident memory usage in bytes.
|
||||||
|
'''
|
||||||
|
return _VmB('VmRSS:') - since
|
||||||
|
|
||||||
|
|
||||||
|
def stacksize(since=0.0):
|
||||||
|
'''Return stack size in bytes.
|
||||||
|
'''
|
||||||
|
return _VmB('VmStk:') - since
|
||||||
|
## end of http://code.activestate.com/recipes/286222/ }}}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user