diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6f509af --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text=auto +*.sh text eol=lf +*.yml text eol=lf +*.json text eol=lf +Makefile text eol=lf +Vagrantfile text eol=lf diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index d8dfeb0..a958950 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -25,7 +25,7 @@ jobs: qmake --version - name: Git Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - run: qmake -r "CONFIG+=${{ matrix.std_cpp }}" - run: make -j @@ -37,13 +37,15 @@ jobs: fail-fast: false matrix: include: + - image: arbmind/qbs-clang-libstdcpp-qt:2.3.1-18-14-6.7.1 + - image: arbmind/qbs-clang-libstdcpp-qt:1.24.1-16-12-6.5.0 - image: arbmind/qbs-clang-libstdcpp-qt:1.22.0-14-11-6.3.0 - image: arbmind/qbs-clang-libstdcpp-qt:1.21.0-13-10-6.2.4 runs-on: ubuntu-latest steps: - name: Git Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Build & Test run: >- @@ -76,19 +78,12 @@ jobs: env: QMAKESPEC: linux-clang steps: - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: QtCache-${{ matrix.qt_version }}-${{ matrix.qt_arch }} - - name: Install Qt - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v4 with: version: "${{ matrix.qt_version }}" arch: "${{ matrix.qt_arch }}" - cached: ${{ steps.cache-qt.outputs.cache-hit }} + cache: true - name: Setup Clang run: | @@ -102,7 +97,7 @@ jobs: clang++ --version - name: Git Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - run: qmake -r "CONFIG+=${{ matrix.std_cpp }}" - run: make -j diff --git a/.github/workflows/gcc.yml b/.github/workflows/gcc.yml index f16a9e4..f1ec50c 100644 --- a/.github/workflows/gcc.yml +++ b/.github/workflows/gcc.yml @@ -9,12 +9,14 @@ jobs: fail-fast: false matrix: include: - - image: arbmind/qbs-gcc-qt:1.22.1-12-6.3.1 + - image: arbmind/qbs-gcc-qt:2.3.1-14-6.7.1 + - image: arbmind/qbs-gcc-qt:1.24.1-12-6.5.0 + - image: arbmind/qbs-gcc-qt:1.22.1-12-6.3.1 runs-on: ubuntu-latest steps: - name: Git Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Build & Test run: >- @@ -48,19 +50,12 @@ jobs: runs-on: "${{ matrix.host_system }}" steps: - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: QtCache-${{ matrix.qt_version }}-${{ matrix.qt_arch }} - - name: Install Qt - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v4 with: version: "${{ matrix.qt_version }}" arch: "${{ matrix.qt_arch }}" - cached: ${{ steps.cache-qt.outputs.cache-hit }} + cache: true - name: Setup Gcc run: | @@ -73,7 +68,7 @@ jobs: g++ --version - name: Git Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - run: qmake -r "CONFIG+=${{ matrix.std_cpp }}" - run: make -j diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index dfcb44e..fcc9f98 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -9,6 +9,16 @@ jobs: fail-fast: false matrix: include: + - host_system: windows-2022 + vcvars: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + qt_version: "6.7.2" + qt_arch: win64_msvc2019_64 + + - host_system: windows-2022 + vcvars: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + qt_version: "6.5.0" + qt_arch: win64_msvc2019_64 + - host_system: windows-2022 vcvars: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat qt_version: "6.3.0" @@ -21,19 +31,12 @@ jobs: runs-on: "${{ matrix.host_system }}" steps: - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: QtCache-${{ matrix.qt_version }}-${{ matrix.qt_arch }} - - name: Install Qt - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v4 with: version: "${{ matrix.qt_version }}" arch: "${{ matrix.qt_arch }}" - cached: ${{ steps.cache-qt.outputs.cache-hit }} + cache: true - name: Install Qbs run: choco install qbs @@ -47,7 +50,7 @@ jobs: qbs config --list profiles - name: Git Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - run: >- qbs build @@ -62,38 +65,31 @@ jobs: matrix: include: - host_system: windows-2022 - cl_version: "14.33" + qt_version: "6.5.0" + qt_arch: win64_msvc2019_64 + + - host_system: windows-2022 qt_version: "6.3.0" qt_arch: win64_msvc2019_64 - host_system: windows-2019 - cl_version: "14.29" qt_version: "6.2.4" qt_arch: win64_msvc2019_64 runs-on: "${{ matrix.host_system }}" steps: - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: QtCache-${{ matrix.qt_version }}-${{ matrix.qt_arch }} - - name: Install Qt - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v4 with: version: "${{ matrix.qt_version }}" arch: "${{ matrix.qt_arch }}" - cached: ${{ steps.cache-qt.outputs.cache-hit }} + cache: true - name: Add msbuild to PATH uses: ilammy/msvc-dev-cmd@v1 - with: - toolset: '${{ matrix.cl_version }}' - name: Git Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - run: qmake -r - run: nmake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98a2751 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/cmake-build-*/ +/build*/ +/.idea/ +/.vscode/ +/*.user +/*.autosave diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8e9503e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.25) + +project(Verdigris + VERSION 2.0 + DESCRIPTION "Replace Qt moc with native C++" + HOMEPAGE_URL "https://github.com/woboq/verdigris" + LANGUAGES CXX +) + +enable_testing() + +find_package(Qt6 "6.2...<6.6" COMPONENTS Core Test REQUIRED) + +add_subdirectory("src") +add_subdirectory("tools") +add_subdirectory("tutorial") +add_subdirectory("tests") +add_subdirectory("benchmarks") + +set_source_files_properties( + "src/wobjectcpp.h" + "src/wobjectdefs.h" + "src/wobjectimpl.h" + "src/wqmlelement.h" + DIRECTORY + "benchmarks/qobject/" + PROPERTIES + SKIP_AUTOMOC ON +) + +add_library(verdigris::verdigris ALIAS verdigris) +include(CMakePackageConfigHelpers) +configure_package_config_file("cmake/verdigrisConfig.cmake.in" + "verdigrisConfig.cmake" + INSTALL_DESTINATION verdigris/cmake +) +write_basic_package_version_file("verdigrisConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion + ARCH_INDEPENDENT +) +include(GNUInstallDirs) +install(TARGETS verdigris + EXPORT verdigris_Targets + FILE_SET HEADERS + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/verdigris" +) +install(FILES + "${PROJECT_BINARY_DIR}/verdigrisConfig.cmake" + "${PROJECT_BINARY_DIR}/verdigrisConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/verdigris/cmake) +install(EXPORT verdigris_Targets + FILE "verdigrisTargets.cmake" + NAMESPACE verdigris:: + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/verdigris/cmake +) + +set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE.LGPLv3") +include(CPack) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..e52da6a --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,38 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "description": "Base for concrete configure presets", + "generator": "Ninja Multi-Config", + "binaryDir": "${sourceDir}/build/${presetName}" + }, + { + "name": "msvc", + "description": "Preferred settings for MSVC 2022", + "cacheVariables": { + "CMAKE_CXX_FLAGS": "/DWIN32 /D_WINDOWS /EHsc /permissive- /Zc:__cplusplus /Zc:externConstexpr /Zc:inline /Zc:preprocessor /Zc:throwingNew /diagnostics:caret" + } + }, + { + "name": "clang", + "cacheVariables": { + "CMAKE_CXX_FLAGS": "--pedantic -Wall -Wextra -ftemplate-backtrace-limit=0 -Wno-gnu-zero-variadic-macro-arguments" + } + }, + { + "name": "clang-libcpp", + "cacheVariables": { + "CMAKE_CXX_FLAGS": "--stdlib=libc++ --pedantic -Wall -Wextra -ftemplate-backtrace-limit=0 -Wno-gnu-zero-variadic-macro-arguments", + "CMAKE_EXE_LINKER_FLAGS": "--stdlib=libc++" + } + }, + { + "name": "gcc", + "cacheVariables": { + "CMAKE_CXX_FLAGS": "--pedantic -Wall -Wextra -Wno-noexcept-type -Wno-address -ftemplate-backtrace-limit=0" + } + } + ] +} diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 0000000..1f52fbf --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,2 @@ + +add_subdirectory("qobject") diff --git a/benchmarks/qobject/CMakeLists.txt b/benchmarks/qobject/CMakeLists.txt new file mode 100644 index 0000000..f371023 --- /dev/null +++ b/benchmarks/qobject/CMakeLists.txt @@ -0,0 +1,11 @@ + +add_executable(qobject_bench + "main.cpp" + "object.cpp" + "object.h" +) +target_link_libraries(qobject_bench PRIVATE + verdigris + Qt6::Test +) +set_target_properties(qobject_bench PROPERTIES AUTOMOC ON) diff --git a/cmake/verdigrisConfig.cmake.in b/cmake/verdigrisConfig.cmake.in new file mode 100644 index 0000000..9c15f36 --- /dev/null +++ b/cmake/verdigrisConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/qbs/modules/wqmlelement/metatypes/metatypes.qbs b/qbs/modules/wqmlelement/metatypes/metatypes.qbs new file mode 100644 index 0000000..7f0afae --- /dev/null +++ b/qbs/modules/wqmlelement/metatypes/metatypes.qbs @@ -0,0 +1,26 @@ +import qbs.File + +Module { + Depends { name: "Qt.core" } + + Rule { + name: "Qt MetaTypes" + inputs: ["verdigris.metatypes"] + inputsFromDependencies: ["verdigris.metatypes"] + Artifact { + filePath: input.fileName + ".json" + fileTags: ["qt.core.metatypes"] + qbs.install: product.Qt.core.metaTypesInstallDir + qbs.installDir: product.Qt.core.metaTypesInstallDir + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName + " from " + input.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + File.copy(input.filePath, output.filePath); + } + return [cmd]; + } + } +} diff --git a/qbs/modules/wqmlelement/objlibrary/objlibrary.qbs b/qbs/modules/wqmlelement/objlibrary/objlibrary.qbs new file mode 100644 index 0000000..80a3f89 --- /dev/null +++ b/qbs/modules/wqmlelement/objlibrary/objlibrary.qbs @@ -0,0 +1,32 @@ + +Module { + additionalProductTypes: ["obj", "verdigris.metatypes"] + + Depends { productTypes: ["metatypes-extractor"] } + Depends { name: "Qt.core" } + + Rule { + name: "Extract MetaTypes" + multiplex: true + inputs: ["obj"] + Artifact { + filePath: product.targetName.toLowerCase() + "_metatypes" + fileTags: ["verdigris.metatypes"] + } + explicitlyDependsOnFromDependencies: ["metatypes-extractor"] + prepare: { + var inputFilePaths = inputs.obj.map(function(a) { + return a.filePath; + }); + var toolPath = explicitlyDependsOn["metatypes-extractor"][0].filePath; + var cmd = new Command(toolPath, + ["--out=" + output.filePath].concat(inputFilePaths)); + cmd.environment = [ + "PATH=" + product.Qt.core.libExecPath // ensure Qt libraries are available + ]; + cmd.description = "generating " + output.fileName; + cmd.highlight = "codegen"; + return cmd; + } + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..0e5504f --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,19 @@ + +add_library(verdigris INTERFACE) +target_sources(verdigris INTERFACE + FILE_SET HEADERS + FILES + "wobjectcpp.h" + "wobjectdefs.h" + "wobjectimpl.h" + "wqmlelement.h" +) +target_compile_features(verdigris INTERFACE + cxx_std_20 +) +target_include_directories(verdigris INTERFACE + $ +) +target_link_libraries(verdigris INTERFACE + Qt6::Core +) diff --git a/src/wobjectdefs.h b/src/wobjectdefs.h index c166e10..c439bfe 100644 --- a/src/wobjectdefs.h +++ b/src/wobjectdefs.h @@ -55,7 +55,7 @@ template constexpr auto viewLiteral(const char (&d)[N]) -> StringView template struct StringViewArray { StringView data[(N > 0 ? N : 1)]{}; - constexpr auto operator[](size_t i) const { return data[i]; } + constexpr auto operator[](size_t i) const { return i < N ? data[i] : StringView{}; } }; template constexpr auto countParsedLiterals(const char (&s)[SN]) { @@ -347,94 +347,142 @@ struct MetaPropertyInfo { StringView name; StringView typeStr; Getter getter = {}; + StringView getterStr{}; Setter setter = {}; + StringView setterStr{}; Member member = {}; + StringView memberStr{}; Notify notify = {}; + StringView notifyStr{}; Reset reset = {}; + StringView resetStr{}; Bindable bindable = {}; + StringView bindableStr{}; uint flags = defaultPropertyFlags(); template - constexpr auto setGetter(const S& s) const -> MetaPropertyInfo { + constexpr auto setGetter(const S& s, const StringView& sv = {}) const + -> MetaPropertyInfo { return { .name = name, .typeStr = typeStr, .getter = s, + .getterStr = sv, .setter = setter, + .setterStr = setterStr, .member = member, + .memberStr = memberStr, .notify = notify, + .notifyStr = notifyStr, .reset = reset, + .resetStr = resetStr, .bindable = bindable, + .bindableStr = bindableStr, .flags = flags | PropertyFlags::Readable, }; } template - constexpr auto setSetter(const S& s) const -> MetaPropertyInfo { + constexpr auto setSetter(const S& s, const StringView& sv = {}) const + -> MetaPropertyInfo { return { .name = name, .typeStr = typeStr, .getter = getter, + .getterStr = getterStr, .setter = s, + .setterStr = sv, .member = member, + .memberStr = memberStr, .notify = notify, + .notifyStr = notifyStr, .reset = reset, + .resetStr = resetStr, .bindable = bindable, + .bindableStr = bindableStr, .flags = flags | PropertyFlags::Writable, }; } template - constexpr auto setMember(const S& s) const -> MetaPropertyInfo { + constexpr auto setMember(const S& s, const StringView& sv = {}) const + -> MetaPropertyInfo { return { .name = name, .typeStr = typeStr, .getter = getter, + .getterStr = getterStr, .setter = setter, + .setterStr = setterStr, .member = s, + .memberStr = sv, .notify = notify, + .notifyStr = notifyStr, .reset = reset, + .resetStr = resetStr, .bindable = bindable, + .bindableStr = bindableStr, .flags = flags | PropertyFlags::Writable | PropertyFlags::Readable, }; } template - constexpr auto setNotify(const S& s) const -> MetaPropertyInfo { + constexpr auto setNotify(const S& s, const StringView& sv = {}) const + -> MetaPropertyInfo { return { .name = name, .typeStr = typeStr, .getter = getter, + .getterStr = getterStr, .setter = setter, + .setterStr = setterStr, .member = member, + .memberStr = memberStr, .notify = s, + .notifyStr = sv, .reset = reset, + .resetStr = resetStr, .bindable = bindable, + .bindableStr = bindableStr, .flags = flags | PropertyFlags::Notify, }; } template - constexpr auto setReset(const S& s) const -> MetaPropertyInfo { + constexpr auto setReset(const S& s, const StringView& sv = {}) const + -> MetaPropertyInfo { return { .name = name, .typeStr = typeStr, .getter = getter, + .getterStr = getterStr, .setter = setter, + .setterStr = setterStr, .member = member, + .memberStr = memberStr, .notify = notify, + .notifyStr = notifyStr, .reset = s, + .resetStr = sv, .bindable = bindable, + .bindableStr = bindableStr, .flags = flags | PropertyFlags::Resettable, }; } template - constexpr auto setBindable(const S& s) const -> MetaPropertyInfo { + constexpr auto setBindable(const S& s, const StringView& sv = {}) const + -> MetaPropertyInfo { return { .name = name, .typeStr = typeStr, .getter = getter, + .getterStr = getterStr, .setter = setter, + .setterStr = setterStr, .member = member, + .memberStr = memberStr, .notify = notify, + .notifyStr = notifyStr, .reset = reset, + .resetStr = resetStr, .bindable = s, + .bindableStr = sv, .flags = flags | PropertyFlags::Bindable, }; } @@ -454,77 +502,83 @@ struct MetaPropertyInfo { /// Parse a property and fill a MetaPropertyInfo (called from W_PROPERTY macro) // base case -template constexpr auto parseProperty(const PropInfo& p) -> PropInfo { return p; } +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray&) -> PropInfo { + return p; +} // setter -template -constexpr auto parseProperty(const PropInfo& p, Ret (Obj::*s)(Arg), Tail... t) { - return parseProperty(p.setSetter(s), t...); +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray& svs, Ret (Obj::*s)(Arg), Tail... t) { + return parseProperty(p.setSetter(s, svs[I]), svs, t...); } #if defined(__cpp_noexcept_function_type) && __cpp_noexcept_function_type >= 201510 -template -constexpr auto parseProperty(const PropInfo& p, Ret (Obj::*s)(Arg) noexcept, Tail... t) { - return parseProperty(p.setSetter(s), t...); +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray& svs, Ret (Obj::*s)(Arg) noexcept, Tail... t) { + return parseProperty(p.setSetter(s, svs[I]), svs, t...); } #endif // getter -template -constexpr auto parseProperty(const PropInfo& p, Ret (Obj::*s)(), Tail... t) { - return parseProperty(p.setGetter(s), t...); +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray& svs, Ret (Obj::*s)(), Tail... t) { + return parseProperty(p.setGetter(s, svs[I]), svs, t...); } -template -constexpr auto parseProperty(const PropInfo& p, Ret (Obj::*s)() const, Tail... t) { - return parseProperty(p.setGetter(s), t...); +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray& svs, Ret (Obj::*s)() const, Tail... t) { + return parseProperty(p.setGetter(s, svs[I]), svs, t...); } #if defined(__cpp_noexcept_function_type) && __cpp_noexcept_function_type >= 201510 -template -constexpr auto parseProperty(const PropInfo& p, Ret (Obj::*s)() noexcept, Tail... t) { - return parseProperty(p.setGetter(s), t...); +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray& svs, Ret (Obj::*s)() noexcept, Tail... t) { + return parseProperty(p.setGetter(s, svs[I]), svs, t...); } -template -constexpr auto parseProperty(const PropInfo& p, Ret (Obj::*s)() const noexcept, Tail... t) { - return parseProperty(p.setGetter(s), t...); +template +constexpr auto parseProperty( + const PropInfo& p, const StringViewArray& svs, Ret (Obj::*s)() const noexcept, Tail... t) { + return parseProperty(p.setGetter(s, svs[I]), svs, t...); } #endif // member -template -constexpr auto parseProperty(const PropInfo& p, Ret Obj::*s, Tail... t) { - return parseProperty(p.setMember(s), t...); +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray& svs, Ret Obj::*s, Tail... t) { + return parseProperty(p.setMember(s, svs[I]), svs, t...); } // notify -template -constexpr auto parseProperty(const PropInfo& p, Notify, F f, Tail... t) { - return parseProperty(p.setNotify(f), t...); +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray& svs, Notify, F f, Tail... t) { + return parseProperty(p.setNotify(f, svs[I + 1]), svs, t...); } // reset -template -constexpr auto parseProperty(const PropInfo& p, Reset, Ret (Obj::*s)(), Tail... t) { - return parseProperty(p.setReset(s), t...); +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray& svs, Reset, Ret (Obj::*s)(), Tail... t) { + return parseProperty(p.setReset(s, svs[I + 1]), svs, t...); } #if defined(__cpp_noexcept_function_type) && __cpp_noexcept_function_type >= 201510 -template -constexpr auto parseProperty(const PropInfo& p, Reset, Ret (Obj::*s)() noexcept, Tail... t) { - return parseProperty(p.setReset(s), t...); +template +constexpr auto parseProperty( + const PropInfo& p, const StringViewArray& svs, Reset, Ret (Obj::*s)() noexcept, Tail... t) { + return parseProperty(p.setReset(s, svs[I + 1]), svs, t...); } #endif // bindable -template -constexpr auto parseProperty(const PropInfo& p, Tagged s, Tail... t) { - return parseProperty(p.setBindable(s.value), t...); +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray& svs, Tagged s, Tail... t) { + return parseProperty(p.setBindable(s.value, svs[I]), svs, t...); } // property flag -template -constexpr auto parseProperty(const PropInfo& p, PropertyFlags flag, Tail... t) { - return parseProperty(p.setFlag(static_cast(flag)), t...); +template +constexpr auto parseProperty(const PropInfo& p, const StringViewArray& svs, PropertyFlags flag, Tail... t) { + return parseProperty(p.setFlag(static_cast(flag)), svs, t...); } -template -constexpr auto parseProperty(const PropInfo& p, SetPropertyFlag, bool value, Tail... t) { - return parseProperty(p.setFlag(static_cast(Flag), value), t...); +template +constexpr auto parseProperty( + const PropInfo& p, const StringViewArray& svs, SetPropertyFlag, bool value, Tail... t) { + return parseProperty(p.setFlag(static_cast(Flag), value), svs, t...); } -template -constexpr auto makeMetaPropertyInfo(StringView name, StringView type, Args... args) { +template +constexpr auto makeMetaPropertyInfo(StringView name, StringView type, StringViewArray defineStrs, Args... args) { auto propInfo = MetaPropertyInfo{.name = name, .typeStr = type}; - return parseProperty(propInfo, args...); + return parseProperty<0>(propInfo, defineStrs, args...); } template struct EnumIsScoped { @@ -629,6 +683,11 @@ template T& getParentObjectHelper(void* (T::*)(const char*)); // helper class that can access the private member of any class with W_OBJECT struct FriendHelper; +template struct Interface { + using Pointer = T*; + StringView name; +}; + } // namespace w_internal #if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG) @@ -717,9 +776,9 @@ namespace w_internal { /// All overloads are found using ADL in the QObject T template concept HasState = requires { - L >= 0; - w_state(index, State{}, TPP{}); -}; + L >= 0; + w_state(index, State{}, TPP{}); + }; template consteval size_t stateCountBetween() { @@ -967,6 +1026,7 @@ public: w_internal::makeMetaPropertyInfo( \ w_internal::viewLiteral(#NAME), \ w_internal::viewLiteral(W_MACRO_STRIGNIFY(W_MACRO_REMOVEPAREN(TYPE))), \ + W_PARAM_TOSTRING_REMOVE_SCOPE(__VA_ARGS__), \ __VA_ARGS__)) #define W_WRITE , &W_ThisType:: @@ -1062,7 +1122,7 @@ public: w_internal::viewLiteral(A), w_internal::viewLiteral(B)}) /// Same as Q_INTERFACE -#define W_INTERFACE(A) W_STATE_APPEND(InterfaceState, static_cast(nullptr)) +#define W_INTERFACE(A) W_STATE_APPEND(InterfaceState, w_internal::Interface{w_internal::viewLiteral(#A)}) /// Same as Q_DECLARE_FLAGS #define W_DECLARE_FLAGS(Flags, Enum) \ diff --git a/src/wobjectimpl.h b/src/wobjectimpl.h index 1942c68..6fe06ea 100644 --- a/src/wobjectimpl.h +++ b/src/wobjectimpl.h @@ -99,15 +99,13 @@ template struct ClassInfoGenerator { /// auto-detect the access specifiers template -concept IsPublic = requires(T** tpp) { - T::w_accessHelper(index, State{}, tpp); -}; +concept IsPublic = requires(T** tpp) { T::w_accessHelper(index, State{}, tpp); }; template struct Derived : T { static constexpr bool w_accessOracle = requires(T * *tpp) { T::w_accessHelper(index, State{}, tpp); }; }; template -concept IsProtected = !std::is_final_v && Derived::w_accessOracle; +concept IsProtected = (!std::is_final_v && Derived::w_accessOracle); #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) template inline constexpr bool is_const_member_method = false; @@ -167,7 +165,7 @@ template struct MethodGenerator { /// compute if type T is a builtin QMetaType template -concept BuiltinMetaTypeId = bool{QMetaTypeId2::IsBuiltIn}; +concept BuiltinMetaTypeId = static_cast(QMetaTypeId2::IsBuiltIn); /// Helper to generate the type information of type 'T': /// If T is a builtin QMetaType, its meta type id need to be added in the state. @@ -431,9 +429,7 @@ template struct DataBuilder { }; template -concept HasExplicitName = requires(StringView result) { - result = StringView{w_explicitObjectName(TP{})}; -}; +concept HasExplicitName = requires(StringView result) { result = StringView{w_explicitObjectName(TP{})}; }; /// fold ObjectInfo into State template consteval auto generateDataPass() -> Result { @@ -556,9 +552,7 @@ template static constexpr auto metaData = buildMetaData(); /// Returns the QMetaObject* of the base type template -concept WithParentMetaObject = requires(const QMetaObject* result) { - result = &T::W_BaseType::staticMetaObject; -}; +concept WithParentMetaObject = requires(const QMetaObject* result) { result = &T::W_BaseType::staticMetaObject; }; template static consteval const QMetaObject* parentMetaObject() { if constexpr (WithParentMetaObject) { return &T::W_BaseType::staticMetaObject; @@ -609,9 +603,62 @@ struct FunctorCall< QtPrivate::ApplyReturnValue(arg[0]); } }; +#elif QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) +template struct FunctorCall; +template +struct FunctorCall, QtPrivate::List, R, Function> : QtPrivate::FunctorCallBase { + static void call(Function f, void **arg) + { + using namespace QtPrivate; + call_internal(arg, [&] { + return f((*reinterpret_cast::Type *>(arg[II+1]))...); + }); + } +}; +template +struct FunctorCall, QtPrivate::List, R, SlotRet (Obj::*)(SlotArgs...)> : QtPrivate::FunctorCallBase { + static void call(SlotRet (Obj::*f)(SlotArgs...), Obj *o, void **arg) + { + using namespace QtPrivate; + call_internal(arg, [&] { + return (o->*f)((*reinterpret_cast::Type *>(arg[II+1]))...); + }); + } +}; +template +struct FunctorCall, QtPrivate::List, R, SlotRet (Obj::*)(SlotArgs...) const> : QtPrivate::FunctorCallBase { + static void call(SlotRet (Obj::*f)(SlotArgs...) const, Obj *o, void **arg) + { + using namespace QtPrivate; + call_internal(arg, [&] { + return (o->*f)((*reinterpret_cast::Type *>(arg[II+1]))...); + }); + } +}; +template +struct FunctorCall, QtPrivate::List, R, SlotRet (Obj::*)(SlotArgs...) noexcept> : QtPrivate::FunctorCallBase { + static void call(SlotRet (Obj::*f)(SlotArgs...) noexcept, Obj *o, void **arg) + { + using namespace QtPrivate; + call_internal(arg, [&]() noexcept { + return (o->*f)((*reinterpret_cast::Type *>(arg[II+1]))...); + }); + } +}; +template +struct FunctorCall, QtPrivate::List, R, SlotRet (Obj::*)(SlotArgs...) const noexcept> : QtPrivate::FunctorCallBase { + static void call(SlotRet (Obj::*f)(SlotArgs...) const noexcept, Obj *o, void **arg) + { + using namespace QtPrivate; + call_internal(arg, [&]() noexcept { + return (o->*f)((*reinterpret_cast::Type *>(arg[II+1]))...); + }); + } +}; #endif -template using InterfacePtr = decltype(w_state(index, InterfaceStateTag{}, TPP{})); +template +using InterfacePtr = typename decltype(w_state(index, InterfaceStateTag{}, TPP{}))::Pointer; struct FriendHelper { template static consteval QMetaObject createMetaObject() { @@ -786,12 +833,8 @@ struct FriendHelper { } template - requires( - std::is_same_v || - (std::is_same_v && - std::is_base_of_v< - QObject, - T>)) static void qt_static_metacall_impl(O* _o, QMetaObject::Call _c, int _id, void** _a) { + requires(std::is_same_v || (std::is_same_v && std::is_base_of_v)) + static void qt_static_metacall_impl(O* _o, QMetaObject::Call _c, int _id, void** _a) { Q_UNUSED(_id) Q_UNUSED(_o) Q_UNUSED(_a) @@ -887,6 +930,8 @@ struct FriendHelper { /// `W_OBJECT_IMPL((MyTemplate2), template)` #define W_OBJECT_IMPL(...) \ W_OBJECT_IMPL_COMMON(W_MACRO_EMPTY, __VA_ARGS__) \ + QT_WARNING_PUSH \ + Q_OBJECT_NO_OVERRIDE_WARNING \ W_MACRO_TEMPLATE_STUFF(__VA_ARGS__) \ void W_MACRO_FIRST_REMOVEPAREN(__VA_ARGS__)::qt_static_metacall( \ QObject* _o, QMetaObject::Call _c, int _id, void** _a) { \ @@ -903,7 +948,8 @@ struct FriendHelper { W_MACRO_TEMPLATE_STUFF(__VA_ARGS__) \ int W_MACRO_FIRST_REMOVEPAREN(__VA_ARGS__)::qt_metacall(QMetaObject::Call _c, int _id, void** _a) { \ return w_internal::FriendHelper::qt_metacall_impl(this, _c, _id, _a); \ - } + } \ + QT_WARNING_POP /// \macro W_GADGET_IMPL(TYPE [, TEMPLATE_STUFF]) /// Same as W_OBJECT_IMPL, but for a W_GADGET @@ -925,6 +971,8 @@ struct FriendHelper { /// (Requires support for c++17 inline variables) #define W_OBJECT_IMPL_INLINE(...) \ W_OBJECT_IMPL_COMMON(inline, __VA_ARGS__) \ + QT_WARNING_PUSH \ + Q_OBJECT_NO_OVERRIDE_WARNING \ W_MACRO_TEMPLATE_STUFF(__VA_ARGS__) \ inline void W_MACRO_FIRST_REMOVEPAREN(__VA_ARGS__)::qt_static_metacall( \ QObject* _o, QMetaObject::Call _c, int _id, void** _a) { \ @@ -941,7 +989,8 @@ struct FriendHelper { W_MACRO_TEMPLATE_STUFF(__VA_ARGS__) \ inline int W_MACRO_FIRST_REMOVEPAREN(__VA_ARGS__)::qt_metacall(QMetaObject::Call _c, int _id, void** _a) { \ return w_internal::FriendHelper::qt_metacall_impl(this, _c, _id, _a); \ - } + } \ + QT_WARNING_POP /// \macro W_GADGET_IMPL_INLINE(TYPE [, TEMPLATE_STUFF]) /// Same as W_GADGET_IMPL, but to be used in a header diff --git a/src/wqmlelement.h b/src/wqmlelement.h new file mode 100644 index 0000000..1523a6e --- /dev/null +++ b/src/wqmlelement.h @@ -0,0 +1,596 @@ +#pragma once +#include "wobjectimpl.h" + +#include + +// ___ decl ___ +namespace w_internal { + +struct QmlFriendHelper; + +} // namespace w_internal + +// mirrors supported macros from qqmlintegration.h +#define W_QML_ELEMENT \ + W_CLASSINFO("QML.Element", "auto"); \ + friend struct w_internal::QmlFriendHelper; \ + static auto w_qmlElementData()->const void*; \ + static constexpr auto W_Header = w_internal::viewLiteral(__FILE__) + +#define W_QML_ANONYMOUS \ + W_CLASSINFO("QML.Element", "anonymous"); \ + friend struct w_internal::QmlFriendHelper; \ + static auto w_qmlElementData()->const void*; \ + static constexpr auto W_Header = w_internal::viewLiteral(__FILE__) + +#define W_QML_NAMED_ELEMENT(NAME) \ + W_CLASSINFO("QML.Element", #NAME); \ + friend struct w_internal::QmlFriendHelper; \ + static auto w_qmlElementData()->const void*; \ + static constexpr auto W_Header = w_internal::viewLiteral(__FILE__) + +#define W_QML_UNCREATABLE(REASON) \ + W_CLASSINFO("QML.Creatable", "false") \ + W_CLASSINFO("QML.UncreatableReason", REASON) + +#define W_QML_VALUE_TYPE(NAME) \ + W_CLASSINFO("QML.Element", #NAME) \ + friend struct w_internal::QmlFriendHelper; \ + static auto w_qmlElementData()->const void*; \ + static constexpr auto W_Header = w_internal::viewLiteral(__FILE__); \ + W_QML_UNCREATABLE("Value types cannot be created.") + +// ___ impl ___ +#if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG) +#pragma section("qmltypes", read, discard) +#define W_QML_TYPES_SECTION_DECLSPEC __declspec(allocate("qmltypes")) +#define W_QML_TYPES_SECTION_ATTRIBUTE +#else +#define W_QML_TYPES_SECTION_DECLSPEC +#define W_QML_TYPES_SECTION_ATTRIBUTE __attribute__((section("qmltypes"))) +#endif + +namespace w_internal { + +template struct Overload : Ts... { + using Ts::operator()...; +}; +template Overload(Ts...) -> Overload; + +struct QuotedStringView { + StringView wrapped; +}; + +struct JsonValue; +using JsonArray = std::vector; +struct JsonKeyValue; +using JsonObject = std::vector; +struct JsonValue { + using Data = std::variant; + Data data; + + constexpr JsonValue() = default; + constexpr JsonValue(bool v); + constexpr JsonValue(int v); + constexpr JsonValue(StringView sv); + constexpr JsonValue(QuotedStringView qsv); + constexpr JsonValue(JsonArray ja); + constexpr JsonValue(JsonObject jo); + + static constexpr auto makeArray() -> JsonValue; + static constexpr auto makeObject() -> JsonValue; + + constexpr bool hasValue() const; + constexpr auto encodeSize() const -> size_t; + constexpr void encodeTo(char*& p) const; + + constexpr void addKeyValue(StringView key, JsonValue value); + constexpr void append(JsonValue value); + +private: + static constexpr auto trueSv = viewLiteral("true"); + static constexpr auto falseSv = viewLiteral("false"); + static constexpr auto intSize(int value) -> size_t { + auto sum = size_t{}; + if (value < 0) sum += 1; + auto abs = value > 0 ? value : -value; + while (abs >= 10) { + sum += 1; + abs /= 10; + } + return sum + 1; + } + static constexpr auto escapedStringSize(const StringView& sv) -> size_t { + auto sum = static_cast(sv.size()); + for (auto c : sv) { + if (c == '\\' || c == '"') sum++; + } + return sum; + } +}; +struct JsonKeyValue { + JsonValue key; + JsonValue value; + + constexpr JsonKeyValue() = default; + constexpr JsonKeyValue(JsonValue key, JsonValue value) + : key{std::move(key)} + , value{std::move(value)} {} +}; +constexpr JsonValue::JsonValue(bool v) + : data{v} {} +constexpr JsonValue::JsonValue(int v) + : data{v} {} +constexpr JsonValue::JsonValue(StringView sv) + : data{sv} {} +constexpr JsonValue::JsonValue(QuotedStringView qsv) + : data{qsv} {} +constexpr JsonValue::JsonValue(JsonArray ja) + : data{ja} {} +constexpr JsonValue::JsonValue(JsonObject jo) + : data{jo} {} + +constexpr auto JsonValue::makeArray() -> JsonValue { return {JsonArray{}}; } +constexpr auto JsonValue::makeObject() -> JsonValue { return {JsonObject{}}; } + +constexpr bool JsonValue::hasValue() const { + auto result = false; + std::visit( + Overload{ + [&](std::monostate) {}, + [&](bool) { result = true; }, + [&](int) { result = true; }, + [&](const StringView& sv) { result = sv.size() > 0; }, + [&](const QuotedStringView& qsv) { result = qsv.wrapped.size() > 0; }, + [&](const JsonArray& arr) { result = arr.size() > 0; }, + [&](const JsonObject& obj) { + for (const auto& entry : obj) { + if (entry.key.hasValue() && entry.value.hasValue()) { + result = true; + break; + } + } + }, + }, + data); + return result; +} +constexpr auto JsonValue::encodeSize() const -> size_t { + auto sum = size_t{}; + std::visit( + Overload{ + [&](std::monostate) {}, + [&](bool v) { sum = v ? trueSv.size() : falseSv.size(); }, + [&](int v) { sum = intSize(v); }, + [&](const StringView& sv) { sum = 1 + escapedStringSize(sv) + 1; }, + [&](const QuotedStringView& qsv) { sum = 3 + escapedStringSize(qsv.wrapped) + 3; }, + [&](const JsonArray& arr) { + sum = 1; + for (auto& entry : arr) { + if (sum != 0) sum += 1; + sum += entry.encodeSize(); + } + sum += 1; + }, + [&](const JsonObject& obj) { + sum = 1; + for (const auto& entry : obj) { + if (entry.key.hasValue() && entry.value.hasValue()) { + if (sum != 0) sum += 1; + sum += entry.key.encodeSize() + 1 + entry.value.encodeSize(); + } + } + sum += 1; + }, + }, + data); + return sum; +} +constexpr void JsonValue::encodeTo(char*& p) const { + std::visit( + Overload{ + [&](std::monostate) {}, + [&](bool v) { + for (auto c : (v ? trueSv : falseSv)) *p++ = c; + }, + [&](int v) { + auto e = p + intSize(v); + if (v < 0) *p = '-'; + p = e--; + auto abs = v > 0 ? v : -v; + while (abs >= 10) { + *e-- = '0' + (abs % 10); + abs /= 10; + } + *e = '0' + abs; + }, + [&](const StringView& sv) { + *p++ = '"'; + for (auto c : sv) { + if (c == '\\' || c == '"') *p++ = '\\'; + *p++ = c; + } + *p++ = '"'; + }, + [&](const QuotedStringView& qsv) { + *p++ = '"'; + *p++ = '\\'; + *p++ = '"'; + for (auto c : qsv.wrapped) { + if (c == '\\' || c == '"') *p++ = '\\'; + *p++ = c; + } + *p++ = '\\'; + *p++ = '"'; + *p++ = '"'; + }, + [&](const JsonArray& arr) { + *p++ = '['; + const auto b = p; + for (auto& entry : arr) { + if (p != b) *p++ = ','; + entry.encodeTo(p); + } + *p++ = ']'; + }, + [&](const JsonObject& obj) { + *p++ = '{'; + const auto b = p; + for (auto& entry : obj) { + if (entry.key.hasValue() && entry.value.hasValue()) { + if (p != b) *p++ = ','; + entry.key.encodeTo(p); + *p++ = ':'; + entry.value.encodeTo(p); + } + } + *p++ = '}'; + }, + }, + data); +} + +constexpr void JsonValue::addKeyValue(StringView key, JsonValue value) { + std::get(data).emplace_back(key, std::move(value)); +} + +constexpr void JsonValue::append(JsonValue value) { std::get(data).emplace_back(std::move(value)); } + +static_assert(JsonValue{68}.encodeSize() == 2); +static_assert(JsonValue{viewLiteral("ab")}.encodeSize() == 4); + +template struct StringArray { + RawArray data; +}; +template constexpr auto jsonToStringArray(const Json& json) -> StringArray { + auto result = StringArray{}; + auto* p = &result.data[0]; + json.encodeTo(p); + return result; +} + +template +concept is_qgadget = requires(T& gad) { gad.qt_check_for_QGADGET_macro(); }; + +template +concept is_qobject = ! +is_qgadget&& requires(T& obj, const char* name, void* result) { result = obj.T::qt_metacast(name); }; + +template +concept has_className = requires(StringView out) { out = T::W_UnscopedName; }; + +static constexpr uint operator&(uint a, PropertyFlags b) { return a & static_cast(b); } + +struct QmlFriendHelper { + template static constexpr auto createClassInfos(std::index_sequence) { + using TPP = T**; + auto result = JsonValue::makeArray(); + auto createEntry = [&](Index) { + constexpr auto data = w_state(index, ClassInfoStateTag{}, TPP{}); + auto json = JsonValue::makeObject(); + json.addKeyValue(viewLiteral("name"), JsonValue{data.first}); + json.addKeyValue(viewLiteral("value"), JsonValue{data.second}); + result.append(std::move(json)); + }; + (createEntry(index), ...); + return result; + } + + template static constexpr auto createEnumValues(const StringViewArray& names) { + auto result = JsonValue::makeArray(); + for (auto i = size_t{}; i < N; i++) { + result.append(names[i]); + } + return result; + } + + template static constexpr auto createEnums(std::index_sequence) { + using TPP = T**; + auto result = JsonValue::makeArray(); + auto createEntry = [&](Index) { + constexpr auto data = w_state(index, EnumStateTag{}, TPP{}); + auto json = JsonValue::makeObject(); + json.addKeyValue(viewLiteral("name"), JsonValue{data.name}); + if (data.hasAlias) { + json.addKeyValue(viewLiteral("alias"), JsonValue{data.alias}); + } + json.addKeyValue(viewLiteral("isFlag"), (0 != (data.flags & 1))); + json.addKeyValue(viewLiteral("isClass"), (0 != (data.flags & 2))); + json.addKeyValue(viewLiteral("values"), createEnumValues(data.names)); + result.append(std::move(json)); + }; + (createEntry(index), ...); + return result; + } + + template static constexpr auto createSuperClasses() { + auto result = JsonValue::makeArray(); + constexpr const QMetaObject* meta = parentMetaObject(); + if constexpr (meta) { + using Base = typename T::W_BaseType; + if constexpr (has_className) { + auto json = JsonValue::makeObject(); + json.addKeyValue(viewLiteral("name"), JsonValue{Base::W_UnscopedName}); + result.append(std::move(json)); + } + else { + auto json = JsonValue::makeObject(); + json.addKeyValue(viewLiteral("name"), viewTypeName()); + json.addKeyValue(viewLiteral("access"), viewLiteral("public")); + result.append(std::move(json)); + } + } + return result; + } + + template static constexpr auto viewTypeName(TypeStr v = {}) { + if constexpr (BuiltinMetaTypeId) { + constexpr auto& name = QMetaTypeId2::nameAsArray; + return StringView{name.data(), name.data() + name.size() - 1}; + } + else if constexpr (std::is_same_v, StringView>) { + return v; + } + else if constexpr (QMetaTypeId::Defined || QMetaTypeId::Defined) { + constexpr auto name = QtPrivate::typenameHelper(); + return StringView{name.data(), name.data() + name.size() - 1}; + } + else { + static_assert(W_TypeRegistery::registered, "Please Register T with W_REGISTER_ARGTYPE"); + return W_TypeRegistery::name; + } + } + + template + static constexpr auto createArgument(const TypeName& typeName, const auto& name) { + auto json = JsonValue::makeObject(); + using Type = typename QtPrivate::RemoveConstRef::Type; + auto typeName2 = std::conditional_t::value, TypeName, std::tuple>{typeName}; + json.addKeyValue(viewLiteral("type"), viewTypeName(typeName2)); + if (name.size() != 0) { + json.addKeyValue(viewLiteral("name"), name); + } + return json; + } + template + static constexpr auto createArguments( + const TypeNames& typeNames, const StringViewArray& paramNames, const index_sequence&) { + auto result = JsonValue::makeArray(); + (result.append(createArgument(stringFetch(typeNames), paramNames[Is])), ...); + return result; + } + + static constexpr auto viewAccess(int flags) -> JsonValue { + if ((flags & AccessPublic) != 0) { + return viewLiteral("public"); + } + if ((flags & AccessPrivate) != 0) { + return viewLiteral("private"); + } + if ((flags & AccessProtected) != 0) { + return viewLiteral("protected"); + } + return {}; + } + + template + static constexpr auto createFunctions(std::index_sequence) { + using TPP = T**; + auto result = JsonValue::makeArray(); + auto createEntry = [&](const Method& method, Index) { + using FP = QtPrivate::FunctionPointer; + auto json = JsonValue::makeObject(); + json.addKeyValue(viewLiteral("name"), JsonValue{method.name}); + // json.addKeyValue(viewLiteral("tag"), ??); + json.addKeyValue(viewLiteral("returnType"), viewTypeName()); + using ArgsPtr = typename FP::Arguments*; + [&json, &method](QtPrivate::List*) { + json.addKeyValue( + viewLiteral("arguments"), + createArguments( + method.paramTypes, method.paramNames, make_index_sequence{})); + }(ArgsPtr{}); + auto flags = method.flags; + if (!(flags & (AccessProtected | AccessPrivate | AccessPublic))) { + // Auto-detect the access specifier + flags |= IsPublic ? AccessPublic + : IsProtected ? AccessProtected + : AccessPrivate; + } + json.addKeyValue(viewLiteral("access"), viewAccess(flags)); + // json.addKeyValue(viewLiteral("revision"), ??); + result.append(std::move(json)); + }; + (createEntry(w_state(index, StateTag{}, TPP{}), index), ...); + return result; + } + + template static constexpr auto createConstructor(std::index_sequence) { + using TPP = T**; + auto result = JsonValue::makeArray(); + auto createEntry = [&](const MetaConstructorInfo& method) { + auto json = JsonValue::makeObject(); + json.addKeyValue(viewLiteral("name"), method.name); + // json.addKeyValue(viewLiteral("tag"), ??); + json.addKeyValue(viewLiteral("returnType"), viewLiteral("")); + json.addKeyValue( + viewLiteral("arguments"), + createArguments( + StringViewArray<>{}, StringViewArray<>{}, make_index_sequence{})); + json.addKeyValue(viewLiteral("access"), viewLiteral("public")); + // json.addKeyValue(viewLiteral("revision"), ??); + result.append(std::move(json)); + }; + (createEntry(w_state(index, ConstructorStateTag{}, TPP{})), ...); + return result; + } + + template static constexpr auto createProperties(std::index_sequence) { + using TPP = T**; + auto result = JsonValue::makeArray(); + auto createEntry = [&](const Prop& prop, Index) { + auto json = JsonValue::makeObject(); + json.addKeyValue(viewLiteral("name"), prop.name); + json.addKeyValue(viewLiteral("type"), viewTypeName(prop.typeStr)); + + if (prop.memberStr.size() > 0) json.addKeyValue(viewLiteral("member"), prop.memberStr); + if (prop.getterStr.size() > 0) json.addKeyValue(viewLiteral("read"), prop.getterStr); + if (prop.setterStr.size() > 0) json.addKeyValue(viewLiteral("write"), prop.setterStr); + if (prop.bindableStr.size() > 0) json.addKeyValue(viewLiteral("bindable"), prop.bindableStr); + if (prop.notifyStr.size() > 0) json.addKeyValue(viewLiteral("notify"), prop.notifyStr); + // json.addKeyValue(viewLiteral("privateClass"), ??); + + json.addKeyValue(viewLiteral("designable"), (prop.flags & PropertyFlags::Designable) != 0); + json.addKeyValue(viewLiteral("scriptable"), (prop.flags & PropertyFlags::Scriptable) != 0); + json.addKeyValue(viewLiteral("stored"), (prop.flags & PropertyFlags::Stored) != 0); + json.addKeyValue(viewLiteral("user"), (prop.flags & PropertyFlags::User) != 0); + + json.addKeyValue(viewLiteral("constant"), (prop.flags & PropertyFlags::Constant) != 0); + json.addKeyValue(viewLiteral("final"), (prop.flags & PropertyFlags::Final) != 0); + json.addKeyValue(viewLiteral("required"), (prop.flags & PropertyFlags::Required) != 0); + json.addKeyValue(viewLiteral("index"), static_cast(I)); + // json.addKeyValue(viewLiteral("revision"), ??); + + result.append(std::move(json)); + }; + (createEntry(w_state(index, PropertyStateTag{}, TPP{}), index), ...); + return result; + } + + template static constexpr auto createInterfaces(std::index_sequence) { + using TPP = T**; + + auto result = JsonValue::makeArray(); + auto createEntry = [&](const Interface& iface) { + auto json = JsonValue::makeObject(); + auto* b = qobject_interface_iid(); + auto* e = b; + while (*e != 0) e++; + json.addKeyValue(viewLiteral("id"), QuotedStringView{StringView{b, e}}); + json.addKeyValue(viewLiteral("className"), iface.name); + auto outerJson = JsonValue::makeArray(); + outerJson.append(std::move(json)); + result.append(std::move(outerJson)); + }; + (createEntry(w_state(index, InterfaceStateTag{}, TPP{})), ...); + return result; + } + +#ifdef QML_ELEMENT_BASE_PATH + static constexpr auto basePath = viewLiteral(QML_ELEMENT_BASE_PATH); +#endif + static constexpr auto shortPathView(const StringView& path) -> StringView { +#ifdef QML_ELEMENT_BASE_PATH + if (path.size() > basePath.size()) { + auto p = path.b; + for (auto c : basePath) { + auto pc = *p; + if (pc == c || (pc == '\\' && c == '/')) { + p++; + } + else { + break; + } + } + if (*p == '/' || *p == '\\') p++; + return StringView{p, path.e}; + } +#endif + return path; + } + + template static constexpr auto createQmlMetaTypes() { + using TPP = T**; + using ObjI = ObjectInfo; + constexpr size_t L = ObjI::counter; + + auto jsonClass = JsonValue::makeObject(); + jsonClass.addKeyValue(viewLiteral("className"), JsonValue{T::W_UnscopedName}); + jsonClass.addKeyValue(viewLiteral("qualifiedClassName"), JsonValue{T::W_MetaObjectCreatorHelper::fullName}); + jsonClass.addKeyValue( + viewLiteral("classInfos"), + createClassInfos(std::make_index_sequence()>{})); + + jsonClass.addKeyValue( + viewLiteral("signals"), + createFunctions(std::make_index_sequence()>{})); + jsonClass.addKeyValue( + viewLiteral("slots"), + createFunctions(std::make_index_sequence()>{})); + jsonClass.addKeyValue( + viewLiteral("constructors"), + createConstructor(std::make_index_sequence()>{})); + jsonClass.addKeyValue( + viewLiteral("methods"), + createFunctions(std::make_index_sequence()>{})); + + jsonClass.addKeyValue( + viewLiteral("properties"), + createProperties(std::make_index_sequence()>{})); + + if constexpr (is_qobject) { + jsonClass.addKeyValue(viewLiteral("object"), true); + } + if constexpr (is_qgadget) { + jsonClass.addKeyValue(viewLiteral("gadget"), true); + } + + jsonClass.addKeyValue( + viewLiteral("enums"), createEnums(std::make_index_sequence()>{})); + + jsonClass.addKeyValue(viewLiteral("superClasses"), createSuperClasses()); + + jsonClass.addKeyValue( + viewLiteral("interfaces"), + createInterfaces(std::make_index_sequence()>{})); + + auto json = JsonValue::makeObject(); + json.addKeyValue(viewLiteral("outputRevision"), JsonValue{68}); + json.addKeyValue(viewLiteral("inputFile"), shortPathView(T::W_Header)); + auto classesArray = JsonValue::makeArray(); + classesArray.append(std::move(jsonClass)); + json.addKeyValue(viewLiteral("classes"), std::move(classesArray)); + return json; + } + + template static constexpr auto createQmlTypes() { + constexpr auto resultSize = createQmlMetaTypes().encodeSize(); + constexpr auto result = jsonToStringArray(createQmlMetaTypes()); + return result; + } + + template static auto qmlElementData() -> const void* { + W_QML_TYPES_SECTION_DECLSPEC static constinit const auto qmltypes W_QML_TYPES_SECTION_ATTRIBUTE = + w_internal::QmlFriendHelper::createQmlTypes(); + return &qmltypes.data; + } +}; + +} // namespace w_internal + +#define W_QML_ELEMENT_IMPL(...) \ + W_OBJECT_IMPL(__VA_ARGS__) \ + W_MACRO_TEMPLATE_STUFF(__VA_ARGS__) auto W_MACRO_FIRST_REMOVEPAREN(__VA_ARGS__)::w_qmlElementData()->const void* { \ + return w_internal::QmlFriendHelper::qmlElementData(); \ + } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..06c6b8d --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_subdirectory("basic") +add_subdirectory("cppapi") +add_subdirectory("internal") +add_subdirectory("manyproperties") +add_subdirectory("qt") +add_subdirectory("templates") +add_subdirectory("inherit") diff --git a/tests/basic/CMakeLists.txt b/tests/basic/CMakeLists.txt new file mode 100644 index 0000000..aca5ed0 --- /dev/null +++ b/tests/basic/CMakeLists.txt @@ -0,0 +1,10 @@ + +add_executable(basic + "anothertu.cpp" + "tst_basic.cpp" + "anothertu.h" +) +target_link_libraries(basic PRIVATE + verdigris + Qt6::Test +) diff --git a/tests/cppapi/CMakeLists.txt b/tests/cppapi/CMakeLists.txt new file mode 100644 index 0000000..038cf34 --- /dev/null +++ b/tests/cppapi/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_executable(cppapi + "tst_cppapi.cpp" +) +target_link_libraries(cppapi PRIVATE + verdigris + Qt6::Test +) diff --git a/tests/inherit/CMakeLists.txt b/tests/inherit/CMakeLists.txt new file mode 100644 index 0000000..eb3fc28 --- /dev/null +++ b/tests/inherit/CMakeLists.txt @@ -0,0 +1,12 @@ + +add_executable(inherit + "api1.h" + "api2.h" + "object.cpp" + "object.h" + "tst_inherit.cpp" +) +target_link_libraries(inherit PRIVATE + verdigris + Qt6::Test +) diff --git a/tests/internal/CMakeLists.txt b/tests/internal/CMakeLists.txt new file mode 100644 index 0000000..1460533 --- /dev/null +++ b/tests/internal/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_executable(internal + "tst_internal.cpp" +) +target_link_libraries(internal PRIVATE + verdigris + Qt6::Test +) diff --git a/tests/manyproperties/CMakeLists.txt b/tests/manyproperties/CMakeLists.txt new file mode 100644 index 0000000..b2333d3 --- /dev/null +++ b/tests/manyproperties/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_executable(manyproperties + "tst_manyproperties.cpp" +) +target_link_libraries(manyproperties PRIVATE + verdigris + Qt6::Test +) diff --git a/tests/qt/CMakeLists.txt b/tests/qt/CMakeLists.txt new file mode 100644 index 0000000..a47e808 --- /dev/null +++ b/tests/qt/CMakeLists.txt @@ -0,0 +1,7 @@ + +add_subdirectory("qmetaenum") +add_subdirectory("qmetamethod") +add_subdirectory("qmetaobject") +add_subdirectory("qmetaproperty") +add_subdirectory("qobject") +add_subdirectory("qproperty") diff --git a/tests/qt/qmetaenum/CMakeLists.txt b/tests/qt/qmetaenum/CMakeLists.txt new file mode 100644 index 0000000..e56e004 --- /dev/null +++ b/tests/qt/qmetaenum/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_executable(qmetaenum + "tst_qmetaenum.cpp" +) +target_link_libraries(qmetaenum PRIVATE + verdigris + Qt6::Test +) diff --git a/tests/qt/qmetamethod/CMakeLists.txt b/tests/qt/qmetamethod/CMakeLists.txt new file mode 100644 index 0000000..8c2ea7b --- /dev/null +++ b/tests/qt/qmetamethod/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_executable(qmetamethod + "tst_qmetamethod6.cpp" +) +target_link_libraries(qmetamethod PRIVATE + verdigris + Qt6::Test +) diff --git a/tests/qt/qmetamethod/tst_qmetamethod6.cpp b/tests/qt/qmetamethod/tst_qmetamethod6.cpp index 82207b7..b4b12d2 100644 --- a/tests/qt/qmetamethod/tst_qmetamethod6.cpp +++ b/tests/qt/qmetamethod/tst_qmetamethod6.cpp @@ -59,6 +59,11 @@ class tst_QMetaMethod : public QObject #if QT_VERSION >= QT_VERSION_CHECK(6,2,0) void isConst(); W_SLOT(isConst, W_Access::Private{}); #endif + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + void methodIndexes_data(); + void methodIndexes(); +#endif }; W_OBJECT_IMPL(tst_QMetaMethod) @@ -948,4 +953,44 @@ void tst_QMetaMethod::isConst() } #endif + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +void tst_QMetaMethod::methodIndexes_data() +{ + QTest::addColumn("signature"); + QTest::addColumn("methodType"); + + QTest::newRow("constructor1") << QByteArray("MethodTestObject()") << QMetaMethod::Constructor; + QTest::newRow("constructor5") << QByteArray("MethodTestObject(CustomUnregisteredType)") + << QMetaMethod::Constructor; + QTest::newRow("method0") << QByteArray("voidInvokable()") << QMetaMethod::Method; + QTest::newRow("method6") << QByteArray("boolInvokable()") << QMetaMethod::Method; +} + +void tst_QMetaMethod::methodIndexes() +{ + QFETCH(QByteArray, signature); + QFETCH(QMetaMethod::MethodType, methodType); + + const bool isConstructor = methodType == QMetaMethod::Constructor; + + // roundtrip: index = QMetaObject::indexOfConstructor/Method() + // <-> method = QMetaObject::constructor/method() + // <-> indexThatShouldBeEqualToAboveIndex = QMetaMethod::methodIndex() + + const QMetaObject *mo = &MethodTestObject::staticMetaObject; + const int index = + isConstructor ? mo->indexOfConstructor(signature) : mo->indexOfMethod(signature); + QVERIFY(index != -1); + + QMetaMethod methodFromMetaObject = + mo->method(index); // should work on all methods (constructors, signals, ...) + const int absoluteMethodIndex = + methodFromMetaObject + .methodIndex(); // should work on all methods (constructors, signals, ...) + + QCOMPARE(absoluteMethodIndex, index); +} +#endif + QTEST_MAIN(tst_QMetaMethod) diff --git a/tests/qt/qmetaobject/CMakeLists.txt b/tests/qt/qmetaobject/CMakeLists.txt new file mode 100644 index 0000000..5f35b84 --- /dev/null +++ b/tests/qt/qmetaobject/CMakeLists.txt @@ -0,0 +1,10 @@ + +add_executable(qmetaobject + "forwarddeclared.cpp" + "forwarddeclared.h" + "tst_qmetaobject6.cpp" +) +target_link_libraries(qmetaobject PRIVATE + verdigris + Qt6::Test +) diff --git a/tests/qt/qmetaobject/forwarddeclared.cpp b/tests/qt/qmetaobject/forwarddeclared.cpp new file mode 100644 index 0000000..8772bc4 --- /dev/null +++ b/tests/qt/qmetaobject/forwarddeclared.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "forwarddeclared.h" + +struct MyForwardDeclaredType { }; +static MyForwardDeclaredType t; + +const MyForwardDeclaredType &getForwardDeclaredType() noexcept +{ + return t; +} + +MyForwardDeclaredType *getForwardDeclaredPointer() noexcept +{ + return &t; +} diff --git a/tests/qt/qmetaobject/forwarddeclared.h b/tests/qt/qmetaobject/forwarddeclared.h new file mode 100644 index 0000000..89fb083 --- /dev/null +++ b/tests/qt/qmetaobject/forwarddeclared.h @@ -0,0 +1,12 @@ +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef FORWARDDECLARED_H +#define FORWARDDECLARED_H + +struct MyForwardDeclaredType; // and ONLY forward-declared + +const MyForwardDeclaredType &getForwardDeclaredType() noexcept; +MyForwardDeclaredType *getForwardDeclaredPointer() noexcept; + +#endif // FORWARDDECLARED_H diff --git a/tests/qt/qmetaobject/qmetaobject.pro b/tests/qt/qmetaobject/qmetaobject.pro index b82644e..20f4508 100644 --- a/tests/qt/qmetaobject/qmetaobject.pro +++ b/tests/qt/qmetaobject/qmetaobject.pro @@ -2,5 +2,6 @@ CONFIG += testcase qtConfig(c++20): CONFIG += c++20 TARGET = tst_qmetaobject QT = core testlib -SOURCES = tst_qmetaobject6.cpp +HEADERS = forwarddeclared.h +SOURCES = forwarddeclared.cpp tst_qmetaobject6.cpp include(../../../src/verdigris.pri) diff --git a/tests/qt/qmetaobject/qmetaobject.qbs b/tests/qt/qmetaobject/qmetaobject.qbs index 853dd4d..2baeda9 100644 --- a/tests/qt/qmetaobject/qmetaobject.qbs +++ b/tests/qt/qmetaobject/qmetaobject.qbs @@ -9,6 +9,8 @@ Application { Depends { name: "Qt.test" } files: [ + "forwarddeclared.cpp", + "forwarddeclared.h", "tst_qmetaobject6.cpp", ] } diff --git a/tests/qt/qmetaobject/tst_qmetaobject6.cpp b/tests/qt/qmetaobject/tst_qmetaobject6.cpp index 5ce88da..0e96b36 100644 --- a/tests/qt/qmetaobject/tst_qmetaobject6.cpp +++ b/tests/qt/qmetaobject/tst_qmetaobject6.cpp @@ -43,6 +43,27 @@ Q_DECLARE_METATYPE(const QMetaObject *) +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +#include "forwarddeclared.h" + +#ifdef USE_COMPAT_Q_ARG +# define tst_QMetaObject tst_QMetaObject_CompatQArg +# if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) +# error "This is a Qt 6 compatibility check test" +# endif + +# undef Q_ARG +# undef Q_RETURN_ARG +# define Q_ARG(type, data) QArgument(#type, data) +# define Q_RETURN_ARG(type, data) QReturnArgument(#type, data) +# define Q_NO_ARG , QGenericArgument() +#else +// This macro is used to force the overload selection to the compat +// (non-variadic) code above +# define Q_NO_ARG +#endif +#endif + W_REGISTER_ARGTYPE(QList) struct MyStruct @@ -310,17 +331,32 @@ class tst_QMetaObject : public QObject QList value4; QVariantList value5; +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + tst_QMetaObject(); +#endif private slots: #define DECLARE_TEST(NAME) void NAME(); W_SLOT(NAME) DECLARE_TEST(connectSlotsByName); DECLARE_TEST(invokeMetaMember); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + DECLARE_TEST(invokeMetaMemberNoMacros); +#endif DECLARE_TEST(invokePointer); DECLARE_TEST(invokeQueuedMetaMember); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + DECLARE_TEST(invokeQueuedMetaMemberNoMacro); +#endif DECLARE_TEST(invokeQueuedPointer); DECLARE_TEST(invokeBlockingQueuedMetaMember); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + DECLARE_TEST(invokeBlockingQueuedMetaMemberNoMacros); +#endif DECLARE_TEST(invokeBlockingQueuedPointer); DECLARE_TEST(invokeCustomTypes); DECLARE_TEST(invokeMetaConstructor); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + DECLARE_TEST(invokeMetaConstructorNoMacro); +#endif DECLARE_TEST(invokeTypedefTypes); DECLARE_TEST(invokeException); DECLARE_TEST(invokeQueuedAutoRegister); @@ -344,6 +380,9 @@ private slots: DECLARE_TEST(classInfo); DECLARE_TEST(metaMethod); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + DECLARE_TEST(metaMethodNoMacro); +#endif DECLARE_TEST(indexOfMethod_data); DECLARE_TEST(indexOfMethod); @@ -483,6 +522,13 @@ W_OBJECT_IMPL(CTestObjectOverloads) #define FUNCTION(x) "QMetaObject::" x ": " +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +tst_QMetaObject::tst_QMetaObject() +{ + qRegisterMetaType(); +} +#endif + void tst_QMetaObject::connectSlotsByName() { CTestObject obj; @@ -517,7 +563,9 @@ void tst_QMetaObject::connectSlotsByName() QCOMPARE(obj2.invokeCount2, 1); } +#if QT_VERSION < QT_VERSION_CHECK(6,5,0) struct MyUnregisteredType { }; +#endif static int countedStructObjectsCount = 0; struct CountedStruct @@ -534,13 +582,24 @@ class ObjectException : public std::exception { }; W_REGISTER_ARGTYPE(QString&) W_REGISTER_ARGTYPE(QThread*) +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +W_REGISTER_ARGTYPE(MyForwardDeclaredType&) +W_REGISTER_ARGTYPE(const MyForwardDeclaredType&) +W_REGISTER_ARGTYPE(qlonglong*) +W_REGISTER_ARGTYPE(MyForwardDeclaredType*) +#else W_REGISTER_ARGTYPE(MyUnregisteredType) +#endif W_REGISTER_ARGTYPE(CountedStruct) class QtTestObject: public QObject { friend class tst_QMetaObject; W_OBJECT(QtTestObject) +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QtTestObject(QObject *parent, int, int); W_CONSTRUCTOR(QObject*, int, int); + QtTestObject(QObject *parent, int); W_CONSTRUCTOR(QObject*, int); +#endif public: QtTestObject(); @@ -582,6 +641,19 @@ public slots: W_SLOT(sl13); qint64 sl14(); W_SLOT(sl14); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + qlonglong *sl15(qlonglong *); + W_SLOT(sl15); + MyForwardDeclaredType *sl16(MyForwardDeclaredType *); + W_SLOT(sl16); + + void overloadedSlot(); + W_SLOT(overloadedSlot, ()); + void overloadedSlot(int, int); + W_SLOT(overloadedSlot, (int, int)); + void overloadedSlot(int); + W_SLOT(overloadedSlot, (int)); +#endif void testSender(); W_SLOT(testSender); @@ -593,11 +665,19 @@ public slots: { QObject::moveToThread(t); } W_SLOT(moveToThread); + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + // void slotWithUnregisteredParameterType(const MyForwardDeclaredType &); + // W_SLOT(slotWithUnregisteredParameterType); + // void slotWithOneUnregisteredParameterType(QString a1, const MyForwardDeclaredType &a2); + // W_SLOT(slotWithOneUnregisteredParameterType); +#else void slotWithUnregisteredParameterType(MyUnregisteredType); W_SLOT(slotWithUnregisteredParameterType); void slotWithOneUnregisteredParameterType(QString a1, MyUnregisteredType a2); W_SLOT(slotWithOneUnregisteredParameterType); +#endif CountedStruct throwingSlot(const CountedStruct &, CountedStruct s2) { #ifndef QT_NO_EXCEPTIONS @@ -657,6 +737,16 @@ QtTestObject::QtTestObject(QObject *parent) { } +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +QtTestObject::QtTestObject(QObject *parent, int, int) + : QObject(parent) +{ slotResult = "ii"; } + +QtTestObject::QtTestObject(QObject *parent, int) + : QObject(parent) +{ slotResult = "i"; } +#endif + void QtTestObject::sl0() { slotResult = "sl0"; } QString QtTestObject::sl1(QString s1) { slotResult = "sl1:" + s1; return "yessir"; } void QtTestObject::sl2(QString s1, QString s2) { slotResult = "sl2:" + s1 + s2; } @@ -689,6 +779,29 @@ QList QtTestObject::sl13(QList l1) { slotResult = "sl13"; return l1; } qint64 QtTestObject::sl14() { slotResult = "sl14"; return Q_INT64_C(123456789)*123456789; } +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +qlonglong *QtTestObject::sl15(qlonglong *ptr) +{ slotResult = "sl15"; return ptr; } +MyForwardDeclaredType *QtTestObject::sl16(MyForwardDeclaredType *ptr) +{ + slotResult = "sl16:"; + if (ptr) { + slotResult += "notnull"; + return nullptr; + } + slotResult += "null"; + return getForwardDeclaredPointer(); +} + +void QtTestObject::overloadedSlot() +{ slotResult = "overloadedSlot"; } + +void QtTestObject::overloadedSlot(int x, int y) +{ slotResult = "overloadedSlot:" + QString::number(x) + ',' + QString::number(y); } + +void QtTestObject::overloadedSlot(int x) +{ slotResult = "overloadedSlot:" + QString::number(x); } +#endif void QtTestObject::testReference(QString &str) { slotResult = "testReference:" + str; str = "gotcha"; } @@ -701,11 +814,19 @@ void QtTestObject::testSender() slotResult = QString::asprintf("%p", static_cast(sender())); } +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +// void QtTestObject::slotWithUnregisteredParameterType(const MyForwardDeclaredType &) +// { slotResult = "slotWithUnregisteredReturnType"; } + +// void QtTestObject::slotWithOneUnregisteredParameterType(QString a1, const MyForwardDeclaredType &) +// { slotResult = "slotWithUnregisteredReturnType-" + a1; } +#else void QtTestObject::slotWithUnregisteredParameterType(MyUnregisteredType) { slotResult = "slotWithUnregisteredReturnType"; } void QtTestObject::slotWithOneUnregisteredParameterType(QString a1, MyUnregisteredType) { slotResult = "slotWithUnregisteredReturnType-" + a1; } +#endif void QtTestObject::staticFunction0() { @@ -725,6 +846,15 @@ void tst_QMetaObject::invokeMetaMember() // Test nullptr char *nullCharArray = nullptr; const char *nullConstCharArray = nullptr; +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(!QMetaObject::invokeMethod(nullptr, nullCharArray Q_NO_ARG)); + QVERIFY(!QMetaObject::invokeMethod(nullptr, nullConstCharArray Q_NO_ARG)); + QVERIFY(!QMetaObject::invokeMethod(nullptr, "sl0" Q_NO_ARG)); + QVERIFY(!QMetaObject::invokeMethod(&obj, nullCharArray Q_NO_ARG)); + QVERIFY(!QMetaObject::invokeMethod(&obj, nullConstCharArray Q_NO_ARG)); + QVERIFY(!QMetaObject::invokeMethod(&obj, nullCharArray, Qt::AutoConnection Q_NO_ARG)); + QVERIFY(!QMetaObject::invokeMethod(&obj, nullConstCharArray, Qt::AutoConnection Q_NO_ARG)); +#else QVERIFY(!QMetaObject::invokeMethod(nullptr, nullCharArray)); QVERIFY(!QMetaObject::invokeMethod(nullptr, nullConstCharArray)); QVERIFY(!QMetaObject::invokeMethod(nullptr, "sl0")); @@ -732,10 +862,15 @@ void tst_QMetaObject::invokeMetaMember() QVERIFY(!QMetaObject::invokeMethod(&obj, nullConstCharArray)); QVERIFY(!QMetaObject::invokeMethod(&obj, nullCharArray, Qt::AutoConnection)); QVERIFY(!QMetaObject::invokeMethod(&obj, nullConstCharArray, Qt::AutoConnection)); +#endif QVERIFY(!QMetaObject::invokeMethod(&obj, nullCharArray, Qt::AutoConnection, QGenericReturnArgument())); QVERIFY(!QMetaObject::invokeMethod(&obj, nullConstCharArray, Qt::AutoConnection, QGenericReturnArgument())); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(QMetaObject::invokeMethod(&obj, "sl0" Q_NO_ARG)); +#else QVERIFY(QMetaObject::invokeMethod(&obj, "sl0")); +#endif QCOMPARE(obj.slotResult, QString("sl0")); QVERIFY(QMetaObject::invokeMethod(&obj, "sl1", Q_ARG(QString, t1))); @@ -774,16 +909,34 @@ void tst_QMetaObject::invokeMetaMember() Q_ARG(QString, t7), Q_ARG(QString, t8), Q_ARG(QString, t9))); QCOMPARE(obj.slotResult, QString("sl9:123456789")); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(QMetaObject::invokeMethod(&obj, "sl11" Q_NO_ARG)); +#else QVERIFY(QMetaObject::invokeMethod(&obj, "sl11")); +#endif QCOMPARE(obj.slotResult, QString("sl11")); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(QMetaObject::invokeMethod(&obj, "testSender" Q_NO_ARG)); +#else QVERIFY(QMetaObject::invokeMethod(&obj, "testSender")); +#endif QCOMPARE(obj.slotResult, QString("0x0")); QString refStr("whatever"); +#ifdef USE_COMPAT_Q_ARG QVERIFY(QMetaObject::invokeMethod(&obj, "testReference", QGenericArgument("QString&", &refStr))); QCOMPARE(obj.slotResult, QString("testReference:whatever")); QCOMPARE(refStr, QString("gotcha")); + obj.slotResult.clear(); +#endif + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + refStr = "whatever"; + QVERIFY(QMetaObject::invokeMethod(&obj, "testReference", Q_ARG(QString&, refStr))); + QCOMPARE(obj.slotResult, QString("testReference:whatever")); + QCOMPARE(refStr, QString("gotcha")); +#endif qint64 ll1 = -1; quint64 ll2 = 0; @@ -834,8 +987,44 @@ void tst_QMetaObject::invokeMetaMember() QCOMPARE(return64, Q_INT64_C(123456789)*123456789); QCOMPARE(obj.slotResult, QString("sl14")); + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + // pointers + QVERIFY(QMetaObject::invokeMethod(&obj, "sl15", Q_ARG(qlonglong*, &return64))); + QCOMPARE(obj.slotResult, QString("sl15")); + QVERIFY(QMetaObject::invokeMethod(&obj, "sl16", Q_ARG(MyForwardDeclaredType*, getForwardDeclaredPointer()))); + QCOMPARE(obj.slotResult, QString("sl16:notnull")); + + obj.slotResult.clear(); + qint64 *return64Ptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl15", Q_RETURN_ARG(qlonglong*, return64Ptr), Q_ARG(qlonglong*, &return64))); + QCOMPARE(return64Ptr, &return64); + QCOMPARE(obj.slotResult, QString("sl15")); + + obj.slotResult.clear(); + MyForwardDeclaredType *forwardPtr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl16", Q_RETURN_ARG(MyForwardDeclaredType*, forwardPtr), + Q_ARG(MyForwardDeclaredType*, nullptr))); + QCOMPARE(forwardPtr, getForwardDeclaredPointer()); + QCOMPARE(obj.slotResult, QString("sl16:null")); + +#ifndef QT_NO_DATA_RELOCATION // this doesn't work with the new API on Windows +#endif + // test overloads + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot" Q_NO_ARG)); + QCOMPARE(obj.slotResult, QString("overloadedSlot")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Q_ARG(int, 1))); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Q_ARG(int, 1), Q_ARG(int, 42))); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1,42")); +#endif + //test signals +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(QMetaObject::invokeMethod(&obj, "sig0" Q_NO_ARG)); +#else QVERIFY(QMetaObject::invokeMethod(&obj, "sig0")); +#endif QCOMPARE(obj.slotResult, QString("sl0")); QVERIFY(QMetaObject::invokeMethod(&obj, "sig1", Q_ARG(QString, "baba"))); @@ -857,10 +1046,183 @@ void tst_QMetaObject::invokeMetaMember() "Candidates are:\n sl1(QString)"); QVERIFY(!QMetaObject::invokeMethod(&obj, "sl1", Q_ARG(QString, "arg"), Q_ARG(QString, "arg"), Q_ARG(QString, "arg"))); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + // QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::testReference(QString)\n" + // "Candidates are:\n testReference(QString&)"); + // QVERIFY(!QMetaObject::invokeMethod(&obj, "testReference", Q_ARG(QString, exp))); +#endif + //should not have changed since last test. QCOMPARE(exp, QString("yessir")); QCOMPARE(obj.slotResult, QString("sl1:hehe")); } +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +// this is a copy-paste-adapt of the above +void tst_QMetaObject::invokeMetaMemberNoMacros() +{ + QtTestObject obj; + + QString t1("1"); QString t2("2"); QString t3("3"); QString t4("4"); QString t5("5"); + QString t6("6"); QString t7("7"); QString t8("8"); QString t9("9"); QString t10("X"); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl0")); + QCOMPARE(obj.slotResult, QString("sl0")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl1", t1)); + QCOMPARE(obj.slotResult, QString("sl1:1")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl2", std::as_const(t1), t2)); + QCOMPARE(obj.slotResult, QString("sl2:12")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl3", t1, t2, t3)); + QCOMPARE(obj.slotResult, QString("sl3:123")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl4", t1, t2, t3, + t4)); + QCOMPARE(obj.slotResult, QString("sl4:1234")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl5", t1, t2, t3, + t4, QStringLiteral("5"))); + QCOMPARE(obj.slotResult, QString("sl5:12345")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl6", t1, t2, t3, + t4, t5, t6)); + QCOMPARE(obj.slotResult, QString("sl6:123456")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl7", t1, t2, t3, + t4, t5, t6, + t7)); + QCOMPARE(obj.slotResult, QString("sl7:1234567")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl8", t1, t2, t3, + t4, t5, t6, + t7, t8)); + QCOMPARE(obj.slotResult, QString("sl8:12345678")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl9", t1, t2, t3, + t4, t5, t6, + t7, t8, t9)); + QCOMPARE(obj.slotResult, QString("sl9:123456789")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl11")); + QCOMPARE(obj.slotResult, QString("sl11")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "testSender")); + QCOMPARE(obj.slotResult, QString("0x0")); + + // this is not working for now + // QString refStr("whatever"); + // QVERIFY(QMetaObject::invokeMethod(&obj, "testReference", refStr)); + // QCOMPARE(obj.slotResult, QString("testReference:whatever")); + // QCOMPARE(refStr, QString("gotcha")); + + qint64 ll1 = -1; + quint64 ll2 = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, + "testLongLong", + ll1, + ll2)); + QCOMPARE(obj.slotResult, QString("testLongLong:-1,0")); + + QString exp; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl1", qReturnArg(exp), QStringLiteral("bubu"))); + QCOMPARE(exp, QString("yessir")); + QCOMPARE(obj.slotResult, QString("sl1:bubu")); + + QObject *ptr = nullptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl11", qReturnArg(ptr))); + QCOMPARE(ptr, (QObject *)&obj); + QCOMPARE(obj.slotResult, QString("sl11")); + // try again with a space: + ptr = nullptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl11", qReturnArg(ptr))); + QCOMPARE(ptr, (QObject *)&obj); + QCOMPARE(obj.slotResult, QString("sl11")); + + const char *ptr2 = nullptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl12", qReturnArg(ptr2))); + QVERIFY(ptr2 != nullptr); + QCOMPARE(obj.slotResult, QString("sl12")); + // try again with a space: + ptr2 = nullptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl12", qReturnArg(ptr2))); + QVERIFY(ptr2 != nullptr); + QCOMPARE(obj.slotResult, QString("sl12")); + + // test w/ template args + QList returnValue, argument; + argument << QString("one") << QString("two") << QString("three"); + QVERIFY(QMetaObject::invokeMethod(&obj, "sl13", + qReturnArg(returnValue), + argument)); + QCOMPARE(returnValue, argument); + QCOMPARE(obj.slotResult, QString("sl13")); + + // return qint64 + qint64 return64; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl14", + qReturnArg(return64))); + QCOMPARE(return64, Q_INT64_C(123456789)*123456789); + QCOMPARE(obj.slotResult, QString("sl14")); + + // pointers + QVERIFY(QMetaObject::invokeMethod(&obj, "sl15", &return64)); + QCOMPARE(obj.slotResult, QString("sl15")); + QVERIFY(QMetaObject::invokeMethod(&obj, "sl16", getForwardDeclaredPointer())); + QCOMPARE(obj.slotResult, QString("sl16:notnull")); + + obj.slotResult.clear(); + qint64 *return64Ptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl15", qReturnArg(return64Ptr), &return64)); + QCOMPARE(return64Ptr, &return64); + QCOMPARE(obj.slotResult, QString("sl15")); + + obj.slotResult.clear(); + MyForwardDeclaredType *forwardPtr = nullptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl16", qReturnArg(forwardPtr), + forwardPtr)); + QCOMPARE(forwardPtr, getForwardDeclaredPointer()); + QCOMPARE(obj.slotResult, QString("sl16:null")); + + // test overloads + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot")); + QCOMPARE(obj.slotResult, QString("overloadedSlot")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", 1)); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", 1, 42)); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1,42")); + + //test signals + QVERIFY(QMetaObject::invokeMethod(&obj, "sig0")); + QCOMPARE(obj.slotResult, QString("sl0")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sig1", QStringLiteral("baba"))); + QCOMPARE(obj.slotResult, QString("sl1:baba")); + + exp.clear(); + QVERIFY(QMetaObject::invokeMethod(&obj, "sig1", qReturnArg(exp), QStringLiteral("hehe"))); + QCOMPARE(exp, QString("yessir")); + QCOMPARE(obj.slotResult, QString("sl1:hehe")); + + QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::doesNotExist()"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "doesNotExist")); + QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::sl1(QString)(QString)"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "sl1(QString)", QStringLiteral("arg"))); + QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::sl3(QString)\n" + "Candidates are:\n sl3(QString,QString,QString)"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "sl3", QStringLiteral("arg"))); + QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::sl1(QString,QString,QString)\n" + "Candidates are:\n sl1(QString)"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "sl1", QStringLiteral("arg"), QStringLiteral("arg"), QStringLiteral("arg"))); + // QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::testReference(QString)\n" + // "Candidates are:\n testReference(QString&)"); + // QVERIFY(!QMetaObject::invokeMethod(&obj, "testReference", exp)); + + //should not have changed since last test. + QCOMPARE(exp, QString("yessir")); + QCOMPARE(obj.slotResult, QString("sl1:hehe")); +} +#endif void testFunction(){} @@ -932,7 +1294,11 @@ void tst_QMetaObject::invokeQueuedMetaMember() { QtTestObject obj; +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(QMetaObject::invokeMethod(&obj, "sl0", Qt::QueuedConnection Q_NO_ARG)); +#else QVERIFY(QMetaObject::invokeMethod(&obj, "sl0", Qt::QueuedConnection)); +#endif QVERIFY(obj.slotResult.isEmpty()); qApp->processEvents(QEventLoop::AllEvents); QCOMPARE(obj.slotResult, QString("sl0")); @@ -952,10 +1318,41 @@ void tst_QMetaObject::invokeQueuedMetaMember() qApp->processEvents(QEventLoop::AllEvents); QCOMPARE(obj.slotResult, QString("sl9:123456789")); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + // pointers + qint64 return64; + obj.slotResult.clear(); + QVERIFY(QMetaObject::invokeMethod(&obj, "sl15", Qt::QueuedConnection, Q_ARG(qlonglong*, &return64))); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl15")); + + // since Qt 6.5, this works even for pointers to forward-declared types + obj.slotResult.clear(); + QVERIFY(QMetaObject::invokeMethod(&obj, "sl16", Qt::QueuedConnection, Q_ARG(MyForwardDeclaredType*, getForwardDeclaredPointer()))); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl16:notnull")); + +#ifndef QT_NO_DATA_RELOCATION // this doesn't work with the new API on Windows +#endif + // test overloads + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::QueuedConnection Q_NO_ARG)); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("overloadedSlot")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::QueuedConnection, Q_ARG(int, 1))); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::QueuedConnection, Q_ARG(int, 1), Q_ARG(int, 42))); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1,42")); +#endif // signals obj.slotResult.clear(); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(QMetaObject::invokeMethod(&obj, "sig0", Qt::QueuedConnection Q_NO_ARG)); +#else QVERIFY(QMetaObject::invokeMethod(&obj, "sig0", Qt::QueuedConnection)); +#endif QVERIFY(obj.slotResult.isEmpty()); qApp->processEvents(QEventLoop::AllEvents); QCOMPARE(obj.slotResult, QString("sl0")); @@ -979,6 +1376,38 @@ void tst_QMetaObject::invokeQueuedMetaMember() qApp->processEvents(QEventLoop::AllEvents); QCOMPARE(obj.slotResult, QString("testLongLong:-1,0")); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + // QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::testReference(QString)\n" + // "Candidates are:\n testReference(QString&)"); + // QVERIFY(!QMetaObject::invokeMethod(&obj, "testReference", Qt::QueuedConnection, Q_ARG(QString, exp))); + + // QString refStr = "whatever"; + // QTest::ignoreMessage(QtWarningMsg, "QMetaMethod::invoke: Unable to handle unregistered datatype 'QString&'"); + // QVERIFY(!QMetaObject::invokeMethod(&obj, "testReference", Qt::QueuedConnection, Q_ARG(QString&, refStr))); + // QCOMPARE(refStr, "whatever"); +#endif + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +#ifdef USE_COMPAT_Q_ARG // this doesn't compile with the new API + obj.slotResult.clear(); + { + const MyForwardDeclaredType &t = getForwardDeclaredType(); + QTest::ignoreMessage(QtWarningMsg, "QMetaMethod::invoke: Unable to handle unregistered datatype 'MyForwardDeclaredType'"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "slotWithUnregisteredParameterType", Qt::QueuedConnection, Q_ARG(MyForwardDeclaredType, t))); + QVERIFY(obj.slotResult.isEmpty()); + } + + obj.slotResult.clear(); + { + QString a1("Cannot happen"); + const MyForwardDeclaredType &t = getForwardDeclaredType(); + QTest::ignoreMessage(QtWarningMsg, "QMetaMethod::invoke: Unable to handle unregistered datatype 'MyForwardDeclaredType'"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "slotWithOneUnregisteredParameterType", Qt::QueuedConnection, + Q_ARG(QString, a1), Q_ARG(MyForwardDeclaredType, t))); + QVERIFY(obj.slotResult.isEmpty()); + } +#endif +#else obj.slotResult.clear(); { MyUnregisteredType t; @@ -996,8 +1425,93 @@ void tst_QMetaObject::invokeQueuedMetaMember() Q_ARG(QString, a1), Q_ARG(MyUnregisteredType, t))); QVERIFY(obj.slotResult.isEmpty()); } +#endif } +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +// this is a copy-paste-adapt of the above +void tst_QMetaObject::invokeQueuedMetaMemberNoMacro() +{ + QtTestObject obj; + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl0", Qt::QueuedConnection)); + QVERIFY(obj.slotResult.isEmpty()); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl0")); + obj.slotResult = QString(); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl1", Qt::QueuedConnection, QString("hallo"))); + QVERIFY(obj.slotResult.isEmpty()); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl1:hallo")); + obj.slotResult = QString(); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl9", Qt::QueuedConnection, QStringLiteral("1"), QStringLiteral("2"), + QStringLiteral("3"), QStringLiteral("4"), QStringLiteral("5"), + QStringLiteral("6"), QStringLiteral("7"), QStringLiteral("8"), + QStringLiteral("9"))); + QVERIFY(obj.slotResult.isEmpty()); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl9:123456789")); + + // pointers + qint64 return64; + obj.slotResult.clear(); + QVERIFY(QMetaObject::invokeMethod(&obj, "sl15", Qt::QueuedConnection, &return64)); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl15")); + + obj.slotResult.clear(); + QVERIFY(QMetaObject::invokeMethod(&obj, "sl16", Qt::QueuedConnection, getForwardDeclaredPointer())); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl16:notnull")); + + // test overloads + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::QueuedConnection)); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("overloadedSlot")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::QueuedConnection, 1)); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::QueuedConnection, 1, 42)); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1,42")); + + // signals + + obj.slotResult.clear(); + QVERIFY(QMetaObject::invokeMethod(&obj, "sig0", Qt::QueuedConnection)); + QVERIFY(obj.slotResult.isEmpty()); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl0")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sig1", Qt::QueuedConnection, QStringLiteral("gogo"))); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl1:gogo")); + + QString exp; + QTest::ignoreMessage(QtWarningMsg, "QMetaMethod::invoke: Unable to invoke methods with return values in queued connections"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "sig1", Qt::QueuedConnection, qReturnArg(exp), + QStringLiteral("nono"))); + + qint64 ll1 = -1; + quint64 ll2 = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, + "testLongLong", + Qt::QueuedConnection, + ll1, + ll2)); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("testLongLong:-1,0")); + + // QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::testReference(QString)\n" + // "Candidates are:\n testReference(QString&)"); + // QVERIFY(!QMetaObject::invokeMethod(&obj, "testReference", exp)); + // QCOMPARE(obj.slotResult, QString("testLongLong:-1,0")); + // QVERIFY(exp.isEmpty()); +} +#endif + void tst_QMetaObject::invokeQueuedPointer() { QtTestObject obj; @@ -1094,16 +1608,33 @@ void tst_QMetaObject::invokeBlockingQueuedMetaMember() Q_ARG(QString, t7), Q_ARG(QString, t8), Q_ARG(QString, t9))); QCOMPARE(obj.slotResult, QString("sl9:123456789")); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(QMetaObject::invokeMethod(&obj, "sl11", Qt::BlockingQueuedConnection Q_NO_ARG)); +#else QVERIFY(QMetaObject::invokeMethod(&obj, "sl11", Qt::BlockingQueuedConnection)); +#endif QCOMPARE(obj.slotResult, QString("sl11")); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(QMetaObject::invokeMethod(&obj, "testSender", Qt::BlockingQueuedConnection Q_NO_ARG)); +#else QVERIFY(QMetaObject::invokeMethod(&obj, "testSender", Qt::BlockingQueuedConnection)); +#endif QCOMPARE(obj.slotResult, QString("0x0")); QString refStr("whatever"); +#ifdef USE_COMPAT_Q_ARG QVERIFY(QMetaObject::invokeMethod(&obj, "testReference", Qt::BlockingQueuedConnection, QGenericArgument("QString&", &refStr))); QCOMPARE(obj.slotResult, QString("testReference:whatever")); QCOMPARE(refStr, QString("gotcha")); + obj.slotResult.clear(); +#endif +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + // refStr = "whatever"; + // QVERIFY(QMetaObject::invokeMethod(&obj, "testReference", Qt::BlockingQueuedConnection, Q_ARG(QString&, refStr))); + // QCOMPARE(obj.slotResult, QString("testReference:whatever")); + // QCOMPARE(refStr, QString("gotcha")); +#endif qint64 ll1 = -1; quint64 ll2 = 0; @@ -1148,8 +1679,50 @@ void tst_QMetaObject::invokeBlockingQueuedMetaMember() QCOMPARE(returnValue, argument); QCOMPARE(obj.slotResult, QString("sl13")); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + // return qint64 + qint64 return64; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl14", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(qint64, return64))); + QCOMPARE(return64, Q_INT64_C(123456789)*123456789); + QCOMPARE(obj.slotResult, QString("sl14")); + + // pointers + QVERIFY(QMetaObject::invokeMethod(&obj, "sl15", Qt::BlockingQueuedConnection, Q_ARG(qlonglong*, &return64))); + QCOMPARE(obj.slotResult, QString("sl15")); + QVERIFY(QMetaObject::invokeMethod(&obj, "sl16", Qt::BlockingQueuedConnection, Q_ARG(MyForwardDeclaredType*, getForwardDeclaredPointer()))); + QCOMPARE(obj.slotResult, QString("sl16:notnull")); + + obj.slotResult.clear(); + qint64 *return64Ptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl15", Qt::BlockingQueuedConnection, Q_RETURN_ARG(qlonglong*, return64Ptr), Q_ARG(qlonglong*, &return64))); + QCOMPARE(return64Ptr, &return64); + QCOMPARE(obj.slotResult, QString("sl15")); + + obj.slotResult.clear(); + MyForwardDeclaredType *forwardPtr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl16", Qt::BlockingQueuedConnection, Q_RETURN_ARG(MyForwardDeclaredType*, forwardPtr), + Q_ARG(MyForwardDeclaredType*, nullptr))); + QCOMPARE(forwardPtr, getForwardDeclaredPointer()); + QCOMPARE(obj.slotResult, QString("sl16:null")); + +#ifndef QT_NO_DATA_RELOCATION // this doesn't work with the new API on Windows +#endif + // test overloads + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::BlockingQueuedConnection Q_NO_ARG)); + QCOMPARE(obj.slotResult, QString("overloadedSlot")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::BlockingQueuedConnection, Q_ARG(int, 1))); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::BlockingQueuedConnection, Q_ARG(int, 1), Q_ARG(int, 42))); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1,42")); +#endif + //test signals +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(QMetaObject::invokeMethod(&obj, "sig0", Qt::BlockingQueuedConnection Q_NO_ARG)); +#else QVERIFY(QMetaObject::invokeMethod(&obj, "sig0", Qt::BlockingQueuedConnection)); +#endif QCOMPARE(obj.slotResult, QString("sl0")); QVERIFY(QMetaObject::invokeMethod(&obj, "sig1", Qt::BlockingQueuedConnection, Q_ARG(QString, "baba"))); @@ -1161,7 +1734,11 @@ void tst_QMetaObject::invokeBlockingQueuedMetaMember() QCOMPARE(obj.slotResult, QString("sl1:hehe")); QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::doesNotExist()"); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QVERIFY(!QMetaObject::invokeMethod(&obj, "doesNotExist", Qt::BlockingQueuedConnection Q_NO_ARG)); +#else QVERIFY(!QMetaObject::invokeMethod(&obj, "doesNotExist", Qt::BlockingQueuedConnection)); +#endif QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::sl1(QString)(QString)"); QVERIFY(!QMetaObject::invokeMethod(&obj, "sl1(QString)", Qt::BlockingQueuedConnection, Q_ARG(QString, "arg"))); QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::sl3(QString)\n" @@ -1170,6 +1747,11 @@ void tst_QMetaObject::invokeBlockingQueuedMetaMember() QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::sl1(QString,QString,QString)\n" "Candidates are:\n sl1(QString)"); QVERIFY(!QMetaObject::invokeMethod(&obj, "sl1", Qt::BlockingQueuedConnection, Q_ARG(QString, "arg"), Q_ARG(QString, "arg"), Q_ARG(QString, "arg"))); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + // QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::testReference(QString)\n" + // "Candidates are:\n testReference(QString&)"); + // QVERIFY(!QMetaObject::invokeMethod(&obj, "testReference", Qt::BlockingQueuedConnection, Q_ARG(QString, exp))); +#endif //should not have changed since last test. QCOMPARE(exp, QString("yessir")); @@ -1178,8 +1760,180 @@ void tst_QMetaObject::invokeBlockingQueuedMetaMember() QVERIFY(QMetaObject::invokeMethod(&obj, "moveToThread", Qt::BlockingQueuedConnection, Q_ARG(QThread*, QThread::currentThread()))); t.quit(); QVERIFY(t.wait()); +} + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +// this is a copy-paste-adapt of the above +void tst_QMetaObject::invokeBlockingQueuedMetaMemberNoMacros() +{ + QThread t; + t.start(); + QtTestObject obj; + obj.moveToThread(&t); + + QString t1("1"); QString t2("2"); QString t3("3"); QString t4("4"); QString t5("5"); + QString t6("6"); QString t7("7"); QString t8("8"); QString t9("9"); QString t10("X"); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl1", Qt::BlockingQueuedConnection, t1)); + QCOMPARE(obj.slotResult, QString("sl1:1")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl2", Qt::BlockingQueuedConnection, t1, t2)); + QCOMPARE(obj.slotResult, QString("sl2:12")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl3", Qt::BlockingQueuedConnection, t1, t2, t3)); + QCOMPARE(obj.slotResult, QString("sl3:123")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl4", Qt::BlockingQueuedConnection, t1, t2, + t3, t4)); + QCOMPARE(obj.slotResult, QString("sl4:1234")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl5", Qt::BlockingQueuedConnection, t1, t2, + t3, t4, QStringLiteral("5"))); + QCOMPARE(obj.slotResult, QString("sl5:12345")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl6", Qt::BlockingQueuedConnection, t1, t2, + t3, t4, t5, t6)); + QCOMPARE(obj.slotResult, QString("sl6:123456")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl7", Qt::BlockingQueuedConnection, t1, t2, + t3, t4, t5, t6, + t7)); + QCOMPARE(obj.slotResult, QString("sl7:1234567")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl8", Qt::BlockingQueuedConnection, t1, t2, + t3, t4, t5, t6, + t7, t8)); + QCOMPARE(obj.slotResult, QString("sl8:12345678")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl9", Qt::BlockingQueuedConnection, t1, t2, + t3, t4, t5, t6, + t7, t8, t9)); + QCOMPARE(obj.slotResult, QString("sl9:123456789")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sl11", Qt::BlockingQueuedConnection)); + QCOMPARE(obj.slotResult, QString("sl11")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "testSender", Qt::BlockingQueuedConnection)); + QCOMPARE(obj.slotResult, QString("0x0")); + + // this is not working + // QString refStr("whatever"); + // QVERIFY(QMetaObject::invokeMethod(&obj, "testReference", Qt::BlockingQueuedConnection, refStr)); + // QCOMPARE(obj.slotResult, QString("testReference:whatever")); + // QCOMPARE(refStr, QString("gotcha")); + + qint64 ll1 = -1; + quint64 ll2 = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, + "testLongLong", + Qt::BlockingQueuedConnection, + ll1, + ll2)); + QCOMPARE(obj.slotResult, QString("testLongLong:-1,0")); + + QString exp; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl1", Qt::BlockingQueuedConnection, qReturnArg(exp), QStringLiteral("bubu"))); + QCOMPARE(exp, QString("yessir")); + QCOMPARE(obj.slotResult, QString("sl1:bubu")); + + QObject *ptr = nullptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl11", Qt::BlockingQueuedConnection, qReturnArg(ptr))); + QCOMPARE(ptr, (QObject *)&obj); + QCOMPARE(obj.slotResult, QString("sl11")); + // try again with a space: + ptr = nullptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl11", Qt::BlockingQueuedConnection, qReturnArg(ptr))); + QCOMPARE(ptr, (QObject *)&obj); + QCOMPARE(obj.slotResult, QString("sl11")); + + const char *ptr2 = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl12", Qt::BlockingQueuedConnection, qReturnArg(ptr2))); + QVERIFY(ptr2 != 0); + QCOMPARE(obj.slotResult, QString("sl12")); + // try again with a space: + ptr2 = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl12", Qt::BlockingQueuedConnection, qReturnArg(ptr2))); + QVERIFY(ptr2 != 0); + QCOMPARE(obj.slotResult, QString("sl12")); + + // test w/ template args + QList returnValue, argument; + argument << QString("one") << QString("two") << QString("three"); + QVERIFY(QMetaObject::invokeMethod(&obj, "sl13", Qt::BlockingQueuedConnection, + qReturnArg(returnValue), + argument)); + QCOMPARE(returnValue, argument); + QCOMPARE(obj.slotResult, QString("sl13")); + + // return qint64 + qint64 return64; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl14", Qt::BlockingQueuedConnection, + qReturnArg(return64))); + QCOMPARE(return64, Q_INT64_C(123456789)*123456789); + QCOMPARE(obj.slotResult, QString("sl14")); + + // pointers + QVERIFY(QMetaObject::invokeMethod(&obj, "sl15", Qt::BlockingQueuedConnection, &return64)); + QCOMPARE(obj.slotResult, QString("sl15")); + QVERIFY(QMetaObject::invokeMethod(&obj, "sl16", Qt::BlockingQueuedConnection, getForwardDeclaredPointer())); + QCOMPARE(obj.slotResult, QString("sl16:notnull")); + + obj.slotResult.clear(); + qint64 *return64Ptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl15", Qt::BlockingQueuedConnection, qReturnArg(return64Ptr), &return64)); + QCOMPARE(return64Ptr, &return64); + QCOMPARE(obj.slotResult, QString("sl15")); + + obj.slotResult.clear(); + MyForwardDeclaredType *forwardPtr = nullptr; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl16", Qt::BlockingQueuedConnection, qReturnArg(forwardPtr), + forwardPtr)); + QCOMPARE(forwardPtr, getForwardDeclaredPointer()); + QCOMPARE(obj.slotResult, QString("sl16:null")); + + // test overloads + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::BlockingQueuedConnection)); + QCOMPARE(obj.slotResult, QString("overloadedSlot")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::BlockingQueuedConnection, 1)); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1")); + QVERIFY(QMetaObject::invokeMethod(&obj, "overloadedSlot", Qt::BlockingQueuedConnection, 1, 42)); + QCOMPARE(obj.slotResult, QString("overloadedSlot:1,42")); + + //test signals + QVERIFY(QMetaObject::invokeMethod(&obj, "sig0", Qt::BlockingQueuedConnection)); + QCOMPARE(obj.slotResult, QString("sl0")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "sig1", Qt::BlockingQueuedConnection, QStringLiteral("baba"))); + QCOMPARE(obj.slotResult, QString("sl1:baba")); + + exp.clear(); + QVERIFY(QMetaObject::invokeMethod(&obj, "sig1", Qt::BlockingQueuedConnection, qReturnArg(exp), QStringLiteral("hehe"))); + QCOMPARE(exp, QString("yessir")); + QCOMPARE(obj.slotResult, QString("sl1:hehe")); + QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::doesNotExist()"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "doesNotExist", Qt::BlockingQueuedConnection)); + QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::sl1(QString)(QString)"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "sl1(QString)", Qt::BlockingQueuedConnection, QStringLiteral("arg"))); + QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::sl3(QString)\n" + "Candidates are:\n sl3(QString,QString,QString)"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "sl3", Qt::BlockingQueuedConnection, QStringLiteral("arg"))); + QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::sl1(QString,QString,QString)\n" + "Candidates are:\n sl1(QString)"); + QVERIFY(!QMetaObject::invokeMethod(&obj, "sl1", Qt::BlockingQueuedConnection, QStringLiteral("arg"), QStringLiteral("arg"), QStringLiteral("arg"))); + // QTest::ignoreMessage(QtWarningMsg, "QMetaObject::invokeMethod: No such method QtTestObject::testReference(QString)\n" + // "Candidates are:\n testReference(QString&)"); + // QVERIFY(!QMetaObject::invokeMethod(&obj, "testReference", Qt::BlockingQueuedConnection, exp)); + + //should not have changed since last test. + QCOMPARE(exp, QString("yessir")); + QCOMPARE(obj.slotResult, QString("sl1:hehe")); + + QVERIFY(QMetaObject::invokeMethod(&obj, "moveToThread", Qt::BlockingQueuedConnection, QThread::currentThread())); + t.quit(); + QVERIFY(t.wait()); } +#endif void tst_QMetaObject::invokeBlockingQueuedPointer() { @@ -1310,6 +2064,12 @@ void tst_QMetaObject::invokeCustomTypes() QCOMPARE(obj.sum, 0); QVERIFY(QMetaObject::invokeMethod(&obj, "sl1", Q_ARG(MyType, tp))); QCOMPARE(obj.sum, 3); + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + obj.sum = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, "sl1", tp)); + QCOMPARE(obj.sum, 3); +#endif } namespace NamespaceWithConstructibleClass @@ -1331,10 +2091,20 @@ W_OBJECT_IMPL(NamespaceWithConstructibleClass::ConstructibleClass) void tst_QMetaObject::invokeMetaConstructor() { const QMetaObject *mo = &QtTestObject::staticMetaObject; + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +#ifdef USE_COMPAT_Q_ARG + { + QObject *obj = mo->newInstance(QGenericArgument()); + QVERIFY(!obj); + } +#endif +#else { QObject *obj = mo->newInstance(); QVERIFY(!obj); } +#endif { QtTestObject obj; QObject *obj2 = mo->newInstance(Q_ARG(QObject*, &obj)); @@ -1357,7 +2127,61 @@ void tst_QMetaObject::invokeMetaConstructor() QTest::ignoreMessage(QtWarningMsg, "QMetaObject::newInstance: type MyGadget does not inherit QObject"); QVERIFY(!MyGadget::staticMetaObject.newInstance()); } + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + // overloaded constructors + QObject parent; + { + QObject *obj = mo->newInstance(Q_ARG(QObject*, &parent)); + QVERIFY(obj); + QCOMPARE(static_cast(obj)->slotResult, ""); + } + { + QObject *obj = mo->newInstance(Q_ARG(QObject*, &parent), Q_ARG(int, 1)); + QVERIFY(obj); + QCOMPARE(static_cast(obj)->slotResult, "i"); + } + { + QObject *obj = mo->newInstance(Q_ARG(QObject*, &parent), Q_ARG(int, 1), Q_ARG(int, 42)); + QVERIFY(obj); + QCOMPARE(static_cast(obj)->slotResult, "ii"); + } +#endif +} + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +// this is a copy-paste-adapt of the above +void tst_QMetaObject::invokeMetaConstructorNoMacro() +{ + const QMetaObject *mo = &QtTestObject::staticMetaObject; + { + QObject *obj = mo->newInstance(); + QVERIFY(!obj); + } + { + QtTestObject obj; + QObject *obj2 = mo->newInstance(static_cast(&obj)); + QVERIFY(obj2 != 0); + QCOMPARE(obj2->parent(), (QObject*)&obj); + QVERIFY(qobject_cast(obj2) != 0); + } + // class in namespace + const QMetaObject *nsmo = &NamespaceWithConstructibleClass::ConstructibleClass::staticMetaObject; + { + QtTestObject obj; + QObject *obj2 = nsmo->newInstance(static_cast(&obj)); + QVERIFY(obj2 != 0); + QCOMPARE(obj2->parent(), (QObject*)&obj); + QVERIFY(qobject_cast(obj2) != 0); + } + // gadget shouldn't return a valid pointer + { + QCOMPARE(MyGadget::staticMetaObject.constructorCount(), 1); + QTest::ignoreMessage(QtWarningMsg, "QMetaObject::newInstance: type MyGadget does not inherit QObject"); + QVERIFY(!MyGadget::staticMetaObject.newInstance()); + } } +#endif void tst_QMetaObject::invokeTypedefTypes() { @@ -1372,6 +2196,14 @@ void tst_QMetaObject::invokeTypedefTypes() QCOMPARE(spy.count(), 1); QCOMPARE(spy.at(0).count(), 1); QCOMPARE(spy.at(0).at(0), QVariant(arg)); + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + spy.clear(); + QVERIFY(QMetaObject::invokeMethod(&obj, "sig_custom", arg)); + QCOMPARE(spy.size(), 1); + QCOMPARE(spy.at(0).size(), 1); + QCOMPARE(spy.at(0).at(0), QVariant(arg)); +#endif } void tst_QMetaObject::invokeException() @@ -1386,6 +2218,16 @@ void tst_QMetaObject::invokeException() QFAIL("Did not throw"); } catch(ObjectException &) {} QCOMPARE(countedStructObjectsCount, 0); + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + try { + CountedStruct s; + QVERIFY(QMetaObject::invokeMethod(&obj, "throwingSlot", qReturnArg(s), + s, s)); + QFAIL("Did not throw"); + } catch(ObjectException &) {} + QCOMPARE(countedStructObjectsCount, 0); +#endif #else QSKIP("Needs exceptions"); #endif @@ -1407,6 +2249,20 @@ void tst_QMetaObject::invokeQueuedAutoRegister() qApp->processEvents(QEventLoop::AllEvents); QCOMPARE(obj.slotResult, QString("slotWithRegistrableArgument:myShared-myShared-myShared-myShared-00")); + +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + obj.slotResult.clear(); + QVERIFY(QMetaObject::invokeMethod( + &obj, "slotWithRegistrableArgument", Qt::QueuedConnection, + shared.data(), QPointer(shared.data()), + QSharedPointer(shared), QWeakPointer(shared), + QList(), + QList())); + QVERIFY(obj.slotResult.isEmpty()); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, + QString("slotWithRegistrableArgument:myShared-myShared-myShared-myShared-00")); +#endif } void tst_QMetaObject::normalizedSignature_data() @@ -1765,9 +2621,17 @@ void tst_QMetaObject::metaMethod() QVERIFY(index > 0); method = QtTestObject::staticMetaObject.method(index); //wrong args +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QTest::ignoreMessage(QtWarningMsg, "QMetaMethod::invoke: too few arguments (5) in call to QtTestObject::sl5(QString,QString,QString,QString,QString)"); +#endif QVERIFY(!method.invoke(&obj, Q_ARG(QString, "1"), Q_ARG(QString, "2"), Q_ARG(QString, "3"), Q_ARG(QString, "4"))); //QVERIFY(!method.invoke(&obj, Q_ARG(QString, "1"), Q_ARG(QString, "2"), Q_ARG(QString, "3"), Q_ARG(QString, "4"), Q_ARG(QString, "5"), Q_ARG(QString, "6"))); //QVERIFY(!method.invoke(&obj, Q_ARG(QString, "1"), Q_ARG(QString, "2"), Q_ARG(QString, "3"), Q_ARG(QString, "4"), Q_ARG(int, 5))); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QTest::ignoreMessage(QtWarningMsg, "QMetaMethod::invokeMethod: return type mismatch for method " + "QtTestObject::sl5(QString,QString,QString,QString,QString): " + "cannot convert from void to QString during invocation"); +#endif QVERIFY(!method.invoke(&obj, Q_RETURN_ARG(QString, ret), Q_ARG(QString, "1"), Q_ARG(QString, "2"), Q_ARG(QString, "3"), Q_ARG(QString, "4"), Q_ARG(QString, "5"))); //wrong object @@ -1794,6 +2658,61 @@ void tst_QMetaObject::metaMethod() QCOMPARE(obj.slotResult, QString("sl13")); } +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +// this is a copy-paste-adapt of the above +void tst_QMetaObject::metaMethodNoMacro() +{ + QString str("foo"); + QString ret("bar"); + QMetaMethod method; + QVERIFY(!method.invoke(this)); + QVERIFY(!method.invoke(this, str)); + QVERIFY(!method.invoke(this, qReturnArg(ret), str)); + QCOMPARE(str, QString("foo")); + QCOMPARE(ret, QString("bar")); + + QtTestObject obj; + QString t1("1"); QString t2("2"); QString t3("3"); QString t4("4"); QString t5("5"); + QString t6("6"); QString t7("7"); QString t8("8"); QString t9("9"); QString t10("X"); + + int index = QtTestObject::staticMetaObject.indexOfMethod("sl5(QString,QString,QString,QString,QString)"); + QVERIFY(index > 0); + method = QtTestObject::staticMetaObject.method(index); + //wrong args + QTest::ignoreMessage(QtWarningMsg, "QMetaMethod::invoke: too few arguments (5) in call to QtTestObject::sl5(QString,QString,QString,QString,QString)"); + QVERIFY(!method.invoke(&obj, QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3"), QStringLiteral("4"))); + //QVERIFY(!method.invoke(&obj, "1", "2", "3", "4", "5", "6")); + //QVERIFY(!method.invoke(&obj, "1", "2", "3", "4", 5)); + QTest::ignoreMessage(QtWarningMsg, "QMetaMethod::invokeMethod: return type mismatch for method " + "QtTestObject::sl5(QString,QString,QString,QString,QString): " + "cannot convert from void to QString during invocation"); + QVERIFY(!method.invoke(&obj, qReturnArg(ret), QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3"), QStringLiteral("4"), QStringLiteral("5"))); + + //wrong object + //QVERIFY(!method.invoke(this, "1", "2", "3", "4", "5")); + QVERIFY(!method.invoke(0, QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3"), QStringLiteral("4"), QStringLiteral("5"))); + QCOMPARE(ret, QString("bar")); + QCOMPARE(obj.slotResult, QString()); + + QVERIFY(method.invoke(&obj, QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3"), QStringLiteral("4"), QStringLiteral("5"))); + QCOMPARE(obj.slotResult, QString("sl5:12345")); + + index = QtTestObject::staticMetaObject.indexOfMethod("sl13(QList)"); + QVERIFY(index > 0); + QMetaMethod sl13 = QtTestObject::staticMetaObject.method(index); + QList returnValue, argument; + argument << QString("one") << QString("two") << QString("three"); + //wrong object + //QVERIFY(!sl13.invoke(this, qReturnArg(returnValue), argument)); + QVERIFY(!sl13.invoke(0, qReturnArg(returnValue), argument)); + QVERIFY(returnValue.isEmpty()); + + QVERIFY(sl13.invoke(&obj, qReturnArg(returnValue), argument)); + QCOMPARE(returnValue, argument); + QCOMPARE(obj.slotResult, QString("sl13")); +} +#endif + void tst_QMetaObject::indexOfMethod_data() { QTest::addColumn("object"); diff --git a/tests/qt/qmetaproperty/CMakeLists.txt b/tests/qt/qmetaproperty/CMakeLists.txt new file mode 100644 index 0000000..72b1132 --- /dev/null +++ b/tests/qt/qmetaproperty/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_executable(qmetaproperty + "tst_qmetaproperty6.cpp" +) +target_link_libraries(qmetaproperty PRIVATE + verdigris + Qt6::Test +) diff --git a/tests/qt/qobject/CMakeLists.txt b/tests/qt/qobject/CMakeLists.txt new file mode 100644 index 0000000..b0d9072 --- /dev/null +++ b/tests/qt/qobject/CMakeLists.txt @@ -0,0 +1,18 @@ + +add_subdirectory("signalbug") +find_package(Qt6 COMPONENTS Network REQUIRED) + +add_executable(qobject + "tst_qobject6.cpp" +) +target_link_libraries(qobject PRIVATE + verdigris + Qt6::Test + Qt6::TestPrivate + Qt6::Network +) +target_compile_definitions(qobject PRIVATE + QT_DISABLE_DEPRECATED_BEFORE=0 + QT_NO_DEBUG + W_SIGNALBUG="$" +) diff --git a/tests/qt/qobject/signalbug/CMakeLists.txt b/tests/qt/qobject/signalbug/CMakeLists.txt new file mode 100644 index 0000000..6cbe08b --- /dev/null +++ b/tests/qt/qobject/signalbug/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_executable(signalbug + "signalbug.cpp" + "signalbug.h" +) +target_link_libraries(signalbug PRIVATE + verdigris +) diff --git a/tests/qt/qobject/tst_qobject6.cpp b/tests/qt/qobject/tst_qobject6.cpp index d268fba..3b9091e 100644 --- a/tests/qt/qobject/tst_qobject6.cpp +++ b/tests/qt/qobject/tst_qobject6.cpp @@ -91,6 +91,9 @@ private slots: void connectNotify_connectSlotsByName(); W_SLOT(connectNotify_connectSlotsByName, W_Access::Private{}); void connectDisconnectNotify_shadowing(); W_SLOT(connectDisconnectNotify_shadowing, W_Access::Private{}); void connectReferenceToIncompleteTypes(); W_SLOT(connectReferenceToIncompleteTypes, W_Access::Private{}); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + void connectAutoQueuedIncomplete(); W_SLOT(connectAutoQueuedIncomplete, W_Access::Private{}); +#endif void emitInDefinedOrder(); W_SLOT(emitInDefinedOrder, W_Access::Private{}); void customTypes(); W_SLOT(customTypes, W_Access::Private{}); void streamCustomTypes(); W_SLOT(streamCustomTypes, W_Access::Private{}); @@ -960,6 +963,43 @@ void tst_QObject::connectReferenceToIncompleteTypes() { QVERIFY(connection); } +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +struct Incomplete2; +class QObjectWithIncomplete2 : public QObject { + W_OBJECT(QObjectWithIncomplete2) + +public: + QObjectWithIncomplete2(QObject *parent=nullptr) : QObject(parent) {} +signals: + void signalWithIncomplete(Incomplete2 *ptr) W_SIGNAL(signalWithIncomplete, (Incomplete2*), ptr); +public slots: + void slotWithIncomplete(Incomplete2 *) { calledSlot = true; } W_SLOT(slotWithIncomplete,(Incomplete2*)); + void run() { Q_EMIT signalWithIncomplete(nullptr); } W_SLOT(run); +public: + bool calledSlot = false; +}; + +void tst_QObject::connectAutoQueuedIncomplete() +{ + auto objectWithIncomplete1 = new QObjectWithIncomplete2(); + auto objectWithIncomplete2 = new QObjectWithIncomplete2(); + auto t = new QThread(this); + auto cleanup = qScopeGuard([&](){ + t->quit(); + QVERIFY(t->wait()); + delete objectWithIncomplete1; + delete objectWithIncomplete2; + }); + + t->start(); + objectWithIncomplete2->moveToThread(t); + + connect(objectWithIncomplete2, &QObjectWithIncomplete2::signalWithIncomplete, + objectWithIncomplete1, &QObjectWithIncomplete2::slotWithIncomplete); + QMetaObject::invokeMethod(objectWithIncomplete2, "run", Qt::QueuedConnection); + QTRY_VERIFY(objectWithIncomplete1->calledSlot); +} +#endif static void connectDisconnectNotifyTestSlot() {} @@ -1561,8 +1601,12 @@ void tst_QObject::customTypes() QCOMPARE(checker.received.value(), t1.value()); checker.received = t0; +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + qRegisterMetaType(); +#else int idx = qRegisterMetaType("CustomType"); QCOMPARE(QMetaType::type("CustomType"), idx); +#endif checker.disconnect(); connect(&checker, SIGNAL(signal1(CustomType)), &checker, SLOT(slot1(CustomType)), @@ -1576,10 +1620,12 @@ void tst_QObject::customTypes() QCOMPARE(checker.received.value(), t2.value()); QCOMPARE(instanceCount, 4); +#if QT_VERSION < QT_VERSION_CHECK(6,5,0) QVERIFY(QMetaType::isRegistered(idx)); QCOMPARE(qRegisterMetaType("CustomType"), idx); QCOMPARE(QMetaType::type("CustomType"), idx); QVERIFY(QMetaType::isRegistered(idx)); +#endif } QCOMPARE(instanceCount, 3); } @@ -1588,13 +1634,23 @@ void tst_QObject::streamCustomTypes() { QByteArray ba; +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + qRegisterMetaType(); + + QMetaType metaType = QMetaType::fromType(); +#else int idx = qRegisterMetaType("CustomType"); +#endif { CustomType t1(1, 2, 3); QCOMPARE(instanceCount, 1); QDataStream stream(&ba, (QIODevice::OpenMode)QIODevice::WriteOnly); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + metaType.save(stream, &t1); +#else QMetaType::save(stream, idx, &t1); +#endif } QCOMPARE(instanceCount, 0); @@ -1603,7 +1659,11 @@ void tst_QObject::streamCustomTypes() CustomType t2; QCOMPARE(instanceCount, 1); QDataStream stream(&ba, (QIODevice::OpenMode)QIODevice::ReadOnly); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + metaType.load(stream, &t2); +#else QMetaType::load(stream, idx, &t2); +#endif QCOMPARE(instanceCount, 1); QCOMPARE(t2.i1, 1); QCOMPARE(t2.i2, 2); @@ -2045,7 +2105,11 @@ void tst_QObject::property() const int idx = mo->indexOfProperty("variant"); QVERIFY(idx != -1); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QCOMPARE(mo->property(idx).userType(), QMetaType::QVariant); +#else QCOMPARE(QMetaType::Type(mo->property(idx).type()), QMetaType::QVariant); +#endif QCOMPARE(object.property("variant"), QVariant()); QVariant variant1(42); QVariant variant2("string"); @@ -2064,7 +2128,11 @@ void tst_QObject::property() QVERIFY(!property.isEnumType()); QCOMPARE(property.typeName(), "CustomType*"); qRegisterMetaType(); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QCOMPARE_GE(property.typeId(), QMetaType::User); +#else QCOMPARE(property.type(), QVariant::UserType); +#endif QCOMPARE(property.userType(), qMetaTypeId()); CustomType *customPointer = nullptr; @@ -2079,7 +2147,11 @@ void tst_QObject::property() property = mo->property(mo->indexOfProperty("custom")); QVERIFY(property.isWritable()); QCOMPARE(property.typeName(), "CustomType*"); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QCOMPARE_GE(property.typeId(), QMetaType::User); +#else QCOMPARE(property.type(), QVariant::UserType); +#endif QCOMPARE(property.userType(), qMetaTypeId()); QVERIFY(object.setProperty("custom", customVariant)); @@ -2113,7 +2185,11 @@ void tst_QObject::property() QCOMPARE(object.property("priority").toInt(), 0); // now it's registered, so it works as expected +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + int priorityMetaTypeId = qRegisterMetaType(); +#else int priorityMetaTypeId = qRegisterMetaType("PropertyObject::Priority"); +#endif QVERIFY(mo->indexOfProperty("priority") != -1); property = mo->property(mo->indexOfProperty("priority")); @@ -2123,7 +2199,11 @@ void tst_QObject::property() #else QCOMPARE(property.typeName(), "PropertyObject::Priority"); #endif +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QCOMPARE_GE(property.typeId(), QMetaType::User); +#else QCOMPARE(property.type(), QVariant::UserType); +#endif QCOMPARE(property.userType(), priorityMetaTypeId); var = object.property("priority"); @@ -2146,7 +2226,11 @@ void tst_QObject::property() object.setProperty("priority", var); QCOMPARE(qvariant_cast(object.property("priority")), PropertyObject::High); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + qRegisterMetaType(); +#else qRegisterMetaType("CustomString"); +#endif QVERIFY(mo->indexOfProperty("customString") != -1); QCOMPARE(object.property("customString").toString(), QString()); object.setCustomString("String1"); @@ -3055,7 +3139,11 @@ void tst_QObject::floatProperty() QVERIFY(idx > 0); QMetaProperty prop = obj.metaObject()->property(idx); QVERIFY(prop.isValid()); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QCOMPARE(prop.typeId(), QMetaType::fromType().id()); +#else QCOMPARE(int(prop.type()), QMetaType::type("float")); +#endif QVERIFY(!prop.write(&obj, QVariant("Hello"))); QVERIFY(prop.write(&obj, QVariant::fromValue(128.0f))); QVariant v = prop.read(&obj); @@ -3070,7 +3158,11 @@ void tst_QObject::qrealProperty() QVERIFY(idx > 0); QMetaProperty prop = obj.metaObject()->property(idx); QVERIFY(prop.isValid()); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QCOMPARE(prop.typeId(), QMetaType::fromType().id()); +#else QCOMPARE(int(prop.type()), QMetaType::type("qreal")); +#endif QVERIFY(!prop.write(&obj, QVariant("Hello"))); QVERIFY(prop.write(&obj, QVariant::fromValue(128.0f))); @@ -3121,7 +3213,11 @@ void tst_QObject::dynamicProperties() QVERIFY(!obj.setProperty("myuserproperty", "Hello")); QCOMPARE(obj.changedDynamicProperties.count(), 1); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QCOMPARE(obj.property("myuserproperty").typeId(), QMetaType::QString); +#else QCOMPARE(obj.property("myuserproperty").type(), QVariant::String); +#endif QCOMPARE(obj.property("myuserproperty").toString(), QString("Hello")); QCOMPARE(obj.dynamicPropertyNames().count(), 1); @@ -3132,7 +3228,11 @@ void tst_QObject::dynamicProperties() QVERIFY(!obj.setProperty("myuserproperty", QByteArray("Hello"))); QCOMPARE(obj.changedDynamicProperties.count(), 1); QCOMPARE(obj.changedDynamicProperties.first(), QByteArray("myuserproperty")); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + QCOMPARE(obj.property("myuserproperty").typeId(), QMetaType::QByteArray); +#else QCOMPARE(obj.property("myuserproperty").type(), QVariant::ByteArray); +#endif QCOMPARE(obj.property("myuserproperty").toString(), QByteArray("Hello")); // unset the property @@ -4891,8 +4991,12 @@ void tst_QObject::customTypesPointer() checker.disconnect(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + qRegisterMetaType(); +#else int idx = qRegisterMetaType("CustomType"); QCOMPARE(QMetaType::type("CustomType"), idx); +#endif connect(&checker, &QCustomTypeChecker::signal1, &checker, &QCustomTypeChecker::slot1, Qt::QueuedConnection); @@ -4905,10 +5009,12 @@ void tst_QObject::customTypesPointer() QCOMPARE(checker.received.value(), t2.value()); QCOMPARE(instanceCount, 4); +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) QVERIFY(QMetaType::isRegistered(idx)); QCOMPARE(qRegisterMetaType("CustomType"), idx); QCOMPARE(QMetaType::type("CustomType"), idx); QVERIFY(QMetaType::isRegistered(idx)); +#endif // Test auto registered type (QList) QList list; @@ -8536,6 +8642,9 @@ W_OBJECT_IMPL(ReceiverObject) W_OBJECT_IMPL(AutoConnectSender) W_OBJECT_IMPL(AutoConnectReceiver) W_OBJECT_IMPL(QObjectWithIncomplete) +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +W_OBJECT_IMPL(QObjectWithIncomplete2) +#endif W_OBJECT_IMPL(ConnectByNameNotifySenderObject) W_OBJECT_IMPL(ConnectByNameNotifyReceiverObject) W_OBJECT_IMPL(ConnectDisconnectNotifyShadowObject) diff --git a/tests/qt/qproperty/CMakeLists.txt b/tests/qt/qproperty/CMakeLists.txt new file mode 100644 index 0000000..ebdd16e --- /dev/null +++ b/tests/qt/qproperty/CMakeLists.txt @@ -0,0 +1,9 @@ + +add_executable(qproperty + "tst_qproperty6.cpp" +) +target_link_libraries(qproperty PRIVATE + verdigris + Qt6::CorePrivate + Qt6::Test +) diff --git a/tests/qt/qproperty/tst_qproperty6.cpp b/tests/qt/qproperty/tst_qproperty6.cpp index 800c2be..6f08f21 100644 --- a/tests/qt/qproperty/tst_qproperty6.cpp +++ b/tests/qt/qproperty/tst_qproperty6.cpp @@ -50,6 +50,16 @@ #include +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +#if __has_include() && __cplusplus >= 202002L && !defined(Q_QDOC) +#include +#define QT_SOURCE_LOCATION_NAMESPACE std +#elif __has_include() && !defined(Q_QDOC) +#include +#define QT_SOURCE_LOCATION_NAMESPACE std::experimental +#endif +#endif + #if QT_VERSION < QT_VERSION_CHECK(6,3,0) // note: this class is marked AUTOTEST_EXPORT which will only work on Qt COIN, I guess. // - we inline this to avoid linker issues @@ -111,6 +121,9 @@ private slots: DECLARE_TEST(quntypedBindableApi); DECLARE_TEST(readonlyConstQBindable); DECLARE_TEST(qobjectBindableManualNotify); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + DECLARE_TEST(qobjectBindableReallocatedBindingStorage); +#endif DECLARE_TEST(qobjectBindableSignalTakingNewValue); DECLARE_TEST(testNewStuff); @@ -128,6 +141,10 @@ private slots: DECLARE_TEST(noDoubleNotification); DECLARE_TEST(groupedNotifications); DECLARE_TEST(groupedNotificationConsistency); +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + DECLARE_TEST(bindingGroupMovingBindingData); + DECLARE_TEST(bindingGroupBindingDeleted); +#endif DECLARE_TEST(uninstalledBindingDoesNotEvaluate); DECLARE_TEST(notify); @@ -140,6 +157,13 @@ private slots: #if QT_VERSION >= QT_VERSION_CHECK(6,3,1) DECLARE_TEST(scheduleNotify); #endif +#if QT_VERSION >= QT_VERSION_CHECK(6,3,2) + DECLARE_TEST(notifyAfterAllDepsGone); +#endif +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) + DECLARE_TEST(propertyAdaptorBinding); +#endif + }; W_OBJECT_IMPL(tst_QProperty) @@ -1231,6 +1255,24 @@ void tst_QProperty::qobjectBindableManualNotify() QCOMPARE(object.foo(), 1); } +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +struct ReallocObject : QObject { + ReallocObject() + { v.setBinding([this] { return x.value() + y.value() + z.value(); }); } + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, v) + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, x) + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, y) + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, z) +}; + +void tst_QProperty::qobjectBindableReallocatedBindingStorage() +{ + ReallocObject object; + object.x = 1; + QCOMPARE(object.v.value(), 1); +} +#endif + void tst_QProperty::qobjectBindableSignalTakingNewValue() { // Given an object of type MyQObject, @@ -1703,6 +1745,193 @@ class FakeDependencyCreator : public QObject }; W_OBJECT_IMPL(FakeDependencyCreator) +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +class PropertyAdaptorTester : public QObject +{ + W_OBJECT(PropertyAdaptorTester) + +signals: + void fooChanged(int newFoo) W_SIGNAL(fooChanged, (int), newFoo); + +public slots: + void fooHasChanged() { fooChangedCount++; } W_SLOT(fooHasChanged); + +public: + int foo() const { return fooData; } + void setFoo(int i) + { + if (i != fooData) { + fooData = i; + fooChanged(fooData); + } + } + +public: + int fooData = 0; + int fooChangedCount = 0; + + W_PROPERTY(int, foo READ foo WRITE setFoo NOTIFY fooChanged); + W_PROPERTY(int, foo1 READ foo WRITE setFoo); +}; +W_OBJECT_IMPL(PropertyAdaptorTester) + +void tst_QProperty::propertyAdaptorBinding() +{ + QProperty source { 5 }; + QProperty dest1 { 99 }; + QProperty dest2 { 98 }; + + // Check binding of non BINDABLE property + PropertyAdaptorTester object; + QObject::connect(&object, &PropertyAdaptorTester::fooChanged, &object, + &PropertyAdaptorTester::fooHasChanged); + QBindable binding(&object, "foo"); + binding.setBinding([&]() { return source + 1; }); + QCOMPARE(object.foo(), 6); + QCOMPARE(object.fooChangedCount, 1); + + struct MyBindable : QBindable { + using QBindable::QBindable; + QtPrivate::QPropertyAdaptorSlotObject* data() { + return static_cast(QUntypedBindable::data); + } + } dataBinding(&object, "foo"); + QPropertyBindingDataPointer data{&dataBinding.data()->bindingData()}; + + QCOMPARE(data.observerCount(), 0); + dest1.setBinding(binding.makeBinding()); + QCOMPARE(data.observerCount(), 1); + dest2.setBinding([=]() { return binding.value() + 1; }); + binding = {}; + QCOMPARE(data.observerCount(), 2); + + // Check addNotifer + { + int local_foo = 0; + auto notifier = QBindable(&object, "foo").addNotifier([&]() { local_foo++; }); + QCOMPARE(data.observerCount(), 3); + QCOMPARE(object.foo(), 6); + QCOMPARE(dest1.value(), 6); + QCOMPARE(dest2.value(), 7); + QCOMPARE(local_foo, 0); + QCOMPARE(object.fooChangedCount, 1); + + source = 7; + QCOMPARE(object.foo(), 8); + QCOMPARE(dest1.value(), 8); + QCOMPARE(dest2.value(), 9); + QCOMPARE(local_foo, 1); + QCOMPARE(object.fooChangedCount, 2); + } + + QCOMPARE(data.observerCount(), 2); + + // Check a new QBindable object can override the existing binding + QBindable(&object, "foo").setValue(10); + QCOMPARE(object.foo(), 10); + QCOMPARE(dest1.value(), 10); + QCOMPARE(dest2.value(), 11); + QCOMPARE(object.fooChangedCount, 3); + source.setValue(99); + QCOMPARE(object.foo(), 10); + QCOMPARE(dest1.value(), 10); + QCOMPARE(dest2.value(), 11); + QCOMPARE(object.fooChangedCount, 3); + object.setFoo(12); + QCOMPARE(object.foo(), 12); + QCOMPARE(dest1.value(), 12); + QCOMPARE(dest2.value(), 13); + QCOMPARE(object.fooChangedCount, 4); + + // Check binding multiple notifiers + QProperty source2 { 20 }; + source.setValue(21); + binding = QBindable(&object, "foo"); + binding.setBinding([&]() { return source + source2; }); + QCOMPARE(object.foo(), 41); + QCOMPARE(dest1.value(), 41); + QCOMPARE(object.fooChangedCount, 5); + source.setValue(22); + QCOMPARE(object.foo(), 42); + QCOMPARE(dest1.value(), 42); + QCOMPARE(object.fooChangedCount, 6); + source2.setValue(21); + QCOMPARE(object.foo(), 43); + QCOMPARE(dest1.value(), 43); + QCOMPARE(object.fooChangedCount, 7); + + // Check update group + Qt::beginPropertyUpdateGroup(); + source.setValue(23); + source2.setValue(22); + QCOMPARE(object.foo(), 43); + QCOMPARE(dest1.value(), 43); + QCOMPARE(object.fooChangedCount, 7); + Qt::endPropertyUpdateGroup(); + QCOMPARE(object.foo(), 45); + QCOMPARE(dest1.value(), 45); + QCOMPARE(object.fooChangedCount, 8); + + PropertyAdaptorTester object2; + PropertyAdaptorTester object3; + + // Check multiple observers + QBindable binding2(&object2, "foo"); + QBindable binding3(&object3, "foo"); + binding.setBinding([=]() { return binding2.value(); }); + binding3.setBinding([=]() { return binding.value(); }); + QCOMPARE(object.foo(), 0); + QCOMPARE(object2.foo(), 0); + QCOMPARE(object3.foo(), 0); + QCOMPARE(dest1.value(), 0); + object2.setFoo(1); + QCOMPARE(object.foo(), 1); + QCOMPARE(object2.foo(), 1); + QCOMPARE(object3.foo(), 1); + QCOMPARE(dest1.value(), 1); + + // Check interoperation with BINDABLE properties + MyQObject bindableObject; + bindableObject.fooData.setBinding([]() { return 5; }); + QVERIFY(bindableObject.fooData.hasBinding()); + QVERIFY(!bindableObject.barData.hasBinding()); + QVERIFY(QBindable(&bindableObject, "foo").hasBinding()); + QBindable bindableBar(&bindableObject, "bar"); + QVERIFY(!bindableBar.hasBinding()); + bindableBar.setBinding([]() { return 6; }); + QVERIFY(bindableBar.hasBinding()); + QVERIFY(bindableObject.barData.hasBinding()); + +// Check bad arguments +#ifndef QT_NO_DEBUG + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property is not valid"); +#endif + QVERIFY(!QBindable(&object, QMetaProperty{}).isValid()); +#ifndef QT_NO_DEBUG + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property foo1 has no notify signal"); +#endif + QVERIFY(!QBindable(&object, "foo1").isValid()); +#ifndef QT_NO_DEBUG + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property foo of type int does not match requested type bool"); +#endif + QVERIFY(!QBindable(&object, "foo").isValid()); +#ifndef QT_NO_DEBUG + QTest::ignoreMessage(QtMsgType::QtWarningMsg, + "QUntypedBindable: Property foo does not belong to this object"); +#endif + QObject qobj; + QVERIFY(!QBindable( + &qobj, + object.metaObject()->property(object.metaObject()->indexOfProperty("foo"))) + .isValid()); +#ifndef QT_NO_DEBUG + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: No property named fizz"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property is not valid"); +#endif + QVERIFY(!QBindable(&object, "fizz").isValid()); +} +#endif + void tst_QProperty::noFakeDependencies() { FakeDependencyCreator fdc; @@ -1739,6 +1968,65 @@ struct CustomType int anotherValue = 0; }; +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0) +void tst_QProperty::bindingGroupMovingBindingData() +{ + // auto tester = std::make_unique(); + // auto testerPriv = QObjectPrivate::get(tester.get()); + + // auto dummyNotifier = tester->property.addNotifier([](){}); + // auto bindingData = testerPriv->bindingStorage.bindingData(&tester->property); + // QVERIFY(bindingData); // we have a notifier, so there should be binding data + + // Qt::beginPropertyUpdateGroup(); + // auto cleanup = qScopeGuard([](){ Qt::endPropertyUpdateGroup(); }); + // tester->property = 42; + // QCOMPARE(testerPriv->bindingStorage.bindingData(&tester->property), bindingData); + // auto proxyData = QPropertyBindingDataPointer::proxyData(bindingData); + // // as we've modified the property, we now should have a proxy for the delayed notification + // QVERIFY(proxyData); + // // trigger binding data reallocation + // std::array propertyDataArray; + // for (auto&& data: propertyDataArray) + // testerPriv->bindingStorage.bindingData(&data, true); + // // binding data has moved + // QVERIFY(testerPriv->bindingStorage.bindingData(&tester->property) != bindingData); + // bindingData = testerPriv->bindingStorage.bindingData(&tester->property); + // // the proxy data has been updated + // QCOMPARE(proxyData->originalBindingData, bindingData); + + // tester.reset(); + // // the property data is gone, proxyData should have been informed + // QCOMPARE(proxyData->originalBindingData, nullptr); + // QVERIFY(proxyData); +} + +void tst_QProperty::bindingGroupBindingDeleted() +{ + auto deleter = std::make_unique(); + auto toBeDeleted = std::make_unique(); + + bool calledHandler = false; + deleter->property.setBinding([&](){ + int newValue = toBeDeleted->property; + if (newValue == 42) + toBeDeleted.reset(); + return newValue; + }); + auto handler = toBeDeleted->property.onValueChanged([&]() { calledHandler = true; } ); + { + Qt::beginPropertyUpdateGroup(); + auto cleanup = qScopeGuard([](){ Qt::endPropertyUpdateGroup(); }); + QVERIFY(toBeDeleted); + toBeDeleted->property = 42; + // ASAN should not complain here + } + QVERIFY(!toBeDeleted); + // the change notification is sent, even if the binding is deleted during evaluation + QVERIFY(calledHandler); +} +#endif + class PropertyWithInitializationTester : public QObject { W_OBJECT(PropertyWithInitializationTester) @@ -1987,4 +2275,28 @@ void tst_QProperty::scheduleNotify() } #endif +#if QT_VERSION >= QT_VERSION_CHECK(6,3,2) +void tst_QProperty::notifyAfterAllDepsGone() +{ + bool b = true; + QProperty iprop; + QProperty jprop(42); + iprop.setBinding([&](){ + if (b) + return jprop.value(); + return 13; + }); + int changeCounter = 0; + auto keepAlive = iprop.onValueChanged([&](){ changeCounter++; }); + QCOMPARE(iprop.value(), 42); + jprop = 44; + QCOMPARE(iprop.value(), 44); + QCOMPARE(changeCounter, 1); + b = false; + jprop = 43; + QCOMPARE(iprop.value(), 13); + QCOMPARE(changeCounter, 2); +} +#endif + QTEST_MAIN(tst_QProperty); diff --git a/tests/templates/CMakeLists.txt b/tests/templates/CMakeLists.txt new file mode 100644 index 0000000..a185595 --- /dev/null +++ b/tests/templates/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_executable(templates + "tst_templates.cpp" +) +target_link_libraries(templates PRIVATE + verdigris + Qt6::Test +) diff --git a/tools/.clang-format b/tools/.clang-format new file mode 100644 index 0000000..28104dc --- /dev/null +++ b/tools/.clang-format @@ -0,0 +1,52 @@ +--- +BasedOnStyle: LLVM +Standard: Latest +ColumnLimit: 120 +IndentWidth: 4 +AccessModifierOffset: -4 + +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true + +AlignAfterOpenBracket: AlwaysBreak +AlignTrailingComments: false +AlignOperands: false + +AlwaysBreakTemplateDeclarations: false +AlwaysBreakBeforeMultilineStrings: true + +BreakConstructorInitializers: BeforeComma + +BreakBeforeBraces: Custom +BraceWrapping: + AfterNamespace: false + AfterExternBlock: false + AfterClass: false + AfterStruct: false + AfterUnion: false + AfterEnum: false + AfterFunction: false + AfterControlStatement: false + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: true + +BinPackArguments: false +BinPackParameters: false + +SpaceAfterTemplateKeyword: false +SpaceBeforeParens: ControlStatements +IndentPPDirectives: None + +SortIncludes: false +SortUsingDeclarations: false + +PointerAlignment: Left + +PenaltyReturnTypeOnItsOwnLine: 500 diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..31b086c --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,2 @@ + +add_subdirectory(metatypes_extractor) diff --git a/tools/metatypes_extractor/BufferSpan.h b/tools/metatypes_extractor/BufferSpan.h new file mode 100644 index 0000000..43e589e --- /dev/null +++ b/tools/metatypes_extractor/BufferSpan.h @@ -0,0 +1,80 @@ +#pragma once +#include +#include + +struct BufferSpan { + using Elem = uint8_t; + using Ptr = const Elem*; + Ptr data{}; + size_t count{}; + + auto begin() const -> Ptr { return data; } + auto end() const -> Ptr { return data + count; } + auto operator[](size_t idx) const -> Elem { return data[idx]; } + + auto seek(size_t offset) const -> BufferSpan { + if (offset >= count) return {}; + return {data + offset, count - offset}; + } + auto truncate_to(size_t to) const -> BufferSpan { + if (count < to) return {}; + return {data, to}; + } + auto is_content_equal(BufferSpan other) const -> bool { + if (count != other.count) return false; + for (auto i = 0u; i < count; i++) + if (data[i] != other[i]) return false; + return true; + } + template auto le_cast() const -> T { + static constexpr auto size = sizeof(T); + auto result = T{}; + if (count < size) return result; + if constexpr (size == 1) { + result = data[0]; + } + else if constexpr (size == 2) { + result = static_cast((static_cast(data[1]) << 8u) + data[0]); + } + else if constexpr (size == 4) { + result = static_cast( + (static_cast(data[3]) << 24u) + (static_cast(data[2]) << 16u) + + (static_cast(data[1]) << 8u) + data[0]); + } + else if constexpr (size == 8) { + result = static_cast( + (static_cast(data[7]) << 56u) + (static_cast(data[6]) << 48u) + + (static_cast(data[5]) << 40u) + (static_cast(data[4]) << 32u) + + (static_cast(data[3]) << 24u) + (static_cast(data[2]) << 16u) + + (static_cast(data[1]) << 8u) + data[0]); + } + else { + static_assert(size == -1, "unhandled"); + } + return result; + } + auto first_string() const -> BufferSpan { + auto len = 0u; + for (; len < count; ++len) { + if (data[len] == 0) break; + } + return truncate_to(len); + } + auto truncate_tail(size_t len) const -> BufferSpan { + if (len >= count) return {}; + return {data + count - len, len}; + } + void each_string(auto callback) const { + auto b = 0u; + while (true) { + while (b < count && data[b] == 0) b++; + auto e = b + 1; + while (e < count && data[e] != 0) e++; + if (b < count) + callback(seek(b).truncate_to(e - b)); + else + break; + b = e + 1; + } + } +}; diff --git a/tools/metatypes_extractor/CMakeLists.txt b/tools/metatypes_extractor/CMakeLists.txt new file mode 100644 index 0000000..e7a87b5 --- /dev/null +++ b/tools/metatypes_extractor/CMakeLists.txt @@ -0,0 +1,13 @@ + +add_executable(metatypes_extractor + "main.cpp" + "BufferSpan.h" + "NtSection.h" + "ElfSection.h" +) +target_compile_features(metatypes_extractor + PRIVATE cxx_std_20 +) +target_link_libraries(metatypes_extractor + PRIVATE Qt6::Core +) diff --git a/tools/metatypes_extractor/ElfSection.h b/tools/metatypes_extractor/ElfSection.h new file mode 100644 index 0000000..d4c726b --- /dev/null +++ b/tools/metatypes_extractor/ElfSection.h @@ -0,0 +1,46 @@ +#pragma once +#include "BufferSpan.h" + +#include + +bool extract_elf_section(BufferSpan image, BufferSpan search_section_name, auto&& callback) { + const auto elf_magic = image.le_cast(); + if (elf_magic != 0x464c457F) return false; // ELF + const auto bit_class = image.seek(4u).le_cast(); + if (bit_class != 1 && bit_class != 2) return false; // 32 or 64 bits + const auto endianess = image.seek(5u).le_cast(); + if (endianess != 1 && endianess != 2) return false; // little or big endian + const auto version = image.seek(6u).le_cast(); + if (version != 1) return false; + if (bit_class == 1) { // 32 bits + // TODO + return true; + } + else { // 64 bits + const auto sections_offset = image.seek(0x28).le_cast(); + const auto section_header_size = image.seek(0x3A).le_cast(); + const auto num_sections = image.seek(0x3C).le_cast(); + const auto name_section_index = image.seek(0x3E).le_cast(); + if (name_section_index >= num_sections) return false; + const auto name_section_header = image.seek(sections_offset + section_header_size * name_section_index).truncate_to(section_header_size); + + const auto names_offset = name_section_header.seek(0x18).le_cast(); + const auto names_size = name_section_header.seek(0x20).le_cast(); + const auto names = image.seek(names_offset).truncate_to(names_size); + + for (auto i = 0u; i < num_sections; i++) { + if (i == name_section_index) continue; + const auto section_header = image.seek(sections_offset + section_header_size * i).truncate_to(section_header_size); + + const auto name_offset = section_header.le_cast(); + const auto name = names.seek(name_offset).first_string().truncate_tail(search_section_name.count); + if (!name.is_content_equal(search_section_name)) continue; + const auto section_offset = section_header.seek(0x18).le_cast(); + const auto section_size = section_header.seek(0x20).le_cast(); + const auto section = image.seek(section_offset).truncate_to(section_size); + if (section.count != section_size) return false; // overflow + callback(section); + } + return true; + } +} \ No newline at end of file diff --git a/tools/metatypes_extractor/NtSection.h b/tools/metatypes_extractor/NtSection.h new file mode 100644 index 0000000..0aa9956 --- /dev/null +++ b/tools/metatypes_extractor/NtSection.h @@ -0,0 +1,41 @@ +#pragma once +#include "BufferSpan.h" + +bool extract_nt_section(BufferSpan image, BufferSpan nt_image, BufferSpan search_section_name, auto&& callback) { + static constexpr auto nt_size = 20u; + const auto num_sections = nt_image.seek(2).le_cast(); + const auto optional_header_size = nt_image.seek(nt_size - 4).le_cast(); + static constexpr auto section_header_size = 40u; + const auto section_headers = + nt_image.seek(nt_size + optional_header_size).truncate_to(num_sections * section_header_size); + if (section_headers.count != num_sections * section_header_size) return false; // overflow + for (auto i = 0u; i < num_sections; i++) { + const auto section_header = section_headers.seek(i * section_header_size); + const auto section_header_name = section_header.truncate_to(8u); + if (!section_header_name.is_content_equal(search_section_name)) continue; + const auto virtual_size = section_header.seek(8u).le_cast(); + if (0 != virtual_size) return virtual_size; // .obj files have not virtual size + const auto size_of_raw_data = section_header.seek(16).le_cast(); + const auto section_size = size_of_raw_data; + const auto section_addr = section_header.seek(20).le_cast(); + const auto section = image.seek(section_addr).truncate_to(section_size); + if (section.count != section_size) return false; // overflow + if (section.data < section_headers.end()) return false; // overlap with headers + callback(section); + } + return true; +} + +bool extract_pe_section(BufferSpan image, BufferSpan search_section_name, auto&& callback) { + static constexpr auto dos_size = 64u; + const auto dos_magic = image.le_cast(); + if (dos_magic != 0x5A4D) return false; // MZ + const auto nt_offset = image.seek(dos_size - 4).le_cast(); + const auto nt_magic = image.seek(nt_offset).le_cast(); + if (nt_magic != 0x4550) return false; // PE + return extract_nt_section(image, image.seek(nt_offset + 4), search_section_name, callback); +} + +bool extract_coff_section(BufferSpan image, BufferSpan search_section_name, auto&& callback) { + return extract_nt_section(image, image, search_section_name, callback); +} diff --git a/tools/metatypes_extractor/main.cpp b/tools/metatypes_extractor/main.cpp new file mode 100644 index 0000000..75d9892 --- /dev/null +++ b/tools/metatypes_extractor/main.cpp @@ -0,0 +1,96 @@ +#include "NtSection.h" +#include "ElfSection.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +auto spanContainer(const auto& container) -> BufferSpan { + return {reinterpret_cast(container.data()), static_cast(container.size())}; +} +auto toByteArray(const BufferSpan& span) -> QByteArray { + return QByteArray::fromRawData(reinterpret_cast(span.data), static_cast(span.count)); +} + +int collectQmlTypes(const QString& outputFilePath, const QStringList& binaryFilePathes) { + auto outputFile = QFile{outputFilePath}; + if (!outputFile.open(QIODevice::WriteOnly)) { + fprintf(stderr, "Error opening %s for writing\n", qPrintable(outputFilePath)); + return EXIT_FAILURE; + } + + const auto qmlTypesString = std::string{"qmltypes"}; + const auto qmlTypesSpan = spanContainer(qmlTypesString); + + auto map = QMap{}; + auto sectionCallback = [&](BufferSpan types) { + types.each_string([&](BufferSpan json) { + auto jsonError = QJsonParseError{}; + auto jsonDocument = QJsonDocument::fromJson(toByteArray(json)); + if (jsonError.error != QJsonParseError::NoError) { + fprintf(stderr, "%s at %d\n", jsonError.errorString().toUtf8().constData(), jsonError.offset); + // return false; + } + auto jsonObject = jsonDocument.object(); + auto key = jsonObject.value(QStringLiteral("inputFile")).toString(); + map.insert(key, jsonObject); + }); + }; + for (auto binaryFilePath : binaryFilePathes) { + auto binaryFile = QFile{binaryFilePath}; + if (!binaryFile.open(QIODevice::ReadOnly)) { + fprintf(stderr, "Error opening %s for reading\n", qPrintable(binaryFilePath)); + return EXIT_FAILURE; + } + const auto binaryData = binaryFile.readAll(); + const auto binarySpan = spanContainer(binaryData); + + const bool isSuccess = extract_pe_section(binarySpan, qmlTypesSpan, sectionCallback) || + extract_elf_section(binarySpan, qmlTypesSpan, sectionCallback) || + extract_coff_section(binarySpan, qmlTypesSpan, sectionCallback); + if (!isSuccess) { + // fprintf(stderr, "Error finding qmltypes section in %s\n", qPrintable(binaryFilePath)); + // return EXIT_FAILURE; + } + } + + auto jsonArray = QJsonArray{}; + const auto jsonValues = map.values(); + for (auto value : jsonValues) { + jsonArray.append(value); + } + + const auto outputDocument = QJsonDocument{jsonArray}; + outputFile.write(outputDocument.toJson()); + + return EXIT_SUCCESS; +} + +int main(int argc, char* argv[]) { + auto app = QCoreApplication{argc, argv}; + QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0")); + + auto cmdParser = QCommandLineParser{}; + cmdParser.addHelpOption(); + cmdParser.addVersionOption(); + + auto outputOption = QCommandLineOption{ + QStringLiteral("out"), QStringLiteral("File path the output json is stored at."), QStringLiteral("file")}; + cmdParser.addOption(outputOption); + + cmdParser.addPositionalArgument( + QStringLiteral("[binary-file]"), QStringLiteral("File pathes of compiled object files")); + + cmdParser.process(app.arguments()); + + const auto binaryFilePathes = cmdParser.positionalArguments(); + const auto outputFilePath = cmdParser.value(outputOption); + + return collectQmlTypes(outputFilePath, binaryFilePathes); +} diff --git a/tools/metatypes_extractor/metatypes_extractor.qbs b/tools/metatypes_extractor/metatypes_extractor.qbs new file mode 100644 index 0000000..62310b4 --- /dev/null +++ b/tools/metatypes_extractor/metatypes_extractor.qbs @@ -0,0 +1,26 @@ + +Application { + name: "metatypes_extractor" + // TODO: condition: qbs is native build + type: base.concat(["metatypes-extractor"]) + version: "1.0.0" + consoleApplication: true + + Depends { name: "cpp" } + Depends { name: "Qt.core" } + cpp.cxxLanguageVersion: "c++20" + cpp.includePaths: ['src'] + + files: [ + "main.cpp", + "BufferSpan.h", + "NtSection.h", + "ElfSection.h", + ] + + Group { + name: "Binary" + fileTagsFilter: product.type + fileTags: "metatypes-extractor" + } +} diff --git a/tools/tools.qbs b/tools/tools.qbs new file mode 100644 index 0000000..6893c7b --- /dev/null +++ b/tools/tools.qbs @@ -0,0 +1,5 @@ +Project { + references: [ + "metatypes_extractor", + ] +} diff --git a/tutorial/CMakeLists.txt b/tutorial/CMakeLists.txt new file mode 100644 index 0000000..6f8f6e1 --- /dev/null +++ b/tutorial/CMakeLists.txt @@ -0,0 +1,4 @@ + +add_subdirectory(features) +add_subdirectory(cpp_api) +add_subdirectory(qml_element) diff --git a/tutorial/cpp_api/CMakeLists.txt b/tutorial/cpp_api/CMakeLists.txt new file mode 100644 index 0000000..e4965fb --- /dev/null +++ b/tutorial/cpp_api/CMakeLists.txt @@ -0,0 +1,13 @@ +find_package(Qt6 COMPONENTS Quick REQUIRED) + +add_executable(cpp_api_tutorial + "main.cpp" + "resources.qrc" +) +target_link_libraries(cpp_api_tutorial + PRIVATE verdigris + PRIVATE Qt6::Quick +) +set_target_properties(cpp_api_tutorial PROPERTIES + AUTORCC ON +) diff --git a/tutorial/cpp_api/cpp_api.qbs b/tutorial/cpp_api/cpp_api.qbs new file mode 100644 index 0000000..8d2705e --- /dev/null +++ b/tutorial/cpp_api/cpp_api.qbs @@ -0,0 +1,14 @@ + +Application { + name: "cpp_api_tutorial" + consoleApplication: true + + Depends { name: "Verdigris" } + Depends { name: "Qt.quick" } + + files: [ + "main.cpp", + "main.qml", + "resources.qrc", + ] +} diff --git a/tutorial/cpp_tutorial.cpp b/tutorial/cpp_api/main.cpp similarity index 99% rename from tutorial/cpp_tutorial.cpp rename to tutorial/cpp_api/main.cpp index 3acb246..715c951 100644 --- a/tutorial/cpp_tutorial.cpp +++ b/tutorial/cpp_api/main.cpp @@ -165,7 +165,7 @@ int main(int argc, char *argv[]) { context->setContextProperty("person", &person); // now you can use it like any other QObject with properties from QML - engine.load(QStringLiteral("qrc:/cpp_tutorial.qml")); + engine.load(QStringLiteral("qrc:/main.qml")); return app.exec(); } diff --git a/tutorial/cpp_tutorial.qml b/tutorial/cpp_api/main.qml similarity index 100% rename from tutorial/cpp_tutorial.qml rename to tutorial/cpp_api/main.qml diff --git a/tutorial/cpp_tutorial.qrc b/tutorial/cpp_api/resources.qrc similarity index 60% rename from tutorial/cpp_tutorial.qrc rename to tutorial/cpp_api/resources.qrc index 593904f..5f6483a 100644 --- a/tutorial/cpp_tutorial.qrc +++ b/tutorial/cpp_api/resources.qrc @@ -1,5 +1,5 @@ - cpp_tutorial.qml + main.qml diff --git a/tutorial/features/CMakeLists.txt b/tutorial/features/CMakeLists.txt new file mode 100644 index 0000000..4486f1e --- /dev/null +++ b/tutorial/features/CMakeLists.txt @@ -0,0 +1,7 @@ + +add_executable(feature_tutorial + "main.cpp" +) +target_link_libraries(feature_tutorial + PRIVATE verdigris +) diff --git a/tutorial/features/features.pro b/tutorial/features/features.pro new file mode 100644 index 0000000..53f0381 --- /dev/null +++ b/tutorial/features/features.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +TARGET = feature_tutorial +QT -= gui +include(../../src/verdigris.pri) + +# Input +SOURCES += main.cpp diff --git a/tutorial/features/features.qbs b/tutorial/features/features.qbs new file mode 100644 index 0000000..ffda61a --- /dev/null +++ b/tutorial/features/features.qbs @@ -0,0 +1,11 @@ + +Application { + name: "feature_tutorial" + consoleApplication: true + + Depends { name: "Verdigris" } + + files: [ + "main.cpp" + ] +} diff --git a/tutorial/tutorial.cpp b/tutorial/features/main.cpp similarity index 100% rename from tutorial/tutorial.cpp rename to tutorial/features/main.cpp diff --git a/tutorial/qml_element/CMakeLists.txt b/tutorial/qml_element/CMakeLists.txt new file mode 100644 index 0000000..6901cad --- /dev/null +++ b/tutorial/qml_element/CMakeLists.txt @@ -0,0 +1,59 @@ + +find_package(Qt6 COMPONENTS Quick REQUIRED) + +# ++++ TODO: create cmake utility +# notes: +# - Two step approach to prevent CMake from generating circular rules +# - An object library with all sources + custom rule to extract metatypes.json +# - A static library where we inject the metatypes as if they were generated by moc +# TODOs: +# - This has proof of concept quality. Improve to make more usable. +# - The injection has some side effects as not all properties are setup correctly (Inspect/PR welcome) +# Eg. Usually Qt creates a folder in build path where qmltypes/qmldir and all qml files are stored. +# - Qt usually does not use generated files directly. But I have no idea why. +function(extract_metatypesjson target obj_target) + set(input_files $) + set(output_file "meta_types/${target}_$_metatypes.json") + target_link_libraries(${target} + PUBLIC ${obj_target} + ) + _qt_internal_get_tool_wrapper_script_path(tool_wrapper) + add_custom_command( + OUTPUT ${output_file} + COMMAND + ${tool_wrapper} + $ --out=${output_file} ${input_files} + DEPENDS metatypes_extractor ${input_files} + COMMENT "Extracting metatypes" + ) + add_custom_target("${target}_metatypesjson" + DEPENDS ${output_file} + ) + add_dependencies(${target} "${target}_metatypesjson") + set_target_properties(${target} PROPERTIES + INTERFACE_QT_MODULE_HAS_META_TYPES YES + INTERFACE_QT_META_TYPES_BUILD_FILE ${output_file} + ) + target_compile_definitions(${obj_target} + PRIVATE QML_ELEMENT_BASE_PATH="${CMAKE_CURRENT_SOURCE_DIR}" + ) +endfunction() +# ---- + +add_library(qml_element_obj OBJECT + "MyObject.h" "MyObject.cpp" +) +target_link_libraries(qml_element_obj + PUBLIC verdigris + PUBLIC Qt6::Quick +) + +add_executable(qml_elemment_tutorial + "main.cpp" +) +extract_metatypesjson(qml_elemment_tutorial qml_element_obj) +qt6_add_qml_module(qml_elemment_tutorial + URI QmlTutorial + VERSION 1.0 + QML_FILES main.qml +) diff --git a/tutorial/qml_element/MyObject.cpp b/tutorial/qml_element/MyObject.cpp new file mode 100644 index 0000000..9654e17 --- /dev/null +++ b/tutorial/qml_element/MyObject.cpp @@ -0,0 +1,24 @@ +#include "MyObject.h" + +/* Here is what would go in the C++ .cpp file: */ +#include + +// And now this is the macro you need to instantiate the meta object. +// It's an additional macro that basically does the same as the code generated by moc. +// W_OBJECT_IMPL(MyObject) + +MyObject::MyObject() {} + +int MyObject::demoProp() const +{ + return m_demoProp; +} + +void MyObject::setDemoProp(int newDemoProp) +{ + if (newDemoProp == m_demoProp) return; + m_demoProp = newDemoProp; + emit demoPropChanged(); +} + +W_QML_ELEMENT_IMPL(MyObject) diff --git a/tutorial/qml_element/MyObject.h b/tutorial/qml_element/MyObject.h new file mode 100644 index 0000000..84dbf46 --- /dev/null +++ b/tutorial/qml_element/MyObject.h @@ -0,0 +1,43 @@ +#pragma once +#include + +#include + +class DemoInterface { +public: + virtual ~DemoInterface() = default; +}; + +QT_BEGIN_NAMESPACE + +#define DemoInterface_iid "demo.DemoInterface" +Q_DECLARE_INTERFACE(DemoInterface, DemoInterface_iid) + +QT_END_NAMESPACE + +class MyObject : public QQuickItem, public DemoInterface +{ + W_OBJECT(MyObject) + W_QML_ELEMENT; + W_INTERFACE(DemoInterface); + + int m_demoProp{}; + +public: + MyObject(); + W_CONSTRUCTOR(); + + enum class DemoEnum { One, Two, Three = 4 }; + W_ENUM(DemoEnum, DemoEnum::One, DemoEnum::Two, DemoEnum::Three); + + void mySlot(const QString &name) { qDebug("hello %s", qPrintable(name)); } + W_SLOT(mySlot, (const QString&)); + + void mySignal(const QString &name) + W_SIGNAL(mySignal, name); + + int demoProp() const; + void setDemoProp(int newDemoProp); + void demoPropChanged() W_SIGNAL(demoPropChanged); + W_PROPERTY(int, demoProp READ demoProp WRITE setDemoProp NOTIFY demoPropChanged); +}; diff --git a/tutorial/qml_element/main.cpp b/tutorial/qml_element/main.cpp new file mode 100644 index 0000000..e6c828c --- /dev/null +++ b/tutorial/qml_element/main.cpp @@ -0,0 +1,15 @@ +#include +#include + +int main(int argc, char *argv[]) { + auto app = QGuiApplication{argc, argv}; + auto engine = QQmlApplicationEngine{}; + const auto url = QUrl(u"qrc:/QmlTutorial/main.qml"_qs); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + engine.load(url); + return app.exec(); +} diff --git a/tutorial/qml_element/main.qml b/tutorial/qml_element/main.qml new file mode 100644 index 0000000..0497ca9 --- /dev/null +++ b/tutorial/qml_element/main.qml @@ -0,0 +1,14 @@ +import QtQuick + +import QmlTutorial 1.0 + +Window { + width: 640 + height: 480 + visible: true + title: qsTr("Hello World") + + MyObject { + demoProp: 93 + } +} diff --git a/tutorial/qml_element/qml_element.qbs b/tutorial/qml_element/qml_element.qbs new file mode 100644 index 0000000..00cb6fd --- /dev/null +++ b/tutorial/qml_element/qml_element.qbs @@ -0,0 +1,66 @@ + +// Background: This does not work out of the box! +// * Qbs orders rules by input and output tags +// * We have a circle in tags: `cpp` -[compile]-> `obj` -[extractor]-> `metatypes.json` -[registar]-> `cpp` +// * `excludedInputs` excludes all rules that may produce the given tags +// * There is no known way to restrict rules to certain inputs + +// As a workaround we use two products: +// 1st Product with output type `obj` +// - add sources with QML_ELEMENTS here +// - `cpp` -[compile]-> `obj` -[extractor]-> `verdigris.metatypes` +// 2nd Library/Application +// - setup for QML plugin +// - `verdigris.metatypes` -> `metatypes.json` -[registar]-> `cpp` +// - build and link with `obj` from first product + +// Important Note: +// =============== +// This requires some patching of the Qbs cpp module +// - add `explicitlyDependsOnFromDependencies: ['obj']` to all linker Rules +// - concat `Cpp.collectLinkerObjectPaths(explicitlyDependsOn)` with regular `Cpp.collectLinkerObjectPaths(inputs)` +// (also add `explicitlyDependsOn` argument to the function in js files if necessary) + +Project { + Product { + name: "qml_element_obj" + condition: project.objlibrary_support + + files: [ + "MyObject.h", + "MyObject.cpp", + ] + + Depends { name: "Verdigris" } + Depends { name: "Qt.quick" } + Depends { name: "wqmlelement.objlibrary" } + + Export { + Depends { name: "Verdigris" } + Depends { name: "Qt.quick" } + } + } + + Application { + name: "qml_element_tutorial" + condition: project.objlibrary_support + consoleApplication: true + version: "1.0.0" + Qt.qml.importName: "QmlTutorial" + + files: [ + "main.cpp", + ] + Group { + name: "qrc:/QmlTutorial" + files: [ + "main.qml", + ] + fileTags: ["qt.qml.qml", "qt.core.resource_data"] + Qt.core.resourcePrefix: "/QmlTutorial" + } + + Depends { name: "qml_element_obj" } + Depends { name: "wqmlelement.metatypes" } + } +} diff --git a/tutorial/tutorial.pro b/tutorial/tutorial.pro index 86c32b3..3af9c9d 100644 --- a/tutorial/tutorial.pro +++ b/tutorial/tutorial.pro @@ -1,11 +1,9 @@ -###################################################################### -# Automatically generated by qmake (3.0) Di. Feb. 16 17:05:14 2016 -###################################################################### +TEMPLATE = subdirs -TEMPLATE = app -TARGET = tutorial -QT -= gui -include(../src/verdigris.pri) +SUBDIRS += features -# Input -SOURCES += tutorial.cpp +# Not implemented (PR welcome) +# SUBDIRS += cpp_api + +# QML Element does not work with QMake (PR welcome) +# SUBDIRS += qml_element diff --git a/tutorial/tutorial.qbs b/tutorial/tutorial.qbs index e3edee3..04ef744 100644 --- a/tutorial/tutorial.qbs +++ b/tutorial/tutorial.qbs @@ -1,29 +1,11 @@ -import qbs - -Project { - - Application { - name: "tutorial" - consoleApplication: true - - Depends { name: "Verdigris" } - - files: [ - "tutorial.cpp" - ] - } - - Application { - name: "cpp_tutorial" - consoleApplication: true - - Depends { name: "Verdigris" } - Depends { name: "Qt.quick" } - - files: [ - "cpp_tutorial.cpp", - "cpp_tutorial.qml", - "cpp_tutorial.qrc", - ] - } -} +import qbs + +Project { + name: "tutorial" + + references: [ + "features", + "cpp_api", + "qml_element", + ] +} diff --git a/verdigris.qbs b/verdigris.qbs index 0c84b5d..21426c9 100644 --- a/verdigris.qbs +++ b/verdigris.qbs @@ -3,11 +3,16 @@ import qbs Project { name: "Verdigris" + // use `qbs build project.objlibrary_support:true` if qbs is patched (needed for W_QML_ELEMENT support) + property bool objlibrary_support: false + references: [ "tutorial", "tests", "benchmarks", + "tools", ] + qbsSearchPaths: "qbs/" AutotestRunner {} @@ -24,6 +29,7 @@ Project { "src/wobjectcpp.h", "src/wobjectdefs.h", "src/wobjectimpl.h", + "src/wqmlelement.h", ] Export { @@ -71,6 +77,8 @@ Project { "ChangeLog", "LICENSE.LGPLv3", "README.md", + "qbs/modules/wqmlelement/metatypes/metatypes.qbs", + "qbs/modules/wqmlelement/objlibrary/objlibrary.qbs", ] } }