From a3bb5e2cae0c054f328a499b3b78498bb5031e93 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 23 Jul 2016 13:04:18 +0530 Subject: [PATCH] Allow creating a custom title-like (short text) column (Preferences->Add your own columns->Short text) Also, allow using Markdown for formatting in custom comments like columns. --- src/calibre/ebooks/metadata/book/render.py | 23 +++++++--- .../gui2/preferences/create_custom_column.py | 43 +++++++++++++++---- src/calibre/library/comments.py | 8 ++++ src/calibre/srv/metadata.py | 13 +++++- src/pyj/book_list/book_details.pyj | 9 +++- 5 files changed, 78 insertions(+), 18 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 26ca442890..e0750a301a 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -14,7 +14,7 @@ from calibre import prepare_string_for_xml, force_unicode from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata.sources.identify import urls_from_identifiers from calibre.constants import filesystem_encoding -from calibre.library.comments import comments_to_html +from calibre.library.comments import comments_to_html, markdown from calibre.utils.icu import sort_key from calibre.utils.formatter import EvalFormatter from calibre.utils.date import is_date_undefined @@ -83,13 +83,24 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers= if not name: name = field name += ':' + disp = metadata['display'] if metadata['datatype'] == 'comments' or field == 'comments': val = getattr(mi, field) if val: - val = comments_to_html(force_unicode(val)) - if metadata['display'].get('show_heading'): - val = '

%s

%s' % (p(name), val) - comment_fields.append('
%s
' % (field.replace('#', '_'), val)) + ctype = disp.get('interpret_as') or 'html' + val = force_unicode(val) + if ctype == 'short-text': + ans.append((field, row % (name, p(val)))) + else: + if ctype == 'long-text': + val = '
%s
' % p(val) + elif ctype == 'markdown': + val = markdown(val) + else: + val = comments_to_html(val) + if disp.get('show_heading'): + val = '

%s

%s' % (p(name), val) + comment_fields.append('
%s
' % (field.replace('#', '_'), val)) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: @@ -102,7 +113,7 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers= val = getattr(mi, field) if val: val = force_unicode(val) - if metadata['display'].get('contains_html', False): + if disp.get('contains_html', False): ans.append((field, row % (name, comments_to_html(val)))) else: if not metadata['is_multiple']: diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 2f51bad9a2..9a5bfd1a77 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -82,6 +82,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): 'is_multiple':True }, ))) + column_types_map = {k['datatype']:idx for idx, k in column_types.iteritems()} def __init__(self, parent, current_row, current_key, standard_colheads, standard_colnames): QDialog.__init__(self, parent) @@ -165,6 +166,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): self.format_box.setText(c['display'].get('number_format', '')) elif ct == 'comments': self.show_comments_heading.setChecked(c['display'].get('show_heading', False)) + idx = max(0, self.comments_type.findData(c['display'].get('interpret_as', 'html'))) + self.comments_type.setCurrentIndex(idx) self.datatype_changed() if ct in ['text', 'composite', 'enumeration']: self.use_decorations.setChecked(c['display'].get('use_decorations', False)) @@ -177,12 +180,13 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): def shortcut_activated(self, url): # {{{ which = unicode(url).split(':')[-1] self.column_type_box.setCurrentIndex({ - 'yesno': 9, - 'tags' : 1, - 'series': 3, - 'rating': 8, - 'people': 1, - }.get(which, 10)) + 'yesno': self.column_types_map['bool'], + 'tags' : self.column_types_map['*text'], + 'series': self.column_types_map['series'], + 'rating': self.column_types_map['rating'], + 'people': self.column_types_map['*text'], + 'text': self.column_types_map['comments'], + }.get(which, self.column_types_map['composite'])) self.column_name_box.setText(which) self.column_heading_box.setText({ 'isbn':'ISBN', @@ -191,7 +195,9 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): 'tags': _('My Tags'), 'series': _('My Series'), 'rating': _('My Rating'), - 'people': _('People')}[which]) + 'people': _('People'), + 'text': _('My Title'), + }[which]) self.is_names.setChecked(which == 'people') if self.composite_box.isVisible(): self.composite_box.setText( @@ -200,6 +206,9 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): 'formats': "{:'approximate_formats()'}", }[which]) self.composite_sort_by.setCurrentIndex(0) + if which == 'text': + self.show_comments_heading.setChecked(True) + self.comments_type.setCurrentIndex(self.comments_type.findData('short-text')) # }}} def setup_ui(self): # {{{ @@ -215,7 +224,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): for col, name in [('isbn', _('ISBN')), ('formats', _('Formats')), ('yesno', _('Yes/No')), ('tags', _('Tags')), ('series', _('Series')), ('rating', - _('Rating')), ('people', _("People's names"))]: + _('Rating')), ('people', _("Names")), ('text', _('Short text'))]: text += ' %s,'%(col, name) text = text[:-1] s.setText(text) @@ -303,6 +312,18 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): sch.setToolTip(_( 'Choose whether to show the heading for this column in the Book Details Panel')) add_row(None, sch) + self.comments_type = ct = QComboBox(self) + for k, text in ( + ('html', 'HTML'), + ('short-text', _('Short text, like a title')), + ('long-text', _('Plain text')), + ('markdown', _('Plain text formatted using markdown')) + ): + ct.addItem(text, k) + ct.setToolTip(_('Choose how the data in this column is interpreted.\n' + 'This control how the data is displayed in the Book Details panel\n' + 'and how it is edited.')) + self.comments_type_label = add_row(_('Interpret this column as:') + ' ', ct) # Values for enum type l = QGridLayout() @@ -394,7 +415,10 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): getattr(self, 'enum_'+x).setVisible(col_type == 'enumeration') self.use_decorations.setVisible(col_type in ['text', 'composite', 'enumeration']) self.is_names.setVisible(col_type == '*text') - self.show_comments_heading.setVisible(col_type == 'comments') + is_comments = col_type == 'comments' + self.show_comments_heading.setVisible(is_comments) + self.comments_type.setVisible(is_comments) + self.comments_type_label.setVisible(is_comments) def accept(self): col = unicode(self.column_name_box.text()).strip() @@ -491,6 +515,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): display_dict = {'number_format': None} elif col_type == 'comments': display_dict['show_heading'] = bool(self.show_comments_heading.isChecked()) + display_dict['interpret_as'] = type(u'')(self.comments_type.currentData()) if col_type in ['text', 'composite', 'enumeration'] and not is_multiple: display_dict['use_decorations'] = self.use_decorations.checkState() diff --git a/src/calibre/library/comments.py b/src/calibre/library/comments.py index e9a4a50410..b3e26472a4 100644 --- a/src/calibre/library/comments.py +++ b/src/calibre/library/comments.py @@ -130,6 +130,14 @@ def comments_to_html(comments): return result.renderContents(encoding=None) +def markdown(val): + try: + md = markdown.Markdown + except AttributeError: + from calibre.ebooks.markdown import Markdown + md = markdown.Markdown = Markdown() + return md.convert(val) + def merge_comments(one, two): return comments_to_html(one) + '\n\n' + comments_to_html(two) diff --git a/src/calibre/srv/metadata.py b/src/calibre/srv/metadata.py index 8d5ad7f87f..e1c411a582 100644 --- a/src/calibre/srv/metadata.py +++ b/src/calibre/srv/metadata.py @@ -12,6 +12,7 @@ from functools import partial from threading import Lock from urllib import quote +from calibre import prepare_string_for_xml from calibre.constants import config_dir from calibre.db.categories import Tag from calibre.ebooks.metadata.sources.identify import urls_from_identifiers @@ -21,7 +22,7 @@ from calibre.utils.formatter import EvalFormatter from calibre.utils.file_type_icons import EXT_MAP from calibre.utils.icu import collation_order from calibre.utils.localization import calibre_langcode_to_name -from calibre.library.comments import comments_to_html +from calibre.library.comments import comments_to_html, markdown from calibre.library.field_metadata import category_icon_map IGNORED_FIELDS = frozenset('cover ondevice path marked au_map size'.split()) @@ -49,7 +50,15 @@ def add_field(field, db, book_id, ans, field_metadata): if val is None: return elif datatype == 'comments' or field == 'comments': - val = comments_to_html(val) + ctype = field_metadata.get('display', {}).get('interpret_as', 'html') + if ctype == 'markdown': + val = markdown(val) + elif ctype == 'short-text': + pass + elif ctype == 'long-text': + val = '
%s
' % prepare_string_for_xml(val) + else: + val = comments_to_html(val) elif datatype == 'composite' and field_metadata['display'].get('contains_html'): val = comments_to_html(val) ans[field] = val diff --git a/src/pyj/book_list/book_details.pyj b/src/pyj/book_list/book_details.pyj index dce565d437..cc8225e833 100644 --- a/src/pyj/book_list/book_details.pyj +++ b/src/pyj/book_list/book_details.pyj @@ -201,7 +201,10 @@ def render_metadata(mi, interface_data, table, field_list=None): datatype = fm.datatype val = mi[field] if field is 'comments' or datatype is 'comments': - comments[field] = val + if fm.display?.interpret_as is 'short-text': + add_row(name, val) + else: + comments[field] = val return func = None if datatype is 'composite': @@ -249,9 +252,13 @@ def render_metadata(mi, interface_data, table, field_list=None): traceback.print_exc() for i, field in enumerate(sorted(comments)): + fm = interface_data.field_metadata[field] comment = comments[field] div = E.div() div.innerHTML = comment + if fm.display?.show_heading: + name = fm.name or field + div.insertBefore(E.h3(name), div.firstChild or None) table.parentNode.appendChild(div) if i is 0: div.style.marginTop = '2ex'