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 0000000..5d98b3c Binary files /dev/null and b/PropertyEditor/resources/Icon/arrow-left-l.png differ diff --git a/PropertyEditor/resources/Icon/arrow-right-l.png b/PropertyEditor/resources/Icon/arrow-right-l.png new file mode 100644 index 0000000..27282e2 Binary files /dev/null and b/PropertyEditor/resources/Icon/arrow-right-l.png differ diff --git a/PropertyEditor/resources/Icon/close.png b/PropertyEditor/resources/Icon/close.png new file mode 100644 index 0000000..d7ff2d7 Binary files /dev/null and b/PropertyEditor/resources/Icon/close.png differ diff --git a/PropertyEditor/resources/Icon/delete.png b/PropertyEditor/resources/Icon/delete.png new file mode 100644 index 0000000..6a7c8a1 Binary files /dev/null and b/PropertyEditor/resources/Icon/delete.png differ diff --git a/PropertyEditor/resources/Icon/down.png b/PropertyEditor/resources/Icon/down.png new file mode 100644 index 0000000..76df783 Binary files /dev/null and b/PropertyEditor/resources/Icon/down.png differ diff --git a/PropertyEditor/resources/Icon/expand.png b/PropertyEditor/resources/Icon/expand.png new file mode 100644 index 0000000..a1dbcf7 Binary files /dev/null and b/PropertyEditor/resources/Icon/expand.png differ diff --git a/PropertyEditor/resources/Icon/plus.png b/PropertyEditor/resources/Icon/plus.png new file mode 100644 index 0000000..cea9ef0 Binary files /dev/null and b/PropertyEditor/resources/Icon/plus.png differ diff --git a/PropertyEditor/resources/Icon/reset.png b/PropertyEditor/resources/Icon/reset.png new file mode 100644 index 0000000..c49badc Binary files /dev/null and b/PropertyEditor/resources/Icon/reset.png differ diff --git a/PropertyEditor/resources/Icon/unexpand.png b/PropertyEditor/resources/Icon/unexpand.png new file mode 100644 index 0000000..18ea16e Binary files /dev/null and b/PropertyEditor/resources/Icon/unexpand.png differ diff --git a/PropertyEditor/resources/Icon/up.png b/PropertyEditor/resources/Icon/up.png new file mode 100644 index 0000000..ff302d5 Binary files /dev/null and b/PropertyEditor/resources/Icon/up.png differ 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 0000000..8c95aa1 Binary files /dev/null and b/PropertyEditor/resources/image-20250826114654194.png differ 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()