From d1e336c3dd25a7b93a8e91e274a24c56ccb18fb1 Mon Sep 17 00:00:00 2001
From: GRiker '+
_('%s has been updated to version %s. '
'See the new features.')%(__appname__, version))
+ '">new features. Only update if one of the '
+ 'new features or bug fixes is important to you.')%(__appname__, version))
self.label.setOpenExternalLinks(True)
self.label.setWordWrap(True)
self.setWindowTitle(_('Update available!'))
From 456b6b423eab32be26a8aba637d5ee57fa16ae33 Mon Sep 17 00:00:00 2001
From: Kovid Goyal ' +
+ _('You can enter more than one tag per box, separated by commas. '
+ 'The comparison ignores letter case. ' +
+ _('At least one of the two color boxes must have a value. Leave '
+ 'one color box empty if you want the template to use the next '
+ 'line in this wizard. If both boxes are filled in, the rest of '
+ 'the lines in this wizard will be ignored.') + ' ' +
+ _('This box is usually filled in only on the last test. If it is '
+ 'filled in before the last test, then the color for tag found box '
+ 'must be empty or all the rest of the tests will be ignored.') + ' ' +
_('You can enter more than one tag per box, separated by commas. '
'The comparison ignores letter case.
'
+ 'A tag value can be a regular expression. '
+ '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 tag.
'
+ 'Regular expression examples:') + '' +
+ _('
.*
matches any tag. No empty tags are '
+ 'checked, so you don\'t need to worry about empty stringsA.*
matches any tag beginning with A.*mystery.*
matches any tag containing '
+ 'the word "mystery"
'
- 'A tag value can be a regular expression. '
- 'When using regular expressions, note that the wizard '
+ 'A tag 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 tag.
'
@@ -85,22 +85,29 @@ class TagWizard(QDialog):
'.*mystery.*
matches any tag containing '
'the word "mystery"
' + + _('Check this box if the tag box contains regular expressions') + '
') + l.addWidget(c, 0, 1, 1, 1) + c = QLabel(_('Color if tag found')) c.setToolTip('' + _('At least one of the two color boxes must have a value. Leave ' 'one color box empty if you want the template to use the next ' 'line in this wizard. If both boxes are filled in, the rest of ' 'the lines in this wizard will be ignored.') + '
') - l.addWidget(c, 0, 1, 1, 1) + l.addWidget(c, 0, 2, 1, 1) c = QLabel(_('Color if tag not found')) c.setToolTip('' + _('This box is usually filled in only on the last test. If it is ' 'filled in before the last test, then the color for tag found box ' 'must be empty or all the rest of the tests will be ignored.') + '
') - l.addWidget(c, 0, 2, 1, 1) + l.addWidget(c, 0, 3, 1, 1) self.tagboxes = [] self.colorboxes = [] self.nfcolorboxes = [] + self.reboxes = [] self.colors = [unicode(s) for s in list(QColor.colorNames())] self.colors.insert(0, '') for i in range(0, 10): @@ -109,14 +116,20 @@ class TagWizard(QDialog): tb.update_items_cache(self.tags) self.tagboxes.append(tb) l.addWidget(tb, i+1, 0, 1, 1) - cb = QComboBox(self) - cb.addItems(self.colors) - self.colorboxes.append(cb) - l.addWidget(cb, i+1, 1, 1, 1) - cb = QComboBox(self) - cb.addItems(self.colors) - self.nfcolorboxes.append(cb) - l.addWidget(cb, i+1, 2, 1, 1) + + w = QCheckBox(self) + self.reboxes.append(w) + l.addWidget(w, i+1, 1, 1, 1) + + w = QComboBox(self) + w.addItems(self.colors) + self.colorboxes.append(w) + l.addWidget(w, i+1, 2, 1, 1) + + w = QComboBox(self) + w.addItems(self.colors) + self.nfcolorboxes.append(w) + l.addWidget(w, i+1, 3, 1, 1) if txt: lines = txt.split('\n')[3:] @@ -127,18 +140,20 @@ class TagWizard(QDialog): if len(vals) == 2: t, c = vals nc = '' + re = False else: - t,c,nc = vals + t,c,nc,re = vals try: self.colorboxes[i].setCurrentIndex(self.colorboxes[i].findText(c)) self.nfcolorboxes[i].setCurrentIndex(self.nfcolorboxes[i].findText(nc)) self.tagboxes[i].setText(t) + self.reboxes[i].setChecked(re == '2') except: pass i += 1 bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, parent=self) - l.addWidget(bb, 100, 1, 1, 1) + l.addWidget(bb, 100, 2, 1, 2) bb.accepted.connect(self.accepted) bb.rejected.connect(self.reject) self.template = '' @@ -147,11 +162,16 @@ class TagWizard(QDialog): res = ("program:\n#tag wizard -- do not directly edit\n" " t = field('tags');\n first_non_empty(\n") lines = [] - for tb, cb, nfcb in zip(self.tagboxes, self.colorboxes, self.nfcolorboxes): + for tb, cb, nfcb, reb in zip(self.tagboxes, self.colorboxes, + self.nfcolorboxes, self.reboxes): tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()] - tags = '$|^'.join(tags) c = unicode(cb.currentText()).strip() nfc = unicode(nfcb.currentText()).strip() + re = reb.checkState() + if re == 2: + tags = '$|^'.join(tags) + else: + tags = ','.join(tags) if not tags or not (c or nfc): continue if c not in self.colors: @@ -164,18 +184,25 @@ class TagWizard(QDialog): _('The color {0} is not valid').format(nfc), show=True, show_copy_button=False) return False - lines.append(" in_list(t, ',', '^{0}$', '{1}', '{2}')".format(tags, c, nfc)) + if re == 2: + lines.append(" in_list(t, ',', '^{0}$', '{1}', '{2}')".\ + format(tags, c, nfc)) + else: + lines.append(" str_in_list(t, ',', '{0}', '{1}', '{2}')".\ + format(tags, c, nfc)) res += ',\n'.join(lines) res += ')\n' self.template = res res = '' - for tb, cb, nfcb in zip(self.tagboxes, self.colorboxes, self.nfcolorboxes): + for tb, cb, nfcb, reb in zip(self.tagboxes, self.colorboxes, + self.nfcolorboxes, self.reboxes): t = unicode(tb.text()).strip() if t.endswith(','): t = t[:-1] c = unicode(cb.currentText()).strip() nfc = unicode(nfcb.currentText()).strip() + re = unicode(reb.checkState()) if t and c: - res += '#' + t + ':|:' + c + ':|:' + nfc + '\n' + res += '#' + t + ':|:' + c + ':|:' + nfc + ':|:' + re + '\n' self.template += res self.accept() diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index fcdd56fd5f..37e4588b9b 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -209,8 +209,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): mi=None try: idx = gui.library_view.currentIndex().row() - if idx: - mi = db.get_metadata(idx, index_is_id=False) + mi = db.get_metadata(idx, index_is_id=False) except: pass diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 76faf04941..7d5dbe3e0e 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -8,7 +8,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal'+ + self.label = QLabel(('
'+ _('%s has been updated to version %s. ' 'See the new features. Only update if one of the ' - 'new features or bug fixes is important to you.')%(__appname__, version)) + '">new features.') + '
'+_('Update only if one of the '
+ 'new features or bug fixes is important to you. '
+ 'If the current version works well for you, do not update.'))%(__appname__, version))
self.label.setOpenExternalLinks(True)
self.label.setWordWrap(True)
self.setWindowTitle(_('Update available!'))
From 19261f15eede6fdf17ec5769a31f8e28c69486bf Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 29 May 2011 20:12:31 +0100
Subject: [PATCH 20/84] Add field box to color wizard
---
.../gui2/dialogs/template_line_editor.py | 169 ++++++++++++------
src/calibre/gui2/preferences/look_feel.py | 3 +-
src/calibre/utils/formatter_functions.py | 16 +-
3 files changed, 127 insertions(+), 61 deletions(-)
diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py
index 3d199b156c..bea2c4e316 100644
--- a/src/calibre/gui2/dialogs/template_line_editor.py
+++ b/src/calibre/gui2/dialogs/template_line_editor.py
@@ -5,12 +5,16 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal ' +
- _('You can enter more than one tag per box, separated by commas. '
+ _('You can enter more than one value per box, separated by commas. '
'The comparison ignores letter case.
'
- 'A tag value can be a regular expression. Check the box to turn '
+ '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 tag.
'
+ 'to the end of the column you are checking.
'
'Regular expression examples:') + '' +
- _('
.*
matches any tag. No empty tags are '
- 'checked, so you don\'t need to worry about empty stringsA.*
matches any tag beginning with A.*mystery.*
matches any tag containing '
+ _('.*
matches anything in the column. No '
+ 'empty values are checked, so you don\'t need to worry about '
+ 'empty stringsA.*
matches anything beginning with A.*mystery.*
matches anything containing '
'the word "mystery"
' + - _('Check this box if the tag box contains regular expressions') + '
') - l.addWidget(c, 0, 1, 1, 1) + _('Check this box if the values box contains regular expressions') + '') + l.addWidget(c, 0, 2, 1, 1) - c = QLabel(_('Color if tag found')) + c = QLabel(_('Color if value found')) c.setToolTip('' + _('At least one of the two color boxes must have a value. Leave ' 'one color box empty if you want the template to use the next ' 'line in this wizard. If both boxes are filled in, the rest of ' 'the lines in this wizard will be ignored.') + '
') - l.addWidget(c, 0, 2, 1, 1) - c = QLabel(_('Color if tag not found')) + l.addWidget(c, 0, 3, 1, 1) + c = QLabel(_('Color if value not found')) c.setToolTip('' + _('This box is usually filled in only on the last test. If it is ' - 'filled in before the last test, then the color for tag found box ' + 'filled in before the last test, then the color for value found box ' 'must be empty or all the rest of the tests will be ignored.') + '
') - l.addWidget(c, 0, 3, 1, 1) + l.addWidget(c, 0, 4, 1, 1) self.tagboxes = [] self.colorboxes = [] self.nfcolorboxes = [] self.reboxes = [] + self.colboxes = [] self.colors = [unicode(s) for s in list(QColor.colorNames())] self.colors.insert(0, '') for i in range(0, 10): + w = QComboBox() + w.addItems(self.columns) + l.addWidget(w, i+1, 0, 1, 1) + self.colboxes.append(w) + tb = MultiCompleteLineEdit(self) tb.set_separator(', ') - tb.update_items_cache(self.tags) self.tagboxes.append(tb) - l.addWidget(tb, i+1, 0, 1, 1) + l.addWidget(tb, i+1, 1, 1, 1) + w.currentIndexChanged[str].connect(partial(self.column_changed, valbox=tb)) w = QCheckBox(self) self.reboxes.append(w) - l.addWidget(w, i+1, 1, 1, 1) - - w = QComboBox(self) - w.addItems(self.colors) - self.colorboxes.append(w) l.addWidget(w, i+1, 2, 1, 1) w = QComboBox(self) w.addItems(self.colors) - self.nfcolorboxes.append(w) + self.colorboxes.append(w) l.addWidget(w, i+1, 3, 1, 1) + w = QComboBox(self) + w.addItems(self.colors) + self.nfcolorboxes.append(w) + l.addWidget(w, i+1, 4, 1, 1) + if txt: lines = txt.split('\n')[3:] i = 0 @@ -141,37 +175,59 @@ class TagWizard(QDialog): t, c = vals nc = '' re = False + f = 'tags' else: - t,c,nc,re = vals + t,c,f,nc,re = vals try: self.colorboxes[i].setCurrentIndex(self.colorboxes[i].findText(c)) self.nfcolorboxes[i].setCurrentIndex(self.nfcolorboxes[i].findText(nc)) self.tagboxes[i].setText(t) self.reboxes[i].setChecked(re == '2') + self.colboxes[i].setCurrentIndex(self.colboxes[i].findText(f)) except: pass i += 1 bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, parent=self) - l.addWidget(bb, 100, 2, 1, 2) + l.addWidget(bb, 100, 3, 1, 2) bb.accepted.connect(self.accepted) bb.rejected.connect(self.reject) self.template = '' + def column_changed(self, s, valbox=None): + k = unicode(s) + 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) + else: + valbox.update_items_cache([]) + valbox.set_separator(None) + def accepted(self): res = ("program:\n#tag wizard -- do not directly edit\n" - " t = field('tags');\n first_non_empty(\n") + " first_non_empty(\n") lines = [] - for tb, cb, nfcb, reb in zip(self.tagboxes, self.colorboxes, - self.nfcolorboxes, self.reboxes): - tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()] + for tb, cb, fb, nfcb, reb in zip(self.tagboxes, self.colorboxes, + self.colboxes, self.nfcolorboxes, self.reboxes): + f = unicode(fb.currentText()) + if not f: + continue + m = self.completion_values[f]['m'] c = unicode(cb.currentText()).strip() nfc = unicode(nfcb.currentText()).strip() re = reb.checkState() - if re == 2: - tags = '$|^'.join(tags) + if m: + tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()] + if re == 2: + tags = '$|^'.join(tags) + else: + tags = ','.join(tags) else: - tags = ','.join(tags) + tags = unicode(tb.text()).strip() + if not tags or not (c or nfc): continue if c not in self.colors: @@ -185,24 +241,33 @@ class TagWizard(QDialog): show=True, show_copy_button=False) return False if re == 2: - lines.append(" in_list(t, ',', '^{0}$', '{1}', '{2}')".\ - format(tags, c, nfc)) + if m: + lines.append(" in_list(field('{3}'), ',', '^{0}$', '{1}', '{2}')".\ + format(tags, c, nfc, f)) + else: + lines.append(" contains(field('{3}'), '{0}', '{1}', '{2}')".\ + format(tags, c, nfc, f)) else: - lines.append(" str_in_list(t, ',', '{0}', '{1}', '{2}')".\ - format(tags, c, nfc)) + if m: + lines.append(" str_in_list(field('{3}'), ',', '{0}', '{1}', '{2}')".\ + format(tags, c, nfc, f)) + else: + lines.append(" strcmp(field('{3}'), '{0}', '{2}', '{1}', '{2}')".\ + format(tags, c, nfc, f)) res += ',\n'.join(lines) res += ')\n' self.template = res res = '' - for tb, cb, nfcb, reb in zip(self.tagboxes, self.colorboxes, - self.nfcolorboxes, self.reboxes): + for tb, cb, fb, nfcb, reb in zip(self.tagboxes, self.colorboxes, + self.colboxes, self.nfcolorboxes, self.reboxes): t = unicode(tb.text()).strip() if t.endswith(','): t = t[:-1] c = unicode(cb.currentText()).strip() + f = unicode(fb.currentText()) nfc = unicode(nfcb.currentText()).strip() re = unicode(reb.checkState()) - if t and c: - res += '#' + t + ':|:' + c + ':|:' + nfc + ':|:' + re + '\n' + if f and t and c: + res += '#' + t + ':|:' + c + ':|:' + f +':|:' + nfc + ':|:' + re + '\n' self.template += res self.accept() diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 37e4588b9b..79db1aecf8 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -204,7 +204,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): choices.sort(key=sort_key) choices.insert(0, '') self.column_color_count = db.column_color_count+1 - tags = db.all_tags() mi=None try: @@ -217,7 +216,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('column_color_name_'+str(i), db.prefs, choices=choices) r('column_color_template_'+str(i), db.prefs) tpl = getattr(self, 'opt_column_color_template_'+str(i)) - tpl.set_tags(tags) + tpl.set_db(db) tpl.set_mi(mi) toolbutton = getattr(self, 'opt_column_color_wizard_'+str(i)) toolbutton.clicked.connect(tpl.tag_wizard) diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 32822e1d72..b66aec2cb9 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -331,9 +331,10 @@ class BuiltinInList(BuiltinFormatterFunction): def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv): l = [v.strip() for v in val.split(sep) if v.strip()] - for v in l: - if re.search(pat, v, flags=re.I): - return fv + if l: + for v in l: + if re.search(pat, v, flags=re.I): + return fv return nfv class BuiltinStrInList(BuiltinFormatterFunction): @@ -349,10 +350,11 @@ class BuiltinStrInList(BuiltinFormatterFunction): def evaluate(self, formatter, kwargs, mi, locals, val, sep, str, fv, nfv): l = [v.strip() for v in val.split(sep) if v.strip()] c = [v.strip() for v in str.split(sep) if v.strip()] - for v in l: - for t in c: - if strcmp(t, v) == 0: - return fv + if l: + for v in l: + for t in c: + if strcmp(t, v) == 0: + return fv return nfv class BuiltinRe(BuiltinFormatterFunction): From cc288ff4cac08b4a3047ef3782aba344aa3b81a5 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 29 May 2011 21:21:02 +0100 Subject: [PATCH 21/84] Add a preview field to the wizard. Vary the button according to whether the wizard created the template Don't show the template if wizard created --- .../gui2/dialogs/template_line_editor.py | 88 ++++++++++++++++--- src/calibre/gui2/preferences/look_feel.py | 9 +- 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py index bea2c4e316..2e4a6595fd 100644 --- a/src/calibre/gui2/dialogs/template_line_editor.py +++ b/src/calibre/gui2/dialogs/template_line_editor.py @@ -8,9 +8,10 @@ __docformat__ = 'restructuredtext en' from functools import partial from collections import defaultdict -from PyQt4.Qt import (QLineEdit, QDialog, QGridLayout, QLabel, QCheckBox, - QDialogButtonBox, QColor, QComboBox, QIcon) +from PyQt4.Qt import (QLineEdit, QDialog, QGridLayout, QLabel, QCheckBox, QIcon, + QDialogButtonBox, QColor, QComboBox, QPushButton) +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 @@ -26,6 +27,7 @@ class TemplateLineEditor(QLineEdit): QLineEdit.__init__(self, parent) self.tags = None self.mi = None + self.txt = None def set_mi(self, mi): self.mi = mi @@ -42,46 +44,82 @@ class TemplateLineEditor(QLineEdit): menu.exec_(event.globalPos()) def open_editor(self): - t = TemplateDialog(self, self.text(), self.mi) + if self.txt: + t = TemplateDialog(self, self.txt, self.mi) + else: + t = TemplateDialog(self, self.text(), self.mi) t.setWindowTitle(_('Edit template')) if t.exec_(): self.setText(t.textbox.toPlainText()) + self.txt = None + + def show_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 txt.startswith('program:\n#tag wizard'): + 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.text())) + 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): - def __init__(self, parent, db, txt): + 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 m['datatype'] in ('text', 'enumeration', 'series'): + if m['datatype'] in ('text', 'enumeration', 'series') and \ + m['is_category'] and k not in ('identifiers'): self.columns.append(k) if m['is_custom']: -# self.completion_values[k] = {} self.completion_values[k]['v'] = db.all_custom(m['label']) elif k == 'tags': -# self.completion_values[k] = {} self.completion_values[k]['v'] = db.all_tags() + elif k == 'formats': + self.completion_values[k]['v'] = db.all_formats() else: - f = getattr(db, 'all' + k, None) + if k in ('publisher'): + ck = k + 's' + else: + ck = k + f = getattr(db, 'all_' + ck, None) if f: - self.completion_values[k] = {} - self.completion_values[k]['v'] = [v[1] for v in 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()] + if k in self.completion_values: self.completion_values[k]['m'] = m['is_multiple'] @@ -188,12 +226,28 @@ class TagWizard(QDialog): pass i += 1 + w = QLabel(_('Preview')) + l.addWidget(w, 99, 0, 1, 1) + w = self.test_box = QLineEdit(self) + w.setReadOnly(True) + l.addWidget(w, 99, 1, 1, 1) + w = QPushButton(_('Test')) + l.addWidget(w, 99, 3, 1, 1) + w.clicked.connect(self.preview) + bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, parent=self) l.addWidget(bb, 100, 3, 1, 2) 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 column_changed(self, s, valbox=None): k = unicode(s) if k in self.completion_values: @@ -206,7 +260,7 @@ class TagWizard(QDialog): valbox.update_items_cache([]) valbox.set_separator(None) - def accepted(self): + def generate_program(self): res = ("program:\n#tag wizard -- do not directly edit\n" " first_non_empty(\n") lines = [] @@ -270,4 +324,10 @@ class TagWizard(QDialog): if f and t and c: res += '#' + t + ':|:' + c + ':|:' + f +':|:' + nfc + ':|:' + re + '\n' self.template += res - self.accept() + return True + + def accepted(self): + if self.generate_program(): + self.accept() + else: + self.template = '' diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 79db1aecf8..d292cada4b 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal' + - _('If you want to color a field based on tags, then click the ' - 'button next to an empty line to open the tags wizard. ' + _('If you want to color a field based on contents of columns, ' + 'then click the button next to an empty line to open the wizard. ' 'It will build a template for you. You can later edit that ' - 'template with the same wizard. If you edit it by hand, the ' - 'wizard might not work or might restore old values.') + + 'template with the same wizard.') + '
' +
_('The template must evaluate to one of the color names shown '
'below. You can use any legal template expression. '
From 1ae716a115664b46b51a02355fa11df6c3c5972b Mon Sep 17 00:00:00 2001
From: Kovid Goyal This wizard will help you choose an appropriate font size key for your needs. Just enter the base font size of the input document and then enter an input font size. The wizard will display what font size it will be mapped to, by the font rescaling algorithm. You can adjust the algorithm by adjusting the output base font size and font key below. When you find values suitable for you, click OK. By default, if the output base font size is zero and/or no font size key is specified, calibre will use the values from the current Output Profile. See the User Manual for a discussion of how font size rescaling works. See the User Manual for a discussion of how font size rescaling works. Search and replace uses regular expressions. See the regular expressions tutorial to get started with regular expressions. Also clicking the wizard buttons below will allow you to test your regular expression against the current input document."
+msgid " Search and replace uses regular expressions. See the regular expressions tutorial to get started with regular expressions. Also clicking the wizard buttons below will allow you to test your regular expression against the current input document."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:173
@@ -6286,7 +6292,7 @@ msgid "(A regular expression)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:89
-msgid " For example, to match all h2 tags that have class=\"chapter\", set tag to h2, attribute to class and value to chapter. Leaving attribute blank will match any attribute and leaving value blank will match any value. Setting tag to * will match any tag. To learn more advanced usage of XPath see the XPath Tutorial."
+msgid " For example, to match all h2 tags that have class=\"chapter\", set tag to h2, attribute to class and value to chapter. Leaving attribute blank will match any attribute and leaving value blank will match any value. Setting tag to * will match any tag. To learn more advanced usage of XPath see the XPath Tutorial."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:128
@@ -6312,8 +6318,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:128
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:148
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:230
-#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:263
-#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:279
+#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:283
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1139
msgid "Undefined"
msgstr ""
@@ -6569,7 +6575,7 @@ msgstr ""
#:
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:148
-#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:421
+#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:437
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:273
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:61
msgid "Invalid template"
@@ -6577,7 +6583,7 @@ msgstr ""
#:
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:149
-#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:422
+#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:438
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:274
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:62
msgid "The template %s is invalid:"
@@ -6937,7 +6943,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:97
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:211
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:73
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:189
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:181
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:199
msgid "&Title:"
msgstr ""
@@ -6951,12 +6958,12 @@ msgid "&Profile:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:24
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:218
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:222
msgid "&OK"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:25
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:219
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:223
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:65
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:233
msgid "&Cancel"
@@ -7011,7 +7018,7 @@ msgstr ""
#:
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:68
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1019
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1025
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:32
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:321
@@ -8019,91 +8026,105 @@ msgid "Negate"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:198
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:196
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:176
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:206
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:168
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:186
msgid "Advanced Search"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:199
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:197
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:207
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:187
msgid "&What kind of match to use:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:200
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:198
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:178
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:170
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:188
msgid "Contains: the word or phrase matches anywhere in the metadata field"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:201
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:199
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:179
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:209
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:171
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:189
msgid "Equals: the word or phrase must match the entire metadata field"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:202
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:200
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:210
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:172
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:190
msgid "Regular expression: the expression must match anywhere in the metadata field"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:203
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:201
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:181
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:211
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:191
msgid "Find entries that have..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:204
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:202
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:182
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:174
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:192
msgid "&All these words:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:205
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:203
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:183
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:213
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:193
msgid "This exact &phrase:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:206
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:204
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:184
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:214
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:176
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:194
msgid "&One or more of these words:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:207
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:205
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:185
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:215
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:195
msgid "But dont show entries that have..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:208
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:206
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:186
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:216
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:178
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:196
msgid "Any of these &unwanted words:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:209
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:207
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:187
-msgid "See the User Manual for more help"
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:217
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:197
+msgid "See the User Manual for more help"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:210
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:208
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:188
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:218
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:198
msgid "A&dvanced Search"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:212
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:210
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:190
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:220
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:182
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:200
msgid "Enter the title."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:213
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:191
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:183
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:201
msgid "&Author:"
msgstr ""
@@ -8126,14 +8147,16 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:219
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:101
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:213
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:193
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:223
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:184
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:203
msgid "&Clear"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:220
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:214
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:194
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:224
+#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:185
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:204
msgid "Search only in specific fields:"
msgstr ""
@@ -8341,6 +8364,10 @@ msgstr ""
msgid "Ctrl+S"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:249
+msgid "EXCEPTION: "
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog_ui.py:71
msgid "Function &name:"
msgstr ""
@@ -8355,53 +8382,90 @@ msgid "Python &code:"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:32
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:36
msgid "Open Template Editor"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:35
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:39
msgid "Open Tag Wizard"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:41
-#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:408
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:45
+#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:424
msgid "Edit template"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:48
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:52
msgid "Invalid text"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:49
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:53
msgid "The text in the box was not generated by this wizard"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:64
msgid "Tag Wizard"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:68
-msgid "Tags (more than one per box permitted)"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:72
+msgid "Tags (see the popup help for more information)"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:69
-msgid "Color"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:74
+msgid "You can enter more than one tag per box, separated by commas. The comparison ignores letter case. Set a regular expression pattern to use when trying to guess ebook metadata from filenames. A tutorial on using regular expressions is available. A tutorial on using regular expressions is available. Use the Test functionality below to test your regular expression on a few sample filenames (remember to include the file extension). The group names for the various metadata entries are documented in tooltips. Date format. Use 1-4 'd's for day, 1-4 'M's for month, and 2 or 4 'y's for year. For example:\n"
@@ -10215,68 +10289,86 @@ msgid ""
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:219
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:235
msgid "Use MMM yyyy for month + year, yyyy for year only"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:220
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:236
msgid "Default: dd MMM yyyy."
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:221
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:237
+msgid ""
+" The format specifier must begin with Default: Not formatted. For format language details see the python documentation"
+msgstr ""
+
+#:
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:241
msgid "Format for &dates"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:222
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:242
+msgid "Format for &numbers"
+msgstr ""
+
+#:
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:243
msgid "&Template"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:223
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:244
msgid "Field template. Uses the same syntax as save templates."
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:224
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:245
msgid "Similar to save templates. For example, {title} {isbn}"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:225
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:246
msgid "Default: (nothing)"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:226
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:247
msgid "&Sort/search column by"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:227
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:248
msgid "How this column should handled in the GUI when sorting and searching"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:228
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:249
msgid "If checked, this column will appear in the tags browser as a category"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:229
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:250
msgid "Show in tags browser"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:230
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:235
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:251
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:256
msgid "Values"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:231
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:252
msgid ""
"A comma-separated list of permitted values. The empty value is always\n"
"included, and is the default. For example, the list 'one,two,three' has\n"
@@ -10284,19 +10376,19 @@ msgid ""
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:255
msgid "The empty string is always the first value"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:257
msgid ""
"A list of color names to use when displaying an item. The\n"
"list must be empty or contain a color for each value."
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:238
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:259
msgid "Colors"
msgstr ""
@@ -10418,7 +10510,7 @@ msgid "Always"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:132
-msgid "Automatic"
+msgid "If there is enough room"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:133
@@ -10438,7 +10530,7 @@ msgid "Partitioned"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:163
-msgid "Here you can specify coloring rules for columns shown in the library view. Choose the column you wish to color, then supply a template that specifies the color to use based on the values in the column. There is a tutorial on using templates."
+msgid "Here you can specify coloring rules for columns shown in the library view. Choose the column you wish to color, then supply a template that specifies the color to use based on the values in the column. There is a tutorial on using templates."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:170
@@ -10920,7 +11012,7 @@ msgid "Search for plugin"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:230
-#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search.py:297
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search.py:321
msgid "No matches"
msgstr ""
@@ -11561,7 +11653,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:279
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:58
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:80
#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:76
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:134
#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:109
@@ -11646,6 +11738,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:83
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:138
#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:113
#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:351
@@ -11718,54 +11811,87 @@ msgid "Open store in external web browswer"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:209
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:219
msgid "&Name:"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:211
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:221
msgid "&Description:"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:222
msgid "&Headquarters:"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:216
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:226
msgid "Enabled:"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:217
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:227
msgid "DRM:"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:218
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:220
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:228
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:233
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:207
msgid "true"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:219
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:221
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:229
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:231
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:208
msgid "false"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:222
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:232
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:206
+msgid "Affiliate:"
+msgstr ""
+
+#:
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/adv_search_builder_ui.py:235
msgid "Nam&e/Description ..."
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:56
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:78
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:132
#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:108
msgid "Query:"
msgstr ""
+#:
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:81
+msgid "Enable"
+msgstr ""
+
+#:
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:82
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:136
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:111
+msgid "All"
+msgstr ""
+
+#:
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:84
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:137
+#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:112
+msgid "Invert"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:21
+msgid "Affiliate"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:21
msgid "Enabled"
msgstr ""
@@ -11779,33 +11905,44 @@ msgid "No DRM"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:108
-msgid " This store is currently diabled and cannot be used in other parts of calibre. This store is currently enabled and can be used in other parts of calibre. This store only distributes ebooks with DRM. This store distributes ebooks with DRM. It may have some titles without DRM, but you will need to check on a per title basis. This store is headquartered in %s. This is a good indication of what market the store caters to. However, this does not necessarily mean that the store is limited to that market only. This store distributes ebooks in the following formats: %s
A tag 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 tag.
Regular expression examples:"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:115
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_line_editor.py:82
+msgid ".*
matches any tag. No empty tags are checked, so you don't need to worry about empty stringsA.*
matches any tag beginning with A.*mystery.*
matches any tag containing the word \"mystery\"
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:158
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:159
msgid "Hide column %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:163
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:164
msgid "Sort on %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:164
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:165
msgid "Ascending"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:168
msgid "Descending"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:179
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:180
msgid "Change text alignment for %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:181
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:182
msgid "Left"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:181
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:182
msgid "Right"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:182
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:183
msgid "Center"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:201
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:202
msgid "Show column"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:213
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:214
msgid "Restore default layout"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:855
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:858
msgid "Dropping onto a device is not supported. First add the book to the calibre library."
msgstr ""
@@ -9995,7 +10059,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:41
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:66
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:153
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:156
msgid "Yes/No"
msgstr ""
@@ -10027,7 +10091,7 @@ msgstr ""
#:
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:65
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:152
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:155
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27
#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:21
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:124
@@ -10071,117 +10135,127 @@ msgid "Selected column is not a user-defined column"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:154
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:157
msgid "My Tags"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:155
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:158
msgid "My Series"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:156
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:159
msgid "My Rating"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:157
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:160
msgid "People"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:185
-msgid "No lookup name was provided"
-msgstr ""
-
-#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:189
-msgid "The lookup name must contain only lower case letters, digits and underscores, and start with a letter"
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:187
+msgid "Examples: The format {0:0>4d}
gives a 4-digit number with leading zeros. The format {0:d} days
prints the number then the word \"days\""
msgstr ""
#:
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:192
+msgid "Examples: The format {0:.1f}
gives a floating point number with 1 digit after the decimal point. The format Price: $ {0:,.2f}
prints \"Price $ \" then displays the number with 2 digits after the decimal point and thousands separated by commas."
+msgstr ""
+
+#:
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:201
+msgid "No lookup name was provided"
+msgstr ""
+
+#:
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:205
+msgid "The lookup name must contain only lower case letters, digits and underscores, and start with a letter"
+msgstr ""
+
+#:
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:208
msgid "Lookup names cannot end with _index, because these names are reserved for the index of a series column."
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:202
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:218
msgid "No column heading was provided"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:228
msgid "The lookup name %s is already used"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:224
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:240
msgid "The heading %s is already used"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:235
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:251
msgid "You must enter a template for composite columns"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:260
msgid "You must enter at least one value for enumeration columns"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:248
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:264
msgid "You cannot provide the empty value, as it is included by default"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:252
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:268
msgid "The value \"{0}\" is in the list more than once"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:276
msgid "The colors box must be empty or contain the same number of items as the value box"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:265
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:281
msgid "The color {0} is unknown"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:201
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:217
msgid "&Lookup name"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:202
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:218
msgid "Column &heading"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:203
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:219
msgid "Used for searching the column. Must contain only digits and lower case letters."
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:204
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:220
msgid "Column heading in the library view and category name in the tag browser"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:205
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:221
msgid "&Column type"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:206
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:222
msgid "What kind of information will be kept in the column."
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:207
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:223
msgid ""
"Show check marks in the GUI. Values of 'yes', 'checked', and 'true'\n"
"will show a green check. Values of 'no', 'unchecked', and 'false' will show a red X.\n"
@@ -10189,22 +10263,22 @@ msgid ""
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:210
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:226
msgid "Show checkmarks"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:211
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:227
msgid "Check this box if this column contains names, like the authors column."
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:228
msgid "Contains names"
msgstr ""
#:
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:213
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:229
msgid ""
"{0:
\n"
+"and end with }
You can have text before and after the format specifier.\n"
+" "
+msgstr ""
+
+#:
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:240
+msgid "
Migrating old database to ebook library in %s
' + + _('Set this box to indicate that the two conditions must both ' + 'be true to return the "color if value found". 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')) - l.addWidget(h, 0, 0, 1, 1) + h.setAlignment(Qt.AlignCenter) + l.addWidget(h, 0, 1, 1, 1) h = QLabel(_('Values (see the popup help for more information)')) h.setToolTip('
' +
_('You can enter more than one value per box, separated by commas. '
- 'The comparison ignores letter case.
'
+ 'The comparison ignores letter case. Special note: you can '
+ 'enter at most one author.
'
'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 '
@@ -158,12 +172,12 @@ class TagWizard(QDialog):
'
A.*
matches anything beginning with A.*mystery.*
matches anything containing '
'the word "mystery"' + _('Check this box if the values box contains regular expressions') + '
') - l.addWidget(c, 0, 2, 1, 1) + l.addWidget(c, 0, 3, 1, 1) c = QLabel(_('Color if value found')) c.setToolTip('' + @@ -171,13 +185,15 @@ class TagWizard(QDialog): 'one color box empty if you want the template to use the next ' 'line in this wizard. If both boxes are filled in, the rest of ' 'the lines in this wizard will be ignored.') + '
') - l.addWidget(c, 0, 3, 1, 1) + l.addWidget(c, 0, 4, 1, 1) c = QLabel(_('Color if value not found')) c.setToolTip('' + _('This box is usually filled in only on the last test. If it is ' 'filled in before the last test, then the color for value found box ' 'must be empty or all the rest of the tests will be ignored.') + '
') - l.addWidget(c, 0, 4, 1, 1) + l.addWidget(c, 0, 5, 1, 1) + + self.andboxes = [] self.tagboxes = [] self.colorboxes = [] self.nfcolorboxes = [] @@ -185,31 +201,42 @@ class TagWizard(QDialog): self.colboxes = [] self.colors = [unicode(s) for s in list(QColor.colorNames())] self.colors.insert(0, '') - for i in range(0, 10): - w = QComboBox() + + maxlines = 10 + for i in range(1, maxlines+1): + ab = QCheckBox(self) + self.andboxes.append(ab) + if i != maxlines: + # let the last box float in space + l.addWidget(ab, i, 0, 2, 1) + ab.stateChanged.connect(partial(self.and_box_changed, line=i-1)) + else: + ab.setVisible(False) + + w = QComboBox(self) w.addItems(self.columns) - l.addWidget(w, i+1, 0, 1, 1) + l.addWidget(w, i, 1, 1, 1) self.colboxes.append(w) tb = MultiCompleteLineEdit(self) tb.set_separator(', ') self.tagboxes.append(tb) - l.addWidget(tb, i+1, 1, 1, 1) + l.addWidget(tb, i, 2, 1, 1) w.currentIndexChanged[str].connect(partial(self.column_changed, valbox=tb)) w = QCheckBox(self) self.reboxes.append(w) - l.addWidget(w, i+1, 2, 1, 1) + l.addWidget(w, i, 3, 1, 1) w = QComboBox(self) w.addItems(self.colors) self.colorboxes.append(w) - l.addWidget(w, i+1, 3, 1, 1) + l.addWidget(w, i, 4, 1, 1) w = QComboBox(self) w.addItems(self.colors) self.nfcolorboxes.append(w) - l.addWidget(w, i+1, 4, 1, 1) + l.addWidget(w, i, 5, 1, 1) if txt: lines = txt.split('\n')[3:] @@ -222,25 +249,29 @@ class TagWizard(QDialog): nc = '' re = False f = 'tags' + a = False else: - t,c,f,nc,re = vals + t,c,f,nc,re,a = vals try: - self.colorboxes[i].setCurrentIndex(self.colorboxes[i].findText(c)) - self.nfcolorboxes[i].setCurrentIndex(self.nfcolorboxes[i].findText(nc)) + self.colboxes[i].setCurrentIndex(self.colboxes[i].findText(f)) + self.colorboxes[i].setCurrentIndex( + self.colorboxes[i].findText(c)) + self.nfcolorboxes[i].setCurrentIndex( + self.nfcolorboxes[i].findText(nc)) self.tagboxes[i].setText(t) self.reboxes[i].setChecked(re == '2') - self.colboxes[i].setCurrentIndex(self.colboxes[i].findText(f)) + self.andboxes[i].setChecked(a == '2') + i += 1 except: pass - i += 1 w = QLabel(_('Preview')) - l.addWidget(w, 99, 0, 1, 1) + l.addWidget(w, 99, 1, 1, 1) w = self.test_box = QLineEdit(self) w.setReadOnly(True) - l.addWidget(w, 99, 1, 1, 1) + l.addWidget(w, 99, 2, 1, 1) w = QPushButton(_('Test')) - l.addWidget(w, 99, 3, 1, 1) + l.addWidget(w, 99, 4, 1, 1) w.clicked.connect(self.preview) bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, parent=self) @@ -272,8 +303,11 @@ class TagWizard(QDialog): res = ("program:\n#tag wizard -- do not directly edit\n" " first_non_empty(\n") lines = [] - for tb, cb, fb, nfcb, reb in zip(self.tagboxes, self.colorboxes, - self.colboxes, self.nfcolorboxes, self.reboxes): + was_and = False + had_line = False + + for l, (tb, cb, fb, nfcb, reb, ab) in enumerate(zip(self.tagboxes, self.colorboxes, + self.colboxes, self.nfcolorboxes, self.reboxes, self.andboxes)): f = unicode(fb.currentText()) if not f: continue @@ -281,6 +315,8 @@ class TagWizard(QDialog): c = unicode(cb.currentText()).strip() nfc = unicode(nfcb.currentText()).strip() re = reb.checkState() + a = ab.checkState() + if m: tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()] if re == 2: @@ -289,9 +325,15 @@ class TagWizard(QDialog): tags = ','.join(tags) else: tags = unicode(tb.text()).strip() + if f == 'authors': + tags.replace(',', '|') + + if (tags or f) and not (tags and f and (a == 2 or c)): + error_dialog(self, _('Invalid line'), + _('Line number {0} is not valid').format(l), + show=True, show_copy_button=False) + return False - if not tags or not (c or nfc): - continue if c not in self.colors: error_dialog(self, _('Invalid color'), _('The color {0} is not valid').format(c), @@ -302,26 +344,41 @@ class TagWizard(QDialog): _('The color {0} is not valid').format(nfc), 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] += ',' + if re == 2: if m: - lines.append(" in_list(field('{3}'), ',', '^{0}$', '{1}', '{2}')".\ - format(tags, c, nfc, f)) + lines.append(" in_list(field('{1}'), ',', '^{0}$', '1', '')".\ + format(tags, f)) else: - lines.append(" contains(field('{3}'), '{0}', '{1}', '{2}')".\ - format(tags, c, nfc, f)) + lines.append(" contains(field('{1}'), '{0}', '1', '')".\ + format(tags, f)) else: if m: - lines.append(" str_in_list(field('{3}'), ',', '{0}', '{1}', '{2}')".\ - format(tags, c, nfc, f)) + lines.append(" str_in_list(field('{1}'), ',', '{0}', '1', '')".\ + format(tags, f)) else: - lines.append(" strcmp(field('{3}'), '{0}', '{2}', '{1}', '{2}')".\ - format(tags, c, nfc, f)) - res += ',\n'.join(lines) + lines.append(" strcmp(field('{1}'), '{0}', '', '1', '')".\ + format(tags, f)) + if a == 2: + was_and = True + else: + was_and = False + lines.append(" ), '{0}', '{1}')".format(c, nfc)) + + res += '\n'.join(lines) res += ')\n' self.template = res res = '' - for tb, cb, fb, nfcb, reb in zip(self.tagboxes, self.colorboxes, - self.colboxes, self.nfcolorboxes, self.reboxes): + for tb, cb, fb, nfcb, reb, ab in zip(self.tagboxes, self.colorboxes, + self.colboxes, self.nfcolorboxes, self.reboxes, self.andboxes): t = unicode(tb.text()).strip() if t.endswith(','): t = t[:-1] @@ -329,11 +386,23 @@ class TagWizard(QDialog): f = unicode(fb.currentText()) nfc = unicode(nfcb.currentText()).strip() re = unicode(reb.checkState()) - if f and t and c: - res += '#' + t + ':|:' + c + ':|:' + f +':|:' + nfc + ':|:' + re + '\n' + a = unicode(ab.checkState()) + if f and t and (a == '2' or c): + res += '#' + t + ':|:' + c + ':|:' + f + ':|:' + \ + nfc + ':|:' + re + ':|:' + a + '\n' self.template += res return True + def and_box_changed(self, state, line=None): + if state == 2: + self.colorboxes[line].setCurrentIndex(0) + self.colorboxes[line].setEnabled(False) + self.nfcolorboxes[line].setCurrentIndex(0) + self.nfcolorboxes[line].setEnabled(False) + else: + self.colorboxes[line].setEnabled(True) + self.nfcolorboxes[line].setEnabled(True) + def accepted(self): if self.generate_program(): self.accept() diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index bde590f30b..7a8c1fb69c 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -5,12 +5,15 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal' + - _('The template must evaluate to one of the color names shown ' - 'below. You can use any legal template expression. ' + _('If you manually construct a template, then the template must ' + 'evaluate to a valid color name shown in the color names box.' + 'You can use any legal template expression. ' 'For example, you can set the title to always display in ' 'green using the template "green" (without the quotes). ' 'To show the title in the color named in the custom column ' @@ -199,6 +204,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): 'of values", it is often easier to specify the ' 'colors in the column definition dialog. There you can ' 'provide a color for each value without using a template.')+ '
') + self.color_help_scrollArea.setVisible(False) + self.color_help_button.clicked.connect(self.change_help_text) + self.colors_scrollArea.setVisible(False) + self.colors_label.setVisible(False) + self.colors_button.clicked.connect(self.change_colors_text) + choices = db.field_metadata.displayable_field_keys() choices.sort(key=sort_key) choices.insert(0, '') @@ -211,22 +222,58 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): except: pass + l = self.column_color_layout for i in range(1, self.column_color_count): + ccn = QComboBox(parent=self) + setattr(self, 'opt_column_color_name_'+str(i), ccn) + l.addWidget(ccn, i, 0, 1, 1) + + wtb = QToolButton(parent=self) + setattr(self, 'opt_column_color_wizard_'+str(i), wtb) + wtb.setIcon(QIcon(I('wizard.png'))) + l.addWidget(wtb, i, 1, 1, 1) + + ttb = QToolButton(parent=self) + setattr(self, 'opt_column_color_tpledit_'+str(i), ttb) + ttb.setIcon(QIcon(I('edit_input.png'))) + l.addWidget(ttb, i, 2, 1, 1) + + tpl = TemplateLineEditor(parent=self) + setattr(self, 'opt_column_color_template_'+str(i), tpl) + tpl.textChanged.connect(partial(self.tpl_edit_text_changed, ctrl=i)) + tpl.set_db(db) + tpl.set_mi(mi) + l.addWidget(tpl, i, 3, 1, 1) + + wtb.clicked.connect(tpl.tag_wizard) + ttb.clicked.connect(tpl.open_editor) + r('column_color_name_'+str(i), db.prefs, choices=choices) r('column_color_template_'+str(i), db.prefs) txt = db.prefs.get('column_color_template_'+str(i), None) - tpl = getattr(self, 'opt_column_color_template_'+str(i)) - tpl.set_db(db) - tpl.set_mi(mi) - toolbutton = getattr(self, 'opt_column_color_wizard_'+str(i)) - if tpl.show_wizard_button(txt): - toolbutton.clicked.connect(tpl.tag_wizard) - else: - toolbutton.clicked.connect(tpl.open_editor) - toolbutton.setIcon(QIcon(I('edit_input.png'))) + + wtb.setEnabled(tpl.enable_wizard_button(txt)) + ttb.setEnabled(not tpl.enable_wizard_button(txt) or not txt) + all_colors = [unicode(s) for s in list(QColor.colorNames())] self.colors_box.setText(', '.join(all_colors)) + def change_help_text(self): + self.color_help_scrollArea.setVisible(not self.color_help_scrollArea.isVisible()) + + def change_colors_text(self): + self.colors_scrollArea.setVisible(not self.colors_scrollArea.isVisible()) + self.colors_label.setVisible(not self.colors_label.isVisible()) + + def tpl_edit_text_changed(self, ign, ctrl=None): + tpl = getattr(self, 'opt_column_color_template_'+str(ctrl)) + txt = unicode(tpl.text()) + wtb = getattr(self, 'opt_column_color_wizard_'+str(ctrl)) + ttb = getattr(self, 'opt_column_color_tpledit_'+str(ctrl)) + wtb.setEnabled(tpl.enable_wizard_button(txt)) + ttb.setEnabled(not tpl.enable_wizard_button(txt) or not txt) + tpl.setFocus() + def initialize(self): ConfigWidgetBase.initialize(self) font = gprefs['font'] diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index fe6134f235..def1bdd41c 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -416,114 +416,95 @@ then the tags will be displayed each on their own line.' + @@ -155,7 +155,21 @@ class TagWizard(QDialog): h.setAlignment(Qt.AlignCenter) l.addWidget(h, 0, 1, 1, 1) + h = QLabel(_('Not')) + h.setToolTip('
' + + _('Set this box to indicate that the value must not match ' + 'to return the "color if value found". For example, you ' + 'can check if a tag does not exist by entering that tag ' + 'and checking this box. You can check if tags are empty by ' + 'checking this box, entering .* (period asterisk) for the text, ' + 'then checking the RE box. The .* regular expression matches ' + 'anything, so if this box is checked, it matches nothing. ' + 'This box is particularly useful when using the AND box.')) + h.setAlignment(Qt.AlignCenter) + l.addWidget(h, 0, 2, 1, 1) + h = QLabel(_('Values (see the popup help for more information)')) + 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: you can ' @@ -172,12 +186,12 @@ class TagWizard(QDialog): '
A.*
matches anything beginning with A.*mystery.*
matches anything containing '
'the word "mystery"' + _('Check this box if the values box contains regular expressions') + '
') - l.addWidget(c, 0, 3, 1, 1) + l.addWidget(c, 0, 4, 1, 1) c = QLabel(_('Color if value found')) c.setToolTip('' + @@ -185,15 +199,16 @@ class TagWizard(QDialog): 'one color box empty if you want the template to use the next ' 'line in this wizard. If both boxes are filled in, the rest of ' 'the lines in this wizard will be ignored.') + '
') - l.addWidget(c, 0, 4, 1, 1) + l.addWidget(c, 0, 5, 1, 1) c = QLabel(_('Color if value not found')) c.setToolTip('' + _('This box is usually filled in only on the last test. If it is ' 'filled in before the last test, then the color for value found box ' 'must be empty or all the rest of the tests will be ignored.') + '
') - l.addWidget(c, 0, 5, 1, 1) + l.addWidget(c, 0, 6, 1, 1) self.andboxes = [] + self.notboxes = [] self.tagboxes = [] self.colorboxes = [] self.nfcolorboxes = [] @@ -218,26 +233,30 @@ class TagWizard(QDialog): l.addWidget(w, i, 1, 1, 1) self.colboxes.append(w) + nb = QCheckBox(self) + self.notboxes.append(nb) + l.addWidget(nb, i, 2, 1, 1) + tb = MultiCompleteLineEdit(self) tb.set_separator(', ') self.tagboxes.append(tb) - l.addWidget(tb, i, 2, 1, 1) + l.addWidget(tb, i, 3, 1, 1) w.currentIndexChanged[str].connect(partial(self.column_changed, valbox=tb)) w = QCheckBox(self) self.reboxes.append(w) - l.addWidget(w, i, 3, 1, 1) - - w = QComboBox(self) - w.addItems(self.colors) - self.colorboxes.append(w) l.addWidget(w, i, 4, 1, 1) w = QComboBox(self) w.addItems(self.colors) - self.nfcolorboxes.append(w) + self.colorboxes.append(w) l.addWidget(w, i, 5, 1, 1) + w = QComboBox(self) + w.addItems(self.colors) + self.nfcolorboxes.append(w) + l.addWidget(w, i, 6, 1, 1) + if txt: lines = txt.split('\n')[3:] i = 0 @@ -250,8 +269,9 @@ class TagWizard(QDialog): re = False f = 'tags' a = False + n = False else: - t,c,f,nc,re,a = vals + t,c,f,nc,re,a,n = vals try: self.colboxes[i].setCurrentIndex(self.colboxes[i].findText(f)) self.colorboxes[i].setCurrentIndex( @@ -261,6 +281,7 @@ class TagWizard(QDialog): self.tagboxes[i].setText(t) self.reboxes[i].setChecked(re == '2') self.andboxes[i].setChecked(a == '2') + self.notboxes[i].setChecked(n == '2') i += 1 except: pass @@ -269,9 +290,9 @@ class TagWizard(QDialog): l.addWidget(w, 99, 1, 1, 1) w = self.test_box = QLineEdit(self) w.setReadOnly(True) - l.addWidget(w, 99, 2, 1, 1) + l.addWidget(w, 99, 3, 1, 1) w = QPushButton(_('Test')) - l.addWidget(w, 99, 4, 1, 1) + l.addWidget(w, 99, 5, 1, 1) w.clicked.connect(self.preview) bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, parent=self) @@ -306,8 +327,10 @@ class TagWizard(QDialog): was_and = False had_line = False - for l, (tb, cb, fb, nfcb, reb, ab) in enumerate(zip(self.tagboxes, self.colorboxes, - self.colboxes, self.nfcolorboxes, self.reboxes, self.andboxes)): + line = 0 + for tb, cb, fb, nfcb, reb, ab, nb in zip( + self.tagboxes, self.colorboxes, self.colboxes, + self.nfcolorboxes, self.reboxes, self.andboxes, self.notboxes): f = unicode(fb.currentText()) if not f: continue @@ -316,6 +339,15 @@ class TagWizard(QDialog): nfc = unicode(nfcb.currentText()).strip() re = reb.checkState() a = ab.checkState() + n = nb.checkState() + line += 1 + + if n == 2: + tval = '' + fval = '1' + else: + tval = '1' + fval = '' if m: tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()] @@ -330,7 +362,7 @@ class TagWizard(QDialog): if (tags or f) and not (tags and f and (a == 2 or c)): error_dialog(self, _('Invalid line'), - _('Line number {0} is not valid').format(l), + _('Line number {0} is not valid').format(line), show=True, show_copy_button=False) return False @@ -355,18 +387,18 @@ class TagWizard(QDialog): if re == 2: if m: - lines.append(" in_list(field('{1}'), ',', '^{0}$', '1', '')".\ - format(tags, f)) + lines.append(" in_list(field('{1}'), ',', '^{0}$', '{2}', '{3}')".\ + format(tags, f, tval, fval)) else: - lines.append(" contains(field('{1}'), '{0}', '1', '')".\ - format(tags, f)) + lines.append(" contains(field('{1}'), '{0}', '{2}', '{3}')".\ + format(tags, f, tval, fval)) else: if m: - lines.append(" str_in_list(field('{1}'), ',', '{0}', '1', '')".\ - format(tags, f)) + lines.append(" str_in_list(field('{1}'), ',', '{0}', '{2}', '{3}')".\ + format(tags, f, tval, fval)) else: - lines.append(" strcmp(field('{1}'), '{0}', '', '1', '')".\ - format(tags, f)) + lines.append(" strcmp(field('{1}'), '{0}', '{3}', '{2}', '{3}')".\ + format(tags, f, tval, fval)) if a == 2: was_and = True else: @@ -377,8 +409,9 @@ class TagWizard(QDialog): res += ')\n' self.template = res res = '' - for tb, cb, fb, nfcb, reb, ab in zip(self.tagboxes, self.colorboxes, - self.colboxes, self.nfcolorboxes, self.reboxes, self.andboxes): + for tb, cb, fb, nfcb, reb, ab, nb in zip( + self.tagboxes, self.colorboxes, self.colboxes, + self.nfcolorboxes, self.reboxes, self.andboxes, self.notboxes): t = unicode(tb.text()).strip() if t.endswith(','): t = t[:-1] @@ -387,9 +420,10 @@ class TagWizard(QDialog): nfc = unicode(nfcb.currentText()).strip() re = unicode(reb.checkState()) a = unicode(ab.checkState()) + n = unicode(nb.checkState()) if f and t and (a == '2' or c): res += '#' + t + ':|:' + c + ':|:' + f + ':|:' + \ - nfc + ':|:' + re + ':|:' + a + '\n' + nfc + ':|:' + re + ':|:' + a + ':|:' + n + '\n' self.template += res return True From 7f3d6f6b155d13d5bc240d3e47fe2fb303f64c4d Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 30 May 2011 15:53:04 +0100 Subject: [PATCH 29/84] Add missing reverse apostrophe in search documentation --- src/calibre/manual/gui.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index a4e18c2e07..e2758bc257 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -352,7 +352,7 @@ The syntax for searching for dates is:: If the date is ambiguous, the current locale is used for date comparison. For example, in an mm/dd/yyyy locale, 2/1/2009 is interpreted as 1 Feb 2009. In a dd/mm/yyyy locale, it is interpreted as 2 Jan 2009. Some special date strings are available. The string ``today`` translates to today's date, whatever it is. The -strings `yesterday`` and ``thismonth`` also work. In addition, the string ``daysago`` can be used to compare +strings ``yesterday`` and ``thismonth`` also work. In addition, the string ``daysago`` can be used to compare to a date some number of days ago, for example: date:>10daysago, date:<=45daysago. You can search for books that have a format of a certain size like this:: From e27a3638d222bef6e7692d83a340ae5757a5376b Mon Sep 17 00:00:00 2001 From: Kovid Goyal'+_('Choose formats not to be deleted'), ids) + '
'+_('Choose formats not to be deleted.
Note that ' + 'this will never remove all formats from a book.'), ids) if fmts is None: return for id in ids: diff --git a/src/calibre/gui2/dialogs/select_formats.py b/src/calibre/gui2/dialogs/select_formats.py index 5934c8c0f9..aea56ad196 100644 --- a/src/calibre/gui2/dialogs/select_formats.py +++ b/src/calibre/gui2/dialogs/select_formats.py @@ -44,7 +44,7 @@ class SelectFormats(QDialog): self.setLayout(self._l) self.setWindowTitle(_('Choose formats')) self._m = QLabel(msg) - self._m.setWordWrap = True + self._m.setWordWrap(True) self._l.addWidget(self._m) self.formats = Formats(fmt_list) self.fview = QListView(self) From cdeb8ade3d36fc14bf2f84fbd774c617b4e277ae Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 31 May 2011 12:13:08 +0100 Subject: [PATCH 40/84] Yet another improvement on the color wizard. Make 'None' compare to zero in the formatter function 'cmp'. --- .../gui2/dialogs/template_line_editor.py | 327 ++++++++++-------- src/calibre/utils/formatter_functions.py | 4 +- 2 files changed, 187 insertions(+), 144 deletions(-) diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py index b24328f0ad..6a0b07200e 100644 --- a/src/calibre/gui2/dialogs/template_line_editor.py +++ b/src/calibre/gui2/dialogs/template_line_editor.py @@ -46,8 +46,8 @@ class TemplateLineEditor(QLineEdit): menu.exec_(event.globalPos()) def clear_field(self): - self.setText('') self.txt = None + self.setText('') self.setReadOnly(False) self.setStyleSheet('TemplateLineEditor { color: black }') @@ -95,6 +95,39 @@ class TemplateLineEditor(QLineEdit): class TagWizard(QDialog): + text_template = " strcmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')" + text_empty_template = " test(field('{f}'), '{fv}', '{tv}')" + text_re_template = " contains(field('{f}'), '{v}', '{tv}', '{fv}')" + + templates = { + 'text.mult' : " str_in_list(field('{f}'), '{mult}', '{v}', '{tv}', '{fv}')", + 'text.mult.re' : " in_list(field('{f}'), '{mult}', '^{v}$', '{tv}', '{fv}')", + 'text.mult.empty' : " test(field('{f}'), '{fv}', '{tv}')", + 'text' : text_template, + 'text.re' : text_re_template, + 'text.empty' : text_empty_template, + 'rating' : " cmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')", + 'rating.empty' : text_empty_template, + 'int' : " cmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')", + 'int.empty' : text_empty_template, + 'float' : " cmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')", + 'float.empty' : text_empty_template, + 'bool' : " strcmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')", + 'bool.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, + } + def __init__(self, parent, db, txt, mi): QDialog.__init__(self, parent) self.setWindowTitle(_('Coloring Wizard')) @@ -106,11 +139,19 @@ class TagWizard(QDialog): self.completion_values = defaultdict(dict) for k in db.all_field_keys(): m = db.metadata_for_field(k) - if m['datatype'] in ('text', 'enumeration', 'series') and \ - m['is_category'] and k not in ('identifiers'): + if k.endswith('_index') or ( + m['kind'] == 'field' and m['name'] and + k not in ('ondevice', 'path', 'size', 'sort') and + m['datatype'] not in ('datetime')): self.columns.append(k) + self.completion_values[k]['dt'] = m['datatype'] if m['is_custom']: - self.completion_values[k]['v'] = db.all_custom(m['label']) + 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': @@ -127,12 +168,15 @@ class TagWizard(QDialog): 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': - self.completion_values[k]['m'] = None + mult = '&' else: - self.completion_values[k]['m'] = m['is_multiple'] + 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, '') @@ -140,12 +184,12 @@ class TagWizard(QDialog): l = QGridLayout() self.setLayout(l) l.setColumnStretch(2, 10) - l.setColumnMinimumWidth(3, 300) + l.setColumnMinimumWidth(5, 300) h = QLabel(_('And')) h.setToolTip('
' + _('Set this box to indicate that the two conditions must both ' - 'be true to return the "color if value found". For example, you ' + '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.')) @@ -155,107 +199,106 @@ class TagWizard(QDialog): h.setAlignment(Qt.AlignCenter) l.addWidget(h, 0, 1, 1, 1) - h = QLabel(_('Not')) - h.setToolTip('
' + - _('Set this box to indicate that the value must not match ' - 'to return the "color if value found". For example, you ' - 'can check if a tag does not exist by entering that tag ' - 'and checking this box. You can check if tags are empty by ' - 'checking this box, entering .* (period asterisk) for the text, ' - 'then checking the RE box. The .* regular expression matches ' - 'anything, so if this box is checked, it matches nothing. ' - 'This box is particularly useful when using the AND box.')) + h = QLabel(_('is')) h.setAlignment(Qt.AlignCenter) l.addWidget(h, 0, 2, 1, 1) - h = QLabel(_('Values (see the popup help for more information)')) + h = QLabel(_('not')) + h.setToolTip('
' + + _('Check this box to indicate that the value must not match ' + 'to use the color. For example, you can check if a tag does ' + 'not exist by entering that tag and checking this box.') + '
') + 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: you can '
- 'enter at most one author.
'
+ '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 you are checking.
'
+ 'to the end of the column/value you are checking.
'
'Regular expression examples:') + '
.*
matches anything in the column. No '
- 'empty values are checked, so you don\'t need to worry about '
- 'empty strings.*
matches anything in the column.A.*
matches anything beginning with A.*mystery.*
matches anything containing '
'the word "mystery"' + _('Check this box if the values box contains regular expressions') + '
') - l.addWidget(c, 0, 4, 1, 1) - - c = QLabel(_('Color if value found')) - c.setToolTip('' + - _('At least one of the two color boxes must have a value. Leave ' - 'one color box empty if you want the template to use the next ' - 'line in this wizard. If both boxes are filled in, the rest of ' - 'the lines in this wizard will be ignored.') + '
') - l.addWidget(c, 0, 5, 1, 1) - c = QLabel(_('Color if value not found')) - c.setToolTip('' + - _('This box is usually filled in only on the last test. If it is ' - 'filled in before the last test, then the color for value found box ' - 'must be empty or all the rest of the tests will be ignored.') + '
') l.addWidget(c, 0, 6, 1, 1) - self.andboxes = [] - self.notboxes = [] - self.tagboxes = [] - self.colorboxes = [] - self.nfcolorboxes = [] - self.reboxes = [] - self.colboxes = [] + 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.notboxes = [] + 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): - ab = QCheckBox(self) - self.andboxes.append(ab) - if i != maxlines: - # let the last box float in space - l.addWidget(ab, i, 0, 2, 1) - ab.stateChanged.connect(partial(self.and_box_changed, line=i-1)) - else: - ab.setVisible(False) + 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 = QComboBox(self) - w.addItems(self.columns) - l.addWidget(w, i, 1, 1, 1) - self.colboxes.append(w) + w = create_widget(QComboBox, self.colboxes, l, i, 1, self.columns) + w.currentIndexChanged[str].connect(partial(self.column_changed, line=i-1)) - nb = QCheckBox(self) - self.notboxes.append(nb) - l.addWidget(nb, i, 2, 1, 1) + w = QLabel(self) + w.setText(_('is')) + l.addWidget(w, i, 2, 1, 1) - tb = MultiCompleteLineEdit(self) - tb.set_separator(', ') - self.tagboxes.append(tb) - l.addWidget(tb, i, 3, 1, 1) - w.currentIndexChanged[str].connect(partial(self.column_changed, valbox=tb)) + create_widget(QCheckBox, self.notboxes, l, i, 3, None) - w = QCheckBox(self) - self.reboxes.append(w) - l.addWidget(w, i, 4, 1, 1) + w = create_widget(QCheckBox, self.emptyboxes, l, i, 4, None) + w.stateChanged.connect(partial(self.empty_box_changed, line=i-1)) - w = QComboBox(self) - w.addItems(self.colors) - self.colorboxes.append(w) - l.addWidget(w, i, 5, 1, 1) + create_widget(MultiCompleteLineEdit, self.tagboxes, l, i, 5, None, align=0) + create_widget(QCheckBox, self.reboxes, l, i, 6, None) + create_widget(QComboBox, self.colorboxes, l, i, 7, self.colors) - w = QComboBox(self) - w.addItems(self.colors) - self.nfcolorboxes.append(w) - l.addWidget(w, i, 6, 1, 1) + 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:] @@ -263,25 +306,27 @@ class TagWizard(QDialog): 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 - nc = '' - re = False f = 'tags' - a = False - n = False + a = n = e = re = False else: - t,c,f,nc,re,a,n = vals + t,c,f,re,a,n,e = vals try: self.colboxes[i].setCurrentIndex(self.colboxes[i].findText(f)) self.colorboxes[i].setCurrentIndex( self.colorboxes[i].findText(c)) - self.nfcolorboxes[i].setCurrentIndex( - self.nfcolorboxes[i].findText(nc)) self.tagboxes[i].setText(t) self.reboxes[i].setChecked(re == '2') self.andboxes[i].setChecked(a == '2') self.notboxes[i].setChecked(n == '2') + self.emptyboxes[i].setChecked(e == '2') i += 1 except: pass @@ -290,13 +335,17 @@ class TagWizard(QDialog): l.addWidget(w, 99, 1, 1, 1) w = self.test_box = QLineEdit(self) w.setReadOnly(True) - l.addWidget(w, 99, 3, 1, 1) + l.addWidget(w, 99, 2, 1, 5) w = QPushButton(_('Test')) - l.addWidget(w, 99, 5, 1, 1) + 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, 3, 1, 2)
+ l.addWidget(bb, 100, 5, 1, 3)
bb.accepted.connect(self.accepted)
bb.rejected.connect(self.reject)
self.template = ''
@@ -308,14 +357,22 @@ class TagWizard(QDialog):
_('EXCEPTION'), self.mi)
self.test_box.setText(t)
- def column_changed(self, s, valbox=None):
+ def column_changed(self, s, line=None):
k = unicode(s)
if k in self.completion_values:
+ valbox = self.tagboxes[line]
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)
else:
valbox.update_items_cache([])
valbox.set_separator(None)
@@ -324,59 +381,44 @@ class TagWizard(QDialog):
res = ("program:\n#tag wizard -- do not directly edit\n"
" first_non_empty(\n")
lines = []
- was_and = False
- had_line = False
+ was_and = had_line = False
line = 0
- for tb, cb, fb, nfcb, reb, ab, nb in zip(
- self.tagboxes, self.colorboxes, self.colboxes,
- self.nfcolorboxes, self.reboxes, self.andboxes, self.notboxes):
+ for tb, cb, fb, reb, ab, nb, eb in zip(
+ self.tagboxes, self.colorboxes, self.colboxes,
+ self.reboxes, self.andboxes, self.notboxes, 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()
- nfc = unicode(nfcb.currentText()).strip()
re = reb.checkState()
a = ab.checkState()
n = nb.checkState()
+ e = eb.checkState()
line += 1
- if n == 2:
- tval = ''
- fval = '1'
- else:
- tval = '1'
- fval = ''
+ tval = '' if n == 2 else '1'
+ fval = '1' if n == 2 else ''
if m:
- tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()]
+ tags = [t.strip() for t in unicode(tb.text()).split(m) if t.strip()]
if re == 2:
tags = '$|^'.join(tags)
else:
- tags = ','.join(tags)
+ tags = m.join(tags)
+ if m == '&':
+ tags = tags.replace(',', '|')
else:
tags = unicode(tb.text()).strip()
- if f == 'authors':
- tags.replace(',', '|')
- if (tags or f) and not (tags and f and (a == 2 or c)):
+ 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 c not in self.colors:
- error_dialog(self, _('Invalid color'),
- _('The color {0} is not valid').format(c),
- show=True, show_copy_button=False)
- return False
- if nfc not in self.colors:
- error_dialog(self, _('Invalid color'),
- _('The color {0} is not valid').format(nfc),
- show=True, show_copy_button=False)
- return False
-
if not was_and:
if had_line:
lines[-1] += ','
@@ -385,57 +427,58 @@ class TagWizard(QDialog):
else:
lines[-1] += ','
- if re == 2:
- if m:
- lines.append(" in_list(field('{1}'), ',', '^{0}$', '{2}', '{3}')".\
- format(tags, f, tval, fval))
- else:
- lines.append(" contains(field('{1}'), '{0}', '{2}', '{3}')".\
- format(tags, f, tval, fval))
- else:
- if m:
- lines.append(" str_in_list(field('{1}'), ',', '{0}', '{2}', '{3}')".\
- format(tags, f, tval, fval))
- else:
- lines.append(" strcmp(field('{1}'), '{0}', '{3}', '{2}', '{3}')".\
- format(tags, f, tval, fval))
+ key = dt + ('.mult' if m else '') + ('.empty' if e else '') + ('.re' if re else '')
+ template = self.templates[key]
+ lines.append(template.format(v=tags, f=f, tv=tval, fv=fval, mult=m))
+
if a == 2:
was_and = True
else:
was_and = False
- lines.append(" ), '{0}', '{1}')".format(c, nfc))
+ 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, nfcb, reb, ab, nb in zip(
- self.tagboxes, self.colorboxes, self.colboxes,
- self.nfcolorboxes, self.reboxes, self.andboxes, self.notboxes):
+ for tb, cb, fb, reb, ab, nb, eb in zip(
+ self.tagboxes, self.colorboxes, self.colboxes,
+ self.reboxes, self.andboxes, self.notboxes, self.emptyboxes):
t = unicode(tb.text()).strip()
if t.endswith(','):
t = t[:-1]
c = unicode(cb.currentText()).strip()
f = unicode(fb.currentText())
- nfc = unicode(nfcb.currentText()).strip()
re = unicode(reb.checkState())
a = unicode(ab.checkState())
n = unicode(nb.checkState())
- if f and t and (a == '2' or c):
- res += '#' + t + ':|:' + c + ':|:' + f + ':|:' + \
- nfc + ':|:' + re + ':|:' + a + ':|:' + n + '\n'
+ e = unicode(eb.checkState())
+ if f and (t or e) and (a == '2' or c):
+ res += '#' + t + ':|:' + c + ':|:' + f + ':|:' + re + ':|:' + \
+ a + ':|:' + n + ':|:' + e + '\n'
+ res += '#else:' + else_txt + '\n'
self.template += res
return True
+ 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)
+
def and_box_changed(self, state, line=None):
if state == 2:
self.colorboxes[line].setCurrentIndex(0)
self.colorboxes[line].setEnabled(False)
- self.nfcolorboxes[line].setCurrentIndex(0)
- self.nfcolorboxes[line].setEnabled(False)
else:
self.colorboxes[line].setEnabled(True)
- self.nfcolorboxes[line].setEnabled(True)
def accepted(self):
if self.generate_program():
diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py
index b66aec2cb9..7b9a2ac51c 100644
--- a/src/calibre/utils/formatter_functions.py
+++ b/src/calibre/utils/formatter_functions.py
@@ -106,8 +106,8 @@ class BuiltinCmp(BuiltinFormatterFunction):
'numbers. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt.')
def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
- x = float(x if x else 0)
- y = float(y if y else 0)
+ x = float(x if x and x != 'None' else 0)
+ y = float(y if y and y != 'None' else 0)
if x < y:
return lt
if x == y:
From f4affb57e3479d5ba811442a4c0654591966e2a7 Mon Sep 17 00:00:00 2001
From: Kovid Goyal ' +
- _('Check this box to indicate that the value must not match '
- 'to use the color. For example, you can check if a tag does '
- 'not exist by entering that tag and checking this box.') + '
Advanced Rule for column: %s +
%s+ ''')%(col, rule) + conditions = [self.condition_to_html(c) for c in rule.conditions] + return _('''\ +
Set the color of %s to %s if the following + conditions are met:
+' + - _('Here you can specify coloring rules for columns shown in the ' - 'library view. Choose the column you wish to color, then ' - 'supply a template that specifies the color to use based on ' - 'the values in the column. There is a ' - '' - 'tutorial on using templates.') + - '
' + - _('If you want to color a field based on contents of columns, ' - 'then click the button next to an empty line to open the wizard. ' - 'It will build a template for you. You can later edit that ' - 'template with the same wizard. This is by far the easiest ' - 'way to specify a template.') + - '
' + - _('If you manually construct a template, then the template must ' - 'evaluate to a valid color name shown in the color names box.' - 'You can use any legal template expression. ' - 'For example, you can set the title to always display in ' - 'green using the template "green" (without the quotes). ' - 'To show the title in the color named in the custom column ' - '#column, use "{#column}". To show the title in blue if the ' - 'custom column #column contains the value "foo", in red if the ' - 'column contains the value "bar", otherwise in black, use ' - '
{#column:switch(foo,blue,bar,red,black)}' - 'To show the title in blue if the book has the exact tag ' - '"Science Fiction", red if the book has the exact tag ' - '"Mystery", or black if the book has neither tag, use' - "
program: \n" - " t = field('tags'); \n" - " first_non_empty(\n" - " in_list(t, ',', '^Science Fiction$', 'blue', ''), \n" - " in_list(t, ',', '^Mystery$', 'red', 'black'))" - 'To show the title in green if it has one format, blue if it ' - 'two formats, and red if more, use' - "
program:cmp(count(field('formats'),','), 2, 'green', 'blue', 'red')") + - '
' + - _('You can access a multi-line template editor from the ' - 'context menu (right-click).') + '
' + - _('Note: if you want to color a "custom column with a fixed set ' - 'of values", it is often easier to specify the ' - 'colors in the column definition dialog. There you can ' - 'provide a color for each value without using a template.')+ '
') - self.color_help_scrollArea.setVisible(False) - self.color_help_button.clicked.connect(self.change_help_text) - self.colors_scrollArea.setVisible(False) - self.colors_label.setVisible(False) - self.colors_button.clicked.connect(self.change_colors_text) - - choices = db.field_metadata.displayable_field_keys() - choices.sort(key=sort_key) - choices.insert(0, '') - self.column_color_count = db.column_color_count+1 - - mi=None - try: - idx = gui.library_view.currentIndex().row() - mi = db.get_metadata(idx, index_is_id=False) - except: - pass - - l = self.column_color_layout - for i in range(1, self.column_color_count): - ccn = QComboBox(parent=self) - setattr(self, 'opt_column_color_name_'+str(i), ccn) - l.addWidget(ccn, i, 0, 1, 1) - - wtb = QToolButton(parent=self) - setattr(self, 'opt_column_color_wizard_'+str(i), wtb) - wtb.setIcon(QIcon(I('wizard.png'))) - l.addWidget(wtb, i, 1, 1, 1) - - ttb = QToolButton(parent=self) - setattr(self, 'opt_column_color_tpledit_'+str(i), ttb) - ttb.setIcon(QIcon(I('edit_input.png'))) - l.addWidget(ttb, i, 2, 1, 1) - - tpl = TemplateLineEditor(parent=self) - setattr(self, 'opt_column_color_template_'+str(i), tpl) - tpl.textChanged.connect(partial(self.tpl_edit_text_changed, ctrl=i)) - tpl.set_db(db) - tpl.set_mi(mi) - l.addWidget(tpl, i, 3, 1, 1) - - wtb.clicked.connect(tpl.tag_wizard) - ttb.clicked.connect(tpl.open_editor) - - r('column_color_name_'+str(i), db.prefs, choices=choices) - r('column_color_template_'+str(i), db.prefs) - txt = db.prefs.get('column_color_template_'+str(i), None) - - wtb.setEnabled(tpl.enable_wizard_button(txt)) - ttb.setEnabled(not tpl.enable_wizard_button(txt) or not txt) - - all_colors = [unicode(s) for s in list(QColor.colorNames())] - self.colors_box.setText(', '.join(all_colors)) - - def change_help_text(self): - self.color_help_scrollArea.setVisible(not self.color_help_scrollArea.isVisible()) - - def change_colors_text(self): - self.colors_scrollArea.setVisible(not self.colors_scrollArea.isVisible()) - self.colors_label.setVisible(not self.colors_label.isVisible()) - - def tpl_edit_text_changed(self, ign, ctrl=None): - tpl = getattr(self, 'opt_column_color_template_'+str(ctrl)) - txt = unicode(tpl.text()) - wtb = getattr(self, 'opt_column_color_wizard_'+str(ctrl)) - ttb = getattr(self, 'opt_column_color_tpledit_'+str(ctrl)) - wtb.setEnabled(tpl.enable_wizard_button(txt)) - ttb.setEnabled(not tpl.enable_wizard_button(txt) or not txt) - tpl.setFocus() + self.edit_rules = EditRules(self.tabWidget) + self.edit_rules.changed.connect(self.changed_signal) + self.tabWidget.addTab(self.edit_rules, + QIcon(I('format-fill-color.png')), _('Column coloring')) + self.tabWidget.setCurrentIndex(0) def initialize(self): ConfigWidgetBase.initialize(self) @@ -283,6 +175,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.current_font = self.initial_font = font self.update_font_display() self.display_model.initialize() + db = self.gui.current_db + self.edit_rules.initialize(db.field_metadata, db.prefs) def restore_defaults(self): ConfigWidgetBase.restore_defaults(self) @@ -292,6 +186,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.changed_signal.emit() self.update_font_display() self.display_model.restore_defaults() + self.edit_rules.clear() self.changed_signal.emit() def build_font_obj(self): @@ -341,12 +236,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.changed_signal.emit() def commit(self, *args): - for i in range(1, self.column_color_count): - col = getattr(self, 'opt_column_color_name_'+str(i)) - tpl = getattr(self, 'opt_column_color_template_'+str(i)) - if not col.currentIndex() or not unicode(tpl.text()).strip(): - col.setCurrentIndex(0) - tpl.setText('') rr = ConfigWidgetBase.commit(self, *args) if self.current_font != self.initial_font: gprefs['font'] = (self.current_font[:4] if self.current_font else @@ -356,10 +245,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): QApplication.setFont(self.font_display.font()) rr = True self.display_model.commit() + self.edit_rules.commit(self.gui.current_db.prefs) return rr def refresh_gui(self, gui): - gui.library_view.model().set_color_templates() + gui.library_view.model().reset() self.update_font_display() gui.tags_view.reread_collapse_parameters() gui.library_view.refresh_book_details() diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index def1bdd41c..cc9133a36f 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -6,7 +6,7 @@' + - _('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"' + - _('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() From d02342fd0c00aeffcab8d02ea649900222a5ee1f Mon Sep 17 00:00:00 2001 From: Kovid Goyal