mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
e548421835
@ -14,22 +14,7 @@ from calibre.constants import preferred_encoding
|
|||||||
from calibre import isbytestring, force_unicode
|
from calibre import isbytestring, force_unicode
|
||||||
from calibre.utils.config import prefs, tweaks
|
from calibre.utils.config import prefs, tweaks
|
||||||
from calibre.utils.icu import strcmp
|
from calibre.utils.icu import strcmp
|
||||||
from calibre.utils.formatter import TemplateFormatter
|
from calibre.utils.formatter import eval_formatter
|
||||||
|
|
||||||
class SafeFormat(TemplateFormatter):
|
|
||||||
'''
|
|
||||||
Provides a format function that substitutes '' for any missing value
|
|
||||||
'''
|
|
||||||
|
|
||||||
def get_value(self, key, args, kwargs):
|
|
||||||
try:
|
|
||||||
if key in kwargs:
|
|
||||||
return kwargs[key]
|
|
||||||
return key
|
|
||||||
except:
|
|
||||||
return key
|
|
||||||
|
|
||||||
safe_formatter = SafeFormat()
|
|
||||||
|
|
||||||
class Book(Metadata):
|
class Book(Metadata):
|
||||||
def __init__(self, prefix, lpath, size=None, other=None):
|
def __init__(self, prefix, lpath, size=None, other=None):
|
||||||
@ -131,10 +116,10 @@ class CollectionsBookList(BookList):
|
|||||||
field_name = field_meta['name']
|
field_name = field_meta['name']
|
||||||
else:
|
else:
|
||||||
field_name = ''
|
field_name = ''
|
||||||
cat_name = safe_formatter.safe_format(
|
cat_name = eval_formatter.safe_format(
|
||||||
fmt=tweaks['sony_collection_name_template'],
|
fmt=tweaks['sony_collection_name_template'],
|
||||||
kwargs={'category':field_name, 'value':field_value},
|
kwargs={'category':field_name, 'value':field_value},
|
||||||
error_value='', book=None)
|
error_value='GET_CATEGORY', book=None)
|
||||||
return cat_name.strip()
|
return cat_name.strip()
|
||||||
|
|
||||||
def get_collections(self, collection_attributes):
|
def get_collections(self, collection_attributes):
|
||||||
|
@ -44,7 +44,10 @@ def render_rows(data):
|
|||||||
key = key.decode(preferred_encoding, 'replace')
|
key = key.decode(preferred_encoding, 'replace')
|
||||||
if isinstance(txt, str):
|
if isinstance(txt, str):
|
||||||
txt = txt.decode(preferred_encoding, 'replace')
|
txt = txt.decode(preferred_encoding, 'replace')
|
||||||
if '</font>' not in txt:
|
if key.endswith(u':html'):
|
||||||
|
key = key[:-5]
|
||||||
|
txt = comments_to_html(txt)
|
||||||
|
elif '</font>' not in txt:
|
||||||
txt = prepare_string_for_xml(txt)
|
txt = prepare_string_for_xml(txt)
|
||||||
if 'id' in data:
|
if 'id' in data:
|
||||||
if key == _('Path'):
|
if key == _('Path'):
|
||||||
|
@ -9,15 +9,17 @@ import sys
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
|
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
|
||||||
QDate, QGroupBox, QVBoxLayout, QPlainTextEdit, QSizePolicy, \
|
QDate, QGroupBox, QVBoxLayout, QSizePolicy, \
|
||||||
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \
|
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \
|
||||||
QPushButton
|
QPushButton
|
||||||
|
|
||||||
from calibre.utils.date import qt_to_dt, now
|
from calibre.utils.date import qt_to_dt, now
|
||||||
from calibre.gui2.widgets import TagsLineEdit, EnComboBox
|
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.gui2 import UNDEFINED_QDATE, error_dialog
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
|
from calibre.library.comments import comments_to_html
|
||||||
|
|
||||||
class Base(object):
|
class Base(object):
|
||||||
|
|
||||||
@ -186,9 +188,9 @@ class Comments(Base):
|
|||||||
self._box = QGroupBox(parent)
|
self._box = QGroupBox(parent)
|
||||||
self._box.setTitle('&'+self.col_metadata['name'])
|
self._box.setTitle('&'+self.col_metadata['name'])
|
||||||
self._layout = QVBoxLayout()
|
self._layout = QVBoxLayout()
|
||||||
self._tb = QPlainTextEdit(self._box)
|
self._tb = CommentsEditor(self._box)
|
||||||
self._tb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
|
self._tb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
|
||||||
self._tb.setTabChangesFocus(True)
|
#self._tb.setTabChangesFocus(True)
|
||||||
self._layout.addWidget(self._tb)
|
self._layout.addWidget(self._tb)
|
||||||
self._box.setLayout(self._layout)
|
self._box.setLayout(self._layout)
|
||||||
self.widgets = [self._box]
|
self.widgets = [self._box]
|
||||||
@ -196,10 +198,10 @@ class Comments(Base):
|
|||||||
def setter(self, val):
|
def setter(self, val):
|
||||||
if val is None:
|
if val is None:
|
||||||
val = ''
|
val = ''
|
||||||
self._tb.setPlainText(val)
|
self._tb.html = comments_to_html(val)
|
||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
val = unicode(self._tb.toPlainText()).strip()
|
val = unicode(self._tb.html).strip()
|
||||||
if not val:
|
if not val:
|
||||||
val = None
|
val = None
|
||||||
return val
|
return val
|
||||||
|
@ -12,6 +12,7 @@ from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo
|
|||||||
from calibre.gui2 import dynamic, open_local_file
|
from calibre.gui2 import dynamic, open_local_file
|
||||||
from calibre import fit_image
|
from calibre import fit_image
|
||||||
from calibre.library.comments import comments_to_html
|
from calibre.library.comments import comments_to_html
|
||||||
|
from calibre.utils.icu import sort_key
|
||||||
|
|
||||||
class BookInfo(QDialog, Ui_BookInfo):
|
class BookInfo(QDialog, Ui_BookInfo):
|
||||||
|
|
||||||
@ -130,9 +131,12 @@ class BookInfo(QDialog, Ui_BookInfo):
|
|||||||
for f in formats:
|
for f in formats:
|
||||||
f = f.strip()
|
f = f.strip()
|
||||||
info[_('Formats')] += '<a href="%s">%s</a>, '%(f,f)
|
info[_('Formats')] += '<a href="%s">%s</a>, '%(f,f)
|
||||||
for key in info.keys():
|
for key in sorted(info.keys(), key=sort_key):
|
||||||
if key == 'id': continue
|
if key == 'id': continue
|
||||||
txt = info[key]
|
txt = info[key]
|
||||||
|
if key.endswith(':html'):
|
||||||
|
key = key[:-5]
|
||||||
|
txt = comments_to_html(txt)
|
||||||
if key != _('Path'):
|
if key != _('Path'):
|
||||||
txt = u'<br />\n'.join(textwrap.wrap(txt, 120))
|
txt = u'<br />\n'.join(textwrap.wrap(txt, 120))
|
||||||
rows += u'<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt)
|
rows += u'<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt)
|
||||||
|
@ -5,6 +5,7 @@ __license__ = 'GPL v3'
|
|||||||
|
|
||||||
from PyQt4.Qt import Qt, QDialog, QDialogButtonBox
|
from PyQt4.Qt import Qt, QDialog, QDialogButtonBox
|
||||||
from calibre.gui2.dialogs.comments_dialog_ui import Ui_CommentsDialog
|
from calibre.gui2.dialogs.comments_dialog_ui import Ui_CommentsDialog
|
||||||
|
from calibre.library.comments import comments_to_html
|
||||||
|
|
||||||
class CommentsDialog(QDialog, Ui_CommentsDialog):
|
class CommentsDialog(QDialog, Ui_CommentsDialog):
|
||||||
|
|
||||||
@ -18,8 +19,8 @@ class CommentsDialog(QDialog, Ui_CommentsDialog):
|
|||||||
self.setWindowIcon(icon)
|
self.setWindowIcon(icon)
|
||||||
|
|
||||||
if text is not None:
|
if text is not None:
|
||||||
self.textbox.setPlainText(text)
|
self.textbox.html = comments_to_html(text)
|
||||||
self.textbox.setTabChangesFocus(True)
|
# self.textbox.setTabChangesFocus(True)
|
||||||
self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK'))
|
self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK'))
|
||||||
self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel'))
|
self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel'))
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>336</width>
|
<width>400</width>
|
||||||
<height>235</height>
|
<height>400</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -19,22 +19,29 @@
|
|||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Edit Comments</string>
|
<string>Edit Comments</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPlainTextEdit" name="textbox"/>
|
<widget class="Editor" name="textbox" native="true"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="standardButtons">
|
<property name="standardButtons">
|
||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>Editor</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>calibre/gui2/comments_editor.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections>
|
<connections>
|
||||||
<connection>
|
<connection>
|
||||||
|
@ -417,6 +417,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
self.multiple_separator.setFixedWidth(30)
|
self.multiple_separator.setFixedWidth(30)
|
||||||
self.multiple_separator.setText(' ::: ')
|
self.multiple_separator.setText(' ::: ')
|
||||||
self.multiple_separator.textChanged.connect(self.s_r_separator_changed)
|
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):
|
def s_r_get_field(self, mi, field):
|
||||||
if field:
|
if field:
|
||||||
@ -439,6 +441,9 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
val = []
|
val = []
|
||||||
return 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):
|
def s_r_template_changed(self):
|
||||||
self.s_r_search_field_changed(self.search_field.currentIndex())
|
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)
|
mi = self.db.get_metadata(self.ids[i], index_is_id=True)
|
||||||
src = unicode(self.search_field.currentText())
|
src = unicode(self.search_field.currentText())
|
||||||
t = self.s_r_get_field(mi, src)
|
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))
|
w.setText(unicode(self.multiple_separator.text()).join(t))
|
||||||
|
|
||||||
if self.search_mode.currentIndex() == 0:
|
if self.search_mode.currentIndex() == 0:
|
||||||
@ -466,12 +474,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
txt = unicode(txt)
|
txt = unicode(txt)
|
||||||
if not txt:
|
if not txt:
|
||||||
txt = unicode(self.search_field.currentText())
|
txt = unicode(self.search_field.currentText())
|
||||||
self.comma_separated.setEnabled(True)
|
|
||||||
if txt and txt in self.writable_fields:
|
if txt and txt in self.writable_fields:
|
||||||
self.destination_field_fm = self.db.metadata_for_field(txt)
|
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)
|
self.s_r_paint_results(None)
|
||||||
|
|
||||||
def s_r_search_mode_changed(self, val):
|
def s_r_search_mode_changed(self, val):
|
||||||
@ -542,6 +546,22 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
dest = src
|
dest = src
|
||||||
dest_mode = self.replace_mode.currentIndex()
|
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:
|
if dest_mode != 0:
|
||||||
dest_val = mi.get(dest, '')
|
dest_val = mi.get(dest, '')
|
||||||
if dest_val is None:
|
if dest_val is None:
|
||||||
@ -602,8 +622,9 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
try:
|
try:
|
||||||
result = self.s_r_do_regexp(mi)
|
result = self.s_r_do_regexp(mi)
|
||||||
t = self.s_r_do_destination(mi, result)
|
t = self.s_r_do_destination(mi, result)
|
||||||
if len(result) > 1 and self.destination_field_fm is not None and \
|
if len(t) > 1 and self.destination_field_fm['is_multiple']:
|
||||||
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)
|
t = unicode(self.multiple_separator.text()).join(t)
|
||||||
else:
|
else:
|
||||||
t = self.s_r_replace_mode_separator().join(t)
|
t = self.s_r_replace_mode_separator().join(t)
|
||||||
|
@ -658,11 +658,12 @@ If blank, the source field is used if the field is modifiable</string>
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="comma_separated">
|
<widget class="QCheckBox" name="comma_separated">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Specifies whether a comma should be put between values when copying from a
|
<string>Specifies whether result items should be split into multiple values or
|
||||||
multiple-valued field to a single-valued field</string>
|
left as single values. This option has the most effect when the source field is
|
||||||
|
not multiple and the destination field is multiple</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Use comma</string>
|
<string>Split &result</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="checked">
|
<property name="checked">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@ -684,28 +685,8 @@ multiple-valued field to a single-valued field</string>
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="1">
|
<item row="8" column="1" colspan="2">
|
||||||
<widget class="QLabel" name="xlabel_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>Test text</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>test_text</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="8" column="2">
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_51">
|
|
||||||
<property name="text">
|
|
||||||
<string>Test result</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>test_result</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<spacer name="HSpacer_347">
|
<spacer name="HSpacer_347">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -719,10 +700,62 @@ multiple-valued field to a single-valued field</string>
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="xlabel_412">
|
||||||
|
<property name="text">
|
||||||
|
<string>For multiple-valued fields, sho&w</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>results_count</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="results_count">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>999</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>999</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="xlabel_412">
|
||||||
|
<property name="text">
|
||||||
|
<string>values starting a&t</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>starting_from</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="starting_from">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>999</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="xlabel_41">
|
<widget class="QLabel" name="xlabel_41">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Multi&ple separator:</string>
|
<string>with values separated b&y</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>multiple_separator</cstring>
|
<cstring>multiple_separator</cstring>
|
||||||
@ -756,6 +789,20 @@ multiple-valued field to a single-valued field</string>
|
|||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="testgrid">
|
<layout class="QGridLayout" name="testgrid">
|
||||||
|
<item row="7" column="1">
|
||||||
|
<widget class="QLabel" name="xlabel_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Test text</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="2">
|
||||||
|
<widget class="QLabel" name="xlabel_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Test result</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="8" column="0">
|
<item row="8" column="0">
|
||||||
<widget class="QLabel" name="label_31">
|
<widget class="QLabel" name="label_31">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -857,6 +904,8 @@ multiple-valued field to a single-valued field</string>
|
|||||||
<tabstop>destination_field</tabstop>
|
<tabstop>destination_field</tabstop>
|
||||||
<tabstop>replace_mode</tabstop>
|
<tabstop>replace_mode</tabstop>
|
||||||
<tabstop>comma_separated</tabstop>
|
<tabstop>comma_separated</tabstop>
|
||||||
|
<tabstop>results_count</tabstop>
|
||||||
|
<tabstop>starting_from</tabstop>
|
||||||
<tabstop>multiple_separator</tabstop>
|
<tabstop>multiple_separator</tabstop>
|
||||||
<tabstop>test_text</tabstop>
|
<tabstop>test_text</tabstop>
|
||||||
<tabstop>test_result</tabstop>
|
<tabstop>test_result</tabstop>
|
||||||
|
25
src/calibre/gui2/dialogs/template_dialog.py
Normal file
25
src/calibre/gui2/dialogs/template_dialog.py
Normal file
@ -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'))
|
||||||
|
|
73
src/calibre/gui2/dialogs/template_dialog.ui
Normal file
73
src/calibre/gui2/dialogs/template_dialog.ui
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>TemplateDialog</class>
|
||||||
|
<widget class="QDialog" name="TemplateDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>336</width>
|
||||||
|
<height>235</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Edit Comments</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPlainTextEdit" name="textbox"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>TemplateDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>229</x>
|
||||||
|
<y>211</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>234</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>TemplateDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>297</x>
|
||||||
|
<y>217</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>234</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
@ -13,7 +13,7 @@ from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \
|
|||||||
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
|
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
|
||||||
QIcon, QDoubleSpinBox, QVariant, QSpinBox, \
|
QIcon, QDoubleSpinBox, QVariant, QSpinBox, \
|
||||||
QStyledItemDelegate, QCompleter, \
|
QStyledItemDelegate, QCompleter, \
|
||||||
QComboBox
|
QComboBox, QTextDocument
|
||||||
|
|
||||||
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
||||||
from calibre.gui2.widgets import EnLineEdit, TagsLineEdit
|
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.formatter import validation_formatter
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
from calibre.gui2.dialogs.comments_dialog import CommentsDialog
|
from calibre.gui2.dialogs.comments_dialog import CommentsDialog
|
||||||
|
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||||
|
|
||||||
|
|
||||||
class RatingDelegate(QStyledItemDelegate): # {{{
|
class RatingDelegate(QStyledItemDelegate): # {{{
|
||||||
COLOR = QColor("blue")
|
COLOR = QColor("blue")
|
||||||
@ -294,6 +296,24 @@ class CcCommentsDelegate(QStyledItemDelegate): # {{{
|
|||||||
Delegate for comments data.
|
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):
|
def createEditor(self, parent, option, index):
|
||||||
m = index.model()
|
m = index.model()
|
||||||
col = m.column_map[index.column()]
|
col = m.column_map[index.column()]
|
||||||
@ -301,11 +321,11 @@ class CcCommentsDelegate(QStyledItemDelegate): # {{{
|
|||||||
editor = CommentsDialog(parent, text)
|
editor = CommentsDialog(parent, text)
|
||||||
d = editor.exec_()
|
d = editor.exec_()
|
||||||
if d:
|
if d:
|
||||||
m.setData(index, QVariant(editor.textbox.toPlainText()), Qt.EditRole)
|
m.setData(index, QVariant(editor.textbox.html), Qt.EditRole)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def setModelData(self, editor, model, index):
|
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): # {{{
|
class CcBoolDelegate(QStyledItemDelegate): # {{{
|
||||||
@ -351,7 +371,7 @@ class CcTemplateDelegate(QStyledItemDelegate): # {{{
|
|||||||
def createEditor(self, parent, option, index):
|
def createEditor(self, parent, option, index):
|
||||||
m = index.model()
|
m = index.model()
|
||||||
text = m.custom_columns[m.column_map[index.column()]]['display']['composite_template']
|
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.setWindowTitle(_("Edit template"))
|
||||||
editor.textbox.setTabChangesFocus(False)
|
editor.textbox.setTabChangesFocus(False)
|
||||||
editor.textbox.setTabStopWidth(20)
|
editor.textbox.setTabStopWidth(20)
|
||||||
|
@ -334,6 +334,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
if key not in cf_to_display:
|
if key not in cf_to_display:
|
||||||
continue
|
continue
|
||||||
name, val = mi.format_field(key)
|
name, val = mi.format_field(key)
|
||||||
|
if mi.metadata_for_field(key)['datatype'] == 'comments':
|
||||||
|
name += ':html'
|
||||||
if val:
|
if val:
|
||||||
data[name] = val
|
data[name] = val
|
||||||
return data
|
return data
|
||||||
|
@ -173,6 +173,8 @@ def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix):
|
|||||||
extra.append('%s: %s<br />'%(xml(name), xml(format_tag_string(val, ',',
|
extra.append('%s: %s<br />'%(xml(name), xml(format_tag_string(val, ',',
|
||||||
ignore_max=True,
|
ignore_max=True,
|
||||||
no_tag_count=True))))
|
no_tag_count=True))))
|
||||||
|
elif datatype == 'comments':
|
||||||
|
extra.append('%s: %s<br />'%(xml(name), comments_to_html(unicode(val))))
|
||||||
else:
|
else:
|
||||||
extra.append('%s: %s<br />'%(xml(name), xml(unicode(val))))
|
extra.append('%s: %s<br />'%(xml(name), xml(unicode(val))))
|
||||||
comments = item[FM['comments']]
|
comments = item[FM['comments']]
|
||||||
|
@ -36,7 +36,7 @@ class _Parser(object):
|
|||||||
return gt
|
return gt
|
||||||
|
|
||||||
def _assign(self, target, value):
|
def _assign(self, target, value):
|
||||||
setattr(self, target, value)
|
self.variables[target] = value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _concat(self, *args):
|
def _concat(self, *args):
|
||||||
@ -55,18 +55,23 @@ class _Parser(object):
|
|||||||
}
|
}
|
||||||
x = float(x if x else 0)
|
x = float(x if x else 0)
|
||||||
y = float(y if y else 0)
|
y = float(y if y else 0)
|
||||||
return ops[op](x, y)
|
return unicode(ops[op](x, y))
|
||||||
|
|
||||||
def _template(self, template):
|
def _template(self, template):
|
||||||
template = template.replace('[[', '{').replace(']]', '}')
|
template = template.replace('[[', '{').replace(']]', '}')
|
||||||
return self.parent.safe_format(template, self.parent.kwargs, 'TEMPLATE',
|
return self.parent.safe_format(template, self.parent.kwargs, 'TEMPLATE',
|
||||||
self.parent.book)
|
self.parent.book)
|
||||||
|
|
||||||
|
def _eval(self, template):
|
||||||
|
template = template.replace('[[', '{').replace(']]', '}')
|
||||||
|
return eval_formatter.safe_format(template, self.variables, 'EVAL', None)
|
||||||
|
|
||||||
local_functions = {
|
local_functions = {
|
||||||
'add' : (2, partial(_math, op='+')),
|
'add' : (2, partial(_math, op='+')),
|
||||||
'assign' : (2, _assign),
|
'assign' : (2, _assign),
|
||||||
'cmp' : (5, _cmp),
|
'cmp' : (5, _cmp),
|
||||||
'divide' : (2, partial(_math, op='/')),
|
'divide' : (2, partial(_math, op='/')),
|
||||||
|
'eval' : (1, _eval),
|
||||||
'field' : (1, lambda s, x: s.parent.get_value(x, [], s.parent.kwargs)),
|
'field' : (1, lambda s, x: s.parent.get_value(x, [], s.parent.kwargs)),
|
||||||
'multiply' : (2, partial(_math, op='*')),
|
'multiply' : (2, partial(_math, op='*')),
|
||||||
'strcat' : (-1, _concat),
|
'strcat' : (-1, _concat),
|
||||||
@ -82,7 +87,7 @@ class _Parser(object):
|
|||||||
if prog[1] != '':
|
if prog[1] != '':
|
||||||
self.error(_('failed to scan program. Invalid input {0}').format(prog[1]))
|
self.error(_('failed to scan program. Invalid input {0}').format(prog[1]))
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
setattr(self, '$', val)
|
self.variables = {'$':val}
|
||||||
|
|
||||||
def error(self, message):
|
def error(self, message):
|
||||||
m = 'Formatter: ' + message + _(' near ')
|
m = 'Formatter: ' + message + _(' near ')
|
||||||
@ -144,7 +149,7 @@ class _Parser(object):
|
|||||||
# We have an identifier. Determine if it is a function
|
# We have an identifier. Determine if it is a function
|
||||||
id = self.token()
|
id = self.token()
|
||||||
if not self.token_op_is_a('('):
|
if not self.token_op_is_a('('):
|
||||||
return getattr(self, id, _('unknown id ') + id)
|
return self.variables.get(id, _('unknown id ') + id)
|
||||||
# We have a function.
|
# We have a function.
|
||||||
# Check if it is a known one. We do this here so error reporting is
|
# Check if it is a known one. We do this here so error reporting is
|
||||||
# better, as it can identify the tokens near the problem.
|
# better, as it can identify the tokens near the problem.
|
||||||
@ -417,15 +422,18 @@ class TemplateFormatter(string.Formatter):
|
|||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
self.book = book
|
self.book = book
|
||||||
self.composite_values = {}
|
self.composite_values = {}
|
||||||
try:
|
if fmt.startswith('program:'):
|
||||||
ans = self.vformat(fmt, [], kwargs).strip()
|
ans = self._eval_program(None, fmt[8:])
|
||||||
except Exception, e:
|
else:
|
||||||
if DEBUG:
|
try:
|
||||||
traceback.print_exc()
|
ans = self.vformat(fmt, [], kwargs).strip()
|
||||||
ans = error_value + ' ' + e.message
|
except Exception, e:
|
||||||
|
if DEBUG:
|
||||||
|
traceback.print_exc()
|
||||||
|
ans = error_value + ' ' + e.message
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
class ValidateFormat(TemplateFormatter):
|
class ValidateFormatter(TemplateFormatter):
|
||||||
'''
|
'''
|
||||||
Provides a format function that substitutes '' for any missing value
|
Provides a format function that substitutes '' for any missing value
|
||||||
'''
|
'''
|
||||||
@ -435,6 +443,14 @@ class ValidateFormat(TemplateFormatter):
|
|||||||
def validate(self, x):
|
def validate(self, x):
|
||||||
return self.vformat(x, [], {})
|
return self.vformat(x, [], {})
|
||||||
|
|
||||||
validation_formatter = ValidateFormat()
|
validation_formatter = ValidateFormatter()
|
||||||
|
|
||||||
|
class EvalFormatter(TemplateFormatter):
|
||||||
|
'''
|
||||||
|
A template formatter that uses a simple dict instead of an mi instance
|
||||||
|
'''
|
||||||
|
def get_value(self, key, args, kwargs):
|
||||||
|
return kwargs.get(key, _('No such variable ') + key)
|
||||||
|
|
||||||
|
eval_formatter = EvalFormatter()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user