mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-07 09:01:38 -04:00
Finish the framework for the edit book plugins
This commit is contained in:
parent
2befb1e2e9
commit
c7ffb86488
@ -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)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -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>'
|
||||||
|
@ -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
|
||||||
|
@ -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']
|
||||||
|
@ -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]
|
||||||
|
@ -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',),
|
||||||
|
@ -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):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user