Uw eigen plugins schrijven om de functionaliteit van calibre uit te breiden¶
calibre heeft een zeer modulair ontwerp. Bijna alle functionaliteit in calibre komt als plug-ins. Plug-ins voor conversie, downloaden van nieuws (recepten genaamd), verschillende componenten van de gebruikersinterface, verbinding maken met verschillende toestellen, bestanden verwerken bij toevoegen aan calibre enz. U vidnt een volledige lijst van ingebouwde plug-ins in calibre op: guilabel: `Voorkeuren-> Geavanceerd-> Plug-ins ‘te gaan.
Hier laten we zien hoe u uw eigen plugins kunt maken om nieuwe functies toe te voegen aan calibre.
Notitie
Dit is enkel van toepassing op calibre releases >= 0.8.60
Opbouw van een calibre plugin¶
Een calibre plug-in is heel eenvoudig, gewoon een ZIP-bestand dat wat Python code bevat en andere bronnen zoals afbeelding die de plug-in nodig heeft. Genoeg, laten we een eenvoudig voorbeeld bekijken.
Stel, u heeft calibre geïnstalleerd om diverse e-documenten zelf te publiceren in EPUB en MOBI-formaten. U wilt dat alle bestanden gegenereerd door calibre als uitgever “Hello world” hebben, hier volgt hoe dit te doen. Maak een bestand genaamd __init__.py
(dit is een speciale naam en moet altijd gebruikt worden voor het hoofdbestand van uw plugin) en geef de volgende Python code in:
from calibre.customize import FileTypePlugin
class HelloWorld(FileTypePlugin):
name = 'Hello World Plugin' # Name of the plugin
description = 'Set the publisher to Hello World for all new conversions'
supported_platforms = ['windows', 'osx', 'linux'] # Platforms this plugin will run on
author = 'Acme Inc.' # The author of this plugin
version = (1, 0, 0) # The version number of this plugin
file_types = {'epub', 'mobi'} # The file types that this plugin will be applied to
on_postprocess = True # Run this plugin after conversion is complete
minimum_calibre_version = (0, 7, 53)
def run(self, path_to_ebook):
from calibre.ebooks.metadata.meta import get_metadata, set_metadata
with open(path_to_ebook, 'r+b') as file:
ext = os.path.splitext(path_to_ebook)[-1][1:].lower()
mi = get_metadata(file, ext)
mi.publisher = 'Hello World'
set_metadata(file, mi, ext)
return path_to_ebook
Dat is alles. Om deze code als plug-in aan calibre toe te voegen, voer het volgende uit in de map waar u __init__.py
:: creëerde
calibre-customize -b .
Notitie
Op macOS bevinden de commandoregelprogramma’s zich in de calibre-bundel, bijvoorbeeld, als u calibre hebt geïnstalleerd in /Applications
bevinden de commandoregelprogramma’s zich in /Applications/calibre.app/Contents/MacOS/
.
U kan de Hello World plugin downloaden van helloworld_plugin.zip.
Elke keer u calibre gebruikt om een boek te converteren, wordt de plugin’s run()
methode geroepen en het geconverteerde boek heeft als uitgever “Hello World”. Dit is een banale plugin, op naar een ingewikkelder voorbeeld dat daadwerkelijk een component toevoegt aan de gebruikersinterface.
Een Gebruikersinterface plugin¶
Deze plug-in zal bestaan uit een paar bestanden (om de code schoon te houden). Het toont u hoe bronnen (afbeeldingen of databestanden) op te halen uit de plug-in’s ZIP , gebruikers toe te staan uw plug-in in te stellen, elementen in de gebruikersinterface voor calibre te maken en de database voor boeken te raadplegen en doorzoeken.
U kan deze plugin downloaden van interface_demo_plugin.zip
Het eerste ding om op te merken is dat dit ZIP bestand veel meer bestanden bevat, onder uitgelegd, let extra op plugin-import-name-interface_demo.txt
.
- plugin-import-name-interface_demo.txt
Een leeg tekstbestand om de multi-file plugin magie in te schakelen. Dit bestand moet aanwezig zijn in alle plug-ins die meer dan één .py bestand gebruiken. Het moet leeg zijn en de bestandsnaam moet volgende vorm hebben: `` plugin-import-name-**een_naam **.txt``. Met dit bestand kunt u code importeren uit de .py bestanden in het ZIP-bestand , met behulp van een statement als:
from calibre_plugins.some_name.some_module import some_objectDe prefix
calibre_plugins
moet altijd aanwezig zijn.some_name
komt van de naam van het lege tekst bestand.some_module
verwijst naarsome_module.py
in het ZIP bestand. Merk op dat deze import even machtig is als gewone Python imports. U kan packages en subpackages van .py modules creëren in het ZIP bestand, net zoals u normaal zou doen (door __init__.py te definiëren in elke submap) en alles zou “moeten werken”.De naam die u gebruikt voor
some_name
gaat in een algemene naamruimte, gedeeld door alle plug-ins, dus maak deze zo uniek mogelijk. Maar vergeet niet dat het een geldig Python-id moet zijn (alleen alfabetten, cijfers en het onderliggend streepje).- __init__.py
Zoals hiervoor, het bestand dat de plugin’s klasse definieert
- main.py
Dit bestand bevat de daadwerkelijke code die iets nuttigs doet
- ui.py
Dit bestand definieert de interface van de plugin
- images/icon.png
Het icoon voor deze plugin
- about.txt
Een tekstbestand met informatie over de plugin
- vertalingen
Een map die .mo-bestanden bevat met vertalingen van de gebruikersinterface van uw plug-in in verschillende talen. Zie onder voor details.
Laten we naar de code kijken.
__init__.py¶
Eerst de verplichte __init__.py
om de plug-in’s metadata te definiëren:
from calibre.customize import InterfaceActionBase
class InterfacePluginDemo(InterfaceActionBase):
'''
This class is a simple wrapper that provides information about the actual
plugin class. The actual interface plugin class is called InterfacePlugin
and is defined in the ui.py file, as specified in the actual_plugin field
below.
The reason for having two classes is that it allows the command line
calibre utilities to run without needing to load the GUI libraries.
'''
name = 'Interface Plugin Demo'
description = 'An advanced plugin demo'
supported_platforms = ['windows', 'osx', 'linux']
author = 'Kovid Goyal'
version = (1, 0, 0)
minimum_calibre_version = (0, 7, 53)
#: This field defines the GUI plugin class that contains all the code
#: that actually does something. Its format is module_path:class_name
#: The specified class must be defined in the specified module.
actual_plugin = 'calibre_plugins.interface_demo.ui:InterfacePlugin'
def is_customizable(self):
'''
This method must return True to enable customization via
Preferences->Plugins
'''
return True
def config_widget(self):
'''
Implement this method and :meth:`save_settings` in your plugin to
use a custom configuration dialog.
This method, if implemented, must return a QWidget. The widget can have
an optional method validate() that takes no arguments and is called
immediately after the user clicks OK. Changes are applied if and only
if the method returns True.
If for some reason you cannot perform the configuration at this time,
return a tuple of two strings (message, details), these will be
displayed as a warning dialog to the user and the process will be
aborted.
The base class implementation of this method raises NotImplementedError
so by default no user configuration is possible.
'''
# It is important to put this import statement here rather than at the
# top of the module as importing the config class will also cause the
# GUI libraries to be loaded, which we do not want when using calibre
# from the command line
from calibre_plugins.interface_demo.config import ConfigWidget
return ConfigWidget()
def save_settings(self, config_widget):
'''
Save the settings specified by the user with config_widget.
:param config_widget: The widget returned by :meth:`config_widget`.
'''
config_widget.save_settings()
# Apply the changes
ac = self.actual_plugin_
if ac is not None:
ac.apply_settings()
De enige noemenswaardige eigenschap is het veld: attr:actual_plugin. Omdat calibre zowel opdrachtregel als GUI interfaces heeft, mogen GUI plug-ins zoals deze geen enkele GUI-bibliotheek laden in __init__.py . Het veld actual_plugin doet dit voor u, door calibre te zeggen dat de daadwerkelijke plugin in een ander bestand in uw ZIP archief te vinden is, dat alleen in een GUI context wordt geladen.
Vergeet niet dat om dit te laten werken, u een plugin-import-name-een_naam.txt bestand nodig hebt in uw ZIP plug-in, zoals boven besproken.
Er zijn meerdere methoden om gebruikersconfiguratie van de plug-in mogelijk te maken. Deze worden onder besproken.
ui.py¶
Laten we nu ui.py bekijken dat de eigenlijke GUI plug-in definieert. De broncode is voorzien van commentaar en zou zichzelf moeten verklaren:
from calibre.gui2.actions import InterfaceAction
from calibre_plugins.interface_demo.main import DemoDialog
class InterfacePlugin(InterfaceAction):
name = 'Interface Plugin Demo'
# Declare the main action associated with this plugin
# The keyboard shortcut can be None if you dont want to use a keyboard
# shortcut. Remember that currently calibre has no central management for
# keyboard shortcuts, so try to use an unusual/unused shortcut.
action_spec = ('Interface Plugin Demo', None,
'Run the Interface Plugin Demo', 'Ctrl+Shift+F1')
def genesis(self):
# This method is called once per plugin, do initial setup here
# Set the icon for this interface action
# The get_icons function is a builtin function defined for all your
# plugin code. It loads icons from the plugin zip file. It returns
# QIcon objects, if you want the actual data, use the analogous
# get_resources builtin function.
#
# Note that if you are loading more than one icon, for performance, you
# should pass a list of names to get_icons. In this case, get_icons
# will return a dictionary mapping names to QIcons. Names that
# are not found in the zip file will result in null QIcons.
icon = get_icons('images/icon.png', 'Interface Demo Plugin')
# The qaction is automatically created from the action_spec defined
# above
self.qaction.setIcon(icon)
self.qaction.triggered.connect(self.show_dialog)
def show_dialog(self):
# The base plugin object defined in __init__.py
base_plugin_object = self.interface_action_base_plugin
# Show the config dialog
# The config dialog can also be shown from within
# Preferences->Plugins, which is why the do_user_config
# method is defined on the base plugin class
do_user_config = base_plugin_object.do_user_config
# self.gui is the main calibre GUI. It acts as the gateway to access
# all the elements of the calibre user interface, it should also be the
# parent of the dialog
d = DemoDialog(self.gui, self.qaction.icon(), do_user_config)
d.show()
def apply_settings(self):
from calibre_plugins.interface_demo.config import prefs
# In an actual non trivial plugin, you would probably need to
# do something based on the settings in prefs
prefs
main.py¶
De eigenlijke logica om de Interface Plugin Demo dialoog te implementeren.
from calibre_plugins.interface_demo.config import prefs
class DemoDialog(QDialog):
def __init__(self, gui, icon, do_user_config):
QDialog.__init__(self, gui)
self.gui = gui
self.do_user_config = do_user_config
# The current database shown in the GUI
# db is an instance of the class LibraryDatabase from db/legacy.py
# This class has many, many methods that allow you to do a lot of
# things. For most purposes you should use db.new_api, which has
# a much nicer interface from db/cache.py
self.db = gui.current_db
self.l = QVBoxLayout()
self.setLayout(self.l)
self.label = QLabel(prefs['hello_world_msg'])
self.l.addWidget(self.label)
self.setWindowTitle('Interface Plugin Demo')
self.setWindowIcon(icon)
self.about_button = QPushButton('About', self)
self.about_button.clicked.connect(self.about)
self.l.addWidget(self.about_button)
self.marked_button = QPushButton(
'Show books with only one format in the calibre GUI', self)
self.marked_button.clicked.connect(self.marked)
self.l.addWidget(self.marked_button)
self.view_button = QPushButton(
'View the most recently added book', self)
self.view_button.clicked.connect(self.view)
self.l.addWidget(self.view_button)
self.update_metadata_button = QPushButton(
'Update metadata in a book\'s files', self)
self.update_metadata_button.clicked.connect(self.update_metadata)
self.l.addWidget(self.update_metadata_button)
self.conf_button = QPushButton(
'Configure this plugin', self)
self.conf_button.clicked.connect(self.config)
self.l.addWidget(self.conf_button)
self.resize(self.sizeHint())
def about(self):
# Get the about text from a file inside the plugin zip file
# The get_resources function is a builtin function defined for all your
# plugin code. It loads files from the plugin zip file. It returns
# the bytes from the specified file.
#
# Note that if you are loading more than one file, for performance, you
# should pass a list of names to get_resources. In this case,
# get_resources will return a dictionary mapping names to bytes. Names that
# are not found in the zip file will not be in the returned dictionary.
text = get_resources('about.txt')
QMessageBox.about(self, 'About the Interface Plugin Demo',
text.decode('utf-8'))
def marked(self):
''' Show books with only one format '''
db = self.db.new_api
matched_ids = {book_id for book_id in db.all_book_ids() if len(db.formats(book_id)) == 1}
# Mark the records with the matching ids
# new_api does not know anything about marked books, so we use the full
# db object
self.db.set_marked_ids(matched_ids)
# Tell the GUI to search for all marked records
self.gui.search.setEditText('marked:true')
self.gui.search.do_search()
def view(self):
''' View the most recently added book '''
most_recent = most_recent_id = None
db = self.db.new_api
for book_id, timestamp in db.all_field_for('timestamp', db.all_book_ids()).items():
if most_recent is None or timestamp > most_recent:
most_recent = timestamp
most_recent_id = book_id
if most_recent_id is not None:
# Get a reference to the View plugin
view_plugin = self.gui.iactions['View']
# Ask the view plugin to launch the viewer for row_number
view_plugin._view_calibre_books([most_recent_id])
def update_metadata(self):
'''
Set the metadata in the files in the selected book's record to
match the current metadata in the database.
'''
from calibre.ebooks.metadata.meta import set_metadata
from calibre.gui2 import error_dialog, info_dialog
# Get currently selected books
rows = self.gui.library_view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
return error_dialog(self.gui, 'Cannot update metadata',
'No books selected', show=True)
# Map the rows to book ids
ids = list(map(self.gui.library_view.model().id, rows))
db = self.db.new_api
for book_id in ids:
# Get the current metadata for this book from the db
mi = db.get_metadata(book_id, get_cover=True, cover_as_data=True)
fmts = db.formats(book_id)
if not fmts:
continue
for fmt in fmts:
fmt = fmt.lower()
# Get a python file object for the format. This will be either
# an in memory file or a temporary on disk file
ffile = db.format(book_id, fmt, as_file=True)
ffile.seek(0)
# Set metadata in the format
set_metadata(ffile, mi, fmt)
ffile.seek(0)
# Now replace the file in the calibre library with the updated
# file. We dont use add_format_with_hooks as the hooks were
# already run when the file was first added to calibre.
db.add_format(book_id, fmt, ffile, run_hooks=False)
info_dialog(self, 'Updated files',
'Updated the metadata in the files of %d book(s)'%len(ids),
show=True)
def config(self):
self.do_user_config(parent=self)
# Apply the changes
self.label.setText(prefs['hello_world_msg'])
Ophalen bronnen uit het plug-in ZIP bestand¶
calibres plug-in laadsysteem definieert een aantal ingebouwde functies waarmee u gemakkelijk bestanden kunt ophalen uit het plug-in ZIP bestand.
- get_resources(name_or_list_of_names)
This function should be called with a list of paths to files inside the ZIP file. For example to access the file
icon.png
in the folder images in the ZIP file, you would use:images/icon.png
. Always use a forward slash as the path separator, even on Windows. When you pass in a single name, the function will return the raw bytes of that file or None if the name was not found in the ZIP file. If you pass in more than one name then it returns a dictionary mapping the names to bytes. If a name is not found, it will not be present in the returned dictionary.- get_icons(name_or_list_of_names, plugin_name=’’)
Een wrapper voor get_resources() die QIcon objecten creëert van de rauwe bytes vanuit get_resources. Werd een naam niet gevonden in het ZIP bestand, zal de overeenkomende QIcon null zijn. Om icoon theme-ing te ondersteunen, geef de mensvriendelijke naam van uw plug-in in als
plugin_name
. Als de gebruiker een icoonthema gebruikt met iconen voor uw plug-in, worden deze bij voorkeur geladen .
Gebruikers-configuratie van uw plugin inschakelen¶
Om gebruikers uw plug-in te laten configureren, moet u drie methoden in uw basis plug-inklasse definiëren, ** is_customizable **, ** config_widget ** en ** save_settings ** zoals onder weergegeven:
def is_customizable(self):
'''
This method must return True to enable customization via
Preferences->Plugins
'''
return True
def config_widget(self):
'''
Implement this method and :meth:`save_settings` in your plugin to
use a custom configuration dialog.
This method, if implemented, must return a QWidget. The widget can have
an optional method validate() that takes no arguments and is called
immediately after the user clicks OK. Changes are applied if and only
if the method returns True.
If for some reason you cannot perform the configuration at this time,
return a tuple of two strings (message, details), these will be
displayed as a warning dialog to the user and the process will be
aborted.
The base class implementation of this method raises NotImplementedError
so by default no user configuration is possible.
'''
# It is important to put this import statement here rather than at the
# top of the module as importing the config class will also cause the
# GUI libraries to be loaded, which we do not want when using calibre
# from the command line
from calibre_plugins.interface_demo.config import ConfigWidget
return ConfigWidget()
def save_settings(self, config_widget):
'''
Save the settings specified by the user with config_widget.
:param config_widget: The widget returned by :meth:`config_widget`.
'''
config_widget.save_settings()
# Apply the changes
ac = self.actual_plugin_
if ac is not None:
ac.apply_settings()
calibre heeft vele manieren om configuratiegegevens te bewaren (een erfenis van lange geschiedenis). De aanbevolen manier is het gebruik van de JSONConfig klasse, die de informatie bewaart in een .json bestand.
De code om configuratiegegevens te beheren in de demoplug-in is in config.py:
from calibre.utils.config import JSONConfig
# This is where all preferences for this plugin will be stored
# Remember that this name (i.e. plugins/interface_demo) is also
# in a global namespace, so make it as unique as possible.
# You should always prefix your config file name with plugins/,
# so as to ensure you dont accidentally clobber a calibre config file
prefs = JSONConfig('plugins/interface_demo')
# Set defaults
prefs.defaults['hello_world_msg'] = 'Hello, World!'
class ConfigWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.l = QHBoxLayout()
self.setLayout(self.l)
self.label = QLabel('Hello world &message:')
self.l.addWidget(self.label)
self.msg = QLineEdit(self)
self.msg.setText(prefs['hello_world_msg'])
self.l.addWidget(self.msg)
self.label.setBuddy(self.msg)
def save_settings(self):
prefs['hello_world_msg'] = self.msg.text()
Het prefs
object is nu beschikbaar in de plug-incode met een simpele:
from calibre_plugins.interface_demo.config import prefs
U kunt het gebruik van prefs
-object zien in main.py:
def config(self):
self.do_user_config(parent=self)
# Apply the changes
self.label.setText(prefs['hello_world_msg'])
Boek plug-ins bewerken¶
Laten we wat hoger schakelen en een plug-in creëren om tools toe te voegen aan de calibre boekeditor. De plug-in is hier beschikbaar: editor_demo_plugin.zip.
De eerste stap, zoals voor alle plug-ins, is het maken van het lege import name .txt bestand zoals above beschreven. We noemen het bestand plugin-import-name-editor_plugin_demo.txt
.
Nu maken we het verplichte bestand __init__.py
waarin de metadata over de plug-in staan – zijn naam, maker, versie enz.
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)
Een enkel editorplug-in kan meerdere tools leveren, iedere tool correspondeert met een knop in de werkbalk en trefwoord in het Plug-ins menu in de editor. Deze kunnen sub-menu’s hebben indien de tool meerdere acties heeft.
De tools moeten allemaal gedefinieerd zijn in het bestand main.py
in uw plugin. Iedere tool is een class, die erft van de calibre.gui2.tweak_book.plugin.Tool
class. Laten we een kijkje nemen in main.py
van de demoplug-in, de broncode is voorzien van commentaar en zou zichzelf moeten verklaren. Lees de API-documenten van de calibre.gui2.tweak_book.plugin.Tool
class voor meer details.
main.py¶
Hier zien we de definitie van een enkele tool dat alle lettergroottes in het boek vermenigvuldigt met een getal ingesteld door de gebruiker. Deze tool demonstreert meerdere belangrijke concepten die u nodig heeft om uw eigen plug-in te ontwikkelen. Lees daarom de (goed van commentaar voorziene) broncode grondig.
import re
from css_parser.css import CSSRule
from qt.core import QAction, QInputDialog
from calibre import force_unicode
from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES, serialize
from calibre.gui2 import error_dialog
# The base class that all tools must inherit from
from calibre.gui2.tweak_book.plugin import Tool
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 in the book, this means css
# stylesheets, <style> tags and style="" attributes
for name, media_type in container.mime_map.items():
if media_type in OEB_STYLES:
# A stylesheet. Parsed stylesheets are css_parser CSSStylesheet
# objects.
self.magnify_stylesheet(container.parsed(name), factor)
container.dirty(name) # Tell the container that we have changed the stylesheet
elif media_type in OEB_DOCS:
# A HTML file. Parsed HTML files are lxml elements
for style_tag in container.parsed(name).xpath('//*[local-name="style"]'):
if style_tag.text and style_tag.get('type', None) in {None, 'text/css'}:
# We have an inline CSS <style> tag, parse it into a
# stylesheet object
sheet = container.parse_css(style_tag.text)
self.magnify_stylesheet(sheet, factor)
style_tag.text = serialize(sheet, 'text/css', pretty_print=True)
container.dirty(name) # Tell the container that we have changed the stylesheet
for elem in container.parsed(name).xpath('//*[@style]'):
# Process inline style attributes
block = container.parse_css(elem.get('style'), is_declaration=True)
self.magnify_declaration(block, factor)
elem.set('style', force_unicode(block.getCssText(separator=' '), 'utf-8'))
def magnify_stylesheet(self, sheet, factor):
# Magnify all fonts in the specified stylesheet by the specified
# factor.
for rule in sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE):
self.magnify_declaration(rule.style, factor)
def magnify_declaration(self, style, factor):
# Magnify all fonts in the specified style declaration by the specified
# factor
val = style.getPropertyValue('font-size')
if not val:
return
# see if the font-size contains a number
num = re.search(r'[0-9.]+', val)
if num is not None:
num = num.group()
val = val.replace(num, '%f' % (float(num) * factor))
style.setProperty('font-size', val)
# We should also be dealing with the font shorthand property and
# font sizes specified as non numbers, but those are left as exercises
# for the reader
Laten we main.py
bekijken. We zien dat een enkele tool wordt gedefinieerd, Magnify fonts. Hij vraagt de gebruiker een getal en vermenigvuldigt alle lettergroottes in het boek met dit getal.
Het eerste belangrijke ding is de naam van de tool die een redelijk unieke tekenreeks moet zijn, deze wordt gebruikt als sleutel voor dit gereedschap.
Het volgende belangrijke punt is calibre.gui2.tweak_book.plugin.Tool.create_action()
. Deze methode maakt de QAction objecten die op de plug-inwerkbalk en het plug-inmenu verschijnen. Optioneel wordt ook een sneltoets toegewezen die de gebruiker kan aanpassen. Het getriggerde signaal van de QAction is verbonden met de ask_user() methode die de gebruiker vraagt naar de lettergrootte vergrotingsfactor en vervolgens de vergrotingscode uitvoert.
De vergrotingscode is goed becommentarieerd en vrij eenvoudig. De belangrijkste dingen om op te merken zijn dat u een verwijzing naar het editorvenster krijgt als self.gui
en de editor Boss als self.boss
. De Boss is het object dat de gebruikersinterface van de editor bestuurt. Het heeft veel nuttige methoden, die zijn gedocumenteerd in de klasse calibre.gui2.tweak_book.boss.Boss
.
Tenslotte is er ``self.current_container’’, een verwijzing naar het boek dat wordt bewerkt als calibre.ebooks.oeb.polish.container.Container
object. Dit stelt het boek voor als een verzameling van z’n samenstellende HTML/CSS/afbeeldingsbestanden en heeft handige methoden om veel nuttige dingen te doen. Het containerobject en verschillende handige hulpprogramma’s die kunnen worden herbruikt in uw plug-incode zijn gedocumenteerd in :ref: polish_api.
Vertalingen toevoegen aan uw plugin¶
U kunt de teksten in het gebruikersinterface in uw plugin laten vertalen in elke taal die is ingesteld voor de calibre gebruikersinterface.
De eerste stap is door uw plug-in’s broncodes te gaan en alle voor gebruikers zichtbare teksten te markeren als vertaalbaar door ze te omgeven met _(). Bijvoorbeeld:
action_spec = (_('My plugin'), None, _('My plugin is cool'), None)
Gebruik dan een programma om .po bestanden te genereren uit uw plug-in’s broncode . Er moet één .po-bestand zijn voor elke taal waarin u wilt vertalen. Bijvoorbeeld: nl.po voor Nederlands, fr.po voor Frans enzovoort. U kunt hiervoor het `Poedit <https://poedit.net/>`_program gebruiken.
Stuur deze .po-bestanden naar uw vertalers. Zodra u ze terug krijgt, compileer ze in .mo-bestanden. U kunt opnieuw Poedit daarvoor gebruiken, of doe gewoon:
calibre-debug -c "from calibre.translations.msgfmt import main; main()" filename.po
Plaats de .mo-bestanden in de vertalingen
-map in uw plug-in.
De laatste stap is om gewoon de functie load_translations() te roepen bovenaan uw plugin’s .py bestanden. Voor prestatie redenen deze functie alleen aanroepen in die .py-bestanden die echt vertaalbare teksten bevatten. Dus in een normale Gebruikersinterfaceplug-in roept u deze functie aan bovenin ui.py
maar niet in __init__.py
.
U kunt de vertalingen van uw plug-ins testen door de taal van de gebruikersinterface in calibre te wijzigen onder Voorkeuren → Interface → Uitstraling & gevoel of door calibre uit te voeren met de CALIBRE_OVERRIDE_LANG
omgevingsvariabele ingesteld. Voorbeeld:
CALIBRE_OVERRIDE_LANG=de
Vervang de
met de taalcode van de taal die u wilt testen.
For translations with plurals, use the ngettext()
function instead of
_()
. For example:
ngettext('Delete a book', 'Delete {} books', num_books).format(num_books)
De plug-in API¶
Zoals u misschien heeft opgemerkt is een plug-in in calibre een class. Er zijn verschillende classes voor verschillende types plug-ins. Details van iedere class, inclusief de basis class van alle plugins zijn te vinden in API documentatie voor plugins.
Uw plugin zal bijna zeker code gebruiken van calibre. Om te leren hoe u diverse stukjes functionaliteit vindt in de calibre code basis, lees de sectie over calibre’s Code opmaak.
Plug-ins foutopsporing¶
De eerste, belangrijkste stap is calibre draaien in foutenopsporingsmodus. U kunt dit doen in de opdrachtregel met:
calibre-debug -g
Of vanuit calibre door met de rechts te klikken op de Preferences knop of gebruik te maken van de Ctrl+Shift+R sneltoets .
Wanneer u werkt met de opdrachtregel verschijnt de foutoplossing op de console, wanneer u werkt in calibre zal dit naar een .txt-bestand worden geschreven.
U kunt overal printinstructies invoegen in uw plug-incode, deze worden uitgevoerd in de foutopsporingsmodus. Vergeet niet, dit is Python, uzou echt niets meer nodig moeten hebben dan print commando’s om te debuggen ;) Ik heb heel calibre ontwikkeld met alleen deze foutopsporingstechniek.
U kunt snel wijzigingen aan uw plugin testen met behulp van de volgende opdrachtregel:
calibre-debug -s; calibre-customize -b /path/to/your/plugin/folder; calibre
Dit sluit een lopende calibre af, wacht totdat het programma geheel afgesloten is, dan uw plugin updaten in calibre en calibre herstarten.
Meer voorbeelden van plugins¶
U kan een lijst met gesofisticeerde calibre plug-ins vinden hier.