plugin_handler.py
Go to the documentation of this file.
00001 # Copyright (c) 2011, Dirk Thomas, Dorian Scholz, TU Darmstadt
00002 # All rights reserved.
00003 #
00004 # Redistribution and use in source and binary forms, with or without
00005 # modification, are permitted provided that the following conditions
00006 # are met:
00007 #
00008 #   * Redistributions of source code must retain the above copyright
00009 #     notice, this list of conditions and the following disclaimer.
00010 #   * Redistributions in binary form must reproduce the above
00011 #     copyright notice, this list of conditions and the following
00012 #     disclaimer in the documentation and/or other materials provided
00013 #     with the distribution.
00014 #   * Neither the name of the TU Darmstadt nor the names of its
00015 #     contributors may be used to endorse or promote products derived
00016 #     from this software without specific prior written permission.
00017 #
00018 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00019 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00020 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00021 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00022 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00023 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00024 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00026 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00027 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00028 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00029 # POSSIBILITY OF SUCH DAMAGE.
00030 
00031 import traceback
00032 
00033 from python_qt_binding.QtCore import qCritical, qDebug, QObject, Qt, qWarning, Signal, Slot
00034 from python_qt_binding.QtGui import QDockWidget, QToolBar
00035 
00036 from .dock_widget import DockWidget
00037 from .dock_widget_title_bar import DockWidgetTitleBar
00038 from .window_title_changed_signaler import WindowTitleChangedSignaler
00039 
00040 
00041 class PluginHandler(QObject):
00042 
00043     """
00044     Base class for the bidirectional exchange between the framework and one `Plugin` instance.
00045     It utilizes a `PluginProvider` to load/unload the plugin and provides callbacks for the `PluginContext`.
00046     """
00047 
00048     close_signal = Signal(str)
00049     reload_signal = Signal(str)
00050     help_signal = Signal(str)
00051     _defered_check_close = Signal()
00052 
00053     def __init__(self, parent, main_window, instance_id, application_context, container_manager, argv=None):
00054         super(PluginHandler, self).__init__(parent)
00055         self.setObjectName('PluginHandler')
00056 
00057         self._main_window = main_window
00058         self._instance_id = instance_id
00059         self._application_context = application_context
00060         self._container_manager = container_manager
00061         self._argv = argv if argv else []
00062 
00063         self._defered_check_close.connect(self._check_close, Qt.QueuedConnection)
00064         self._plugin_provider = None
00065         self.__callback = None
00066         self.__instance_settings = None
00067 
00068         self._plugin_has_configuration = False
00069 
00070         # mapping of added widgets to their parent dock widget and WindowTitleChangedSignaler
00071         self._widgets = {}
00072 
00073         self._toolbars = []
00074 
00075     def instance_id(self):
00076         return self._instance_id
00077 
00078     def argv(self):
00079         return self._argv
00080 
00081     def load(self, plugin_provider, callback=None):
00082         """
00083         Load plugin.
00084         Completion is signaled asynchronously if a callback is passed.
00085         """
00086         self._plugin_provider = plugin_provider
00087         self.__callback = callback
00088         try:
00089             self._load()
00090         except Exception as e:
00091             self._emit_load_completed(e)
00092 
00093     def _load(self):
00094         raise NotImplementedError
00095 
00096     def _emit_load_completed(self, exception=None):
00097         if exception is not None:
00098             self._garbage_widgets_and_toolbars()
00099         if self.__callback is not None:
00100             callback = self.__callback
00101             self.__callback = None
00102             callback(self, exception)
00103         elif exception is not None:
00104             qCritical('PluginHandler.load() failed%s' % (':\n%s' % str(exception) if exception != True else ''))
00105 
00106     def _garbage_widgets_and_toolbars(self):
00107         for widget in self._widgets.keys():
00108             self.remove_widget(widget)
00109             self._delete_widget(widget)
00110         for toolbar in self._toolbars:
00111             self.remove_toolbar(toolbar)
00112             self._delete_toolbar(toolbar)
00113 
00114     def shutdown_plugin(self, callback):
00115         """
00116         Shutdown plugin (`Plugin.shutdown_plugin()`) and remove all added widgets.
00117         Completion is signaled asynchronously if a callback is passed.
00118         """
00119         self.__callback = callback
00120         try:
00121             self._shutdown_plugin()
00122         except Exception:
00123             qCritical('PluginHandler.shutdown_plugin() plugin "%s" raised an exception:\n%s' % (str(self._instance_id), traceback.format_exc()))
00124             self.emit_shutdown_plugin_completed()
00125 
00126     def _shutdown_plugin(self):
00127         raise NotImplementedError
00128 
00129     def emit_shutdown_plugin_completed(self):
00130         self._garbage_widgets_and_toolbars()
00131         if self.__callback is not None:
00132             callback = self.__callback
00133             self.__callback = None
00134             callback(self._instance_id)
00135 
00136     def _delete_widget(self, widget):
00137         widget.deleteLater()
00138 
00139     def _delete_toolbar(self, toolbar):
00140         toolbar.deleteLater()
00141 
00142     def unload(self, callback=None):
00143         """
00144         Unload plugin.
00145         Completion is signaled asynchronously if a callback is passed.
00146         """
00147         self.__callback = callback
00148         try:
00149             self._unload()
00150         except Exception:
00151             qCritical('PluginHandler.unload() plugin "%s" raised an exception:\n%s' % (str(self._instance_id), traceback.format_exc()))
00152             self._emit_unload_completed()
00153 
00154     def _unload(self):
00155         raise NotImplementedError
00156 
00157     def _emit_unload_completed(self):
00158         if self.__callback is not None:
00159             callback = self.__callback
00160             self.__callback = None
00161             callback(self._instance_id)
00162 
00163     def save_settings(self, plugin_settings, instance_settings, callback=None):
00164         """
00165         Save settings of the plugin (`Plugin.save_settings()`) and all dock widget title bars.
00166         Completion is signaled asynchronously if a callback is passed.
00167         """
00168         qDebug('PluginHandler.save_settings()')
00169         self.__instance_settings = instance_settings
00170         self.__callback = callback
00171         try:
00172             self._save_settings(plugin_settings, instance_settings)
00173         except Exception:
00174             qCritical('PluginHandler.save_settings() plugin "%s" raised an exception:\n%s' % (str(self._instance_id), traceback.format_exc()))
00175             self.emit_save_settings_completed()
00176 
00177     def _save_settings(self, plugin_settings, instance_settings):
00178         raise NotImplementedError
00179 
00180     def emit_save_settings_completed(self):
00181         qDebug('PluginHandler.emit_save_settings_completed()')
00182         self._call_method_on_all_dock_widgets('save_settings', self.__instance_settings)
00183         self.__instance_settings = None
00184         if self.__callback is not None:
00185             callback = self.__callback
00186             self.__callback = None
00187             callback(self._instance_id)
00188 
00189     def _call_method_on_all_dock_widgets(self, method_name, instance_settings):
00190         for dock_widget, _ in self._widgets.values():
00191             name = 'dock_widget' + dock_widget.objectName().replace(self._instance_id.tidy_str(), '', 1)
00192             settings = instance_settings.get_settings(name)
00193             method = getattr(dock_widget, method_name)
00194             try:
00195                 method(settings)
00196             except Exception:
00197                 qCritical('PluginHandler._call_method_on_all_dock_widgets(%s) failed:\n%s' % (method_name, traceback.format_exc()))
00198 
00199     def restore_settings(self, plugin_settings, instance_settings, callback=None):
00200         """
00201         Restore settings of the plugin (`Plugin.restore_settings()`) and all dock widget title bars.
00202         Completion is signaled asynchronously if a callback is passed.
00203         """
00204         qDebug('PluginHandler.restore_settings()')
00205         self.__instance_settings = instance_settings
00206         self.__callback = callback
00207         try:
00208             self._restore_settings(plugin_settings, instance_settings)
00209         except Exception:
00210             qCritical('PluginHandler.restore_settings() plugin "%s" raised an exception:\n%s' % (str(self._instance_id), traceback.format_exc()))
00211             self.emit_restore_settings_completed()
00212 
00213     def _restore_settings(self, plugin_settings, instance_settings):
00214         raise NotImplementedError
00215 
00216     def emit_restore_settings_completed(self):
00217         qDebug('PluginHandler.emit_restore_settings_completed()')
00218         # call after plugin has restored settings as it may spawn additional dock widgets
00219         self._call_method_on_all_dock_widgets('restore_settings', self.__instance_settings)
00220         self.__instance_settings = None
00221         if self.__callback is not None:
00222             callback = self.__callback
00223             self.__callback = None
00224             callback(self._instance_id)
00225 
00226     def _create_dock_widget(self):
00227         dock_widget = DockWidget(self._container_manager)
00228         if self._application_context.options.lock_perspective or self._application_context.options.standalone_plugin:
00229             # plugins are not closable when perspective is locked or plugins is running standalone
00230             features = dock_widget.features()
00231             dock_widget.setFeatures(features ^ QDockWidget.DockWidgetClosable)
00232         self._update_title_bar(dock_widget)
00233         return dock_widget
00234 
00235     def _update_title_bar(self, dock_widget, hide_help=False, hide_reload=False):
00236         title_bar = dock_widget.titleBarWidget()
00237         if title_bar is None:
00238             title_bar = DockWidgetTitleBar(dock_widget)
00239             dock_widget.setTitleBarWidget(title_bar)
00240 
00241             # connect extra buttons
00242             title_bar.connect_close_button(self._remove_widget_by_dock_widget)
00243             title_bar.connect_button('help', self._emit_help_signal)
00244             title_bar.show_button('help', not hide_help)
00245             title_bar.connect_button('reload', self._emit_reload_signal)
00246             title_bar.show_button('reload', not hide_reload)
00247             title_bar.connect_button('configuration', self._trigger_configuration)
00248             title_bar.show_button('configuration', self._plugin_has_configuration)
00249 
00250     def _update_title_bars(self):
00251         if self._plugin_has_configuration:
00252             for dock_widget, _ in self._widgets.values():
00253                 title_bar = dock_widget.titleBarWidget()
00254                 title_bar.show_button('configuration')
00255 
00256     def _remove_widget_by_dock_widget(self, dock_widget):
00257         widget = [key for key, value in self._widgets.iteritems() if value[0] == dock_widget][0]
00258         self.remove_widget(widget)
00259 
00260     def _emit_help_signal(self):
00261         self.help_signal.emit(str(self._instance_id))
00262 
00263     def _emit_reload_signal(self):
00264         self.reload_signal.emit(str(self._instance_id))
00265 
00266     def _trigger_configuration(self):
00267         self._plugin.trigger_configuration()
00268 
00269     def _add_dock_widget(self, dock_widget, widget):
00270         dock_widget.setWidget(widget)
00271         # every dock widget needs a unique name for save/restore geometry/state to work
00272         dock_widget.setObjectName(self._instance_id.tidy_str() + '__' + widget.objectName())
00273         self._add_dock_widget_to_main_window(dock_widget)
00274         signaler = WindowTitleChangedSignaler(widget, widget)
00275         signaler.window_title_changed_signal.connect(self._on_widget_title_changed)
00276         self._widgets[widget] = [dock_widget, signaler]
00277         # trigger to update initial window title
00278         signaler.window_title_changed_signal.emit(widget)
00279 
00280     def _add_dock_widget_to_main_window(self, dock_widget):
00281         if self._main_window is not None:
00282             # warn about dock_widget with same object name
00283             old_dock_widget = self._main_window.findChild(DockWidget, dock_widget.objectName())
00284             if old_dock_widget is not None:
00285                 qWarning('PluginHandler._add_dock_widget_to_main_window() duplicate object name "%s", assign unique object names before adding widgets!' % dock_widget.objectName())
00286             self._main_window.addDockWidget(Qt.BottomDockWidgetArea, dock_widget)
00287 
00288     def _on_widget_title_changed(self, widget):
00289         dock_widget, _ = self._widgets[widget]
00290         dock_widget.setWindowTitle(widget.windowTitle())
00291 
00292     def _update_widget_title(self, widget, title):
00293         dock_widget, _ = self._widgets[widget]
00294         dock_widget.setWindowTitle(title)
00295 
00296     # pointer to QWidget must be used for PySide to work (at least with 1.0.1)
00297     @Slot('QWidget*')
00298     def remove_widget(self, widget):
00299         dock_widget, signaler = self._widgets[widget]
00300         self._widgets.pop(widget)
00301         if signaler is not None:
00302             signaler.window_title_changed_signal.disconnect(self._on_widget_title_changed)
00303         # remove dock widget from parent and delete later
00304         if self._main_window is not None:
00305             dock_widget.parent().removeDockWidget(dock_widget)
00306         # do not delete the widget, only the dock widget
00307         widget.setParent(None)
00308         dock_widget.deleteLater()
00309         # defer check for last widget closed to give plugin a chance to add another widget right away
00310         self._defered_check_close.emit()
00311 
00312     def _add_toolbar(self, toolbar):
00313         # every toolbar needs a unique name for save/restore geometry/state to work
00314         toolbar_object_name = toolbar.objectName()
00315         prefix = self._instance_id.tidy_str() + '__'
00316         # when added, removed and readded the prefix should not be prepended multiple times
00317         if not toolbar_object_name.startswith(prefix):
00318             toolbar_object_name = prefix + toolbar_object_name
00319         toolbar.setObjectName(toolbar_object_name)
00320 
00321         self._toolbars.append(toolbar)
00322         if self._main_window is not None:
00323             # warn about toolbar with same object name
00324             old_toolbar = self._main_window.findChild(QToolBar, toolbar.objectName())
00325             if old_toolbar is not None:
00326                 qWarning('PluginHandler._add_toolbar() duplicate object name "%s", assign unique object names before adding toolbars!' % toolbar.objectName())
00327             self._main_window.addToolBar(Qt.TopToolBarArea, toolbar)
00328 
00329     # pointer to QToolBar must be used for PySide to work (at least with 1.0.1)
00330     @Slot('QToolBar*')
00331     def remove_toolbar(self, toolbar):
00332         self._toolbars.remove(toolbar)
00333         # detach toolbar from parent
00334         if toolbar.parent():
00335             toolbar.parent().removeToolBar(toolbar)
00336         # defer check for last widget closed to give plugin a chance to add another widget right away
00337         self._defered_check_close.emit()
00338 
00339     def _check_close(self):
00340         # close plugin when no widgets or toolbars are left
00341         if len(self._widgets) + len(self._toolbars) == 0:
00342             self._emit_close_plugin()
00343 
00344     def _emit_close_plugin(self):
00345         self.close_signal.emit(str(self._instance_id))


qt_gui
Author(s): Dirk Thomas
autogenerated on Fri Jan 3 2014 11:44:00