mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Allow the coloring of columns in the book list. You can either create a custom column with a fixed set of values and assign a color to each value, or you can use the calibre template language to color any column in arbitrarily powerful ways. For example, you can have the title appear in red if the book has a particular tag.
This commit is contained in:
commit
a346f7a947
32
src/calibre/gui2/dialogs/template_line_editor.py
Normal file
32
src/calibre/gui2/dialogs/template_line_editor.py
Normal file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import QLineEdit
|
||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||
|
||||
class TemplateLineEditor(QLineEdit):
|
||||
|
||||
'''
|
||||
Extend the context menu of a QLineEdit to include more actions.
|
||||
'''
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
menu = self.createStandardContextMenu()
|
||||
menu.addSeparator()
|
||||
|
||||
action_open_editor = menu.addAction(_('Open Template Editor'))
|
||||
|
||||
action_open_editor.triggered.connect(self.open_editor)
|
||||
menu.exec_(event.globalPos())
|
||||
|
||||
def open_editor(self):
|
||||
t = TemplateDialog(self, self.text())
|
||||
t.setWindowTitle(_('Edit template'))
|
||||
if t.exec_():
|
||||
self.setText(t.textbox.toPlainText())
|
||||
|
||||
|
@ -7,11 +7,12 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
from math import cos, sin, pi
|
||||
|
||||
from PyQt4.Qt import (QColor, Qt, QModelIndex, QSize,
|
||||
from PyQt4.Qt import (QColor, Qt, QModelIndex, QSize, QApplication,
|
||||
QPainterPath, QLinearGradient, QBrush,
|
||||
QPen, QStyle, QPainter, QStyleOptionViewItemV4,
|
||||
QIcon, QDoubleSpinBox, QVariant, QSpinBox,
|
||||
QStyledItemDelegate, QComboBox, QTextDocument)
|
||||
QStyledItemDelegate, QComboBox, QTextDocument,
|
||||
QAbstractTextDocumentLayout)
|
||||
|
||||
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
||||
from calibre.gui2.widgets import EnLineEdit
|
||||
@ -62,12 +63,14 @@ class RatingDelegate(QStyledItemDelegate): # {{{
|
||||
painter.restore()
|
||||
|
||||
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())
|
||||
else:
|
||||
painter.fillRect(option.rect, option.backgroundBrush)
|
||||
|
||||
try:
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
painter.setClipRect(option.rect)
|
||||
@ -316,18 +319,22 @@ class CcCommentsDelegate(QStyledItemDelegate): # {{{
|
||||
self.document = QTextDocument()
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
style = self.parent().style()
|
||||
self.document.setHtml(index.data(Qt.DisplayRole).toString())
|
||||
painter.save()
|
||||
self.initStyleOption(option, index)
|
||||
style = QApplication.style() if option.widget is None \
|
||||
else option.widget.style()
|
||||
self.document.setHtml(option.text)
|
||||
option.text = u''
|
||||
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()
|
||||
style.drawControl(QStyle.CE_ItemViewItem, option, painter)
|
||||
ctx = QAbstractTextDocumentLayout.PaintContext()
|
||||
ctx.palette = option.palette #.setColor(QPalette.Text, QColor("red"));
|
||||
if hasattr(QStyle, 'SE_ItemViewItemText'):
|
||||
textRect = style.subElementRect(QStyle.SE_ItemViewItemText, option)
|
||||
painter.save()
|
||||
painter.translate(textRect.topLeft())
|
||||
painter.setClipRect(textRect.translated(-textRect.topLeft()))
|
||||
self.document.documentLayout().draw(painter, ctx)
|
||||
painter.restore()
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
m = index.model()
|
||||
|
@ -14,6 +14,7 @@ from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage,
|
||||
from calibre.gui2 import NONE, UNDEFINED_QDATE
|
||||
from calibre.utils.pyparsing import ParseException
|
||||
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
|
||||
from calibre.ebooks.metadata.book.base import composite_formatter
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.utils.config import tweaks, prefs
|
||||
from calibre.utils.date import dt_factory, qt_to_dt
|
||||
@ -96,6 +97,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
self.ids_to_highlight_set = set()
|
||||
self.current_highlighted_idx = None
|
||||
self.highlight_only = False
|
||||
self.column_color_map = {}
|
||||
self.read_config()
|
||||
|
||||
def change_alignment(self, colname, alignment):
|
||||
@ -151,6 +153,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
self.headers[col] = self.custom_columns[col]['name']
|
||||
|
||||
self.build_data_convertors()
|
||||
self.set_color_templates(reset=False)
|
||||
self.reset()
|
||||
self.database_changed.emit(db)
|
||||
self.stop_metadata_backup()
|
||||
@ -532,6 +535,15 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
img = self.default_image
|
||||
return img
|
||||
|
||||
def set_color_templates(self, reset=True):
|
||||
self.column_color_map = {}
|
||||
for i in range(1,self.db.column_color_count+1):
|
||||
name = self.db.prefs.get('column_color_name_'+str(i))
|
||||
if name:
|
||||
self.column_color_map[name] = \
|
||||
self.db.prefs.get('column_color_template_'+str(i))
|
||||
if reset:
|
||||
self.reset()
|
||||
|
||||
def build_data_convertors(self):
|
||||
def authors(r, idx=-1):
|
||||
@ -696,6 +708,31 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
elif role == Qt.BackgroundRole:
|
||||
if self.id(index) in self.ids_to_highlight_set:
|
||||
return QVariant(QColor('lightgreen'))
|
||||
elif role == Qt.ForegroundRole:
|
||||
key = self.column_map[col]
|
||||
if key in self.column_color_map:
|
||||
mi = self.db.get_metadata(self.id(index), index_is_id=True)
|
||||
fmt = self.column_color_map[key]
|
||||
try:
|
||||
color = QColor(composite_formatter.safe_format(fmt, mi, '', mi))
|
||||
if color.isValid():
|
||||
return QVariant(color)
|
||||
except:
|
||||
return NONE
|
||||
elif self.is_custom_column(key) and \
|
||||
self.custom_columns[key]['datatype'] == 'enumeration':
|
||||
cc = self.custom_columns[self.column_map[col]]['display']
|
||||
colors = cc.get('enum_colors', [])
|
||||
values = cc.get('enum_values', [])
|
||||
txt = unicode(index.data(Qt.DisplayRole).toString())
|
||||
if len(colors) > 0 and txt in values:
|
||||
try:
|
||||
color = QColor(colors[values.index(txt)])
|
||||
if color.isValid():
|
||||
return QVariant(color)
|
||||
except:
|
||||
pass
|
||||
return NONE
|
||||
elif role == Qt.DecorationRole:
|
||||
if self.column_to_dc_decorator_map[col] is not None:
|
||||
return self.column_to_dc_decorator_map[index.column()](index.row())
|
||||
|
@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import re
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant
|
||||
from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant, QColor
|
||||
|
||||
from calibre.gui2.preferences.create_custom_column_ui import Ui_QCreateCustomColumn
|
||||
from calibre.gui2 import error_dialog
|
||||
@ -126,11 +126,15 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
c['display'].get('make_category', False))
|
||||
elif ct == 'enumeration':
|
||||
self.enum_box.setText(','.join(c['display'].get('enum_values', [])))
|
||||
self.enum_colors.setText(','.join(c['display'].get('enum_colors', [])))
|
||||
self.datatype_changed()
|
||||
if ct in ['text', 'composite', 'enumeration']:
|
||||
self.use_decorations.setChecked(c['display'].get('use_decorations', False))
|
||||
elif ct == '*text':
|
||||
self.is_names.setChecked(c['display'].get('is_names', False))
|
||||
|
||||
all_colors = [unicode(s) for s in list(QColor.colorNames())]
|
||||
self.enum_colors_label.setToolTip('<p>' + ', '.join(all_colors) + '</p>')
|
||||
self.exec_()
|
||||
|
||||
def shortcut_activated(self, url):
|
||||
@ -170,7 +174,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
for x in ('box', 'default_label', 'label', 'sort_by', 'sort_by_label',
|
||||
'make_category'):
|
||||
getattr(self, 'composite_'+x).setVisible(col_type in ['composite', '*composite'])
|
||||
for x in ('box', 'default_label', 'label'):
|
||||
for x in ('box', 'default_label', 'label', 'colors', 'colors_label'):
|
||||
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')
|
||||
@ -247,7 +251,20 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
if l[i] in l[i+1:]:
|
||||
return self.simple_error('', _('The value "{0}" is in the '
|
||||
'list more than once').format(l[i]))
|
||||
display_dict = {'enum_values': l}
|
||||
c = unicode(self.enum_colors.text())
|
||||
if c:
|
||||
c = [v.strip() for v in unicode(self.enum_colors.text()).split(',')]
|
||||
else:
|
||||
c = []
|
||||
if len(c) != 0 and len(c) != len(l):
|
||||
return self.simple_error('', _('The colors box must be empty or '
|
||||
'contain the same number of items as the value box'))
|
||||
for tc in c:
|
||||
if tc not in QColor.colorNames():
|
||||
return self.simple_error('',
|
||||
_('The color {0} is unknown').format(tc))
|
||||
|
||||
display_dict = {'enum_values': l, 'enum_colors': c}
|
||||
elif col_type == 'text' and is_multiple:
|
||||
display_dict = {'is_names': self.is_names.isChecked()}
|
||||
|
||||
|
@ -304,8 +304,8 @@ Everything else will show nothing.</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="horizontalLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLineEdit" name="enum_box">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
@ -320,13 +320,34 @@ four values, the first of them being the empty value.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="enum_default_label">
|
||||
<property name="toolTip">
|
||||
<string>The empty string is always the first value</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Default: (nothing)</string>
|
||||
<string>Values</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="enum_colors">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>A list of color names to use when displaying an item. The
|
||||
list must be empty or contain a color for each value.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="enum_colors_label">
|
||||
<property name="text">
|
||||
<string>Colors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import (QApplication, QFont, QFontInfo, QFontDialog,
|
||||
QAbstractListModel, Qt)
|
||||
QAbstractListModel, Qt, QColor)
|
||||
|
||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList
|
||||
from calibre.gui2.preferences.look_feel_ui import Ui_Form
|
||||
@ -159,6 +159,51 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.df_up_button.clicked.connect(self.move_df_up)
|
||||
self.df_down_button.clicked.connect(self.move_df_down)
|
||||
|
||||
self.color_help_text.setText('<p>' +
|
||||
_('Here you can specify coloring rules for columns shown in the '
|
||||
'library view. Choose the column you wish to color, then '
|
||||
'supply a template that specifies the color to use based on '
|
||||
'the values in the column. There is a '
|
||||
'<a href="http://calibre-ebook.com/user_manual/template_lang.html">'
|
||||
'tutorial</a> on using templates.') +
|
||||
'</p><p>' +
|
||||
_('The template must evaluate to one of the color names shown '
|
||||
'below. You can use any legal template expression. '
|
||||
'For example, you can set the title to always display in '
|
||||
'green using the template "green" (without the quotes). '
|
||||
'To show the title in the color named in the custom column '
|
||||
'#column, use "{#column}". To show the title in blue if the '
|
||||
'custom column #column contains the value "foo", in red if the '
|
||||
'column contains the value "bar", otherwise in black, use '
|
||||
'<pre>{#column:switch(foo,blue,bar,red,black)}</pre>'
|
||||
'To show the title in blue if the book has the exact tag '
|
||||
'"Science Fiction", red if the book has the exact tag '
|
||||
'"Mystery", or black if the book has neither tag, use'
|
||||
"<pre>program: \n"
|
||||
" t = field('tags'); \n"
|
||||
" first_non_empty(\n"
|
||||
" in_list(t, ',', '^Science Fiction$', 'blue', ''), \n"
|
||||
" in_list(t, ',', '^Mystery$', 'red', 'black'))</pre>"
|
||||
'To show the title in green if it has one format, blue if it '
|
||||
'two formats, and red if more, use'
|
||||
"<pre>program:cmp(count(field('formats'),','), 2, 'green', 'blue', 'red')</pre>") +
|
||||
'</p><p>' +
|
||||
_('You can access a multi-line template editor from the '
|
||||
'context menu (right-click).') + '</p><p>' +
|
||||
_('<b>Note:</b> if you want to color a "custom column with a fixed set '
|
||||
'of values", it is often easier to specify the '
|
||||
'colors in the column definition dialog. There you can '
|
||||
'provide a color for each value without using a template.')+ '</p>')
|
||||
choices = db.field_metadata.displayable_field_keys()
|
||||
choices.sort(key=sort_key)
|
||||
choices.insert(0, '')
|
||||
self.column_color_count = db.column_color_count+1
|
||||
for i in range(1, self.column_color_count):
|
||||
r('column_color_name_'+str(i), db.prefs, choices=choices)
|
||||
r('column_color_template_'+str(i), db.prefs)
|
||||
all_colors = [unicode(s) for s in list(QColor.colorNames())]
|
||||
self.colors_box.setText(', '.join(all_colors))
|
||||
|
||||
def initialize(self):
|
||||
ConfigWidgetBase.initialize(self)
|
||||
font = gprefs['font']
|
||||
@ -226,6 +271,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.changed_signal.emit()
|
||||
|
||||
def commit(self, *args):
|
||||
for i in range(1, self.column_color_count):
|
||||
col = getattr(self, 'opt_column_color_name_'+str(i))
|
||||
if not col.currentText():
|
||||
temp = getattr(self, 'opt_column_color_template_'+str(i))
|
||||
temp.setText('')
|
||||
rr = ConfigWidgetBase.commit(self, *args)
|
||||
if self.current_font != self.initial_font:
|
||||
gprefs['font'] = (self.current_font[:4] if self.current_font else
|
||||
@ -238,6 +288,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
return rr
|
||||
|
||||
def refresh_gui(self, gui):
|
||||
gui.library_view.model().set_color_templates()
|
||||
self.update_font_display()
|
||||
gui.tags_view.reread_collapse_parameters()
|
||||
gui.library_view.refresh_book_details()
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>717</width>
|
||||
<height>390</height>
|
||||
<height>519</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -407,6 +407,125 @@ then the tags will be displayed each on their own line.</string>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_5">
|
||||
<attribute name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/format-fill-color.png</normaloff>:/images/format-fill-color.png</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Column Coloring</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="column_color_layout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Column name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Selection template</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QComboBox" name="opt_column_color_name_1"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="TemplateLineEditor" name="opt_column_color_template_1"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QComboBox" name="opt_column_color_name_2"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="TemplateLineEditor" name="opt_column_color_template_2"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QComboBox" name="opt_column_color_name_3"/>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="TemplateLineEditor" name="opt_column_color_template_3"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QComboBox" name="opt_column_color_name_4"/>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="TemplateLineEditor" name="opt_column_color_template_4"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QComboBox" name="opt_column_color_name_5"/>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="TemplateLineEditor" name="opt_column_color_template_5"/>
|
||||
</item>
|
||||
<item row="20" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Color names</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="21" column="0" colspan="2">
|
||||
<widget class="QTextEdit" name="colors_box">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>687</width>
|
||||
<height>194</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="color_help_text">
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -417,6 +536,11 @@ then the tags will be displayed each on their own line.</string>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>calibre/gui2/complete.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TemplateLineEditor</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>calibre/gui2/dialogs/template_line_editor.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../../resources/images.qrc"/>
|
||||
|
@ -7,12 +7,12 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import copy
|
||||
|
||||
from PyQt4.Qt import Qt, QLineEdit, QComboBox, SIGNAL, QListWidgetItem
|
||||
from PyQt4.Qt import Qt, QComboBox, QListWidgetItem
|
||||
|
||||
from calibre.customize.ui import is_disabled
|
||||
from calibre.gui2 import error_dialog, question_dialog
|
||||
from calibre.gui2.device import device_name_for_plugboards
|
||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||
from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor
|
||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
||||
from calibre.gui2.preferences.plugboard_ui import Ui_Form
|
||||
from calibre.customize.ui import metadata_writers, device_plugins
|
||||
@ -24,26 +24,6 @@ from calibre.library.server.content import plugboard_content_server_value, \
|
||||
from calibre.utils.formatter import validation_formatter
|
||||
|
||||
|
||||
class LineEditWithTextBox(QLineEdit):
|
||||
|
||||
'''
|
||||
Extend the context menu of a QLineEdit to include more actions.
|
||||
'''
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
menu = self.createStandardContextMenu()
|
||||
menu.addSeparator()
|
||||
|
||||
action_open_editor = menu.addAction(_('Open Editor'))
|
||||
|
||||
self.connect(action_open_editor, SIGNAL('triggered()'), self.open_editor)
|
||||
menu.exec_(event.globalPos())
|
||||
|
||||
def open_editor(self):
|
||||
t = TemplateDialog(self, self.text())
|
||||
if t.exec_():
|
||||
self.setText(t.textbox.toPlainText())
|
||||
|
||||
class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
|
||||
def genesis(self, gui):
|
||||
@ -107,7 +87,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.source_widgets = []
|
||||
self.dest_widgets = []
|
||||
for i in range(0, len(self.dest_fields)-1):
|
||||
w = LineEditWithTextBox(self)
|
||||
w = TemplateLineEditor(self)
|
||||
self.source_widgets.append(w)
|
||||
self.fields_layout.addWidget(w, 5+i, 0, 1, 1)
|
||||
w = QComboBox(self)
|
||||
|
@ -211,6 +211,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
defs = self.prefs.defaults
|
||||
defs['gui_restriction'] = defs['cs_restriction'] = ''
|
||||
defs['categories_using_hierarchy'] = []
|
||||
self.column_color_count = 5
|
||||
for i in range(1,self.column_color_count+1):
|
||||
defs['column_color_name_'+str(i)] = ''
|
||||
defs['column_color_template_'+str(i)] = ''
|
||||
|
||||
# Migrate the bool tristate tweak
|
||||
defs['bools_are_tristate'] = \
|
||||
|
@ -123,7 +123,8 @@ The functions available are:
|
||||
* ``contains(pattern, text if match, text if not match`` -- checks if field contains matches for the regular expression `pattern`. Returns `text if match` if matches are found, otherwise it returns `text if no match`.
|
||||
* ``count(separator)`` -- interprets the value as a list of items separated by `separator`, returning the number of items in the list. Most lists use a comma as the separator, but authors uses an ampersand. Examples: `{tags:count(,)}`, `{authors:count(&)}`
|
||||
* ``ifempty(text)`` -- if the field is not empty, return the value of the field. Otherwise return `text`.
|
||||
* ``list_item(index, separator)`` -- interpret the value as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the `count` function.
|
||||
* ``in_list(separator, pattern, found_val, not_found_val)`` -- interpret the field as a list of items separated by `separator`, comparing the `pattern` against each value in the list. If the pattern matches a value, return `found_val`, otherwise return `not_found_val`.
|
||||
* ``list_item(index, separator)`` -- interpret the field as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the `count` function.
|
||||
* ``re(pattern, replacement)`` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of |app|, these are python-compatible regular expressions.
|
||||
* ``shorten(left chars, middle text, right chars)`` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use ``{title:shorten(9,-,5)}``, the result will be `Ancient E-nhoe`. If the field's length is less than ``left chars`` + ``right chars`` + the length of ``middle text``, then the field will be used intact. For example, the title `The Dome` would not be changed.
|
||||
* ``switch(pattern, value, pattern, value, ..., else_value)`` -- for each ``pattern, value`` pair, checks if the field matches the regular expression ``pattern`` and if so, returns that ``value``. If no ``pattern`` matches, then ``else_value`` is returned. You can have as many ``pattern, value`` pairs as you want.
|
||||
@ -234,6 +235,7 @@ The following functions are available in addition to those described in single-f
|
||||
* ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``.
|
||||
* ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers.
|
||||
* ``field(name)`` -- returns the metadata field named by ``name``.
|
||||
* ``first_non_empty(value, value, ...) -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want.
|
||||
* ``format_date(x, date_format)`` -- format_date(val, format_string) -- format the value, which must be a date field, using the format_string, returning a string. The formatting codes are::
|
||||
|
||||
d : the day as number without a leading zero (1 to 31)
|
||||
|
@ -327,6 +327,22 @@ class BuiltinSwitch(BuiltinFormatterFunction):
|
||||
return args[i+1]
|
||||
i += 2
|
||||
|
||||
class BuiltinInList(BuiltinFormatterFunction):
|
||||
name = 'in_list'
|
||||
arg_count = 5
|
||||
doc = _('in_list(val, separator, pattern, found_val, not_found_val) -- '
|
||||
'treat val as a list of items separated by separator, '
|
||||
'comparing the pattern against each value in the list. If the '
|
||||
'pattern matches a value, return found_val, otherwise return '
|
||||
'not_found_val.')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv):
|
||||
l = [v.strip() for v in val.split(sep) if v.strip()]
|
||||
for v in l:
|
||||
if re.search(pat, v):
|
||||
return fv
|
||||
return nfv
|
||||
|
||||
class BuiltinRe(BuiltinFormatterFunction):
|
||||
name = 're'
|
||||
arg_count = 3
|
||||
@ -562,6 +578,22 @@ class BuiltinBooksize(BuiltinFormatterFunction):
|
||||
pass
|
||||
return ''
|
||||
|
||||
class BuiltinFirstNonEmpty(BuiltinFormatterFunction):
|
||||
name = 'first_non_empty'
|
||||
arg_count = -1
|
||||
doc = _('first_non_empty(value, value, ...) -- '
|
||||
'returns the first value that is not empty. If all values are '
|
||||
'empty, then the empty value is returned.'
|
||||
'You can have as many values as you want.')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, *args):
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i]:
|
||||
return args[i]
|
||||
i += 1
|
||||
return ''
|
||||
|
||||
builtin_add = BuiltinAdd()
|
||||
builtin_assign = BuiltinAssign()
|
||||
builtin_booksize = BuiltinBooksize()
|
||||
@ -571,9 +603,11 @@ builtin_contains = BuiltinContains()
|
||||
builtin_count = BuiltinCount()
|
||||
builtin_divide = BuiltinDivide()
|
||||
builtin_eval = BuiltinEval()
|
||||
builtin_format_date = BuiltinFormat_date()
|
||||
builtin_first_non_empty = BuiltinFirstNonEmpty()
|
||||
builtin_field = BuiltinField()
|
||||
builtin_format_date = BuiltinFormat_date()
|
||||
builtin_ifempty = BuiltinIfempty()
|
||||
builtin_in_list = BuiltinInList()
|
||||
builtin_list_item = BuiltinListitem()
|
||||
builtin_lookup = BuiltinLookup()
|
||||
builtin_lowercase = BuiltinLowercase()
|
||||
|
Loading…
x
Reference in New Issue
Block a user