Finish the framework for the edit book plugins

This commit is contained in:
Kovid Goyal 2014-07-17 17:59:20 +05:30
parent 2befb1e2e9
commit c7ffb86488
7 changed files with 142 additions and 9 deletions

View File

@ -687,6 +687,8 @@ class StoreBase(Plugin): # {{{
class ViewerPlugin(Plugin): # {{{ class ViewerPlugin(Plugin): # {{{
type = _('Viewer')
''' '''
These plugins are used to add functionality to the calibre viewer. These plugins are used to add functionality to the calibre viewer.
''' '''
@ -744,7 +746,8 @@ class ViewerPlugin(Plugin): # {{{
class EditBookToolPlugin(Plugin): # {{{ class EditBookToolPlugin(Plugin): # {{{
minimum_calibre_version = (1, 45, 0) type = _('Edit Book Tool')
minimum_calibre_version = (1, 46, 0)
# }}} # }}}

View File

@ -707,6 +707,8 @@ class PluginUpdaterDialog(SizePersistedDialog):
do_restart = False do_restart = False
try: try:
from calibre.customize.ui import config
installed_plugins = frozenset(config['plugins'])
try: try:
plugin = add_plugin(zip_path) plugin = add_plugin(zip_path)
except NameConflict as e: except NameConflict as e:
@ -715,7 +717,7 @@ class PluginUpdaterDialog(SizePersistedDialog):
# Check for any toolbars to add to. # Check for any toolbars to add to.
widget = ConfigWidget(self.gui) widget = ConfigWidget(self.gui)
widget.gui = self.gui widget.gui = self.gui
widget.check_for_add_to_toolbars(plugin) widget.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins)
self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name) self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name)
d = info_dialog(self.gui, _('Success'), d = info_dialog(self.gui, _('Success'),
_('Plugin <b>{0}</b> successfully installed under <b>' _('Plugin <b>{0}</b> successfully installed under <b>'

View File

@ -304,6 +304,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
' Are you sure you want to proceed?'), ' Are you sure you want to proceed?'),
show_copy_button=False): show_copy_button=False):
return return
from calibre.customize.ui import config
installed_plugins = frozenset(config['plugins'])
try: try:
plugin = add_plugin(path) plugin = add_plugin(path)
except NameConflict as e: except NameConflict as e:
@ -312,7 +314,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self._plugin_model.populate() self._plugin_model.populate()
self._plugin_model.reset() self._plugin_model.reset()
self.changed_signal.emit() self.changed_signal.emit()
self.check_for_add_to_toolbars(plugin) self.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins)
info_dialog(self, _('Success'), info_dialog(self, _('Success'),
_('Plugin <b>{0}</b> successfully installed under <b>' _('Plugin <b>{0}</b> successfully installed under <b>'
' {1} plugins</b>. You may have to restart calibre ' ' {1} plugins</b>. You may have to restart calibre '
@ -399,9 +401,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
if 'Store' in self.gui.iactions: if 'Store' in self.gui.iactions:
self.gui.iactions['Store'].load_menu() self.gui.iactions['Store'].load_menu()
def check_for_add_to_toolbars(self, plugin): def check_for_add_to_toolbars(self, plugin, previously_installed=True):
from calibre.gui2.preferences.toolbar import ConfigWidget from calibre.gui2.preferences.toolbar import ConfigWidget
from calibre.customize import InterfaceActionBase from calibre.customize import InterfaceActionBase, EditBookToolPlugin
if isinstance(plugin, EditBookToolPlugin):
return self.check_for_add_to_editor_toolbar(plugin, previously_installed)
if not isinstance(plugin, InterfaceActionBase): if not isinstance(plugin, InterfaceActionBase):
return return
@ -436,6 +441,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
installed_actions.append(plugin_action.name) installed_actions.append(plugin_action.name)
gprefs['action-layout-'+key] = tuple(installed_actions) gprefs['action-layout-'+key] = tuple(installed_actions)
def check_for_add_to_editor_toolbar(self, plugin, previously_installed):
if not previously_installed:
from calibre.gui2.tweak_book.plugin import install_plugin
install_plugin(plugin)
if __name__ == '__main__': if __name__ == '__main__':
from PyQt4.Qt import QApplication from PyQt4.Qt import QApplication

View File

@ -52,6 +52,7 @@ d['remove_unused_classes'] = False
d['global_book_toolbar'] = [ d['global_book_toolbar'] = [
'new-file', 'open-book', 'save-book', None, 'global-undo', 'global-redo', 'create-checkpoint', None, 'donate', 'user-manual'] 'new-file', 'open-book', 'save-book', None, 'global-undo', 'global-redo', 'create-checkpoint', None, 'donate', 'user-manual']
d['global_tools_toolbar'] = ['check-book', 'spell-check-book', 'edit-toc', 'insert-character', 'manage-fonts', 'smarten-punctuation', 'remove-unused-css'] d['global_tools_toolbar'] = ['check-book', 'spell-check-book', 'edit-toc', 'insert-character', 'manage-fonts', 'smarten-punctuation', 'remove-unused-css']
d['global_plugins_toolbar'] = []
d['editor_css_toolbar'] = ['pretty-current', 'insert-image'] d['editor_css_toolbar'] = ['pretty-current', 'insert-image']
d['editor_xml_toolbar'] = ['pretty-current', 'insert-tag'] d['editor_xml_toolbar'] = ['pretty-current', 'insert-tag']
d['editor_html_toolbar'] = ['fix-html-current', 'pretty-current', 'insert-image', 'insert-hyperlink', 'insert-tag', 'change-paragraph'] d['editor_html_toolbar'] = ['fix-html-current', 'pretty-current', 'insert-image', 'insert-hyperlink', 'insert-tag', 'change-paragraph']

View File

@ -6,6 +6,13 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import importlib
from PyQt4.Qt import QToolButton
from calibre import prints
from calibre.customize.ui import all_edit_book_tool_plugins
from calibre.gui2.tweak_book import tprefs
from calibre.gui2.tweak_book.boss import get_boss from calibre.gui2.tweak_book.boss import get_boss
class Tool(object): class Tool(object):
@ -19,6 +26,9 @@ class Tool(object):
#: If True the user can choose to place this tool in the plugins menu #: If True the user can choose to place this tool in the plugins menu
allowed_in_menu = True allowed_in_menu = True
#: The popup mode for the menu (if any) of the toolbar button. Possible values are 'delayed', 'instant', 'button'
toolbar_button_popup_mode = 'delayed'
@property @property
def boss(self): def boss(self):
' The :class:`calibre.gui2.tweak_book.boss.Boss` object. Used to control the user interface. ' ' The :class:`calibre.gui2.tweak_book.boss.Boss` object. Used to control the user interface. '
@ -29,13 +39,105 @@ class Tool(object):
' The main window of the user interface ' ' The main window of the user interface '
return self.boss.gui return self.boss.gui
def register_shortcut(self, qaction, unique_name, default_keys=(), short_text=None, description=None, **extra_data):
'''
Register a keyboard shortcut that will trigger the specified ``qaction``. This keyboard shortcut
will become automatically customizable by the user in the Keyboard section of the editor preferences.
:param qaction: A QAction object, it will be triggered when the
configured key combination is pressed by the user.
:param unique_name: A unique name for this shortcut/action. It will be
used internally, it must not be shared by any other actions in this
plugin.
:param default_keys: A list of the default keyboard shortcuts. If not
specified no default shortcuts will be set. If the shortcuts specified
here conflict with either builtin shortcuts or shortcuts from user
configuration/other plugins, they will be ignored. In that case, users
will have to configure the shortcuts manually via Preferences. For example:
``default_keys=('Ctrl+J', 'F9')``.
:param short_text: An optional short description of this action. If not
specified the text from the QAction will be used.
:param description: An optional longer description of this action, it
will be used in the preferences entry for this shortcut.
'''
short_text = short_text or unicode(qaction.text()).replace('&&', '\0').replace('&', '').replace('\0', '&')
self.gui.keyboard.register_shortcut(
self.name + '_' + unique_name, short_text, default_keys=default_keys,
description=description or '', group=_('Plugins'))
def create_action(self, for_toolbar=True): def create_action(self, for_toolbar=True):
''' '''
Create a QAction that will be added to either the plugins toolbar or Create a QAction that will be added to either the plugins toolbar or
the plugins menu depending on ``for_toolbar``. For example:: the plugins menu depending on ``for_toolbar``. For example::
def create_action(self, for_toolbar): def create_action(self, for_toolbar=True):
ac = QAction( ac = QAction(get_icons('myicon.png'), 'Do something')
if for_toolbar:
# We want the toolbar button to have a popup menu
menu = QMenu()
ac.setMenu(menu)
menu.addAction('Do something else')
subaction = menu.addAction('And another')
# Register a keyboard shortcut for this toolbar action be
# careful to do this for only one of the toolbar action or
# the menu action, not both.
self.register_shortcut(ac, 'some-unique-name', default_keys=('Ctrl+K',))
return ac
.. seealso:: Method :meth:`register_shortcut`.
''' '''
raise NotImplementedError() raise NotImplementedError()
def load_plugin_tools(plugin):
try:
main = importlib.import_module(plugin.__class__.__module__+'.main')
except ImportError:
import traceback
traceback.print_stack()
else:
for x in vars(main).itervalues():
if isinstance(x, type) and x is not Tool and issubclass(x, Tool):
yield x()
def plugin_action_sid(plugin, tool, for_toolbar=True):
return plugin.name + tool.name + ('toolbar' if for_toolbar else 'menu')
def create_plugin_action(plugin, tool, for_toolbar, actions=None, toolbar_actions=None, plugin_menu_actions=None):
try:
ac = tool.create_action(for_toolbar=for_toolbar)
except Exception:
import traceback
traceback.print_stack()
return
sid = plugin_action_sid(plugin, tool, for_toolbar)
if actions is not None and sid in actions:
prints('The %s tool from the %s plugin has a non unique name, ignoring' % (tool.name, plugin.name))
else:
if actions is not None:
actions[sid] = ac
ac.sid = sid
if for_toolbar:
if toolbar_actions is not None:
toolbar_actions[sid] = ac
ac.popup_mode = {'instant':QToolButton.InstantPopup, 'button':QToolButton.MenuButtonPopup}.get(
tool.toolbar_button_popup_mode, QToolButton.DelayedPopup)
else:
if plugin_menu_actions is not None:
plugin_menu_actions.append(ac)
return ac
def create_plugin_actions(actions, toolbar_actions, plugin_menu_actions):
for plugin in all_edit_book_tool_plugins():
for tool in load_plugin_tools(plugin):
if tool.allowed_in_toolbar:
create_plugin_action(plugin, tool, True, actions, toolbar_actions, plugin_menu_actions)
if tool.allowed_in_menu:
create_plugin_action(plugin, tool, False, actions, toolbar_actions, plugin_menu_actions)
def install_plugin(plugin):
for tool in load_plugin_tools(plugin):
if tool.allowed_in_toolbar:
sid = plugin_action_sid(plugin, tool, True)
if sid not in tprefs['global_plugins_toolbar']:
tprefs['global_plugins_toolbar'] = tprefs['global_plugins_toolbar'] + [sid]

View File

@ -339,6 +339,7 @@ class ToolbarSettings(QWidget):
for name, text in ( for name, text in (
('global_book_toolbar', _('Book wide actions'),), ('global_book_toolbar', _('Book wide actions'),),
('global_tools_toolbar', _('Book wide tools'),), ('global_tools_toolbar', _('Book wide tools'),),
('global_plugins_toolbar', _('Book wide tools from third party plugins'),),
('editor_html_toolbar', ft % 'HTML',), ('editor_html_toolbar', ft % 'HTML',),
('editor_css_toolbar', ft % 'CSS',), ('editor_css_toolbar', ft % 'CSS',),
('editor_xml_toolbar', ft % 'XML',), ('editor_xml_toolbar', ft % 'XML',),

View File

@ -28,6 +28,7 @@ from calibre.gui2.tweak_book.job import BlockingJob
from calibre.gui2.tweak_book.boss import Boss from calibre.gui2.tweak_book.boss import Boss
from calibre.gui2.tweak_book.undo import CheckpointView from calibre.gui2.tweak_book.undo import CheckpointView
from calibre.gui2.tweak_book.preview import Preview from calibre.gui2.tweak_book.preview import Preview
from calibre.gui2.tweak_book.plugin import create_plugin_actions
from calibre.gui2.tweak_book.search import SearchPanel from calibre.gui2.tweak_book.search import SearchPanel
from calibre.gui2.tweak_book.check import Check from calibre.gui2.tweak_book.check import Check
from calibre.gui2.tweak_book.spell import SpellCheck from calibre.gui2.tweak_book.spell import SpellCheck
@ -38,7 +39,7 @@ from calibre.gui2.tweak_book.live_css import LiveCSS
from calibre.gui2.tweak_book.manage_fonts import ManageFonts from calibre.gui2.tweak_book.manage_fonts import ManageFonts
from calibre.gui2.tweak_book.editor.widget import register_text_editor_actions from calibre.gui2.tweak_book.editor.widget import register_text_editor_actions
from calibre.gui2.tweak_book.editor.insert_resource import InsertImage from calibre.gui2.tweak_book.editor.insert_resource import InsertImage
from calibre.utils.icu import character_name from calibre.utils.icu import character_name, sort_key
def open_donate(): def open_donate():
open_url(QUrl('http://calibre-ebook.com/donate')) open_url(QUrl('http://calibre-ebook.com/donate'))
@ -447,6 +448,10 @@ class Main(MainWindow):
self.action_compare_book = treg('diff.png', _('&Compare to another book'), self.boss.compare_book, 'compare-book', (), _( self.action_compare_book = treg('diff.png', _('&Compare to another book'), self.boss.compare_book, 'compare-book', (), _(
'Compare to another book')) 'Compare to another book'))
self.plugin_menu_actions = []
create_plugin_actions(actions, toolbar_actions, self.plugin_menu_actions)
def create_menubar(self): def create_menubar(self):
p, q = self.create_application_menubar() p, q = self.create_application_menubar()
q.triggered.connect(self.action_quit.trigger) q.triggered.connect(self.action_quit.trigger)
@ -536,6 +541,11 @@ class Main(MainWindow):
e.addSeparator() e.addSeparator()
a(self.action_saved_searches) a(self.action_saved_searches)
if self.plugin_menu_actions:
e = b.addMenu(_('&Plugins'))
for ac in sorted(self.plugin_menu_actions, key=lambda x:sort_key(unicode(x.text()))):
e.addAction(ac)
e = b.addMenu(_('&Help')) e = b.addMenu(_('&Help'))
a = e.addAction a = e.addAction
a(self.action_help) a(self.action_help)
@ -558,10 +568,11 @@ class Main(MainWindow):
return b return b
self.global_bar = create(_('Book tool bar'), 'global') self.global_bar = create(_('Book tool bar'), 'global')
self.tools_bar = create(_('Tools tool bar'), 'tools') self.tools_bar = create(_('Tools tool bar'), 'tools')
self.plugins_bar = create(_('Plugins tool bar'), 'plugins')
self.populate_toolbars(animate=True) self.populate_toolbars(animate=True)
def populate_toolbars(self, animate=False): def populate_toolbars(self, animate=False):
self.global_bar.clear(), self.tools_bar.clear() self.global_bar.clear(), self.tools_bar.clear(), self.plugins_bar.clear()
def add(bar, ac): def add(bar, ac):
if ac is None: if ac is None:
bar.addSeparator() bar.addSeparator()
@ -591,6 +602,10 @@ class Main(MainWindow):
for x in tprefs['global_tools_toolbar']: for x in tprefs['global_tools_toolbar']:
add(self.tools_bar, x) add(self.tools_bar, x)
for x in tprefs['global_plugins_toolbar']:
add(self.plugins_bar, x)
self.plugins_bar.setVisible(bool(tprefs['global_plugins_toolbar']))
def create_docks(self): def create_docks(self):
def create(name, oname): def create(name, oname):