mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
...
This commit is contained in:
commit
ee942255a7
@ -5,12 +5,17 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import (QLineEdit, QDialog, QGridLayout, QLabel, QCheckBox,
|
||||
QDialogButtonBox, QColor, QComboBox, QIcon)
|
||||
from functools import partial
|
||||
from collections import defaultdict
|
||||
|
||||
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
|
||||
from calibre.utils.icu import sort_key
|
||||
|
||||
class TemplateLineEditor(QLineEdit):
|
||||
|
||||
@ -22,115 +27,190 @@ class TemplateLineEditor(QLineEdit):
|
||||
QLineEdit.__init__(self, parent)
|
||||
self.tags = None
|
||||
self.mi = None
|
||||
self.txt = None
|
||||
|
||||
def set_mi(self, mi):
|
||||
self.mi = mi
|
||||
|
||||
def set_tags(self, tags):
|
||||
self.tags = tags
|
||||
def set_db(self, db):
|
||||
self.db = db
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
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)
|
||||
if self.tags:
|
||||
action_tag_wizard = menu.addAction(_('Open Tag Wizard'))
|
||||
action_tag_wizard.triggered.connect(self.tag_wizard)
|
||||
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):
|
||||
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.tags, 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, tags, txt):
|
||||
def __init__(self, parent, db, txt, mi):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setWindowTitle(_('Tag Wizard'))
|
||||
self.setWindowTitle(_('Coloring Wizard'))
|
||||
self.setWindowIcon(QIcon(I('wizard.png')))
|
||||
|
||||
self.tags = tags
|
||||
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') and \
|
||||
m['is_category'] and k not in ('identifiers'):
|
||||
self.columns.append(k)
|
||||
if m['is_custom']:
|
||||
self.completion_values[k]['v'] = db.all_custom(m['label'])
|
||||
elif k == 'tags':
|
||||
self.completion_values[k]['v'] = db.all_tags()
|
||||
elif k == 'formats':
|
||||
self.completion_values[k]['v'] = db.all_formats()
|
||||
else:
|
||||
if k in ('publisher'):
|
||||
ck = k + 's'
|
||||
else:
|
||||
ck = k
|
||||
f = getattr(db, 'all_' + ck, None)
|
||||
if f:
|
||||
if k == 'authors':
|
||||
self.completion_values[k]['v'] = [v[1].\
|
||||
replace('|', ',') for v in f()]
|
||||
else:
|
||||
self.completion_values[k]['v'] = [v[1] for v in f()]
|
||||
|
||||
if k in self.completion_values:
|
||||
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(0, 1)
|
||||
l.setColumnMinimumWidth(0, 300)
|
||||
h = QLabel(_('Tags (see the popup help for more information)'))
|
||||
l.setColumnStretch(1, 10)
|
||||
l.setColumnMinimumWidth(1, 300)
|
||||
|
||||
h = QLabel(_('Column'))
|
||||
l.addWidget(h, 0, 0, 1, 1)
|
||||
|
||||
h = QLabel(_('Values (see the popup help for more information)'))
|
||||
h.setToolTip('<p>' +
|
||||
_('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.<br>'
|
||||
'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.<br>'
|
||||
'to the end of the column you are checking.<br>'
|
||||
'Regular expression examples:') + '<ul>' +
|
||||
_('<li><code><b>.*</b></code> matches any tag. No empty tags are '
|
||||
'checked, so you don\'t need to worry about empty strings</li>'
|
||||
'<li><code><b>A.*</b></code> matches any tag beginning with A</li>'
|
||||
'<li><code><b>.*mystery.*</b></code> matches any tag containing '
|
||||
_('<li><code><b>.*</b></code> matches anything in the column. No '
|
||||
'empty values are checked, so you don\'t need to worry about '
|
||||
'empty strings</li>'
|
||||
'<li><code><b>A.*</b></code> matches anything beginning with A</li>'
|
||||
'<li><code><b>.*mystery.*</b></code> matches anything containing '
|
||||
'the word "mystery"</li>') + '</ul></p>')
|
||||
l.addWidget(h , 0, 0, 1, 1)
|
||||
l.addWidget(h , 0, 1, 1, 1)
|
||||
|
||||
c = QLabel(_('is RE'))
|
||||
c.setToolTip('<p>' +
|
||||
_('Check this box if the tag box contains regular expressions') + '</p>')
|
||||
l.addWidget(c, 0, 1, 1, 1)
|
||||
_('Check this box if the values box contains regular expressions') + '</p>')
|
||||
l.addWidget(c, 0, 2, 1, 1)
|
||||
|
||||
c = QLabel(_('Color if tag found'))
|
||||
c = QLabel(_('Color if value found'))
|
||||
c.setToolTip('<p>' +
|
||||
_('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.') + '</p>')
|
||||
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('<p>' +
|
||||
_('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.') + '</p>')
|
||||
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 +221,75 @@ 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
|
||||
|
||||
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, 2, 1, 2)
|
||||
l.addWidget(bb, 100, 3, 1, 2)
|
||||
bb.accepted.connect(self.accepted)
|
||||
bb.rejected.connect(self.reject)
|
||||
self.template = ''
|
||||
|
||||
def accepted(self):
|
||||
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:
|
||||
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 generate_program(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 +303,39 @@ 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()
|
||||
return True
|
||||
|
||||
def accepted(self):
|
||||
if self.generate_program():
|
||||
self.accept()
|
||||
else:
|
||||
self.template = ''
|
||||
|
@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import (QApplication, QFont, QFontInfo, QFontDialog,
|
||||
QAbstractListModel, Qt, QColor)
|
||||
QAbstractListModel, Qt, QColor, QIcon)
|
||||
|
||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList
|
||||
from calibre.gui2.preferences.look_feel_ui import Ui_Form
|
||||
@ -167,11 +167,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
'<a href="http://manual.calibre-ebook.com/template_lang.html">'
|
||||
'tutorial</a> on using templates.') +
|
||||
'</p><p>' +
|
||||
_('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.') +
|
||||
'</p><p>' +
|
||||
_('The template must evaluate to one of the color names shown '
|
||||
'below. You can use any legal template expression. '
|
||||
@ -204,7 +203,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:
|
||||
@ -216,11 +214,16 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
for i in range(1, self.column_color_count):
|
||||
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_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)
|
||||
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')))
|
||||
all_colors = [unicode(s) for s in list(QColor.colorNames())]
|
||||
self.colors_box.setText(', '.join(all_colors))
|
||||
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user