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