From 19261f15eede6fdf17ec5769a31f8e28c69486bf Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 29 May 2011 20:12:31 +0100
Subject: [PATCH 01/18] 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 02/18] 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 dbdee0d46dcd1d43581f95f0ae9582a08d6fd243 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 29 May 2011 21:41:36 +0100
Subject: [PATCH 04/18] Add a context menu item to clear the template from a
box
---
src/calibre/gui2/dialogs/template_line_editor.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py
index 2e4a6595fd..90dec0ccf8 100644
--- a/src/calibre/gui2/dialogs/template_line_editor.py
+++ b/src/calibre/gui2/dialogs/template_line_editor.py
@@ -39,10 +39,18 @@ class TemplateLineEditor(QLineEdit):
menu = self.createStandardContextMenu()
menu.addSeparator()
+ action_clear_field = menu.addAction(_('Remove any template from the box'))
+ action_clear_field.triggered.connect(self.clear_field)
action_open_editor = menu.addAction(_('Open Template Editor'))
action_open_editor.triggered.connect(self.open_editor)
menu.exec_(event.globalPos())
+ def clear_field(self):
+ self.setText('')
+ self.txt = None
+ self.setReadOnly(False)
+ self.setStyleSheet('TemplateLineEditor { color: black }')
+
def open_editor(self):
if self.txt:
t = TemplateDialog(self, self.txt, self.mi)
From c319d4f6c567d418a881280ed417a71794011754 Mon Sep 17 00:00:00 2001
From: Kovid Goyal ' +
+ _('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.
'), lambda match: '')]
+
+ keep_only_tags = [dict(name='div', attrs={'class':'article'})]
+
+ remove_tags = [
+ dict(name='p',attrs={'class':'meta links'}),
+ dict(name='div',attrs={'class':'float-right'}),
+ dict(name='span',attrs={'class':'article-link-id'})
+ ]
+
+ feeds = [
+ (u'Seneste nyheder' , u'http://www.version2.dk/feeds/nyheder')
+ ,(u'Forretningssoftware' , u'http://www.version2.dk/feeds/forretningssoftware')
+ ,(u'Internet & styresystemer' , u'http://www.version2.dk/feeds/styresystemer')
+ ,(u'It-arkitektur' , u'http://www.version2.dk/feeds/it-arkitektur')
+ ,(u'It-styring & outsourcing' , u'http://www.version2.dk/feeds/it-styring')
+ ,(u'Job & karriere' , u'http://www.version2.dk/feeds/karriere')
+ ,(u'Mobil it & tele' , u'http://www.version2.dk/feeds/tele')
+ ,(u'Server/storage & netværk' , u'http://www.version2.dk/feeds/server-storage')
+ ,(u'Sikkerhed' , u'http://www.version2.dk/feeds/sikkerhed')
+ ,(u'Softwareudvikling' , u'http://www.version2.dk/feeds/softwareudvikling')
+ ]
diff --git a/resources/template-functions.json b/resources/template-functions.json
index 9fdc1782ea..606b102d41 100644
--- a/resources/template-functions.json
+++ b/resources/template-functions.json
@@ -1,26 +1,27 @@
{
"and": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if not args[i]:\n return ''\n i += 1\n return '1'\n",
- "contains": "def evaluate(self, formatter, kwargs, mi, locals,\n val, test, value_if_present, value_if_not):\n if re.search(test, val):\n return value_if_present\n else:\n return value_if_not\n",
+ "contains": "def evaluate(self, formatter, kwargs, mi, locals,\n val, test, value_if_present, value_if_not):\n if re.search(test, val, flags=re.I):\n return value_if_present\n else:\n return value_if_not\n",
"divide": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x / y)\n",
"uppercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.upper()\n",
"strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n res = ''\n for i in range(0, len(args)):\n res += args[i]\n return res\n",
- "in_list": "def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv):\n l = [v.strip() for v in val.split(sep) if v.strip()]\n for v in l:\n if re.search(pat, v):\n return fv\n return nfv\n",
+ "in_list": "def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv):\n l = [v.strip() for v in val.split(sep) if v.strip()]\n if l:\n for v in l:\n if re.search(pat, v, flags=re.I):\n return fv\n return nfv\n",
"multiply": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x * y)\n",
"ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n if val:\n return val\n else:\n return value_if_empty\n",
"booksize": "def evaluate(self, formatter, kwargs, mi, locals):\n if mi.book_size is not None:\n try:\n return str(mi.book_size)\n except:\n pass\n return ''\n",
"select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n if not val:\n return ''\n vals = [v.strip() for v in val.split(',')]\n for v in vals:\n if v.startswith(key+':'):\n return v[len(key)+1:]\n return ''\n",
"strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n v = strcmp(x, y)\n if v < 0:\n return lt\n if v == 0:\n return eq\n return gt\n",
"first_non_empty": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return args[i]\n i += 1\n return ''\n",
- "re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val)\n",
+ "re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val, flags=re.I)\n",
"subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x - y)\n",
"list_item": "def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):\n if not val:\n return ''\n index = int(index)\n val = val.split(sep)\n try:\n return val[index]\n except:\n return ''\n",
"shorten": "def evaluate(self, formatter, kwargs, mi, locals,\n val, leading, center_string, trailing):\n l = max(0, int(leading))\n t = max(0, int(trailing))\n if len(val) > l + len(center_string) + t:\n return val[0:l] + center_string + ('' if t == 0 else val[-t:])\n else:\n return val\n",
"field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n",
"add": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x + y)\n",
- "lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if len(args) == 2: # here for backwards compatibility\n if val:\n return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n else:\n return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n if (len(args) % 2) != 1:\n raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n if re.search(args[i], val):\n return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n i += 2\n",
+ "lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if len(args) == 2: # here for backwards compatibility\n if val:\n return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n else:\n return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n if (len(args) % 2) != 1:\n raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n if re.search(args[i], val, flags=re.I):\n return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n i += 2\n",
"template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n template = template.replace('[[', '{').replace(']]', '}')\n return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)\n",
"print": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n print args\n return None\n",
"merge_lists": "def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):\n l1 = [l.strip() for l in list1.split(separator) if l.strip()]\n l2 = [l.strip() for l in list2.split(separator) if l.strip()]\n lcl1 = set([icu_lower(l) for l in l1])\n res = []\n for i in l1:\n res.append(i)\n for i in l2:\n if icu_lower(i) not in lcl1:\n res.append(i)\n return ', '.join(sorted(res, key=sort_key))\n",
+ "str_in_list": "def evaluate(self, formatter, kwargs, mi, locals, val, sep, str, fv, nfv):\n l = [v.strip() for v in val.split(sep) if v.strip()]\n c = [v.strip() for v in str.split(sep) if v.strip()]\n if l:\n for v in l:\n for t in c:\n if strcmp(t, v) == 0:\n return fv\n return nfv\n",
"titlecase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return titlecase(val)\n",
"subitems": "def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index):\n if not val:\n return ''\n si = int(start_index)\n ei = int(end_index)\n items = [v.strip() for v in val.split(',')]\n rv = set()\n for item in items:\n component = item.split('.')\n try:\n if ei == 0:\n rv.add('.'.join(component[si:]))\n else:\n rv.add('.'.join(component[si:ei]))\n except:\n pass\n return ', '.join(sorted(rv, key=sort_key))\n",
"sublist": "def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index, sep):\n if not val:\n return ''\n si = int(start_index)\n ei = int(end_index)\n val = val.split(sep)\n try:\n if ei == 0:\n return sep.join(val[si:])\n else:\n return sep.join(val[si:ei])\n except:\n return ''\n",
@@ -32,9 +33,10 @@
"count": "def evaluate(self, formatter, kwargs, mi, locals, val, sep):\n return unicode(len(val.split(sep)))\n",
"lowercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.lower()\n",
"substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n",
- "assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n",
- "switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
"or": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return '1'\n i += 1\n return ''\n",
+ "switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val, flags=re.I):\n return args[i+1]\n i += 2\n",
+ "ondevice": "def evaluate(self, formatter, kwargs, mi, locals):\n if mi.ondevice_col:\n return _('Yes')\n return ''\n",
+ "assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n",
"raw_field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return unicode(getattr(mi, name, None))\n",
"cmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n x = float(x if x else 0)\n y = float(y if y else 0)\n if x < y:\n return lt\n if x == y:\n return eq\n return gt\n"
}
\ No newline at end of file
From 56b239991cc6a61e71a8208257d3e8e4eb4197a1 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 30 May 2011 14:56:52 +0100
Subject: [PATCH 06/18] Clean up color preferences to make it less
intimidating. Add an 'and' box to the wizard. Fix a couple of small bugs.
---
.../gui2/dialogs/template_line_editor.py | 159 +++++++++----
src/calibre/gui2/preferences/look_feel.py | 73 ++++--
src/calibre/gui2/preferences/look_feel.ui | 216 ++++++++----------
3 files changed, 266 insertions(+), 182 deletions(-)
diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py
index 90dec0ccf8..68eda27bf1 100644
--- a/src/calibre/gui2/dialogs/template_line_editor.py
+++ b/src/calibre/gui2/dialogs/template_line_editor.py
@@ -8,8 +8,8 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from collections import defaultdict
-from PyQt4.Qt import (QLineEdit, QDialog, QGridLayout, QLabel, QCheckBox, QIcon,
- QDialogButtonBox, QColor, QComboBox, QPushButton)
+from PyQt4.Qt import (Qt, 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
@@ -58,10 +58,10 @@ class TemplateLineEditor(QLineEdit):
t = TemplateDialog(self, self.text(), self.mi)
t.setWindowTitle(_('Edit template'))
if t.exec_():
- self.setText(t.textbox.toPlainText())
self.txt = None
+ self.setText(t.textbox.toPlainText())
- def show_wizard_button(self, txt):
+ def enable_wizard_button(self, txt):
if not txt or txt.startswith('program:\n#tag wizard'):
return True
return False
@@ -129,23 +129,37 @@ class TagWizard(QDialog):
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']
+ if k == 'authors':
+ self.completion_values[k]['m'] = None
+ else:
+ self.completion_values[k]['m'] = m['is_multiple']
self.columns.sort(key=sort_key)
self.columns.insert(0, '')
l = QGridLayout()
self.setLayout(l)
- l.setColumnStretch(1, 10)
- l.setColumnMinimumWidth(1, 300)
+ l.setColumnStretch(2, 10)
+ l.setColumnMinimumWidth(2, 300)
+
+ h = QLabel(_('And'))
+ h.setToolTip('
'
+ '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 08/18] 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)