WigglyWidget#
The original Qt/C++ example can be found here: https://doc.qt.io/qt-6/qtwidgets-widgets-wiggly-example.html
This example shows how to interact with a custom widget from two different ways:
A full Python translation from a C++ example,
A Python binding generated from the C++ file.
The original example contained three different files:
main.cpp/h
, which was translated tomain.py
,dialog.cpp/h
, which was translated todialog.py
,wigglywidget.cpp/h
, which was translated towigglywidget.py
, but also remains as is, to enable the binding generation through Shiboken.
In the dialog.py
file you will find two imports that will be related
to each of the two approaches described before::
# Python translated file
from wigglywidget import WigglyWidget
# Binding module create with Shiboken
from wiggly import WigglyWidget
Steps to build the bindings#
The most important files are:
bindings.xml
, to specify the class that we want to expose from C++ to Python,bindings.h
to include the header of the classes we want to exposeCMakeList.txt
, with all the instructions to build the shared libraries (DLL, or dylib)pyside_config.py
which is located in the utils directory, one level up, to get the path for Shiboken and PySide.
Now create a build/
directory, and from inside run cmake
to use
the provided CMakeLists.txt
:
macOS/Linux:
cd ~/pyside-setup/examples/widgetbinding
On Windows:
cd C:\pyside-setup\examples\widgetbinding
mkdir build
cd build
cmake -S.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja
ninja install
cd ..
The final example can then be run by:
python main.py
You should see two identical custom widgets, one being the Python translation, and the other one being the C++ one.
Final words#
Since this example originated by mixing the concepts of the scriptableapplication
and samplebinding
examples, you can complement this README with the ones in
those directories.
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef BINDINGS_H
#define BINDINGS_H
#include "wigglywidget.h"
#endif // BINDINGS_H
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtWidgets import QDialog, QLineEdit, QVBoxLayout
# Python binding from the C++ widget
from wiggly import WigglyWidget as WigglyWidgetCPP
# Python-only widget
from wigglywidget import WigglyWidget as WigglyWidgetPY
class Dialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
wiggly_widget_py = WigglyWidgetPY(self)
wiggly_widget_cpp = WigglyWidgetCPP(self)
lineEdit = QLineEdit(self)
layout = QVBoxLayout(self)
layout.addWidget(wiggly_widget_py)
layout.addWidget(wiggly_widget_cpp)
layout.addWidget(lineEdit)
lineEdit.setClearButtonEnabled(True)
wiggly_widget_py.running = True
wiggly_widget_cpp.setRunning(True)
lineEdit.textChanged.connect(wiggly_widget_py.setText)
lineEdit.textChanged.connect(wiggly_widget_cpp.setText)
lineEdit.setText("🖖 Hello world!")
self.setWindowTitle("Wiggly")
self.resize(360, 145)
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef MACROS_H
#define MACROS_H
#include <QtCore/qglobal.h>
// Export symbols when creating .dll and .lib, and import them when using .lib.
#if BINDINGS_BUILD
# define BINDINGS_API Q_DECL_EXPORT
#else
# define BINDINGS_API Q_DECL_IMPORT
#endif
#endif // MACROS_H
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import sys
from PySide6.QtWidgets import QApplication
from dialog import Dialog
if __name__ == "__main__":
app = QApplication()
w = Dialog()
w.show()
sys.exit(app.exec())
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
from wigglywidget import WigglyWidget
# Set PYSIDE_DESIGNER_PLUGINS to point to this directory and load the plugin
TOOLTIP = "A cool wiggly widget (Python)"
DOM_XML = """
<ui language='c++'>
<widget class='WigglyWidget' name='wigglyWidget'>
<property name='geometry'>
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>200</height>
</rect>
</property>
<property name='text'>
<string>Hello, world</string>
</property>
</widget>
</ui>
"""
if __name__ == '__main__':
QPyDesignerCustomWidgetCollection.registerCustomWidget(WigglyWidget, module="wigglywidget",
tool_tip=TOOLTIP, xml=DOM_XML)
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "wigglywidget.h"
#include <QtGui/QFontMetrics>
#include <QtGui/QPainter>
#include <QtCore/QTimerEvent>
//! [0]
WigglyWidget::WigglyWidget(QWidget *parent)
: QWidget(parent)
{
setBackgroundRole(QPalette::Midlight);
setAutoFillBackground(true);
QFont newFont = font();
newFont.setPointSize(newFont.pointSize() + 20);
setFont(newFont);
}
//! [0]
//! [1]
void WigglyWidget::paintEvent(QPaintEvent * /* event */)
//! [1] //! [2]
{
if (m_text.isEmpty())
return;
static constexpr int sineTable[16] = {
0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100, -92, -71, -38
};
QFontMetrics metrics(font());
int x = (width() - metrics.horizontalAdvance(m_text)) / 2;
int y = (height() + metrics.ascent() - metrics.descent()) / 2;
QColor color;
//! [2]
//! [3]
QPainter painter(this);
//! [3] //! [4]
int offset = 0;
const auto codePoints = m_text.toUcs4();
for (char32_t codePoint : codePoints) {
const int index = (m_step + offset++) % 16;
color.setHsv((15 - index) * 16, 255, 191);
painter.setPen(color);
QString symbol = QString::fromUcs4(&codePoint, 1);
const int dy = (sineTable[index] * metrics.height()) / 400;
painter.drawText(x, y - dy, symbol);
x += metrics.horizontalAdvance(symbol);
}
}
//! [4]
//! [5]
void WigglyWidget::timerEvent(QTimerEvent *event)
//! [5] //! [6]
{
if (event->timerId() == m_timer.timerId()) {
++m_step;
update();
} else {
QWidget::timerEvent(event);
}
//! [6]
}
QString WigglyWidget::text() const
{
return m_text;
}
void WigglyWidget::setText(const QString &newText)
{
m_text = newText;
}
bool WigglyWidget::isRunning() const
{
return m_timer.isActive();
}
void WigglyWidget::setRunning(bool r)
{
if (r == isRunning())
return;
if (r)
m_timer.start(60, this);
else
m_timer.stop();
}
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef WIGGLYWIDGET_H
#define WIGGLYWIDGET_H
#include "macros.h"
#include <QtWidgets/QWidget>
#include <QtCore/QBasicTimer>
//! [0]
class BINDINGS_API WigglyWidget : public QWidget
{
Q_OBJECT
Q_PROPERTY(bool running READ isRunning WRITE setRunning)
Q_PROPERTY(QString text READ text WRITE setText)
public:
WigglyWidget(QWidget *parent = nullptr);
QString text() const;
bool isRunning() const;
public slots:
void setText(const QString &newText);
void setRunning(bool r);
protected:
void paintEvent(QPaintEvent *event) override;
void timerEvent(QTimerEvent *event) override;
private:
QBasicTimer m_timer;
QString m_text;
int m_step = 0;
};
//! [0]
#endif
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtCore import QBasicTimer, Property
from PySide6.QtGui import QColor, QFontMetrics, QPainter, QPalette
from PySide6.QtWidgets import QWidget
class WigglyWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._step = 0
self._text = ""
self.setBackgroundRole(QPalette.Midlight)
self.setAutoFillBackground(True)
new_font = self.font()
new_font.setPointSize(new_font.pointSize() + 20)
self.setFont(new_font)
self._timer = QBasicTimer()
def isRunning(self):
return self._timer.isActive()
def setRunning(self, r):
if r == self.isRunning():
return
if r:
self._timer.start(60, self)
else:
self._timer.stop()
def paintEvent(self, event):
if not self._text:
return
sineTable = [0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100,
-92, -71, -38]
metrics = QFontMetrics(self.font())
x = (self.width() - metrics.horizontalAdvance(self.text)) / 2
y = (self.height() + metrics.ascent() - metrics.descent()) / 2
color = QColor()
with QPainter(self) as painter:
for i in range(len(self.text)):
index = (self._step + i) % 16
color.setHsv((15 - index) * 16, 255, 191)
painter.setPen(color)
dy = (sineTable[index] * metrics.height()) / 400
c = self._text[i]
painter.drawText(x, y - dy, str(c))
x += metrics.horizontalAdvance(c)
def timerEvent(self, event):
if event.timerId() == self._timer.timerId():
self._step += 1
self.update()
else:
QWidget.timerEvent(event)
def text(self):
return self._text
def setText(self, text):
self._text = text
running = Property(bool, isRunning, setRunning)
text = Property(str, text, setText)