summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDominik Holland <dominik.holland@qt.io>2022-09-06 17:02:14 +0200
committerNicholas Bennett <nicholas.bennett@qt.io>2022-09-19 10:50:28 +0000
commit3bdac53057ae99b86c17010002059a60d94424eb (patch)
tree50da96673873f9c014021b6db7506d2fc8c23e46
parent0f16a2fc9ce3bbd1f0fbd37ddd1b7945d003cdf9 (diff)
Extend the tutorial by a chapter for own templates
Fixes: QTBUG-98998 Change-Id: Iec90f15b2d2854dbd3c7d0be3fea0c078c64f440 Reviewed-by: Robert Griebl <robert.griebl@qt.io> (cherry picked from commit e6e69d27c763cdfb8105341be16df2c6175ae751) Reviewed-by: Nicholas Bennett <nicholas.bennett@qt.io>
-rw-r--r--examples/interfaceframework/qface-tutorial/CMakeLists.txt1
-rw-r--r--examples/interfaceframework/qface-tutorial/ch6-own-backend/demo_server/instrumentcluster.cpp117
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/CMakeLists.txt9
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/backend_dbus/CMakeLists.txt38
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/backend_dbus/backend_dbus.pro22
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/CMakeLists.txt46
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/backend_simulator.pro24
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/simulation.qml98
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/simulation.qrc5
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/chapter6-own-backend.pro16
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/CMakeLists.txt44
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/dbus_conversion.h65
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/demo_server.pro26
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.cpp117
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.h62
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.xml55
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/main.cpp26
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/frontend/CMakeLists.txt34
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/frontend/frontend.pro12
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/imports/CMakeLists.txt36
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/imports/imports.pro25
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster.qface24
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/CMakeLists.txt79
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Cluster.qml56
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Dial.qml30
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Fuel.qml34
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Label.qml36
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/LeftDial.qml72
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/RightDial.qml180
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Top.qml83
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/app.qrc11
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/instrument-cluster.pro16
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/main.cpp18
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus.yaml12
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/CMakeLists.txt.tpl40
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.cpp.tpl85
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.h.tpl42
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.cpp.tpl36
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.h.tpl33
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.json.tpl11
-rw-r--r--examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.pri.tpl24
-rw-r--r--examples/interfaceframework/qface-tutorial/doc/src/qface-tutorial.qdoc258
-rw-r--r--examples/interfaceframework/qface-tutorial/qface-tutorial.pro5
-rw-r--r--src/interfaceframework/doc/src/ifcodegen/filter-reference.qdoc1
44 files changed, 2063 insertions, 1 deletions
diff --git a/examples/interfaceframework/qface-tutorial/CMakeLists.txt b/examples/interfaceframework/qface-tutorial/CMakeLists.txt
index a91d5980..959ca6c8 100644
--- a/examples/interfaceframework/qface-tutorial/CMakeLists.txt
+++ b/examples/interfaceframework/qface-tutorial/CMakeLists.txt
@@ -9,4 +9,5 @@ add_subdirectory(chapter4-simulation-behavior)
add_subdirectory(chapter5-ipc)
if(TARGET Qt::DBus)
add_subdirectory(chapter6-own-backend)
+ add_subdirectory(ch7-own-template)
endif()
diff --git a/examples/interfaceframework/qface-tutorial/ch6-own-backend/demo_server/instrumentcluster.cpp b/examples/interfaceframework/qface-tutorial/ch6-own-backend/demo_server/instrumentcluster.cpp
new file mode 100644
index 00000000..0f0c513c
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch6-own-backend/demo_server/instrumentcluster.cpp
@@ -0,0 +1,117 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "instrumentcluster.h"
+#include <QThread>
+
+InstrumentCluster::InstrumentCluster(QObject *parent)
+ : QObject(parent)
+ , m_speed(0)
+ , m_rpm(0)
+ , m_fuel(0)
+ , m_temperature(15)
+ , m_systemType(InstrumentClusterModule::Metric)
+{
+ startTimer(100);
+}
+
+int InstrumentCluster::speed() const
+{
+ return m_speed;
+}
+
+int InstrumentCluster::rpm() const
+{
+ return m_rpm;
+}
+
+qreal InstrumentCluster::fuel() const
+{
+ return m_fuel;
+}
+
+qreal InstrumentCluster::temperature() const
+{
+ return m_temperature;
+}
+
+InstrumentClusterModule::SystemType InstrumentCluster::systemType() const
+{
+ return m_systemType;
+}
+
+Warning InstrumentCluster::currentWarning() const
+{
+ return m_currentWarning;
+}
+
+void InstrumentCluster::setSpeed(int speed)
+{
+ if (m_speed == speed)
+ return;
+
+ m_speed = speed;
+ emit speedChanged(m_speed);
+}
+
+void InstrumentCluster::setRpm(int rpm)
+{
+ if (m_rpm == rpm)
+ return;
+
+ m_rpm = rpm;
+ emit rpmChanged(m_rpm);
+}
+
+void InstrumentCluster::setFuel(qreal fuel)
+{
+ if (qFuzzyCompare(m_fuel, fuel))
+ return;
+
+ m_fuel = fuel;
+ emit fuelChanged(m_fuel);
+}
+
+void InstrumentCluster::setTemperature(qreal temperature)
+{
+ if (qFuzzyCompare(m_temperature, temperature))
+ return;
+
+ m_temperature = temperature;
+ emit temperatureChanged(m_temperature);
+}
+
+void InstrumentCluster::setSystemType(InstrumentClusterModule::SystemType systemType)
+{
+ if (m_systemType == systemType)
+ return;
+
+ m_systemType = systemType;
+ emit systemTypeChanged(m_systemType);
+}
+
+void InstrumentCluster::setCurrentWarning(const Warning &currentWarning)
+{
+ if (m_currentWarning == currentWarning)
+ return;
+
+ m_currentWarning = currentWarning;
+ emit currentWarningChanged(m_currentWarning);
+}
+
+void InstrumentCluster::timerEvent(QTimerEvent *event)
+{
+ Q_UNUSED(event);
+
+ if (speed() >= 250)
+ setSpeed(0);
+ else
+ setSpeed(speed() + 1);
+
+ if (rpm() >= 5000)
+ setRpm(0);
+ else
+ setRpm(rpm() + 100);
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/CMakeLists.txt b/examples/interfaceframework/qface-tutorial/ch7-own-template/CMakeLists.txt
new file mode 100644
index 00000000..4123a5a0
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/CMakeLists.txt
@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 3.16)
+project(qface-chapter7 LANGUAGES CXX)
+
+add_subdirectory(instrument-cluster)
+add_subdirectory(frontend)
+add_subdirectory(backend_simulator)
+add_subdirectory(backend_dbus)
+add_subdirectory(imports)
+add_subdirectory(demo_server)
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_dbus/CMakeLists.txt b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_dbus/CMakeLists.txt
new file mode 100644
index 00000000..f295e642
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_dbus/CMakeLists.txt
@@ -0,0 +1,38 @@
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/interfaceframework/qface-tutorial/chapter7-own-template")
+
+find_package(Qt6 REQUIRED COMPONENTS Core DBus Gui InterfaceFramework)
+
+qt_add_plugin(ic_chapter7_dbus)
+set_target_properties(ic_chapter7_dbus PROPERTIES LIBRARY_OUTPUT_DIRECTORY ../interfaceframework)
+
+# Interface Framework Generator:
+qt_ifcodegen_extend_target(ic_chapter7_dbus
+ IDL_FILES ../instrument-cluster.qface
+ TEMPLATE ../templates/backend_dbus
+)
+
+target_include_directories(ic_chapter7_dbus PRIVATE
+ ../demo_server
+)
+
+target_link_libraries(ic_chapter7_dbus PUBLIC
+ libIc_chapter7
+ Qt::Core
+ Qt::DBus
+ Qt::Gui
+ Qt::InterfaceFramework
+)
+
+install(TARGETS ic_chapter7_dbus
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_dbus/backend_dbus.pro b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_dbus/backend_dbus.pro
new file mode 100644
index 00000000..8b468c21
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_dbus/backend_dbus.pro
@@ -0,0 +1,22 @@
+TEMPLATE = lib
+TARGET = $$qtLibraryTarget(instrumentcluster_dbus)
+DESTDIR = ../interfaceframework
+
+QT += interfaceframework dbus
+CONFIG += plugin
+
+LIBS += -L$$OUT_PWD/../ -l$$qtLibraryTarget(QtIfInstrumentCluster)
+INCLUDEPATH += $$OUT_PWD/../frontend $$PWD/../demo_server
+QMAKE_RPATHDIR += $$QMAKE_REL_RPATH_BASE/../
+
+PLUGIN_TYPE = interfaceframework
+
+IFCODEGEN_TEMPLATE = $$PWD/../templates/backend_dbus
+IFCODEGEN_SOURCES = ../instrument-cluster.qface
+
+HEADERS += \
+ ../demo_server/dbus_conversion.h \
+
+CONFIG += install_ok # Do not cargo-cult this!
+target.path = $$[QT_INSTALL_EXAMPLES]/interfaceframework/qface-tutorial/chapter7-own-template
+INSTALLS += target
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/CMakeLists.txt b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/CMakeLists.txt
new file mode 100644
index 00000000..f2f327e4
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/CMakeLists.txt
@@ -0,0 +1,46 @@
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/interfaceframework/qface-tutorial/chapter7-own-template")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui InterfaceFramework)
+
+qt_add_plugin(ic_chapter7_simulation)
+set_target_properties(ic_chapter7_simulation PROPERTIES LIBRARY_OUTPUT_DIRECTORY ../interfaceframework)
+
+# Interface Framework Generator:
+qt_ifcodegen_extend_target(ic_chapter7_simulation
+ IDL_FILES ../instrument-cluster.qface
+ TEMPLATE backend_simulator
+)
+
+target_link_libraries(ic_chapter7_simulation PUBLIC
+ libIc_chapter7
+)
+
+set(import_path "${CMAKE_CURRENT_BINARY_DIR}/backend_simulator/qml")
+if (NOT ${import_path} IN_LIST QML_IMPORT_PATH)
+ list (APPEND QML_IMPORT_PATH "${import_path}")
+ set(QML_IMPORT_PATH ${QML_IMPORT_PATH} CACHE INTERNAL "" FORCE)
+endif()
+
+# Resources:
+set(simulation_resource_files
+ "simulation.qml"
+)
+
+qt_add_resources(ic_chapter7_simulation "simulation"
+ PREFIX
+ "/"
+ FILES
+ ${simulation_resource_files}
+)
+
+install(TARGETS ic_chapter7_simulation
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/backend_simulator.pro b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/backend_simulator.pro
new file mode 100644
index 00000000..84a0ae83
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/backend_simulator.pro
@@ -0,0 +1,24 @@
+TEMPLATE = lib
+TARGET = $$qtLibraryTarget(instrumentcluster_simulation)
+DESTDIR = ../interfaceframework
+
+QT += core interfaceframework
+CONFIG += ifcodegen plugin
+
+LIBS += -L$$OUT_PWD/../ -l$$qtLibraryTarget(QtIfInstrumentCluster)
+INCLUDEPATH += $$OUT_PWD/../frontend
+QMAKE_RPATHDIR += $$QMAKE_REL_RPATH_BASE/../
+
+IFCODEGEN_TEMPLATE = backend_simulator
+IFCODEGEN_SOURCES = ../instrument-cluster.qface
+PLUGIN_TYPE = interfaceframework
+
+RESOURCES += \
+ simulation.qrc
+
+# Additional import path used to resolve QML modules in Qt Creator's code model
+QML_IMPORT_PATH = $$OUT_PWD/../frontend/qml
+
+CONFIG += install_ok # Do not cargo-cult this!
+target.path = $$[QT_INSTALL_EXAMPLES]/interfaceframework/qface-tutorial/chapter7-own-template
+INSTALLS += target
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/simulation.qml b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/simulation.qml
new file mode 100644
index 00000000..db44cfb5
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/simulation.qml
@@ -0,0 +1,98 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import Example.If.InstrumentClusterModule.simulation
+
+QtObject {
+ property var settings : IfSimulator.findData(IfSimulator.simulationData, "InstrumentCluster")
+ property bool defaultInitialized: false
+ property LoggingCategory qLcInstrumentCluster: LoggingCategory {
+ name: "example.if.instrumentclustermodule.simulation.instrumentclusterbackend"
+ }
+ property var backend : InstrumentClusterBackend {
+
+ function initialize() {
+ console.log(qLcInstrumentCluster, "INITIALIZE")
+ if (!defaultInitialized) {
+ IfSimulator.initializeDefault(settings, backend)
+ defaultInitialized = true
+ }
+ Base.initialize()
+ }
+
+ property int gearSpeed: 260 / 6
+ property int currentGear: speed / gearSpeed
+ rpm: currentGear >= 1 ? 3000 + (speed % gearSpeed) / gearSpeed * 2000
+ : (speed % gearSpeed) / gearSpeed * 5000
+
+ property var animation: SequentialAnimation {
+ loops: Animation.Infinite
+ running: true
+
+ ParallelAnimation {
+ SequentialAnimation {
+
+ NumberAnimation {
+ target: backend
+ property: "speed"
+ from: 0
+ to: 80
+ duration: 4000
+ }
+
+ NumberAnimation {
+ target: backend
+ property: "speed"
+ to: 50
+ duration: 2000
+ }
+
+ NumberAnimation {
+ target: backend
+ property: "speed"
+ to: 200
+ duration: 7000
+ }
+
+ ScriptAction {
+ script: {
+ backend.currentWarning = InstrumentClusterModule.warning("red","LOW FUEL", "images/fuelsymbol_orange.png")
+ }
+ }
+
+ NumberAnimation {
+ target: backend
+ property: "speed"
+ to: 0
+ duration: 5000
+ }
+
+ ScriptAction {
+ script: {
+ backend.currentWarning = InstrumentClusterModule.warning()
+ }
+ }
+ }
+
+ NumberAnimation {
+ target: backend
+ property: "fuel"
+ from: 1
+ to: 0
+ }
+ }
+
+ NumberAnimation {
+ target: backend
+ property: "fuel"
+ from: 0
+ to: 1
+ duration: 4000
+ }
+ }
+ }
+}
+
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/simulation.qrc b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/simulation.qrc
new file mode 100644
index 00000000..64b8d048
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/backend_simulator/simulation.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>simulation.qml</file>
+ </qresource>
+</RCC>
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/chapter6-own-backend.pro b/examples/interfaceframework/qface-tutorial/ch7-own-template/chapter6-own-backend.pro
new file mode 100644
index 00000000..86589117
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/chapter6-own-backend.pro
@@ -0,0 +1,16 @@
+TEMPLATE = subdirs
+SUBDIRS += \
+ instrument-cluster \
+ frontend \
+ backend_simulator \
+ backend_dbus \
+ imports \
+ demo_server \
+
+instrument-cluster.depends = frontend
+backend_simulator.depends = frontend
+backend_dbus.depends = frontend
+imports.depends = frontend
+demo_server.depends = frontend
+
+OTHER_FILES += instrument-cluster.qface
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/CMakeLists.txt b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/CMakeLists.txt
new file mode 100644
index 00000000..f621a508
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/CMakeLists.txt
@@ -0,0 +1,44 @@
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/interfaceframework/qface-tutorial/chapter7-own-template")
+
+find_package(Qt6 REQUIRED COMPONENTS Core DBus InterfaceFramework)
+
+set_source_files_properties(instrumentcluster.xml PROPERTIES INCLUDE "")
+set(cluster_SRCS)
+qt_add_dbus_adaptor(cluster_SRCS
+ instrumentcluster.xml
+ dbus_conversion.h
+ "" # empty parent_class value on purpose to not pass -l flag
+ instrumentcluster_adaptor
+)
+
+qt_add_executable(chapter7-demo-server
+ dbus_conversion.h
+ instrumentcluster.cpp instrumentcluster.h
+ main.cpp
+ ${cluster_SRCS}
+)
+
+set_target_properties(chapter7-demo-server PROPERTIES RUNTIME_OUTPUT_DIRECTORY ../)
+set_target_properties(chapter7-demo-server PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE FALSE
+)
+
+target_link_libraries(chapter7-demo-server PUBLIC
+ libIc_chapter7
+ Qt::DBus
+)
+
+install(TARGETS chapter7-demo-server
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/dbus_conversion.h b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/dbus_conversion.h
new file mode 100644
index 00000000..541be767
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/dbus_conversion.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef DBUS_CONVERSION_H
+#define DBUS_CONVERSION_H
+
+#include <QDBusArgument>
+#include "warning.h"
+#include "instrumentclustermodule.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_ALWAYS_INLINE QDBusArgument &operator<<(QDBusArgument &argument, const Warning &data)
+{
+ argument.beginStructure();
+ argument << data.color();
+ argument << data.text();
+ argument << data.icon();
+ argument.endStructure();
+
+ return argument;
+}
+
+Q_ALWAYS_INLINE const QDBusArgument &operator>>(const QDBusArgument &argument, Warning &data)
+{
+ argument.beginStructure();
+ QString color;
+ QString text;
+ QString icon;
+ argument >> color;
+ argument >> text;
+ argument >> icon;
+ data.setColor(color);
+ data.setText(text);
+ data.setIcon(icon);
+ argument.endStructure();
+
+ return argument;
+}
+
+Q_ALWAYS_INLINE QDBusArgument &operator<<(QDBusArgument &argument, InstrumentClusterModule::SystemType data)
+{
+ argument.beginStructure();
+ argument << quint32(data);
+ argument.endStructure();
+
+ return argument;
+}
+
+Q_ALWAYS_INLINE const QDBusArgument &operator>>(const QDBusArgument &argument, InstrumentClusterModule::SystemType &data)
+{
+ argument.beginStructure();
+ quint32 systemType;
+ argument >> systemType;
+ data = InstrumentClusterModule::toSystemType(systemType, nullptr);
+ argument.endStructure();
+
+ return argument;
+}
+
+QT_END_NAMESPACE
+
+#endif // DBUS_CONVERSION_H
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/demo_server.pro b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/demo_server.pro
new file mode 100644
index 00000000..47c41984
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/demo_server.pro
@@ -0,0 +1,26 @@
+TARGET = chapter7-demo-server
+DESTDIR = ..
+
+QT += interfaceframework dbus
+QT -= gui
+CONFIG -= app_bundle
+
+LIBS += -L$$OUT_PWD/../ -l$$qtLibraryTarget(QtIfInstrumentCluster)
+INCLUDEPATH += $$OUT_PWD/../frontend
+
+cluster.files = instrumentcluster.xml
+cluster.header_flags += -i dbus_conversion.h
+
+DBUS_ADAPTORS += cluster
+
+CONFIG += install_ok # Do not cargo-cult this!
+target.path = $$[QT_INSTALL_EXAMPLES]/interfaceframework/qface-tutorial/chapter7-own-template
+INSTALLS += target
+
+SOURCES += \
+ instrumentcluster.cpp \
+ main.cpp
+
+HEADERS += \
+ instrumentcluster.h \
+ dbus_conversion.h \
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.cpp b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.cpp
new file mode 100644
index 00000000..0f0c513c
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.cpp
@@ -0,0 +1,117 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "instrumentcluster.h"
+#include <QThread>
+
+InstrumentCluster::InstrumentCluster(QObject *parent)
+ : QObject(parent)
+ , m_speed(0)
+ , m_rpm(0)
+ , m_fuel(0)
+ , m_temperature(15)
+ , m_systemType(InstrumentClusterModule::Metric)
+{
+ startTimer(100);
+}
+
+int InstrumentCluster::speed() const
+{
+ return m_speed;
+}
+
+int InstrumentCluster::rpm() const
+{
+ return m_rpm;
+}
+
+qreal InstrumentCluster::fuel() const
+{
+ return m_fuel;
+}
+
+qreal InstrumentCluster::temperature() const
+{
+ return m_temperature;
+}
+
+InstrumentClusterModule::SystemType InstrumentCluster::systemType() const
+{
+ return m_systemType;
+}
+
+Warning InstrumentCluster::currentWarning() const
+{
+ return m_currentWarning;
+}
+
+void InstrumentCluster::setSpeed(int speed)
+{
+ if (m_speed == speed)
+ return;
+
+ m_speed = speed;
+ emit speedChanged(m_speed);
+}
+
+void InstrumentCluster::setRpm(int rpm)
+{
+ if (m_rpm == rpm)
+ return;
+
+ m_rpm = rpm;
+ emit rpmChanged(m_rpm);
+}
+
+void InstrumentCluster::setFuel(qreal fuel)
+{
+ if (qFuzzyCompare(m_fuel, fuel))
+ return;
+
+ m_fuel = fuel;
+ emit fuelChanged(m_fuel);
+}
+
+void InstrumentCluster::setTemperature(qreal temperature)
+{
+ if (qFuzzyCompare(m_temperature, temperature))
+ return;
+
+ m_temperature = temperature;
+ emit temperatureChanged(m_temperature);
+}
+
+void InstrumentCluster::setSystemType(InstrumentClusterModule::SystemType systemType)
+{
+ if (m_systemType == systemType)
+ return;
+
+ m_systemType = systemType;
+ emit systemTypeChanged(m_systemType);
+}
+
+void InstrumentCluster::setCurrentWarning(const Warning &currentWarning)
+{
+ if (m_currentWarning == currentWarning)
+ return;
+
+ m_currentWarning = currentWarning;
+ emit currentWarningChanged(m_currentWarning);
+}
+
+void InstrumentCluster::timerEvent(QTimerEvent *event)
+{
+ Q_UNUSED(event);
+
+ if (speed() >= 250)
+ setSpeed(0);
+ else
+ setSpeed(speed() + 1);
+
+ if (rpm() >= 5000)
+ setRpm(0);
+ else
+ setRpm(rpm() + 100);
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.h b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.h
new file mode 100644
index 00000000..43a6922b
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef INSTRUMENTCLUSTER_H
+#define INSTRUMENTCLUSTER_H
+
+#include <QObject>
+#include "warning.h"
+#include "instrumentclustermodule.h"
+
+class InstrumentCluster : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int speed READ speed WRITE setSpeed NOTIFY speedChanged)
+ Q_PROPERTY(int rpm READ rpm WRITE setRpm NOTIFY rpmChanged)
+ Q_PROPERTY(qreal fuel READ fuel WRITE setFuel NOTIFY fuelChanged)
+ Q_PROPERTY(qreal temperature READ temperature WRITE setTemperature NOTIFY temperatureChanged)
+ Q_PROPERTY(InstrumentClusterModule::SystemType systemType READ systemType WRITE setSystemType NOTIFY systemTypeChanged)
+ Q_PROPERTY(Warning currentWarning READ currentWarning WRITE setCurrentWarning NOTIFY currentWarningChanged)
+
+public:
+ explicit InstrumentCluster(QObject *parent = nullptr);
+
+ Q_INVOKABLE int speed() const;
+ Q_INVOKABLE int rpm() const;
+ Q_INVOKABLE qreal fuel() const;
+ Q_INVOKABLE qreal temperature() const;
+ Q_INVOKABLE InstrumentClusterModule::SystemType systemType() const;
+ Q_INVOKABLE Warning currentWarning() const;
+
+signals:
+ void speedChanged(int speed);
+ void rpmChanged(int rpm);
+ void fuelChanged(qreal fuel);
+ void temperatureChanged(qreal temperature);
+ void systemTypeChanged(InstrumentClusterModule::SystemType systemType);
+ void currentWarningChanged(Warning currentWarning);
+
+public slots:
+ void setSpeed(int speed);
+ void setRpm(int rpm);
+ void setFuel(qreal fuel);
+ void setTemperature(qreal temperature);
+ void setSystemType(InstrumentClusterModule::SystemType systemType);
+ void setCurrentWarning(const Warning &currentWarning);
+
+protected:
+ void timerEvent(QTimerEvent *event) override;
+
+private:
+ int m_speed;
+ int m_rpm;
+ qreal m_fuel;
+ qreal m_temperature;
+ InstrumentClusterModule::SystemType m_systemType;
+ Warning m_currentWarning;
+};
+
+#endif // INSTRUMENTCLUSTER_H
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.xml b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.xml
new file mode 100644
index 00000000..e5e20d09
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/instrumentcluster.xml
@@ -0,0 +1,55 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="Example.If.InstrumentCluster">
+ <property name="speed" type="i" access="read"/>
+ <property name="rpm" type="i" access="read"/>
+ <property name="fuel" type="d" access="read"/>
+ <property name="temperature" type="d" access="read"/>
+ <property name="systemType" type="(i)" access="read">
+ <annotation name="org.qtproject.QtDBus.QtTypeName" value="InstrumentClusterModule::SystemType"/>
+ </property>
+ <property name="currentWarning" type="(sss)" access="read">
+ <annotation name="org.qtproject.QtDBus.QtTypeName" value="Warning"/>
+ </property>
+ <method name="speed" >
+ <arg name="speed" type="i" direction="out"/>
+ </method>
+ <method name="rpm" >
+ <arg name="rpm" type="i" direction="out"/>
+ </method>
+ <method name="fuel" >
+ <arg name="fuel" type="d" direction="out"/>
+ </method>
+ <method name="temperature" >
+ <arg name="temperature" type="d" direction="out"/>
+ </method>
+ <method name="systemType" >
+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="InstrumentClusterModule::SystemType"/>
+ <arg name="systemType" type="(i)" direction="out"/>
+ </method>
+ <method name="currentWarning" >
+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="Warning"/>
+ <arg name="currentWarning" type="(sss)" direction="out"/>
+ </method>
+ <signal name="speedChanged">
+ <arg name="speed" type="i" direction="out"/>
+ </signal>
+ <signal name="rpmChanged">
+ <arg name="rpm" type="i" direction="out"/>
+ </signal>
+ <signal name="fuelChanged">
+ <arg name="fuel" type="d" direction="out"/>
+ </signal>
+ <signal name="temperatureChanged">
+ <arg name="temperature" type="d" direction="out"/>
+ </signal>
+ <signal name="systemTypeChanged">
+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="InstrumentClusterModule::SystemType"/>
+ <arg name="systemType" type="(i)" direction="out"/>
+ </signal>
+ <signal name="currentWarningChanged">
+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="Warning"/>
+ <arg name="currentWarning" type="(sss)" direction="out"/>
+ </signal>
+ </interface>
+</node>
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/main.cpp b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/main.cpp
new file mode 100644
index 00000000..e20c985b
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/demo_server/main.cpp
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QCoreApplication>
+#include <QDBusConnection>
+
+#include "instrumentcluster.h"
+#include "instrumentcluster_adaptor.h"
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ InstrumentCluster cluster;
+ qDBusRegisterMetaType<Warning>();
+ qDBusRegisterMetaType<InstrumentClusterModule::SystemType>();
+
+ InstrumentClusterAdaptor adaptor(&cluster);
+ QDBusConnection connection = QDBusConnection::sessionBus();
+ connection.registerObject("/", &cluster);
+ connection.registerService("Example.If.InstrumentCluster");
+
+ return app.exec();
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/frontend/CMakeLists.txt b/examples/interfaceframework/qface-tutorial/ch7-own-template/frontend/CMakeLists.txt
new file mode 100644
index 00000000..8f0f61b7
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/frontend/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.16)
+project(QtIfInstrumentCluster LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/interfaceframework/qface-tutorial/chapter7-own-template")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui InterfaceFramework Qml Quick)
+
+qt_add_library(libIc_chapter7)
+set_target_properties(libIc_chapter7 PROPERTIES OUTPUT_NAME "InstrumentCluster")
+set_target_properties(libIc_chapter7 PROPERTIES RUNTIME_OUTPUT_DIRECTORY ../)
+
+# Interface Framework Generator:
+qt_ifcodegen_extend_target(libIc_chapter7
+ IDL_FILES ../instrument-cluster.qface
+ TEMPLATE frontend
+)
+
+set(import_path "${CMAKE_CURRENT_BINARY_DIR}/frontend/qml")
+if (NOT ${import_path} IN_LIST QML_IMPORT_PATH)
+ list (APPEND QML_IMPORT_PATH "${import_path}")
+ set(QML_IMPORT_PATH ${QML_IMPORT_PATH} CACHE INTERNAL "" FORCE)
+endif()
+
+install(TARGETS libIc_chapter7
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/frontend/frontend.pro b/examples/interfaceframework/qface-tutorial/ch7-own-template/frontend/frontend.pro
new file mode 100644
index 00000000..d54111ef
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/frontend/frontend.pro
@@ -0,0 +1,12 @@
+TARGET = $$qtLibraryTarget(QtIfInstrumentCluster)
+TEMPLATE = lib
+DESTDIR = ..
+
+QT += interfaceframework qml quick
+
+CONFIG += ifcodegen
+IFCODEGEN_SOURCES = ../instrument-cluster.qface
+
+CONFIG += install_ok # Do not cargo-cult this!
+target.path = $$[QT_INSTALL_EXAMPLES]/interfaceframework/qface-tutorial/chapter7-own-template
+INSTALLS += target
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/imports/CMakeLists.txt b/examples/interfaceframework/qface-tutorial/ch7-own-template/imports/CMakeLists.txt
new file mode 100644
index 00000000..9f1d217c
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/imports/CMakeLists.txt
@@ -0,0 +1,36 @@
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/interfaceframework/qface-tutorial/chapter7-own-template/imports")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui InterfaceFramework)
+
+# Interface Framework Generator:
+qt_ifcodegen_import_variables(CLUSTER
+ IDL_FILES ../instrument-cluster.qface
+ TEMPLATE qmlplugin
+)
+
+qt_add_qml_module(ic_chapter7_imports
+ OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CLUSTER_URI_PATH}"
+ URI ${CLUSTER_URI}
+ VERSION ${CLUSTER_VERSION}
+ PLUGIN_TARGET ic_chapter7_imports
+ NO_PLUGIN_OPTIONAL
+ NO_GENERATE_PLUGIN_SOURCE
+ SOURCES
+ ${CLUSTER_SOURCES}
+)
+
+target_link_libraries(ic_chapter7_imports PUBLIC
+ libIc_chapter7
+)
+
+install(TARGETS ic_chapter7_imports
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/imports/imports.pro b/examples/interfaceframework/qface-tutorial/ch7-own-template/imports/imports.pro
new file mode 100644
index 00000000..cee967c5
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/imports/imports.pro
@@ -0,0 +1,25 @@
+TEMPLATE = lib
+CONFIG += plugin
+QT += interfaceframework
+
+LIBS += -L$$OUT_PWD/../ -l$$qtLibraryTarget(QtIfInstrumentCluster)
+INCLUDEPATH += $$OUT_PWD/../frontend
+
+IFCODEGEN_TEMPLATE = qmlplugin
+IFCODEGEN_SOURCES = ../instrument-cluster.qface
+
+load(ifcodegen)
+
+DESTDIR = $$OUT_PWD/$$replace(URI, \\., /)
+QMAKE_RPATHDIR += $$QMAKE_REL_RPATH_BASE/../../../../
+
+exists($$OUT_PWD/qmldir) {
+ cpqmldir.files = $$OUT_PWD/qmldir \
+ $$OUT_PWD/plugins.qmltypes
+ cpqmldir.path = $$DESTDIR
+ cpqmldir.CONFIG = no_check_exist
+ COPIES += cpqmldir
+}
+
+target.path = $$[QT_INSTALL_EXAMPLES]/interfaceframework/qface-tutorial/chapter7-own-template/imports
+INSTALLS += target
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster.qface b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster.qface
new file mode 100644
index 00000000..f3f93a7e
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster.qface
@@ -0,0 +1,24 @@
+@config_simulator: { simulationFile: "qrc:/simulation.qml" }
+module Example.If.InstrumentClusterModule 1.0
+
+@config_dbus: { xml: "../demo_server/instrumentcluster.xml", interfaceName: "Example.If.InstrumentCluster", className: "ExampleIfInstrumentClusterInterface" }
+interface InstrumentCluster {
+ readonly int speed;
+ readonly int rpm;
+ readonly real fuel;
+ @config_simulator: { default: 15 }
+ readonly real temperature;
+ readonly SystemType systemType;
+ readonly Warning currentWarning;
+}
+
+enum SystemType {
+ Imperial,
+ Metric
+}
+
+struct Warning {
+ string color
+ string text
+ string icon
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/CMakeLists.txt b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/CMakeLists.txt
new file mode 100644
index 00000000..004a7472
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/CMakeLists.txt
@@ -0,0 +1,79 @@
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/interfaceframework/qface-tutorial/chapter7-own-template")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)
+
+qt_add_executable(ic_chapter7
+ main.cpp
+)
+
+set_target_properties(ic_chapter7 PROPERTIES OUTPUT_NAME "chapter7-own-template")
+set_target_properties(ic_chapter7 PROPERTIES RUNTIME_OUTPUT_DIRECTORY ../)
+set_target_properties(ic_chapter7 PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE FALSE
+)
+
+target_link_libraries(ic_chapter7 PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+)
+
+# Resources:
+set(app_resource_files
+ "Cluster.qml"
+ "Dial.qml"
+ "Fuel.qml"
+ "Label.qml"
+ "LeftDial.qml"
+ "RightDial.qml"
+ "Top.qml"
+)
+
+qt_add_resources(ic_chapter7 "app"
+ PREFIX
+ "/"
+ FILES
+ ${app_resource_files}
+)
+
+set(images_resource_files
+ "../../images/+--.png"
+ "../../images/P-R-N-D.png"
+ "../../images/dial_cursor.png"
+ "../../images/dial_cursor_right.png"
+ "../../images/dial_fill_color.png"
+ "../../images/dial_fill_color_left.png"
+ "../../images/dial_pattern.png"
+ "../../images/fuel.png"
+ "../../images/fuel_level.png"
+ "../../images/fuelsymbol_orange.png"
+ "../../images/left_dial.png"
+ "../../images/mask_overlay.png"
+ "../../images/middle-bkg.png"
+ "../../images/middle-circle.png"
+ "../../images/right_dial.png"
+ "../../images/top_bar.png"
+)
+
+qt_add_resources(ic_chapter7 "images"
+ PREFIX
+ "/images"
+ BASE
+ "../../images"
+ FILES
+ ${images_resource_files}
+)
+
+install(TARGETS ic_chapter7
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Cluster.qml b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Cluster.qml
new file mode 100644
index 00000000..f5494ba3
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Cluster.qml
@@ -0,0 +1,56 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Window
+import Example.If.InstrumentClusterModule
+
+Window {
+ id: root
+
+ width: 1920
+ height: 720
+ title: qsTr("QtIF Instrument Cluster Chapter 6")
+ visible: true
+ color: "#0c0c0c"
+
+ InstrumentCluster {
+ id: instrumentCluster
+ }
+
+ LeftDial {
+ id: leftDial
+ anchors.left: parent.left
+ anchors.leftMargin: 0.1 * width
+
+ value: instrumentCluster.speed
+ metricSystem: instrumentCluster.systemType === InstrumentClusterModule.Metric
+ }
+
+ RightDial {
+ id: rightDial
+ anchors.right: parent.right
+ anchors.rightMargin: 0.1 * width
+
+ value: instrumentCluster.rpm
+ warningColor: instrumentCluster.currentWarning.color
+ warningText: instrumentCluster.currentWarning.text
+ warningIcon: instrumentCluster.currentWarning.icon
+ fuelLevel: 1.0//instrumentCluster.fuel
+ }
+
+ Top {
+ id: topbar
+ y: 7
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ temperature: instrumentCluster.temperature
+ }
+
+ Image {
+ anchors.fill: parent
+ source: Qt.resolvedUrl("images/mask_overlay.png")
+ }
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Dial.qml b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Dial.qml
new file mode 100644
index 00000000..bdebb5ad
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Dial.qml
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Item {
+ id: root
+
+ width: 480
+ height: 480
+
+ property real value: 0
+ property int upDuration: 2000
+ property int downDuration: 1000
+ property string fillImage: "images/dial_fill_color_left.png"
+ property string circleRadius: "0.193"
+ property string dialCursor: "images/dial_cursor.png"
+
+ Image {
+ id: meter
+ property real min: -83.5
+ property real max: 157
+ width: root.width
+ height: width - 1
+ rotation: min + (max - min) * root.value
+ source: Qt.resolvedUrl(root.dialCursor)
+ }
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Fuel.qml b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Fuel.qml
new file mode 100644
index 00000000..6c8a15b5
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Fuel.qml
@@ -0,0 +1,34 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Item {
+ id: root
+
+ width: 0.73 * 720
+ height: width - 1
+
+ property real value: 0
+
+ Item {
+ width: root.value * parent.width
+ height: parent.height
+ clip: true
+ Image {
+ width: root.width
+ height: root.height
+ source: Qt.resolvedUrl("images/fuel_level.png")
+ }
+ }
+
+ Image {
+ id: fuel
+ anchors.fill: parent
+ source: Qt.resolvedUrl("images/fuel.png")
+
+ }
+}
+
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Label.qml b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Label.qml
new file mode 100644
index 00000000..2c597f93
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Label.qml
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Item {
+ id: root
+
+ property alias textObject: textElement
+ property alias text: textElement.text
+ property alias font: textElement.font
+ property alias fontSize: textElement.font.pixelSize
+ property alias color: textElement.color
+ property alias textFormat: textElement.textFormat
+ property alias horizontalAlignment: textElement.horizontalAlignment
+ property alias elide: textElement.elide
+ property alias wrapMode: textElement.wrapMode
+ property alias verticalAlignment: textElement.verticalAlignment
+
+ Text {
+ id: textElement
+
+ anchors.fill: parent
+ anchors.margins: 8
+
+ verticalAlignment: Text.AlignVCenter
+
+ font.family: "Source Sans Pro"
+ font.pixelSize: 28
+ font.weight: Font.Light
+
+ color: "white"
+ }
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/LeftDial.qml b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/LeftDial.qml
new file mode 100644
index 00000000..950c1fc2
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/LeftDial.qml
@@ -0,0 +1,72 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Item {
+ id: root
+
+ width: 0.8 * 720
+ height: 720
+
+ property real value: 0.0
+ property bool metricSystem: false
+
+ Image {
+ id: overlay
+
+ width: 0.91 * root.width
+ height: 0.98 * width
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.verticalCenter: parent.verticalCenter
+ source: Qt.resolvedUrl("images/left_dial.png")
+ }
+
+ Text {
+ id: speedText
+
+ anchors.verticalCenter: overlay.verticalCenter
+ anchors.horizontalCenter: overlay.horizontalCenter
+ anchors.verticalCenterOffset: -7
+ anchors.horizontalCenterOffset: 5
+ font.family: "Source Sans Pro"
+ font.pixelSize: 60
+ font.letterSpacing: 4
+ color: "white"
+ text: root.value
+ }
+
+ Rectangle {
+ width: 60
+ height: 1
+ opacity: 0.4
+ anchors.top: speedText.bottom
+ anchors.topMargin: -8
+ anchors.horizontalCenter: overlay.horizontalCenter
+ anchors.horizontalCenterOffset: 2
+ }
+
+ Text {
+ id: mph
+ anchors.top: speedText.bottom
+ anchors.topMargin: -5
+ anchors.horizontalCenter: overlay.horizontalCenter
+ anchors.horizontalCenterOffset: 2
+ font.family: "Source Sans Pro"
+ font.pixelSize: 24
+ color: "white"
+ text: root.metricSystem ? "km/h" : "mph"
+ }
+
+ Dial {
+ width: 0.66 * root.height
+ height: width
+ anchors.centerIn: overlay
+ anchors.verticalCenterOffset: 2
+ fillImage: "images/dial_fill_color_left.png"
+ value: root.value / 240
+ }
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/RightDial.qml b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/RightDial.qml
new file mode 100644
index 00000000..81565001
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/RightDial.qml
@@ -0,0 +1,180 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Item {
+ id: root
+
+ width: 0.8 * 720
+ height: 720
+
+ property color warningColor: "transparent"
+ property string warningIcon
+ property string warningText
+ property int value
+ property alias fuelLevel: fuelMeter.value
+
+ Item {
+ width: root.width/3
+ height: 0.1 * root.width
+ anchors.bottom: overlay.top
+ anchors.left: parent.left
+ anchors.leftMargin: 0.2 * root.width
+
+ Image {
+ id: gears
+
+ width: 0.2 * root.width
+ height: 0.25 * width
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ source: Qt.resolvedUrl("images/P-R-N-D.png")
+ }
+
+ Image {
+ id: plusMinus
+ anchors.bottom: parent.bottom
+ anchors.left: gears.right
+ anchors.leftMargin: 10
+ source: Qt.resolvedUrl("images/+--.png")
+ }
+ }
+
+ Image {
+ id: overlay
+
+ width: 0.91 * root.width
+ height: 0.99 * width
+
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ anchors.verticalCenter: parent.verticalCenter
+ source: Qt.resolvedUrl("images/right_dial.png")
+
+ Rectangle {
+ id: rect
+ width: circle.width + 5
+ height: width
+ radius: width
+ color: "transparent"
+ border.color: root.warningColor
+ border.width: rect.borderWidth
+ anchors.centerIn: parent
+ anchors.horizontalCenterOffset: -3
+
+ property int borderWidth: 3
+
+ SequentialAnimation {
+ running: root.warningColor != "transparent"
+ loops: Animation.Infinite
+ NumberAnimation {
+
+ target: rect
+ properties: "borderWidth"
+ from: 3
+ to: 7
+ duration: 500
+ }
+
+ NumberAnimation {
+
+ target: rect
+ properties: "borderWidth"
+ from: 7
+ to: 3
+ duration: 500
+ }
+
+ onStopped: rect.borderWidth = 3
+
+ }
+ }
+
+ Image {
+ id: circle
+
+ width: root.width/2
+ height: width
+ anchors.centerIn: parent
+ anchors.horizontalCenterOffset: -6
+ anchors.verticalCenterOffset: 0
+ source: Qt.resolvedUrl("images/middle-bkg.png")
+
+ Image {
+ id: circle_overlay
+
+ width: parent.width
+ height: parent.height
+ anchors.centerIn: parent
+ source: Qt.resolvedUrl("images/middle-circle.png")
+ }
+
+ Image {
+ id: fuelSymbol
+ width: 70
+ height: 70
+ anchors.top: parent.top
+ anchors.topMargin: 55
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.horizontalCenterOffset: 0
+ source: root.warningIcon
+ fillMode: Image.PreserveAspectFit
+
+ }
+
+ Item {
+ width: parent.width
+ height: parent.height/4
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: 40
+
+ Rectangle {
+ id: speedText
+ width: parent.width - 80
+ height: 40
+ radius: 20
+ anchors.horizontalCenter: parent.horizontalCenter
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: Qt.darker("grey", 1.5) }
+ GradientStop { position: 0.4; color: "#0c0c0c" }
+ }
+
+ Label {
+ width: parent.width
+ anchors.centerIn: parent
+ horizontalAlignment: Text.AlignHCenter
+ font.pixelSize: text.length > 10 ? 18 : 24
+ font.bold: true
+ text: root.warningText
+ elide: Text.ElideMiddle
+ }
+ }
+ }
+ }
+ }
+
+ Fuel {
+ id: fuelMeter
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 0.05 * root.height
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.horizontalCenterOffset: -20
+ }
+
+ Dial {
+ id: dial
+
+ width: 0.69 * root.height
+ height: width
+ anchors.centerIn: overlay
+ anchors.verticalCenterOffset: 0
+ anchors.horizontalCenterOffset: -5
+ fillImage: "images/dial_fill_color.png"
+ circleRadius: "0.29"
+ dialCursor: "images/dial_cursor_right.png"
+ value: root.value / 7000
+ }
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Top.qml b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Top.qml
new file mode 100644
index 00000000..a1219d3e
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/Top.qml
@@ -0,0 +1,83 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Item {
+ id: root
+ width: 0.37 * 1920
+ height: 0.12 * 720
+
+ property real temperature: 0
+
+ Image {
+ id: background
+ width: parent.width
+ height: parent.height
+ source: Qt.resolvedUrl("images/top_bar.png")
+ }
+
+ Label {
+ id: timeText
+ anchors.verticalCenter: background.verticalCenter
+ anchors.left: background.left
+ anchors.leftMargin: 0.2 * background.width
+
+ font.pixelSize: 0.42 * background.height
+ font.bold: true
+
+ text: Qt.formatTime(currentDate, "hh:mm")
+
+ property var currentDate: new Date();
+
+ Timer {
+ interval: 1000
+ repeat: true
+ running: true
+ onTriggered: {
+ timeText.currentDate = new Date();
+ }
+ }
+ }
+
+ Item {
+ id: navigator
+
+ width: 0.25 * background.width
+ height: background.height
+ anchors.verticalCenter: background.verticalCenter
+ anchors.horizontalCenter: background.horizontalCenter
+
+ Row {
+ id: row
+ property int radius: 7
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: -16
+ spacing: 8
+
+ Repeater {
+ model: 3
+ delegate: Rectangle {
+ height: row.radius * 2
+ width: row.radius * 2
+ radius: row.radius
+ color: 1 === index ? "white" : "#4d4d4d"
+ }
+ }
+ }
+ }
+
+ Label {
+ id: temperatureText
+ anchors.verticalCenter: background.verticalCenter
+ anchors.left: navigator.right
+ anchors.leftMargin: 15
+
+ font.pixelSize: 0.42 * background.height
+ font.bold: true
+
+ text: root.temperature + "°C"
+ }
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/app.qrc b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/app.qrc
new file mode 100644
index 00000000..97948209
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/app.qrc
@@ -0,0 +1,11 @@
+<RCC>
+ <qresource prefix="/">
+ <file>Cluster.qml</file>
+ <file>Top.qml</file>
+ <file>RightDial.qml</file>
+ <file>LeftDial.qml</file>
+ <file>Dial.qml</file>
+ <file>Fuel.qml</file>
+ <file>Label.qml</file>
+ </qresource>
+</RCC>
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/instrument-cluster.pro b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/instrument-cluster.pro
new file mode 100644
index 00000000..40c3244b
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/instrument-cluster.pro
@@ -0,0 +1,16 @@
+TARGET = chapter7-own-template
+DESTDIR = ..
+
+QT += qml quick
+CONFIG -= app_bundle
+
+SOURCES = main.cpp
+RESOURCES += app.qrc \
+ ../../images/images.qrc \
+
+# Additional import path used to resolve QML modules in Qt Creator's code model
+QML_IMPORT_PATH = $$OUT_PWD/../frontend/qml
+
+CONFIG += install_ok # Do not cargo-cult this!
+target.path = $$[QT_INSTALL_EXAMPLES]/interfaceframework/qface-tutorial/chapter7-own-template
+INSTALLS += target
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/main.cpp b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/main.cpp
new file mode 100644
index 00000000..0035beea
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster/main.cpp
@@ -0,0 +1,18 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Luxoft Sweden AB
+// Copyright (C) 2018 Pelagicore AG
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ engine.addImportPath(app.applicationDirPath() + "/imports");
+ engine.load(QUrl(QStringLiteral("qrc:///Cluster.qml")));
+
+ return app.exec();
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus.yaml b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus.yaml
new file mode 100644
index 00000000..01c63fa7
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus.yaml
@@ -0,0 +1,12 @@
+backend_dbus:
+ module:
+ documents:
+ - "{{module.module_name|lower}}dbusplugin.h": "plugin.h.tpl"
+ - "{{module.module_name|lower}}dbusplugin.cpp": "plugin.cpp.tpl"
+ - "{{module.module_name|lower}}.json": "plugin.json.tpl"
+ - "{{srcBase|lower}}.pri": "plugin.pri.tpl"
+ - '{{srcBase|lower}}.cmake': 'CMakeLists.txt.tpl'
+ interface:
+ documents:
+ - '{{interface|lower}}dbusbackend.h': 'backend.h.tpl'
+ - '{{interface|lower}}dbusbackend.cpp': 'backend.cpp.tpl'
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/CMakeLists.txt.tpl b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/CMakeLists.txt.tpl
new file mode 100644
index 00000000..a5a19dc2
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/CMakeLists.txt.tpl
@@ -0,0 +1,40 @@
+{#
+# Copyright (C) 2021 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+#}
+{% include "common/generated_comment.cmake.tpl" %}
+
+qt6_set_ifcodegen_variable(${VAR_PREFIX}_SOURCES
+{% for interface in module.interfaces %}
+ ${CMAKE_CURRENT_LIST_DIR}/{{interface|lower}}dbusbackend.cpp
+{% endfor %}
+ ${CMAKE_CURRENT_LIST_DIR}/{{module.module_name|lower}}dbusplugin.cpp
+)
+
+qt6_set_ifcodegen_variable(${VAR_PREFIX}_LIBRARIES
+ Qt6::DBus
+ Qt6::InterfaceFramework
+)
+
+if (TARGET ${CURRENT_TARGET})
+{% for interface in module.interfaces %}
+ set_source_files_properties({{interface.tags.config_dbus.xml}} PROPERTIES INCLUDE dbus_conversion.h)
+ qt_add_dbus_interface(${VAR_PREFIX}_SOURCES
+ {{interface.tags.config_dbus.xml}}
+ {{interface|lower}}_interface
+ )
+{% endfor %}
+
+ target_sources(${CURRENT_TARGET}
+ PRIVATE
+ ${${VAR_PREFIX}_SOURCES}
+ )
+
+ target_include_directories(${CURRENT_TARGET} PRIVATE
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
+ )
+
+ target_link_libraries(${CURRENT_TARGET} PRIVATE
+ ${${VAR_PREFIX}_LIBRARIES}
+ )
+endif()
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.cpp.tpl b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.cpp.tpl
new file mode 100644
index 00000000..4bd5a49e
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.cpp.tpl
@@ -0,0 +1,85 @@
+{#
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+#}
+{% include "common/generated_comment.cpp.tpl" %}
+{% set class = '{0}DBusBackend'.format(interface) %}
+
+#include "{{class|lower}}.h"
+
+#include <QDBusConnection>
+
+{{class}}::{{class}}(QObject *parent)
+ : {{interface}}BackendInterface(parent)
+ , m_client(nullptr)
+{
+{% for struct in module.structs %}
+ qDBusRegisterMetaType<{{struct}}>();
+{% endfor %}
+{% for enum in module.enums %}
+ qDBusRegisterMetaType<{{module.module_name}}::{{enum|flag_type}}>();
+{% endfor %}
+}
+
+void {{class}}::initialize()
+{
+ if (!m_client)
+ setupConnection();
+ if (m_fetchList.isEmpty()) {
+{% for property in interface.properties %}
+ emit {{property}}Changed(m_{{property}});
+{% endfor %}
+ emit initializationDone();
+ }
+}
+
+void {{class}}::setupConnection()
+{
+ qInfo() << "Connecting to the Server";
+ m_client = new {{interface.tags.config_dbus.className}}("{{interface.tags.config_dbus.interfaceName}}", "/", QDBusConnection::sessionBus());
+{% for property in interface.properties %}
+ connect(m_client, &{{interface.tags.config_dbus.className}}::{{property}}Changed,
+ this, &{{class}}::on{{property|upperfirst}}Changed);
+{% endfor %}
+{% for property in interface.properties %}
+ void fetch{{property|upperfirst}}();
+{% endfor %}
+}
+
+{% for property in interface.properties %}
+void {{class}}::fetch{{property|upperfirst}}()
+{
+ m_fetchList.append("{{property}}");
+ auto reply = m_client->asyncCall("{{property}}");
+ auto watcher = new QDBusPendingCallWatcher(reply, this);
+ connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
+ QDBusPendingReply<{{property|return_type}}> reply = *watcher;
+ if (reply.isError()) {
+ qCritical() << reply.error();
+ } else {
+ m_fetchList.removeAll("{{property}}");
+ this->on{{property|upperfirst}}Changed(reply.value());
+ watcher->deleteLater();
+ this->checkInitDone();
+ }
+ });
+}
+{% endfor %}
+
+void {{class}}::checkInitDone()
+{
+ if (m_fetchList.isEmpty()) {
+ qInfo() << "All properties initialized";
+ emit initializationDone();
+ }
+}
+
+{% for property in interface.properties %}
+void {{class}}::on{{property|upperfirst}}Changed({{property|parameter_type}})
+{
+ m_{{property}} = {{property}};
+ emit {{property}}Changed({{property}});
+}
+{% endfor %}
+
+
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.h.tpl b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.h.tpl
new file mode 100644
index 00000000..d760327d
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.h.tpl
@@ -0,0 +1,42 @@
+{#
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+#}
+{% include "common/generated_comment.cpp.tpl" %}
+{% set class = '{0}DBusBackend'.format(interface) %}
+{% set oncedefine = '{0}_{1}_H_'.format(module.module_name|upper, class|upper) %}
+#ifndef {{oncedefine}}
+#define {{oncedefine}}
+
+#include "{{interface|lower}}backendinterface.h"
+#include "{{interface|lower}}_interface.h"
+
+class {{class}} : public {{interface}}BackendInterface
+{
+ Q_OBJECT
+public:
+ {{class}}(QObject *parent = nullptr);
+
+public:
+ void initialize() override;
+
+ void setupConnection();
+{% for property in interface.properties %}
+ void fetch{{property|upperfirst}}();
+{% endfor %}
+ void checkInitDone();
+
+public Q_SLOTS:
+{% for property in interface.properties %}
+ void on{{property|upperfirst}}Changed({{property|parameter_type}});
+{% endfor %}
+
+private:
+ {{interface.tags.config_dbus.className}} *m_client;
+ QStringList m_fetchList;
+{% for property in interface.properties %}
+ {{property|return_type}} m_{{property}};
+{% endfor %}
+};
+
+#endif // {{oncedefine}}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.cpp.tpl b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.cpp.tpl
new file mode 100644
index 00000000..6478430f
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.cpp.tpl
@@ -0,0 +1,36 @@
+{#
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+#}
+{% include "common/generated_comment.cpp.tpl" %}
+{% set class = '{0}DBusPlugin'.format(module.module_name) %}
+
+#include "{{class|lower}}.h"
+
+{% for interface in module.interfaces %}
+#include "{{interface|lower}}dbusbackend.h"
+{% endfor %}
+
+{{class}}::{{class}}(QObject *parent)
+ : QObject(parent)
+{
+{% for interface in module.interfaces %}
+ m_interfaces << new {{interface}}DBusBackend(this);
+{% endfor %}
+}
+
+QStringList {{class}}::interfaces() const
+{
+ QStringList list;
+{% for iface in module.interfaces %}
+{% if loop.first %} list{% endif %} << {{module.module_name|upperfirst}}_{{iface}}_iid{% if loop.last %};{% endif %}
+{% endfor %}
+
+ return list;
+}
+
+QIfFeatureInterface *{{class}}::interfaceInstance(const QString &interface) const
+{
+ int index = interfaces().indexOf(interface);
+ return index < 0 ? nullptr : m_interfaces.at(index);
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.h.tpl b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.h.tpl
new file mode 100644
index 00000000..27f4e75b
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.h.tpl
@@ -0,0 +1,33 @@
+{#
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+#}
+{% include "common/generated_comment.cpp.tpl" %}
+{% set class = '{0}DBusPlugin'.format(module.module_name) %}
+{% set oncedefine = '{0}_H_'.format(class|upper) %}
+
+#ifndef {{oncedefine}}
+#define {{oncedefine}}
+
+#include <QObject>
+#include <QVector>
+#include <QIfServiceInterface>
+
+class {{class}} : public QObject, public QIfServiceInterface
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QIfServiceInterface_iid FILE "{{module.module_name|lower}}.json")
+ Q_INTERFACES(QIfServiceInterface)
+
+public:
+ explicit {{class}}(QObject *parent = nullptr);
+
+ QStringList interfaces() const override;
+ QIfFeatureInterface* interfaceInstance(const QString &interface) const override;
+
+private:
+ QVector<QIfFeatureInterface *> m_interfaces;
+};
+
+#endif // {{oncedefine}}
+
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.json.tpl b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.json.tpl
new file mode 100644
index 00000000..a742ef34
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.json.tpl
@@ -0,0 +1,11 @@
+{#
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+#}
+{
+ "interfaces" : [
+{% for interface in module.interfaces %}
+ "{{interface.qualified_name}}"{% if not loop.last %},{%endif%}
+{% endfor%}
+ ]
+}
diff --git a/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.pri.tpl b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.pri.tpl
new file mode 100644
index 00000000..1d4b5307
--- /dev/null
+++ b/examples/interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.pri.tpl
@@ -0,0 +1,24 @@
+{#
+# Copyright (C) 2021 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+#}
+{% include "common/generated_comment.qmake.tpl" %}
+
+HEADERS += \
+{% for interface in module.interfaces %}
+ $$PWD/{{interface|lower}}dbusbackend.h \
+{% endfor %}
+ $$PWD/{{module.module_name|lower}}dbusplugin.h
+
+SOURCES += \
+{% for interface in module.interfaces %}
+ $$PWD/{{interface|lower}}dbusbackend.cpp \
+{% endfor %}
+ $$PWD/{{module.module_name|lower}}dbusplugin.cpp
+
+{% for interface in module.interfaces %}
+{{interface}}.files = {{interface.tags.config_dbus.xml}}
+{{interface}}.header_flags += -i dbus_conversion.h
+
+DBUS_INTERFACES += {{interface}}
+{% endfor %}
diff --git a/examples/interfaceframework/qface-tutorial/doc/src/qface-tutorial.qdoc b/examples/interfaceframework/qface-tutorial/doc/src/qface-tutorial.qdoc
index 7f8549ec..d0bda255 100644
--- a/examples/interfaceframework/qface-tutorial/doc/src/qface-tutorial.qdoc
+++ b/examples/interfaceframework/qface-tutorial/doc/src/qface-tutorial.qdoc
@@ -889,4 +889,262 @@
\image qface-tutorial-dbus.gif
+ \target chapter7
+ \section1 Chapter 7: Write a Template for a Back End with D-Bus
+
+ Now as we have our own production backend ready, it's a good time to move it into an ifcodegen
+ template. For a simple example like this, keeping the handwritten backend would be totally fine,
+ but once you have defined multiple Modules and Interfaces it is a lot of boiler-plate code to
+ maintain.
+
+ \section2 Create a template from scratch
+
+ The best way to start a new template, is to copy the existing code to a new folder for our
+ template. First we create a \c templates folder and within that a folder called \c backend_dbus.
+ This follows the template naming convention from ifcodegen, but you can also use any other
+ name as well.
+
+ The next step is to rename the files in our template folder, to make them more generic and
+ identify them as templates by adding an additional \c tpl suffix.
+
+ The template folder should now look like this:
+
+ \list
+ \li backend.cpp.tpl
+ \li backend.h.tpl
+ \li plugin.cpp.tpl
+ \li plugin.h.tpl
+ \li plugin.json.tpl
+ \endlist
+
+ \section2 Convert all files to templates
+
+ Let's go over all the files in the template and use the special \l {Jinja Template Syntax} to
+ extend them for our needs.
+
+ \section3 plugin.h.tpl
+
+ The first thing we usually want to do is to add a special comment for the autogenerated file.
+ This will mark it as autogenerated and that it shouldn't be edited manually.
+
+ The following line includes a predefined comment file (part of ifcodegen):
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.h.tpl
+ \skipto {% include
+ \printuntil {% include
+
+ \note The \c.cpp suffix of the included file indicates that the comments are intended to be used
+ in C++ files.
+
+ Next we want to replace the hardcoded \c #ifdef names with something derived from the module name.
+ This is done by defining some helper variables first.
+ The first variable is named \c class and uses a format string which consists of the name of the
+ module and the static text \c DBusPlugin.
+
+ Given that our module is named \c Example.If.InstrumentClusterModule the resulting string will be
+ \c InstrumentClusterModuleDBusPlugin.
+
+ The second variable also uses a format string but uses the previously defined class variable
+ followed by a pipe symbol. The pipe indicates that the variable should be given to a function that
+ proccess the variable as input to create some new output. This is called a \l {Filter
+ Reference}{filter} in Jinja. The filter is called \c upper and will make the given module name
+ all upper-case.
+
+ The next step is to use the new defined variables to replace the hardcoded defines. In order
+ to use a variable and print its content to the autogenerated code, double curly braces have to
+ be used. All text that does not use the Jinja syntax is printed as is. With that in mind we
+ can keep the include statements as they are and the template file should look like this:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.h.tpl
+ \skipto #ifndef
+ \printuntil #define
+ \dots
+ \skipto #endif
+ \printto
+
+ In the same way we continue to replace all occurrences of the hardcoded class name with \c {{class}}
+ and replace the hardcoded plugin metadata json with \c {{module.module_name|lower}}.json.
+
+ The class declaration will now look like this:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.h.tpl
+ \skipto class {{class}}
+ \printuntil };
+
+ You might have noticed that instead of using a hardcoded pointer to the \c InstrumentClusterBackend
+ we now use \c QVector<QIfFeatureInterface *>. This is needed as a module can have multiple
+ interfaces and the plugin needs to store instances for all of them. This can be easily achieved
+ by using a vector instead of hardcoded values.
+
+ \section3 plugin.cpp.tpl
+
+ In a similar fashion the \c plugin.cpp.tpl can be extended with \l {Jinja Template Syntax}
+ {Jinja syntax}. It also starts by including the generated comment and by defining the \c class
+ variable. In addition to including the autogenerated plugin header, we also need to include the
+ header for all backend classes. As mentioned before, a module can have multiple interfaces. To
+ generate an include statement for every interface within a module, a Jinja for loop is used:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.cpp.tpl
+ \skipto {% for
+ \printuntil {% endfor
+
+ Afterwards we continue to define the constructor for our plugin and also use a for loop to fill
+ the \c m_instances vector with backend class instances.
+
+ In addition, for the plugin to work correctly the \c interfaces() and \c interfaceInstance()
+ methods need to be implemented. The \c interfaces() method needs to return a list of interfaces
+ it provides. For this also a Jinja for loop is used. Inside the for loop, Jinja if statements
+ are used to decide whether some extra content needs to be printed at the beginning and the end
+ of the loop.
+
+ The full plugin definition now looks like this:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.cpp.tpl
+ \skipto {{class}}
+ \printuntil
+
+ \section3 backend.h.tpl and backend.cpp.tpl
+
+ The backend class files follow the same schema as the plugin. All fetch methods are generated
+ using a loop like this:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.h.tpl
+ \skipto {% for
+ \printuntil {% endfor
+
+ In a similar fashion the slots for changed values are generated. But as those take the property
+ as an argument we need to generate this part as well.
+ This is done by using a filter called \l parameter_type, which takes care of that.
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.h.tpl
+ \skipuntil public Q_SLOTS:
+ \printuntil {% endfor
+
+ Lastly, a for loop also needs to be used to generate all the member variables. To convert the
+ QFace IDL type of the property to the correct C++ type, we use the \l return_type filter.
+
+ In the source file, we register all enums and structs using \l qDBusRegisterMetaType and
+ replace the hardcoded \c propertyChanged method calls in the \c initialize() function with a Jinja
+ for loop.
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.cpp.tpl
+ \skipto {{class}}
+ \printto void {{class}}::setupConnection()
+
+ The rest of the code is ported accordingly and looks like this:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.cpp.tpl
+ \skipto void {{class}}::setupConnection()
+ \printto
+
+ \section3 plugin.json
+
+ For the plugin to be loaded correctly we also need to generate the \c plugin.json file, which is
+ done like this:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.json.tpl
+ \skipuntil #}
+ \printto
+
+ \section2 Add new Annotations
+
+ If you did the excercise to port all the files by yourself, you might have noticed that not
+ everything in the backend code can be derived from the QFace module or interface names.
+
+ As we still want to use the DBus interface generated by \l qdbusxml2cpp, the class name and dbus
+ interface name are given by the \c instrumentcluster.xml file and once we add an additional
+ interface to the IDL file we also need an DBus XML for that.
+
+ That means, we need additional information for every interface in our IDL file. This can be
+ achieved by adding a new annotation to the interface:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/instrument-cluster.qface
+ \skipto @config_dbus
+ \printuntil interface InstrumentCluster
+ \dots
+
+ Now as the information is part of the IDL file, we can also access it in the template like this:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/backend.cpp.tpl
+ \skipto m_client =
+ \printuntil m_client =
+
+ \section2 Finalize the template
+
+ To finalize the template we need to create some more files.
+
+ \section3 Build-system template files
+
+ Right now ifcodegen supports both QMake and CMake as build systems. For each one we need to
+ provide additional files to let the build system know how to generate and compile our code.
+
+ For QMake we add a \c plugin.pri.tpl:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/plugin.pri.tpl
+ \skipuntil #}
+ \printto
+
+ There we add all the generated C++ files to the \c HEADERS and \c SOURCES variables
+ accordingly. Afterwards we add some extra code to generate the DBus interface for every
+ interface in our module.
+
+ You might wonder, why the actual file names differ from the template names? We will explain
+ that after we had a look at the CMake integration:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/CMakeLists.txt.tpl
+ \skipuntil #}
+ \printto
+
+ For CMake it works a bit different, as ifcodegen provides two different mechanisms to
+ integrate. First all the needed information is saved as ifcodegen variables using a call to
+ \l qt6_set_ifcodegen_variable. All variables have to be prefixed with \c {${VAR_PREFIX}_}. The
+ \c VAR_PREFIX variable is set by CMake when importing the file and is used to import the
+ variables using \l qt_ifcodegen_import_variables.
+
+ The second way to integrate with CMake is \l qt_ifcodegen_extend_target. In that case the
+ \c ${CURRENT_TARGET} variable is set and the previous defined variables are used to call the
+ needed cmake functions, e.g:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus/CMakeLists.txt.tpl
+ \skipto target_sources
+ \printuntil )
+
+ \section3 Create a Generation YAML file
+
+ One important piece of information is still missing in order for ifcodegen to generate
+ something useful. It doesn't know how the generated files should be named or whether a file
+ should only be generated once or per module.
+
+ All this is defined within the \l {Generation YAML} file, which is named after the template and
+ is located within the same directory:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus.yaml
+ \printto
+
+ First the YAML defines that for every module in the IDL file some files should be generated.
+ Every document consists of the output file name (using \l {Jinja Template Syntax} {Jinja
+ syntax}), followed by the input template file name.
+
+ The same is done for all files which should be generated for every interface in the IDL file:
+
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/templates/backend_dbus.yaml
+ \skipto interface:
+ \printto
+
+ \section2 Use the new template in the build system
+
+ In order to use the new template, you need to integrate it into the build system:
+
+ \e CMake:
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/backend_dbus/CMakeLists.txt
+ \skipto qt_ifcodegen_extend_target
+ \printuntil )
+
+ \e QMake:
+ \quotefromfile interfaceframework/qface-tutorial/ch7-own-template/backend_dbus/backend_dbus.pro
+ \skipto IFCODEGEN_TEMPLATE
+ \printuntil IFCODEGEN_SOURCES
+
+ Instead of just using the template name, you need to provide the full path to the template, or
+ add the template folder to the \l {cmake-variable-QT_IFCODEGEN_TEMPLATE_SEARCH_PATH}{template search path}.
*/
diff --git a/examples/interfaceframework/qface-tutorial/qface-tutorial.pro b/examples/interfaceframework/qface-tutorial/qface-tutorial.pro
index fb64b783..2f6f3ef2 100644
--- a/examples/interfaceframework/qface-tutorial/qface-tutorial.pro
+++ b/examples/interfaceframework/qface-tutorial/qface-tutorial.pro
@@ -7,4 +7,7 @@ SUBDIRS += \
chapter4-simulation-behavior \
chapter5-ipc \
-!win32:qtHaveModule(dbus): SUBDIRS += chapter6-own-backend
+!win32:qtHaveModule(dbus): {
+ SUBDIRS += chapter6-own-backend \
+ ch7-own-template
+}
diff --git a/src/interfaceframework/doc/src/ifcodegen/filter-reference.qdoc b/src/interfaceframework/doc/src/ifcodegen/filter-reference.qdoc
index 5697d4a0..57d49154 100644
--- a/src/interfaceframework/doc/src/ifcodegen/filter-reference.qdoc
+++ b/src/interfaceframework/doc/src/ifcodegen/filter-reference.qdoc
@@ -24,6 +24,7 @@ Returns the C++ type for the passed symbol.
E.g. QString for a symbol of type "string".
\target parameter_type(symbol)
+\target parameter_type
\section2 parameter_type(symbol)
Returns the C++ type for the passed symbol. This returns the same type as return_type(), but already