diff --git a/manual/creating_plugins.rst b/manual/creating_plugins.rst index fe58b6e34e..b595cd1611 100644 --- a/manual/creating_plugins.rst +++ b/manual/creating_plugins.rst @@ -59,6 +59,8 @@ and query the books database in |app|. You can download this plugin from `interface_demo_plugin.zip `_ +.. _import_name_txt: + The first thing to note is that this zip file has a lot more files in it, explained below, pay particular attention to ``plugin-import-name-interface_demo.txt``. @@ -180,6 +182,73 @@ You can see the ``prefs`` object being used in main.py: .. literalinclude:: plugin_examples/interface_demo/main.py :pyobject: DemoDialog.config + +Edit Book plugins +------------------------------------------ + +Now let's change gears for a bit and look at creating a plugin to add tools to +the |app| book editor. The plugin is available here: +`editor_demo_plugin.zip `_. + +The first step, as for all plugins is to create the +import name empty txt file, as described :ref:`above `. +We shall name the file ``plugin-import-name-editor_plugin_demo.txt``. + +Now we create the mandatory ``__init__.py`` file that contains metadata about +the plugin -- its name, author, version, etc. + +.. literalinclude:: plugin_examples/editor_demo/__init__.py + :lines: 8- + +A single editor plugin can provide multiple tools each tool corresponds to a +single button in the toolbar and entry in the :guilabel:`Plugins` menu in the +editor. These can have sub-menus in case the tool has multiple related actions. + +The tools must all be defined in the file ``main.py`` in your plugin. Every +tool is a class that inherits from the +:class:`calibre.gui2.tweak_book.plugin.Tool` class. Let's look at ``main.py`` +from the demo plugin, the source code is heavily commented and should be +self-explanatory. Read the API documents of the +:class:`calibre.gui2.tweak_book.plugin.Tool` class for more details. + +main.py +^^^^^^^^^ + +Here we will see the definition of a single tool that does a does a couple of +simple things that demonstrate the editor API most plugins will use. + +.. literalinclude:: plugin_examples/editor_demo/main.py + :lines: 8- + +Let's break down ``main.py``. We see that it defines a single tool, named +*Magnify fonts*. This tool will ask the user for a number and multiply all font +sizes in the book by that number. + +The first important thing is the tool name which you must set to some +relatively unique string as it will be used as the key for this tool. + +The next important entry point is the +:meth:`calibre.gui2.tweak_book.plugin.Tool.create_action`. This method creates +the QAction objects that appear in the plugins toolbar and plugin menu. +It also, optionally, assigns a keyboard shortcut that the user can customize. +The triggered signal from the QAction is connected to the ask_user() method +that asks the user for the font size multiplier, and then runs the +magnification code. + +The magnification code is well commented and fairly simple. The main things to +note are that you get a reference to the editor window as ``self.gui`` and the +editor *Boss* as ``self.boss``. The *Boss* is the object that controls the editor +user interface. It has many useful methods, that are documented in the +:class:`calibre.gui2.tweak_book.boss.Boss` class. + +Finally, there is ``self.current_container`` which is a reference to the book +being edited as a :class:`calibre.ebooks.oeb.polish.container.Container` +object. This represents the book as a collection of its constituent +HTML/CSS/image files and has convenience methods for doing many useful things. +The container object and various useful utility functions that can be reused in +your plugin code are documented in :ref:`polish_api`. + + Adding translations to your plugin -------------------------------------- diff --git a/manual/plugin_examples/editor_demo/__init__.py b/manual/plugin_examples/editor_demo/__init__.py new file mode 100644 index 0000000000..13fac4f6e4 --- /dev/null +++ b/manual/plugin_examples/editor_demo/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2014, Kovid Goyal ' + +from calibre.customize import EditBookToolPlugin + + +class DemoPlugin(EditBookToolPlugin): + + name = 'Edit Book plugin demo' + version = (1, 0, 0) + author = 'Kovid Goyal' + supported_platforms = ['windows', 'osx', 'linux'] + description = 'A demonstration of the plugin interface for the ebook editor' + minimum_calibre_version = (1, 46, 0) diff --git a/manual/plugin_examples/editor_demo/images/icon.png b/manual/plugin_examples/editor_demo/images/icon.png new file mode 100644 index 0000000000..7512b6ef07 Binary files /dev/null and b/manual/plugin_examples/editor_demo/images/icon.png differ diff --git a/manual/plugin_examples/editor_demo/main.py b/manual/plugin_examples/editor_demo/main.py new file mode 100644 index 0000000000..8cda8c7f92 --- /dev/null +++ b/manual/plugin_examples/editor_demo/main.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2014, Kovid Goyal ' + +import re +from PyQt4.Qt import QAction, QInputDialog +from cssutils.css import CSSRule + +# The base class that all tools must inherit from +from calibre.gui2.tweak_book.plugin import Tool + +from calibre import force_unicode +from calibre.gui2 import error_dialog +from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES, serialize + +class DemoTool(Tool): + + #: Set this to a unique name it will be used as a key + name = 'demo-tool' + + #: If True the user can choose to place this tool in the plugins toolbar + allowed_in_toolbar = True + + #: If True the user can choose to place this tool in the plugins menu + allowed_in_menu = True + + def create_action(self, for_toolbar=True): + # Create an action, this will be added to the plugins toolbar and + # the plugins menu + ac = QAction(get_icons('images/icon.png'), 'Magnify fonts', self.gui) # noqa + if not for_toolbar: + # Register a keyboard shortcut for this toolbar action. We only + # register it for the action created for the menu, not the toolbar, + # to avoid a double trigger + self.register_shortcut(ac, 'magnify-fonts-tool', default_keys=('Ctrl+Shift+Alt+D',)) + ac.triggered.connect(self.ask_user) + return ac + + def ask_user(self): + # Ask the user for a factor by which to multiply all font sizes + factor, ok = QInputDialog.getDouble( + self.gui, 'Enter a magnification factor', 'Allow font sizes in the book will be multiplied by the specified factor', + value=2, min=0.1, max=4 + ) + if ok: + # Ensure any in progress editing the user is doing is present in the container + self.boss.commit_all_editors_to_container() + try: + self.magnify_fonts(factor) + except Exception: + # Something bad happened report the error to the user + import traceback + error_dialog(self.gui, _('Failed to magnify fonts'), _( + 'Failed to magnify fonts, click "Show details" for more info'), + det_msg=traceback.format_exc(), show=True) + # Revert to the saved restore point + self.boss.revert_requested(self.boss.global_undo.previous_container) + else: + # Show the user what changes we have made, allowing her to + # revert them if necessary + self.boss.show_current_diff() + # Update the editor UI to take into account all the changes we + # have made + self.boss.apply_container_update_to_gui() + + def magnify_fonts(self, factor): + # Magnify all font sizes defined in the book by the specified factor + # First we create a restore point so that the user can undo all changes + # we make. + self.boss.add_savepoint('Before: Magnify fonts') + + container = self.current_container # The book being edited as a container object + + # Iterate over all style declarations int he book, this means css + # stylesheets,