mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Conversion: Implement style transformation via rules. Look under the "Transform Styles" tab of the Look & Feel section of the conversion dialog
This commit is contained in:
parent
941f395ca3
commit
fa0533ec0d
@ -6,6 +6,8 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import json
|
||||
|
||||
from PyQt5.Qt import Qt, QSize
|
||||
|
||||
from calibre.gui2.convert.look_and_feel_ui import Ui_Form
|
||||
@ -40,7 +42,7 @@ class LookAndFeelWidget(Widget, Ui_Form):
|
||||
'insert_blank_line_size',
|
||||
'input_encoding', 'filter_css', 'expand_css',
|
||||
'asciiize', 'keep_ligatures',
|
||||
'linearize_tables']
|
||||
'linearize_tables', 'transform_css_rules']
|
||||
)
|
||||
for val, text in [
|
||||
('original', _('Original')),
|
||||
@ -80,6 +82,8 @@ class LookAndFeelWidget(Widget, Ui_Form):
|
||||
val = unicode(g.text()).strip()
|
||||
val = [x.strip() for x in val.split(',' if ',' in val else ' ') if x.strip()]
|
||||
return ', '.join(val) or None
|
||||
if g is self.opt_transform_css_rules:
|
||||
return json.dumps(g.rules)
|
||||
return Widget.get_value_handler(self, g)
|
||||
|
||||
def set_value_handler(self, g, val):
|
||||
@ -106,6 +110,9 @@ class LookAndFeelWidget(Widget, Ui_Form):
|
||||
if g is self.opt_extra_css:
|
||||
g.load_text(val or '', 'css')
|
||||
return True
|
||||
if g is self.opt_transform_css_rules:
|
||||
g.rules = json.loads(val) if val else []
|
||||
return True
|
||||
|
||||
def connect_gui_obj_handler(self, gui_obj, slot):
|
||||
if gui_obj is self.opt_filter_css:
|
||||
@ -114,6 +121,9 @@ class LookAndFeelWidget(Widget, Ui_Form):
|
||||
w.stateChanged.connect(slot)
|
||||
self.filter_css_others.textChanged.connect(slot)
|
||||
return
|
||||
if gui_obj is self.opt_transform_css_rules:
|
||||
gui_obj.changed.connect(slot)
|
||||
return
|
||||
raise NotImplementedError()
|
||||
|
||||
def font_key_wizard(self):
|
||||
|
@ -475,6 +475,31 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_4">
|
||||
<attribute name="title">
|
||||
<string>Transform styles</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="RulesWidget" name="opt_transform_css_rules" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -495,6 +520,12 @@
|
||||
<extends>QPlainTextEdit</extends>
|
||||
<header>calibre/gui2/tweak_book/editor/text.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RulesWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>calibre/gui2/css_transform_rules.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../../resources/images.qrc"/>
|
||||
|
@ -6,15 +6,16 @@ from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
from PyQt5.Qt import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QLineEdit, QPushButton, QSize
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QLineEdit,
|
||||
QPushButton, QSize, pyqtSignal, QMenu
|
||||
)
|
||||
|
||||
from calibre.ebooks.css_transform_rules import (
|
||||
validate_rule, safe_parser, compile_rules, transform_sheet, ACTION_MAP, MATCH_TYPE_MAP)
|
||||
from calibre.gui2 import error_dialog, elided_text
|
||||
validate_rule, safe_parser, compile_rules, transform_sheet, ACTION_MAP, MATCH_TYPE_MAP, export_rules, import_rules)
|
||||
from calibre.gui2 import error_dialog, elided_text, choose_save_file, choose_files
|
||||
from calibre.gui2.tag_mapper import (
|
||||
RuleEditDialog as RuleEditDialogBase, Rules as RulesBase, RulesDialog as
|
||||
RulesDialogBase, RuleItem as RuleItemBase)
|
||||
RulesDialogBase, RuleItem as RuleItemBase, SaveLoadMixin)
|
||||
from calibre.gui2.widgets2 import Dialog
|
||||
from calibre.utils.config import JSONConfig
|
||||
|
||||
@ -152,6 +153,7 @@ class RuleItem(RuleItemBase): # {{{
|
||||
|
||||
@staticmethod
|
||||
def text_from_rule(rule, parent):
|
||||
try:
|
||||
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(
|
||||
@ -160,6 +162,10 @@ class RuleItem(RuleItemBase): # {{{
|
||||
if rule['action_data']:
|
||||
ad = elided_text(rule['action_data'], font=parent.font(), width=200, pos='right')
|
||||
text += ' <code>%s</code>' % ad
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
text = _('This rule is invalid, please remove it')
|
||||
return text
|
||||
# }}}
|
||||
|
||||
@ -218,7 +224,7 @@ class Tester(Dialog): # {{{
|
||||
return QSize(800, 600)
|
||||
# }}}
|
||||
|
||||
class RulesDialog(RulesDialogBase):
|
||||
class RulesDialog(RulesDialogBase): # {{{
|
||||
|
||||
DIALOG_TITLE = _('Edit style transform rules')
|
||||
PREFS_NAME = 'edit-style-transform-rules'
|
||||
@ -230,6 +236,85 @@ class RulesDialog(RulesDialogBase):
|
||||
# multiple processes
|
||||
self.PREFS_OBJECT = JSONConfig('style-transform-rules')
|
||||
RulesDialogBase.__init__(self, *args, **kw)
|
||||
# }}}
|
||||
|
||||
class RulesWidget(QWidget, SaveLoadMixin): # {{{
|
||||
|
||||
changed = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.loaded_ruleset = None
|
||||
QWidget.__init__(self, parent)
|
||||
self.PREFS_OBJECT = JSONConfig('style-transform-rules')
|
||||
l = QVBoxLayout(self)
|
||||
self.rules_widget = w = Rules(self)
|
||||
w.changed.connect(self.changed.emit)
|
||||
l.addWidget(w)
|
||||
self.h = h = QHBoxLayout()
|
||||
l.addLayout(h)
|
||||
self.export_button = b = QPushButton(_('E&xport'), self)
|
||||
b.setToolTip(_('Export these rules to a file'))
|
||||
b.clicked.connect(self.export_rules)
|
||||
h.addWidget(b)
|
||||
self.import_button = b = QPushButton(_('&Import'), self)
|
||||
b.setToolTip(_('Import previously exported rules'))
|
||||
b.clicked.connect(self.import_rules)
|
||||
h.addWidget(b)
|
||||
self.test_button = b = QPushButton(_('&Test rules'), self)
|
||||
b.clicked.connect(self.test_rules)
|
||||
h.addWidget(b)
|
||||
h.addStretch(10)
|
||||
self.save_button = b = QPushButton(_('&Save'), self)
|
||||
b.setToolTip(_('Save this ruleset for later re-use'))
|
||||
b.clicked.connect(self.save_ruleset)
|
||||
h.addWidget(b)
|
||||
self.export_button = b = QPushButton(_('&Load'), self)
|
||||
self.load_menu = QMenu(self)
|
||||
b.setMenu(self.load_menu)
|
||||
b.setToolTip(_('Load a previously saved ruleset'))
|
||||
b.clicked.connect(self.load_ruleset)
|
||||
h.addWidget(b)
|
||||
self.build_load_menu()
|
||||
|
||||
def export_rules(self):
|
||||
rules = self.rules_widget.rules
|
||||
if not rules:
|
||||
return error_dialog(self, _('No rules'), _(
|
||||
'There are no rules to export'), show=True)
|
||||
path = choose_save_file(self, 'export-style-transform-rules', _('Choose file for exported rules'), initial_filename='rules.txt')
|
||||
if path:
|
||||
raw = export_rules(rules)
|
||||
with open(path, 'wb') as f:
|
||||
f.write(raw)
|
||||
|
||||
def import_rules(self):
|
||||
paths = choose_files(self, 'export-style-transform-rules', _('Choose file to import rules from'), select_only_single_file=True)
|
||||
if paths:
|
||||
with open(paths[0], 'rb') as f:
|
||||
rules = import_rules(f.read())
|
||||
self.rules_widget.rules = list(rules) + list(self.rules_widget.rules)
|
||||
self.changed.emit()
|
||||
|
||||
def load_ruleset(self, name):
|
||||
SaveLoadMixin.load_ruleset(self, name)
|
||||
self.changed.emit()
|
||||
|
||||
def test_rules(self):
|
||||
Tester(self.rules_widget.rules, self).exec_()
|
||||
|
||||
@property
|
||||
def rules(self):
|
||||
return self.rules_widget.rules
|
||||
|
||||
@rules.setter
|
||||
def rules(self, val):
|
||||
try:
|
||||
self.rules_widget.rules = val or []
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.rules_widget.rules = []
|
||||
# }}}
|
||||
|
||||
if __name__ == '__main__':
|
||||
from calibre.gui2 import Application
|
||||
|
@ -11,7 +11,7 @@ from functools import partial
|
||||
from PyQt5.Qt import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QListWidget, QIcon,
|
||||
QSize, QComboBox, QLineEdit, QListWidgetItem, QStyledItemDelegate,
|
||||
QStaticText, Qt, QStyle, QToolButton, QInputDialog, QMenu
|
||||
QStaticText, Qt, QStyle, QToolButton, QInputDialog, QMenu, pyqtSignal
|
||||
)
|
||||
|
||||
from calibre.ebooks.metadata.tag_mapper import map_tags, compile_pat
|
||||
@ -194,6 +194,7 @@ class Rules(QWidget):
|
||||
|
||||
RuleItemClass = RuleItem
|
||||
RuleEditDialogClass = RuleEditDialog
|
||||
changed = pyqtSignal()
|
||||
|
||||
MSG = _('You can specify rules to filter/transform tags here. Click the "Add Rule" button'
|
||||
' below to get started. The rules will be processed in order for every tag until either a'
|
||||
@ -249,6 +250,7 @@ class Rules(QWidget):
|
||||
if d.exec_() == d.Accepted:
|
||||
i = self.RuleItemClass(d.edit_widget.rule, self.rule_list)
|
||||
self.rule_list.scrollToItem(i)
|
||||
self.changed.emit()
|
||||
|
||||
def edit_rule(self):
|
||||
i = self.rule_list.currentItem()
|
||||
@ -259,10 +261,15 @@ class Rules(QWidget):
|
||||
rule = d.edit_widget.rule
|
||||
i.setData(DATA_ROLE, rule)
|
||||
i.setData(RENDER_ROLE, self.RuleItemClass.text_from_rule(rule, self.rule_list))
|
||||
self.changed.emit()
|
||||
|
||||
def remove_rules(self):
|
||||
changed = False
|
||||
for item in self.rule_list.selectedItems():
|
||||
self.rule_list.takeItem(self.rule_list.row(item))
|
||||
changed = True
|
||||
if changed:
|
||||
self.changed.emit()
|
||||
|
||||
def move_up(self):
|
||||
i = self.rule_list.currentItem()
|
||||
@ -272,6 +279,7 @@ class Rules(QWidget):
|
||||
self.rule_list.takeItem(row)
|
||||
self.rule_list.insertItem(row - 1, i)
|
||||
self.rule_list.setCurrentItem(i)
|
||||
self.changed.emit()
|
||||
|
||||
def move_down(self):
|
||||
i = self.rule_list.currentItem()
|
||||
@ -281,6 +289,7 @@ class Rules(QWidget):
|
||||
self.rule_list.takeItem(row)
|
||||
self.rule_list.insertItem(row + 1, i)
|
||||
self.rule_list.setCurrentItem(i)
|
||||
self.changed.emit()
|
||||
|
||||
@property
|
||||
def rules(self):
|
||||
@ -342,41 +351,7 @@ class Tester(Dialog):
|
||||
ans.setWidth(ans.width() + 150)
|
||||
return ans
|
||||
|
||||
class RulesDialog(Dialog):
|
||||
|
||||
DIALOG_TITLE = _('Edit tag mapper rules')
|
||||
PREFS_NAME = 'edit-tag-mapper-rules'
|
||||
RulesClass = Rules
|
||||
TesterClass = Tester
|
||||
PREFS_OBJECT = tag_maps
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.loaded_ruleset = None
|
||||
Dialog.__init__(self, self.DIALOG_TITLE, self.PREFS_NAME, parent=parent)
|
||||
|
||||
def setup_ui(self):
|
||||
self.l = l = QVBoxLayout(self)
|
||||
self.edit_widget = w = self.RulesClass(self)
|
||||
l.addWidget(w)
|
||||
l.addWidget(self.bb)
|
||||
self.save_button = b = self.bb.addButton(_('&Save'), self.bb.ActionRole)
|
||||
b.setToolTip(_('Save this ruleset for later re-use'))
|
||||
b.clicked.connect(self.save_ruleset)
|
||||
self.load_button = b = self.bb.addButton(_('&Load'), self.bb.ActionRole)
|
||||
b.setToolTip(_('Load a previously saved ruleset'))
|
||||
self.load_menu = QMenu(self)
|
||||
b.setMenu(self.load_menu)
|
||||
self.build_load_menu()
|
||||
self.test_button = b = self.bb.addButton(_('&Test rules'), self.bb.ActionRole)
|
||||
b.clicked.connect(self.test_rules)
|
||||
|
||||
@property
|
||||
def rules(self):
|
||||
return self.edit_widget.rules
|
||||
|
||||
@rules.setter
|
||||
def rules(self, rules):
|
||||
self.edit_widget.rules = rules
|
||||
class SaveLoadMixin(object):
|
||||
|
||||
def save_ruleset(self):
|
||||
if not self.rules:
|
||||
@ -418,6 +393,42 @@ class RulesDialog(Dialog):
|
||||
del self.PREFS_OBJECT[name]
|
||||
self.build_load_menu()
|
||||
|
||||
class RulesDialog(Dialog, SaveLoadMixin):
|
||||
|
||||
DIALOG_TITLE = _('Edit tag mapper rules')
|
||||
PREFS_NAME = 'edit-tag-mapper-rules'
|
||||
RulesClass = Rules
|
||||
TesterClass = Tester
|
||||
PREFS_OBJECT = tag_maps
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.loaded_ruleset = None
|
||||
Dialog.__init__(self, self.DIALOG_TITLE, self.PREFS_NAME, parent=parent)
|
||||
|
||||
def setup_ui(self):
|
||||
self.l = l = QVBoxLayout(self)
|
||||
self.edit_widget = w = self.RulesClass(self)
|
||||
l.addWidget(w)
|
||||
l.addWidget(self.bb)
|
||||
self.save_button = b = self.bb.addButton(_('&Save'), self.bb.ActionRole)
|
||||
b.setToolTip(_('Save this ruleset for later re-use'))
|
||||
b.clicked.connect(self.save_ruleset)
|
||||
self.load_button = b = self.bb.addButton(_('&Load'), self.bb.ActionRole)
|
||||
b.setToolTip(_('Load a previously saved ruleset'))
|
||||
self.load_menu = QMenu(self)
|
||||
b.setMenu(self.load_menu)
|
||||
self.build_load_menu()
|
||||
self.test_button = b = self.bb.addButton(_('&Test rules'), self.bb.ActionRole)
|
||||
b.clicked.connect(self.test_rules)
|
||||
|
||||
@property
|
||||
def rules(self):
|
||||
return self.edit_widget.rules
|
||||
|
||||
@rules.setter
|
||||
def rules(self, rules):
|
||||
self.edit_widget.rules = rules
|
||||
|
||||
def test_rules(self):
|
||||
self.TesterClass(self.rules, self).exec_()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user