Releases: mottosso/Qt.py
1.1.0.b7
1.1.0.b6
1.1.0.b4
1.1.0.b3
Upgraded QtCompat
An additional layer was added to QtCompat
to facilitate more members.
Before
from Qt import QtCompat
QtCompat.setSectionResizeMode
After
from Qt import QtCompat
QtCompat.QHeaderView.setSectionResizeMode
Backwards compatibility is maintained till the next major version release.
QtSiteConfig
example
From /examples
This example illustrates how to make a QtSiteConfig module and how it affects Qt.py at run-time.
Usage
$ cd to/this/directory
$ python main.py
# Qt.QtCore was successfully removed by QSiteConfig.py
Because QtSiteConfig.py
is in the current working directory, it is available to import by Python. If running from a different directory, then you can append this directory to your PYTHONPATH
$ set PYTHONPATH=path/to/QtSiteConfig/
$ python main.py
# Qt.QtCore was successfully removed by QSiteConfig.py
Linux and MacOS users: Replace
set
withexport
Advanced examples
If you need to you can also add modules that are not in the standard Qt.py. All of these functions are optional in QtSiteConfig, so only implement the functions you need.
QtSiteConfig.py: Adding non-standard modules
By default Qt.py only exposes the "lowest common denominator" of all bindings. This example shows how to add the Qsci module that is not included by default with Qt.py.
def update_members(members):
"""An example of adding Qsci to Qt.py.
Arguments:
members (dict): The default list of members in Qt.py.
Update this dict with any modifications needed.
"""
# Include Qsci module for scintilla lexer support.
members["Qsci"] = [
"QsciAPIs",
"QsciAbstractAPIs",
"QsciCommand",
"QsciCommandSet",
"QsciDocument",
"QsciLexer",
"QsciLexerAVS",
"QsciLexerBash",
"QsciLexerBatch",
"QsciLexerCMake",
"QsciLexerCPP",
"QsciLexerCSS",
"QsciLexerCSharp",
"QsciLexerCoffeeScript",
"QsciLexerCustom",
"QsciLexerD",
"QsciLexerDiff",
"QsciLexerFortran",
"QsciLexerFortran77",
"QsciLexerHTML",
"QsciLexerIDL",
"QsciLexerJSON",
"QsciLexerJava",
"QsciLexerJavaScript",
"QsciLexerLua",
"QsciLexerMakefile",
"QsciLexerMarkdown",
"QsciLexerMatlab",
"QsciLexerOctave",
"QsciLexerPO",
"QsciLexerPOV",
"QsciLexerPascal",
"QsciLexerPerl",
"QsciLexerPostScript",
"QsciLexerProperties",
"QsciLexerPython",
"QsciLexerRuby",
"QsciLexerSQL",
"QsciLexerSpice",
"QsciLexerTCL",
"QsciLexerTeX",
"QsciLexerVHDL",
"QsciLexerVerilog",
"QsciLexerXML",
"QsciLexerYAML",
"QsciMacro",
"QsciPrinter",
"QsciScintilla",
"QsciScintillaBase",
"QsciStyle",
"QsciStyledText",
]
QtSiteConfig.py: Standardizing the location of Qt classes
Some classes have been moved to new locations between bindings. Qt.py uses the namespace dictated by PySide2 and most members are already in place.
This example reproduces functionality already in Qt.py but it provides a good example of how use this function.
def update_misplaced_members(members):
"""This optional function is called by Qt.py to standardize the location
and naming of exposed classes.
Arguments:
members (dict): The members considered by Qt.py
"""
# Standardize the the Property name
members["PySide2"]["QtCore.Property"] = "QtCore.Property"
members["PyQt5"]["QtCore.pyqtProperty"] = "QtCore.Property"
members["PySide"]["QtCore.Property"] = "QtCore.Property"
members["PyQt4"]["QtCore.pyqtProperty"] = "QtCore.Property"
QtSiteConfig.py: Standardizing PyQt4's QFileDialog functionality
This example reproduces functionality already in Qt.py but it provides a good example of what is necessary to create your QtCompat namespaces with custom method decorators to change how the source method runs.
def update_compatibility_members(members):
"""This function is called by Qt.py to modify the modules it exposes.
Arguments:
members (dict): The members considered by Qt.py
"""
members['PyQt4']["QFileDialog"] = {
"getOpenFileName": "QtWidgets.QFileDialog.getOpenFileName",
"getOpenFileNames": "QtWidgets.QFileDialog.getOpenFileNames",
"getSaveFileName": "QtWidgets.QFileDialog.getSaveFileName",
}
def update_compatibility_decorators(binding, decorators):
""" This function is called by Qt.py to modify the decorators applied to
QtCompat namespace objects. Defining this method is optional.
Arguments:
binding (str): The Qt binding being wrapped by Qt.py
decorators (dict): Maps specific decorator methods to
QtCompat namespace methods. See Qt._build_compatibility_members
for more info.
"""
if binding == 'PyQt4':
# QFileDialog QtCompat decorator
def _standardizeQFileDialog(some_function):
""" decorator that makes PyQt4 return conform to other bindings
"""
def wrapper(*args, **kwargs):
ret = some_function(*args, **kwargs)
# PyQt4 only returns the selected filename, force it to a
# standard return of the selected filename, and a empty string
# for the selected filter
return (ret, '')
# preserve docstring and name of original method
wrapper.__doc__ = some_function.__doc__
wrapper.__name__ = some_function.__name__
return wrapper
decorators.setdefault("QFileDialog",{})["getOpenFileName"] = \
_standardizeQFileDialog
decorators.setdefault("QFileDialog",{})["getOpenFileNames"] = \
_standardizeQFileDialog
decorators.setdefault("QFileDialog",{})["getSaveFileName"] = \
_standardizeQFileDialog
1.1.0.b2
1.1.0.b1
This adds a wrapper for wrapInstance
and getCppPointer
from shiboken2
and automatically unifies the differences with shiboken
and sip
for both Python 2 and 3.
Attribute | Returns |
---|---|
QtCompat.wrapInstance(addr=long, type=QObject) |
QObject |
QtCompat.getCppPointer(object=QObject) |
long |
Usage
import sys
from Qt import QtCompat, QtWidgets
app = QtWidgets.QApplication(sys.argv)
button = QtWidgets.QPushButton("Hello world")
pointer = QtCompat.getCppPointer(button)
widget = QtCompat.wrapInstance(long(pointer), QtWidgets.QWidget)
assert widget == button
app.exit()
Maya Example
This works for both 2016 and 2017.
import sys
from maya import OpenMayaUI
from Qt import QtCompat, QtWidgets
pointer = OpenMayaUI.MQtUtil.mainWindow()
widget = QtCompat.wrapInstance(long(pointer), QtWidgets.QWidget)
assert isinstance(widget, QtWidgets.QWidget)
Important
This addition requires sip
, shiboken
or shiboken2
to be available on your system. If not found, Qt.py will still import successfully, but these members will not be available.
In such cases, here is a Qt-only version and guaranteed cross-compatible version of the above.
from Qt import QtWidgets
app = QtWidgets.QApplication.instance()
widget = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"]
The same pattern may be applied to any and all uses of sip
, shiboken
and shiboken2
, as discussed in-depth at #53.
Enjoy!
More Examples
For consideration into the main README.
wrapInstance
have found particular use in Autodesk Maya, below are a few scenarios in which it is commonly used along with cross-binding alternatives.
Finding Widget Through MEL
shiboken
from maya import mel, OpenMayaUI
from Qt import QtWidgets
import shiboken2
status_line = mel.eval('$temp=$gStatusLineForm')
ptr = OpenMayaUI.MQtUtil.findControl(status_line)
status_line = shiboken2.wrapInstance(long(ptr), QtWidgets.QWidget)
status_line = status_line.children()[1].children()[1]
status_line.setStyleSheet("QWidget {background: red}")
Qt
from maya import mel
from Qt import QtWidgets
app = QtWidgets.QApplication.instance()
window = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"]
status_line = mel.eval('$temp=$gStatusLineForm')
status_line = window.findChild(QtGui.QWidget, gStatusLine)
status_lne.setStyleSheet("QWidget {background: red}")
Finding Widget through Object Name
shiboken
import shiboken
import maya.OpenMayaUI as apiUI
from Qt import QtWidgets
channel_box = apiUI.MQtUtil.findControl("mainChannelBox")
channel_box = shiboken.wrapInstance(long(channel_box), QtWidgets.QTableView)
channel_box.setStyleSheet("QWidget {background: red}")
Qt
from Qt import QtWidgets
app = QtWidgets.QApplication.instance()
window = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"]
channel_box = window.findChild(QtWidgets.QTableView, "mainChannelBox")
channel_box.setStyleSheet("QWidget {background: green}")
Custom Attribute Editor Template
For testing purposes, we'll create a custom node and associate an attribute editor template with it. The modification of the resulting template via Qt is what differs between shiboken
and Qt
.
Boilerplate
These two files are identical and cross-compatible.
// AEMyNodeTemplate.mel
global proc AEMyNodeTemplate(string $nodeName)
{
editorTemplate -beginScrollLayout;
editorTemplate -beginLayout "My Attributes" -collapse 0;
editorTemplate -callCustom "MyNode_build_ui" "MyNode_update_ui" $nodeName;
editorTemplate -addControl "x";
editorTemplate -addControl "y";
editorTemplate -addControl "z";
editorTemplate -endLayout;
editorTemplate -addExtraControls;
editorTemplate -endScrollLayout;
}
global proc MyNode_build_ui( string $nodeName )
{
string $parent = `setParent -q`;
python("import myNodeUi");
python("myNodeUi.build_ui('" + $parent + "', '" + $nodeName + "')");
}
global proc MyNode_update_ui( string $nodeName )
{
string $parent = `setParent -q`;
python("myNodeUi.update_ui('" + $parent + "', '" + $nodeName + "')");
}
# myNode.py
from maya import OpenMaya, OpenMayaMPx
kPluginNodeName = "MyNode"
MyNodeId = OpenMaya.MTypeId(524286)
class MyNode(OpenMayaMPx.MPxNode):
_x = OpenMaya.MObject()
_y = OpenMaya.MObject()
_z = OpenMaya.MObject()
def __init__(self):
OpenMayaMPx.MPxNode.__init__(self)
def compute(self, plug, data_block):
print("Computing..")
def MyNodeCreator():
return OpenMayaMPx.asMPxPtr(MyNode())
def MyNodeInit():
attr = OpenMaya.MFnNumericAttribute()
MyNode._x = attr.create("x", "x", OpenMaya.MFnNumericData.kFloat, 0.0)
attr.setKeyable(True)
MyNode._y = attr.create("y", "y", OpenMaya.MFnNumericData.kFloat, 0.0)
attr.setKeyable(True)
MyNode._z = attr.create("z", "z", OpenMaya.MFnNumericData.kFloat, 0.0)
attr.setKeyable(True)
MyNode.addAttribute(MyNode._x)
MyNode.addAttribute(MyNode._y)
MyNode.addAttribute(MyNode._z)
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
mplugin.registerNode(
kPluginNodeName,
MyNodeId,
MyNodeCreator,
MyNodeInit,
OpenMayaMPx.MPxNode.kDependNode
)
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
mplugin.deregisterNode(MyNodeId)
shiboken
Notice the OpenMayaUI and shiboken dependency.
# myNodeUi.py
from maya import cmds, OpenMayaUI
from Qt import QtWidgets
if cmds.about(api=True) >= 201700:
from shiboken2 import wrapInstance
else:
from shiboken import wrapInstance
def build_ui(layout, node):
layout_ptr = OpenMayaUI.MQtUtil.findLayout(layout)
layout_obj = wrapInstance(long(layout_ptr), QtWidgets.QWidget)
layout_wid = layout_obj.findChild(QtWidgets.QBoxLayout) # Cast to QBoxLayout
widget = QtWidgets.QLabel("Hello World")
layout_wid.insertWidget(0, widget)
def update_ui(layout, node):
pass
Qt
# myNodeUi.py
from Qt import QtWidgets
def build_ui(layout, node):
app = QtWidgets.QApplication.instance()
window = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"]
parent = window
for child in layout.split("|")[1:]:
parent = parent.findChild(QtWidgets.QWidget, child)
widget = QtWidgets.QLabel("Hello World")
layout = parent.findChild(QtWidgets.QBoxLayout) # Cast to QBoxLayout
layout.insertWidget(0, widget)
def update_ui(layout, node):
pass
Get top-level window in any binding and any application.
sip and shiboken is sometimes used to fetch the main window of an application in order to make it a parent of a custom window. Below is an example of how to find said window efficiently and in any situation.
from Qt import QtWidgets
current = QtWidgets.QApplication.activeWindow()
while current:
parent = current
current = parent.parent()
print(parent)
Limitations
- If run from within an already custom window that did not have it's parent set to the main window or a descendant of it, then this will return the custom window and may exit when it exists.
1.0.0
Stable release of 1.0!
This BACKWARDS INCOMPATIBLE version boasts quite a few additions and changes.
- Enforce PySide2 API | Read more here and here
- Optional Submodules, now you can use Qt.py on distribution missing common members, such as in Houdini, along with QML, OpenGL and third-party additions like Qscintilla | Read more
- Improved consistency,
load_ui()
is now calledloadUi()
, just like its original | Read more - Support for
baseinstance
inloadUi()
| Read more - Binding constants reap the benefits of static checking from your IDE | Read more
QtSiteConfig.py
for detailed customisation of member availability | Read more
Enjoy!
1.0.0.b6
QtSiteConfig.py
Individual members of Qt.py may now be customised via an additional module called QtSiteConfig.py
. For example, one could remove QtCore
if for whatever reason it shouldn't be used at all, or add site-specific modules like Qsci
.
Thanks to @MHendricks for this feature!
1.0.0.b5
Added support for binding constants.
import Qt
# Before
if Qt.__binding__ == "PyQt5":
# Do PyQt5 things
# After
if Qt.IsPyQt5:
# Do PyQt5 things
Which in addition to cutting down on typed characters also enables your IDE to detect potential misspellings. The previous method still exists and continues to work, there are no plans to deprecate it.
Thanks to @dgovil for this feature!
1.0.0.b4
Added support for baseinstance
to QtCompat.loadUi()
.
QtCompat.loadUi(uifile="my.ui", baseinstance=QtWidgets.QWidget)
This feature mimics the functionality (warts and all) of PyQt5.uic.loadUi
found here.
- Documentation
- See #196 for more details.
Thanks to @dgovil for this feature!