summaryrefslogtreecommitdiff
path: root/src/silx/gui/qt/_pyside_dynamic.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/silx/gui/qt/_pyside_dynamic.py')
-rw-r--r--src/silx/gui/qt/_pyside_dynamic.py290
1 files changed, 159 insertions, 131 deletions
diff --git a/src/silx/gui/qt/_pyside_dynamic.py b/src/silx/gui/qt/_pyside_dynamic.py
index 80520ac..4c1ceba 100644
--- a/src/silx/gui/qt/_pyside_dynamic.py
+++ b/src/silx/gui/qt/_pyside_dynamic.py
@@ -1,26 +1,58 @@
-
-# Taken from: https://gist.github.com/cpbotha/1b42a20c8f3eb9bb7cb8
-# Plus: https://github.com/spyder-ide/qtpy/commit/001a862c401d757feb63025f88dbb4601d353c84
-
+# Adapted from https://github.com/spyder-ide/qtpy/blob/296dee3da8aba381b3cf17da34a6d17626e50357/qtpy/uic.py
+# In PySide, loadUi does not exist, so we define it using QUiLoader, and
+# then make sure we expose that function. This is adapted from qt-helpers
+# which was released under a 3-clause BSD license:
+# qt-helpers - a common front-end to various Qt modules
+#
+# Copyright (c) 2015, Chris Beaumont and Thomas Robitaille
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of the Glue project nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Which itself was based on the solution at
+#
+# https://gist.github.com/cpbotha/1b42a20c8f3eb9bb7cb8
+#
+# which was released under the MIT license:
+#
# Copyright (c) 2011 Sebastian Wiesner <lunaryorn@gmail.com>
# Modifications by Charl Botha <cpbotha@vxlabs.com>
-# * customWidgets support (registerCustomWidget() causes segfault in
-# pyside 1.1.2 on Ubuntu 12.04 x86_64)
-# * workingDirectory support in loadUi
-
-# found this here:
-# https://github.com/lunaryorn/snippets/blob/master/qt4/designer/pyside_dynamic.py
-
+#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
-
+#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
-
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
@@ -29,40 +61,68 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
-"""
- How to load a user interface dynamically with PySide.
-
- .. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
-"""
+"""How to load a user interface dynamically with PySide6"""
import logging
from ._qt import BINDING
-if BINDING == 'PySide2':
- from PySide2.QtCore import QMetaObject, Property, Qt
- from PySide2.QtWidgets import QFrame
- from PySide2.QtUiTools import QUiLoader
-elif BINDING == 'PySide6':
- from PySide6.QtCore import QMetaObject, Property, Qt
- from PySide6.QtWidgets import QFrame
- from PySide6.QtUiTools import QUiLoader
-else:
+
+if BINDING != "PySide6":
raise RuntimeError("Unsupported Qt binding: %s", BINDING)
+from PySide6.QtCore import QMetaObject, Property, Qt
+from PySide6.QtWidgets import QFrame
+from PySide6.QtUiTools import QUiLoader
+
_logger = logging.getLogger(__name__)
+# Specific custom widgets
+
+
+class _Line(QFrame):
+ """Widget to use as 'Line' Qt designer"""
+
+ def __init__(self, parent=None):
+ super(_Line, self).__init__(parent)
+ self.setFrameShape(QFrame.HLine)
+ self.setFrameShadow(QFrame.Sunken)
+
+ def getOrientation(self):
+ shape = self.frameShape()
+ if shape == QFrame.HLine:
+ return Qt.Horizontal
+ elif shape == QFrame.VLine:
+ return Qt.Vertical
+ else:
+ raise RuntimeError("Wrong shape: %d", shape)
+
+ def setOrientation(self, orientation):
+ if orientation == Qt.Horizontal:
+ self.setFrameShape(QFrame.HLine)
+ elif orientation == Qt.Vertical:
+ self.setFrameShape(QFrame.VLine)
+ else:
+ raise ValueError("Unsupported orientation %s" % str(orientation))
+
+ orientation = Property("Qt::Orientation", getOrientation, setOrientation)
+
+
+CUSTOM_WIDGETS = {"Line": _Line}
+"""Default custom widgets for `loadUi`"""
+
+
class UiLoader(QUiLoader):
"""
- Subclass :class:`~PySide.QtUiTools.QUiLoader` to create the user interface
- in a base instance.
+ Subclass of :class:`~PySide.QtUiTools.QUiLoader` to create the user
+ interface in a base instance.
Unlike :class:`~PySide.QtUiTools.QUiLoader` itself this class does not
create a new instance of the top-level widget, but creates the user
- interface in an existing instance of the top-level class.
+ interface in an existing instance of the top-level class if needed.
- This mimics the behaviour of :func:`PyQt*.uic.loadUi`.
+ This mimics the behaviour of :func:`PyQt4.uic.loadUi`.
"""
def __init__(self, baseinstance, customWidgets=None):
@@ -74,129 +134,91 @@ class UiLoader(QUiLoader):
subclass thereof.
``customWidgets`` is a dictionary mapping from class name to class
- object for widgets that you've promoted in the Qt Designer
- interface. Usually, this should be done by calling
- registerCustomWidget on the QUiLoader, but
- with PySide 1.1.2 on Ubuntu 12.04 x86_64 this causes a segfault.
+ object for custom widgets. Usually, this should be done by calling
+ registerCustomWidget on the QUiLoader, but with PySide 1.1.2 on
+ Ubuntu 12.04 x86_64 this causes a segfault.
``parent`` is the parent object of this loader.
"""
QUiLoader.__init__(self, baseinstance)
+
self.baseinstance = baseinstance
- self.customWidgets = {}
- self.uifile = None
- self.customWidgets.update(customWidgets)
- def createWidget(self, class_name, parent=None, name=''):
+ if customWidgets is None:
+ self.customWidgets = {}
+ else:
+ self.customWidgets = customWidgets
+
+ def createWidget(self, class_name, parent=None, name=""):
"""
Function that is called for each widget defined in ui file,
overridden here to populate baseinstance instead.
"""
if parent is None and self.baseinstance:
- # supposed to create the top-level widget, return the base instance
- # instead
+ # supposed to create the top-level widget, return the base
+ # instance instead
return self.baseinstance
else:
- if class_name in self.availableWidgets():
+ # For some reason, Line is not in the list of available
+ # widgets, but works fine, so we have to special case it here.
+ if class_name in self.availableWidgets() or class_name == "Line":
# create a new widget for child widgets
widget = QUiLoader.createWidget(self, class_name, parent, name)
else:
- # if not in the list of availableWidgets,
- # must be a custom widget
- # this will raise KeyError if the user has not supplied the
- # relevant class_name in the dictionary, or TypeError, if
- # customWidgets is None
- if class_name not in self.customWidgets:
- raise Exception('No custom widget ' + class_name +
- ' found in customWidgets param of' +
- 'UiFile %s.' % self.uifile)
+ # If not in the list of availableWidgets, must be a custom
+ # widget. This will raise KeyError if the user has not
+ # supplied the relevant class_name in the dictionary or if
+ # customWidgets is empty.
try:
widget = self.customWidgets[class_name](parent)
- except Exception:
- _logger.error("Fail to instanciate widget %s from file %s", class_name, self.uifile)
- raise
+ except KeyError as error:
+ raise Exception(
+ f"No custom widget {class_name} " "found in customWidgets"
+ ) from error
if self.baseinstance:
# set an attribute for the new child widget on the base
- # instance, just like PyQt*.uic.loadUi does.
+ # instance, just like PyQt4.uic.loadUi does.
setattr(self.baseinstance, name, widget)
- # this outputs the various widget names, e.g.
- # sampleGraphicsView, dockWidget, samplesTableView etc.
- # print(name)
-
return widget
- def _parse_custom_widgets(self, ui_file):
- """
- This function is used to parse a ui file and look for the <customwidgets>
- section, then automatically load all the custom widget classes.
- """
- import importlib
- from xml.etree.ElementTree import ElementTree
-
- # Parse the UI file
- etree = ElementTree()
- ui = etree.parse(ui_file)
-
- # Get the customwidgets section
- custom_widgets = ui.find('customwidgets')
-
- if custom_widgets is None:
- return
-
- custom_widget_classes = {}
-
- for custom_widget in custom_widgets.getchildren():
- cw_class = custom_widget.find('class').text
- cw_header = custom_widget.find('header').text
-
- module = importlib.import_module(cw_header)
-
- custom_widget_classes[cw_class] = getattr(module, cw_class)
+def _get_custom_widgets(ui_file):
+ """
+ This function is used to parse a ui file and look for the <customwidgets>
+ section, then automatically load all the custom widget classes.
+ """
- self.customWidgets.update(custom_widget_classes)
+ import sys
+ import importlib
+ from xml.etree.ElementTree import ElementTree
- def load(self, uifile):
- self._parse_custom_widgets(uifile)
- self.uifile = uifile
- return QUiLoader.load(self, uifile)
+ # Parse the UI file
+ etree = ElementTree()
+ ui = etree.parse(ui_file)
+ # Get the customwidgets section
+ custom_widgets = ui.find("customwidgets")
-class _Line(QFrame):
- """Widget to use as 'Line' Qt designer"""
- def __init__(self, parent=None):
- super(_Line, self).__init__(parent)
- self.setFrameShape(QFrame.HLine)
- self.setFrameShadow(QFrame.Sunken)
+ if custom_widgets is None:
+ return {}
- def getOrientation(self):
- shape = self.frameShape()
- if shape == QFrame.HLine:
- return Qt.Horizontal
- elif shape == QFrame.VLine:
- return Qt.Vertical
- else:
- raise RuntimeError("Wrong shape: %d", shape)
+ custom_widget_classes = {}
- def setOrientation(self, orientation):
- if orientation == Qt.Horizontal:
- self.setFrameShape(QFrame.HLine)
- elif orientation == Qt.Vertical:
- self.setFrameShape(QFrame.VLine)
- else:
- raise ValueError("Unsupported orientation %s" % str(orientation))
+ for custom_widget in list(custom_widgets):
+ cw_class = custom_widget.find("class").text
+ cw_header = custom_widget.find("header").text
- orientation = Property("Qt::Orientation", getOrientation, setOrientation)
+ module = importlib.import_module(cw_header)
+ custom_widget_classes[cw_class] = getattr(module, cw_class)
-CUSTOM_WIDGETS = {"Line": _Line}
-"""Default custom widgets for `loadUi`"""
+ return custom_widget_classes
def loadUi(uifile, baseinstance=None, package=None, resource_suffix=None):
@@ -205,30 +227,36 @@ def loadUi(uifile, baseinstance=None, package=None, resource_suffix=None):
``uifile`` is a string containing a file name of the UI file to load.
- If ``baseinstance`` is ``None``, the a new instance of the top-level widget
- will be created. Otherwise, the user interface is created within the given
- ``baseinstance``. In this case ``baseinstance`` must be an instance of the
- top-level widget class in the UI file to load, or a subclass thereof. In
- other words, if you've created a ``QMainWindow`` interface in the designer,
- ``baseinstance`` must be a ``QMainWindow`` or a subclass thereof, too. You
- cannot load a ``QMainWindow`` UI file with a plain
- :class:`~PySide.QtGui.QWidget` as ``baseinstance``.
+ If ``baseinstance`` is ``None``, the a new instance of the top-level
+ widget will be created. Otherwise, the user interface is created within
+ the given ``baseinstance``. In this case ``baseinstance`` must be an
+ instance of the top-level widget class in the UI file to load, or a
+ subclass thereof. In other words, if you've created a ``QMainWindow``
+ interface in the designer, ``baseinstance`` must be a ``QMainWindow``
+ or a subclass thereof, too. You cannot load a ``QMainWindow`` UI file
+ with a plain :class:`~PySide.QtGui.QWidget` as ``baseinstance``.
- :method:`~PySide.QtCore.QMetaObject.connectSlotsByName()` is called on the
- created user interface, so you can implemented your slots according to its
- conventions in your widget class.
+ :method:`~PySide.QtCore.QMetaObject.connectSlotsByName()` is called on
+ the created user interface, so you can implemented your slots according
+ to its conventions in your widget class.
Return ``baseinstance``, if ``baseinstance`` is not ``None``. Otherwise
return the newly created instance of the user interface.
"""
if package is not None:
- _logger.warning(
- "loadUi package parameter not implemented with PySide")
+ _logger.warning("loadUi package parameter not implemented with PySide")
if resource_suffix is not None:
- _logger.warning(
- "loadUi resource_suffix parameter not implemented with PySide")
+ _logger.warning("loadUi resource_suffix parameter not implemented with PySide")
+
+ # We parse the UI file and import any required custom widgets
+ customWidgets = _get_custom_widgets(uifile)
+
+ # Add CUSTOM_WIDGETS
+ for name, klass in CUSTOM_WIDGETS.items():
+ customWidgets.setdefault(name, klass)
+
+ loader = UiLoader(baseinstance, customWidgets)
- loader = UiLoader(baseinstance, customWidgets=CUSTOM_WIDGETS)
widget = loader.load(uifile)
QMetaObject.connectSlotsByName(widget)
return widget