diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index a3caa82e4b..50ce72686a 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -44,7 +44,9 @@ def render_rows(data): key = key.decode(preferred_encoding, 'replace') if isinstance(txt, str): txt = txt.decode(preferred_encoding, 'replace') - if '' not in txt: + if key.endswith(u':html'): + key = key[:-5] + elif '' not in txt: txt = prepare_string_for_xml(txt) if 'id' in data: if key == _('Path'): diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index ca9243e51e..40abb05f89 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -9,12 +9,13 @@ import sys from functools import partial from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \ - QDate, QGroupBox, QVBoxLayout, QPlainTextEdit, QSizePolicy, \ + QDate, QGroupBox, QVBoxLayout, QSizePolicy, \ QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \ QPushButton from calibre.utils.date import qt_to_dt, now from calibre.gui2.widgets import TagsLineEdit, EnComboBox +from calibre.gui2.comments_editor import Editor as CommentsEditor from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.utils.config import tweaks from calibre.utils.icu import sort_key @@ -186,9 +187,9 @@ class Comments(Base): self._box = QGroupBox(parent) self._box.setTitle('&'+self.col_metadata['name']) self._layout = QVBoxLayout() - self._tb = QPlainTextEdit(self._box) + self._tb = CommentsEditor(self._box) self._tb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) - self._tb.setTabChangesFocus(True) + #self._tb.setTabChangesFocus(True) self._layout.addWidget(self._tb) self._box.setLayout(self._layout) self.widgets = [self._box] @@ -196,10 +197,10 @@ class Comments(Base): def setter(self, val): if val is None: val = '' - self._tb.setPlainText(val) + self._tb.html = val def getter(self): - val = unicode(self._tb.toPlainText()).strip() + val = unicode(self._tb.html).strip() if not val: val = None return val diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index 016f132c57..6cae27d926 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -12,6 +12,7 @@ from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo from calibre.gui2 import dynamic, open_local_file from calibre import fit_image from calibre.library.comments import comments_to_html +from calibre.utils.icu import sort_key class BookInfo(QDialog, Ui_BookInfo): @@ -130,9 +131,11 @@ class BookInfo(QDialog, Ui_BookInfo): for f in formats: f = f.strip() info[_('Formats')] += '%s, '%(f,f) - for key in info.keys(): + for key in sorted(info.keys(), key=sort_key): if key == 'id': continue txt = info[key] + if key.endswith(':html'): + key = key[:-5] if key != _('Path'): txt = u'
\n'.join(textwrap.wrap(txt, 120)) rows += u'%s:%s'%(key, txt) diff --git a/src/calibre/gui2/dialogs/comments_dialog.py b/src/calibre/gui2/dialogs/comments_dialog.py index 5d53448b94..51b29fa989 100644 --- a/src/calibre/gui2/dialogs/comments_dialog.py +++ b/src/calibre/gui2/dialogs/comments_dialog.py @@ -5,6 +5,7 @@ __license__ = 'GPL v3' from PyQt4.Qt import Qt, QDialog, QDialogButtonBox from calibre.gui2.dialogs.comments_dialog_ui import Ui_CommentsDialog +from calibre.library.comments import comments_to_html class CommentsDialog(QDialog, Ui_CommentsDialog): @@ -18,8 +19,8 @@ class CommentsDialog(QDialog, Ui_CommentsDialog): self.setWindowIcon(icon) if text is not None: - self.textbox.setPlainText(text) - self.textbox.setTabChangesFocus(True) + self.textbox.html = comments_to_html(text) + # self.textbox.setTabChangesFocus(True) self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel')) diff --git a/src/calibre/gui2/dialogs/comments_dialog.ui b/src/calibre/gui2/dialogs/comments_dialog.ui index dccfa48652..76b23e233d 100644 --- a/src/calibre/gui2/dialogs/comments_dialog.ui +++ b/src/calibre/gui2/dialogs/comments_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 336 - 235 + 400 + 400 @@ -19,22 +19,29 @@ Edit Comments - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Editor + QWidget +
calibre/gui2/comments_editor.h
+
+
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index bde5cae128..9dbc3dee5e 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -417,6 +417,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.multiple_separator.setFixedWidth(30) self.multiple_separator.setText(' ::: ') self.multiple_separator.textChanged.connect(self.s_r_separator_changed) + self.results_count.valueChanged[int].connect(self.s_r_display_bounds_changed) + self.starting_from.valueChanged[int].connect(self.s_r_display_bounds_changed) def s_r_get_field(self, mi, field): if field: @@ -439,6 +441,9 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): val = [] return val + def s_r_display_bounds_changed(self, i): + self.s_r_search_field_changed(self.search_field.currentIndex()) + def s_r_template_changed(self): self.s_r_search_field_changed(self.search_field.currentIndex()) @@ -454,6 +459,9 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): mi = self.db.get_metadata(self.ids[i], index_is_id=True) src = unicode(self.search_field.currentText()) t = self.s_r_get_field(mi, src) + if len(t) > 1: + t = t[self.starting_from.value()-1: + self.starting_from.value()-1 + self.results_count.value()] w.setText(unicode(self.multiple_separator.text()).join(t)) if self.search_mode.currentIndex() == 0: @@ -466,12 +474,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): txt = unicode(txt) if not txt: txt = unicode(self.search_field.currentText()) - self.comma_separated.setEnabled(True) if txt and txt in self.writable_fields: self.destination_field_fm = self.db.metadata_for_field(txt) - if self.destination_field_fm['is_multiple']: - self.comma_separated.setEnabled(False) - self.comma_separated.setChecked(True) self.s_r_paint_results(None) def s_r_search_mode_changed(self, val): @@ -542,6 +546,22 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): dest = src dest_mode = self.replace_mode.currentIndex() + if self.destination_field_fm['is_multiple']: + if self.comma_separated.isChecked(): + if dest == 'authors': + splitter = ' & ' + else: + splitter = ',' + + res = [] + for v in val: + for x in v.split(splitter): + if x.strip(): + res.append(x.strip()) + val = res + else: + val = [v.replace(',', '') for v in val] + if dest_mode != 0: dest_val = mi.get(dest, '') if dest_val is None: @@ -602,8 +622,9 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): try: result = self.s_r_do_regexp(mi) t = self.s_r_do_destination(mi, result) - if len(result) > 1 and self.destination_field_fm is not None and \ - self.destination_field_fm['is_multiple']: + if len(t) > 1 and self.destination_field_fm['is_multiple']: + t = t[self.starting_from.value()-1: + self.starting_from.value()-1 + self.results_count.value()] t = unicode(self.multiple_separator.text()).join(t) else: t = self.s_r_replace_mode_separator().join(t) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index d945909f96..41858b099b 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -658,11 +658,12 @@ If blank, the source field is used if the field is modifiable - Specifies whether a comma should be put between values when copying from a -multiple-valued field to a single-valued field + Specifies whether result items should be split into multiple values or +left as single values. This option has the most effect when the source field is +not multiple and the destination field is multiple - &Use comma + Split &result true @@ -684,28 +685,8 @@ multiple-valued field to a single-valued field - - - - Test text - - - test_text - - - - + - - - - Test result - - - test_result - - - @@ -719,10 +700,62 @@ multiple-valued field to a single-valued field + + + + For multiple-valued fields, sho&w + + + results_count + + + + + + + true + + + 1 + + + 999 + + + 999 + + + + + + + values starting a&t + + + starting_from + + + + + + + true + + + 1 + + + 999 + + + 1 + + + - Multi&ple separator: + with values separated b&y multiple_separator @@ -756,6 +789,20 @@ multiple-valued field to a single-valued field + + + + Test text + + + + + + + Test result + + + @@ -857,6 +904,8 @@ multiple-valued field to a single-valued field destination_field replace_mode comma_separated + results_count + starting_from multiple_separator test_text test_result diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py new file mode 100644 index 0000000000..aaa4e2bb9a --- /dev/null +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' +__license__ = 'GPL v3' + +from PyQt4.Qt import Qt, QDialog, QDialogButtonBox +from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog + +class TemplateDialog(QDialog, Ui_TemplateDialog): + + def __init__(self, parent, text): + QDialog.__init__(self, parent) + Ui_TemplateDialog.__init__(self) + self.setupUi(self) + # Remove help icon on title bar + icon = self.windowIcon() + self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) + self.setWindowIcon(icon) + + if text is not None: + self.textbox.setPlainText(text) + self.textbox.setTabChangesFocus(True) + self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK')) + self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel')) + diff --git a/src/calibre/gui2/dialogs/template_dialog.ui b/src/calibre/gui2/dialogs/template_dialog.ui new file mode 100644 index 0000000000..3eacace2c5 --- /dev/null +++ b/src/calibre/gui2/dialogs/template_dialog.ui @@ -0,0 +1,73 @@ + + + TemplateDialog + + + + 0 + 0 + 336 + 235 + + + + + 0 + 0 + + + + Edit Comments + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + TemplateDialog + accept() + + + 229 + 211 + + + 157 + 234 + + + + + buttonBox + rejected() + TemplateDialog + reject() + + + 297 + 217 + + + 286 + 234 + + + + + diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index fe7e7d55ba..fef1542737 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -13,7 +13,7 @@ from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \ QPen, QStyle, QPainter, QStyleOptionViewItemV4, \ QIcon, QDoubleSpinBox, QVariant, QSpinBox, \ QStyledItemDelegate, QCompleter, \ - QComboBox + QComboBox, QTextDocument from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.gui2.widgets import EnLineEdit, TagsLineEdit @@ -22,6 +22,8 @@ from calibre.utils.config import tweaks from calibre.utils.formatter import validation_formatter from calibre.utils.icu import sort_key from calibre.gui2.dialogs.comments_dialog import CommentsDialog +from calibre.gui2.dialogs.template_dialog import TemplateDialog + class RatingDelegate(QStyledItemDelegate): # {{{ COLOR = QColor("blue") @@ -294,6 +296,24 @@ class CcCommentsDelegate(QStyledItemDelegate): # {{{ Delegate for comments data. ''' + def __init__(self, parent): + QStyledItemDelegate.__init__(self, parent) + self.document = QTextDocument() + + def paint(self, painter, option, index): + style = self.parent().style() + self.document.setHtml(index.data(Qt.DisplayRole).toString()) + painter.save() + if hasattr(QStyle, 'CE_ItemViewItem'): + style.drawControl(QStyle.CE_ItemViewItem, option, + painter, self._parent) + elif option.state & QStyle.State_Selected: + painter.fillRect(option.rect, option.palette.highlight()) + painter.setClipRect(option.rect) + painter.translate(option.rect.topLeft()) + self.document.drawContents(painter) + painter.restore() + def createEditor(self, parent, option, index): m = index.model() col = m.column_map[index.column()] @@ -301,11 +321,11 @@ class CcCommentsDelegate(QStyledItemDelegate): # {{{ editor = CommentsDialog(parent, text) d = editor.exec_() if d: - m.setData(index, QVariant(editor.textbox.toPlainText()), Qt.EditRole) + m.setData(index, QVariant(editor.textbox.html), Qt.EditRole) return None def setModelData(self, editor, model, index): - model.setData(index, QVariant(editor.textbox.toPlainText()), Qt.EditRole) + model.setData(index, QVariant(editor.textbox.html), Qt.EditRole) # }}} class CcBoolDelegate(QStyledItemDelegate): # {{{ @@ -351,7 +371,7 @@ class CcTemplateDelegate(QStyledItemDelegate): # {{{ def createEditor(self, parent, option, index): m = index.model() text = m.custom_columns[m.column_map[index.column()]]['display']['composite_template'] - editor = CommentsDialog(parent, text) + editor = TemplateDialog(parent, text) editor.setWindowTitle(_("Edit template")) editor.textbox.setTabChangesFocus(False) editor.textbox.setTabStopWidth(20) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 9da9a2f538..920753a77d 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -334,6 +334,8 @@ class BooksModel(QAbstractTableModel): # {{{ if key not in cf_to_display: continue name, val = mi.format_field(key) + if mi.metadata_for_field(key)['datatype'] == 'comments': + name += ':html' if val: data[name] = val return data diff --git a/src/calibre/library/server/opds.py b/src/calibre/library/server/opds.py index e447c6966c..fd8c50c594 100644 --- a/src/calibre/library/server/opds.py +++ b/src/calibre/library/server/opds.py @@ -173,6 +173,8 @@ def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix): extra.append('%s: %s
'%(xml(name), xml(format_tag_string(val, ',', ignore_max=True, no_tag_count=True)))) + elif datatype == 'comments': + extra.append('%s: %s
'%(xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s
'%(xml(name), xml(unicode(val)))) comments = item[FM['comments']]