diff --git a/manual/creating_plugins.rst b/manual/creating_plugins.rst index 4be39495d1..6ff64157c7 100644 --- a/manual/creating_plugins.rst +++ b/manual/creating_plugins.rst @@ -266,6 +266,9 @@ The important part of the plugin is in two functions: .. literalinclude:: plugin_examples/webengine_demo/ui.py :lines: 47- +.. literalinclude:: plugin_examples/webengine_demo/main.py + :lines: 12- + The ``show_demo()`` function asks the user for a URL and then runs the ``main()`` function passing it that URL. The ``main()`` function diff --git a/manual/plugin_examples/webengine_demo/main.py b/manual/plugin_examples/webengine_demo/main.py index 300b85ad3f..4843f484f2 100644 --- a/manual/plugin_examples/webengine_demo/main.py +++ b/manual/plugin_examples/webengine_demo/main.py @@ -1,154 +1,22 @@ #!/usr/bin/env python2 -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2019, Kovid Goyal + from __future__ import absolute_import, division, print_function, unicode_literals -__license__ = 'GPL v3' -__copyright__ = '2011, Kovid Goyal ' -__docformat__ = 'restructuredtext en' +from PyQt5.Qt import QUrl +from PyQt5.QtWebEngineWidgets import QWebEngineView -if False: - # This is here to keep my python error checker from complaining about - # the builtin functions that will be defined by the plugin loading system - # You do not need this code in your plugins - get_icons = get_resources = None - -from PyQt5.Qt import QDialog, QVBoxLayout, QPushButton, QMessageBox, QLabel - -from calibre_plugins.interface_demo.config import prefs +from calibre.gui2 import Application -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']) +def main(url): + # This function is run in a separate process and can do anything it likes, + # including use QWebEngine. Here it simply opens the passed in URL + # in a QWebEngineView + app = Application([]) + w = QWebEngineView() + w.setUrl(QUrl(url)) + w.show() + w.raise_() + app.exec_() diff --git a/manual/plugin_examples/webengine_demo/ui.py b/manual/plugin_examples/webengine_demo/ui.py index 41e105c936..4a1c87c3fc 100644 --- a/manual/plugin_examples/webengine_demo/ui.py +++ b/manual/plugin_examples/webengine_demo/ui.py @@ -11,7 +11,7 @@ if False: # The class that all interface action plugins must inherit from from calibre.gui2.actions import InterfaceAction -from PyQt5.Qt import QInputDialog, QUrl +from PyQt5.Qt import QInputDialog class InterfacePlugin(InterfaceAction): @@ -52,21 +52,4 @@ class InterfacePlugin(InterfaceAction): return # Launch a separate process to view the URL in WebEngine self.gui.job_manager.launch_gui_app('webengine-dialog', kwargs={ - 'module':'calibre_plugins.webengine_demo.ui', 'url':url}) - - -def main(url): - # This function is run in a separate process and can do anything it likes, - # including use QWebEngine. Here it simply opens the passed in URL - # in a QWebEngineView - - # This import must happen before creating the Application() object - from PyQt5.QtWebEngineWidgets import QWebEngineView - - from calibre.gui2 import Application - app = Application([]) - w = QWebEngineView() - w.setUrl(QUrl(url)) - w.show() - w.raise_() - app.exec_() + 'module':'calibre_plugins.webengine_demo.main', 'url':url})