mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Finish implementation of style transform rules dialog
This commit is contained in:
parent
70a36a10b4
commit
acd87396d9
@ -7,7 +7,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
from cssutils.css import Property
|
from cssutils.css import Property, CSSRule
|
||||||
import regex
|
import regex
|
||||||
|
|
||||||
from calibre import force_unicode
|
from calibre import force_unicode
|
||||||
@ -199,7 +199,7 @@ def validate_rule(rule):
|
|||||||
if rule['property'] in normalizers:
|
if rule['property'] in normalizers:
|
||||||
return _('Shorthand property not allowed'), _(
|
return _('Shorthand property not allowed'), _(
|
||||||
'{0} is a shorthand property. Use the full form of the property,'
|
'{0} is a shorthand property. Use the full form of the property,'
|
||||||
' for example, instead of font, use font-family, instead of margin, use margin-top, etc.')
|
' for example, instead of font, use font-family, instead of margin, use margin-top, etc.').format(rule['property'])
|
||||||
if not rule['query'] and mt != '*':
|
if not rule['query'] and mt != '*':
|
||||||
_('Query required'), _(
|
_('Query required'), _(
|
||||||
'You must specify a value for the CSS property to match')
|
'You must specify a value for the CSS property to match')
|
||||||
@ -232,6 +232,33 @@ def validate_rule(rule):
|
|||||||
return _('Invalid number'), _('%s is not a number') % ad
|
return _('Invalid number'), _('%s is not a number') % ad
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
def compile_rules(serialized_rules):
|
||||||
|
return [Rule(**r) for r in serialized_rules]
|
||||||
|
|
||||||
|
def transform_declaration(compiled_rules, decl):
|
||||||
|
decl = StyleDeclaration(decl)
|
||||||
|
changed = False
|
||||||
|
for rule in compiled_rules:
|
||||||
|
if rule.process_declaration(decl):
|
||||||
|
changed = True
|
||||||
|
return changed
|
||||||
|
|
||||||
|
def transform_sheet(compiled_rules, sheet):
|
||||||
|
changed = False
|
||||||
|
for rule in sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE):
|
||||||
|
if transform_declaration(compiled_rules, rule.style):
|
||||||
|
changed = True
|
||||||
|
return changed
|
||||||
|
|
||||||
|
def transform_container(container, serialized_rules, names=()):
|
||||||
|
from calibre.ebooks.oeb.polish.css import transform_css
|
||||||
|
rules = compile_rules(serialized_rules)
|
||||||
|
return transform_css(
|
||||||
|
container, transform_sheet=partial(transform_sheet, rules),
|
||||||
|
transform_style=partial(transform_declaration, rules), names=names
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test(): # {{{
|
def test(): # {{{
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
@ -7,12 +7,16 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QLineEdit, QListWidgetItem
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QLineEdit, QPushButton, QSize
|
||||||
)
|
)
|
||||||
|
|
||||||
from calibre.ebooks.css_transform_rules import validate_rule
|
from calibre.ebooks.css_transform_rules import validate_rule, safe_parser, compile_rules, transform_sheet
|
||||||
from calibre.gui2 import error_dialog, elided_text
|
from calibre.gui2 import error_dialog, elided_text
|
||||||
from calibre.gui2.tag_mapper import RuleEditDialog as RuleEditDialogBase, Rules as RulesBase
|
from calibre.gui2.tag_mapper import (
|
||||||
|
RuleEditDialog as RuleEditDialogBase, Rules as RulesBase, RulesDialog as
|
||||||
|
RulesDialogBase, RuleItem as RuleItemBase)
|
||||||
|
from calibre.gui2.widgets2 import Dialog
|
||||||
|
from calibre.utils.config import JSONConfig
|
||||||
|
|
||||||
class RuleEdit(QWidget): # {{{
|
class RuleEdit(QWidget): # {{{
|
||||||
|
|
||||||
@ -31,7 +35,7 @@ class RuleEdit(QWidget): # {{{
|
|||||||
('is_not', _('is not')),
|
('is_not', _('is not')),
|
||||||
('*', _('is any value')),
|
('*', _('is any value')),
|
||||||
('matches', _('matches pattern')),
|
('matches', _('matches pattern')),
|
||||||
('not_matches', _('does not match pattern'))
|
('not_matches', _('does not match pattern')),
|
||||||
('==', _('is the same length as')),
|
('==', _('is the same length as')),
|
||||||
('!=', _('is not the same length as')),
|
('!=', _('is not the same length as')),
|
||||||
('<', _('is less than')),
|
('<', _('is less than')),
|
||||||
@ -39,6 +43,7 @@ class RuleEdit(QWidget): # {{{
|
|||||||
('<=', _('is less than or equal to')),
|
('<=', _('is less than or equal to')),
|
||||||
('>=', _('is greater than or equal to')),
|
('>=', _('is greater than or equal to')),
|
||||||
))
|
))
|
||||||
|
MSG = _('Create the rule below, the rule can be used to transform style properties')
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
@ -66,11 +71,13 @@ class RuleEdit(QWidget): # {{{
|
|||||||
self.match_type = w = QComboBox(self)
|
self.match_type = w = QComboBox(self)
|
||||||
for action, text in self.MATCH_TYPE_MAP.iteritems():
|
for action, text in self.MATCH_TYPE_MAP.iteritems():
|
||||||
w.addItem(text, action)
|
w.addItem(text, action)
|
||||||
|
w.currentIndexChanged.connect(self.update_state)
|
||||||
elif clause == '{query}':
|
elif clause == '{query}':
|
||||||
self.query = w = QLineEdit(self)
|
self.query = w = QLineEdit(self)
|
||||||
h.addWidget(w)
|
h.addWidget(w)
|
||||||
if clause is not parts[-1]:
|
if clause is not parts[-1]:
|
||||||
h.addWidget(QLabel('\xa0'))
|
h.addWidget(QLabel('\xa0'))
|
||||||
|
self.preamble.setBuddy(self.property)
|
||||||
|
|
||||||
self.h2 = h = QHBoxLayout()
|
self.h2 = h = QHBoxLayout()
|
||||||
l.addLayout(h)
|
l.addLayout(h)
|
||||||
@ -84,6 +91,7 @@ class RuleEdit(QWidget): # {{{
|
|||||||
self.action = w = QComboBox(self)
|
self.action = w = QComboBox(self)
|
||||||
for action, text in self.ACTION_MAP.iteritems():
|
for action, text in self.ACTION_MAP.iteritems():
|
||||||
w.addItem(text, action)
|
w.addItem(text, action)
|
||||||
|
w.currentIndexChanged.connect(self.update_state)
|
||||||
elif clause == '{action_data}':
|
elif clause == '{action_data}':
|
||||||
self.action_data = w = QLineEdit(self)
|
self.action_data = w = QLineEdit(self)
|
||||||
h.addWidget(w)
|
h.addWidget(w)
|
||||||
@ -142,8 +150,10 @@ class RuleEdit(QWidget): # {{{
|
|||||||
idx = 0
|
idx = 0
|
||||||
c.setCurrentIndex(idx)
|
c.setCurrentIndex(idx)
|
||||||
sc('action'), sc('match_type')
|
sc('action'), sc('match_type')
|
||||||
|
self.property.setText(unicode(rule.get('property', '')).strip())
|
||||||
self.query.setText(unicode(rule.get('query', '')).strip())
|
self.query.setText(unicode(rule.get('query', '')).strip())
|
||||||
self.action_data.setText(unicode(rule.get('action_data', '')).strip())
|
self.action_data.setText(unicode(rule.get('action_data', '')).strip())
|
||||||
|
self.update_state()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
rule = self.rule
|
rule = self.rule
|
||||||
@ -161,7 +171,7 @@ class RuleEditDialog(RuleEditDialogBase): # {{{
|
|||||||
RuleEditClass = RuleEdit
|
RuleEditClass = RuleEdit
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class RuleItem(QListWidgetItem): # {{{
|
class RuleItem(RuleItemBase): # {{{
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def text_from_rule(rule, parent):
|
def text_from_rule(rule, parent):
|
||||||
@ -176,10 +186,82 @@ class RuleItem(QListWidgetItem): # {{{
|
|||||||
return text
|
return text
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class Rules(RulesBase):
|
class Rules(RulesBase): # {{{
|
||||||
|
|
||||||
RuleItemClass = RuleItem
|
RuleItemClass = RuleItem
|
||||||
RuleEditDialogClass = RuleEditDialog
|
RuleEditDialogClass = RuleEditDialog
|
||||||
|
|
||||||
MSG = _('You can specify rules to transform styles here. Click the "Add Rule" button'
|
MSG = _('You can specify rules to transform styles here. Click the "Add Rule" button'
|
||||||
' below to get started.')
|
' below to get started.')
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class Tester(Dialog): # {{{
|
||||||
|
|
||||||
|
DIALOG_TITLE = _('Test style transform rules')
|
||||||
|
PREFS_NAME = 'test-style-transform-rules'
|
||||||
|
LABEL = _('Enter a CSS stylesheet below to test')
|
||||||
|
|
||||||
|
def __init__(self, rules, parent=None):
|
||||||
|
self.rules = compile_rules(rules)
|
||||||
|
Dialog.__init__(self, self.DIALOG_TITLE, self.PREFS_NAME, parent=parent)
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
from calibre.gui2.tweak_book.editor.text import TextEdit
|
||||||
|
self.l = l = QVBoxLayout(self)
|
||||||
|
self.bb.setStandardButtons(self.bb.Close)
|
||||||
|
self.la = la = QLabel(self.LABEL)
|
||||||
|
l.addWidget(la)
|
||||||
|
self.css = t = TextEdit(self)
|
||||||
|
t.load_text('/* %s */\n' % _('Enter CSS rules below and click the Test button'), 'css')
|
||||||
|
la.setBuddy(t)
|
||||||
|
c = t.textCursor()
|
||||||
|
c.movePosition(c.End)
|
||||||
|
t.setTextCursor(c)
|
||||||
|
self.h = h = QHBoxLayout()
|
||||||
|
l.addLayout(h)
|
||||||
|
h.addWidget(t)
|
||||||
|
self.test_button = b = QPushButton(_('&Test'), self)
|
||||||
|
b.clicked.connect(self.do_test)
|
||||||
|
h.addWidget(b)
|
||||||
|
self.result = la = TextEdit(self)
|
||||||
|
la.setReadOnly(True)
|
||||||
|
l.addWidget(la)
|
||||||
|
l.addWidget(self.bb)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self.css.toPlainText()
|
||||||
|
|
||||||
|
def do_test(self):
|
||||||
|
decl = safe_parser().parseString(self.value)
|
||||||
|
transform_sheet(self.rules, decl)
|
||||||
|
self.result.load_text('/* %s */\n\n%s' % (_('Resulting stylesheet'), decl.cssText), 'css')
|
||||||
|
|
||||||
|
def sizeHint(self):
|
||||||
|
return QSize(800, 600)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class RulesDialog(RulesDialogBase):
|
||||||
|
|
||||||
|
DIALOG_TITLE = _('Edit style transform rules')
|
||||||
|
PREFS_NAME = 'edit-style-transform-rules'
|
||||||
|
RulesClass = Rules
|
||||||
|
TesterClass = Tester
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
# This has to be loaded on instantiation as it can be shared by
|
||||||
|
# multiple processes
|
||||||
|
self.PREFS_OBJECT = JSONConfig('style-transform-rules')
|
||||||
|
RulesDialogBase.__init__(self, *args, **kw)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from calibre.gui2 import Application
|
||||||
|
app = Application([])
|
||||||
|
d = RulesDialog()
|
||||||
|
d.rules = [
|
||||||
|
{'property':'color', 'match_type':'*', 'query':'', 'action':'change', 'action_data':'green'},
|
||||||
|
]
|
||||||
|
d.exec_()
|
||||||
|
from pprint import pprint
|
||||||
|
pprint(d.rules)
|
||||||
|
del d, app
|
||||||
|
Loading…
x
Reference in New Issue
Block a user