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 02/12] 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 04/12] 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