Písanie vlastných zásuvných modulov na rozšírenie funkcií calibre¶
calibre má veľmi modulárny dizajn. Takmer všetky funkcie v calibre prichádzajú vo forme zásuvných modulov. Zásuvné moduly sa používajú na konverziu, na sťahovanie správ (hovorí sa im však recepty), pre rôzne komponenty používateľského rozhrania, na pripojenie k rôznym zariadeniam, na spracovanie súborov pri ich pridávaní do calibre atď. Úplný zoznam všetkých vstavaných zásuvných modulov v calibre získate prechodom na Nastavenia → Pokročilé → Zásuvné moduly.
Tu vás naučíme, ako si vytvoriť vlastné zásuvné moduly, ktoré pridajú do calibre nové funkcie.
Poznámka
Toto sa vzťahuje len na verzie calibre >= 0.8.60
Anatómia zásuvného modulu calibre¶
Zásuvný modul calibre je veľmi jednoduchý — je to len ZIP súbor, ktorý obsahuje kód v Pythone a akékoľvek ďalšie prostriedky, ako sú obrázkové súbory, ktoré modul potrebuje. Bez ďalších rečí sa pozrime na základný príklad.
Predpokladajme, že máte inštaláciu calibre, ktorú používate na samostatné publikovanie rôznych elektronických dokumentov vo formátoch EPUB a MOBI. Chceli by ste, aby všetky súbory vytvorené calibre mali vydavateľa nastaveného na „Hello world“. Tu je postup: Vytvorte súbor s názvom __init__.py (je to špeciálny názov, ktorý sa musí vždy použiť pre hlavný súbor vášho modulu) a vložte doň nasledujúci kód v Pythone:
import os
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
To je všetko. Ak chcete tento kód pridať do calibre ako zásuvný modul, jednoducho v priečinku, v ktorom ste vytvorili __init__.py, spustite nasledujúce:
calibre-customize -b .
Poznámka
V macOS sú nástroje príkazového riadka vnútri balíka calibre; napríklad ak ste calibre nainštalovali do /Applications, nástroje príkazového riadka sú v /Applications/calibre.app/Contents/MacOS/.
Zásuvný modul Hello World si môžete stiahnuť z helloworld_plugin.zip.
Vždy, keď použijete calibre na konverziu knihy, zavolá sa metóda modulu run() a skonvertovaná kniha bude mať vydavateľa nastaveného na „Hello World“. Toto je triviálny modul; prejdime k zložitejšiemu príkladu, ktorý skutočne pridá komponent do používateľského rozhrania.
Zásuvný modul používateľského rozhrania¶
Tento modul bude rozdelený do niekoľkých súborov (aby bol kód prehľadný). Ukáže vám, ako získať prostriedky (obrázky alebo dátové súbory) zo ZIP súboru modulu, ako umožniť používateľom konfigurovať váš modul, ako vytvárať prvky v používateľskom rozhraní calibre a ako pristupovať k databáze kníh v calibre a dotazovať sa na ňu.
Tento modul si môžete stiahnuť z interface_demo_plugin.zip
Prvá vec, ktorú si treba všimnúť, je, že tento ZIP súbor obsahuje oveľa viac súborov, ktoré sú vysvetlené nižšie; venujte osobitnú pozornosť súboru plugin-import-name-interface_demo.txt.
- plugin-import-name-interface_demo.txt
Prázdny textový súbor, ktorý sa používa na povolenie mágie viacsúborových modulov. Tento súbor musí byť prítomný vo všetkých moduloch, ktoré používajú viac ako jeden .py súbor. Mal by byť prázdny a jeho názov musí mať tvar:
plugin-import-name-**some_name**.txt. Prítomnosť tohto súboru vám umožňuje importovať kód zo .py súborov prítomných vnútri ZIP súboru pomocou príkazu, ako je:from calibre_plugins.some_name.some_module import some_objectPredpona
calibre_pluginsmusí byť vždy prítomná.some_namepochádza z názvu prázdneho textového súboru.some_moduleodkazuje na súborsome_module.pyvnútri ZIP súboru. Všimnite si, že tento import je rovnako výkonný ako bežné importy v Pythone. Vnútri ZIP súboru môžete vytvárať balíky a podbalíky .py modulov, rovnako ako obvykle (definovaním __init__.py v každom podpriečinku), a všetko by malo „jednoducho fungovať“.Názov, ktorý použijete pre
some_name, vstupuje do globálneho menného priestoru zdieľaného všetkými modulmi, takže ho urobte čo najjedinečnejším. Pamätajte však, že musí byť platným identifikátorom Pythonu (iba písmená, číslice a podčiarknik).- __init__.py
Rovnako ako predtým, súbor, ktorý definuje triedu modulu
- main.py
Tento súbor obsahuje skutočný kód, ktorý robí niečo užitočné
- ui.py
Tento súbor definuje rozhranie modulu
- images/icon.png
Ikona tohto modulu
- about.txt
Textový súbor s informáciami o module
- translations
Priečinok obsahujúci .mo súbory s prekladmi používateľského rozhrania vášho modulu do rôznych jazykov. Podrobnosti nájdete nižšie.
Teraz sa pozrime na kód.
__init__.py¶
Najprv povinný súbor __init__.py, ktorý definuje metadáta modulu:
# The class that all Interface Action plugin wrappers must inherit from
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()
Jedinou pozoruhodnou funkciou je pole actual_plugin. Keďže calibre má rozhranie pre príkazový riadok aj GUI, GUI moduly ako tento by nemali načítať žiadne GUI knižnice v __init__.py. Pole actual_plugin to urobí za vás tak, že calibre povie, že skutočný modul sa nachádza v inom súbore vnútri vášho ZIP archívu, ktorý sa načíta iba v kontexte GUI.
Nezabudnite, že aby to fungovalo, musíte mať vo svojom ZIP súbore modulu súbor plugin-import-name-some_name.txt, ako sme uviedli vyššie.
Tiež existuje niekoľko metód na povolenie používateľskej konfigurácie modulu. Sú diskutované nižšie.
ui.py¶
Teraz sa pozrime na ui.py, ktorý definuje samotný GUI modul. Zdrojový kód je bohato komentovaný a mal by byť samovysvetľujúci:
# The class that all interface action plugins must inherit from
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 don't 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
assert base_plugin_object is not None
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¶
Skutočná logika na implementáciu dialógového okna ukážky modulu rozhrania.
from qt.core import QDialog, QLabel, QMessageBox, QPushButton, QVBoxLayout
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 don't 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', f'Updated the metadata in the files of {len(ids)} book(s)', show=True)
def config(self):
self.do_user_config(parent=self)
# Apply the changes
self.label.setText(prefs['hello_world_msg'])
Získavanie prostriedkov zo ZIP súboru modulu¶
Systém načítavania modulov calibre definuje niekoľko vstavaných funkcií, ktoré vám umožňujú pohodlne získať súbory zo ZIP súboru modulu.
- get_resources(name_or_list_of_names)
Túto funkciu by ste mali volať so zoznamom ciest k súborom vnútri ZIP súboru. Ak chcete napríklad získať prístup k súboru
icon.pngv priečinku images v ZIP súbore, použili by ste:images/icon.png. Ako oddeľovač cesty vždy používajte lomítko, a to aj vo Windows. Ak zadáte jeden názov, funkcia vráti surové bajty tohto súboru alebo None, ak sa názov v ZIP súbore nenašiel. Ak zadáte viac ako jeden názov, vráti slovník priraďujúci názvy k bajtom. Ak sa názov nenájde, nebude prítomný vo vrátenom slovníku.- get_icons(name_or_list_of_names, plugin_name=‘‘)
Obal pre get_resources(), ktorý vytvára objekty QIcon zo surových bajtov vrátených funkciou get_resources. Ak sa názov v ZIP súbore nenájde, zodpovedajúci QIcon bude null. Ak chcete podporiť témy ikon, zadajte používateľsky prívetivý názov svojho modulu ako
plugin_name. Ak používateľ používa tému ikon s ikonami pre váš modul, načítajú sa prednostne.
Povolenie používateľskej konfigurácie vášho modulu¶
Ak chcete používateľom umožniť konfigurovať váš modul, musíte v základnej triede modulu definovať tri metódy: is_customizable, config_widget a save_settings, ako je uvedené nižšie:
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 má mnoho rôznych spôsobov, ako ukladať konfiguračné údaje (dedičstvo jeho dlhej histórie). Odporúčaným spôsobom je použiť triedu JSONConfig, ktorá ukladá vaše konfiguračné informácie do súboru .json.
Kód na správu konfiguračných údajov v ukážkovom module je v config.py:
from qt.core import QHBoxLayout, QLabel, QLineEdit, QWidget
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 don't 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()
Objekt prefs je teraz dostupný v celom kóde modulu jednoduchým:
from calibre_plugins.interface_demo.config import prefs
Použitie objektu prefs môžete vidieť v main.py:
def config(self):
self.do_user_config(parent=self)
# Apply the changes
self.label.setText(prefs['hello_world_msg'])
Zásuvné moduly editora kníh¶
Teraz trochu zmeňme tému a pozrime sa na vytvorenie modulu, ktorý pridá nástroje do editora kníh calibre. Modul je k dispozícii tu: editor_demo_plugin.zip.
Prvým krokom, rovnako ako pri všetkých moduloch, je vytvoriť prázdny txt súbor s názvom importu, ako je opísané vyššie. Súbor pomenujeme plugin-import-name-editor_plugin_demo.txt.
Teraz vytvoríme povinný súbor __init__.py, ktorý obsahuje metadáta o module — jeho názov, autora, verziu atď.
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)
Jeden modul editora môže poskytovať viacero nástrojov; každý nástroj zodpovedá jednému tlačidlu na paneli nástrojov a jednej položke v ponuke Zásuvné moduly v editore. Tieto môžu mať podponuky, ak má nástroj viacero súvisiacich akcií.
Všetky nástroje musia byť definované v súbore main.py vo vašom module. Každý nástroj je trieda, ktorá dedí z triedy calibre.gui2.tweak_book.plugin.Tool. Pozrime sa na main.py z ukážkového modulu; zdrojový kód je bohato komentovaný a mal by byť samovysvetľujúci. Ďalšie podrobnosti nájdete v dokumentácii API triedy calibre.gui2.tweak_book.plugin.Tool.
main.py¶
Tu uvidíme definíciu jedného nástroja, ktorý vynásobí všetky veľkosti písma v knihe číslom zadaným používateľom. Tento nástroj predvádza rôzne dôležité koncepty, ktoré budete potrebovať pri vývoji vlastných modulov, takže by ste mali pozorne prečítať (bohato komentovaný) zdrojový kód.
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: F821
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:f}')
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
Rozoberme si main.py. Vidíme, že definuje jeden nástroj s názvom Zväčšiť písma. Tento nástroj sa opýta používateľa na číslo a vynásobí všetky veľkosti písma v knihe týmto číslom.
Prvou dôležitou vecou je názov nástroja, ktorý musíte nastaviť na pomerne jedinečný reťazec, pretože sa použije ako kľúč tohto nástroja.
Ďalším dôležitým vstupným bodom je metóda calibre.gui2.tweak_book.plugin.Tool.create_action(). Táto metóda vytvára objekty QAction, ktoré sa objavujú na paneli nástrojov modulov a v ponuke modulov. Tiež voliteľne priraďuje klávesovú skratku, ktorú môže používateľ prispôsobiť. Signál triggered z QAction je prepojený s metódou ask_user(), ktorá sa opýta používateľa na násobiteľ veľkosti písma a potom spustí kód na zväčšenie.
Kód na zväčšenie je dobre komentovaný a pomerne jednoduchý. Hlavné veci, ktoré si treba všimnúť, sú, že získate referenciu na okno editora ako self.gui a editor Boss ako self.boss. Boss je objekt, ktorý ovláda používateľské rozhranie editora. Má mnoho užitočných metód, ktoré sú zdokumentované v triede calibre.gui2.tweak_book.boss.Boss.
Nakoniec existuje self.current_container, čo je referencia na upravovanú knihu ako objekt calibre.ebooks.oeb.polish.container.Container. Ten predstavuje knihu ako kolekciu jej základných HTML/CSS/obrázkových súborov a má pomocné metódy na vykonávanie mnohých užitočných vecí. Objekt kontajnera a rôzne užitočné pomocné funkcie, ktoré možno znova použiť v kóde vášho modulu, sú zdokumentované v Dokumentácia API pre nástroje na úpravu e-kníh.
Pridanie prekladov do vášho modulu¶
Môžete dosiahnuť, aby boli všetky reťazce používateľského rozhrania vo vašom module preložené a zobrazené v jazyku, ktorý je nastavený pre hlavné používateľské rozhranie calibre.
Prvým krokom je prejsť zdrojový kód vášho modulu a označiť všetky používateľovi viditeľné reťazce ako preložiteľné tak, že ich obklopíte znakmi _(). Napríklad:
action_spec = (_('My plugin'), None, _('My plugin is cool'), None)
Potom použite nejaký program na vygenerovanie .po súborov zo zdrojového kódu vášho modulu. Mal by existovať jeden .po súbor pre každý jazyk, do ktorého chcete prekladať. Napríklad: de.po pre nemčinu, fr.po pre francúzštinu atď. Môžete na to použiť program Poedit.
Pošlite tieto .po súbory svojim prekladateľom. Keď ich dostanete späť, skompilujte ich do .mo súborov. Môžete na to opäť použiť Poedit alebo jednoducho spustiť:
calibre-debug -c "from calibre.translations.msgfmt import main; main()" filename.po
Vložte .mo súbory do priečinka translations vo vašom module.
Posledným krokom je jednoducho zavolať funkciu load_translations() v hornej časti .py súborov vášho modulu. Z dôvodu výkonu by ste túto funkciu mali volať iba v tých .py súboroch, ktoré skutočne obsahujú preložiteľné reťazce. Takže v typickom module používateľského rozhrania by ste ju zavolali v hornej časti ui.py, ale nie v __init__.py.
Preklady svojich modulov môžete otestovať zmenou jazyka používateľského rozhrania v calibre v Nastavenia → Rozhranie → Vzhľad a správanie alebo spustením calibre s nastavenou premennou prostredia CALIBRE_OVERRIDE_LANG. Napríklad:
CALIBRE_OVERRIDE_LANG=de
Nahraďte de kódom jazyka, ktorý chcete otestovať.
Pre preklady s množným číslom použite funkciu ngettext() namiesto _(). Napríklad:
ngettext('Delete a book', 'Delete {} books', num_books).format(num_books)
API zásuvných modulov¶
Ako ste si mohli všimnúť vyššie, modul v calibre je trieda. Pre rôzne typy modulov v calibre existujú rôzne triedy. Podrobnosti o každej triede vrátane základnej triedy všetkých modulov nájdete v API dokumentácia pre zásuvné moduly.
Váš modul takmer určite bude používať kód z calibre. Ak sa chcete naučiť, ako nájsť rôzne časti funkčnosti v kódovej základni calibre, prečítajte si sekciu o Usporiadanie kódu calibre.
Ladenie zásuvných modulov¶
Prvým a najdôležitejším krokom je spustiť calibre v režime ladenia. Môžete to urobiť z príkazového riadka pomocou:
calibre-debug -g
alebo z prostredia calibre kliknutím pravým tlačidlom na tlačidlo Nastavenia alebo pomocou klávesovej skratky Ctrl+Shift+R.
Pri spustení z príkazového riadka sa výstup ladenia vypíše do konzoly; pri spustení z prostredia calibre pôjde výstup do txt súboru.
Do kódu svojho modulu môžete kdekoľvek vložiť príkazy print; v režime ladenia sa vypíšu. Pamätajte, toto je Python, na ladenie by ste naozaj nemali potrebovať nič viac ako príkazy print ;) Celé calibre som vyvíjal len pomocou tejto techniky ladenia.
Zmeny vo svojom module môžete rýchlo otestovať pomocou nasledujúceho príkazu:
calibre-debug -s; calibre-customize -b /path/to/your/plugin/folder; calibre
Týmto sa ukončí bežiace calibre, počká sa na dokončenie ukončenia, potom sa aktualizuje váš modul v calibre a calibre sa znova spustí.
Ďalšie príklady modulov¶
Zoznam mnohých sofistikovaných modulov calibre nájdete tu.
