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 .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
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
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
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
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
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
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
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
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
00304 if self._main_window is not None:
00305 dock_widget.parent().removeDockWidget(dock_widget)
00306
00307 widget.setParent(None)
00308 dock_widget.deleteLater()
00309
00310 self._defered_check_close.emit()
00311
00312 def _add_toolbar(self, toolbar):
00313
00314 toolbar_object_name = toolbar.objectName()
00315 prefix = self._instance_id.tidy_str() + '__'
00316
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
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
00330 @Slot('QToolBar*')
00331 def remove_toolbar(self, toolbar):
00332 self._toolbars.remove(toolbar)
00333
00334 if toolbar.parent():
00335 toolbar.parent().removeToolBar(toolbar)
00336
00337 self._defered_check_close.emit()
00338
00339 def _check_close(self):
00340
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))