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 .icon_loader import get_icon
00039 from .window_changed_signaler import WindowChangedSignaler
00040 
00041 
00042 class PluginHandler(QObject):
00043 
00044     """
00045     Base class for the bidirectional exchange between the framework and one `Plugin` instance.
00046     It utilizes a `PluginProvider` to load/unload the plugin and provides callbacks for the `PluginContext`.
00047     """
00048 
00049     close_signal = Signal(str)
00050     reload_signal = Signal(str)
00051     help_signal = Signal(str)
00052     _defered_check_close = Signal()
00053 
00054     def __init__(self, parent, main_window, instance_id, application_context, container_manager, argv=None):
00055         super(PluginHandler, self).__init__(parent)
00056         self.setObjectName('PluginHandler')
00057 
00058         self._main_window = main_window
00059         self._instance_id = instance_id
00060         self._application_context = application_context
00061         self._container_manager = container_manager
00062         self._argv = argv if argv else []
00063         self._minimized_dock_widgets_toolbar = None
00064         self._plugin_descriptor = None
00065 
00066         self._defered_check_close.connect(self._check_close, Qt.QueuedConnection)
00067         self._plugin_provider = None
00068         self.__callback = None
00069         self.__instance_settings = None
00070 
00071         self._plugin_has_configuration = False
00072 
00073         # mapping of added widgets to their parent dock widget and WindowChangedSignaler
00074         self._widgets = {}
00075 
00076         self._toolbars = []
00077 
00078     def instance_id(self):
00079         return self._instance_id
00080 
00081     def argv(self):
00082         return self._argv
00083 
00084     def set_minimized_dock_widgets_toolbar(self, toolbar):
00085         self._minimized_dock_widgets_toolbar = toolbar
00086 
00087     def set_plugin_descriptor(self, plugin_descriptor):
00088         self._plugin_descriptor = plugin_descriptor
00089 
00090     def load(self, plugin_provider, callback=None):
00091         """
00092         Load plugin.
00093         Completion is signaled asynchronously if a callback is passed.
00094         """
00095         self._plugin_provider = plugin_provider
00096         self.__callback = callback
00097         try:
00098             self._load()
00099         except Exception as e:
00100             self._emit_load_completed(e)
00101 
00102     def _load(self):
00103         raise NotImplementedError
00104 
00105     def _emit_load_completed(self, exception=None):
00106         if exception is not None:
00107             self._garbage_widgets_and_toolbars()
00108         if self.__callback is not None:
00109             callback = self.__callback
00110             self.__callback = None
00111             callback(self, exception)
00112         elif exception is not None:
00113             qCritical('PluginHandler.load() failed%s' % (':\n%s' % str(exception) if exception != True else ''))
00114 
00115     def _garbage_widgets_and_toolbars(self):
00116         for widget in self._widgets.keys():
00117             self.remove_widget(widget)
00118             self._delete_widget(widget)
00119         for toolbar in self._toolbars:
00120             self.remove_toolbar(toolbar)
00121             self._delete_toolbar(toolbar)
00122 
00123     def shutdown_plugin(self, callback):
00124         """
00125         Shutdown plugin (`Plugin.shutdown_plugin()`) and remove all added widgets.
00126         Completion is signaled asynchronously if a callback is passed.
00127         """
00128         self.__callback = callback
00129         try:
00130             self._shutdown_plugin()
00131         except Exception:
00132             qCritical('PluginHandler.shutdown_plugin() plugin "%s" raised an exception:\n%s' % (str(self._instance_id), traceback.format_exc()))
00133             self.emit_shutdown_plugin_completed()
00134 
00135     def _shutdown_plugin(self):
00136         raise NotImplementedError
00137 
00138     def emit_shutdown_plugin_completed(self):
00139         self._garbage_widgets_and_toolbars()
00140         if self.__callback is not None:
00141             callback = self.__callback
00142             self.__callback = None
00143             callback(self._instance_id)
00144 
00145     def _delete_widget(self, widget):
00146         widget.deleteLater()
00147 
00148     def _delete_toolbar(self, toolbar):
00149         toolbar.deleteLater()
00150 
00151     def unload(self, callback=None):
00152         """
00153         Unload plugin.
00154         Completion is signaled asynchronously if a callback is passed.
00155         """
00156         self.__callback = callback
00157         try:
00158             self._unload()
00159         except Exception:
00160             qCritical('PluginHandler.unload() plugin "%s" raised an exception:\n%s' % (str(self._instance_id), traceback.format_exc()))
00161             self._emit_unload_completed()
00162 
00163     def _unload(self):
00164         raise NotImplementedError
00165 
00166     def _emit_unload_completed(self):
00167         if self.__callback is not None:
00168             callback = self.__callback
00169             self.__callback = None
00170             callback(self._instance_id)
00171 
00172     def save_settings(self, plugin_settings, instance_settings, callback=None):
00173         """
00174         Save settings of the plugin (`Plugin.save_settings()`) and all dock widget title bars.
00175         Completion is signaled asynchronously if a callback is passed.
00176         """
00177         qDebug('PluginHandler.save_settings()')
00178         self.__instance_settings = instance_settings
00179         self.__callback = callback
00180         try:
00181             self._save_settings(plugin_settings, instance_settings)
00182         except Exception:
00183             qCritical('PluginHandler.save_settings() plugin "%s" raised an exception:\n%s' % (str(self._instance_id), traceback.format_exc()))
00184             self.emit_save_settings_completed()
00185 
00186     def _save_settings(self, plugin_settings, instance_settings):
00187         raise NotImplementedError
00188 
00189     def emit_save_settings_completed(self):
00190         qDebug('PluginHandler.emit_save_settings_completed()')
00191         self._call_method_on_all_dock_widgets('save_settings', self.__instance_settings)
00192         self.__instance_settings = None
00193         if self.__callback is not None:
00194             callback = self.__callback
00195             self.__callback = None
00196             callback(self._instance_id)
00197 
00198     def _call_method_on_all_dock_widgets(self, method_name, instance_settings):
00199         for dock_widget, _, _ in self._widgets.values():
00200             name = 'dock_widget' + dock_widget.objectName().replace(self._instance_id.tidy_str(), '', 1)
00201             settings = instance_settings.get_settings(name)
00202             method = getattr(dock_widget, method_name)
00203             try:
00204                 method(settings)
00205             except Exception:
00206                 qCritical('PluginHandler._call_method_on_all_dock_widgets(%s) failed:\n%s' % (method_name, traceback.format_exc()))
00207 
00208     def restore_settings(self, plugin_settings, instance_settings, callback=None):
00209         """
00210         Restore settings of the plugin (`Plugin.restore_settings()`) and all dock widget title bars.
00211         Completion is signaled asynchronously if a callback is passed.
00212         """
00213         qDebug('PluginHandler.restore_settings()')
00214         self.__instance_settings = instance_settings
00215         self.__callback = callback
00216         try:
00217             self._restore_settings(plugin_settings, instance_settings)
00218         except Exception:
00219             qCritical('PluginHandler.restore_settings() plugin "%s" raised an exception:\n%s' % (str(self._instance_id), traceback.format_exc()))
00220             self.emit_restore_settings_completed()
00221 
00222     def _restore_settings(self, plugin_settings, instance_settings):
00223         raise NotImplementedError
00224 
00225     def emit_restore_settings_completed(self):
00226         qDebug('PluginHandler.emit_restore_settings_completed()')
00227         # call after plugin has restored settings as it may spawn additional dock widgets
00228         self._call_method_on_all_dock_widgets('restore_settings', self.__instance_settings)
00229         self.__instance_settings = None
00230         if self.__callback is not None:
00231             callback = self.__callback
00232             self.__callback = None
00233             callback(self._instance_id)
00234 
00235     def _create_dock_widget(self):
00236         dock_widget = DockWidget(self._container_manager)
00237         self._update_dock_widget_features(dock_widget)
00238         self._update_title_bar(dock_widget)
00239         self._set_window_icon(dock_widget)
00240         return dock_widget
00241 
00242     def _update_dock_widget_features(self, dock_widget):
00243         if self._application_context.options.lock_perspective or self._application_context.options.standalone_plugin:
00244             # dock widgets are not closable when perspective is locked or plugin is running standalone
00245             features = dock_widget.features()
00246             dock_widget.setFeatures(features ^ QDockWidget.DockWidgetClosable)
00247         if self._application_context.options.freeze_layout:
00248             # dock widgets are not closable when perspective is locked or plugin is running standalone
00249             features = dock_widget.features()
00250             dock_widget.setFeatures(features ^ (QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable))
00251 
00252     def _update_title_bar(self, dock_widget, hide_help=False, hide_reload=False):
00253         title_bar = dock_widget.titleBarWidget()
00254         if title_bar is None:
00255             title_bar = DockWidgetTitleBar(dock_widget, self._application_context.qtgui_path)
00256             dock_widget.setTitleBarWidget(title_bar)
00257 
00258             # connect extra buttons
00259             title_bar.connect_close_button(self._remove_widget_by_dock_widget)
00260             title_bar.connect_button('help', self._emit_help_signal)
00261             if hide_help:
00262                 title_bar.show_button('help', not hide_help)
00263             title_bar.connect_button('reload', self._emit_reload_signal)
00264             if hide_reload:
00265                 title_bar.show_button('reload', not hide_reload)
00266             title_bar.connect_button('configuration', self._trigger_configuration)
00267             title_bar.show_button('configuration', self._plugin_has_configuration)
00268 
00269     def _set_window_icon(self, widget):
00270         if self._plugin_descriptor:
00271             action_attributes = self._plugin_descriptor.action_attributes()
00272             if 'icon' in action_attributes and action_attributes['icon'] is not None:
00273                 base_path = self._plugin_descriptor.attributes().get('plugin_path')
00274                 icon = get_icon(action_attributes['icon'], action_attributes.get('icontype', None), base_path)
00275                 widget.setWindowIcon(icon)
00276 
00277     def _update_title_bars(self):
00278         if self._plugin_has_configuration:
00279             for dock_widget, _, _ in self._widgets.values():
00280                 title_bar = dock_widget.titleBarWidget()
00281                 title_bar.show_button('configuration')
00282 
00283     def _remove_widget_by_dock_widget(self, dock_widget):
00284         widget = [key for key, value in self._widgets.iteritems() if value[0] == dock_widget][0]
00285         self.remove_widget(widget)
00286 
00287     def _emit_help_signal(self):
00288         self.help_signal.emit(str(self._instance_id))
00289 
00290     def _emit_reload_signal(self):
00291         self.reload_signal.emit(str(self._instance_id))
00292 
00293     def _trigger_configuration(self):
00294         self._plugin.trigger_configuration()
00295 
00296     def _add_dock_widget(self, dock_widget, widget):
00297         dock_widget.setWidget(widget)
00298         # every dock widget needs a unique name for save/restore geometry/state to work
00299         dock_widget.setObjectName(self._instance_id.tidy_str() + '__' + widget.objectName())
00300         self._add_dock_widget_to_main_window(dock_widget)
00301         signaler = WindowChangedSignaler(widget, widget)
00302         signaler.window_icon_changed_signal.connect(self._on_widget_icon_changed)
00303         signaler.window_title_changed_signal.connect(self._on_widget_title_changed)
00304         signaler2 = WindowChangedSignaler(dock_widget, dock_widget)
00305         signaler2.hide_signal.connect(self._on_dock_widget_hide)
00306         signaler2.show_signal.connect(self._on_dock_widget_show)
00307         self._widgets[widget] = [dock_widget, signaler, signaler2]
00308         # trigger to update initial window icon and title
00309         signaler.emit_all()
00310         # trigger to update initial window state
00311         signaler2.emit_all()
00312 
00313     def _add_dock_widget_to_main_window(self, dock_widget):
00314         if self._main_window is not None:
00315             # warn about dock_widget with same object name
00316             old_dock_widget = self._main_window.findChild(DockWidget, dock_widget.objectName())
00317             if old_dock_widget is not None:
00318                 qWarning('PluginHandler._add_dock_widget_to_main_window() duplicate object name "%s", assign unique object names before adding widgets!' % dock_widget.objectName())
00319             self._main_window.addDockWidget(Qt.BottomDockWidgetArea, dock_widget)
00320 
00321     def _on_widget_icon_changed(self, widget):
00322         dock_widget, _, _ = self._widgets[widget]
00323         dock_widget.setWindowIcon(widget.windowIcon())
00324 
00325     def _on_widget_title_changed(self, widget):
00326         dock_widget, _, _ = self._widgets[widget]
00327         dock_widget.setWindowTitle(widget.windowTitle())
00328 
00329     def _on_dock_widget_hide(self, dock_widget):
00330         if self._minimized_dock_widgets_toolbar:
00331             self._minimized_dock_widgets_toolbar.addDockWidget(dock_widget)
00332 
00333     def _on_dock_widget_show(self, dock_widget):
00334         if self._minimized_dock_widgets_toolbar:
00335             self._minimized_dock_widgets_toolbar.removeDockWidget(dock_widget)
00336 
00337     # pointer to QWidget must be used for PySide to work (at least with 1.0.1)
00338     @Slot('QWidget*')
00339     def remove_widget(self, widget):
00340         dock_widget, signaler, signaler2 = self._widgets[widget]
00341         self._widgets.pop(widget)
00342         if signaler is not None:
00343             signaler.window_icon_changed_signal.disconnect(self._on_widget_icon_changed)
00344             signaler.window_title_changed_signal.disconnect(self._on_widget_title_changed)
00345         if signaler2 is not None:
00346             # emit show signal to remove dock widget from minimized toolbar before removal
00347             signaler2.show_signal.emit(dock_widget)
00348             signaler2.hide_signal.disconnect(self._on_dock_widget_hide)
00349             signaler2.show_signal.disconnect(self._on_dock_widget_show)
00350         # remove dock widget from parent and delete later
00351         if self._main_window is not None:
00352             dock_widget.parent().removeDockWidget(dock_widget)
00353         # do not delete the widget, only the dock widget
00354         widget.setParent(None)
00355         dock_widget.deleteLater()
00356         # defer check for last widget closed to give plugin a chance to add another widget right away
00357         self._defered_check_close.emit()
00358 
00359     def _add_toolbar(self, toolbar):
00360         # every toolbar needs a unique name for save/restore geometry/state to work
00361         toolbar_object_name = toolbar.objectName()
00362         prefix = self._instance_id.tidy_str() + '__'
00363         # when added, removed and readded the prefix should not be prepended multiple times
00364         if not toolbar_object_name.startswith(prefix):
00365             toolbar_object_name = prefix + toolbar_object_name
00366         toolbar.setObjectName(toolbar_object_name)
00367 
00368         if self._application_context.options.freeze_layout:
00369             toolbar.setMovable(False)
00370 
00371         self._toolbars.append(toolbar)
00372         if self._main_window is not None:
00373             # warn about toolbar with same object name
00374             old_toolbar = self._main_window.findChild(QToolBar, toolbar.objectName())
00375             if old_toolbar is not None:
00376                 qWarning('PluginHandler._add_toolbar() duplicate object name "%s", assign unique object names before adding toolbars!' % toolbar.objectName())
00377             self._main_window.addToolBar(Qt.TopToolBarArea, toolbar)
00378 
00379     # pointer to QToolBar must be used for PySide to work (at least with 1.0.1)
00380     @Slot('QToolBar*')
00381     def remove_toolbar(self, toolbar):
00382         self._toolbars.remove(toolbar)
00383         # detach toolbar from parent
00384         if toolbar.parent():
00385             toolbar.parent().removeToolBar(toolbar)
00386         # defer check for last widget closed to give plugin a chance to add another widget right away
00387         self._defered_check_close.emit()
00388 
00389     def _check_close(self):
00390         # close plugin when no widgets or toolbars are left
00391         if len(self._widgets) + len(self._toolbars) == 0:
00392             self._emit_close_plugin()
00393 
00394     def _emit_close_plugin(self):
00395         self.close_signal.emit(str(self._instance_id))


qt_gui
Author(s): Dirk Thomas
autogenerated on Fri Aug 28 2015 12:15:40