Fix quotes in identifiers causing Tag Browser to be blank. Fixes #791044 (info in tag browser disappeared)

This commit is contained in:
Kovid Goyal 2011-06-02 09:55:15 -06:00
commit a1f4b9ee14
11 changed files with 148 additions and 555 deletions

View File

@ -54,7 +54,10 @@ class SafeFormat(TemplateFormatter):
key = orig_key
else:
raise ValueError(_('Value: unknown field ') + orig_key)
b = self.book.get_user_metadata(key, False)
try:
b = self.book.get_user_metadata(key, False)
except:
b = None
if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0:
v = ''
elif b and b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0:

View File

@ -5,13 +5,15 @@ __license__ = 'GPL v3'
import json
from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter,
QRegExp, QApplication,
QTextCharFormat, QFont, QColor, QCursor)
from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter, QFont,
QRegExp, QApplication, QTextCharFormat, QColor, QCursor)
from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog
from calibre.utils.formatter_functions import formatter_functions
from calibre.ebooks.metadata.book.base import composite_formatter
from calibre.ebooks.metadata.book.base import composite_formatter, Metadata
from calibre.library.coloring import (displayable_columns)
class ParenPosition:
@ -195,12 +197,24 @@ class TemplateHighlighter(QSyntaxHighlighter):
class TemplateDialog(QDialog, Ui_TemplateDialog):
def __init__(self, parent, text, mi):
def __init__(self, parent, text, mi=None, fm=None, color_field=None):
QDialog.__init__(self, parent)
Ui_TemplateDialog.__init__(self)
self.setupUi(self)
self.mi = mi
self.coloring = color_field is not None
if self.coloring:
cols = sorted([k for k in displayable_columns(fm)])
self.colored_field.addItems(cols)
self.colored_field.setCurrentIndex(self.colored_field.findText(color_field))
else:
self.colored_field.setVisible(False)
self.colored_field_label.setVisible(False)
if mi:
self.mi = mi
else:
self.mi = Metadata(None, None)
# Remove help icon on title bar
icon = self.windowIcon()
@ -238,24 +252,26 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
self.function.setCurrentIndex(0)
self.function.currentIndexChanged[str].connect(self.function_changed)
self.textbox_changed()
self.rule = (None, '')
def textbox_changed(self):
cur_text = unicode(self.textbox.toPlainText())
if self.last_text != cur_text:
self.last_text = cur_text
self.highlighter.regenerate_paren_positions()
self.text_cursor_changed()
self.template_value.setText(
composite_formatter.safe_format(cur_text, self.mi,
_('EXCEPTION: '), self.mi))
def text_cursor_changed(self):
cursor = self.textbox.textCursor()
block_number = cursor.blockNumber()
pos_in_block = cursor.positionInBlock()
position = cursor.position()
t = unicode(self.textbox.toPlainText())
if position < len(t):
self.highlighter.check_cursor_pos(t[position], block_number,
if position > 0 and position <= len(t):
block_number = cursor.blockNumber()
pos_in_block = cursor.positionInBlock() - 1
self.highlighter.check_cursor_pos(t[position-1], block_number,
pos_in_block)
def function_changed(self, toWhat):
@ -270,3 +286,17 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
else:
self.source_code.setPlainText(self.funcs[name].program_text)
def accept(self):
txt = unicode(self.textbox.toPlainText()).rstrip()
if self.coloring:
if self.colored_field.currentIndex() == -1:
error_dialog(self, _('No column chosen'),
_('You must specify a column to be colored'), show=True)
return
if not txt:
error_dialog(self, _('No template provided'),
_('The template box cannot be empty'), show=True)
return
self.rule = (unicode(self.colored_field.currentText()), txt)
QDialog.accept(self)

View File

@ -20,12 +20,30 @@
<string>Edit Comments</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="colored_field_label">
<property name="text">
<string>Set the color of the column:</string>
</property>
<property name="buddy">
<cstring>colored_field</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="colored_field">
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="textbox"/>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<item row="5" column="0">
<widget class="QLabel">
<property name="text">
<string>Template value:</string>
@ -38,14 +56,14 @@
</property>
</widget>
</item>
<item row="0" column="1">
<item row="5" column="1">
<widget class="QLineEdit" name="template_value">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="6" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -55,7 +73,7 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Function &amp;name:</string>
@ -65,10 +83,10 @@
</property>
</widget>
</item>
<item row="2" column="1">
<item row="7" column="1">
<widget class="QComboBox" name="function"/>
</item>
<item row="3" column="0">
<item row="8" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&amp;Documentation:</string>
@ -81,7 +99,7 @@
</property>
</widget>
</item>
<item row="4" column="0">
<item row="9" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Python &amp;code:</string>
@ -94,7 +112,7 @@
</property>
</widget>
</item>
<item row="3" column="1">
<item row="8" column="1">
<widget class="QPlainTextEdit" name="documentation">
<property name="maximumSize">
<size>
@ -104,7 +122,7 @@
</property>
</widget>
</item>
<item row="4" column="1">
<item row="9" column="1">
<widget class="QPlainTextEdit" name="source_code"/>
</item>
</layout>

View File

@ -5,17 +5,10 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from functools import partial
from collections import defaultdict
from PyQt4.Qt import (Qt, QLineEdit, QDialog, QGridLayout, QLabel, QCheckBox,
QIcon, QDialogButtonBox, QColor, QComboBox, QPushButton)
from PyQt4.Qt import QLineEdit
from calibre.ebooks.metadata.book.base import composite_formatter
from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.gui2.complete import MultiCompleteLineEdit
from calibre.gui2 import error_dialog
from calibre.utils.icu import sort_key
class TemplateLineEditor(QLineEdit):
@ -25,16 +18,11 @@ class TemplateLineEditor(QLineEdit):
def __init__(self, parent):
QLineEdit.__init__(self, parent)
self.tags = None
self.mi = None
self.txt = None
def set_mi(self, mi):
self.mi = mi
def set_db(self, db):
self.db = db
def contextMenuEvent(self, event):
menu = self.createStandardContextMenu()
menu.addSeparator()
@ -46,494 +34,10 @@ class TemplateLineEditor(QLineEdit):
menu.exec_(event.globalPos())
def clear_field(self):
self.txt = None
self.setText('')
self.setReadOnly(False)
self.setStyleSheet('TemplateLineEditor { color: black }')
def open_editor(self):
if self.txt:
t = TemplateDialog(self, self.txt, self.mi)
else:
t = TemplateDialog(self, self.text(), self.mi)
t = TemplateDialog(self, self.text(), mi=self.mi)
t.setWindowTitle(_('Edit template'))
if t.exec_():
self.txt = None
self.setText(t.textbox.toPlainText())
def enable_wizard_button(self, txt):
if not txt or txt.startswith('program:\n#tag wizard'):
return True
return False
def setText(self, txt):
txt = unicode(txt)
if txt and txt.startswith('program:\n#tag wizard'):
self.txt = txt
self.setReadOnly(True)
QLineEdit.setText(self, '')
QLineEdit.setText(self, _('Template generated by the wizard'))
self.setStyleSheet('TemplateLineEditor { color: gray }')
else:
QLineEdit.setText(self, txt)
def tag_wizard(self):
txt = unicode(self.text())
if txt and not self.txt:
error_dialog(self, _('Invalid text'),
_('The text in the box was not generated by this wizard'),
show=True, show_copy_button=False)
return
d = TagWizard(self, self.db, unicode(self.txt), self.mi)
if d.exec_():
self.setText(d.template)
def text(self):
if self.txt:
return self.txt
return QLineEdit.text(self)
class TagWizard(QDialog):
text_template = (" strcmp(field('{f}'), '{v}', '{ltv}', '{eqv}', '{gtv}')", True)
text_empty_template = (" test(field('{f}'), '{fv}', '{tv}')", False)
text_re_template = (" contains(field('{f}'), '{v}', '{tv}', '{fv}')", False)
templates = {
'text.mult' : (" str_in_list(field('{f}'), '{mult}', '{v}', '{tv}', '{fv}')", False),
'text.mult.re' : (" in_list(field('{f}'), '{mult}', '^{v}$', '{tv}', '{fv}')", False),
'text.mult.empty' : (" test(field('{f}'), '{fv}', '{tv}')", False),
'text' : text_template,
'text.re' : text_re_template,
'text.empty' : text_empty_template,
'rating' : (" cmp(raw_field('{f}'), '{v}', '{ltv}', '{eqv}', '{gtv}')", True),
'rating.empty' : text_empty_template,
'int' : (" cmp(raw_field('{f}'), '{v}', '{ltv}', '{eqv}', '{gtv}')", True),
'int.empty' : text_empty_template,
'float' : (" cmp(raw_field('{f}'), '{v}', '{ltv}', '{eqv}', '{gtv}')", True),
'float.empty' : text_empty_template,
'bool' : (" strcmp(field('{f}'), '{v}', '{ltv}', '{eqv}', '{gtv}')", True),
'bool.empty' : text_empty_template,
'datetime' : (" strcmp(format_date(raw_field('{f}'), 'yyyyMMdd'), format_date('{v}', 'yyyyMMdd'), '{ltv}', '{eqv}', '{gtv}')", True),
'datetime.empty' : text_empty_template,
'series' : text_template,
'series.re' : text_re_template,
'series.empty' : text_empty_template,
'composite' : text_template,
'composite.re' : text_re_template,
'composite.empty' : text_empty_template,
'enumeration' : text_template,
'enumeration.re' : text_re_template,
'enumeration.empty' : text_empty_template,
'comments' : text_template,
'comments.re' : text_re_template,
'comments.empty' : text_empty_template,
}
relationals = ('=', '!=', '<', '>', '<=', '>=')
relational_truth_vals = {
'=': ('', '1', ''),
'!=': ('1', '', '1'),
'<': ('1', '', ''),
'>': ('', '', '1'),
'<=': ('1', '1', ''),
'>=': ('', '1', '1'),
}
@staticmethod
def uses_this_wizard(txt):
if not txt or txt.startswith('program:\n#tag wizard'):
return True
return False
def __init__(self, parent, db, txt, mi):
QDialog.__init__(self, parent)
self.setWindowTitle(_('Coloring Wizard'))
self.setWindowIcon(QIcon(I('wizard.png')))
self.mi = mi
self.columns = []
self.completion_values = defaultdict(dict)
for k in db.all_field_keys():
m = db.metadata_for_field(k)
if k.endswith('_index') or (
m['kind'] == 'field' and m['name'] and
k not in ('ondevice', 'path', 'size', 'sort')):
self.columns.append(k)
self.completion_values[k]['dt'] = m['datatype']
if m['is_custom']:
if m['datatype'] in ('int', 'float'):
self.completion_values[k]['v'] = []
elif m['datatype'] == 'bool':
self.completion_values[k]['v'] = [_('Yes'), _('No')]
else:
self.completion_values[k]['v'] = db.all_custom(m['label'])
elif k == 'tags':
self.completion_values[k]['v'] = db.all_tags()
elif k == 'formats':
self.completion_values[k]['v'] = db.all_formats()
else:
if k in ('publisher'):
ck = k + 's'
else:
ck = k
f = getattr(db, 'all_' + ck, None)
if f:
if k == 'authors':
self.completion_values[k]['v'] = [v[1].\
replace('|', ',') for v in f()]
else:
self.completion_values[k]['v'] = [v[1] for v in f()]
else:
self.completion_values[k]['v'] = []
if k in self.completion_values:
if k == 'authors':
mult = '&'
else:
mult = ',' if m['is_multiple'] == '|' else m['is_multiple']
self.completion_values[k]['m'] = mult
self.columns.sort(key=sort_key)
self.columns.insert(0, '')
l = QGridLayout()
self.setLayout(l)
l.setColumnStretch(2, 10)
l.setColumnMinimumWidth(5, 300)
h = QLabel(_('And'))
h.setToolTip('<p>' +
_('Set this box to indicate that the two conditions must both '
'be true to use the color. For example, you '
'can check if two tags are present, if the book has a tag '
'and a #read custom column is checked, or if a book has '
'some tag and has a particular format.'))
l.addWidget(h, 0, 0, 1, 1)
h = QLabel(_('Column'))
h.setAlignment(Qt.AlignCenter)
l.addWidget(h, 0, 1, 1, 1)
h = QLabel(_('is'))
h.setAlignment(Qt.AlignCenter)
l.addWidget(h, 0, 2, 1, 1)
h = QLabel(_('op'))
h.setToolTip('<p>' +
_('Use this box to tell what comparison operation to use. Some '
'comparisons cannot be used with certain options. For example, '
'if regular expressions are used, only equals and not equals '
'are valid.') + '</p>')
h.setAlignment(Qt.AlignCenter)
l.addWidget(h, 0, 3, 1, 1)
c = QLabel(_('empty'))
c.setToolTip('<p>' +
_('Check this box to check if the column is empty') + '</p>')
l.addWidget(c, 0, 4, 1, 1)
h = QLabel(_('Values'))
h.setAlignment(Qt.AlignCenter)
h.setToolTip('<p>' +
_('You can enter more than one value per box, separated by commas. '
'The comparison ignores letter case. Special note: authors are '
'separated by ampersands (&).<br>'
'A value can be a regular expression. Check the box to turn '
'them on. When using regular expressions, note that the wizard '
'puts anchors (^ and $) around the expression, so you '
'must ensure your expression matches from the beginning '
'to the end of the column/value you are checking.<br>'
'Regular expression examples:') + '<ul>' +
_('<li><code><b>.*</b></code> matches anything in the column.</li>'
'<li><code><b>A.*</b></code> matches anything beginning with A</li>'
'<li><code><b>.*mystery.*</b></code> matches anything containing '
'the word "mystery"</li>') + '</ul></p>')
l.addWidget(h , 0, 5, 1, 1)
c = QLabel(_('is RE'))
c.setToolTip('<p>' +
_('Check this box if the values box contains regular expressions') + '</p>')
l.addWidget(c, 0, 6, 1, 1)
c = QLabel(_('color'))
c.setAlignment(Qt.AlignCenter)
c.setToolTip('<p>' +
_('Use this color if the column matches the tests.') + '</p>')
l.addWidget(c, 0, 7, 1, 1)
self.andboxes = []
self.opboxes = []
self.tagboxes = []
self.colorboxes = []
self.reboxes = []
self.colboxes = []
self.emptyboxes = []
self.colors = [unicode(s) for s in list(QColor.colorNames())]
self.colors.insert(0, '')
def create_widget(klass, box, layout, row, col, items,
align=Qt.AlignCenter, rowspan=False):
w = klass(self)
if box is not None:
box.append(w)
if rowspan:
layout.addWidget(w, row, col, 2, 1, alignment=Qt.Alignment(align))
else:
layout.addWidget(w, row, col, 1, 1, alignment=Qt.Alignment(align))
if items:
w.addItems(items)
return w
maxlines = 10
for i in range(1, maxlines+1):
w = create_widget(QCheckBox, self.andboxes, l, i, 0, None, rowspan=True)
w.stateChanged.connect(partial(self.and_box_changed, line=i-1))
if i == maxlines:
# last box is invisible
w.setVisible(False)
w = create_widget(QComboBox, self.colboxes, l, i, 1, self.columns)
w.currentIndexChanged[str].connect(partial(self.column_changed, line=i-1))
w = QLabel(self)
w.setText(_('is'))
l.addWidget(w, i, 2, 1, 1)
w = create_widget(QComboBox, self.opboxes, l, i, 3, None)
w.setMaximumWidth(40)
w = create_widget(QCheckBox, self.emptyboxes, l, i, 4, None)
w.stateChanged.connect(partial(self.empty_box_changed, line=i-1))
create_widget(MultiCompleteLineEdit, self.tagboxes, l, i, 5, None, align=0)
w = create_widget(QCheckBox, self.reboxes, l, i, 6, None)
w.stateChanged.connect(partial(self.re_box_changed, line=i-1))
create_widget(QComboBox, self.colorboxes, l, i, 7, self.colors)
w = create_widget(QLabel, None, l, maxlines+1, 5, None)
w.setText(_('If none of the tests match, set the color to'))
self.elsebox = create_widget(QComboBox, None, l, maxlines+1, 7, self.colors)
self.elsebox.setToolTip('<p>' +
_('If this box contains a color, it will be used if none '
'of the above rules match.') + '</p>')
if txt:
lines = txt.split('\n')[3:]
i = 0
for line in lines:
if line.startswith('#'):
vals = line[1:].split(':|:')
if len(vals) == 1 and line.startswith('#else:'):
try:
self.elsebox.setCurrentIndex(self.elsebox.findText(line[6:]))
except:
pass
continue
if len(vals) == 2:
t, c = vals
f = 'tags'
a = re = e = 0
op = '='
else:
t,c,f,re,a,op,e = vals
try:
self.colboxes[i].setCurrentIndex(self.colboxes[i].findText(f))
self.colorboxes[i].setCurrentIndex(
self.colorboxes[i].findText(c))
self.tagboxes[i].setText(t)
self.reboxes[i].setChecked(re == '2')
self.emptyboxes[i].setChecked(e == '2')
self.andboxes[i].setChecked(a == '2')
self.opboxes[i].setCurrentIndex(self.opboxes[i].findText(op))
i += 1
except:
import traceback
traceback.print_exc()
pass
w = QLabel(_('Preview'))
l.addWidget(w, 99, 1, 1, 1)
w = self.test_box = QLineEdit(self)
w.setReadOnly(True)
l.addWidget(w, 99, 2, 1, 5)
w = QPushButton(_('Test'))
w.setToolTip('<p>' +
_('Press this button to see what color this template will '
'produce for the book that was selected when you '
'entered the preferences dialog.'))
l.addWidget(w, 99, 7, 1, 1)
w.clicked.connect(self.preview)
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, parent=self)
l.addWidget(bb, 100, 5, 1, 3)
bb.accepted.connect(self.accepted)
bb.rejected.connect(self.reject)
self.template = ''
def preview(self):
if not self.generate_program():
return
t = composite_formatter.safe_format(self.template, self.mi,
_('EXCEPTION'), self.mi)
self.test_box.setText(t)
def generate_program(self):
res = ("program:\n#tag wizard -- do not directly edit\n"
" first_non_empty(\n")
lines = []
was_and = had_line = False
line = 0
for tb, cb, fb, reb, ab, ob, eb in zip(
self.tagboxes, self.colorboxes, self.colboxes,
self.reboxes, self.andboxes, self.opboxes, self.emptyboxes):
f = unicode(fb.currentText())
if not f:
continue
m = self.completion_values[f]['m']
dt = self.completion_values[f]['dt']
c = unicode(cb.currentText()).strip()
re = reb.checkState()
a = ab.checkState()
op = unicode(ob.currentText())
e = eb.checkState()
line += 1
if m:
tags = [t.strip() for t in unicode(tb.text()).split(m) if t.strip()]
if re == 2:
tags = '$|^'.join(tags)
else:
tags = m.join(tags)
if m == '&':
tags = tags.replace(',', '|')
else:
tags = unicode(tb.text()).strip()
if (tags or f) and not ((tags or e) and f and (a == 2 or c)):
error_dialog(self, _('Invalid line'),
_('Line number {0} is not valid').format(line),
show=True, show_copy_button=False)
return False
if not was_and:
if had_line:
lines[-1] += ','
had_line = True
lines.append(" test(and(")
else:
lines[-1] += ','
key = dt + ('.mult' if m else '') + ('.empty' if e else '') + ('.re' if re else '')
tval = '1' if op == '=' else ''
fval = '' if op == '=' else '1'
template, is_relational = self.templates[key]
if is_relational:
ltv, eqv, gtv = self.relational_truth_vals[op]
else:
ltv, eqv, gtv = (None, None, None)
lines.append(template.format(v=tags, f=f, tv=tval, fv=fval, mult=m,
ltv=ltv, eqv=eqv, gtv=gtv))
if a == 2:
was_and = True
else:
was_and = False
lines.append(" ), '{0}', '')".format(c))
res += '\n'.join(lines)
else_txt = unicode(self.elsebox.currentText())
if else_txt:
res += ",\n '" + else_txt + "'"
res += ')\n'
self.template = res
res = ''
for tb, cb, fb, reb, ab, ob, eb in zip(
self.tagboxes, self.colorboxes, self.colboxes,
self.reboxes, self.andboxes, self.opboxes, self.emptyboxes):
t = unicode(tb.text()).strip()
if t.endswith(','):
t = t[:-1]
c = unicode(cb.currentText()).strip()
f = unicode(fb.currentText())
re = unicode(reb.checkState())
a = unicode(ab.checkState())
op = unicode(ob.currentText())
e = unicode(eb.checkState())
if f and (t or e) and (a == '2' or c):
res += '#' + t + ':|:' + c + ':|:' + f + ':|:' + re + ':|:' + \
a + ':|:' + op + ':|:' + e + '\n'
res += '#else:' + else_txt + '\n'
self.template += res
return True
def column_changed(self, s, line=None):
k = unicode(s)
valbox = self.tagboxes[line]
if k in self.completion_values:
valbox.update_items_cache(self.completion_values[k]['v'])
if self.completion_values[k]['m']:
valbox.set_separator(', ')
else:
valbox.set_separator(None)
dt = self.completion_values[k]['dt']
if dt in ('int', 'float', 'rating', 'bool'):
self.reboxes[line].setChecked(0)
self.reboxes[line].setEnabled(False)
else:
self.reboxes[line].setEnabled(True)
self.fill_in_opbox(line)
else:
valbox.update_items_cache([])
valbox.set_separator(None)
def fill_in_opbox(self, line):
opbox = self.opboxes[line]
opbox.clear()
k = unicode(self.colboxes[line].currentText())
if not k:
return
if k in self.completion_values:
rebox = self.reboxes[line]
ebox = self.emptyboxes[line]
idx = opbox.currentIndex()
if self.completion_values[k]['m'] or \
rebox.checkState() == 2 or ebox.checkState() == 2:
opbox.addItems(self.relationals[0:2])
idx = idx if idx < 2 else 0
else:
opbox.addItems(self.relationals)
opbox.setCurrentIndex(max(idx, 0))
def re_box_changed(self, state, line=None):
self.fill_in_opbox(line)
def empty_box_changed(self, state, line=None):
if state == 2:
self.tagboxes[line].setText('')
self.tagboxes[line].setEnabled(False)
self.reboxes[line].setChecked(0)
self.reboxes[line].setEnabled(False)
else:
self.reboxes[line].setEnabled(True)
self.tagboxes[line].setEnabled(True)
self.fill_in_opbox(line)
def and_box_changed(self, state, line=None):
if state == 2:
self.colorboxes[line].setCurrentIndex(0)
self.colorboxes[line].setEnabled(False)
else:
self.colorboxes[line].setEnabled(True)
def accepted(self):
if self.generate_program():
self.accept()
else:
self.template = ''
self.setText(t.rule[1])

View File

@ -426,7 +426,7 @@ class CcTemplateDelegate(QStyledItemDelegate): # {{{
editor.textbox.setTabStopWidth(20)
d = editor.exec_()
if d:
m.setData(index, QVariant(editor.textbox.toPlainText()), Qt.EditRole)
m.setData(index, QVariant(editor.rule[1]), Qt.EditRole)
return None
def setModelData(self, editor, model, index):

View File

@ -408,9 +408,9 @@ class RulesModel(QAbstractListModel): # {{{
self.reset()
def rule_to_html(self, col, rule):
if isinstance(rule, basestring):
if not isinstance(rule, Rule):
return _('''
<p>Advanced Rule for column: %s
<p>Advanced Rule for column <b>%s</b>:
<pre>%s</pre>
''')%(col, rule)
conditions = [self.condition_to_html(c) for c in rule.conditions]
@ -483,25 +483,28 @@ class EditRules(QWidget): # {{{
b.clicked.connect(self.add_advanced)
l.addWidget(b, 3, 0, 1, 2)
def initialize(self, fm, prefs):
def initialize(self, fm, prefs, mi):
self.model = RulesModel(prefs, fm)
self.rules_view.setModel(self.model)
self.fm = fm
self.mi = mi
def add_rule(self):
d = RuleEditor(self.model.fm)
d.add_blank_condition()
if d.exec_() == d.Accepted:
col, r = d.rule
if r is not None and col:
def _add_rule(self, dlg):
if dlg.exec_() == dlg.Accepted:
col, r = dlg.rule
if r and col:
idx = self.model.add_rule(col, r)
self.rules_view.scrollTo(idx)
self.changed.emit()
def add_rule(self):
d = RuleEditor(self.model.fm)
d.add_blank_condition()
self._add_rule(d)
def add_advanced(self):
td = TemplateDialog(self, '', None)
if td.exec_() == td.Accepted:
self.changed.emit()
pass # TODO
td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, color_field='')
self._add_rule(td)
def edit_rule(self, index):
try:
@ -511,17 +514,14 @@ class EditRules(QWidget): # {{{
if isinstance(rule, Rule):
d = RuleEditor(self.model.fm)
d.apply_rule(col, rule)
if d.exec_() == d.Accepted:
col, r = d.rule
if r is not None and col:
self.model.replace_rule(index, col, r)
self.rules_view.scrollTo(index)
self.changed.emit()
else:
td = TemplateDialog(self, rule, None)
if td.exec_() == td.Accepted:
d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, color_field=col)
if d.exec_() == d.Accepted:
col, r = d.rule
if r is not None and col:
self.model.replace_rule(index, col, r)
self.rules_view.scrollTo(index)
self.changed.emit()
pass # TODO
def get_selected_row(self, txt):
sm = self.rules_view.selectionModel()
@ -575,7 +575,7 @@ if __name__ == '__main__':
db = db()
if False:
if True:
d = RuleEditor(db.field_metadata)
d.add_blank_condition()
d.exec_()
@ -588,7 +588,7 @@ if __name__ == '__main__':
else:
d = EditRules()
d.resize(QSize(800, 600))
d.initialize(db.field_metadata, db.prefs)
d.initialize(db.field_metadata, db.prefs, None)
d.show()
app.exec_()
d.commit(db.prefs)

View File

@ -176,7 +176,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.update_font_display()
self.display_model.initialize()
db = self.gui.current_db
self.edit_rules.initialize(db.field_metadata, db.prefs)
try:
idx = self.gui.library_view.currentIndex().row()
mi = db.get_metadata(idx, index_is_id=False)
except:
mi=None
self.edit_rules.initialize(db.field_metadata, db.prefs, mi)
def restore_defaults(self):
ConfigWidgetBase.restore_defaults(self)

View File

@ -167,7 +167,10 @@ def conditionable_columns(fm):
dt = m['datatype']
if m.get('name', False) and dt in ('bool', 'int', 'float', 'rating', 'series',
'comments', 'text', 'enumeration', 'datetime'):
yield key
if key == 'sort':
yield 'title_sort'
else:
yield key
def displayable_columns(fm):
for key in fm.displayable_field_keys():

View File

@ -223,7 +223,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if self.prefs.get('column_color_name_1', None) is not None:
from calibre.library.coloring import migrate_old_rule
old_rules = []
for i in range(1, 5):
for i in range(1, 6):
col = self.prefs.get('column_color_name_'+str(i), None)
templ = self.prefs.get('column_color_template_'+str(i), None)
if col and templ:
@ -1571,13 +1571,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if ids is not None:
count = self.conn.get('''SELECT COUNT(id)
FROM data
WHERE format="%s" AND
books_list_filter(book)'''%fmt,
WHERE format=? AND
books_list_filter(book)''', (fmt,),
all=False)
else:
count = self.conn.get('''SELECT COUNT(id)
FROM data
WHERE format="%s"'''%fmt,
WHERE format=?''', (fmt,),
all=False)
if count > 0:
categories['formats'].append(Tag(fmt, count=count, icon=icon,
@ -1599,13 +1599,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if ids is not None:
count = self.conn.get('''SELECT COUNT(book)
FROM identifiers
WHERE type="%s" AND
books_list_filter(book)'''%ident,
WHERE type=? AND
books_list_filter(book)''', (ident,),
all=False)
else:
count = self.conn.get('''SELECT COUNT(id)
FROM identifiers
WHERE type="%s"'''%ident,
WHERE type=?''', (ident,),
all=False)
if count > 0:
categories['identifiers'].append(Tag(ident, count=count, icon=icon,

View File

@ -374,6 +374,8 @@ class FieldMetadata(dict):
self.get = self._tb_cats.get
def __getitem__(self, key):
if key == 'title_sort':
return self._tb_cats['sort']
return self._tb_cats[key]
def __setitem__(self, key, val):
@ -390,6 +392,8 @@ class FieldMetadata(dict):
return self.has_key(key)
def has_key(self, key):
if key == 'title_sort':
return True
return key in self._tb_cats
def keys(self):

View File

@ -361,8 +361,7 @@ class BuiltinInList(BuiltinFormatterFunction):
class BuiltinStrInList(BuiltinFormatterFunction):
name = 'str_in_list'
arg_count = 5
category = 'List Lookup'
category = 'Iterating over values'
category = 'List lookup'
__doc__ = doc = _('str_in_list(val, separator, string, found_val, not_found_val) -- '
'treat val as a list of items separated by separator, '
'comparing the string against each value in the list. If the '
@ -380,6 +379,32 @@ class BuiltinStrInList(BuiltinFormatterFunction):
return fv
return nfv
class BuiltinIdentifierInList(BuiltinFormatterFunction):
name = 'identifier_in_list'
arg_count = 4
category = 'List lookup'
__doc__ = doc = _('identifier_in_list(val, id, found_val, not_found_val) -- '
'treat val as a list of identifiers separated by commas, '
'comparing the string against each value in the list. An identifier '
'has the format "identifier:value". The id parameter should be '
'either "id" or "id:regexp". The first case matches if there is any '
'identifier with that id. The second case matches if the regexp '
'matches the identifier\'s value. If there is a match, '
'return found_val, otherwise return not_found_val.')
def evaluate(self, formatter, kwargs, mi, locals, val, ident, fv, nfv):
l = [v.strip() for v in val.split(',') if v.strip()]
(id, _, regexp) = ident.partition(':')
if not id:
return nfv
id += ':'
if l:
for v in l:
if v.startswith(id):
if not regexp or re.search(regexp, v[len(id):], flags=re.I):
return fv
return nfv
class BuiltinRe(BuiltinFormatterFunction):
name = 're'
arg_count = 3
@ -748,6 +773,7 @@ builtin_eval = BuiltinEval()
builtin_first_non_empty = BuiltinFirstNonEmpty()
builtin_field = BuiltinField()
builtin_format_date = BuiltinFormatDate()
builtin_identifier_in_list = BuiltinIdentifierInList()
builtin_ifempty = BuiltinIfempty()
builtin_in_list = BuiltinInList()
builtin_list_item = BuiltinListitem()