Skip to content

Commit

Permalink
Merge pull request #306 from mikrosimage/ui_custom_widgets
Browse files Browse the repository at this point in the history
loadUi: load custom widgets with PySide(2)
  • Loading branch information
mottosso authored Jan 4, 2019
2 parents f9cd54f + 957f3c0 commit 1f7e1ca
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 2 deletions.
42 changes: 40 additions & 2 deletions Qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@
import sys
import types
import shutil
import importlib


__version__ = "1.2.0.b2"
__version__ = "1.2.0.b3"

# Enable support for `from Qt import *`
__all__ = []
Expand Down Expand Up @@ -851,6 +852,41 @@ class _UiLoader(Qt._QtUiTools.QUiLoader):
def __init__(self, baseinstance):
super(_UiLoader, self).__init__(baseinstance)
self.baseinstance = baseinstance
self.custom_widgets = {}

def _loadCustomWidgets(self, etree):
"""
Workaround to pyside-77 bug.
From QUiLoader doc we should use registerCustomWidget method.
But this causes a segfault on some platforms.
Instead we fetch from customwidgets DOM node the python class
objects. Then we can directly use them in createWidget method.
"""

def headerToModule(header):
"""
Translate a header file to python module path
foo/bar.h => foo.bar
"""
# Remove header extension
module = os.path.splitext(header)[0]

# Replace os separator by python module separator
return module.replace("/", ".").replace("\\", ".")

custom_widgets = etree.find("customwidgets")

if custom_widgets is None:
return

for custom_widget in custom_widgets:
class_name = custom_widget.find("class").text
header = custom_widget.find("header").text
module = importlib.import_module(headerToModule(header))
self.custom_widgets[class_name] = getattr(module,
class_name)

def load(self, uifile, *args, **kwargs):
from xml.etree.ElementTree import ElementTree
Expand All @@ -860,6 +896,7 @@ def load(self, uifile, *args, **kwargs):
# a RuntimeError.
etree = ElementTree()
etree.parse(uifile)
self._loadCustomWidgets(etree)

widget = Qt._QtUiTools.QUiLoader.load(
self, uifile, *args, **kwargs)
Expand Down Expand Up @@ -889,7 +926,8 @@ def createWidget(self, class_name, parent=None, name=""):
class_name,
parent,
name)

elif class_name in self.custom_widgets:
widget = self.custom_widgets[class_name](parent)
else:
raise Exception("Custom widget '%s' not supported"
% class_name)
Expand Down
66 changes: 66 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ def captured_output():
sys.stdout, sys.stderr = old_out, old_err


def CustomWidget(parent=None):
"""
Wrap CustomWidget class into a function to avoid global Qt import
"""
from Qt import QtWidgets

class Widget(QtWidgets.QWidget):
pass

return Widget(parent)


self = sys.modules[__name__]


Expand Down Expand Up @@ -197,6 +209,38 @@ def captured_output():
"""


qcustomwidget_ui = u"""\
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>238</width>
<height>44</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="CustomWidget" name="customwidget">
</widget>
</widget>
<customwidgets>
<customwidget>
<class>CustomWidget</class>
<extends>QWidget</extends>
<header>tests.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
"""


def setup():
"""Module-wide initialisation
Expand All @@ -216,6 +260,8 @@ def saveUiFile(filename, ui_template):
self.ui_qmainwindow = saveUiFile("qmainwindow.ui", qmainwindow_ui)
self.ui_qdialog = saveUiFile("qdialog.ui", qdialog_ui)
self.ui_qdockwidget = saveUiFile("qdockwidget.ui", qdockwidget_ui)
self.ui_qcustomwidget = saveUiFile("qcustomwidget.ui", qcustomwidget_ui)


def teardown():
shutil.rmtree(self.tempdir)
Expand Down Expand Up @@ -350,6 +396,26 @@ def test_load_ui_dockwidget():
app.exit()


def test_load_ui_customwidget():
"""Tests to see if loadUi loads a custom widget properly"""
import sys
from Qt import QtWidgets, QtCompat

app = QtWidgets.QApplication(sys.argv)
win = QtWidgets.QMainWindow()

QtCompat.loadUi(self.ui_qcustomwidget, win)

# Ensure that the derived class was properly created
# and not the base class (in case of failure)
custom_class_name = getattr(win, "customwidget", None).__class__.__name__
excepted_class_name = CustomWidget(win).__class__.__name__
assert custom_class_name == excepted_class_name, \
"loadUi could not load custom widget to main window"

app.exit()


def test_load_ui_invalidpath():
"""Tests to see if loadUi successfully fails on invalid paths"""
import sys
Expand Down

0 comments on commit 1f7e1ca

Please sign in to comment.