Start work on CSS rule transformer

This commit is contained in:
Kovid Goyal 2016-03-08 15:52:26 +05:30
parent b1664c7417
commit 5ca8ccf78a
2 changed files with 222 additions and 0 deletions

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import (unicode_literals, division, absolute_import,
print_function)
import regex
REGEX_FLAGS = regex.VERSION1 | regex.UNICODE
def compile_pat(pat):
return regex.compile(pat, flags=REGEX_FLAGS)
def parse_length(raw):
raise NotImplementedError('TODO: implement this')

View File

@ -0,0 +1,206 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import (unicode_literals, division, absolute_import,
print_function)
from collections import OrderedDict
from PyQt5.Qt import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QLineEdit, QListWidgetItem
)
from calibre.ebooks.css_transform_rules import compile_pat, parse_length
from calibre.gui2 import error_dialog, elided_text
from calibre.gui2.tag_mapper import RuleEditDialog as RuleEditDialogBase, Rules as RulesBase
class RuleEdit(QWidget): # {{{
ACTION_MAP = OrderedDict((
('remove', _('Remove the property')),
('append', _('Add extra properties')),
('change', _('Change the value to')),
('*', _('Multiply the value by')),
('/', _('Divide the value by')),
('+', _('Add to the value')),
('-', _('Subtract from the value')),
))
MATCH_TYPE_MAP = OrderedDict((
('==', _('is')),
('<', _('is less than')),
('>', _('is greater than')),
('<=', _('is less than or equal to')),
('>=', _('is greater than or equal to')),
('matches', _('matches pattern')),
('not_matches', _('does not match pattern'))
))
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.l = l = QVBoxLayout(self)
self.h = h = QHBoxLayout()
self.la = la = QLabel(self.MSG)
la.setWordWrap(True)
l.addWidget(la)
l.addLayout(h)
english_sentence = '{preamble} {property} {match_type} {query}'
sentence = _('{preamble} {property} {match_type} {query}')
if set(sentence.split()) != set(english_sentence.split()):
sentence = english_sentence
parts = sentence.split()
for clause in parts:
if clause == '{preamble}':
self.preamble = w = QLabel(_('If the &property:'))
elif clause == '{property}':
self.property = w = QLineEdit(self)
w.setToolTip(_('The name of a CSS property, for example: font-size\n'))
elif clause == '{match_type}':
self.match_type = w = QComboBox(self)
for action, text in self.MATCH_TYPE_MAP.iteritems():
w.addItem(text, action)
elif clause == '{query}':
self.query = w = QLineEdit(self)
h.addWidget(w)
if clause is not parts[-1]:
h.addWidget(QLabel('\xa0'))
self.h2 = h = QHBoxLayout()
l.addLayout(h)
english_sentence = '{action} {action_data}'
sentence = _('{action} {action_data}')
if set(sentence.split()) != set(english_sentence.split()):
sentence = english_sentence
parts = sentence.split()
for clause in parts:
if clause == '{action}':
self.action = w = QComboBox(self)
for action, text in self.ACTION_MAP.iteritems():
w.addItem(text, action)
elif clause == '{action_data}':
self.action_data = w = QLineEdit(self)
h.addWidget(w)
if clause is not parts[-1]:
h.addWidget(QLabel('\xa0'))
self.update_state()
def sizeHint(self):
a = QWidget.sizeHint(self)
a.setHeight(a.height() + 75)
a.setWidth(a.width() + 100)
return a
def update_state(self):
r = self.rule
self.action_data.setVisible(r['action'] != 'remove')
tt = _('The CSS property value')
mt = r['match_type']
if 'matches' in mt:
tt = _('A regular expression')
elif mt in '< > <= >='.split():
tt = _('Either a CSS length, such as 10pt or a unit less number. If a unitless'
' number is used it will compared with the CSS value using whatever unit'
' the value has.')
self.query.setToolTip(tt)
tt = ''
ac = r['action']
if ac == 'append':
tt = _('CSS properties for to add to the rule that contains the matching style. You'
' can specify more than one property, separated by semi-colons, for example:'
' color:red; font-weight: bold')
elif ac in '+=*/':
tt = _('A number')
self.action_data.setToolTip(tt)
@property
def rule(self):
return {
'property':self.property.text().strip(),
'match_type': self.match_type.currentData(),
'query': self.query.text().strip(),
'action': self.action.currentData(),
'action_data': self.action_data.text().strip(),
}
@rule.setter
def rule(self, rule):
def sc(name):
c = getattr(self, name)
idx = c.findData(unicode(rule.get(name, '')))
if idx < 0:
idx = 0
c.setCurrentIndex(idx)
sc('action'), sc('match_type')
self.query.setText(unicode(rule.get('query', '')).strip())
self.action_data.setText(unicode(rule.get('action_data', '')).strip())
def validate(self):
rule = self.rule
if not rule['query']:
error_dialog(self, _('Query required'), _(
'You must specify a value for the CSS property to match'), show=True)
return False
mt = rule['match_type']
if 'matches' in mt:
try:
compile_pat(rule['query'])
except Exception:
error_dialog(self, _('Query invalid'), _(
'%s is not a valid regular expression') % rule['query'], show=True)
return False
elif mt in '< > <= >='.split():
try:
parse_length(rule['query'])
except Exception:
error_dialog(self, _('Query invalid'), _(
'%s is not a valid length or number') % rule['query'], show=True)
return False
ac, ad = rule['action'], rule['action_data']
if not ad and ac != 'remove':
msg = _('You must specify a number')
if ac == 'append':
msg = _('You must specify at least one CSS property to add')
elif ac == 'change':
msg = _('You must specify a value to change the property to')
error_dialog(self, _('No data'), msg, show=True)
return False
if ac in '+-*/':
try:
float(ad)
except Exception:
error_dialog(self, _('Invalid number'), _('%s is not a number') % ad, show=True)
return False
return True
# }}}
class RuleEditDialog(RuleEditDialogBase): # {{{
PREFS_NAME = 'edit-css-transform-rule'
DIALOG_TITLE = _('Edit rule')
RuleEditClass = RuleEdit
# }}}
class RuleItem(QListWidgetItem): # {{{
@staticmethod
def text_from_rule(rule, parent):
query = elided_text(rule['query'], font=parent.font(), width=200, pos='right')
text = _(
'If the property <i>{property}</i> <b>{match_type}</b> <b>{query}</b><br>{action}').format(
property=rule['property'], action=RuleEdit.ACTION_MAP[rule['action']],
match_type=RuleEdit.MATCH_TYPE_MAP[rule['match_type']], query=query)
if rule['action_data']:
ad = elided_text(rule['action_data'], font=parent.font(), width=200, pos='right')
text += ' <code>%s</code>' % ad
return text
# }}}
class Rules(RulesBase):
RuleItemClass = RuleItem
RuleEditDialogClass = RuleEditDialog
MSG = _('You can specify rules to transform styles here. Click the "Add Rule" button'
' below to get started.')