From 26d90debf09cd78d5d75ef18e3d001a523763629 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 2 Jun 2011 07:18:25 +0100 Subject: [PATCH 1/2] Fix exception in get_categories caused by quotes in identifier names --- src/calibre/library/database2.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index df465c919e..050ef5ea42 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1556,13 +1556,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, @@ -1584,13 +1584,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, From 2b44c67af6fb9ebbef00ee0229903c22fe2f1b02 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 2 Jun 2011 11:52:06 +0100 Subject: [PATCH 2/2] Changes for new coloring system --- src/calibre/ebooks/metadata/book/base.py | 5 +- src/calibre/gui2/dialogs/template_dialog.py | 50 +- src/calibre/gui2/dialogs/template_dialog.ui | 36 +- .../gui2/dialogs/template_line_editor.py | 502 +----------------- src/calibre/gui2/library/delegates.py | 2 +- src/calibre/gui2/preferences/coloring.py | 46 +- src/calibre/gui2/preferences/look_feel.py | 7 +- src/calibre/library/coloring.py | 5 +- src/calibre/library/database2.py | 2 +- src/calibre/library/field_metadata.py | 4 + src/calibre/utils/formatter_functions.py | 30 +- 11 files changed, 141 insertions(+), 548 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 2c96b8d3d3..378d4ab5f0 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -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: diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index 083dacbf00..852bbcc221 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -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) \ No newline at end of file diff --git a/src/calibre/gui2/dialogs/template_dialog.ui b/src/calibre/gui2/dialogs/template_dialog.ui index d36cbbd3d4..13586e7049 100644 --- a/src/calibre/gui2/dialogs/template_dialog.ui +++ b/src/calibre/gui2/dialogs/template_dialog.ui @@ -20,12 +20,30 @@ Edit Comments + + + + + + Set the color of the column: + + + colored_field + + + + + + + + + - + Template value: @@ -38,14 +56,14 @@ - + true - + Qt::Horizontal @@ -55,7 +73,7 @@ - + Function &name: @@ -65,10 +83,10 @@ - + - + &Documentation: @@ -81,7 +99,7 @@ - + Python &code: @@ -94,7 +112,7 @@ - + @@ -104,7 +122,7 @@ - + diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py index af70a16d31..02293e4df7 100644 --- a/src/calibre/gui2/dialogs/template_line_editor.py +++ b/src/calibre/gui2/dialogs/template_line_editor.py @@ -5,17 +5,10 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __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('

' + - _('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('

' + - _('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.') + '

') - h.setAlignment(Qt.AlignCenter) - l.addWidget(h, 0, 3, 1, 1) - - c = QLabel(_('empty')) - c.setToolTip('

' + - _('Check this box to check if the column is empty') + '

') - l.addWidget(c, 0, 4, 1, 1) - - h = QLabel(_('Values')) - h.setAlignment(Qt.AlignCenter) - h.setToolTip('

' + - _('You can enter more than one value per box, separated by commas. ' - 'The comparison ignores letter case. Special note: authors are ' - 'separated by ampersands (&).
' - '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.
' - 'Regular expression examples:') + '

    ' + - _('
  • .* matches anything in the column.
  • ' - '
  • A.* matches anything beginning with A
  • ' - '
  • .*mystery.* matches anything containing ' - 'the word "mystery"
  • ') + '

') - l.addWidget(h , 0, 5, 1, 1) - - c = QLabel(_('is RE')) - c.setToolTip('

' + - _('Check this box if the values box contains regular expressions') + '

') - l.addWidget(c, 0, 6, 1, 1) - - c = QLabel(_('color')) - c.setAlignment(Qt.AlignCenter) - c.setToolTip('

' + - _('Use this color if the column matches the tests.') + '

') - 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('

' + - _('If this box contains a color, it will be used if none ' - 'of the above rules match.') + '

') - - 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('

' + - _('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]) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 94c3deb403..b97cb3074a 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -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): diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index a8825ec582..aa4c930194 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -410,7 +410,7 @@ class RulesModel(QAbstractListModel): # {{{ def rule_to_html(self, col, rule): if isinstance(rule, basestring): return _(''' -

Advanced Rule for column: %s +

Advanced Rule for column %s:

%s
''')%(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) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index feaf3dd677..a2850679f1 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -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) diff --git a/src/calibre/library/coloring.py b/src/calibre/library/coloring.py index db13da9532..0a6f5f7960 100644 --- a/src/calibre/library/coloring.py +++ b/src/calibre/library/coloring.py @@ -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(): diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 14f8d05e8e..a1a012a822 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -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: diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index 979e98a819..c884542241 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -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): diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 78ed4fa306..1a8867b44e 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -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()