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


qt_gui
Author(s): Dirk Thomas
autogenerated on Thu Jun 6 2019 18:07:34