From 56b239991cc6a61e71a8208257d3e8e4eb4197a1 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 30 May 2011 14:56:52 +0100 Subject: [PATCH 1/2] 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('
' + + _('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