mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Fix quotes in identifiers causing Tag Browser to be blank. Fixes #791044 (info in tag browser disappeared)
This commit is contained in:
commit
a1f4b9ee14
@ -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:
|
||||
|
@ -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)
|
@ -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 &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>&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 &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>
|
||||
|
@ -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])
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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():
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user