diff --git a/resources/recipes/le_temps.recipe b/resources/recipes/le_temps.recipe
index c33d9a51d2..7e320fe710 100644
--- a/resources/recipes/le_temps.recipe
+++ b/resources/recipes/le_temps.recipe
@@ -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'})
diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py
index 8a339afe4c..edd4d54cba 100644
--- a/src/calibre/ebooks/conversion/utils.py
+++ b/src/calibre/ebooks/conversion/utils.py
@@ -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 = '
'
+ hr_open = '
'
if re.findall('(<|>)', replacement_break):
if re.match('^
\d+).*', '\g', 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+''
@@ -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)
diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py
index fb3e627789..b32568f8fd 100644
--- a/src/calibre/gui2/actions/device.py
+++ b/src/calibre/gui2/actions/device.py
@@ -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'))
diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py
index ce8609fc99..0ad8fb13d4 100644
--- a/src/calibre/gui2/complete.py
+++ b/src/calibre/gui2/complete.py
@@ -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)
diff --git a/src/calibre/gui2/convert/heuristics.py b/src/calibre/gui2/convert/heuristics.py
index 77fadf059c..5e7e4aa506 100644
--- a/src/calibre/gui2/convert/heuristics.py
+++ b/src/calibre/gui2/convert/heuristics.py
@@ -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'
', u'* * *', u'• • •', u'✦ ✦ ✦',
- u'✮ ✮ ✮', u'☆ ☆ ☆', u'❂ ❂ ❂', u'✣ ✣ ✣', u'❖ ❖ ❖', u'☼ ☼ ☼', u'✠ ✠ ✠']
+ self.rssb_defaults = [u'', u'
', u'∗ ∗ ∗', u'• • •', u'♦ ♦ ♦',
+ u'† †', u'‡ ‡ ‡', u'∞ ∞ ∞', u'¤ ¤ ¤', u'§']
self.initialize_options(get_option, get_help, db, book_id)
self.load_histories()
diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py
index ae9d6e2f71..fed2e42470 100644
--- a/src/calibre/gui2/library/delegates.py
+++ b/src/calibre/gui2/library/delegates.py
@@ -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,38 +151,15 @@ 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
-# }}}
+#}}}
class CompleteDelegate(QStyledItemDelegate): # {{{
def __init__(self, parent, sep, items_func_name, space_before_sep=False):
@@ -197,13 +174,15 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
def createEditor(self, parent, option, index):
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
# }}}
diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py
index 8ec037278e..6b89e306e6 100644
--- a/src/calibre/gui2/metadata/basic_widgets.py
+++ b/src/calibre/gui2/metadata/basic_widgets.py
@@ -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:
diff --git a/src/calibre/manual/conversion.rst b/src/calibre/manual/conversion.rst
index ecd8609ecc..60f8a10fc6 100644
--- a/src/calibre/manual/conversion.rst
+++ b/src/calibre/manual/conversion.rst
@@ -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.
- 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.
+ tags, i.e. horizontal rules, and
![]()
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):
+

+
+ Example horizontal rule with styles:
+
:guilabel:`Remove unnecessary hyphens`
|app| will analyze all hyphenated content in the document when this option is enabled. The document itself is used