diff options
Diffstat (limited to 'examples/widgetbinding')
-rw-r--r-- | examples/widgetbinding/CMakeLists.txt | 294 | ||||
-rw-r--r-- | examples/widgetbinding/bindings.h | 7 | ||||
-rw-r--r-- | examples/widgetbinding/bindings.xml | 9 | ||||
-rw-r--r-- | examples/widgetbinding/dialog.py | 33 | ||||
-rw-r--r-- | examples/widgetbinding/doc/widgetbinding.md | 78 | ||||
-rw-r--r-- | examples/widgetbinding/doc/widgetbinding.pyproject | 10 | ||||
-rw-r--r-- | examples/widgetbinding/macros.h | 16 | ||||
-rw-r--r-- | examples/widgetbinding/main.py | 14 | ||||
-rw-r--r-- | examples/widgetbinding/registerwigglywidget.py | 31 | ||||
-rw-r--r-- | examples/widgetbinding/wigglywidget.cpp | 92 | ||||
-rw-r--r-- | examples/widgetbinding/wigglywidget.h | 40 | ||||
-rw-r--r-- | examples/widgetbinding/wigglywidget.py | 69 |
12 files changed, 693 insertions, 0 deletions
diff --git a/examples/widgetbinding/CMakeLists.txt b/examples/widgetbinding/CMakeLists.txt new file mode 100644 index 000000000..1c5eefa50 --- /dev/null +++ b/examples/widgetbinding/CMakeLists.txt @@ -0,0 +1,294 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.18) +cmake_policy(VERSION 3.18) + +# Enable policy to not use RPATH settings for install_name on macOS. +if(POLICY CMP0068) + cmake_policy(SET CMP0068 NEW) +endif() + +# Enable policy to run automoc on generated files. +if(POLICY CMP0071) + cmake_policy(SET CMP0071 NEW) +endif() + +# Consider changing the project name to something relevant for you. +project(wiggly LANGUAGES CXX) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +find_package(Qt6 COMPONENTS Core Gui Widgets) + +# ================================ General configuration ====================================== + +# Set CPP standard to C++17 minimum. +set(CMAKE_CXX_STANDARD 17) + +# The wiggly library for which we will create bindings. You can change the name to something +# relevant for your project. +set(wiggly_library "libwiggly") + +# The name of the generated bindings module (as imported in Python). You can change the name +# to something relevant for your project. +set(bindings_library "wiggly") + +# The header file with all the types and functions for which bindings will be generated. +# Usually it simply includes other headers of the library you are creating bindings for. +set(wrapped_header ${CMAKE_SOURCE_DIR}/bindings.h) + +# The typesystem xml file which defines the relationships between the C++ types / functions +# and the corresponding Python equivalents. +set(typesystem_file ${CMAKE_SOURCE_DIR}/bindings.xml) + +# Specify which C++ files will be generated by shiboken. This includes the module wrapper +# and a '.cpp' file per C++ type. These are needed for generating the module shared +# library. +set(generated_sources + ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/wiggly_module_wrapper.cpp + ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/wigglywidget_wrapper.cpp) + + +# ================================== Shiboken detection ====================================== +# Use provided python interpreter if given. +if(NOT python_interpreter) + if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + find_program(python_interpreter "python_d") + if(NOT python_interpreter) + message(FATAL_ERROR + "A debug Python interpreter could not be found, which is a requirement when " + "building this example in a debug configuration. Make sure python_d.exe is in " + "PATH.") + endif() + else() + find_program(python_interpreter "python") + if(NOT python_interpreter) + message(FATAL_ERROR + "No Python interpreter could be found. Make sure python is in PATH.") + endif() + endif() +endif() +message(STATUS "Using python interpreter: ${python_interpreter}") + +# Macro to get various pyside / python include / link flags and paths. +# Uses the not entirely supported utils/pyside_config.py file. +macro(pyside_config option output_var) + if(${ARGC} GREATER 2) + set(is_list ${ARGV2}) + else() + set(is_list "") + endif() + + execute_process( + COMMAND ${python_interpreter} "${CMAKE_SOURCE_DIR}/../utils/pyside_config.py" + ${option} + OUTPUT_VARIABLE ${output_var} + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if ("${${output_var}}" STREQUAL "") + message(FATAL_ERROR "Error: Calling pyside_config.py ${option} returned no output.") + endif() + if(is_list) + string (REPLACE " " ";" ${output_var} "${${output_var}}") + endif() +endmacro() + +# Query for the shiboken generator path, Python path, include paths and linker flags. +pyside_config(--shiboken-module-path shiboken_module_path) +pyside_config(--shiboken-generator-path shiboken_generator_path) +pyside_config(--pyside-path pyside_path) +pyside_config(--pyside-include-path pyside_include_dir 1) +pyside_config(--python-include-path python_include_dir) +pyside_config(--shiboken-generator-include-path shiboken_include_dir 1) +pyside_config(--shiboken-module-shared-libraries-cmake shiboken_shared_libraries 0) +pyside_config(--python-link-flags-cmake python_linking_data 0) +pyside_config(--pyside-shared-libraries-cmake pyside_shared_libraries 0) + +set(shiboken_path "${shiboken_generator_path}/shiboken6${CMAKE_EXECUTABLE_SUFFIX}") +if(NOT EXISTS ${shiboken_path}) + message(FATAL_ERROR "Shiboken executable not found at path: ${shiboken_path}") +endif() + + +# ==================================== RPATH configuration ==================================== + + +# ============================================================================================= +# !!! (The section below is deployment related, so in a real world application you will want to +# take care of this properly with some custom script or tool). +# ============================================================================================= +# Enable rpaths so that the built shared libraries find their dependencies. +set(CMAKE_SKIP_BUILD_RPATH FALSE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +set(CMAKE_INSTALL_RPATH ${shiboken_module_path} ${CMAKE_CURRENT_SOURCE_DIR}) +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +# ============================================================================================= +# !!! End of dubious section. +# ============================================================================================= + + +# =============================== CMake target - wiggly_library =============================== + + +# Get the relevant Qt include dirs, to pass them on to shiboken. +get_property(QT_WIDGETS_INCLUDE_DIRS TARGET Qt6::Widgets PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +set(INCLUDES "") +foreach(INCLUDE_DIR ${QT_WIDGETS_INCLUDE_DIRS}) + list(APPEND INCLUDES "-I${INCLUDE_DIR}") +endforeach() + +# On macOS, check if Qt is a framework build. This affects how include paths should be handled. +get_target_property(QtCore_is_framework Qt6::Core FRAMEWORK) +if (QtCore_is_framework) + get_target_property(qt_core_library_location Qt6::Core LOCATION) + get_filename_component(qt_core_library_location_dir "${qt_core_library_location}" DIRECTORY) + get_filename_component(lib_dir "${qt_core_library_location_dir}/../" ABSOLUTE) + list(APPEND INCLUDES "--framework-include-paths=${lib_dir}") +endif() + +# We need to include the headers for the module bindings that we use. +set(pyside_additional_includes "") +foreach(INCLUDE_DIR ${pyside_include_dir}) + list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtCore") + list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtGui") + list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtWidgets") +endforeach() + + +# Define the wiggly shared library for which we will create bindings. +set(${wiggly_library}_sources wigglywidget.cpp) +add_library(${wiggly_library} SHARED ${${wiggly_library}_sources}) +set_property(TARGET ${wiggly_library} PROPERTY PREFIX "") + +# Needed mostly on Windows to export symbols, and create a .lib file, otherwise the binding +# library can't link to the wiggly library. +target_compile_definitions(${wiggly_library} PRIVATE BINDINGS_BUILD) + + +# ====================== Shiboken target for generating binding C++ files ==================== + + +# Set up the options to pass to shiboken. +set(shiboken_options --generator-set=shiboken --enable-parent-ctor-heuristic + --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero + --avoid-protected-hack + ${INCLUDES} + -I${CMAKE_SOURCE_DIR} + -T${CMAKE_SOURCE_DIR} + -T${pyside_path}/typesystems + --output-directory=${CMAKE_CURRENT_BINARY_DIR} + ) + +set(generated_sources_dependencies ${wrapped_header} ${typesystem_file}) + +# Add custom target to run shiboken to generate the binding cpp files. +add_custom_command(OUTPUT ${generated_sources} + COMMAND ${shiboken_path} + ${shiboken_options} ${wrapped_header} ${typesystem_file} + DEPENDS ${generated_sources_dependencies} + #IMPLICIT_DEPENDS CXX ${wrapped_header} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Running generator for ${typesystem_file}.") + + +# =============================== CMake target - bindings_library ============================= + + +# Set the cpp files which will be used for the bindings library. +set(${bindings_library}_sources ${generated_sources}) + +# Define and build the bindings library. +add_library(${bindings_library} SHARED ${${bindings_library}_sources}) + + +# Apply relevant include and link flags. +target_include_directories(${bindings_library} PRIVATE ${pyside_additional_includes}) +target_include_directories(${bindings_library} PRIVATE ${pyside_include_dir}) +target_include_directories(${bindings_library} PRIVATE ${python_include_dir}) +target_include_directories(${bindings_library} PRIVATE ${shiboken_include_dir}) + +target_link_libraries(${wiggly_library} PRIVATE Qt6::Widgets) +target_link_libraries(${bindings_library} PRIVATE Qt6::Widgets) +target_link_libraries(${bindings_library} PRIVATE ${wiggly_library}) +target_link_libraries(${bindings_library} PRIVATE ${pyside_shared_libraries}) +target_link_libraries(${bindings_library} PRIVATE ${shiboken_shared_libraries}) + +# Adjust the name of generated module. +set_property(TARGET ${bindings_library} PROPERTY PREFIX "") +set_property(TARGET ${bindings_library} PROPERTY OUTPUT_NAME + "${bindings_library}${PYTHON_EXTENSION_SUFFIX}") +if(WIN32) + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set_property(TARGET ${bindings_library} PROPERTY SUFFIX "_d.pyd") + else() + set_property(TARGET ${bindings_library} PROPERTY SUFFIX ".pyd") + endif() +endif() + +# Make sure the linker doesn't complain about not finding Python symbols on macOS. +if(APPLE) + set_target_properties(${bindings_library} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") +endif(APPLE) + +# Find and link to the python import library only on Windows. +# On Linux and macOS, the undefined symbols will get resolved by the dynamic linker +# (the symbols will be picked up in the Python executable). +if (WIN32) + list(GET python_linking_data 0 python_libdir) + list(GET python_linking_data 1 python_lib) + find_library(python_link_flags ${python_lib} PATHS ${python_libdir} HINTS ${python_libdir}) + target_link_libraries(${bindings_library} PRIVATE ${python_link_flags}) +endif() + +# ================================= Dubious deployment section ================================ + +set(windows_shiboken_shared_libraries) + +if(WIN32) + # ========================================================================================= + # !!! (The section below is deployment related, so in a real world application you will + # want to take care of this properly (this is simply to eliminate errors that users usually + # encounter. + # ========================================================================================= + # Circumvent some "#pragma comment(lib)"s in "include/pyconfig.h" which might force to link + # against a wrong python shared library. + + set(python_versions_list 3 36 37 38 39) + set(python_additional_link_flags "") + foreach(ver ${python_versions_list}) + set(python_additional_link_flags + "${python_additional_link_flags} /NODEFAULTLIB:\"python${ver}_d.lib\"") + set(python_additional_link_flags + "${python_additional_link_flags} /NODEFAULTLIB:\"python${ver}.lib\"") + endforeach() + + set_target_properties(${bindings_library} + PROPERTIES LINK_FLAGS "${python_additional_link_flags}") + + # Compile a list of shiboken shared libraries to be installed, so that + # the user doesn't have to set the PATH manually to point to the PySide package. + foreach(library_path ${shiboken_shared_libraries}) + string(REGEX REPLACE ".lib$" ".dll" library_path ${library_path}) + file(TO_CMAKE_PATH ${library_path} library_path) + list(APPEND windows_shiboken_shared_libraries "${library_path}") + endforeach() + # ========================================================================================= + # !!! End of dubious section. + # ========================================================================================= +endif() + +# ============================================================================================= +# !!! (The section below is deployment related, so in a real world application you will want to +# take care of this properly with some custom script or tool). +# ============================================================================================= +# Install the library and the bindings module into the source folder near the main.py file, so +# that the Python interpeter successfully imports the used module. +install(TARGETS ${bindings_library} ${wiggly_library} + LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR} + RUNTIME DESTINATION ${CMAKE_CURRENT_SOURCE_DIR} + ) +install(FILES ${windows_shiboken_shared_libraries} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}) +# ============================================================================================= +# !!! End of dubious section. +# ============================================================================================= diff --git a/examples/widgetbinding/bindings.h b/examples/widgetbinding/bindings.h new file mode 100644 index 000000000..7b45ad1bf --- /dev/null +++ b/examples/widgetbinding/bindings.h @@ -0,0 +1,7 @@ +// 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 diff --git a/examples/widgetbinding/bindings.xml b/examples/widgetbinding/bindings.xml new file mode 100644 index 000000000..957684074 --- /dev/null +++ b/examples/widgetbinding/bindings.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +--> +<typesystem package="wiggly"> + <load-typesystem name="typesystem_widgets.xml" generate="no"/> + <object-type name="WigglyWidget"/> +</typesystem> diff --git a/examples/widgetbinding/dialog.py b/examples/widgetbinding/dialog.py new file mode 100644 index 000000000..1bd127804 --- /dev/null +++ b/examples/widgetbinding/dialog.py @@ -0,0 +1,33 @@ +# 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) diff --git a/examples/widgetbinding/doc/widgetbinding.md b/examples/widgetbinding/doc/widgetbinding.md new file mode 100644 index 000000000..910961b1e --- /dev/null +++ b/examples/widgetbinding/doc/widgetbinding.md @@ -0,0 +1,78 @@ +(widgetbinding-example)= +# WigglyWidget Example + +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 to `main.py`, + * `dialog.cpp/h`, which was translated to `dialog.py`, + * `wigglywidget.cpp/h`, which was translated to `wigglywidget.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 expose + * `CMakeList.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`: + +Run CMake on macOS/Linux: +```bash +cd ~/pyside-setup/examples/widgetbinding +cd build +cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release +``` + +Run CMake on Windows: +```bash +cd C:\pyside-setup\examples\widgetbinding +mkdir build +cd build +cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe +``` + +To build: +```bash + +ninja +ninja install +cd .. +``` + +The final example can then be run by: +```bash +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. diff --git a/examples/widgetbinding/doc/widgetbinding.pyproject b/examples/widgetbinding/doc/widgetbinding.pyproject new file mode 100644 index 000000000..e423ea347 --- /dev/null +++ b/examples/widgetbinding/doc/widgetbinding.pyproject @@ -0,0 +1,10 @@ +{ + "files": ["../bindings.h", + "../dialog.py", + "../macros.h", + "../main.py", + "../registerwigglywidget.py", + "../wigglywidget.cpp", + "../wigglywidget.h", + "../wigglywidget.py"] +} diff --git a/examples/widgetbinding/macros.h b/examples/widgetbinding/macros.h new file mode 100644 index 000000000..001647966 --- /dev/null +++ b/examples/widgetbinding/macros.h @@ -0,0 +1,16 @@ +// 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 diff --git a/examples/widgetbinding/main.py b/examples/widgetbinding/main.py new file mode 100644 index 000000000..2b24018b1 --- /dev/null +++ b/examples/widgetbinding/main.py @@ -0,0 +1,14 @@ +# 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()) diff --git a/examples/widgetbinding/registerwigglywidget.py b/examples/widgetbinding/registerwigglywidget.py new file mode 100644 index 000000000..c5560ba6b --- /dev/null +++ b/examples/widgetbinding/registerwigglywidget.py @@ -0,0 +1,31 @@ +# 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) diff --git a/examples/widgetbinding/wigglywidget.cpp b/examples/widgetbinding/wigglywidget.cpp new file mode 100644 index 000000000..6fdc65f39 --- /dev/null +++ b/examples/widgetbinding/wigglywidget.cpp @@ -0,0 +1,92 @@ +// 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(); +} diff --git a/examples/widgetbinding/wigglywidget.h b/examples/widgetbinding/wigglywidget.h new file mode 100644 index 000000000..e527a8f49 --- /dev/null +++ b/examples/widgetbinding/wigglywidget.h @@ -0,0 +1,40 @@ +// 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 diff --git a/examples/widgetbinding/wigglywidget.py b/examples/widgetbinding/wigglywidget.py new file mode 100644 index 000000000..3362eab6b --- /dev/null +++ b/examples/widgetbinding/wigglywidget.py @@ -0,0 +1,69 @@ +# 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) |