mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
GwR patches for 'change_justification'
This commit is contained in:
commit
a8db54b6fa
BIN
resources/images/news/kurier.png
Normal file
BIN
resources/images/news/kurier.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 658 B |
BIN
resources/images/news/virtualshackles.png
Normal file
BIN
resources/images/news/virtualshackles.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
@ -119,5 +119,7 @@ class Guardian(BasicNewsRecipe):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def postprocess_html(self,soup,first):
|
||||
return soup.findAll('html')[0]
|
||||
|
||||
|
||||
|
50
resources/recipes/kurier.recipe
Normal file
50
resources/recipes/kurier.recipe
Normal file
@ -0,0 +1,50 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
kurier.at
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Kurier(BasicNewsRecipe):
|
||||
title = 'Kurier'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'News from Austria'
|
||||
publisher = 'KURIER'
|
||||
category = 'news, politics, Austria'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'cp1252'
|
||||
use_embedded_content = False
|
||||
language = 'de_AT'
|
||||
remove_empty_feeds = True
|
||||
publication_type = 'newspaper'
|
||||
extra_css = ' body{font-family: Verdana,Helvetica,sans-serif } img{margin-bottom: 0.4em} .bild_us{font-size: x-small} '
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
remove_tags = [dict(attrs={'class':['contenttabs','drucken','versenden','leserbrief','kommentieren','addthis_button']})]
|
||||
keep_only_tags = [dict(attrs={'id':'content'})]
|
||||
remove_tags_after = dict(attrs={'id':'author'})
|
||||
remove_attributes = ['width','height']
|
||||
|
||||
feeds = [
|
||||
(u'Nachrichten', u'http://kurier.at/rss/nachrichten_nachrichten_rss.xml' )
|
||||
,(u'Techno' , u'http://kurier.at/rss/techno_techno_rss.xml' )
|
||||
,(u'Wirtschaft' , u'http://kurier.at/rss/wirtschaft_wirtschaft_rss.xml' )
|
||||
,(u'Kultur' , u'http://kurier.at/rss/kultur_kultur_rss.xml' )
|
||||
,(u'Freizeit' , u'http://kurier.at/rss/freizeit_freizeit_rss.xml' )
|
||||
,(u'Wetter' , u'http://kurier.at/rss/oewetter_rss.xml' )
|
||||
,(u'Verkehr' , u'http://kurier.at/rss/verkehr_rss.xml' )
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return self.adeify_images(soup)
|
@ -22,21 +22,36 @@ class LaRepublica(BasicNewsRecipe):
|
||||
language = 'it'
|
||||
timefmt = '[%a, %d %b, %Y]'
|
||||
|
||||
oldest_article = 1
|
||||
oldest_article = 5
|
||||
max_articles_per_feed = 100
|
||||
use_embedded_content = False
|
||||
recursion = 10
|
||||
|
||||
remove_javascript = True
|
||||
def get_article_url(self, article):
|
||||
link = article.get('id', article.get('guid', None))
|
||||
if link is None:
|
||||
return article
|
||||
return link
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'articolo'}),
|
||||
dict(name='div', attrs={'class':'body-text'}),
|
||||
dict(name='div', attrs={'class':'page-content'}),
|
||||
dict(name='div', attrs={'id':'contA'})
|
||||
]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'articolo'})]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['object','link']),
|
||||
dict(name='span',attrs={'class':'linkindice'}),
|
||||
dict(name='div',attrs={'class':'bottom-mobile'}),
|
||||
dict(name='div',attrs={'id':['rssdiv','blocco']})
|
||||
dict(name='div', attrs={'class':'bottom-mobile'}),
|
||||
dict(name='div', attrs={'id':['rssdiv','blocco']}),
|
||||
dict(name='div', attrs={'class':'utility'}),
|
||||
dict(name='div', attrs={'class':'generalbox'})
|
||||
]
|
||||
remove_tags_after = [
|
||||
dict(name='div',attrs={'id':'ugc_linkUpload'})
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'Repubblica Rilievo', u'http://www.repubblica.it/rss/homepage/rss2.0.xml'),
|
||||
@ -48,8 +63,12 @@ class LaRepublica(BasicNewsRecipe):
|
||||
(u'Repubblica Tecnologia', u'http://www.repubblica.it/rss/tecnologia/rss2.0.xml'),
|
||||
(u'Repubblica Scuola e Universita', u'http://www.repubblica.it/rss/scuola_e_universita/rss2.0.xml'),
|
||||
(u'Repubblica Ambiente', u'http://www.repubblica.it/rss/ambiente/rss2.0.xml'),
|
||||
(u'Repubblica Cultura', u'http://www.repubblica.it/rss/spettacoli_e_cultura/rss2.0.xml'),
|
||||
(u'Repubblica Persone', u'http://www.repubblica.it/rss/persone/rss2.0.xml'),
|
||||
(u'Repubblica Sport', u'http://www.repubblica.it/rss/sport/rss2.0.xml'),
|
||||
(u'Repubblica Calcio', u'http://www.repubblica.it/rss/sport/calcio/rss2.0.xml')
|
||||
]
|
||||
(u'Repubblica Cultura', u'http://www.repubblica.it/rss/spettacoli_e_cultura/rss2.0.xml'),
|
||||
(u'Repubblica Persone', u'http://www.repubblica.it/rss/persone/rss2.0.xml'),
|
||||
(u'Repubblica Sport', u'http://www.repubblica.it/rss/sport/rss2.0.xml'),
|
||||
(u'Repubblica Calcio', u'http://www.repubblica.it/rss/sport/calcio/rss2.0.xml'),
|
||||
(u'Repubblica Motori', u'http://www.repubblica.it/rss/motori/rss2.0.xml'),
|
||||
(u'Repubblica Roma', u'http://roma.repubblica.it/rss/rss2.0.xml'),
|
||||
(u'Repubblica Torino', u'http://torino.repubblica.it/rss/rss2.0.xml')
|
||||
]
|
||||
|
||||
|
18
resources/recipes/npr_music_blogs.recipe
Normal file
18
resources/recipes/npr_music_blogs.recipe
Normal file
@ -0,0 +1,18 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class nprmusic(BasicNewsRecipe):
|
||||
title = 'NPR Music Blogs'
|
||||
__author__ = 'cix3'
|
||||
timefmt = ' [%b %d, %Y]'
|
||||
language = 'en'
|
||||
|
||||
oldest_article = 30
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'id':['logo', 'comments', 'related_objects', 'inset module', 'footer', 'strip_control', 'header', 'navigation']}), dict(name='hr'), dict(name='img')]
|
||||
|
||||
feeds = [
|
||||
('A Blog Supreme', 'http://www.npr.org/blogs/ablogsupreme/index.xml'),
|
||||
('All Songs Considered', 'http://www.npr.org/blogs/allsongs/index.xml'),
|
||||
('Monitor Mix', 'http://www.npr.org/blogs/monitormix/index.xml')]
|
@ -377,8 +377,9 @@ class USAToday(BasicNewsRecipe):
|
||||
if byline:
|
||||
byline['class'] = 'byline'
|
||||
# Replace comma with middot
|
||||
byline.contents[0].replaceWith(re.sub(","," ·", byline.renderContents()))
|
||||
return byline.renderContents()
|
||||
byline.contents[0].replaceWith(re.sub(u",", u" ·",
|
||||
byline.renderContents(encoding=None)))
|
||||
return byline.renderContents(encoding=None)
|
||||
else :
|
||||
paras = soup.findAll(text=True)
|
||||
for para in paras:
|
||||
|
33
resources/recipes/virtualshackles.recipe
Normal file
33
resources/recipes/virtualshackles.recipe
Normal file
@ -0,0 +1,33 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.virtualshackles.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class Virtualshackles(BasicNewsRecipe):
|
||||
title = 'Virtual Shackles'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = "The adventures of Orion and Jack, making games they'd never play for people they don't like."
|
||||
category = 'virtual shackles, virtualshackles, games, webcomic, comic, video game, orion, jack'
|
||||
oldest_article = 10
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = True
|
||||
encoding = 'cp1252'
|
||||
publisher = 'Virtual Shackles'
|
||||
language = 'en'
|
||||
publication_type = 'comic'
|
||||
|
||||
conversion_options = {
|
||||
'comments' : description
|
||||
,'tags' : category
|
||||
,'language' : language
|
||||
,'publisher' : publisher
|
||||
}
|
||||
|
||||
feeds = [(u'Virtual Shackles', u'http://feeds2.feedburner.com/virtualshackles' )]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
return self.adeify_images(soup)
|
@ -399,38 +399,49 @@ def my_unichr(num):
|
||||
except ValueError:
|
||||
return u'?'
|
||||
|
||||
def entity_to_unicode(match, exceptions=[], encoding='cp1252'):
|
||||
def entity_to_unicode(match, exceptions=[], encoding='cp1252',
|
||||
result_exceptions={}):
|
||||
'''
|
||||
@param match: A match object such that '&'+match.group(1)';' is the entity.
|
||||
@param exceptions: A list of entities to not convert (Each entry is the name of the entity, for e.g. 'apos' or '#1234'
|
||||
@param encoding: The encoding to use to decode numeric entities between 128 and 256.
|
||||
:param match: A match object such that '&'+match.group(1)';' is the entity.
|
||||
|
||||
:param exceptions: A list of entities to not convert (Each entry is the name of the entity, for e.g. 'apos' or '#1234'
|
||||
|
||||
:param encoding: The encoding to use to decode numeric entities between 128 and 256.
|
||||
If None, the Unicode UCS encoding is used. A common encoding is cp1252.
|
||||
|
||||
:param result_exceptions: A mapping of characters to entities. If the result
|
||||
is in result_exceptions, result_exception[result] is returned instead.
|
||||
Convenient way to specify exception for things like < or > that can be
|
||||
specified by various actual entities.
|
||||
'''
|
||||
def check(ch):
|
||||
return result_exceptions.get(ch, ch)
|
||||
|
||||
ent = match.group(1)
|
||||
if ent in exceptions:
|
||||
return '&'+ent+';'
|
||||
if ent == 'apos':
|
||||
return "'"
|
||||
return check("'")
|
||||
if ent == 'hellips':
|
||||
ent = 'hellip'
|
||||
if ent.startswith(u'#x'):
|
||||
if ent.lower().startswith(u'#x'):
|
||||
num = int(ent[2:], 16)
|
||||
if encoding is None or num > 255:
|
||||
return my_unichr(num)
|
||||
return chr(num).decode(encoding)
|
||||
return check(my_unichr(num))
|
||||
return check(chr(num).decode(encoding))
|
||||
if ent.startswith(u'#'):
|
||||
try:
|
||||
num = int(ent[1:])
|
||||
except ValueError:
|
||||
return '&'+ent+';'
|
||||
if encoding is None or num > 255:
|
||||
return my_unichr(num)
|
||||
return check(my_unichr(num))
|
||||
try:
|
||||
return chr(num).decode(encoding)
|
||||
return check(chr(num).decode(encoding))
|
||||
except UnicodeDecodeError:
|
||||
return my_unichr(num)
|
||||
return check(my_unichr(num))
|
||||
try:
|
||||
return my_unichr(name2codepoint[ent])
|
||||
return check(my_unichr(name2codepoint[ent]))
|
||||
except KeyError:
|
||||
return '&'+ent+';'
|
||||
|
||||
|
@ -444,7 +444,7 @@ from calibre.devices.eslick.driver import ESLICK
|
||||
from calibre.devices.nuut2.driver import NUUT2
|
||||
from calibre.devices.iriver.driver import IRIVER_STORY
|
||||
from calibre.devices.binatone.driver import README
|
||||
from calibre.devices.hanvon.driver import N516, EB511
|
||||
from calibre.devices.hanvon.driver import N516, EB511, ALEX
|
||||
from calibre.devices.edge.driver import EDGE
|
||||
from calibre.devices.teclast.driver import TECLAST_K3
|
||||
from calibre.devices.sne.driver import SNE
|
||||
@ -526,7 +526,8 @@ plugins += [
|
||||
ELONEX,
|
||||
TECLAST_K3,
|
||||
EDGE,
|
||||
SNE
|
||||
SNE,
|
||||
ALEX
|
||||
]
|
||||
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||
x.__name__.endswith('MetadataReader')]
|
||||
|
@ -34,6 +34,22 @@ class N516(USBMS):
|
||||
EBOOK_DIR_MAIN = 'e_book'
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
|
||||
class ALEX(N516):
|
||||
|
||||
name = 'Alex driver'
|
||||
gui_name = 'SpringDesign Alex'
|
||||
description = _('Communicate with the SpringDesign Alex eBook reader.')
|
||||
author = 'Kovid Goyal'
|
||||
|
||||
FORMATS = ['epub', 'pdf']
|
||||
VENDOR_NAME = 'ALEX'
|
||||
WINDOWS_MAIN_MEM = 'READER'
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = 'Alex Internal Memory'
|
||||
|
||||
EBOOK_DIR_MAIN = 'eBooks'
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
|
||||
class EB511(USBMS):
|
||||
name = 'Elonex EB 511 driver'
|
||||
gui_name = 'EB 511'
|
||||
|
@ -322,7 +322,7 @@ class ComicInput(InputFormatPlugin):
|
||||
('margin_bottom', 0, OptionRecommendation.HIGH),
|
||||
('insert_blank_line', False, OptionRecommendation.HIGH),
|
||||
('remove_paragraph_spacing', False, OptionRecommendation.HIGH),
|
||||
('dont_justify', True, OptionRecommendation.HIGH),
|
||||
('change_justification', 'left', OptionRecommendation.HIGH),
|
||||
('dont_split_on_pagebreaks', True, OptionRecommendation.HIGH),
|
||||
('chapter', None, OptionRecommendation.HIGH),
|
||||
('page_breaks_brefore', None, OptionRecommendation.HIGH),
|
||||
|
@ -124,7 +124,7 @@ def add_pipeline_options(parser, plumber):
|
||||
'linearize_tables',
|
||||
'extra_css',
|
||||
'margin_top', 'margin_left', 'margin_right',
|
||||
'margin_bottom', 'dont_justify',
|
||||
'margin_bottom', 'change_justification',
|
||||
'insert_blank_line', 'remove_paragraph_spacing','remove_paragraph_spacing_indent_size',
|
||||
'asciiize', 'remove_header', 'header_regex',
|
||||
'remove_footer', 'footer_regex',
|
||||
|
@ -299,12 +299,13 @@ OptionRecommendation(name='margin_right',
|
||||
help=_('Set the right margin in pts. Default is %default. '
|
||||
'Note: 72 pts equals 1 inch')),
|
||||
|
||||
OptionRecommendation(name='dont_justify',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Do not force text to be justified in output. Whether text '
|
||||
'is actually displayed justified or not depends on whether '
|
||||
'the ebook format and reading device support justification.')
|
||||
),
|
||||
OptionRecommendation(name='change_justification',
|
||||
recommended_value='original', level=OptionRecommendation.LOW,
|
||||
choices=['left','justify','original'],
|
||||
help=_('Specify optional justification override. A value of '
|
||||
'"left" or "justify" overrides default justification.'
|
||||
'A value of '
|
||||
'"original" uses existing alignment.')),
|
||||
|
||||
OptionRecommendation(name='remove_paragraph_spacing',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
|
@ -130,7 +130,7 @@ class LRFOutput(OutputFormatPlugin):
|
||||
])
|
||||
|
||||
recommendations = set([
|
||||
('dont_justify', True, OptionRecommendation.HIGH),
|
||||
('change_justification', 'original', OptionRecommendation.HIGH),
|
||||
])
|
||||
|
||||
def convert_images(self, pages, opts, wide):
|
||||
|
@ -303,7 +303,12 @@ class MobiReader(object):
|
||||
for pat in ENCODING_PATS:
|
||||
self.processed_html = pat.sub('', self.processed_html)
|
||||
e2u = functools.partial(entity_to_unicode,
|
||||
exceptions=['lt', 'gt', 'amp', 'apos', 'quot', '#60', '#62'])
|
||||
result_exceptions={
|
||||
'<' : u'<',
|
||||
'>' : u'>',
|
||||
'&' : u'&',
|
||||
'"' : u'"',
|
||||
"'" : u'''})
|
||||
self.processed_html = re.sub(r'&(\S+?);', e2u,
|
||||
self.processed_html)
|
||||
self.extract_images(processed_records, output_dir)
|
||||
@ -619,6 +624,7 @@ class MobiReader(object):
|
||||
opf.cover = None
|
||||
|
||||
cover = opf.cover
|
||||
cover_copied = None
|
||||
if cover is not None:
|
||||
cover = cover.replace('/', os.sep)
|
||||
if os.path.exists(cover):
|
||||
@ -626,13 +632,19 @@ class MobiReader(object):
|
||||
if os.path.exists(ncover):
|
||||
os.remove(ncover)
|
||||
shutil.copyfile(cover, ncover)
|
||||
opf.cover = ncover.replace(os.sep, '/')
|
||||
cover_copied = os.path.abspath(ncover)
|
||||
opf.cover = ncover.replace(os.sep, '/')
|
||||
|
||||
manifest = [(htmlfile, 'application/xhtml+xml'),
|
||||
(os.path.abspath('styles.css'), 'text/css')]
|
||||
bp = os.path.dirname(htmlfile)
|
||||
added = set([])
|
||||
for i in getattr(self, 'image_names', []):
|
||||
manifest.append((os.path.join(bp, 'images/', i), 'image/jpeg'))
|
||||
path = os.path.join(bp, 'images', i)
|
||||
added.add(path)
|
||||
manifest.append((path, 'image/jpeg'))
|
||||
if cover_copied is not None:
|
||||
manifest.append((cover_copied, 'image/jpeg'))
|
||||
|
||||
opf.create_manifest(manifest)
|
||||
opf.create_spine([os.path.basename(htmlfile)])
|
||||
|
@ -212,11 +212,12 @@ class EbookIterator(object):
|
||||
|
||||
cover = self.opf.cover
|
||||
if self.ebook_ext in ('lit', 'mobi', 'prc', 'opf') and cover:
|
||||
cfile = os.path.join(os.path.dirname(self.spine[0]),
|
||||
'calibre_iterator_cover.html')
|
||||
chtml = (TITLEPAGE%cover).encode('utf-8')
|
||||
cfile = os.path.join(self.base, 'calibre_iterator_cover.html')
|
||||
chtml = (TITLEPAGE%os.path.relpath(cover, self.base).replace(os.sep,
|
||||
'/')).encode('utf-8')
|
||||
open(cfile, 'wb').write(chtml)
|
||||
self.spine[0:0] = [SpineItem(cfile)]
|
||||
self.spine[0:0] = [SpineItem(cfile,
|
||||
mime_type='application/xhtml+xml')]
|
||||
self.delete_on_exit.append(cfile)
|
||||
|
||||
if self.opf.path_to_html_toc is not None and \
|
||||
|
@ -318,8 +318,8 @@ class Stylizer(object):
|
||||
if text == 'inherit':
|
||||
style['text-align'] = 'inherit'
|
||||
else:
|
||||
if text in ('left', 'justify'):
|
||||
val = 'left' if self.opts.dont_justify else 'justify'
|
||||
if text in ('left', 'justify') and self.opts.change_justification in ('left', 'justify'):
|
||||
val = self.opts.change_justification
|
||||
style['text-align'] = val
|
||||
else:
|
||||
style['text-align'] = text
|
||||
|
@ -138,8 +138,8 @@ class CSSFlattener(object):
|
||||
float(self.context.margin_left))
|
||||
bs.append('margin-right : %fpt'%\
|
||||
float(self.context.margin_right))
|
||||
bs.append('text-align: '+ \
|
||||
('left' if self.context.dont_justify else 'justify'))
|
||||
if self.context.change_justification != 'original':
|
||||
bs.append('text-align: '+ self.context.change_justification)
|
||||
body.set('style', '; '.join(bs))
|
||||
stylizer = Stylizer(html, item.href, self.oeb, self.context, profile,
|
||||
user_css=self.context.extra_css,
|
||||
|
@ -19,7 +19,7 @@ class LookAndFeelWidget(Widget, Ui_Form):
|
||||
|
||||
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
||||
Widget.__init__(self, parent, 'look_and_feel',
|
||||
['dont_justify', 'extra_css', 'base_font_size',
|
||||
['change_justification', 'extra_css', 'base_font_size',
|
||||
'font_size_mapping', 'line_height',
|
||||
'linearize_tables',
|
||||
'disable_font_rescaling', 'insert_blank_line',
|
||||
|
@ -84,7 +84,7 @@
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/images/wizard.svg</normaloff>:/images/wizard.svg</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
@ -181,21 +181,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="opt_insert_blank_line">
|
||||
<property name="text">
|
||||
<string>Insert &blank line</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QCheckBox" name="opt_dont_justify">
|
||||
<property name="text">
|
||||
<string>No text &justification</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QCheckBox" name="opt_linearize_tables">
|
||||
<property name="text">
|
||||
<string>&Linearize tables</string>
|
||||
@ -221,6 +207,42 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QCheckBox" name="opt_insert_blank_line">
|
||||
<property name="text">
|
||||
<string>Insert &blank line</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Text justification:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<widget class="QComboBox" name="opt_change_justification">
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>justify</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>left</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>original</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
|
@ -29,6 +29,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
self.recipe_model.do_refresh()
|
||||
|
||||
self.search = SearchBox2(self)
|
||||
self.search.setMinimumContentsLength(25)
|
||||
self.search.initialize('scheduler_search_history')
|
||||
self.recipe_box.layout().insertWidget(0, self.search)
|
||||
self.connect(self.search, SIGNAL('search(PyQt_PyObject,PyQt_PyObject)'),
|
||||
|
@ -7,9 +7,9 @@ from math import cos, sin, pi
|
||||
from contextlib import closing
|
||||
|
||||
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
|
||||
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
|
||||
QPen, QStyle, QPainter, \
|
||||
QImage, QApplication, QMenu, \
|
||||
QPainterPath, QLinearGradient, QBrush, \
|
||||
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
|
||||
QImage, QMenu, \
|
||||
QStyledItemDelegate, QCompleter
|
||||
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, pyqtSignal, \
|
||||
SIGNAL, QObject, QSize, QModelIndex, QDate
|
||||
@ -28,14 +28,15 @@ from calibre.ebooks.metadata import string_to_authors, fmt_sidx, \
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
|
||||
|
||||
class LibraryDelegate(QItemDelegate):
|
||||
class LibraryDelegate(QStyledItemDelegate):
|
||||
COLOR = QColor("blue")
|
||||
SIZE = 16
|
||||
PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
|
||||
|
||||
def __init__(self, parent):
|
||||
QItemDelegate.__init__(self, parent)
|
||||
QStyledItemDelegate.__init__(self, parent)
|
||||
self._parent = parent
|
||||
self.dummy = QModelIndex()
|
||||
self.star_path = QPainterPath()
|
||||
self.star_path.moveTo(90, 50)
|
||||
for i in range(1, 5):
|
||||
@ -54,6 +55,9 @@ class LibraryDelegate(QItemDelegate):
|
||||
return QSize(5*(self.SIZE), self.SIZE+4)
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
style = self._parent.style()
|
||||
option = QStyleOptionViewItemV4(option)
|
||||
self.initStyleOption(option, self.dummy)
|
||||
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
||||
def draw_star():
|
||||
painter.save()
|
||||
@ -66,11 +70,10 @@ class LibraryDelegate(QItemDelegate):
|
||||
|
||||
painter.save()
|
||||
if hasattr(QStyle, 'CE_ItemViewItem'):
|
||||
QApplication.style().drawControl(QStyle.CE_ItemViewItem, option,
|
||||
style.drawControl(QStyle.CE_ItemViewItem, option,
|
||||
painter, self._parent)
|
||||
elif option.state & QStyle.State_Selected:
|
||||
painter.fillRect(option.rect, option.palette.highlight())
|
||||
self.drawFocus(painter, option, option.rect)
|
||||
try:
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
painter.setClipRect(option.rect)
|
||||
@ -89,7 +92,7 @@ class LibraryDelegate(QItemDelegate):
|
||||
painter.restore()
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
sb = QItemDelegate.createEditor(self, parent, option, index)
|
||||
sb = QStyledItemDelegate.createEditor(self, parent, option, index)
|
||||
sb.setMinimum(0)
|
||||
sb.setMaximum(5)
|
||||
return sb
|
||||
|
@ -194,6 +194,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.tool_bar2.insertSeparator(self.action_find_next)
|
||||
self.setFocusPolicy(Qt.StrongFocus)
|
||||
self.search = SearchBox2(self)
|
||||
self.search.setMinimumContentsLength(20)
|
||||
self.search.initialize('viewer_search_history')
|
||||
self.search.setToolTip(_('Search for text in book'))
|
||||
self.search.setMinimumWidth(200)
|
||||
|
@ -551,7 +551,8 @@ class LineEditECM(object):
|
||||
self.setText(unicode(self.text()).swapcase())
|
||||
|
||||
def title_case(self):
|
||||
self.setText(unicode(self.text()).title())
|
||||
from calibre.utils.titlecase import titlecase
|
||||
self.setText(titlecase(unicode(self.text())))
|
||||
|
||||
|
||||
class EnLineEdit(LineEditECM, QLineEdit):
|
||||
|
@ -123,10 +123,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
self.is_case_sensitive = not iswindows and not isosx and \
|
||||
not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB'))
|
||||
SchemaUpgrade.__init__(self)
|
||||
CustomColumns.__init__(self)
|
||||
self.initialize_dynamic()
|
||||
|
||||
def initialize_dynamic(self):
|
||||
CustomColumns.__init__(self)
|
||||
template = '''\
|
||||
(SELECT {query} FROM books_{table}_link AS link INNER JOIN
|
||||
{table} ON(link.{link_col}={table}.id) WHERE link.book=books.id)
|
||||
@ -1428,6 +1428,7 @@ books_series_link feeds
|
||||
os.remove(self.dbpath)
|
||||
shutil.copyfile(dest, self.dbpath)
|
||||
self.connect()
|
||||
self.initialize_dynamic()
|
||||
self.refresh()
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
|
@ -81,7 +81,7 @@ Device Integration
|
||||
|
||||
What devices does |app| support?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
At the moment |app| has full support for the SONY PRS 300/500/505/600/700/900, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle 1/2/DX, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, various Android phones and the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
|
||||
At the moment |app| has full support for the SONY PRS 300/500/505/600/700/900, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle 1/2/DX, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, SpringDesign Alex, various Android phones and the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
|
||||
|
||||
How can I help get my device supported in |app|?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
94
src/calibre/utils/titlecase.py
Executable file
94
src/calibre/utils/titlecase.py
Executable file
@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Original Perl version by: John Gruber http://daringfireball.net/ 10 May 2008
|
||||
Python version by Stuart Colville http://muffinresearch.co.uk
|
||||
License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
__all__ = ['titlecase']
|
||||
__version__ = '0.5'
|
||||
|
||||
SMALL = 'a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|v\.?|via|vs\.?'
|
||||
PUNCT = r"""!"#$%&'‘()*+,\-./:;?@[\\\]_`{|}~"""
|
||||
|
||||
SMALL_WORDS = re.compile(r'^(%s)$' % SMALL, re.I)
|
||||
INLINE_PERIOD = re.compile(r'[a-z][.][a-z]', re.I)
|
||||
UC_ELSEWHERE = re.compile(r'[%s]*?[a-zA-Z]+[A-Z]+?' % PUNCT)
|
||||
CAPFIRST = re.compile(r"^[%s]*?([A-Za-z])" % PUNCT)
|
||||
SMALL_FIRST = re.compile(r'^([%s]*)(%s)\b' % (PUNCT, SMALL), re.I)
|
||||
SMALL_LAST = re.compile(r'\b(%s)[%s]?$' % (SMALL, PUNCT), re.I)
|
||||
SUBPHRASE = re.compile(r'([:.;?!][ ])(%s)' % SMALL)
|
||||
APOS_SECOND = re.compile(r"^[dol]{1}['‘]{1}[a-z]+$", re.I)
|
||||
ALL_CAPS = re.compile(r'^[A-Z\s%s]+$' % PUNCT)
|
||||
UC_INITIALS = re.compile(r"^(?:[A-Z]{1}\.{1}|[A-Z]{1}\.{1}[A-Z]{1})+$")
|
||||
MAC_MC = re.compile(r"^([Mm]a?c)(\w+)")
|
||||
|
||||
def titlecase(text):
|
||||
|
||||
"""
|
||||
Titlecases input text
|
||||
|
||||
This filter changes all words to Title Caps, and attempts to be clever
|
||||
about *un*capitalizing SMALL words like a/an/the in the input.
|
||||
|
||||
The list of "SMALL words" which are not capped comes from
|
||||
the New York Times Manual of Style, plus 'vs' and 'v'.
|
||||
|
||||
"""
|
||||
|
||||
all_caps = ALL_CAPS.match(text)
|
||||
|
||||
words = re.split('\s', text)
|
||||
line = []
|
||||
for word in words:
|
||||
if all_caps:
|
||||
if UC_INITIALS.match(word):
|
||||
line.append(word)
|
||||
continue
|
||||
else:
|
||||
word = word.lower()
|
||||
|
||||
if APOS_SECOND.match(word):
|
||||
word = word.replace(word[0], word[0].upper())
|
||||
word = word.replace(word[2], word[2].upper())
|
||||
line.append(word)
|
||||
continue
|
||||
if INLINE_PERIOD.search(word) or UC_ELSEWHERE.match(word):
|
||||
line.append(word)
|
||||
continue
|
||||
if SMALL_WORDS.match(word):
|
||||
line.append(word.lower())
|
||||
continue
|
||||
|
||||
match = MAC_MC.match(word)
|
||||
if match:
|
||||
line.append("%s%s" % (match.group(1).capitalize(),
|
||||
match.group(2).capitalize()))
|
||||
continue
|
||||
|
||||
hyphenated = []
|
||||
for item in word.split('-'):
|
||||
hyphenated.append(CAPFIRST.sub(lambda m: m.group(0).upper(), item))
|
||||
line.append("-".join(hyphenated))
|
||||
|
||||
|
||||
result = " ".join(line)
|
||||
|
||||
result = SMALL_FIRST.sub(lambda m: '%s%s' % (
|
||||
m.group(1),
|
||||
m.group(2).capitalize()
|
||||
), result)
|
||||
|
||||
result = SMALL_LAST.sub(lambda m: m.group(0).capitalize(), result)
|
||||
|
||||
result = SUBPHRASE.sub(lambda m: '%s%s' % (
|
||||
m.group(1),
|
||||
m.group(2).capitalize()
|
||||
), result)
|
||||
|
||||
return result
|
||||
|
@ -113,7 +113,7 @@ class NewsItem(NewsTreeItem):
|
||||
return NONE
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.title, getattr(other, 'title', ''))
|
||||
return cmp(self.title.lower(), getattr(other, 'title', '').lower())
|
||||
|
||||
class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user