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
9036dd83d7
@ -15,12 +15,26 @@ class LeTemps(BasicNewsRecipe):
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
__author__ = 'Sujata Raman'
|
||||
description = 'French news. Needs a subscription from http://www.letemps.ch'
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
recursions = 1
|
||||
encoding = 'UTF-8'
|
||||
match_regexps = [r'http://www.letemps.ch/Page/Uuid/[-0-9a-f]+\|[1-9]']
|
||||
language = 'fr'
|
||||
needs_subscription = True
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser(self)
|
||||
br.open('http://www.letemps.ch/login')
|
||||
br['username'] = self.username
|
||||
br['password'] = self.password
|
||||
raw = br.submit().read()
|
||||
if '>Login' in raw:
|
||||
raise ValueError('Failed to login to letemp.ch. Check '
|
||||
'your username and password')
|
||||
return br
|
||||
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'content'}),
|
||||
dict(name='div', attrs={'class':'story'})
|
||||
|
@ -490,11 +490,12 @@ class HeuristicProcessor(object):
|
||||
applied to wrapping divs. This is because many ebook devices don't support margin:auto
|
||||
All other html is converted to text.
|
||||
'''
|
||||
hr_open = '<div id="scenebreak" style="margin-left: 45%; margin-right: 45%; margin-top:1.5em; margin-bottom:1.5em">'
|
||||
hr_open = '<div id="scenebreak" style="margin-left: 45%; margin-right: 45%; margin-top:1.5em; margin-bottom:1.5em; page-break-before:avoid">'
|
||||
if re.findall('(<|>)', replacement_break):
|
||||
if re.match('^<hr', replacement_break):
|
||||
if replacement_break.find('width') != -1:
|
||||
width = int(re.sub('.*?width(:|=)(?P<wnum>\d+).*', '\g<wnum>', replacement_break))
|
||||
replacement_break = re.sub('(?i)(width=\d+\%?|width:\s*\d+(\%|px|pt|em)?;?)', '', replacement_break)
|
||||
divpercent = (100 - width) / 2
|
||||
hr_open = re.sub('45', str(divpercent), hr_open)
|
||||
scene_break = hr_open+replacement_break+'</div>'
|
||||
@ -642,7 +643,7 @@ class HeuristicProcessor(object):
|
||||
# or 'hard' scene breaks are replaced, depending on which is in use
|
||||
# Otherwise separator lines are centered, use a bit larger margin in this case
|
||||
replacement_break = getattr(self.extra_opts, 'replace_scene_breaks', None)
|
||||
if replacement_break is not None:
|
||||
if replacement_break:
|
||||
replacement_break = self.markup_user_break(replacement_break)
|
||||
if len(scene_break.findall(html)) >= 1:
|
||||
html = scene_break.sub(replacement_break, html)
|
||||
|
@ -74,23 +74,29 @@ class ShareConnMenu(QMenu): # {{{
|
||||
opts = email_config().parse()
|
||||
if opts.accounts:
|
||||
self.email_to_menu = QMenu(_('Email to')+'...', self)
|
||||
ac = self.addMenu(self.email_to_menu)
|
||||
self.email_actions.append(ac)
|
||||
self.email_to_and_delete_menu = QMenu(
|
||||
_('Email to and delete from library')+'...', self)
|
||||
keys = sorted(opts.accounts.keys())
|
||||
for account in keys:
|
||||
formats, auto, default = opts.accounts[account]
|
||||
dest = 'mail:'+account+';'+formats
|
||||
action1 = DeviceAction(dest, False, False, I('mail.png'),
|
||||
_('Email to')+' '+account)
|
||||
account)
|
||||
action2 = DeviceAction(dest, True, False, I('mail.png'),
|
||||
_('Email to')+' '+account+ _(' and delete from library'))
|
||||
map(self.email_to_menu.addAction, (action1, action2))
|
||||
account + ' ' + _('(delete from library)'))
|
||||
self.email_to_menu.addAction(action1)
|
||||
self.email_to_and_delete_menu.addAction(action2)
|
||||
map(self.memory.append, (action1, action2))
|
||||
if default:
|
||||
map(self.addAction, (action1, action2))
|
||||
map(self.email_actions.append, (action1, action2))
|
||||
self.email_to_menu.addSeparator()
|
||||
ac = DeviceAction(dest, False, False,
|
||||
I('mail.png'), _('Email to') + ' ' +account)
|
||||
self.addAction(ac)
|
||||
self.email_actions.append(ac)
|
||||
action1.a_s.connect(sync_menu.action_triggered)
|
||||
action2.a_s.connect(sync_menu.action_triggered)
|
||||
ac = self.addMenu(self.email_to_menu)
|
||||
ac = self.addMenu(self.email_to_and_delete_menu)
|
||||
self.email_actions.append(ac)
|
||||
else:
|
||||
ac = self.addAction(_('Setup email based sharing of books'))
|
||||
|
@ -64,8 +64,8 @@ class CompleteWindow(QListView): # {{{
|
||||
|
||||
def do_selected(self, idx=None):
|
||||
idx = self.currentIndex() if idx is None else idx
|
||||
if not idx.isValid() and self.model().rowCount() > 0:
|
||||
idx = self.model().index(0)
|
||||
#if not idx.isValid() and self.model().rowCount() > 0:
|
||||
# idx = self.model().index(0)
|
||||
if idx.isValid():
|
||||
data = unicode(self.model().data(idx, Qt.DisplayRole))
|
||||
self.completion_selected.emit(data)
|
||||
@ -175,9 +175,9 @@ class MultiCompleteLineEdit(QLineEdit):
|
||||
|
||||
self._model = CompleteModel(parent=self)
|
||||
self.complete_window = CompleteWindow(self, self._model)
|
||||
self.textChanged.connect(self.text_changed)
|
||||
self.cursorPositionChanged.connect(self.cursor_position_changed)
|
||||
self.textEdited.connect(self.text_edited)
|
||||
self.complete_window.completion_selected.connect(self.completion_selected)
|
||||
self.installEventFilter(self)
|
||||
|
||||
# Interface {{{
|
||||
def update_items_cache(self, complete_items):
|
||||
@ -198,14 +198,13 @@ class MultiCompleteLineEdit(QLineEdit):
|
||||
return QLineEdit.eventFilter(self, o, e)
|
||||
|
||||
|
||||
def text_changed(self, *args):
|
||||
self.update_completions()
|
||||
|
||||
def cursor_position_changed(self, *args):
|
||||
def text_edited(self, *args):
|
||||
self.update_completions()
|
||||
|
||||
def update_completions(self):
|
||||
' Update the list of completions '
|
||||
if not self.complete_window.isVisible() and not self.hasFocus():
|
||||
return
|
||||
cpos = self.cursorPosition()
|
||||
text = unicode(self.text())
|
||||
prefix = text[:cpos]
|
||||
@ -223,7 +222,7 @@ class MultiCompleteLineEdit(QLineEdit):
|
||||
text
|
||||
'''
|
||||
if self.sep is None:
|
||||
return text
|
||||
return -1, text
|
||||
else:
|
||||
cursor_pos = self.cursorPosition()
|
||||
before_text = unicode(self.text())[:cursor_pos]
|
||||
@ -232,24 +231,18 @@ class MultiCompleteLineEdit(QLineEdit):
|
||||
if len(after_parts) < 3 and not after_parts[-1].strip():
|
||||
after_text = u''
|
||||
prefix_len = len(before_text.split(self.sep)[-1].lstrip())
|
||||
if self.space_before_sep:
|
||||
complete_text_pat = '%s%s %s %s'
|
||||
len_extra = 3
|
||||
else:
|
||||
complete_text_pat = '%s%s%s %s'
|
||||
len_extra = 2
|
||||
return prefix_len, len_extra, complete_text_pat % (
|
||||
before_text[:cursor_pos - prefix_len], text, self.sep, after_text)
|
||||
return prefix_len, \
|
||||
before_text[:cursor_pos - prefix_len] + text + after_text
|
||||
|
||||
def completion_selected(self, text):
|
||||
prefix_len, len_extra, ctext = self.get_completed_text(text)
|
||||
prefix_len, ctext = self.get_completed_text(text)
|
||||
if self.sep is None:
|
||||
self.setText(ctext)
|
||||
self.setCursorPosition(len(ctext))
|
||||
else:
|
||||
cursor_pos = self.cursorPosition()
|
||||
self.setText(ctext)
|
||||
self.setCursorPosition(cursor_pos - prefix_len + len(text) + len_extra)
|
||||
self.setCursorPosition(cursor_pos - prefix_len + len(text))
|
||||
|
||||
def update_complete_window(self, matches):
|
||||
self._model.update_matches(matches)
|
||||
@ -334,6 +327,11 @@ class MultiCompleteComboBox(EnComboBox):
|
||||
def __init__(self, *args):
|
||||
EnComboBox.__init__(self, *args)
|
||||
self.setLineEdit(MultiCompleteLineEdit(self))
|
||||
# Needed to allow changing the case of an existing item
|
||||
# otherwise on focus out, the text is changed to the
|
||||
# item that matches case insensitively
|
||||
c = self.lineEdit().completer()
|
||||
c.setCaseSensitivity(Qt.CaseSensitive)
|
||||
|
||||
def update_items_cache(self, complete_items):
|
||||
self.lineEdit().update_items_cache(complete_items)
|
||||
|
@ -27,8 +27,8 @@ class HeuristicsWidget(Widget, Ui_Form):
|
||||
'dehyphenate', 'renumber_headings']
|
||||
)
|
||||
self.db, self.book_id = db, book_id
|
||||
self.rssb_defaults = [u'', u'<hr />', u'* * *', u'• • •', u'✦ ✦ ✦',
|
||||
u'✮ ✮ ✮', u'☆ ☆ ☆', u'❂ ❂ ❂', u'✣ ✣ ✣', u'❖ ❖ ❖', u'☼ ☼ ☼', u'✠ ✠ ✠']
|
||||
self.rssb_defaults = [u'', u'<hr />', u'∗ ∗ ∗', u'• • •', u'♦ ♦ ♦',
|
||||
u'† †', u'‡ ‡ ‡', u'∞ ∞ ∞', u'¤ ¤ ¤', u'§']
|
||||
self.initialize_options(get_option, get_help, db, book_id)
|
||||
|
||||
self.load_histories()
|
||||
|
@ -12,11 +12,11 @@ from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \
|
||||
QPainterPath, QLinearGradient, QBrush, \
|
||||
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
|
||||
QIcon, QDoubleSpinBox, QVariant, QSpinBox, \
|
||||
QStyledItemDelegate, QCompleter, \
|
||||
QComboBox, QTextDocument
|
||||
QStyledItemDelegate, QComboBox, QTextDocument
|
||||
|
||||
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
||||
from calibre.gui2.widgets import EnLineEdit, CompleteLineEdit
|
||||
from calibre.gui2.widgets import EnLineEdit
|
||||
from calibre.gui2.complete import MultiCompleteLineEdit
|
||||
from calibre.utils.date import now, format_date
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre.utils.formatter import validation_formatter
|
||||
@ -151,34 +151,11 @@ class TextDelegate(QStyledItemDelegate): # {{{
|
||||
self.auto_complete_function = f
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = EnLineEdit(parent)
|
||||
if self.auto_complete_function:
|
||||
editor = MultiCompleteLineEdit(parent)
|
||||
editor.set_separator(None)
|
||||
complete_items = [i[1] for i in self.auto_complete_function()]
|
||||
completer = QCompleter(complete_items, self)
|
||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
completer.setCompletionMode(QCompleter.PopupCompletion)
|
||||
editor.setCompleter(completer)
|
||||
return editor
|
||||
#}}}
|
||||
|
||||
class TagsDelegate(QStyledItemDelegate): # {{{
|
||||
def __init__(self, parent):
|
||||
QStyledItemDelegate.__init__(self, parent)
|
||||
self.db = None
|
||||
|
||||
def set_database(self, db):
|
||||
self.db = db
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
if self.db:
|
||||
col = index.model().column_map[index.column()]
|
||||
if not index.model().is_custom_column(col):
|
||||
editor = CompleteLineEdit(parent, self.db.all_tags())
|
||||
else:
|
||||
editor = CompleteLineEdit(parent,
|
||||
sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
|
||||
key=sort_key))
|
||||
return editor
|
||||
editor.update_items_cache(complete_items)
|
||||
else:
|
||||
editor = EnLineEdit(parent)
|
||||
return editor
|
||||
@ -197,13 +174,15 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
|
||||
def createEditor(self, parent, option, index):
|
||||
if self.db and hasattr(self.db, self.items_func_name):
|
||||
col = index.model().column_map[index.column()]
|
||||
editor = MultiCompleteLineEdit(parent)
|
||||
editor.set_separator(self.sep)
|
||||
editor.set_space_before_sep(self.space_before_sep)
|
||||
if not index.model().is_custom_column(col):
|
||||
editor = CompleteLineEdit(parent, getattr(self.db, self.items_func_name)(),
|
||||
self.sep, self.space_before_sep)
|
||||
all_items = getattr(self.db, self.items_func_name)()
|
||||
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)
|
||||
all_items = list(self.db.all_custom(
|
||||
label=self.db.field_metadata.key_to_label(col)))
|
||||
editor.update_items_cache(all_items)
|
||||
else:
|
||||
editor = EnLineEdit(parent)
|
||||
return editor
|
||||
@ -273,13 +252,11 @@ class CcTextDelegate(QStyledItemDelegate): # {{{
|
||||
editor.setRange(-100., float(sys.maxint))
|
||||
editor.setDecimals(2)
|
||||
else:
|
||||
editor = EnLineEdit(parent)
|
||||
editor = MultiCompleteLineEdit(parent)
|
||||
editor.set_separator(None)
|
||||
complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))),
|
||||
key=sort_key)
|
||||
completer = QCompleter(complete_items, self)
|
||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
completer.setCompletionMode(QCompleter.PopupCompletion)
|
||||
editor.setCompleter(completer)
|
||||
editor.update_items_cache(complete_items)
|
||||
return editor
|
||||
|
||||
# }}}
|
||||
|
@ -12,7 +12,7 @@ from PyQt4.Qt import Qt, QDateEdit, QDate, \
|
||||
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \
|
||||
QPushButton, QSpinBox, QLineEdit
|
||||
|
||||
from calibre.gui2.widgets import EnLineEdit, EnComboBox, FormatList, ImageView
|
||||
from calibre.gui2.widgets import EnLineEdit, FormatList, ImageView
|
||||
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.config import tweaks, prefs
|
||||
@ -283,13 +283,14 @@ class AuthorSortEdit(EnLineEdit):
|
||||
# }}}
|
||||
|
||||
# Series {{{
|
||||
class SeriesEdit(EnComboBox):
|
||||
class SeriesEdit(MultiCompleteComboBox):
|
||||
|
||||
TOOLTIP = _('List of known series. You can add new series.')
|
||||
LABEL = _('&Series:')
|
||||
|
||||
def __init__(self, parent):
|
||||
EnComboBox.__init__(self, parent)
|
||||
MultiCompleteComboBox.__init__(self, parent)
|
||||
self.set_separator(None)
|
||||
self.dialog = parent
|
||||
self.setSizeAdjustPolicy(
|
||||
self.AdjustToMinimumContentsLengthWithIcon)
|
||||
@ -314,6 +315,7 @@ class SeriesEdit(EnComboBox):
|
||||
def initialize(self, db, id_):
|
||||
all_series = db.all_series()
|
||||
all_series.sort(key=lambda x : sort_key(x[1]))
|
||||
self.update_items_cache([x[1] for x in all_series])
|
||||
series_id = db.series_id(id_, index_is_id=True)
|
||||
idx, c = None, 0
|
||||
for i in all_series:
|
||||
@ -910,11 +912,12 @@ class ISBNEdit(QLineEdit): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class PublisherEdit(EnComboBox): # {{{
|
||||
class PublisherEdit(MultiCompleteComboBox): # {{{
|
||||
LABEL = _('&Publisher:')
|
||||
|
||||
def __init__(self, parent):
|
||||
EnComboBox.__init__(self, parent)
|
||||
MultiCompleteComboBox.__init__(self, parent)
|
||||
self.set_separator(None)
|
||||
self.setSizeAdjustPolicy(
|
||||
self.AdjustToMinimumContentsLengthWithIcon)
|
||||
|
||||
@ -935,6 +938,7 @@ class PublisherEdit(EnComboBox): # {{{
|
||||
def initialize(self, db, id_):
|
||||
all_publishers = db.all_publishers()
|
||||
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
||||
self.update_items_cache([x[1] for x in all_publishers])
|
||||
publisher_id = db.publisher_id(id_, index_is_id=True)
|
||||
idx, c = None, 0
|
||||
for i in all_publishers:
|
||||
|
@ -316,9 +316,19 @@ remove all non-breaking-space entities, or may include false positive matches re
|
||||
|
||||
:guilabel:`Replace scene breaks`
|
||||
If this option is configured then |app| will replace scene break markers it finds with the replacement text specified by the
|
||||
user. In general you should avoid using html tags, |app| will discard any tags and use pre-defined markup. <hr />
|
||||
tags, i.e. horizontal rules, are an exception. These can optionally be specified with styles, if you choose to add your own
|
||||
style be sure to include the 'width' setting, otherwise the style information will be discarded.
|
||||
user. Please note that some ornamental characters may not be supported across all reading devices.
|
||||
|
||||
In general you should avoid using html tags, |app| will discard any tags and use pre-defined markup. <hr />
|
||||
tags, i.e. horizontal rules, and <img> tags are exceptions. Horizontal rules can optionally be specified with styles, if you
|
||||
choose to add your own style be sure to include the 'width' setting, otherwise the style information will be discarded. Image
|
||||
tags can used, but |app| does not provide the ability to add the image during conversion, this must be done after the fact using
|
||||
the 'Tweak Epub' feature, or Sigil.
|
||||
|
||||
Example image tag (place the image within an 'Images' folder inside the epub after conversion):
|
||||
<img style="width:10%" src="../Images/scenebreak.png" />
|
||||
|
||||
Example horizontal rule with styles:
|
||||
<hr style="width:20%;padding-top: 1px;border-top: 2px ridge black;border-bottom: 2px groove black;"/>
|
||||
|
||||
:guilabel:`Remove unnecessary hyphens`
|
||||
|app| will analyze all hyphenated content in the document when this option is enabled. The document itself is used
|
||||
|
Loading…
x
Reference in New Issue
Block a user