Merge from trunk

This commit is contained in:
Charles Haley 2011-02-02 18:42:45 +00:00
commit 9036dd83d7
8 changed files with 88 additions and 78 deletions

View File

@ -15,12 +15,26 @@ class LeTemps(BasicNewsRecipe):
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100
__author__ = 'Sujata Raman' __author__ = 'Sujata Raman'
description = 'French news. Needs a subscription from http://www.letemps.ch'
no_stylesheets = True no_stylesheets = True
remove_javascript = True remove_javascript = True
recursions = 1 recursions = 1
encoding = 'UTF-8' encoding = 'UTF-8'
match_regexps = [r'http://www.letemps.ch/Page/Uuid/[-0-9a-f]+\|[1-9]'] match_regexps = [r'http://www.letemps.ch/Page/Uuid/[-0-9a-f]+\|[1-9]']
language = 'fr' 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'}), keep_only_tags = [dict(name='div', attrs={'id':'content'}),
dict(name='div', attrs={'class':'story'}) dict(name='div', attrs={'class':'story'})

View File

@ -490,11 +490,12 @@ class HeuristicProcessor(object):
applied to wrapping divs. This is because many ebook devices don't support margin:auto applied to wrapping divs. This is because many ebook devices don't support margin:auto
All other html is converted to text. 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.findall('(<|>)', replacement_break):
if re.match('^<hr', replacement_break): if re.match('^<hr', replacement_break):
if replacement_break.find('width') != -1: if replacement_break.find('width') != -1:
width = int(re.sub('.*?width(:|=)(?P<wnum>\d+).*', '\g<wnum>', replacement_break)) 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 divpercent = (100 - width) / 2
hr_open = re.sub('45', str(divpercent), hr_open) hr_open = re.sub('45', str(divpercent), hr_open)
scene_break = hr_open+replacement_break+'</div>' 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 # 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 # Otherwise separator lines are centered, use a bit larger margin in this case
replacement_break = getattr(self.extra_opts, 'replace_scene_breaks', None) 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) replacement_break = self.markup_user_break(replacement_break)
if len(scene_break.findall(html)) >= 1: if len(scene_break.findall(html)) >= 1:
html = scene_break.sub(replacement_break, html) html = scene_break.sub(replacement_break, html)

View File

@ -74,23 +74,29 @@ class ShareConnMenu(QMenu): # {{{
opts = email_config().parse() opts = email_config().parse()
if opts.accounts: if opts.accounts:
self.email_to_menu = QMenu(_('Email to')+'...', self) 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()) keys = sorted(opts.accounts.keys())
for account in keys: for account in keys:
formats, auto, default = opts.accounts[account] formats, auto, default = opts.accounts[account]
dest = 'mail:'+account+';'+formats dest = 'mail:'+account+';'+formats
action1 = DeviceAction(dest, False, False, I('mail.png'), action1 = DeviceAction(dest, False, False, I('mail.png'),
_('Email to')+' '+account) account)
action2 = DeviceAction(dest, True, False, I('mail.png'), action2 = DeviceAction(dest, True, False, I('mail.png'),
_('Email to')+' '+account+ _(' and delete from library')) account + ' ' + _('(delete from library)'))
map(self.email_to_menu.addAction, (action1, action2)) self.email_to_menu.addAction(action1)
self.email_to_and_delete_menu.addAction(action2)
map(self.memory.append, (action1, action2)) map(self.memory.append, (action1, action2))
if default: if default:
map(self.addAction, (action1, action2)) ac = DeviceAction(dest, False, False,
map(self.email_actions.append, (action1, action2)) I('mail.png'), _('Email to') + ' ' +account)
self.email_to_menu.addSeparator() self.addAction(ac)
self.email_actions.append(ac)
action1.a_s.connect(sync_menu.action_triggered) action1.a_s.connect(sync_menu.action_triggered)
action2.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) self.email_actions.append(ac)
else: else:
ac = self.addAction(_('Setup email based sharing of books')) ac = self.addAction(_('Setup email based sharing of books'))

View File

@ -64,8 +64,8 @@ class CompleteWindow(QListView): # {{{
def do_selected(self, idx=None): def do_selected(self, idx=None):
idx = self.currentIndex() if idx is None else idx idx = self.currentIndex() if idx is None else idx
if not idx.isValid() and self.model().rowCount() > 0: #if not idx.isValid() and self.model().rowCount() > 0:
idx = self.model().index(0) # idx = self.model().index(0)
if idx.isValid(): if idx.isValid():
data = unicode(self.model().data(idx, Qt.DisplayRole)) data = unicode(self.model().data(idx, Qt.DisplayRole))
self.completion_selected.emit(data) self.completion_selected.emit(data)
@ -175,9 +175,9 @@ class MultiCompleteLineEdit(QLineEdit):
self._model = CompleteModel(parent=self) self._model = CompleteModel(parent=self)
self.complete_window = CompleteWindow(self, self._model) self.complete_window = CompleteWindow(self, self._model)
self.textChanged.connect(self.text_changed) self.textEdited.connect(self.text_edited)
self.cursorPositionChanged.connect(self.cursor_position_changed)
self.complete_window.completion_selected.connect(self.completion_selected) self.complete_window.completion_selected.connect(self.completion_selected)
self.installEventFilter(self)
# Interface {{{ # Interface {{{
def update_items_cache(self, complete_items): def update_items_cache(self, complete_items):
@ -198,14 +198,13 @@ class MultiCompleteLineEdit(QLineEdit):
return QLineEdit.eventFilter(self, o, e) return QLineEdit.eventFilter(self, o, e)
def text_changed(self, *args): def text_edited(self, *args):
self.update_completions()
def cursor_position_changed(self, *args):
self.update_completions() self.update_completions()
def update_completions(self): def update_completions(self):
' Update the list of completions ' ' Update the list of completions '
if not self.complete_window.isVisible() and not self.hasFocus():
return
cpos = self.cursorPosition() cpos = self.cursorPosition()
text = unicode(self.text()) text = unicode(self.text())
prefix = text[:cpos] prefix = text[:cpos]
@ -223,7 +222,7 @@ class MultiCompleteLineEdit(QLineEdit):
text text
''' '''
if self.sep is None: if self.sep is None:
return text return -1, text
else: else:
cursor_pos = self.cursorPosition() cursor_pos = self.cursorPosition()
before_text = unicode(self.text())[:cursor_pos] 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(): if len(after_parts) < 3 and not after_parts[-1].strip():
after_text = u'' after_text = u''
prefix_len = len(before_text.split(self.sep)[-1].lstrip()) prefix_len = len(before_text.split(self.sep)[-1].lstrip())
if self.space_before_sep: return prefix_len, \
complete_text_pat = '%s%s %s %s' before_text[:cursor_pos - prefix_len] + text + after_text
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)
def completion_selected(self, 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: if self.sep is None:
self.setText(ctext) self.setText(ctext)
self.setCursorPosition(len(ctext)) self.setCursorPosition(len(ctext))
else: else:
cursor_pos = self.cursorPosition() cursor_pos = self.cursorPosition()
self.setText(ctext) 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): def update_complete_window(self, matches):
self._model.update_matches(matches) self._model.update_matches(matches)
@ -334,6 +327,11 @@ class MultiCompleteComboBox(EnComboBox):
def __init__(self, *args): def __init__(self, *args):
EnComboBox.__init__(self, *args) EnComboBox.__init__(self, *args)
self.setLineEdit(MultiCompleteLineEdit(self)) 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): def update_items_cache(self, complete_items):
self.lineEdit().update_items_cache(complete_items) self.lineEdit().update_items_cache(complete_items)

View File

@ -27,8 +27,8 @@ class HeuristicsWidget(Widget, Ui_Form):
'dehyphenate', 'renumber_headings'] 'dehyphenate', 'renumber_headings']
) )
self.db, self.book_id = db, book_id self.db, self.book_id = db, book_id
self.rssb_defaults = [u'', u'<hr />', u'* * *', u'• • •', u'✦ ✦ ✦', self.rssb_defaults = [u'', u'<hr />', u' ', u'• • •', u'♦ ♦ ♦',
u'✮ ✮ ✮', u'☆ ☆ ☆', u'❂ ❂ ❂', u'✣ ✣ ✣', u'❖ ❖ ❖', u'☼ ☼ ☼', u'✠ ✠ ✠'] u'† †', u'‡ ‡ ‡', u'∞ ∞ ∞', u'¤ ¤ ¤', u'§']
self.initialize_options(get_option, get_help, db, book_id) self.initialize_options(get_option, get_help, db, book_id)
self.load_histories() self.load_histories()

View File

@ -12,11 +12,11 @@ from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \
QPainterPath, QLinearGradient, QBrush, \ QPainterPath, QLinearGradient, QBrush, \
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \ QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
QIcon, QDoubleSpinBox, QVariant, QSpinBox, \ QIcon, QDoubleSpinBox, QVariant, QSpinBox, \
QStyledItemDelegate, QCompleter, \ QStyledItemDelegate, 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, CompleteLineEdit from calibre.gui2.widgets import EnLineEdit
from calibre.gui2.complete import MultiCompleteLineEdit
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
@ -151,38 +151,15 @@ class TextDelegate(QStyledItemDelegate): # {{{
self.auto_complete_function = f self.auto_complete_function = f
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
editor = EnLineEdit(parent)
if self.auto_complete_function: if self.auto_complete_function:
editor = MultiCompleteLineEdit(parent)
editor.set_separator(None)
complete_items = [i[1] for i in self.auto_complete_function()] complete_items = [i[1] for i in self.auto_complete_function()]
completer = QCompleter(complete_items, self) editor.update_items_cache(complete_items)
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
else: else:
editor = EnLineEdit(parent) editor = EnLineEdit(parent)
return editor return editor
# }}} #}}}
class CompleteDelegate(QStyledItemDelegate): # {{{ class CompleteDelegate(QStyledItemDelegate): # {{{
def __init__(self, parent, sep, items_func_name, space_before_sep=False): 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): def createEditor(self, parent, option, index):
if self.db and hasattr(self.db, self.items_func_name): if self.db and hasattr(self.db, self.items_func_name):
col = index.model().column_map[index.column()] 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): if not index.model().is_custom_column(col):
editor = CompleteLineEdit(parent, getattr(self.db, self.items_func_name)(), all_items = getattr(self.db, self.items_func_name)()
self.sep, self.space_before_sep)
else: else:
editor = CompleteLineEdit(parent, all_items = list(self.db.all_custom(
sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))), label=self.db.field_metadata.key_to_label(col)))
key=sort_key), self.sep, self.space_before_sep) editor.update_items_cache(all_items)
else: else:
editor = EnLineEdit(parent) editor = EnLineEdit(parent)
return editor return editor
@ -273,13 +252,11 @@ class CcTextDelegate(QStyledItemDelegate): # {{{
editor.setRange(-100., float(sys.maxint)) editor.setRange(-100., float(sys.maxint))
editor.setDecimals(2) editor.setDecimals(2)
else: 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))), complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))),
key=sort_key) key=sort_key)
completer = QCompleter(complete_items, self) editor.update_items_cache(complete_items)
completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setCompletionMode(QCompleter.PopupCompletion)
editor.setCompleter(completer)
return editor return editor
# }}} # }}}

View File

@ -12,7 +12,7 @@ from PyQt4.Qt import Qt, QDateEdit, QDate, \
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \ QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \
QPushButton, QSpinBox, QLineEdit 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.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.config import tweaks, prefs from calibre.utils.config import tweaks, prefs
@ -283,13 +283,14 @@ class AuthorSortEdit(EnLineEdit):
# }}} # }}}
# Series {{{ # Series {{{
class SeriesEdit(EnComboBox): class SeriesEdit(MultiCompleteComboBox):
TOOLTIP = _('List of known series. You can add new series.') TOOLTIP = _('List of known series. You can add new series.')
LABEL = _('&Series:') LABEL = _('&Series:')
def __init__(self, parent): def __init__(self, parent):
EnComboBox.__init__(self, parent) MultiCompleteComboBox.__init__(self, parent)
self.set_separator(None)
self.dialog = parent self.dialog = parent
self.setSizeAdjustPolicy( self.setSizeAdjustPolicy(
self.AdjustToMinimumContentsLengthWithIcon) self.AdjustToMinimumContentsLengthWithIcon)
@ -314,6 +315,7 @@ class SeriesEdit(EnComboBox):
def initialize(self, db, id_): def initialize(self, db, id_):
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]))
self.update_items_cache([x[1] for x in all_series])
series_id = db.series_id(id_, index_is_id=True) series_id = db.series_id(id_, index_is_id=True)
idx, c = None, 0 idx, c = None, 0
for i in all_series: for i in all_series:
@ -910,11 +912,12 @@ class ISBNEdit(QLineEdit): # {{{
# }}} # }}}
class PublisherEdit(EnComboBox): # {{{ class PublisherEdit(MultiCompleteComboBox): # {{{
LABEL = _('&Publisher:') LABEL = _('&Publisher:')
def __init__(self, parent): def __init__(self, parent):
EnComboBox.__init__(self, parent) MultiCompleteComboBox.__init__(self, parent)
self.set_separator(None)
self.setSizeAdjustPolicy( self.setSizeAdjustPolicy(
self.AdjustToMinimumContentsLengthWithIcon) self.AdjustToMinimumContentsLengthWithIcon)
@ -935,6 +938,7 @@ class PublisherEdit(EnComboBox): # {{{
def initialize(self, db, id_): def initialize(self, db, id_):
all_publishers = db.all_publishers() all_publishers = db.all_publishers()
all_publishers.sort(key=lambda x : sort_key(x[1])) 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) publisher_id = db.publisher_id(id_, index_is_id=True)
idx, c = None, 0 idx, c = None, 0
for i in all_publishers: for i in all_publishers:

View File

@ -316,9 +316,19 @@ remove all non-breaking-space entities, or may include false positive matches re
:guilabel:`Replace scene breaks` :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 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 /> user. Please note that some ornamental characters may not be supported across all reading devices.
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. 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` :guilabel:`Remove unnecessary hyphens`
|app| will analyze all hyphenated content in the document when this option is enabled. The document itself is used |app| will analyze all hyphenated content in the document when this option is enabled. The document itself is used