Skip to content

Commit

Permalink
Merge pull request #119 from fredrikaverpil/feature/examples
Browse files Browse the repository at this point in the history
Add examples
  • Loading branch information
fredrikaverpil authored Aug 31, 2016
2 parents 6d86cc9 + 6649b6b commit 36effce
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 9 deletions.
11 changes: 6 additions & 5 deletions Dockerfile-py2.7
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ WORKDIR /workspace/Qt.py
ENTRYPOINT cp -r /Qt.py /workspace && \
python build_caveats.py && \
python build_membership.py && \
Xvfb :99 -screen 0 1024x768x16 2>/dev/null & \
sleep 3 && \
Xvfb :99 -screen 0 1024x768x16 2>/dev/null & \
while ! ps aux | grep -q '[0]:00 Xvfb :99 -screen 0 1024x768x16'; \
do echo "Waiting for Xvfb..."; sleep 1; done && \
ps aux | grep Xvfb && \
nosetests \
--verbose \
--with-process-isolation \
--with-doctest \
--exe \
test_membership.py \
test_caveats.py \
tests.py
test*.py \
examples/*/*.py
9 changes: 5 additions & 4 deletions Dockerfile-py3.5
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ ENV DISPLAY :99
WORKDIR /workspace/Qt.py
ENTRYPOINT cp -r /Qt.py /workspace && \
python3 build_caveats.py && \
Xvfb :99 -screen 0 1024x768x16 2>/dev/null & \
sleep 3 && \
Xvfb :99 -screen 0 1024x768x16 2>/dev/null & \
while ! ps aux | grep -q '[0]:00 Xvfb :99 -screen 0 1024x768x16'; \
do echo "Waiting for Xvfb..."; sleep 1; done && \
nosetests \
--verbose \
--with-process-isolation \
--with-doctest \
--exe \
test_caveats.py \
tests.py
test*.py \
examples/*/*.py
11 changes: 11 additions & 0 deletions build_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import os
import glob
import shutil

# Copy example files into current working directory
for filepath in glob.glob('examples/*/*'):
filename = os.path.basename(filepath)
if filepath.endswith('.py'):
shutil.copyfile(filepath, 'test_'+filename) # Prepend 'test' to *.py
else:
shutil.copyfile(filepath, filename)
15 changes: 15 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Examples

#### Purpose

Sometimes a pull request or a feature request doesn't make it into Qt.py because it doesn't fit [the contribution guidelines](https://github.com/mottosso/Qt.py/blob/master/CONTRIBUTING.md). This is hopefully a good thing for the end product in the long term perspective, but we're always sad to see good code drift into oblivion and disappear when a pull request is turned down and closed.

This part of the Qt.py project is a more loosely maintained (although tested) space, where we welcome example use of Qt.py to be shown off especially if it solves a problem Qt.py by itself doesn't solve out of the box.

If you wish to contribute, make a pull request. Please put your example files in a sub-folder of `/examples`. If you also wish to have your example included in testing, make sure the function you wish to be executed during testing is named in such a way that it starts with `test`. For a working example of examples :wink:, see the `/examples/load_ui` folder.

<br>

#### List of examples

* [`load_ui`](https://github.com/mottosso/Qt.py/blob/master/examples/load_ui/README.md) - add base instance argument
Empty file added examples/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions examples/load_ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## `load_ui` examples

#### Base instance as argument

The `uic.loadUi` function of PyQt4 and PyQt5 as well as the `QtUiTools.QUiLoader().load` function of PySide/PySide2 are mapped to a convenience function in Qt.py called `load_ui`. This function only accepts the filename argument of the .ui file.

A popular approach is to provide a base instance argument to PyQt's `uic.loadUi`, into which all widgets are loaded:

```python
# PyQt4, PyQt5
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
uic.loadUi('uifile.ui', self) # Loads all widgets of uifile.ui into self
```

PySide does not support this out of the box, but it can be implemented in various ways. In the example in `baseinstance1.py`, a support function `setup_ui` is defined which wraps `load_ui` and provides this second base instance argument. In `baseinstance2.py`, another approach is used where `pysideuic` is required for PySide/PySide2 and `uic` is required for PyQt4/PyQt5.
Empty file added examples/load_ui/__init__.py
Empty file.
56 changes: 56 additions & 0 deletions examples/load_ui/baseinstance1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import sys
import os

# Set preferred binding
os.environ['QT_PREFERRED_BINDING'] = os.pathsep.join(['PySide', 'PyQt4'])

from Qt import QtWidgets, load_ui


def setup_ui(uifile, base_instance=None):
"""Load a Qt Designer .ui file and returns an instance of the user interface
Args:
uifile (str): Absolute path to .ui file
base_instance (QWidget): The widget into which UI widgets are loaded
Returns:
QWidget: the base instance
"""
ui = load_ui(uifile) # Qt.py mapped function
if not base_instance:
return ui
else:
for member in dir(ui):
if not member.startswith('__') and \
member is not 'staticMetaObject':
setattr(base_instance, member, getattr(ui, member))
return ui


class MainWindow(QtWidgets.QWidget):
"""Load .ui file example, using setattr/getattr approach"""
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.base_instance = setup_ui('qwidget.ui', self)


def test():
"""Example: load_ui with setup_ui wrapper"""
working_directory = os.path.dirname(__file__)
os.chdir(working_directory)

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()

# Tests
assert isinstance(window, QtWidgets.QWidget)
assert isinstance(window.parent(), type(None))
assert isinstance(window.base_instance, QtWidgets.QWidget)
assert isinstance(window.lineEdit, QtWidgets.QWidget)
assert window.lineEdit.text() == ''
window.lineEdit.setText('Hello')
assert window.lineEdit.text() == 'Hello'

app.exit()
135 changes: 135 additions & 0 deletions examples/load_ui/baseinstance2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import sys
import os

# Set preferred binding, or Qt.py tests will fail which doesn't have pysideuic
os.environ['QT_PREFERRED_BINDING'] = 'PyQt4'

from Qt import QtWidgets
from Qt import __binding__


def load_ui_type(uifile):
"""Pyside equivalent for the loadUiType function in PyQt.
From the PyQt4 documentation:
Load a Qt Designer .ui file and return a tuple of the generated form
class and the Qt base class. These can then be used to create any
number of instances of the user interface without having to parse the
.ui file more than once.
Note:
Pyside lacks the "loadUiType" command, so we have to convert the ui
file to py code in-memory first and then execute it in a special frame
to retrieve the form_class.
Args:
uifile (str): Absolute path to .ui file
Returns:
tuple: the generated form class, the Qt base class
"""
import pysideuic
import xml.etree.ElementTree as ElementTree
from cStringIO import StringIO

parsed = ElementTree.parse(uifile)
widget_class = parsed.find('widget').get('class')
form_class = parsed.find('class').text

with open(uifile, 'r') as f:
o = StringIO()
frame = {}

pysideuic.compileUi(f, o, indent=0)
pyc = compile(o.getvalue(), '<string>', 'exec')
exec(pyc) in frame

# Fetch the base_class and form class based on their type in
# the xml from designer
form_class = frame['Ui_%s' % form_class]
base_class = eval('QtWidgets.%s' % widget_class)
return form_class, base_class


def pyside_load_ui(uifile, base_instance=None):
"""Provide PyQt4.uic.loadUi functionality to PySide
Args:
uifile (str): Absolute path to .ui file
base_instance (QWidget): The widget into which UI widgets are loaded
Note:
pysideuic is required for this to work with PySide.
This seems to work correctly in Maya as well as outside of it as
opposed to other implementations which involve overriding QUiLoader.
Returns:
QWidget: the base instance
"""
form_class, base_class = load_ui_type(uifile)
if not base_instance:
typeName = form_class.__name__
finalType = type(typeName,
(form_class, base_class),
{})
base_instance = finalType()
else:
if not isinstance(base_instance, base_class):
raise RuntimeError(
'The base_instance passed to loadUi does not inherit from'
' needed base type (%s)' % type(base_class))
typeName = type(base_instance).__name__
base_instance.__class__ = type(typeName,
(form_class, type(base_instance)),
{})
base_instance.setupUi(base_instance)
return base_instance


def load_ui_wrapper(uifile, base_instance=None):
"""Load a Qt Designer .ui file and returns an instance of the user interface
Args:
uifile (str): Absolute path to .ui file
base_instance (QWidget): The widget into which UI widgets are loaded
Returns:
function: pyside_load_ui or uic.loadUi
"""
if 'PySide' in __binding__:
return pyside_load_ui(uifile, base_instance)
elif 'PyQt' in __binding__:
from Qt import uic
return uic.loadUi(uifile, base_instance)


class MainWindow(QtWidgets.QWidget):
"""Load .ui file example, utilizing pysideuic and/or PyQt4.uic.loadUi"""
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.base_instance = load_ui_wrapper('qwidget.ui', self)


def test():
"""Example: load_ui with custom uic.loadUi-like wrapper"""
working_directory = os.path.dirname(__file__)
os.chdir(working_directory)

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()

# Tests
assert isinstance(window, QtWidgets.QWidget)
assert isinstance(window.parent(), type(None))
assert isinstance(window.base_instance, QtWidgets.QWidget)
assert isinstance(window.lineEdit, QtWidgets.QWidget)
assert window.lineEdit.text() == ''
window.lineEdit.setText('Hello')
assert window.lineEdit.text() == 'Hello'

app.exit()
24 changes: 24 additions & 0 deletions examples/load_ui/qwidget.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>235</width>
<height>149</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLineEdit" name="lineEdit"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

0 comments on commit 36effce

Please sign in to comment.