00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
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
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
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
00246 features = dock_widget.features()
00247 dock_widget.setFeatures(features ^ QDockWidget.DockWidgetClosable)
00248 if self._application_context.options.freeze_layout:
00249
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
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
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
00311 signaler.emit_all()
00312
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
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
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
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
00356 if self._main_window is not None:
00357 dock_widget.parent().removeDockWidget(dock_widget)
00358
00359 dock_widget.setParent(None)
00360 widget.setParent(None)
00361 dock_widget.deleteLater()
00362
00363 self._defered_check_close.emit()
00364
00365 def _add_toolbar(self, toolbar):
00366
00367 toolbar_object_name = toolbar.objectName()
00368 prefix = self._instance_id.tidy_str() + '__'
00369
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
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
00386 @Slot('QToolBar*')
00387 def remove_toolbar(self, toolbar):
00388 self._toolbars.remove(toolbar)
00389
00390 if toolbar.parent():
00391 toolbar.parent().removeToolBar(toolbar)
00392
00393 self._defered_check_close.emit()
00394
00395 def _check_close(self):
00396
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))