From 9765d91570f6550ce77e6801cc0b99298db7bd41 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 7 May 2009 21:40:01 -0700 Subject: [PATCH 1/4] Widget for strucutre detection settings --- src/calibre/ebooks/comic/input.py | 1 + src/calibre/gui2/convert/__init__.py | 9 + src/calibre/gui2/convert/single.py | 11 +- .../gui2/convert/structure_detection.py | 42 + .../gui2/convert/structure_detection.ui | 103 ++ src/calibre/gui2/convert/xpath_edit.ui | 59 + src/calibre/gui2/convert/xpath_wizard.py | 89 ++ src/calibre/gui2/convert/xpath_wizard.ui | 143 ++ src/calibre/gui2/images/wizard.svg | 1385 +++++++++++++++++ upload.py | 2 + 10 files changed, 1842 insertions(+), 2 deletions(-) create mode 100644 src/calibre/gui2/convert/structure_detection.py create mode 100644 src/calibre/gui2/convert/structure_detection.ui create mode 100644 src/calibre/gui2/convert/xpath_edit.ui create mode 100644 src/calibre/gui2/convert/xpath_wizard.py create mode 100644 src/calibre/gui2/convert/xpath_wizard.ui create mode 100644 src/calibre/gui2/images/wizard.svg diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index e2a522a356..7a4faba3c2 100755 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -304,6 +304,7 @@ class ComicInput(InputFormatPlugin): ('chapter', None, OptionRecommendation.HIGH), ('page_breaks_brefore', None, OptionRecommendation.HIGH), ('use_auto_toc', False, OptionRecommendation.HIGH), + ('page_breaks_before', None, OptionRecommendation.HIGH), ]) def get_comics_from_collection(self, stream): diff --git a/src/calibre/gui2/convert/__init__.py b/src/calibre/gui2/convert/__init__.py index dd24d05120..ff04eeef91 100644 --- a/src/calibre/gui2/convert/__init__.py +++ b/src/calibre/gui2/convert/__init__.py @@ -154,6 +154,7 @@ class Widget(QWidget): def get_value(self, g): + from calibre.gui2.convert.xpath_wizard import XPathEdit ret = self.get_value_handler(g) if ret != 'this is a dummy return value, xcswx1avcx4x': return ret @@ -169,11 +170,14 @@ class Widget(QWidget): return unicode(g.currentText()) elif isinstance(g, QCheckBox): return bool(g.isChecked()) + elif isinstance(g, XPathEdit): + return g.xpath else: raise Exception('Can\'t get value from %s'%type(g)) def set_value(self, g, val): + from calibre.gui2.convert.xpath_wizard import XPathEdit if self.set_value_handler(g, val): return if isinstance(g, (QSpinBox, QDoubleSpinBox)): @@ -190,6 +194,8 @@ class Widget(QWidget): g.setCurrentIndex(idx) elif isinstance(g, QCheckBox): g.setCheckState(Qt.Checked if bool(val) else Qt.Unchecked) + elif isinstance(g, XPathEdit): + g.edit.setText(val if val else '') else: raise Exception('Can\'t set value %s in %s'%(repr(val), type(g))) self.post_set_value(g, val) @@ -223,6 +229,9 @@ class Widget(QWidget): def post_get_value(self, g): pass + def pre_commit_check(self): + return True + def commit(self, save_defaults=False): return self.commit_options(save_defaults) diff --git a/src/calibre/gui2/convert/single.py b/src/calibre/gui2/convert/single.py index 5c6832f58a..2b0cfd52d0 100644 --- a/src/calibre/gui2/convert/single.py +++ b/src/calibre/gui2/convert/single.py @@ -17,6 +17,8 @@ from calibre.gui2.convert.single_ui import Ui_Dialog from calibre.gui2.convert.metadata import MetadataWidget from calibre.gui2.convert.look_and_feel import LookAndFeelWidget from calibre.gui2.convert.page_setup import PageSetupWidget +from calibre.gui2.convert.structure_detection import StructureDetectionWidget + from calibre.ebooks.conversion.plumber import Plumber, supported_input_formats, \ INPUT_FORMAT_PREFERENCES, OUTPUT_FORMAT_PREFERENCES @@ -115,6 +117,7 @@ class Config(ResizableDialog, Ui_Dialog): def setup_pipeline(self, *args): + oidx = self.groups.currentIndex().row() input_format = self.input_format output_format = self.output_format input_path = self.db.format_abspath(self.book_id, input_format, @@ -134,6 +137,7 @@ class Config(ResizableDialog, Ui_Dialog): self.setWindowTitle(_('Convert')+ ' ' + unicode(self.mw.title.text())) lf = widget_factory(LookAndFeelWidget) ps = widget_factory(PageSetupWidget) + sd = widget_factory(StructureDetectionWidget) output_widget = None name = 'calibre.gui2.convert.%s' % self.plumber.output_plugin.name.lower().replace(' ', '_') @@ -162,7 +166,7 @@ class Config(ResizableDialog, Ui_Dialog): if not c: break self.stack.removeWidget(c) - widgets = [self.mw, lf, ps] + widgets = [self.mw, lf, ps, sd] if input_widget is not None: widgets.append(input_widget) if output_widget is not None: @@ -175,7 +179,8 @@ class Config(ResizableDialog, Ui_Dialog): self._groups_model = GroupModel(widgets) self.groups.setModel(self._groups_model) - self.groups.setCurrentIndex(self._groups_model.index(0)) + idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 + self.groups.setCurrentIndex(self._groups_model.index(idx)) def setup_input_output_formats(self, db, book_id, preferred_input_format, @@ -213,6 +218,8 @@ class Config(ResizableDialog, Ui_Dialog): def accept(self): recs = GuiRecommendations() for w in self._groups_model.widgets: + if not w.pre_commit_check(): + return x = w.commit(save_defaults=False) recs.update(x) self.opf_path, self.cover_path = self.mw.opf_file, self.mw.cover_file diff --git a/src/calibre/gui2/convert/structure_detection.py b/src/calibre/gui2/convert/structure_detection.py new file mode 100644 index 0000000000..a757ccb8b3 --- /dev/null +++ b/src/calibre/gui2/convert/structure_detection.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +from calibre.gui2.convert.structure_detection_ui import Ui_Form +from calibre.gui2.convert import Widget +from calibre.gui2 import error_dialog + +class StructureDetectionWidget(Widget, Ui_Form): + + TITLE = _('Structure\nDetection') + ICON = ':/images/chapters.svg' + HELP = _('Fine tune the detection of chapter headings and ' + 'other document structure.') + + def __init__(self, parent, get_option, get_help, db=None, book_id=None): + Widget.__init__(self, parent, 'structure_detection', + ['chapter', 'chapter_mark', + 'remove_first_image', + 'insert_metadata', 'page_breaks_before', + 'preprocess_html'] + ) + self.db, self.book_id = db, book_id + self.initialize_options(get_option, get_help, db, book_id) + self.opt_chapter.set_msg(_('Detect chapters at (XPath expression):')) + self.opt_page_breaks_before.set_msg(_('Insert page breaks before ' + '(XPath expression):')) + + + def pre_commit_check(self): + for x in ('chapter', 'page_breaks_before'): + x = getattr(self, 'opt_'+x) + if not x.check(): + error_dialog(self, _('Invalid XPath'), + _('The XPath expression %s is invalid.')%x.xpath).exec_() + return False + return True diff --git a/src/calibre/gui2/convert/structure_detection.ui b/src/calibre/gui2/convert/structure_detection.ui new file mode 100644 index 0000000000..768b430c5a --- /dev/null +++ b/src/calibre/gui2/convert/structure_detection.ui @@ -0,0 +1,103 @@ + + + Form + + + + 0 + 0 + 657 + 479 + + + + Form + + + + + + Chapter &mark: + + + opt_chapter_mark + + + + + + + + pagebreak + + + + + rule + + + + + both + + + + + none + + + + + + + + Remove first &image + + + + + + + Insert &metadata as page at start of book + + + + + + + &Preprocess input file to possibly improve structure detection + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + XPathEdit + QWidget +
convert/xpath_wizard.h
+ 1 +
+
+ + +
diff --git a/src/calibre/gui2/convert/xpath_edit.ui b/src/calibre/gui2/convert/xpath_edit.ui new file mode 100644 index 0000000000..c1e8946635 --- /dev/null +++ b/src/calibre/gui2/convert/xpath_edit.ui @@ -0,0 +1,59 @@ + + Form + + + + 0 + 0 + 400 + 64 + + + + Form + + + + + + + + TextLabel + + + true + + + + + + + + + + + + Use a wizard to help construct the XPath expression + + + ... + + + + :/images/wizard.svg:/images/wizard.svg + + + + 40 + 40 + + + + + + + + + + + diff --git a/src/calibre/gui2/convert/xpath_wizard.py b/src/calibre/gui2/convert/xpath_wizard.py new file mode 100644 index 0000000000..a98db16c09 --- /dev/null +++ b/src/calibre/gui2/convert/xpath_wizard.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QDialog, QWidget, SIGNAL, Qt, QDialogButtonBox, QVBoxLayout + +from calibre.gui2.convert.xpath_wizard_ui import Ui_Form +from calibre.gui2.convert.xpath_edit_ui import Ui_Form as Ui_Edit + + +class WizardWidget(QWidget, Ui_Form): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + + @property + def xpath(self): + tag = unicode(self.tag.currentText()).strip() + if tag != '*': + tag = 'h:'+tag + attr, val = map(unicode, (self.attribute.text(), self.value.text())) + attr, val = attr.strip(), val.strip() + q = '' + if attr: + if val: + q = '[re:test(@%s, "%s", "i")]'%(attr, val) + else: + q = '[@%s]'%attr + expr = '//'+tag + q + return expr + +class Wizard(QDialog): + + def __init__(self, parent=None): + QDialog.__init__(self, parent) + self.resize(400, 300) + self.verticalLayout = QVBoxLayout(self) + self.widget = WizardWidget(self) + self.verticalLayout.addWidget(self.widget) + self.buttonBox = QDialogButtonBox(self) + self.buttonBox.setOrientation(Qt.Horizontal) + self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) + self.verticalLayout.addWidget(self.buttonBox) + + self.connect(self.buttonBox, SIGNAL("accepted()"), self.accept) + self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject) + self.setModal(Qt.WindowModal) + + @property + def xpath(self): + return self.widget.xpath + + +class XPathEdit(QWidget, Ui_Edit): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + self.connect(self.button, SIGNAL('clicked()'), self.wizard) + + def wizard(self): + wiz = Wizard(self) + if wiz.exec_() == wiz.Accepted: + self.edit.setText(wiz.xpath) + + + def set_msg(self, msg): + self.msg.setText(msg) + + @property + def text(self): + return unicode(self.edit.text()) + + def check(self): + from calibre.ebooks.oeb.base import XPNSMAP + from lxml.etree import XPath + try: + XPath(self.text, namespace=XPNSMAP) + except: + return False + return True + + + diff --git a/src/calibre/gui2/convert/xpath_wizard.ui b/src/calibre/gui2/convert/xpath_wizard.ui new file mode 100644 index 0000000000..c23a2d1b7c --- /dev/null +++ b/src/calibre/gui2/convert/xpath_wizard.ui @@ -0,0 +1,143 @@ + + Form + + + + 0 + 0 + 400 + 381 + + + + Form + + + + + + Match HTML &tags with tag name: + + + tag + + + + + + + true + + + + * + + + + + a + + + + + br + + + + + div + + + + + h1 + + + + + h2 + + + + + h3 + + + + + h4 + + + + + h5 + + + + + h6 + + + + + hr + + + + + span + + + + + + + + Having the &attribute: + + + attribute + + + + + + + + + + With &value: + + + value + + + + + + + + + + (A regular expression) + + + + + + + <p>For example, to match all h2 tags that have class="chapter", set tag to <i>h2</i>, attribute to <i>class</i> and value to <i>chapter</i>.</p><p>Leaving attribute blank will match any attribute and leaving value blank will match any value. Setting tag to * will match any tag.</p><p>To learn more advanced usage of XPath see the <a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath Tutorial</a>. + + + true + + + true + + + + + + + + diff --git a/src/calibre/gui2/images/wizard.svg b/src/calibre/gui2/images/wizard.svg new file mode 100644 index 0000000000..200fc4d7a7 --- /dev/null +++ b/src/calibre/gui2/images/wizard.svg @@ -0,0 +1,1385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Oxygen team + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/upload.py b/upload.py index 131c43c56c..9effeb0542 100644 --- a/upload.py +++ b/upload.py @@ -312,6 +312,8 @@ class gui(OptionlessCommand): dat = dat.replace('import images_rc', 'from calibre.gui2 import images_rc') dat = dat.replace('from library import', 'from calibre.gui2.library import') dat = dat.replace('from widgets import', 'from calibre.gui2.widgets import') + dat = dat.replace('from convert.xpath_wizard import', + 'from calibre.gui2.convert.xpath_wizard import') dat = re.compile(r'QtGui.QApplication.translate\(.+?,\s+"(.+?)(? Date: Thu, 7 May 2009 22:50:23 -0700 Subject: [PATCH 2/4] GUI for Tale of Contents options --- src/calibre/gui2/convert/bulk.py | 8 +- src/calibre/gui2/convert/single.py | 5 +- .../gui2/convert/structure_detection.py | 2 +- src/calibre/gui2/convert/toc.py | 41 +++++++ src/calibre/gui2/convert/toc.ui | 104 ++++++++++++++++++ src/calibre/gui2/convert/xpath_edit.ui | 42 +++---- src/calibre/gui2/convert/xpath_wizard.py | 8 +- 7 files changed, 186 insertions(+), 24 deletions(-) create mode 100644 src/calibre/gui2/convert/toc.py create mode 100644 src/calibre/gui2/convert/toc.ui diff --git a/src/calibre/gui2/convert/bulk.py b/src/calibre/gui2/convert/bulk.py index d6d03c9aec..8a8c796403 100644 --- a/src/calibre/gui2/convert/bulk.py +++ b/src/calibre/gui2/convert/bulk.py @@ -14,6 +14,8 @@ from calibre.customize.ui import available_output_formats from calibre.gui2 import ResizableDialog from calibre.gui2.convert.look_and_feel import LookAndFeelWidget from calibre.gui2.convert.page_setup import PageSetupWidget +from calibre.gui2.convert.structure_detection import StructureDetectionWidget +from calibre.gui2.convert.toc import TOCWidget from calibre.gui2.convert import GuiRecommendations from calibre.ebooks.conversion.plumber import Plumber, OUTPUT_FORMAT_PREFERENCES from calibre.utils.logging import Log @@ -58,6 +60,8 @@ class BulkConfig(Config): self.setWindowTitle(_('Bulk Convert')) lf = widget_factory(LookAndFeelWidget) ps = widget_factory(PageSetupWidget) + sd = widget_factory(StructureDetectionWidget) + toc = widget_factory(TOCWidget) output_widget = None name = 'calibre.gui2.convert.%s' % self.plumber.output_plugin.name.lower().replace(' ', '_') @@ -76,7 +80,7 @@ class BulkConfig(Config): if not c: break self.stack.removeWidget(c) - widgets = [lf, ps] + widgets = [lf, ps, sd, toc] if output_widget is not None: widgets.append(output_widget) for w in widgets: @@ -89,7 +93,7 @@ class BulkConfig(Config): idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 self.groups.setCurrentIndex(self._groups_model.index(idx)) - + self.stack.setCurrentIndex(idx) def setup_output_formats(self, db, preferred_output_format): available_formats = '' diff --git a/src/calibre/gui2/convert/single.py b/src/calibre/gui2/convert/single.py index 2b0cfd52d0..eea4b6e11a 100644 --- a/src/calibre/gui2/convert/single.py +++ b/src/calibre/gui2/convert/single.py @@ -18,6 +18,7 @@ from calibre.gui2.convert.metadata import MetadataWidget from calibre.gui2.convert.look_and_feel import LookAndFeelWidget from calibre.gui2.convert.page_setup import PageSetupWidget from calibre.gui2.convert.structure_detection import StructureDetectionWidget +from calibre.gui2.convert.toc import TOCWidget from calibre.ebooks.conversion.plumber import Plumber, supported_input_formats, \ @@ -138,6 +139,7 @@ class Config(ResizableDialog, Ui_Dialog): lf = widget_factory(LookAndFeelWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) + toc = widget_factory(TOCWidget) output_widget = None name = 'calibre.gui2.convert.%s' % self.plumber.output_plugin.name.lower().replace(' ', '_') @@ -166,7 +168,7 @@ class Config(ResizableDialog, Ui_Dialog): if not c: break self.stack.removeWidget(c) - widgets = [self.mw, lf, ps, sd] + widgets = [self.mw, lf, ps, sd, toc] if input_widget is not None: widgets.append(input_widget) if output_widget is not None: @@ -181,6 +183,7 @@ class Config(ResizableDialog, Ui_Dialog): idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 self.groups.setCurrentIndex(self._groups_model.index(idx)) + self.stack.setCurrentIndex(idx) def setup_input_output_formats(self, db, book_id, preferred_input_format, diff --git a/src/calibre/gui2/convert/structure_detection.py b/src/calibre/gui2/convert/structure_detection.py index a757ccb8b3..66dff86aca 100644 --- a/src/calibre/gui2/convert/structure_detection.py +++ b/src/calibre/gui2/convert/structure_detection.py @@ -37,6 +37,6 @@ class StructureDetectionWidget(Widget, Ui_Form): x = getattr(self, 'opt_'+x) if not x.check(): error_dialog(self, _('Invalid XPath'), - _('The XPath expression %s is invalid.')%x.xpath).exec_() + _('The XPath expression %s is invalid.')%x.text).exec_() return False return True diff --git a/src/calibre/gui2/convert/toc.py b/src/calibre/gui2/convert/toc.py new file mode 100644 index 0000000000..0343e164ea --- /dev/null +++ b/src/calibre/gui2/convert/toc.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +from calibre.gui2.convert.toc_ui import Ui_Form +from calibre.gui2.convert import Widget +from calibre.gui2 import error_dialog + +class TOCWidget(Widget, Ui_Form): + + TITLE = _('Table of\nContents') + ICON = ':/images/series.svg' + HELP = _('Control the creation/conversion of the Table of Contents.') + + def __init__(self, parent, get_option, get_help, db=None, book_id=None): + Widget.__init__(self, parent, 'structure_detection', + ['level1_toc', 'level2_toc', 'level3_toc', + 'toc_threshold', 'max_toc_links', 'no_chapters_in_toc', + 'use_auto_toc', 'toc_filter', + ] + ) + self.db, self.book_id = db, book_id + self.initialize_options(get_option, get_help, db, book_id) + self.opt_level1_toc.set_msg(_('Level &1 TOC (XPath expression):')) + self.opt_level2_toc.set_msg(_('Level &2 TOC (XPath expression):')) + self.opt_level3_toc.set_msg(_('Level &3 TOC (XPath expression):')) + + + def pre_commit_check(self): + for x in ('level1', 'level2', 'level3'): + x = getattr(self, 'opt_'+x) + if not x.check(): + error_dialog(self, _('Invalid XPath'), + _('The XPath expression %s is invalid.')%x.text).exec_() + return False + return True diff --git a/src/calibre/gui2/convert/toc.ui b/src/calibre/gui2/convert/toc.ui new file mode 100644 index 0000000000..e4ebc7fee9 --- /dev/null +++ b/src/calibre/gui2/convert/toc.ui @@ -0,0 +1,104 @@ + + + Form + + + + 0 + 0 + 436 + 382 + + + + Form + + + + + + Do not add &detected chapters to the Table of Contents + + + + + + + Number of &links to add to Table of Contents + + + opt_max_toc_links + + + + + + + + + + Chapter &threshold + + + opt_toc_threshold + + + + + + + + + + &Force use of auto-generated Table of Contents + + + + + + + TOC &Filter: + + + opt_toc_filter + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + XPathEdit + QWidget +
convert/xpath_wizard.h
+ 1 +
+
+ + +
diff --git a/src/calibre/gui2/convert/xpath_edit.ui b/src/calibre/gui2/convert/xpath_edit.ui index c1e8946635..00f0782c47 100644 --- a/src/calibre/gui2/convert/xpath_edit.ui +++ b/src/calibre/gui2/convert/xpath_edit.ui @@ -1,7 +1,8 @@ - + + Form - - + + 0 0 @@ -9,40 +10,43 @@ 64 - + Form - - - + + + - - + + TextLabel - + true + + edit + - + - - - + + + Use a wizard to help construct the XPath expression - + ... - - + + :/images/wizard.svg:/images/wizard.svg - + 40 40 @@ -53,7 +57,7 @@ - + diff --git a/src/calibre/gui2/convert/xpath_wizard.py b/src/calibre/gui2/convert/xpath_wizard.py index a98db16c09..63914b0b94 100644 --- a/src/calibre/gui2/convert/xpath_wizard.py +++ b/src/calibre/gui2/convert/xpath_wizard.py @@ -76,12 +76,18 @@ class XPathEdit(QWidget, Ui_Edit): def text(self): return unicode(self.edit.text()) + @property + def xpath(self): + return self.text + def check(self): from calibre.ebooks.oeb.base import XPNSMAP from lxml.etree import XPath try: - XPath(self.text, namespace=XPNSMAP) + XPath(self.text, namespaces=XPNSMAP) except: + import traceback + traceback.print_exc() return False return True From ea9ad56eaeeb370931adb00eee01679f44e2d857 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 7 May 2009 22:59:56 -0700 Subject: [PATCH 3/4] Conversion dialog complete (I hope) --- src/calibre/ebooks/comic/input.py | 2 + src/calibre/gui2/convert/look_and_feel.py | 6 +- src/calibre/gui2/convert/look_and_feel.ui | 144 +++++++++++----------- 3 files changed, 74 insertions(+), 78 deletions(-) diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index 7a4faba3c2..b228182ef4 100755 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -305,6 +305,8 @@ class ComicInput(InputFormatPlugin): ('page_breaks_brefore', None, OptionRecommendation.HIGH), ('use_auto_toc', False, OptionRecommendation.HIGH), ('page_breaks_before', None, OptionRecommendation.HIGH), + ('disable_font_rescaling', True, OptionRecommendation.HIGH), + ('linearize_tables', False, OptionRecommendation.HIGH), ]) def get_comics_from_collection(self, stream): diff --git a/src/calibre/gui2/convert/look_and_feel.py b/src/calibre/gui2/convert/look_and_feel.py index a6fe4efce9..ed2b9a380d 100644 --- a/src/calibre/gui2/convert/look_and_feel.py +++ b/src/calibre/gui2/convert/look_and_feel.py @@ -19,9 +19,9 @@ class LookAndFeelWidget(Widget, Ui_Form): def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, 'look_and_feel', ['dont_justify', 'extra_css', 'base_font_size', - 'font_size_mapping', 'insert_metadata', 'line_height', - 'linearize_tables', 'remove_first_image', - 'disable_font_rescaling', + 'font_size_mapping', 'line_height', + 'linearize_tables', + 'disable_font_rescaling', 'insert_blank_line', 'remove_paragraph_spacing', 'input_encoding'] ) self.db, self.book_id = db, book_id diff --git a/src/calibre/gui2/convert/look_and_feel.ui b/src/calibre/gui2/convert/look_and_feel.ui index e3be914ffa..75891ac489 100644 --- a/src/calibre/gui2/convert/look_and_feel.ui +++ b/src/calibre/gui2/convert/look_and_feel.ui @@ -1,7 +1,8 @@ - + + Form - - + + 0 0 @@ -9,142 +10,135 @@ 500 - + Form - + - - - - + + + + Base &font size: - + opt_base_font_size - - - + + + pt - + 1 - + 0.000000000000000 - + 30.000000000000000 - + 1.000000000000000 - + 15.000000000000000 - - - + + + Line &height: - + opt_line_height - - - + + + pt - + 1 - - - + + + Remove &spacing between paragraphs - - - + + + No text &justification - - - + + + &Linearize tables - - - - Remove &first image from source file - - - - - - + + + Font size &key: - + opt_font_size_mapping - - + + - - - - Insert &metadata at start of book - - - - - - + + + Input character &encoding - + opt_input_encoding - - + + - - - + + + &Disable font size rescaling + + + + Insert &blank line + + + - - + + Extra &CSS - - - + + + @@ -152,8 +146,8 @@ - - + + @@ -162,11 +156,11 @@ opt_base_font_size setDisabled(bool) - + 154 16 - + 385 40 @@ -178,11 +172,11 @@ opt_font_size_mapping setDisabled(bool) - + 80 20 - + 285 72 From 725a5b2794cce0f205809805b89b9cbe628b3143 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 7 May 2009 23:46:23 -0700 Subject: [PATCH 4/4] GUI for setting conversion defaults --- src/calibre/gui2/convert/__init__.py | 14 +++- src/calibre/gui2/convert/bulk.py | 2 + src/calibre/gui2/convert/toc.py | 2 +- src/calibre/gui2/convert/xpath_wizard.py | 3 +- src/calibre/gui2/dialogs/config.py | 83 ++++++++++++++++++++++-- 5 files changed, 94 insertions(+), 10 deletions(-) diff --git a/src/calibre/gui2/convert/__init__.py b/src/calibre/gui2/convert/__init__.py index ff04eeef91..67b6e47aa9 100644 --- a/src/calibre/gui2/convert/__init__.py +++ b/src/calibre/gui2/convert/__init__.py @@ -83,8 +83,11 @@ class GuiRecommendations(dict): if d: self.update(d) - def merge_recommendations(self, get_option, level, options): + def merge_recommendations(self, get_option, level, options, + only_existing=False): for name in options: + if only_existing and name not in self: + continue opt = get_option(name) if opt is None: continue if opt.level == OptionRecommendation.HIGH: @@ -125,8 +128,10 @@ class Widget(QWidget): if db is not None: specifics = load_specifics(db, book_id) specifics.merge_recommendations(get_option, OptionRecommendation.HIGH, - self._options) + self._options, only_existing=True) defaults.update(specifics) + + self.apply_recommendations(defaults) self.setup_help(get_help) @@ -202,7 +207,10 @@ class Widget(QWidget): def set_help(self, msg): if msg and getattr(msg, 'strip', lambda:True)(): - self.emit(SIGNAL('set_help(PyQt_PyObject)'), msg) + try: + self.emit(SIGNAL('set_help(PyQt_PyObject)'), msg) + except: + pass def setup_help(self, help_provider): for name in self._options: diff --git a/src/calibre/gui2/convert/bulk.py b/src/calibre/gui2/convert/bulk.py index 8a8c796403..7bdacc26ee 100644 --- a/src/calibre/gui2/convert/bulk.py +++ b/src/calibre/gui2/convert/bulk.py @@ -112,6 +112,8 @@ class BulkConfig(Config): def accept(self): recs = GuiRecommendations() for w in self._groups_model.widgets: + if not w.pre_commit_check(): + return x = w.commit(save_defaults=False) recs.update(x) self._recommendations = recs diff --git a/src/calibre/gui2/convert/toc.py b/src/calibre/gui2/convert/toc.py index 0343e164ea..d86c8333c9 100644 --- a/src/calibre/gui2/convert/toc.py +++ b/src/calibre/gui2/convert/toc.py @@ -33,7 +33,7 @@ class TOCWidget(Widget, Ui_Form): def pre_commit_check(self): for x in ('level1', 'level2', 'level3'): - x = getattr(self, 'opt_'+x) + x = getattr(self, 'opt_'+x+'_toc') if not x.check(): error_dialog(self, _('Invalid XPath'), _('The XPath expression %s is invalid.')%x.text).exec_() diff --git a/src/calibre/gui2/convert/xpath_wizard.py b/src/calibre/gui2/convert/xpath_wizard.py index 63914b0b94..ebb43e7e79 100644 --- a/src/calibre/gui2/convert/xpath_wizard.py +++ b/src/calibre/gui2/convert/xpath_wizard.py @@ -84,7 +84,8 @@ class XPathEdit(QWidget, Ui_Edit): from calibre.ebooks.oeb.base import XPNSMAP from lxml.etree import XPath try: - XPath(self.text, namespaces=XPNSMAP) + if self.text.strip(): + XPath(self.text, namespaces=XPNSMAP) except: import traceback traceback.print_exc() diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py index 0de7826212..faf749fc73 100644 --- a/src/calibre/gui2/dialogs/config.py +++ b/src/calibre/gui2/dialogs/config.py @@ -8,7 +8,7 @@ from PyQt4.Qt import QDialog, QMessageBox, QListWidgetItem, QIcon, \ QStringListModel, QAbstractItemModel, QFont, \ SIGNAL, QTimer, Qt, QSize, QVariant, QUrl, \ QModelIndex, QInputDialog, QAbstractTableModel, \ - QDialogButtonBox + QDialogButtonBox, QTabWidget from calibre.constants import islinux, iswindows from calibre.gui2.dialogs.config_ui import Ui_Dialog @@ -23,8 +23,72 @@ from calibre.ebooks.oeb.iterator import is_supported from calibre.library import server_config from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \ disable_plugin, customize_plugin, \ - plugin_customization, add_plugin, remove_plugin + plugin_customization, add_plugin, \ + remove_plugin, input_format_plugins, \ + output_format_plugins from calibre.utils.smtp import config as smtp_prefs +from calibre.gui2.convert.look_and_feel import LookAndFeelWidget +from calibre.gui2.convert.page_setup import PageSetupWidget +from calibre.gui2.convert.structure_detection import StructureDetectionWidget +from calibre.ebooks.conversion.plumber import Plumber +from calibre.utils.logging import Log +from calibre.gui2.convert.toc import TOCWidget + +class ConfigTabs(QTabWidget): + + def __init__(self, parent): + QTabWidget.__init__(self, parent) + log = Log() + log.outputs = [] + + self.plumber = Plumber('dummt.epub', 'dummy.epub', log) + + def widget_factory(cls): + return cls(self, self.plumber.get_option_by_name, + self.plumber.get_option_help, None, None) + + lf = widget_factory(LookAndFeelWidget) + ps = widget_factory(PageSetupWidget) + sd = widget_factory(StructureDetectionWidget) + toc = widget_factory(TOCWidget) + + self.widgets = [lf, ps, sd, toc] + + for plugin in input_format_plugins(): + name = plugin.name.lower().replace(' ', '_') + try: + input_widget = __import__('calibre.gui2.convert.'+name, + fromlist=[1]) + pw = input_widget.PluginWidget + pw.ICON = ':/images/forward.svg' + pw.HELP = _('Options specific to the input format.') + self.widgets.append(widget_factory(pw)) + except ImportError: + continue + + for plugin in output_format_plugins(): + name = plugin.name.lower().replace(' ', '_') + try: + output_widget = __import__('calibre.gui2.convert.'+name, + fromlist=[1]) + pw = output_widget.PluginWidget + pw.ICON = ':/images/forward.svg' + pw.HELP = _('Options specific to the input format.') + self.widgets.append(widget_factory(pw)) + except ImportError: + continue + + for widget in self.widgets: + self.addTab(widget, widget.TITLE.replace('\n', ' ').replace('&', + '&&')) + + def commit(self): + for widget in self.widgets: + if not widget.pre_commit_check(): + return False + widget.commit(save_defaults=True) + return True + class PluginModel(QAbstractItemModel): @@ -124,10 +188,12 @@ class CategoryModel(QStringListModel): def __init__(self, *args): QStringListModel.__init__(self, *args) - self.setStringList([_('General'), _('Interface'), _('Email\nDelivery'), + self.setStringList([_('General'), _('Interface'), _('Conversion'), + _('Email\nDelivery'), _('Advanced'), _('Content\nServer'), _('Plugins')]) self.icons = list(map(QVariant, map(QIcon, [':/images/dialog_information.svg', ':/images/lookfeel.svg', + ':/images/convert.svg', ':/images/mail.svg', ':/images/view.svg', ':/images/network-server.svg', ':/images/plugins.svg']))) @@ -401,6 +467,11 @@ class ConfigDialog(QDialog, Ui_Dialog): self.delete_news.setEnabled(bool(self.sync_news.isChecked())) self.connect(self.sync_news, SIGNAL('toggled(bool)'), self.delete_news.setEnabled) + self.setup_conversion_options() + + def setup_conversion_options(self): + self.conversion_options = ConfigTabs(self) + self.stackedWidget.insertWidget(2, self.conversion_options) def setup_email_page(self): opts = smtp_prefs().parse() @@ -547,13 +618,13 @@ class ConfigDialog(QDialog, Ui_Dialog): config_dialog.connect(button_box, SIGNAL('accepted()'), config_dialog.accept) config_dialog.connect(button_box, SIGNAL('rejected()'), config_dialog.reject) - + config_widget = plugin.config_widget() v = QVBoxLayout(config_dialog) v.addWidget(config_widget) v.addWidget(button_box) config_dialog.exec_() - + if config_dialog.result() == QDialog.Accepted: plugin.save_settings(config_widget) self._plugin_model.refresh_plugin(plugin) @@ -670,6 +741,8 @@ class ConfigDialog(QDialog, Ui_Dialog): return if not self.set_email_settings(): return + if not self.conversion_options.commit(): + return config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked()) config['new_version_notification'] = bool(self.new_version_notification.isChecked()) prefs['network_timeout'] = int(self.timeout.value())