diff --git a/Dockerfile b/Dockerfile
index 7b8c470e..a34cbe68 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,8 @@ FROM ubuntu:14.04
RUN apt-get update && apt-get install -y \
python-qt4 \
python-pyside \
- python-pip
+ python-pip \
+ xvfb
# Nose is the Python test-runner
RUN pip install nose nosepipe
@@ -11,9 +12,14 @@ RUN pip install nose nosepipe
# Enable additional output from Qt.py
ENV QT_VERBOSE true
+# Xvfb
+ENV DISPLAY :99
+
WORKDIR /workspace/Qt.py
ENTRYPOINT cp -r /Qt.py /workspace && \
python build_caveats_tests.py && \
+ Xvfb :99 -screen 0 1024x768x16 2>/dev/null & \
+ sleep 3 && \
nosetests \
--verbose \
--with-process-isolation \
diff --git a/Qt.py b/Qt.py
index 17257c36..01adaba8 100644
--- a/Qt.py
+++ b/Qt.py
@@ -26,6 +26,7 @@
def _pyqt5():
import PyQt5.Qt
+ from PyQt5 import uic
# Remap
PyQt5.QtCore.Signal = PyQt5.QtCore.pyqtSignal
@@ -37,7 +38,7 @@ def _pyqt5():
PyQt5.__binding__ = "PyQt5"
PyQt5.__binding_version__ = PyQt5.QtCore.PYQT_VERSION_STR
PyQt5.__qt_version__ = PyQt5.QtCore.QT_VERSION_STR
- PyQt5.load_ui = pyqt5_load_ui
+ PyQt5.load_ui = _pyqt_load_ui_factory(uic)
return PyQt5
@@ -61,6 +62,7 @@ def _pyqt4():
raise ImportError
import PyQt4.Qt
+ from PyQt4 import uic
# Remap
PyQt4.QtWidgets = PyQt4.QtGui
@@ -75,14 +77,14 @@ def _pyqt4():
PyQt4.__binding__ = "PyQt4"
PyQt4.__binding_version__ = PyQt4.QtCore.PYQT_VERSION_STR
PyQt4.__qt_version__ = PyQt4.QtCore.QT_VERSION_STR
- PyQt4.load_ui = pyqt4_load_ui
+ PyQt4.load_ui = _pyqt_load_ui_factory(uic)
return PyQt4
def _pyside2():
import PySide2
- from PySide2 import QtGui, QtCore
+ from PySide2 import QtGui, QtCore, QtUiTools
# Remap
QtCore.QStringListModel = QtGui.QStringListModel
@@ -92,14 +94,14 @@ def _pyside2():
PySide2.__binding__ = "PySide2"
PySide2.__binding_version__ = PySide2.__version__
PySide2.__qt_version__ = PySide2.QtCore.qVersion()
- PySide2.load_ui = pyside2_load_ui
+ PySide2.load_ui = _pyside_load_ui_factory(QtUiTools.QUiLoader)
return PySide2
def _pyside():
import PySide
- from PySide import QtGui, QtCore
+ from PySide import QtGui, QtCore, QtUiTools
QtCore, QtGui # bypass linter warnings
# Remap
@@ -114,65 +116,84 @@ def _pyside():
PySide.__binding__ = "PySide"
PySide.__binding_version__ = PySide.__version__
PySide.__qt_version__ = PySide.QtCore.qVersion()
- PySide.load_ui = pyside_load_ui
+ PySide.load_ui = _pyside_load_ui_factory(QtUiTools.QUiLoader)
return PySide
-def pyside_load_ui(fname):
- """Read Qt Designer .ui `fname`
-
- Args:
- fname (str): Absolute path to .ui file
+def _pyside_load_ui_factory(superclass):
+ """load_ui factory function for PySide and PySide2
- Usage:
- >> from Qt import load_ui
- >> class MyWindow(QtWidgets.QWidget):
- .. fname = 'my_ui.ui'
- .. self.ui = load_ui(fname)
- ..
- >> window = MyWindow()
+ Produce a load_ui function using the provided superclass
"""
- from PySide import QtUiTools
- return QtUiTools.QUiLoader().load(fname)
+ def load_ui(fname, base_instance=None):
+ """Read Qt Designer .ui `fname`
+ Args:
+ fname (str): Absolute path to .ui file
+ base_instance (widget, optional): Instance of the Qt base class.
-def pyside2_load_ui(fname):
- """Read Qt Designer .ui `fname`
+ Usage:
+ import sys
+ from Qt import QtWidgets, load_ui
+ class MyWindow(QtWidgets.QMainWindow):
+ def __init__(self, parent=None):
+ super(MyWindow, self).__init__(parent)
+ fname = 'my_ui.ui'
+ load_ui(fname, self)
+ app = QtWidgets.QApplication(sys.argv)
+ window = MyWindow()
+ window.show()
+ app.exec_()
- Args:
- fname (str): Absolute path to .ui file
+ """
- """
+ if base_instance:
+ ui = superclass().load(fname)
+ for member in dir(ui):
+ if not member.startswith('__'):
+ setattr(base_instance, member, getattr(ui, member))
+ return ui
+ else:
+ return superclass().load(fname)
- from PySide2 import QtUiTools
- return QtUiTools.QUiLoader().load(fname)
+ return load_ui
-def pyqt4_load_ui(fname):
- """Read Qt Designer .ui `fname`
+def _pyqt_load_ui_factory(uic):
+ """load_ui factory function for PyQt4 and PyQt5
- Args:
- fname (str): Absolute path to .ui file
+ Produce a load_ui function using the provided module
"""
- from PyQt4 import uic
- return uic.loadUi(fname)
+ def load_ui(fname, base_instance=None):
+ """Read Qt Designer .ui `fname`
+ Args:
+ fname (str): Absolute path to .ui file
+ base_instance (widget, optional): Instance of the Qt base class.
-def pyqt5_load_ui(fname):
- """Read Qt Designer .ui `fname`
+ Usage:
+ import sys
+ from Qt import QtWidgets, load_ui
+ class MyWindow(QtWidgets.QMainWindow):
+ def __init__(self, parent=None):
+ super(MyWindow, self).__init__(parent)
+ fname = 'my_ui.ui'
+ load_ui(fname, self)
+ app = QtWidgets.QApplication(sys.argv)
+ window = MyWindow()
+ window.show()
+ app.exec_()
- Args:
- fname (str): Absolute path to .ui file
+ """
- """
+ return uic.loadUi(fname, base_instance)
- from PyQt5 import uic
- return uic.loadUi(fname)
+ return load_ui
def _log(text, verbose):
@@ -190,18 +211,18 @@ def _init():
this has executed.
"""
-
+
preferred = os.getenv("QT_PREFERRED_BINDING")
verbose = os.getenv("QT_VERBOSE") is not None
bindings = (_pyside2, _pyqt5, _pyside, _pyqt4)
if preferred:
-
+
# Internal flag (used in installer)
if preferred == "None":
sys.modules[__name__].__wrapper_version__ = __version__
return
-
+
preferred = preferred.split(os.pathsep)
available = {
"PySide2": _pyside2,
diff --git a/README.md b/README.md
index 06e119b9..de033824 100644
--- a/README.md
+++ b/README.md
@@ -187,7 +187,22 @@ ui.show()
app.exec_()
```
-Please note, for maximum compatibility, only pass the argument of the filename to the `load_ui` function.
+This `load_ui` function accepts a second argument; the base instance into which the UI is loaded:
+
+```python
+import sys
+from Qt import QtWidgets, load_ui
+
+class MyWindow(QtWidgets.QMainWindow):
+ def __init__(self, parent=None):
+ super(MyWindow, self).__init__(parent)
+ load_ui("my.ui", self)
+
+app = QtWidgets.QApplication(sys.argv)
+window = MyWindow()
+window.show()
+app.exec_()
+```
##### sip API v2
diff --git a/appveyor.yml b/appveyor.yml
index 224a2e9f..454d9968 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -22,7 +22,7 @@ install:
# the registry key.
- REG ADD HKCU\Software\Python\PythonCore\2.7\InstallPath /f /ve /t REG_SZ /d %PYTHON%
- - ps: (new-object net.webclient).DownloadFile($env:PYQT, "C:\install-PyQt4.exe")
+ - ps: do{sleep 5;(New-Object Net.WebClient).DownloadFile($env:PYQT, "C:\install-PyQt4.exe")}while(!$?)
- ps: Start-Process -FilePath C:\install-PyQt4.exe -ArgumentList "/S" -Wait -Passthru
# Test install
diff --git a/tests.py b/tests.py
index 6c3ca8fd..2c6bf674 100644
--- a/tests.py
+++ b/tests.py
@@ -6,6 +6,7 @@
"""
import os
+import io
import sys
import imp
import shutil
@@ -25,6 +26,206 @@
def setup():
self.tempdir = tempfile.mkdtemp()
+ self.ui_qmainwindow = os.path.join(self.tempdir, "qmainwindow.ui")
+ source_qmainwindow = u"""\
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 216
+ 149
+
+
+
+ MainWindow
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+"""
+ with io.open(self.ui_qmainwindow, "w", encoding="utf-8") as f:
+ f.write(source_qmainwindow)
+
+ self.ui_qwidget = os.path.join(self.tempdir, "qwidget.ui")
+ source_qwidget = u"""\
+
+
+ Form
+
+
+
+ 0
+ 0
+ 235
+ 149
+
+
+
+ Form
+
+
+ -
+
+
+
+
+
+
+
+
+"""
+ with io.open(self.ui_qwidget, "w", encoding="utf-8") as f:
+ f.write(source_qwidget)
+
+ self.ui_qdialog = os.path.join(self.tempdir, "qdialog.ui")
+ source_qdialog = u"""\
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 201
+ 176
+
+
+
+ Dialog
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
+
+"""
+ with io.open(self.ui_qdialog, "w", encoding="utf-8") as f:
+ f.write(source_qdialog)
+
+ self.ui_custom_pyqt = os.path.join(self.tempdir, "custom_widget.ui")
+ source_custom_pyqt = u"""\
+
+
+ MainWindow
+
+
+ MainWindow
+
+
+
+
+
+ 10
+ 10
+ 113
+ 32
+
+
+
+ PushButton
+
+
+
+
+
+
+
+
+ MyCustomWidget
+ QPushButton
+
+
+
+
+
+
+
+"""
+ with io.open(self.ui_custom_pyqt, "w", encoding="utf-8") as f:
+ f.write(source_custom_pyqt)
+
def teardown():
shutil.rmtree(self.tempdir)
@@ -179,6 +380,262 @@ def test_vendoring():
) == 0
+def test_load_ui_into_self_qmainwindow_pyside():
+ """load_ui: Load UI into self (QMainWindow) using PySide"""
+
+ with pyside():
+ from Qt import QtWidgets, load_ui
+
+ class MainWindow(QtWidgets.QMainWindow):
+ def __init__(self, parent=None):
+ QtWidgets.QMainWindow.__init__(self, parent)
+ load_ui(sys.modules[__name__].ui_qmainwindow, self)
+
+ app = QtWidgets.QApplication(sys.argv)
+ window = MainWindow()
+
+ # Inherited from .ui file
+ assert hasattr(window, "lineEdit")
+ assert isinstance(window.__class__, type(QtWidgets.QMainWindow))
+ assert isinstance(window.parent(), type(None))
+ assert isinstance(window.lineEdit.__class__, type(QtWidgets.QWidget))
+ assert window.lineEdit.text() == ''
+ window.lineEdit.setText('Hello')
+ assert window.lineEdit.text() == 'Hello'
+
+ app.exit()
+
+
+def test_load_ui_into_self_qwidget_pyside():
+ """load_ui: Load UI into self (QWidget) using PySide"""
+
+ with pyside():
+ from Qt import QtWidgets, load_ui
+
+ class MainWindow(QtWidgets.QWidget):
+ def __init__(self, parent=None):
+ QtWidgets.QWidget.__init__(self, parent)
+ load_ui(sys.modules[__name__].ui_qwidget, self)
+
+ app = QtWidgets.QApplication(sys.argv)
+ window = MainWindow()
+
+ # Inherited from .ui file
+ assert hasattr(window, "lineEdit")
+ assert isinstance(window.__class__, type(QtWidgets.QWidget))
+ assert isinstance(window.parent(), type(None))
+ assert isinstance(window.lineEdit.__class__, type(QtWidgets.QWidget))
+ assert window.lineEdit.text() == ''
+ window.lineEdit.setText('Hello')
+ assert window.lineEdit.text() == 'Hello'
+
+ app.exit()
+
+
+def test_load_ui_into_self_qdialog_pyside():
+ """load_ui: Load UI into self (QDialog) using PySide"""
+
+ with pyside():
+ from Qt import QtWidgets, load_ui
+
+ class MainWindow(QtWidgets.QDialog):
+ def __init__(self, parent=None):
+ QtWidgets.QDialog.__init__(self, parent)
+ load_ui(sys.modules[__name__].ui_qdialog, self)
+
+ app = QtWidgets.QApplication(sys.argv)
+ window = MainWindow()
+
+ # Inherited from .ui file
+ assert hasattr(window, "lineEdit")
+ assert isinstance(window.__class__, type(QtWidgets.QDialog))
+ assert isinstance(window.parent(), type(None))
+ assert isinstance(window.lineEdit.__class__, type(QtWidgets.QWidget))
+ assert window.lineEdit.text() == ''
+ window.lineEdit.setText('Hello')
+ assert window.lineEdit.text() == 'Hello'
+
+ app.exit()
+
+
+def test_load_ui_into_self_qmainwindow_pyqt4():
+ """load_ui: Load UI into self (QMainWindow) using PyQt4"""
+
+ with pyqt4():
+ from Qt import QtWidgets, load_ui
+
+ class MainWindow(QtWidgets.QMainWindow):
+ def __init__(self, parent=None):
+ QtWidgets.QMainWindow.__init__(self, parent)
+ load_ui(sys.modules[__name__].ui_qmainwindow, self)
+
+ app = QtWidgets.QApplication(sys.argv)
+ window = MainWindow()
+
+ # From .ui file
+ assert hasattr(window, "lineEdit")
+ assert isinstance(window.__class__, type(QtWidgets.QMainWindow))
+ assert isinstance(window.parent(), type(None))
+ assert isinstance(window.lineEdit.__class__, type(QtWidgets.QWidget))
+ assert window.lineEdit.text() == ''
+ window.lineEdit.setText('Hello')
+ assert window.lineEdit.text() == 'Hello'
+
+ app.exit()
+
+
+def test_load_ui_into_self_qwidget_pyqt4():
+ """load_ui: Load UI into self (QWidget) using PyQt4"""
+
+ with pyqt4():
+ from Qt import QtWidgets, load_ui
+
+ class MainWindow(QtWidgets.QWidget):
+ def __init__(self, parent=None):
+ QtWidgets.QWidget.__init__(self, parent)
+ load_ui(sys.modules[__name__].ui_qwidget, self)
+
+ app = QtWidgets.QApplication(sys.argv)
+ window = MainWindow()
+
+ # From .ui file
+ assert hasattr(window, "lineEdit")
+ assert isinstance(window.__class__, type(QtWidgets.QWidget))
+ assert isinstance(window.parent(), type(None))
+ assert isinstance(window.lineEdit.__class__, type(QtWidgets.QWidget))
+ assert window.lineEdit.text() == ''
+ window.lineEdit.setText('Hello')
+ assert window.lineEdit.text() == 'Hello'
+
+ app.exit()
+
+
+def test_load_ui_into_self_qdialog_pyqt4():
+ """load_ui: Load UI into self (QWidget) using PyQt4"""
+
+ with pyqt4():
+ from Qt import QtWidgets, load_ui
+
+ class MainWindow(QtWidgets.QDialog):
+ def __init__(self, parent=None):
+ QtWidgets.QDialog.__init__(self, parent)
+ load_ui(sys.modules[__name__].ui_qdialog, self)
+
+ app = QtWidgets.QApplication(sys.argv)
+ window = MainWindow()
+
+ # From .ui file
+ assert hasattr(window, "lineEdit")
+ assert isinstance(window.__class__, type(QtWidgets.QDialog))
+ assert isinstance(window.parent(), type(None))
+ assert isinstance(window.lineEdit.__class__, type(QtWidgets.QWidget))
+ assert window.lineEdit.text() == ''
+ window.lineEdit.setText('Hello')
+ assert window.lineEdit.text() == 'Hello'
+
+ app.exit()
+
+
+def test_load_ui_into_custom_pyside():
+ """load_ui: Load UI into widget (QMainWindow) using PySide"""
+
+ with pyside():
+ from Qt import QtWidgets, load_ui
+
+ app = QtWidgets.QApplication(sys.argv)
+ widget = load_ui(sys.modules[__name__].ui_qmainwindow)
+
+ # From .ui file
+ assert hasattr(widget, "lineEdit")
+ assert isinstance(widget.__class__, type(QtWidgets.QMainWindow))
+ assert isinstance(widget.parent(), type(None))
+ assert isinstance(widget.lineEdit.__class__, type(QtWidgets.QWidget))
+ assert widget.lineEdit.text() == ''
+ widget.lineEdit.setText('Hello')
+ assert widget.lineEdit.text() == 'Hello'
+
+ app.exit()
+
+
+def test_load_ui_into_custom_pyqt4():
+ """load_ui: Load UI into widget (QMainWindow) using PyQt4"""
+
+ with pyqt4():
+ from Qt import QtWidgets, load_ui
+
+ app = QtWidgets.QApplication(sys.argv)
+ widget = load_ui(sys.modules[__name__].ui_qmainwindow)
+
+ # From .ui file
+ assert hasattr(widget, "lineEdit")
+ assert isinstance(widget.__class__, type(QtWidgets.QMainWindow))
+ assert isinstance(widget.parent(), type(None))
+ assert isinstance(widget.lineEdit.__class__, type(QtWidgets.QWidget))
+ assert widget.lineEdit.text() == ''
+ widget.lineEdit.setText('Hello')
+ assert widget.lineEdit.text() == 'Hello'
+
+ app.exit()
+
+
+def test_load_ui_connection_pyside():
+ """load_ui: Load UI into self, set up signals in PySide"""
+
+ with pyside():
+ import sys
+ from Qt import QtWidgets, load_ui
+
+ def setup_ui(base_instance=None):
+ return load_ui(sys.modules[__name__].ui_qmainwindow, base_instance)
+
+ app = QtWidgets.QApplication(sys.argv)
+ Ui = type(setup_ui()) # Get the un-instantiated class
+
+ class MyWidget(Ui): # Inherit from it.
+ def __init__(self, parent=None):
+ super(MyWidget, self).__init__(parent)
+ setup_ui(self)
+ self.x = False
+ self.lineEdit.textChanged.connect(self.some_method)
+ self.lineEdit.setText('hello')
+ assert self.x is True
+
+ def some_method(self):
+ self.x = True
+
+ my_widget = MyWidget()
+ my_widget.show()
+
+
+def test_load_ui_connection_pyqt4():
+ """load_ui: Load UI into self, set up signals in PySide"""
+
+ with pyqt4():
+ import sys
+ from Qt import QtWidgets, load_ui
+
+ def setup_ui(base_instance=None):
+ return load_ui(sys.modules[__name__].ui_qmainwindow, base_instance)
+
+ app = QtWidgets.QApplication(sys.argv)
+ Ui = type(setup_ui()) # Get the un-instantiated class
+
+ class MyWidget(Ui): # Inherit from it.
+ def __init__(self, parent=None):
+ super(MyWidget, self).__init__(parent)
+ setup_ui(self)
+ self.x = False
+ self.lineEdit.textChanged.connect(self.some_method)
+ self.lineEdit.setText('hello')
+ assert self.x is True
+
+ def some_method(self):
+ self.x = True
+
+ my_widget = MyWidget()
+ my_widget.show()
+
+
if PYTHON == 2:
def test_sip_api_already_set():
"""Raise ImportError if sip API v1 was already set (Python 2.x only)"""