mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
969feb39c3
@ -1,4 +1,5 @@
|
|||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from calibre.ebooks.BeautifulSoup import Tag
|
||||||
import re
|
import re
|
||||||
|
|
||||||
class NatureNews(BasicNewsRecipe):
|
class NatureNews(BasicNewsRecipe):
|
||||||
@ -10,17 +11,76 @@ class NatureNews(BasicNewsRecipe):
|
|||||||
max_articles_per_feed = 50
|
max_articles_per_feed = 50
|
||||||
|
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
remove_tags_before = dict(name='h1', attrs={'class':'heading entry-title'})
|
keep_only_tags = [dict(name='div', attrs={'id':'content'})]
|
||||||
remove_tags_after = dict(name='h2', attrs={'id':'comments'})
|
# remove_tags_before = dict(name='h1', attrs={'class':'heading entry-title'})
|
||||||
|
# remove_tags_after = dict(name='h2', attrs={'id':'comments'})
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name='h2', attrs={'id':'comments'}),
|
dict(name='h2', attrs={'id':'comments'}),
|
||||||
dict(attrs={'alt':'Advertisement'}),
|
dict(attrs={'alt':'Advertisement'}),
|
||||||
dict(name='div', attrs={'class':'ad'}),
|
dict(name='div', attrs={'class':'ad'}),
|
||||||
]
|
dict(attrs={'class':'Z3988'}),
|
||||||
|
dict(attrs={'class':['formatpublished','type-of-article','cleardiv','disclaimer','buttons','comments xoxo']}),
|
||||||
|
dict(name='a', attrs={'href':'#comments'}),
|
||||||
|
dict(name='h2',attrs={'class':'subheading plusicon icon-add-comment'})
|
||||||
|
]
|
||||||
|
|
||||||
preprocess_regexps = [
|
preprocess_regexps = [
|
||||||
(re.compile(r'<p>ADVERTISEMENT</p>', re.DOTALL|re.IGNORECASE), lambda match: ''),
|
(re.compile(r'<p>ADVERTISEMENT</p>', re.DOTALL|re.IGNORECASE), lambda match: ''),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
extra_css = '''
|
||||||
|
.author { text-align: right; font-size: small; line-height:1em; margin-top:0px; margin-left:0; margin-right:0; margin-bottom: 0; }
|
||||||
|
.imagedescription { font-size: small; font-style:italic; line-height:1em; margin-top:5px; margin-left:0; margin-right:0; margin-bottom: 0; }
|
||||||
|
.imagecredit { font-size: x-small; font-style: normal; font-weight: bold}
|
||||||
|
'''
|
||||||
|
|
||||||
feeds = [('Nature News', 'http://feeds.nature.com/news/rss/most_recent')]
|
feeds = [('Nature News', 'http://feeds.nature.com/news/rss/most_recent')]
|
||||||
|
|
||||||
|
def preprocess_html(self,soup):
|
||||||
|
# The author name is slightly buried - dig it up
|
||||||
|
author = soup.find('p', {'class':'byline'})
|
||||||
|
if author:
|
||||||
|
# Find out the author's name
|
||||||
|
authornamediv = author.find('span',{'class':'author fn'})
|
||||||
|
authornamelink = authornamediv.find('a')
|
||||||
|
if authornamelink:
|
||||||
|
authorname = authornamelink.contents[0]
|
||||||
|
else:
|
||||||
|
authorname = authornamediv.contents[0]
|
||||||
|
# Stick the author's name in the byline tag
|
||||||
|
tag = Tag(soup,'div')
|
||||||
|
tag['class'] = 'author'
|
||||||
|
tag.insert(0,authorname.strip())
|
||||||
|
author.replaceWith(tag)
|
||||||
|
|
||||||
|
# Change the intro from a p to a div
|
||||||
|
intro = soup.find('p',{'class':'intro'})
|
||||||
|
if intro:
|
||||||
|
tag = Tag(soup,'div')
|
||||||
|
tag['class'] = 'intro'
|
||||||
|
tag.insert(0,intro.contents[0])
|
||||||
|
intro.replaceWith(tag)
|
||||||
|
|
||||||
|
# Change span class=imagedescription to div
|
||||||
|
descr = soup.find('span',{'class':'imagedescription'})
|
||||||
|
if descr:
|
||||||
|
tag = Tag(soup,'div')
|
||||||
|
tag['class'] = 'imagedescription'
|
||||||
|
tag.insert(0,descr.renderContents())
|
||||||
|
descr.replaceWith(tag)
|
||||||
|
|
||||||
|
# The references are in a list, let's make them simpler
|
||||||
|
reflistcont = soup.find('ul',{'id':'article-refrences'})
|
||||||
|
if reflistcont:
|
||||||
|
reflist = reflistcont.li.renderContents()
|
||||||
|
tag = Tag(soup,'div')
|
||||||
|
tag['class'] = 'article-references'
|
||||||
|
tag.insert(0,reflist)
|
||||||
|
reflistcont.replaceWith(tag)
|
||||||
|
|
||||||
|
# Within the id=content div, we need to remove all the stuff after the end of the class=entry-content
|
||||||
|
entrycontent = soup.find('div',{'class':'entry-content'})
|
||||||
|
for nextSibling in entrycontent.findNextSiblings():
|
||||||
|
nextSibling.extract()
|
||||||
|
|
||||||
|
return soup
|
||||||
|
@ -27,6 +27,9 @@ class NikkeiNet_sub_economy(BasicNewsRecipe):
|
|||||||
{'class':"JSID_basePageMove JSID_baseAsyncSubmit cmn-form_area JSID_optForm_utoken"},
|
{'class':"JSID_basePageMove JSID_baseAsyncSubmit cmn-form_area JSID_optForm_utoken"},
|
||||||
{'class':"cmn-article_keyword cmn-clearfix"},
|
{'class':"cmn-article_keyword cmn-clearfix"},
|
||||||
{'class':"cmn-print_headline cmn-clearfix"},
|
{'class':"cmn-print_headline cmn-clearfix"},
|
||||||
|
{'class':"cmn-article_list"},
|
||||||
|
dict(id="ABOUT-NIKKEI"),
|
||||||
|
{'class':"cmn-sub_market"},
|
||||||
]
|
]
|
||||||
remove_tags_after = {'class':"cmn-pr_list"}
|
remove_tags_after = {'class':"cmn-pr_list"}
|
||||||
|
|
||||||
|
@ -104,13 +104,17 @@ class FB2Input(InputFormatPlugin):
|
|||||||
entries = [(f, guess_type(f)[0]) for f in os.listdir('.')]
|
entries = [(f, guess_type(f)[0]) for f in os.listdir('.')]
|
||||||
opf.create_manifest(entries)
|
opf.create_manifest(entries)
|
||||||
opf.create_spine(['index.xhtml'])
|
opf.create_spine(['index.xhtml'])
|
||||||
|
if mi.cover_data and mi.cover_data[1]:
|
||||||
for img in doc.xpath('//f:coverpage/f:image', namespaces=NAMESPACES):
|
with open('fb2_cover_calibre_mi.jpg', 'wb') as f:
|
||||||
href = img.get('{%s}href'%XLINK_NS, img.get('href', None))
|
f.write(mi.cover_data[1])
|
||||||
if href is not None:
|
opf.guide.set_cover(os.path.abspath('fb2_cover_calibre_mi.jpg'))
|
||||||
if href.startswith('#'):
|
else:
|
||||||
href = href[1:]
|
for img in doc.xpath('//f:coverpage/f:image', namespaces=NAMESPACES):
|
||||||
opf.guide.set_cover(os.path.abspath(href))
|
href = img.get('{%s}href'%XLINK_NS, img.get('href', None))
|
||||||
|
if href is not None:
|
||||||
|
if href.startswith('#'):
|
||||||
|
href = href[1:]
|
||||||
|
opf.guide.set_cover(os.path.abspath(href))
|
||||||
|
|
||||||
opf.render(open('metadata.opf', 'wb'))
|
opf.render(open('metadata.opf', 'wb'))
|
||||||
return os.path.join(os.getcwd(), 'metadata.opf')
|
return os.path.join(os.getcwd(), 'metadata.opf')
|
||||||
|
@ -542,7 +542,17 @@ class MobiReader(object):
|
|||||||
elif tag.tag == 'img':
|
elif tag.tag == 'img':
|
||||||
tag.set('height', height)
|
tag.set('height', height)
|
||||||
else:
|
else:
|
||||||
styles.append('margin-top: %s' % self.ensure_unit(height))
|
if tag.tag == 'div' and not tag.text and \
|
||||||
|
(not tag.tail or not tag.tail.strip()) and \
|
||||||
|
not len(list(tag.iterdescendants())):
|
||||||
|
# Paragraph spacer
|
||||||
|
# Insert nbsp so that the element is never
|
||||||
|
# discarded by a renderer
|
||||||
|
tag.text = u'\u00a0' # nbsp
|
||||||
|
styles.append('height: %s' %
|
||||||
|
self.ensure_unit(height))
|
||||||
|
else:
|
||||||
|
styles.append('margin-top: %s' % self.ensure_unit(height))
|
||||||
if attrib.has_key('width'):
|
if attrib.has_key('width'):
|
||||||
width = attrib.pop('width').strip()
|
width = attrib.pop('width').strip()
|
||||||
if width and re.search(r'\d+', width):
|
if width and re.search(r'\d+', width):
|
||||||
|
@ -227,7 +227,7 @@ class EbookIterator(object):
|
|||||||
self.log.warn('Missing spine item:', repr(spath))
|
self.log.warn('Missing spine item:', repr(spath))
|
||||||
|
|
||||||
cover = self.opf.cover
|
cover = self.opf.cover
|
||||||
if self.ebook_ext in ('lit', 'mobi', 'prc', 'opf') and cover:
|
if self.ebook_ext in ('lit', 'mobi', 'prc', 'opf', 'fb2') and cover:
|
||||||
cfile = os.path.join(self.base, 'calibre_iterator_cover.html')
|
cfile = os.path.join(self.base, 'calibre_iterator_cover.html')
|
||||||
chtml = (TITLEPAGE%os.path.relpath(cover, self.base).replace(os.sep,
|
chtml = (TITLEPAGE%os.path.relpath(cover, self.base).replace(os.sep,
|
||||||
'/')).encode('utf-8')
|
'/')).encode('utf-8')
|
||||||
|
@ -34,18 +34,15 @@ class PML_HTMLizer(object):
|
|||||||
'ra',
|
'ra',
|
||||||
'c',
|
'c',
|
||||||
'r',
|
'r',
|
||||||
't',
|
|
||||||
's',
|
's',
|
||||||
'l',
|
'l',
|
||||||
'k',
|
'k',
|
||||||
'T',
|
|
||||||
'FN',
|
'FN',
|
||||||
'SB',
|
'SB',
|
||||||
]
|
]
|
||||||
|
|
||||||
STATES_VALUE_REQ = [
|
STATES_VALUE_REQ = [
|
||||||
'a',
|
'a',
|
||||||
'T',
|
|
||||||
'FN',
|
'FN',
|
||||||
'SB',
|
'SB',
|
||||||
]
|
]
|
||||||
@ -96,8 +93,6 @@ class PML_HTMLizer(object):
|
|||||||
'Sb': 'sb',
|
'Sb': 'sb',
|
||||||
'c': 'c',
|
'c': 'c',
|
||||||
'r': 'r',
|
'r': 'r',
|
||||||
't': 't',
|
|
||||||
'T': 'T',
|
|
||||||
'i': 'i',
|
'i': 'i',
|
||||||
'I': 'i',
|
'I': 'i',
|
||||||
'u': 'u',
|
'u': 'u',
|
||||||
@ -133,8 +128,6 @@ class PML_HTMLizer(object):
|
|||||||
DIV_STATES = [
|
DIV_STATES = [
|
||||||
'c',
|
'c',
|
||||||
'r',
|
'r',
|
||||||
't',
|
|
||||||
'T',
|
|
||||||
'FN',
|
'FN',
|
||||||
'SB',
|
'SB',
|
||||||
]
|
]
|
||||||
@ -255,8 +248,6 @@ class PML_HTMLizer(object):
|
|||||||
|
|
||||||
for key, val in self.state.items():
|
for key, val in self.state.items():
|
||||||
if val[0]:
|
if val[0]:
|
||||||
if key == 'T':
|
|
||||||
self.state['T'][0] = False
|
|
||||||
if key in self.DIV_STATES:
|
if key in self.DIV_STATES:
|
||||||
div.append(key)
|
div.append(key)
|
||||||
elif key in self.SPAN_STATES:
|
elif key in self.SPAN_STATES:
|
||||||
@ -506,6 +497,9 @@ class PML_HTMLizer(object):
|
|||||||
self.toc = TOC()
|
self.toc = TOC()
|
||||||
self.file_name = file_name
|
self.file_name = file_name
|
||||||
|
|
||||||
|
indent_state = {'t': False, 'T': False}
|
||||||
|
adv_indent_val = ''
|
||||||
|
|
||||||
for s in self.STATES:
|
for s in self.STATES:
|
||||||
self.state[s] = [False, ''];
|
self.state[s] = [False, ''];
|
||||||
|
|
||||||
@ -515,6 +509,8 @@ class PML_HTMLizer(object):
|
|||||||
|
|
||||||
parsed = []
|
parsed = []
|
||||||
empty = True
|
empty = True
|
||||||
|
basic_indent = indent_state['t']
|
||||||
|
adv_indent = indent_state['T']
|
||||||
|
|
||||||
# Must use StringIO, cStringIO does not support unicode
|
# Must use StringIO, cStringIO does not support unicode
|
||||||
line = StringIO.StringIO(line)
|
line = StringIO.StringIO(line)
|
||||||
@ -527,7 +523,7 @@ class PML_HTMLizer(object):
|
|||||||
if c == '\\':
|
if c == '\\':
|
||||||
c = line.read(1)
|
c = line.read(1)
|
||||||
|
|
||||||
if c in 'qcrtTiIuobBlk':
|
if c in 'qcriIuobBlk':
|
||||||
text = self.process_code(c, line)
|
text = self.process_code(c, line)
|
||||||
elif c in 'FS':
|
elif c in 'FS':
|
||||||
l = line.read(1)
|
l = line.read(1)
|
||||||
@ -574,6 +570,15 @@ class PML_HTMLizer(object):
|
|||||||
elif c == 'w':
|
elif c == 'w':
|
||||||
empty = False
|
empty = False
|
||||||
text = '<hr width="%s" />' % self.code_value(line)
|
text = '<hr width="%s" />' % self.code_value(line)
|
||||||
|
elif c == 't':
|
||||||
|
indent_state[c] = not indent_state[c]
|
||||||
|
if indent_state[c]:
|
||||||
|
basic_indent = True
|
||||||
|
elif c == 'T':
|
||||||
|
indent_state[c] = not indent_state[c]
|
||||||
|
if indent_state[c]:
|
||||||
|
adv_indent = True
|
||||||
|
adv_indent_val = self.code_value(line)
|
||||||
elif c == '-':
|
elif c == '-':
|
||||||
empty = False
|
empty = False
|
||||||
text = '­'
|
text = '­'
|
||||||
@ -590,6 +595,16 @@ class PML_HTMLizer(object):
|
|||||||
if not empty:
|
if not empty:
|
||||||
text = self.end_line()
|
text = self.end_line()
|
||||||
parsed.append(text)
|
parsed.append(text)
|
||||||
|
|
||||||
|
if basic_indent:
|
||||||
|
parsed.insert(0, self.STATES_TAGS['t'][0])
|
||||||
|
parsed.append(self.STATES_TAGS['t'][1])
|
||||||
|
elif adv_indent:
|
||||||
|
parsed.insert(0, self.STATES_TAGS['T'][0] % adv_indent_val)
|
||||||
|
parsed.append(self.STATES_TAGS['T'][1])
|
||||||
|
indent_state['T'] = False
|
||||||
|
adv_indent_val = ''
|
||||||
|
|
||||||
output.append(u''.join(parsed))
|
output.append(u''.join(parsed))
|
||||||
line.close()
|
line.close()
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ def _config():
|
|||||||
c.add_opt('LRF_ebook_viewer_options', default=None,
|
c.add_opt('LRF_ebook_viewer_options', default=None,
|
||||||
help=_('Options for the LRF ebook viewer'))
|
help=_('Options for the LRF ebook viewer'))
|
||||||
c.add_opt('internally_viewed_formats', default=['LRF', 'EPUB', 'LIT',
|
c.add_opt('internally_viewed_formats', default=['LRF', 'EPUB', 'LIT',
|
||||||
'MOBI', 'PRC', 'HTML', 'FB2', 'PDB', 'RB', 'SNB'],
|
'MOBI', 'PRC', 'AZW', 'HTML', 'FB2', 'PDB', 'RB', 'SNB'],
|
||||||
help=_('Formats that are viewed using the internal viewer'))
|
help=_('Formats that are viewed using the internal viewer'))
|
||||||
c.add_opt('column_map', default=ALL_COLUMNS,
|
c.add_opt('column_map', default=ALL_COLUMNS,
|
||||||
help=_('Columns to be displayed in the book list'))
|
help=_('Columns to be displayed in the book list'))
|
||||||
|
@ -68,6 +68,9 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
def initialize_metadata_options(self):
|
def initialize_metadata_options(self):
|
||||||
self.initialize_combos()
|
self.initialize_combos()
|
||||||
self.author.editTextChanged.connect(self.deduce_author_sort)
|
self.author.editTextChanged.connect(self.deduce_author_sort)
|
||||||
|
self.author.set_separator('&')
|
||||||
|
self.author.set_space_before_sep(True)
|
||||||
|
self.author.update_items_cache(self.db.all_author_names())
|
||||||
|
|
||||||
mi = self.db.get_metadata(self.book_id, index_is_id=True)
|
mi = self.db.get_metadata(self.book_id, index_is_id=True)
|
||||||
self.title.setText(mi.title)
|
self.title.setText(mi.title)
|
||||||
@ -75,7 +78,7 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
self.publisher.setCurrentIndex(self.publisher.findText(mi.publisher))
|
self.publisher.setCurrentIndex(self.publisher.findText(mi.publisher))
|
||||||
self.author_sort.setText(mi.author_sort if mi.author_sort else '')
|
self.author_sort.setText(mi.author_sort if mi.author_sort else '')
|
||||||
self.tags.setText(', '.join(mi.tags if mi.tags else []))
|
self.tags.setText(', '.join(mi.tags if mi.tags else []))
|
||||||
self.tags.update_tags_cache(self.db.all_tags())
|
self.tags.update_items_cache(self.db.all_tags())
|
||||||
self.comment.setPlainText(mi.comments if mi.comments else '')
|
self.comment.setPlainText(mi.comments if mi.comments else '')
|
||||||
if mi.series:
|
if mi.series:
|
||||||
self.series.setCurrentIndex(self.series.findText(mi.series))
|
self.series.setCurrentIndex(self.series.findText(mi.series))
|
||||||
|
@ -190,7 +190,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="TagsLineEdit" name="tags">
|
<widget class="CompleteLineEdit" name="tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -255,7 +255,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="EnComboBox" name="author">
|
<widget class="CompleteComboBox" name="author">
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -310,7 +310,12 @@
|
|||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>TagsLineEdit</class>
|
<class>CompleteComboBox</class>
|
||||||
|
<extends>QComboBox</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>CompleteLineEdit</class>
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
@ -14,7 +14,7 @@ from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
|
|||||||
QPushButton
|
QPushButton
|
||||||
|
|
||||||
from calibre.utils.date import qt_to_dt, now
|
from calibre.utils.date import qt_to_dt, now
|
||||||
from calibre.gui2.widgets import TagsLineEdit, EnComboBox
|
from calibre.gui2.widgets import CompleteLineEdit, EnComboBox
|
||||||
from calibre.gui2.comments_editor import Editor as CommentsEditor
|
from calibre.gui2.comments_editor import Editor as CommentsEditor
|
||||||
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
@ -212,7 +212,7 @@ class Text(Base):
|
|||||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(key=sort_key)
|
values.sort(key=sort_key)
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
w = TagsLineEdit(parent, values)
|
w = CompleteLineEdit(parent, values)
|
||||||
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||||
else:
|
else:
|
||||||
w = EnComboBox(parent)
|
w = EnComboBox(parent)
|
||||||
@ -226,7 +226,7 @@ class Text(Base):
|
|||||||
val = self.normalize_db_val(val)
|
val = self.normalize_db_val(val)
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
self.setter(val)
|
self.setter(val)
|
||||||
self.widgets[1].update_tags_cache(self.all_values)
|
self.widgets[1].update_items_cache(self.all_values)
|
||||||
else:
|
else:
|
||||||
idx = None
|
idx = None
|
||||||
for i, c in enumerate(self.all_values):
|
for i, c in enumerate(self.all_values):
|
||||||
@ -656,7 +656,7 @@ class RemoveTags(QWidget):
|
|||||||
layout.setSpacing(5)
|
layout.setSpacing(5)
|
||||||
layout.setContentsMargins(0, 0, 0, 0)
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
self.tags_box = TagsLineEdit(parent, values)
|
self.tags_box = CompleteLineEdit(parent, values)
|
||||||
layout.addWidget(self.tags_box, stretch = 1)
|
layout.addWidget(self.tags_box, stretch = 1)
|
||||||
# self.tags_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
# self.tags_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||||
|
|
||||||
@ -678,7 +678,7 @@ class BulkText(BulkBase):
|
|||||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(key=sort_key)
|
values.sort(key=sort_key)
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
w = TagsLineEdit(parent, values)
|
w = CompleteLineEdit(parent, values)
|
||||||
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+': ' +
|
self.widgets = [QLabel('&'+self.col_metadata['name']+': ' +
|
||||||
_('tags to add'), parent), w]
|
_('tags to add'), parent), w]
|
||||||
@ -697,7 +697,7 @@ class BulkText(BulkBase):
|
|||||||
|
|
||||||
def initialize(self, book_ids):
|
def initialize(self, book_ids):
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
self.widgets[1].update_tags_cache(self.all_values)
|
self.widgets[1].update_items_cache(self.all_values)
|
||||||
else:
|
else:
|
||||||
val = self.get_initial_value(book_ids)
|
val = self.get_initial_value(book_ids)
|
||||||
self.initial_val = val = self.normalize_db_val(val)
|
self.initial_val = val = self.normalize_db_val(val)
|
||||||
|
@ -279,8 +279,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
self.changed = False
|
self.changed = False
|
||||||
|
|
||||||
all_tags = self.db.all_tags()
|
all_tags = self.db.all_tags()
|
||||||
self.tags.update_tags_cache(all_tags)
|
self.tags.update_items_cache(all_tags)
|
||||||
self.remove_tags.update_tags_cache(all_tags)
|
self.remove_tags.update_items_cache(all_tags)
|
||||||
|
|
||||||
self.initialize_combos()
|
self.initialize_combos()
|
||||||
|
|
||||||
@ -299,6 +299,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
self.pubdate.setDisplayFormat(pubdate_format)
|
self.pubdate.setDisplayFormat(pubdate_format)
|
||||||
self.pubdate.setSpecialValueText(_('Undefined'))
|
self.pubdate.setSpecialValueText(_('Undefined'))
|
||||||
self.clear_pubdate_button.clicked.connect(self.clear_pubdate)
|
self.clear_pubdate_button.clicked.connect(self.clear_pubdate)
|
||||||
|
self.pubdate.dateChanged.connect(self.do_apply_pubdate)
|
||||||
|
|
||||||
if len(self.db.custom_field_keys(include_composites=False)) == 0:
|
if len(self.db.custom_field_keys(include_composites=False)) == 0:
|
||||||
self.central_widget.removeTab(1)
|
self.central_widget.removeTab(1)
|
||||||
@ -315,6 +316,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
self.central_widget.setCurrentIndex(tab)
|
self.central_widget.setCurrentIndex(tab)
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
|
def do_apply_pubdate(self, *args):
|
||||||
|
self.apply_pubdate.setChecked(True)
|
||||||
|
|
||||||
def clear_pubdate(self, *args):
|
def clear_pubdate(self, *args):
|
||||||
self.pubdate.setDate(UNDEFINED_QDATE)
|
self.pubdate.setDate(UNDEFINED_QDATE)
|
||||||
|
|
||||||
@ -722,6 +726,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
name = name.strip().replace('|', ',')
|
name = name.strip().replace('|', ',')
|
||||||
self.authors.addItem(name)
|
self.authors.addItem(name)
|
||||||
self.authors.setEditText('')
|
self.authors.setEditText('')
|
||||||
|
|
||||||
|
self.authors.set_separator('&')
|
||||||
|
self.authors.set_space_before_sep(True)
|
||||||
|
self.authors.update_items_cache(self.db.all_author_names())
|
||||||
|
|
||||||
def initialize_series(self):
|
def initialize_series(self):
|
||||||
all_series = self.db.all_series()
|
all_series = self.db.all_series()
|
||||||
@ -747,8 +755,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
if d.result() == QDialog.Accepted:
|
if d.result() == QDialog.Accepted:
|
||||||
tag_string = ', '.join(d.tags)
|
tag_string = ', '.join(d.tags)
|
||||||
self.tags.setText(tag_string)
|
self.tags.setText(tag_string)
|
||||||
self.tags.update_tags_cache(self.db.all_tags())
|
self.tags.update_items_cache(self.db.all_tags())
|
||||||
self.remove_tags.update_tags_cache(self.db.all_tags())
|
self.remove_tags.update_items_cache(self.db.all_tags())
|
||||||
|
|
||||||
def auto_number_changed(self, state):
|
def auto_number_changed(self, state):
|
||||||
if state:
|
if state:
|
||||||
|
@ -76,7 +76,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="EnComboBox" name="authors">
|
<widget class="CompleteComboBox" name="authors">
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -195,7 +195,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="1">
|
<item row="5" column="1">
|
||||||
<widget class="TagsLineEdit" name="tags">
|
<widget class="CompleteLineEdit" name="tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -229,7 +229,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="1">
|
<item row="6" column="1">
|
||||||
<widget class="TagsLineEdit" name="remove_tags">
|
<widget class="CompleteLineEdit" name="remove_tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Comma separated list of tags to remove from the books. </string>
|
<string>Comma separated list of tags to remove from the books. </string>
|
||||||
</property>
|
</property>
|
||||||
@ -367,6 +367,9 @@ from the value in the box</string>
|
|||||||
<property name="displayFormat">
|
<property name="displayFormat">
|
||||||
<string>MMM yyyy</string>
|
<string>MMM yyyy</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="calendarPopup">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -871,8 +874,8 @@ not multiple and the destination field is multiple</string>
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>826</width>
|
<width>197</width>
|
||||||
<height>313</height>
|
<height>60</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="testgrid">
|
<layout class="QGridLayout" name="testgrid">
|
||||||
@ -952,7 +955,12 @@ not multiple and the destination field is multiple</string>
|
|||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>TagsLineEdit</class>
|
<class>CompleteComboBox</class>
|
||||||
|
<extends>QComboBox</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>CompleteLineEdit</class>
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
@ -556,7 +556,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
tags = self.db.tags(row)
|
tags = self.db.tags(row)
|
||||||
self.original_tags = ', '.join(tags.split(',')) if tags else ''
|
self.original_tags = ', '.join(tags.split(',')) if tags else ''
|
||||||
self.tags.setText(self.original_tags)
|
self.tags.setText(self.original_tags)
|
||||||
self.tags.update_tags_cache(self.db.all_tags())
|
self.tags.update_items_cache(self.db.all_tags())
|
||||||
rating = self.db.rating(row)
|
rating = self.db.rating(row)
|
||||||
if rating > 0:
|
if rating > 0:
|
||||||
self.rating.setValue(int(rating/2.))
|
self.rating.setValue(int(rating/2.))
|
||||||
@ -724,6 +724,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
au = _('Unknown')
|
au = _('Unknown')
|
||||||
au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')])
|
au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')])
|
||||||
self.authors.setEditText(au)
|
self.authors.setEditText(au)
|
||||||
|
|
||||||
|
self.authors.set_separator('&')
|
||||||
|
self.authors.set_space_before_sep(True)
|
||||||
|
self.authors.update_items_cache(self.db.all_author_names())
|
||||||
|
|
||||||
def initialize_series(self):
|
def initialize_series(self):
|
||||||
self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
|
self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
|
||||||
@ -776,7 +780,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
if d.result() == QDialog.Accepted:
|
if d.result() == QDialog.Accepted:
|
||||||
tag_string = ', '.join(d.tags)
|
tag_string = ', '.join(d.tags)
|
||||||
self.tags.setText(tag_string)
|
self.tags.setText(tag_string)
|
||||||
self.tags.update_tags_cache(self.db.all_tags())
|
self.tags.update_items_cache(self.db.all_tags())
|
||||||
|
|
||||||
|
|
||||||
def fetch_metadata(self):
|
def fetch_metadata(self):
|
||||||
|
@ -240,7 +240,7 @@ Using this button to create author sort will change author sort from red to gree
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="EnComboBox" name="authors">
|
<widget class="CompleteComboBox" name="authors">
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -335,7 +335,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<item row="6" column="1">
|
<item row="6" column="1">
|
||||||
<layout class="QHBoxLayout" name="_2">
|
<layout class="QHBoxLayout" name="_2">
|
||||||
<item>
|
<item>
|
||||||
<widget class="TagsLineEdit" name="tags">
|
<widget class="CompleteLineEdit" name="tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -842,10 +842,15 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>TagsLineEdit</class>
|
<class>CompleteLineEdit</class>
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>CompleteComboBox</class>
|
||||||
|
<extends>QComboBox</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>FormatList</class>
|
<class>FormatList</class>
|
||||||
<extends>QListWidget</extends>
|
<extends>QListWidget</extends>
|
||||||
|
@ -31,6 +31,9 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
self.authors_box.setEditText('')
|
self.authors_box.setEditText('')
|
||||||
self.authors_box.completer().setCompletionMode(QCompleter.PopupCompletion)
|
self.authors_box.completer().setCompletionMode(QCompleter.PopupCompletion)
|
||||||
self.authors_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
|
self.authors_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
|
||||||
|
self.authors_box.set_separator('&')
|
||||||
|
self.authors_box.set_space_before_sep(True)
|
||||||
|
self.authors_box.update_items_cache(db.all_author_names())
|
||||||
|
|
||||||
all_series = db.all_series()
|
all_series = db.all_series()
|
||||||
all_series.sort(key=lambda x : sort_key(x[1]))
|
all_series.sort(key=lambda x : sort_key(x[1]))
|
||||||
@ -42,7 +45,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
self.series_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
|
self.series_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
|
||||||
|
|
||||||
all_tags = db.all_tags()
|
all_tags = db.all_tags()
|
||||||
self.tags_box.update_tags_cache(all_tags)
|
self.tags_box.update_items_cache(all_tags)
|
||||||
|
|
||||||
self.box_last_values = copy.deepcopy(box_values)
|
self.box_last_values = copy.deepcopy(box_values)
|
||||||
if self.box_last_values:
|
if self.box_last_values:
|
||||||
|
@ -265,7 +265,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="EnComboBox" name="authors_box">
|
<widget class="CompleteComboBox" name="authors_box">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Enter an author's name. Only one author can be used.</string>
|
<string>Enter an author's name. Only one author can be used.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -279,7 +279,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="TagsLineEdit" name="tags_box">
|
<widget class="CompleteLineEdit" name="tags_box">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Enter tags separated by spaces</string>
|
<string>Enter tags separated by spaces</string>
|
||||||
</property>
|
</property>
|
||||||
@ -360,10 +360,15 @@
|
|||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>TagsLineEdit</class>
|
<class>CompleteLineEdit</class>
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>CompleteComboBox</class>
|
||||||
|
<extends>QComboBox</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>all</tabstop>
|
<tabstop>all</tabstop>
|
||||||
|
@ -148,7 +148,6 @@ class StatusBar(QStatusBar): # {{{
|
|||||||
self.get_version() + ' ' + _('created by Kovid Goyal')
|
self.get_version() + ' ' + _('created by Kovid Goyal')
|
||||||
self.device_string = ''
|
self.device_string = ''
|
||||||
self.update_label = QLabel('')
|
self.update_label = QLabel('')
|
||||||
self.update_label.setOpenExternalLinks(True)
|
|
||||||
self.addPermanentWidget(self.update_label)
|
self.addPermanentWidget(self.update_label)
|
||||||
self.update_label.setVisible(False)
|
self.update_label.setVisible(False)
|
||||||
self._font = QFont()
|
self._font = QFont()
|
||||||
@ -174,8 +173,9 @@ class StatusBar(QStatusBar): # {{{
|
|||||||
self.clearMessage()
|
self.clearMessage()
|
||||||
|
|
||||||
def new_version_available(self, ver, url):
|
def new_version_available(self, ver, url):
|
||||||
msg = (u'<span style="color:red; font-weight: bold">%s: <a href="%s">%s<a></span>') % (
|
msg = (u'<span style="color:red; font-weight: bold">%s: <a'
|
||||||
_('Update found'), url, ver)
|
' href="update:%s">%s<a></span>') % (
|
||||||
|
_('Update found'), ver, ver)
|
||||||
self.update_label.setText(msg)
|
self.update_label.setText(msg)
|
||||||
self.update_label.setCursor(Qt.PointingHandCursor)
|
self.update_label.setCursor(Qt.PointingHandCursor)
|
||||||
self.update_label.setVisible(True)
|
self.update_label.setVisible(True)
|
||||||
@ -240,6 +240,13 @@ class LayoutMixin(object): # {{{
|
|||||||
self.status_bar.addPermanentWidget(button)
|
self.status_bar.addPermanentWidget(button)
|
||||||
self.status_bar.addPermanentWidget(self.jobs_button)
|
self.status_bar.addPermanentWidget(self.jobs_button)
|
||||||
self.setStatusBar(self.status_bar)
|
self.setStatusBar(self.status_bar)
|
||||||
|
self.status_bar.update_label.linkActivated.connect(self.update_link_clicked)
|
||||||
|
|
||||||
|
def update_link_clicked(self, url):
|
||||||
|
url = unicode(url)
|
||||||
|
if url.startswith('update:'):
|
||||||
|
version = url.partition(':')[-1]
|
||||||
|
self.update_found(version, force=True)
|
||||||
|
|
||||||
def finalize_layout(self):
|
def finalize_layout(self):
|
||||||
self.status_bar.initialize(self.system_tray_icon)
|
self.status_bar.initialize(self.system_tray_icon)
|
||||||
|
@ -16,7 +16,7 @@ from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \
|
|||||||
QComboBox, QTextDocument
|
QComboBox, QTextDocument
|
||||||
|
|
||||||
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
||||||
from calibre.gui2.widgets import EnLineEdit, TagsLineEdit
|
from calibre.gui2.widgets import EnLineEdit, CompleteLineEdit
|
||||||
from calibre.utils.date import now, format_date
|
from calibre.utils.date import now, format_date
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
from calibre.utils.formatter import validation_formatter
|
from calibre.utils.formatter import validation_formatter
|
||||||
@ -173,9 +173,9 @@ class TagsDelegate(QStyledItemDelegate): # {{{
|
|||||||
if self.db:
|
if self.db:
|
||||||
col = index.model().column_map[index.column()]
|
col = index.model().column_map[index.column()]
|
||||||
if not index.model().is_custom_column(col):
|
if not index.model().is_custom_column(col):
|
||||||
editor = TagsLineEdit(parent, self.db.all_tags())
|
editor = CompleteLineEdit(parent, self.db.all_tags())
|
||||||
else:
|
else:
|
||||||
editor = TagsLineEdit(parent,
|
editor = CompleteLineEdit(parent,
|
||||||
sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
|
sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
|
||||||
key=sort_key))
|
key=sort_key))
|
||||||
return editor
|
return editor
|
||||||
@ -184,6 +184,31 @@ class TagsDelegate(QStyledItemDelegate): # {{{
|
|||||||
return editor
|
return editor
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class CompleteDelegate(QStyledItemDelegate): # {{{
|
||||||
|
def __init__(self, parent, sep, items_func_name, space_before_sep=False):
|
||||||
|
QStyledItemDelegate.__init__(self, parent)
|
||||||
|
self.sep = sep
|
||||||
|
self.items_func_name = items_func_name
|
||||||
|
self.space_before_sep = space_before_sep
|
||||||
|
|
||||||
|
def set_database(self, db):
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
def createEditor(self, parent, option, index):
|
||||||
|
if self.db and hasattr(self.db, self.items_func_name):
|
||||||
|
col = index.model().column_map[index.column()]
|
||||||
|
if not index.model().is_custom_column(col):
|
||||||
|
editor = CompleteLineEdit(parent, getattr(self.db, self.items_func_name)(),
|
||||||
|
self.sep, self.space_before_sep)
|
||||||
|
else:
|
||||||
|
editor = CompleteLineEdit(parent,
|
||||||
|
sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
|
||||||
|
key=sort_key), self.sep, self.space_before_sep)
|
||||||
|
else:
|
||||||
|
editor = EnLineEdit(parent)
|
||||||
|
return editor
|
||||||
|
# }}}
|
||||||
|
|
||||||
class CcDateDelegate(QStyledItemDelegate): # {{{
|
class CcDateDelegate(QStyledItemDelegate): # {{{
|
||||||
'''
|
'''
|
||||||
Delegate for custom columns dates. Because this delegate stores the
|
Delegate for custom columns dates. Because this delegate stores the
|
||||||
|
@ -13,7 +13,7 @@ from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \
|
|||||||
QPoint, QPixmap, QUrl, QImage, QPainter, QColor, QRect
|
QPoint, QPixmap, QUrl, QImage, QPainter, QColor, QRect
|
||||||
|
|
||||||
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
|
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
|
||||||
TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
|
TextDelegate, DateDelegate, CompleteDelegate, CcTextDelegate, \
|
||||||
CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcTemplateDelegate, \
|
CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcTemplateDelegate, \
|
||||||
CcEnumDelegate
|
CcEnumDelegate
|
||||||
from calibre.gui2.library.models import BooksModel, DeviceBooksModel
|
from calibre.gui2.library.models import BooksModel, DeviceBooksModel
|
||||||
@ -76,8 +76,8 @@ class BooksView(QTableView): # {{{
|
|||||||
self.rating_delegate = RatingDelegate(self)
|
self.rating_delegate = RatingDelegate(self)
|
||||||
self.timestamp_delegate = DateDelegate(self)
|
self.timestamp_delegate = DateDelegate(self)
|
||||||
self.pubdate_delegate = PubDateDelegate(self)
|
self.pubdate_delegate = PubDateDelegate(self)
|
||||||
self.tags_delegate = TagsDelegate(self)
|
self.tags_delegate = CompleteDelegate(self, ',', 'all_tags')
|
||||||
self.authors_delegate = TextDelegate(self)
|
self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True)
|
||||||
self.series_delegate = TextDelegate(self)
|
self.series_delegate = TextDelegate(self)
|
||||||
self.publisher_delegate = TextDelegate(self)
|
self.publisher_delegate = TextDelegate(self)
|
||||||
self.text_delegate = TextDelegate(self)
|
self.text_delegate = TextDelegate(self)
|
||||||
@ -410,8 +410,7 @@ class BooksView(QTableView): # {{{
|
|||||||
self.save_state()
|
self.save_state()
|
||||||
self._model.set_database(db)
|
self._model.set_database(db)
|
||||||
self.tags_delegate.set_database(db)
|
self.tags_delegate.set_database(db)
|
||||||
self.authors_delegate.set_auto_complete_function(
|
self.authors_delegate.set_database(db)
|
||||||
lambda: [(x, y.replace('|', ',')) for (x, y) in db.all_authors()])
|
|
||||||
self.series_delegate.set_auto_complete_function(db.all_series)
|
self.series_delegate.set_auto_complete_function(db.all_series)
|
||||||
self.publisher_delegate.set_auto_complete_function(db.all_publishers)
|
self.publisher_delegate.set_auto_complete_function(db.all_publishers)
|
||||||
|
|
||||||
|
@ -52,8 +52,7 @@ class UpdateNotification(QDialog):
|
|||||||
self.label = QLabel('<p>'+
|
self.label = QLabel('<p>'+
|
||||||
_('%s has been updated to version <b>%s</b>. '
|
_('%s has been updated to version <b>%s</b>. '
|
||||||
'See the <a href="http://calibre-ebook.com/whats-new'
|
'See the <a href="http://calibre-ebook.com/whats-new'
|
||||||
'">new features</a>. Visit the download pa'
|
'">new features</a>.')%(__appname__, version))
|
||||||
'ge?')%(__appname__, version))
|
|
||||||
self.label.setOpenExternalLinks(True)
|
self.label.setOpenExternalLinks(True)
|
||||||
self.label.setWordWrap(True)
|
self.label.setWordWrap(True)
|
||||||
self.setWindowTitle(_('Update available!'))
|
self.setWindowTitle(_('Update available!'))
|
||||||
@ -94,13 +93,13 @@ class UpdateMixin(object):
|
|||||||
type=Qt.QueuedConnection)
|
type=Qt.QueuedConnection)
|
||||||
self.update_checker.start()
|
self.update_checker.start()
|
||||||
|
|
||||||
def update_found(self, version):
|
def update_found(self, version, force=False):
|
||||||
os = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
os = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||||
url = 'http://calibre-ebook.com/download_%s'%os
|
url = 'http://calibre-ebook.com/download_%s'%os
|
||||||
self.status_bar.new_version_available(version, url)
|
self.status_bar.new_version_available(version, url)
|
||||||
|
|
||||||
if config.get('new_version_notification') and \
|
if force or (config.get('new_version_notification') and \
|
||||||
dynamic.get('update to version %s'%version, True):
|
dynamic.get('update to version %s'%version, True)):
|
||||||
self._update_notification__ = UpdateNotification(version,
|
self._update_notification__ = UpdateNotification(version,
|
||||||
parent=self)
|
parent=self)
|
||||||
self._update_notification__.show()
|
self._update_notification__.show()
|
||||||
|
@ -426,46 +426,47 @@ class EnLineEdit(LineEditECM, QLineEdit):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TagsCompleter(QCompleter):
|
class ItemsCompleter(QCompleter):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
A completer object that completes a list of tags. It is used in conjunction
|
A completer object that completes a list of tags. It is used in conjunction
|
||||||
with a CompleterLineEdit.
|
with a CompleterLineEdit.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, parent, all_tags):
|
def __init__(self, parent, all_items):
|
||||||
QCompleter.__init__(self, all_tags, parent)
|
QCompleter.__init__(self, all_items, parent)
|
||||||
self.all_tags = set(all_tags)
|
self.all_items = set(all_items)
|
||||||
|
|
||||||
def update(self, text_tags, completion_prefix):
|
def update(self, text_items, completion_prefix):
|
||||||
tags = list(self.all_tags.difference(text_tags))
|
items = list(self.all_items.difference(text_items))
|
||||||
model = QStringListModel(tags, self)
|
model = QStringListModel(items, self)
|
||||||
self.setModel(model)
|
self.setModel(model)
|
||||||
|
|
||||||
self.setCompletionPrefix(completion_prefix)
|
self.setCompletionPrefix(completion_prefix)
|
||||||
if completion_prefix.strip() != '':
|
if completion_prefix.strip() != '':
|
||||||
self.complete()
|
self.complete()
|
||||||
|
|
||||||
def update_tags_cache(self, tags):
|
def update_items_cache(self, items):
|
||||||
self.all_tags = set(tags)
|
self.all_items = set(items)
|
||||||
model = QStringListModel(tags, self)
|
model = QStringListModel(items, self)
|
||||||
self.setModel(model)
|
self.setModel(model)
|
||||||
|
|
||||||
|
|
||||||
class TagsLineEdit(EnLineEdit):
|
class CompleteLineEdit(EnLineEdit):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
A QLineEdit that can complete parts of text separated by separator.
|
A QLineEdit that can complete parts of text separated by separator.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, parent=0, tags=[]):
|
def __init__(self, parent=0, complete_items=[], sep=',', space_before_sep=False):
|
||||||
EnLineEdit.__init__(self, parent)
|
EnLineEdit.__init__(self, parent)
|
||||||
|
|
||||||
self.separator = ','
|
self.separator = sep
|
||||||
|
self.space_before_sep = space_before_sep
|
||||||
|
|
||||||
self.connect(self, SIGNAL('textChanged(QString)'), self.text_changed)
|
self.connect(self, SIGNAL('textChanged(QString)'), self.text_changed)
|
||||||
|
|
||||||
self.completer = TagsCompleter(self, tags)
|
self.completer = ItemsCompleter(self, complete_items)
|
||||||
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
|
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||||
|
|
||||||
self.connect(self,
|
self.connect(self,
|
||||||
@ -476,32 +477,43 @@ class TagsLineEdit(EnLineEdit):
|
|||||||
|
|
||||||
self.completer.setWidget(self)
|
self.completer.setWidget(self)
|
||||||
|
|
||||||
def update_tags_cache(self, tags):
|
def update_items_cache(self, complete_items):
|
||||||
self.completer.update_tags_cache(tags)
|
self.completer.update_items_cache(complete_items)
|
||||||
|
|
||||||
|
def set_separator(self, sep):
|
||||||
|
self.separator = sep
|
||||||
|
|
||||||
|
def set_space_before_sep(self, space_before):
|
||||||
|
self.space_before_sep = space_before
|
||||||
|
|
||||||
def text_changed(self, text):
|
def text_changed(self, text):
|
||||||
all_text = unicode(text)
|
all_text = unicode(text)
|
||||||
text = all_text[:self.cursorPosition()]
|
text = all_text[:self.cursorPosition()]
|
||||||
prefix = text.split(',')[-1].strip()
|
prefix = text.split(self.separator)[-1].strip()
|
||||||
|
|
||||||
text_tags = []
|
text_items = []
|
||||||
for t in all_text.split(self.separator):
|
for t in all_text.split(self.separator):
|
||||||
t1 = unicode(t).strip()
|
t1 = unicode(t).strip()
|
||||||
if t1 != '':
|
if t1 != '':
|
||||||
text_tags.append(t)
|
text_items.append(t)
|
||||||
text_tags = list(set(text_tags))
|
text_items = list(set(text_items))
|
||||||
|
|
||||||
self.emit(SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'),
|
self.emit(SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'),
|
||||||
text_tags, prefix)
|
text_items, prefix)
|
||||||
|
|
||||||
def complete_text(self, text):
|
def complete_text(self, text):
|
||||||
cursor_pos = self.cursorPosition()
|
cursor_pos = self.cursorPosition()
|
||||||
before_text = unicode(self.text())[:cursor_pos]
|
before_text = unicode(self.text())[:cursor_pos]
|
||||||
after_text = unicode(self.text())[cursor_pos:]
|
after_text = unicode(self.text())[cursor_pos:]
|
||||||
prefix_len = len(before_text.split(',')[-1].strip())
|
prefix_len = len(before_text.split(self.separator)[-1].strip())
|
||||||
self.setText('%s%s%s %s' % (before_text[:cursor_pos - prefix_len],
|
if self.space_before_sep:
|
||||||
text, self.separator, after_text))
|
complete_text_pat = '%s%s %s %s'
|
||||||
self.setCursorPosition(cursor_pos - prefix_len + len(text) + 2)
|
len_extra = 3
|
||||||
|
else:
|
||||||
|
complete_text_pat = '%s%s%s %s'
|
||||||
|
len_extra = 2
|
||||||
|
self.setText(complete_text_pat % (before_text[:cursor_pos - prefix_len], text, self.separator, after_text))
|
||||||
|
self.setCursorPosition(cursor_pos - prefix_len + len(text) + len_extra)
|
||||||
|
|
||||||
|
|
||||||
class EnComboBox(QComboBox):
|
class EnComboBox(QComboBox):
|
||||||
@ -528,6 +540,22 @@ class EnComboBox(QComboBox):
|
|||||||
idx = 0
|
idx = 0
|
||||||
self.setCurrentIndex(idx)
|
self.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
class CompleteComboBox(EnComboBox):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
EnComboBox.__init__(self, *args)
|
||||||
|
self.setLineEdit(CompleteLineEdit(self))
|
||||||
|
|
||||||
|
def update_items_cache(self, complete_items):
|
||||||
|
self.lineEdit().update_items_cache(complete_items)
|
||||||
|
|
||||||
|
def set_separator(self, sep):
|
||||||
|
self.lineEdit().set_separator(sep)
|
||||||
|
|
||||||
|
def set_space_before_sep(self, space_before):
|
||||||
|
self.lineEdit().set_space_before_sep(space_before)
|
||||||
|
|
||||||
|
|
||||||
class HistoryLineEdit(QComboBox):
|
class HistoryLineEdit(QComboBox):
|
||||||
|
|
||||||
lost_focus = pyqtSignal()
|
lost_focus = pyqtSignal()
|
||||||
|
@ -1060,6 +1060,10 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
return [ (i[0], i[1]) for i in \
|
return [ (i[0], i[1]) for i in \
|
||||||
self.conn.get('SELECT id, name FROM authors')]
|
self.conn.get('SELECT id, name FROM authors')]
|
||||||
|
|
||||||
|
def all_author_names(self):
|
||||||
|
return filter(None, [i[0].strip().replace('|', ',') for i in self.conn.get(
|
||||||
|
'SELECT name FROM authors')])
|
||||||
|
|
||||||
def all_publishers(self):
|
def all_publishers(self):
|
||||||
return [ (i[0], i[1]) for i in \
|
return [ (i[0], i[1]) for i in \
|
||||||
self.conn.get('SELECT id, name FROM publishers')]
|
self.conn.get('SELECT id, name FROM publishers')]
|
||||||
|
@ -547,6 +547,7 @@ Some limitations of PDF input are:
|
|||||||
* Extraction of vector images and tables from within the document is also not supported.
|
* Extraction of vector images and tables from within the document is also not supported.
|
||||||
* Some PDFs use special glyphs to represent ll or ff or fi, etc. Conversion of these may or may not work depending on just how they are represented internally in the PDF.
|
* Some PDFs use special glyphs to represent ll or ff or fi, etc. Conversion of these may or may not work depending on just how they are represented internally in the PDF.
|
||||||
* Some PDFs store their images upside down with a rotation instruction, |app| currently doesn't support that instruction, so the images will be rotated in the output as well.
|
* Some PDFs store their images upside down with a rotation instruction, |app| currently doesn't support that instruction, so the images will be rotated in the output as well.
|
||||||
|
* Links and Tables of Contents are not supported
|
||||||
|
|
||||||
To re-iterate **PDF is a really, really bad** format to use as input. If you absolutely must use PDF, then be prepared for an
|
To re-iterate **PDF is a really, really bad** format to use as input. If you absolutely must use PDF, then be prepared for an
|
||||||
output ranging anywhere from decent to unusable, depending on the input PDF.
|
output ranging anywhere from decent to unusable, depending on the input PDF.
|
||||||
|
@ -450,6 +450,11 @@ How do I use purchased EPUB books with |app|?
|
|||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Most purchased EPUB books have `DRM <http://wiki.mobileread.com/wiki/DRM>`_. This prevents |app| from opening them. You can still use |app| to store and transfer them to your e-book reader. First, you must authorize your reader on a windows machine with Adobe Digital Editions. Once this is done, EPUB books transferred with |app| will work fine on your reader. When you purchase an epub book from a website, you will get an ".acsm" file. This file should be opened with Adobe Digital Editions, which will then download the actual ".epub" e-book. The e-book file will be stored in the folder "My Digital Editions", from where you can add it to |app|.
|
Most purchased EPUB books have `DRM <http://wiki.mobileread.com/wiki/DRM>`_. This prevents |app| from opening them. You can still use |app| to store and transfer them to your e-book reader. First, you must authorize your reader on a windows machine with Adobe Digital Editions. Once this is done, EPUB books transferred with |app| will work fine on your reader. When you purchase an epub book from a website, you will get an ".acsm" file. This file should be opened with Adobe Digital Editions, which will then download the actual ".epub" e-book. The e-book file will be stored in the folder "My Digital Editions", from where you can add it to |app|.
|
||||||
|
|
||||||
|
I am getting a "Permission Denied" error?
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A permission denied error can occur because of many possible reasons, none of them having anything to do with |app|. You can get permission denied errors if you are using an SD card with write protect enabled. Or if you, or some program you used changed the file permissions of the files in question to read only. Or if there is a filesystem error on the device which caused your operating system to mount the filesystem in read only mode or mark a particular file as read only pending recovery. Or if the files have their owner set to a user other than you. You will need to fix the underlying cause of the permissions error before resuming to use |app|. Read the error message carefully, see what file it points to and fix the permissions on that file.
|
||||||
|
|
||||||
Can I have the comment metadata show up on my reader?
|
Can I have the comment metadata show up on my reader?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -901,10 +901,7 @@ class BasicNewsRecipe(Recipe):
|
|||||||
if self.test:
|
if self.test:
|
||||||
feeds = feeds[:2]
|
feeds = feeds[:2]
|
||||||
self.has_single_feed = len(feeds) == 1
|
self.has_single_feed = len(feeds) == 1
|
||||||
|
|
||||||
if self.use_embedded_content is None:
|
|
||||||
self.use_embedded_content = feeds[0].has_embedded_content()
|
|
||||||
|
|
||||||
index = os.path.join(self.output_dir, 'index.html')
|
index = os.path.join(self.output_dir, 'index.html')
|
||||||
|
|
||||||
html = self.feeds2index(feeds)
|
html = self.feeds2index(feeds)
|
||||||
@ -939,7 +936,9 @@ class BasicNewsRecipe(Recipe):
|
|||||||
url = None
|
url = None
|
||||||
if not url:
|
if not url:
|
||||||
continue
|
continue
|
||||||
func, arg = (self.fetch_embedded_article, article) if self.use_embedded_content else \
|
func, arg = (self.fetch_embedded_article, article) \
|
||||||
|
if self.use_embedded_content or (self.use_embedded_content == None and feed.has_embedded_content()) \
|
||||||
|
else \
|
||||||
((self.fetch_obfuscated_article if self.articles_are_obfuscated \
|
((self.fetch_obfuscated_article if self.articles_are_obfuscated \
|
||||||
else self.fetch_article), url)
|
else self.fetch_article), url)
|
||||||
req = WorkRequest(func, (arg, art_dir, f, a, len(feed)),
|
req = WorkRequest(func, (arg, art_dir, f, a, len(feed)),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user