From 5592c20747bd059f8b2a2862fdd6335d3cfb1d96 Mon Sep 17 00:00:00 2001 From: baiYue Date: Tue, 27 Jan 2026 16:36:59 +0800 Subject: [PATCH] add propertyEditor library --- CMakeLists.txt | 16 +- PropertyEditor/CMakeLists.txt | 72 +++ PropertyEditor/README.md | 162 ++++++ PropertyEditor/README_zh.md | 159 ++++++ PropertyEditor/example/CMakeLists.txt | 29 ++ PropertyEditor/example/CommonInclude.h | 18 + PropertyEditor/example/CustomGadget.h | 20 + PropertyEditor/example/CustomObject.h | 48 ++ PropertyEditor/example/CustomType.h | 18 + .../PropertyTypeCustomization_CustomType.cpp | 73 +++ .../PropertyTypeCustomization_CustomType.h | 12 + PropertyEditor/example/main.cpp | 20 + PropertyEditor/resources.qrc | 27 + .../resources/Icon/arrow-left-l.png | Bin 0 -> 2500 bytes .../resources/Icon/arrow-right-l.png | Bin 0 -> 2515 bytes PropertyEditor/resources/Icon/close.png | Bin 0 -> 5692 bytes PropertyEditor/resources/Icon/delete.png | Bin 0 -> 1960 bytes PropertyEditor/resources/Icon/down.png | Bin 0 -> 2399 bytes PropertyEditor/resources/Icon/expand.png | Bin 0 -> 3220 bytes PropertyEditor/resources/Icon/plus.png | Bin 0 -> 1927 bytes PropertyEditor/resources/Icon/reset.png | Bin 0 -> 4433 bytes PropertyEditor/resources/Icon/unexpand.png | Bin 0 -> 2863 bytes PropertyEditor/resources/Icon/up.png | Bin 0 -> 2400 bytes .../Qml/ColorPalette/ColorPalette.qml | 9 + .../Qml/ColorPalette/ColorPalette_Dark.qml | 27 + .../Qml/ColorPalette/ColorPalette_Light.qml | 27 + .../resources/Qml/ValueEditor/ColorBox.qml | 40 ++ .../Qml/ValueEditor/DirectorySelector.qml | 63 +++ .../Qml/ValueEditor/FileSelector.qml | 39 ++ .../resources/Qml/ValueEditor/LineTextBox.qml | 72 +++ .../Qml/ValueEditor/MultiLineTextBox.qml | 83 +++ .../resources/Qml/ValueEditor/NumberBox.qml | 159 ++++++ .../Qml/ValueEditor/TextComboBox.qml | 103 ++++ .../resources/Qml/ValueEditor/Vec2Box.qml | 44 ++ .../resources/Qml/ValueEditor/Vec3Box.qml | 55 ++ .../resources/Qml/ValueEditor/Vec4Box.qml | 66 +++ .../resources/image-20250826114654194.png | Bin 0 -> 125086 bytes .../include/IPropertyTypeCustomization.h | 18 + .../PropertyHandleImpl/IPropertyHandleImpl.h | 33 ++ .../QPropertyHandleImpl_Associative.h | 27 + .../QPropertyHandleImpl_Enum.h | 20 + .../QPropertyHandleImpl_Object.h | 27 + .../QPropertyHandleImpl_RawType.h | 14 + .../QPropertyHandleImpl_Sequential.h | 26 + PropertyEditor/source/include/QDetailsView.h | 20 + .../source/include/QDetailsViewAPI.h | 21 + .../source/include/QPropertyHandle.h | 108 ++++ .../source/include/QQuickDetailsView.h | 28 + .../include/QQuickDetailsViewLayoutBuilder.h | 36 ++ .../include/QQuickDetailsViewMananger.h | 58 +++ .../source/include/QQuickDetailsViewModel.h | 44 ++ .../source/include/QQuickDetailsViewRow.h | 66 +++ .../source/include/QQuickFunctionLibrary.h | 64 +++ .../source/include/QQuickTreeViewEx.h | 53 ++ .../PropertyTypeCustomization_Associative.cpp | 35 ++ .../PropertyTypeCustomization_Associative.h | 11 + .../PropertyTypeCustomization_Matrix4x4.cpp | 94 ++++ .../PropertyTypeCustomization_Matrix4x4.h | 11 + ...ropertyTypeCustomization_ObjectDefault.cpp | 52 ++ .../PropertyTypeCustomization_ObjectDefault.h | 11 + .../PropertyTypeCustomization_Sequential.cpp | 40 ++ .../PropertyTypeCustomization_Sequential.h | 11 + .../source/src/IPropertyTypeCustomization.cpp | 13 + .../IPropertyHandleImpl.cpp | 38 ++ .../QPropertyHandleImpl_Associative.cpp | 72 +++ .../QPropertyHandleImpl_Enum.cpp | 46 ++ .../QPropertyHandleImpl_Object.cpp | 98 ++++ .../QPropertyHandleImpl_RawType.cpp | 14 + .../QPropertyHandleImpl_Sequential.cpp | 69 +++ PropertyEditor/source/src/QDetailsView.cpp | 86 ++++ PropertyEditor/source/src/QPropertyHandle.cpp | 322 ++++++++++++ .../source/src/QQuickDetailsView.cpp | 86 ++++ .../src/QQuickDetailsViewBasicTypeEditor.cpp | 238 +++++++++ .../src/QQuickDetailsViewLayoutBuilder.cpp | 216 ++++++++ .../source/src/QQuickDetailsViewMananger.cpp | 138 +++++ .../source/src/QQuickDetailsViewModel.cpp | 137 +++++ .../source/src/QQuickDetailsViewPrivate.h | 41 ++ .../source/src/QQuickDetailsViewRow.cpp | 118 +++++ .../source/src/QQuickFunctionLibrary.cpp | 58 +++ .../source/src/QQuickTreeViewEx.cpp | 480 ++++++++++++++++++ .../source/src/QQuickTreeViewExPrivate.h | 34 ++ diagramCavas/CMakeLists.txt | 8 +- .../include/propertyType/CustomGadget.h | 20 + .../include/propertyType/CustomType.h | 18 + .../PropertyTypeCustomization_CustomType.h | 12 + .../PropertyTypeCustomization_CustomType.cpp | 73 +++ include/CommonInclude.h | 18 + include/mainwindow.h | 2 + source/mainwindow.cpp | 15 + 89 files changed, 4854 insertions(+), 5 deletions(-) create mode 100644 PropertyEditor/CMakeLists.txt create mode 100644 PropertyEditor/README.md create mode 100644 PropertyEditor/README_zh.md create mode 100644 PropertyEditor/example/CMakeLists.txt create mode 100644 PropertyEditor/example/CommonInclude.h create mode 100644 PropertyEditor/example/CustomGadget.h create mode 100644 PropertyEditor/example/CustomObject.h create mode 100644 PropertyEditor/example/CustomType.h create mode 100644 PropertyEditor/example/PropertyTypeCustomization_CustomType.cpp create mode 100644 PropertyEditor/example/PropertyTypeCustomization_CustomType.h create mode 100644 PropertyEditor/example/main.cpp create mode 100644 PropertyEditor/resources.qrc create mode 100644 PropertyEditor/resources/Icon/arrow-left-l.png create mode 100644 PropertyEditor/resources/Icon/arrow-right-l.png create mode 100644 PropertyEditor/resources/Icon/close.png create mode 100644 PropertyEditor/resources/Icon/delete.png create mode 100644 PropertyEditor/resources/Icon/down.png create mode 100644 PropertyEditor/resources/Icon/expand.png create mode 100644 PropertyEditor/resources/Icon/plus.png create mode 100644 PropertyEditor/resources/Icon/reset.png create mode 100644 PropertyEditor/resources/Icon/unexpand.png create mode 100644 PropertyEditor/resources/Icon/up.png create mode 100644 PropertyEditor/resources/Qml/ColorPalette/ColorPalette.qml create mode 100644 PropertyEditor/resources/Qml/ColorPalette/ColorPalette_Dark.qml create mode 100644 PropertyEditor/resources/Qml/ColorPalette/ColorPalette_Light.qml create mode 100644 PropertyEditor/resources/Qml/ValueEditor/ColorBox.qml create mode 100644 PropertyEditor/resources/Qml/ValueEditor/DirectorySelector.qml create mode 100644 PropertyEditor/resources/Qml/ValueEditor/FileSelector.qml create mode 100644 PropertyEditor/resources/Qml/ValueEditor/LineTextBox.qml create mode 100644 PropertyEditor/resources/Qml/ValueEditor/MultiLineTextBox.qml create mode 100644 PropertyEditor/resources/Qml/ValueEditor/NumberBox.qml create mode 100644 PropertyEditor/resources/Qml/ValueEditor/TextComboBox.qml create mode 100644 PropertyEditor/resources/Qml/ValueEditor/Vec2Box.qml create mode 100644 PropertyEditor/resources/Qml/ValueEditor/Vec3Box.qml create mode 100644 PropertyEditor/resources/Qml/ValueEditor/Vec4Box.qml create mode 100644 PropertyEditor/resources/image-20250826114654194.png create mode 100644 PropertyEditor/source/include/IPropertyTypeCustomization.h create mode 100644 PropertyEditor/source/include/PropertyHandleImpl/IPropertyHandleImpl.h create mode 100644 PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Associative.h create mode 100644 PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Enum.h create mode 100644 PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Object.h create mode 100644 PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_RawType.h create mode 100644 PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Sequential.h create mode 100644 PropertyEditor/source/include/QDetailsView.h create mode 100644 PropertyEditor/source/include/QDetailsViewAPI.h create mode 100644 PropertyEditor/source/include/QPropertyHandle.h create mode 100644 PropertyEditor/source/include/QQuickDetailsView.h create mode 100644 PropertyEditor/source/include/QQuickDetailsViewLayoutBuilder.h create mode 100644 PropertyEditor/source/include/QQuickDetailsViewMananger.h create mode 100644 PropertyEditor/source/include/QQuickDetailsViewModel.h create mode 100644 PropertyEditor/source/include/QQuickDetailsViewRow.h create mode 100644 PropertyEditor/source/include/QQuickFunctionLibrary.h create mode 100644 PropertyEditor/source/include/QQuickTreeViewEx.h create mode 100644 PropertyEditor/source/src/Customization/PropertyTypeCustomization_Associative.cpp create mode 100644 PropertyEditor/source/src/Customization/PropertyTypeCustomization_Associative.h create mode 100644 PropertyEditor/source/src/Customization/PropertyTypeCustomization_Matrix4x4.cpp create mode 100644 PropertyEditor/source/src/Customization/PropertyTypeCustomization_Matrix4x4.h create mode 100644 PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.cpp create mode 100644 PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.h create mode 100644 PropertyEditor/source/src/Customization/PropertyTypeCustomization_Sequential.cpp create mode 100644 PropertyEditor/source/src/Customization/PropertyTypeCustomization_Sequential.h create mode 100644 PropertyEditor/source/src/IPropertyTypeCustomization.cpp create mode 100644 PropertyEditor/source/src/PropertyHandleImpl/IPropertyHandleImpl.cpp create mode 100644 PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Associative.cpp create mode 100644 PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Enum.cpp create mode 100644 PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Object.cpp create mode 100644 PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_RawType.cpp create mode 100644 PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Sequential.cpp create mode 100644 PropertyEditor/source/src/QDetailsView.cpp create mode 100644 PropertyEditor/source/src/QPropertyHandle.cpp create mode 100644 PropertyEditor/source/src/QQuickDetailsView.cpp create mode 100644 PropertyEditor/source/src/QQuickDetailsViewBasicTypeEditor.cpp create mode 100644 PropertyEditor/source/src/QQuickDetailsViewLayoutBuilder.cpp create mode 100644 PropertyEditor/source/src/QQuickDetailsViewMananger.cpp create mode 100644 PropertyEditor/source/src/QQuickDetailsViewModel.cpp create mode 100644 PropertyEditor/source/src/QQuickDetailsViewPrivate.h create mode 100644 PropertyEditor/source/src/QQuickDetailsViewRow.cpp create mode 100644 PropertyEditor/source/src/QQuickFunctionLibrary.cpp create mode 100644 PropertyEditor/source/src/QQuickTreeViewEx.cpp create mode 100644 PropertyEditor/source/src/QQuickTreeViewExPrivate.h create mode 100644 diagramCavas/include/propertyType/CustomGadget.h create mode 100644 diagramCavas/include/propertyType/CustomType.h create mode 100644 diagramCavas/include/propertyType/PropertyTypeCustomization_CustomType.h create mode 100644 diagramCavas/source/propertyType/PropertyTypeCustomization_CustomType.cpp create mode 100644 include/CommonInclude.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bf2a52..e0564ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,14 @@ cmake_minimum_required(VERSION 3.5) +cmake_policy(SET CMP0079 NEW) +if(POLICY CMP0079) + cmake_policy(GET CMP0079 policy_status) + message(STATUS "CMP0079 策略状态: ${policy_status}") +else() + message(STATUS "CMP0079 策略不可用") +endif() project(DiagramDesigner LANGUAGES CXX VERSION 1.0) - - set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) @@ -14,7 +19,6 @@ find_package(Qt6 REQUIRED COMPONENTS SvgWidgets) find_package(Qt6 COMPONENTS Network WebSockets REQUIRED) find_package(PostgreSQL REQUIRED) - set(CMAKE_INCLUDE_CURRENT_DIR ON) # 默认 ui 文件要和 .h 头文件在一个目录,若不在一个目录,需要指定其所在目录 set(CMAKE_AUTOUIC_SEARCH_PATHS "ui") @@ -140,6 +144,7 @@ include_directories(common/include) include_directories(${PostgreSQL_INCLUDE_DIRS}) target_include_directories(DiagramDesigner PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_include_directories(DiagramDesigner PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/PropertyEditor/source/include)") target_link_libraries(DiagramDesigner PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets) @@ -162,9 +167,12 @@ set_target_properties(DiagramDesigner PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${dd_PlatformDir}/bin" ) -target_link_libraries(DiagramDesigner PRIVATE diagramCavas diagramUtils diagramCommunication) +add_subdirectory(PropertyEditor) add_subdirectory(diagramCavas) add_subdirectory(diagramUtils) add_subdirectory(diagramCommunication) +target_link_libraries(DiagramDesigner PRIVATE PropertyEditor) +target_link_libraries(DiagramDesigner PRIVATE diagramCavas diagramUtils diagramCommunication) + file(COPY setting.xml DESTINATION "${CMAKE_BINARY_DIR}/${dd_PlatformDir}/bin") diff --git a/PropertyEditor/CMakeLists.txt b/PropertyEditor/CMakeLists.txt new file mode 100644 index 0000000..fb4c221 --- /dev/null +++ b/PropertyEditor/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.12) + +project(PropertyEditor CXX) + +find_package(Qt6 COMPONENTS Core Widgets Gui QuickWidgets QuickTemplates2 QuickControls2 REQUIRED) + +qt6_add_resources(QRC_FILE resources.qrc) + +file(GLOB_RECURSE PROJECT_SOURCE FILES ${CMAKE_CURRENT_SOURCE_DIR}/source/*.h ${CMAKE_CURRENT_SOURCE_DIR}/source/*.hpp ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/resources/*) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${PROJECT_SOURCE}) + +#if(BUILD_SHARED_LIBS) +# add_library(PropertyEditor SHARED ${PROJECT_SOURCE} ${QRC_FILE}) +# target_compile_definitions(PropertyEditor PRIVATE PROPERTY_EDITOR_SHARED_LIBRARY) +# message(STATUS "[CMake] Building as SHARED library") +# message(STATUS "[CMake] Defined: PROPERTY_EDITOR_SHARED_LIBRARY") +#else() +# add_library(PropertyEditor STATIC ${PROJECT_SOURCE} ${QRC_FILE}) +# target_compile_definitions(PropertyEditor PUBLIC PROPERTY_EDITOR_STATIC_LIBRARY) +# message(STATUS "[CMake] Building as STATIC library") +# message(STATUS "[CMake] Defined: PROPERTY_EDITOR_STATIC_LIBRARY") +#endif() + +add_library(PropertyEditor SHARED ${PROJECT_SOURCE} ${QRC_FILE}) + +target_compile_definitions(PropertyEditor + PUBLIC + DIAGRAM_DESIGNER_SHARED + PRIVATE + DIAGRAM_DESIGNER_EXPORTS + #QT_NO_KEYWORDS +) + + +set_property(TARGET PropertyEditor PROPERTY AUTOMOC ON) +set_property(TARGET PropertyEditor PROPERTY USE_FOLDERS ON) +set_property(TARGET PropertyEditor PROPERTY AUTOGEN_SOURCE_GROUP "Generated Files") + +target_compile_definitions(PropertyEditor PRIVATE PROPERTY_EDITOR_LIBRARY) + +target_link_libraries(PropertyEditor PUBLIC + Qt::Gui + Qt::GuiPrivate + Qt::Widgets + Qt::WidgetsPrivate + Qt::QuickWidgets + Qt::QuickPrivate + Qt::QuickTemplates2 + Qt::QuickTemplates2Private + Qt::QuickControls2 +) + +target_include_directories(PropertyEditor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/source/include) +file(GLOB_RECURSE PUBLIC_FILES LIST_DIRECTORIES TRUE ${CMAKE_CURRENT_SOURCE_DIR}/source/include/*) +foreach(PUBLIC_FILE ${PUBLIC_FILES}) + if(IS_DIRECTORY ${PUBLIC_FILE}) + target_include_directories(PropertyEditor PRIVATE ${PUBLIC_FILE}) + endif() +endforeach() + +set_target_properties( + PropertyEditor + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${dd_PlatformDir}/bin" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${dd_PlatformDir}/lib" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${dd_PlatformDir}/lib" +) + +if(PROJECT_IS_TOP_LEVEL) + add_subdirectory(example) +endif() diff --git a/PropertyEditor/README.md b/PropertyEditor/README.md new file mode 100644 index 0000000..dc94be6 --- /dev/null +++ b/PropertyEditor/README.md @@ -0,0 +1,162 @@ +# QDetailsView + +[中文用户?点我查看中文介绍](README_zh.md) + +Inspired by the details view of Unreal Engine, QDetailsView leverages Qt's reflection system to easily build property editors for qobject. + +Its core features are: + +- Create type-based control editors that automatically organize the editor layout according to the reflection structure of the object. +- Utilize QML GPU rendering and control management based on the preview view. + +## Usage + +It is extremely easy to use—simply declare the meta-properties of a `QObject` using **`Q_PROPERTY(...)`**: + +```c++ +class QCustomObject : public QObject { + Q_OBJECT + Q_PROPERTY(int Int READ getInt WRITE setInt) + Q_PROPERTY(float Float READ getFloat WRITE setFloat) + ... +}; +``` + +```c++ +QCustomObject obj; +QDetailsView view; +view.setObject(&obj); +view.show(); +``` + +You will get the following result: + +![image-20250826114654194](resources/image-20250826114654194.png) + +## Customization + +### About QPropertyHandle + +`QPropertyHandle` serves as the unified entry point for QDetailsView to manipulate properties. It is typically constructed via the following interface: + +```c++ +static QPropertyHandle* QPropertyHandle::FindOrCreate( + QObject* inParent, // Parent object that manages the lifecycle of the PropertyHandle + QMetaType inType, // Meta-type of the property + QString inPropertyPath, // Path field of the property + Getter inGetter, // Getter function for the property + Setter inSetter // Setter function for the property +); +``` + +To ensure that changes to property values are detected by DetailsView, all value modifications must use the interface provided by PropertyHandle: + +```c++ +QPropertyHandle* handle = QPropertyHandle::Find(object, "propertyName"); +if (handle) { + handle->setVar(QVariant::fromValue(newValue)); +} +``` + +When creating a `QPropertyHandle`, you must specify a `parent`—the handle will be attached to the parent as a child object. Thus, its lifecycle is tied to the parent object. To clean it up, call: + +```c++ +static void QPropertyHandle::Cleanup(QObject* inParent); +``` + +### Custom Enum + +For enum types, they must be defined within a class and declared using **`Q_ENUM(...)`**: + +```c++ +class QCustomObject : public QObject { + Q_OBJECT +public: + enum QCustomEnum { + One, + Two, + Three + }; + Q_ENUM(QCustomEnum); +}; +``` + +### Custom Type Editor + +For custom types that do not inherit from `QObject`, you first need to declare the type using the macro **`Q_DECLARE_METATYPE(...)`** during definition. + +For specific types that require only a single editor control, you can directly register the type editor using the following interface: + +```c++ +QQuickDetailsViewManager::Get()->registerTypeEditor( + metaType, + [](QPropertyHandle* handle, QQuickItem* parent) -> QQuickItem* { + // Implementation of the editor creation logic + } +); +``` + +The source code directory `QQuickDetailsViewBasicTypeEditor.cpp` contains many reference examples, such as the editor for `QDir`: + +```c++ +registerTypeEditor( + QMetaType::fromType(), + [](QPropertyHandle* handle, QQuickItem* parent) -> QQuickItem* { + QQmlEngine* engine = qmlEngine(parent); + QQmlContext* context = qmlContext(parent); + QQmlComponent comp(engine); + + comp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import "qrc:/resources/Qml/ValueEditor" + DirectorySelector { + anchors.verticalCenter: parent.verticalCenter + width: parent.width + } + )", QUrl()); + + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(parent); + auto valueEditor = qobject_cast(comp.createWithInitialProperties(initialProperties, context)); + + if (!comp.errors().isEmpty()) { + qDebug() << comp.errorString(); + } + + valueEditor->setParentItem(parent); + valueEditor->setProperty("value", handle->getVar()); + + connect(valueEditor, SIGNAL(asValueChanged(QVariant)), handle, SLOT(setVar(QVariant))); + connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant))); + + return valueEditor; + } +); +``` + +For editor controls that span multiple rows or have complex behaviors, you can extend the property editor by deriving from `IPropertyTypeCustomization`. This class provides two virtual functions: + +```c++ +class IPropertyTypeCustomization : public QEnableSharedFromThis +{ +public: + // Used to assemble the editor for the current property row + virtual void customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder); + + // Used to extend child items for the current property row + virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder); +}; +``` + +After implementing the derived class, register it using the following interface: + +```c++ +QQuickDetailsViewManager::Get()->registerPropertyTypeCustomization(); +``` + +## TODO + +- Undo/redo +- Container operations +- Extended meta-data support diff --git a/PropertyEditor/README_zh.md b/PropertyEditor/README_zh.md new file mode 100644 index 0000000..5027f82 --- /dev/null +++ b/PropertyEditor/README_zh.md @@ -0,0 +1,159 @@ +# QDetailsView + +QDetailsView 受虚幻引擎的属性面板所启发,借助Qt的反射系统,可以轻易地搭建对象的属性编辑器。 + +其核心在于: + +- 编写基于类型的控件编辑器,自动根据对象的反射结构来组织编辑器布局。 +- 使用QML GPU渲染,基于预览视图的控件管理。 + +## 使用 + +它的使用非常简单,只需要使用 **Q_PROPERTY(...) ** 声明 `QObject` 的元属性: + +``` c++ +class QCustomObject :public QObject { + Q_OBJECT + Q_PROPERTY(int Int READ getInt WRITE setInt) + Q_PROPERTY(float Float READ getFloat WRITE setFloat) + ... +}; +``` + +然后创建 `QDetailsView`,并指定Object: + +``` c++ +QCustomObject obj; +QDetailsView view; +view.setObject(&obj); +view.show(); +``` + +你就能得到: + +![image-20250826114654194](resources/image-20250826114654194.png) + +## 定制化 + +### 关于 QPropertyHandle + +`QPropertyHandle` 是QDetailsView操作属性的统一入口,它通常通过如下接口构建: + +``` c++ +static QPropertyHandle* QPropertyHandle::FindOrCreate( + QObject* inParent, // 父对象,它会持有PropertyHandle周期 + QMetaType inType, // 该属性的元类型 + QString inPropertyPath, // 该属性的路径字段 + Getter inGetter, // 该属性的获取器 + Setter inSetter // 该属性的设置器 +); +``` + +为了保证属性值的变动可以被DetailsView响应,所有对值的修改请统一使用PropertyHandle的接口: + +``` C++ +QPropertyHandle* handle = QPropertyHandle::Find(object, "propertyName"); +if(handle){ + handle->setVar(QVariant::fromValue(newValue)); +} +``` + +`QPropertyHandle`创建时要求指定`parent`,它将会作为子对象挂载上去,因此它的生命周期将跟随父对象,如果要清理,请调用: + +``` c++ +static void QPropertyHandle::Cleanup(QObject* inParent); +``` + +### 自定义枚举 + +对于枚举类型,需要使用类内定义的方式,并通过**Q_ENUM(...)**声明: + +``` c++ +class QCustomObject: public QObject { + Q_OBJECT +public: + enum QCustomEnum { + One, + Two, + Three + }; + Q_ENUM(QCustomEnum); +}; +``` + +### 自定义类型编辑器 + +非`QObject`的自定义类型,首先需要在定义类型时使用宏**Q_DECLARE_METATYPE(...)**进行声明。 + +对于只有单个编辑器控件的特定类型,可以使用如下接口直接注册类型编辑器: + +``` c++ +QQuickDetailsViewManager::Get()->registerTypeEditor( + metaType, + [](QPropertyHandle* handle, QQuickItem* parent)->QQuickItem* { + + } +); +``` + +在源代码的`QQuickDetailsViewBasicTypeEditor.cpp`目录下有许多参考示例,比如QDir: + +``` c++ +registerTypeEditor( + QMetaType::fromType(), + [](QPropertyHandle* handle, QQuickItem* parent)->QQuickItem* { + QQmlEngine* engine = qmlEngine(parent); + QQmlContext* context = qmlContext(parent); + QQmlComponent comp(engine); + comp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import "qrc:/resources/Qml/ValueEditor" + DirectorySelector{ + anchors.verticalCenter: parent.verticalCenter + width: parent.width + } + )", QUrl()); + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(parent); + auto valueEditor = qobject_cast(comp.createWithInitialProperties(initialProperties, context)); + if (!comp.errors().isEmpty()) { + qDebug() << comp.errorString(); + } + valueEditor->setParentItem(parent); + valueEditor->setProperty("value", handle->getVar()); + connect(valueEditor, SIGNAL(asValueChanged(QVariant)), handle, SLOT(setVar(QVariant))); + connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant))); + return valueEditor; + } +); +``` + +对于具有多行或者行为较为复杂的编辑器控件,可以通过派生`IPropertyTypeCustomization`来扩展属性编辑器,它提供两个虚函数: + +``` c++ +class IPropertyTypeCustomization :public QEnableSharedFromThis +{ +public: + // 用于装配当前属性行的编辑器 + virtual void customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder); + + // 用于扩展当前属性行的子项 + virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder); +}; +``` + +之后使用如下接口进行注册: + +``` c++ +QQuickDetailsViewManager::Get()->registerPropertyTypeCustomization(); +``` + +## TODO + +- 撤销重做 +- 容器操作 +- 扩展元信息 + + + diff --git a/PropertyEditor/example/CMakeLists.txt b/PropertyEditor/example/CMakeLists.txt new file mode 100644 index 0000000..b70b81b --- /dev/null +++ b/PropertyEditor/example/CMakeLists.txt @@ -0,0 +1,29 @@ + +file(GLOB_RECURSE PROJECT_SOURCE FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${PROJECT_SOURCE}) + +add_executable(PropertyEditorExample + ${PROJECT_SOURCE} +) + +set_property(TARGET PropertyEditorExample PROPERTY AUTOMOC ON) + +find_package(Qt6 REQUIRED COMPONENTS QuickControls2) + +target_link_libraries(PropertyEditorExample PUBLIC PropertyEditor + Qt::Widgets + Qt::QuickWidgets + Qt::QuickPrivate + Qt::QuickTemplates2 + Qt::QuickTemplates2Private + Qt::QuickControls2 +) + +set_target_properties( + PropertyEditorExample + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${pd_PlatformDir}/bin" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${pd_PlatformDir}/lib" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${pd_PlatformDir}/lib" +) diff --git a/PropertyEditor/example/CommonInclude.h b/PropertyEditor/example/CommonInclude.h new file mode 100644 index 0000000..6424340 --- /dev/null +++ b/PropertyEditor/example/CommonInclude.h @@ -0,0 +1,18 @@ +#ifndef CommonInclude_h__ +#define CommonInclude_h__ + +#include +#include +#include +#include + +#define Q_PROPERTY_VAR(Type,Name)\ + Q_PROPERTY(Type Name READ get##Name WRITE set##Name) \ + Type get##Name(){ return Name; } \ + void set##Name(Type var){ \ + Name = var; \ + qDebug() <<"Set" <); + +#endif // CustomGadget_h__ \ No newline at end of file diff --git a/PropertyEditor/example/CustomObject.h b/PropertyEditor/example/CustomObject.h new file mode 100644 index 0000000..abe60ff --- /dev/null +++ b/PropertyEditor/example/CustomObject.h @@ -0,0 +1,48 @@ +#ifndef CustomObject_h__ +#define CustomObject_h__ + +#include +#include +#include +#include +#include +#include +#include "CommonInclude.h" +#include "CustomGadget.h" +#include "CustomType.h" + +class QCustomObject :public QObject { + Q_OBJECT + Q_CLASSINFO("LimitedDouble", "Min=0,Max=10") +public: + enum QCustomEnum { + One, + Two, + Three + }; + Q_ENUM(QCustomEnum); + + Q_PROPERTY_VAR(int, Int) = 0; + Q_PROPERTY_VAR(float, Float) = 1.23f; + Q_PROPERTY_VAR(double, LimitedDouble) = 5; + Q_PROPERTY_VAR(QString, String); + Q_PROPERTY_VAR(QDir, Directory) = QDir("."); + Q_PROPERTY_VAR(QVector2D, Vec2) = QVector2D(1, 2); + Q_PROPERTY_VAR(QVector3D, Vec3) = QVector3D(1, 2, 3); + Q_PROPERTY_VAR(QVector4D, Vec4) = QVector4D(1, 2, 3, 4); + Q_PROPERTY_VAR(QMatrix4x4, Mat4); + Q_PROPERTY_VAR(QColor, Color); + Q_PROPERTY_VAR(QList, ColorList) = { Qt::red,Qt::green,Qt::blue }; + + typedef QMap StringColorMap; + Q_PROPERTY_VAR(StringColorMap, ColorMap) = { {"Red",Qt::red},{"Green",Qt::green},{"Blue",Qt::blue} }; + + Q_PROPERTY_VAR(QCustomEnum, CustomEnum) = QCustomEnum::One; + Q_PROPERTY_VAR(QCustomType, CustomType); + Q_PROPERTY_VAR(QCustomGadget, CustomGadget); + Q_PROPERTY_VAR(QCustomGadget*, CustomGadgetPtr) = new QCustomGadget; + Q_PROPERTY_VAR(QSharedPointer, CustomGadgetSharedPtr) = QSharedPointer::create(); + Q_PROPERTY_VAR(QCustomObject*, SubCustomObject) = nullptr; +}; + +#endif // CustomObject_h__ diff --git a/PropertyEditor/example/CustomType.h b/PropertyEditor/example/CustomType.h new file mode 100644 index 0000000..7674a7e --- /dev/null +++ b/PropertyEditor/example/CustomType.h @@ -0,0 +1,18 @@ +#ifndef CustomType_h__ +#define CustomType_h__ + +#include +#include + +struct QCustomType { + unsigned int ArraySize = 0; + QVector Array; +}; + +static QDebug operator<<(QDebug debug, const QCustomType& it) { + return debug << it.Array; +} + +Q_DECLARE_METATYPE(QCustomType) + +#endif // CustomType_h__ diff --git a/PropertyEditor/example/PropertyTypeCustomization_CustomType.cpp b/PropertyEditor/example/PropertyTypeCustomization_CustomType.cpp new file mode 100644 index 0000000..660db32 --- /dev/null +++ b/PropertyEditor/example/PropertyTypeCustomization_CustomType.cpp @@ -0,0 +1,73 @@ +#include "PropertyTypeCustomization_CustomType.h" +#include "QQuickDetailsViewLayoutBuilder.h" +#include "QPropertyHandle.h" +#include +#include +#include +#include "QQuickDetailsViewModel.h" +#include "CustomType.h" +#include "QQuickFunctionLibrary.h" + +void PropertyTypeCustomization_CustomType::customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder) +{ + auto editorSlot = inBuilder->makeNameValueSlot(); + inPropertyHandle->setupNameEditor(editorSlot.first); + auto buttonItem = inBuilder->setupItem(editorSlot.second, R"( + import QtQuick; + import QtQuick.Controls; + Button{ + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + width: 80 + height: 20 + text: "Sort" + } + )"); + + + QQuickFunctionLibrary::connect(buttonItem, SIGNAL(clicked()), inPropertyHandle, [inPropertyHandle]() { + QCustomType customType = inPropertyHandle->getVar().value(); + std::sort(customType.Array.begin(), customType.Array.end()); + inPropertyHandle->setVar(QVariant::fromValue(customType)); + if (auto arrayHandle = inPropertyHandle->findChild("Array")) { + } + }); +} + +void PropertyTypeCustomization_CustomType::customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) +{ + auto arrayHandle = inPropertyHandle->findOrCreateChild( + QMetaType::fromType>(), + "Array", + [inPropertyHandle]() { + return QVariant::fromValue(inPropertyHandle->getVar().value().Array); + }, + [inPropertyHandle](QVariant var) { + QCustomType customType = inPropertyHandle->getVar().value(); + customType.Array = var.value>(); + inPropertyHandle->setVar(QVariant::fromValue(customType)); + } + ); + + auto arraySizeHandle = inPropertyHandle->findOrCreateChild( + QMetaType::fromType(), + "ArraySize", + [inPropertyHandle]() { + return inPropertyHandle->getVar().value().ArraySize; + }, + [inPropertyHandle, arrayHandle](QVariant var) { + QCustomType customType = inPropertyHandle->getVar().value(); + customType.ArraySize = var.toUInt(); + customType.Array.resize(customType.ArraySize); + for (int i = 0; i < customType.ArraySize; ++i) { + customType.Array[i] = QRandomGenerator::global()->bounded(-100000, 100000); + } + inPropertyHandle->setVar(QVariant::fromValue(customType)); + arrayHandle->invalidateStructure(); + } + ); + + inBuilder->addProperty(arraySizeHandle); + inBuilder->addProperty(arrayHandle); +} + diff --git a/PropertyEditor/example/PropertyTypeCustomization_CustomType.h b/PropertyEditor/example/PropertyTypeCustomization_CustomType.h new file mode 100644 index 0000000..3d847cf --- /dev/null +++ b/PropertyEditor/example/PropertyTypeCustomization_CustomType.h @@ -0,0 +1,12 @@ +#ifndef PropertyTypeCustomization_CustomType_h__ +#define PropertyTypeCustomization_CustomType_h__ + +#include "IPropertyTypeCustomization.h" + +class PropertyTypeCustomization_CustomType : public IPropertyTypeCustomization { +protected: + virtual void customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder) override; + virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) override; +}; + +#endif // PropertyTypeCustomization_CustomType_h__ diff --git a/PropertyEditor/example/main.cpp b/PropertyEditor/example/main.cpp new file mode 100644 index 0000000..5f0297f --- /dev/null +++ b/PropertyEditor/example/main.cpp @@ -0,0 +1,20 @@ +#include +#include "QDetailsView.h" +#include "CustomObject.h" +#include "CustomType.h" +#include "PropertyTypeCustomization_CustomType.h" +#include "QQuickDetailsViewMananger.h" + +int main(int argc, char** argv) { + QApplication app(argc, argv); + + QCustomObject obj; + obj.setSubCustomObject(new QCustomObject); + obj.getSubCustomObject()->setSubCustomObject(new QCustomObject); + QQuickDetailsViewManager::Get()->registerPropertyTypeCustomization(); + QDetailsView view; + view.setObject(&obj); + view.show(); + + return app.exec(); +} \ No newline at end of file diff --git a/PropertyEditor/resources.qrc b/PropertyEditor/resources.qrc new file mode 100644 index 0000000..55b790d --- /dev/null +++ b/PropertyEditor/resources.qrc @@ -0,0 +1,27 @@ + + + resources/Icon/arrow-left-l.png + resources/Icon/arrow-right-l.png + resources/Icon/close.png + resources/Icon/delete.png + resources/Icon/down.png + resources/Icon/expand.png + resources/Icon/plus.png + resources/Icon/reset.png + resources/Icon/unexpand.png + resources/Icon/up.png + resources/Qml/ValueEditor/ColorBox.qml + resources/Qml/ValueEditor/NumberBox.qml + resources/Qml/ValueEditor/TextComboBox.qml + resources/Qml/ValueEditor/Vec2Box.qml + resources/Qml/ValueEditor/Vec3Box.qml + resources/Qml/ValueEditor/Vec4Box.qml + resources/Qml/ColorPalette/ColorPalette.qml + resources/Qml/ColorPalette/ColorPalette_Dark.qml + resources/Qml/ColorPalette/ColorPalette_Light.qml + resources/Qml/ValueEditor/DirectorySelector.qml + resources/Qml/ValueEditor/FileSelector.qml + resources/Qml/ValueEditor/LineTextBox.qml + resources/Qml/ValueEditor/MultiLineTextBox.qml + + diff --git a/PropertyEditor/resources/Icon/arrow-left-l.png b/PropertyEditor/resources/Icon/arrow-left-l.png new file mode 100644 index 0000000000000000000000000000000000000000..5d98b3c0a258c84b0caf5f330f45ef8fe0400ac6 GIT binary patch literal 2500 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 literal 0 HcmV?d00001 diff --git a/PropertyEditor/resources/Icon/close.png b/PropertyEditor/resources/Icon/close.png new file mode 100644 index 0000000000000000000000000000000000000000..d7ff2d74b41748c6ace7c21b9848fb710721e46b GIT binary patch literal 5692 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 literal 0 HcmV?d00001 diff --git a/PropertyEditor/resources/Icon/expand.png b/PropertyEditor/resources/Icon/expand.png new file mode 100644 index 0000000000000000000000000000000000000000..a1dbcf702a24fde595a06a35a232f115becd3da7 GIT binary patch literal 3220 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 literal 0 HcmV?d00001 diff --git a/PropertyEditor/resources/Icon/up.png b/PropertyEditor/resources/Icon/up.png new file mode 100644 index 0000000000000000000000000000000000000000..ff302d53952bb32a3d692a0bb3952f74f6952042 GIT binary patch literal 2400 zcmeHJ`!`$J9*@Y}gd&M&J+3)PPXw*0>CBR5sv{vGXDFpq+Za`}9;L2OLi&j4B{8JQ z5sHb&RdlqAsb;FGMLTpvt+J|*cns}GQZ=n=l;TqN9{<6;zt3K4@9$pc^ZoAcXMfh& z`@1Fkg9FS>9ZXRulsUvQhE#+E~^HHb0F2n+BOshwZU56XGc!fr0^PHY1B1|lJa4nrg9EhVF)@IK5o*m21tA!e z)N|Ixshad*L5->=p`~m2NKXO}JFn8HH1j>@=9ar=l*^OUD1z{wKYA_3*a(;6A_Ri4 zn2&%ED8^z47wm;XDT*qVI$nbb3>nCL(j-NRUU#&)n;2mr%db=a!~uVS|Covaomz2YhTaFoxuuSf0n2gxcLOz!>s)qB6w^|5-Id#vlO_udddZG!S z7B{yg(9pQ)f+}nY2gURcD%pF7$RN<3dMC2bxLvWyZ^uedqK<#PVs1ltc83Y-P9-d* z9laCS;fR@~E|MR7I=u&9go86>=Aul3Z`m zz&N_%09h6(NNx;`>+=Egv3W8U{@Sd_s_*k|e+$qsEU{`Z`atIaQ2xE$%=NmR zBrLq5e_%4L&V}f*zAtwN69l8SJV0{xloGHg_Rupd3sH3DTR@=B1G84sMO$wyDU2br zd>!Vn^QkR#S^TOKooQClzWEWY#~iju#`AyyUJg&RLw$pa;P+$b%vZf{LoL9{+U-;| z+DuSx4B6r;tXTMT3|+>m-APrOWo;YC1HTfz!i@cWN{qz6qi2X*D@t}?VOR7Frqbe( zpS0)CENMlz$Fu77c7T&Jc5j6&dU4DR`~yGf{8gv< zl-h~>Y()<~x_k1Qr0GxN6SDXcC(*i{xX|fEIDKj|y=doESb105`F&ad>?bCvdpwbJ zS05TSS`TpNd#<0mbGnH4>FIG!7(`P000`#GYPFT)uLRE)tPTAPE4@@h?Rw6yY1!GO zT9tc$|3njSS~J`NE59_Hn@gRg44f13l}@$c^lzUbA*T}}p`jxk?{aTHh2w4e{G=DJ zI@LOw8!E{QBC#?q{CtF03wZc5xAXYxxY(St`}Hd$3nVfxw@l})ooP^jmjP$q-AMh) zI6pB`A7Uf7W9!h`nZ{D^M+8u4Xswp=w35QpU1xI6>L^6F|JbDDG{Ru%CK&nK4^A(* z!LOZf4sM@g!wdEG`Z9lbhlsBZojMSqTY1deMSc3k{FpnexOL@Fv>P}8AR_4}JJ`}% z4=S&4y#ELFAUSO6bz;sxFM*w#OAe=7)6d;hveD zH#WrEzw&$k03|M)1o8tl_V}uZ)rEHJlWl&GB}Bl}Yl!kUElrx{cPa}}p&Tqi$aqu8 zwB6=yOK~+>guNF)A_n<4Oo+D!a+9my__&d24n7%t$lnf;yq~SJ)iRSKx)-xlhTg*R z_;@5YKZteeE9biB`oN2~9MWPBLH;&~YXDnkp}jxbcxHAfOZ&*oZ9u%a2eJ6~k91-Q**n$+({=G7X6i_zVl9U6XVFK5># zRVPp9qQ?*CX;beBaPg+dzo{6j=9tb)4cScDwr=kCfg>tFO*|@%D4hIm1i3%u0n%2= zY$!perUu-C7hP~XUD@S8DAy7>sh9UaxkSXqRWs~-I+g((59oJ6KcXagY|gyU`zT3n zI9`gQdH2+M0(?*eV0(ME=eba!ElgRQ=JCso6I979Kz;xEFy&<~%r_M|Wd!6ey`xGl z0nX<=OAQ0;dwS4d2}WjSIu@Wr7bLL%4x}J{7Li;)5(3BS8;D!bxL9Uc)iE5vsc=o| z6QM+h_BWo`ipE*GeCN=HR9V~bX8XTF|38`5&7&;LMpOM%T;$1wg4n^VR^J%We*rZA B`vm|1 literal 0 HcmV?d00001 diff --git a/PropertyEditor/resources/Qml/ColorPalette/ColorPalette.qml b/PropertyEditor/resources/Qml/ColorPalette/ColorPalette.qml new file mode 100644 index 0000000..c445a74 --- /dev/null +++ b/PropertyEditor/resources/Qml/ColorPalette/ColorPalette.qml @@ -0,0 +1,9 @@ +import QtQuick + +pragma Singleton + +QtObject { + id: colorPalette + property var theme : ColorPalette_Light +} + \ No newline at end of file diff --git a/PropertyEditor/resources/Qml/ColorPalette/ColorPalette_Dark.qml b/PropertyEditor/resources/Qml/ColorPalette/ColorPalette_Dark.qml new file mode 100644 index 0000000..a946b7a --- /dev/null +++ b/PropertyEditor/resources/Qml/ColorPalette/ColorPalette_Dark.qml @@ -0,0 +1,27 @@ +import QtQuick + +pragma Singleton + +QtObject { + property color labelPrimary : "#FFFFFF" + property color textPrimary: "#EEEEEE" + + property color textBoxBackground: "#444444" + + property color rowBackground: "#333333" + property color rowBackgroundHover: "#888888" + property color rowBorder: "#666666" + property color rowIndicator: "#CCCCCC" + property color rowSplitter: "#666666" + property color rowShadowStart: "#00000000" + property color rowShadowEnd: "#66000000" + + property color boxHover: "#A478DB" + + property color comboBoxBackground: "#444444" + property color comboBoxItemBackground: "#666666" + property color comboBoxItemBackgroundHover: "#888888" + + property color buttonBackground: "#111111" +} + \ No newline at end of file diff --git a/PropertyEditor/resources/Qml/ColorPalette/ColorPalette_Light.qml b/PropertyEditor/resources/Qml/ColorPalette/ColorPalette_Light.qml new file mode 100644 index 0000000..e6550e8 --- /dev/null +++ b/PropertyEditor/resources/Qml/ColorPalette/ColorPalette_Light.qml @@ -0,0 +1,27 @@ +import QtQuick + +pragma Singleton + +QtObject { + property color labelPrimary : "#444444" + property color textPrimary: "#666666" + + property color textBoxBackground: "#f8fef9" + + property color rowBackground: "white" + property color rowBackgroundHover: "#F3F3F3" + property color rowBorder: "#EEEEEE" + property color rowIndicator: "#cef9ce" + property color rowSplitter: "#EEEEEE" + property color rowShadowStart: "#00000000" + property color rowShadowEnd: "#14000000" + + property color boxHover: "#cef9ce" + + property color comboBoxBackground: "#f8fef9" + property color comboBoxItemBackground: "#FFFFFF" + property color comboBoxItemBackgroundHover: "#cef9ce" + + property color buttonBackground: "#cef9ce" +} + \ No newline at end of file diff --git a/PropertyEditor/resources/Qml/ValueEditor/ColorBox.qml b/PropertyEditor/resources/Qml/ValueEditor/ColorBox.qml new file mode 100644 index 0000000..4312120 --- /dev/null +++ b/PropertyEditor/resources/Qml/ValueEditor/ColorBox.qml @@ -0,0 +1,40 @@ +import QtQuick; +import QtQuick.Controls; +import QtQuick.Dialogs + +Item{ + id: control + property color value + implicitHeight: 25 + signal asValueChanged(text:var) + + function setValue(newValue:var){ + if(newValue !== value){ + value = newValue + asValueChanged(value) + } + } + + Button{ + anchors.margins: 2 + anchors.fill: parent + palette { + button: value + } + background: Rectangle { + color: value + } + onClicked: { + colorDialog.open() + } + } + ColorDialog { + id: colorDialog + selectedColor: value + onAccepted: { + control.setValue(selectedColor) + } + } + +} + diff --git a/PropertyEditor/resources/Qml/ValueEditor/DirectorySelector.qml b/PropertyEditor/resources/Qml/ValueEditor/DirectorySelector.qml new file mode 100644 index 0000000..9f48888 --- /dev/null +++ b/PropertyEditor/resources/Qml/ValueEditor/DirectorySelector.qml @@ -0,0 +1,63 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Layouts +import QtCore +import ColorPalette + +Item{ + id: control + property var value + implicitHeight: dirBox.implicitHeight + signal asValueChanged(text: var) + + function setValue(newValue: var){ + if(newValue !== value){ + value = newValue + dirBox.value = value + asValueChanged(value) + } + } + + LineTextBox { + id: dirBox + value: control.value + anchors.left: parent.left + anchors.right: button.left + anchors.verticalCenter: parent.verticalCenter + onValueChanged: { + control.setValue(value) + } + } + + Button { + id: button + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + width: 30 + height: 25 + text: "..." + palette.buttonText: ColorPalette.theme.textPrimary + background: Rectangle { + color: ColorPalette.theme.buttonBackground + } + + onClicked: { + folderDialog.open() + } + } + FolderDialog { + id: folderDialog + title: "选择目录" + onAccepted: { + var filePath = currentFolder.toString(); + + if (filePath.startsWith("file:///")) { + filePath = filePath.substring(8); + } + + control.setValue(filePath); + } + } +} + diff --git a/PropertyEditor/resources/Qml/ValueEditor/FileSelector.qml b/PropertyEditor/resources/Qml/ValueEditor/FileSelector.qml new file mode 100644 index 0000000..f67e1fb --- /dev/null +++ b/PropertyEditor/resources/Qml/ValueEditor/FileSelector.qml @@ -0,0 +1,39 @@ +import QtQuick; +import QtQuick.Controls; +import QtQuick.Dialogs + +Item{ + id: control + property var value + implicitHeight: 25 + signal asValueChanged(text:var) + + function setValue(newValue:var){ + if(newValue !== value){ + value = newValue + asValueChanged(value) + } + } + Button{ + anchors.margins: 2 + anchors.fill: parent + palette { + button: value + } + background: Rectangle { + color: value + } + onClicked: { + colorDialog.open() + } + } + ColorDialog { + id: colorDialog + selectedColor: value + onAccepted: { + control.setValue(selectedColor) + } + } + +} + diff --git a/PropertyEditor/resources/Qml/ValueEditor/LineTextBox.qml b/PropertyEditor/resources/Qml/ValueEditor/LineTextBox.qml new file mode 100644 index 0000000..6617d79 --- /dev/null +++ b/PropertyEditor/resources/Qml/ValueEditor/LineTextBox.qml @@ -0,0 +1,72 @@ +import QtQuick; +import QtQuick.Controls; +import ColorPalette + +Item{ + id: control + property var value + implicitHeight: lineEditor.implicitHeight + 2 + signal asValueChanged(text:var) + function setValue(newText:var){ + if(newText !== value){ + value = newText + asValueChanged(value) + } + } + Rectangle { + anchors.fill: parent + border.color: ColorPalette.theme.textBoxBackground + color: ColorPalette.theme.textBoxBackground + border.width: 1 + clip: true + TextInput{ + id: lineEditor + enabled: true + clip: true + padding : 3 + anchors.fill: parent + anchors.leftMargin: 2 + anchors.rightMargin: 2 + text: control.value + color: ColorPalette.theme.textPrimary + wrapMode: TextInput.WordWrap + verticalAlignment: Text.AlignVCenter + onEditingFinished:{ + setValue(lineEditor.text) + } + } + MouseArea{ + id: hoverArea + hoverEnabled: true + propagateComposedEvents: true + anchors.fill: parent + onEntered:{ + exitAnimation.stop() + enterAnimation.start() + hoverArea.cursorShape = Qt.IBeamCursor + } + onExited:{ + enterAnimation.stop() + exitAnimation.start() + hoverArea.cursorShape = Qt.ArrowCursor + } + onPressed: (mouse)=> mouse.accepted = false + onReleased:(mouse)=> mouse.accepted = false + onClicked:(mouse)=> mouse.accepted = false + onDoubleClicked:(mouse)=> mouse.accepted = false + } + ColorAnimation on border.color{ + id: enterAnimation + to: ColorPalette.theme.boxHover + duration: 100 + running: false + } + ColorAnimation on border.color{ + id: exitAnimation + to: ColorPalette.theme.textBoxBackground + duration: 100 + running: false + } + } +} + diff --git a/PropertyEditor/resources/Qml/ValueEditor/MultiLineTextBox.qml b/PropertyEditor/resources/Qml/ValueEditor/MultiLineTextBox.qml new file mode 100644 index 0000000..df1ff01 --- /dev/null +++ b/PropertyEditor/resources/Qml/ValueEditor/MultiLineTextBox.qml @@ -0,0 +1,83 @@ +import QtQuick; +import QtQuick.Controls; +import ColorPalette + +Item{ + id: control + property var value + implicitHeight: lineEditor.implicitHeight + 2 + signal asValueChanged(text:var) + function setValue(newText:var){ + if(newText !== value){ + value = newText + asValueChanged(value) + } + } + Rectangle { + anchors.fill: parent + border.color: ColorPalette.theme.textBoxBackground + color: ColorPalette.theme.textBoxBackground + border.width: 1 + clip: true + TextArea{ + id: lineEditor + enabled: true + clip: true + padding: 3 + anchors.fill: parent + anchors.leftMargin: 2 + anchors.rightMargin: 2 + text: control.value + color: ColorPalette.theme.textPrimary + wrapMode: TextInput.WordWrap + verticalAlignment: Text.AlignVCenter + onEditingFinished:{ + setValue(lineEditor.text) + } + Keys.onPressed: { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + if (event.modifiers & Qt.ShiftModifier) { + // Shift+Enter:保留换行,不处理事件 + } else { + // 单独按Enter:触发编辑完成,不换行 + event.accepted = true; + lineEditor.editingFinished(); + } + } + } + } + MouseArea{ + id: hoverArea + hoverEnabled: true + propagateComposedEvents: true + anchors.fill: parent + onEntered:{ + exitAnimation.stop() + enterAnimation.start() + hoverArea.cursorShape = Qt.IBeamCursor + } + onExited:{ + enterAnimation.stop() + exitAnimation.start() + hoverArea.cursorShape = Qt.ArrowCursor + } + onPressed: (mouse)=> mouse.accepted = false + onReleased:(mouse)=> mouse.accepted = false + onClicked:(mouse)=> mouse.accepted = false + onDoubleClicked:(mouse)=> mouse.accepted = false + } + ColorAnimation on border.color{ + id: enterAnimation + to: ColorPalette.theme.boxHover + duration: 100 + running: false + } + ColorAnimation on border.color{ + id: exitAnimation + to: ColorPalette.theme.textBoxBackground + duration: 100 + running: false + } + } +} + diff --git a/PropertyEditor/resources/Qml/ValueEditor/NumberBox.qml b/PropertyEditor/resources/Qml/ValueEditor/NumberBox.qml new file mode 100644 index 0000000..2178bb6 --- /dev/null +++ b/PropertyEditor/resources/Qml/ValueEditor/NumberBox.qml @@ -0,0 +1,159 @@ +import QtQuick; +import QtQuick.Controls; +import ColorPalette + +Item{ + id: control + implicitHeight: 25 + implicitWidth: 100 + property bool isLimited: false + property bool isHovered: false + property var min:0 + property var max:100 + property var number: 0 + property real step : 1 + property int precision: 3 + property bool isMousePressed: false + + signal valueChanged(number:var) + function setNumber(value:var){ + if(value !== number && !isNaN(value)){ + number = value + if(min < max){ + if(number>max){ + number = max + } + if(number{ + if(mouse.button === Qt.LeftButton){ + control.isMousePressed = true + lastPressX = mouse.x + lastPressY = mouse.y + cursorShape = Qt.BlankCursor + } + } + onReleased: + (mouse)=>{ + control.isMousePressed = false + lastPressX = -1 + lastPressY = -1 + cursorShape = Qt.SplitHCursor + if(!isHovered){ + enterAnimation.stop() + exitAnimation.start() + } + } + onPositionChanged: + (mouse)=>{ + if(!input.enabled && mouse.buttons&Qt.LeftButton){ + if(!isLimited){ + var offset = mouse.x - lastPressX + setNumber(number + offset * step) + var global = dragArea.mapToGlobal(lastPressX, lastPressY) + var local = dragArea.mapFromGlobal(global.x,global.y) + helper.setCursorPos(global.x,global.y) + } + else{ + var xPercent = Math.max(0, Math.min(1, mouse.x / dragArea.width)) + var range = max - min + var newValue = min + xPercent * range + control.setNumber(newValue) + const validMouseX = Math.max (0, Math.min (dragArea.width, mouse.x)); + const validMouseY = Math.max (0, Math.min (dragArea.height, mouse.y)); + if (mouse.x !== validMouseX || mouse.y !== validMouseY) { + const validGlobalPos = dragArea.mapToGlobal (validMouseX, validMouseY); + helper.setCursorPos (validGlobalPos.x, validGlobalPos.y); + } + } + } + } + } + ColorAnimation on border.color{ + id: enterAnimation + to: ColorPalette.theme.boxHover + duration: 100 + running: false + } + ColorAnimation on border.color{ + id: exitAnimation + to: ColorPalette.theme.textBoxBackground + duration: 100 + running: false + } + } +} diff --git a/PropertyEditor/resources/Qml/ValueEditor/TextComboBox.qml b/PropertyEditor/resources/Qml/ValueEditor/TextComboBox.qml new file mode 100644 index 0000000..34ddf79 --- /dev/null +++ b/PropertyEditor/resources/Qml/ValueEditor/TextComboBox.qml @@ -0,0 +1,103 @@ +import QtQuick; +import QtQuick.Controls; +import Qt5Compat.GraphicalEffects +import ColorPalette + +Item{ + id: editor + property var value + property var model + implicitHeight: 25 + signal asValueChanged(value:var) + function setValue(newValue:var){ + if(newValue !== value){ + value = newValue + asValueChanged(value) + } + } + ComboBox { + id: control + anchors.margins: 2 + anchors.fill: parent + model: editor.model + onCurrentTextChanged: { + setValue(currentText) + } + delegate: ItemDelegate { + id: itemDelegate + width: control.width + height: 25 + padding: 5 + background: Rectangle { + color: itemDelegate.highlighted ? ColorPalette.theme.comboBoxItemBackgroundHover + : itemDelegate.hovered ? ColorPalette.theme.comboBoxItemBackgroundHover + : ColorPalette.theme.comboBoxItemBackground + Behavior on color { + ColorAnimation { duration: 100 } + } + } + contentItem: Text { + text: modelData + color: ColorPalette.theme.textPrimary + font: control.font + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter // 文字垂直居中 + horizontalAlignment: Text.AlignLeft // 文字左对齐 + padding: 0 + } + highlighted: control.highlightedIndex === index + required property int index + required property var modelData + } + + indicator: Image { + id: indicator + x: control.width - width/2 - control.rightPadding + y: control.topPadding + (control.availableHeight - height) / 2 + width: 13 + height: 13 + mipmap: true + source: "qrc:/resources/Icon/expand.png" + ColorOverlay { + anchors.fill: parent + source: parent + color: ColorPalette.theme.rowIndicator + opacity: 1.0 + } + } + + contentItem: Text { + leftPadding: 3 + rightPadding: control.indicator.width + control.spacing + + text: control.displayText + font: control.font + color: ColorPalette.theme.textPrimary + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + background: Rectangle { + color: ColorPalette.theme.comboBoxBackground + } + + popup: Popup { + y: control.height + width: control.width + implicitHeight: contentItem.implicitHeight + 5 + padding : 2 + contentItem: ListView { + clip: true + implicitHeight: contentHeight + model: control.popup.visible ? control.delegateModel : null + currentIndex: control.highlightedIndex + ScrollIndicator.vertical: ScrollIndicator { } + } + + background: Rectangle { + color: ColorPalette.theme.comboBoxBackground + } + } + } +} + diff --git a/PropertyEditor/resources/Qml/ValueEditor/Vec2Box.qml b/PropertyEditor/resources/Qml/ValueEditor/Vec2Box.qml new file mode 100644 index 0000000..8c26d38 --- /dev/null +++ b/PropertyEditor/resources/Qml/ValueEditor/Vec2Box.qml @@ -0,0 +1,44 @@ +import QtQuick; +import QtQuick.Controls; +import QtQuick.Layouts; + +Item{ + id: control + property vector2d value + implicitHeight: 25 + signal asValueChanged(value:var) + function setValue(newValue:var){ + if(value !== newValue){ + value = newValue + asValueChanged(value) + } + } + RowLayout{ + anchors.fill: parent + NumberBox{ + id: xBox + width: parent.width/4 + Layout.alignment: Qt.AlignLeft + number: value.x + onNumberChanged: { + if (control.value) { + control.setValue(Qt.vector2d(number, control.value.y)) + } + } + } + NumberBox{ + id: yBox + width: parent.width/4 + Layout.alignment: Qt.AlignLeft + number: value.y + onNumberChanged: { + if (control.value) { + control.setValue(Qt.vector2d(control.value.x, number)) + } + } + } + Item { + Layout.fillWidth: true + } + } +} diff --git a/PropertyEditor/resources/Qml/ValueEditor/Vec3Box.qml b/PropertyEditor/resources/Qml/ValueEditor/Vec3Box.qml new file mode 100644 index 0000000..935bbf0 --- /dev/null +++ b/PropertyEditor/resources/Qml/ValueEditor/Vec3Box.qml @@ -0,0 +1,55 @@ +import QtQuick; +import QtQuick.Controls; +import QtQuick.Layouts; + +Item{ + id: control + property vector3d value + implicitHeight: 25 + signal asValueChanged(value:var) + function setValue(newValue:var){ + if(value !== newValue){ + value = newValue + asValueChanged(value) + } + } + RowLayout{ + anchors.fill: parent + NumberBox{ + id: xBox + width: parent.width/4 + Layout.alignment: Qt.AlignLeft + number: value.x + onNumberChanged: { + if (control.value) { + control.setValue(Qt.vector3d(number, control.value.y, control.value.z)) + } + } + } + NumberBox{ + id: yBox + width: parent.width/4 + Layout.alignment: Qt.AlignLeft + number: value.y + onNumberChanged: { + if (control.value) { + control.setValue(Qt.vector3d(control.value.x, number, control.value.z)) + } + } + } + NumberBox{ + id: zBox + width: parent.width/4 + Layout.alignment: Qt.AlignLeft + number: value.z + onNumberChanged: { + if (control.value) { + control.setValue(Qt.vector3d(control.value.x, control.value.y, number)) + } + } + } + Item { + Layout.fillWidth: true + } + } +} diff --git a/PropertyEditor/resources/Qml/ValueEditor/Vec4Box.qml b/PropertyEditor/resources/Qml/ValueEditor/Vec4Box.qml new file mode 100644 index 0000000..b962a2a --- /dev/null +++ b/PropertyEditor/resources/Qml/ValueEditor/Vec4Box.qml @@ -0,0 +1,66 @@ +import QtQuick; +import QtQuick.Controls; +import QtQuick.Layouts; + +Item{ + id: control + property vector4d value + implicitHeight: 25 + signal asValueChanged(value:var) + function setValue(newValue:var){ + if(value !== newValue){ + value = newValue + asValueChanged(value) + } + } + RowLayout{ + anchors.fill: parent + NumberBox{ + id: xBox + width: parent.width/4 + Layout.alignment: Qt.AlignLeft + number: value.x + onNumberChanged: { + if (control.value) { + control.setValue(Qt.vector4d(number, control.value.y, control.value.z, control.value.w)) + } + } + } + NumberBox{ + id: yBox + width: parent.width/4 + Layout.alignment: Qt.AlignLeft + number: value.y + onNumberChanged: { + if (control.value) { + control.setValue(Qt.vector4d(control.value.x, number, control.value.z, control.value.w)) + } + } + } + NumberBox{ + id: zBox + width: parent.width/4 + Layout.alignment: Qt.AlignLeft + number: value.z + onNumberChanged: { + if (control.value) { + control.setValue(Qt.vector4d(control.value.x, control.value.y, number, control.value.w)) + } + } + } + NumberBox{ + id: wBox + width: parent.width/4 + Layout.alignment: Qt.AlignLeft + number: value.w + onNumberChanged: { + if (control.value) { + control.setValue(Qt.vector4d(control.value.x, control.value.y, control.value.z, number)) + } + } + } + Item { + Layout.fillWidth: true + } + } +} diff --git a/PropertyEditor/resources/image-20250826114654194.png b/PropertyEditor/resources/image-20250826114654194.png new file mode 100644 index 0000000000000000000000000000000000000000..8c95aa14c475ae5258d5d123438a62af5e94a7c5 GIT binary patch literal 125086 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 IQtv+hH=5DSA^-pY literal 0 HcmV?d00001 diff --git a/PropertyEditor/source/include/IPropertyTypeCustomization.h b/PropertyEditor/source/include/IPropertyTypeCustomization.h new file mode 100644 index 0000000..0b09267 --- /dev/null +++ b/PropertyEditor/source/include/IPropertyTypeCustomization.h @@ -0,0 +1,18 @@ +#ifndef IPropertyTypeCustomization_h__ +#define IPropertyTypeCustomization_h__ + +#include +#include "export.hpp" + +class QPropertyHandle; +class QQuickDetailsViewRowBuilder; +class QQuickDetailsViewLayoutBuilder; + +class DIAGRAM_DESIGNER_PUBLIC IPropertyTypeCustomization :public QEnableSharedFromThis +{ +public: + virtual void customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder); + virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder); +}; + +#endif // IPropertyTypeCustomization_h__ diff --git a/PropertyEditor/source/include/PropertyHandleImpl/IPropertyHandleImpl.h b/PropertyEditor/source/include/PropertyHandleImpl/IPropertyHandleImpl.h new file mode 100644 index 0000000..d08d78d --- /dev/null +++ b/PropertyEditor/source/include/PropertyHandleImpl/IPropertyHandleImpl.h @@ -0,0 +1,33 @@ +#ifndef IPropertyHandleImpl_h__ +#define IPropertyHandleImpl_h__ + +#include +#include +#include +#include +#include "export.hpp" + +class QPropertyHandle; + +class IPropertyHandleImpl{ + friend class QPropertyHandle; +public: + enum Type { + Null, + RawType, + Associative, + Sequential, + Enum, + Object, + }; +protected: + IPropertyHandleImpl(QPropertyHandle* inHandle); + virtual QQuickItem* createNameEditor(QQuickItem* inParent); + virtual QQuickItem* createValueEditor(QQuickItem* inParent)= 0; + virtual Type type() { return Type::Null; }; + +protected: + QPropertyHandle* mHandle; +}; + +#endif // IPropertyHandleImpl_h__ diff --git a/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Associative.h b/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Associative.h new file mode 100644 index 0000000..3ae61c9 --- /dev/null +++ b/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Associative.h @@ -0,0 +1,27 @@ +#ifndef QAssociativePropertyHandle_h__ +#define QAssociativePropertyHandle_h__ + +#include "QMetaContainer" +#include "IPropertyHandleImpl.h" + +class DIAGRAM_DESIGNER_PUBLIC QPropertyHandleImpl_Associative: public IPropertyHandleImpl { +public: + QPropertyHandleImpl_Associative(QPropertyHandle* inHandle); + + const QMetaAssociation& metaAssociation() const; + + void appendItem(QString inKey, QVariant inValue); + bool renameItem(QString inSrc, QString inDst); + void removeItem(QString inKey); + +protected: + Type type() override { return Type::Associative; }; + QQuickItem* createValueEditor(QQuickItem* inParent)override; + +private: + QMetaAssociation mMetaAssociation; +}; + + +#endif // QAssociativePropertyHandle_h__ + diff --git a/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Enum.h b/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Enum.h new file mode 100644 index 0000000..c3a50c2 --- /dev/null +++ b/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Enum.h @@ -0,0 +1,20 @@ +#ifndef QPropertyHandleImpl_Enum_h__ +#define QPropertyHandleImpl_Enum_h__ + +#include "IPropertyHandleImpl.h" + +class DIAGRAM_DESIGNER_PUBLIC QPropertyHandleImpl_Enum: public IPropertyHandleImpl { +public: + QPropertyHandleImpl_Enum(QPropertyHandle* inHandle); + +protected: + QQuickItem* createValueEditor(QQuickItem* inParent) override; + Type type() override { return Type::Enum; }; + +private: + QHash mNameToValueMap; + QList mKeys; +}; + + +#endif // QPropertyHandleImpl_Enum_h__ diff --git a/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Object.h b/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Object.h new file mode 100644 index 0000000..1a6cb47 --- /dev/null +++ b/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Object.h @@ -0,0 +1,27 @@ +#ifndef QPropertyHandleImpl_Object_h__ +#define QPropertyHandleImpl_Object_h__ + +#include "IPropertyHandleImpl.h" + +class DIAGRAM_DESIGNER_PUBLIC QPropertyHandleImpl_Object : public IPropertyHandleImpl { +public: + QPropertyHandleImpl_Object(QPropertyHandle* inHandle); + QObject* getObject(); + void* getGadget(); + bool isGadget() const; + QObject* getOwnerObject(); + const QMetaObject* getMetaObject() const; + QQuickItem* createValueEditor(QQuickItem* inParent)override; + Type type() override { return Type::Object; }; + void refreshObjectPtr(); + QVariant& getObjectHolder(); +private: + QVariant mObjectHolder; + void* mObjectPtr = nullptr; + QObject* mOwnerObject = nullptr; + const QMetaObject* mMetaObject = nullptr; + bool bIsSharedPointer = false; + bool bIsPointer = false; +}; + +#endif // QPropertyHandleImpl_Object_h__ diff --git a/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_RawType.h b/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_RawType.h new file mode 100644 index 0000000..385bb2e --- /dev/null +++ b/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_RawType.h @@ -0,0 +1,14 @@ +#ifndef QPropertyHandleImpl_RawType_h__ +#define QPropertyHandleImpl_RawType_h__ + +#include "IPropertyHandleImpl.h" + +class DIAGRAM_DESIGNER_PUBLIC QPropertyHandleImpl_RawType : public IPropertyHandleImpl { +public: + QPropertyHandleImpl_RawType(QPropertyHandle* inHandle); +protected: + QQuickItem* createValueEditor(QQuickItem* inParent) override; + Type type() override { return Type::RawType; }; +}; + +#endif // QPropertyHandleImpl_RawType_h__ diff --git a/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Sequential.h b/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Sequential.h new file mode 100644 index 0000000..7f79a96 --- /dev/null +++ b/PropertyEditor/source/include/PropertyHandleImpl/QPropertyHandleImpl_Sequential.h @@ -0,0 +1,26 @@ +#ifndef QSequentialPropertyHandle_h__ +#define QSequentialPropertyHandle_h__ + +#include "QMetaContainer" +#include "IPropertyHandleImpl.h" + +class DIAGRAM_DESIGNER_PUBLIC QPropertyHandleImpl_Sequential: public IPropertyHandleImpl +{ +public: + QPropertyHandleImpl_Sequential(QPropertyHandle* inHandle); + + const QMetaSequence& metaSequence() const; + int itemCount(); + void appendItem(QVariant InVar); + void moveItem(int InSrcIndex, int InDstIndex); + void removeItem(int InIndex); + +protected: + QQuickItem* createValueEditor(QQuickItem* inParent)override; + Type type() override { return Type::Sequential; }; + +private: + QMetaSequence mMetaSequence; +}; + +#endif // QSequentialPropertyHandle_h__ diff --git a/PropertyEditor/source/include/QDetailsView.h b/PropertyEditor/source/include/QDetailsView.h new file mode 100644 index 0000000..5118634 --- /dev/null +++ b/PropertyEditor/source/include/QDetailsView.h @@ -0,0 +1,20 @@ +#ifndef QDetailsView_h__ +#define QDetailsView_h__ + +#include +#include +#include "QQuickDetailsView.h" + +class DIAGRAM_DESIGNER_PUBLIC QDetailsView : public QWidget { + Q_OBJECT +public: + explicit QDetailsView(QWidget* parent = nullptr); + QQuickDetailsView* getQuickDetailsView() const; + void setObject(QObject* inObject); + QObject* getObject() const; +private: + QQuickWidget* mQuickWidget; + QQuickDetailsView* mQuickDetailsView; +}; + +#endif // QDetailsView_h__ diff --git a/PropertyEditor/source/include/QDetailsViewAPI.h b/PropertyEditor/source/include/QDetailsViewAPI.h new file mode 100644 index 0000000..38e8d06 --- /dev/null +++ b/PropertyEditor/source/include/QDetailsViewAPI.h @@ -0,0 +1,21 @@ +#ifndef QDETAILS_VIEW_API_H +#define QDETAILS_VIEW_API_H + +#include + +#ifdef _WIN32 +# ifdef PROPERTY_EDITOR_STATIC_LIBRARY +# define QDETAILS_VIEW_API +# else +# ifdef QDETAILS_VIEW_SHARED_LIBRARY +# define QDETAILS_VIEW_API __declspec(dllexport) +# else +# define QDETAILS_VIEW_API __declspec(dllimport) +# endif +# endif +#else +# define QDETAILS_VIEW_API __attribute__((visibility("default"))) +#endif + + +#endif // QDETAILS_VIEW_API_H diff --git a/PropertyEditor/source/include/QPropertyHandle.h b/PropertyEditor/source/include/QPropertyHandle.h new file mode 100644 index 0000000..bfb05fc --- /dev/null +++ b/PropertyEditor/source/include/QPropertyHandle.h @@ -0,0 +1,108 @@ +#ifndef QPropertyHandle_h__ +#define QPropertyHandle_h__ + +#include +#include +#include "PropertyHandleImpl/QPropertyHandleImpl_Enum.h" +#include "PropertyHandleImpl/QPropertyHandleImpl_Object.h" +#include "PropertyHandleImpl/QPropertyHandleImpl_Associative.h" +#include "PropertyHandleImpl/QPropertyHandleImpl_Sequential.h" + +class IPropertyHandleImpl; + +class DIAGRAM_DESIGNER_PUBLIC QPropertyHandle : public QObject { + Q_OBJECT + Q_PROPERTY(QVariant Var READ getVar WRITE setVar NOTIFY asVarChanged) +public: + using Getter = std::function; + using Setter = std::function; + + enum PropertyType { + Unknown, + RawType, + Enum, + Sequential, + Associative, + Object + }; + + static QPropertyHandle* Find(const QObject* inParent, const QString& inPropertyPath); + static QPropertyHandle* FindOrCreate(QObject* inParent, QMetaType inType, QString inPropertyPath, Getter inGetter, Setter inSetter); + static QPropertyHandle* FindOrCreate(QObject* inObject); + static QPropertyHandle* Create(QObject* inParent, QMetaType inType, QString inPropertyPath, Getter inGetter, Setter inSetter); + static void Cleanup(QObject* inParent); + + QMetaType getType(); + PropertyType getPropertyType() const; + QString getName(); + QString getPropertyPath(); + QString createSubPath(const QString& inSubName); + + void invalidateStructure(); + + Q_INVOKABLE QVariant getVar(); + Q_INVOKABLE void setVar(QVariant var); + + bool hasMetaData(const QString& inName) const; + QVariant getMetaData(const QString& inName) const; + const QVariantHash& getMetaDataMap() const; + + QPropertyHandle* findChild(QString inPropertyName); + QPropertyHandle* findOrCreateChild(QMetaType inType, QString inPropertyName, QPropertyHandle::Getter inGetter, QPropertyHandle::Setter inSetter); + + QQuickItem* setupNameEditor(QQuickItem* inParent); + QQuickItem* steupValueEditor(QQuickItem* inParent); + + IPropertyHandleImpl::Type type(); + QPropertyHandleImpl_Enum* asEnum(); + QPropertyHandleImpl_Object* asObject(); + QPropertyHandleImpl_Associative* asAssociative(); + QPropertyHandleImpl_Sequential* asSequential(); + + static PropertyType parserType(QMetaType inType); + static QVariant createNewVariant(QMetaType inOutputType); +Q_SIGNALS: + void asVarChanged(QVariant); + void asStructureChanged(); + void asRequestRollback(QVariant); +protected: + QPropertyHandle(QObject* inParent, QMetaType inType, QString inPropertyPath, Getter inGetter, Setter inSetter); + void resloveMetaData(); +private: + QMetaType mType; + PropertyType mPropertyType; + QString mPropertyPath; + Getter mGetter; + Setter mSetter; + QVariant mInitialValue; + QVariantHash mMetaData; + QSharedPointer mImpl; +}; + +struct DIAGRAM_DESIGNER_PUBLIC ExternalRefCountWithMetaType : public QtSharedPointer::ExternalRefCountData { + typedef ExternalRefCountData Parent; + QMetaType mMetaType; + void* mData; + + static void deleter(ExternalRefCountData* self) { + ExternalRefCountWithMetaType* that = + static_cast(self); + that->mMetaType.destroy(that->mData); + Q_UNUSED(that); // MSVC warns if T has a trivial destructor + } + + static inline ExternalRefCountData* create(QMetaType inMetaType, void* inPtr) + { + ExternalRefCountWithMetaType* d = static_cast(::operator new(sizeof(ExternalRefCountWithMetaType))); + + // initialize the d-pointer sub-object + // leave d->data uninitialized + new (d) Parent(ExternalRefCountWithMetaType::deleter); // can't throw + d->mData = inPtr; + d->mMetaType = inMetaType; + return d; + } +}; + + +#endif // QPropertyHandle_h__ diff --git a/PropertyEditor/source/include/QQuickDetailsView.h b/PropertyEditor/source/include/QQuickDetailsView.h new file mode 100644 index 0000000..aeea44c --- /dev/null +++ b/PropertyEditor/source/include/QQuickDetailsView.h @@ -0,0 +1,28 @@ +#ifndef QQuickDetailsView_h__ +#define QQuickDetailsView_h__ + +#include "QQuickTreeViewEx.h" + +class QQuickDetailsViewPrivate; + +class DIAGRAM_DESIGNER_PUBLIC QQuickDetailsView: public QQuickTreeViewEx { + Q_OBJECT + QML_NAMED_ELEMENT(DetailsView) + Q_DISABLE_COPY(QQuickDetailsView) + Q_DECLARE_PRIVATE(QQuickDetailsView) + Q_PROPERTY(qreal SpliterPencent READ getSpliterPencent WRITE setSpliterPencent NOTIFY asSpliterPencentChanged FINAL) + Q_PROPERTY(QObject* Object READ getObject WRITE setObject NOTIFY asObjectChanged FINAL) +public: + QQuickDetailsView(QQuickItem* parent = nullptr); + qreal getSpliterPencent() const; + void setSpliterPencent(qreal val); + Q_INVOKABLE void setObject(QObject* inObject); + Q_INVOKABLE QObject* getObject() const; +Q_SIGNALS: + void asSpliterPencentChanged(qreal); + void asObjectChanged(QObject*); +protected: + void componentComplete() override; +}; + +#endif // QQuickDetailsView_h__ diff --git a/PropertyEditor/source/include/QQuickDetailsViewLayoutBuilder.h b/PropertyEditor/source/include/QQuickDetailsViewLayoutBuilder.h new file mode 100644 index 0000000..81bc2b4 --- /dev/null +++ b/PropertyEditor/source/include/QQuickDetailsViewLayoutBuilder.h @@ -0,0 +1,36 @@ +#ifndef QQuickDetailsViewLayoutBuilder_h__ +#define QQuickDetailsViewLayoutBuilder_h__ + +#include "QQuickDetailsViewRow.h" + +class DIAGRAM_DESIGNER_PUBLIC QQuickDetailsViewRowBuilder { +public: + QQuickDetailsViewRowBuilder(IDetailsViewRow* inRow, QQuickItem* inRootItem); + QPair makeNameValueSlot(); + + IDetailsViewRow* row() const; + QQuickItem* rootItem() const; + + void makePropertyRow(QPropertyHandle* inHandle); + QQuickItem* setupItem(QQuickItem* inParent, QString inQmlCode); + void setupLabel(QQuickItem* inParent, QString inText); + void setHeightProxy(QQuickItem* inProxyItem); +private: + IDetailsViewRow* mRow = nullptr; + QQuickItem* mRootItem = nullptr; +}; + +class DIAGRAM_DESIGNER_PUBLIC QQuickDetailsViewLayoutBuilder { +public: + QQuickDetailsViewLayoutBuilder(IDetailsViewRow* inRootRow); + + IDetailsViewRow* row() const; + + void addCustomRow(std::function inCustomRowCreator, QString inOverrideName = ""); + void addProperty(QPropertyHandle* inPropertyHandle, QString inOverrideName = ""); + void addObject(QObject* inObject); +private: + IDetailsViewRow* mRootRow = nullptr; +}; + +#endif // QQuickDetailsViewLayoutBuilder_h__ diff --git a/PropertyEditor/source/include/QQuickDetailsViewMananger.h b/PropertyEditor/source/include/QQuickDetailsViewMananger.h new file mode 100644 index 0000000..de81849 --- /dev/null +++ b/PropertyEditor/source/include/QQuickDetailsViewMananger.h @@ -0,0 +1,58 @@ +#ifndef QQuickDetailsViewManager_h__ +#define QQuickDetailsViewManager_h__ + +#include +#include +#include +#include +#include +#include +#include "IPropertyTypeCustomization.h" +#include "export.hpp" + +class QPropertyHandle; + +class DIAGRAM_DESIGNER_PUBLIC QQuickDetailsViewManager : public QObject{ +public: + using PropertyTypeCustomizationCreator = std::function()>; + using TypeEditorCreator = std::function; + + static QQuickDetailsViewManager* Get(); + + void initialize(); + bool isInitialized() const; + + template + void registerPropertyTypeCustomization() { + QMetaType metaType = QMetaType::fromType(); + if (metaType.metaObject()) { + mClassCustomizationMap.insert(metaType.metaObject(), []() { + return QSharedPointer::create(); + }); + } + else { + mMetaTypeCustomizationMap.insert(metaType, []() { + return QSharedPointer::create(); + }); + } + + } + void unregisterPropertyTypeCustomization(const QMetaType& inMetaType); + + void registerTypeEditor(const QMetaType& inMetaType, TypeEditorCreator Creator); + void unregisterTypeEditor(const QMetaType& inMetaType); + + QQuickItem* createValueEditor(QPropertyHandle* inHandle, QQuickItem* parent); + QSharedPointer getCustomPropertyType(QPropertyHandle* inHandle); +protected: + QQuickDetailsViewManager(); + void RegisterBasicTypeEditor(); +private: + bool mInitialized = false; + + QHash mClassCustomizationMap; + QHash mMetaTypeCustomizationMap; + QHash mTypeEditorCreatorMap; +}; + +#endif // QQuickDetailsViewManager_h__ diff --git a/PropertyEditor/source/include/QQuickDetailsViewModel.h b/PropertyEditor/source/include/QQuickDetailsViewModel.h new file mode 100644 index 0000000..577d249 --- /dev/null +++ b/PropertyEditor/source/include/QQuickDetailsViewModel.h @@ -0,0 +1,44 @@ +#ifndef QQuickDetailsViewModel_h__ +#define QQuickDetailsViewModel_h__ + +#include +#include +#include +#include +#include +#include "export.hpp" + +class IDetailsViewRow; +class QDetailsViewRow_Property; + +class DIAGRAM_DESIGNER_PUBLIC QQuickDetailsViewModel : public QAbstractItemModel { + Q_OBJECT + enum Roles { + name = 0, + }; + friend class IDetailsViewRow; +public: + QQuickDetailsViewModel(QObject* parent = 0); + ~QQuickDetailsViewModel() {} + QVariant data(const QModelIndex& index, int role) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + QHash roleNames() const override; + + void setObject(QObject* inObject); + QObject* getObject() const; + + QModelIndex indexForRow(IDetailsViewRow* row) const; + void updateRowIndex(IDetailsViewRow* row); + +private: + QSharedPointer mRoot; + QObject* mObject; +}; + +#endif // QQuickDetailsViewModel_h__ diff --git a/PropertyEditor/source/include/QQuickDetailsViewRow.h b/PropertyEditor/source/include/QQuickDetailsViewRow.h new file mode 100644 index 0000000..b40b8f7 --- /dev/null +++ b/PropertyEditor/source/include/QQuickDetailsViewRow.h @@ -0,0 +1,66 @@ +#ifndef QQuickDetailsViewRow_h__ +#define QQuickDetailsViewRow_h__ + +#include +#include +#include +#include +#include +#include "QPropertyHandle.h" +#include "IPropertyTypeCustomization.h" +#include "QQuickDetailsViewModel.h" +#include "export.hpp" + +class DIAGRAM_DESIGNER_PUBLIC IDetailsViewRow { + friend class QQuickDetailsViewModel; +public: + ~IDetailsViewRow() {}; + + void setName(QString inName); + virtual QString name(); + + virtual void setupItem(QQuickItem* inParent){} + virtual void attachChildren() {} + virtual void addChild(QSharedPointer inChild); + + void clear(); + QQuickDetailsViewModel* model(); + QModelIndex modelIndex() const { return mModelIndex; } + void setModelIndex(const QModelIndex& index) { mModelIndex = index; } + int rowNumber() const { + if (!mParent) return -1; + return mParent->mChildren.indexOf(const_cast(this)); + } + void invalidateChildren(); +protected: + QString mName; + QQuickDetailsViewModel* mModel = nullptr; + QModelIndex mModelIndex; + IDetailsViewRow* mParent = nullptr; + QList> mChildren; +}; + +class DIAGRAM_DESIGNER_PUBLIC QDetailsViewRow_Property : public IDetailsViewRow { +public: + QDetailsViewRow_Property(QPropertyHandle* inHandle); + ~QDetailsViewRow_Property(); + void setHandle(QPropertyHandle* inHandle); + void setupItem(QQuickItem* inParent) override; + void attachChildren() override; +protected: + QPropertyHandle* mHandle = nullptr; + QMetaObject::Connection mStructureChangedConnection; + QSharedPointer mPropertyTypeCustomization; +}; + +class DIAGRAM_DESIGNER_PUBLIC QDetailsViewRow_Custom : public IDetailsViewRow { +public: + QDetailsViewRow_Custom(std::function inRowCreator); +protected: + QString name() override { return "Custom"; } + void setupItem(QQuickItem* inParent) override; +private: + std::function mRowCreator; +}; + +#endif // QQuickDetailsViewRow_h__ diff --git a/PropertyEditor/source/include/QQuickFunctionLibrary.h b/PropertyEditor/source/include/QQuickFunctionLibrary.h new file mode 100644 index 0000000..de355ed --- /dev/null +++ b/PropertyEditor/source/include/QQuickFunctionLibrary.h @@ -0,0 +1,64 @@ +#ifndef QQuickFunctionLibrary_h__ +#define QQuickFunctionLibrary_h__ + +#include +#include +#include +#include "export.hpp" + +class QQuickLambdaHolder; +class QQuickLambdaHolder_OneParam; + +class DIAGRAM_DESIGNER_PUBLIC QQuickFunctionLibrary : public QObject { + Q_OBJECT +public: + static QQuickFunctionLibrary* Get(); + + Q_INVOKABLE QString numberToString(QVariant var,int precision); + Q_INVOKABLE void setCursorPos(qreal x, qreal y); + Q_INVOKABLE void setOverrideCursorShape(Qt::CursorShape shape); + Q_INVOKABLE void restoreOverrideCursorShape(); + Q_INVOKABLE void setCursorPosTest(QQuickItem* item, qreal x, qreal y); + + + static QMetaObject::Connection connect(QObject* sender, const char* signal, QObject* receiver, std::function callback); + static QMetaObject::Connection connect(QObject* sender, const char* signal, QObject* receiver, std::function callback); + static QMetaObject::Connection connect(QObject* sender, const char* signal, QObject* receiver, std::function callback); +}; + +class DIAGRAM_DESIGNER_PUBLIC QQuickLambdaHolder : public QObject { + Q_OBJECT + std::function mCallback; +public: + QQuickLambdaHolder(std::function callback, QObject* parent) + : QObject(parent) + , mCallback(std::move(callback)) {} + + Q_SLOT void call() { mCallback(); } +}; + +class DIAGRAM_DESIGNER_PUBLIC QQuickLambdaHolder_OneParam : public QObject { + Q_OBJECT + std::function mCallback; +public: + QQuickLambdaHolder_OneParam(std::function callback, QObject* parent) + : QObject(parent) + , mCallback(std::move(callback)) { + } + + Q_SLOT void call(QVariant inParam0) { mCallback(inParam0); } +}; + +class DIAGRAM_DESIGNER_PUBLIC QQuickLambdaHolder_TwoParams : public QObject { + Q_OBJECT + std::function mCallback; +public: + QQuickLambdaHolder_TwoParams(std::function callback, QObject* parent) + : QObject(parent) + , mCallback(std::move(callback)) { + } + + Q_SLOT void call(QVariant inParam0, QVariant inParam1) { mCallback(inParam0, inParam1); } +}; + +#endif // QQuickFunctionLibrary_h__ diff --git a/PropertyEditor/source/include/QQuickTreeViewEx.h b/PropertyEditor/source/include/QQuickTreeViewEx.h new file mode 100644 index 0000000..ef9c136 --- /dev/null +++ b/PropertyEditor/source/include/QQuickTreeViewEx.h @@ -0,0 +1,53 @@ +#ifndef QQuickTreeViewEx_h__ +#define QQuickTreeViewEx_h__ + +#include "export.hpp" +#include "private/qquicktableview_p.h" + +class QQuickTreeViewExPrivate; + +class DIAGRAM_DESIGNER_PUBLIC QQuickTreeViewEx: public QQuickTableView { + Q_OBJECT +public: + QQuickTreeViewEx(QQuickItem* parent = nullptr); + ~QQuickTreeViewEx() override; + + QModelIndex rootIndex() const; + void setRootIndex(const QModelIndex& index); + void resetRootIndex(); + + Q_INVOKABLE int depth(int row) const; + + Q_INVOKABLE bool isExpanded(int row) const; + Q_INVOKABLE void expand(int row); + Q_INVOKABLE void collapse(int row); + Q_INVOKABLE void toggleExpanded(int row); + Q_INVOKABLE void invalidateLayout(); + + Q_REVISION(6, 4) Q_INVOKABLE void expandRecursively(int row = -1, int depth = -1); + Q_REVISION(6, 4) Q_INVOKABLE void collapseRecursively(int row = -1); + Q_REVISION(6, 4) Q_INVOKABLE void expandToIndex(const QModelIndex& index); + + Q_INVOKABLE QModelIndex modelIndex(const QPoint& cell) const override; + Q_INVOKABLE QPoint cellAtIndex(const QModelIndex& index) const override; + +#if QT_DEPRECATED_SINCE(6, 4) + QT_DEPRECATED_VERSION_X_6_4("Use index(row, column) instead") + Q_REVISION(6, 4) Q_INVOKABLE QModelIndex modelIndex(int row, int column) const override; +#endif + +Q_SIGNALS: + void expanded(int row, int depth); + void collapsed(int row, bool recursively); + Q_REVISION(6, 6) void rootIndexChanged(); +protected: + QQuickTreeViewEx(QQuickTreeViewExPrivate& dd, QQuickItem* parent); + void keyPressEvent(QKeyEvent* event) override; +private: + Q_DISABLE_COPY(QQuickTreeViewEx) + Q_DECLARE_PRIVATE(QQuickTreeViewEx) +}; + +QML_DECLARE_TYPE(QQuickTreeViewEx) + +#endif // QQuickTreeViewEx_h__ diff --git a/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Associative.cpp b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Associative.cpp new file mode 100644 index 0000000..6cf62b1 --- /dev/null +++ b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Associative.cpp @@ -0,0 +1,35 @@ +#include "PropertyTypeCustomization_Associative.h" +#include "QQuickDetailsViewLayoutBuilder.h" +#include "QPropertyHandle.h" +#include "qsequentialiterable.h" +#include "qassociativeiterable.h" + +void PropertyTypeCustomization_Associative::customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) +{ + auto associativelHandle = inPropertyHandle->asAssociative(); + QMetaAssociation metaAssociation = associativelHandle->metaAssociation(); + QVariant varMap = inPropertyHandle->getVar(); + QAssociativeIterable iterable = varMap.value(); + for (auto iter = iterable.begin(); iter != iterable.end(); ++iter) { + QString key = iter.key().toString(); + inBuilder->addProperty(inPropertyHandle->findOrCreateChild( + metaAssociation.mappedMetaType(), + key, + [inPropertyHandle, key]() { + QVariant varMap = inPropertyHandle->getVar(); + QAssociativeIterable iterable = varMap.value(); + return iterable.value(key); + }, + [inPropertyHandle, key, metaAssociation](QVariant var) { + QVariant varMap = inPropertyHandle->getVar(); + QAssociativeIterable iterable = varMap.value(); + QtPrivate::QVariantTypeCoercer keyCoercer; + QtPrivate::QVariantTypeCoercer mappedCoercer; + void* containterPtr = const_cast(iterable.constIterable()); + const void* dataPtr = mappedCoercer.coerce(var, metaAssociation.mappedMetaType()); + metaAssociation.setMappedAtKey(containterPtr, keyCoercer.coerce(key, metaAssociation.keyMetaType()), dataPtr); + inPropertyHandle->setVar(varMap); + } + )); + } +} \ No newline at end of file diff --git a/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Associative.h b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Associative.h new file mode 100644 index 0000000..3e2be28 --- /dev/null +++ b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Associative.h @@ -0,0 +1,11 @@ +#ifndef PropertyTypeCustomization_Associative_h__ +#define PropertyTypeCustomization_Associative_h__ + +#include "IPropertyTypeCustomization.h" + +class PropertyTypeCustomization_Associative : public IPropertyTypeCustomization { +protected: + virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) override; +}; + +#endif // PropertyTypeCustomization_Associative_h__ diff --git a/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Matrix4x4.cpp b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Matrix4x4.cpp new file mode 100644 index 0000000..c2e31b1 --- /dev/null +++ b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Matrix4x4.cpp @@ -0,0 +1,94 @@ +#include "PropertyTypeCustomization_Matrix4x4.h" +#include "QQuickDetailsViewLayoutBuilder.h" +#include "QPropertyHandle.h" +#include +#include +#include +#include "QQuickFunctionLibrary.h" + +void PropertyTypeCustomization_Matrix4x4::customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) +{ + //inBuilder->addCustomRow([inPropertyHandle](QQuickDetailsViewRowBuilder* builder) { + // auto editorSlot = builder->makeNameValueSlot(); + // builder->setupLabel(editorSlot.first, "0"); + // auto valueItem = builder->setupItem(editorSlot.second, R"( + // import QtQuick; + // import QtQuick.Controls; + // import "qrc:/resources/Qml/ValueEditor" + // Vec4Box{ + // anchors.verticalCenter: parent.verticalCenter + // width: parent.width + // } + // )"); + // builder->setHeightProxy(valueItem); + // QMatrix4x4 mat = inPropertyHandle->getVar().value(); + // valueItem->setProperty("value", mat.row(0)); + + // QQuickFunctionLibrary::connect(valueItem, SIGNAL(asValueChanged(QVariant)), inPropertyHandle, [inPropertyHandle](QVariant var) { + // QMatrix4x4 mat = inPropertyHandle->getVar().value(); + // mat.setRow(0, var.value()); + // inPropertyHandle->setVar(mat); + // }); + // QQuickFunctionLibrary::connect(inPropertyHandle, SIGNAL(asRequestRollback(QVariant)), valueItem, [inPropertyHandle, valueItem](QVariant var) { + // QMatrix4x4 mat = var.value(); + // valueItem->setProperty("value", mat.row(0)); + // }); + //}); + + inBuilder->addProperty(QPropertyHandle::FindOrCreate( + inPropertyHandle->parent(), + QMetaType::fromType(), + inPropertyHandle->createSubPath("Row 0"), + [inPropertyHandle]() { + return inPropertyHandle->getVar().value().row(0); + }, + [inPropertyHandle](QVariant var) { + QMatrix4x4 mat = inPropertyHandle->getVar().value(); + mat.setRow(0, var.value()); + inPropertyHandle->setVar(mat); + } + )); + + inBuilder->addProperty(QPropertyHandle::FindOrCreate( + inPropertyHandle->parent(), + QMetaType::fromType(), + inPropertyHandle->createSubPath("Row 1"), + [inPropertyHandle]() { + return inPropertyHandle->getVar().value().row(1); + }, + [inPropertyHandle](QVariant var) { + QMatrix4x4 mat = inPropertyHandle->getVar().value(); + mat.setRow(1, var.value()); + inPropertyHandle->setVar(mat); + } + )); + + inBuilder->addProperty(QPropertyHandle::FindOrCreate( + inPropertyHandle->parent(), + QMetaType::fromType(), + inPropertyHandle->createSubPath("Row 2"), + [inPropertyHandle]() { + return inPropertyHandle->getVar().value().row(2); + }, + [inPropertyHandle](QVariant var) { + QMatrix4x4 mat = inPropertyHandle->getVar().value(); + mat.setRow(2, var.value()); + inPropertyHandle->setVar(mat); + } + )); + + inBuilder->addProperty(QPropertyHandle::FindOrCreate( + inPropertyHandle->parent(), + QMetaType::fromType(), + inPropertyHandle->createSubPath("Row 3"), + [inPropertyHandle]() { + return inPropertyHandle->getVar().value().row(3); + }, + [inPropertyHandle](QVariant var) { + QMatrix4x4 mat = inPropertyHandle->getVar().value(); + mat.setRow(3, var.value()); + inPropertyHandle->setVar(mat); + } + )); +} + diff --git a/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Matrix4x4.h b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Matrix4x4.h new file mode 100644 index 0000000..5ecd67b --- /dev/null +++ b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Matrix4x4.h @@ -0,0 +1,11 @@ +#ifndef PropertyTypeCustomization_Matrix4x4_h__ +#define PropertyTypeCustomization_Matrix4x4_h__ + +#include "IPropertyTypeCustomization.h" + +class PropertyTypeCustomization_Matrix4x4 : public IPropertyTypeCustomization { +protected: + virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) override; +}; + +#endif // PropertyTypeCustomization_Matrix4x4_h__ diff --git a/PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.cpp b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.cpp new file mode 100644 index 0000000..57ad671 --- /dev/null +++ b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.cpp @@ -0,0 +1,52 @@ +#include "PropertyTypeCustomization_ObjectDefault.h" +#include "QQuickDetailsViewLayoutBuilder.h" +#include "QPropertyHandle.h" + +void PropertyTypeCustomization_ObjectDefault::customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) +{ + auto objectHandle = inPropertyHandle->asObject(); + const QMetaObject* metaObject = objectHandle->getMetaObject(); + if (objectHandle->isGadget()) { + for (int i = objectHandle->isGadget() ? 0 : 1; i < metaObject->propertyCount(); i++) { + QMetaProperty prop = metaObject->property(i); + QString propName = prop.name(); + inBuilder->addProperty( + inPropertyHandle->findOrCreateChild( + prop.metaType(), + propName, + [this, prop, objectHandle]() { + return prop.readOnGadget(objectHandle->getGadget()); + }, + [this, prop, objectHandle, inPropertyHandle](QVariant var) { + prop.writeOnGadget(objectHandle->getGadget(), var); + inPropertyHandle->setVar(objectHandle->getObjectHolder()); + objectHandle->refreshObjectPtr(); + } + ), + propName + ); + } + } + else { + if (objectHandle->getObject() != nullptr) { + for (int i = objectHandle->isGadget() ? 0 : 1; i < metaObject->propertyCount(); i++) { + QMetaProperty prop = metaObject->property(i); + QString propName = prop.name(); + inBuilder->addProperty( + inPropertyHandle->findOrCreateChild( + prop.metaType(), + propName, + [this, prop, objectHandle]() { + return prop.read(objectHandle->getObject()); + }, + [this, prop, objectHandle, inPropertyHandle](QVariant var) { + prop.write(objectHandle->getObject(), var); + } + ), + propName + ); + } + } + } +} + diff --git a/PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.h b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.h new file mode 100644 index 0000000..11a8488 --- /dev/null +++ b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.h @@ -0,0 +1,11 @@ +#ifndef PropertyTypeCustomization_ObjectDefault_h__ +#define PropertyTypeCustomization_ObjectDefault_h__ + +#include "IPropertyTypeCustomization.h" + +class PropertyTypeCustomization_ObjectDefault : public IPropertyTypeCustomization { +protected: + virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) override; +}; + +#endif // PropertyTypeCustomization_ObjectDefault_h__ diff --git a/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Sequential.cpp b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Sequential.cpp new file mode 100644 index 0000000..b24bc93 --- /dev/null +++ b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Sequential.cpp @@ -0,0 +1,40 @@ +#include "PropertyTypeCustomization_Sequential.h" +#include "QQuickDetailsViewLayoutBuilder.h" +#include "QPropertyHandle.h" +#include "qsequentialiterable.h" +#include "qassociativeiterable.h" + +void PropertyTypeCustomization_Sequential::customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) +{ + auto sequentialHandle = inPropertyHandle->asSequential(); + QVariant varList = inPropertyHandle->getVar(); + QSequentialIterable iterable = varList.value(); + for (int index = 0; index < iterable.size(); index++) { + QString name = QString::number(index); + inBuilder->addProperty(inPropertyHandle->findOrCreateChild( + sequentialHandle->metaSequence().valueMetaType(), + name, + [inPropertyHandle, index]() { + QVariant varList = inPropertyHandle->getVar(); + QSequentialIterable iterable = varList.value(); + if (index < 0 || index >= iterable.size()) { + return QVariant(); + } + return iterable.at(index); + }, + [inPropertyHandle, index](QVariant var) { + QVariant varList = inPropertyHandle->getVar(); + QSequentialIterable iterable = varList.value(); + if (index >= 0 && index < iterable.size()) { + const QMetaSequence metaSequence = iterable.metaContainer(); + void* containterPtr = const_cast(iterable.constIterable()); + QtPrivate::QVariantTypeCoercer coercer; + const void* dataPtr = coercer.coerce(var, iterable.valueMetaType()); + metaSequence.setValueAtIndex(containterPtr, index, dataPtr); + inPropertyHandle->setVar(varList); + } + } + )); + } +} + diff --git a/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Sequential.h b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Sequential.h new file mode 100644 index 0000000..b07e41a --- /dev/null +++ b/PropertyEditor/source/src/Customization/PropertyTypeCustomization_Sequential.h @@ -0,0 +1,11 @@ +#ifndef PropertyTypeCustomization_Sequential_h__ +#define PropertyTypeCustomization_Sequential_h__ + +#include "IPropertyTypeCustomization.h" + +class PropertyTypeCustomization_Sequential : public IPropertyTypeCustomization { +protected: + virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) override; +}; + +#endif // PropertyTypeCustomization_Sequential_h__ diff --git a/PropertyEditor/source/src/IPropertyTypeCustomization.cpp b/PropertyEditor/source/src/IPropertyTypeCustomization.cpp new file mode 100644 index 0000000..43669c3 --- /dev/null +++ b/PropertyEditor/source/src/IPropertyTypeCustomization.cpp @@ -0,0 +1,13 @@ +#include "IPropertyTypeCustomization.h" +#include "QQuickDetailsViewLayoutBuilder.h" + +void IPropertyTypeCustomization::customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder) +{ + inBuilder->makePropertyRow(inPropertyHandle); +} + +void IPropertyTypeCustomization::customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) +{ + +} + diff --git a/PropertyEditor/source/src/PropertyHandleImpl/IPropertyHandleImpl.cpp b/PropertyEditor/source/src/PropertyHandleImpl/IPropertyHandleImpl.cpp new file mode 100644 index 0000000..c95c6ff --- /dev/null +++ b/PropertyEditor/source/src/PropertyHandleImpl/IPropertyHandleImpl.cpp @@ -0,0 +1,38 @@ +#include "PropertyHandleImpl/IPropertyHandleImpl.h" +#include "QPropertyHandle.h" + +IPropertyHandleImpl::IPropertyHandleImpl(QPropertyHandle* inHandle): + mHandle(inHandle) +{ +} + +QQuickItem* IPropertyHandleImpl::createNameEditor(QQuickItem* inParent) +{ + QQmlEngine* engine = qmlEngine(inParent); + QQmlContext* context = qmlContext(inParent); + QQmlComponent nameComp(engine); + nameComp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import ColorPalette; + Item{ + implicitHeight: 25 + width: parent.width + anchors.verticalCenter: parent.verticalCenter + Text { + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + clip: true + elide: Text.ElideRight + text: model.name + color: ColorPalette.theme.labelPrimary + } + } + )", QUrl()); + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(inParent); + auto nameEditor = qobject_cast(nameComp.createWithInitialProperties(initialProperties, context)); + nameEditor->setParentItem(inParent); + return nameEditor; +} + diff --git a/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Associative.cpp b/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Associative.cpp new file mode 100644 index 0000000..44e46c3 --- /dev/null +++ b/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Associative.cpp @@ -0,0 +1,72 @@ +#include "PropertyHandleImpl/QPropertyHandleImpl_Associative.h" +#include +#include "QBoxLayout" +#include "QPropertyHandle.h" + +QPropertyHandleImpl_Associative::QPropertyHandleImpl_Associative(QPropertyHandle* inHandle) + :IPropertyHandleImpl(inHandle) { + QVariant varMap = mHandle->getVar(); + QAssociativeIterable iterable = varMap.value(); + mMetaAssociation = iterable.metaContainer(); +} + +const QMetaAssociation& QPropertyHandleImpl_Associative::metaAssociation() const +{ + return mMetaAssociation; +} + +QQuickItem* QPropertyHandleImpl_Associative::createValueEditor(QQuickItem* inParent) +{ + return nullptr; +} + +bool QPropertyHandleImpl_Associative::renameItem(QString inSrc, QString inDst) { + bool canRename = false; + QVariant varMap = mHandle->getVar(); + QAssociativeIterable iterable = varMap.value(); + if (iterable.containsKey(inSrc) && !iterable.containsKey(inDst)) { + canRename = true; + QVariant var = iterable.value(inSrc); + QtPrivate::QVariantTypeCoercer keyCoercer; + QtPrivate::QVariantTypeCoercer mappedCoercer; + void* containterPtr = const_cast(iterable.constIterable()); + QMetaAssociation metaAssociation = iterable.metaContainer(); + metaAssociation.removeKey(containterPtr, keyCoercer.coerce(inSrc, QMetaType::fromType())); + metaAssociation.setMappedAtKey( + containterPtr, + keyCoercer.coerce(inDst, QMetaType::fromType()), + mappedCoercer.coerce(var, var.metaType()) + ); + //mHandle->setVar(varMap, QString("Rename: %1 -> %2").arg(inSrc).arg(inDst)); + //Q_EMIT mHandle->asRequestRebuildRow(); + } + return canRename; +} + +void QPropertyHandleImpl_Associative::appendItem(QString inKey, QVariant inValue) { + QVariant varList = mHandle->getVar(); + QAssociativeIterable iterable = varList.value(); + void* containterPtr = const_cast(iterable.constIterable()); + QtPrivate::QVariantTypeCoercer coercer; + QVariant key(inKey); + const void* keyDataPtr = coercer.coerce(key, key.metaType()); + const void* valueDataPtr = coercer.coerce(inValue, inValue.metaType()); + //metaAssociation.insertKey(containterPtr, keyDataPtr); + mMetaAssociation.setMappedAtKey(containterPtr, keyDataPtr, valueDataPtr); + //mHandle->setVar(varList, QString("%1 Insert: %2").arg(mHandle->getPath()).arg(inKey)); + //Q_EMIT mHandle->asRequestRebuildRow(); +} + +void QPropertyHandleImpl_Associative::removeItem(QString inKey) { + QVariant varList = mHandle->getVar(); + QAssociativeIterable iterable = varList.value(); + const QMetaAssociation metaAssociation = iterable.metaContainer(); + void* containterPtr = const_cast(iterable.constIterable()); + QtPrivate::QVariantTypeCoercer coercer; + QVariant key(inKey); + const void* keyDataPtr = coercer.coerce(key, key.metaType()); + metaAssociation.removeKey(containterPtr, keyDataPtr); + //mHandle->setVar(varList, QString("%1 Remove: %2").arg(mHandle->getPath()).arg(inKey)); + //Q_EMIT mHandle->asRequestRebuildRow(); +} + diff --git a/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Enum.cpp b/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Enum.cpp new file mode 100644 index 0000000..a927f96 --- /dev/null +++ b/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Enum.cpp @@ -0,0 +1,46 @@ +#include "PropertyHandleImpl/QPropertyHandleImpl_Enum.h" +#include +#include +#include +#include +#include "QPropertyHandle.h" + + +QPropertyHandleImpl_Enum::QPropertyHandleImpl_Enum(QPropertyHandle* inHandle) + :IPropertyHandleImpl(inHandle) { + const QMetaObject* metaObj = mHandle->getType().metaObject(); + if (metaObj){ + const QMetaEnum& metaEnum = metaObj->enumerator(metaObj->indexOfEnumerator(QString(mHandle->getType().name()).split("::").last().toLocal8Bit())); + for (int i = 0; i < metaEnum.keyCount(); i++) { + mNameToValueMap[metaEnum.key(i)] = metaEnum.value(i); + mKeys << metaEnum.key(i); + } + } +} + +QQuickItem* QPropertyHandleImpl_Enum::createValueEditor(QQuickItem* inParent) +{ + QQmlEngine* engine = qmlEngine(inParent); + QQmlContext* context = qmlContext(inParent); + QQmlComponent comp(engine); + comp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import "qrc:/resources/Qml/ValueEditor" + TextComboBox{ + width: parent.width + } + )", QUrl()); + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(inParent); + auto valueEditor = qobject_cast(comp.createWithInitialProperties(initialProperties, context)); + if (!comp.errors().isEmpty()) { + qDebug() << comp.errorString(); + } + valueEditor->setParentItem(inParent); + valueEditor->setProperty("value", mHandle->getVar()); + valueEditor->setProperty("model", mKeys); + QObject::connect(valueEditor, SIGNAL(asValueChanged(QVariant)), mHandle, SLOT(setVar(QVariant))); + QObject::connect(mHandle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant))); + return valueEditor; +} \ No newline at end of file diff --git a/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Object.cpp b/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Object.cpp new file mode 100644 index 0000000..34b8454 --- /dev/null +++ b/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Object.cpp @@ -0,0 +1,98 @@ +#include "PropertyHandleImpl/QPropertyHandleImpl_Object.h" +#include +#include +#include "QPropertyHandle.h" +#include + +QPropertyHandleImpl_Object::QPropertyHandleImpl_Object(QPropertyHandle* inHandle) + :IPropertyHandleImpl(inHandle) { + mObjectHolder = mHandle->getVar(); + QMetaType metaType = mHandle->getType(); + QRegularExpression reg("QSharedPointer\\<(.+)\\>"); + QRegularExpressionMatch match = reg.match(metaType.name()); + QStringList matchTexts = match.capturedTexts(); + if (!matchTexts.isEmpty()) { + QMetaType innerMetaType = QMetaType::fromName((matchTexts.back()).toLocal8Bit()); + mMetaObject = innerMetaType.metaObject(); + const void* ptr = *(const void**)mObjectHolder.data(); + bIsSharedPointer = true; + bIsPointer = true; + if (ptr) { + mObjectHolder = QVariant(innerMetaType, mObjectHolder.data()); + } + else { + mObjectHolder = QVariant(); + } + } + else{ + bIsPointer = metaType.flags().testFlag(QMetaType::IsPointer); + mMetaObject = metaType.metaObject(); + } + mOwnerObject = mHandle->parent(); + refreshObjectPtr(); +} + +QObject* QPropertyHandleImpl_Object::getObject() +{ + if(mMetaObject->inherits(&QObject::staticMetaObject)) + return (QObject*)mObjectPtr; + return nullptr; +} + +void* QPropertyHandleImpl_Object::getGadget() +{ + return mObjectPtr; +} + +bool QPropertyHandleImpl_Object::isGadget() const +{ + if (mMetaObject->inherits(&QObject::staticMetaObject)) + return false; + return mObjectPtr != nullptr; +} + +QObject* QPropertyHandleImpl_Object::getOwnerObject() +{ + return mOwnerObject; +} + +const QMetaObject* QPropertyHandleImpl_Object::getMetaObject() const +{ + return mMetaObject; +} + +void QPropertyHandleImpl_Object::refreshObjectPtr() { + mObjectHolder = mHandle->getVar(); + if (mObjectHolder.isValid()) { + if (mMetaObject->inherits(&QObject::staticMetaObject)) { + QObject* objectPtr = mObjectHolder.value(); + if (objectPtr) { + mMetaObject = objectPtr->metaObject(); + } + mObjectPtr = objectPtr; + mOwnerObject = objectPtr; + if (mOwnerObject) { + //QMetaObject::invokeMethod(mOwnerObject, std::bind(&QObject::moveToThread, mOwnerObject, mHandle->thread())); + //QMetaObject::invokeMethod(mOwnerObject, std::bind(&QObject::installEventFilter, mOwnerObject, mHandle)); + //mOwnerObject->installEventFilter(mHandle); + } + } + else { + void* ptr = mObjectHolder.data(); + if (bIsPointer) + ptr = *(void**)mObjectHolder.data(); + mObjectPtr = ptr; + } + } +} + +QVariant& QPropertyHandleImpl_Object::getObjectHolder() +{ + return mObjectHolder; +} + +QQuickItem* QPropertyHandleImpl_Object::createValueEditor(QQuickItem* inParent) +{ + return nullptr; +} + diff --git a/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_RawType.cpp b/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_RawType.cpp new file mode 100644 index 0000000..4182b5b --- /dev/null +++ b/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_RawType.cpp @@ -0,0 +1,14 @@ +#include "PropertyHandleImpl/IPropertyHandleImpl.h" +#include "PropertyHandleImpl/QPropertyHandleImpl_RawType.h" +#include "QQuickDetailsViewMananger.h" + +QPropertyHandleImpl_RawType::QPropertyHandleImpl_RawType(QPropertyHandle* inHandle) + : IPropertyHandleImpl(inHandle) +{ + +} + +QQuickItem* QPropertyHandleImpl_RawType::createValueEditor(QQuickItem* inParent) +{ + return QQuickDetailsViewManager::Get()->createValueEditor(mHandle, inParent); +} diff --git a/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Sequential.cpp b/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Sequential.cpp new file mode 100644 index 0000000..4195d8e --- /dev/null +++ b/PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Sequential.cpp @@ -0,0 +1,69 @@ +#include "PropertyHandleImpl/QPropertyHandleImpl_Sequential.h" +#include +#include "QBoxLayout" +#include "QPropertyHandle.h" + + +QPropertyHandleImpl_Sequential::QPropertyHandleImpl_Sequential(QPropertyHandle* inHandle) + :IPropertyHandleImpl(inHandle) { + QVariant varList = mHandle->getVar(); + QSequentialIterable iterable = varList.value(); + mMetaSequence = iterable.metaContainer(); +} + +const QMetaSequence& QPropertyHandleImpl_Sequential::metaSequence() const +{ + return mMetaSequence; +} + +QQuickItem* QPropertyHandleImpl_Sequential::createValueEditor(QQuickItem* inParent) +{ + return nullptr; +} + +int QPropertyHandleImpl_Sequential::itemCount() { + QVariant varList = mHandle->getVar(); + QSequentialIterable iterable = varList.value(); + return iterable.size(); +} + +void QPropertyHandleImpl_Sequential::appendItem( QVariant InVar) { + QVariant varList = mHandle->getVar(); + QSequentialIterable iterable = varList.value(); + const QMetaSequence metaSequence = iterable.metaContainer(); + void* containterPtr = const_cast(iterable.constIterable()); + QtPrivate::QVariantTypeCoercer coercer; + const void* dataPtr = coercer.coerce(InVar, InVar.metaType()); + metaSequence.addValue(containterPtr, dataPtr); + //mHandle->setVar(varList, QString("%1 Append: %2").arg(mHandle->getPath()).arg(metaSequence.size(containterPtr) - 1)); + //Q_EMIT mHandle->asRequestRebuildRow(); +} + +void QPropertyHandleImpl_Sequential::moveItem(int InSrcIndex, int InDstIndex) { + QVariant varList = mHandle->getVar(); + QSequentialIterable iterable = varList.value(); + const QMetaSequence metaSequence = iterable.metaContainer(); + void* containterPtr = const_cast(iterable.constIterable()); + QtPrivate::QVariantTypeCoercer coercer; + QVariant srcVar = iterable.at(InSrcIndex); + QVariant dstVar = iterable.at(InDstIndex); + metaSequence.setValueAtIndex(containterPtr, InDstIndex, coercer.coerce(srcVar, srcVar.metaType())); + metaSequence.setValueAtIndex(containterPtr, InSrcIndex, coercer.coerce(dstVar, dstVar.metaType())); + //mHandle->setVar(varList, QString("%1 Move: %2->%3").arg(mHandle->getPath()).arg(InSrcIndex).arg(InDstIndex)); + //Q_EMIT mHandle->asRequestRebuildRow(); +} + +void QPropertyHandleImpl_Sequential::removeItem(int InIndex) { + QVariant varList = mHandle->getVar(); + QSequentialIterable iterable = varList.value(); + const QMetaSequence metaSequence = iterable.metaContainer(); + void* containterPtr = const_cast(iterable.constIterable()); + QtPrivate::QVariantTypeCoercer coercer; + for (int i = InIndex; i < iterable.size() - 1; i++) { + QVariant nextVar = iterable.at(i + 1); + metaSequence.setValueAtIndex(containterPtr, InIndex, coercer.coerce(nextVar, nextVar.metaType())); + } + metaSequence.removeValueAtEnd(containterPtr); + //mHandle->setVar(varList, QString("%1 Remove: %2").arg(mHandle->getPath()).arg(InIndex)); + //Q_EMIT mHandle->asRequestRebuildRow(); +} diff --git a/PropertyEditor/source/src/QDetailsView.cpp b/PropertyEditor/source/src/QDetailsView.cpp new file mode 100644 index 0000000..ea68b04 --- /dev/null +++ b/PropertyEditor/source/src/QDetailsView.cpp @@ -0,0 +1,86 @@ +#include "QDetailsView.h" +#include +#include +#include +#include +#include +#include "QQuickDetailsViewMananger.h" + +QDetailsView::QDetailsView(QWidget* parent) + : QWidget(parent) + , mQuickWidget(nullptr) + , mQuickDetailsView(nullptr) +{ + setMinimumSize(200, 200); + + if (!QQuickDetailsViewManager::Get()->isInitialized()) { + QQuickDetailsViewManager::Get()->initialize(); + } + + mQuickWidget = new QQuickWidget(this); + mQuickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); + + const QString qmlContent = R"( + import QtQuick + import QtQuick.Controls + import QtQuick.DetailsView + + + DetailsView { + id: detailsView + anchors.fill: parent + boundsBehavior: Flickable.OvershootBounds + ScrollBar.vertical: ScrollBar { + parent: detailsView.parent + width : 10 + anchors.top: detailsView.top + anchors.right: detailsView.right + anchors.bottom: detailsView.bottom + } + } + )"; + + QQmlComponent component(mQuickWidget->engine()); + component.setData(qmlContent.toUtf8(), QUrl("")); + QObject* rootObject = component.create(); + if (!component.errors().isEmpty()) { + qDebug() << component.errorString(); + } + if (rootObject) { + mQuickWidget->setSource(QUrl()); + mQuickWidget->engine()->setObjectOwnership(rootObject, QQmlEngine::CppOwnership); + + QQuickItem* rootItem = qobject_cast(rootObject); + if (rootItem) { + mQuickWidget->setContent(QUrl(), &component, rootItem); + mQuickDetailsView = qobject_cast(rootItem); + } + } + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(mQuickWidget); + setLayout(layout); +} + +QQuickDetailsView* QDetailsView::getQuickDetailsView() const +{ + return mQuickDetailsView; +} + +void QDetailsView::setObject(QObject* inObject) +{ + QQuickItem* rootObject = mQuickWidget->rootObject(); + if (rootObject) { + rootObject->setProperty("Object", QVariant::fromValue(inObject)); + } +} + +QObject* QDetailsView::getObject() const +{ + QQuickItem* rootObject = mQuickWidget->rootObject(); + if (rootObject) { + return rootObject->property("Object").value(); + } + return nullptr; +} diff --git a/PropertyEditor/source/src/QPropertyHandle.cpp b/PropertyEditor/source/src/QPropertyHandle.cpp new file mode 100644 index 0000000..ac8e165 --- /dev/null +++ b/PropertyEditor/source/src/QPropertyHandle.cpp @@ -0,0 +1,322 @@ +#include "QPropertyHandle.h" +#include +#include "PropertyHandleImpl/QPropertyHandleImpl_Sequential.h" +#include "PropertyHandleImpl/QPropertyHandleImpl_Associative.h" +#include "PropertyHandleImpl/QPropertyHandleImpl_Enum.h" +#include "PropertyHandleImpl/QPropertyHandleImpl_Object.h" +#include "PropertyHandleImpl/QPropertyHandleImpl_RawType.h" + +QPropertyHandle::QPropertyHandle(QObject* inParent, QMetaType inType, QString inPropertyPath, Getter inGetter, Setter inSetter) + : mType(inType) + , mPropertyPath(inPropertyPath) + , mGetter(inGetter) + , mSetter(inSetter) +{ + setParent(inParent); + setObjectName(inPropertyPath.split(".").back()); + resloveMetaData(); + mInitialValue = inGetter(); + mPropertyType = parserType(inType); + switch (mPropertyType) + { + default: + break; + case PropertyType::RawType: + mImpl.reset(new QPropertyHandleImpl_RawType(this)); + break; + case PropertyType::Enum: + mImpl.reset(new QPropertyHandleImpl_Enum(this)); + break; + case PropertyType::Sequential: + mImpl.reset(new QPropertyHandleImpl_Sequential(this)); + break; + case PropertyType::Associative: + mImpl.reset(new QPropertyHandleImpl_Associative(this)); + break; + case PropertyType::Object: + mImpl.reset(new QPropertyHandleImpl_Object(this)); + break; + } +} + +void QPropertyHandle::resloveMetaData() +{ + auto metaObj = parent()->metaObject(); + auto firstField = getPropertyPath().split(".").first(); + for (int i = 0; i < metaObj->classInfoCount(); i++) { + auto metaClassInfo = metaObj->classInfo(i); + if (metaClassInfo.name() == firstField) { + QStringList fields = QString(metaClassInfo.value()).split(",", Qt::SplitBehaviorFlags::SkipEmptyParts); + for (auto field : fields) { + QStringList pair = field.split("="); + QString key, value; + if (pair.size() > 0) { + key = pair.first().trimmed(); + } + if (pair.size() > 1) { + value = pair[1].trimmed(); + } + mMetaData[key] = value; + } + return; + } + } +} + +QPropertyHandle* QPropertyHandle::Find(const QObject* inParent, const QString& inPropertyPath) +{ + for (QObject* child : inParent->children()) { + QPropertyHandle* handler = qobject_cast(child); + if (handler && handler->getPropertyPath() == inPropertyPath) { + return handler; + } + } + return nullptr; +} + +QPropertyHandle* QPropertyHandle::FindOrCreate(QObject* inParent, QMetaType inType, QString inPropertyPath, Getter inGetter, Setter inSetter) +{ + QPropertyHandle* handle = Find(inParent, inPropertyPath); + if (handle) + return handle; + return new QPropertyHandle( + inParent, + inType, + inPropertyPath, + inGetter, + inSetter + ); +} + +QPropertyHandle* QPropertyHandle::FindOrCreate(QObject* inObject) +{ + QPropertyHandle* handle = Find(inObject, ""); + if (handle) + return handle; + return new QPropertyHandle( + inObject, + inObject->metaObject()->metaType(), + "", + [inObject]() {return QVariant::fromValue(inObject); }, + [inObject](QVariant var) {} + ); +} + +QPropertyHandle* QPropertyHandle::Create(QObject* inParent, QMetaType inType, QString inPropertyPath, Getter inGetter, Setter inSetter) +{ + return new QPropertyHandle(inParent, inType, inPropertyPath, inGetter, inSetter); +} + +void QPropertyHandle::Cleanup(QObject* inParent) +{ + for (QObject* child : inParent->children()) { + QPropertyHandle* handler = qobject_cast(child); + if (handler) { + handler->deleteLater(); + } + } +} + +QMetaType QPropertyHandle::getType() +{ + return mType; +} + +QPropertyHandle::PropertyType QPropertyHandle::getPropertyType() const +{ + return mPropertyType; +} + +QString QPropertyHandle::getName() +{ + return objectName(); +} + +QString QPropertyHandle::getPropertyPath() +{ + return mPropertyPath; +} + +QString QPropertyHandle::createSubPath(const QString& inSubName) +{ + return getPropertyPath() + "." + inSubName; +} + +void QPropertyHandle::invalidateStructure() +{ + Q_EMIT asStructureChanged(); +} + +QVariant QPropertyHandle::getVar() +{ + return mGetter(); +} + +void QPropertyHandle::setVar(QVariant var) +{ + QVariant lastVar = mGetter(); + if (lastVar != var) { + mSetter(var); + Q_EMIT asVarChanged(var); + QVariant currVar = mGetter(); + if (currVar != var) { + Q_EMIT asRequestRollback(currVar); + } + } +} + +bool QPropertyHandle::hasMetaData(const QString& inName) const +{ + return mMetaData.contains(inName); +} + +QVariant QPropertyHandle::getMetaData(const QString& inName) const +{ + return mMetaData.value(inName); +} + +const QVariantHash& QPropertyHandle::getMetaDataMap() const +{ + return mMetaData; +} + +QPropertyHandle* QPropertyHandle::findChild(QString inPropertyName) +{ + return Find(this->parent(), this->createSubPath(inPropertyName)); +} + +QPropertyHandle* QPropertyHandle::findOrCreateChild(QMetaType inType, QString inPropertyName, Getter inGetter, Setter inSetter) +{ + QVariant var = inGetter(); + QObject* object = var.value(); + QPropertyHandle* childHandle = nullptr; + if (object) { + childHandle = QPropertyHandle::FindOrCreate( + object + ); + childHandle->setObjectName(inPropertyName); + } + else { + childHandle = QPropertyHandle::FindOrCreate( + this->parent(), + inType, + this->createSubPath(inPropertyName), + inGetter, + inSetter + ); + } + + childHandle->disconnect(this); + connect(this, &QPropertyHandle::asVarChanged, childHandle, [childHandle](QVariant) { + QVariant var = childHandle->getVar(); + Q_EMIT childHandle->asRequestRollback(var); + }); + connect(this, &QPropertyHandle::asRequestRollback, childHandle, [childHandle](QVariant) { + QVariant var = childHandle->getVar(); + Q_EMIT childHandle->asRequestRollback(var); + }); + return childHandle; +} + +QQuickItem* QPropertyHandle::setupNameEditor(QQuickItem* inParent) +{ + return mImpl->createNameEditor(inParent); +} + +QQuickItem* QPropertyHandle::steupValueEditor(QQuickItem* inParent) +{ + return mImpl->createValueEditor(inParent); +} + +IPropertyHandleImpl::Type QPropertyHandle::type() +{ + return mImpl->type(); +} + +QPropertyHandleImpl_Enum* QPropertyHandle::asEnum() +{ + return static_cast(mImpl.get()); +} + +QPropertyHandleImpl_Object* QPropertyHandle::asObject() +{ + return static_cast(mImpl.get()); +} + +QPropertyHandleImpl_Associative* QPropertyHandle::asAssociative() +{ + return static_cast(mImpl.get()); +} + +QPropertyHandleImpl_Sequential* QPropertyHandle::asSequential() +{ + return static_cast(mImpl.get()); +} + +QPropertyHandle::PropertyType QPropertyHandle::parserType(QMetaType inType) +{ + if (QMetaType::canConvert(inType, QMetaType::fromType()) + && !QMetaType::canConvert(inType, QMetaType::fromType()) + ) { + return Sequential; + } + else if (QMetaType::canConvert(inType, QMetaType::fromType())) { + return Associative; + } + else if (inType.flags() & QMetaType::IsEnumeration) { + return Enum; + } + else { + QRegularExpression reg("QSharedPointer\\<(.+)\\>"); + QRegularExpressionMatch match = reg.match(inType.name(), 0, QRegularExpression::MatchType::PartialPreferCompleteMatch, QRegularExpression::AnchorAtOffsetMatchOption); + QStringList matchTexts = match.capturedTexts(); + QMetaType innerMetaType; + if (!matchTexts.isEmpty()) { + QString metaTypeName = matchTexts.back(); + innerMetaType = QMetaType::fromName(metaTypeName.toLocal8Bit()); + } + if (innerMetaType.metaObject() || inType.metaObject()) { + return Object; + } + else { + return RawType; + } + } +} + +QVariant QPropertyHandle::createNewVariant(QMetaType inOutputType) +{ + QRegularExpression reg("QSharedPointer\\<(.+)\\>"); + QRegularExpressionMatch match = reg.match(inOutputType.name()); + QStringList matchTexts = match.capturedTexts(); + if (!matchTexts.isEmpty()) { + QMetaType innerMetaType = QMetaType::fromName((matchTexts.back()).toLocal8Bit()); + if (innerMetaType.isValid()) { + void* ptr = innerMetaType.create(); + QVariant sharedPtr(inOutputType); + memcpy(sharedPtr.data(), &ptr, sizeof(ptr)); + QtSharedPointer::ExternalRefCountData* data = ExternalRefCountWithMetaType::create(innerMetaType, ptr); + memcpy((char*)sharedPtr.data() + sizeof(ptr), &data, sizeof(data)); + return sharedPtr; + } + } + else if (inOutputType.flags().testFlag(QMetaType::IsPointer)) { + const QMetaObject* metaObject = inOutputType.metaObject(); + if (metaObject && metaObject->inherits(&QObject::staticMetaObject)) { + QObject* obj = metaObject->newInstance(); + if (obj) + return QVariant::fromValue(obj); + } + QMetaType innerMetaType = QMetaType::fromName(QString(inOutputType.name()).remove("*").toLocal8Bit()); + if (innerMetaType.isValid()) { + void* ptr = innerMetaType.create(); + QVariant var(inOutputType, ptr); + memcpy(var.data(), &ptr, sizeof(ptr)); + return var; + } + else { + return QVariant(); + } + } + return QVariant(inOutputType); +} diff --git a/PropertyEditor/source/src/QQuickDetailsView.cpp b/PropertyEditor/source/src/QQuickDetailsView.cpp new file mode 100644 index 0000000..2e3e517 --- /dev/null +++ b/PropertyEditor/source/src/QQuickDetailsView.cpp @@ -0,0 +1,86 @@ +#include "QQuickDetailsView.h" +#include "private/qqmldata_p.h" +#include + +#include "qqmlcontext.h" +#include "QQuickDetailsViewPrivate.h" +#include "QQuickDetailsViewRow.h" +#include "QQuickFunctionLibrary.h" + +void QQuickDetailsViewPrivate::initItemCallback(int serializedModelIndex, QObject* object) +{ + QQuickTreeViewExPrivate::initItemCallback(serializedModelIndex, object); + auto item = qobject_cast(object); + if (!item) + return; + const QModelIndex& index = m_treeModelToTableModel.mapToModel(serializedModelIndex);; + IDetailsViewRow* node = static_cast(index.internalPointer()); + node->setupItem(item); +} + +QQuickDetailsView::QQuickDetailsView(QQuickItem* parent /*= nullptr*/) + : QQuickTreeViewEx(*(new QQuickDetailsViewPrivate()),parent) +{ + setModel(QVariant::fromValue(d_func()->mModel)); + setReuseItems(false); + setEditTriggers(QQuickTableView::EditTrigger::DoubleTapped); +} + +qreal QQuickDetailsView::getSpliterPencent() const +{ + return d_func()->mSpliterPencent; +} + +void QQuickDetailsView::setSpliterPencent(qreal val) +{ + if(val != d_func()->mSpliterPencent){ + d_func()->mSpliterPencent = val; + Q_EMIT asSpliterPencentChanged(val); + } +} + +void QQuickDetailsView::setObject(QObject* inObject) +{ + if (inObject != d_func()->mModel->getObject()) { + d_func()->mModel->setObject(inObject); + Q_EMIT asObjectChanged(inObject); + } +} + +QObject* QQuickDetailsView::getObject() const +{ + return d_func()->mModel->getObject(); +} + +void QQuickDetailsView::componentComplete() +{ + QQmlEngine* engine = qmlEngine(this); + QQmlComponent* delegate = new QQmlComponent(engine, this); + engine->rootContext()->setContextProperty("helper", QQuickFunctionLibrary::Get()); + delegate->setData(R"( + import QtQuick; + import QtQuick.Controls; + import QtQuick.Layouts; + import QtQuick.DetailsView; + Item { + id: detailsDelegate + readonly property real indent: 20 + readonly property real padding: 5 + required property DetailsView detailsView + required property int row + required property bool isTreeNode + required property bool expanded + required property int hasChildren + required property int depth + implicitWidth: detailsView.width + implicitHeight: heightProxy ? heightProxy.height + 5 : 30 + TapHandler { + onTapped: detailsView.toggleExpanded(row) + } + onImplicitHeightChanged: { + detailsView.invalidateLayout(); + } + })", QUrl("QQuickDetailsView.componentComplete"));; + setDelegate(delegate); + QQuickTreeViewEx::componentComplete(); +} diff --git a/PropertyEditor/source/src/QQuickDetailsViewBasicTypeEditor.cpp b/PropertyEditor/source/src/QQuickDetailsViewBasicTypeEditor.cpp new file mode 100644 index 0000000..4933e8a --- /dev/null +++ b/PropertyEditor/source/src/QQuickDetailsViewBasicTypeEditor.cpp @@ -0,0 +1,238 @@ +#include "QQuickDetailsViewMananger.h" +#include "QPropertyHandle.h" +#include "QQuickFunctionLibrary.h" +#include +#include +#include + +#define REGINTER_NUMBER_EDITOR_CREATOR(TypeName, DefaultPrecision) \ + registerTypeEditor(QMetaType::fromType(), [](QPropertyHandle* handle, QQuickItem* parent)->QQuickItem* { \ + QQmlEngine* engine = qmlEngine(parent); \ + QQmlContext* context = qmlContext(parent); \ + QQmlComponent nameComp(engine); \ + const char* qmlData = \ + "import QtQuick;\n" \ + "import QtQuick.Controls;\n" \ + "import \"qrc:/resources/Qml/ValueEditor\"\n" \ + "NumberBox{\n" \ + " anchors.verticalCenter: parent.verticalCenter\n" \ + " width: parent.width\n" \ + "}"; \ + nameComp.setData(qmlData, QUrl()); \ + QVariantMap initialProperties; \ + initialProperties["parent"] = QVariant::fromValue(parent); \ + auto valueEditor = qobject_cast(nameComp.createWithInitialProperties(initialProperties, context)); \ + if (!nameComp.errors().isEmpty()) { \ + qDebug() << nameComp.errorString(); \ + } \ + if (valueEditor) { \ + valueEditor->setParentItem(parent); \ + TypeName min = handle->getMetaData("Min").toDouble(); \ + TypeName max = handle->getMetaData("Max").toDouble(); \ + double step = handle->getMetaData("Step").toDouble(); \ + int precision = handle->getMetaData("Precision").toInt(); \ + valueEditor->setProperty("step", step <= 0.0001 ? 1.0 / pow(10.0, DefaultPrecision) : step); \ + valueEditor->setProperty("number", handle->getVar()); \ + valueEditor->setProperty("precision", precision == 0 ? DefaultPrecision : precision); \ + if (min >= max) { \ + min = std::numeric_limits::min(); \ + max = std::numeric_limits::max(); \ + } else { \ + valueEditor->setProperty("isLimited", true); \ + } \ + valueEditor->setProperty("min", QVariant::fromValue(min)); \ + valueEditor->setProperty("max", QVariant::fromValue(max)); \ + qDebug() << min << max; \ + QObject::connect(valueEditor, SIGNAL(valueChanged(QVariant)), handle, SLOT(setVar(QVariant))); \ + QObject::connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setNumber(QVariant))); \ + } \ + return valueEditor; \ + }); + + +void QQuickDetailsViewManager::RegisterBasicTypeEditor() { + REGINTER_NUMBER_EDITOR_CREATOR(int, 0); + REGINTER_NUMBER_EDITOR_CREATOR(unsigned int, 0); + REGINTER_NUMBER_EDITOR_CREATOR(size_t, 0); + REGINTER_NUMBER_EDITOR_CREATOR(float, 2); + REGINTER_NUMBER_EDITOR_CREATOR(double, 3); + + registerTypeEditor(QMetaType::fromType(), [](QPropertyHandle* handle, QQuickItem* parent)->QQuickItem* { + QQmlEngine* engine = qmlEngine(parent); + QQmlContext* context = qmlContext(parent); + QQmlComponent comp(engine); + comp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import "qrc:/resources/Qml/ValueEditor" + MultiLineTextBox{ + anchors.verticalCenter: parent.verticalCenter + width: parent.width + } + )", QUrl()); + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(parent); + auto valueEditor = qobject_cast(comp.createWithInitialProperties(initialProperties, context)); + if (!comp.errors().isEmpty()) { + qDebug() << comp.errorString(); + } + valueEditor->setParentItem(parent); + valueEditor->setProperty("value", handle->getVar()); + connect(valueEditor, SIGNAL(asValueChanged(QVariant)), handle, SLOT(setVar(QVariant))); + + connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant))); + return valueEditor; + }); + + registerTypeEditor(QMetaType::fromType(), [](QPropertyHandle* handle, QQuickItem* parent)->QQuickItem* { + QQmlEngine* engine = qmlEngine(parent); + QQmlContext* context = qmlContext(parent); + QQmlComponent comp(engine); + comp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import "qrc:/resources/Qml/ValueEditor" + Vec4Box{ + anchors.verticalCenter: parent.verticalCenter + width: parent.width + } + )", QUrl()); + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(parent); + auto valueEditor = qobject_cast(comp.createWithInitialProperties(initialProperties, context)); + if (!comp.errors().isEmpty()) { + qDebug() << comp.errorString(); + } + valueEditor->setParentItem(parent); + valueEditor->setProperty("value", handle->getVar()); + connect(valueEditor, SIGNAL(asValueChanged(QVariant)), handle, SLOT(setVar(QVariant))); + connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant))); + return valueEditor; + }); + + registerTypeEditor(QMetaType::fromType(), [](QPropertyHandle* handle, QQuickItem* parent)->QQuickItem* { + QQmlEngine* engine = qmlEngine(parent); + QQmlContext* context = qmlContext(parent); + QQmlComponent comp(engine); + comp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import "qrc:/resources/Qml/ValueEditor" + Vec3Box{ + anchors.verticalCenter: parent.verticalCenter + width: parent.width + } + )", QUrl()); + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(parent); + auto valueEditor = qobject_cast(comp.createWithInitialProperties(initialProperties, context)); + if (!comp.errors().isEmpty()) { + qDebug() << comp.errorString(); + } + valueEditor->setParentItem(parent); + valueEditor->setProperty("value", handle->getVar()); + connect(valueEditor, SIGNAL(asValueChanged(QVariant)), handle, SLOT(setVar(QVariant))); + connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant))); + return valueEditor; + }); + + registerTypeEditor(QMetaType::fromType(), [](QPropertyHandle* handle, QQuickItem* parent)->QQuickItem* { + QQmlEngine* engine = qmlEngine(parent); + QQmlContext* context = qmlContext(parent); + QQmlComponent comp(engine); + comp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import "qrc:/resources/Qml/ValueEditor" + Vec2Box{ + anchors.verticalCenter: parent.verticalCenter + width: parent.width + } + )", QUrl()); + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(parent); + auto valueEditor = qobject_cast(comp.createWithInitialProperties(initialProperties, context)); + if (!comp.errors().isEmpty()) { + qDebug() << comp.errorString(); + } + valueEditor->setParentItem(parent); + valueEditor->setProperty("value", handle->getVar()); + connect(valueEditor, SIGNAL(asValueChanged(QVariant)), handle, SLOT(setVar(QVariant))); + connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant))); + return valueEditor; + }); + + registerTypeEditor(QMetaType::fromType(), [](QPropertyHandle* handle, QQuickItem* parent)->QQuickItem* { + QQmlEngine* engine = qmlEngine(parent); + QQmlContext* context = qmlContext(parent); + QQmlComponent comp(engine); + comp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import "qrc:/resources/Qml/ValueEditor" + ColorBox{ + anchors.verticalCenter: parent.verticalCenter + width: parent.width + } + )", QUrl()); + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(parent); + auto valueEditor = qobject_cast(comp.createWithInitialProperties(initialProperties, context)); + if (!comp.errors().isEmpty()) { + qDebug() << comp.errorString(); + } + valueEditor->setParentItem(parent); + valueEditor->setProperty("value", handle->getVar()); + connect(valueEditor, SIGNAL(asValueChanged(QVariant)), handle, SLOT(setVar(QVariant))); + connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant))); + return valueEditor; + }); + + QMetaType::registerConverterFunction( + [](const void* src, void* target) -> bool { + const QDir& dir = *static_cast(src); + QString& str = *static_cast(target); + str = dir.absolutePath(); + return true; + }, + QMetaType::fromType(), + QMetaType::fromType() + ); + + QMetaType::registerConverterFunction( + [](const void* src, void* target) -> bool { + const QString& str = *static_cast(src); + QDir& dir = *static_cast(target); + dir = QDir(str); + return true; + }, + QMetaType::fromType(), + QMetaType::fromType() + ); + + registerTypeEditor(QMetaType::fromType(), [](QPropertyHandle* handle, QQuickItem* parent)->QQuickItem* { + QQmlEngine* engine = qmlEngine(parent); + QQmlContext* context = qmlContext(parent); + QQmlComponent comp(engine); + comp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import "qrc:/resources/Qml/ValueEditor" + DirectorySelector{ + anchors.verticalCenter: parent.verticalCenter + width: parent.width + } + )", QUrl()); + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(parent); + auto valueEditor = qobject_cast(comp.createWithInitialProperties(initialProperties, context)); + if (!comp.errors().isEmpty()) { + qDebug() << comp.errorString(); + } + valueEditor->setParentItem(parent); + valueEditor->setProperty("value", handle->getVar()); + connect(valueEditor, SIGNAL(asValueChanged(QVariant)), handle, SLOT(setVar(QVariant))); + connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant))); + return valueEditor; + }); +} diff --git a/PropertyEditor/source/src/QQuickDetailsViewLayoutBuilder.cpp b/PropertyEditor/source/src/QQuickDetailsViewLayoutBuilder.cpp new file mode 100644 index 0000000..56a4169 --- /dev/null +++ b/PropertyEditor/source/src/QQuickDetailsViewLayoutBuilder.cpp @@ -0,0 +1,216 @@ +#include "QQuickDetailsViewLayoutBuilder.h" +#include "QQuickDetailsViewMananger.h" + +QQuickDetailsViewRowBuilder::QQuickDetailsViewRowBuilder(IDetailsViewRow* inRow, QQuickItem* inRootItem) + : mRow(inRow) + , mRootItem(inRootItem) +{ + setHeightProxy(nullptr); +} + +QPair QQuickDetailsViewRowBuilder::makeNameValueSlot() +{ + QQmlEngine* engine = qmlEngine(mRootItem); + QQmlContext* context = qmlContext(mRootItem); + QQmlContext* newContext = new QQmlContext(context, mRootItem); + QQmlComponent rootComp(newContext->engine()); + rootComp.setData(R"( + import QtQuick; + import QtQuick.Controls; + import Qt5Compat.GraphicalEffects + import ColorPalette + Rectangle{ + id: topLevelRect + anchors.fill: parent + color: hoverHandler.hovered ? ColorPalette.theme.rowBackgroundHover : ColorPalette.theme.rowBackground + border.color: ColorPalette.theme.rowBorder + border.width: 0.5 + Behavior on color { + ColorAnimation { duration: 100 } + } + HoverHandler { id: hoverHandler } + Image { + id: indicator + visible: detailsDelegate.isTreeNode && detailsDelegate.hasChildren + x: padding + (detailsDelegate.depth * detailsDelegate.indent) + anchors.verticalCenter: parent.verticalCenter + mipmap: true + source: detailsDelegate.expanded + ? "qrc:/resources/Icon/expand.png" + : "qrc:/resources/Icon/unexpand.png" + + width: 12 + height: 12 + ColorOverlay { + anchors.fill: parent + source: parent + color: ColorPalette.theme.rowIndicator + opacity: 1.0 + } + } + Item{ + id: nameEditorContent + height: parent.height + anchors.left: parent.left + anchors.leftMargin: padding + (detailsDelegate.isTreeNode ? (detailsDelegate.depth + 1) * detailsDelegate.indent : 0) + anchors.right: splitter.left + anchors.verticalCenter: parent.verticalCenter + } + Item{ + id: valueEditorContent + implicitHeight: 25 + anchors.left: splitter.left + anchors.leftMargin: 10 + anchors.rightMargin: 10 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + } + Rectangle{ + id: splitter + width: 3 + height: parent.height + color : ColorPalette.theme.rowBackground + x: detailsView.SpliterPencent * detailsView.width - 1 + + Rectangle{ + id: visibleSplitter + width: 1 + height: parent.height + color: ColorPalette.theme.rowSplitter + anchors.horizontalCenter: parent.horizontalCenter + } + + MouseArea { + id: dragArea + hoverEnabled: true + cursorShape: containsMouse ? Qt.SplitHCursor : Qt.ArrowCursor + anchors.fill: parent + drag.target: splitter + drag.axis: Drag.XAxis + drag.minimumX: 10 + 1 + drag.maximumX: detailsView.width - 10 - 1 + onPositionChanged: { + detailsView.SpliterPencent = (splitter.x + 1) / detailsView.width + } + } + } + Rectangle { + id: gradientBox + visible: detailsDelegate.depth > 0 + width: 5 + x: padding + (detailsDelegate.depth * detailsDelegate.indent) - 10 + height: parent.height + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: ColorPalette.theme.rowShadowStart } + GradientStop { position: 1.0; color: ColorPalette.theme.rowShadowEnd } + } + } + } + )", QUrl()); + QQuickItem* slotItem = qobject_cast(rootComp.create(newContext)); + if (!rootComp.errors().isEmpty()) { + qDebug() << rootComp.errorString(); + } + slotItem->setParentItem(mRootItem); + return { slotItem->childItems()[1] ,slotItem->childItems()[2] }; +} + +QQuickItem* QQuickDetailsViewRowBuilder::rootItem() const +{ + return mRootItem; +} + +IDetailsViewRow* QQuickDetailsViewRowBuilder::row() const +{ + return mRow; +} + +QQuickItem* QQuickDetailsViewRowBuilder::setupItem(QQuickItem* inParent, QString inQmlCode) +{ + QQmlEngine* engine = qmlEngine(inParent); + QQmlContext* context = qmlContext(inParent); + QQmlComponent comp(engine); + QQmlComponent nameComp(engine); + comp.setData(inQmlCode.toLocal8Bit(), QUrl()); + QVariantMap initialProperties; + initialProperties["parent"] = QVariant::fromValue(inParent); + auto item = qobject_cast(comp.createWithInitialProperties(initialProperties, context)); + if (!comp.errors().isEmpty()) { + qDebug() << comp.errorString(); + } + item->setParentItem(inParent); + return item; +} + +void QQuickDetailsViewRowBuilder::setupLabel(QQuickItem* inParent, QString inText) +{ + QQuickItem* lableItem = setupItem(inParent, R"( + import QtQuick; + import QtQuick.Controls; + import ColorPalette; + Item{ + property string lableText + implicitHeight: 25 + width: parent.width + anchors.verticalCenter: parent.verticalCenter + Text { + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + clip: true + elide: Text.ElideRight + text: lableText + color: ColorPalette.theme.labelPrimary + } + } + )"); + lableItem->setProperty("lableText", inText); +} + +void QQuickDetailsViewRowBuilder::setHeightProxy(QQuickItem* inProxyItem) +{ + QQmlEngine* engine = qmlEngine(mRootItem); + QQmlContext* context = qmlContext(mRootItem); + context->parentContext()->setContextProperty("heightProxy", inProxyItem); +} + +void QQuickDetailsViewRowBuilder::makePropertyRow(QPropertyHandle* inHandle) +{ + QQmlEngine* engine = qmlEngine(mRootItem); + QQmlContext* context = qmlContext(mRootItem); + QPair slotItem = makeNameValueSlot(); + QQuickItem* nameEditor = inHandle->setupNameEditor(slotItem.first); + QQuickItem* valueEditor = inHandle->steupValueEditor(slotItem.second); + context->parentContext()->setContextProperty("heightProxy", valueEditor ? valueEditor : nameEditor); +} + +QQuickDetailsViewLayoutBuilder::QQuickDetailsViewLayoutBuilder(IDetailsViewRow* inRow) + : mRootRow(inRow) +{ +} + +IDetailsViewRow* QQuickDetailsViewLayoutBuilder::row() const +{ + return mRootRow; +} + +void QQuickDetailsViewLayoutBuilder::addCustomRow(std::function creator, QString inOverrideName) +{ + QSharedPointer child(new QDetailsViewRow_Custom(creator)); + child->setName(inOverrideName); + mRootRow->addChild(child); + child->attachChildren(); +} + +void QQuickDetailsViewLayoutBuilder::addProperty(QPropertyHandle* inPropertyHandle, QString inOverrideName) +{ + QSharedPointer child(new QDetailsViewRow_Property(inPropertyHandle)); + child->setName(inOverrideName.isEmpty() ? inPropertyHandle->getName() : inOverrideName); + mRootRow->addChild(child); + child->attachChildren(); +} + +void QQuickDetailsViewLayoutBuilder::addObject(QObject* inObject) +{ + addProperty(QPropertyHandle::FindOrCreate(inObject)); +} diff --git a/PropertyEditor/source/src/QQuickDetailsViewMananger.cpp b/PropertyEditor/source/src/QQuickDetailsViewMananger.cpp new file mode 100644 index 0000000..91e7779 --- /dev/null +++ b/PropertyEditor/source/src/QQuickDetailsViewMananger.cpp @@ -0,0 +1,138 @@ +#include "QQuickDetailsViewMananger.h" +#include "QPropertyHandle.h" +#include "QQuickFunctionLibrary.h" +#include +#include "QQuickDetailsView.h" +#include "Customization/PropertyTypeCustomization_Sequential.h" +#include "Customization/PropertyTypeCustomization_Associative.h" +#include "Customization/PropertyTypeCustomization_ObjectDefault.h" +#include "Customization/PropertyTypeCustomization_Matrix4x4.h" +#include +#include + +QQuickDetailsViewManager* QQuickDetailsViewManager::Get() +{ + static QQuickDetailsViewManager ins; + return &ins; +} + +void QQuickDetailsViewManager::initialize() +{ + mInitialized = true; + +#ifdef PROPERTY_EDITOR_STATIC_LIBRARY + Q_INIT_RESOURCE(resources); +#endif + + QQuickStyle::setStyle("Basic"); + + qmlRegisterType("QtQuick.DetailsView", 1, 0, "DetailsView"); + + qmlRegisterSingletonType(QUrl("qrc:/resources/Qml/ColorPalette/ColorPalette.qml"), + "ColorPalette", + 1, 0, + "ColorPalette"); + + qmlRegisterSingletonType(QUrl("qrc:/resources/Qml/ColorPalette/ColorPalette_Light.qml"), + "ColorPalette", + 1, 0, + "ColorPalette_Light"); + + qmlRegisterSingletonType(QUrl("qrc:/resources/Qml/ColorPalette/ColorPalette_Dark.qml"), + "ColorPalette", + 1, 0, + "ColorPalette_Dark"); +} + +bool QQuickDetailsViewManager::isInitialized() const +{ + return mInitialized; +} + +void QQuickDetailsViewManager::unregisterPropertyTypeCustomization(const QMetaType& InMetaType) +{ + if(InMetaType.metaObject()) + mMetaTypeCustomizationMap.remove(InMetaType); +} + +void QQuickDetailsViewManager::registerTypeEditor(const QMetaType& inMetaType, TypeEditorCreator Creator) +{ + mTypeEditorCreatorMap.insert(inMetaType, Creator); +} + +void QQuickDetailsViewManager::unregisterTypeEditor(const QMetaType& inMetaType) +{ + mTypeEditorCreatorMap.remove(inMetaType); +} + +QQuickItem* QQuickDetailsViewManager::createValueEditor(QPropertyHandle* inHandle, QQuickItem* parent) +{ + if (mTypeEditorCreatorMap.contains(inHandle->getType())) { + QQuickItem* item = mTypeEditorCreatorMap[inHandle->getType()](inHandle, parent); + if (parent) { + item->setProperty("anchors.verticalCenter", QVariant::fromValue(parent->property("anchors.verticalCenter"))); + //item->update(); + } + return item; + } + return nullptr; +} + +QSharedPointer QQuickDetailsViewManager::getCustomPropertyType(QPropertyHandle* inHandle) +{ + if (inHandle->getPropertyType() == QPropertyHandle::Sequential) { + return QSharedPointer::create(); + } + else if (inHandle->getPropertyType() == QPropertyHandle::Associative) { + return QSharedPointer::create(); + } + else if (inHandle->getPropertyType() == QPropertyHandle::Object) { + const QMetaObject* metaObject = inHandle->metaObject(); + for (const auto& It : mClassCustomizationMap.asKeyValueRange()) { + if (It.first == metaObject) { + return It.second(); + } + } + for (const auto& It : mClassCustomizationMap.asKeyValueRange()) { + if (metaObject->inherits(It.first)) { + return It.second(); + } + } + return QSharedPointer::create(); + } + else if (inHandle->getPropertyType() == QPropertyHandle::RawType) { + const QMetaType& metaType = inHandle->getType(); + for (const auto& It : mMetaTypeCustomizationMap.asKeyValueRange()) { + if (It.first == metaType) { + return It.second(); + } + } + const QMetaObject* Child = nullptr; + QRegularExpression reg("QSharedPointer\\<(.+)\\>"); + QRegularExpressionMatch match = reg.match(metaType.name()); + QStringList matchTexts = match.capturedTexts(); + QMetaType innerMetaType; + if (!matchTexts.isEmpty()) { + innerMetaType = QMetaType::fromName((matchTexts.back()).toLocal8Bit()); + Child = innerMetaType.metaObject(); + } + else { + Child = metaType.metaObject(); + } + for (const auto& It : mMetaTypeCustomizationMap.asKeyValueRange()) { + const QMetaObject* Parent = It.first.metaObject(); + if (Parent && Child && Child->inherits(Parent)) { + return It.second(); + } + } + } + return nullptr; +} + +QQuickDetailsViewManager::QQuickDetailsViewManager() +{ + RegisterBasicTypeEditor(); + + registerPropertyTypeCustomization(); +} + diff --git a/PropertyEditor/source/src/QQuickDetailsViewModel.cpp b/PropertyEditor/source/src/QQuickDetailsViewModel.cpp new file mode 100644 index 0000000..022c399 --- /dev/null +++ b/PropertyEditor/source/src/QQuickDetailsViewModel.cpp @@ -0,0 +1,137 @@ +#include "QQuickDetailsViewModel.h" +#include "QQuickDetailsViewRow.h" +#include "QQuickDetailsViewLayoutBuilder.h" + +QQuickDetailsViewModel::QQuickDetailsViewModel(QObject* parent) + : QAbstractItemModel(parent) + , mRoot(new QDetailsViewRow_Property(nullptr)) +{ + mRoot->mModel = this; +} + +QVariant QQuickDetailsViewModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) + return QVariant(); + IDetailsViewRow* node = static_cast(index.internalPointer()); + return node->name(); +} + +Qt::ItemFlags QQuickDetailsViewModel::flags(const QModelIndex& index) const { + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +QModelIndex QQuickDetailsViewModel::index(int row, int column, const QModelIndex& parent) const { + if (column < 0 || column >= columnCount(parent)) { + return QModelIndex(); + } + + IDetailsViewRow* parentNode = nullptr; + if (!parent.isValid()) { + parentNode = mRoot.get(); + } + else { + parentNode = static_cast(parent.internalPointer()); + if (!parentNode) { + return QModelIndex(); + } + } + + if (row < 0 || row >= parentNode->mChildren.size()) { + return QModelIndex(); + } + + IDetailsViewRow* childNode = parentNode->mChildren[row].get(); + QModelIndex childIndex = createIndex(row, column, childNode); + childNode->setModelIndex(childIndex); + return childIndex; +} + +QModelIndex QQuickDetailsViewModel::parent(const QModelIndex& index) const { + if (!index.isValid()) + return QModelIndex(); + + IDetailsViewRow* node = static_cast(index.internalPointer()); + IDetailsViewRow* parentNode = node->mParent; + + if (!parentNode || parentNode == mRoot.get()) { + return QModelIndex(); + } + + IDetailsViewRow* grandparent = parentNode->mParent; + int parentRow = grandparent ? grandparent->mChildren.indexOf(parentNode) : -1; + + if (parentRow == -1) { + return QModelIndex(); + } + + QModelIndex parentIndex = createIndex(parentRow, 0, parentNode); + parentNode->setModelIndex(parentIndex); + return parentIndex; +} + +int QQuickDetailsViewModel::rowCount(const QModelIndex& parent) const { + if (!parent.isValid()) + return mRoot->mChildren.size(); + IDetailsViewRow* node = static_cast(parent.internalPointer()); + return node->mChildren.count(); +} + +void QQuickDetailsViewModel::setObject(QObject* inObject) +{ + mObject = inObject; + mRoot->setHandle(QPropertyHandle::FindOrCreate(mObject)); + mRoot->invalidateChildren(); +} + +QObject* QQuickDetailsViewModel::getObject() const +{ + return mObject; +} + +QModelIndex QQuickDetailsViewModel::indexForRow(IDetailsViewRow* row) const +{ + if (!row || row == mRoot.get()) { + return QModelIndex(); + } + + IDetailsViewRow* parentRow = row->mParent; + if (!parentRow) { + return QModelIndex(); + } + + int rowNum = -1; + for (int i = 0; i < parentRow->mChildren.size(); ++i) { + if (parentRow->mChildren[i].data() == row) { + rowNum = i; + break; + } + } + if (rowNum == -1) { + return QModelIndex(); + } + QModelIndex parentIndex = indexForRow(parentRow); + return createIndex(rowNum, 0, row); +} + +void QQuickDetailsViewModel::updateRowIndex(IDetailsViewRow* row) +{ + if (!row) return; + + QModelIndex oldIndex = row->modelIndex(); + QModelIndex newIndex = indexForRow(row); + + if (oldIndex != newIndex) { + row->setModelIndex(newIndex); + dataChanged(newIndex, newIndex); + } +} + +int QQuickDetailsViewModel::columnCount(const QModelIndex& parent) const { + return 1; +} + +QHash QQuickDetailsViewModel::roleNames() const { + return { + { Roles::name,"name" }, + }; +} diff --git a/PropertyEditor/source/src/QQuickDetailsViewPrivate.h b/PropertyEditor/source/src/QQuickDetailsViewPrivate.h new file mode 100644 index 0000000..a0efd06 --- /dev/null +++ b/PropertyEditor/source/src/QQuickDetailsViewPrivate.h @@ -0,0 +1,41 @@ +#ifndef QQuickDetailsViewPrivate_h__ +#define QQuickDetailsViewPrivate_h__ + +#include "private/qquicktreeview_p_p.h" +#include "QQuickTreeViewExPrivate.h" +#include "QQuickDetailsView.h" +#include "QQuickDetailsViewModel.h" + +class QQuickDetailsViewPrivate : public QQuickTreeViewExPrivate +{ + Q_DECLARE_PUBLIC(QQuickDetailsView) +public: + QQuickDetailsViewPrivate(); + void initItemCallback(int serializedModelIndex, QObject* object) override; + void updateRequiredProperties(int serializedModelIndex, QObject* object, bool init) override; +private: + qreal mSpliterPencent = 0.3; + QList mObjects; + QQuickDetailsViewModel* mModel; +}; + +QQuickDetailsViewPrivate::QQuickDetailsViewPrivate() + :mModel(new QQuickDetailsViewModel) +{ +} + +void QQuickDetailsViewPrivate::updateRequiredProperties(int serializedModelIndex, QObject* object, bool init) +{ + Q_Q(QQuickDetailsView); + const QPoint cell = cellAtModelIndex(serializedModelIndex); + const int row = cell.y(); + const int column = cell.x(); + setRequiredProperty("row", QVariant::fromValue(serializedModelIndex), serializedModelIndex, object, init); + setRequiredProperty("detailsView", QVariant::fromValue(q), serializedModelIndex, object, init); + setRequiredProperty("isTreeNode", column == 0, serializedModelIndex, object, init); + setRequiredProperty("hasChildren", m_treeModelToTableModel.hasChildren(row), serializedModelIndex, object, init); + setRequiredProperty("expanded", q->isExpanded(row), serializedModelIndex, object, init); + setRequiredProperty("depth", m_treeModelToTableModel.depthAtRow(row), serializedModelIndex, object, init); +} + +#endif // QQuickDetailsViewPrivate_h__ \ No newline at end of file diff --git a/PropertyEditor/source/src/QQuickDetailsViewRow.cpp b/PropertyEditor/source/src/QQuickDetailsViewRow.cpp new file mode 100644 index 0000000..a537b42 --- /dev/null +++ b/PropertyEditor/source/src/QQuickDetailsViewRow.cpp @@ -0,0 +1,118 @@ +#include "QQuickDetailsViewRow.h" +#include "QQuickDetailsViewLayoutBuilder.h" +#include "QQuickDetailsViewMananger.h" +#include "QQuickDetailsViewModel.h" + +void IDetailsViewRow::setName(QString inName) +{ + mName = inName; +} + +QString IDetailsViewRow::name() +{ + return mName; +} + +void IDetailsViewRow::addChild(QSharedPointer inChild) +{ + mChildren << inChild; + inChild->mModel = mModel; + inChild->mParent = this; +} + +void IDetailsViewRow::clear() +{ + mChildren.clear(); +} + +QQuickDetailsViewModel* IDetailsViewRow::model() +{ + return mModel; +} + +void IDetailsViewRow::invalidateChildren() +{ + if (!mModel) { + mChildren.clear(); + return; + } + + QModelIndex parentIndex = mModel->indexForRow(this); + if (!parentIndex.isValid()) { + parentIndex = QModelIndex(); + } + + const int oldChildCount = mChildren.size(); + if (oldChildCount > 0) { + mModel->beginRemoveRows(parentIndex, 0, oldChildCount - 1); + mChildren.clear(); + mModel->endRemoveRows(); + } + + attachChildren(); + + const int newChildCount = mChildren.size(); + if (newChildCount > 0) { + mModel->beginInsertRows(parentIndex, 0, newChildCount - 1); + for (auto& child : mChildren) { + child->mParent = this; + child->mModel = mModel; + } + mModel->endInsertRows(); + } +} + +QDetailsViewRow_Property::QDetailsViewRow_Property(QPropertyHandle* inHandle) +{ + setHandle(inHandle); +} + +QDetailsViewRow_Property::~QDetailsViewRow_Property() +{ + if (mStructureChangedConnection) { + QObject::disconnect(mStructureChangedConnection); + } +} + +void QDetailsViewRow_Property::setupItem(QQuickItem* inParent) +{ + QQuickDetailsViewRowBuilder builder(this, inParent); + if (mPropertyTypeCustomization) { + mPropertyTypeCustomization->customizeHeaderRow(mHandle, &builder); + return; + } + builder.makePropertyRow(mHandle); +} + +void QDetailsViewRow_Property::attachChildren() +{ + if (mPropertyTypeCustomization){ + QQuickDetailsViewLayoutBuilder builder(this); + mPropertyTypeCustomization->customizeChildren(mHandle, &builder); + } +} + +void QDetailsViewRow_Property::setHandle(QPropertyHandle* inHandle) +{ + mHandle = inHandle; + if (!inHandle) + return; + mPropertyTypeCustomization = QQuickDetailsViewManager::Get()->getCustomPropertyType(inHandle); + mStructureChangedConnection = QObject::connect(inHandle, &QPropertyHandle::asStructureChanged, [this]() { + invalidateChildren(); + }); +} + +QDetailsViewRow_Custom::QDetailsViewRow_Custom(std::function inRowCreator) + : mRowCreator(inRowCreator) +{ + +} + +void QDetailsViewRow_Custom::setupItem(QQuickItem* inParent) +{ + if (mRowCreator) { + QQuickDetailsViewRowBuilder builder(this, inParent); + mRowCreator(&builder); + } +} diff --git a/PropertyEditor/source/src/QQuickFunctionLibrary.cpp b/PropertyEditor/source/src/QQuickFunctionLibrary.cpp new file mode 100644 index 0000000..bfdcc95 --- /dev/null +++ b/PropertyEditor/source/src/QQuickFunctionLibrary.cpp @@ -0,0 +1,58 @@ +#include "QQuickFunctionLibrary.h" +#include +#include + +QQuickFunctionLibrary* QQuickFunctionLibrary::Get() +{ + static QQuickFunctionLibrary Ins; + return &Ins; +} + +QString QQuickFunctionLibrary::numberToString(QVariant var, int precision) +{ + double value = var.toDouble(); + return QString::number(value, 'f', precision); +} + +void QQuickFunctionLibrary::setCursorPos(qreal x, qreal y) +{ + QCursor::setPos(x, y); +} + +void QQuickFunctionLibrary::setOverrideCursorShape(Qt::CursorShape shape) +{ + qApp->setOverrideCursor(shape); +} + +void QQuickFunctionLibrary::restoreOverrideCursorShape() +{ + qApp->restoreOverrideCursor(); +} + +void QQuickFunctionLibrary::setCursorPosTest(QQuickItem* item, qreal x, qreal y) +{ + QPointF global = item->mapToGlobal(x, y); + QCursor::setPos(global.x(), global.y()); + qDebug() << x << y << global << QCursor::pos(); +} + +QMetaObject::Connection QQuickFunctionLibrary::connect(QObject* sender, const char* signal, QObject* receiver, std::function callback) +{ + if (!sender) + return {}; + return QObject::connect(sender, signal, new QQuickLambdaHolder(callback, receiver ? receiver : sender), SLOT(call())); +} + +QMetaObject::Connection QQuickFunctionLibrary::connect(QObject* sender, const char* signal, QObject* receiver, std::function callback) +{ + if (!sender) + return {}; + return QObject::connect(sender, signal, new QQuickLambdaHolder_OneParam(callback, receiver ? receiver : sender), SLOT(call(QVariant))); +} + +QMetaObject::Connection QQuickFunctionLibrary::connect(QObject* sender, const char* signal, QObject* receiver, std::function callback) +{ + if (!sender) + return {}; + return QObject::connect(sender, signal, new QQuickLambdaHolder_TwoParams(callback, receiver ? receiver : sender), SLOT(call(QVariant, QVariant))); +} diff --git a/PropertyEditor/source/src/QQuickTreeViewEx.cpp b/PropertyEditor/source/src/QQuickTreeViewEx.cpp new file mode 100644 index 0000000..0df2f01 --- /dev/null +++ b/PropertyEditor/source/src/QQuickTreeViewEx.cpp @@ -0,0 +1,480 @@ +#include "QQuickTreeViewEx.h" +#include "QQuickTreeViewExPrivate.h" +#include +#include +#include +#include + +// Hard-code the tree column to be 0 for now +static const int kTreeColumn = 0; + + +QQuickTreeViewExPrivate::QQuickTreeViewExPrivate() + : QQuickTableViewPrivate() +{ +} + +QQuickTreeViewExPrivate::~QQuickTreeViewExPrivate() +{ +} + +QVariant QQuickTreeViewExPrivate::modelImpl() const +{ + return m_assignedModel; +} + +void QQuickTreeViewExPrivate::setModelImpl(const QVariant& newModel) +{ + Q_Q(QQuickTreeViewEx); + + if (newModel == m_assignedModel) + return; + + m_assignedModel = newModel; + QVariant effectiveModel = m_assignedModel; + if (effectiveModel.userType() == qMetaTypeId()) + effectiveModel = effectiveModel.value().toVariant(); + + if (effectiveModel.isNull()) + m_treeModelToTableModel.setModel(nullptr); + else if (const auto qaim = qvariant_cast(effectiveModel)) + m_treeModelToTableModel.setModel(qaim); + else + qmlWarning(q) << "TreeView only accepts a model of type QAbstractItemModel"; + + + scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All); + emit q->modelChanged(); +} + +void QQuickTreeViewExPrivate::initItemCallback(int serializedModelIndex, QObject* object) +{ + Q_Q(QQuickTreeViewEx); + updateRequiredProperties(serializedModelIndex, object, true); + QQuickTableViewPrivate::initItemCallback(serializedModelIndex, object); +} + +void QQuickTreeViewExPrivate::itemReusedCallback(int serializedModelIndex, QObject* object) +{ + updateRequiredProperties(serializedModelIndex, object, false); + QQuickTableViewPrivate::itemReusedCallback(serializedModelIndex, object); +} + +void QQuickTreeViewExPrivate::dataChangedCallback( + const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) +{ + Q_Q(QQuickTreeViewEx); + Q_UNUSED(roles); + + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { + for (int column = topLeft.column(); column <= bottomRight.column(); ++column) { + const QPoint cell(column, row); + auto item = q->itemAtCell(cell); + if (!item) + continue; + + const int serializedModelIndex = modelIndexAtCell(QPoint(column, row)); + updateRequiredProperties(serializedModelIndex, item, false); + } + } +} + +void QQuickTreeViewExPrivate::updateRequiredProperties(int serializedModelIndex, QObject* object, bool init) +{ + Q_Q(QQuickTreeViewEx); + const QPoint cell = cellAtModelIndex(serializedModelIndex); + const int row = cell.y(); + const int column = cell.x(); + + setRequiredProperty("treeView", QVariant::fromValue(q), serializedModelIndex, object, init); + setRequiredProperty("isTreeNode", column == kTreeColumn, serializedModelIndex, object, init); + setRequiredProperty("hasChildren", m_treeModelToTableModel.hasChildren(row), serializedModelIndex, object, init); + setRequiredProperty("expanded", q->isExpanded(row), serializedModelIndex, object, init); + setRequiredProperty("depth", m_treeModelToTableModel.depthAtRow(row), serializedModelIndex, object, init); +} + +void QQuickTreeViewExPrivate::updateSelection(const QRect& oldSelection, const QRect& newSelection) +{ + Q_Q(QQuickTreeViewEx); + + const QRect oldRect = oldSelection.normalized(); + const QRect newRect = newSelection.normalized(); + + if (oldSelection == newSelection) + return; + + // Select the rows inside newRect that doesn't overlap with oldRect + for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row) { + if (oldRect.y() != -1 && oldRect.y() <= row && row <= oldRect.y() + oldRect.height()) + continue; + const QModelIndex startIndex = q->index(row, newRect.x()); + const QModelIndex endIndex = q->index(row, newRect.x() + newRect.width()); + selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select); + } + + if (oldRect.x() != -1) { + // Since oldRect is valid, this update is a continuation of an already existing selection! + + // Select the columns inside newRect that don't overlap with oldRect + for (int column = newRect.x(); column <= newRect.x() + newRect.width(); ++column) { + if (oldRect.x() <= column && column <= oldRect.x() + oldRect.width()) + continue; + for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row) + selectionModel->select(q->index(row, column), QItemSelectionModel::Select); + } + + // Unselect the rows inside oldRect that don't overlap with newRect + for (int row = oldRect.y(); row <= oldRect.y() + oldRect.height(); ++row) { + if (newRect.y() <= row && row <= newRect.y() + newRect.height()) + continue; + const QModelIndex startIndex = q->index(row, oldRect.x()); + const QModelIndex endIndex = q->index(row, oldRect.x() + oldRect.width()); + selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Deselect); + } + + // Unselect the columns inside oldRect that don't overlap with newRect + for (int column = oldRect.x(); column <= oldRect.x() + oldRect.width(); ++column) { + if (newRect.x() <= column && column <= newRect.x() + newRect.width()) + continue; + // Since we're not allowed to call select/unselect on the selectionModel with + // indices from different parents, and since indicies from different parents are + // expected when working with trees, we need to unselect the indices in the column + // one by one, rather than the whole column in one go. This, however, can cause a + // lot of selection fragments in the selectionModel, which eventually can hurt + // performance. But large selections containing a lot of columns is not normally + // the case for a treeview, so accept this potential corner case for now. + for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row) + selectionModel->select(q->index(row, column), QItemSelectionModel::Deselect); + } + } +} + +QQuickTreeViewEx::QQuickTreeViewEx(QQuickItem* parent) + : QQuickTableView(*(new QQuickTreeViewExPrivate), parent) +{ + Q_D(QQuickTreeViewEx); + + setSelectionBehavior(SelectRows); + setEditTriggers(EditKeyPressed); + + // Note: QQuickTableView will only ever see the table model m_treeModelToTableModel, and + // never the actual tree model that is assigned to us by the application. + const auto modelAsVariant = QVariant::fromValue(std::addressof(d->m_treeModelToTableModel)); + d->QQuickTableViewPrivate::setModelImpl(modelAsVariant); + QObjectPrivate::connect(&d->m_treeModelToTableModel, &QAbstractItemModel::dataChanged, + d, &QQuickTreeViewExPrivate::dataChangedCallback); + QObject::connect(&d->m_treeModelToTableModel, &QQmlTreeModelToTableModel::rootIndexChanged, + this, &QQuickTreeViewEx::rootIndexChanged); + + auto tapHandler = new QQuickTapHandler(this); + tapHandler->setAcceptedModifiers(Qt::NoModifier); + connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, tapHandler] { + if (!pointerNavigationEnabled()) + return; + if (editTriggers() & DoubleTapped) + return; + + const int row = cellAtPosition(tapHandler->point().pressPosition()).y(); + toggleExpanded(row); + }); +} + +QQuickTreeViewEx::QQuickTreeViewEx(QQuickTreeViewExPrivate& dd, QQuickItem* parent) + : QQuickTableView(dd, parent) +{ + Q_D(QQuickTreeViewEx); + + setSelectionBehavior(SelectRows); + setEditTriggers(EditKeyPressed); + + // Note: QQuickTableView will only ever see the table model m_treeModelToTableModel, and + // never the actual tree model that is assigned to us by the application. + const auto modelAsVariant = QVariant::fromValue(std::addressof(d->m_treeModelToTableModel)); + d->QQuickTableViewPrivate::setModelImpl(modelAsVariant); + QObjectPrivate::connect(&d->m_treeModelToTableModel, &QAbstractItemModel::dataChanged, + d, &QQuickTreeViewExPrivate::dataChangedCallback); + QObject::connect(&d->m_treeModelToTableModel, &QQmlTreeModelToTableModel::rootIndexChanged, + this, &QQuickTreeViewEx::rootIndexChanged); + + auto tapHandler = new QQuickTapHandler(this); + tapHandler->setAcceptedModifiers(Qt::NoModifier); + connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, tapHandler] { + if (!pointerNavigationEnabled()) + return; + if (editTriggers() & DoubleTapped) + return; + + const int row = cellAtPosition(tapHandler->point().pressPosition()).y(); + toggleExpanded(row); + }); + d_func()->init(); +} + +QQuickTreeViewEx::~QQuickTreeViewEx() +{ +} + +QModelIndex QQuickTreeViewEx::rootIndex() const +{ + return d_func()->m_treeModelToTableModel.rootIndex(); +} + +void QQuickTreeViewEx::setRootIndex(const QModelIndex& index) +{ + Q_D(QQuickTreeViewEx); + d->m_treeModelToTableModel.setRootIndex(index); + positionViewAtCell({ 0, 0 }, QQuickTableView::AlignTop | QQuickTableView::AlignLeft); +} + +void QQuickTreeViewEx::resetRootIndex() +{ + Q_D(QQuickTreeViewEx); + d->m_treeModelToTableModel.resetRootIndex(); + positionViewAtCell({ 0, 0 }, QQuickTableView::AlignTop | QQuickTableView::AlignLeft); +} + +int QQuickTreeViewEx::depth(int row) const +{ + Q_D(const QQuickTreeViewEx); + if (row < 0 || row >= d->m_treeModelToTableModel.rowCount()) + return -1; + + return d->m_treeModelToTableModel.depthAtRow(row); +} + +bool QQuickTreeViewEx::isExpanded(int row) const +{ + Q_D(const QQuickTreeViewEx); + if (row < 0 || row >= d->m_treeModelToTableModel.rowCount()) + return false; + + return d->m_treeModelToTableModel.isExpanded(row); +} + +void QQuickTreeViewEx::expand(int row) +{ + if (row >= 0) + expandRecursively(row, 1); +} + +void QQuickTreeViewEx::expandRecursively(int row, int depth) +{ + Q_D(QQuickTreeViewEx); + if (row >= d->m_treeModelToTableModel.rowCount()) + return; + if (row < 0 && row != -1) + return; + if (depth == 0 || depth < -1) + return; + + auto expandRowRecursively = [this, d, depth](int startRow) { + d->m_treeModelToTableModel.expandRecursively(startRow, depth); + // Update the expanded state of the startRow. The descendant rows that gets + // expanded will get the correct state set from initItem/itemReused instead. + for (int c = leftColumn(); c <= rightColumn(); ++c) { + const QPoint treeNodeCell(c, startRow); + if (const auto item = itemAtCell(treeNodeCell)) + d->setRequiredProperty("expanded", true, d->modelIndexAtCell(treeNodeCell), item, false); + } + }; + + if (row >= 0) { + // Expand only one row recursively + const bool isExpanded = d->m_treeModelToTableModel.isExpanded(row); + if (isExpanded && depth == 1) + return; + expandRowRecursively(row); + } + else { + // Expand all root nodes recursively + const auto model = d->m_treeModelToTableModel.model(); + for (int r = 0; r < model->rowCount(); ++r) { + const int rootRow = d->m_treeModelToTableModel.itemIndex(model->index(r, 0)); + if (rootRow != -1) + expandRowRecursively(rootRow); + } + } + + emit expanded(row, depth); +} + +void QQuickTreeViewEx::expandToIndex(const QModelIndex& index) +{ + Q_D(QQuickTreeViewEx); + + if (!index.isValid()) { + qmlWarning(this) << "index is not valid: " << index; + return; + } + + if (index.model() != d->m_treeModelToTableModel.model()) { + qmlWarning(this) << "index doesn't belong to correct model: " << index; + return; + } + + if (rowAtIndex(index) != -1) { + // index is already visible + return; + } + + int depth = 1; + QModelIndex parent = index.parent(); + int row = rowAtIndex(parent); + + while (parent.isValid()) { + if (row != -1) { + // The node is already visible, since it maps to a row in the table! + d->m_treeModelToTableModel.expandRow(row); + + // Update the state of the already existing delegate item + for (int c = leftColumn(); c <= rightColumn(); ++c) { + const QPoint treeNodeCell(c, row); + if (const auto item = itemAtCell(treeNodeCell)) + d->setRequiredProperty("expanded", true, d->modelIndexAtCell(treeNodeCell), item, false); + } + + // When we hit a node that is visible, we know that all other nodes + // up to the parent have to be visible as well, so we can stop. + break; + } + else { + d->m_treeModelToTableModel.expand(parent); + parent = parent.parent(); + row = rowAtIndex(parent); + depth++; + } + } + + emit expanded(row, depth); +} + +void QQuickTreeViewEx::collapse(int row) +{ + Q_D(QQuickTreeViewEx); + if (row < 0 || row >= d->m_treeModelToTableModel.rowCount()) + return; + + if (!d->m_treeModelToTableModel.isExpanded(row)) + return; + + d_func()->m_treeModelToTableModel.collapseRow(row); + + for (int c = leftColumn(); c <= rightColumn(); ++c) { + const QPoint treeNodeCell(c, row); + if (const auto item = itemAtCell(treeNodeCell)) + d->setRequiredProperty("expanded", false, d->modelIndexAtCell(treeNodeCell), item, false); + } + + emit collapsed(row, false); +} + +void QQuickTreeViewEx::collapseRecursively(int row) +{ + Q_D(QQuickTreeViewEx); + if (row >= d->m_treeModelToTableModel.rowCount()) + return; + if (row < 0 && row != -1) + return; + + auto collapseRowRecursive = [this, d](int startRow) { + // Always collapse descendants recursively, + // even if the top row itself is already collapsed. + d->m_treeModelToTableModel.collapseRecursively(startRow); + // Update the expanded state of the (still visible) startRow + for (int c = leftColumn(); c <= rightColumn(); ++c) { + const QPoint treeNodeCell(c, startRow); + if (const auto item = itemAtCell(treeNodeCell)) + d->setRequiredProperty("expanded", false, d->modelIndexAtCell(treeNodeCell), item, false); + } + }; + + if (row >= 0) { + collapseRowRecursive(row); + } + else { + // Collapse all root nodes recursively + const auto model = d->m_treeModelToTableModel.model(); + for (int r = 0; r < model->rowCount(); ++r) { + const int rootRow = d->m_treeModelToTableModel.itemIndex(model->index(r, 0)); + if (rootRow != -1) + collapseRowRecursive(rootRow); + } + } + + emit collapsed(row, true); +} + +void QQuickTreeViewEx::toggleExpanded(int row) +{ + if (isExpanded(row)) + collapse(row); + else + expand(row); +} + +void QQuickTreeViewEx::invalidateLayout() +{ + Q_D(QQuickTreeViewEx); + d->forceLayout(false); +} + +QModelIndex QQuickTreeViewEx::modelIndex(const QPoint& cell) const +{ + Q_D(const QQuickTreeViewEx); + const QModelIndex tableIndex = d->m_treeModelToTableModel.index(cell.y(), cell.x()); + return d->m_treeModelToTableModel.mapToModel(tableIndex); +} + +QPoint QQuickTreeViewEx::cellAtIndex(const QModelIndex& index) const +{ + const QModelIndex tableIndex = d_func()->m_treeModelToTableModel.mapFromModel(index); + return QPoint(tableIndex.column(), tableIndex.row()); +} + +#if QT_DEPRECATED_SINCE(6, 4) +QModelIndex QQuickTreeViewEx::modelIndex(int row, int column) const +{ + static const bool compat6_4 = qEnvironmentVariable("QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4"); + if (compat6_4) { + // XXX Qt 7: Remove this compatibility path here and in QQuickTableView. + // In Qt 6.4.0 and 6.4.1, a source incompatible change led to row and column + // being documented to be specified in the opposite order. + // QT_QUICK_TABLEVIEW_COMPAT_VERSION can therefore be set to force tableview + // to continue accepting calls to modelIndex(column, row). + return modelIndex({ row, column }); + } + else { + qmlWarning(this) << "modelIndex(row, column) is deprecated. " + "Use index(row, column) instead. For more information, see " + "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html"; + return modelIndex({ column, row }); + } +} +#endif + +void QQuickTreeViewEx::keyPressEvent(QKeyEvent* event) +{ + event->ignore(); + + if (!keyNavigationEnabled()) + return; + if (!selectionModel()) + return; + + const int row = cellAtIndex(selectionModel()->currentIndex()).y(); + switch (event->key()) { + case Qt::Key_Left: + collapse(row); + event->accept(); + break; + case Qt::Key_Right: + expand(row); + event->accept(); + break; + default: + break; + } + + if (!event->isAccepted()) + QQuickTableView::keyPressEvent(event); +} \ No newline at end of file diff --git a/PropertyEditor/source/src/QQuickTreeViewExPrivate.h b/PropertyEditor/source/src/QQuickTreeViewExPrivate.h new file mode 100644 index 0000000..b25e548 --- /dev/null +++ b/PropertyEditor/source/src/QQuickTreeViewExPrivate.h @@ -0,0 +1,34 @@ +#ifndef QQuickTreeViewExPrivate_h__ +#define QQuickTreeViewExPrivate_h__ + +#include "private/qquicktreeview_p_p.h" +#include "QQuickTreeViewEx.h" + +class QQuickTreeViewExPrivate : public QQuickTableViewPrivate +{ +public: + Q_DECLARE_PUBLIC(QQuickTreeViewEx) + + QQuickTreeViewExPrivate(); + ~QQuickTreeViewExPrivate() override; + + static inline QQuickTreeViewExPrivate* get(QQuickTreeViewEx* q) { return q->d_func(); } + + QVariant modelImpl() const override; + void setModelImpl(const QVariant& newModel) override; + + void initItemCallback(int serializedModelIndex, QObject* object) override; + void itemReusedCallback(int serializedModelIndex, QObject* object) override; + void dataChangedCallback(const QModelIndex& topLeft, + const QModelIndex& bottomRight, + const QVector& roles); + + virtual void updateRequiredProperties(int serializedModelIndex, QObject* object, bool init); + void updateSelection(const QRect& oldSelection, const QRect& newSelection) override; +public: + QQmlTreeModelToTableModel m_treeModelToTableModel; + QVariant m_assignedModel; +}; + + +#endif // QQuickTreeViewExPrivate_h__ \ No newline at end of file diff --git a/diagramCavas/CMakeLists.txt b/diagramCavas/CMakeLists.txt index ad082e6..61c9479 100644 --- a/diagramCavas/CMakeLists.txt +++ b/diagramCavas/CMakeLists.txt @@ -125,6 +125,10 @@ set(DIAGRAMCAVAS_HEADER_FILES include/util/subMovingSelector.h include/instance/dataAccessor.h include/instance/extraPropertyManager.h + + include/propertyType/CustomGadget.h + include/propertyType/CustomType.h + include/propertyType/PropertyTypeCustomization_CustomType.h ../common/include/httpInterface.h ../common/include/tools.h ../common/include/global.h @@ -258,6 +262,8 @@ set(DIAGRAMCAVAS_SOURCE_FILES source/util/subMovingSelector.cpp source/instance/dataAccessor.cpp source/instance/extraPropertyManager.cpp + + source/propertyType/PropertyTypeCustomization_CustomType.cpp ../common/source/httpInterface.cpp ../common/source/baseProperty.cpp ../common/source/tools.cpp @@ -324,11 +330,11 @@ target_link_libraries(diagramCavas PRIVATE Qt6::Sql ${PostgreSQL_LIBRARIES}) option(BUILD_SHARED_LIBS "Build as shared library" ON) - target_include_directories(diagramCavas PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_link_libraries(diagramCavas PRIVATE diagramUtils) target_link_libraries(diagramCavas PRIVATE diagramCommunication) +target_link_libraries(diagramCavas PUBLIC PropertyEditor) target_compile_definitions(diagramCavas PUBLIC diff --git a/diagramCavas/include/propertyType/CustomGadget.h b/diagramCavas/include/propertyType/CustomGadget.h new file mode 100644 index 0000000..2862458 --- /dev/null +++ b/diagramCavas/include/propertyType/CustomGadget.h @@ -0,0 +1,20 @@ +#ifndef CustomGadget_h__ +#define CustomGadget_h__ + +#include "CommonInclude.h" + +class QCustomGadget { + Q_GADGET + Q_CLASSINFO("LimitedDouble", "Min=0,Max=10") +public: + Q_PROPERTY_VAR(double, LimitedDouble) = 1; + Q_PROPERTY_VAR(QString, Desc) = "This is inline Gadget"; +}; + +static QDebug operator<<(QDebug debug, const QCustomGadget& gadget) { + return debug << gadget.LimitedDouble << gadget.Desc; +} + +Q_DECLARE_METATYPE(QSharedPointer); + +#endif // CustomGadget_h__ \ No newline at end of file diff --git a/diagramCavas/include/propertyType/CustomType.h b/diagramCavas/include/propertyType/CustomType.h new file mode 100644 index 0000000..7674a7e --- /dev/null +++ b/diagramCavas/include/propertyType/CustomType.h @@ -0,0 +1,18 @@ +#ifndef CustomType_h__ +#define CustomType_h__ + +#include +#include + +struct QCustomType { + unsigned int ArraySize = 0; + QVector Array; +}; + +static QDebug operator<<(QDebug debug, const QCustomType& it) { + return debug << it.Array; +} + +Q_DECLARE_METATYPE(QCustomType) + +#endif // CustomType_h__ diff --git a/diagramCavas/include/propertyType/PropertyTypeCustomization_CustomType.h b/diagramCavas/include/propertyType/PropertyTypeCustomization_CustomType.h new file mode 100644 index 0000000..3d847cf --- /dev/null +++ b/diagramCavas/include/propertyType/PropertyTypeCustomization_CustomType.h @@ -0,0 +1,12 @@ +#ifndef PropertyTypeCustomization_CustomType_h__ +#define PropertyTypeCustomization_CustomType_h__ + +#include "IPropertyTypeCustomization.h" + +class PropertyTypeCustomization_CustomType : public IPropertyTypeCustomization { +protected: + virtual void customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder) override; + virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) override; +}; + +#endif // PropertyTypeCustomization_CustomType_h__ diff --git a/diagramCavas/source/propertyType/PropertyTypeCustomization_CustomType.cpp b/diagramCavas/source/propertyType/PropertyTypeCustomization_CustomType.cpp new file mode 100644 index 0000000..612738f --- /dev/null +++ b/diagramCavas/source/propertyType/PropertyTypeCustomization_CustomType.cpp @@ -0,0 +1,73 @@ +#include "propertyType/PropertyTypeCustomization_CustomType.h" +#include "QQuickDetailsViewLayoutBuilder.h" +#include "QPropertyHandle.h" +#include +#include +#include +#include "QQuickDetailsViewModel.h" +#include "propertyType/CustomType.h" +#include "QQuickFunctionLibrary.h" + +void PropertyTypeCustomization_CustomType::customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder) +{ + auto editorSlot = inBuilder->makeNameValueSlot(); + inPropertyHandle->setupNameEditor(editorSlot.first); + auto buttonItem = inBuilder->setupItem(editorSlot.second, R"( + import QtQuick; + import QtQuick.Controls; + Button{ + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + width: 80 + height: 20 + text: "Sort" + } + )"); + + + QQuickFunctionLibrary::connect(buttonItem, SIGNAL(clicked()), inPropertyHandle, [inPropertyHandle]() { + QCustomType customType = inPropertyHandle->getVar().value(); + std::sort(customType.Array.begin(), customType.Array.end()); + inPropertyHandle->setVar(QVariant::fromValue(customType)); + if (auto arrayHandle = inPropertyHandle->findChild("Array")) { + } + }); +} + +void PropertyTypeCustomization_CustomType::customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) +{ + auto arrayHandle = inPropertyHandle->findOrCreateChild( + QMetaType::fromType>(), + "Array", + [inPropertyHandle]() { + return QVariant::fromValue(inPropertyHandle->getVar().value().Array); + }, + [inPropertyHandle](QVariant var) { + QCustomType customType = inPropertyHandle->getVar().value(); + customType.Array = var.value>(); + inPropertyHandle->setVar(QVariant::fromValue(customType)); + } + ); + + auto arraySizeHandle = inPropertyHandle->findOrCreateChild( + QMetaType::fromType(), + "ArraySize", + [inPropertyHandle]() { + return inPropertyHandle->getVar().value().ArraySize; + }, + [inPropertyHandle, arrayHandle](QVariant var) { + QCustomType customType = inPropertyHandle->getVar().value(); + customType.ArraySize = var.toUInt(); + customType.Array.resize(customType.ArraySize); + for (int i = 0; i < customType.ArraySize; ++i) { + customType.Array[i] = QRandomGenerator::global()->bounded(-100000, 100000); + } + inPropertyHandle->setVar(QVariant::fromValue(customType)); + arrayHandle->invalidateStructure(); + } + ); + + inBuilder->addProperty(arraySizeHandle); + inBuilder->addProperty(arrayHandle); +} + diff --git a/include/CommonInclude.h b/include/CommonInclude.h new file mode 100644 index 0000000..acb5d46 --- /dev/null +++ b/include/CommonInclude.h @@ -0,0 +1,18 @@ +#ifndef CommonInclude_h__ +#define CommonInclude_h__ + +#include +#include +#include +#include + +#define Q_PROPERTY_VAR(Type,Name)\ + Q_PROPERTY(Type Name READ get##Name WRITE set##Name) \ + Type get##Name(){ return Name; } \ + void set##Name(Type var){ \ + Name = var; \ + qDebug() << "Set" <<#Name <<": " <getQuickDetailsView(); + delete pView; + delete m_pPropertiesEditorView; + } } @@ -126,6 +133,14 @@ void CMainWindow::initializeDockUi() m_pProjectModelDlg = new projectModelDlg(this); connect(&ProjectModelManager::instance(),&ProjectModelManager::modelChange,m_pElectricElementsBox,&ElectricElementsBox::onSignal_modelChanged); + + m_pPropertiesEditorView = new QDetailsView(); + QDockWidget* PropertyEditorDock = new QDockWidget(QString::fromWCharArray(L"属性面板"),this); + PropertyEditorDock->setWidget(m_pPropertiesEditorView); + PropertyEditorDock->setMinimumSize(200,150); + m_pPropertiesEditorView->setObject(m_pDiagramCavas); + PropertyEditorDock->setAllowedAreas(Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea); + this->addDockWidget(Qt::RightDockWidgetArea,PropertyEditorDock); } void CMainWindow::initializeAction()