From 8627ec9683548dff8414dcf5d6d7ee35959bfd0c Mon Sep 17 00:00:00 2001 From: ldolse Date: Wed, 2 Feb 2011 14:09:25 +0800 Subject: [PATCH 01/29] improve new docs, user specfied width handled correctly, default scene break markers work across Kindle and ADE based devices --- src/calibre/ebooks/conversion/utils.py | 5 +++-- src/calibre/gui2/convert/heuristics.py | 4 ++-- src/calibre/manual/conversion.rst | 16 +++++++++++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py index 8a339afe4c..16ef4c86e2 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/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/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 From b22e640b509d811d22705e2ad997a535ec053f34 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 2 Feb 2011 09:03:06 +0000 Subject: [PATCH 02/29] Fix template program regression triggered by recursively calling the processor --- src/calibre/utils/formatter_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 2e5852df89..518f2ed140 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -186,7 +186,7 @@ class BuiltinTemplate(BuiltinFormatterFunction): def evaluate(self, formatter, kwargs, mi, locals, template): template = template.replace('[[', '{').replace(']]', '}') - return formatter.safe_format(template, kwargs, 'TEMPLATE', mi) + return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi) class BuiltinEval(BuiltinFormatterFunction): name = 'eval' From dece4f236fd8e149089619f455e2522cff8f545b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 09:21:21 -0700 Subject: [PATCH 03/29] Connect/share menu: Re-organize to make it a little less easy to select email and delete instead of just email by mistake --- src/calibre/gui2/actions/device.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) 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')) From 92b313ec7e5f4ee21a1ecd0b43503db85f2502ee Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 09:47:18 -0700 Subject: [PATCH 04/29] ... --- resources/recipes/le_temps.recipe | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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'}) From 1de9e0b94b1398c577cab36b87b849569f773b0d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 10:44:18 -0700 Subject: [PATCH 05/29] use the new completer for publisher and series boxes as well --- src/calibre/gui2/complete.py | 18 +++++++++++++----- src/calibre/gui2/metadata/basic_widgets.py | 14 +++++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index ce8609fc99..7fbfae901a 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,10 @@ class MultiCompleteLineEdit(QLineEdit): self._model = CompleteModel(parent=self) 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.installEventFilter(self) # Interface {{{ def update_items_cache(self, complete_items): @@ -198,7 +199,7 @@ class MultiCompleteLineEdit(QLineEdit): 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): @@ -206,6 +207,8 @@ class MultiCompleteLineEdit(QLineEdit): 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 +226,7 @@ class MultiCompleteLineEdit(QLineEdit): text ''' if self.sep is None: - return text + return -1, -1, text else: cursor_pos = self.cursorPosition() before_text = unicode(self.text())[:cursor_pos] @@ -334,6 +337,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/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: From 99fa9659c9f8618eaa89494599e782ab277ddaee Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 11:03:03 -0700 Subject: [PATCH 06/29] dont popup completer on cursor position changing as that can be confusing for noobs --- src/calibre/gui2/complete.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index 7fbfae901a..39ecd847ec 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -176,7 +176,6 @@ class MultiCompleteLineEdit(QLineEdit): self._model = CompleteModel(parent=self) self.complete_window = CompleteWindow(self, self._model) self.textEdited.connect(self.text_edited) - self.cursorPositionChanged.connect(self.cursor_position_changed) self.complete_window.completion_selected.connect(self.completion_selected) self.installEventFilter(self) @@ -202,9 +201,6 @@ class MultiCompleteLineEdit(QLineEdit): def text_edited(self, *args): self.update_completions() - def cursor_position_changed(self, *args): - self.update_completions() - def update_completions(self): ' Update the list of completions ' if not self.complete_window.isVisible() and not self.hasFocus(): From b2055e106ee9d7d637292a08960a89a396269d83 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 11:07:16 -0700 Subject: [PATCH 07/29] Don't insert separator after completion, again confusing for noobs --- src/calibre/gui2/complete.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index 39ecd847ec..0ad8fb13d4 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -222,7 +222,7 @@ class MultiCompleteLineEdit(QLineEdit): text ''' if self.sep is None: - return -1, -1, text + return -1, text else: cursor_pos = self.cursorPosition() before_text = unicode(self.text())[:cursor_pos] @@ -231,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) From fd3ed9b8f6f20519fdfd337538d16c24e0e5dfaa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 11:40:37 -0700 Subject: [PATCH 08/29] Use the new completer in all delegates --- src/calibre/gui2/library/delegates.py | 57 ++++++++------------------- 1 file changed, 17 insertions(+), 40 deletions(-) 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 # }}} From e8fd598c48e08bdba95e7d894aa31af1ee40a300 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 2 Feb 2011 18:42:03 +0000 Subject: [PATCH 09/29] Handle case change for tags and authors. --- src/calibre/gui2/actions/edit_metadata.py | 1 + src/calibre/gui2/dialogs/metadata_single.py | 17 ++++--- src/calibre/gui2/library/models.py | 6 ++- src/calibre/library/database2.py | 54 ++++++++++++++++----- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index f50251e700..03fb8fa4eb 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -160,6 +160,7 @@ class EditMetadataAction(InterfaceAction): break changed.add(d.id) + changed |= d.books_to_refresh if d.row_delta == 0: break current_row += d.row_delta diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index fa20658c12..43e26fad9d 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -622,6 +622,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.original_author = unicode(self.authors.text()).strip() self.original_title = unicode(self.title.text()).strip() + self.books_to_refresh = set() self.show() @@ -775,7 +776,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): _('You have changed the tags. In order to use the tags' ' editor, you must either discard or apply these ' 'changes. Apply changes?'), show_copy_button=False): - self.apply_tags(commit=True, notify=True) + self.books_to_refresh |= self.apply_tags(commit=True, notify=True, + allow_case_change=True) self.original_tags = unicode(self.tags.text()) else: self.tags.setText(self.original_tags) @@ -882,9 +884,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): break def apply_tags(self, commit=False, notify=False): - self.db.set_tags(self.id, [x.strip() for x in - unicode(self.tags.text()).split(',')], - notify=notify, commit=commit) + return self.db.set_tags(self.id, [x.strip() for x in + unicode(self.tags.text()).split(',')], + notify=notify, commit=commit, allow_case_change=True) def next_triggered(self, row_delta, *args): self.row_delta = row_delta @@ -903,7 +905,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.db.set_title_sort(self.id, ts, notify=False, commit=False) au = unicode(self.authors.text()).strip() if au and au != self.original_author: - self.db.set_authors(self.id, string_to_authors(au), notify=False) + self.books_to_refresh |= self.db.set_authors(self.id, + string_to_authors(au), + notify=False, + allow_case_change=True) aus = unicode(self.author_sort.text()).strip() if aus: self.db.set_author_sort(self.id, aus, notify=False, commit=False) @@ -913,7 +918,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): notify=False, commit=False) self.db.set_rating(self.id, 2*self.rating.value(), notify=False, commit=False) - self.apply_tags() + self.books_to_refresh |= self.apply_tags() self.db.set_publisher(self.id, unicode(self.publisher.currentText()).strip(), notify=False, commit=False) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 31b8cf46bf..088b9f6d02 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -819,6 +819,7 @@ class BooksModel(QAbstractTableModel): # {{{ value.toDate() if column in ('timestamp', 'pubdate') else \ unicode(value.toString()) id = self.db.id(row) + books_to_refresh = set([id]) if column == 'rating': val = 0 if val < 0 else 5 if val > 5 else val val *= 2 @@ -850,8 +851,9 @@ class BooksModel(QAbstractTableModel): # {{{ return False self.db.set_pubdate(id, qt_to_dt(val, as_utc=False)) else: - self.db.set(row, column, val) - self.refresh_ids([id], row) + books_to_refresh |= self.db.set(row, column, val, + allow_case_change=True) + self.refresh_ids(list(books_to_refresh), row) self.dataChanged.emit(index, index) return True diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index bfe54df36e..88f2ce32de 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1479,17 +1479,19 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return float(tweaks['series_index_auto_increment']) return 1.0 - def set(self, row, column, val): + def set(self, row, column, val, allow_case_change=False): ''' Convenience method for setting the title, authors, publisher or rating ''' id = self.data[row][0] col = {'title':1, 'authors':2, 'publisher':3, 'rating':4, 'tags':7}[column] + books_to_refresh = set() self.data.set(row, col, val) if column == 'authors': val = string_to_authors(val) - self.set_authors(id, val, notify=False) + books_to_refresh |= self.set_authors(id, val, notify=False, + allow_case_change=allow_case_change) elif column == 'title': self.set_title(id, val, notify=False) elif column == 'publisher': @@ -1497,11 +1499,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): elif column == 'rating': self.set_rating(id, val, notify=False) elif column == 'tags': - self.set_tags(id, [x.strip() for x in val.split(',') if x.strip()], - append=False, notify=False) + books_to_refresh |= \ + self.set_tags(id, [x.strip() for x in val.split(',') if x.strip()], + append=False, notify=False, allow_case_change=allow_case_change) self.data.refresh_ids(self, [id]) self.set_path(id, True) self.notify('metadata', [id]) + return books_to_refresh def set_metadata(self, id, mi, ignore_errors=False, set_title=True, set_authors=True, commit=True): @@ -1627,28 +1631,38 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): result.append(r) return ' & '.join(result).replace('|', ',') - def _set_authors(self, id, authors): + def _set_authors(self, id, authors, allow_case_change=False): if not authors: authors = [_('Unknown')] self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,)) + books_to_refresh = set() for a in authors: if not a: continue a = a.strip().replace(',', '|') if not isinstance(a, unicode): a = a.decode(preferred_encoding, 'replace') - author = self.conn.get('SELECT id from authors WHERE name=?', (a,), all=False) - if author: - aid = author + author_id, name = \ + self.conn.get('SELECT id, name from authors WHERE name=?', (a,))[0] + if author_id: + aid = author_id # Handle change of case - self.conn.execute('UPDATE authors SET name=? WHERE id=?', (a, aid)) + if allow_case_change and name != a: + self.conn.execute('UPDATE authors SET name=? WHERE id=?', (a, aid)) + case_change = True else: aid = self.conn.execute('INSERT INTO authors(name) VALUES (?)', (a,)).lastrowid + case_change = False try: self.conn.execute('INSERT INTO books_authors_link(book, author) VALUES (?,?)', (id, aid)) except IntegrityError: # Sometimes books specify the same author twice in their metadata pass + if case_change: + bks = self.conn.get('SELECT book FROM books_authors_link WHERE author=?', + (aid,)) + books_to_refresh |= set([bk[0] for bk in bks]) + ss = self.author_sort_from_book(id, index_is_id=True) self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (ss, id)) @@ -1660,21 +1674,25 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.data.set(id, self.FIELD_MAP['au_map'], ':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]), row_is_id=True) + return books_to_refresh - def set_authors(self, id, authors, notify=True, commit=True): + def set_authors(self, id, authors, notify=True, commit=True, + allow_case_change=False): ''' Note that even if commit is False, the db will still be committed to because this causes the location of files to change :param authors: A list of authors. ''' - self._set_authors(id, authors) + books_to_refresh = self._set_authors(id, authors, + allow_case_change=allow_case_change) self.dirtied([id], commit=False) if commit: self.conn.commit() self.set_path(id, index_is_id=True) if notify: self.notify('metadata', [id]) + return books_to_refresh def set_title_sort(self, id, title_sort_, notify=True, commit=True): if not title_sort_: @@ -2119,7 +2137,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def commit(self): self.conn.commit() - def set_tags(self, id, tags, append=False, notify=True, commit=True): + def set_tags(self, id, tags, append=False, notify=True, commit=True, + allow_case_change=False): ''' @param tags: list of strings @param append: If True existing tags are not removed @@ -2129,6 +2148,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.conn.execute('DELETE FROM tags WHERE (SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) < 1') otags = self.get_tags(id) tags = self.cleanup_tags(tags) + books_to_refresh = set() for tag in (set(tags)-otags): tag = tag.strip() if not tag: @@ -2144,8 +2164,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if idx > -1: etag = existing_tags[idx] tid = self.conn.get('SELECT id FROM tags WHERE name=?', (etag,), all=False) - if etag != tag: + if allow_case_change and etag != tag: self.conn.execute('UPDATE tags SET name=? WHERE id=?', (tag, tid)) + case_changed = True + else: + case_changed = False else: tid = self.conn.execute('INSERT INTO tags(name) VALUES(?)', (tag,)).lastrowid @@ -2153,6 +2176,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): (id, tid), all=False): self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)', (id, tid)) + if case_changed: + bks = self.conn.get('SELECT book FROM books_tags_link WHERE tag=?', + (tid,)) + books_to_refresh |= set([bk[0] for bk in bks]) self.dirtied([id], commit=False) if commit: self.conn.commit() @@ -2160,6 +2187,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.data.set(id, self.FIELD_MAP['tags'], tags, row_is_id=True) if notify: self.notify('metadata', [id]) + return books_to_refresh def unapply_tags(self, book_id, tags, notify=True): for tag in tags: From fa0d8db045e3c548a230e191ae2c0910b20ae782 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 2 Feb 2011 18:53:19 +0000 Subject: [PATCH 10/29] Correct implementation of case in _set_authors --- src/calibre/library/database2.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 88f2ce32de..ce2d24c045 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1642,8 +1642,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): a = a.strip().replace(',', '|') if not isinstance(a, unicode): a = a.decode(preferred_encoding, 'replace') - author_id, name = \ - self.conn.get('SELECT id, name from authors WHERE name=?', (a,))[0] + aus = self.conn.get('SELECT id, name from authors WHERE name=?', (a,)) + if aus: + author_id, name = aus[0] + else: + author_id, name = (None, None) if author_id: aid = author_id # Handle change of case From c6260c678ac073b63e65c8f6900fecdc169c5f10 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 12:12:45 -0700 Subject: [PATCH 11/29] Refactoring to use new completer --- src/calibre/gui2/convert/metadata.ui | 12 +++++----- src/calibre/gui2/custom_column_widgets.py | 28 +++++++++++++---------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/calibre/gui2/convert/metadata.ui b/src/calibre/gui2/convert/metadata.ui index 61c27594c4..47d983d870 100644 --- a/src/calibre/gui2/convert/metadata.ui +++ b/src/calibre/gui2/convert/metadata.ui @@ -190,7 +190,7 @@ - + Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas. @@ -255,7 +255,7 @@ - + true @@ -282,14 +282,14 @@
widgets.h
- CompleteComboBox + MultiCompleteComboBox QComboBox -
widgets.h
+
calibre/gui2/complete.h
- CompleteLineEdit + MultiCompleteLineEdit QLineEdit -
widgets.h
+
calibre/gui2/complete.h
ImageView diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 360a5bcd0a..0555d42b4f 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -14,7 +14,7 @@ from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \ QPushButton from calibre.utils.date import qt_to_dt, now -from calibre.gui2.widgets import CompleteLineEdit, EnComboBox +from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox from calibre.gui2.comments_editor import Editor as CommentsEditor from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.utils.config import tweaks @@ -228,10 +228,11 @@ class Text(Base): values = self.all_values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) if self.col_metadata['is_multiple']: - w = CompleteLineEdit(parent, values) + w = MultiCompleteLineEdit(parent) + w.update_items_cache(values) w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) else: - w = EnComboBox(parent) + w = MultiCompleteComboBox(parent) w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) w.setMinimumContentsLength(25) self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w] @@ -240,9 +241,10 @@ class Text(Base): val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) self.initial_val = val val = self.normalize_db_val(val) + self.widgets[1].update_items_cache(self.all_values) + if self.col_metadata['is_multiple']: self.setter(val) - self.widgets[1].update_items_cache(self.all_values) else: idx = None for i, c in enumerate(self.all_values): @@ -276,7 +278,7 @@ class Series(Base): def setup_ui(self, parent): values = self.all_values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) - w = EnComboBox(parent) + w = MultiCompleteComboBox(parent) w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) w.setMinimumContentsLength(25) self.name_widget = w @@ -305,6 +307,7 @@ class Series(Base): if c == val: idx = i self.name_widget.addItem(c) + self.name_widget.update_items_cache(self.all_values) self.name_widget.setEditText('') if idx is not None: self.widgets[1].setCurrentIndex(idx) @@ -670,7 +673,7 @@ class BulkDateTime(BulkBase): class BulkSeries(BulkBase): def setup_ui(self, parent): - self.make_widgets(parent, EnComboBox) + self.make_widgets(parent, MultiCompleteComboBox) values = self.all_values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon) @@ -705,6 +708,7 @@ class BulkSeries(BulkBase): def initialize(self, book_id): self.idx_widget.setChecked(False) + self.main_widget.update_items_cache(self.all_values) for c in self.all_values: self.main_widget.addItem(c) self.main_widget.setEditText('') @@ -795,7 +799,8 @@ class RemoveTags(QWidget): layout.setSpacing(5) layout.setContentsMargins(0, 0, 0, 0) - self.tags_box = CompleteLineEdit(parent, values) + self.tags_box = MultiCompleteLineEdit(parent) + self.tags_box.update_items_cache(values) layout.addWidget(self.tags_box, stretch=3) self.checkbox = QCheckBox(_('Remove all tags'), parent) layout.addWidget(self.checkbox) @@ -816,7 +821,7 @@ class BulkText(BulkBase): values = self.all_values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) if self.col_metadata['is_multiple']: - self.make_widgets(parent, CompleteLineEdit, + self.make_widgets(parent, MultiCompleteLineEdit, extra_label_text=_('tags to add')) self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self.adding_widget = self.main_widget @@ -829,16 +834,15 @@ class BulkText(BulkBase): w.tags_box.textChanged.connect(self.a_c_checkbox_changed) w.checkbox.stateChanged.connect(self.a_c_checkbox_changed) else: - self.make_widgets(parent, EnComboBox) + self.make_widgets(parent, MultiCompleteComboBox) self.main_widget.setSizeAdjustPolicy( self.main_widget.AdjustToMinimumContentsLengthWithIcon) self.main_widget.setMinimumContentsLength(25) self.ignore_change_signals = False def initialize(self, book_ids): - if self.col_metadata['is_multiple']: - self.main_widget.update_items_cache(self.all_values) - else: + self.main_widget.update_items_cache(self.all_values) + if not self.col_metadata['is_multiple']: val = self.get_initial_value(book_ids) self.initial_val = val = self.normalize_db_val(val) idx = None From fc3123e4c171f504f5e8fa9cd14f6c444b1e575a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 12:20:07 -0700 Subject: [PATCH 12/29] Refactor advanced search dialog to use new completer --- src/calibre/gui2/dialogs/search.py | 7 ++----- src/calibre/gui2/dialogs/search.ui | 14 +++++++------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index ab3fd3ec4e..0fbe188aa5 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal ' import re, copy -from PyQt4.Qt import QDialog, QDialogButtonBox, QCompleter, Qt +from PyQt4.Qt import QDialog, QDialogButtonBox from calibre.gui2.dialogs.search_ui import Ui_Dialog from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH @@ -29,20 +29,17 @@ class SearchDialog(QDialog, Ui_Dialog): name = name.strip().replace('|', ',') self.authors_box.addItem(name) self.authors_box.setEditText('') - self.authors_box.completer().setCompletionMode(QCompleter.PopupCompletion) - 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.sort(key=lambda x : sort_key(x[1])) + self.series_box.update_items_cache([x[1] for x in all_series]) for i in all_series: id, name = i self.series_box.addItem(name) self.series_box.setEditText('') - self.series_box.completer().setCompletionMode(QCompleter.PopupCompletion) - self.series_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive) all_tags = db.all_tags() self.tags_box.update_items_cache(all_tags) diff --git a/src/calibre/gui2/dialogs/search.ui b/src/calibre/gui2/dialogs/search.ui index 1d013a1e9f..842787a2da 100644 --- a/src/calibre/gui2/dialogs/search.ui +++ b/src/calibre/gui2/dialogs/search.ui @@ -265,21 +265,21 @@
- + Enter an author's name. Only one author can be used. - + Enter a series name, without an index. Only one series name can be used. - + Enter tags separated by spaces @@ -360,14 +360,14 @@
widgets.h
- CompleteLineEdit + MultiCompleteLineEdit QLineEdit -
widgets.h
+
calibre/gui2/complete.h
- CompleteComboBox + MultiCompleteComboBox QComboBox -
widgets.h
+
calibre/gui2/complete.h
From c7c3027c8d2602687e1f6cb198486b19b1edd671 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 12:29:54 -0700 Subject: [PATCH 13/29] Refactor bulk metadata edit dialog to use new completer (apart from S&R widgets) --- src/calibre/gui2/dialogs/metadata_bulk.py | 2 ++ src/calibre/gui2/dialogs/metadata_bulk.ui | 23 +++++++++-------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 533a344de5..9239a0e136 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -764,6 +764,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): def initialize_series(self): all_series = self.db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) + self.series.update_items_cache([x[1] for x in all_series]) for i in all_series: id, name = i @@ -773,6 +774,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): def initialize_publisher(self): all_publishers = self.db.all_publishers() all_publishers.sort(key=lambda x : sort_key(x[1])) + self.publisher.update_items_cache([x[1] for x in all_publishers]) for i in all_publishers: id, name = i diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index b0f2c144fc..ecdb396662 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -76,7 +76,7 @@
- + true @@ -175,7 +175,7 @@ - + true @@ -195,7 +195,7 @@ - + Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas. @@ -229,7 +229,7 @@ - + Comma separated list of tags to remove from the books. @@ -262,7 +262,7 @@ - + 0 @@ -1072,19 +1072,14 @@ not multiple and the destination field is multiple
widgets.h
- EnComboBox + MultiCompleteComboBox QComboBox -
widgets.h
+
calibre/gui2/complete.h
- CompleteComboBox - QComboBox -
widgets.h
-
- - CompleteLineEdit + MultiCompleteLineEdit QLineEdit -
widgets.h
+
calibre/gui2/complete.h
HistoryLineEdit From b02186802f8320c1bcb4f1bf11c09624c8bec29d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 12:43:19 -0700 Subject: [PATCH 14/29] ... --- src/calibre/gui2/convert/metadata.py | 10 +++++++--- src/calibre/gui2/convert/metadata.ui | 9 ++------- src/calibre/gui2/custom_column_widgets.py | 3 +++ src/calibre/gui2/dialogs/metadata_bulk.py | 2 ++ src/calibre/gui2/dialogs/search.py | 1 + 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index 23cac74cf8..81274f25a8 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -70,9 +70,6 @@ class MetadataWidget(Widget, Ui_Form): def initialize_metadata_options(self): self.initialize_combos() 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) self.title.setText(mi.title) @@ -109,6 +106,9 @@ class MetadataWidget(Widget, Ui_Form): def initalize_authors(self): all_authors = self.db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) + self.author.set_separator('&') + self.author.set_space_before_sep(True) + self.author.update_items_cache(self.db.all_author_names()) for i in all_authors: id, name = i @@ -124,6 +124,8 @@ class MetadataWidget(Widget, Ui_Form): def initialize_series(self): all_series = self.db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) + self.series.set_separator(None) + self.series.update_items_cache([x[1] for x in all_series]) for i in all_series: id, name = i @@ -133,6 +135,8 @@ class MetadataWidget(Widget, Ui_Form): def initialize_publisher(self): all_publishers = self.db.all_publishers() all_publishers.sort(key=lambda x : sort_key(x[1])) + self.publisher.set_separator(None) + self.publisher.update_items_cache([x[1] for x in all_publishers]) for i in all_publishers: id, name = i diff --git a/src/calibre/gui2/convert/metadata.ui b/src/calibre/gui2/convert/metadata.ui index 47d983d870..95ccac6890 100644 --- a/src/calibre/gui2/convert/metadata.ui +++ b/src/calibre/gui2/convert/metadata.ui @@ -213,7 +213,7 @@
- + 10 @@ -248,7 +248,7 @@ - + true @@ -276,11 +276,6 @@ QLineEdit
widgets.h
- - EnComboBox - QComboBox -
widgets.h
-
MultiCompleteComboBox QComboBox diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 0555d42b4f..5180999379 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -233,6 +233,7 @@ class Text(Base): w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) else: w = MultiCompleteComboBox(parent) + w.set_separator(None) w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) w.setMinimumContentsLength(25) self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w] @@ -708,6 +709,7 @@ class BulkSeries(BulkBase): def initialize(self, book_id): self.idx_widget.setChecked(False) + self.main_widget.set_separator(None) self.main_widget.update_items_cache(self.all_values) for c in self.all_values: self.main_widget.addItem(c) @@ -835,6 +837,7 @@ class BulkText(BulkBase): w.checkbox.stateChanged.connect(self.a_c_checkbox_changed) else: self.make_widgets(parent, MultiCompleteComboBox) + self.main_widget.set_separator(None) self.main_widget.setSizeAdjustPolicy( self.main_widget.AdjustToMinimumContentsLengthWithIcon) self.main_widget.setMinimumContentsLength(25) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 9239a0e136..12f49baaca 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -764,6 +764,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): def initialize_series(self): all_series = self.db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) + self.series.set_separator(None) self.series.update_items_cache([x[1] for x in all_series]) for i in all_series: @@ -774,6 +775,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): def initialize_publisher(self): all_publishers = self.db.all_publishers() all_publishers.sort(key=lambda x : sort_key(x[1])) + self.publisher.set_separator(None) self.publisher.update_items_cache([x[1] for x in all_publishers]) for i in all_publishers: diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index 0fbe188aa5..9c91446f3c 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -35,6 +35,7 @@ class SearchDialog(QDialog, Ui_Dialog): all_series = db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) + self.series_box.set_separator(None) self.series_box.update_items_cache([x[1] for x in all_series]) for i in all_series: id, name = i From f76a8e5f4f493eaaf2329db418cb9d84d7c17fdc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 12:50:31 -0700 Subject: [PATCH 15/29] Refactor old edit metadata dialog to use new completer --- src/calibre/gui2/dialogs/metadata_single.py | 4 ++++ src/calibre/gui2/dialogs/metadata_single.ui | 21 ++++++++------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index fa20658c12..f36fd3019d 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -739,6 +739,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow) all_series = self.db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) + self.series.set_separator(None) + self.series.update_items_cache([x[1] for x in all_series]) series_id = self.db.series_id(self.row) idx, c = None, 0 for i in all_series: @@ -756,6 +758,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): def initialize_publisher(self): all_publishers = self.db.all_publishers() all_publishers.sort(key=lambda x : sort_key(x[1])) + self.publisher.set_separator(None) + self.publisher.update_items_cache([x[1] for x in all_publishers]) publisher_id = self.db.publisher_id(self.row) idx, c = None, 0 for i in all_publishers: diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index 23efc45399..5bcf268aaa 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -240,7 +240,7 @@ Using this button to create author sort will change author sort from red to gree
- + true @@ -313,7 +313,7 @@ If the box is colored green, then text matches the individual author's sort stri - + true @@ -335,7 +335,7 @@ If the box is colored green, then text matches the individual author's sort stri - + Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas. @@ -379,7 +379,7 @@ If the box is colored green, then text matches the individual author's sort stri 5 - + List of known series. You can add new series. @@ -837,19 +837,14 @@ If the box is colored green, then text matches the individual author's sort stri
widgets.h
- EnComboBox - QComboBox -
widgets.h
-
- - CompleteLineEdit + MultiCompleteLineEdit QLineEdit -
widgets.h
+
calibre/gui2/complete.h
- CompleteComboBox + MultiCompleteComboBox QComboBox -
widgets.h
+
calibre/gui2/complete.h
FormatList From ef88d0b5ccc5f30cde2df90fc364d509c397576b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 12:54:10 -0700 Subject: [PATCH 16/29] ... --- src/calibre/gui2/dialogs/add_empty_book.py | 4 ++-- src/calibre/gui2/dialogs/search.ui | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/dialogs/add_empty_book.py b/src/calibre/gui2/dialogs/add_empty_book.py index b8339f95f5..9e5fb07308 100644 --- a/src/calibre/gui2/dialogs/add_empty_book.py +++ b/src/calibre/gui2/dialogs/add_empty_book.py @@ -7,8 +7,8 @@ __license__ = 'GPL v3' from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \ QApplication, QSpinBox, QToolButton, QIcon from calibre.ebooks.metadata import authors_to_string, string_to_authors -from calibre.gui2.widgets import CompleteComboBox from calibre.utils.icu import sort_key +from calibre.gui2.complete import MultiCompleteComboBox class AddEmptyBookDialog(QDialog): @@ -32,7 +32,7 @@ class AddEmptyBookDialog(QDialog): self.author_label = QLabel(_('Set the author of the new books to:')) self._layout.addWidget(self.author_label, 2, 0, 1, 2) - self.authors_combo = CompleteComboBox(self) + self.authors_combo = MultiCompleteComboBox(self) self.authors_combo.setSizeAdjustPolicy( self.authors_combo.AdjustToMinimumContentsLengthWithIcon) self.authors_combo.setEditable(True) diff --git a/src/calibre/gui2/dialogs/search.ui b/src/calibre/gui2/dialogs/search.ui index 842787a2da..eb6fffdb60 100644 --- a/src/calibre/gui2/dialogs/search.ui +++ b/src/calibre/gui2/dialogs/search.ui @@ -354,11 +354,6 @@ QLineEdit
widgets.h
- - EnComboBox - QComboBox -
widgets.h
-
MultiCompleteLineEdit QLineEdit From f52ba0aae37d0534503619bd730ac5ffaa89636c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 14:13:24 -0700 Subject: [PATCH 17/29] ... --- src/calibre/gui2/complete.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index 0ad8fb13d4..bdfbaaf0da 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -64,8 +64,6 @@ 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 idx.isValid(): data = unicode(self.model().data(idx, Qt.DisplayRole)) self.completion_selected.emit(data) @@ -81,6 +79,9 @@ class CompleteWindow(QListView): # {{{ self.hide() return True elif key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab): + if key == Qt.Key_Tab and not self.currentIndex().isValid(): + if self.model().rowCount() > 0: + self.setCurrentIndex(self.model().index(0)) self.do_selected() return True elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, From 23ffe549a0f3dcb5cfaded30947603d383678025 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 16:10:15 -0700 Subject: [PATCH 18/29] Improve La Tribua de --- resources/recipes/la_tribuna.recipe | 43 ++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/resources/recipes/la_tribuna.recipe b/resources/recipes/la_tribuna.recipe index 739d11cc8d..c6d3f5cb28 100644 --- a/resources/recipes/la_tribuna.recipe +++ b/resources/recipes/la_tribuna.recipe @@ -2,24 +2,23 @@ __license__ = 'GPL v3' __author__ = 'Luis Hernandez' __copyright__ = 'Luis Hernandez' -description = 'Diario local de Talavera de la Reina - v1.2 - 27 Jan 2011' +__version__ = 'v1.0' +__date__ = '01 Feb 2011' ''' -http://www.latribunadetalavera.es/ +http://www.promecal.es/ ''' - from calibre.web.feeds.news import BasicNewsRecipe class AdvancedUserRecipe1294946868(BasicNewsRecipe): - title = u'La Tribuna de Talavera' + title = u'La Tribuna de' publisher = u'Grupo PROMECAL' __author__ = 'Luis Hernández' - description = 'Diario local de Talavera de la Reina' - cover_url = 'http://www.latribunadetalavera.es/entorno/mancheta.gif' + description = 'Varios diarios locales del grupo PROMECAL' - oldest_article = 5 + oldest_article = 3 max_articles_per_feed = 50 remove_javascript = True @@ -27,7 +26,7 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe): use_embedded_content = False encoding = 'utf-8' - language = 'es' + language = 'es_ES' timefmt = '[%a, %d %b, %Y]' keep_only_tags = [ @@ -39,7 +38,20 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe): remove_tags_before = dict(name='div' , attrs={'class':['comparte']}) remove_tags_after = dict(name='div' , attrs={'id':['relacionadas']}) - extra_css = ' p{text-align: justify; font-size: 100%} body{ text-align: left; font-family: serif; font-size: 100% } h1{ font-family: sans-serif; font-size:150%; font-weight: 700; text-align: justify; } h2{ font-family: sans-serif; font-size:120%; font-weight: 600; text-align: justify } h3{ font-family: sans-serif; font-size:60%; font-weight: 600; text-align: left } h4{ font-family: sans-serif; font-size:80%; font-weight: 600; text-align: left } h5{ font-family: sans-serif; font-size:70%; font-weight: 600; text-align: left }img{margin-bottom: 0.4em} ' + remove_tags = [ + dict(name='div', attrs={'id':['relacionadas']}) + ,dict(name='h3') + ,dict(name='h5') + ] + + extra_css = """ + p{text-align: justify; font-size: 100%} + body{text-align: left; font-family: serif; font-size: 100%} + h1{font-family: sans; font-size:150%; font-weight: bold; text-align: justify;} + h2{font-family: sans-serif; font-size:85%; font-style: italic; text-align: justify;} + h4{font-family: sans; font-size:75%; font-weight: bold; text-align: center;} + img{margin-bottom: 0.4em} + """ def preprocess_html(self, soup): for alink in soup.findAll('a'): @@ -48,4 +60,15 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe): alink.replaceWith(tstr) return soup - feeds = [(u'Portada', u'http://www.latribunadetalavera.es/rss.html')] + + feeds = [ + (u'Albacete', u'http://www.latribunadealbacete.es/rss.html') + ,(u'Avila', u'http://www.diariodeavila.es/rss.html') + ,(u'Burgos', u'http://www.diariodeburgos.es/rss.html') + ,(u'Ciudad Real', u'http://www.latribunadeciudadreal.es/rss.html') + ,(u'Palencia', u'http://www.diariopalentino.es/rss.html') + ,(u'Puertollano', u'http://www.latribunadepuertollano.es/rss.html') + ,(u'Talavera de la Reina', u'http://www.latribunadetalavera.es/rss.html') + ,(u'Toledo', u'http://www.latribunadetoledo.es/rss.html') + ,(u'Valladolid', u'http://www.eldiadevalladolid.com/rss.html') + ] From 399ce2c888defba16eedc88164293f4853bd2493 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 16:40:50 -0700 Subject: [PATCH 19/29] ... --- src/calibre/gui2/complete.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index bdfbaaf0da..f589b30679 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -198,6 +198,9 @@ class MultiCompleteLineEdit(QLineEdit): return True # Filter this event since the cw is visible return QLineEdit.eventFilter(self, o, e) + def hide_completion_window(self): + self.complete_window.hide() + def text_edited(self, *args): self.update_completions() From ac40d2e66d79b259e19c5d1ae471146522177f41 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Feb 2011 18:12:38 -0700 Subject: [PATCH 20/29] Fix Next and Previous buttons in new metadata dialog --- src/calibre/gui2/metadata/single.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index e9d34bd47b..e5de2e3d97 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -314,6 +314,10 @@ class MetadataSingleDialogBase(ResizableDialog): widget.commit(self.book_id) self.db.commit() + rows = self.db.refresh_ids(list(self.books_to_refresh)) + if rows: + self.rows_to_refresh |= set(rows) + return True def accept(self): @@ -335,12 +339,14 @@ class MetadataSingleDialogBase(ResizableDialog): self.current_row = current_row if view_slot is not None: self.view_format.connect(view_slot) - self.do_one() + self.do_one(apply_changes=False) ret = self.exec_() self.break_cycles() return ret - def do_one(self, delta=0): + def do_one(self, delta=0, apply_changes=True): + if apply_changes: + self.apply_changes() self.current_row += delta prev = next_ = None if self.current_row > 0: @@ -357,9 +363,7 @@ class MetadataSingleDialogBase(ResizableDialog): self.prev_button.setToolTip(tip) self.prev_button.setVisible(prev is not None) self(self.db.id(self.row_list[self.current_row])) - rows = self.db.refresh_ids(list(self.books_to_refresh)) - if rows: - self.rows_to_refresh |= set(rows) + def break_cycles(self): # Break any reference cycles that could prevent python From cbb668b6ddfb362e58b505a475bc41893c8f01ac Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Thu, 3 Feb 2011 13:21:44 +0900 Subject: [PATCH 21/29] recipe: msn sankei news changes its charcode. --- resources/recipes/msnsankei.recipe | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/resources/recipes/msnsankei.recipe b/resources/recipes/msnsankei.recipe index ae195559d5..59664d055f 100644 --- a/resources/recipes/msnsankei.recipe +++ b/resources/recipes/msnsankei.recipe @@ -13,15 +13,12 @@ class MSNSankeiNewsProduct(BasicNewsRecipe): description = 'Products release from Japan' oldest_article = 7 max_articles_per_feed = 100 - encoding = 'Shift_JIS' + encoding = 'utf-8' language = 'ja' cover_url = 'http://sankei.jp.msn.com/images/common/sankeShinbunLogo.jpg' masthead_url = 'http://sankei.jp.msn.com/images/common/sankeiNewsLogo.gif' feeds = [(u'\u65b0\u5546\u54c1', u'http://sankei.jp.msn.com/rss/news/release.xml')] - remove_tags_before = dict(id="__r_article_title__") - remove_tags_after = dict(id="ajax_release_news") - remove_tags = [{'class':"parent chromeCustom6G"}, - dict(id="RelatedImg") - ] + remove_tags_before = dict(id="NewsTitle") + remove_tags_after = dict(id="RelatedTitle") From 0c9dbec71f9da7095b387f06af9cebcb8444d4f8 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 3 Feb 2011 10:33:40 +0000 Subject: [PATCH 22/29] Ticket #8732: Search and replace title sort --- src/calibre/gui2/dialogs/metadata_bulk.py | 65 ++++++++++++++++------- src/calibre/gui2/dialogs/metadata_bulk.ui | 11 ++++ src/calibre/library/database2.py | 4 -- 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 12f49baaca..8e098ef17b 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -11,7 +11,7 @@ from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \ from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.tag_editor import TagEditor -from calibre.ebooks.metadata import string_to_authors, authors_to_string +from calibre.ebooks.metadata import string_to_authors, authors_to_string, title_sort from calibre.ebooks.metadata.book.base import composite_formatter from calibre.ebooks.metadata.meta import get_metadata from calibre.gui2.custom_column_widgets import populate_metadata_page @@ -134,7 +134,7 @@ class MyBlockingBusy(QDialog): # {{{ do_autonumber, do_remove_format, remove_format, do_swap_ta, \ do_remove_conv, do_auto_author, series, do_series_restart, \ series_start_value, do_title_case, cover_action, clear_series, \ - pubdate, adddate = self.args + pubdate, adddate, do_title_sort = self.args # first loop: do author and title. These will commit at the end of each @@ -159,6 +159,9 @@ class MyBlockingBusy(QDialog): # {{{ if do_title_case and not title_set: title = self.db.title(id, index_is_id=True) self.db.set_title(id, titlecase(title), notify=False) + if do_title_sort: + title = self.db.title(id, index_is_id=True) + self.db.set_title_sort(id, title_sort(title), notify=False) if au: self.db.set_authors(id, string_to_authors(au), notify=False) if cover_action == 'remove': @@ -360,11 +363,11 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): if (f in ['author_sort'] or (fm[f]['datatype'] in ['text', 'series', 'enumeration'] and fm[f].get('search_terms', None) - and f not in ['formats', 'ondevice', 'sort']) or + and f not in ['formats', 'ondevice']) or fm[f]['datatype'] in ['int', 'float', 'bool'] ): self.all_fields.append(f) self.writable_fields.append(f) - if f in ['sort'] or fm[f]['datatype'] == 'composite': + if fm[f]['datatype'] == 'composite': self.all_fields.append(f) self.all_fields.sort() self.all_fields.insert(1, '{template}') @@ -437,7 +440,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.replace_func.addItems(sorted(self.s_r_functions.keys())) self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed) self.search_field.currentIndexChanged[int].connect(self.s_r_search_field_changed) - self.destination_field.currentIndexChanged[str].connect(self.s_r_destination_field_changed) + self.destination_field.currentIndexChanged[int].connect(self.s_r_destination_field_changed) self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results) self.replace_func.currentIndexChanged[str].connect(self.s_r_paint_results) @@ -469,6 +472,16 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.query_field.currentIndexChanged[str].connect(self.s_r_query_change) self.query_field.setCurrentIndex(0) + def s_r_sf_itemdata(self, idx): + if idx is None: + idx = self.search_field.currentIndex() + return unicode(self.search_field.itemData(idx).toString()) + + def s_r_df_itemdata(self, idx): + if idx is None: + idx = self.destination_field.currentIndex() + return unicode(self.destination_field.itemData(idx).toString()) + def s_r_get_field(self, mi, field): if field: if field == '{template}': @@ -508,7 +521,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): for i in range(0, self.s_r_number_of_books): w = getattr(self, 'book_%d_text'%(i+1)) mi = self.db.get_metadata(self.ids[i], index_is_id=True) - src = unicode(self.search_field.currentText()) + src = self.s_r_sf_itemdata(idx) t = self.s_r_get_field(mi, src) if len(t) > 1: t = t[self.starting_from.value()-1: @@ -518,13 +531,13 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): if self.search_mode.currentIndex() == 0: self.destination_field.setCurrentIndex(idx) else: - self.s_r_destination_field_changed(self.destination_field.currentText()) + self.s_r_destination_field_changed(self.destination_field.currentIndex()) self.s_r_paint_results(None) - def s_r_destination_field_changed(self, txt): - txt = unicode(txt) + def s_r_destination_field_changed(self, idx): + txt = self.s_r_df_itemdata(idx) if not txt: - txt = unicode(self.search_field.currentText()) + txt = self.s_r_sf_itemdata(None) if txt and txt in self.writable_fields: self.destination_field_fm = self.db.metadata_for_field(txt) self.s_r_paint_results(None) @@ -533,8 +546,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.search_field.clear() self.destination_field.clear() if val == 0: - self.search_field.addItems(self.writable_fields) - self.destination_field.addItems(self.writable_fields) + for f in self.writable_fields: + self.search_field.addItem(f if f != 'sort' else 'title_sort', f) + self.destination_field.addItem(f if f != 'sort' else 'title_sort', f) self.destination_field.setCurrentIndex(0) self.destination_field.setVisible(False) self.destination_field_label.setVisible(False) @@ -544,8 +558,16 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.comma_separated.setVisible(False) self.s_r_heading.setText('

'+self.main_heading + self.character_heading) else: - self.search_field.addItems(self.all_fields) - self.destination_field.addItems(self.writable_fields) + self.search_field.blockSignals(True) + self.destination_field.blockSignals(True) + for f in self.all_fields: + self.search_field.addItem(f if f != 'sort' else 'title_sort', f) + for f in self.writable_fields: + self.destination_field.addItem(f if f != 'sort' else 'title_sort', f) + self.search_field.blockSignals(False) + self.destination_field.blockSignals(False) + for i in range(0, len(self.all_fields)): + print unicode(self.search_field.itemData(i).toString()) self.destination_field.setVisible(True) self.destination_field_label.setVisible(True) self.replace_mode.setVisible(True) @@ -575,7 +597,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): return rfunc(rtext) def s_r_do_regexp(self, mi): - src_field = unicode(self.search_field.currentText()) + src_field = self.s_r_sf_itemdata(None) src = self.s_r_get_field(mi, src_field) result = [] rfunc = self.s_r_functions[unicode(self.replace_func.currentText())] @@ -587,10 +609,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): return result def s_r_do_destination(self, mi, val): - src = unicode(self.search_field.currentText()) + src = self.s_r_sf_itemdata(None) if src == '': return '' - dest = unicode(self.destination_field.currentText()) + dest = self.s_r_df_itemdata(None) if dest == '': if self.db.metadata_for_field(src)['datatype'] == 'composite': raise Exception(_('You must specify a destination when source is a composite field')) @@ -680,10 +702,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): break def do_search_replace(self, id): - source = unicode(self.search_field.currentText()) + source = self.s_r_sf_itemdata(None) if not source or not self.s_r_obj: return - dest = unicode(self.destination_field.currentText()) + dest = self.s_r_df_itemdata(None) if not dest: dest = source dfm = self.db.field_metadata[dest] @@ -717,6 +739,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): else: if dest == 'comments': setter = self.db.set_comment + elif dest == 'sort': + setter = self.db.set_title_sort else: setter = getattr(self.db, 'set_'+dest) if dest in ['title', 'authors']: @@ -844,6 +868,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): do_remove_conv = self.remove_conversion_settings.isChecked() do_auto_author = self.auto_author_sort.isChecked() do_title_case = self.change_title_to_title_case.isChecked() + do_title_sort = self.update_title_sort.isChecked() pubdate = adddate = None if self.apply_pubdate.isChecked(): pubdate = qt_to_dt(self.pubdate.date()) @@ -862,7 +887,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): do_autonumber, do_remove_format, remove_format, do_swap_ta, do_remove_conv, do_auto_author, series, do_series_restart, series_start_value, do_title_case, cover_action, clear_series, - pubdate, adddate) + pubdate, adddate, do_title_sort) bb = MyBlockingBusy(_('Applying changes to %d books.\nPhase {0} {1}%%.') %len(self.ids), args, self.db, self.ids, diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index ecdb396662..576c6b060f 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -489,6 +489,17 @@ title and author are swapped before the title case is set + + + + Recompute the title sort value and store it in title sort. +This will happen after any title case changes + + + Update &title sort + + + diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 95e568bdbe..c147773f1f 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1718,10 +1718,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): title = title.decode(preferred_encoding, 'replace') self.conn.execute('UPDATE books SET title=? WHERE id=?', (title, id)) self.data.set(id, self.FIELD_MAP['title'], title, row_is_id=True) - if tweaks['title_series_sorting'] == 'library_order': - self.data.set(id, self.FIELD_MAP['sort'], title_sort(title), row_is_id=True) - else: - self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True) return True def set_title(self, id, title, notify=True, commit=True): From 975f1a77079918c7b1ef3cc41f2de024f3b4ab74 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 3 Feb 2011 10:44:43 +0000 Subject: [PATCH 23/29] Remove print statement --- src/calibre/gui2/dialogs/metadata_bulk.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 8e098ef17b..e355144544 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -566,8 +566,6 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.destination_field.addItem(f if f != 'sort' else 'title_sort', f) self.search_field.blockSignals(False) self.destination_field.blockSignals(False) - for i in range(0, len(self.all_fields)): - print unicode(self.search_field.itemData(i).toString()) self.destination_field.setVisible(True) self.destination_field_label.setVisible(True) self.replace_mode.setVisible(True) From 947ae40a97ea6a2b25ec946c074a5021708a57e0 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 3 Feb 2011 11:43:38 +0000 Subject: [PATCH 24/29] Add case change to series, publishers, and custom columns --- src/calibre/gui2/custom_column_widgets.py | 12 ++- src/calibre/gui2/dialogs/metadata_single.py | 12 +-- src/calibre/gui2/library/models.py | 11 +- src/calibre/library/custom_columns.py | 33 +++--- src/calibre/library/database2.py | 105 ++++++++++++-------- 5 files changed, 108 insertions(+), 65 deletions(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 5180999379..eae6dc79c3 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -44,8 +44,10 @@ class Base(object): val = self.gui_val val = self.normalize_ui_val(val) if val != self.initial_val: - self.db.set_custom(book_id, val, num=self.col_id, notify=notify, - commit=False) + return self.db.set_custom(book_id, val, num=self.col_id, + notify=notify, commit=False, allow_case_change=True) + else: + return set() def normalize_db_val(self, val): return val @@ -330,8 +332,10 @@ class Series(Base): num=self.col_id) else: s_index = None - self.db.set_custom(book_id, val, extra=s_index, - num=self.col_id, notify=notify, commit=False) + return self.db.set_custom(book_id, val, extra=s_index, num=self.col_id, + notify=notify, commit=False, allow_case_change=True) + else: + return set() class Enumeration(Base): diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 0085d40c2d..aec8c4fd60 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -923,12 +923,12 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.db.set_rating(self.id, 2*self.rating.value(), notify=False, commit=False) self.books_to_refresh |= self.apply_tags() - self.db.set_publisher(self.id, - unicode(self.publisher.currentText()).strip(), - notify=False, commit=False) - self.db.set_series(self.id, + self.books_to_refresh |= self.db.set_publisher(self.id, + unicode(self.publisher.currentText()).strip(), + notify=False, commit=False, allow_case_change=True) + self.books_to_refresh |= self.db.set_series(self.id, unicode(self.series.currentText()).strip(), notify=False, - commit=False) + commit=False, allow_case_change=True) self.db.set_series_index(self.id, self.series_index.value(), notify=False, commit=False) self.db.set_comment(self.id, @@ -949,7 +949,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): else: self.db.remove_cover(self.id) for w in getattr(self, 'custom_column_widgets', []): - w.commit(self.id) + self.books_to_refresh |= w.commit(self.id) self.db.commit() except IOError, err: if err.errno == 13: # Permission denied diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 088b9f6d02..0b6991665b 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -800,9 +800,10 @@ class BooksModel(QAbstractTableModel): # {{{ return True id = self.db.id(row) - self.db.set_custom(id, val, extra=s_index, + books_to_refresh = set([id]) + books_to_refresh |= self.db.set_custom(id, val, extra=s_index, label=label, num=None, append=False, notify=True) - self.refresh_ids([id], current_row=row) + self.refresh_ids(list(books_to_refresh), current_row=row) return True def setData(self, index, value, role): @@ -827,7 +828,8 @@ class BooksModel(QAbstractTableModel): # {{{ elif column == 'series': val = val.strip() if not val: - self.db.set_series(id, val) + books_to_refresh |= self.db.set_series(id, val, + allow_case_change=True) self.db.set_series_index(id, 1.0) else: pat = re.compile(r'\[([.0-9]+)\]') @@ -841,7 +843,8 @@ class BooksModel(QAbstractTableModel): # {{{ if ni != 1: self.db.set_series_index(id, ni) if val: - self.db.set_series(id, val) + books_to_refresh |= self.db.set_series(id, val, + allow_case_change=True) elif column == 'timestamp': if val.isNull() or not val.isValid(): return False diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index f94081f046..f166acfbb3 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -440,22 +440,24 @@ class CustomColumns(object): self.dirtied(ids, commit=False) self.conn.commit() - def set_custom(self, id, val, label=None, num=None, - append=False, notify=True, extra=None, commit=True): - self._set_custom(id, val, label=label, num=num, append=append, - notify=notify, extra=extra) + def set_custom(self, id, val, label=None, num=None, append=False, + notify=True, extra=None, commit=True, allow_case_change=False): + rv = self._set_custom(id, val, label=label, num=num, append=append, + notify=notify, extra=extra, + allow_case_change=allow_case_change) self.dirtied([id], commit=False) if commit: self.conn.commit() + return rv - def _set_custom(self, id_, val, label=None, num=None, - append=False, notify=True, extra=None): + def _set_custom(self, id_, val, label=None, num=None, append=False, + notify=True, extra=None, allow_case_change=False): if label is not None: data = self.custom_column_label_map[label] if num is not None: data = self.custom_column_num_map[num] if data['datatype'] == 'composite': - return None + return set() if not data['editable']: raise ValueError('Column %r is not editable'%data['label']) table, lt = self.custom_table_names(data['num']) @@ -466,10 +468,11 @@ class CustomColumns(object): if data['datatype'] == 'series' and extra is None: (val, extra) = self._get_series_values(val) + books_to_refresh = set() if data['normalized']: if data['datatype'] == 'enumeration' and ( val and val not in data['display']['enum_values']): - return None + return books_to_refresh if not append or not data['is_multiple']: self.conn.execute('DELETE FROM %s WHERE book=?'%lt, (id_,)) self.conn.execute( @@ -483,6 +486,7 @@ class CustomColumns(object): for x in set(set_val) - set(existing): if x is None: continue + case_change = False existing = list(self.all_custom(num=data['num'])) lx = [t.lower() if hasattr(t, 'lower') else t for t in existing] try: @@ -492,13 +496,14 @@ class CustomColumns(object): if idx > -1: ex = existing[idx] xid = self.conn.get( - 'SELECT id FROM %s WHERE value=?'%table, (ex,), all=False) + 'SELECT id FROM %s WHERE value=?'%table, (ex,), all=False) if ex != x: + case_change = True self.conn.execute( - 'UPDATE %s SET value=? WHERE id=?'%table, (x, xid)) + 'UPDATE %s SET value=? WHERE id=?'%table, (x, xid)) else: xid = self.conn.execute( - 'INSERT INTO %s(value) VALUES(?)'%table, (x,)).lastrowid + 'INSERT INTO %s(value) VALUES(?)'%table, (x,)).lastrowid if not self.conn.get( 'SELECT book FROM %s WHERE book=? AND value=?'%lt, (id_, xid), all=False): @@ -512,6 +517,10 @@ class CustomColumns(object): self.conn.execute( '''INSERT INTO %s(book, value) VALUES (?,?)'''%lt, (id_, xid)) + if case_change: + bks = self.conn.get('SELECT book FROM %s WHERE value=?'%lt, + (xid,)) + books_to_refresh |= set([bk[0] for bk in bks]) nval = self.conn.get( 'SELECT custom_%s FROM meta2 WHERE id=?'%data['num'], (id_,), all=False) @@ -530,7 +539,7 @@ class CustomColumns(object): row_is_id=True) if notify: self.notify('metadata', [id_]) - return nval + return books_to_refresh def clean_custom(self): st = ('DELETE FROM {table} WHERE (SELECT COUNT(id) FROM {lt} WHERE' diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index c147773f1f..54ade889c1 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1495,7 +1495,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): elif column == 'title': self.set_title(id, val, notify=False) elif column == 'publisher': - self.set_publisher(id, val, notify=False) + books_to_refresh |= self.set_publisher(id, val, notify=False, + allow_case_change=allow_case_change) elif column == 'rating': self.set_rating(id, val, notify=False) elif column == 'tags': @@ -1637,35 +1638,32 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,)) books_to_refresh = set() for a in authors: + case_change = False if not a: continue a = a.strip().replace(',', '|') if not isinstance(a, unicode): a = a.decode(preferred_encoding, 'replace') - aus = self.conn.get('SELECT id, name from authors WHERE name=?', (a,)) + aus = self.conn.get('SELECT id, name FROM authors WHERE name=?', (a,)) if aus: - author_id, name = aus[0] - else: - author_id, name = (None, None) - if author_id: - aid = author_id + aid, name = aus[0] # Handle change of case if allow_case_change and name != a: - self.conn.execute('UPDATE authors SET name=? WHERE id=?', (a, aid)) - case_change = True + self.conn.execute('''UPDATE authors + SET name=? WHERE id=?''', (a, aid)) + case_change = True else: - aid = self.conn.execute('INSERT INTO authors(name) VALUES (?)', (a,)).lastrowid - case_change = False + aid = self.conn.execute('''INSERT INTO authors(name) + VALUES (?)''', (a,)).lastrowid try: - self.conn.execute('INSERT INTO books_authors_link(book, author) VALUES (?,?)', - (id, aid)) + self.conn.execute('''INSERT INTO books_authors_link(book, author) + VALUES (?,?)''', (id, aid)) except IntegrityError: # Sometimes books specify the same author twice in their metadata pass if case_change: - bks = self.conn.get('SELECT book FROM books_authors_link WHERE author=?', - (aid,)) + bks = self.conn.get('''SELECT book FROM books_authors_link + WHERE author=?''', (aid,)) books_to_refresh |= set([bk[0] for bk in bks]) - ss = self.author_sort_from_book(id, index_is_id=True) self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (ss, id)) @@ -1755,24 +1753,41 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.notify('metadata', [id]) - def set_publisher(self, id, publisher, notify=True, commit=True): + def set_publisher(self, id, publisher, notify=True, commit=True, + allow_case_change=False): self.conn.execute('DELETE FROM books_publishers_link WHERE book=?',(id,)) - self.conn.execute('DELETE FROM publishers WHERE (SELECT COUNT(id) FROM books_publishers_link WHERE publisher=publishers.id) < 1') + self.conn.execute('''DELETE FROM publishers WHERE (SELECT COUNT(id) + FROM books_publishers_link + WHERE publisher=publishers.id) < 1''') + books_to_refresh = set() if publisher: + case_change = False if not isinstance(publisher, unicode): publisher = publisher.decode(preferred_encoding, 'replace') - pub = self.conn.get('SELECT id from publishers WHERE name=?', (publisher,), all=False) - if pub: - aid = pub + pubx = self.conn.get('''SELECT id,name from publishers + WHERE name=?''', (publisher,)) + if pubx: + aid, cur_name = pubx[0] + if allow_case_change and publisher != cur_name: + self.conn.execute('''UPDATE publishers SET name=? + WHERE id=?''', (publisher, aid)) + case_change = True else: - aid = self.conn.execute('INSERT INTO publishers(name) VALUES (?)', (publisher,)).lastrowid - self.conn.execute('INSERT INTO books_publishers_link(book, publisher) VALUES (?,?)', (id, aid)) - self.dirtied([id], commit=False) - if commit: - self.conn.commit() - self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True) - if notify: - self.notify('metadata', [id]) + aid = self.conn.execute('''INSERT INTO publishers(name) + VALUES (?)''', (publisher,)).lastrowid + self.conn.execute('''INSERT INTO books_publishers_link(book, publisher) + VALUES (?,?)''', (id, aid)) + if case_change: + bks = self.conn.get('''SELECT book FROM books_publishers_link + WHERE publisher=?''', (aid,)) + books_to_refresh |= set([bk[0] for bk in bks]) + self.dirtied([id], commit=False) + if commit: + self.conn.commit() + self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True) + if notify: + self.notify('metadata', [id]) + return books_to_refresh def set_uuid(self, id, uuid, notify=True, commit=True): if uuid: @@ -2144,7 +2159,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): ''' if not append: self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,)) - self.conn.execute('DELETE FROM tags WHERE (SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) < 1') + self.conn.execute('''DELETE FROM tags WHERE (SELECT COUNT(id) + FROM books_tags_link WHERE tag=tags.id) < 1''') otags = self.get_tags(id) tags = self.cleanup_tags(tags) books_to_refresh = set() @@ -2170,10 +2186,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): else: tid = self.conn.execute('INSERT INTO tags(name) VALUES(?)', (tag,)).lastrowid - if not self.conn.get('SELECT book FROM books_tags_link WHERE book=? AND tag=?', - (id, tid), all=False): - self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)', - (id, tid)) + if not self.conn.get('''SELECT book FROM books_tags_link + WHERE book=? AND tag=?''', (id, tid), all=False): + self.conn.execute('''INSERT INTO books_tags_link(book, tag) + VALUES (?,?)''', (id, tid)) if case_changed: bks = self.conn.get('SELECT book FROM books_tags_link WHERE tag=?', (tid,)) @@ -2191,7 +2207,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): for tag in tags: id = self.conn.get('SELECT id FROM tags WHERE name=?', (tag,), all=False) if id: - self.conn.execute('DELETE FROM books_tags_link WHERE tag=? AND book=?', (id, book_id)) + self.conn.execute('''DELETE FROM books_tags_link + WHERE tag=? AND book=?''', (id, book_id)) self.conn.commit() self.data.refresh_ids(self, [book_id]) if notify: @@ -2235,31 +2252,41 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): pass return (val, None) - def set_series(self, id, series, notify=True, commit=True): + def set_series(self, id, series, notify=True, commit=True, allow_case_change=True): self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,)) self.conn.execute('''DELETE FROM series WHERE (SELECT COUNT(id) FROM books_series_link WHERE series=series.id) < 1''') (series, idx) = self._get_series_values(series) + books_to_refresh = set() if series: + case_change = False if not isinstance(series, unicode): series = series.decode(preferred_encoding, 'replace') series = series.strip() series = u' '.join(series.split()) - s = self.conn.get('SELECT id from series WHERE name=?', (series,), all=False) - if s: - aid = s + sx = self.conn.get('SELECT id,name from series WHERE name=?', (series,)) + if sx: + aid, cur_name = sx[0] + if allow_case_change and cur_name != series: + self.conn.execute('UPDATE series SET name=? WHERE id=?', (series, aid)) + case_change = True else: aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid)) if idx: self.set_series_index(id, idx, notify=notify, commit=commit) + if case_change: + bks = self.conn.get('SELECT book FROM books_series_link WHERE series=?', + (aid,)) + books_to_refresh |= set([bk[0] for bk in bks]) self.dirtied([id], commit=False) if commit: self.conn.commit() self.data.set(id, self.FIELD_MAP['series'], series, row_is_id=True) if notify: self.notify('metadata', [id]) + return books_to_refresh def set_series_index(self, id, idx, notify=True, commit=True): if idx is None: From 75b28092d38ad7b68a301ff96dc888838004ffad Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 3 Feb 2011 12:29:21 +0000 Subject: [PATCH 25/29] Changes to refactored metadata dialog for case change --- src/calibre/gui2/metadata/basic_widgets.py | 10 ++++++++-- src/calibre/gui2/metadata/single.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index d9c450807a..f9058fc333 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -300,6 +300,7 @@ class SeriesEdit(MultiCompleteComboBox): self.setToolTip(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP) self.setEditable(True) + self.books_to_refresh = set([]) @dynamic_property def current_val(self): @@ -316,6 +317,7 @@ class SeriesEdit(MultiCompleteComboBox): return property(fget=fget, fset=fset) def initialize(self, db, id_): + self.books_to_refresh = set([]) 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]) @@ -335,7 +337,8 @@ class SeriesEdit(MultiCompleteComboBox): def commit(self, db, id_): series = self.current_val - db.set_series(id_, series, notify=False, commit=True) + self.books_to_refresh |= db.set_series(id_, series, notify=False, + commit=True, allow_case_change=True) return True class SeriesIndexEdit(QDoubleSpinBox): @@ -927,6 +930,7 @@ class PublisherEdit(MultiCompleteComboBox): # {{{ self.set_separator(None) self.setSizeAdjustPolicy( self.AdjustToMinimumContentsLengthWithIcon) + self.books_to_refresh = set([]) @dynamic_property def current_val(self): @@ -943,6 +947,7 @@ class PublisherEdit(MultiCompleteComboBox): # {{{ return property(fget=fget, fset=fset) def initialize(self, db, id_): + self.books_to_refresh = set([]) 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]) @@ -960,7 +965,8 @@ class PublisherEdit(MultiCompleteComboBox): # {{{ self.setCurrentIndex(idx) def commit(self, db, id_): - db.set_publisher(id_, self.current_val, notify=False, commit=False) + self.books_to_refresh |= db.set_publisher(id_, self.current_val, + notify=False, commit=False, allow_case_change=True) return True # }}} diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index e5de2e3d97..1be954155c 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -311,7 +311,7 @@ class MetadataSingleDialogBase(ResizableDialog): return False raise for widget in getattr(self, 'custom_metadata_widgets', []): - widget.commit(self.book_id) + self.books_to_refresh |= widget.commit(self.book_id) self.db.commit() rows = self.db.refresh_ids(list(self.books_to_refresh)) From c7da3c8c1f35b7fa07ab4ff21e15106330ce62a3 Mon Sep 17 00:00:00 2001 From: GRiker Date: Thu, 3 Feb 2011 06:41:40 -0700 Subject: [PATCH 26/29] GwR updates to catalog css --- resources/catalog/stylesheet.css | 37 ++++++++++++++++++---------- src/calibre/library/catalog.py | 41 +++++++++++++++----------------- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/resources/catalog/stylesheet.css b/resources/catalog/stylesheet.css index 336d015e44..4b32056400 100644 --- a/resources/catalog/stylesheet.css +++ b/resources/catalog/stylesheet.css @@ -52,6 +52,17 @@ p.formats { text-indent: 0.0in; } +/* +* Minimize widows and orphans by logically grouping chunks +* Some reports of problems with Sony (ADE) ereaders +* ADE: page-break-inside:avoid; +* iBooks: display:inline-block; +* width:100%; +*/ +div.author_logical_group { + page-break-inside:avoid; + } + div.description > p:first-child { margin: 0 0 0 0; text-indent: 0em; @@ -62,27 +73,19 @@ div.description { text-indent: 1em; } -/* -* Attempt to minimize widows and orphans by logically grouping chunks -* Recommend enabling for iPad -* Some reports of problems with Sony ereaders, presumably ADE engines -*/ -/* -div.logical_group { - display:inline-block; - width:100%; +div.initial_letter { + page-break-before:always; } -*/ -p.date_index { +p.author_title_letter_index { font-size:x-large; text-align:center; font-weight:bold; - margin-top:1em; + margin-top:0px; margin-bottom:0px; } -p.letter_index { +p.date_index { font-size:x-large; text-align:center; font-weight:bold; @@ -99,6 +102,14 @@ p.series { text-indent:-2em; } +p.series_letter_index { + font-size:x-large; + text-align:center; + font-weight:bold; + margin-top:1em; + margin-bottom:0px; + } + p.read_book { text-align:left; margin-top:0px; diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 8ad64c8cdd..092cc66ff9 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -1832,8 +1832,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) body.insert(btc,pTag) btc += 1 - #

- #

divTag = Tag(soup, "div") dtc = 0 current_letter = "" @@ -1861,11 +1859,12 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) divTag.insert(dtc, divRunningTag) dtc += 1 divRunningTag = Tag(soup, 'div') - divRunningTag['class'] = "logical_group" + if dtc > 0: + divRunningTag['class'] = "initial_letter" drtc = 0 current_letter = self.letter_or_symbol(book['title_sort'][0]) pIndexTag = Tag(soup, "p") - pIndexTag['class'] = "letter_index" + pIndexTag['class'] = "author_title_letter_index" aTag = Tag(soup, "a") aTag['name'] = "%s" % self.letter_or_symbol(book['title_sort'][0]) pIndexTag.insert(0,aTag) @@ -1973,8 +1972,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) body.insert(btc, aTag) btc += 1 - #

- #

divTag = Tag(soup, "div") dtc = 0 divOpeningTag = None @@ -2008,10 +2005,11 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) current_letter = self.letter_or_symbol(book['author_sort'][0].upper()) author_count = 0 divOpeningTag = Tag(soup, 'div') - divOpeningTag['class'] = "logical_group" + if dtc > 0: + divOpeningTag['class'] = "initial_letter" dotc = 0 pIndexTag = Tag(soup, "p") - pIndexTag['class'] = "letter_index" + pIndexTag['class'] = "author_title_letter_index" aTag = Tag(soup, "a") aTag['name'] = "%sauthors" % self.letter_or_symbol(current_letter) pIndexTag.insert(0,aTag) @@ -2023,16 +2021,21 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) # Start a new author current_author = book['author'] author_count += 1 - if author_count == 2: + if author_count >= 2: # Add divOpeningTag to divTag, kill divOpeningTag - divTag.insert(dtc, divOpeningTag) - dtc += 1 - divOpeningTag = None - dotc = 0 + if divOpeningTag: + divTag.insert(dtc, divOpeningTag) + dtc += 1 + divOpeningTag = None + dotc = 0 + + # Create a divRunningTag for the next author + if author_count > 2: + divTag.insert(dtc, divRunningTag) + dtc += 1 - # Create a divRunningTag for the rest of the authors in this letter divRunningTag = Tag(soup, 'div') - divRunningTag['class'] = "logical_group" + divRunningTag['class'] = "author_logical_group" drtc = 0 non_series_books = 0 @@ -2364,8 +2367,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) body.insert(btc,pTag) btc += 1 - #

- #

divTag = Tag(soup, "div") dtc = 0 @@ -2549,8 +2550,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) body.insert(btc, aTag) btc += 1 - #

- #

divTag = Tag(soup, "div") dtc = 0 @@ -2652,8 +2651,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) body.insert(btc, aTag) btc += 1 - #

- #

divTag = Tag(soup, "div") dtc = 0 current_letter = "" @@ -2668,7 +2665,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) # Start a new letter with Index letter current_letter = self.letter_or_symbol(sort_title[0].upper()) pIndexTag = Tag(soup, "p") - pIndexTag['class'] = "letter_index" + pIndexTag['class'] = "series_letter_index" aTag = Tag(soup, "a") aTag['name'] = "%s_series" % self.letter_or_symbol(current_letter) pIndexTag.insert(0,aTag) From be76d5b41e2fb298be7e2f63cf11274d2345e66d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 3 Feb 2011 07:57:53 -0700 Subject: [PATCH 27/29] Fix #8735 (Updated recipe for The Onion) --- resources/recipes/theonion.recipe | 78 ++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/resources/recipes/theonion.recipe b/resources/recipes/theonion.recipe index 3be4ae4e04..b0eacbb5e0 100644 --- a/resources/recipes/theonion.recipe +++ b/resources/recipes/theonion.recipe @@ -1,7 +1,5 @@ -#!/usr/bin/env python - __license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' +__copyright__ = '2009-2011, Darko Miletic ' ''' theonion.com @@ -12,35 +10,73 @@ from calibre.web.feeds.news import BasicNewsRecipe class TheOnion(BasicNewsRecipe): title = 'The Onion' __author__ = 'Darko Miletic' - description = "America's finest news source" - oldest_article = 2 + description = "America's finest news source" + oldest_article = 2 max_articles_per_feed = 100 - publisher = u'Onion, Inc.' - category = u'humor, news, USA' - language = 'en' - + publisher = 'Onion, Inc.' + category = 'humor, news, USA' + language = 'en' no_stylesheets = True use_embedded_content = False encoding = 'utf-8' - remove_javascript = True - html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' - - html2lrf_options = [ - '--comment' , description - , '--category' , category - , '--publisher' , publisher - ] + publication_type = 'newsportal' + masthead_url = 'http://o.onionstatic.com/img/headers/onion_190.png' + extra_css = """ + body{font-family: Helvetica,Arial,sans-serif} + .section_title{color: gray; text-transform: uppercase} + .title{font-family: Georgia,serif} + .meta{color: gray; display: inline} + .has_caption{display: block} + .caption{font-size: x-small; color: gray; margin-bottom: 0.8em} + """ - keep_only_tags = [dict(name='div', attrs={'id':'main'})] - + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher': publisher + , 'language' : language + } + + keep_only_tags = [ + dict(name='h2', attrs={'class':['section_title','title']}) + ,dict(attrs={'class':['main_image','meta','article_photo_lead','article_body']}) + ,dict(attrs={'id':['entries']}) + ] + remove_attributes=['lang','rel'] + remove_tags_after = dict(attrs={'class':['article_body','feature_content']}) remove_tags = [ - dict(name=['object','link','iframe','base']) + dict(name=['object','link','iframe','base','meta']) ,dict(name='div', attrs={'class':['toolbar_side','graphical_feature','toolbar_bottom']}) ,dict(name='div', attrs={'id':['recent_slider','sidebar','pagination','related_media']}) ] - + feeds = [ (u'Daily' , u'http://feeds.theonion.com/theonion/daily' ) ,(u'Sports' , u'http://feeds.theonion.com/theonion/sports' ) ] + + def get_article_url(self, article): + artl = BasicNewsRecipe.get_article_url(self, article) + if artl.startswith('http://www.theonion.com/audio/'): + artl = None + return artl + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + for item in soup.findAll('a'): + limg = item.find('img') + if item.string is not None: + str = item.string + item.replaceWith(str) + else: + if limg: + item.name = 'div' + item.attrs = [] + if not limg.has_key('alt'): + limg['alt'] = 'image' + else: + str = self.tag_to_string(item) + item.replaceWith(str) + return soup From 57efe4fb061c92a00db48cadc169fd05f6d8d833 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 3 Feb 2011 10:18:00 -0700 Subject: [PATCH 28/29] Fix #8739 (get_matches() got multiple values for keyword argument 'allow_recursion') --- src/calibre/library/caches.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index dd4509acea..e818e6a3c0 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -420,7 +420,8 @@ class ResultCache(SearchQueryParser): # {{{ return candidates - res return res - def get_matches(self, location, query, allow_recursion=True, candidates=None): + def get_matches(self, location, query, candidates=None, + allow_recursion=True): matches = set([]) if candidates is None: candidates = self.universal_set() @@ -434,8 +435,8 @@ class ResultCache(SearchQueryParser): # {{{ if isinstance(location, list): if allow_recursion: for loc in location: - matches |= self.get_matches(loc, query, candidates, - allow_recursion=False) + matches |= self.get_matches(loc, query, + candidates=candidates, allow_recursion=False) return matches raise ParseException(query, len(query), 'Recursive query group detected', self) From 8749611440861d79f53a3a43d19d1b276fbf13f6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 3 Feb 2011 11:33:19 -0700 Subject: [PATCH 29/29] Nook Color driver: Send downloaded news to the My Files/Magazines folder on the Nook Color. Also when getting the list of books on the device look at all folders in My Files, not just My Files/Books. --- src/calibre/devices/nook/driver.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py index ca05885645..39d0763735 100644 --- a/src/calibre/devices/nook/driver.py +++ b/src/calibre/devices/nook/driver.py @@ -89,21 +89,21 @@ class NOOK_COLOR(NOOK): BCD = [0x216] WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_DISK' - EBOOK_DIR_MAIN = 'My Files/Books' + EBOOK_DIR_MAIN = 'My Files' - ''' def create_upload_path(self, path, mdata, fname, create_dirs=True): filepath = NOOK.create_upload_path(self, path, mdata, fname, - create_dirs=create_dirs) - edm = self.EBOOK_DIR_MAIN.replace('/', os.sep) - npath = os.path.join(edm, _('News')) + os.sep - if npath in filepath: - filepath = filepath.replace(npath, os.sep.join('My Files', - 'Magazines')+os.sep) - filedir = os.path.dirname(filepath) - if create_dirs and not os.path.exists(filedir): - os.makedirs(filedir) + create_dirs=False) + edm = self.EBOOK_DIR_MAIN + subdir = 'Books' + if mdata.tags: + if _('News') in mdata.tags: + subdir = 'Magazines' + filepath = filepath.replace(os.sep+edm+os.sep, + os.sep+edm+os.sep+subdir+os.sep) + filedir = os.path.dirname(filepath) + if create_dirs and not os.path.exists(filedir): + os.makedirs(filedir) return filepath - '''