plugin_handler.py
Go to the documentation of this file.
1 # Copyright (c) 2011, Dirk Thomas, Dorian Scholz, TU Darmstadt
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions
6 # are met:
7 #
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following
12 # disclaimer in the documentation and/or other materials provided
13 # with the distribution.
14 # * Neither the name of the TU Darmstadt nor the names of its
15 # contributors may be used to endorse or promote products derived
16 # from this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
30 
31 import traceback
32 
33 from python_qt_binding.QtCore import qCritical, qDebug, QObject, Qt, qWarning, Signal, Slot
34 from python_qt_binding.QtWidgets import QDockWidget, QToolBar
35 
36 from qt_gui.dock_widget import DockWidget
37 from qt_gui.dock_widget_title_bar import DockWidgetTitleBar
38 from qt_gui.icon_loader import get_icon
39 from qt_gui.window_changed_signaler import WindowChangedSignaler
40 
41 
42 class PluginHandler(QObject):
43  """
44  Base class for the bidirectional exchange between the framework and one `Plugin` instance.
45 
46  It utilizes a `PluginProvider` to load/unload the plugin and provides callbacks for the
47  `PluginContext`.
48  """
49 
50  label_updated = Signal(str, str)
51  close_signal = Signal(str)
52  reload_signal = Signal(str)
53  help_signal = Signal(str)
54  _defered_check_close = Signal()
55 
56  def __init__(self, parent, main_window, instance_id, application_context, container_manager,
57  argv=None):
58  super(PluginHandler, self).__init__(parent)
59  self.setObjectName('PluginHandler')
60 
61  self._main_window = main_window
62  self._instance_id = instance_id
63  self._application_context = application_context
64  self._container_manager = container_manager
65  self._argv = argv if argv else []
67  self._plugin_descriptor = None
68 
69  self._defered_check_close.connect(self._check_close, Qt.QueuedConnection)
70  self._plugin_provider = None
71  self.__callback = None
72  self.__instance_settings = None
73 
75 
76  # mapping of added widgets to their parent dock widget and WindowChangedSignaler
77  self._widgets = {}
78 
79  self._toolbars = []
80 
81  def instance_id(self):
82  return self._instance_id
83 
84  def argv(self):
85  return self._argv
86 
88  self._minimized_dock_widgets_toolbar = toolbar
89 
90  def set_plugin_descriptor(self, plugin_descriptor):
91  self._plugin_descriptor = plugin_descriptor
92 
93  def load(self, plugin_provider, callback=None):
94  """
95  Load plugin.
96 
97  Completion is signaled asynchronously if a callback is passed.
98  """
99  self._plugin_provider = plugin_provider
100  self.__callback = callback
101  try:
102  self._load()
103  except Exception as e:
104  self._emit_load_completed(e)
105 
106  def _load(self):
107  raise NotImplementedError
108 
109  def _emit_load_completed(self, exception=None):
110  if exception is not None:
112  if self.__callback is not None:
113  callback = self.__callback
114  self.__callback = None
115  callback(self, exception)
116  elif exception is not None:
117  qCritical('PluginHandler.load() failed%s' %
118  (':\n%s' % str(exception) if not exception else ''))
119 
121  for widget in list(self._widgets.keys()):
122  self.remove_widget(widget)
123  self._delete_widget(widget)
124  for toolbar in list(self._toolbars):
125  self.remove_toolbar(toolbar)
126  self._delete_toolbar(toolbar)
127 
128  def shutdown_plugin(self, callback):
129  """
130  Shut down the plugin and remove all added widgets.
131 
132  Completion is signaled asynchronously if a callback is passed.
133  """
134  self.__callback = callback
135  try:
136  self._shutdown_plugin()
137  except Exception:
138  qCritical('PluginHandler.shutdown_plugin() plugin "%s" raised an exception:\n%s' %
139  (str(self._instance_id), traceback.format_exc()))
141 
142  def _shutdown_plugin(self):
143  raise NotImplementedError
144 
147  if self.__callback is not None:
148  callback = self.__callback
149  self.__callback = None
150  callback(self._instance_id)
151 
152  def _delete_widget(self, widget):
153  widget.deleteLater()
154 
155  def _delete_toolbar(self, toolbar):
156  toolbar.deleteLater()
157 
158  def unload(self, callback=None):
159  """
160  Unload plugin.
161 
162  Completion is signaled asynchronously if a callback is passed.
163  """
164  self.__callback = callback
165  try:
166  self._unload()
167  except Exception:
168  qCritical('PluginHandler.unload() plugin "%s" raised an exception:\n%s' %
169  (str(self._instance_id), traceback.format_exc()))
171 
172  def _unload(self):
173  raise NotImplementedError
174 
176  if self.__callback is not None:
177  callback = self.__callback
178  self.__callback = None
179  callback(self._instance_id)
180 
181  def save_settings(self, plugin_settings, instance_settings, callback=None):
182  """
183  Save settings of the plugin and all dock widget title bars.
184 
185  Completion is signaled asynchronously if a callback is passed.
186  """
187  qDebug('PluginHandler.save_settings()')
188  self.__instance_settings = instance_settings
189  self.__callback = callback
190  try:
191  self._save_settings(plugin_settings, instance_settings)
192  except Exception:
193  qCritical('PluginHandler.save_settings() plugin "%s" raised an exception:\n%s' %
194  (str(self._instance_id), traceback.format_exc()))
196 
197  def _save_settings(self, plugin_settings, instance_settings):
198  raise NotImplementedError
199 
201  qDebug('PluginHandler.emit_save_settings_completed()')
202  self._call_method_on_all_dock_widgets('save_settings', self.__instance_settings)
203  self.__instance_settings = None
204  if self.__callback is not None:
205  callback = self.__callback
206  self.__callback = None
207  callback(self._instance_id)
208 
209  def _call_method_on_all_dock_widgets(self, method_name, instance_settings):
210  for dock_widget, _, _ in self._widgets.values():
211  name = 'dock_widget' + \
212  dock_widget.objectName().replace(self._instance_id.tidy_str(), '', 1)
213  settings = instance_settings.get_settings(name)
214  method = getattr(dock_widget, method_name)
215  try:
216  method(settings)
217  except Exception:
218  qCritical('PluginHandler._call_method_on_all_dock_widgets(%s) failed:\n%s' %
219  (method_name, traceback.format_exc()))
220 
221  def restore_settings(self, plugin_settings, instance_settings, callback=None):
222  """
223  Restore settings of the plugin and all dock widget title bars.
224 
225  Completion is signaled asynchronously if a callback is passed.
226  """
227  qDebug('PluginHandler.restore_settings()')
228  self.__instance_settings = instance_settings
229  self.__callback = callback
230  try:
231  self._restore_settings(plugin_settings, instance_settings)
232  except Exception:
233  qCritical('PluginHandler.restore_settings() plugin "%s" raised an exception:\n%s' %
234  (str(self._instance_id), traceback.format_exc()))
236 
237  def _restore_settings(self, plugin_settings, instance_settings):
238  raise NotImplementedError
239 
241  qDebug('PluginHandler.emit_restore_settings_completed()')
242  # call after plugin has restored settings as it may spawn additional dock widgets
243  self._call_method_on_all_dock_widgets('restore_settings', self.__instance_settings)
244  self.__instance_settings = None
245  if self.__callback is not None:
246  callback = self.__callback
247  self.__callback = None
248  callback(self._instance_id)
249 
251  dock_widget = DockWidget(self._container_manager)
252  self._update_dock_widget_features(dock_widget)
253  self._update_title_bar(dock_widget)
254  self._set_window_icon(dock_widget)
255  return dock_widget
256 
257  def _update_dock_widget_features(self, dock_widget):
258  if self._application_context.options.lock_perspective or \
259  self._application_context.options.standalone_plugin:
260  # dock widgets are not closable when perspective is locked or plugin is
261  # running standalone
262  features = dock_widget.features()
263  dock_widget.setFeatures(features ^ QDockWidget.DockWidgetClosable)
264  if self._application_context.options.freeze_layout:
265  # dock widgets are not closable when perspective is locked or plugin is
266  # running standalone
267  features = dock_widget.features()
268  dock_widget.setFeatures(
269  features ^ (QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable))
270 
271  def _update_title_bar(self, dock_widget, hide_help=False, hide_reload=False):
272  title_bar = dock_widget.titleBarWidget()
273  if title_bar is None:
274  title_bar = DockWidgetTitleBar(
275  dock_widget, self._application_context.qtgui_path,
276  hide_title=self._application_context.options.hide_title)
277  dock_widget.setTitleBarWidget(title_bar)
278 
279  # connect extra buttons
280  title_bar.connect_close_button(self._remove_widget_by_dock_widget)
281  title_bar.connect_button('help', self._emit_help_signal)
282  if hide_help:
283  title_bar.show_button('help', not hide_help)
284  title_bar.connect_button('reload', self._emit_reload_signal)
285  if hide_reload:
286  title_bar.show_button('reload', not hide_reload)
287  title_bar.connect_button('configuration', self._trigger_configuration)
288  title_bar.show_button('configuration', self._plugin_has_configuration)
289 
290  def _set_window_icon(self, widget):
291  if self._plugin_descriptor:
292  action_attributes = self._plugin_descriptor.action_attributes()
293  if 'icon' in action_attributes and action_attributes['icon'] is not None:
294  base_path = self._plugin_descriptor.attributes().get('plugin_path')
295  icon = get_icon(
296  action_attributes['icon'], action_attributes.get('icontype', None), base_path)
297  widget.setWindowIcon(icon)
298 
301  for dock_widget, _, _ in self._widgets.values():
302  title_bar = dock_widget.titleBarWidget()
303  title_bar.show_button('configuration')
304 
305  def _remove_widget_by_dock_widget(self, dock_widget):
306  widget = [key for key, value in self._widgets.items() if value[0] == dock_widget][0]
307  self.remove_widget(widget)
308 
309  def _emit_help_signal(self):
310  self.help_signal.emit(str(self._instance_id))
311 
313  self.reload_signal.emit(str(self._instance_id))
314 
316  self._plugin.trigger_configuration()
317 
318  def _add_dock_widget(self, dock_widget, widget):
319  dock_widget.setWidget(widget)
320  # every dock widget needs a unique name for save/restore geometry/state to work
321  dock_widget.setObjectName(self._instance_id.tidy_str() + '__' + widget.objectName())
322  self._add_dock_widget_to_main_window(dock_widget)
323  signaler = WindowChangedSignaler(widget, widget)
324  signaler.window_icon_changed_signal.connect(self._on_widget_icon_changed)
325  signaler.window_title_changed_signal.connect(self._on_widget_title_changed)
326  signaler2 = WindowChangedSignaler(dock_widget, dock_widget)
327  signaler2.hide_signal.connect(self._on_dock_widget_hide)
328  signaler2.show_signal.connect(self._on_dock_widget_show)
329  signaler2.window_title_changed_signal.connect(self._on_dock_widget_title_changed)
330  self._widgets[widget] = [dock_widget, signaler, signaler2]
331  # trigger to update initial window icon and title
332  signaler.emit_all()
333  # trigger to update initial window state
334  signaler2.emit_all()
335 
336  def _add_dock_widget_to_main_window(self, dock_widget):
337  if self._main_window is not None:
338  # warn about dock_widget with same object name
339  old_dock_widget = self._main_window.findChild(DockWidget, dock_widget.objectName())
340  if old_dock_widget is not None:
341  qWarning('PluginHandler._add_dock_widget_to_main_window() duplicate object name ' +
342  '"%s", assign unique object names before adding widgets!' %
343  dock_widget.objectName())
344 
345  self._main_window.addDockWidget(Qt.BottomDockWidgetArea, dock_widget)
346 
347  def _on_widget_icon_changed(self, widget):
348  dock_widget, _, _ = self._widgets[widget]
349  dock_widget.setWindowIcon(widget.windowIcon())
350 
351  def _on_widget_title_changed(self, widget):
352  dock_widget, _, _ = self._widgets[widget]
353  dock_widget.setWindowTitle(widget.windowTitle())
354 
355  def _on_dock_widget_hide(self, dock_widget):
357  self._minimized_dock_widgets_toolbar.addDockWidget(dock_widget)
358 
359  def _on_dock_widget_show(self, dock_widget):
361  self._minimized_dock_widgets_toolbar.removeDockWidget(dock_widget)
362 
363  def _on_dock_widget_title_changed(self, dock_widget):
364  self.label_updated.emit(str(self._instance_id), dock_widget.windowTitle())
365 
366  # pointer to QWidget must be used for PySide to work (at least with 1.0.1)
367  @Slot('QWidget*')
368  def remove_widget(self, widget):
369  dock_widget, signaler, signaler2 = self._widgets[widget]
370  self._widgets.pop(widget)
371  if signaler is not None:
372  signaler.window_icon_changed_signal.disconnect(self._on_widget_icon_changed)
373  signaler.window_title_changed_signal.disconnect(self._on_widget_title_changed)
374  if signaler2 is not None:
375  # emit show signal to remove dock widget from minimized toolbar before removal
376  signaler2.show_signal.emit(dock_widget)
377  signaler2.hide_signal.disconnect(self._on_dock_widget_hide)
378  signaler2.show_signal.disconnect(self._on_dock_widget_show)
379  # remove dock widget from parent and delete later
380  if self._main_window is not None:
381  dock_widget.parent().removeDockWidget(dock_widget)
382  # do not delete the widget, only the dock widget
383  dock_widget.setParent(None)
384  widget.setParent(None)
385  dock_widget.deleteLater()
386  # defer check for last widget closed to give plugin a chance to add
387  # another widget right away
388  self._defered_check_close.emit()
389 
390  def _add_toolbar(self, toolbar):
391  # every toolbar needs a unique name for save/restore geometry/state to work
392  toolbar_object_name = toolbar.objectName()
393  prefix = self._instance_id.tidy_str() + '__'
394  # when added, removed and readded the prefix should not be prepended multiple times
395  if not toolbar_object_name.startswith(prefix):
396  toolbar_object_name = prefix + toolbar_object_name
397  toolbar.setObjectName(toolbar_object_name)
398 
399  if self._application_context.options.freeze_layout:
400  toolbar.setMovable(False)
401 
402  self._toolbars.append(toolbar)
403  if self._main_window is not None:
404  # warn about toolbar with same object name
405  old_toolbar = self._main_window.findChild(QToolBar, toolbar.objectName())
406  if old_toolbar is not None:
407  qWarning('PluginHandler._add_toolbar() duplicate object name "%s", '
408  'assign unique object names before adding toolbars!' %
409  toolbar.objectName())
410  self._main_window.addToolBar(Qt.TopToolBarArea, toolbar)
411 
412  # pointer to QToolBar must be used for PySide to work (at least with 1.0.1)
413  @Slot('QToolBar*')
414  def remove_toolbar(self, toolbar):
415  self._toolbars.remove(toolbar)
416  # detach toolbar from parent
417  if toolbar.parent():
418  toolbar.parent().removeToolBar(toolbar)
419  # defer check for last widget closed to give plugin a chance to add
420  # another widget right away
421  self._defered_check_close.emit()
422 
423  def _check_close(self):
424  # close plugin when no widgets or toolbars are left
425  if len(self._widgets) + len(self._toolbars) == 0:
426  self._emit_close_plugin()
427 
429  self.close_signal.emit(str(self._instance_id))
def set_plugin_descriptor(self, plugin_descriptor)
def unload(self, callback=None)
def __init__(self, parent, main_window, instance_id, application_context, container_manager, argv=None)
def _on_dock_widget_show(self, dock_widget)
def save_settings(self, plugin_settings, instance_settings, callback=None)
def set_minimized_dock_widgets_toolbar(self, toolbar)
def _on_dock_widget_hide(self, dock_widget)
def _update_dock_widget_features(self, dock_widget)
def _emit_load_completed(self, exception=None)
def restore_settings(self, plugin_settings, instance_settings, callback=None)
def _remove_widget_by_dock_widget(self, dock_widget)
def _on_widget_title_changed(self, widget)
def _add_dock_widget_to_main_window(self, dock_widget)
def _restore_settings(self, plugin_settings, instance_settings)
def _call_method_on_all_dock_widgets(self, method_name, instance_settings)
def _update_title_bar(self, dock_widget, hide_help=False, hide_reload=False)
def _add_dock_widget(self, dock_widget, widget)
def get_icon(name, type_=None, base_path=None)
Definition: icon_loader.py:39
def _on_dock_widget_title_changed(self, dock_widget)
def load(self, plugin_provider, callback=None)
def _save_settings(self, plugin_settings, instance_settings)


qt_gui
Author(s): Dirk Thomas
autogenerated on Tue Apr 13 2021 03:03:12