diff options
author | Miguel Costa <[email protected]> | 2025-06-22 19:36:56 +0200 |
---|---|---|
committer | Miguel Costa <[email protected]> | 2025-06-25 14:00:48 +0000 |
commit | 059df9cd95a82ff6febec4dcc448962393bf2e88 (patch) | |
tree | 7be1802cd4a315a08c8e33bbef1dbd94ea014957 | |
parent | 251910aacf46f2c97c7d7f80adabd47d8d23b819 (diff) |
Includes:
* Implement QDotNetNativeInterface: specialization of QDotNetInterface
that wraps a pointer to native data.
* Implement IQVariant: QDotNetNativeInterface that wraps a QVariant.
* Implement IQModelIndex interface: QDotNetNativeInterface that wraps
a QModelIndex.
* Implement QDotNetAbstractListModel base class: allows extending
QAbstractListModel in C#.
* Add class QDotNetDelegate
* Remove old examples
Change-Id: I2eb21df29e1cf9379e14a64d5174eb934b5bf18a
Reviewed-by: Karsten Heimrich <[email protected]>
105 files changed, 2137 insertions, 5038 deletions
@@ -188,7 +188,7 @@ public: ... }; -class SensorData : public QObject, public QDotNetObject, public QDotNetObject::IEventHandler +class SensorData : public QObject, public QDotNetObject, public QDotNetEventHandler { Q_OBJECT Q_PROPERTY(double temperature READ temperature NOTIFY temperatureChanged) @@ -197,7 +197,7 @@ public: Q_DOTNET_OBJECT_INLINE(SensorData, "QtAzureIoT.Device.SensorData, SensorData"); SensorData() : QDotNetObject(getConstructor<SensorData>().invoke(nullptr)) { - subscribeEvent("PropertyChanged", this); + subscribe("PropertyChanged", this); } double temperature() const { @@ -331,14 +331,14 @@ objects in a Qt application. This includes receiving notifications of .NET event corresponding Qt signals. ```cpp -class Ping : public QObject, public QDotNetObject, public QDotNetObject::IEventHandler +class Ping : public QObject, public QDotNetObject, public QDotNetEventHandler { Q_OBJECT public: Q_DOTNET_OBJECT_INLINE(Ping, "System.Net.NetworkInformation.Ping, System"); Ping() : QDotNetObject(constructor<Ping>().invoke(nullptr)) { - subscribeEvent("PingCompleted", this); + subscribe("PingCompleted", this); } void sendAsync(const QString &hostNameOrAddress) { @@ -430,7 +430,7 @@ signals: ... }; ... -struct QChronometerPrivate : public QDotNetObject::IEventHandler +struct QChronometerPrivate : public QDotNetEventHandler { ... void handleEvent(const QString &eventName, QDotNetObject &sender, QDotNetObject &args) override diff --git a/examples/Chronometer/Chronometer/Chronometer.cs b/examples/Chronometer/Chronometer/Chronometer.cs deleted file mode 100644 index 51db4bb..0000000 --- a/examples/Chronometer/Chronometer/Chronometer.cs +++ /dev/null @@ -1,232 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -using System.ComponentModel; -using System.Diagnostics; - -namespace WatchModels -{ - public interface ILapRecorder - { - void Mark(int hours, int minutes, int seconds, int milliseconds); - } - - public class Chronometer : INotifyPropertyChanged - { - public double Hours - { - get => hours; - private set => SetProperty(ref hours, value, nameof(Hours)); - } - public double Minutes - { - get => minutes; - private set => SetProperty(ref minutes, value, nameof(Minutes)); - } - public double Seconds - { - get => seconds; - private set => SetProperty(ref seconds, value, nameof(Seconds)); - } - - public int Day - { - get => day; - private set => SetProperty(ref day, value, nameof(Day)); - } - - public double ElapsedHours - { - get => elapsedHours; - private set => SetProperty(ref elapsedHours, value, nameof(ElapsedHours)); - } - - public double ElapsedMinutes - { - get => elapsedMinutes; - private set => SetProperty(ref elapsedMinutes, value, nameof(ElapsedMinutes)); - } - - public double ElapsedSeconds - { - get => elapsedSeconds; - private set => SetProperty(ref elapsedSeconds, value, nameof(ElapsedSeconds)); - } - - public double ElapsedMilliseconds - { - get => elapsedMilliseconds; - private set => SetProperty(ref elapsedMilliseconds, value, nameof(ElapsedMilliseconds)); - } - - public bool Started - { - get => Stopwatch.IsRunning; - private set - { - if (value == Stopwatch.IsRunning) - return; - if (value) - Stopwatch.Start(); - else - Stopwatch.Stop(); - NotifyPropertyChanged(nameof(Started)); - } - } - - public bool AdjustDayMode - { - get => adjustDayMode; - set - { - if (value == adjustDayMode) - return; - adjustDayMode = value; - if (adjustDayMode) { - if (adjustTimeMode) { - adjustTimeMode = false; - NotifyPropertyChanged(nameof(AdjustTimeMode)); - } - Started = false; - Reset(); - Time.Stop(); - baseTime = baseTime.Add(Time.Elapsed); - Time.Reset(); - } else if (!adjustTimeMode) { - Time.Start(); - } - NotifyPropertyChanged(nameof(AdjustDayMode)); - } - } - - public bool AdjustTimeMode - { - get => adjustTimeMode; - set - { - if (value == adjustTimeMode) - return; - adjustTimeMode = value; - if (adjustTimeMode) { - if (adjustDayMode) { - adjustDayMode = false; - NotifyPropertyChanged(nameof(AdjustDayMode)); - } - Started = false; - Reset(); - Time.Stop(); - baseTime = baseTime.Add(Time.Elapsed); - Time.Reset(); - } else if (!adjustDayMode) { - Time.Start(); - } - NotifyPropertyChanged(nameof(AdjustTimeMode)); - } - } - - public ILapRecorder LapRecorder { get; set; } - - public Chronometer() - { - Mechanism = new Task(async () => await MechanismLoopAsync(), MechanismLoop.Token); - Mechanism.Start(); - baseTime = DateTime.Now; - Time.Start(); - } - - public void StartStop() - { - if (AdjustTimeMode || AdjustDayMode) - return; - Started = !Started; - } - - public void Reset() - { - ElapsedHours = ElapsedMinutes = ElapsedSeconds = ElapsedMilliseconds = 0; - if (!Stopwatch.IsRunning) { - Stopwatch.Reset(); - } else { - LapRecorder?.Mark( - Stopwatch.Elapsed.Hours, - Stopwatch.Elapsed.Minutes, - Stopwatch.Elapsed.Seconds, - Stopwatch.Elapsed.Milliseconds); - Stopwatch.Restart(); - } - } - - public void Adjust(int delta) - { - if (AdjustDayMode) - baseTime = baseTime.AddDays(delta); - else if (AdjustTimeMode) - baseTime = baseTime.AddSeconds(delta * 60); - Refresh(); - } - - public event PropertyChangedEventHandler PropertyChanged; - private void NotifyPropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - private void Refresh() - { - DateTime now = baseTime.Add(Time.Elapsed); - Day = now.Day; - - TimeSpan time = now.TimeOfDay; - Hours = time.TotalHours; - Minutes = time.TotalMinutes - - (time.Hours * 60); - Seconds = time.TotalSeconds - - (time.Hours * 3600) - (time.Minutes * 60); - - TimeSpan elapsed = Stopwatch.Elapsed; - ElapsedHours = elapsed.TotalHours; - ElapsedMinutes = elapsed.TotalMinutes - - (elapsed.Hours * 60); - ElapsedSeconds = elapsed.TotalSeconds - - (elapsed.Hours * 3600) - (elapsed.Minutes * 60); - ElapsedMilliseconds = elapsed.TotalMilliseconds - - (elapsed.Hours * 3600000) - (elapsed.Minutes * 60000) - (elapsed.Seconds * 1000); - } - - private async Task MechanismLoopAsync() - { - while (!MechanismLoop.IsCancellationRequested) { - await Task.Delay(5); - Refresh(); - } - } - - private void SetProperty<T>(ref T currentValue, T newValue, string name) - { - if (newValue.Equals(currentValue)) - return; - currentValue = newValue; - NotifyPropertyChanged(name); - } - - private double hours; - private double minutes; - private double seconds; - private int day; - private double elapsedHours; - private double elapsedMinutes; - private double elapsedSeconds; - private double elapsedMilliseconds; - private bool adjustDayMode; - private bool adjustTimeMode; - - private DateTime baseTime; - private Stopwatch Time { get; } = new(); - private Stopwatch Stopwatch { get; } = new(); - - private CancellationTokenSource MechanismLoop { get; } = new(); - private Task Mechanism { get; } - } -} diff --git a/examples/Chronometer/Chronometer/ChronometerModel.csproj b/examples/Chronometer/Chronometer/ChronometerModel.csproj deleted file mode 100644 index 141e38f..0000000 --- a/examples/Chronometer/Chronometer/ChronometerModel.csproj +++ /dev/null @@ -1,9 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>net6.0</TargetFramework> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>disable</Nullable> - </PropertyGroup> - -</Project> diff --git a/examples/Chronometer/QmlChronometer/OutDir.props b/examples/Chronometer/QmlChronometer/OutDir.props deleted file mode 100644 index c7a9463..0000000 --- a/examples/Chronometer/QmlChronometer/OutDir.props +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ImportGroup Label="PropertySheets" /> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup> - <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir> - <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir> - </PropertyGroup> - <ItemDefinitionGroup /> - <ItemGroup /> -</Project>
\ No newline at end of file diff --git a/examples/Chronometer/QmlChronometer/QChronometer/AdjustmentWheel.qml b/examples/Chronometer/QmlChronometer/QChronometer/AdjustmentWheel.qml deleted file mode 100644 index 0c5d904..0000000 --- a/examples/Chronometer/QmlChronometer/QChronometer/AdjustmentWheel.qml +++ /dev/null @@ -1,125 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -import QtQml -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Shapes - -////////////////////////////////////////////////////////////////////// -// Adjustment wheel -Tumbler { - id: adjustmentWheel - model: 100 - property int startX - property int startY - - x: startX + ((chrono.adjustDayMode || chrono.adjustTimeMode) ? 8 : 0) - Behavior on x { - SpringAnimation { spring: 3; damping: 0.5 } - } - y: startY; width: 25; height: 70 - - enabled: chrono.adjustDayMode || chrono.adjustTimeMode - onEnabledChanged: { - if (enabled) { - chrono.reset(); - if (!chrono.started) { - laps.reset(); - showLap = currentLap; - } - } - } - - property int lastIndex: 0 - property var lastTime: Date.UTC(0) - property double turnSpeed: 0.0 - onCurrentIndexChanged: { - if (currentIndex != lastIndex) { - var i1 = currentIndex; - var i0 = lastIndex; - if (Math.abs(i1 - i0) > 50) { - if (i1 < i0) - i1 += 100; - else - i0 += 100; - } - var deltaX = i1 - i0; - chrono.adjust(deltaX); - lastIndex = currentIndex; - - var deltaT = Date.now() - lastTime; - lastTime += deltaT; - turnSpeed = Math.abs((deltaX * 1000) / deltaT); - } - } - - MouseArea { - anchors.fill: parent - onWheel: function(wheel) { - turn(wheel.angleDelta.y > 0 ? 1 : -1); - } - } - - function turn(delta) { - if (enabled) { - adjustmentWheel.currentIndex = (100 + adjustmentWheel.currentIndex + (delta)) % 100; - } - } - - ////////////////////////////////////////////////////////////////////// - // Wheel surface - background: Rectangle { - anchors.fill: adjustmentWheel - color: gray6 - border.color: gray8 - border.width: 2 - radius: 2 - } - - ////////////////////////////////////////////////////////////////////// - // Notches - delegate: Component { - Item { - Rectangle { - x: 4; y: 0; width: Tumbler.tumbler.width - 8; height: 2 - color: gray3 - } - } - } - - ////////////////////////////////////////////////////////////////////// - // Wheel shadow - Rectangle { - anchors.centerIn: parent - width: parent.width; height: parent.height - gradient: Gradient { - GradientStop { position: 0.0; color: gray3 } - GradientStop { position: 0.3; color: "transparent" } - GradientStop { position: 0.5; color: "transparent" } - GradientStop { position: 0.7; color: "transparent" } - GradientStop { position: 1.0; color: gray3 } - } - } - - ////////////////////////////////////////////////////////////////////// - // Wheel axis - Rectangle { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: parent.startX - parent.x + 2 - width: 20; height: 20 - z: -1 - gradient: Gradient { - GradientStop { position: 0.0; color: gray3 } - GradientStop { position: 0.3; color: gray6 } - GradientStop { position: 0.5; color: gray6 } - GradientStop { position: 0.7; color: gray6 } - GradientStop { position: 1.0; color: gray3 } - } - border.color: "transparent" - } -} diff --git a/examples/Chronometer/QmlChronometer/QChronometer/InsetDial.qml b/examples/Chronometer/QmlChronometer/QChronometer/InsetDial.qml deleted file mode 100644 index 7c07106..0000000 --- a/examples/Chronometer/QmlChronometer/QChronometer/InsetDial.qml +++ /dev/null @@ -1,65 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -import QtQml -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Shapes - -////////////////////////////////////////////////////////////////// -// Inset dial -Item { - id: insetDial - property string handSource - property string pinSource - property int centerX - property int centerY - property double rotationAngle - ////////////////////////////////////////////////////////////////// - // Hand - Image { - source: insetDial.handSource - transform: Rotation { - origin.x: insetDial.centerX; origin.y: insetDial.centerY - Behavior on angle { - SpringAnimation { spring: 3; damping: 0.5; modulus: 360 } - } - angle: insetDial.rotationAngle - } - } - ////////////////////////////////////////////////////////////////// - // Highlight - Shape { - id: insetDialHighlight - anchors.fill: insetDial - opacity: 0.5 - property var centerX: insetDial.centerX - property var centerY: insetDial.centerY - property var color: - showLap == lastLap ? blue86 : showLap == bestLap ? green86 : "transparent" - ShapePath { - startX: insetDialHighlight.centerX; startY: insetDialHighlight.centerY - strokeColor: "transparent" - PathAngleArc { - centerX: insetDialHighlight.centerX; centerY: insetDialHighlight.centerY - radiusX: 55; radiusY: 55; startAngle: 0; sweepAngle: 360 - } - fillGradient: RadialGradient { - centerX: insetDialHighlight.centerX; centerY: insetDialHighlight.centerY - centerRadius: 55; - focalX: centerX; focalY: centerY - GradientStop { position: 0; color: "transparent" } - GradientStop { position: 0.6; color: "transparent" } - GradientStop { position: 1; color: insetDialHighlight.color } - } - } - } - ////////////////////////////////////////////////////////////////// - // Center pin - Image { - source: insetDial.pinSource - } -} diff --git a/examples/Chronometer/QmlChronometer/QChronometer/WatchButton.qml b/examples/Chronometer/QmlChronometer/QChronometer/WatchButton.qml deleted file mode 100644 index 6894c69..0000000 --- a/examples/Chronometer/QmlChronometer/QChronometer/WatchButton.qml +++ /dev/null @@ -1,31 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -import QtQml -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Shapes - -RoundButton { - id: watchButton - property string buttonText - property bool split - property string color - width: 70; height: 70; radius: 25 - palette.button: color - Text { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - font.bold: true - text: watchButton.buttonText - } - Rectangle { - visible: watchButton.split - color: "black" - width: 55; height: 1 - anchors.centerIn: parent - } -} diff --git a/examples/Chronometer/QmlChronometer/QmlChronometer.vcxproj b/examples/Chronometer/QmlChronometer/QmlChronometer.vcxproj deleted file mode 100644 index 8fb3def..0000000 --- a/examples/Chronometer/QmlChronometer/QmlChronometer.vcxproj +++ /dev/null @@ -1,158 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|x64"> - <Configuration>Debug</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|x64"> - <Configuration>Release</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{5A1E5424-CDB1-4776-A9CC-01B2CD57F516}</ProjectGuid> - <Keyword>QtVS_v304</Keyword> - <WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">10.0.19041.0</WindowsTargetPlatformVersion> - <WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">10.0.19041.0</WindowsTargetPlatformVersion> - <QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <PlatformToolset>v143</PlatformToolset> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <PlatformToolset>v143</PlatformToolset> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')"> - <Import Project="$(QtMsBuild)\qt_defaults.props" /> - </ImportGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings"> - <QtInstall>$(DefaultQtVersion)</QtInstall> - <QtModules>quick;core</QtModules> - <QtBuildConfig>debug</QtBuildConfig> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings"> - <QtInstall>$(DefaultQtVersion)</QtInstall> - <QtModules>quick;core</QtModules> - <QtBuildConfig>release</QtBuildConfig> - </PropertyGroup> - <Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')"> - <Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." /> - </Target> - <ImportGroup Label="ExtensionSettings" /> - <ImportGroup Label="Shared" /> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - <Import Project="OutDir.props" /> - <Import Project="$(QtMsBuild)\Qt.props" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - <Import Project="OutDir.props" /> - <Import Project="$(QtMsBuild)\Qt.props" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> - <IncludePath>..\..\..\include;$(IncludePath)</IncludePath> - <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir> - <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> - <IncludePath>..\..\..\include;$(IncludePath)</IncludePath> - <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir> - <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration"> - <ClCompile> - <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> - <MultiProcessorCompilation>true</MultiProcessorCompilation> - <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> - <Optimization>Disabled</Optimization> - <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> - </ClCompile> - <Link> - <SubSystem>Windows</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration"> - <ClCompile> - <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> - <MultiProcessorCompilation>true</MultiProcessorCompilation> - <DebugInformationFormat>None</DebugInformationFormat> - <Optimization>MaxSpeed</Optimization> - <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> - </ClCompile> - <Link> - <SubSystem>Windows</SubSystem> - <GenerateDebugInformation>false</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemGroup> - <ClCompile Include="main.cpp" /> - <ClCompile Include="qchronometer.cpp" /> - <ClCompile Include="qlaprecorder.cpp" /> - <None Include="QChronometer\AdjustmentWheel.qml" /> - <None Include="QChronometer\InsetDial.qml" /> - <None Include="QChronometer\WatchButton.qml" /> - <QtRcc Include="qml.qrc" /> - <CopyFileToFolders Include="..\..\..\bin\Qt.DotNet.Adapter.dll"> - <DeploymentContent>true</DeploymentContent> - <FileType>Document</FileType> - <TreatOutputAsContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</TreatOutputAsContent> - </CopyFileToFolders> - <None Include="main.qml" /> - </ItemGroup> - <ItemGroup> - <QtMoc Include="qchronometer.h" /> - </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\Chronometer\ChronometerModel.csproj"> - <Project>{d3d5ed0e-fd65-4f54-b2d8-d3a380f247a2}</Project> - </ProjectReference> - <ProjectReference - Condition="Exists('..\..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj')" - Include="..\..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj" /> - </ItemGroup> - <ItemGroup> - <ClInclude Include="..\..\..\include\qdotnetadapter.h" /> - <ClInclude Include="..\..\..\include\qdotnetarray.h" /> - <ClInclude Include="..\..\..\include\qdotnetcallback.h" /> - <ClInclude Include="..\..\..\include\qdotnetevent.h" /> - <ClInclude Include="..\..\..\include\qdotnetexception.h" /> - <ClInclude Include="..\..\..\include\qdotnetfunction.h" /> - <ClInclude Include="..\..\..\include\qdotnethost.h" /> - <ClInclude Include="..\..\..\include\qdotnethostfxr.h" /> - <ClInclude Include="..\..\..\include\qdotnetinterface.h" /> - <ClInclude Include="..\..\..\include\qdotnetmarshal.h" /> - <ClInclude Include="..\..\..\include\qdotnetobject.h" /> - <ClInclude Include="..\..\..\include\qdotnetparameter.h" /> - <ClInclude Include="..\..\..\include\qdotnetref.h" /> - <ClInclude Include="..\..\..\include\qdotnetsafemethod.h" /> - <ClInclude Include="..\..\..\include\qdotnettype.h" /> - <QtMoc Include="qlaprecorder.h" /> - </ItemGroup> - <ItemGroup> - <Image Include="content\center.png" /> - <Image Include="content\chrono_1_center.png" /> - <Image Include="content\chrono_1_hand.png" /> - <Image Include="content\chrono_2_center.png" /> - <Image Include="content\chrono_2_hand.png" /> - <Image Include="content\chrono_3_center.png" /> - <Image Include="content\chrono_3_needle.png" /> - <Image Include="content\hour_hand.png" /> - <Image Include="content\minute_hand.png" /> - <Image Include="content\second_hand.png" /> - <Image Include="content\watchface.png" /> - </ItemGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')"> - <Import Project="$(QtMsBuild)\qt.targets" /> - </ImportGroup> - <ImportGroup Label="ExtensionTargets"> - </ImportGroup> -</Project>
\ No newline at end of file diff --git a/examples/Chronometer/QmlChronometer/QmlChronometer.vcxproj.filters b/examples/Chronometer/QmlChronometer/QmlChronometer.vcxproj.filters deleted file mode 100644 index 30ca20d..0000000 --- a/examples/Chronometer/QmlChronometer/QmlChronometer.vcxproj.filters +++ /dev/null @@ -1,156 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup> - <Filter Include="Source Files"> - <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> - <Extensions>qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> - </Filter> - <Filter Include="Header Files"> - <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> - <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions> - </Filter> - <Filter Include="Resource Files"> - <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> - <Extensions>qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> - </Filter> - <Filter Include="Form Files"> - <UniqueIdentifier>{99349809-55BA-4b9d-BF79-8FDBB0286EB3}</UniqueIdentifier> - <Extensions>ui</Extensions> - </Filter> - <Filter Include="Translation Files"> - <UniqueIdentifier>{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}</UniqueIdentifier> - <Extensions>ts</Extensions> - </Filter> - <Filter Include="Header Files\qtdotnet"> - <UniqueIdentifier>{a17873af-389e-48bb-9b3d-0d6fcd150a7f}</UniqueIdentifier> - </Filter> - <Filter Include="Resource Files\png"> - <UniqueIdentifier>{1f892826-c7e1-49be-b4eb-0007d1308298}</UniqueIdentifier> - </Filter> - <Filter Include="Source Files\qml"> - <UniqueIdentifier>{9f5c583a-a421-4bcf-9f22-59ef59da1b60}</UniqueIdentifier> - <Extensions>.qml</Extensions> - </Filter> - </ItemGroup> - <ItemGroup> - <ClCompile Include="main.cpp"> - <Filter>Source Files</Filter> - </ClCompile> - <QtRcc Include="qml.qrc"> - <Filter>Resource Files</Filter> - </QtRcc> - <ClCompile Include="qchronometer.cpp"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="qlaprecorder.cpp"> - <Filter>Source Files</Filter> - </ClCompile> - </ItemGroup> - <ItemGroup> - <QtMoc Include="qchronometer.h"> - <Filter>Header Files</Filter> - </QtMoc> - <QtMoc Include="qlaprecorder.h"> - <Filter>Header Files</Filter> - </QtMoc> - </ItemGroup> - <ItemGroup> - <CopyFileToFolders Include="..\..\..\bin\Qt.DotNet.Adapter.dll" /> - </ItemGroup> - <ItemGroup> - <ClInclude Include="..\..\..\include\qdotnetadapter.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetcallback.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetevent.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetfunction.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnethost.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnethostfxr.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetinterface.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetmarshal.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetobject.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetparameter.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetref.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnettype.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetarray.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetexception.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - <ClInclude Include="..\..\..\include\qdotnetsafemethod.h"> - <Filter>Header Files\qtdotnet</Filter> - </ClInclude> - </ItemGroup> - <ItemGroup> - <Image Include="content\center.png"> - <Filter>Resource Files\png</Filter> - </Image> - <Image Include="content\chrono_1_center.png"> - <Filter>Resource Files\png</Filter> - </Image> - <Image Include="content\chrono_1_hand.png"> - <Filter>Resource Files\png</Filter> - </Image> - <Image Include="content\chrono_2_center.png"> - <Filter>Resource Files\png</Filter> - </Image> - <Image Include="content\chrono_2_hand.png"> - <Filter>Resource Files\png</Filter> - </Image> - <Image Include="content\chrono_3_center.png"> - <Filter>Resource Files\png</Filter> - </Image> - <Image Include="content\chrono_3_needle.png"> - <Filter>Resource Files\png</Filter> - </Image> - <Image Include="content\hour_hand.png"> - <Filter>Resource Files\png</Filter> - </Image> - <Image Include="content\minute_hand.png"> - <Filter>Resource Files\png</Filter> - </Image> - <Image Include="content\second_hand.png"> - <Filter>Resource Files\png</Filter> - </Image> - <Image Include="content\watchface.png"> - <Filter>Resource Files\png</Filter> - </Image> - </ItemGroup> - <ItemGroup> - <None Include="QChronometer\AdjustmentWheel.qml"> - <Filter>Source Files\qml</Filter> - </None> - <None Include="QChronometer\InsetDial.qml"> - <Filter>Source Files\qml</Filter> - </None> - <None Include="QChronometer\WatchButton.qml"> - <Filter>Source Files\qml</Filter> - </None> - <None Include="main.qml"> - <Filter>Source Files</Filter> - </None> - </ItemGroup> -</Project>
\ No newline at end of file diff --git a/examples/Chronometer/QmlChronometer/content/center.png b/examples/Chronometer/QmlChronometer/content/center.png Binary files differdeleted file mode 100644 index a6c610f..0000000 --- a/examples/Chronometer/QmlChronometer/content/center.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/content/chrono_1_center.png b/examples/Chronometer/QmlChronometer/content/chrono_1_center.png Binary files differdeleted file mode 100644 index eb57973..0000000 --- a/examples/Chronometer/QmlChronometer/content/chrono_1_center.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/content/chrono_1_hand.png b/examples/Chronometer/QmlChronometer/content/chrono_1_hand.png Binary files differdeleted file mode 100644 index d507b5e..0000000 --- a/examples/Chronometer/QmlChronometer/content/chrono_1_hand.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/content/chrono_2_center.png b/examples/Chronometer/QmlChronometer/content/chrono_2_center.png Binary files differdeleted file mode 100644 index 1552403..0000000 --- a/examples/Chronometer/QmlChronometer/content/chrono_2_center.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/content/chrono_2_hand.png b/examples/Chronometer/QmlChronometer/content/chrono_2_hand.png Binary files differdeleted file mode 100644 index 390b0e4..0000000 --- a/examples/Chronometer/QmlChronometer/content/chrono_2_hand.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/content/chrono_3_center.png b/examples/Chronometer/QmlChronometer/content/chrono_3_center.png Binary files differdeleted file mode 100644 index 6dbfb64..0000000 --- a/examples/Chronometer/QmlChronometer/content/chrono_3_center.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/content/chrono_3_needle.png b/examples/Chronometer/QmlChronometer/content/chrono_3_needle.png Binary files differdeleted file mode 100644 index 6499d0a..0000000 --- a/examples/Chronometer/QmlChronometer/content/chrono_3_needle.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/content/hour_hand.png b/examples/Chronometer/QmlChronometer/content/hour_hand.png Binary files differdeleted file mode 100644 index 2bd7ad7..0000000 --- a/examples/Chronometer/QmlChronometer/content/hour_hand.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/content/minute_hand.png b/examples/Chronometer/QmlChronometer/content/minute_hand.png Binary files differdeleted file mode 100644 index ee352fe..0000000 --- a/examples/Chronometer/QmlChronometer/content/minute_hand.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/content/second_hand.png b/examples/Chronometer/QmlChronometer/content/second_hand.png Binary files differdeleted file mode 100644 index 33f4504..0000000 --- a/examples/Chronometer/QmlChronometer/content/second_hand.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/content/watchface.png b/examples/Chronometer/QmlChronometer/content/watchface.png Binary files differdeleted file mode 100644 index e336d5e..0000000 --- a/examples/Chronometer/QmlChronometer/content/watchface.png +++ /dev/null diff --git a/examples/Chronometer/QmlChronometer/main.cpp b/examples/Chronometer/QmlChronometer/main.cpp deleted file mode 100644 index 94f8f45..0000000 --- a/examples/Chronometer/QmlChronometer/main.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#include "qchronometer.h" -#include "qlaprecorder.h" - -#include <QGuiApplication> -#include <QQmlApplicationEngine> -#include <QQmlContext> - -int main(int argc, char *argv[]) -{ - QGuiApplication app(argc, argv); - QQmlApplicationEngine engine; - - QLapRecorder lapRecorder; - engine.rootContext()->setContextProperty("laps", &lapRecorder); - - QChronometer chrono(lapRecorder); - engine.rootContext()->setContextProperty("chrono", &chrono); - - engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); - if (engine.rootObjects().isEmpty()) - return -1; - - return app.exec(); -} diff --git a/examples/Chronometer/QmlChronometer/main.qml b/examples/Chronometer/QmlChronometer/main.qml deleted file mode 100644 index 059c8ae..0000000 --- a/examples/Chronometer/QmlChronometer/main.qml +++ /dev/null @@ -1,453 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -import QtQml -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Shapes - -import "QChronometer" - -Window { - visible: true - property int windowWidth: 544 + (showHelp.checked ? 265 : 0) - Behavior on windowWidth { SmoothedAnimation { duration: 750 } } - width: windowWidth; minimumWidth: windowWidth; maximumWidth: windowWidth - height: 500; minimumHeight: height; maximumHeight: height - title: "QML Chronometer" - - ////////////////////////////////////////////////////////////////// - // Colors - readonly property var gray1: "#111111" - readonly property var gray3: "#333333" - readonly property var gray4: "#444444" - readonly property var gray6: "#666666" - readonly property var redF6: "#FF6666" - readonly property var green86: "#668866" - readonly property var blue86: "#666688" - readonly property var gray8: "#888888" - - ////////////////////////////////////////////////////////////////// - // Window background color - color: gray4 - - ////////////////////////////////////////////////////////////////// - // Stopwatch mode - property int showLap: currentLap - // 0: showing current lap, or stopwatch not running - readonly property int currentLap: 0 - // 1: showing last recorded lap - readonly property int lastLap: 1 - // 2: showing best recorded lap - readonly property int bestLap: 2 - - ////////////////////////////////////////////////////////////////// - // Watch - Image { - id: watch - source: "watchface.png" - } - - ////////////////////////////////////////////////////////////////// - // Rim - Rectangle { - color: "transparent" - border { color: gray8; width: 3 } - anchors.centerIn: watch - width: watch.width; height: watch.height; radius: watch.width / 2 - } - - ////////////////////////////////////////////////////////////////// - // Calendar - Text { - enabled: false - x: 345; y: 295; width: 32; height: 22 - transform: Rotation { origin.x: 0; origin.y: 0; angle: 30 } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font { bold: true; pointSize: 11 } - text: chrono.day - } - - ////////////////////////////////////////////////////////////////// - // Inset dial #1 (above-left of center; 30x minutes) - InsetDial { - id: insetDial1 - handSource: "/chrono_1_hand.png" - pinSource: "/chrono_1_center.png" - centerX: 176; centerY: 208 - rotationAngle: - (showLap == lastLap) ? ( - ///////////////////////////////////////////////////////////// - // Show minutes of previous lap - (laps.lastMinutes % 30) * 12 - ) : (showLap == bestLap) ? ( - ///////////////////////////////////////////////////////////// - // Show minutes of best lap - (laps.bestMinutes % 30) * 12 - ) : ( - ///////////////////////////////////////////////////////////// - // Show minutes of current lap - (chrono.elapsedMinutes % 30) * 12 - ) - } - - //////////////////////////////////////////////////////////////////////////////////// - // Inset chrono counter #2 (above-right of center; 10x 1/10 second or 10x hours) - InsetDial { - id: insetDial2 - handSource: "/chrono_2_hand.png" - pinSource: "/chrono_2_center.png" - centerX: 325; centerY: 208 - rotationAngle: - (showLap == lastLap) ? ( - ///////////////////////////////////////////////////////////// - // Show previous lap - (laps.lastHours == 0 && laps.lastMinutes < 30) ? ( - ///////////////////////////////////////////////////////////// - // 1/10 seconds - laps.lastMilliseconds * 360 / 1000 - ) : ( - ///////////////////////////////////////////////////////////// - // hours - ((laps.lastHours % 10) + (laps.lastMinutes / 60)) * 360 / 10 - ) - ) : (showLap == bestLap) ? ( - ///////////////////////////////////////////////////////////// - // Show best lap - (laps.bestHours == 0 && laps.bestMinutes < 30) ? ( - ///////////////////////////////////////////////////////////// - // 1/10 seconds - laps.bestMilliseconds * 360 / 1000 - ) : ( - ///////////////////////////////////////////////////////////// - // hours - ((laps.bestHours % 10) + (laps.bestMinutes / 60)) * 360 / 10 - ) - ) : ( - ///////////////////////////////////////////////////////////// - // Show current lap - (chrono.elapsedHours < 1 && chrono.elapsedMinutes < 30) ? ( - ///////////////////////////////////////////////////////////// - // 1/10 seconds - chrono.elapsedMilliseconds * 360 / 1000 - ) : ( - ///////////////////////////////////////////////////////////// - // hours - (chrono.elapsedHours % 10) * 360 / 10 - ) - ) - } - - ////////////////////////////////////////////////////////////////////// - // Inset chrono counter #3 (below center; 60x seconds) - InsetDial { - id: insetDial3 - handSource: "/chrono_3_needle.png" - pinSource: "/chrono_3_center.png" - centerX: 250; centerY: 336 - rotationAngle: 150 + ( - (showLap == lastLap) ? ( - ///////////////////////////////////////////////////////////// - // Show seconds of previous lap - laps.lastSeconds * 6 - ) : (showLap == bestLap) ? ( - ///////////////////////////////////////////////////////////// - // Show seconds of best lap - laps.bestSeconds * 6 - ) : ( - ///////////////////////////////////////////////////////////// - // Show seconds of current (wall-clock) time - chrono.seconds * 6 - )) - } - - ////////////////////////////////////////////////////////////////////// - // Hours hand for current (wall-clock) time - Image { - id: hoursHand; - source: "hour_hand.png" - transform: Rotation { - origin.x: 249; origin.y: 251 - angle: 110 + (chrono.hours % 12) * 30 - Behavior on angle { - enabled: adjustmentWheel.turnSpeed < 75 - SpringAnimation { spring: 3; damping: 0.5; modulus: 360 } - } - } - } - - ////////////////////////////////////////////////////////////////////// - // Minutes hand for current (wall-clock) time - Image { - id: minutesHand; - source: "minute_hand.png" - transform: Rotation { - origin.x: 249; origin.y: 251 - angle: -108 + chrono.minutes * 6 - Behavior on angle { - enabled: adjustmentWheel.turnSpeed < 75 - SpringAnimation { spring: 3; damping: 0.5; modulus: 360 } - } - } - } - Image { - source: "center.png" - } - - ////////////////////////////////////////////////////////////////////// - // Stopwatch seconds hand - Image { - id: secondsHand; - source: "second_hand.png" - transform: Rotation { - origin.x: 250; origin.y: 250 - angle: chrono.elapsedSeconds * 6 - Behavior on angle { - SpringAnimation { spring: 3; damping: 0.5; modulus: 360 } - } - } - } - - ////////////////////////////////////////////////////////////////////// - // Adjustment wheel - AdjustmentWheel { - id: adjustmentWheel - startX: 498; startY: 215 - } - - ////////////////////////////////////////////////////////////////////// - // Adjust date - Switch { - id: adjustDay - x: 500; y: 290; padding: 0; spacing: -35 - palette.button: gray8; font { bold: true; pointSize: 9 } - text: "Date" - checked: chrono.adjustDayMode - onToggled: chrono.adjustDayMode = (position == 1) - } - - ////////////////////////////////////////////////////////////////////// - // Adjust time - Switch { - id: adjustTime - x: 500; y: 310; padding: 0; spacing: -35 - palette.button: gray8; font { bold: true; pointSize: 9 } - text: "Time" - checked: chrono.adjustTimeMode - onToggled: chrono.adjustTimeMode = (position == 1) - } - - ////////////////////////////////////////////////////////////////////// - // Stopwatch start/stop button - WatchButton { - id: buttonStartStop - x: 425; y: 5 - buttonText: "Start\n\nStop"; split: true - color: chrono.started ? redF6 : !enabled ? gray3 : gray6 - enabled: !chrono.adjustDayMode && !chrono.adjustTimeMode - onClicked: chrono.startStop() - } - - ////////////////////////////////////////////////////////////////////// - // Stopwatch lap/reset button - WatchButton { - id: buttonLapReset - x: 425; y: 425 - buttonText: "Lap\n\nReset"; split: true - color: !enabled ? gray3 : gray6 - enabled: chrono.started - || chrono.elapsedHours > 0 - || chrono.elapsedMinutes > 0 - || chrono.elapsedSeconds > 0 - || chrono.elapsedMilliseconds > 0 - || laps.lapCount > 0 - onClicked: { - chrono.reset(); - if (!chrono.started) { - laps.reset(); - showLap = currentLap; - } - } - } - - ////////////////////////////////////////////////////////////////////// - // Stopwatch last lap button - WatchButton { - id: buttonLastLap - x: 5; y: 425 - buttonText: "Last\nLap"; split: false - color: (showLap == lastLap) ? blue86 : !enabled ? gray3 : gray6 - enabled: laps.lapCount > 0 - onClicked: { - showLapTimer.stop(); - if (laps.lapCount > 0) { - showLap = (showLap != lastLap) ? lastLap : currentLap; - } - } - } - - ////////////////////////////////////////////////////////////////////// - // Stopwatch best lap button - WatchButton { - id: buttonBestLap - x: 5; y: 5 - buttonText: "Best\nLap"; split: false - color: (showLap == bestLap) ? green86 : !enabled ? gray3 : gray6 - enabled: laps.lapCount > 1 - onClicked: { - showLapTimer.stop(); - if (laps.lapCount > 1) { - showLap = (showLap != bestLap) ? bestLap : currentLap; - } - } - } - - ////////////////////////////////////////////////////////////////////// - // Timer to show last/best lap for 5 secs. after mark - Timer { - id: showLapTimer - interval: 5000 - running: false - repeat: false - onTriggered: showLap = currentLap - } - - ////////////////////////////////////////////////////////////////////// - // Lap events - Connections { - target: laps - - ////////////////////////////////////////////////////////////////////// - // Lap counter changed: new lap recorded, or lap counter reset - function onLapCountChanged() { - if (laps.lapCount > 0) { - showLap = lastLap; - showLapTimer.restart() - } - } - - ////////////////////////////////////////////////////////////////////// - // New best lap recorded - function onNewBestLap() { - if (laps.lapCount > 1) { - showLap = bestLap; - showLapTimer.restart() - } - } - } - - ////////////////////////////////////////////////////////////////////// - // Keyboard events - Shortcut { - sequence: " " - onActivated: buttonStartStop.clicked() - } - Shortcut { - sequences: [ "Return", "Enter" ] - onActivated: { - if (chrono.started) - buttonLapReset.clicked(); - } - } - Shortcut { - sequence: "Escape" - onActivated: { - if (chrono.adjustDayMode || chrono.adjustTimeMode) - chrono.adjustDayMode = chrono.adjustTimeMode = false; - else if (!chrono.started) - buttonLapReset.clicked() - else - showLap = currentLap; - } - } - Shortcut { - sequence: "Tab" - onActivated: buttonLastLap.clicked() - } - Shortcut { - sequence: "Shift+Tab" - onActivated: buttonBestLap.clicked() - } - Shortcut { - sequence: "Ctrl+D" - onActivated: { - adjustDay.toggle(); - adjustDay.onToggled(); - } - } - Shortcut { - sequence: "Ctrl+T" - onActivated: { - adjustTime.toggle(); - adjustTime.onToggled(); - } - } - Shortcut { - sequence: "Up" - onActivated: adjustmentWheel.turn(1) - } - Shortcut { - sequence: "Down" - onActivated: adjustmentWheel.turn(-1) - } - Shortcut { - sequence: "F1" - onActivated: showHelp.toggle() - } - - ////////////////////////////////////////////////////////////////////// - // Usage instructions - RoundButton { - id: showHelp - checkable: true - x: 524; y: 0; width: 20; height: 40 - palette.button: gray6 - radius: 0 - contentItem: Text { - font.bold: true - font.pointSize: 11 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: parent.checked ? "<" : "?" - } - } - Rectangle { - x: 544; y:0 - width: 265 - height: 500 - color: gray6 - border.width: 0 - Text { - anchors.fill: parent - anchors.topMargin: 10 - anchors.leftMargin: 5 - anchors.rightMargin: 5 - textFormat: Text.MarkdownText - wrapMode: Text.WordWrap - color: gray1 - text: "### Usage instructions - -The **hours and minutes hands** show the current (wall-clock) time. The **seconds hand** shows the -stopwatch elapsed seconds. **Inset dial #1** (above-left of center) shows elapsed minutes of -current/last/best lap. **Inset dial #2** (above-right of center) shows a 1/10th-second counter of -current/last/best lap. **Inset dial #3** (below center) shows seconds of current time, or elapsed -seconds of last/best lap. - -Press **Start|Stop** (shortcut: **[Space]**) to begin timing. While the stopwatch is running, press -**Lap|Reset** (shortcut: **[Enter]**) to record a lap. The stopwatch can memorize the last and the -best lap. Press **Last** **Lap** (shortcut: **[Tab]**) or **Best** **Lap** (shortcut: -**[Shift+Tab]**) to view the recorded time of the last or best lap. Press **Start|Stop** (shortcut: -**[Space]**) again to stop timing. Press **Lap|Reset** (shortcut: **[Esc]**) to reset stopwatch -counters and clear lap memory. - -Press the **Date** switch (shortcut: **[Ctrl+D]**) or **Time** switch (shortcut: **[Ctrl+T]**) to -enter adjustment mode. Turn the **adjustment wheel** (shortcut: **mouse wheel** or **[Up]** / -**[Down]**) to set the desired date or time. Press the active adjustment switch (or **[Esc]**) to -leave adjustment mode. Note: entering adjustment mode resets the stopwatch." - } - } -} diff --git a/examples/Chronometer/QmlChronometer/qchronometer.cpp b/examples/Chronometer/QmlChronometer/qchronometer.cpp deleted file mode 100644 index 16951a0..0000000 --- a/examples/Chronometer/QmlChronometer/qchronometer.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#include "qchronometer.h" -#include "qlaprecorder.h" - -#include <qdotnetevent.h> - -struct QChronometerPrivate : QDotNetObject::IEventHandler -{ - QChronometerPrivate(QChronometer *q) - :q(q) - {} - - void handleEvent(const QString &eventName, QDotNetObject &sender, QDotNetObject &args) override - { - if (eventName != "PropertyChanged") - return; - - if (args.type().fullName() != QDotNetPropertyEvent::FullyQualifiedTypeName) - return; - - const auto propertyChangedEvent = args.cast<QDotNetPropertyEvent>(); - if (propertyChangedEvent.propertyName() == "Hours") - emit q->hoursChanged(); - else if (propertyChangedEvent.propertyName() == "Minutes") - emit q->minutesChanged(); - else if (propertyChangedEvent.propertyName() == "Seconds") - emit q->secondsChanged(); - else if (propertyChangedEvent.propertyName() == "Day") - emit q->dayChanged(); - else if (propertyChangedEvent.propertyName() == "Started") - emit q->startedChanged(); - else if (propertyChangedEvent.propertyName() == "ElapsedHours") - emit q->elapsedHoursChanged(); - else if (propertyChangedEvent.propertyName() == "ElapsedMinutes") - emit q->elapsedMinutesChanged(); - else if (propertyChangedEvent.propertyName() == "ElapsedSeconds") - emit q->elapsedSecondsChanged(); - else if (propertyChangedEvent.propertyName() == "ElapsedMilliseconds") - emit q->elapsedMillisecondsChanged(); - else if (propertyChangedEvent.propertyName() == "AdjustDayMode") - emit q->adjustDayModeChanged(); - else if (propertyChangedEvent.propertyName() == "AdjustTimeMode") - emit q->adjustTimeModeChanged(); - } - - QChronometer *q = nullptr; - - QDotNetFunction<double> hours= nullptr; - QDotNetFunction<double> minutes = nullptr; - QDotNetFunction<double> seconds = nullptr; - QDotNetFunction<int> day = nullptr; - QDotNetFunction<double> elapsedHours = nullptr; - QDotNetFunction<double> elapsedMinutes = nullptr; - QDotNetFunction<double> elapsedSeconds = nullptr; - QDotNetFunction<double> elapsedMilliseconds = nullptr; - QDotNetFunction<bool> started, adjustDayMode, adjustTimeMode = nullptr; - QDotNetFunction<void> startStop, reset, breakWatch = nullptr; - QDotNetFunction<void, bool> setAdjustDayMode, setAdjustTimeMode = nullptr; - QDotNetFunction<void, int> adjust = nullptr; -}; - - -Q_DOTNET_OBJECT_IMPL(QChronometer, Q_DOTNET_OBJECT_INIT(d(new QChronometerPrivate(this)))); - - -QChronometer::QChronometer(const ILapRecorder &lapRecorder) - : d(new QChronometerPrivate(this)) -{ - *this = constructor<QChronometer>().invoke(nullptr); - method<void, ILapRecorder>("set_LapRecorder").invoke(*this, lapRecorder); - subscribeEvent("PropertyChanged", d); -} - -QChronometer::~QChronometer() -{ - if (isValid()) - unsubscribeEvent("PropertyChanged", d); - delete d; -} - -double QChronometer::hours() const -{ - return method("get_Hours", d->hours).invoke(*this); -} - -double QChronometer::minutes() const -{ - return method("get_Minutes", d->minutes).invoke(*this); -} - -double QChronometer::seconds() const -{ - return method("get_Seconds", d->seconds).invoke(*this); -} - -int QChronometer::day() const -{ - return method("get_Day", d->day).invoke(*this); -} - -bool QChronometer::started() const -{ - return method("get_Started", d->started).invoke(*this); -} - -double QChronometer::elapsedHours() const -{ - return method("get_ElapsedHours", d->elapsedHours).invoke(*this); -} - -double QChronometer::elapsedMinutes() const -{ - return method("get_ElapsedMinutes", d->elapsedMinutes).invoke(*this); -} - -double QChronometer::elapsedSeconds() const -{ - return method("get_ElapsedSeconds", d->elapsedSeconds).invoke(*this); -} - -double QChronometer::elapsedMilliseconds() const -{ - return method("get_ElapsedMilliseconds", d->elapsedMilliseconds).invoke(*this); -} - -bool QChronometer::adjustDayMode() const -{ - return method("get_AdjustDayMode", d->adjustDayMode).invoke(*this); -} - -void QChronometer::setAdjustDayMode(bool value) -{ - method("set_AdjustDayMode", d->setAdjustDayMode).invoke(*this, value); -} - -bool QChronometer::adjustTimeMode() const -{ - return method("get_AdjustTimeMode", d->adjustTimeMode).invoke(*this); -} - -void QChronometer::setAdjustTimeMode(bool value) -{ - method("set_AdjustTimeMode", d->setAdjustTimeMode).invoke(*this, value); -} - -void QChronometer::adjust(int delta) -{ - method("Adjust", d->adjust).invoke(*this, delta); -} - -void QChronometer::startStop() -{ - method("StartStop", d->startStop).invoke(*this); -} - -void QChronometer::reset() -{ - method("Reset", d->reset).invoke(*this); -} diff --git a/examples/Chronometer/QmlChronometer/qchronometer.h b/examples/Chronometer/QmlChronometer/qchronometer.h deleted file mode 100644 index b90c0e5..0000000 --- a/examples/Chronometer/QmlChronometer/qchronometer.h +++ /dev/null @@ -1,84 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#pragma once - -#include <qdotnetinterface.h> -#include <qdotnetobject.h> - -#ifdef __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wconversion" -#endif -#include <QObject> -#include <QString> -#ifdef __GNUC__ -# pragma GCC diagnostic pop -#endif - -struct ILapRecorder; -struct QChronometerPrivate; - -class QChronometer : public QObject, public QDotNetObject -{ - Q_OBJECT - Q_PROPERTY(double hours READ hours NOTIFY hoursChanged) - Q_PROPERTY(double minutes READ minutes NOTIFY minutesChanged) - Q_PROPERTY(double seconds READ seconds NOTIFY secondsChanged) - Q_PROPERTY(int day READ day NOTIFY dayChanged) - Q_PROPERTY(bool started READ started NOTIFY startedChanged) - Q_PROPERTY(double elapsedHours READ elapsedHours NOTIFY elapsedHoursChanged) - Q_PROPERTY(double elapsedMinutes READ elapsedMinutes NOTIFY elapsedMinutesChanged) - Q_PROPERTY(double elapsedSeconds READ elapsedSeconds NOTIFY elapsedSecondsChanged) - Q_PROPERTY(double elapsedMilliseconds - READ elapsedMilliseconds NOTIFY elapsedMillisecondsChanged) - Q_PROPERTY(bool adjustDayMode - READ adjustDayMode WRITE setAdjustDayMode NOTIFY adjustDayModeChanged) - Q_PROPERTY(bool adjustTimeMode - READ adjustTimeMode WRITE setAdjustTimeMode NOTIFY adjustTimeModeChanged) - -public: - Q_DOTNET_OBJECT(QChronometer, "WatchModels.Chronometer, ChronometerModel"); - - QChronometer(const ILapRecorder &lapRecorder); - ~QChronometer() override; - - double hours() const; - double minutes() const; - double seconds() const; - int day() const; - bool started() const; - double elapsedHours() const; - double elapsedMinutes() const; - double elapsedSeconds() const; - double elapsedMilliseconds() const; - bool adjustDayMode() const; - bool adjustTimeMode() const; - -public slots: - void startStop(); - void reset(); - void setAdjustDayMode(bool value); - void setAdjustTimeMode(bool value); - void adjust(int delta); - - -signals: - void hoursChanged(); - void minutesChanged(); - void secondsChanged(); - void dayChanged(); - void startedChanged(); - void elapsedHoursChanged(); - void elapsedMinutesChanged(); - void elapsedSecondsChanged(); - void elapsedMillisecondsChanged(); - void adjustDayModeChanged(); - void adjustTimeModeChanged(); - void lap(int hours, int minutes, int seconds, int milliseconds); - -private: - QChronometerPrivate *d = nullptr; -}; diff --git a/examples/Chronometer/QmlChronometer/qlaprecorder.cpp b/examples/Chronometer/QmlChronometer/qlaprecorder.cpp deleted file mode 100644 index 20956c0..0000000 --- a/examples/Chronometer/QmlChronometer/qlaprecorder.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#include "qlaprecorder.h" - -struct QLapRecorderPrivate -{ - QLapRecorderPrivate() = default; - - int lapCount = 0; - int lastHours = 0; - int lastMinutes = 0; - int lastSeconds = 0; - int lastMilliseconds = 0; - int bestHours = 0; - int bestMinutes = 0; - int bestSeconds = 0; - int bestMilliseconds = 0; -}; - - -ILapRecorder::ILapRecorder() : QDotNetInterface(FullyQualifiedTypeName) -{ - setCallback<void, int, int, int, int>( - "Mark", [this](int hours, int minutes, int seconds, int milliseconds) - { - mark(hours, minutes, seconds, milliseconds); - }); -} - - -QLapRecorder::QLapRecorder(QObject *parent) - : QObject(parent), d(new QLapRecorderPrivate()) -{} - -QLapRecorder::~QLapRecorder() -{ - delete d; -} - -int QLapRecorder::lapCount() const { return d->lapCount; } -int QLapRecorder::lastHours() const { return d->lastHours; } -int QLapRecorder::lastMinutes() const { return d->lastMinutes; } -int QLapRecorder::lastSeconds() const { return d->lastSeconds; } -int QLapRecorder::lastMilliseconds() const { return d->lastMilliseconds; } -int QLapRecorder::bestHours() const { return d->bestHours; } -int QLapRecorder::bestMinutes() const { return d->bestMinutes; } -int QLapRecorder::bestSeconds() const { return d->bestSeconds; } -int QLapRecorder::bestMilliseconds() const { return d->bestMilliseconds; } - -void QLapRecorder::mark(int hours, int minutes, int seconds, int milliseconds) -{ - d->lastHours = hours; - emit lastHoursChanged(); - - d->lastMinutes = minutes; - emit lastMinutesChanged(); - - d->lastSeconds = seconds; - emit lastSecondsChanged(); - - d->lastMilliseconds = milliseconds; - emit lastMillisecondsChanged(); - - d->lapCount++; - emit lapCountChanged(); - - if (d->lapCount > 1 - && (d->lastHours > d->bestHours - || (d->lastHours == d->bestHours - && d->lastMinutes > d->bestMinutes) - || (d->lastHours == d->bestHours - && d->lastMinutes == d->bestMinutes - && d->lastSeconds > d->bestSeconds) - || (d->lastHours == d->bestHours - && d->lastMinutes == d->bestMinutes - && d->lastSeconds == d->bestSeconds - && d->lastMilliseconds > d->bestMilliseconds))) { - return; - } - - d->bestHours = hours; - emit bestHoursChanged(); - - d->bestMinutes = minutes; - emit bestMinutesChanged(); - - d->bestSeconds = seconds; - emit bestSecondsChanged(); - - d->bestMilliseconds = milliseconds; - emit bestMillisecondsChanged(); - - if (d->lapCount > 1) - emit newBestLap(); -} - -void QLapRecorder::reset() -{ - d->lastHours = 0; - emit lastHoursChanged(); - - d->lastMinutes = 0; - emit lastMinutesChanged(); - - d->lastSeconds = 0; - emit lastSecondsChanged(); - - d->lastMilliseconds = 0; - emit lastMillisecondsChanged(); - - d->lapCount = 0; - emit lapCountChanged(); - - d->bestHours = 0; - emit bestHoursChanged(); - - d->bestMinutes = 0; - emit bestMinutesChanged(); - - d->bestSeconds = 0; - emit bestSecondsChanged(); - - d->bestMilliseconds = 0; - emit bestMillisecondsChanged(); -} diff --git a/examples/Chronometer/QmlChronometer/qlaprecorder.h b/examples/Chronometer/QmlChronometer/qlaprecorder.h deleted file mode 100644 index 0456288..0000000 --- a/examples/Chronometer/QmlChronometer/qlaprecorder.h +++ /dev/null @@ -1,69 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#pragma once - -#include "qchronometer.h" - -#include <QObject> - -struct QLapRecorderPrivate; - -struct ILapRecorder : QDotNetInterface -{ - static inline const QString& FullyQualifiedTypeName = - QStringLiteral("WatchModels.ILapRecorder, ChronometerModel"); - ILapRecorder(); - - virtual void mark(int hours, int minutes, int seconds, int milliseconds) = 0; - virtual void reset() = 0; -}; - -class QLapRecorder : public QObject, public ILapRecorder -{ - Q_OBJECT - Q_PROPERTY(int lastHours READ lastHours NOTIFY lastHoursChanged) - Q_PROPERTY(int lastMinutes READ lastMinutes NOTIFY lastMinutesChanged) - Q_PROPERTY(int lastSeconds READ lastSeconds NOTIFY lastSecondsChanged) - Q_PROPERTY(int lastMilliseconds READ lastMilliseconds NOTIFY lastMillisecondsChanged) - Q_PROPERTY(int bestHours READ bestHours NOTIFY bestHoursChanged) - Q_PROPERTY(int bestMinutes READ bestMinutes NOTIFY bestMinutesChanged) - Q_PROPERTY(int bestSeconds READ bestSeconds NOTIFY bestSecondsChanged) - Q_PROPERTY(int bestMilliseconds READ bestMilliseconds NOTIFY bestMillisecondsChanged) - Q_PROPERTY(int lapCount READ lapCount NOTIFY lapCountChanged) - -public: - QLapRecorder(QObject *parent = nullptr); - ~QLapRecorder() override; - - [[nodiscard]] int lapCount() const; - [[nodiscard]] int lastHours() const; - [[nodiscard]] int lastMinutes() const; - [[nodiscard]] int lastSeconds() const; - [[nodiscard]] int lastMilliseconds() const; - [[nodiscard]] int bestHours() const; - [[nodiscard]] int bestMinutes() const; - [[nodiscard]] int bestSeconds() const; - [[nodiscard]] int bestMilliseconds() const; - -public slots: - void mark(int hours, int minutes, int seconds, int milliseconds) override; - void reset() override; - -signals: - void lapCountChanged(); - void lastHoursChanged(); - void lastMinutesChanged(); - void lastSecondsChanged(); - void lastMillisecondsChanged(); - void bestHoursChanged(); - void bestMinutesChanged(); - void bestSecondsChanged(); - void bestMillisecondsChanged(); - void newBestLap(); - -private: - QLapRecorderPrivate *d = nullptr; -}; diff --git a/examples/Chronometer/QmlChronometer/qml.qrc b/examples/Chronometer/QmlChronometer/qml.qrc deleted file mode 100644 index 15d835d..0000000 --- a/examples/Chronometer/QmlChronometer/qml.qrc +++ /dev/null @@ -1,19 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>main.qml</file> - <file alias="watchface.png">content/watchface.png</file> - <file alias="chrono_1_hand.png">content/chrono_1_hand.png</file> - <file alias="chrono_2_hand.png">content/chrono_2_hand.png</file> - <file alias="chrono_3_needle.png">content/chrono_3_needle.png</file> - <file alias="hour_hand.png">content/hour_hand.png</file> - <file alias="minute_hand.png">content/minute_hand.png</file> - <file alias="second_hand.png">content/second_hand.png</file> - <file alias="center.png">content/center.png</file> - <file alias="chrono_1_center.png">content/chrono_1_center.png</file> - <file alias="chrono_2_center.png">content/chrono_2_center.png</file> - <file alias="chrono_3_center.png">content/chrono_3_center.png</file> - <file>QChronometer/AdjustmentWheel.qml</file> - <file>QChronometer/InsetDial.qml</file> - <file>QChronometer/WatchButton.qml</file> - </qresource> -</RCC> diff --git a/examples/EmbeddedWindow/QmlApp/QmlApp.vcxproj b/examples/EmbeddedWindow/QmlApp/QmlApp.vcxproj deleted file mode 100644 index 2544420..0000000 --- a/examples/EmbeddedWindow/QmlApp/QmlApp.vcxproj +++ /dev/null @@ -1,111 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|x64"> - <Configuration>Debug</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|x64"> - <Configuration>Release</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{8C7C4962-6AAA-4A90-927A-BC88C782CAAA}</ProjectGuid> - <Keyword>QtVS_v304</Keyword> - <WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">10.0.19041.0</WindowsTargetPlatformVersion> - <WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">10.0.19041.0</WindowsTargetPlatformVersion> - <QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <PlatformToolset>v143</PlatformToolset> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <PlatformToolset>v143</PlatformToolset> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')"> - <Import Project="$(QtMsBuild)\qt_defaults.props" /> - </ImportGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings"> - <QtInstall>$(DefaultQtVersion)</QtInstall> - <QtModules>quick;core</QtModules> - <QtBuildConfig>debug</QtBuildConfig> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings"> - <QtInstall>$(DefaultQtVersion)</QtInstall> - <QtModules>quick;core</QtModules> - <QtBuildConfig>release</QtBuildConfig> - </PropertyGroup> - <Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')"> - <Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." /> - </Target> - <ImportGroup Label="ExtensionSettings" /> - <ImportGroup Label="Shared" /> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - <Import Project="$(QtMsBuild)\Qt.props" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - <Import Project="$(QtMsBuild)\Qt.props" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> - <IncludePath>../../../include/;$(IncludePath)</IncludePath> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> - <IncludePath>../../../include/;$(IncludePath)</IncludePath> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration"> - <ClCompile> - <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> - <MultiProcessorCompilation>true</MultiProcessorCompilation> - <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> - <Optimization>Disabled</Optimization> - </ClCompile> - <Link> - <SubSystem>Windows</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration"> - <ClCompile> - <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> - <MultiProcessorCompilation>true</MultiProcessorCompilation> - <DebugInformationFormat>None</DebugInformationFormat> - <Optimization>MaxSpeed</Optimization> - </ClCompile> - <Link> - <SubSystem>Windows</SubSystem> - <GenerateDebugInformation>false</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemGroup> - <QtMoc Include="mainwindow.h" /> - <QtMoc Include="embeddedwindow.h" /> - <ClCompile Include="main.cpp" /> - <QtRcc Include="qml.qrc" /> - <ClCompile Include="mainwindow.cpp" /> - <ClCompile Include="embeddedwindow.cpp" /> - <None Include="main.qml" /> - </ItemGroup> - <ItemGroup> - <Reference Include="Qt.DotNet.Adapter"> - <HintPath>..\..\..\bin\Qt.DotNet.Adapter.dll</HintPath> - </Reference> - <ProjectReference Include="..\WpfApp\WpfApp.csproj" /> - </ItemGroup> - <ItemGroup> - <Image Include="qt_logo.png" /> - </ItemGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')"> - <Import Project="$(QtMsBuild)\qt.targets" /> - </ImportGroup> - <ImportGroup Label="ExtensionTargets"> - </ImportGroup> -</Project>
\ No newline at end of file diff --git a/examples/EmbeddedWindow/QmlApp/QmlApp.vcxproj.filters b/examples/EmbeddedWindow/QmlApp/QmlApp.vcxproj.filters deleted file mode 100644 index d958351..0000000 --- a/examples/EmbeddedWindow/QmlApp/QmlApp.vcxproj.filters +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup> - <Filter Include="Source Files"> - <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> - <Extensions>qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> - </Filter> - <Filter Include="Header Files"> - <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> - <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions> - </Filter> - <Filter Include="Resource Files"> - <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> - <Extensions>qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> - </Filter> - <Filter Include="Form Files"> - <UniqueIdentifier>{99349809-55BA-4b9d-BF79-8FDBB0286EB3}</UniqueIdentifier> - <Extensions>ui</Extensions> - </Filter> - <Filter Include="Translation Files"> - <UniqueIdentifier>{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}</UniqueIdentifier> - <Extensions>ts</Extensions> - </Filter> - </ItemGroup> - <ItemGroup> - <QtRcc Include="qml.qrc"> - <Filter>Resource Files</Filter> - </QtRcc> - <None Include="main.qml"> - <Filter>Source Files</Filter> - </None> - <ClCompile Include="mainwindow.cpp"> - <Filter>Source Files</Filter> - </ClCompile> - </ItemGroup> - <ItemGroup> - <QtMoc Include="mainwindow.h"> - <Filter>Header Files</Filter> - </QtMoc> - <QtMoc Include="embeddedwindow.h"> - <Filter>Header Files</Filter> - </QtMoc> - </ItemGroup> - <ItemGroup> - <ClCompile Include="embeddedwindow.cpp"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="main.cpp"> - <Filter>Source Files</Filter> - </ClCompile> - </ItemGroup> - <ItemGroup> - <Image Include="qt_logo.png"> - <Filter>Resource Files</Filter> - </Image> - </ItemGroup> -</Project>
\ No newline at end of file diff --git a/examples/EmbeddedWindow/QmlApp/embeddedwindow.cpp b/examples/EmbeddedWindow/QmlApp/embeddedwindow.cpp deleted file mode 100644 index 3b7b17d..0000000 --- a/examples/EmbeddedWindow/QmlApp/embeddedwindow.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#include "embeddedwindow.h" - -#include <QQmlContext> -#include <QQmlEngine> -#include <QQuickView> -#include <QWindow> - -#include "mainwindow.h" - -EmbeddedWindow::EmbeddedWindow(QQmlEngine *qmlEngine, MainWindow *mainWindow) - : qmlEngine(qmlEngine), mainWindow(mainWindow) -{ - connect(mainWindow, &MainWindow::contentRendered, this, &EmbeddedWindow::show); - connect(mainWindow, &MainWindow::closed, this, &EmbeddedWindow::close); -} - -EmbeddedWindow::~EmbeddedWindow() -{ - delete quickView; -} - -void EmbeddedWindow::show() -{ - embeddedWindow = QWindow::fromWinId((WId)mainWindow->hostHandle()); - quickView = new QQuickView(qmlEngine, embeddedWindow); - qmlEngine->rootContext()->setContextProperty("window", quickView); - quickView->setSource(QUrl(QStringLiteral("qrc:/main.qml"))); - quickView->show(); -} - -void EmbeddedWindow::close() -{ - embeddedWindow->close(); -} diff --git a/examples/EmbeddedWindow/QmlApp/embeddedwindow.h b/examples/EmbeddedWindow/QmlApp/embeddedwindow.h deleted file mode 100644 index c84a370..0000000 --- a/examples/EmbeddedWindow/QmlApp/embeddedwindow.h +++ /dev/null @@ -1,31 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#pragma once - -#include <QObject> - -class QQmlEngine; -class QQuickView; -class QWindow; -class MainWindow; - -class EmbeddedWindow : public QObject -{ - Q_OBJECT -public: - EmbeddedWindow(QQmlEngine *qmlEngine, MainWindow *mainWindow); - ~EmbeddedWindow(); - -public slots: - void show(); - void close(); - -private: - QQmlEngine *qmlEngine = nullptr; - QQuickView *quickView = nullptr; - MainWindow *mainWindow = nullptr; - QWindow *embeddedWindow = nullptr; -}; diff --git a/examples/EmbeddedWindow/QmlApp/main.cpp b/examples/EmbeddedWindow/QmlApp/main.cpp deleted file mode 100644 index 957c749..0000000 --- a/examples/EmbeddedWindow/QmlApp/main.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#include <QGuiApplication> -#include <QQmlApplicationEngine> -#include <QQmlContext> -#include <QThread> -#include <QFile> - -#include <objbase.h> - -#include "mainwindow.h" -#include "embeddedwindow.h" - -int main(int argc, char *argv[]) -{ -#if defined(Q_OS_WIN) - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#endif - QGuiApplication app(argc, argv); - - MainWindow mainWindow; - QObject::connect(&mainWindow, &MainWindow::closed, &app, &QCoreApplication::quit); - - QQmlApplicationEngine engine; - engine.rootContext()->setContextProperty("mainWindow", &mainWindow); - - EmbeddedWindow embeddedWindow(&engine, &mainWindow); - - QThread *wpfThread = QThread::create([&app, &mainWindow] { - - if (FAILED(CoInitialize(nullptr))) { - app.quit(); - return; - } - - QString runtimeConfig = R"[json]( -{ - "runtimeOptions": { - "tfm": "net6.0-windows", - "rollForward": "LatestMinor", - "framework": { - "name": "Microsoft.WindowsDesktop.App", - "version": "6.0.0" - } - } -} -)[json]"; - QFile wpfAppRuntimeConfig(QGuiApplication::applicationDirPath() + "/WpfApp.runtimeconfig.json"); - if (wpfAppRuntimeConfig.open(QFile::ReadOnly | QFile::Text)) - runtimeConfig = QString(wpfAppRuntimeConfig.readAll()); - - QDotNetHost host; - if (!host.load(runtimeConfig)) { - app.quit(); - return; - } - QDotNetAdapter::init(&host); - mainWindow.init(); - }); - wpfThread->start(); - return app.exec(); -} diff --git a/examples/EmbeddedWindow/QmlApp/main.qml b/examples/EmbeddedWindow/QmlApp/main.qml deleted file mode 100644 index 04b018d..0000000 --- a/examples/EmbeddedWindow/QmlApp/main.qml +++ /dev/null @@ -1,135 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -import QtQuick -import QtQuick3D - -Rectangle { - width: mainWindow.hostWidth - height: mainWindow.hostHeight - gradient: Gradient { - GradientStop { position: 0.0; color: mainWindow.backgroundColor } - GradientStop { position: 0.40; color: "#E6ECED" } - GradientStop { position: 0.50; color: "#CCD9DB" } - GradientStop { position: 0.60; color: "#B3C6C9" } - GradientStop { position: 0.70; color: "#99B3B7" } - GradientStop { position: 0.75; color: "#80A0A5" } - GradientStop { position: 0.80; color: "#668D92" } - GradientStop { position: 0.85; color: "#4D7A80" } - GradientStop { position: 0.90; color: "#33676E" } - GradientStop { position: 0.95; color: "#19545C" } - GradientStop { position: 1.0; color: "#00414A" } - } - Text { - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.bottomMargin: 5 - anchors.rightMargin: 10 - font.pointSize: 20 - font.weight: Font.Bold - color: "#001012" - text: "QML" - } - - View3D { - id: view - anchors.fill: parent - - PerspectiveCamera { - position: Qt.vector3d( - mainWindow.cameraPositionX, - mainWindow.cameraPositionY + 200, - mainWindow.cameraPositionZ + 300) - eulerRotation.x: (mainWindow.cameraRotationX - 30) % 360 - eulerRotation.y: mainWindow.cameraRotationY - eulerRotation.z: mainWindow.cameraRotationZ - } - - DirectionalLight { - eulerRotation.x: (mainWindow.cameraRotationX - 30) % 360 - eulerRotation.y: mainWindow.cameraRotationY - eulerRotation.z: mainWindow.cameraRotationZ - } - - Model { - id: cube - source: "#Cube" - materials: DefaultMaterial { - diffuseMap: Texture { - sourceItem: Item { - id: qt_logo - width: 230 - height: 230 - visible: false - layer.enabled: true - Rectangle { - anchors.fill: parent - color: "black" - Image { - anchors.fill: parent - source: "qt_logo.png" - } - Text { - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - color: "white" - font.pixelSize: 17 - text: "The Future is Written with Qt" - } - Text { - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - color: "white" - font.pixelSize: 17 - text: "The Future is Written with Qt" - } - } - } - } - } - property var rotation: Qt.vector3d(0, 90, 0) - - eulerRotation.x: rotation.x % 360 - eulerRotation.y: rotation.y % 360 - eulerRotation.z: rotation.z % 360 - - Vector3dAnimation on rotation { - property var delta: Qt.vector3d(0, 0, 0) - id: cubeAnimation - loops: Animation.Infinite - duration: mainWindow.animationDuration - from: Qt.vector3d(0, 0, 0).plus(delta) - to: Qt.vector3d(360, 0, 360).plus(delta) - onDurationChanged: { - delta = cube.eulerRotation; - restart(); - } - } - } - } - - property var t0: 0 - property var n: 0 - - Component.onCompleted: { - window.afterFrameEnd.connect( - function() { - var t = Date.now(); - if (t0 == 0) { - t0 = t; - n = 1; - } else { - var dt = t - t0; - if (dt >= 1000) { - mainWindow.framesPerSecond = (1000 * n) / dt; - n = 0; - t0 = t; - } else { - n++; - } - } - }); - } -} diff --git a/examples/EmbeddedWindow/QmlApp/mainwindow.cpp b/examples/EmbeddedWindow/QmlApp/mainwindow.cpp deleted file mode 100644 index 2829f89..0000000 --- a/examples/EmbeddedWindow/QmlApp/mainwindow.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#include "mainwindow.h" - -class HwndHost : public QDotNetObject -{ -public: - Q_DOTNET_OBJECT_INLINE(HwndHost, "System.Windows.Interop.HwndHost, PresentationFramework"); - void *handle() { return method("get_Handle", fnGetHandle).invoke(*this); } - double width() { return method("get_Width", fnGetWidth).invoke(*this); } - double height() { return method("get_Height", fnGetHeight).invoke(*this); } -private: - QDotNetFunction<void *> fnGetHandle = nullptr; - QDotNetFunction<double> fnGetWidth = nullptr; - QDotNetFunction<double> fnGetHeight = nullptr; -}; - -class MouseEventArgs : public QDotNetObject -{ -public: - Q_DOTNET_OBJECT_INLINE(MouseEventArgs, "System.Windows.Input.MouseEventArgs, PresentationCore"); -}; - -class MainWindowPrivate : public QDotNetObject::IEventHandler -{ -public: - MainWindowPrivate(MainWindow *q) : q(q) - {} - void handleEvent(const QString &evName, QDotNetObject &evSource, QDotNetObject &evArgs) override - { - if (evName == "ContentRendered") { - emit q->contentRendered(); - } else if (evName == "SizeChanged") { - double width = evArgs.object("NewSize").call<double>("get_Width"); - double height = evArgs.object("NewSize").call<double>("get_Height"); - if (width != hostWidth) { - hostWidth = width; - emit q->hostWidthChanged(); - } - if (height != hostHeight) { - hostHeight = height; - emit q->hostHeightChanged(); - } - } else if (evName == "Closed") { - emit q->closed(); - } else if (evName == "PropertyChanged") { - QString propertyName = evArgs.call<QString>("get_PropertyName"); - if (propertyName == "CameraPositionX") - emit q->cameraPositionXChanged(); - else if (propertyName == "CameraPositionY") - emit q->cameraPositionYChanged(); - else if (propertyName == "CameraPositionZ") - emit q->cameraPositionZChanged(); - else if (propertyName == "CameraRotationX") - emit q->cameraRotationXChanged(); - else if (propertyName == "CameraRotationY") - emit q->cameraRotationYChanged(); - else if (propertyName == "CameraRotationZ") - emit q->cameraRotationZChanged(); - else if (propertyName == "AnimationDuration") - emit q->animationDurationChanged(); - } - }; - - HwndHost hwndHost = nullptr; - double hostWidth = 0.0, hostHeight = 0.0; - QDotNetFunction<void, double> fnSetEmbeddedFps = nullptr; - QDotNetFunction<double> fnGetCameraPositionX = nullptr; - QDotNetFunction<double> fnGetCameraPositionY = nullptr; - QDotNetFunction<double> fnGetCameraPositionZ = nullptr; - QDotNetFunction<double> fnGetCameraRotationX = nullptr; - QDotNetFunction<double> fnGetCameraRotationY = nullptr; - QDotNetFunction<double> fnGetCameraRotationZ = nullptr; - QDotNetFunction<double> fnGetAnimationDuration = nullptr; - QDotNetFunction<double> fnGetFramesPerSecond = nullptr; - QDotNetFunction<QString> fnGetBackgroundColor = nullptr; - -private: - MainWindow *q; -}; - -Q_DOTNET_OBJECT_IMPL(MainWindow, Q_DOTNET_OBJECT_INIT(d(new MainWindowPrivate(this)))); - -MainWindow::MainWindow() : QDotNetObject(nullptr), d(new MainWindowPrivate(this)) -{} - -MainWindow::~MainWindow() -{} - -void MainWindow::init() -{ - *this = constructor<MainWindow>().invoke(nullptr); - d->hwndHost = method<HwndHost>("get_HwndHost").invoke(*this); - subscribeEvent("ContentRendered", d); - subscribeEvent("Closed", d); - subscribeEvent("PropertyChanged", d); - d->hwndHost.subscribeEvent("SizeChanged", d); - - QtDotNet::call<void, MainWindow>("WpfApp.Program, WpfApp", "set_MainWindow", *this); - QtDotNet::call<int>("WpfApp.Program, WpfApp", "Main"); -} - -void *MainWindow::hostHandle() -{ - return d->hwndHost.handle(); -} - -int MainWindow::hostWidth() -{ - return d->hostWidth; -} - -int MainWindow::hostHeight() -{ - return d->hostHeight; -} - - -double MainWindow::cameraPositionX() -{ - return method("get_CameraPositionX", d->fnGetCameraPositionX).invoke(*this); -} - -double MainWindow::cameraPositionY() -{ - return method("get_CameraPositionY", d->fnGetCameraPositionY).invoke(*this); -} - -double MainWindow::cameraPositionZ() -{ - return method("get_CameraPositionZ", d->fnGetCameraPositionZ).invoke(*this); -} - -double MainWindow::cameraRotationX() -{ - return method("get_CameraRotationX", d->fnGetCameraRotationX).invoke(*this); -} - -double MainWindow::cameraRotationY() -{ - return method("get_CameraRotationY", d->fnGetCameraRotationY).invoke(*this); -} - -double MainWindow::cameraRotationZ() -{ - return method("get_CameraRotationZ", d->fnGetCameraRotationZ).invoke(*this); -} - -double MainWindow::animationDuration() -{ - return method("get_AnimationDuration", d->fnGetAnimationDuration).invoke(*this); -} - -double MainWindow::framesPerSecond() -{ - return method("get_FramesPerSecond", d->fnGetFramesPerSecond).invoke(*this); -} - -QString MainWindow::backgroundColor() -{ - return method("get_BackgroundColor", d->fnGetBackgroundColor).invoke(*this); -} - -void MainWindow::setFramesPerSecond(double fps) -{ - method("set_FramesPerSecond", d->fnSetEmbeddedFps).invoke(*this, fps); -} diff --git a/examples/EmbeddedWindow/QmlApp/mainwindow.h b/examples/EmbeddedWindow/QmlApp/mainwindow.h deleted file mode 100644 index 4d81ed0..0000000 --- a/examples/EmbeddedWindow/QmlApp/mainwindow.h +++ /dev/null @@ -1,61 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#pragma once - -#include <QObject> -#include <QDotNetObject> - -class MainWindowPrivate; - -class MainWindow : public QObject, public QDotNetObject -{ - Q_OBJECT - Q_PROPERTY(double hostWidth READ hostWidth NOTIFY hostWidthChanged) - Q_PROPERTY(double hostHeight READ hostHeight NOTIFY hostHeightChanged) - Q_PROPERTY(double cameraPositionX READ cameraPositionX NOTIFY cameraPositionXChanged) - Q_PROPERTY(double cameraPositionY READ cameraPositionY NOTIFY cameraPositionYChanged) - Q_PROPERTY(double cameraPositionZ READ cameraPositionZ NOTIFY cameraPositionZChanged) - Q_PROPERTY(double cameraRotationX READ cameraRotationX NOTIFY cameraRotationXChanged) - Q_PROPERTY(double cameraRotationY READ cameraRotationY NOTIFY cameraRotationYChanged) - Q_PROPERTY(double cameraRotationZ READ cameraRotationZ NOTIFY cameraRotationZChanged) - Q_PROPERTY(double animationDuration READ animationDuration NOTIFY animationDurationChanged) - Q_PROPERTY(QString backgroundColor READ backgroundColor NOTIFY backgroundColorChanged) - Q_PROPERTY(double framesPerSecond READ framesPerSecond WRITE setFramesPerSecond) -public: - Q_DOTNET_OBJECT(MainWindow, "WpfApp.MainWindow, WpfApp"); - MainWindow(); - ~MainWindow(); - void init(); - void *hostHandle(); - int hostWidth(); - int hostHeight(); - double cameraPositionX(); - double cameraPositionY(); - double cameraPositionZ(); - double cameraRotationX(); - double cameraRotationY(); - double cameraRotationZ(); - double animationDuration(); - double framesPerSecond(); - QString backgroundColor(); -signals: - void contentRendered(); - void hostWidthChanged(); - void hostHeightChanged(); - void cameraPositionXChanged(); - void cameraPositionYChanged(); - void cameraPositionZChanged(); - void cameraRotationXChanged(); - void cameraRotationYChanged(); - void cameraRotationZChanged(); - void animationDurationChanged(); - void backgroundColorChanged(); - void closed(); -public slots: - void setFramesPerSecond(double fps); -private: - MainWindowPrivate *d; -}; diff --git a/examples/EmbeddedWindow/QmlApp/qml.qrc b/examples/EmbeddedWindow/QmlApp/qml.qrc deleted file mode 100644 index 040dc48..0000000 --- a/examples/EmbeddedWindow/QmlApp/qml.qrc +++ /dev/null @@ -1,6 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>main.qml</file> - <file>qt_logo.png</file> - </qresource> -</RCC> diff --git a/examples/EmbeddedWindow/QmlApp/qt_logo.png b/examples/EmbeddedWindow/QmlApp/qt_logo.png Binary files differdeleted file mode 100644 index 30c621c..0000000 --- a/examples/EmbeddedWindow/QmlApp/qt_logo.png +++ /dev/null diff --git a/examples/EmbeddedWindow/WpfApp/AssemblyInfo.cs b/examples/EmbeddedWindow/WpfApp/AssemblyInfo.cs deleted file mode 100644 index 8b5504e..0000000 --- a/examples/EmbeddedWindow/WpfApp/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Windows; - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] diff --git a/examples/EmbeddedWindow/WpfApp/MainWindow.xaml b/examples/EmbeddedWindow/WpfApp/MainWindow.xaml deleted file mode 100644 index 2695ba9..0000000 --- a/examples/EmbeddedWindow/WpfApp/MainWindow.xaml +++ /dev/null @@ -1,58 +0,0 @@ -<Window x:Class="WpfApp.MainWindow" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" - xmlns:local="clr-namespace:WpfApp" mc:Ignorable="d" Name="this" - Title="WPF + QML Embedded Window" Width="640" Height="480" MinWidth="640" MinHeight="480"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="50*" /> - <ColumnDefinition Width="100*" /> - </Grid.ColumnDefinitions> - <StackPanel Name="StackPanel" Grid.Column="0" Orientation="Vertical" Background="#FFF0F0F0"> - <Label Content="WPF" FontSize="20" FontWeight="Bold" HorizontalAlignment="Left" - VerticalAlignment="Top" Height="37" Margin="10,0,10,0" /> - <Label Content="Camera Position X" Margin="10,0,10,0" /> - <Slider Name="SliderCameraPositionX" Minimum="-500" Maximum="500" SmallChange="1" - ValueChanged="Slider_ValueChanged" MouseDoubleClick="Slider_MouseDoubleClick" - Margin="10,0,10,0" /> - <Label Content="Camera Position Y" Margin="10,0,10,0" /> - <Slider Name="SliderCameraPositionY" Minimum="-500" Maximum="500" SmallChange="1" - ValueChanged="Slider_ValueChanged" MouseDoubleClick="Slider_MouseDoubleClick" - Margin="10,0,10,0" /> - <Label Content="Camera Position Z" Margin="10,0,10,0" /> - <Slider Name="SliderCameraPositionZ" Minimum="-500" Maximum="500" SmallChange="1" - IsDirectionReversed="True" ValueChanged="Slider_ValueChanged" - MouseDoubleClick="Slider_MouseDoubleClick" Margin="10,0,10,0" /> - <Label Content="Camera Rotation X" Margin="10,0,10,0" /> - <Slider Name="SliderCameraRotationX" Minimum="-180" Maximum="180" SmallChange="1" - ValueChanged="Slider_ValueChanged" MouseDoubleClick="Slider_MouseDoubleClick" - Margin="10,0,10,0" /> - <Label Content="Camera Rotation Y" Margin="10,0,10,0" /> - <Slider Name="SliderCameraRotationY" Minimum="-180" Maximum="180" SmallChange="1" - ValueChanged="Slider_ValueChanged" MouseDoubleClick="Slider_MouseDoubleClick" - Margin="10,0,10,0" /> - <Label Content="Camera Rotation Z" Margin="10,0,10,0" /> - <Slider Name="SliderCameraRotationZ" Minimum="-180" Maximum="180" SmallChange="1" - ValueChanged="Slider_ValueChanged" MouseDoubleClick="Slider_MouseDoubleClick" - Margin="10,0,10,0" /> - <Label Content="Animation Speed" Margin="10,0,10,0" /> - <Slider Name="SliderAnimationDuration" Minimum="0" Value="0.1" Maximum="1" SmallChange="0.001" - TickPlacement="BottomRight" TickFrequency="0.05" ValueChanged="Slider_ValueChanged" - MouseDoubleClick="Slider_MouseDoubleClick" Margin="10,0,10,0" /> - <Label Content="{Binding Rpm, ElementName=this}" ContentStringFormat="{}{0:F0} rpm" - HorizontalContentAlignment="Center" Margin="10,-5,10,0" /> - <Label Content="Frame Rate" Margin="10,0,10,0" /> - <Grid Margin="10,0,10,0"> - <ProgressBar Name="FpsValue" Value="0" Maximum="60" Margin="10,0,10,0" /> - <TextBlock Name="FpsLabel" HorizontalAlignment="Center" VerticalAlignment="Center" - Text="--.- fps" /> - </Grid> - </StackPanel> - <WindowsFormsHost Name="EmbeddedAppHost" Grid.Column="1"> - <wf:Panel Name="EmbeddedAppPanel" BackColor="#AAAAAA" /> - </WindowsFormsHost> - </Grid> -</Window> diff --git a/examples/EmbeddedWindow/WpfApp/MainWindow.xaml.cs b/examples/EmbeddedWindow/WpfApp/MainWindow.xaml.cs deleted file mode 100644 index 77f1bf8..0000000 --- a/examples/EmbeddedWindow/WpfApp/MainWindow.xaml.cs +++ /dev/null @@ -1,109 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -using System; -using System.ComponentModel; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Media; - -namespace WpfApp -{ - public partial class MainWindow : Window, INotifyPropertyChanged - { - public MainWindow() - { - InitializeComponent(); - } - - public HwndHost HwndHost => EmbeddedAppHost; - - public double CameraPositionX => WpfThread(() => SliderCameraPositionX.Value); - public double CameraPositionY => WpfThread(() => SliderCameraPositionY.Value); - public double CameraPositionZ => WpfThread(() => SliderCameraPositionZ.Value); - public double CameraRotationX => WpfThread(() => SliderCameraRotationX.Value); - public double CameraRotationY => WpfThread(() => SliderCameraRotationY.Value); - public double CameraRotationZ => WpfThread(() => SliderCameraRotationZ.Value); - - public static double MaxRpm => 100; - public double Rpm => MaxRpm * Math.Max(1 / MaxRpm, SliderAnimationDuration.Value); - public double AnimationDuration => WpfThread(() => 60000 / Rpm); - - public double FramesPerSecond - { - get => WpfThread(() => FpsValue.Value); - set - { - WpfThread(() => - { - if (value <= FpsValue.Maximum) - FpsValue.Value = value; - FpsLabel.Text = $"{value:0.0} fps"; - }); - } - } - - public string BackgroundColor { get; set; } = "#FFFFFF"; - - public event PropertyChangedEventHandler PropertyChanged; - private void NotifyPropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) - { - if (sender is not Slider slider) - return; - NotifyPropertyChanged(slider.Name switch - { - nameof(SliderCameraPositionX) => nameof(CameraPositionX), - nameof(SliderCameraPositionY) => nameof(CameraPositionY), - nameof(SliderCameraPositionZ) => nameof(CameraPositionZ), - nameof(SliderCameraRotationX) => nameof(CameraRotationX), - nameof(SliderCameraRotationY) => nameof(CameraRotationY), - nameof(SliderCameraRotationZ) => nameof(CameraRotationZ), - nameof(SliderAnimationDuration) => nameof(AnimationDuration), - _ => throw new NotSupportedException() - }); - if (slider.Name is nameof(SliderAnimationDuration)) - NotifyPropertyChanged(nameof(Rpm)); - } - - private void Slider_MouseDoubleClick(object sender, MouseButtonEventArgs e) - { - if (sender is not Slider slider) - return; - if (slider.Name is nameof(SliderAnimationDuration)) - slider.Value = (slider.Maximum + slider.Minimum) / 10; - else - slider.Value = (slider.Maximum + slider.Minimum) / 2; - } - - protected override void OnRender(DrawingContext drawingContext) - { - base.OnRender(drawingContext); - if (StackPanel.Background is not SolidColorBrush panelBrush) - return; - if (BackgroundColor != (BackgroundColor = panelBrush.Color.ToString())) - NotifyPropertyChanged(nameof(BackgroundColor)); - } - - private static void WpfThread(Action action) - { - if (Application.Current?.Dispatcher is { } dispatcher) - dispatcher.Invoke(action); - } - - private static T WpfThread<T>(Func<T> func) - { - if (Application.Current?.Dispatcher is not { } dispatcher) - return default; - return dispatcher.Invoke(func); - } - } -} diff --git a/examples/EmbeddedWindow/WpfApp/Properties/launchSettings.json b/examples/EmbeddedWindow/WpfApp/Properties/launchSettings.json deleted file mode 100644 index 70d4330..0000000 --- a/examples/EmbeddedWindow/WpfApp/Properties/launchSettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "profiles": { - "WpfApp": { - "commandName": "Project", - "environmentVariables": { - "PATH": "C:/lib/Qt/6.2.4/msvc2019_64/bin;%PATH%" - }, - "nativeDebugging": false - } - } -}
\ No newline at end of file diff --git a/examples/EmbeddedWindow/WpfApp/WpfApp.cs b/examples/EmbeddedWindow/WpfApp/WpfApp.cs deleted file mode 100644 index b80d9df..0000000 --- a/examples/EmbeddedWindow/WpfApp/WpfApp.cs +++ /dev/null @@ -1,23 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -using System; -using System.Windows; - -namespace WpfApp -{ - public static class Program - { - [STAThread] - public static int Main() - { - var application = new Application(); - (MainWindow ??= new MainWindow()).InitializeComponent(); - MainWindow.Show(); - return application.Run(); - } - public static MainWindow MainWindow { get; set; } - } -} diff --git a/examples/EmbeddedWindow/WpfApp/WpfApp.csproj b/examples/EmbeddedWindow/WpfApp/WpfApp.csproj deleted file mode 100644 index 8046823..0000000 --- a/examples/EmbeddedWindow/WpfApp/WpfApp.csproj +++ /dev/null @@ -1,33 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <OutputType>WinExe</OutputType> - <TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-windows</TargetFramework> - <Nullable>disable</Nullable> - <UseWPF>true</UseWPF> - <StartupObject>WpfApp.Program</StartupObject> - </PropertyGroup> - - <ItemGroup> - <ProjectReference Include="..\..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj" /> - </ItemGroup> - - <ItemGroup> - <Reference Include="Qt.DotNet.Adapter" Condition="Exists('..\..\..\bin\Qt.DotNet.Adapter.dll')"> - <HintPath>..\..\..\bin\Qt.DotNet.Adapter.dll</HintPath> - </Reference> - <Reference Include="System.Drawing.Common"> - <HintPath>$(ProgramFiles)\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\$(BundledNETCoreAppPackageVersion)\ref\net$(BundledNETCoreAppTargetFrameworkVersion)\System.Drawing.Common.dll</HintPath> - </Reference> - <Reference Include="System.Windows.Forms"> - <HintPath>$(ProgramFiles)\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\$(BundledNETCoreAppPackageVersion)\ref\net$(BundledNETCoreAppTargetFrameworkVersion)\System.Windows.Forms.dll</HintPath> - </Reference> - <Reference Include="System.Windows.Forms.Primitives"> - <HintPath>$(ProgramFiles)\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\$(BundledNETCoreAppPackageVersion)\ref\net$(BundledNETCoreAppTargetFrameworkVersion)\System.Windows.Forms.Primitives.dll</HintPath> - </Reference> - <Reference Include="WindowsFormsIntegration"> - <HintPath>$(ProgramFiles)\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\$(BundledNETCoreAppPackageVersion)\ref\net$(BundledNETCoreAppTargetFrameworkVersion)\WindowsFormsIntegration.dll</HintPath> - </Reference> - </ItemGroup> - -</Project> diff --git a/examples/QtAzureIoT/QtAzureIoT.sln b/examples/QtAzureIoT/QtAzureIoT.sln deleted file mode 100644 index 4f5a653..0000000 --- a/examples/QtAzureIoT/QtAzureIoT.sln +++ /dev/null @@ -1,88 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.33130.402 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "device", "device", "{BD2258EF-0E47-4DAA-94DF-D08CA1B09A93}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SensorData", "device\SensorData\SensorData.csproj", "{D8CD7E8D-7ECA-46A7-AA39-E471F99C94F0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceToBackoffice", "device\DeviceToBackoffice\DeviceToBackoffice.csproj", "{4F2A52A9-9DAE-4250-A881-F0A013A589F4}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "deviceapp", "device\deviceapp\deviceapp.vcxproj", "{2B3D1059-93F6-42F8-9709-AE06BEDE1EEF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{AC26C5B9-CA58-434C-B607-DC94BFE2A665}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "app", "app", "{60F1722D-12B9-4671-B9E3-EDE5C41F1086}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CardReader", "device\CardReader\CardReader.csproj", "{66A69341-3B00-4812-AA77-EC5C2E9EA23A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{34513197-4269-4943-9F6D-CE4D89CB4DD2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils", "common\Utils.csproj", "{DEF7470A-3D27-4D71-9E48-A96C9129FA42}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D8CD7E8D-7ECA-46A7-AA39-E471F99C94F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D8CD7E8D-7ECA-46A7-AA39-E471F99C94F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D8CD7E8D-7ECA-46A7-AA39-E471F99C94F0}.Debug|x64.ActiveCfg = Debug|Any CPU - {D8CD7E8D-7ECA-46A7-AA39-E471F99C94F0}.Debug|x64.Build.0 = Debug|Any CPU - {D8CD7E8D-7ECA-46A7-AA39-E471F99C94F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D8CD7E8D-7ECA-46A7-AA39-E471F99C94F0}.Release|Any CPU.Build.0 = Release|Any CPU - {D8CD7E8D-7ECA-46A7-AA39-E471F99C94F0}.Release|x64.ActiveCfg = Release|Any CPU - {D8CD7E8D-7ECA-46A7-AA39-E471F99C94F0}.Release|x64.Build.0 = Release|Any CPU - {4F2A52A9-9DAE-4250-A881-F0A013A589F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F2A52A9-9DAE-4250-A881-F0A013A589F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F2A52A9-9DAE-4250-A881-F0A013A589F4}.Debug|x64.ActiveCfg = Debug|Any CPU - {4F2A52A9-9DAE-4250-A881-F0A013A589F4}.Debug|x64.Build.0 = Debug|Any CPU - {4F2A52A9-9DAE-4250-A881-F0A013A589F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F2A52A9-9DAE-4250-A881-F0A013A589F4}.Release|Any CPU.Build.0 = Release|Any CPU - {4F2A52A9-9DAE-4250-A881-F0A013A589F4}.Release|x64.ActiveCfg = Release|Any CPU - {4F2A52A9-9DAE-4250-A881-F0A013A589F4}.Release|x64.Build.0 = Release|Any CPU - {2B3D1059-93F6-42F8-9709-AE06BEDE1EEF}.Debug|Any CPU.ActiveCfg = Debug|x64 - {2B3D1059-93F6-42F8-9709-AE06BEDE1EEF}.Debug|Any CPU.Build.0 = Debug|x64 - {2B3D1059-93F6-42F8-9709-AE06BEDE1EEF}.Debug|x64.ActiveCfg = Debug|x64 - {2B3D1059-93F6-42F8-9709-AE06BEDE1EEF}.Debug|x64.Build.0 = Debug|x64 - {2B3D1059-93F6-42F8-9709-AE06BEDE1EEF}.Release|Any CPU.ActiveCfg = Release|x64 - {2B3D1059-93F6-42F8-9709-AE06BEDE1EEF}.Release|Any CPU.Build.0 = Release|x64 - {2B3D1059-93F6-42F8-9709-AE06BEDE1EEF}.Release|x64.ActiveCfg = Release|x64 - {2B3D1059-93F6-42F8-9709-AE06BEDE1EEF}.Release|x64.Build.0 = Release|x64 - {66A69341-3B00-4812-AA77-EC5C2E9EA23A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {66A69341-3B00-4812-AA77-EC5C2E9EA23A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {66A69341-3B00-4812-AA77-EC5C2E9EA23A}.Debug|x64.ActiveCfg = Debug|Any CPU - {66A69341-3B00-4812-AA77-EC5C2E9EA23A}.Debug|x64.Build.0 = Debug|Any CPU - {66A69341-3B00-4812-AA77-EC5C2E9EA23A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {66A69341-3B00-4812-AA77-EC5C2E9EA23A}.Release|Any CPU.Build.0 = Release|Any CPU - {66A69341-3B00-4812-AA77-EC5C2E9EA23A}.Release|x64.ActiveCfg = Release|Any CPU - {66A69341-3B00-4812-AA77-EC5C2E9EA23A}.Release|x64.Build.0 = Release|Any CPU - {DEF7470A-3D27-4D71-9E48-A96C9129FA42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DEF7470A-3D27-4D71-9E48-A96C9129FA42}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DEF7470A-3D27-4D71-9E48-A96C9129FA42}.Debug|x64.ActiveCfg = Debug|Any CPU - {DEF7470A-3D27-4D71-9E48-A96C9129FA42}.Debug|x64.Build.0 = Debug|Any CPU - {DEF7470A-3D27-4D71-9E48-A96C9129FA42}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DEF7470A-3D27-4D71-9E48-A96C9129FA42}.Release|Any CPU.Build.0 = Release|Any CPU - {DEF7470A-3D27-4D71-9E48-A96C9129FA42}.Release|x64.ActiveCfg = Release|Any CPU - {DEF7470A-3D27-4D71-9E48-A96C9129FA42}.Release|x64.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {D8CD7E8D-7ECA-46A7-AA39-E471F99C94F0} = {AC26C5B9-CA58-434C-B607-DC94BFE2A665} - {4F2A52A9-9DAE-4250-A881-F0A013A589F4} = {AC26C5B9-CA58-434C-B607-DC94BFE2A665} - {2B3D1059-93F6-42F8-9709-AE06BEDE1EEF} = {60F1722D-12B9-4671-B9E3-EDE5C41F1086} - {AC26C5B9-CA58-434C-B607-DC94BFE2A665} = {BD2258EF-0E47-4DAA-94DF-D08CA1B09A93} - {60F1722D-12B9-4671-B9E3-EDE5C41F1086} = {BD2258EF-0E47-4DAA-94DF-D08CA1B09A93} - {66A69341-3B00-4812-AA77-EC5C2E9EA23A} = {AC26C5B9-CA58-434C-B607-DC94BFE2A665} - {DEF7470A-3D27-4D71-9E48-A96C9129FA42} = {34513197-4269-4943-9F6D-CE4D89CB4DD2} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9EC3D98D-2164-4E07-A5D2-334E9C93C570} - EndGlobalSection -EndGlobal diff --git a/examples/QtAzureIoT/common/PropertySet.cs b/examples/QtAzureIoT/common/PropertySet.cs deleted file mode 100644 index 2744a5c..0000000 --- a/examples/QtAzureIoT/common/PropertySet.cs +++ /dev/null @@ -1,27 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -using System.ComponentModel; - -namespace QtAzureIoT.Utils -{ - public class PropertySet : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - - protected void NotifyPropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected void SetProperty<T>(ref T currentValue, T newValue, string name) - { - if (newValue.Equals(currentValue)) - return; - currentValue = newValue; - NotifyPropertyChanged(name); - } - } -} diff --git a/examples/QtAzureIoT/common/Utils.csproj b/examples/QtAzureIoT/common/Utils.csproj deleted file mode 100644 index 141e38f..0000000 --- a/examples/QtAzureIoT/common/Utils.csproj +++ /dev/null @@ -1,9 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>net6.0</TargetFramework> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>disable</Nullable> - </PropertyGroup> - -</Project> diff --git a/examples/QtAzureIoT/device/CardReader/CardReader.cs b/examples/QtAzureIoT/device/CardReader/CardReader.cs deleted file mode 100644 index e97de9e..0000000 --- a/examples/QtAzureIoT/device/CardReader/CardReader.cs +++ /dev/null @@ -1,98 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -using System.Device.I2c; -using System.Diagnostics; -using Iot.Device.Pn532; -using Iot.Device.Pn532.ListPassive; -using Iot.Device.Pn532.RfConfiguration; -using QtAzureIoT.Utils; - -namespace QtAzureIoT.Device -{ - public class CardReader : PropertySet, IDisposable - { - public CardReader() - { } - - public bool CardInReader - { - get => propertyCardInReader; - private set => SetProperty(ref propertyCardInReader, value, nameof(CardInReader)); - } - - public void StartPolling() - { - if (Nfc != null) - return; - BusConnectionSettings = new I2cConnectionSettings(1, 0x24); - BusDevice = I2cDevice.Create(BusConnectionSettings); - Nfc = new Pn532(BusDevice); - - PollingLoop = new CancellationTokenSource(); - Polling = new Task(async () => await PollingLoopAsync(), PollingLoop.Token); - Polling.Start(); - } - - public void StopPolling() - { - if (Nfc == null) - return; - PollingLoop.Cancel(); - Polling.Wait(); - Nfc.Dispose(); - Nfc = null; - BusDevice.Dispose(); - BusDevice = null; - BusConnectionSettings = null; - PollingLoop.Dispose(); - PollingLoop = null; - Polling.Dispose(); - Polling = null; - } - - #region private - private I2cConnectionSettings BusConnectionSettings { get; set; } - private I2cDevice BusDevice { get; set; } - private Pn532 Nfc { get; set; } - private CancellationTokenSource PollingLoop { get; set; } - private Task Polling { get; set; } - - - private async Task PollingLoopAsync() - { - TargetBaudRate cardType = TargetBaudRate.B106kbpsTypeA; - while (!PollingLoop.IsCancellationRequested) { - try { - if (Nfc.ListPassiveTarget(MaxTarget.One, cardType) is object) { - CardInReader = true; - var timeSinceDetected = Stopwatch.StartNew(); - while (timeSinceDetected.ElapsedMilliseconds < 3000) { - if (Nfc.ListPassiveTarget(MaxTarget.One, cardType) is object) - timeSinceDetected.Restart(); - await Task.Delay(200); - } - CardInReader = false; - } else { - Nfc.SetRfField(RfFieldMode.None); - await Task.Delay(1000); - Nfc.SetRfField(RfFieldMode.RF); - } - } catch (Exception e) { - Debug.WriteLine($"Exception: {e.GetType().Name}: {e.Message}"); - Nfc.SetRfField(RfFieldMode.None); - } - } - } - - public void Dispose() - { - StopPolling(); - } - - private bool propertyCardInReader = false; - #endregion - } -} diff --git a/examples/QtAzureIoT/device/CardReader/CardReader.csproj b/examples/QtAzureIoT/device/CardReader/CardReader.csproj deleted file mode 100644 index c694f5b..0000000 --- a/examples/QtAzureIoT/device/CardReader/CardReader.csproj +++ /dev/null @@ -1,17 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>net6.0</TargetFramework> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>disable</Nullable> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Iot.Device.Bindings" Version="2.2.0" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\..\common\Utils.csproj" /> - </ItemGroup> - -</Project> diff --git a/examples/QtAzureIoT/device/DeviceToBackoffice/Backoffice.cs b/examples/QtAzureIoT/device/DeviceToBackoffice/Backoffice.cs deleted file mode 100644 index 5fe3cb9..0000000 --- a/examples/QtAzureIoT/device/DeviceToBackoffice/Backoffice.cs +++ /dev/null @@ -1,70 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Devices.Client; -using Microsoft.Azure.Devices.Client.Samples; -using Microsoft.Azure.Devices.Provisioning.Client; -using Microsoft.Azure.Devices.Provisioning.Client.PlugAndPlay; -using Microsoft.Azure.Devices.Provisioning.Client.Transport; -using Microsoft.Azure.Devices.Shared; -using Microsoft.Extensions.Logging; - -namespace QtAzureIoT.Device -{ - public class Backoffice - { - public Backoffice() - { - DeviceClient = DeviceClient.CreateFromConnectionString( - "HostName=QtDotNetDemo-Hub.azure-devices.net;DeviceId=QtDotNetDemoDevice;SharedAccessKey=YkZmsSOZf8lvQb5HDthosRHP4XV1hYSuDEoExe/2Fj8=", - TransportType.Mqtt, - new ClientOptions - { - ModelId = "dtmi:com:example:TemperatureController;2" - }); - BackofficeInterface = new TemperatureControllerSample(DeviceClient); - } - - public void SetTelemetry(string name, double value) - { - BackofficeInterface.SetTelemetry(name, value); - } - - public void SetTelemetry(string name, bool value) - { - BackofficeInterface.SetTelemetry(name, value); - } - - public void StartPolling() - { - PollingLoop = new CancellationTokenSource(); - Polling = new Task(async () => await PollingLoopAsync(), PollingLoop.Token); - Polling.Start(); - } - - public void StopPolling() - { - PollingLoop.Cancel(); - } - - #region private - private CancellationTokenSource PollingLoop { get; set; } - private Task Polling { get; set; } - private DeviceClient DeviceClient { get; } - private TemperatureControllerSample BackofficeInterface { get; } - - private async Task PollingLoopAsync() - { - await BackofficeInterface.InitOperationsAsync(PollingLoop.Token); - while (!PollingLoop.IsCancellationRequested) { - await BackofficeInterface.PerformOperationsAsync(PollingLoop.Token); - } - } - #endregion - } -} diff --git a/examples/QtAzureIoT/device/DeviceToBackoffice/DeviceToBackoffice.csproj b/examples/QtAzureIoT/device/DeviceToBackoffice/DeviceToBackoffice.csproj deleted file mode 100644 index a53efc9..0000000 --- a/examples/QtAzureIoT/device/DeviceToBackoffice/DeviceToBackoffice.csproj +++ /dev/null @@ -1,17 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>net6.0</TargetFramework> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>disable</Nullable> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Microsoft.Azure.Devices" Version="1.38.2" /> - <PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.41.3" /> - <PackageReference Include="Microsoft.Azure.Devices.Provisioning.Client" Version="1.19.2" /> - <PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Mqtt" Version="1.17.2" /> - <PackageReference Include="Microsoft.Azure.Devices.Shared" Version="1.30.2" /> - </ItemGroup> - -</Project> diff --git a/examples/QtAzureIoT/device/DeviceToBackoffice/PnpConvention.cs b/examples/QtAzureIoT/device/DeviceToBackoffice/PnpConvention.cs deleted file mode 100644 index 16bef2b..0000000 --- a/examples/QtAzureIoT/device/DeviceToBackoffice/PnpConvention.cs +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.Azure.Devices.Client; -using Microsoft.Azure.Devices.Shared; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Text; - -namespace PnpHelpers -{ - public class PnpConvention - { - /// <summary> - /// The content type for a plug and play compatible telemetry message. - /// </summary> - public const string ContentApplicationJson = "application/json"; - - /// <summary> - /// The key for a component identifier within a property update patch. Corresponding value is <see cref="PropertyComponentIdentifierValue"/>. - /// </summary> - public const string PropertyComponentIdentifierKey = "__t"; - - /// <summary> - /// The value for a component identifier within a property update patch. Corresponding key is <see cref="PropertyComponentIdentifierKey"/>. - /// </summary> - public const string PropertyComponentIdentifierValue = "c"; - - /// <summary> - /// Create a plug and play compatible telemetry message. - /// </summary> - /// <param name="telemetryName">The name of the telemetry, as defined in the DTDL interface. Must be 64 characters or less. For more details see - /// <see href="https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md#telemetry"/>.</param> - /// <param name="telemetryValue">The unserialized telemetry payload, in the format defined in the DTDL interface.</param> - /// <param name="componentName">The name of the component in which the telemetry is defined. Can be null for telemetry defined under the root interface.</param> - /// <param name="encoding">The character encoding to be used when encoding the message body to bytes. This defaults to utf-8.</param> - /// <returns>A plug and play compatible telemetry message, which can be sent to IoT Hub. The caller must dispose this object when finished.</returns> - public static Message CreateMessage(string telemetryName, object telemetryValue, string componentName = default, Encoding encoding = default) - { - if (string.IsNullOrWhiteSpace(telemetryName)) - { - throw new ArgumentNullException(nameof(telemetryName)); - } - if (telemetryValue == null) - { - throw new ArgumentNullException(nameof(telemetryValue)); - } - - return CreateMessage(new Dictionary<string, object> { { telemetryName, telemetryValue } }, componentName, encoding); - } - - /// <summary> - /// Create a plug and play compatible telemetry message. - /// </summary> - /// <param name="componentName">The name of the component in which the telemetry is defined. Can be null for telemetry defined under the root interface.</param> - /// <param name="telemetryPairs">The unserialized name and value telemetry pairs, as defined in the DTDL interface. Names must be 64 characters or less. For more details see - /// <see href="https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md#telemetry"/>.</param> - /// <param name="encoding">The character encoding to be used when encoding the message body to bytes. This defaults to utf-8.</param> - /// <returns>A plug and play compatible telemetry message, which can be sent to IoT Hub. The caller must dispose this object when finished.</returns> - public static Message CreateMessage(IDictionary<string, object> telemetryPairs, string componentName = default, Encoding encoding = default) - { - if (telemetryPairs == null) - { - throw new ArgumentNullException(nameof(telemetryPairs)); - } - - Encoding messageEncoding = encoding ?? Encoding.UTF8; - string payload = JsonConvert.SerializeObject(telemetryPairs); - var message = new Message(messageEncoding.GetBytes(payload)) - { - ContentEncoding = messageEncoding.WebName, - ContentType = ContentApplicationJson, - }; - - if (!string.IsNullOrWhiteSpace(componentName)) - { - message.ComponentName = componentName; - } - - return message; - } - - /// <summary> - /// Creates a batch property update payload for the specified property key/value pairs. - /// </summary> - /// <param name="propertyName">The name of the twin property.</param> - /// <param name="propertyValue">The unserialized value of the twin property.</param> - /// <returns>A compact payload of the properties to update.</returns> - /// <remarks> - /// This creates a property patch for both read-only and read-write properties, both of which are named from a service perspective. - /// All properties are read-write from a device's perspective. - /// For a root-level property update, the patch is in the format: <c>{ "samplePropertyName": 20 }</c> - /// </remarks> - public static TwinCollection CreatePropertyPatch(string propertyName, object propertyValue) - { - return CreatePropertyPatch(new Dictionary<string, object> { { propertyName, propertyValue } }); - } - - /// <summary> - /// Creates a batch property update payload for the specified property key/value pairs - /// </summary> - /// <remarks> - /// This creates a property patch for both read-only and read-write properties, both of which are named from a service perspective. - /// All properties are read-write from a device's perspective. - /// For a root-level property update, the patch is in the format: <c>{ "samplePropertyName": 20 }</c> - /// </remarks> - /// <param name="propertyPairs">The twin properties and values to update.</param> - /// <returns>A compact payload of the properties to update.</returns> - public static TwinCollection CreatePropertyPatch(IDictionary<string, object> propertyPairs) - { - return new TwinCollection(JsonConvert.SerializeObject(propertyPairs)); - } - - /// <summary> - /// Create a key/value property patch for updating digital twin properties. - /// </summary> - /// <remarks> - /// This creates a property patch for both read-only and read-write properties, both of which are named from a service perspective. - /// All properties are read-write from a device's perspective. - /// For a component-level property update, the patch is in the format: - /// <code> - /// { - /// "sampleComponentName": { - /// "__t": "c", - /// "samplePropertyName"": 20 - /// } - /// } - /// </code> - /// </remarks> - /// <param name="componentName">The name of the component in which the property is defined. Can be null for property defined under the root interface.</param> - /// <param name="propertyName">The name of the twin property.</param> - /// <param name="propertyValue">The unserialized value of the twin property.</param> - /// <returns>The property patch for read-only and read-write property updates.</returns> - public static TwinCollection CreateComponentPropertyPatch(string componentName, string propertyName, object propertyValue) - { - if (string.IsNullOrWhiteSpace(propertyName)) - { - throw new ArgumentNullException(nameof(propertyName)); - } - if (propertyValue == null) - { - throw new ArgumentNullException(nameof(propertyValue)); - } - - return CreateComponentPropertyPatch(componentName, new Dictionary<string, object> { { propertyName, propertyValue } }); - } - - /// <summary> - /// Create a key/value property patch for updating digital twin properties. - /// </summary> - /// <remarks> - /// This creates a property patch for both read-only and read-write properties, both of which are named from a service perspective. - /// All properties are read-write from a device's perspective. - /// For a component-level property update, the patch is in the format: - /// <code> - /// { - /// "sampleComponentName": { - /// "__t": "c", - /// "samplePropertyName": 20 - /// } - /// } - /// </code> - /// </remarks> - /// <param name="componentName">The name of the component in which the property is defined. Can be null for property defined under the root interface.</param> - /// <param name="propertyPairs">The property name and an unserialized value, as defined in the DTDL interface.</param> - /// <returns>The property patch for read-only and read-write property updates.</returns> - public static TwinCollection CreateComponentPropertyPatch(string componentName, IDictionary<string, object> propertyPairs) - { - if (string.IsNullOrWhiteSpace(componentName)) - { - throw new ArgumentNullException(nameof(componentName)); - } - if (propertyPairs == null) - { - throw new ArgumentNullException(nameof(propertyPairs)); - } - - var propertyPatch = new StringBuilder(); - propertyPatch.Append('{'); - propertyPatch.Append($"\"{componentName}\":"); - propertyPatch.Append('{'); - propertyPatch.Append($"\"{PropertyComponentIdentifierKey}\":\"{PropertyComponentIdentifierValue}\","); - foreach (var kvp in propertyPairs) - { - propertyPatch.Append($"\"{kvp.Key}\":{JsonConvert.SerializeObject(kvp.Value)},"); - } - - // remove the extra comma - propertyPatch.Remove(propertyPatch.Length - 1, 1); - - propertyPatch.Append("}}"); - - return new TwinCollection(propertyPatch.ToString()); - } - - /// <summary> - /// Creates a response to a write request on a device property. - /// </summary> - /// <remarks> - /// This creates a property patch for both read-only and read-write properties, both of which are named from a service perspective. - /// All properties are read-write from a device's perspective. - /// For a component-level property update, the patch is in the format: - /// <code> - /// { - /// "sampleComponentName": { - /// "__t": "c", - /// "samplePropertyName": 20 - /// } - /// } - /// </code> - /// </remarks> - /// <param name="propertyName">The name of the property to report.</param> - /// <param name="propertyValue">The unserialized property value.</param> - /// <param name="ackCode">The acknowledgment code, usually an HTTP Status Code e.g. 200, 400.</param> - /// <param name="ackVersion">The acknowledgment version, as supplied in the property update request.</param> - /// <param name="ackDescription">The acknowledgment description, an optional, human-readable message about the result of the property update.</param> - /// <returns>A serialized json string response.</returns> - public static TwinCollection CreateWritablePropertyResponse( - string propertyName, - object propertyValue, - int ackCode, - long ackVersion, - string ackDescription = null) - { - if (string.IsNullOrWhiteSpace(propertyName)) - { - throw new ArgumentNullException(nameof(propertyName)); - } - - return CreateWritablePropertyResponse( - new Dictionary<string, object> { { propertyName, propertyValue } }, - ackCode, - ackVersion, - ackDescription); - } - - /// <summary> - /// Creates a response to a write request on a device property. - /// </summary> - /// <param name="propertyPairs">The name and unserialized value of the property to report.</param> - /// <param name="ackCode">The acknowledgment code, usually an HTTP Status Code e.g. 200, 400.</param> - /// <param name="ackVersion">The acknowledgment version, as supplied in the property update request.</param> - /// <param name="ackDescription">The acknowledgment description, an optional, human-readable message about the result of the property update.</param> - /// <returns>A serialized json string response.</returns> - public static TwinCollection CreateWritablePropertyResponse( - IDictionary<string, object> propertyPairs, - int ackCode, - long ackVersion, - string ackDescription = null) - { - if (propertyPairs == null) - { - throw new ArgumentNullException(nameof(propertyPairs)); - } - - var response = new Dictionary<string, WritablePropertyResponse>(propertyPairs.Count); - foreach (var kvp in propertyPairs) - { - if (string.IsNullOrWhiteSpace(kvp.Key)) - { - throw new ArgumentNullException(nameof(kvp.Key), $"One of the propertyPairs keys was null, empty, or white space."); - } - response.Add(kvp.Key, new WritablePropertyResponse(kvp.Value, ackCode, ackVersion, ackDescription)); - } - - return new TwinCollection(JsonConvert.SerializeObject(response)); - } - - /// <summary> - /// Creates a response to a write request on a device property. - /// </summary> - /// <remarks> - /// For a component-level property update, the patch is in the format: - /// <code> - /// "sampleComponentName": { - /// "__t": "c", - /// "samplePropertyName": { - /// "value": 20, - /// "ac": 200, - /// "av": 5, - /// "ad": "The update was successful." - /// } - /// } - /// } - /// </code> - /// </remarks> - /// <param name="componentName">The component to which the property belongs.</param> - /// <param name="propertyName">The name of the property to report.</param> - /// <param name="propertyValue">The unserialized property value.</param> - /// <param name="ackCode">The acknowledgment code, usually an HTTP Status Code e.g. 200, 400.</param> - /// <param name="ackVersion">The acknowledgment version, as supplied in the property update request.</param> - /// <param name="ackDescription">The acknowledgment description, an optional, human-readable message about the result of the property update.</param> - /// <returns>A serialized json string response.</returns> - public static TwinCollection CreateComponentWritablePropertyResponse( - string componentName, - string propertyName, - object propertyValue, - int ackCode, - long ackVersion, - string ackDescription = null) - { - if (string.IsNullOrWhiteSpace(componentName)) - { - throw new ArgumentNullException(nameof(componentName)); - } - if (string.IsNullOrWhiteSpace(propertyName)) - { - throw new ArgumentNullException(nameof(propertyName)); - } - - return CreateComponentWritablePropertyResponse( - componentName, - new Dictionary<string, object> { { propertyName, propertyValue } }, - ackCode, - ackVersion, - ackDescription); - } - - /// <summary> - /// Creates a response to a write request on a device property. - /// </summary> - /// <remarks> - /// For a component-level property update, the patch is in the format: - /// <code> - /// "sampleComponentName": { - /// "__t": "c", - /// "samplePropertyName": { - /// "value": 20, - /// "ac": 200, - /// "av": 5, - /// "ad": "The update was successful." - /// } - /// } - /// } - /// </code> - /// </remarks> - /// <param name="componentName">The component to which the property belongs.</param> - /// <param name="propertyPairs">The name and unserialized value of the property to report.</param> - /// <param name="ackCode">The acknowledgment code, usually an HTTP Status Code e.g. 200, 400.</param> - /// <param name="ackVersion">The acknowledgment version, as supplied in the property update request.</param> - /// <param name="ackDescription">The acknowledgment description, an optional, human-readable message about the result of the property update.</param> - /// <returns>A serialized json string response.</returns> - public static TwinCollection CreateComponentWritablePropertyResponse( - string componentName, - IDictionary<string, object> propertyPairs, - int ackCode, - long ackVersion, - string ackDescription = null) - { - if (string.IsNullOrWhiteSpace(componentName)) - { - throw new ArgumentNullException(nameof(componentName)); - } - if (propertyPairs == null) - { - throw new ArgumentNullException(nameof(propertyPairs)); - } - - var propertyPatch = new Dictionary<string, object> - { - { PropertyComponentIdentifierKey, PropertyComponentIdentifierValue }, - }; - foreach (var kvp in propertyPairs) - { - if (string.IsNullOrWhiteSpace(kvp.Key)) - { - throw new ArgumentNullException(nameof(kvp.Key), $"One of the propertyPairs keys was null, empty, or white space."); - } - propertyPatch.Add(kvp.Key, new WritablePropertyResponse(kvp.Value, ackCode, ackVersion, ackDescription)); - } - - var response = new Dictionary<string, object> - { - { componentName, propertyPatch }, - }; - - return new TwinCollection(JsonConvert.SerializeObject(response)); - } - - /// <summary> - /// Helper to retrieve the property value from the <see cref="TwinCollection"/> property update patch which was received as a result of service-initiated update. - /// </summary> - /// <typeparam name="T">The data type of the property, as defined in the DTDL interface.</typeparam> - /// <param name="collection">The <see cref="TwinCollection"/> property update patch received as a result of service-initiated update.</param> - /// <param name="propertyName">The property name, as defined in the DTDL interface.</param> - /// <param name="propertyValue">The corresponding property value.</param> - /// <param name="componentName">The name of the component in which the property is defined. Can be null for property defined under the root interface.</param> - /// <returns>A boolean indicating if the <see cref="TwinCollection"/> property update patch received contains the property update.</returns> - public static bool TryGetPropertyFromTwin<T>(TwinCollection collection, string propertyName, out T propertyValue, string componentName = null) - { - if (collection == null) - { - throw new ArgumentNullException(nameof(collection)); - } - - // If the desired property update is for a root component or nested component, verify that property patch received contains the desired property update. - propertyValue = default; - - if (string.IsNullOrWhiteSpace(componentName)) - { - if (collection.Contains(propertyName)) - { - propertyValue = (T)collection[propertyName]; - return true; - } - } - - if (collection.Contains(componentName)) - { - JObject componentProperty = collection[componentName]; - if (componentProperty.ContainsKey(propertyName)) - { - propertyValue = componentProperty.Value<T>(propertyName); - return true; - } - } - - return false; - } - } -} diff --git a/examples/QtAzureIoT/device/DeviceToBackoffice/TemperatureControllerSample.cs b/examples/QtAzureIoT/device/DeviceToBackoffice/TemperatureControllerSample.cs deleted file mode 100644 index 58e7885..0000000 --- a/examples/QtAzureIoT/device/DeviceToBackoffice/TemperatureControllerSample.cs +++ /dev/null @@ -1,483 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Devices.Shared; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using PnpHelpers; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - internal enum StatusCode - { - Completed = 200, - InProgress = 202, - ReportDeviceInitialProperty = 203, - BadRequest = 400, - NotFound = 404 - } - - - public class TemperatureControllerSample - { - public ConcurrentDictionary<string, object> Telemetry = new(); - public void SetTelemetry(string name, double value) - { - Telemetry.AddOrUpdate(name, value, (k, v) => value); - } - public void SetTelemetry(string name, bool value) - { - Telemetry.AddOrUpdate(name, value, (k, v) => value); - } - - // The default reported "value" and "av" for each "Thermostat" component on the client initial startup. - // See https://docs.microsoft.com/azure/iot-develop/concepts-convention#writable-properties for more details in acknowledgment responses. - private const double DefaultPropertyValue = 0d; - - private const long DefaultAckVersion = 0L; - - private const string TargetTemperatureProperty = "targetTemperature"; - - private const string Thermostat1 = "thermostat1"; - private const string Thermostat2 = "thermostat2"; - private const string SerialNumber = "SR-123456"; - - private static readonly Random s_random = new Random(); - - private readonly DeviceClient _deviceClient; - //private readonly ILogger _logger; - - // Dictionary to hold the temperature updates sent over each "Thermostat" component. - // NOTE: Memory constrained devices should leverage storage capabilities of an external service to store this - // information and perform computation. - // See https://docs.microsoft.com/en-us/azure/event-grid/compare-messaging-services for more details. - private readonly Dictionary<string, Dictionary<DateTimeOffset, double>> _temperatureReadingsDateTimeOffset = - new Dictionary<string, Dictionary<DateTimeOffset, double>>(); - - // A dictionary to hold all desired property change callbacks that this pnp device should be able to handle. - // The key for this dictionary is the componentName. - private readonly IDictionary<string, DesiredPropertyUpdateCallback> _desiredPropertyUpdateCallbacks = - new Dictionary<string, DesiredPropertyUpdateCallback>(); - - // Dictionary to hold the current temperature for each "Thermostat" component. - private readonly Dictionary<string, double> _temperature = new Dictionary<string, double>(); - - // Dictionary to hold the max temperature since last reboot, for each "Thermostat" component. - private readonly Dictionary<string, double> _maxTemp = new Dictionary<string, double>(); - - // A safe initial value for caching the writable properties version is 1, so the client - // will process all previous property change requests and initialize the device application - // after which this version will be updated to that, so we have a high water mark of which version number - // has been processed. - private static long s_localWritablePropertiesVersion = 1; - - public TemperatureControllerSample(DeviceClient deviceClient) - { - _deviceClient = deviceClient ?? throw new ArgumentNullException(nameof(deviceClient)); - //_logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task InitOperationsAsync(CancellationToken cancellationToken) - { - _deviceClient.SetConnectionStatusChangesHandler(async (status, reason) => - { - //_logger.LogDebug($"Connection status change registered - status={status}, reason={reason}."); - - // Call GetWritablePropertiesAndHandleChangesAsync() to get writable properties from the server once the connection status changes into Connected. - // This can get back "lost" property updates in a device reconnection from status Disconnected_Retrying or Disconnected. - if (status == ConnectionStatus.Connected) { - await GetWritablePropertiesAndHandleChangesAsync(); - } - }); - - //_logger.LogDebug("Set handler for 'reboot' command."); - await _deviceClient.SetMethodHandlerAsync("reboot", HandleRebootCommandAsync, _deviceClient, cancellationToken); - - // For a component-level command, the command name is in the format "<component-name>*<command-name>". - //_logger.LogDebug($"Set handler for \"getMaxMinReport\" command."); - await _deviceClient.SetMethodHandlerAsync("thermostat1*getMaxMinReport", HandleMaxMinReportCommand, Thermostat1, cancellationToken); - await _deviceClient.SetMethodHandlerAsync("thermostat2*getMaxMinReport", HandleMaxMinReportCommand, Thermostat2, cancellationToken); - - //_logger.LogDebug("Set handler to receive 'targetTemperature' updates."); - await _deviceClient.SetDesiredPropertyUpdateCallbackAsync(SetDesiredPropertyUpdateCallback, null, cancellationToken); - _desiredPropertyUpdateCallbacks.Add(Thermostat1, TargetTemperatureUpdateCallbackAsync); - _desiredPropertyUpdateCallbacks.Add(Thermostat2, TargetTemperatureUpdateCallbackAsync); - - //_logger.LogDebug("For each component, check if the device properties are empty on the initial startup."); - await CheckEmptyPropertiesAsync(Thermostat1, cancellationToken); - await CheckEmptyPropertiesAsync(Thermostat2, cancellationToken); - - await UpdateDeviceInformationAsync(cancellationToken); - await SendDeviceSerialNumberAsync(cancellationToken); - - _maxTemp[Thermostat1] = 0d; - _maxTemp[Thermostat2] = 0d; - } - - public async Task PerformOperationsAsync(CancellationToken cancellationToken) - { - // This sample follows the following workflow: - // -> Set handler to receive and respond to connection status changes. - // -> Set handler to receive "reboot" command - root interface. - // -> Set handler to receive "getMaxMinReport" command - on "Thermostat" components. - // -> Set handler to receive "targetTemperature" property updates from service - on "Thermostat" components. - // -> Check if the properties are empty on the initial startup - for each "Thermostat" component. If so, report the default values with ACK to the hub. - // -> Update device information on "deviceInformation" component. - // -> Send initial device info - "workingSet" over telemetry, "serialNumber" over reported property update - root interface. - // -> Periodically send "temperature" over telemetry - on "Thermostat" components. - // -> Send "maxTempSinceLastReboot" over property update, when a new max temperature is set - on "Thermostat" components. - - - - //while (!cancellationToken.IsCancellationRequested) - { - //if (temperatureReset) - //{ - // // Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component. - // _temperature[Thermostat1] = Math.Round(s_random.NextDouble() * 40.0 + 5.0, 1); - // _temperature[Thermostat2] = Math.Round(s_random.NextDouble() * 40.0 + 5.0, 1); - //} - - await SendTemperatureAsync(Thermostat1, cancellationToken); - //await SendTemperatureAsync(Thermostat2, cancellationToken); - //await SendDeviceMemoryAsync(cancellationToken); - - //temperatureReset = _temperature[Thermostat1] == 0 && _temperature[Thermostat2] == 0; - await Task.Delay(5 * 1000, cancellationToken); - } - } - - private async Task GetWritablePropertiesAndHandleChangesAsync() - { - Twin twin = await _deviceClient.GetTwinAsync(); - //_logger.LogInformation($"Device retrieving twin values on CONNECT: {twin.ToJson()}"); - - TwinCollection twinCollection = twin.Properties.Desired; - long serverWritablePropertiesVersion = twinCollection.Version; - - // Check if the writable property version is outdated on the local side. - // For the purpose of this sample, we'll only check the writable property versions between local and server - // side without comparing the property values. - if (serverWritablePropertiesVersion > s_localWritablePropertiesVersion) - { - //_logger.LogInformation($"The writable property version cached on local is changing " + - //$"from {s_localWritablePropertiesVersion} to {serverWritablePropertiesVersion}."); - - foreach (KeyValuePair<string, object> propertyUpdate in twinCollection) - { - string componentName = propertyUpdate.Key; - switch (componentName) - { - case Thermostat1: - case Thermostat2: - // This will be called when a device client gets initialized and the _temperature dictionary is still empty. - if (!_temperature.TryGetValue(componentName, out double value)) - { - _temperature[componentName] = 21d; // The default temperature value is 21°C. - } - await TargetTemperatureUpdateCallbackAsync(twinCollection, componentName); - break; - - default: - //_logger.LogWarning($"Property: Received an unrecognized property update from service:" + - //$"\n[ {propertyUpdate.Key}: {propertyUpdate.Value} ]."); - break; - } - } - - //_logger.LogInformation($"The writable property version on local is currently {s_localWritablePropertiesVersion}."); - } - } - - // The callback to handle "reboot" command. This method will send a temperature update (of 0°C) over telemetry for both associated components. - private async Task<MethodResponse> HandleRebootCommandAsync(MethodRequest request, object userContext) - { - try - { - int delay = JsonConvert.DeserializeObject<int>(request.DataAsJson); - - //_logger.LogDebug($"Command: Received - Rebooting thermostat (resetting temperature reading to 0°C after {delay} seconds)."); - await Task.Delay(delay * 1000); - - //_logger.LogDebug("\tRebooting..."); - - _temperature[Thermostat1] = _maxTemp[Thermostat1] = 0; - _temperature[Thermostat2] = _maxTemp[Thermostat2] = 0; - - _temperatureReadingsDateTimeOffset.Clear(); - - //_logger.LogDebug("\tRestored."); - } catch (JsonReaderException /*ex*/) - { - //_logger.LogDebug($"Command input is invalid: {ex.Message}."); - return new MethodResponse((int)StatusCode.BadRequest); - } - - return new MethodResponse((int)StatusCode.Completed); - } - - // The callback to handle "getMaxMinReport" command. This method will returns the max, min and average temperature from the - // specified time to the current time. - private Task<MethodResponse> HandleMaxMinReportCommand(MethodRequest request, object userContext) - { - try - { - string componentName = (string)userContext; - DateTime sinceInUtc = JsonConvert.DeserializeObject<DateTime>(request.DataAsJson); - var sinceInDateTimeOffset = new DateTimeOffset(sinceInUtc); - - if (_temperatureReadingsDateTimeOffset.ContainsKey(componentName)) - { - //_logger.LogDebug($"Command: Received - component=\"{componentName}\", generating max, min and avg temperature " + - //$"report since {sinceInDateTimeOffset.LocalDateTime}."); - - Dictionary<DateTimeOffset, double> allReadings = _temperatureReadingsDateTimeOffset[componentName]; - Dictionary<DateTimeOffset, double> filteredReadings = allReadings.Where(i => i.Key > sinceInDateTimeOffset) - .ToDictionary(i => i.Key, i => i.Value); - - if (filteredReadings != null && filteredReadings.Any()) - { - var report = new - { - maxTemp = filteredReadings.Values.Max<double>(), - minTemp = filteredReadings.Values.Min<double>(), - avgTemp = filteredReadings.Values.Average(), - startTime = filteredReadings.Keys.Min(), - endTime = filteredReadings.Keys.Max(), - }; - - //_logger.LogDebug($"Command: component=\"{componentName}\", MaxMinReport since {sinceInDateTimeOffset.LocalDateTime}:" + - // $" maxTemp={report.maxTemp}, minTemp={report.minTemp}, avgTemp={report.avgTemp}, startTime={report.startTime.LocalDateTime}, " + - // $"endTime={report.endTime.LocalDateTime}"); - - byte[] responsePayload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(report)); - return Task.FromResult(new MethodResponse(responsePayload, (int)StatusCode.Completed)); - } - - //_logger.LogDebug($"Command: component=\"{componentName}\", no relevant readings found since {sinceInDateTimeOffset.LocalDateTime}, " + - // $"cannot generate any report."); - return Task.FromResult(new MethodResponse((int)StatusCode.NotFound)); - } - - //_logger.LogDebug($"Command: component=\"{componentName}\", no temperature readings sent yet, cannot generate any report."); - return Task.FromResult(new MethodResponse((int)StatusCode.NotFound)); - } - catch (JsonReaderException /*ex*/) - { - //_logger.LogDebug($"Command input is invalid: {ex.Message}."); - return Task.FromResult(new MethodResponse((int)StatusCode.BadRequest)); - } - } - - private Task SetDesiredPropertyUpdateCallback(TwinCollection desiredProperties, object userContext) - { - bool callbackNotInvoked = true; - - foreach (KeyValuePair<string, object> propertyUpdate in desiredProperties) - { - string componentName = propertyUpdate.Key; - if (_desiredPropertyUpdateCallbacks.ContainsKey(componentName)) - { - _desiredPropertyUpdateCallbacks[componentName]?.Invoke(desiredProperties, componentName); - callbackNotInvoked = false; - } - } - - if (callbackNotInvoked) - { - //_logger.LogDebug($"Property: Received a property update that is not implemented by any associated component."); - } - - return Task.CompletedTask; - } - - // The desired property update callback, which receives the target temperature as a desired property update, - // and updates the current temperature value over telemetry and property update. - private async Task TargetTemperatureUpdateCallbackAsync(TwinCollection desiredProperties, object userContext) - { - string componentName = (string)userContext; - - bool targetTempUpdateReceived = PnpConvention.TryGetPropertyFromTwin( - desiredProperties, - TargetTemperatureProperty, - out double targetTemperature, - componentName); - if (!targetTempUpdateReceived) - { - //_logger.LogDebug($"Property: Update - component=\"{componentName}\", received an update which is not associated with a valid property.\n{desiredProperties.ToJson()}"); - return; - } - - //_logger.LogDebug($"Property: Received - component=\"{componentName}\", {{ \"{TargetTemperatureProperty}\": {targetTemperature}°C }}."); - - s_localWritablePropertiesVersion = desiredProperties.Version; - - TwinCollection pendingReportedProperty = PnpConvention.CreateComponentWritablePropertyResponse( - componentName, - TargetTemperatureProperty, - targetTemperature, - (int)StatusCode.InProgress, - desiredProperties.Version, - "In progress - reporting current temperature"); - - await _deviceClient.UpdateReportedPropertiesAsync(pendingReportedProperty); - //_logger.LogDebug($"Property: Update - component=\"{componentName}\", {{\"{TargetTemperatureProperty}\": {targetTemperature} }} in °C is {StatusCode.InProgress}."); - - // Update Temperature in 2 steps - double step = (targetTemperature - _temperature[componentName]) / 2d; - for (int i = 1; i <= 2; i++) - { - _temperature[componentName] = Math.Round(_temperature[componentName] + step, 1); - await Task.Delay(6 * 1000); - } - - TwinCollection completedReportedProperty = PnpConvention.CreateComponentWritablePropertyResponse( - componentName, - TargetTemperatureProperty, - _temperature[componentName], - (int)StatusCode.Completed, - desiredProperties.Version, - "Successfully updated target temperature"); - - await _deviceClient.UpdateReportedPropertiesAsync(completedReportedProperty); - //_logger.LogDebug($"Property: Update - component=\"{componentName}\", {{\"{TargetTemperatureProperty}\": {_temperature[componentName]} }} in °C is {StatusCode.Completed}"); - } - - // Report the property updates on "deviceInformation" component. - private async Task UpdateDeviceInformationAsync(CancellationToken cancellationToken) - { - const string componentName = "deviceInformation"; - - TwinCollection deviceInfoTc = PnpConvention.CreateComponentPropertyPatch( - componentName, - new Dictionary<string, object> - { - { "manufacturer", "element15" }, - { "model", "ModelIDxcdvmk" }, - { "swVersion", "1.0.0" }, - { "osName", "Windows 10" }, - { "processorArchitecture", "64-bit" }, - { "processorManufacturer", "Intel" }, - { "totalStorage", 256 }, - { "totalMemory", 1024 }, - }); - - await _deviceClient.UpdateReportedPropertiesAsync(deviceInfoTc, cancellationToken); - //_logger.LogDebug($"Property: Update - component = '{componentName}', properties update is complete."); - } - - // Send working set of device memory over telemetry. - private async Task SendDeviceMemoryAsync(CancellationToken cancellationToken) - { - const string workingSetName = "workingSet"; - - long workingSet = Process.GetCurrentProcess().PrivateMemorySize64 / 1024; - - var telemetry = new Dictionary<string, object> - { - { workingSetName, workingSet }, - }; - - using Message msg = PnpConvention.CreateMessage(telemetry); - - await _deviceClient.SendEventAsync(msg, cancellationToken); - //_logger.LogDebug($"Telemetry: Sent - {JsonConvert.SerializeObject(telemetry)} in KB."); - } - - // Send device serial number over property update. - private async Task SendDeviceSerialNumberAsync(CancellationToken cancellationToken) - { - const string propertyName = "serialNumber"; - TwinCollection reportedProperties = PnpConvention.CreatePropertyPatch(propertyName, SerialNumber); - - await _deviceClient.UpdateReportedPropertiesAsync(reportedProperties, cancellationToken); - //var oBrace = '{'; - //var cBrace = '}'; - //_logger.LogDebug($"Property: Update - {oBrace} \"{propertyName}\": \"{SerialNumber}\" {cBrace} is complete."); - } - - private async Task SendTemperatureAsync(string componentName, CancellationToken cancellationToken) - { - await SendTemperatureTelemetryAsync(componentName, cancellationToken); - - double maxTemp = _temperatureReadingsDateTimeOffset[componentName].Values.Max<double>(); - if (maxTemp > _maxTemp[componentName]) - { - _maxTemp[componentName] = maxTemp; - await UpdateMaxTemperatureSinceLastRebootAsync(componentName, cancellationToken); - } - } - - private async Task SendTemperatureTelemetryAsync(string componentName, CancellationToken cancellationToken) - { - //const string telemetryName = "temperature"; - double currentTemperature = _temperature[componentName]; - using Message msg = PnpConvention.CreateMessage(Telemetry, /*telemetryName, currentTemperature, */componentName); - await _deviceClient.SendEventAsync(msg, cancellationToken); - - //_logger.LogDebug($"Telemetry: Sent - component=\"{componentName}\", {{ \"{telemetryName}\": {currentTemperature} }} in °C."); - - if (_temperatureReadingsDateTimeOffset.ContainsKey(componentName)) - { - _temperatureReadingsDateTimeOffset[componentName].TryAdd(DateTimeOffset.UtcNow, currentTemperature); - } - else - { - _temperatureReadingsDateTimeOffset.TryAdd( - componentName, - new Dictionary<DateTimeOffset, double> - { - { DateTimeOffset.UtcNow, currentTemperature }, - }); - } - } - - private async Task UpdateMaxTemperatureSinceLastRebootAsync(string componentName, CancellationToken cancellationToken) - { - const string propertyName = "maxTempSinceLastReboot"; - double maxTemp = _maxTemp[componentName]; - TwinCollection reportedProperties = PnpConvention.CreateComponentPropertyPatch(componentName, propertyName, maxTemp); - - await _deviceClient.UpdateReportedPropertiesAsync(reportedProperties, cancellationToken); - //_logger.LogDebug($"Property: Update - component=\"{componentName}\", {{ \"{propertyName}\": {maxTemp} }} in °C is complete."); - } - - private async Task CheckEmptyPropertiesAsync(string componentName, CancellationToken cancellationToken) - { - Twin twin = await _deviceClient.GetTwinAsync(cancellationToken); - TwinCollection writableProperty = twin.Properties.Desired; - TwinCollection reportedProperty = twin.Properties.Reported; - - // Check if the device properties (both writable and reported) for the current component are empty. - if (!writableProperty.Contains(componentName) && !reportedProperty.Contains(componentName)) - { - await ReportInitialPropertyAsync(componentName, TargetTemperatureProperty, cancellationToken); - } - } - - private async Task ReportInitialPropertyAsync(string componentName, string propertyName, CancellationToken cancellationToken) - { - // If the device properties are empty, report the default value with ACK(ac=203, av=0) as part of the PnP convention. - // "DefaultPropertyValue" is set from the device when the desired property is not set via the hub. - TwinCollection reportedProperties = PnpConvention.CreateComponentWritablePropertyResponse( - componentName, - propertyName, - DefaultPropertyValue, - (int)StatusCode.ReportDeviceInitialProperty, - DefaultAckVersion, - "Initialized with default value"); - - await _deviceClient.UpdateReportedPropertiesAsync(reportedProperties, cancellationToken); - - //_logger.LogDebug($"Report the default values for \"{componentName}\".\nProperty: Update - {reportedProperties.ToJson()} is complete."); - } - } -} diff --git a/examples/QtAzureIoT/device/DeviceToBackoffice/WritablePropertyResponse.cs b/examples/QtAzureIoT/device/DeviceToBackoffice/WritablePropertyResponse.cs deleted file mode 100644 index 3cbfb64..0000000 --- a/examples/QtAzureIoT/device/DeviceToBackoffice/WritablePropertyResponse.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Newtonsoft.Json; - -namespace PnpHelpers -{ - /// <summary> - /// The payload for a property update response. - /// </summary> - public class WritablePropertyResponse - { - /// <summary> - /// Empty constructor. - /// </summary> - public WritablePropertyResponse() { } - - /// <summary> - /// Convenience constructor for specifying the properties. - /// </summary> - /// <param name="propertyValue">The unserialized property value.</param> - /// <param name="ackCode">The acknowledgment code, usually an HTTP Status Code e.g. 200, 400.</param> - /// <param name="ackVersion">The acknowledgment version, as supplied in the property update request.</param> - /// <param name="ackDescription">The acknowledgment description, an optional, human-readable message about the result of the property update.</param> - public WritablePropertyResponse(object propertyValue, int ackCode, long ackVersion, string ackDescription = null) - { - PropertyValue = propertyValue; - AckCode = ackCode; - AckVersion = ackVersion; - AckDescription = ackDescription; - } - - /// <summary> - /// The unserialized property value. - /// </summary> - [JsonProperty("value")] - public object PropertyValue { get; set; } - - /// <summary> - /// The acknowledgment code, usually an HTTP Status Code e.g. 200, 400. - /// </summary> - [JsonProperty("ac")] - public int AckCode { get; set; } - - /// <summary> - /// The acknowledgment version, as supplied in the property update request. - /// </summary> - [JsonProperty("av")] - public long AckVersion { get; set; } - - /// <summary> - /// The acknowledgment description, an optional, human-readable message about the result of the property update. - /// </summary> - [JsonProperty("ad", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string AckDescription { get; set; } - } -} diff --git a/examples/QtAzureIoT/device/SensorData/SensorData.cs b/examples/QtAzureIoT/device/SensorData/SensorData.cs deleted file mode 100644 index c58c5c1..0000000 --- a/examples/QtAzureIoT/device/SensorData/SensorData.cs +++ /dev/null @@ -1,107 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -using System.Diagnostics; -using System.Device.I2c; -using Iot.Device.Bmxx80; -using Iot.Device.Bmxx80.PowerMode; -using QtAzureIoT.Utils; - -namespace QtAzureIoT.Device -{ - public class SensorData : PropertySet, IDisposable - { - public SensorData() - { } - - public double Temperature - { - get => propertyTemperature; - private set => SetProperty(ref propertyTemperature, value, nameof(Temperature)); - } - - public double Pressure - { - get => propertyPressure; - private set => SetProperty(ref propertyPressure, value, nameof(Pressure)); - } - - public double Humidity - { - get => propertyHumidity; - private set => SetProperty(ref propertyHumidity, value, nameof(Humidity)); - } - - public void StartPolling() - { - if (Sensor != null) - return; - BusConnectionSettings = new I2cConnectionSettings(1, Bme280.DefaultI2cAddress); - BusDevice = I2cDevice.Create(BusConnectionSettings); - Sensor = new Bme280(BusDevice); - MesasuramentDelay = Sensor.GetMeasurementDuration(); - - PollingLoop = new CancellationTokenSource(); - Polling = new Task(async () => await PollingLoopAsync(), PollingLoop.Token); - Polling.Start(); - } - - public void StopPolling() - { - if (Sensor == null) - return; - PollingLoop.Cancel(); - Polling.Wait(); - Sensor.Dispose(); - Sensor = null; - BusDevice.Dispose(); - BusDevice = null; - BusConnectionSettings = null; - PollingLoop.Dispose(); - PollingLoop = null; - Polling.Dispose(); - Polling = null; - } - - #region private - private I2cConnectionSettings BusConnectionSettings { get; set; } - private I2cDevice BusDevice { get; set; } - private Bme280 Sensor { get; set; } - int MesasuramentDelay { get; set; } - private CancellationTokenSource PollingLoop { get; set; } - private Task Polling { get; set; } - - - private async Task PollingLoopAsync() - { - while (!PollingLoop.IsCancellationRequested) { - try { - Sensor.SetPowerMode(Bmx280PowerMode.Forced); - await Task.Delay(MesasuramentDelay); - - if (Sensor.TryReadTemperature(out var tempValue)) - Temperature = tempValue.DegreesCelsius; - if (Sensor.TryReadPressure(out var preValue)) - Pressure = preValue.Hectopascals; - if (Sensor.TryReadHumidity(out var humValue)) - Humidity = humValue.Percent; - } catch (Exception e) { - Debug.WriteLine($"Exception: {e.GetType().Name}: {e.Message}"); - } - await Task.Delay(1000); - } - } - - public void Dispose() - { - StopPolling(); - } - - double propertyTemperature; - double propertyPressure; - double propertyHumidity; - #endregion - } -} diff --git a/examples/QtAzureIoT/device/SensorData/SensorData.csproj b/examples/QtAzureIoT/device/SensorData/SensorData.csproj deleted file mode 100644 index c694f5b..0000000 --- a/examples/QtAzureIoT/device/SensorData/SensorData.csproj +++ /dev/null @@ -1,17 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>net6.0</TargetFramework> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>disable</Nullable> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Iot.Device.Bindings" Version="2.2.0" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\..\common\Utils.csproj" /> - </ItemGroup> - -</Project> diff --git a/examples/QtAzureIoT/device/deviceapp/deviceapp.vcxproj b/examples/QtAzureIoT/device/deviceapp/deviceapp.vcxproj deleted file mode 100644 index fb25e64..0000000 --- a/examples/QtAzureIoT/device/deviceapp/deviceapp.vcxproj +++ /dev/null @@ -1,126 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|x64"> - <Configuration>Debug</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|x64"> - <Configuration>Release</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{2B3D1059-93F6-42F8-9709-AE06BEDE1EEF}</ProjectGuid> - <Keyword>QtVS_v304</Keyword> - <WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">10.0.19041.0</WindowsTargetPlatformVersion> - <WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">10.0.19041.0</WindowsTargetPlatformVersion> - <QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <PlatformToolset>v143</PlatformToolset> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <PlatformToolset>v143</PlatformToolset> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')"> - <Import Project="$(QtMsBuild)\qt_defaults.props" /> - </ImportGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings"> - <QtInstall>6.2.7_msvc2019_64</QtInstall> - <QtModules>core;quick</QtModules> - <QtBuildConfig>debug</QtBuildConfig> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings"> - <QtInstall>6.2.7_msvc2019_64</QtInstall> - <QtModules>core;quick</QtModules> - <QtBuildConfig>release</QtBuildConfig> - </PropertyGroup> - <Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')"> - <Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." /> - </Target> - <ImportGroup Label="ExtensionSettings" /> - <ImportGroup Label="Shared" /> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - <Import Project="$(QtMsBuild)\Qt.props" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - <Import Project="$(QtMsBuild)\Qt.props" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> - <IncludePath>C:\dev\source\qt-labs\qtdotnet\include;$(IncludePath)</IncludePath> - <OutDir>bin\$(Platform)\$(Configuration)\</OutDir> - <IntDir>obj\$(Platform)\$(Configuration)\</IntDir> - <CopyLocalProjectReference>true</CopyLocalProjectReference> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> - <IncludePath>C:\dev\source\qt-labs\qtdotnet\include;$(IncludePath)</IncludePath> - <OutDir>bin\$(Platform)\$(Configuration)\</OutDir> - <IntDir>obj\$(Platform)\$(Configuration)\</IntDir> - <CopyLocalProjectReference>true</CopyLocalProjectReference> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration"> - <ClCompile> - <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> - <MultiProcessorCompilation>true</MultiProcessorCompilation> - <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> - <Optimization>Disabled</Optimization> - </ClCompile> - <Link> - <SubSystem>Windows</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration"> - <ClCompile> - <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> - <MultiProcessorCompilation>true</MultiProcessorCompilation> - <DebugInformationFormat>None</DebugInformationFormat> - <Optimization>MaxSpeed</Optimization> - </ClCompile> - <Link> - <SubSystem>Windows</SubSystem> - <GenerateDebugInformation>false</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemGroup> - <QtMoc Include="main.cpp"> - <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">input</DynamicSource> - <QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(Filename).moc</QtMocFileName> - <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Release|x64'">input</DynamicSource> - <QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(Filename).moc</QtMocFileName> - </QtMoc> - <QtRcc Include="qml.qrc" /> - <CopyFileToFolders Include="..\..\..\qtdotnet\bin\QtVsTools.QtDotNet.Adapter.dll"> - <FileType>Document</FileType> - <TreatOutputAsContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</TreatOutputAsContent> - <TreatOutputAsContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</TreatOutputAsContent> - </CopyFileToFolders> - <None Include="main.qml" /> - </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\CardReader\CardReader.csproj"> - <Project>{66a69341-3b00-4812-aa77-ec5c2e9ea23a}</Project> - <ReferenceOutputAssembly>true</ReferenceOutputAssembly> - <LinkLibraryDependencies>false</LinkLibraryDependencies> - </ProjectReference> - <ProjectReference Include="..\SensorData\SensorData.csproj"> - <Project>{d8cd7e8d-7eca-46a7-aa39-e471f99c94f0}</Project> - <ReferenceOutputAssembly>true</ReferenceOutputAssembly> - <LinkLibraryDependencies>false</LinkLibraryDependencies> - </ProjectReference> - </ItemGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')"> - <Import Project="$(QtMsBuild)\qt.targets" /> - </ImportGroup> - <ImportGroup Label="ExtensionTargets"> - </ImportGroup> -</Project>
\ No newline at end of file diff --git a/examples/QtAzureIoT/device/deviceapp/deviceapp.vcxproj.filters b/examples/QtAzureIoT/device/deviceapp/deviceapp.vcxproj.filters deleted file mode 100644 index 4965748..0000000 --- a/examples/QtAzureIoT/device/deviceapp/deviceapp.vcxproj.filters +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup> - <Filter Include="Source Files"> - <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> - <Extensions>qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> - </Filter> - <Filter Include="Header Files"> - <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> - <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions> - </Filter> - <Filter Include="Resource Files"> - <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> - <Extensions>qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> - </Filter> - <Filter Include="Form Files"> - <UniqueIdentifier>{99349809-55BA-4b9d-BF79-8FDBB0286EB3}</UniqueIdentifier> - <Extensions>ui</Extensions> - </Filter> - <Filter Include="Translation Files"> - <UniqueIdentifier>{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}</UniqueIdentifier> - <Extensions>ts</Extensions> - </Filter> - </ItemGroup> - <ItemGroup> - <QtRcc Include="qml.qrc"> - <Filter>Resource Files</Filter> - </QtRcc> - <None Include="main.qml"> - <Filter>Source Files</Filter> - </None> - </ItemGroup> - <ItemGroup> - <CopyFileToFolders Include="..\..\..\qtdotnet\bin\QtVsTools.QtDotNet.Adapter.dll" /> - </ItemGroup> - <ItemGroup> - <QtMoc Include="main.cpp"> - <Filter>Source Files</Filter> - </QtMoc> - </ItemGroup> -</Project>
\ No newline at end of file diff --git a/examples/QtAzureIoT/device/deviceapp/main.cpp b/examples/QtAzureIoT/device/deviceapp/main.cpp deleted file mode 100644 index ba049d1..0000000 --- a/examples/QtAzureIoT/device/deviceapp/main.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -#include <QtGui/QGuiApplication> -#include <QtQml/QQmlApplicationEngine> -#include <QtQml/QQmlContext> - -#include <qdotnetobject.h> -#include <qdotnetevent.h> - -class Backoffice : public QDotNetObject -{ -public: - Q_DOTNET_OBJECT_INLINE(Backoffice, "QtAzureIoT.Device.Backoffice, DeviceToBackoffice"); - Backoffice() : QDotNetObject(getConstructor<Backoffice>().invoke(nullptr)) - {} - void setTelemetry(QString name, double value) - { - getMethod("SetTelemetry", fnSetTelemetryDouble).invoke(*this, name, value); - } - void setTelemetry(QString name, bool value) - { - getMethod("SetTelemetry", fnSetTelemetryBool).invoke(*this, name, value); - } -public: - void startPolling() { - getMethod("StartPolling", fnStartPolling).invoke(*this); - } - void stopPolling() { - getMethod("StopPolling", fnStopPolling).invoke(*this); - } -private: - mutable QDotNetFunction<void, QString, double> fnSetTelemetryDouble; - mutable QDotNetFunction<void, QString, bool> fnSetTelemetryBool; - mutable QDotNetFunction<void> fnStartPolling; - mutable QDotNetFunction<void> fnStopPolling; -}; - -class SensorData : public QObject, public QDotNetObject, public QDotNetObject::IEventHandler -{ - Q_OBJECT - Q_PROPERTY(double temperature READ temperature NOTIFY temperatureChanged) - Q_PROPERTY(double pressure READ pressure NOTIFY pressureChanged) - Q_PROPERTY(double humidity READ humidity NOTIFY humidityChanged) -public: - Q_DOTNET_OBJECT_INLINE(SensorData, "QtAzureIoT.Device.SensorData, SensorData"); - SensorData() : QDotNetObject(getConstructor<SensorData>().invoke(nullptr)) - { - subscribeEvent("PropertyChanged", this); - } - double temperature() const - { - return getMethod("get_Temperature", fnGet_Temperature).invoke(*this); - } - double pressure() const - { - return getMethod("get_Pressure", fnGet_Pressure).invoke(*this); - } - double humidity() const - { - return getMethod("get_Humidity", fnGet_Humidity).invoke(*this); - } -public slots: - void startPolling() { - getMethod("StartPolling", fnStartPolling).invoke(*this); - } - void stopPolling() { - getMethod("StopPolling", fnStopPolling).invoke(*this); - } -signals: - void temperatureChanged(); - void pressureChanged(); - void humidityChanged(); -private: - void handleEvent(const QString& evName, QDotNetObject& evSrc, QDotNetObject& evArgs) override - { - if (evName == "PropertyChanged") { - if (evArgs.type().fullName() == QDotNetPropertyEvent::FullyQualifiedTypeName) { - auto propertyChangedEvent = evArgs.cast<QDotNetPropertyEvent>(); - if (propertyChangedEvent.propertyName() == "Temperature") - emit temperatureChanged(); - else if (propertyChangedEvent.propertyName() == "Pressure") - emit pressureChanged(); - else if (propertyChangedEvent.propertyName() == "Humidity") - emit humidityChanged(); - } - } - } - mutable QDotNetFunction<double> fnGet_Temperature; - mutable QDotNetFunction<double> fnGet_Pressure; - mutable QDotNetFunction<double> fnGet_Humidity; - mutable QDotNetFunction<void> fnStartPolling; - mutable QDotNetFunction<void> fnStopPolling; -}; - -class CardReader : public QObject, public QDotNetObject, public QDotNetObject::IEventHandler -{ - Q_OBJECT - Q_PROPERTY(bool cardInReader READ cardInReader NOTIFY cardInReaderChanged) -public: - Q_DOTNET_OBJECT_INLINE(CardReader, "QtAzureIoT.Device.CardReader, CardReader"); - CardReader() : QDotNetObject(getConstructor<CardReader>().invoke(nullptr)) - { - subscribeEvent("PropertyChanged", this); - } - bool cardInReader() const - { - return getMethod("get_CardInReader", fnGet_CardInReader).invoke(*this); - } -public slots: - void startPolling() { - getMethod("StartPolling", fnStartPolling).invoke(*this); - } - void stopPolling() { - getMethod("StopPolling", fnStopPolling).invoke(*this); - } -signals: - void cardInReaderChanged(); -private: - void handleEvent(const QString& evName, QDotNetObject& evSrc, QDotNetObject& evArgs) override - { - if (evName == "PropertyChanged") { - if (evArgs.type().fullName() == QDotNetPropertyEvent::FullyQualifiedTypeName) { - auto propertyChangedEvent = evArgs.cast<QDotNetPropertyEvent>(); - if (propertyChangedEvent.propertyName() == "CardInReader") - emit cardInReaderChanged(); - } - } - } - mutable QDotNetFunction<bool> fnGet_CardInReader; - mutable QDotNetFunction<void> fnStartPolling; - mutable QDotNetFunction<void> fnStopPolling; -}; - -int main(int argc, char* argv[]) -{ - QGuiApplication app(argc, argv); - QQmlApplicationEngine engine; - - CardReader card; - card.startPolling(); - engine.rootContext()->setContextProperty("card", &card); - - SensorData sensor; - sensor.startPolling(); - engine.rootContext()->setContextProperty("sensor", &sensor); - - Backoffice backoffice; - QObject::connect(&card, &CardReader::cardInReaderChanged, - [&backoffice, &card]() - { - backoffice.setTelemetry("card", card.cardInReader()); - }); - - QObject::connect(&sensor, &SensorData::temperatureChanged, - [&backoffice, &sensor]() - { - backoffice.setTelemetry("temperature", sensor.temperature()); - }); - - QObject::connect(&sensor, &SensorData::pressureChanged, - [&backoffice, &sensor]() - { - backoffice.setTelemetry("pressure", sensor.pressure()); - }); - - QObject::connect(&sensor, &SensorData::humidityChanged, - [&backoffice, &sensor]() - { - backoffice.setTelemetry("humidity", sensor.humidity()); - }); - backoffice.startPolling(); - - engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); - if (engine.rootObjects().isEmpty()) - return -1; - - return app.exec(); -} - -#include "main.moc" diff --git a/examples/QtAzureIoT/device/deviceapp/main.qml b/examples/QtAzureIoT/device/deviceapp/main.qml deleted file mode 100644 index 368ce0c..0000000 --- a/examples/QtAzureIoT/device/deviceapp/main.qml +++ /dev/null @@ -1,56 +0,0 @@ -/*************************************************************************************************** - Copyright (C) 2023 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -***************************************************************************************************/ - -import QtQml -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Shapes - -Window { - visible: true - width: 800 - height: 480 - flags: Qt.FramelessWindowHint - color: "black" - GridLayout { - anchors.fill: parent - columns: 2 - Text { - Layout.alignment: Qt.AlignCenter - horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter - font { bold: true; pointSize: 32 } - color: "white" - text: sensor.temperature.toFixed(2) + " deg.C." - } - Text { - Layout.alignment: Qt.AlignCenter - horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter - font { bold: true; pointSize: 32 } - color: "white" - text: card.cardInReader ? "CARD DETECTED" : "No card"; - } - Text { - Layout.alignment: Qt.AlignCenter - horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter - font { bold: true; pointSize: 32 } - color: "white" - text: sensor.pressure.toFixed(2) + " hPa." - } - Text { - Layout.alignment: Qt.AlignCenter - horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter - font { bold: true; pointSize: 32 } - color: "white" - text: sensor.humidity.toFixed(2) + " %" - } - } - Button { - text: "EXIT" - onClicked: Qt.exit(0) - anchors.right: parent.right - anchors.bottom: parent.bottom - } -} diff --git a/examples/QtAzureIoT/device/deviceapp/qml.qrc b/examples/QtAzureIoT/device/deviceapp/qml.qrc deleted file mode 100644 index 5f6483a..0000000 --- a/examples/QtAzureIoT/device/deviceapp/qml.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>main.qml</file> - </qresource> -</RCC> diff --git a/include/iqmodelindex.h b/include/iqmodelindex.h new file mode 100644 index 0000000..84dd309 --- /dev/null +++ b/include/iqmodelindex.h @@ -0,0 +1,67 @@ +/*************************************************************************************************** + Copyright (C) 2024 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#pragma once + +#include "qdotnetinterface.h" + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif +#include <QModelIndex> +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +#include <functional> + +struct IQModelIndex : public QDotNetNativeInterface<QModelIndex> +{ + static inline const QString &AssemblyQualifiedName = + QStringLiteral("Qt.DotNet.IQModelIndex, Qt.DotNet.Adapter"); + + IQModelIndex(const void *objectRef = nullptr) + : QDotNetNativeInterface<QModelIndex>(objectRef) + { + } + + IQModelIndex(const QModelIndex &idx) + : QDotNetNativeInterface<QModelIndex>(AssemblyQualifiedName, new QModelIndex(idx), true) + { + init(); + } + + IQModelIndex(bool doCleanUp) + : QDotNetNativeInterface<QModelIndex>(AssemblyQualifiedName, new QModelIndex(), doCleanUp) + { + init(); + } + + void init() { + setCallback<bool>("IsValid", [this](void *data) + { + return reinterpret_cast<QModelIndex *>(data)->isValid(); + }); + setCallback<int>("Column", [this](void *data) + { + return reinterpret_cast<QModelIndex *>(data)->column(); + }); + setCallback<int>("Row", [this](void *data) + { + return reinterpret_cast<QModelIndex *>(data)->row(); + }); + setCallback<void *>("InternalPointer", [this](void *data) + { + return reinterpret_cast<QModelIndex *>(data)->internalPointer(); + }); + } + + static void staticInit(QDotNetInterface *sta) + { + sta->setCallback<IQModelIndex>("QModelIndex_Create", + [](void *) { return IQModelIndex(true); }); + } +}; diff --git a/include/iqvariant.h b/include/iqvariant.h new file mode 100644 index 0000000..255b082 --- /dev/null +++ b/include/iqvariant.h @@ -0,0 +1,73 @@ +/*************************************************************************************************** + Copyright (C) 2024 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#pragma once + +#include "qdotnetinterface.h" + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif +#include <QVariant> +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +#include <functional> + +struct IQVariant : public QDotNetNativeInterface<QVariant> +{ + static inline const QString &AssemblyQualifiedName = + QStringLiteral("Qt.DotNet.IQVariant, Qt.DotNet.Adapter"); + + IQVariant(const void *objectRef = nullptr) + : QDotNetNativeInterface<QVariant>(objectRef) + { + } + + IQVariant(QVariant &value, bool doCleanUp = false) + : QDotNetNativeInterface<QVariant>(AssemblyQualifiedName, &value, doCleanUp) + { + init(); + } + + IQVariant(const QString &value, bool doCleanUp = true) + : QDotNetNativeInterface<QVariant>(AssemblyQualifiedName, new QVariant(value), doCleanUp) + { + init(); + } + + IQVariant(bool doCleanUp) + : QDotNetNativeInterface<QVariant>(AssemblyQualifiedName, new QVariant(), doCleanUp) + { + init(); + } + + void init() { + setCallback<QString>("ToStringValue", [this](void *data) + { + QVariant *v = reinterpret_cast<QVariant *>(data); + if (!v) + return QString(); + return v->toString(); + }); + setCallback<void, QString>("SetValue", [this](void *data, const auto &newValue) + { + QVariant *v = reinterpret_cast<QVariant *>(data); + if (!v) + return; + v->setValue(newValue); + }); + } + + static void staticInit(QDotNetInterface *sta) + { + sta->setCallback<IQVariant, QString>("QVariant_Create", + [](void *, QString value) { return IQVariant(value, true); }); + sta->setCallback<IQVariant>("QVariant_Create", + [](void *) { return IQVariant(true); }); + } +}; diff --git a/include/qdotnetabstractlistmodel.h b/include/qdotnetabstractlistmodel.h new file mode 100644 index 0000000..d917d37 --- /dev/null +++ b/include/qdotnetabstractlistmodel.h @@ -0,0 +1,241 @@ +/*************************************************************************************************** + Copyright (C) 2024 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#pragma once + +#include "qdotnetinterface.h" +#include "qdotnetobject.h" +#include "qdotnetarray.h" +#include "iqvariant.h" +#include "iqmodelindex.h" + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif +#include <QAbstractListModel> +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +#include <functional> + +template<typename Base> +struct IQAbstractListModel : public QDotNetNativeInterface<QAbstractListModel> +{ + static inline const QString &AssemblyQualifiedName = + QStringLiteral("Qt.DotNet.IQAbstractListModel, Qt.DotNet.Adapter"); + + IQAbstractListModel(Base *self) + : QDotNetNativeInterface<QAbstractListModel>(AssemblyQualifiedName, self, false) + { + setCallback<int, IQModelIndex>( + "Flags", [this](void *selfPtr, IQModelIndex index) + { + Base &self = *reinterpret_cast<Base *>(selfPtr); + return (int)self.base_flags(index); + }); + setCallback<bool, IQModelIndex, IQVariant, int>( + "SetData", [this](void *selfPtr, IQModelIndex index, IQVariant value, int role) + { + auto *self = reinterpret_cast<Base *>(selfPtr); + return self->base_setData(index, value, role); + }); + setCallback<bool, int, int, IQModelIndex>( + "InsertRows", [this](void *selfPtr, int row, int count, IQModelIndex parent) + { + auto *self = reinterpret_cast<Base *>(selfPtr); + return self->base_insertRows(row, count, parent); + }); + setCallback<bool, int, int, IQModelIndex>( + "RemoveRows", [this](void *selfPtr, int row, int count, IQModelIndex parent) + { + auto *self = reinterpret_cast<Base *>(selfPtr); + return self->base_removeRows(row, count, parent); + }); + setCallback<void, IQModelIndex, int, int>( + "BeginInsertRows", [this](void *selfPtr, IQModelIndex parent, int first, int last) + { + auto *self = reinterpret_cast<Base *>(selfPtr); + self->base_beginInsertRows(parent, first, last); + }); + setCallback<void>( + "EndInsertRows", [this](void *selfPtr) + { + auto *self = reinterpret_cast<Base *>(selfPtr); + self->base_endInsertRows(); + }); + setCallback<void, IQModelIndex, int, int>( + "BeginRemoveRows", [this](void *selfPtr, IQModelIndex parent, int first, int last) + { + auto *self = reinterpret_cast<Base *>(selfPtr); + self->base_beginRemoveRows(parent, first, last); + }); + setCallback<void>( + "EndRemoveRows", [this](void *selfPtr) + { + auto *self = reinterpret_cast<Base *>(selfPtr); + self->base_endRemoveRows(); + }); + setCallback<IQModelIndex, int, int, void *>( + "CreateIndex", [this](void *selfPtr, int row, int col, void *ptr) + { + auto *self = reinterpret_cast<Base *>(selfPtr); + return IQModelIndex(self->base_createIndex(row, col, ptr)); + }); + setCallback<void, IQModelIndex, IQModelIndex, QDotNetArray<int>>( + "EmitDataChanged", [this](void *selfPtr, + IQModelIndex topLeft, IQModelIndex bottomRight, QDotNetArray<int> roles) + { + auto *self = reinterpret_cast<Base *>(selfPtr); + if (roles.isValid()) { + QList<int> listRoles(roles.length()); + for (int i = 0; i < roles.length(); ++i) + listRoles[i] = roles[i]; + self->emit_base_dataChanged(topLeft, bottomRight, listRoles); + } + else { + self->emit_base_dataChanged(topLeft, bottomRight); + } + }); + } +}; + +class QDotNetAbstractListModel : public QAbstractListModel, public QDotNetObject +{ + +public: + using IBase = IQAbstractListModel<QDotNetAbstractListModel>; + Q_DOTNET_OBJECT_REF_INLINE(QDotNetAbstractListModel, Q_DOTNET_OBJECT_INIT(base(this))) + Q_DOTNET_OBJECT_COPY_INLINE(QDotNetAbstractListModel, Q_DOTNET_OBJECT_INIT(base(this))) + + QDotNetAbstractListModel(QDotNetObject &&movSrc) noexcept + : QDotNetObject(std::move(movSrc)), base(this) + {} + + QDotNetAbstractListModel &operator=(QDotNetObject &&movSrc) noexcept + { + QDotNetObject::operator=(std::move(movSrc)); + return *this; + } + + static void staticInit(QDotNetInterface *sta) + { + sta->setCallback<IBase, QDotNetObject>("QAbstractListModel_Create", + [](void *, QDotNetObject self) + { + auto *obj = new QDotNetAbstractListModel(std::move(self)); + return obj->base; + }); + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + return method("RowCount", fnRowCount).invoke(*this, parent); + } + int base_rowCount(const QModelIndex &parent = QModelIndex()) const + { + return QAbstractListModel::rowCount(parent); + } + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + return method("Data", fnData).invoke(*this, index, role); + } + QVariant base_data(const QModelIndex &index, int role = Qt::DisplayRole) const + { + return QAbstractListModel::data(index, role); + } + + Qt::ItemFlags flags(const QModelIndex &index) const override + { + return Qt::ItemFlags::fromInt(method("Flags", fnFlags).invoke(*this, index)); + } + Qt::ItemFlags base_flags(const QModelIndex &index) const + { + return QAbstractListModel::flags(index); + } + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override + { + return method("SetData", fnSetData) + .invoke(*this, index, const_cast<std::remove_const_t<QVariant &>>(value), role); + } + bool base_setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) + { + return QAbstractListModel::setData(index, value, role); + } + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override + { + return method("InsertRows", fnInsertRows).invoke(*this, row, count, parent); + } + bool base_insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) + { + return QAbstractListModel::insertRows(row, count, parent); + } + + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override + { + return method("RemoveRows", fnRemoveRows).invoke(*this, row, count, parent); + } + bool base_removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) + { + return QAbstractListModel::removeRows(row, count, parent); + } + + QHash<int, QByteArray> roleNames() const override + { + auto names = method("RoleNames", fnRoleNames).invoke(*this); + if (names.isEmpty()) + return QAbstractListModel::roleNames(); + auto nameList = names.split(u',', Qt::SkipEmptyParts); + QHash<int, QByteArray> roles; + for (int i = 0; i < nameList.size(); ++i) + roles[Qt::UserRole + i + 1] = nameList[i].toUtf8(); + return roles; + } + + void base_beginInsertRows(const QModelIndex &parent, int first, int last) + { + QAbstractListModel::beginInsertRows(parent, first, last); + } + + void base_endInsertRows() + { + QAbstractListModel::endInsertRows(); + } + + void base_beginRemoveRows(const QModelIndex &parent, int first, int last) + { + QAbstractListModel::beginRemoveRows(parent, first, last); + } + + void base_endRemoveRows() + { + QAbstractListModel::endRemoveRows(); + } + + QModelIndex base_createIndex(int arow, int acolumn, const void *adata) const + { + return QAbstractListModel::createIndex(arow, acolumn, adata); + } + + void emit_base_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QList<int> &roles = QList<int>()) + { + emit QAbstractListModel::dataChanged(topLeft, bottomRight, roles); + } + +protected: + IBase base; + mutable QDotNetFunction<int, IQModelIndex> fnFlags = nullptr; + mutable QDotNetFunction<int, IQModelIndex> fnRowCount = nullptr; + mutable QDotNetFunction<IQVariant, IQModelIndex, int> fnData = nullptr; + mutable QDotNetFunction<bool, IQModelIndex, IQVariant, int> fnSetData = nullptr; + mutable QDotNetFunction<bool, int, int, IQModelIndex> fnInsertRows = nullptr; + mutable QDotNetFunction<bool, int, int, IQModelIndex> fnRemoveRows = nullptr; + mutable QDotNetFunction<QString> fnRoleNames = nullptr; +}; diff --git a/include/qdotnetadapter.h b/include/qdotnetadapter.h index fc9b74e..d9b5b95 100644 --- a/include/qdotnetadapter.h +++ b/include/qdotnetadapter.h @@ -33,6 +33,14 @@ private: ~QDotNetAdapter() { + if (staticInterface && dtor_staticInterface) { + dtor_staticInterface(staticInterface); + staticInterface = nullptr; + } + fnReset(); + //gcCollect(); + //gcWaitForPendingFinalizers(); + defaultHost.unload(); } @@ -53,6 +61,12 @@ public: .filePath(defaultDllName), defaultAssemblyName, defaultTypeName, externalHost); } + static void init(const QString &assemblyPath, const QString &typeAndAssemblyName, + QDotNetHost *externalHost = nullptr) + { + init(assemblyPath, typeAndAssemblyName, typeAndAssemblyName, externalHost); + } + static void init(const QString &assemblyPath, const QString &assemblyName, const QString &typeName, QDotNetHost *externalHost = nullptr) { @@ -92,10 +106,18 @@ public: host->resolveFunction(QDOTNETADAPTER_DELEGATE(SetInterfaceMethod)); host->resolveFunction(QDOTNETADAPTER_DELEGATE(Stats)); host->resolveFunction(QDOTNETADAPTER_DELEGATE(GetObject)); + host->resolveFunction(QDOTNETADAPTER_DELEGATE(Reset)); #undef QDOTNETADAPTER_DELEGATE instance().host = host; + + if (ctor_staticInterface) + instance().staticInterface = ctor_staticInterface(); + gcCollect = instance().resolveStaticMethod("System.GC, System.Runtime", + "Collect", { QDotNetInbound<void>::Parameter }); + gcWaitForPendingFinalizers = instance().resolveStaticMethod("System.GC, System.Runtime", + "WaitForPendingFinalizers", { QDotNetInbound<void>::Parameter }); } static QDotNetAdapter &instance() @@ -225,12 +247,12 @@ public: fnFreeTypeRef(typeName); } - void *addInterfaceProxy(const QString &interfaceName) const + void *addInterfaceProxy(const QString &interfaceName, void *data, void *cleanUp) const { init(); if (interfaceName.isEmpty()) return nullptr; - return fnAddInterfaceProxy(interfaceName); + return fnAddInterfaceProxy(interfaceName, data, cleanUp); } void setInterfaceMethod(const QDotNetRef &obj, const QString &methodName, @@ -261,6 +283,8 @@ public: Stats s{ }; init(); fnStats(&s.refCount, &s.staticCount, &s.eventCount); + if (staticInterface && s.refCount > 0) + --s.refCount; return s; } @@ -287,13 +311,22 @@ private: mutable QDotNetFunction<void, void *> fnFreeDelegateRef; mutable QDotNetFunction<void, QDotNetRef> fnFreeObjectRef; mutable QDotNetFunction<void, QString> fnFreeTypeRef; - mutable QDotNetFunction<void *, QString> fnAddInterfaceProxy; + mutable QDotNetFunction<void *, QString, void *, void *> fnAddInterfaceProxy; mutable QDotNetFunction<void, QDotNetRef, QString, qint32, QList<QDotNetParameter>, void *, void *, void *> fnSetInterfaceMethod; mutable QDotNetFunction<void, qint32 *, qint32 *, qint32 *> fnStats; mutable QDotNetFunction<void *, QDotNetRef, QString> fnGetObject; + mutable QDotNetFunction<void> fnReset; static inline const QString defaultDllName = QLatin1String("Qt.DotNet.Adapter.dll"); static inline const QString defaultAssemblyName = QLatin1String("Qt.DotNet.Adapter"); static inline const QString defaultTypeName = QLatin1String("Qt.DotNet.Adapter"); + + mutable void *staticInterface = nullptr; + +public: + inline static std::function<void *()> ctor_staticInterface = nullptr; + inline static std::function<void(void *)> dtor_staticInterface = nullptr; + inline static QDotNetFunction<void> gcCollect = nullptr; + inline static QDotNetFunction<void> gcWaitForPendingFinalizers = nullptr; }; diff --git a/include/qdotnetarray.h b/include/qdotnetarray.h index 3a0289b..9f97b5f 100644 --- a/include/qdotnetarray.h +++ b/include/qdotnetarray.h @@ -36,9 +36,9 @@ public: QDotNetArray(qint32 length) { const QString elementTypeName = QDotNetTypeOf<T>::TypeName; - const QDotNetType elementType = QDotNetType::find(elementTypeName); + const QDotNetType elementType = QDotNetType::typeOf(elementTypeName); - QDotNetType arrayType = QDotNetType::find(QDotNetArray::FullyQualifiedTypeName); + QDotNetType arrayType = QDotNetType::typeOf(QDotNetArray::AssemblyQualifiedName); auto ctor = constructor<QDotNetArray, qint32>(); *this = ctor(length); } diff --git a/include/qdotnetcallback.h b/include/qdotnetcallback.h index 6efea1e..9f8c150 100644 --- a/include/qdotnetcallback.h +++ b/include/qdotnetcallback.h @@ -18,6 +18,19 @@ #include <functional> +template<typename T> +struct QDotNetCallbackArg : public QDotNetInbound<T> {}; + +template<typename T> +struct QDotNetCallbackReturn : public QDotNetOutbound<T> {}; + +template<> +struct QDotNetCallbackReturn<QString> : public QDotNetOutbound<QString> +{ + using SourceType = QString; + static inline const QDotNetParameter Parameter = QDotNetParameter::String; +}; + class QDotNetCallbackBase { protected: @@ -30,18 +43,18 @@ template<typename TResult, typename... TArg> class QDotNetCallback : public QDotNetCallbackBase { public: - using ReturnType = typename QDotNetInbound<TResult>::TargetType; - using FunctionType = std::function<ReturnType( - typename QDotNetInbound<TArg>::TargetType... arg)>; + using FunctionType = std::function<TResult(void *, TArg... arg)>; + using CleanUpType = std::function<void(TResult *)>; - using OutboundType = typename QDotNetOutbound<TResult>::OutboundType; + using OutboundType = typename QDotNetCallbackReturn<TResult>::OutboundType; using Delegate = OutboundType(QDOTNETFUNCTION_CALLTYPE *)( - QDotNetCallback *callback, quint64 key, typename QDotNetInbound<TArg>::InboundType...); + QDotNetCallback *callback, quint64 key, + void *data, typename QDotNetCallbackArg<TArg>::InboundType...); using CleanUp = void(QDOTNETFUNCTION_CALLTYPE *)(QDotNetCallback *callback, quint64 key); - QDotNetCallback(FunctionType function) - : function(function) + QDotNetCallback(FunctionType fnCallback, CleanUpType fnCleanUp = nullptr) + : fnCallback(fnCallback), fnCleanUp(fnCleanUp) {} ~QDotNetCallback() override = default; @@ -59,64 +72,69 @@ public: private: struct Box { - ReturnType returnValue; + TResult returnValue; + Box(TResult &&ret) : returnValue(std::move(ret)) {} }; QMap<quint64, Box *> boxes; static OutboundType QDOTNETFUNCTION_CALLTYPE callbackDelegate( - QDotNetCallback *callback, quint64 key, typename QDotNetInbound<TArg>::InboundType... arg) + QDotNetCallback *callback, quint64 key, + void *data, typename QDotNetCallbackArg<TArg>::InboundType... arg) { - Box *box = callback->boxes[key] = new Box - { - callback->function(QDotNetInbound<TArg>::convert(arg)...) - }; - const auto result = QDotNetOutbound<TResult>::convert(box->returnValue); - return result; + Box *box = callback->boxes[key] = new Box( + callback->fnCallback(data, QDotNetCallbackArg<TArg>::convert(arg)...)); + return QDotNetCallbackReturn<TResult>::convert(box->returnValue); } static void QDOTNETFUNCTION_CALLTYPE callbackCleanUp(QDotNetCallback *callback, quint64 key) { - if (const Box *box = callback->boxes.take(key)) + if (const Box *box = callback->boxes.take(key)) { + if (callback->fnCleanUp) + callback->fnCleanUp(const_cast<std::remove_const_t<TResult*>>(&(box->returnValue))); delete box; + } } - FunctionType function = nullptr; + FunctionType fnCallback = nullptr; + CleanUpType fnCleanUp = nullptr; }; template<typename... TArg> class QDotNetCallback<void, TArg...> : public QDotNetCallbackBase { public: - using FunctionType = std::function<void(typename QDotNetOutbound<TArg>::SourceType... arg)>; + using FunctionType = std::function<void(void *, TArg... arg)>; + using CleanUpType = nullptr_t; + + using Delegate = void(QDOTNETFUNCTION_CALLTYPE *)( + QDotNetCallback *callback, quint64 key, + void *data, typename QDotNetCallbackArg<TArg>::InboundType...); - QDotNetCallback(FunctionType function) - : function(function) + using CleanUp = nullptr_t; + + QDotNetCallback(FunctionType fnCallback, CleanUpType fnCleanUp = nullptr) + : fnCallback(fnCallback) {} ~QDotNetCallback() override = default; - using Delegate = void(QDOTNETFUNCTION_CALLTYPE *)( - QDotNetCallback *callback, quint64 key, typename QDotNetInbound<TArg>::InboundType...); static Delegate delegate() { return callbackDelegate; } - using CleanUp = void(QDOTNETFUNCTION_CALLTYPE *)(QDotNetCallback *callback, quint64 key); static CleanUp cleanUp() { - return callbackCleanUp; + return nullptr; } private: - static void QDOTNETFUNCTION_CALLTYPE callbackDelegate(QDotNetCallback *callback, quint64 key, - typename QDotNetInbound<TArg>::InboundType... arg) + static void QDOTNETFUNCTION_CALLTYPE callbackDelegate( + QDotNetCallback *callback, quint64 key, + void *data, typename QDotNetCallbackArg<TArg>::InboundType... arg) { - callback->function(QDotNetInbound<TArg>::convert(arg)...); + callback->fnCallback(data, QDotNetCallbackArg<TArg>::convert(arg)...); } - static void QDOTNETFUNCTION_CALLTYPE callbackCleanUp(QDotNetCallback *callback, quint64 key) - {} - - FunctionType function = nullptr; + FunctionType fnCallback = nullptr; }; diff --git a/include/qdotnetdelegate.h b/include/qdotnetdelegate.h new file mode 100644 index 0000000..25fc3a8 --- /dev/null +++ b/include/qdotnetdelegate.h @@ -0,0 +1,37 @@ +/*************************************************************************************************** + Copyright (C) 2025 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#pragma once + +#include "qdotnetobject.h" + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif +#include <QString> +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +template<typename T, typename... TArg> +class QDotNetDelegate : public QDotNetObject +{ +public: + Q_DOTNET_OBJECT_INLINE(QDotNetDelegate, "System.Delegate"); + + T invoke(TArg... arg) const + { + return method("Invoke", fnInvoke).invoke(*this, arg...); + } + + T operator()(TArg... arg) const + { + return invoke(arg...); + } + +private: + mutable QDotNetFunction<T, TArg...> fnInvoke; +}; diff --git a/include/qdotnetevent.h b/include/qdotnetevent.h index 11df0a1..00ea036 100644 --- a/include/qdotnetevent.h +++ b/include/qdotnetevent.h @@ -20,7 +20,8 @@ class QDotNetPropertyEvent : public QDotNetObject { public: - Q_DOTNET_OBJECT_INLINE(QDotNetPropertyEvent, "System.ComponentModel.PropertyChangedEventArgs"); + Q_DOTNET_OBJECT_INLINE(QDotNetPropertyEvent, + "System.ComponentModel.PropertyChangedEventArgs, System.ObjectModel"); QString propertyName() const { diff --git a/include/qdotnethost.h b/include/qdotnethost.h index c2fe298..05b0922 100644 --- a/include/qdotnethost.h +++ b/include/qdotnethost.h @@ -35,7 +35,7 @@ public: unload(); } - bool load(const QString& runtimeConfig = defaultRuntimeConfig, const QString &runtimePath = {}) + bool load(const QString &runtimeConfig = defaultRuntimeConfig, const QString &runtimePath = {}) { if (isLoaded()) return true; @@ -48,6 +48,57 @@ public: return true; } + bool appMain(const QString &appHostPath, const QString &appLibPath) + { + if (isLoaded()) + return false; + + if (!loadRuntime({})) + return false; + const char_t *host_path = STR(appHostPath); + const char_t *app_path = STR(appLibPath); + const char_t *dotnet_root = (char_t *)L"C:\\Program Files\\dotnet\\"; + + int argc = 1; + const char_t *argv[] = { host_path, nullptr }; + + return fnMainStartup(argc, argv, host_path, dotnet_root, app_path) == 0; + } + + bool loadApp(const QString &appPath, const QStringList &args = {}, const QString &runtimePath = {}) + { + if (isLoaded()) + return false; + + if (!loadRuntime(runtimePath)) + return false; + + int argc = 1; + const char_t *argv[] = { STR(appPath), nullptr }; + + auto result = fnInitApp(argc, argv, nullptr, &hostContext); + if (HOSTFN_FAILED(result) || hostContext == nullptr) { + qCritical() << "Error calling function: hostfxr_initialize_for_dotnet_command_line"; + unloadRuntime(); + return false; + } + + setRuntimeProperty("STARTUP_HOOKS", + QDir(QCoreApplication::applicationDirPath()).filePath("Qt.DotNet.Adapter.dll")); + + setRuntimeProperty("QT_DOTNET_RESOLVE_FN", QString("%1") + .arg((qulonglong)(&fnLoadAssemblyAndGetFunctionPointer), 16, 16, QChar('0'))); + + return true; + } + + int runApp() + { + if (!isLoaded() || fnLoadAssemblyAndGetFunctionPointer != nullptr) + return false; + return fnRunApp(hostContext); + } + void unload() { if (!isLoaded()) @@ -61,6 +112,11 @@ public: return (hostContext != nullptr); } + bool isReady() const + { + return (fnLoadAssemblyAndGetFunctionPointer != nullptr); + } + bool resolveFunction(QDotNetFunction<quint32, void *, qint32> &outFunc, const QString &assemblyPath, const QString &typeName, const QString &methodName) { @@ -172,51 +228,49 @@ private: } const QString dotNetInfo(procDotNetInfo.readAllStandardOutput()); - QString runtimeDirPath = {}; - QString hostVersion = {}; - QVersionNumber maxVersion; + QVersionNumber selectedVersion = {}; + QString selectedRuntimePath = {}; const QRegularExpression dotNetInfoParser(regexParseDotNetInfo, QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption); for (const auto &match : dotNetInfoParser.globalMatch(dotNetInfo)) { - const auto version = QVersionNumber::fromString(match.captured("version")); - if (version > maxVersion) { - maxVersion = version; - hostVersion = match.captured("version"); - runtimeDirPath = match.captured("path"); - } - } - - if (runtimeDirPath.isEmpty()) { - qCritical() << "Error parsing dotnet info"; - return {}; - } - QDir runtimeDir(runtimeDirPath); - if (!runtimeDir.exists()) { - qCritical() << "Error dotnet runtime directory not found"; - return {}; - } - - runtimeDir.cd(QString("../../host/fxr/%1").arg(hostVersion)); - if (!runtimeDir.exists()) { - qCritical() << "Error dotnet host fxr directory not found"; - return {}; - } + const auto hostVersion = match.captured("version"); + const auto version = QVersionNumber::fromString(hostVersion); + if (version <= selectedVersion) + continue; + + const auto runtimeDirPath = match.captured("path"); + if (runtimeDirPath.isEmpty()) + continue; + QDir runtimeDir(runtimeDirPath); + if (!runtimeDir.exists()) + continue; + runtimeDir.cd(QString("../../host/fxr/%1").arg(hostVersion)); + if (!runtimeDir.exists()) + continue; #ifdef Q_OS_WINDOWS - QString runtimePath = runtimeDir.absoluteFilePath("hostfxr.dll"); + const auto runtimePath = runtimeDir.absoluteFilePath("hostfxr.dll"); #else - QString runtimePath = runtimeDir.absoluteFilePath("libhostfxr.so"); + const auto runtimePath = runtimeDir.absoluteFilePath("libhostfxr.so"); #endif - if (!QFile::exists(runtimePath)) { - qCritical() << "Error dotnet host fxr dll not found"; + if (!QFile::exists(runtimePath)) + continue; + + selectedVersion = version; + selectedRuntimePath = runtimePath; + } + + if (selectedVersion.isNull()) { + qCritical() << "Error locating runtime host library."; return {}; } - return runtimePath; + + return selectedRuntimePath; } bool loadRuntime(const QString & runtimePath) { - if (fnInitHost != nullptr) + if (fnCloseHost != nullptr) return true; if (!runtimePath.isEmpty()) { @@ -235,45 +289,53 @@ private: return false; } - fnInitHost = GET_FN(runtime, hostfxr_initialize_for_runtime_config_fn); - if (!fnInitHost) { - qCritical() << "Error loading function: hostfxr_initialize_for_runtime_config"; + if (!(fnMainStartup = GET_FN(runtime, hostfxr_main_startupinfo_fn))) { + qCritical() << "Error loading function: hostfxr_main_startupinfo"; return false; } - fnGetRuntimeDelegate = GET_FN(runtime, hostfxr_get_runtime_delegate_fn); - if (!fnGetRuntimeDelegate) { - qCritical() << "Error loading function: hostfxr_get_runtime_delegate"; + if (!(fnSetErrorWriter = GET_FN(runtime, hostfxr_set_error_writer_fn))) { + qCritical() << "Error loading function: hostfxr_set_error_writer"; return false; } - fnCloseHost = GET_FN(runtime, hostfxr_close_fn); - if (!fnCloseHost) { - qCritical() << "Error loading function: hostfxr_close"; + if (!(fnInitApp = GET_FN(runtime, hostfxr_initialize_for_dotnet_command_line_fn))) { + qCritical() << "Error loading function: hostfxr_initialize_for_dotnet_command_line"; return false; } - fnSetErrorWriter = GET_FN(runtime, hostfxr_set_error_writer_fn); - if (!fnSetErrorWriter) { - qCritical() << "Error loading function: hostfxr_set_error_writer"; + if (!(fnInitHost = GET_FN(runtime, hostfxr_initialize_for_runtime_config_fn))) { + qCritical() << "Error loading function: hostfxr_initialize_for_runtime_config"; + return false; + } + + if (!(fnRuntimeProperty = GET_FN(runtime, hostfxr_get_runtime_property_value_fn))) { + qCritical() << "Error loading function: hostfxr_get_runtime_property_value_fn"; + return false; + } + + if (!(fnSetRuntimeProperty = GET_FN(runtime, hostfxr_set_runtime_property_value_fn))) { + qCritical() << "Error loading function: hostfxr_set_runtime_property_value_fn"; return false; } - fnAllRuntimeProperties = GET_FN(runtime, hostfxr_get_runtime_properties_fn); - if (!fnAllRuntimeProperties) { + if (!(fnAllRuntimeProperties = GET_FN(runtime, hostfxr_get_runtime_properties_fn))) { qCritical() << "Error loading function: hostfxr_get_runtime_properties_fn"; return false; } - fnRuntimeProperty = GET_FN(runtime, hostfxr_get_runtime_property_value_fn); - if (!fnRuntimeProperty) { - qCritical() << "Error loading function: hostfxr_get_runtime_property_value_fn"; + if (!(fnRunApp = GET_FN(runtime, hostfxr_run_app_fn))) { + qCritical() << "Error loading function: hostfxr_run_app"; return false; } - fnSetRuntimeProperty = GET_FN(runtime, hostfxr_set_runtime_property_value_fn); - if (!fnSetRuntimeProperty) { - qCritical() << "Error loading function: hostfxr_set_runtime_property_value_fn"; + if (!(fnGetRuntimeDelegate = GET_FN(runtime, hostfxr_get_runtime_delegate_fn))) { + qCritical() << "Error loading function: hostfxr_get_runtime_delegate"; + return false; + } + + if (!(fnCloseHost = GET_FN(runtime, hostfxr_close_fn))) { + qCritical() << "Error loading function: hostfxr_close"; return false; } @@ -283,17 +345,26 @@ private: void unloadRuntime() { - runtime.unload(); + fnSetErrorWriter = nullptr; + fnInitApp = nullptr; fnInitHost = nullptr; - fnGetRuntimeDelegate = nullptr; - fnCloseHost = nullptr; - fnAllRuntimeProperties = nullptr; fnRuntimeProperty = nullptr; fnSetRuntimeProperty = nullptr; + fnAllRuntimeProperties = nullptr; + fnRunApp = nullptr; + fnGetRuntimeDelegate = nullptr; + fnCloseHost = nullptr; + fnLoadAssemblyAndGetFunctionPointer = nullptr; + fnLoadAssembly = nullptr; + fnGetFunctionPointer = nullptr; + runtime.unload(); } bool init(const QString &runtimeConfig) { + if (fnLoadAssemblyAndGetFunctionPointer) + return true; + if (fnInitHost == nullptr) return false; @@ -365,11 +436,11 @@ private: static inline const QString defaultRuntimeConfig = QStringLiteral(R"[json]( { "runtimeOptions": { - "tfm": "net6.0", + "tfm": "net8.0", "rollForward": "LatestMinor", "framework": { "name": "Microsoft.NETCore.App", - "version": "6.0.0" + "version": "8.0.0" } } } @@ -379,16 +450,24 @@ private: \bMicrosoft\.NETCore\.App[^0-9]*(?<version>[0-9\.]+)[^\[]*\[(?<path>[^\]]+)\] )[regex]").remove('\r').remove('\n'); + static inline const QString regexParseDotNetRoot = QStringLiteral(R"[regex]( +^(?:(?![\\\/]host[\\\/]).)*[\\\/] +)[regex]").remove('\r').remove('\n'); QLibrary runtime; - hostfxr_initialize_for_runtime_config_fn fnInitHost = nullptr; - hostfxr_get_runtime_delegate_fn fnGetRuntimeDelegate = nullptr; - hostfxr_close_fn fnCloseHost = nullptr; + hostfxr_main_startupinfo_fn fnMainStartup = nullptr; hostfxr_set_error_writer_fn fnSetErrorWriter = nullptr; - hostfxr_get_runtime_properties_fn fnAllRuntimeProperties = nullptr; + hostfxr_initialize_for_dotnet_command_line_fn fnInitApp = nullptr; + hostfxr_initialize_for_runtime_config_fn fnInitHost = nullptr; hostfxr_get_runtime_property_value_fn fnRuntimeProperty = nullptr; hostfxr_set_runtime_property_value_fn fnSetRuntimeProperty = nullptr; + hostfxr_get_runtime_properties_fn fnAllRuntimeProperties = nullptr; + hostfxr_run_app_fn fnRunApp = nullptr; + hostfxr_get_runtime_delegate_fn fnGetRuntimeDelegate = nullptr; + hostfxr_close_fn fnCloseHost = nullptr; hostfxr_handle hostContext = nullptr; load_assembly_and_get_function_pointer_fn fnLoadAssemblyAndGetFunctionPointer = nullptr; + load_assembly_fn fnLoadAssembly = nullptr; + get_function_pointer_fn fnGetFunctionPointer = nullptr; }; diff --git a/include/qdotnethostfxr.h b/include/qdotnethostfxr.h index e5e1d0c..dc57ce0 100644 --- a/include/qdotnethostfxr.h +++ b/include/qdotnethostfxr.h @@ -116,6 +116,13 @@ using get_hostfxr_path_fn = quint32(NETHOST_CALLTYPE *)( # define HOSTFXR_CALLTYPE #endif +using hostfxr_main_startupinfo_fn = int(HOSTFXR_CALLTYPE *)( + const int argc, + const char_t **argv, + const char_t *host_path, + const char_t *dotnet_root, + const char_t *app_path); + enum hostfxr_delegate_type { hdt_com_activation, @@ -124,7 +131,9 @@ enum hostfxr_delegate_type hdt_com_register, hdt_com_unregister, hdt_load_assembly_and_get_function_pointer, - hdt_get_function_pointer + hdt_get_function_pointer, + hdt_load_assembly, + hdt_load_assembly_bytes }; using hostfxr_error_writer_fn = void(HOSTFXR_CALLTYPE *)(const char_t *message); @@ -140,6 +149,13 @@ struct hostfxr_initialize_parameters const char_t *dotnet_root; }; +using hostfxr_initialize_for_dotnet_command_line_fn = int(HOSTFXR_CALLTYPE *)( + int argc, + const char_t *argv[], + const hostfxr_initialize_parameters *parameters, + /*out*/ hostfxr_handle *host_context_handle +); + using hostfxr_initialize_for_runtime_config_fn = quint32(HOSTFXR_CALLTYPE *)( const char_t *runtime_config_path, const hostfxr_initialize_parameters *parameters, @@ -161,6 +177,8 @@ using hostfxr_get_runtime_properties_fn = quint32(HOSTFXR_CALLTYPE *)( /*out*/ const char_t **keys, /*out*/ const char_t **values); +using hostfxr_run_app_fn = int (HOSTFXR_CALLTYPE *)(const hostfxr_handle host_context_handle); + using hostfxr_get_runtime_delegate_fn = quint32(HOSTFXR_CALLTYPE *)( hostfxr_handle host_context_handle, hostfxr_delegate_type type, @@ -168,7 +186,6 @@ using hostfxr_get_runtime_delegate_fn = quint32(HOSTFXR_CALLTYPE *)( using hostfxr_close_fn = quint32(HOSTFXR_CALLTYPE *)(hostfxr_handle host_context_handle); - /* adapted from: https://github.com/dotnet/runtime/blob/main/src/native/corehost/coreclr_delegates.h @@ -179,25 +196,26 @@ using hostfxr_close_fn = quint32(HOSTFXR_CALLTYPE *)(hostfxr_handle host_context # define CORECLR_DELEGATE_CALLTYPE #endif -using component_entry_point_fn = quint32(CORECLR_DELEGATE_CALLTYPE *)( - void *arg, qint32 arg_size_in_bytes); - using load_assembly_and_get_function_pointer_fn = quint32(CORECLR_DELEGATE_CALLTYPE *)( const char_t *assembly_path , const char_t *type_name , const char_t *method_name , const char_t *delegate_type_name, - void *reserved, + nullptr_t reserved, /*out*/ void **delegate ); using get_function_pointer_fn = quint32(CORECLR_DELEGATE_CALLTYPE *)( const char_t *type_name, const char_t *method_name, const char_t *delegate_type_name, - void *load_context, - void *reserved, + nullptr_t load_context, + nullptr_t reserved, /*out*/ void **delegate); +using load_assembly_fn = int (CORECLR_DELEGATE_CALLTYPE *)( + const char_t *assembly_path, + nullptr_t load_context, + nullptr_t reserved); /* adapted from: diff --git a/include/qdotnetinterface.h b/include/qdotnetinterface.h index 4dae61c..c6850e3 100644 --- a/include/qdotnetinterface.h +++ b/include/qdotnetinterface.h @@ -21,44 +21,71 @@ class QDotNetInterface : public QDotNetRef { public: - QDotNetInterface(const QString &interfaceName) - : QDotNetRef(adapter().addInterfaceProxy(interfaceName)) + QDotNetInterface(const QString &interfaceName, void *data = nullptr, void *cleanUp = nullptr) + : QDotNetRef(adapter().addInterfaceProxy(interfaceName, data, cleanUp)) {} - template<typename TResult, typename... TArg> - void setCallback(const QString &methodName, const QList<QDotNetParameter> ¶ms, - typename QDotNetCallback<TResult, TArg...>::FunctionType function) + QDotNetInterface(const void *objectRef = nullptr) + : QDotNetRef(objectRef) + {} + + QDotNetInterface(const QDotNetInterface &cpySrc) + : QDotNetRef(cpySrc) + {} + + QDotNetInterface &operator =(const QDotNetInterface &cpySrc) { - auto *callback = new QDotNetCallback<TResult, TArg...>(function); - callbacks.append(callback); + QDotNetRef::operator=(cpySrc); + return *this; + } - QList<QDotNetParameter> modifiedParams - { - params[0], - UnmanagedType::SysInt, - UnmanagedType::U8 - }; - for (qsizetype i = 1; i < params.size(); ++i) - modifiedParams.append(params[i]); + QDotNetInterface(QDotNetInterface &&movSrc) noexcept + : QDotNetRef(std::move(movSrc)) + {} - adapter().setInterfaceMethod(*this, methodName, modifiedParams, - reinterpret_cast<void *>(callback->delegate()), - reinterpret_cast<void *>(callback->cleanUp()), callback); + QDotNetInterface &operator=(QDotNetInterface &&movSrc) noexcept + { + QDotNetRef::operator=(std::move(movSrc)); + return *this; + } + + template<typename T> + T *dataAs() + { + if (!fnDataPtr.isValid()) { + const QList<QDotNetParameter> parameters + { + QDotNetInbound<void *>::Parameter + }; + fnDataPtr = adapter().resolveInstanceMethod(*this, "get_Data", parameters); + } + return reinterpret_cast<T *>(fnDataPtr()); + } + + virtual ~QDotNetInterface() override + { + if (!isValid()) + return; + for (const QDotNetCallbackBase *callback : callbacks) + delete callback; + callbacks.clear(); } template<typename TResult, typename... TArg> void setCallback(const QString &methodName, - typename QDotNetCallback<TResult, TArg...>::FunctionType function) + typename QDotNetCallback<TResult, TArg...>::FunctionType function, + typename QDotNetCallback<TResult, TArg...>::CleanUpType cleanUp = nullptr) { - auto *callback = new QDotNetCallback<TResult, TArg...>(function); + auto *callback = new QDotNetCallback<TResult, TArg...>(function, cleanUp); callbacks.append(callback); const QList<QDotNetParameter> parameters { - QDotNetInbound<TResult>::Parameter, + QDotNetCallbackReturn<TResult>::Parameter, UnmanagedType::SysInt, UnmanagedType::U8, - QDotNetInbound<TArg>::Parameter... + UnmanagedType::SysInt, + QDotNetCallbackArg<TArg>::Parameter... }; adapter().setInterfaceMethod( @@ -66,20 +93,44 @@ public: callback->delegate(), callback->cleanUp(), callback); } - ~QDotNetInterface() override - { - for (const QDotNetCallbackBase *callback : callbacks) - delete callback; - callbacks.clear(); - } - private: QList<QDotNetCallbackBase *> callbacks; + QDotNetFunction<void *> fnDataPtr = nullptr; }; template<typename T> struct QDotNetTypeOf<T, std::enable_if_t<std::is_base_of_v<QDotNetInterface, T>>> { - static inline const QString TypeName = T::FullyQualifiedTypeName; + static inline const QString TypeName = T::AssemblyQualifiedName; static inline UnmanagedType MarshalAs = UnmanagedType::ObjectRef; }; + +template<typename T> +struct QDotNetNativeInterface : public QDotNetInterface +{ + QDotNetNativeInterface(const void *objectRef = nullptr) + : QDotNetInterface(objectRef) + { + } + + QDotNetNativeInterface(const QString &interfaceName, T *data, bool doCleanUp = true) + : QDotNetInterface(interfaceName, data, doCleanUp ? cleanUp : nullptr) + { + } + + T *data() + { + return dataAs<T>(); + } + + operator T&() + { + return *data(); + } + + static void QDOTNETFUNCTION_CALLTYPE cleanUp(void *data) + { + if (data) + delete reinterpret_cast<T *>(data); + } +}; diff --git a/include/qdotnetobject.h b/include/qdotnetobject.h index 34dbc3c..5657c6f 100644 --- a/include/qdotnetobject.h +++ b/include/qdotnetobject.h @@ -17,6 +17,8 @@ # pragma GCC diagnostic pop #endif +struct QDotNetEventHandler; + class QDotNetObject : public QDotNetRef { private: @@ -37,7 +39,7 @@ private: // Fully qualified .NET class name #define Q_DOTNET_OBJECT_TYPE(T,type_name)\ - static inline const QString &FullyQualifiedTypeName = QString(type_name) + static inline const QString &AssemblyQualifiedName = QString(type_name) // All required declarations #define Q_DOTNET_OBJECT(T,type_name)\ @@ -125,7 +127,7 @@ private: #define Q_DOTNET_OBJECT_INIT(...) , __VA_ARGS__ public: - static inline const QString &FullyQualifiedTypeName = QStringLiteral("System.Object"); + static inline const QString &AssemblyQualifiedName = QStringLiteral("System.Object"); QDotNetObject(const void *objectRef = nullptr) : QDotNetRef(objectRef) @@ -151,6 +153,8 @@ public: return *this; } + virtual ~QDotNetObject() override = default; + const QDotNetType &type() const { if (!fnGetType.isValid()) { @@ -160,16 +164,6 @@ public: return objType; } - QString toString() const - { - return method("ToString", fnToString).invoke(*this); - } - - bool equals(const QDotNetRef &obj) const - { - return method("Equals", fnEquals).invoke(*this, obj); - } - template<typename TResult, typename ...TArg> QDotNetFunction<TResult, TArg...> method(const QString &methodName) const { @@ -243,19 +237,12 @@ public: return QDotNetType::constructor(typeName, ctor); } - struct IEventHandler - { - virtual ~IEventHandler() = default; - virtual void handleEvent(const QString &eventName, QDotNetObject &eventSource, - QDotNetObject &eventArgs) = 0; - }; - - void subscribeEvent(const QString &eventName, IEventHandler *eventHandler) + void subscribe(const QString &eventName, QDotNetEventHandler *eventHandler) { adapter().addEventHandler(*this, eventName, eventHandler, eventCallback); } - void unsubscribeEvent(const QString &eventName, IEventHandler *eventHandler) + void unsubscribe(const QString &eventName, QDotNetEventHandler *eventHandler) { adapter().removeEventHandler(*this, eventName, eventHandler); } @@ -281,31 +268,24 @@ protected: template<typename T, typename ...TArg> static QDotNetFunction<T, TArg...> constructor() { - return QDotNetType::constructor<T, TArg...>(T::FullyQualifiedTypeName); + return QDotNetType::constructor<T, TArg...>(T::AssemblyQualifiedName); } template<typename T, typename ...TArg> static QDotNetFunction<T, TArg...> &constructor(QDotNetFunction<T, TArg...> &ctor) { - return QDotNetType::constructor(T::FullyQualifiedTypeName, ctor); + return QDotNetType::constructor(T::AssemblyQualifiedName, ctor); } template<typename T, typename ...TArg> static QDotNetSafeMethod<T, TArg...> &constructor(QDotNetSafeMethod<T, TArg...> &ctor) { - return QDotNetType::constructor(T::FullyQualifiedTypeName, ctor); + return QDotNetType::constructor(T::AssemblyQualifiedName, ctor); } private: - static void QDOTNETFUNCTION_CALLTYPE eventCallback(void *context, void *eventNameChars, - void *eventSourceRef, void *eventArgsRef) - { - auto *receiver = static_cast<IEventHandler *>(context); - const QString eventName(static_cast<const QChar *>(eventNameChars)); - QDotNetObject eventSource(eventSourceRef); - QDotNetObject eventArgs(eventArgsRef); - receiver->handleEvent(eventName, eventSource, eventArgs); - } + static inline void QDOTNETFUNCTION_CALLTYPE eventCallback(void *context, void *eventNameChars, + void *eventSourceRef, void *eventArgsRef); mutable QDotNetFunction<QDotNetType> fnGetType; mutable QDotNetType objType = nullptr; @@ -313,9 +293,26 @@ private: mutable QDotNetFunction<bool, QDotNetRef> fnEquals; }; +struct QDotNetEventHandler +{ + virtual ~QDotNetEventHandler() = default; + virtual void handleEvent(const QString &eventName, QDotNetObject &eventSource, + QDotNetObject &eventArgs) = 0; +}; + +inline void QDOTNETFUNCTION_CALLTYPE QDotNetObject::eventCallback(void *context, void *eventNameChars, + void *eventSourceRef, void *eventArgsRef) +{ + auto *receiver = static_cast<QDotNetEventHandler *>(context); + const QString eventName(static_cast<const QChar *>(eventNameChars)); + QDotNetObject eventSource(eventSourceRef); + QDotNetObject eventArgs(eventArgsRef); + receiver->handleEvent(eventName, eventSource, eventArgs); +} + template<typename T> struct QDotNetTypeOf<T, std::enable_if_t<std::is_base_of_v<QDotNetObject, T>>> { - static inline const QString TypeName = T::FullyQualifiedTypeName; + static inline const QString TypeName = T::AssemblyQualifiedName; static inline UnmanagedType MarshalAs = UnmanagedType::ObjectRef; }; diff --git a/include/qdotnetref.h b/include/qdotnetref.h index fff2e06..06ce02a 100644 --- a/include/qdotnetref.h +++ b/include/qdotnetref.h @@ -10,7 +10,7 @@ class QDotNetRef { public: - static inline const QString &FullyQualifiedTypeName = QStringLiteral("System.Object"); + static inline const QString &AssemblyQualifiedName = QStringLiteral("System.Object"); const void *gcHandle() const { return objectRef; } bool isValid() const { return gcHandle() != nullptr; } @@ -59,6 +59,32 @@ public: class Null {}; + QString toString() const + { + if (!fnToString.isValid()) { + const QList<QDotNetParameter> parameters + { + QDotNetInbound<QString>::Parameter + }; + fnToString = adapter().resolveInstanceMethod(*this, "ToString", parameters); + } + return fnToString(); + } + + bool equals(const QDotNetRef &obj) const + { + if (!fnEquals.isValid()) { + const QList<QDotNetParameter> parameters + { + QDotNetInbound<bool>::Parameter, + QDotNetOutbound<QDotNetRef>::Parameter + }; + fnEquals = adapter().resolveInstanceMethod(*this, "Equals", parameters); + } + return fnEquals(obj); + } + + protected: static QDotNetAdapter &adapter() { return QDotNetAdapter::instance(); } @@ -93,6 +119,9 @@ private: } const void *objectRef = nullptr; + + mutable QDotNetFunction<QString> fnToString = nullptr; + mutable QDotNetFunction<bool, QDotNetRef> fnEquals = nullptr; }; template<typename T> diff --git a/include/qdotnetstatic.h b/include/qdotnetstatic.h new file mode 100644 index 0000000..7b07147 --- /dev/null +++ b/include/qdotnetstatic.h @@ -0,0 +1,53 @@ +/*************************************************************************************************** + Copyright (C) 2024 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#pragma once + +#include "qdotnetinterface.h" +#include "iqmodelindex.h" +#include "iqvariant.h" +#include "qdotnetabstractlistmodel.h" + +#include <functional> + +class QDotNetStatic : public QDotNetInterface +{ +public: + static inline const QString &AssemblyQualifiedName = + QStringLiteral("Qt.DotNet.Adapter+IStatic, Qt.DotNet.Adapter"); + + QDotNetStatic(const void *objectRef) : QDotNetInterface(objectRef) {} + + QDotNetStatic() : QDotNetInterface(AssemblyQualifiedName, nullptr) + { + IQVariant::staticInit(this); + IQModelIndex::staticInit(this); + QDotNetAbstractListModel::staticInit(this); + } +}; + +inline static bool ctor_static = std::invoke([]() + { + QDotNetAdapter::ctor_staticInterface = []() + { + auto *staticQVariant = new QDotNetStatic(); + auto setStatic = QDotNetType::staticMethod<void, QDotNetStatic>( + "Qt.DotNet.Adapter, Qt.DotNet.Adapter", "set_Static"); + setStatic(*staticQVariant); + return staticQVariant; + }; + return true; + }); + +inline static bool dtor_static = std::invoke([]() + { + QDotNetAdapter::dtor_staticInterface = [](void *that) + { + QtDotNet::call<void, QDotNetStatic>( + "Qt.DotNet.Adapter, Qt.DotNet.Adapter", "set_Static", nullptr); + delete reinterpret_cast<QDotNetStatic *>(that); + }; + return true; + }); diff --git a/include/qdotnettype.h b/include/qdotnettype.h index 4ab99bf..dc3bb90 100644 --- a/include/qdotnettype.h +++ b/include/qdotnettype.h @@ -19,7 +19,7 @@ class QDotNetType : public QDotNetRef { public: - static inline const QString &FullyQualifiedTypeName = QStringLiteral("System.Type"); + static inline const QString &AssemblyQualifiedName = QStringLiteral("System.Type"); QDotNetType(const void *typeRef = nullptr) : QDotNetRef(typeRef) @@ -45,6 +45,18 @@ public: return *this; } + QString assemblyQualifiedName() const + { + if (!isValid()) + return QStringLiteral(""); + if (!fnAssemblyQualifiedName.isValid()) { + fnAssemblyQualifiedName = adapter().resolveInstanceMethod(*this, + "get_AssemblyQualifiedName", { UnmanagedType::LPWStr }); + strFullName = fnAssemblyQualifiedName(); + } + return strFullName; + } + QString fullName() const { if (!isValid()) @@ -57,16 +69,16 @@ public: return strFullName; } - static QDotNetType find(const QString &typeName) + static QDotNetType typeOf(const QString &typeName) { QDotNetFunction<QDotNetType, QString> fnGetType; - return staticMethod(FullyQualifiedTypeName, "GetType", fnGetType).invoke(nullptr, typeName); + return staticMethod(AssemblyQualifiedName, "GetType", fnGetType).invoke(nullptr, typeName); } template<typename T> - static QDotNetType find() + static QDotNetType typeOf() { - return find(T::FullyQualifiedTypeName); + return typeOf(T::AssemblyQualifiedName); } template<typename TResult, typename ...TArg> @@ -102,7 +114,7 @@ public: template<typename TResult, typename ...TArg> QDotNetFunction<TResult, TArg...> staticMethod(const QString &methodName) const { - return staticMethod<TResult, TArg...>(fullName(), methodName); + return staticMethod<TResult, TArg...>(assemblyQualifiedName(), methodName); } template<typename TResult, typename ...TArg> @@ -155,24 +167,24 @@ public: template<typename T, typename ...TArg> QDotNetFunction<T, TArg...> constructor() const { - return constructor<T, TArg...>(fullName()); + return constructor<T, TArg...>(assemblyQualifiedName()); } template<typename T, typename ...TArg> QDotNetFunction<T, TArg...> &constructor(QDotNetFunction<T, TArg...> &ctor) const { - return constructor(fullName(), ctor); + return constructor(assemblyQualifiedName(), ctor); } template<typename T, typename ...TArg> QDotNetFunction<T, TArg...> &constructor(QDotNetSafeMethod<T, TArg...> &ctor) const { - return constructor(fullName(), ctor); + return constructor(assemblyQualifiedName(), ctor); } void freeTypeRef() { - freeTypeRef(fullName()); + freeTypeRef(assemblyQualifiedName()); } static void freeTypeRef(const QString &typeName) @@ -183,12 +195,53 @@ public: template<typename T> static void freeTypeRef() { - freeTypeRef(T::FullyQualifiedTypeName); + freeTypeRef(T::AssemblyQualifiedName); + } + + bool isAssignableFrom(QDotNetType c) const + { + if (!c.isValid()) + return false; + if (!fnIsAssignableFrom.isValid()) { + fnIsAssignableFrom = adapter().resolveInstanceMethod(*this, "IsAssignableFrom", + { UnmanagedType::Bool, QStringLiteral("System.Type") }); + if (!fnIsAssignableFrom.isValid()) + return false; + } + return fnIsAssignableFrom(c); + } + + template<typename T> + bool isAssignableFrom() const + { + return isAssignableFrom(typeOf<T>()); + } + + bool isAssignableTo(QDotNetType c) const + { + if (!c.isValid()) + return false; + if (!fnIsAssignableTo.isValid()) { + fnIsAssignableTo = adapter().resolveInstanceMethod(*this, "IsAssignableTo", + { UnmanagedType::Bool, QStringLiteral("System.Type") }); + if (!fnIsAssignableTo.isValid()) + return false; + } + return fnIsAssignableTo(c); + } + + template<typename T> + bool isAssignableTo() const + { + return isAssignableTo(typeOf<T>()); } private: + mutable QDotNetFunction<QString> fnAssemblyQualifiedName; mutable QDotNetFunction<QString> fnFullName; mutable QString strFullName; + mutable QDotNetFunction<bool, QDotNetRef> fnIsAssignableFrom; + mutable QDotNetFunction<bool, QDotNetRef> fnIsAssignableTo; }; namespace QtDotNet diff --git a/qtdotnet.sln b/qtdotnet.sln index e3bfd88..005b70c 100644 --- a/qtdotnet.sln +++ b/qtdotnet.sln @@ -1,18 +1,16 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33403.182 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QmlApp", "examples\EmbeddedWindow\QmlApp\QmlApp.vcxproj", "{8C7C4962-6AAA-4A90-927A-BC88C782CAAA}" - ProjectSection(ProjectDependencies) = postProject - {B003D2B0-BCAE-4CEE-8A73-413C914716D0} = {B003D2B0-BCAE-4CEE-8A73-413C914716D0} - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "include", "include", "{63F4FEA7-5B8B-4496-988E-B84410C50D42}" ProjectSection(SolutionItems) = preProject + include\iqmodelindex.h = include\iqmodelindex.h + include\iqvariant.h = include\iqvariant.h + include\qdotnetabstractlistmodel.h = include\qdotnetabstractlistmodel.h include\qdotnetadapter.h = include\qdotnetadapter.h include\qdotnetarray.h = include\qdotnetarray.h include\qdotnetcallback.h = include\qdotnetcallback.h + include\qdotnetdelegate.h = include\qdotnetdelegate.h include\qdotnetevent.h = include\qdotnetevent.h include\qdotnetexception.h = include\qdotnetexception.h include\qdotnetfunction.h = include\qdotnetfunction.h @@ -24,6 +22,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "include", "include", "{63F4 include\qdotnetparameter.h = include\qdotnetparameter.h include\qdotnetref.h = include\qdotnetref.h include\qdotnetsafemethod.h = include\qdotnetsafemethod.h + include\qdotnetstatic.h = include\qdotnetstatic.h include\qdotnettype.h = include\qdotnettype.h EndProjectSection EndProject @@ -35,23 +34,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{0F README.md = README.md EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{D6E014F1-7EC9-4005-A8BC-80E0E3491F0B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chronometer", "Chronometer", "{0A2A51C5-E759-420C-9EFA-9AF6A9A4BB83}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EmbeddedWindow", "EmbeddedWindow", "{D63F2ACE-DE7B-4208-B602-5C48F53052B6}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0828626A-BAD8-4E02-99DC-3AAA15073223}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChronometerModel", "examples\Chronometer\Chronometer\ChronometerModel.csproj", "{F56CB23A-BE44-4639-A498-D6882E373AB4}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QmlChronometer", "examples\Chronometer\QmlChronometer\QmlChronometer.vcxproj", "{5A1E5424-CDB1-4776-A9CC-01B2CD57F516}" - ProjectSection(ProjectDependencies) = postProject - {3863807C-2F87-4E27-A9C9-8675645A8DA5} = {3863807C-2F87-4E27-A9C9-8675645A8DA5} - {F56CB23A-BE44-4639-A498-D6882E373AB4} = {F56CB23A-BE44-4639-A498-D6882E373AB4} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "examples\EmbeddedWindow\WpfApp\WpfApp.csproj", "{B003D2B0-BCAE-4CEE-8A73-413C914716D0}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tst_qtdotnet", "tests\tst_qtdotnet\tst_qtdotnet.vcxproj", "{4E317E0F-0565-4B8A-84F3-56ADF18C65AD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Qt.DotNet.Adapter", "src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj", "{3863807C-2F87-4E27-A9C9-8675645A8DA5}" EndProject @@ -59,12 +44,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "includegen", "src\includege EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FooLib", "tests\FooLib\FooLib.csproj", "{45D3DDF3-135B-46CA-B3EE-3537FCFFFBEB}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tst_qtdotnet", "tests\tst_qtdotnet\tst_qtdotnet.vcxproj", "{4E317E0F-0565-4B8A-84F3-56ADF18C65AD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test_Qt.DotNet.Adapter", "tests\Test_Qt.DotNet.Adapter\Test_Qt.DotNet.Adapter.csproj", "{F7D48930-4705-4775-91DA-A948F30D224F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Perf_Qt.DotNet.Adapter", "tests\Perf_Qt.DotNet.Adapter\Perf_Qt.DotNet.Adapter.csproj", "{9E29FE05-FAA4-4440-AE1D-7AF736D6676D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FooConsoleApp", "tests\FooConsoleApp\FooConsoleApp.csproj", "{72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -78,74 +63,6 @@ Global Tests|x86 = Tests|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Debug|Any CPU.ActiveCfg = Debug|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Debug|Any CPU.Build.0 = Debug|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Debug|x64.ActiveCfg = Debug|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Debug|x64.Build.0 = Debug|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Debug|x86.ActiveCfg = Debug|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Debug|x86.Build.0 = Debug|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Release|Any CPU.ActiveCfg = Release|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Release|x64.ActiveCfg = Release|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Release|x64.Build.0 = Release|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Release|x86.ActiveCfg = Release|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Release|x86.Build.0 = Release|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Tests|Any CPU.ActiveCfg = Release|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Tests|Any CPU.Build.0 = Release|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Tests|x64.ActiveCfg = Debug|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Tests|x64.Build.0 = Debug|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Tests|x86.ActiveCfg = Release|x64 - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA}.Tests|x86.Build.0 = Release|x64 - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Debug|x64.ActiveCfg = Debug|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Debug|x64.Build.0 = Debug|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Debug|x86.ActiveCfg = Debug|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Debug|x86.Build.0 = Debug|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Release|x64.ActiveCfg = Release|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Release|x64.Build.0 = Release|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Release|x86.ActiveCfg = Release|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Release|x86.Build.0 = Release|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Tests|Any CPU.ActiveCfg = Release|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Tests|Any CPU.Build.0 = Release|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Tests|x64.ActiveCfg = Debug|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Tests|x64.Build.0 = Debug|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Tests|x86.ActiveCfg = Release|Any CPU - {F56CB23A-BE44-4639-A498-D6882E373AB4}.Tests|x86.Build.0 = Release|Any CPU - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Debug|Any CPU.ActiveCfg = Debug|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Debug|Any CPU.Build.0 = Debug|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Debug|x64.ActiveCfg = Debug|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Debug|x64.Build.0 = Debug|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Debug|x86.ActiveCfg = Debug|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Debug|x86.Build.0 = Debug|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Release|Any CPU.ActiveCfg = Release|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Release|x64.ActiveCfg = Release|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Release|x64.Build.0 = Release|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Release|x86.ActiveCfg = Release|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Release|x86.Build.0 = Release|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Tests|Any CPU.ActiveCfg = Release|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Tests|Any CPU.Build.0 = Release|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Tests|x64.ActiveCfg = Debug|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Tests|x64.Build.0 = Debug|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Tests|x86.ActiveCfg = Release|x64 - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516}.Tests|x86.Build.0 = Release|x64 - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Debug|x64.ActiveCfg = Debug|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Debug|x64.Build.0 = Debug|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Debug|x86.ActiveCfg = Debug|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Debug|x86.Build.0 = Debug|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Release|x64.ActiveCfg = Release|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Release|x64.Build.0 = Release|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Release|x86.ActiveCfg = Release|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Release|x86.Build.0 = Release|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Tests|Any CPU.ActiveCfg = Release|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Tests|Any CPU.Build.0 = Release|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Tests|x64.ActiveCfg = Debug|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Tests|x64.Build.0 = Debug|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Tests|x86.ActiveCfg = Release|Any CPU - {B003D2B0-BCAE-4CEE-8A73-413C914716D0}.Tests|x86.Build.0 = Release|Any CPU {3863807C-2F87-4E27-A9C9-8675645A8DA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3863807C-2F87-4E27-A9C9-8675645A8DA5}.Debug|Any CPU.Build.0 = Debug|Any CPU {3863807C-2F87-4E27-A9C9-8675645A8DA5}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -250,23 +167,36 @@ Global {9E29FE05-FAA4-4440-AE1D-7AF736D6676D}.Tests|x64.Build.0 = Tests|Any CPU {9E29FE05-FAA4-4440-AE1D-7AF736D6676D}.Tests|x86.ActiveCfg = Tests|Any CPU {9E29FE05-FAA4-4440-AE1D-7AF736D6676D}.Tests|x86.Build.0 = Tests|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Debug|x64.ActiveCfg = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Debug|x64.Build.0 = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Debug|x86.ActiveCfg = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Debug|x86.Build.0 = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Release|Any CPU.Build.0 = Release|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Release|x64.ActiveCfg = Release|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Release|x64.Build.0 = Release|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Release|x86.ActiveCfg = Release|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Release|x86.Build.0 = Release|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Tests|Any CPU.ActiveCfg = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Tests|Any CPU.Build.0 = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Tests|x64.ActiveCfg = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Tests|x64.Build.0 = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Tests|x86.ActiveCfg = Debug|Any CPU + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2}.Tests|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {8C7C4962-6AAA-4A90-927A-BC88C782CAAA} = {D63F2ACE-DE7B-4208-B602-5C48F53052B6} - {0A2A51C5-E759-420C-9EFA-9AF6A9A4BB83} = {D6E014F1-7EC9-4005-A8BC-80E0E3491F0B} - {D63F2ACE-DE7B-4208-B602-5C48F53052B6} = {D6E014F1-7EC9-4005-A8BC-80E0E3491F0B} - {F56CB23A-BE44-4639-A498-D6882E373AB4} = {0A2A51C5-E759-420C-9EFA-9AF6A9A4BB83} - {5A1E5424-CDB1-4776-A9CC-01B2CD57F516} = {0A2A51C5-E759-420C-9EFA-9AF6A9A4BB83} - {B003D2B0-BCAE-4CEE-8A73-413C914716D0} = {D63F2ACE-DE7B-4208-B602-5C48F53052B6} {3863807C-2F87-4E27-A9C9-8675645A8DA5} = {41193496-02AE-44FA-9A63-28E7A168A3AC} {2F31204A-2084-4C9D-977E-B1673A7EE6DD} = {63F4FEA7-5B8B-4496-988E-B84410C50D42} {45D3DDF3-135B-46CA-B3EE-3537FCFFFBEB} = {0828626A-BAD8-4E02-99DC-3AAA15073223} {4E317E0F-0565-4B8A-84F3-56ADF18C65AD} = {0828626A-BAD8-4E02-99DC-3AAA15073223} {F7D48930-4705-4775-91DA-A948F30D224F} = {0828626A-BAD8-4E02-99DC-3AAA15073223} {9E29FE05-FAA4-4440-AE1D-7AF736D6676D} = {0828626A-BAD8-4E02-99DC-3AAA15073223} + {72CA8EDA-C9CF-40C2-B7FE-4A529204EEC2} = {0828626A-BAD8-4E02-99DC-3AAA15073223} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E4CDB648-EF1A-4ADE-B6EA-D4E9D668E676} diff --git a/src/Qt.DotNet.Adapter/Qt.DotNet.Adapter.csproj b/src/Qt.DotNet.Adapter/Qt.DotNet.Adapter.csproj index db731eb..5c0114f 100644 --- a/src/Qt.DotNet.Adapter/Qt.DotNet.Adapter.csproj +++ b/src/Qt.DotNet.Adapter/Qt.DotNet.Adapter.csproj @@ -1,4 +1,4 @@ -<!-- +<!-- /*************************************************************************************************** Copyright (C) 2023 The Qt Company Ltd. SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only @@ -8,21 +8,40 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net6.0</TargetFramework> + <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>disable</Nullable> <Configurations>Debug;Release;Tests</Configurations> + <RootNamespace></RootNamespace> </PropertyGroup> <Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Exec Command="MKDIR $(SolutionDir)bin 2> NUL
COPY $(OutputPath)$(AssemblyName).dll $(SolutionDir)bin\$(AssemblyName).dll
" /> </Target> <Target Name="CleanPostBuild" AfterTargets="Clean"> - <Delete Files="$(SolutionDir)bin\$(AssemblyName).dll"/> + <Delete Files="$(SolutionDir)bin\$(AssemblyName).dll" /> </Target> + <ItemGroup> + <None Remove="build\Qt.DotNet.Adapter.targets" /> + </ItemGroup> + <ItemGroup> + <Content Include="build\Qt.DotNet.Adapter.targets"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> + </ItemGroup> <ItemGroup> <ProjectReference Include="..\includegen\includegen.csproj" /> </ItemGroup> + <ItemGroup> + <Folder Include="build\include\" /> + </ItemGroup> + + <ItemGroup> + <Content Include="..\..\include\*" Link="build\include\%(FileName)%(Extension)"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> + </ItemGroup> + </Project> diff --git a/src/Qt.DotNet.Adapter/Adapter.Delegates.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Delegates.cs index c3f2b9d..bb5e716 100644 --- a/src/Qt.DotNet.Adapter/Adapter.Delegates.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Delegates.cs @@ -105,7 +105,9 @@ namespace Qt.DotNet MarshalTypeRef = typeof(ObjectMarshaler))] public delegate InterfaceProxy AddInterfaceProxy( [MarshalAs(UnmanagedType.LPWStr)] - [In] string interfaceName); + [In] string interfaceName, + [In] IntPtr data, + [In] IntPtr cleanUp); [UnmanagedFunctionPointer(CallingConvention.Winapi)] public delegate void SetInterfaceMethod( @@ -125,6 +127,9 @@ namespace Qt.DotNet public delegate IntPtr GetObject ([In] IntPtr objRefPtr, [MarshalAs(UnmanagedType.LPWStr)][In] string path); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate void Reset(); + #if DEBUG || TESTS [UnmanagedFunctionPointer(CallingConvention.Winapi)] public delegate void Stats( diff --git a/src/Qt.DotNet.Adapter/Adapter.Events.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Events.cs index 777121d..777121d 100644 --- a/src/Qt.DotNet.Adapter/Adapter.Events.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Events.cs diff --git a/src/Qt.DotNet.Adapter/Adapter.Methods.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Methods.cs index 47db8a6..47db8a6 100644 --- a/src/Qt.DotNet.Adapter/Adapter.Methods.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Methods.cs diff --git a/src/Qt.DotNet.Adapter/Adapter.Objects.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Objects.cs index 8692ea3..5377d55 100644 --- a/src/Qt.DotNet.Adapter/Adapter.Objects.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Objects.cs @@ -3,6 +3,7 @@ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only ***************************************************************************************************/ +using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; @@ -65,8 +66,11 @@ namespace Qt.DotNet // Compile-time signature check of delegate vs. method _ = new Delegates.FreeObjectRef(FreeObjectRef); #endif - if (!ObjectRefs.TryRemove(objRefPtr, out var objRef)) - throw new ArgumentException("Invalid object reference", nameof(objRefPtr)); + if (!ObjectRefs.TryRemove(objRefPtr, out var objRef)) { + Debug.WriteLine(@$" +ADAPTER::FreeObjectRef: WARNING Invalid object reference: 0x{objRefPtr:x16}"); + return; + } RemoveAllEventHandlers(objRef); var liveObjects = ObjectRefs.Values.Select(x => x.Target).ToList(); diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Static.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Static.cs new file mode 100644 index 0000000..cfc2c96 --- /dev/null +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Static.cs @@ -0,0 +1,16 @@ +/*************************************************************************************************** + Copyright (C) 2024 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +namespace Qt.DotNet +{ + public partial class Adapter + { + public partial interface IStatic + { + } + + public static IStatic Static { get; set; } + } +} diff --git a/src/Qt.DotNet.Adapter/Adapter.Test.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Test.cs index 983e582..983e582 100644 --- a/src/Qt.DotNet.Adapter/Adapter.Test.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.Test.cs diff --git a/src/Qt.DotNet.Adapter/Adapter.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.cs index b26d89e..286c28c 100644 --- a/src/Qt.DotNet.Adapter/Adapter.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Adapter.cs @@ -3,9 +3,71 @@ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only ***************************************************************************************************/ +using System; using System.Collections.Concurrent; +using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; +using System.Runtime.Loader; +using static Qt.DotNet.Adapter; + +internal class StartupHook +{ + private static ConcurrentBag<DelegateRef> Callbacks { get; set; } = new(); + public static void Initialize() + { + if (AppContext.GetData("QT_DOTNET_RESOLVE_FN") is not string refFnPtrHex) + return; + var refFnPtrInt = Convert.ToInt64(refFnPtrHex, 16); + var refFnPtr = new IntPtr(refFnPtrInt); + var fnPtr = Marshal.ReadIntPtr(refFnPtr); + if (fnPtr != IntPtr.Zero) + return; + var delegateType = typeof(ResolveFnDelegate); + var method = typeof(StartupHook).GetMethod("ResolveFn"); + var methodDelegate = Delegate.CreateDelegate(delegateType, method, false); + var methodHandle = GCHandle.Alloc(methodDelegate); + fnPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate); + Callbacks.Add(new DelegateRef(methodHandle, fnPtr)); + Marshal.WriteIntPtr(refFnPtr, fnPtr); + } + + public static int ResolveFn( + IntPtr assemblyPathNative, + IntPtr typeNameNative, + IntPtr methodNameNative, + IntPtr delegateTypeNative, + IntPtr reserved, + IntPtr functionHandle) + { + string assemblyPath = Marshal.PtrToStringUni(assemblyPathNative); + string typeName = Marshal.PtrToStringUni(typeNameNative); + string methodName = Marshal.PtrToStringUni(methodNameNative); + string delegateTypeName = Marshal.PtrToStringUni(delegateTypeNative); + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); + var type = Type.GetType(typeName); + var delegateType = string.IsNullOrEmpty(delegateTypeName) ? typeof(EntryPointDelegate) : Type.GetType(delegateTypeName); + var invoke = delegateType.GetMethod("Invoke"); + var paramTypes = invoke.GetParameters().Select(x => x.ParameterType).ToArray(); + var method = type.GetMethod(methodName, paramTypes); + var methodDelegate = Delegate.CreateDelegate(delegateType, method, false); + var methodHandle = GCHandle.Alloc(methodDelegate); + var fnPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate); + Callbacks.Add(new DelegateRef(methodHandle, fnPtr)); + Marshal.WriteIntPtr(functionHandle, fnPtr); + return 0; + } + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate int ResolveFnDelegate( + IntPtr assemblyPathNative, + IntPtr typeNameNative, + IntPtr methodNameNative, + IntPtr delegateTypeNative, + IntPtr reserved, + IntPtr functionHandle); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate int EntryPointDelegate(IntPtr arg, int argLength); +} namespace Qt.DotNet { @@ -50,6 +112,21 @@ namespace Qt.DotNet return false; } + /// <summary> + /// Reset adapter cache + /// </summary> + public static void Reset() + { +#if DEBUG + // Compile-time signature check of delegate vs. method + _ = new Delegates.Reset(Reset); +#endif + ObjectRefs.Clear(); + DelegateRefs.Clear(); + DelegatesByMethod.Clear(); + Events.Clear(); + } + internal class DelegateRef { public GCHandle Handle { get; } diff --git a/src/Qt.DotNet.Adapter/CodeGenerator.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/CodeGenerator.cs index 5922960..bb97c60 100644 --- a/src/Qt.DotNet.Adapter/CodeGenerator.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/CodeGenerator.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Qt.DotNet @@ -141,6 +142,7 @@ namespace Qt.DotNet .Select(x => x.ParameterType) .ToArray(); var callbackParamTypes = paramTypes + .Prepend(typeof(IntPtr)) .Prepend(typeof(ulong)) .Prepend(typeof(IntPtr)) .ToArray(); @@ -154,17 +156,27 @@ namespace Qt.DotNet TypeAttributes.Sealed | TypeAttributes.NestedPublic, typeof(MulticastDelegate)); var callbackInvoke = InitDelegateType(delegateGen, callbackParameters); +#if TEST || DEBUG + Debug.Assert(callbackInvoke.ReturnType == method.ReturnType); +#endif + var fieldData = typeof(InterfaceProxy).GetField("DataPtr"); - var callbackGen = typeGen.DefineField( + var prototype = typeGen.DefineField( UniqueName(method.Name, "Callback"), delegateGen, FieldAttributes.Public); var nativeCallbackGen = typeGen.DefineField( - $"Native_{callbackGen.Name}", typeof(Delegate), FieldAttributes.Public); + $"Call_{prototype.Name}", typeof(Delegate), FieldAttributes.Public); var cleanUpPtr = typeGen.DefineField( - $"CleanUp_{callbackGen.Name}", typeof(IntPtr), FieldAttributes.Public); + $"CleanUp_{prototype.Name}", typeof(IntPtr), FieldAttributes.Public); var contextGen = typeGen.DefineField( - $"Context_{callbackGen.Name}", typeof(IntPtr), FieldAttributes.Public); + $"Context_{prototype.Name}", typeof(IntPtr), FieldAttributes.Public); var countGen = typeGen.DefineField( - $"Count_{callbackGen.Name}", typeof(ulong), FieldAttributes.Public); + $"Count_{prototype.Name}", typeof(ulong), FieldAttributes.Public); + + var monitorEnter = typeof(Monitor).GetMethod("Enter", new[] { typeof(object) }); + var monitorExit = typeof(Monitor).GetMethod("Exit", new[] { typeof(object) }); +#if TEST || DEBUG + Debug.Assert(monitorEnter != null && monitorExit != null); +#endif var methodGen = typeGen.DefineMethod(method.Name, MethodAttributes.Public @@ -175,53 +187,88 @@ namespace Qt.DotNet method.ReturnType, paramTypes); var code = methodGen.GetILGenerator(); - // ++Count; - code.Emit(OpCodes.Ldarg_0); - code.Emit(OpCodes.Ldarg_0); - code.Emit(OpCodes.Ldfld, countGen); - code.Emit(OpCodes.Ldc_I4_1); - code.Emit(OpCodes.Conv_I8); - code.Emit(OpCodes.Add); - code.Emit(OpCodes.Stfld, countGen); - - // NativeCallback.Invoke( - code.Emit(OpCodes.Ldarg_0); - code.Emit(OpCodes.Ldfld, nativeCallbackGen); - // context, - code.Emit(OpCodes.Ldarg_0); - code.Emit(OpCodes.Ldfld, contextGen); - // count, - code.Emit(OpCodes.Ldarg_0); - code.Emit(OpCodes.Ldfld, countGen); - // Load method call arguments into stack - for (int paramIdx = 0; paramIdx < paramTypes.Length; ++paramIdx) { - if (paramIdx == 0) - code.Emit(OpCodes.Ldarg_1); - else if (paramIdx == 1) - code.Emit(OpCodes.Ldarg_2); - else if (paramIdx == 2) - code.Emit(OpCodes.Ldarg_3); - else - code.Emit(OpCodes.Ldarg_S, paramIdx + 1); + // locals + if (method.ReturnType != typeof(void)) { + // [0] = default; + code.DeclareLocal(method.ReturnType); + code.Emit(OpCodes.Ldloca_S, 0); + code.Emit(OpCodes.Initobj, method.ReturnType); + } + + // try { + { + code.BeginExceptionBlock(); + + // Monitor.Enter(<Callback>); + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, nativeCallbackGen); + code.Emit(OpCodes.Call, monitorEnter); + + // ++Count; + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, countGen); + code.Emit(OpCodes.Ldc_I4_1); + code.Emit(OpCodes.Conv_I8); + code.Emit(OpCodes.Add); + code.Emit(OpCodes.Stfld, countGen); + + // NativeCallback.Invoke( + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, nativeCallbackGen); + // context, + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, contextGen); + // count, + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, countGen); + // data, + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, fieldData); + // Load method call arguments into stack + for (int paramIdx = 0; paramIdx < paramTypes.Length; ++paramIdx) { + if (paramIdx == 0) + code.Emit(OpCodes.Ldarg_1); + else if (paramIdx == 1) + code.Emit(OpCodes.Ldarg_2); + else if (paramIdx == 2) + code.Emit(OpCodes.Ldarg_3); + else + code.Emit(OpCodes.Ldarg_S, paramIdx + 1); + } + // ); //NativeCallback.Invoke + code.Emit(OpCodes.Callvirt, callbackInvoke); + + // [0] = ^^^ + if (method.ReturnType != typeof(void)) + code.Emit(OpCodes.Stloc_0); + + // CleanUp.Invoke( + code.Emit(OpCodes.Ldarg_0); + // cleanUpPtr, + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, cleanUpPtr); + // context, + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, contextGen); + // count, + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, countGen); + // ); //CleanUp.Invoke + code.Emit(OpCodes.Callvirt, interfaceImpl_CleanUp); } - // ); //NativeCallback.Invoke - code.Emit(OpCodes.Callvirt, callbackInvoke); - - // CleanUp.Invoke( - code.Emit(OpCodes.Ldarg_0); - // cleanUpPtr, - code.Emit(OpCodes.Ldarg_0); - code.Emit(OpCodes.Ldfld, cleanUpPtr); - // context, - code.Emit(OpCodes.Ldarg_0); - code.Emit(OpCodes.Ldfld, contextGen); - // count, - code.Emit(OpCodes.Ldarg_0); - code.Emit(OpCodes.Ldfld, countGen); - // ); //CleanUp.Invoke - code.Emit(OpCodes.Callvirt, interfaceImpl_CleanUp); - - // return <ret>; + code.BeginFinallyBlock(); // } finally { + { + // Monitor.Exit(<Callback>); + code.Emit(OpCodes.Ldarg_0); + code.Emit(OpCodes.Ldfld, nativeCallbackGen); + code.Emit(OpCodes.Call, monitorExit); + } + code.EndExceptionBlock(); // } /* try */ + + // return [0]; + if (method.ReturnType != typeof(void)) + code.Emit(OpCodes.Ldloc_0); code.Emit(OpCodes.Ret); // Generate nested type diff --git a/src/Qt.DotNet.Adapter/EventRelay.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/EventRelay.cs index f64b938..f64b938 100644 --- a/src/Qt.DotNet.Adapter/EventRelay.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/EventRelay.cs diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/IQModelIndex.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/IQModelIndex.cs new file mode 100644 index 0000000..9ce0a61 --- /dev/null +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/IQModelIndex.cs @@ -0,0 +1,29 @@ +/*************************************************************************************************** + Copyright (C) 2024 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +namespace Qt.DotNet +{ + public interface IQModelIndex + { + bool IsValid(); + int Row(); + int Column(); + IntPtr InternalPointer(); + } + + public partial class Adapter + { + public partial interface IStatic + { + IQModelIndex QModelIndex_Create(); + } + public static IQModelIndex QModelIndex() => Static.QModelIndex_Create(); + } + + public static class QModelIndex + { + public static IQModelIndex Create() => Adapter.Static?.QModelIndex_Create(); + } +} diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/IQVariant.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/IQVariant.cs new file mode 100644 index 0000000..df5c7fd --- /dev/null +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/IQVariant.cs @@ -0,0 +1,25 @@ +/*************************************************************************************************** + Copyright (C) 2024 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +namespace Qt.DotNet +{ + public interface IQVariant + { + string ToStringValue(); + void SetValue(string value); + } + + public partial class Adapter + { + public partial interface IStatic + { + IQVariant QVariant_Create(); + IQVariant QVariant_Create(string value); + } + public static IQVariant QVariant() => Static.QVariant_Create(); + public static IQVariant QVariant(string value) => Static.QVariant_Create(value); + } + +} diff --git a/src/Qt.DotNet.Adapter/InterfaceProxy.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/InterfaceProxy.cs index ec40e22..f378e0d 100644 --- a/src/Qt.DotNet.Adapter/InterfaceProxy.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/InterfaceProxy.cs @@ -6,23 +6,40 @@ using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; +using System.Text.RegularExpressions; namespace Qt.DotNet { public class InterfaceProxy { public delegate void CleanUpDelegate(IntPtr context, ulong count); - public CleanUpDelegate CleanUpCallback; public void CleanUp(IntPtr callback, IntPtr context, ulong count) { - CleanUpCallback(context, count); + if (callback == IntPtr.Zero) + return; + var CleanUp = Marshal.GetDelegateForFunctionPointer<CleanUpDelegate>(callback); + CleanUp?.Invoke(context, count); + } + + public IntPtr DataPtr = IntPtr.Zero; + public IntPtr Data => DataPtr; + public IntPtr CleanUpData { get; set; } = IntPtr.Zero; + + public delegate void DeleteDelegate(IntPtr data); + + ~InterfaceProxy() + { + if (Data == IntPtr.Zero || CleanUpData == IntPtr.Zero) + return; + Marshal.GetDelegateForFunctionPointer<DeleteDelegate>(CleanUpData)?.Invoke(Data); } } public partial class Adapter { - public static InterfaceProxy AddInterfaceProxy(string interfaceName) + public static InterfaceProxy AddInterfaceProxy(string interfaceName, + IntPtr data, IntPtr cleanUp) { #if DEBUG // Compile-time signature check of delegate vs. method @@ -36,8 +53,12 @@ namespace Qt.DotNet #if DEBUG Debug.Assert(ctor != null, nameof(ctor) + " is null"); #endif - var obj = ctor.Invoke(null); - return obj as InterfaceProxy; + if (ctor.Invoke(null) is not InterfaceProxy proxy) + throw new InvalidOperationException($"Error creating proxy for {interfaceName}"); + + proxy.DataPtr = data; + proxy.CleanUpData = cleanUp; + return proxy; } public static void SetInterfaceMethod( @@ -55,7 +76,7 @@ namespace Qt.DotNet #endif var type = proxy.GetType(); var parameterTypes = parameters - .Skip(3) + .Skip(4) // (return, context, key, data) .Select((x, i) => x.GetParameterType() ?? throw new ArgumentException($"Type not found [{i}]", nameof(parameters))) .ToArray(); @@ -70,33 +91,54 @@ namespace Qt.DotNet Debug.Assert(delegateTypeInvoke != null, nameof(delegateTypeInvoke) + " != null"); #endif var paramTypes = delegateTypeInvoke.GetParameters() - .Select(p => p.ParameterType); - - var fieldDelegate = proxy.GetType().GetFields() - .FirstOrDefault(f => f.FieldType.IsAssignableTo(typeof(Delegate)) - && f.FieldType.GetMethod("Invoke") is MethodInfo invoke - && invoke.GetParameters() - .Zip(paramTypes) - .All(x => x.First.ParameterType == x.Second) - && invoke.ReturnType.IsAssignableTo(delegateTypeInvoke.ReturnType)) - ?? throw new ArgumentException("Signature mismatch", nameof(parameters)); - - var fieldNative = proxy.GetType().GetField($"Native_{fieldDelegate.Name}"); + .Select(p => p.ParameterType) + .ToArray(); + + var callbacks = proxy.GetType().GetFields() + .Where(f => Regex.IsMatch(f.Name, $@"^{methodName}_Callback_\w{{11}}$")) + .ToArray(); + if (!callbacks.Any()) + throw new ArgumentException("Method not found", methodName); + + FieldInfo prototype = null; + foreach (var callback in callbacks) { + bool sigOk = callback.FieldType.IsAssignableTo(typeof(Delegate)); + var callbackInvoke = callback.FieldType.GetMethod("Invoke"); + sigOk = sigOk && callbackInvoke is not null; + if (!sigOk) + continue; + var callbackParams = callbackInvoke.GetParameters(); + sigOk = sigOk && callbackParams is not null; + sigOk = sigOk && callbackParams.Length == paramTypes.Length; + sigOk = sigOk && callbackParams.Zip(paramTypes) + .All(x => x.First.ParameterType == x.Second); + if (!sigOk) + continue; + + prototype = callback; + break; + } + if (prototype is null) + throw new ArgumentException("Signature mismatch", nameof(parameters)); + + var fieldCallback = proxy.GetType().GetField($"Call_{prototype.Name}"); var callbackDelegate = Marshal.GetDelegateForFunctionPointer(callbackPtr, delegateType); #if DEBUG - Debug.Assert(fieldNative != null, nameof(fieldNative) + " is null"); + Debug.Assert(fieldCallback != null, nameof(fieldCallback) + " is null"); #endif - fieldNative.SetValue(proxy, callbackDelegate); + fieldCallback.SetValue(proxy, callbackDelegate); - var fieldContext = proxy.GetType().GetField($"Context_{fieldDelegate.Name}"); + var fieldCleanup = proxy.GetType().GetField($"CleanUp_{prototype.Name}"); +#if DEBUG + Debug.Assert(fieldCleanup != null, nameof(fieldCleanup) + " is null"); +#endif + fieldCleanup.SetValue(proxy, cleanUpPtr); + + var fieldContext = proxy.GetType().GetField($"Context_{prototype.Name}"); #if DEBUG Debug.Assert(fieldContext != null, nameof(fieldContext) + " is null"); #endif fieldContext.SetValue(proxy, context); - - proxy.CleanUpCallback = Marshal.GetDelegateForFunctionPointer( - cleanUpPtr, typeof(InterfaceProxy.CleanUpDelegate)) - as InterfaceProxy.CleanUpDelegate; } } } diff --git a/src/Qt.DotNet.Adapter/ObjectMarshaler.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/ObjectMarshaler.cs index 49c011f..49c011f 100644 --- a/src/Qt.DotNet.Adapter/ObjectMarshaler.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/ObjectMarshaler.cs diff --git a/src/Qt.DotNet.Adapter/Parameter.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/Parameter.cs index 4caa9ad..7c1f4f3 100644 --- a/src/Qt.DotNet.Adapter/Parameter.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/Parameter.cs @@ -96,10 +96,10 @@ namespace Qt.DotNet return type; } - public Parameter(Type type, ulong paramInfo = 0) + public Parameter(Type type) { - TypeName = type.FullName; - ParamInfo = paramInfo; + TypeName = type.AssemblyQualifiedName; + ParamInfo = 0; } public Parameter(string typeName, ulong paramInfo = 0) diff --git a/src/Qt.DotNet.Adapter/Qt/DotNet/QAbstractListModel.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/QAbstractListModel.cs new file mode 100644 index 0000000..98f1a07 --- /dev/null +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/QAbstractListModel.cs @@ -0,0 +1,153 @@ +/*************************************************************************************************** + Copyright (C) 2024 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +namespace Qt.DotNet +{ + public enum ItemDataRole + { + DisplayRole = 0, + DecorationRole = 1, + EditRole = 2, + ToolTipRole = 3, + StatusTipRole = 4, + WhatsThisRole = 5, + FontRole = 6, + TextAlignmentRole = 7, + BackgroundRole = 8, + ForegroundRole = 9, + CheckStateRole = 10, + // Accessibility + AccessibleTextRole = 11, + AccessibleDescriptionRole = 12, + // More general purpose + SizeHintRole = 13, + InitialSortOrderRole = 14, + // Internal UiLib roles. Start worrying when public roles go that high. + DisplayPropertyRole = 27, + DecorationPropertyRole = 28, + ToolTipPropertyRole = 29, + StatusTipPropertyRole = 30, + WhatsThisPropertyRole = 31, + // Reserved + UserRole = 0x0100 + }; + + [Flags] + public enum ItemFlag + { + NoItemFlags = 0, + ItemIsSelectable = 1, + ItemIsEditable = 2, + ItemIsDragEnabled = 4, + ItemIsDropEnabled = 8, + ItemIsUserCheckable = 16, + ItemIsEnabled = 32, + ItemIsAutoTristate = 64, + ItemNeverHasChildren = 128, + ItemIsUserTristate = 256 + }; + + public interface IQAbstractListModel + { + int Flags(IQModelIndex index); + bool SetData(IQModelIndex index, IQVariant value, int role); + + bool InsertRows(int row, int count, IQModelIndex parent); + bool RemoveRows(int row, int count, IQModelIndex parent); + + void BeginInsertRows(IQModelIndex parent, int first, int last); + void EndInsertRows(); + + void BeginRemoveRows(IQModelIndex parent, int first, int last); + void EndRemoveRows(); + + IQModelIndex CreateIndex(int arow, int acolumn, IntPtr adata); + + void EmitDataChanged(IQModelIndex topLeft, IQModelIndex bottomRight, int[] roles); + } + + public abstract class QAbstractListModel + { + public IQAbstractListModel Base { get; private set; } + + public QAbstractListModel() + { + Base = Adapter.Static.QAbstractListModel_Create(this); + } + + public abstract int RowCount(IQModelIndex parent = null); + + public abstract IQVariant Data(IQModelIndex index, + int role = (int)ItemDataRole.DisplayRole); + + public virtual int Flags(IQModelIndex index) + { + if (Base == null) + return 0; + var flags = (ItemFlag)Base.Flags(index); + return (int)flags; + } + + public virtual bool SetData(IQModelIndex index, IQVariant value, + int role = (int)ItemDataRole.EditRole) + { + return Base?.SetData(index, value, role) ?? false; + } + + public virtual bool InsertRows(int row, int count, IQModelIndex parent) + { + return Base?.InsertRows(row, count, parent) ?? false; + } + + public virtual bool RemoveRows(int row, int count, IQModelIndex parent) + { + return Base?.RemoveRows(row, count, parent) ?? false; + } + + public virtual string RoleNames() + { + return string.Empty; + } + + protected void BeginInsertRows(IQModelIndex parent, int first, int last) + { + Base?.BeginInsertRows(parent, first, last); + } + + protected void EndInsertRows() + { + Base?.EndInsertRows(); + } + + protected void BeginRemoveRows(IQModelIndex parent, int first, int last) + { + Base?.BeginRemoveRows(parent, first, last); + } + + protected void EndRemoveRows() + { + Base?.EndRemoveRows(); + } + + protected IQModelIndex CreateIndex(int arow, int acolumn, IntPtr adata) + { + return Base?.CreateIndex(arow, acolumn, adata); + } + + protected void EmitDataChanged(IQModelIndex topLeft, IQModelIndex bottomRight, int[] roles) + { + Base?.EmitDataChanged(topLeft, bottomRight, roles); + } + } + + + public partial class Adapter + { + public partial interface IStatic + { + IQAbstractListModel QAbstractListModel_Create(object self); + } + } +} diff --git a/src/Qt.DotNet.Adapter/StringMarshaler.cs b/src/Qt.DotNet.Adapter/Qt/DotNet/StringMarshaler.cs index c7b1869..c7b1869 100644 --- a/src/Qt.DotNet.Adapter/StringMarshaler.cs +++ b/src/Qt.DotNet.Adapter/Qt/DotNet/StringMarshaler.cs diff --git a/src/Qt.DotNet.Adapter/build/Qt.DotNet.Adapter.targets b/src/Qt.DotNet.Adapter/build/Qt.DotNet.Adapter.targets new file mode 100644 index 0000000..805deb1 --- /dev/null +++ b/src/Qt.DotNet.Adapter/build/Qt.DotNet.Adapter.targets @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + + <Target Name="QtDotNetBuildNative" AfterTargets="CoreCompile" BeforeTargets="_CreateAppHost"> + <PropertyGroup> + <QtDir Condition="'$(QtDir)' == ''">C:\lib\qt\6.8.0\msvc2022_64</QtDir> + <QtDotNetDir Condition="'$(QtDotNetDir)' == ''">$(ProjectDir)\qtdotnet</QtDotNetDir> + <QtNativeSource>$(TargetDir)\native_source</QtNativeSource> + <QtNativeBuild>$(TargetDir)\native_build</QtNativeBuild> + </PropertyGroup> + + <ItemGroup> + <QtDotNetBin Include="$(QtDotNetDir)\adapter\*" /> + <QtDotNetGenerated Include="$(QtDotNetDir)\generated_files\*" /> + <Qml Include="$(ProjectDir)\*.qml" /> + </ItemGroup> + + <MakeDir Directories="$(QtNativeSource);$(QtNativeBuild)" /> + <Copy SkipUnchangedFiles="true" SourceFiles="@(QtDotNetGenerated);@(Qml)" DestinationFolder="$(QtNativeSource)" /> + <Copy SkipUnchangedFiles="true" SourceFiles="@(QtDotNetBin)" DestinationFolder="$(TargetDir)" /> + + <Message Importance="high" Text="Qt/.NET: building native code..." /> + <PropertyGroup> + <BuildNative> + CALL "$(VsInstallRoot)\VC\Auxiliary\Build\vcvars64.bat" > NUL + SET QtDir=$(QtDir) + SET QtDotNetDir=$(QtDotNetDir) + CD "$(QtNativeBuild)" + cmake "-DCMAKE_PREFIX_PATH=$(QtDir)" -G Ninja -S ..\native_source + cmake --build . + </BuildNative> + </PropertyGroup> + <Exec Command="$(BuildNative)" /> + <Copy SkipUnchangedFiles="true" SourceFiles="$(QtNativeBuild)\qmlapp.exe" DestinationFolder="$(TargetDir)" /> + + <Message Importance="high" Text="Deploying QML app..." /> + <PropertyGroup> + <RunWinDeployQt> + CD "$(TargetDir)" + "$(QtDir)\bin\windeployqt.exe" --no-translations --no-compiler-runtime qmlapp.exe > NUL + </RunWinDeployQt> + </PropertyGroup> + <Exec Command="$(RunWinDeployQt)" /> + + <PropertyGroup> + <AppHostSourcePath>$(TargetDir)qmlapp.exe</AppHostSourcePath> + </PropertyGroup> + </Target> + + <Target Name="CleanNative" AfterTargets="Clean"> + <PropertyGroup> + <CleanNative> + CD "$(TargetDir)" && RD /S /Q "$(TargetDir)" > NUL 2>&1 + </CleanNative> + </PropertyGroup> + <Exec Command="$(CleanNative)" /> + </Target> +</Project> diff --git a/tests/FooConsoleApp/FooConsoleApp.csproj b/tests/FooConsoleApp/FooConsoleApp.csproj new file mode 100644 index 0000000..77f8870 --- /dev/null +++ b/tests/FooConsoleApp/FooConsoleApp.csproj @@ -0,0 +1,14 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net8.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>disable</Nullable> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj" /> + </ItemGroup> + +</Project> diff --git a/tests/FooConsoleApp/Program.cs b/tests/FooConsoleApp/Program.cs new file mode 100644 index 0000000..944f95f --- /dev/null +++ b/tests/FooConsoleApp/Program.cs @@ -0,0 +1,23 @@ +/*************************************************************************************************** + Copyright (C) 2024 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +namespace FooConsoleApp +{ + public static class Program + { + public static bool KeepRunning { get; set; } = true; + public static int Main(string[] args) + { + Console.WriteLine("FooConsoleApp: started"); + Thread.Sleep(1000); + while (KeepRunning) { + Console.WriteLine("FooConsoleApp: running"); + Thread.Sleep(1000); + } + Console.WriteLine("FooConsoleApp: stopped"); + return 0; + } + } +} diff --git a/tests/FooLib/FooClass.cs b/tests/FooLib/FooClass.cs index 5182192..f4bd64f 100644 --- a/tests/FooLib/FooClass.cs +++ b/tests/FooLib/FooClass.cs @@ -4,26 +4,35 @@ ***************************************************************************************************/ using System.ComponentModel; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Qt.DotNet; + namespace FooLib { public interface IBarTransformation { string Transform(string bar); + Uri GetUri(int n); + void SetUri(Uri uri); + int GetNumber(); } public class BarIdentity : IBarTransformation { public string Transform(string bar) => bar; + public Uri GetUri(int n) => null; + public void SetUri(Uri uri) { } + public int GetNumber() => 0; } public class Foo : INotifyPropertyChanged { public Foo(IBarTransformation barTransformation) { - BarTransformation = barTransformation ?? new BarIdentity(); + BarTransformation = barTransformation; } public Foo() : this(null) @@ -44,7 +53,14 @@ namespace FooLib get => bar; set { - bar = BarTransformation?.Transform(value) ?? value; + bar = value; + if (BarTransformation is IBarTransformation) { + bar = BarTransformation.Transform(value) ?? bar; + BarTransformation.SetUri(new("https://qt.io/developers")); + var n = BarTransformation.GetNumber(); + if (BarTransformation.GetUri(n) is { } uri) + bar += $" ({uri})"; + } NotifyPropertyChanged(); } } @@ -83,5 +99,78 @@ namespace FooLib { return string.Format(format, date.Year, date.Month, date.Day); } + + public static void VariantStringToUpper(IQVariant v) + { + v.SetValue(v.ToStringValue().ToUpper()); + } + + public static IQVariant GetVariant() + { + return Adapter.Static.QVariant_Create(); + } + + public static IQVariant GetVariant(string value) + { + return Adapter.Static.QVariant_Create(value); + } + + public static IQModelIndex GetModelIndex() + { + return Adapter.Static.QModelIndex_Create(); + } + + public static int ModelIndexRowColPtr(IQModelIndex idx) + { + return idx.Row() * idx.Column() * (int)idx.InternalPointer(); + } + + public static void InitModel() + { + if (Model is null) + Model = new TestListModel(); + } + + public static void CleanupModel() + { + if (Model is null) + return; + Model = null; + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + public static QAbstractListModel Model { get; private set; } + + public class TestListModel : QAbstractListModel + { + public override int Flags(IQModelIndex index = null) + { + if (index == null) + return base.Flags(index); + return (int)( ItemFlag.ItemIsSelectable + | ItemFlag.ItemIsEnabled + | ItemFlag.ItemNeverHasChildren); + } + + public override int RowCount(IQModelIndex parent = null) + { + return 2; + } + + public override IQVariant Data(IQModelIndex index, int role = 0) + { + switch (index.Row()) { + case 0: + return Adapter.Static.QVariant_Create("FOO"); + default: + return Adapter.Static.QVariant_Create("BAR"); + } + } + } + + public delegate int FooFunc(int x); + + public static FooFunc Plus42 { get; } = new FooFunc(x => x + 42); } } diff --git a/tests/FooLib/FooLib.csproj b/tests/FooLib/FooLib.csproj index 2b8cb94..1759ee7 100644 --- a/tests/FooLib/FooLib.csproj +++ b/tests/FooLib/FooLib.csproj @@ -8,9 +8,13 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net6.0</TargetFramework> + <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>disable</Nullable> </PropertyGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj" /> + </ItemGroup> + </Project> diff --git a/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj b/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj index f49a9b8..187520b 100644 --- a/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj +++ b/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj @@ -8,7 +8,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net6.0</TargetFramework> + <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <Configurations>Debug;Release;Tests</Configurations> diff --git a/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj b/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj index 37742b4..3fc80bc 100644 --- a/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj +++ b/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj @@ -7,7 +7,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net6.0</TargetFramework> + <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>disable</Nullable> <IsPackable>false</IsPackable> diff --git a/tests/tst_qtdotnet/foo.cpp b/tests/tst_qtdotnet/foo.cpp index 8c8c3d3..789ec1b 100644 --- a/tests/tst_qtdotnet/foo.cpp +++ b/tests/tst_qtdotnet/foo.cpp @@ -7,7 +7,7 @@ #include <qdotnetevent.h> -struct FooPrivate final : QDotNetObject::IEventHandler +struct FooPrivate final : QDotNetEventHandler { Foo *q; FooPrivate(Foo *q) : q(q) {} @@ -17,12 +17,16 @@ struct FooPrivate final : QDotNetObject::IEventHandler QDotNetFunction<QString> bar; QDotNetFunction<void, QString> setBar; + QDotNetType typePropertyEvent = nullptr; + void handleEvent(const QString &eventName, QDotNetObject &sender, QDotNetObject &args) override { if (eventName != "PropertyChanged") return; - if (args.type().fullName() != QDotNetPropertyEvent::FullyQualifiedTypeName) + if (!typePropertyEvent.isValid()) + typePropertyEvent = QDotNetType::typeOf<QDotNetPropertyEvent>(); + if (!args.type().equals(typePropertyEvent)) return; const auto propertyChangedEvent = args.cast<QDotNetPropertyEvent>(); @@ -37,13 +41,13 @@ Foo::Foo() : d(new FooPrivate(this)) { const auto ctor = constructor<Foo, Null<IBarTransformation>>(); *this = ctor(nullptr); - subscribeEvent("PropertyChanged", d); + subscribe("PropertyChanged", d); } Foo::Foo(const IBarTransformation &transformation) : d(new FooPrivate(this)) { *this = constructor(d->ctor).invoke(*this, transformation); - subscribeEvent("PropertyChanged", d); + subscribe("PropertyChanged", d); } Foo::~Foo() @@ -61,10 +65,28 @@ void Foo::setBar(const QString &value) method("set_Bar", d->setBar).invoke(*this, value); } -IBarTransformation::IBarTransformation() : QDotNetInterface(FullyQualifiedTypeName) +IBarTransformation::IBarTransformation() : QDotNetInterface(AssemblyQualifiedName, nullptr) { - setCallback<QString, QString>("Transform", { QDotNetParameter::String, UnmanagedType::LPWStr }, - [this](const QString &bar) { + setCallback<QString, QString>("Transform", + [this](void *, const QString &bar) { return transform(bar); }); + + setCallback<Uri, int>("GetUri", + [this](void *, int n) + { + return getUri(n); + }); + + setCallback<void, Uri>("SetUri", + [this](void *, Uri uri) + { + setUri(uri); + }); + + setCallback<int>("GetNumber", + [this](void *) + { + return getNumber(); + }); } diff --git a/tests/tst_qtdotnet/foo.h b/tests/tst_qtdotnet/foo.h index 1c8f9fc..6510c31 100644 --- a/tests/tst_qtdotnet/foo.h +++ b/tests/tst_qtdotnet/foo.h @@ -8,6 +8,8 @@ #include <qdotnetinterface.h> #include <qdotnetobject.h> +#include "uri.h" + #ifdef __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wconversion" @@ -23,10 +25,13 @@ struct FooPrivate; class IBarTransformation : public QDotNetInterface { public: - static inline const QString &FullyQualifiedTypeName = + static inline const QString &AssemblyQualifiedName = QStringLiteral("FooLib.IBarTransformation, FooLib"); virtual QString transform(const QString &) = 0; + virtual Uri getUri(int) = 0; + virtual void setUri(const Uri &uri) = 0; + virtual int getNumber() = 0; protected: IBarTransformation(); diff --git a/tests/tst_qtdotnet/tst_qtdotnet.cpp b/tests/tst_qtdotnet/tst_qtdotnet.cpp index 92dbe85..65fdb30 100644 --- a/tests/tst_qtdotnet/tst_qtdotnet.cpp +++ b/tests/tst_qtdotnet/tst_qtdotnet.cpp @@ -15,6 +15,13 @@ #include <qdotnetobject.h> #include <qdotnetsafemethod.h> #include <qdotnettype.h> +#include <qdotnetstatic.h> +#include <qdotnetdelegate.h> + +#include <iqvariant.h> +#include <iqmodelindex.h> + +#include <qdotnetabstractlistmodel.h> #ifdef __GNUC__ # pragma GCC diagnostic push @@ -29,12 +36,30 @@ #include <QObject> #include <QSignalSpy> #include <QString> +#include <QStringListModel> +#include <QThread> #include <QtTest> #ifdef __GNUC__ # pragma GCC diagnostic pop #endif +#define TESTCASE_DOTNET_MAIN +//#define TESTCASE_QT_MAIN + +#if defined(TESTCASE_DOTNET_MAIN) + #define TEST_APP_STARTUP + #define TEST_FUNCTION_CALLS + #define TEST_APP_SHUTDOWN + #define TEST_HOST_UNLOAD +#elif defined(TESTCASE_QT_MAIN) + #define TEST_HOST_LOAD + #define TEST_FUNCTION_CALLS + #define TEST_HOST_UNLOAD +#endif + +//#define COREHOST_TRACE + class tst_qtdotnet : public QObject { Q_OBJECT @@ -42,31 +67,123 @@ class tst_qtdotnet : public QObject public: tst_qtdotnet() = default; +private: + int refCount = 0; + bool skipCleanup = false; + private slots: + + void initTestCase() + { +#ifdef COREHOST_TRACE + qputenv("COREHOST_TRACE", "1"); +#endif + } + + void init() + { + if (!QDotNetAdapter::instance().isValid()) + return; + refCount = QDotNetAdapter::instance().stats().refCount; + } + + void cleanup() + { + if (skipCleanup) { + skipCleanup = false; + QSKIP("cleanup skipped"); + } + if (!QDotNetAdapter::instance().isValid()) + return; + QVERIFY(QDotNetAdapter::instance().stats().refCount == refCount); + } + +#ifdef TEST_APP_STARTUP + void appStartup(); +#endif //TEST_APP_STARTUP +#ifdef TEST_HOST_LOAD void loadHost(); void runtimeProperties(); +#endif //TEST_HOST_LOAD +#ifdef TEST_FUNCTION_CALLS void resolveFunction(); void callFunction(); void callFunctionWithCustomMarshaling(); void callDefaultEntryPoint(); void callWithComplexArg(); +#endif //TEST_FUNCTION_CALLS +#ifdef TEST_HOST_LOAD void adapterInit(); +#endif //TEST_HOST_LOAD +#ifdef TEST_FUNCTION_CALLS void callStaticMethod(); void handleException(); void createObject(); void callInstanceMethod(); - void useWrapperClass(); + void useWrapperClassForStringBuilder(); + void useWrapperClassForUri(); void emitSignalFromEvent(); void propertyBinding(); void implementInterface(); void arrayOfInts(); void arrayOfStrings(); void arrayOfObjects(); + void variantNull(); + void variantGet(); + void variantSet(); + void modelIndexNull(); + void modelIndexGet(); + void models(); + void delegates(); +#endif //TEST_FUNCTION_CALLS +#ifdef TEST_APP_SHUTDOWN + void appShutdown(); +#endif //TEST_APP_SHUTDOWN +#ifdef TEST_HOST_UNLOAD void unloadHost(); +#endif //TEST_HOST_UNLOAD }; QDotNetHost dotNetHost; +QThread *dotnetThread = nullptr; + +#ifdef TEST_APP_STARTUP + +void tst_qtdotnet::appStartup() +{ + dotnetThread = QThread::create( + [this]() + { + dotNetHost.loadApp( + QDir(QCoreApplication::applicationDirPath()).filePath("FooConsoleApp.dll")); + dotNetHost.runApp(); + }); + dotnetThread->start(); + bool block = true; + while (!dotNetHost.isReady()) + QThread::sleep(1); + QDotNetAdapter::instance().init( + QDir(QCoreApplication::applicationDirPath()).filePath("Qt.DotNet.Adapter.dll"), + "Qt.DotNet.Adapter", "Qt.DotNet.Adapter", &dotNetHost); +} +#endif //TEST_APP_STARTUP +#ifdef TEST_APP_STARTUP +void tst_qtdotnet::appShutdown() +{ + if (!dotnetThread) + QSKIP("App thread not running"); + + QtDotNet::call<void, bool>("FooConsoleApp.Program, FooConsoleApp", "set_KeepRunning", false); + QThread::sleep(1); + while (dotnetThread->isRunning()) { + qInfo() << "App thread still running..."; + QThread::sleep(1); + } +} +#endif //TEST_APP_STARTUP + +#ifdef TEST_HOST_LOAD void tst_qtdotnet::loadHost() { QVERIFY(!dotNetHost.isLoaded()); @@ -85,6 +202,17 @@ void tst_qtdotnet::runtimeProperties() } } +void tst_qtdotnet::adapterInit() +{ + QVERIFY(!QDotNetAdapter::instance().isValid()); + QDotNetAdapter::instance().init( + QDir(QCoreApplication::applicationDirPath()).filePath("Qt.DotNet.Adapter.dll"), + "Qt.DotNet.Adapter", "Qt.DotNet.Adapter", &dotNetHost); + QVERIFY(QDotNetAdapter::instance().isValid()); +} +#endif //TEST_HOST_LOAD + +#ifdef TEST_FUNCTION_CALLS QDotNetFunction<QString, QString, int> formatNumber; void tst_qtdotnet::resolveFunction() @@ -93,7 +221,7 @@ void tst_qtdotnet::resolveFunction() QVERIFY(!formatNumber.isValid()); QVERIFY(dotNetHost.resolveFunction(formatNumber, QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"), - Foo::FullyQualifiedTypeName, "FormatNumber", "FooLib.Foo+FormatNumberDelegate, FooLib")); + Foo::AssemblyQualifiedName, "FormatNumber", "FooLib.Foo+FormatNumberDelegate, FooLib")); QVERIFY(formatNumber.isValid()); } @@ -149,7 +277,7 @@ void tst_qtdotnet::callFunctionWithCustomMarshaling() QDotNetFunction<QUpperCaseString, QString, DoubleAsInt> formatDouble; QVERIFY(dotNetHost.resolveFunction(formatDouble, QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"), - Foo::FullyQualifiedTypeName, "FormatNumber", "FooLib.Foo+FormatNumberDelegate, FooLib")); + Foo::AssemblyQualifiedName, "FormatNumber", "FooLib.Foo+FormatNumberDelegate, FooLib")); QVERIFY(formatDouble.isValid()); @@ -165,7 +293,7 @@ void tst_qtdotnet::callDefaultEntryPoint() QDotNetFunction<quint32, void*, qint32> entryPoint; QVERIFY(dotNetHost.resolveFunction(entryPoint, QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"), - Foo::FullyQualifiedTypeName, "EntryPoint")); + Foo::AssemblyQualifiedName, "EntryPoint")); QVERIFY(entryPoint.isValid()); @@ -206,7 +334,7 @@ void tst_qtdotnet::callWithComplexArg() QDotNetFunction<QString, QString, Date> formatDate; QVERIFY(dotNetHost.resolveFunction(formatDate, QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"), - Foo::FullyQualifiedTypeName, "FormatDate", "FooLib.Foo+FormatDateDelegate, FooLib")); + Foo::AssemblyQualifiedName, "FormatDate", "FooLib.Foo+FormatDateDelegate, FooLib")); QVERIFY(formatDate.isValid()); @@ -216,105 +344,77 @@ void tst_qtdotnet::callWithComplexArg() QCOMPARE(formattedText, "Today is 2022-12-25"); } -void tst_qtdotnet::adapterInit() -{ - QVERIFY(!QDotNetAdapter::instance().isValid()); - QDotNetAdapter::instance().init( - QDir(QCoreApplication::applicationDirPath()).filePath("Qt.DotNet.Adapter.dll"), - "Qt.DotNet.Adapter", "Qt.DotNet.Adapter", &dotNetHost); - QVERIFY(QDotNetAdapter::instance().isValid()); -} - void tst_qtdotnet::callStaticMethod() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); - { - const QDotNetType environment = QDotNetType::find("System.Environment"); - const auto getEnvironmentVariable - = environment.staticMethod<QString, QString>("GetEnvironmentVariable"); - const QString path = getEnvironmentVariable("PATH"); - QVERIFY(path.length() > 0); - const QString samePath = QtDotNet::call<QString, QString>( - "System.Environment", "GetEnvironmentVariable", "PATH"); - QVERIFY(path == samePath); - } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + const QDotNetType environment = QDotNetType::typeOf("System.Environment"); + const auto getEnvironmentVariable + = environment.staticMethod<QString, QString>("GetEnvironmentVariable"); + const QString path = getEnvironmentVariable("PATH"); + QVERIFY(path.length() > 0); + const QString samePath = QtDotNet::call<QString, QString>( + "System.Environment", "GetEnvironmentVariable", "PATH"); + QVERIFY(path == samePath); } void tst_qtdotnet::createObject() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); - { - const auto newStringBuilder = QDotNetObject::constructor("System.Text.StringBuilder"); - QDotNetObject stringBuilder = newStringBuilder(); - QVERIFY(QDotNetAdapter::instance().stats().refCount == 1); - } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + const auto newStringBuilder = QDotNetObject::constructor("System.Text.StringBuilder"); + QDotNetObject stringBuilder = newStringBuilder(); + QVERIFY(QDotNetAdapter::instance().stats().refCount == 1); } void tst_qtdotnet::callInstanceMethod() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); - { - const auto newStringBuilder = QDotNetObject::constructor("System.Text.StringBuilder"); - const auto stringBuilder = newStringBuilder(); - const auto append = stringBuilder.method<QDotNetObject, QString>("Append"); - std::ignore = append("Hello"); - std::ignore = append(" World!"); - const QString helloWorld = stringBuilder.toString(); - QVERIFY(helloWorld == "Hello World!"); - } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + const auto newStringBuilder = QDotNetObject::constructor("System.Text.StringBuilder"); + const auto stringBuilder = newStringBuilder(); + const auto append = stringBuilder.method<QDotNetObject, QString>("Append"); + std::ignore = append("Hello"); + std::ignore = append(" World!"); + const QString helloWorld = stringBuilder.toString(); + QVERIFY(helloWorld == "Hello World!"); } -void tst_qtdotnet::useWrapperClass() +void tst_qtdotnet::useWrapperClassForStringBuilder() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); - { - StringBuilder sb; - QVERIFY(QDotNetAdapter::instance().stats().refCount == 1); - QVERIFY(sb.isValid()); - sb.append("Hello").append(" "); - StringBuilder sbCpy(sb); - QVERIFY(QDotNetAdapter::instance().stats().refCount == 2); - QVERIFY(sbCpy.isValid()); - sbCpy.append("World"); - sb = StringBuilder(std::move(sbCpy)); - QVERIFY(QDotNetAdapter::instance().stats().refCount == 1); - sb.append("!"); - QCOMPARE(sb.toString(), "Hello World!"); - } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); - { - const Uri uri(QStringLiteral( - "https://user:[email protected]:80/Home/Index.htm?q1=v1&q2=v2#FragmentName")); - QVERIFY(uri.segments().length() == 3); - QVERIFY(uri.segments()[0]->compare("/") == 0); - } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + StringBuilder sb; + QVERIFY(QDotNetAdapter::instance().stats().refCount == 1); + QVERIFY(sb.isValid()); + sb.append("Hello").append(" "); + StringBuilder sbCpy(sb); + QVERIFY(QDotNetAdapter::instance().stats().refCount == 2); + QVERIFY(sbCpy.isValid()); + sbCpy.append("World"); + sb = StringBuilder(std::move(sbCpy)); + QVERIFY(QDotNetAdapter::instance().stats().refCount == 1); + sb.append("!"); + QCOMPARE(sb.toString(), "Hello World!"); +} + +void tst_qtdotnet::useWrapperClassForUri() +{ + const Uri uri(QStringLiteral( + "https://user:[email protected]:80/Home/Index.htm?q1=v1&q2=v2#FragmentName")); + QVERIFY(uri.segments().length() == 3); + QVERIFY(uri.segments()[0]->compare("/") == 0); } void tst_qtdotnet::handleException() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); - { - StringBuilder stringBuilder(5, 5); - QString helloWorld; - try { - stringBuilder.append("Hello"); - QVERIFY(stringBuilder.toString() == "Hello"); - stringBuilder.append(" World!"); - helloWorld = stringBuilder.toString(); - } - catch (QDotNetException&) { - helloWorld = "<ERROR>"; - } - QVERIFY(helloWorld == "<ERROR>"); + StringBuilder stringBuilder(5, 5); + QString helloWorld; + try { + stringBuilder.append("Hello"); + QVERIFY(stringBuilder.toString() == "Hello"); + stringBuilder.append(" World!"); + helloWorld = stringBuilder.toString(); } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + catch (const QDotNetException &ex) { + helloWorld = ex.type().cast<QDotNetType>().fullName(); + } + QVERIFY(helloWorld == "System.ArgumentOutOfRangeException"); } -class Ping final : public QObject, public QDotNetObject, public QDotNetObject::IEventHandler +class Ping final : public QObject, public QDotNetObject, public QDotNetEventHandler { Q_OBJECT @@ -324,7 +424,7 @@ public: Ping() : QDotNetObject(QDotNetSafeMethod(constructor<Ping>()).invoke(nullptr)) { - subscribeEvent("PingCompleted", this); + subscribe("PingCompleted", this); } ~Ping() override = default; @@ -366,135 +466,210 @@ private: void tst_qtdotnet::emitSignalFromEvent() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); - { - Ping ping; - bool waiting = true; - int signalCount = 0; - connect(&ping, &Ping::pingCompleted, - [&waiting, &signalCount](const QString& address, qint64 roundtripMsecs) { - qInfo() << "Reply from" << address << "in" << roundtripMsecs << "msecs"; - signalCount++; - waiting = false; - }); - connect(&ping, &Ping::pingError, - [&waiting, &signalCount] { - qInfo() << "Ping error"; - signalCount++; - waiting = false; - }); - qInfo() << "Pinging www.qt.io:"; - QElapsedTimer waitTime; - for (int i = 0; i < 4; ++i) { - waitTime.restart(); - waiting = true; - ping.sendAsync("www.qt.io"); - while (waiting) { - QCoreApplication::processEvents(); - if (waitTime.elapsed() > 3000) { - ping.sendAsyncCancel(); - waiting = false; - qInfo() << "Ping timeout"; - } + Ping ping; + bool waiting = true; + int signalCount = 0; + connect(&ping, &Ping::pingCompleted, + [&waiting, &signalCount](const QString& address, qint64 roundtripMsecs) { + qInfo() << "Reply from" << address << "in" << roundtripMsecs << "msecs"; + signalCount++; + waiting = false; + }); + connect(&ping, &Ping::pingError, + [&waiting, &signalCount] { + qInfo() << "Ping error"; + signalCount++; + waiting = false; + }); + qInfo() << "Pinging www.qt.io:"; + QElapsedTimer waitTime; + for (int i = 0; i < 4; ++i) { + waitTime.restart(); + waiting = true; + ping.sendAsync("www.qt.io"); + while (waiting) { + QCoreApplication::processEvents(); + if (waitTime.elapsed() > 3000) { + ping.sendAsyncCancel(); + waiting = false; + qInfo() << "Ping timeout"; } } - QVERIFY(signalCount == 4); } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + QVERIFY(signalCount == 4); } void tst_qtdotnet::propertyBinding() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); - { - Foo foo; - const QSignalSpy spy(&foo, &Foo::barChanged); - for (int i = 0; i < 1000; ++i) - foo.setBar(QString("hello x %1").arg(i + 1)); - QVERIFY(foo.bar() == "hello x 1000"); - QVERIFY(spy.count() == 1000); - } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + Foo foo; + const QSignalSpy spy(&foo, &Foo::barChanged); + for (int i = 0; i < 1000; ++i) + foo.setBar(QString("hello x %1").arg(i + 1)); + QVERIFY(foo.bar() == "hello x 1000"); + QVERIFY(spy.count() == 1000); } struct ToUpper : IBarTransformation { + Uri uri = Uri("https://qt.io/"); QString transform(const QString& bar) override { return bar.toUpper(); } + Uri getUri(int n) override + { + return uri; + } + void setUri(const Uri &uri) override + { + this->uri = uri; + } + int getNumber() override + { + return 42; + } }; void tst_qtdotnet::implementInterface() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); - { - const ToUpper transfToUpper; - Foo foo(transfToUpper); - const QSignalSpy spy(&foo, &Foo::barChanged); - for (int i = 0; i < 1000; ++i) - foo.setBar(QString("hello x %1").arg(i + 1)); - QVERIFY(foo.bar() == "HELLO X 1000"); - QVERIFY(spy.count() == 1000); - } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + const ToUpper transfToUpper; + Foo foo(transfToUpper); + foo.setBar("hello there"); + QVERIFY(foo.bar() == "HELLO THERE (https://qt.io/developers)"); } void tst_qtdotnet::arrayOfInts() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); - { - QDotNetArray<qint32> a(11); - a[0] = 0; - a[1] = 1; - for (int i = 2; i < a.length(); ++i) - a[i] = a[i - 1] + a[i - 2]; - QVERIFY(a[10] == 55); - } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + QDotNetArray<qint32> a(11); + a[0] = 0; + a[1] = 1; + for (int i = 2; i < a.length(); ++i) + a[i] = a[i - 1] + a[i - 2]; + QVERIFY(a[10] == 55); } void tst_qtdotnet::arrayOfStrings() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + QDotNetArray<QString> a(8); + a[0] = "Lorem"; + a[1] = "ipsum"; + a[2] = "dolor"; + a[3] = "sit"; + a[4] = "amet,"; + a[5] = "consectetur"; + a[6] = "adipiscing"; + a[7] = "elit."; + const auto stringType = QDotNetType::typeOf("System.String"); + const auto join = stringType.staticMethod<QString, QString, QDotNetArray<QString>>("Join"); + const auto loremIpsum = join(" ", a); + QVERIFY(loremIpsum == "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); +} + +void tst_qtdotnet::arrayOfObjects() +{ + QDotNetArray<StringBuilder> a(8); + for (int i = 0; i < a.length(); ++i) + a[i] = StringBuilder(); + a[0]->append("Lorem"); + a[1]->append(a[0]->toString()).append(" ipsum"); + a[2]->append(a[1]->toString()).append(" dolor"); + a[3]->append(a[2]->toString()).append(" sit"); + a[4]->append(a[3]->toString()).append(" amet,"); + a[5]->append(a[4]->toString()).append(" consectetur"); + a[6]->append(a[5]->toString()).append(" adipiscing"); + a[7]->append(a[6]->toString()).append(" elit."); + QVERIFY(a[7]->toString() == "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); +} + +void tst_qtdotnet::variantNull() +{ + auto getVariant = QDotNetType::staticMethod<IQVariant>("FooLib.Foo, FooLib", "GetVariant"); + auto iqv = getVariant(); + auto &qv = *iqv.dataAs<QVariant>(); + QVERIFY(!qv.isValid()); +} + +void tst_qtdotnet::variantGet() +{ + auto getVariant = QDotNetType::staticMethod<IQVariant, QString>("FooLib.Foo, FooLib", "GetVariant"); + auto iqv = getVariant("foobar"); + auto &qv = *iqv.dataAs<QVariant>(); + QVERIFY(qv.toString() == "foobar"); +} + +void tst_qtdotnet::variantSet() +{ + QVariant qv = "foobar"; + IQVariant iqv(qv); + auto toUpper = QDotNetType::staticMethod<void, IQVariant>("FooLib.Foo, FooLib", "VariantStringToUpper"); + toUpper(iqv); + QVERIFY(qv.toString() == "FOOBAR"); +} + +struct TestModel : public QStringListModel +{ + QModelIndex getIndex(int row, int col, void *ptr) { - QDotNetArray<QString> a(8); - a[0] = "Lorem"; - a[1] = "ipsum"; - a[2] = "dolor"; - a[3] = "sit"; - a[4] = "amet,"; - a[5] = "consectetur"; - a[6] = "adipiscing"; - a[7] = "elit."; - const auto stringType = QDotNetType::find("System.String"); - const auto join = stringType.staticMethod<QString, QString, QDotNetArray<QString>>("Join"); - const auto loremIpsum = join(" ", a); - QVERIFY(loremIpsum == "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + return createIndex(row, col, ptr); } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +}; + +void tst_qtdotnet::modelIndexNull() +{ + auto getModelIndex = QDotNetType::staticMethod<IQModelIndex>("FooLib.Foo, FooLib", "GetModelIndex"); + auto iqmi = getModelIndex(); + auto &qmi = *iqmi.dataAs<QModelIndex>(); + QVERIFY(!qmi.isValid()); } -void tst_qtdotnet::arrayOfObjects() +void tst_qtdotnet::modelIndexGet() { - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + TestModel tm; + auto idx = IQModelIndex(tm.getIndex(2, 3, reinterpret_cast<void *>(7))); + auto idxRowColPtr = QDotNetType::staticMethod<int, IQModelIndex>("FooLib.Foo, FooLib", "ModelIndexRowColPtr"); + auto rcp = idxRowColPtr(idx); + QVERIFY(rcp == 42); +} + +struct TestListModel : public QDotNetObject +{ + Q_DOTNET_OBJECT_INLINE(TestListModel, "FooLib.Foo+TestListModel, FooLib"); + TestListModel() + : QDotNetObject(constructor<TestListModel>().invoke(nullptr)) + { } + QAbstractListModel *base() const { - QDotNetArray<StringBuilder> a(8); - for (int i = 0; i < a.length(); ++i) - a[i] = StringBuilder(); - a[0]->append("Lorem"); - a[1]->append(a[0]->toString()).append(" ipsum"); - a[2]->append(a[1]->toString()).append(" dolor"); - a[3]->append(a[2]->toString()).append(" sit"); - a[4]->append(a[3]->toString()).append(" amet,"); - a[5]->append(a[4]->toString()).append(" consectetur"); - a[6]->append(a[5]->toString()).append(" adipiscing"); - a[7]->append(a[6]->toString()).append(" elit."); - QVERIFY(a[7]->toString() == "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + auto baseObj = method("get_Base", fnBase).invoke(*this); + auto baseInterface = baseObj.cast<QDotNetInterface>(); + return baseInterface.dataAs<QAbstractListModel>(); } - QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + mutable QDotNetFunction<QDotNetRef> fnBase = nullptr; +}; + +void tst_qtdotnet::models() +{ + const auto testModel = TestListModel(); + auto *baseModel = testModel.base(); + auto n = baseModel->rowCount(); + QVERIFY(n == 2); + auto ff = baseModel->flags(baseModel->index(0)); + QVERIFY(ff == (Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren)); + auto it0 = baseModel->data(baseModel->index(0)); + QVERIFY(it0.toString() == "FOO"); + auto it1 = baseModel->data(baseModel->index(1)); + QVERIFY(it1.toString() == "BAR"); + skipCleanup = true; // TODO: figure out why refs are still pending here +} + +void tst_qtdotnet::delegates() +{ + auto plus42 = QtDotNet::call<QDotNetDelegate<int, int>>("FooLib.Foo, FooLib", "get_Plus42"); + QVERIFY(plus42(3) == 45); } +#endif //TEST_FUNCTION_CALLS + +#ifdef TEST_HOST_UNLOAD void tst_qtdotnet::unloadHost() { QVERIFY(dotNetHost.isLoaded()); @@ -503,6 +678,7 @@ void tst_qtdotnet::unloadHost() QVERIFY(!dotNetHost.isLoaded()); } +#endif //TEST_HOST_UNLOAD QTEST_MAIN(tst_qtdotnet) #include "tst_qtdotnet.moc" diff --git a/tests/tst_qtdotnet/tst_qtdotnet.vcxproj b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj index 1d0efa1..be822fe 100644 --- a/tests/tst_qtdotnet/tst_qtdotnet.vcxproj +++ b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj @@ -30,7 +30,7 @@ <RootNamespace>tst_qtdotnet</RootNamespace> <WindowsTargetPlatformVersion Condition="'$(VisualStudioVersion)'=='15.0'">10.0.17763.0</WindowsTargetPlatformVersion> <WindowsTargetPlatformVersion Condition="'$(VisualStudioVersion)'=='16.0'">10.0</WindowsTargetPlatformVersion> - <WindowsTargetPlatformVersion Condition="'$(VisualStudioVersion)'=='17.0'">10.0</WindowsTargetPlatformVersion> + <WindowsTargetPlatformVersion Condition="'$(VisualStudioVersion)'=='17.0'">10.0.19041.0</WindowsTargetPlatformVersion> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <PropertyGroup> @@ -205,6 +205,26 @@ <QtMoc Include="foo.h" /> </ItemGroup> <ItemGroup> + <ClInclude Include="..\..\include\iqmodelindex.h" /> + <ClInclude Include="..\..\include\iqvariant.h" /> + <ClInclude Include="..\..\include\qdotnetabstractlistmodel.h" /> + <ClInclude Include="..\..\include\qdotnetadapter.h" /> + <ClInclude Include="..\..\include\qdotnetarray.h" /> + <ClInclude Include="..\..\include\qdotnetcallback.h" /> + <ClInclude Include="..\..\include\qdotnetdelegate.h" /> + <ClInclude Include="..\..\include\qdotnetevent.h" /> + <ClInclude Include="..\..\include\qdotnetexception.h" /> + <ClInclude Include="..\..\include\qdotnetfunction.h" /> + <ClInclude Include="..\..\include\qdotnethost.h" /> + <ClInclude Include="..\..\include\qdotnethostfxr.h" /> + <ClInclude Include="..\..\include\qdotnetinterface.h" /> + <ClInclude Include="..\..\include\qdotnetmarshal.h" /> + <ClInclude Include="..\..\include\qdotnetobject.h" /> + <ClInclude Include="..\..\include\qdotnetparameter.h" /> + <ClInclude Include="..\..\include\qdotnetref.h" /> + <ClInclude Include="..\..\include\qdotnetsafemethod.h" /> + <ClInclude Include="..\..\include\qdotnetstatic.h" /> + <ClInclude Include="..\..\include\qdotnettype.h" /> <ClInclude Include="uri.h" /> <ClInclude Include="stringbuilder.h" /> </ItemGroup> @@ -215,6 +235,9 @@ <ProjectReference Include="..\FooLib\FooLib.csproj"> <Project>{45d3ddf3-135b-46ca-b3ee-3537fcfffbeb}</Project> </ProjectReference> + <ProjectReference Include="..\FooConsoleApp\FooConsoleApp.csproj"> + <Project>{72ca8eda-c9cf-40c2-b7fe-4a529204eec2}</Project> + </ProjectReference> </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')"> diff --git a/tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters index 4c9a2f3..aff5e85 100644 --- a/tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters +++ b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters @@ -21,6 +21,9 @@ <UniqueIdentifier>{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}</UniqueIdentifier> <Extensions>ts</Extensions> </Filter> + <Filter Include="qtdotnet"> + <UniqueIdentifier>{3c233e4d-a97c-47c6-b8bb-5f97e38b5090}</UniqueIdentifier> + </Filter> </ItemGroup> <ItemGroup> <QtMoc Include="tst_qtdotnet.cpp"> @@ -48,5 +51,65 @@ <ClInclude Include="uri.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\..\include\iqvariant.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetadapter.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetarray.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetcallback.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetevent.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetexception.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetfunction.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnethost.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnethostfxr.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetinterface.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetmarshal.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetobject.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetparameter.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetref.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetsafemethod.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnettype.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\iqmodelindex.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetabstractlistmodel.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetstatic.h"> + <Filter>qtdotnet</Filter> + </ClInclude> + <ClInclude Include="..\..\include\qdotnetdelegate.h"> + <Filter>qtdotnet</Filter> + </ClInclude> </ItemGroup> </Project>
\ No newline at end of file |