Initial commit: BayTemplate Grid Framework DesignTime tool

- Qt-based application for designing electrical power grid structures
- Features dockable UI with graphics canvas for placing grid elements
- Includes QtADS (Advanced Docking System v4.3.1) and PropertyEditor
- Supports Qt5/Qt6, multiple platforms (Windows, macOS, Linux, Android)
- Architecture: DesignerView/Scene, SelectorManager, Command Pattern for undo/redo
This commit is contained in:
Jesse Qu 2026-03-25 17:15:51 +08:00
commit ab6960acb5
492 changed files with 48864 additions and 0 deletions

17
.drone.yml Normal file
View File

@ -0,0 +1,17 @@
kind: pipeline
type: ssh
name: default
server:
host: 192.168.46.16
user: jessequ
password:
from_secret: password
steps:
- name: build
commands:
- mkdir -p build/debug
- cd build/debug
- /home/jessequ/Qt/Tools/CMake/bin/cmake -S ../.. -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_GENERATOR:STRING=Ninja -DCMAKE_MAKE_PROGRAM:FILEPATH=/home/jessequ/Qt/Tools/Ninja/ninja -DCMAKE_PREFIX_PATH:PATH=/home/jessequ/Qt/6.7.3/gcc_64 -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=/home/jessequ/Qt/Platforms/package-manager/auto-setup.cmake
- /home/jessequ/Qt/Tools/Ninja/ninja

107
.gitignore vendored Normal file
View File

@ -0,0 +1,107 @@
build/
.vscode/
.qtcreator/
# ---> CMake
CMakeLists.txt.user
CMakeUserPresets.json
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
# ---> C++
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# ---> C
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf

144
CLAUDE.md Normal file
View File

@ -0,0 +1,144 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
BayTemplate is a **Grid Framework DesignTime** tool - a Qt-based application for designing electrical power grid structures. It features a dockable UI with a graphics canvas for placing and manipulating grid elements.
## Build Commands
### Build the Project
```bash
# Create build/debug directory and configure
mkdir -p build/debug && cd build/debug
/Users/jesse/Qt/Tools/CMake/CMake.app/Contents/bin/cmake -S ../.. -B . -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_GENERATOR:STRING=Ninja -DCMAKE_MAKE_PROGRAM:FILEPATH=/Users/jesse/Qt/Tools/Ninja/ninja -DQT_QMAKE_EXECUTABLE:FILEPATH=/Users/jesse/Qt/6.9.3/macos/bin/qmake -DCMAKE_PREFIX_PATH:PATH=/Users/jesse/Qt/6.9.3/macos -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=/Users/jesse/Platforms/package-manager/auto-setup.cmake -DCMAKE_CXX_COMPILER:FILEPATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -DCMAKE_C_COMPILER:FILEPATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -DCMAKE_CXX_FLAGS_INIT:STRING=-DQT_QML_DEBUG -DPostgreSQL_INCLUDE_DIR:FILEPATH=/opt/homebrew/opt/postgresql@17/include/postgresql -DPostgreSQL_LIBRARY_DIR:FILEPATH=/opt/homebrew/opt/postgresql@17/lib/postgresql -DPostgreSQL_LIBRARY:FILEPATH=/opt/homebrew/opt/postgresql@17/lib/postgresql/libpq.dylib
# Build
/Users/jesse/Qt/Tools/Ninja/ninja -C /Users/jesse/Workspaces/projects/qt-projects/qtclaw/BayTemplate/build/debug
```
### Build Output Locations
Platform-specific output directories under `build/`:
- `build/x64/bin/BayTemplate` - Executable (64-bit Intel/AMD)
- `build/x86/bin/BayTemplate` - Executable (32-bit)
- `build/arm64/bin/BayTemplate` - Executable (64-bit ARM)
- `build/${pd_PlatformDir}/lib/` - Libraries
## Architecture
### Core Components
#### 1. Docking System (Qt Advanced Docking System 4.3.1)
- **CDockManager** manages dockable panels
- **Left Dock**: 图元面板 (GraphicElementsPanel) - library of draggable grid elements
- **Center**: DrawingPanel - main canvas area with DesignerScene
- **Right Dock**: 属性编辑器 (PropertyEditor) - QDetailsView for editing selected item properties
#### 2. Graphics View Framework
- **DesignerView** (`QGraphicsView` subclass): Custom view with zoom (0.02-50x), pan (middle-button drag), checkerboard background
- **DesignerScene** (`QGraphicsScene` subclass): Custom scene with grid overlay (20px spacing), routes mouse events through SelectorManager
#### 3. Selector System
Selector hierarchy in `include/util/`:
- **BaseSelector**: Abstract base for all selectors
- **CreatingSelector**: Mode for creating new items from template
- **MovingSelector**: Mode for moving items
- **RotationSelector**: Mode for rotating items around origin
- **ScalingSelector**: Mode for scaling items
- **EditingSelector**: Mode for editing polygon vertex positions
- **SelectorManager**: Singleton that manages the active working selector
Mouse events flow: `DesignerScene``SelectorManager::getWorkingSelector()` → specific selector implementation
#### 4. Graphics Items (`include/graphicsItem/`)
- **GraphicsBaseItem**: Abstract base class for all grid elements
- **GraphicsRectItem**: Rectangle element
- **GraphicsPolygonItem**: Polygon element (editable vertices)
- **GraphicsItemGroup**: Container for grouped items (supports flattening to prevent nesting)
- **GraphicsBusSectionItem**: Bus section element
- **ItemControlHandle**: Visual handles for manipulation (rotation, scaling, editing)
#### 5. Property Editor (Qt PropertyEditor)
- **QDetailsView**: Qt Quick-based property editor in right dock
- **QCustomType**: Custom property type for graphics item properties
- **PropertyTypeCustomization_CustomType**: Custom property editor integration
- Property editor observes `QGraphicsScene` and displays properties of selected items
#### 6. Command Pattern (Undo/Redo)
- **QUndoStack** (`m_pUndoStack` in CMainWindow) manages undo/redo
- **OperationCommand**: Base command class
- **AddItemCommand**: Add item to scene
- **DeleteItemCommand**: Remove item from scene
- **CreateItemGoupCommand**: Group selected items (with flattening)
- **DestroyItemGoupCommand**: Ungroup items
### Signal Flow
1. **User drags from 图元面板**`GraphicElementsPanel` emits signal → `DesignerScene::signalAddItem()` → creates item with `AddItemCommand`
2. **User clicks on canvas**`DesignerView``DesignerScene``SelectorManager::getWorkingSelector()` → selector processes event
3. **User selects items**`DesignerScene::selectionChanged()``CMainWindow::onSignal_selectionChanged()` → updates property editor
4. **User modifies property in 属性编辑器**`QDetailsView` updates item property → scene updates
### Key Files and Relationships
```
source/main.cpp # Entry point - creates CMainWindow
└── source/mainwindow.h/.cpp # Main window with CDockManager, initializes all components
├── initializeDockUi() # Sets up docks (left, center, right)
├── initializeAction() # Sets up QUndoStack and menu actions
└── Event handlers # onSignal_addItem, onSignal_selectionChanged, etc.
source/drawingPanel.h/.cpp # Central dock widget containing DesignerView
└── DesignerView + DesignerScene
source/designerView.h/.cpp # QGraphicsView subclass - zoom, pan, middle-button navigation
source/designerScene.h/.cpp # QGraphicsScene subclass - grid background, group operations
└── Delegates to SelectorManager for mouse events
source/util/selectorManager.h/.cpp # Singleton managing active selector
└── Selector::mousePressEvent/ReleaseEvent/MoveEvent()
source/graphicsItem/ # Graphics item implementations
├── GraphicsBaseItem # Base class with common functionality
├── GraphicsRectItem # Rectangle
├── GraphicsPolygonItem # Editable polygon
├── GraphicsItemGroup # Item grouping
└── GraphicsBusSectionItem
source/propertyType/ # Custom property editor integration
├── CustomType.h
├── CustomGadget.h
└── PropertyTypeCustomization_CustomType.cpp
```
### Third-Party Dependencies
1. **QtADS** (`QtADS/` subdirectory, v4.3.1): Advanced docking system - must be present as subdirectory
2. **PropertyEditor** (`PropertyEditor/` subdirectory): Qt Quick-based property editing system with QDetailsView
Both are added via `add_subdirectory()` in CMakeLists.txt and must exist in the repository root.
### Platform Support
- **Qt Version**: Qt5 or Qt6 (auto-detected, `find_package(QT NAMES Qt6 Qt5 ...)`)
- **Architectures**: x86, x64, arm64, aarch64 (auto-detected in CMakeLists.txt)
- **Platforms**: Windows, macOS, Linux, Android (Android uses shared library)
### Important Implementation Details
1. **CMAKE_AUTOUIC_SEARCH_PATHS**: Set to `"ui"` in CMakeLists.txt because .ui files are in separate directory from header files
2. **Group Flattening**: When creating a group that contains existing groups, the scene flattens nested groups first to prevent group nesting (see `DesignerScene::createGroup()`)
3. **Zoom Implementation**: Uses `QGraphicsView::zoom()` with smooth factor `qPow(1.0015, angleDelta.y())` per wheel event
4. **Wchar String Conversion**: Uses `QString::fromWCharArray(L"中文")` for Chinese UI strings
5. **Resource File**: `resource/BayTemplate.qrc` contains checkerboard background and icons, referenced in .ui files and CMakeLists.txt

150
CMakeLists.txt Normal file
View File

@ -0,0 +1,150 @@
cmake_minimum_required(VERSION 3.5...4.3)
project(BayTemplate LANGUAGES CXX VERSION 1.0)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(ADS_VERSION 4.3.1)
add_subdirectory(QtADS)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widgets REQUIRED)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
#ui.h
set(CMAKE_AUTOUIC_SEARCH_PATHS "ui")
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
set(pd_PlatformDir "x86")
else()
if(DEFINED CMAKE_SYSTEM_PROCESSOR)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
set(pd_PlatformDir "x64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|ARM64")
set(pd_PlatformDir "arm64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64")
set(pd_PlatformDir "aarch64")
else()
set(pd_PlatformDir "x64")
endif()
else()
set(pd_PlatformDir "x64")
endif()
endif()
add_subdirectory(PropertyEditor)
set(H_HEADER_FILES
include/global.h
include/mainwindow.h
include/graphicElementsPanel.h
include/drawingPanel.h
include/designerScene.h
include/designerView.h
include/operationCommand.h
include/propertyType/CustomGadget.h
include/propertyType/CustomType.h
include/propertyType/PropertyTypeCustomization_CustomType.h
include/util/baseSelector.h
include/util/creatingSelector.h
include/util/movingSelector.h
include/util/rotationSelector.h
include/util/scalingSelector.h
include/util/editingSelector.h
include/util/selectorManager.h
include/graphicsItem/itemControlHandle.h
include/graphicsItem/graphicsBaseItem.h
include/graphicsItem/graphicsRectItem.h
include/graphicsItem/graphicsPolygonItem.h
include/graphicsItem/graphicsItemGroup.h
include/graphicsItem/graphicsBusSectionItem.h
)
set(CPP_SOURCE_FILES
source/main.cpp
source/mainwindow.cpp
source/graphicElementsPanel.cpp
source/drawingPanel.cpp
source/designerScene.cpp
source/designerView.cpp
source/operationCommand.cpp
source/propertyType/PropertyTypeCustomization_CustomType.cpp
source/util/baseSelector.cpp
source/util/creatingSelector.cpp
source/util/movingSelector.cpp
source/util/rotationSelector.cpp
source/util/scalingSelector.cpp
source/util/editingSelector.cpp
source/util/selectorManager.cpp
source/graphicsItem/itemControlHandle.cpp
source/graphicsItem/graphicsBaseItem.cpp
source/graphicsItem/graphicsRectItem.cpp
source/graphicsItem/graphicsPolygonItem.cpp
source/graphicsItem/graphicsItemGroup.cpp
source/graphicsItem/graphicsBusSectionItem.cpp
)
set(UI_FILES
ui/mainwindow.ui
ui/graphicElementsPanel.ui
ui/drawingPanel.ui
)
#
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR})
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(BayTemplate
MANUAL_FINALIZATION
${H_HEADER_FILES}
${CPP_SOURCE_FILES}
${UI_FILES}
resource/BayTemplate.qrc
)
else()
if(ANDROID)
add_library(BayTemplate SHARED
${H_HEADER_FILES}
${CPP_SOURCE_FILES}
${UI_FILES}
resource/BayTemplate.qrc
)
else()
add_executable(BayTemplate WIN32
${H_HEADER_FILES}
${CPP_SOURCE_FILES}
${UI_FILES}
resource/BayTemplate.qrc
)
endif()
endif()
include_directories(include)
target_include_directories(BayTemplate PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_include_directories(BayTemplate PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/PropertyEditor/source/include")
target_link_libraries(BayTemplate PRIVATE qt${QT_VERSION_MAJOR}advanceddocking)
target_link_libraries(BayTemplate PRIVATE PropertyEditor)
target_link_libraries(BayTemplate PUBLIC Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets)
set_target_properties(BayTemplate PROPERTIES
AUTOMOC ON
AUTORCC ON
AUTOUIC ON
CXX_STANDARD 14
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
VERSION 1.0
EXPORT_NAME "BayTemplate with Qt Advanced Docking System"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${pd_PlatformDir}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${pd_PlatformDir}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${pd_PlatformDir}/bin"
)

View File

@ -0,0 +1,60 @@
cmake_minimum_required(VERSION 3.12)
project(PropertyEditor CXX)
option(BUILD_SHARED_LIBS "Build shared library (ON) or static library (OFF)" OFF)
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)
else()
add_library(PropertyEditor STATIC ${PROJECT_SOURCE} ${QRC_FILE})
target_compile_definitions(PropertyEditor PUBLIC PROPERTY_EDITOR_STATIC_LIBRARY)
endif()
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}/${pd_PlatformDir}/bin"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${pd_PlatformDir}/lib"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${pd_PlatformDir}/lib"
)
if(PROJECT_IS_TOP_LEVEL)
add_subdirectory(example)
endif()

162
PropertyEditor/README.md Normal file
View File

@ -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<QDir>(),
[](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<QQuickItem*>(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<IPropertyTypeCustomization>
{
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<QCustomType, PropertyTypeCustomization_CustomType>();
```
## TODO
- Undo/redo
- Container operations
- Extended meta-data support

159
PropertyEditor/README_zh.md Normal file
View File

@ -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<QDir>(),
[](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<QQuickItem*>(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<IPropertyTypeCustomization>
{
public:
// 用于装配当前属性行的编辑器
virtual void customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder);
// 用于扩展当前属性行的子项
virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder);
};
```
之后使用如下接口进行注册:
``` c++
QQuickDetailsViewManager::Get()->registerPropertyTypeCustomization<QCustomType, PropertyTypeCustomization_CustomType>();
```
## TODO
- 撤销重做
- 容器操作
- 扩展元信息

View File

@ -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"
)

View File

@ -0,0 +1,18 @@
#ifndef CommonInclude_h__
#define CommonInclude_h__
#include <QObject>
#include <QSharedPointer>
#include <QDebug>
#include <QMetaType>
#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" <<this <<#Name <<": " <<var; \
} \
Type Name
#endif // CommonInclude_h__

View File

@ -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<QCustomGadget>);
#endif // CustomGadget_h__

View File

@ -0,0 +1,48 @@
#ifndef CustomObject_h__
#define CustomObject_h__
#include <QObject>
#include <qvectornd.h>
#include <QFile>
#include <QDir>
#include <QColor>
#include <QMatrix4x4>
#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<QColor>, ColorList) = { Qt::red,Qt::green,Qt::blue };
typedef QMap<QString, QColor> 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<QCustomGadget>, CustomGadgetSharedPtr) = QSharedPointer<QCustomGadget>::create();
Q_PROPERTY_VAR(QCustomObject*, SubCustomObject) = nullptr;
};
#endif // CustomObject_h__

View File

@ -0,0 +1,18 @@
#ifndef CustomType_h__
#define CustomType_h__
#include <QVector>
#include <QMetaType>
struct QCustomType {
unsigned int ArraySize = 0;
QVector<int> Array;
};
static QDebug operator<<(QDebug debug, const QCustomType& it) {
return debug << it.Array;
}
Q_DECLARE_METATYPE(QCustomType)
#endif // CustomType_h__

View File

@ -0,0 +1,73 @@
#include "PropertyTypeCustomization_CustomType.h"
#include "QQuickDetailsViewLayoutBuilder.h"
#include "QPropertyHandle.h"
#include <QMetaType>
#include <QObject>
#include <QRandomGenerator>
#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<QCustomType>();
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<QVector<int>>(),
"Array",
[inPropertyHandle]() {
return QVariant::fromValue(inPropertyHandle->getVar().value<QCustomType>().Array);
},
[inPropertyHandle](QVariant var) {
QCustomType customType = inPropertyHandle->getVar().value<QCustomType>();
customType.Array = var.value<QVector<int>>();
inPropertyHandle->setVar(QVariant::fromValue(customType));
}
);
auto arraySizeHandle = inPropertyHandle->findOrCreateChild(
QMetaType::fromType<unsigned int>(),
"ArraySize",
[inPropertyHandle]() {
return inPropertyHandle->getVar().value<QCustomType>().ArraySize;
},
[inPropertyHandle, arrayHandle](QVariant var) {
QCustomType customType = inPropertyHandle->getVar().value<QCustomType>();
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);
}

View File

@ -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__

View File

@ -0,0 +1,20 @@
#include <QApplication>
#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<QCustomType, PropertyTypeCustomization_CustomType>();
QDetailsView view;
view.setObject(&obj);
view.show();
return app.exec();
}

View File

@ -0,0 +1,27 @@
<RCC>
<qresource prefix="/">
<file>resources/Icon/arrow-left-l.png</file>
<file>resources/Icon/arrow-right-l.png</file>
<file>resources/Icon/close.png</file>
<file>resources/Icon/delete.png</file>
<file>resources/Icon/down.png</file>
<file>resources/Icon/expand.png</file>
<file>resources/Icon/plus.png</file>
<file>resources/Icon/reset.png</file>
<file>resources/Icon/unexpand.png</file>
<file>resources/Icon/up.png</file>
<file>resources/Qml/ValueEditor/ColorBox.qml</file>
<file>resources/Qml/ValueEditor/NumberBox.qml</file>
<file>resources/Qml/ValueEditor/TextComboBox.qml</file>
<file>resources/Qml/ValueEditor/Vec2Box.qml</file>
<file>resources/Qml/ValueEditor/Vec3Box.qml</file>
<file>resources/Qml/ValueEditor/Vec4Box.qml</file>
<file>resources/Qml/ColorPalette/ColorPalette.qml</file>
<file>resources/Qml/ColorPalette/ColorPalette_Dark.qml</file>
<file>resources/Qml/ColorPalette/ColorPalette_Light.qml</file>
<file>resources/Qml/ValueEditor/DirectorySelector.qml</file>
<file>resources/Qml/ValueEditor/FileSelector.qml</file>
<file>resources/Qml/ValueEditor/LineTextBox.qml</file>
<file>resources/Qml/ValueEditor/MultiLineTextBox.qml</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,9 @@
import QtQuick
pragma Singleton
QtObject {
id: colorPalette
property var theme : ColorPalette_Light
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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)
}
}
}

View File

@ -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);
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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<min){
number = min
}
}
valueChanged(number)
}
}
Rectangle {
anchors.fill: parent
anchors.margins: 2
border.color: ColorPalette.theme.textBoxBackground
border.width: 1
color: ColorPalette.theme.textBoxBackground
clip: true
Rectangle{
visible: isHovered && isLimited
anchors.fill: parent
anchors.rightMargin: parent.width * (max - number) /(max - min)
color: ColorPalette.theme.boxHover
}
TextInput{
id: input
enabled: false
anchors.fill: parent
anchors.leftMargin: 5
anchors.rightMargin: 5
text: helper.numberToString(number,precision)
color: ColorPalette.theme.textPrimary
verticalAlignment: Text.AlignVCenter
validator: DoubleValidator{}
onEditingFinished:{
enabled = false
dragArea.visible = true
dragArea.cursorShape = Qt.SplitHCursor
var newVar = parseFloat(input.text)
if(isNaN(newVar)){
text = helper.numberToString(number,precision)
}
else{
control.setNumber(parseFloat(input.text))
}
}
}
MouseArea{
id: dragArea
property real lastPressX : -1
property real lastPressY : -1
anchors.fill: parent
hoverEnabled : true
cursorShape: Qt.SplitHCursor
onDoubleClicked: {
cursorShape = Qt.ArrowCursor
input.enabled = true
dragArea.visible = false
input.forceActiveFocus()
input.selectAll()
}
onClicked: {
input.forceActiveFocus()
}
onEntered:{
isHovered = true
exitAnimation.stop()
enterAnimation.start()
}
onExited:{
isHovered = false
if(!control.isMousePressed){
enterAnimation.stop()
exitAnimation.start()
}
}
onPressed:
(mouse)=>{
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
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@ -0,0 +1,18 @@
#ifndef IPropertyTypeCustomization_h__
#define IPropertyTypeCustomization_h__
#include <QSharedPointer>
#include "QDetailsViewAPI.h"
class QPropertyHandle;
class QQuickDetailsViewRowBuilder;
class QQuickDetailsViewLayoutBuilder;
class QDETAILS_VIEW_API IPropertyTypeCustomization :public QEnableSharedFromThis<IPropertyTypeCustomization>
{
public:
virtual void customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder);
virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder);
};
#endif // IPropertyTypeCustomization_h__

View File

@ -0,0 +1,33 @@
#ifndef IPropertyHandleImpl_h__
#define IPropertyHandleImpl_h__
#include <QQmlContext>
#include <QQuickItem>
#include <QVariant>
#include <QObject>
#include "QDetailsViewAPI.h"
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__

View File

@ -0,0 +1,27 @@
#ifndef QAssociativePropertyHandle_h__
#define QAssociativePropertyHandle_h__
#include "QMetaContainer"
#include "IPropertyHandleImpl.h"
class QDETAILS_VIEW_API 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__

View File

@ -0,0 +1,20 @@
#ifndef QPropertyHandleImpl_Enum_h__
#define QPropertyHandleImpl_Enum_h__
#include "IPropertyHandleImpl.h"
class QDETAILS_VIEW_API QPropertyHandleImpl_Enum: public IPropertyHandleImpl {
public:
QPropertyHandleImpl_Enum(QPropertyHandle* inHandle);
protected:
QQuickItem* createValueEditor(QQuickItem* inParent) override;
Type type() override { return Type::Enum; };
private:
QHash<QString, int> mNameToValueMap;
QList<QString> mKeys;
};
#endif // QPropertyHandleImpl_Enum_h__

View File

@ -0,0 +1,27 @@
#ifndef QPropertyHandleImpl_Object_h__
#define QPropertyHandleImpl_Object_h__
#include "IPropertyHandleImpl.h"
class QDETAILS_VIEW_API 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__

View File

@ -0,0 +1,14 @@
#ifndef QPropertyHandleImpl_RawType_h__
#define QPropertyHandleImpl_RawType_h__
#include "IPropertyHandleImpl.h"
class QDETAILS_VIEW_API 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__

View File

@ -0,0 +1,26 @@
#ifndef QSequentialPropertyHandle_h__
#define QSequentialPropertyHandle_h__
#include "QMetaContainer"
#include "IPropertyHandleImpl.h"
class QDETAILS_VIEW_API 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__

View File

@ -0,0 +1,20 @@
#ifndef QDetailsView_h__
#define QDetailsView_h__
#include <QWidget>
#include <QQuickWidget>
#include "QQuickDetailsView.h"
class QDETAILS_VIEW_API 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__

View File

@ -0,0 +1,21 @@
#ifndef QDETAILS_VIEW_API_H
#define QDETAILS_VIEW_API_H
#include <QtCore/qglobal.h>
#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

View File

@ -0,0 +1,108 @@
#ifndef QPropertyHandle_h__
#define QPropertyHandle_h__
#include <QObject>
#include <QQuickItem>
#include "PropertyHandleImpl/QPropertyHandleImpl_Enum.h"
#include "PropertyHandleImpl/QPropertyHandleImpl_Object.h"
#include "PropertyHandleImpl/QPropertyHandleImpl_Associative.h"
#include "PropertyHandleImpl/QPropertyHandleImpl_Sequential.h"
class IPropertyHandleImpl;
class QDETAILS_VIEW_API QPropertyHandle : public QObject {
Q_OBJECT
Q_PROPERTY(QVariant Var READ getVar WRITE setVar NOTIFY asVarChanged)
public:
using Getter = std::function<QVariant()>;
using Setter = std::function<void(QVariant)>;
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<IPropertyHandleImpl> mImpl;
};
struct QDETAILS_VIEW_API ExternalRefCountWithMetaType : public QtSharedPointer::ExternalRefCountData {
typedef ExternalRefCountData Parent;
QMetaType mMetaType;
void* mData;
static void deleter(ExternalRefCountData* self) {
ExternalRefCountWithMetaType* that =
static_cast<ExternalRefCountWithMetaType*>(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<ExternalRefCountWithMetaType*>(::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__

View File

@ -0,0 +1,28 @@
#ifndef QQuickDetailsView_h__
#define QQuickDetailsView_h__
#include "QQuickTreeViewEx.h"
class QQuickDetailsViewPrivate;
class QDETAILS_VIEW_API 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__

View File

@ -0,0 +1,36 @@
#ifndef QQuickDetailsViewLayoutBuilder_h__
#define QQuickDetailsViewLayoutBuilder_h__
#include "QQuickDetailsViewRow.h"
class QDETAILS_VIEW_API QQuickDetailsViewRowBuilder {
public:
QQuickDetailsViewRowBuilder(IDetailsViewRow* inRow, QQuickItem* inRootItem);
QPair<QQuickItem*, QQuickItem*> 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 QDETAILS_VIEW_API QQuickDetailsViewLayoutBuilder {
public:
QQuickDetailsViewLayoutBuilder(IDetailsViewRow* inRootRow);
IDetailsViewRow* row() const;
void addCustomRow(std::function<void(QQuickDetailsViewRowBuilder*)> inCustomRowCreator, QString inOverrideName = "");
void addProperty(QPropertyHandle* inPropertyHandle, QString inOverrideName = "");
void addObject(QObject* inObject);
private:
IDetailsViewRow* mRootRow = nullptr;
};
#endif // QQuickDetailsViewLayoutBuilder_h__

View File

@ -0,0 +1,58 @@
#ifndef QQuickDetailsViewManager_h__
#define QQuickDetailsViewManager_h__
#include <QObject>
#include <QHash>
#include <functional>
#include <QMetaType>
#include <QQmlEngine>
#include <QQuickItem>
#include "IPropertyTypeCustomization.h"
#include "QDetailsViewAPI.h"
class QPropertyHandle;
class QDETAILS_VIEW_API QQuickDetailsViewManager : public QObject{
public:
using PropertyTypeCustomizationCreator = std::function<QSharedPointer<IPropertyTypeCustomization>()>;
using TypeEditorCreator = std::function<QQuickItem* (QPropertyHandle*, QQuickItem*)>;
static QQuickDetailsViewManager* Get();
void initialize();
bool isInitialized() const;
template<typename MetaType, typename IPropertyTypeCustomizationType>
void registerPropertyTypeCustomization() {
QMetaType metaType = QMetaType::fromType<MetaType>();
if (metaType.metaObject()) {
mClassCustomizationMap.insert(metaType.metaObject(), []() {
return QSharedPointer<IPropertyTypeCustomizationType>::create();
});
}
else {
mMetaTypeCustomizationMap.insert(metaType, []() {
return QSharedPointer<IPropertyTypeCustomizationType>::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<IPropertyTypeCustomization> getCustomPropertyType(QPropertyHandle* inHandle);
protected:
QQuickDetailsViewManager();
void RegisterBasicTypeEditor();
private:
bool mInitialized = false;
QHash<const QMetaObject*, PropertyTypeCustomizationCreator> mClassCustomizationMap;
QHash<QMetaType, PropertyTypeCustomizationCreator> mMetaTypeCustomizationMap;
QHash<QMetaType, TypeEditorCreator> mTypeEditorCreatorMap;
};
#endif // QQuickDetailsViewManager_h__

View File

@ -0,0 +1,44 @@
#ifndef QQuickDetailsViewModel_h__
#define QQuickDetailsViewModel_h__
#include <QObject>
#include <QMap>
#include <QAbstractItemModel>
#include <QMetaProperty>
#include <QQuickItem>
#include "QDetailsViewAPI.h"
class IDetailsViewRow;
class QDetailsViewRow_Property;
class QDETAILS_VIEW_API 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<int, QByteArray> roleNames() const override;
void setObject(QObject* inObject);
QObject* getObject() const;
QModelIndex indexForRow(IDetailsViewRow* row) const;
void updateRowIndex(IDetailsViewRow* row);
private:
QSharedPointer<QDetailsViewRow_Property> mRoot;
QObject* mObject;
};
#endif // QQuickDetailsViewModel_h__

View File

@ -0,0 +1,66 @@
#ifndef QQuickDetailsViewRow_h__
#define QQuickDetailsViewRow_h__
#include <QObject>
#include <QMap>
#include <QAbstractItemModel>
#include <QMetaProperty>
#include <QQuickItem>
#include "QPropertyHandle.h"
#include "IPropertyTypeCustomization.h"
#include "QQuickDetailsViewModel.h"
#include "QDetailsViewAPI.h"
class QDETAILS_VIEW_API 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<IDetailsViewRow> 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<IDetailsViewRow*>(this));
}
void invalidateChildren();
protected:
QString mName;
QQuickDetailsViewModel* mModel = nullptr;
QModelIndex mModelIndex;
IDetailsViewRow* mParent = nullptr;
QList<QSharedPointer<IDetailsViewRow>> mChildren;
};
class QDETAILS_VIEW_API 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<IPropertyTypeCustomization> mPropertyTypeCustomization;
};
class QDETAILS_VIEW_API QDetailsViewRow_Custom : public IDetailsViewRow {
public:
QDetailsViewRow_Custom(std::function<void(QQuickDetailsViewRowBuilder*)> inRowCreator);
protected:
QString name() override { return "Custom"; }
void setupItem(QQuickItem* inParent) override;
private:
std::function<void(QQuickDetailsViewRowBuilder*)> mRowCreator;
};
#endif // QQuickDetailsViewRow_h__

View File

@ -0,0 +1,64 @@
#ifndef QQuickFunctionLibrary_h__
#define QQuickFunctionLibrary_h__
#include <QObject>
#include <QVariant>
#include <QQuickItem>
#include "QDetailsViewAPI.h"
class QQuickLambdaHolder;
class QQuickLambdaHolder_OneParam;
class QDETAILS_VIEW_API 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<void()> callback);
static QMetaObject::Connection connect(QObject* sender, const char* signal, QObject* receiver, std::function<void(QVariant)> callback);
static QMetaObject::Connection connect(QObject* sender, const char* signal, QObject* receiver, std::function<void(QVariant, QVariant)> callback);
};
class QDETAILS_VIEW_API QQuickLambdaHolder : public QObject {
Q_OBJECT
std::function<void()> mCallback;
public:
QQuickLambdaHolder(std::function<void()> callback, QObject* parent)
: QObject(parent)
, mCallback(std::move(callback)) {}
Q_SLOT void call() { mCallback(); }
};
class QDETAILS_VIEW_API QQuickLambdaHolder_OneParam : public QObject {
Q_OBJECT
std::function<void(QVariant)> mCallback;
public:
QQuickLambdaHolder_OneParam(std::function<void(QVariant)> callback, QObject* parent)
: QObject(parent)
, mCallback(std::move(callback)) {
}
Q_SLOT void call(QVariant inParam0) { mCallback(inParam0); }
};
class QDETAILS_VIEW_API QQuickLambdaHolder_TwoParams : public QObject {
Q_OBJECT
std::function<void(QVariant, QVariant)> mCallback;
public:
QQuickLambdaHolder_TwoParams(std::function<void(QVariant, QVariant)> callback, QObject* parent)
: QObject(parent)
, mCallback(std::move(callback)) {
}
Q_SLOT void call(QVariant inParam0, QVariant inParam1) { mCallback(inParam0, inParam1); }
};
#endif // QQuickFunctionLibrary_h__

View File

@ -0,0 +1,53 @@
#ifndef QQuickTreeViewEx_h__
#define QQuickTreeViewEx_h__
#include "QDetailsViewAPI.h"
#include "private/qquicktableview_p.h"
class QQuickTreeViewExPrivate;
class QDETAILS_VIEW_API 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__

View File

@ -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<QAssociativeIterable>();
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<QAssociativeIterable>();
return iterable.value(key);
},
[inPropertyHandle, key, metaAssociation](QVariant var) {
QVariant varMap = inPropertyHandle->getVar();
QAssociativeIterable iterable = varMap.value<QAssociativeIterable>();
QtPrivate::QVariantTypeCoercer keyCoercer;
QtPrivate::QVariantTypeCoercer mappedCoercer;
void* containterPtr = const_cast<void*>(iterable.constIterable());
const void* dataPtr = mappedCoercer.coerce(var, metaAssociation.mappedMetaType());
metaAssociation.setMappedAtKey(containterPtr, keyCoercer.coerce(key, metaAssociation.keyMetaType()), dataPtr);
inPropertyHandle->setVar(varMap);
}
));
}
}

View File

@ -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__

View File

@ -0,0 +1,94 @@
#include "PropertyTypeCustomization_Matrix4x4.h"
#include "QQuickDetailsViewLayoutBuilder.h"
#include "QPropertyHandle.h"
#include <QMetaType>
#include <QObject>
#include <QMatrix4x4>
#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<QMatrix4x4>();
// valueItem->setProperty("value", mat.row(0));
// QQuickFunctionLibrary::connect(valueItem, SIGNAL(asValueChanged(QVariant)), inPropertyHandle, [inPropertyHandle](QVariant var) {
// QMatrix4x4 mat = inPropertyHandle->getVar().value<QMatrix4x4>();
// mat.setRow(0, var.value<QVector4D>());
// inPropertyHandle->setVar(mat);
// });
// QQuickFunctionLibrary::connect(inPropertyHandle, SIGNAL(asRequestRollback(QVariant)), valueItem, [inPropertyHandle, valueItem](QVariant var) {
// QMatrix4x4 mat = var.value<QMatrix4x4>();
// valueItem->setProperty("value", mat.row(0));
// });
//});
inBuilder->addProperty(QPropertyHandle::FindOrCreate(
inPropertyHandle->parent(),
QMetaType::fromType<QVector4D>(),
inPropertyHandle->createSubPath("Row 0"),
[inPropertyHandle]() {
return inPropertyHandle->getVar().value<QMatrix4x4>().row(0);
},
[inPropertyHandle](QVariant var) {
QMatrix4x4 mat = inPropertyHandle->getVar().value<QMatrix4x4>();
mat.setRow(0, var.value<QVector4D>());
inPropertyHandle->setVar(mat);
}
));
inBuilder->addProperty(QPropertyHandle::FindOrCreate(
inPropertyHandle->parent(),
QMetaType::fromType<QVector4D>(),
inPropertyHandle->createSubPath("Row 1"),
[inPropertyHandle]() {
return inPropertyHandle->getVar().value<QMatrix4x4>().row(1);
},
[inPropertyHandle](QVariant var) {
QMatrix4x4 mat = inPropertyHandle->getVar().value<QMatrix4x4>();
mat.setRow(1, var.value<QVector4D>());
inPropertyHandle->setVar(mat);
}
));
inBuilder->addProperty(QPropertyHandle::FindOrCreate(
inPropertyHandle->parent(),
QMetaType::fromType<QVector4D>(),
inPropertyHandle->createSubPath("Row 2"),
[inPropertyHandle]() {
return inPropertyHandle->getVar().value<QMatrix4x4>().row(2);
},
[inPropertyHandle](QVariant var) {
QMatrix4x4 mat = inPropertyHandle->getVar().value<QMatrix4x4>();
mat.setRow(2, var.value<QVector4D>());
inPropertyHandle->setVar(mat);
}
));
inBuilder->addProperty(QPropertyHandle::FindOrCreate(
inPropertyHandle->parent(),
QMetaType::fromType<QVector4D>(),
inPropertyHandle->createSubPath("Row 3"),
[inPropertyHandle]() {
return inPropertyHandle->getVar().value<QMatrix4x4>().row(3);
},
[inPropertyHandle](QVariant var) {
QMatrix4x4 mat = inPropertyHandle->getVar().value<QMatrix4x4>();
mat.setRow(3, var.value<QVector4D>());
inPropertyHandle->setVar(mat);
}
));
}

View File

@ -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__

View File

@ -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
);
}
}
}
}

View File

@ -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__

View File

@ -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<QSequentialIterable>();
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<QSequentialIterable>();
if (index < 0 || index >= iterable.size()) {
return QVariant();
}
return iterable.at(index);
},
[inPropertyHandle, index](QVariant var) {
QVariant varList = inPropertyHandle->getVar();
QSequentialIterable iterable = varList.value<QSequentialIterable>();
if (index >= 0 && index < iterable.size()) {
const QMetaSequence metaSequence = iterable.metaContainer();
void* containterPtr = const_cast<void*>(iterable.constIterable());
QtPrivate::QVariantTypeCoercer coercer;
const void* dataPtr = coercer.coerce(var, iterable.valueMetaType());
metaSequence.setValueAtIndex(containterPtr, index, dataPtr);
inPropertyHandle->setVar(varList);
}
}
));
}
}

View File

@ -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__

View File

@ -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)
{
}

View File

@ -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<QQuickItem*>(nameComp.createWithInitialProperties(initialProperties, context));
nameEditor->setParentItem(inParent);
return nameEditor;
}

View File

@ -0,0 +1,72 @@
#include "PropertyHandleImpl/QPropertyHandleImpl_Associative.h"
#include <QAssociativeIterable>
#include "QBoxLayout"
#include "QPropertyHandle.h"
QPropertyHandleImpl_Associative::QPropertyHandleImpl_Associative(QPropertyHandle* inHandle)
:IPropertyHandleImpl(inHandle) {
QVariant varMap = mHandle->getVar();
QAssociativeIterable iterable = varMap.value<QAssociativeIterable>();
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<QAssociativeIterable>();
if (iterable.containsKey(inSrc) && !iterable.containsKey(inDst)) {
canRename = true;
QVariant var = iterable.value(inSrc);
QtPrivate::QVariantTypeCoercer keyCoercer;
QtPrivate::QVariantTypeCoercer mappedCoercer;
void* containterPtr = const_cast<void*>(iterable.constIterable());
QMetaAssociation metaAssociation = iterable.metaContainer();
metaAssociation.removeKey(containterPtr, keyCoercer.coerce(inSrc, QMetaType::fromType<QString>()));
metaAssociation.setMappedAtKey(
containterPtr,
keyCoercer.coerce(inDst, QMetaType::fromType<QString>()),
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<QAssociativeIterable>();
void* containterPtr = const_cast<void*>(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<QAssociativeIterable>();
const QMetaAssociation metaAssociation = iterable.metaContainer();
void* containterPtr = const_cast<void*>(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();
}

View File

@ -0,0 +1,46 @@
#include "PropertyHandleImpl/QPropertyHandleImpl_Enum.h"
#include <QComboBox>
#include <QMetaEnum>
#include <QWidget>
#include <QBoxLayout>
#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<QQuickItem*>(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;
}

View File

@ -0,0 +1,98 @@
#include "PropertyHandleImpl/QPropertyHandleImpl_Object.h"
#include <qsequentialiterable.h>
#include <QRegularExpression>
#include "QPropertyHandle.h"
#include <QMetaProperty>
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<QObject*>();
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;
}

View File

@ -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);
}

View File

@ -0,0 +1,69 @@
#include "PropertyHandleImpl/QPropertyHandleImpl_Sequential.h"
#include <qsequentialiterable.h>
#include "QBoxLayout"
#include "QPropertyHandle.h"
QPropertyHandleImpl_Sequential::QPropertyHandleImpl_Sequential(QPropertyHandle* inHandle)
:IPropertyHandleImpl(inHandle) {
QVariant varList = mHandle->getVar();
QSequentialIterable iterable = varList.value<QSequentialIterable>();
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<QSequentialIterable>();
return iterable.size();
}
void QPropertyHandleImpl_Sequential::appendItem( QVariant InVar) {
QVariant varList = mHandle->getVar();
QSequentialIterable iterable = varList.value<QSequentialIterable>();
const QMetaSequence metaSequence = iterable.metaContainer();
void* containterPtr = const_cast<void*>(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<QSequentialIterable>();
const QMetaSequence metaSequence = iterable.metaContainer();
void* containterPtr = const_cast<void*>(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<QSequentialIterable>();
const QMetaSequence metaSequence = iterable.metaContainer();
void* containterPtr = const_cast<void*>(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();
}

View File

@ -0,0 +1,86 @@
#include "QDetailsView.h"
#include <QVBoxLayout>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QQuickItem>
#include <QUrl>
#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<QQuickItem*>(rootObject);
if (rootItem) {
mQuickWidget->setContent(QUrl(), &component, rootItem);
mQuickDetailsView = qobject_cast<QQuickDetailsView*>(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<QObject*>();
}
return nullptr;
}

View File

@ -0,0 +1,322 @@
#include "QPropertyHandle.h"
#include <QRegularExpression>
#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<QPropertyHandle*>(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<QPropertyHandle*>(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<QObject*>();
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<QPropertyHandleImpl_Enum*>(mImpl.get());
}
QPropertyHandleImpl_Object* QPropertyHandle::asObject()
{
return static_cast<QPropertyHandleImpl_Object*>(mImpl.get());
}
QPropertyHandleImpl_Associative* QPropertyHandle::asAssociative()
{
return static_cast<QPropertyHandleImpl_Associative*>(mImpl.get());
}
QPropertyHandleImpl_Sequential* QPropertyHandle::asSequential()
{
return static_cast<QPropertyHandleImpl_Sequential*>(mImpl.get());
}
QPropertyHandle::PropertyType QPropertyHandle::parserType(QMetaType inType)
{
if (QMetaType::canConvert(inType, QMetaType::fromType<QVariantList>())
&& !QMetaType::canConvert(inType, QMetaType::fromType<QString>())
) {
return Sequential;
}
else if (QMetaType::canConvert(inType, QMetaType::fromType<QVariantMap>())) {
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);
}

View File

@ -0,0 +1,86 @@
#include "QQuickDetailsView.h"
#include "private/qqmldata_p.h"
#include <QQmlEngine>
#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<QQuickItem*>(object);
if (!item)
return;
const QModelIndex& index = m_treeModelToTableModel.mapToModel(serializedModelIndex);;
IDetailsViewRow* node = static_cast<IDetailsViewRow*>(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();
}

View File

@ -0,0 +1,238 @@
#include "QQuickDetailsViewMananger.h"
#include "QPropertyHandle.h"
#include "QQuickFunctionLibrary.h"
#include <QRegularExpression>
#include <QDir>
#include <QMetaType>
#define REGINTER_NUMBER_EDITOR_CREATOR(TypeName, DefaultPrecision) \
registerTypeEditor(QMetaType::fromType<TypeName>(), [](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<QQuickItem*>(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<TypeName>::min(); \
max = std::numeric_limits<TypeName>::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<QString>(), [](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<QQuickItem*>(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<QVector4D>(), [](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<QQuickItem*>(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<QVector3D>(), [](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<QQuickItem*>(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<QVector2D>(), [](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<QQuickItem*>(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<QColor>(), [](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<QQuickItem*>(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<const QDir*>(src);
QString& str = *static_cast<QString*>(target);
str = dir.absolutePath();
return true;
},
QMetaType::fromType<QDir>(),
QMetaType::fromType<QString>()
);
QMetaType::registerConverterFunction(
[](const void* src, void* target) -> bool {
const QString& str = *static_cast<const QString*>(src);
QDir& dir = *static_cast<QDir*>(target);
dir = QDir(str);
return true;
},
QMetaType::fromType<QString>(),
QMetaType::fromType<QDir>()
);
registerTypeEditor(QMetaType::fromType<QDir>(), [](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<QQuickItem*>(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;
});
}

View File

@ -0,0 +1,216 @@
#include "QQuickDetailsViewLayoutBuilder.h"
#include "QQuickDetailsViewMananger.h"
QQuickDetailsViewRowBuilder::QQuickDetailsViewRowBuilder(IDetailsViewRow* inRow, QQuickItem* inRootItem)
: mRow(inRow)
, mRootItem(inRootItem)
{
setHeightProxy(nullptr);
}
QPair<QQuickItem*, QQuickItem*> 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<QQuickItem*>(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<QQuickItem*>(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<QQuickItem*, QQuickItem*> 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<void(QQuickDetailsViewRowBuilder*)> creator, QString inOverrideName)
{
QSharedPointer<IDetailsViewRow> child(new QDetailsViewRow_Custom(creator));
child->setName(inOverrideName);
mRootRow->addChild(child);
child->attachChildren();
}
void QQuickDetailsViewLayoutBuilder::addProperty(QPropertyHandle* inPropertyHandle, QString inOverrideName)
{
QSharedPointer<IDetailsViewRow> 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));
}

View File

@ -0,0 +1,138 @@
#include "QQuickDetailsViewMananger.h"
#include "QPropertyHandle.h"
#include "QQuickFunctionLibrary.h"
#include <QRegularExpression>
#include "QQuickDetailsView.h"
#include "Customization/PropertyTypeCustomization_Sequential.h"
#include "Customization/PropertyTypeCustomization_Associative.h"
#include "Customization/PropertyTypeCustomization_ObjectDefault.h"
#include "Customization/PropertyTypeCustomization_Matrix4x4.h"
#include <QtQuickControls2/QQuickStyle>
#include <QMatrix4x4>
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<QQuickDetailsView>("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<IPropertyTypeCustomization> QQuickDetailsViewManager::getCustomPropertyType(QPropertyHandle* inHandle)
{
if (inHandle->getPropertyType() == QPropertyHandle::Sequential) {
return QSharedPointer<PropertyTypeCustomization_Sequential>::create();
}
else if (inHandle->getPropertyType() == QPropertyHandle::Associative) {
return QSharedPointer<PropertyTypeCustomization_Associative>::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<PropertyTypeCustomization_ObjectDefault>::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<QMatrix4x4, PropertyTypeCustomization_Matrix4x4>();
}

View File

@ -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<IDetailsViewRow*>(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<IDetailsViewRow*>(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<IDetailsViewRow*>(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<IDetailsViewRow*>(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<int, QByteArray> QQuickDetailsViewModel::roleNames() const {
return {
{ Roles::name,"name" },
};
}

View File

@ -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<QObject*> 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__

View File

@ -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<IDetailsViewRow> 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<void(QQuickDetailsViewRowBuilder*)> inRowCreator)
: mRowCreator(inRowCreator)
{
}
void QDetailsViewRow_Custom::setupItem(QQuickItem* inParent)
{
if (mRowCreator) {
QQuickDetailsViewRowBuilder builder(this, inParent);
mRowCreator(&builder);
}
}

View File

@ -0,0 +1,58 @@
#include "QQuickFunctionLibrary.h"
#include <QCursor>
#include <QApplication>
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<void()> 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<void(QVariant)> 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<void(QVariant, QVariant)> callback)
{
if (!sender)
return {};
return QObject::connect(sender, signal, new QQuickLambdaHolder_TwoParams(callback, receiver ? receiver : sender), SLOT(call(QVariant, QVariant)));
}

View File

@ -0,0 +1,480 @@
#include "QQuickTreeViewEx.h"
#include "QQuickTreeViewExPrivate.h"
#include <QtCore/qobject.h>
#include <QtQml/qqmlcontext.h>
#include <QtQuick/private/qquicktaphandler_p.h>
#include <QtQmlModels/private/qqmltreemodeltotablemodel_p_p.h>
// 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<QJSValue>())
effectiveModel = effectiveModel.value<QJSValue>().toVariant();
if (effectiveModel.isNull())
m_treeModelToTableModel.setModel(nullptr);
else if (const auto qaim = qvariant_cast<QAbstractItemModel*>(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<int>& 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);
}

View File

@ -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<int>& 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__

151
QtADS/.appveyor.yml Normal file
View File

@ -0,0 +1,151 @@
version: '{build}'
branches:
only:
- master
image: Visual Studio 2017
environment:
global:
# Appveyor doesn't have Qt 12 yet
LatestQtVersion: 5.13
matrix:
# 32 bit builds
# MSVC 2015 builds
# Dynamic Library builds
# LTS version of Qt, dll, 32bit, MSVC 2015, qmake
- QT5: C:\Qt\%LatestQtVersion%\msvc2017
COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build
targetPlatform: x86
use_mingw: "false"
use_static: "false"
use_cmake: "false"
# LTS version of Qt, dll, 32bit, MSVC 2015, cmake
- QT5: C:\Qt\%LatestQtVersion%\msvc2017
COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build
targetPlatform: x86
use_mingw: "false"
use_static: "false"
use_cmake: "true"
# end Dynamic Library builds
# Static Library builds
# LTS version of Qt, static, 32bit, MSVC 2015, qmake
- QT5: C:\Qt\%LatestQtVersion%\msvc2017
COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build
targetPlatform: x86
use_mingw: "false"
use_static: "true"
use_cmake: "false"
# LTS version of Qt, static, 32bit, MSVC 2015, cmake
- QT5: C:\Qt\%LatestQtVersion%\msvc2017
COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build
targetPlatform: x86
use_mingw: "false"
use_static: "true"
use_cmake: "true"
# end Static Library builds
# end MSVC 2015 builds
# MinGW builds
# Dynamic Library builds
# LTS version of Qt, dll, 32bit, MinGW, qmake
- QT5: C:\Qt\%LatestQtVersion%\mingw73_32
COMPILER: C:\Qt\Tools\mingw730_32
targetPlatform: x86
use_mingw: "true"
use_static: "false"
use_cmake: "false"
# LTS version of Qt, dll, 32bit, MinGW, cmake
- QT5: C:\Qt\%LatestQtVersion%\mingw73_32
COMPILER: C:\Qt\Tools\mingw730_32
targetPlatform: x86
use_mingw: "true"
use_static: "false"
use_cmake: "true"
# end Dynamic Library builds
# Static Library builds
# LTS version of Qt, static, 32bit, MinGW, qmake
- QT5: C:\Qt\%LatestQtVersion%\mingw73_32
COMPILER: C:\Qt\Tools\mingw730_32
targetPlatform: x86
use_mingw: "true"
use_static: "true"
use_cmake: "false"
# LTS version of Qt, static, 32bit, MinGW, cmake
- QT5: C:\Qt\%LatestQtVersion%\mingw73_32
COMPILER: C:\Qt\Tools\mingw730_32
targetPlatform: x86
use_mingw: "true"
use_static: "true"
use_cmake: "true"
# end Static Library builds
# end MinGW builds
# end 32 bit builds
# 64 bit builds
# MSVC 2015 builds
# Dynamic Library builds
# LTS version of Qt, dll, 64bit, MSVC 2015, qmake
- QT5: C:\Qt\%LatestQtVersion%\msvc2017_64
COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build
targetPlatform: amd64
use_mingw: "false"
use_static: "false"
use_cmake: "false"
# LTS version of Qt, dll, 64bit, MSVC 2015, cmake
- QT5: C:\Qt\%LatestQtVersion%\msvc2017_64
COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build
targetPlatform: amd64
use_mingw: "false"
use_static: "false"
use_cmake: "true"
# end Dynamic Library builds
# Static Library builds
# LTS version of Qt, static, 64bit, MSVC 2015, qmake
- QT5: C:\Qt\%LatestQtVersion%\msvc2017_64
COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build
targetPlatform: amd64
use_mingw: "false"
use_static: "true"
use_cmake: "false"
# LTS version of Qt, static, 64bit, MSVC 2015, cmake
- QT5: C:\Qt\%LatestQtVersion%\msvc2017_64
COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build
targetPlatform: amd64
use_mingw: "false"
use_static: "true"
use_cmake: "true"
# end Static Library builds
# end MSVC 2015 builds
# end 64 bit builds
matrix:
fast_finish: true
before_build:
- set originalWD=%CD%
- call "%QT5%\bin\qtenv2.bat"
- cd /D %originalWD%
- if %use_mingw%==false call "%COMPILER%\vcvarsall.bat" %targetPlatform%
- if %use_static%==true (set USESTATIC=ON) else (set USESTATIC=OFF)
- if %use_mingw%==true (set CMAKEGENERATOR="MinGW Makefiles") else (set CMAKEGENERATOR="NMake Makefiles")
- if %use_mingw%==true (set MAKEENGINE=mingw32-make) else (set MAKEENGINE=nmake)
- if %use_mingw%==true set PATH=%PATH:C:\Program Files\Git\usr\bin;=%
build_script:
- if %use_cmake%==true mkdir build
- if %use_cmake%==true cd build
- if %use_cmake%==true cmake --version
- if %use_cmake%==true cmake -G %CMAKEGENERATOR% -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_EXAMPLES=ON -DCMAKE_DEBUG_POSTFIX=d -DBUILD_STATIC=%USESTATIC% -DCMAKE_INSTALL_PREFIX="./installed" ../
- if %use_cmake%==true cmake --build .
- if %use_cmake%==true cmake --build . --target install
- if %use_cmake%==true cmake -G %CMAKEGENERATOR% -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_EXAMPLES=ON -DBUILD_STATIC=%USESTATIC% -DCMAKE_INSTALL_PREFIX="./installed" ../
- if %use_cmake%==true cmake --build .
- if %use_cmake%==true cmake --build . --target install
- if %use_cmake%==false if %use_static%==true qmake "CONFIG+=adsBuildStatic"
- if %use_cmake%==false if %use_static%==false qmake
- if %use_cmake%==false %MAKEENGINE% debug
- if %use_cmake%==false %MAKEENGINE% install
- if %use_cmake%==false %MAKEENGINE% release
- if %use_cmake%==false %MAKEENGINE% install
after_build:
- if %use_mingw%==true set PATH=C:\Program Files\Git\usr\bin;%PATH%

146
QtADS/.clang-format Normal file
View File

@ -0,0 +1,146 @@
---
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: Inline
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: true
BeforeElse: true
BeforeLambdaBody: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: AfterColon
BreakStringLiterals: true
ColumnLimit: 82
CommentPragmas: '^(!.*|@c)'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
FixNamespaceComments: true
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<windows(\.h)?>' # windows headers
Priority: -1
- Regex: '^<labbcan[[:alnum:]._/]+' # labbcan headers
Priority: 1
- Regex: '^<diag[[:alnum:]._/]+' # usl headers
Priority: 1
- Regex: '^<usl[[:alnum:]._/]+' # usl headers
Priority: 1
- Regex: '^<qt5compat[[:alnum:]._/]+' # qtlabb headers
Priority: 2
- Regex: '^<qtcoreaddons[[:alnum:]._/]+' # qtlabb headers
Priority: 2
- Regex: '^<qtlabb[[:alnum:]._/]+' # qtlabb headers
Priority: 2
- Regex: '<Q[[:alnum:].]+>' # Qt headers
Priority: 3
- Regex: '^<sila_cpp[[:alnum:]._/]+' # sila_cpp headers
Priority: 4
- Regex: '<[[:alnum:]._/]+\.h>' # other headers
Priority: 5
- Regex: '<[[:alnum:]._/]+>' # system headers
Priority: 6
- Regex: '.*'
Priority: 7
IncludeIsMainRegex: '(_p)?$'
IndentCaseBlocks: false
IndentCaseLabels: false
IndentPPDirectives: AfterHash
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 20
PenaltyBreakBeforeFirstCallParameter: 15
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 5
# PenaltyBreakOpenParenthesis: 30
PenaltyBreakString: 150
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 100
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 150
PointerAlignment: Left
ReflowComments: true
SortIncludes: CaseSensitive
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++17
StatementMacros: [ 'Q_UNUSED', 'PIMPL_D', 'PIMPL_Q', 'OD_ENTRY', 'OD_ENTRY_PROCIMG' ]
TabWidth: 4
UseTab: Never

1
QtADS/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
PyQtAds/_version.py export-subst

389
QtADS/.gitignore vendored Normal file
View File

@ -0,0 +1,389 @@
*.pro.user*
/build
*.o
*.dylib
*.app
qrc_*
moc_*
ui_*
Makefile
*.dll
*.a
build-*
# IDEs
.idea
# Python
.eggs
*.pyc
*.pyd
__pycache__
PyQtAds/rc.py
/.cproject
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*[.json, .xml, .info]
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/ build
/Settings.ini
.vscode/settings.json
/.settings

27
QtADS/.project Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>QtAdvancedDockingSystem</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.core.ccnature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
</natures>
</projectDescription>

233
QtADS/.travis.yml Normal file
View File

@ -0,0 +1,233 @@
language: cpp
# gcc is clang on mac
compiler: gcc
matrix:
fast_finish: true
include:
- name: Ubuntu qmake Qt5.5.1
os: linux
dist: trusty
group: stable
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:beineri/opt-qt551-trusty'
update: true
packages:
- qt55base
- qt55tools
- gcc-9
- g++-9
script:
- PATH="/opt/qt55/bin:$PATH"
- CXX="g++-9"
- CC="gcc-9"
- qt55-env.sh
- qmake
- make
- make install
- name: Ubuntu qmake dll
os: linux
dist: bionic
group: stable
services:
- xvfb
compiler: gcc
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic'
update: true
packages:
- qt514base
- qt514tools
- gcc-9
- g++-9
- libc6-i386
- libgl-dev
- libgl1-mesa-dev
- mesa-common-dev
script:
- PATH="/opt/qt514/bin:$PATH"
- CXX="g++-9"
- CC="gcc-9"
- qt514-env.sh
- qmake
- make
- make install
- name: Ubuntu qmake static
os: linux
dist: bionic
group: stable
services:
- xvfb
compiler: gcc
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic'
update: true
packages:
- qt514base
- qt514tools
- gcc-9
- g++-9
- libc6-i386
- libgl-dev
- libgl1-mesa-dev
- mesa-common-dev
script:
- PATH="/opt/qt514/bin:$PATH"
- CXX="g++-9"
- CC="gcc-9"
- qt514-env.sh
- qmake "CONFIG+=adsBuildStatic"
- make
- make install
- name: Ubuntu CMake dll
os: linux
dist: bionic
group: stable
services:
- xvfb
compiler: gcc
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic'
update: true
packages:
- qt514base
- qt514tools
- gcc-9
- g++-9
- libc6-i386
- libgl-dev
- libgl1-mesa-dev
- mesa-common-dev
script:
- PATH="/opt/qt514/bin:$PATH"
- CXX="g++-9"
- CC="gcc-9"
- qt514-env.sh
- mkdir ./build
- cd ./build
- cmake --version
- cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_STATIC=OFF -DBUILD_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX="./installed" ../
- cmake --build .
- cmake --build . --target install
- cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_STATIC=OFF -DBUILD_EXAMPLES=ON -DCMAKE_DEBUG_POSTFIX=d -DCMAKE_INSTALL_PREFIX="./installed" ../
- cmake --build .
- cmake --build . --target install
- name: Ubuntu CMake Static
os: linux
dist: bionic
group: stable
services:
- xvfb
compiler: gcc
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic'
update: true
packages:
- qt514base
- qt514tools
- gcc-9
- g++-9
- libc6-i386
- libgl-dev
- libgl1-mesa-dev
- mesa-common-dev
script:
- PATH="/opt/qt514/bin:$PATH"
- CXX="g++-9"
- CC="gcc-9"
- qt514-env.sh
- mkdir ./build
- cd ./build
- cmake --version
- cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_STATIC=ON -DBUILD_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX="./installed" ../
- cmake --build .
- cmake --build . --target install
- cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_STATIC=ON -DBUILD_EXAMPLES=ON -DCMAKE_DEBUG_POSTFIX=d -DCMAKE_INSTALL_PREFIX="./installed" ../
- cmake --build .
- cmake --build . --target install
- name: macOS CMake dll
os: osx
osx_image: xcode11.3
compiler: clang
addons:
homebrew:
packages:
- qt
update: true
script:
- PATH="/usr/local/opt/qt5/bin:$PATH"
- mkdir -p build
- cd build
- cmake --version
- cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_DEBUG_POSTFIX=_debug -DBUILD_EXAMPLES=ON -DBUILD_STATIC=OFF -DCMAKE_INSTALL_PREFIX="./installed" ../
- cmake --build .
- cmake --build . --target install
- cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_EXAMPLES=ON -DBUILD_STATIC=OFF -DCMAKE_INSTALL_PREFIX="./installed" ../
- cmake --build .
- cmake --build . --target install
- name: macOS CMake static
os: osx
osx_image: xcode11.3
compiler: clang
addons:
homebrew:
packages:
- qt
update: true
script:
- PATH="/usr/local/opt/qt5/bin:$PATH"
- mkdir -p build
- cd build
- cmake --version
- cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_DEBUG_POSTFIX=_debug -DBUILD_EXAMPLES=ON -DBUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX="./installed" ../
- cmake --build .
- cmake --build . --target install
- cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_EXAMPLES=ON -DBUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX="./installed" ../
- cmake --build .
- cmake --build . --target install
- name: macOS qmake dll
os: osx
osx_image: xcode11.3
compiler: clang
addons:
homebrew:
packages:
- qt
update: true
script:
- PATH="/usr/local/opt/qt5/bin:$PATH"
- qmake
- make
- make install
- name: macOS qmake static
os: osx
osx_image: xcode11.3
compiler: clang
addons:
homebrew:
packages:
- qt
update: true
script:
- PATH="/usr/local/opt/qt5/bin:$PATH"
- qmake "CONFIG+=adsBuildStatic"
- make
- make install
notifications:
email: false

58
QtADS/CMakeLists.txt Normal file
View File

@ -0,0 +1,58 @@
cmake_minimum_required(VERSION 3.5...4.3)
if (POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif (POLICY CMP0091)
# By default, the version information is extracted from the git index. However,
# we can override this behavior by explicitly setting ADS_VERSION and
# skipping the git checks. This is useful for cases where this project is being
# used independently of its original git repo (e.g. vendored in another project)
if(NOT ADS_VERSION)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH})
include(GetGitRevisionDescription)
git_describe(GitTagVersion --tags)
string(REGEX REPLACE "^([0-9]+)\\..*" "\\1" VERSION_MAJOR "${GitTagVersion}")
string(REGEX REPLACE "^[0-9]+\\.([0-9]+).*" "\\1" VERSION_MINOR "${GitTagVersion}")
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" VERSION_PATCH "${GitTagVersion}")
set(VERSION_SHORT "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
else()
string(REGEX MATCHALL "[\.]" VERSION_DOT_MATCHES ${ADS_VERSION})
list(LENGTH VERSION_DOT_MATCHES VERSION_DOT_COUNT)
if(VERSION_DOT_COUNT EQUAL 2)
set(VERSION_SHORT ${ADS_VERSION})
else()
message(FATAL_ERROR "ADS_VERSION must be in major.minor.patch format, e.g. 3.8.1. Got ${ADS_VERSION}")
endif()
endif()
project(QtADS LANGUAGES CXX VERSION ${VERSION_SHORT})
option(BUILD_STATIC "Build the static library" OFF)
option(BUILD_EXAMPLES "Build the examples" ON)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
set(ads_PlatformDir "x86")
else()
if(DEFINED CMAKE_SYSTEM_PROCESSOR)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
set(ads_PlatformDir "x64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|ARM64")
set(ads_PlatformDir "arm64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64")
set(ads_PlatformDir "aarch64")
else()
set(ads_PlatformDir "x64")
endif()
else()
set(ads_PlatformDir "x64")
endif()
endif()
add_subdirectory(src)
if(BUILD_EXAMPLES)
add_subdirectory(examples)
add_subdirectory(demo)
endif()

504
QtADS/LICENSE Normal file
View File

@ -0,0 +1,504 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random
Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

2
QtADS/MANIFEST.in Normal file
View File

@ -0,0 +1,2 @@
include versioneer.py
include PyQtAds/_version.py

717
QtADS/README.md Normal file
View File

@ -0,0 +1,717 @@
![ukraine](doc/ukraine.jpg)
![logo](doc/ads_logo.svg)
------------------
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/githubuser0xFFFF/Qt-Advanced-Docking-System?color=%23ff9833)](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest)
[![License: LGPL v2.1](https://img.shields.io/badge/License-LGPL%20v2.1-blue.svg)](gnu-lgpl-v2.1.md)
[![Build status](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/workflows/linux-builds/badge.svg)](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/actions?query=workflow%3Alinux-builds)
[![Build status](https://ci.appveyor.com/api/projects/status/qcfb3cy932jw9mpy/branch/master?svg=true)](https://ci.appveyor.com/project/githubuser0xFFFF/qt-advanced-docking-system/branch/master)
[![GitHub contributors](https://img.shields.io/github/contributors/githubuser0xFFFF/Qt-Advanced-Docking-System?color=ffdf00)](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/graphs/contributors)
Qt Advanced Docking System lets you create customizable layouts using a full
featured window docking system similar to what is found in many popular
integrated development environments (IDEs) such as Visual Studio.
- [What's new...](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest)
- [Documentation](doc/user-guide.md)
- Original Repository: https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System
[![Video Advanced Docking](doc/advanced-docking_video.png)](https://www.youtube.com/watch?v=7pdNfafg3Qc)
## New and Noteworthy
Release [4.1](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest) significantly improves the Auto-Hide functionality and also brings improvements
for Drag and Drop of dock widgets into dock area tabs. These are the highlights of the new version:
#### Drag & Drop to Auto-Hide
Now you can easily drag any dock widget or any floating widget to the
borders of a window to pin it as a auto-hide tab in one of the 4 sidebars.
If you drag a dock widget close the one of the four window borders, special
drop overlays will be shown to indicate the drop area for auto-hide widgets:
![Auo-Hide drag to Sidebar](doc/AutoHide_Drag_to_Sidebar.gif)
Of course, this also works with dock areas:
![Auo-Hide drag Dock Area](doc/AutoHide_Drag_DockArea.gif)
If you drag a dock widget or dock area into a sidebar, then you even have
control over where tabs are inserted. Simply drag your mouse over a specific
auto-hide tab, and your dragged dock widget will be inserted before this tab.
Drag to the sidebar area behind the last tab, and the dragged widget will be
appended as last tab. In the following screen capture, the **Image Viewer 1** will
be inserted before the **Table 0** Auto-Hide tab and the **Image Viewer 2**
is appende behind the last tab:
![Auo-Hide tab insert order](doc/AutoHide_Tab_Insert_Order.gif)
#### Auto-Hide Tab Insertion Order
It is also possible to drag Auto-Hide tabs to a new auto-hide position.
That means, you can drag them to a different border or sidebar:
![Auto-Hide change sidebar](doc/AutoHide_Change_Sidebar.gif)
#### Auto-Hide Tab Sorting
You can drag Auto-Hide tabs to a new position in the current sidebar
to sort them:
![Auo-Hide sort tabs](doc/AutoHide_Sort_Tabs.gif)
#### Auto-Hide Drag to Float / Dock
But that is not all. You can also simply move Auto-Hide tabs to another
floating widget or dock them via drag and drop:
![Auo-Hide drag to float or dock](doc/AutoHide_Drag_to_Float_or_Dock.gif)
#### Auto-Hide Context Menu
All Auto-Hide tabs now have a context menu, that provides all the functionality
that you know from Dock widget tabs. With the **Pin To...** item from the
context menu it is very easy to move an Auto-Hide tab to a different Auto-Hide
sidebar:
![Auo-Hide context menu](doc/AutoHide_Context_Menu.png)
#### Dock Area Tab Insert Order
And last but not least the new version also improves the docking of widgets
into the tabs of a Dock area. Just as with Auto-Hide tabs, you can now determine the position at which a tab is inserted by moving the mouse over an already existing tab (insertion before the tab) or behind the last tab
(appending):
![Dock area tab insert order](doc/DockArea_Tab_Insertion_Order.gif)
The [release 4.0](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest)
adds the following features:
- Auto-Hide functionality ([read more...](#auto-hide-functionality))
![Auto Hide Functionality](doc/AutoHide_Animation.gif)
- improved demo application with new image viewer dock widgets
![Auto Hide Functionality](doc/Feature_ImageViewer.png)
- Visual Studio like CSS theme in demo application
The [release 3.8](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/3.8.3)
adds the following features:
- option to close tabs with the middle mouse button
- `DeleteContentOnClose` flag for dynamic deletion and creation of dock widget
content
- improved focus highlighting functionality
The [release 3.7](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/tag/3.7.2)
adds the following features:
- support for **Qt6.**
- support for [empty dock area](doc/user-guide.md#empty-dock-area)
The [release 3.6](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/tag/3.6.3)
adds some nice new features:
- support for [central widget](doc/user-guide.md#central-widget) concept
![Central Widget](doc/central_widget.gif)
- support for [native floating widgets](doc/user-guide.md#floatingcontainerforcenativetitlebar-linux-only) on Linux
![FloatingContainerForceNativeTitleBar true](doc/cfg_flag_FloatingContainerForceNativeTitleBar_true.png)
Both features are contributions from ADS users. Read the [documentation](doc/user-guide.md)
to learn more about both new features.
The [release 3.5](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/tag/3.5.0)
adds the new [focus highlighting](doc/user-guide.md#focushighlighting) feature.
This optional feature enables highlighting of the focused dock widget like you
know it from Visual Studio.
![FocusHighlighting](doc/cfg_flag_FocusHighlighting.gif)
[learn more...](doc/user-guide.md#focushighlighting)
## Features
### Overview
- [New and Noteworthy](#new-and-noteworthy)
- [Drag \& Drop to Auto-Hide](#drag--drop-to-auto-hide)
- [Auto-Hide Tab Insertion Order](#auto-hide-tab-insertion-order)
- [Auto-Hide Tab Sorting](#auto-hide-tab-sorting)
- [Auto-Hide Drag to Float / Dock](#auto-hide-drag-to-float--dock)
- [Auto-Hide Context Menu](#auto-hide-context-menu)
- [Dock Area Tab Insert Order](#dock-area-tab-insert-order)
- [Features](#features)
- [Overview](#overview)
- [Docking everywhere - no central widget](#docking-everywhere---no-central-widget)
- [Docking inside floating windows](#docking-inside-floating-windows)
- [Grouped dragging](#grouped-dragging)
- [Perspectives for fast switching of the complete main window layout](#perspectives-for-fast-switching-of-the-complete-main-window-layout)
- [Opaque and non-opaque splitter resizing](#opaque-and-non-opaque-splitter-resizing)
- [Cancelable docking process](#cancelable-docking-process)
- [Tab-menu for easy handling of many tabbed dock widgets](#tab-menu-for-easy-handling-of-many-tabbed-dock-widgets)
- [Many different ways to detach dock widgets](#many-different-ways-to-detach-dock-widgets)
- [Supports deletion of dynamically created dock widgets](#supports-deletion-of-dynamically-created-dock-widgets)
- [Auto-Hide Functionality](#auto-hide-functionality)
- [Python Bindings](#python-bindings)
- [PySide6](#pyside6)
- [PyQt5](#pyqt5)
- [Tested Compatible Environments](#tested-compatible-environments)
- [Supported Qt Versions](#supported-qt-versions)
- [Windows](#windows)
- [macOS](#macos)
- [Linux](#linux)
- [Build](#build)
- [Qt5 on Ubuntu 18.04 or 20.04](#qt5-on-ubuntu-1804-or-2004)
- [Qt5 on Ubuntu 22.04](#qt5-on-ubuntu-2204)
- [Qt6 on Ubuntu 22.04](#qt6-on-ubuntu-2204)
- [Getting started / Example](#getting-started--example)
- [License information](#license-information)
- [Donation](#donation)
- [Showcase](#showcase)
- [Qt Creator IDE](#qt-creator-ide)
- [Qt Design Studio](#qt-design-studio)
- [CETONI Elements](#cetoni-elements)
- [ezEditor](#ezeditor)
- [D-Tect X](#d-tect-x)
- [HiveWE](#hivewe)
- [Ramses Composer](#ramses-composer)
- [Plot Juggler](#plot-juggler)
- [Notepad Next](#notepad-next)
- [MetGem](#metgem)
- [PRE Workbench](#pre-workbench)
- [RDE Robox Development Environment](#rde--robox-development-environment)
- [ResInsight](#resinsight)
- [ADTF 3](#adtf-3)
- [DREAM.3D NX](#dream3d-nx)
- [LabPlot](#labplot)
- [Alternative Docking System Implementations](#alternative-docking-system-implementations)
- [KDDockWidgets](#kddockwidgets)
- [QtitanDocking](#qtitandocking)
- [DockingPanes](#dockingpanes)
### Docking everywhere - no central widget
There is no central widget like in the Qt docking system. You can dock on every
border of the main window or you can dock into each dock area - so you are
free to dock almost everywhere.
![Dropping widgets](doc/preview-dragndrop.png)
![Dropping widgets](doc/preview-dragndrop_dark.png)
### Docking inside floating windows
There is no difference between the main window and a floating window. Docking
into floating windows is supported.
![Docking inside floating windows](doc/floating-widget-dragndrop.png)
![Docking inside floating windows](doc/floating-widget-dragndrop_dark.png)
### Grouped dragging
When dragging the titlebar of a dock, all the tabs that are tabbed with it are
going to be dragged. So you can move complete groups of tabbed widgets into
a floating widget or from one dock area to another one.
![Grouped dragging](doc/grouped-dragging.gif)
![Grouped dragging](doc/grouped-dragging_dark.png)
### Perspectives for fast switching of the complete main window layout
A perspective defines the set and layout of dock windows in the main
window. You can save the current layout of the dockmanager into a named
perspective to make your own custom perspective. Later you can simply
select a perspective from the perspective list to quickly switch the complete
main window layout.
![Perspective](doc/perspectives.gif)
![Perspective](doc/perspectives_dark.png)
### Opaque and non-opaque splitter resizing
The advanced docking system uses standard QSplitters as resize separators and thus supports opaque and non-opaque resizing functionality of QSplitter. In some rare cases, for very complex widgets or on slow machines resizing via separator on the fly may cause flicking and glaring of rendered content inside a widget. The global dock manager flag `OpaqueSplitterResize` configures the resizing behaviour of the splitters. If this flag is set, then widgets are resized dynamically (opaquely) while interactively moving the splitters.
![Opaque resizing](doc/opaque_resizing.gif)
If this flag is cleared, the widget resizing is deferred until the mouse button is released - this is some kind of lazy resizing separator.
![Non-opaque resizing](doc/non_opaque_resizing.gif)
### Cancelable docking process
In contrast to the standard Qt docking system, docking with the ADS works more like a drag & drop operation. That means, the dragged dock widget or dock area is not undocked immediately. Instead, a drag preview widget is created and dragged around to indicate the future position of the dock widget or dock area. The actual dock operation is only executed when the mouse button is released. That makes it possible, to cancel an active drag operation with the escape key.
The drag preview widget can be configured by a number of global dock manager flags:
- `DragPreviewIsDynamic`: if this flag is enabled, the preview will be adjusted dynamically to the drop area
- `DragPreviewShowsContentPixmap`: the created drag preview window shows a static copy of the content of the dock widget / dock are that is dragged
- `DragPreviewHasWindowFrame`: this flag configures if the drag preview is frameless like a QRubberBand or looks like a real window
### Tab-menu for easy handling of many tabbed dock widgets
Tabs are a good way to quickly switch between dockwidgets in a dockarea. However, if the number of dockwidgets in a dockarea is too large, this may affect the usability of the tab bar. To keep track in this situation, you can use the tab menu. The menu allows you to quickly select the dockwidget you want to activate from a drop down menu.
![Tab menu](doc/tab_menu.gif)
### Many different ways to detach dock widgets
You can detach dock widgets and also dock areas in the following ways:
- by dragging the dock widget tab or the dock area title bar
- by double clicking the tab or title bar
- by using the detach menu entry from the tab and title bar drop down menu
### Supports deletion of dynamically created dock widgets
Normally clicking the close button of a dock widget will just hide the widget and the user can show it again using the toggleView() action of the dock widget. This is meant for user interfaces with a static amount of widgets. But the advanced docking system also supports dynamic dock widgets that will get deleted on close. If you set the dock widget flag `DockWidgetDeleteOnClose` for a certain dock widget, then it will be deleted as soon as you close this dock widget. This enables the implementation of user interfaces with dynamically created editors, like in word processing applications or source code development tools.
### Auto-Hide Functionality
The 4.0 release of ADS added the new **Auto-Hide** feature. Thanks to the
[initial contribution](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/pull/452) by [Ahmad Syarifuddin](https://github.com/SyarifFakhri) it was
possible to close this long standing [feature request](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/147). The "Auto Hide" feature
allows to display more information using less screen space by hiding or showing
windows pinned to one of the four dock container borders.
![Auto-Hide Movie](doc/AutoHide_Movie.gif)
The Advanced Docking
System supports "Auto-Hide" functionality for **all** dock containers - that means,
for the main window and for each floating widget. Here is short list of all
auto hide features:
- supported for the main window and all floating dock containers
- supports showing and hiding via mouse click or mouse hover
- respects opaque / non opaque splitter resizing flag
- context menu for pinning a dock widget or a complete dock area to a certain border
- configuration option to configure if the pin button should pin the current
dock widget tab or a complete dock area
- click the pin button holding the Ctrl key to pin a complete dock area
- fully CSS styleable
- backward compatible state file format - is is possible to load older dock manager
state files without auto hide support and older versions can load the new state
files with Auto-Hide state information
More about the auto hide configuration options in the [online documentation...](doc/user-guide.md#auto-hide-configuration-flags)
## Python Bindings
![Python Logo](doc/python_logo.png)
Thanks to the contribution of several users, the Advanced Docking System comes
with a complete Python integration. Python bindings are available for **PyQt5** and
**PySide6**.
### PySide6
A PySide6 ADS package is available via PyPi and can be installed on Windows,
macOS, and Linux with:
```bash
pip install PySide6-QtAds
```
Sample code is available [here](https://github.com/mborgerson/Qt-Advanced-Docking-System/tree/pyside6/examples). To run the samples, you'll also need to install latest qtpy
from source (pip install https://github.com/spyder-ide/qtpy/archive/refs/heads/master.zip).
The PySide6 bindings were contributed by:
- [mborgerson](https://github.com/mborgerson)
Please file PySide6-QtAds-specific issues on its [pyside6_qtads](https://github.com/mborgerson/pyside6_qtads) fork for tracking. For more information about the PySide6 bindings read [this](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/298) issue.
### PyQt5
A package is available via [conda-forge](https://github.com/conda-forge/pyqtads-feedstock).
The python integration has been contributed to this project by the following people:
- [n-elie](https://github.com/n-elie)
- [Hugo Slepicka](https://github.com/hhslepicka)
- [K Lauer](https://github.com/klauer)
A Python integration is also available via PyPi. You can install the
[PyQtAds](https://pypi.org/project/PyQtAds/) package via pip. This feature has been
contributed to this project by:
- [Mira Weller](https://github.com/luelista)
## Tested Compatible Environments
### Supported Qt Versions
The library supports **Qt5** and **Qt6**.
### Windows
Windows 10 [![Build status](https://ci.appveyor.com/api/projects/status/qcfb3cy932jw9mpy/branch/master?svg=true)](https://ci.appveyor.com/project/githubuser0xFFFF/qt-advanced-docking-system/branch/master)
The library was developed on and for Windows. It is used in a commercial Windows application and is therefore constantly tested.
### macOS
macOS [![Build Status](https://travis-ci.org/githubuser0xFFFF/Qt-Advanced-Docking-System.svg?branch=master)](https://travis-ci.org/githubuser0xFFFF/Qt-Advanced-Docking-System)
The application can be compiled for macOS. A user reported, that the library works on macOS. If have not tested it.
![Advanced Docking on macOS](doc/macos.png)
### Linux
[![Build Status](https://travis-ci.org/githubuser0xFFFF/Qt-Advanced-Docking-System.svg?branch=master)](https://travis-ci.org/githubuser0xFFFF/Qt-Advanced-Docking-System)
[![Build status](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/workflows/linux-builds/badge.svg)](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/actions?query=workflow%3Alinux-builds)
Unfortunately, there is no such thing as a Linux operating system. Linux is a heterogeneous environment with a variety of different distributions. So it is not possible to support "Linux" like it is possible for Windows. It is only possible to support and test a small subset of Linux distributions. The library can be compiled for and has been developed and tested with some Linux distributions. Depending on the used window manager or compositor, dock widgets
with native title bars are supported or not. If native title bars are not supported,
the library switches to `QWidget` based title bars.
- **Kubuntu 18.04 and 19.10** - uses KWin - no native title bars
- **Ubuntu 18.04, 19.10 and 20.04** - native title bars are supported
- **Ubuntu 22.04** - uses Wayland -> no native title bars
There are some requirements for the Linux distribution that have to be met:
- an X server that supports ARGB visuals and a compositing window manager. This is required to display the translucent dock overlays ([https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows](https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows)). If your Linux distribution does not support this, or if you disable this feature, you will very likely see issue [#95](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/95).
- Wayland is not properly supported by Qt yet. If you use Wayland, then you should set the session type to x11: `XDG_SESSION_TYPE=x11 ./AdvancedDockingSystemDemo`. You will find more details about this in issue [#288](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/288).
Screenshot Kubuntu:
![Advanced Docking on Kubuntu Linux](doc/linux_kubuntu_1804.png)
Screenshot Ubuntu:
![Advanced Docking on Ubuntu Linux](doc/linux_ubuntu_1910.png)
## Build
The Linux build requires private header files. Make sure that they are installed.
The library uses SVG icons, so ensure that Qt SVG support is installed. The demo
application creates a `QQuickWidget` for testing, so ensure that the required
libraries are installed.
### Qt5 on Ubuntu 18.04 or 20.04
```bash
sudo apt install qt5-default qtbase5-private-dev
```
### Qt5 on Ubuntu 22.04
```bash
sudo apt install qtbase5-dev qtbase5-private-dev qtbase5-dev-tools libqt5svg5 libqt5qml5 qtdeclarative5-dev
```
### Qt6 on Ubuntu 22.04
```bash
sudo apt install qt6-default qt6-base-dev qt6-base-private-dev qt6-tools-dev libqt6svg6 qt6-qtdeclarative
```
Open the `ads.pro` file with QtCreator and start the build, that's it.
You can run the demo project and test it yourself.
## Getting started / Example
The following example shows the minimum code required to use the advanced Qt docking system.
*MainWindow.h*
```cpp
#include <QMainWindow>
#include "DockManager.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
// The main container for docking
ads::CDockManager* m_DockManager;
};
```
*MainWindow.cpp*
```cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QLabel>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// Create the dock manager after the ui is setup. Because the
// parent parameter is a QMainWindow the dock manager registers
// itself as the central widget as such the ui must be set up first.
m_DockManager = new ads::CDockManager(this);
// Create example content label - this can be any application specific
// widget
QLabel* l = new QLabel();
l->setWordWrap(true);
l->setAlignment(Qt::AlignTop | Qt::AlignLeft);
l->setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ");
// Create a dock widget with the title Label 1 and set the created label
// as the dock widget content
ads::CDockWidget* DockWidget = new ads::CDockWidget("Label 1");
DockWidget->setWidget(l);
// Add the toggleViewAction of the dock widget to the menu to give
// the user the possibility to show the dock widget if it has been closed
ui->menuView->addAction(DockWidget->toggleViewAction());
// Add the dock widget to the top dock widget area
m_DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget);
}
MainWindow::~MainWindow()
{
delete ui;
}
```
## License information
[![License: LGPL v2.1](https://img.shields.io/badge/License-LGPL%20v2.1-blue.svg)](gnu-lgpl-v2.1.md)
This project uses the [LGPLv2.1 license](gnu-lgpl-v2.1.md)
## Donation
If this project help you reduce time to develop or if you just like it, you can give me a cup of coffee :coffee::wink:.
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=85R64TMMSY9T6">
<img src="doc/donate.png" alt="Donate with PayPal" width="160"/>
</a>
## Showcase
### [Qt Creator IDE](https://www.qt.io/development-tools)
From version 4.12 on, Qt Creator uses the Advanced Docking Framework for its
Qt Quick Designer. This improves the usability when using multiple screens.
![Qt Creator](doc/showcase_qtcreator.png)
### [Qt Design Studio](https://www.qt.io/ui-design-tools)
Taken from the [Qt Blog](https://www.qt.io/blog/qt-design-studio-1.5-beta-released):
> The most obvious change in [Qt Design Studio 1.5](https://www.qt.io/blog/qt-design-studio-1.5-beta-released) is the integration of dock widgets using the Qt Advanced Docking System. This allows the user to fully customize the workspace and also to undock any view into its own top level window. This especially improves the usability when using multiple screens.
[![Qt Design Studio](doc/showcase_qt_design_studio_video.png)](https://youtu.be/za9KBWcFXEw?t=84)
### [CETONI Elements](https://cetoni.com/cetoni-elements/)
The CETONI Elements software from [CETONI](https://www.cetoni.com) is a comprehensive,
plugin-based and modular laboratory automation software for controlling CETONI devices using a joint graphical user interface. The software features a powerful script system to automate processes. The software uses the advanced docking system to give the user the freedom to arrange all the views and windows that are provided by the various plugins.
[learn more...](https://cetoni.com/cetoni-elements/)
[![CETONI_Elements](doc/showcase_qmix_elements.png)](https://www.youtube.com/watch?v=7pdNfafg3Qc)
### [ezEditor](https://github.com/ezEngine/ezEngine)
The ezEditor is a full blown graphical editor used for editing scenes and
importing and authoring assets for the [ezEngine](https://github.com/ezEngine/ezEngine) -
an open source C++ game engine in active development.
![ezEditor](doc/showcase_ezEngine_editor.png)
### [D-Tect X](https://www.duerr-ndt.com/products/ndt-software/d-tect-xray-inspection-software.html)
D-Tect X is a X-ray inspection software for industrial radiography. It is a state-of-the-art 64-bit application which supports GPU (Graphics Processing Unit) acceleration and takes full advantage of computers with multiple CPU cores. A large set of tools assist the user in image analysis and evaluation. Thanks to the Qt Advanced Docking System the flexible and intuitive user interface can be completely customized to each users preference.
[learn more...](https://www.duerr-ndt.com/products/ndt-software/d-tect-xray-inspection-software.html)
[![D-TectX](doc/showcase_d-tect-x.png)](https://youtu.be/mOor7GmmIJo?t=13)
### [HiveWE](https://github.com/stijnherfst/HiveWE)
HiveWE is a Warcraft III world editor. It focusses on speed and ease of use,
especially for large maps where the regular World Editor is often too slow and clunky.
It has a JASS editor with syntax highlighting, tabs, code completion and more.
The JASS editor uses the Qt Advanced Docking System for the management and layout
of the open editor windows.
[learn more...](https://github.com/stijnherfst/HiveWE)
![HiveWE](doc/showcase_hivewe.png)
### [Ramses Composer](https://github.com/GENIVI/ramses-composer)
Ramses Composer is the authoring tool for the open source [RAMSES](https://github.com/GENIVI/ramses)
rendering ecosystem.
Ramses is a low-level rendering engine which is optimized for embedded hardware
mobile devices, automotive ECUs, IoT electronics. Ramses was initially developed
at the BMW Group and open-sourced in 2018 as part of a collaboration initiative
with the Genivi Alliance. It is an important part of the BMW infotainment cluster
and digital portfolio.
[learn more...](https://github.com/GENIVI/ramses-composer)
![RamsesComposer](doc/showcase_ramses_composer.png)
### [Plot Juggler](https://github.com/facontidavide/PlotJuggler)
PlotJuggler is a fast, powerful and intuitive tool to visualize time series.
It makes it easy to visualize data but also to analyze it. You can manipulate
your time series using a simple and extendable Transform Editor. Some of the
highlights are:
- Simple Drag & Drop user interface.
- Load data from file.
- Connect to live streaming of data.
- Save the visualization layout and configurations to re-use them later.
- Fast OpenGL visualization.
- Can handle thousands of timeseries and millions of data points.
- Transform your data using a simple editor: derivative, moving average, integral, etc…
- PlotJuggler can be easily extended using plugins.
[read more...](https://github.com/facontidavide/PlotJuggler)
[![Plot Juggler](doc/showcase_plot_juggler.png)](https://vimeo.com/480588113#t=46s)
### [Notepad Next](https://github.com/dail8859/NotepadNext)
Notepad Next is a cross-platform reimplementation of Notepad++ that uses the
Advanced Docking System to arrange the open source files on the screen.
[read more...](https://github.com/dail8859/NotepadNext)
![NotepadNext](doc/showcase_notepad_next.png)
### [MetGem](https://metgem.github.io/)
MetGem is an open-source software for tandem mass-spectrometry data visualization.
It's key features are standalone molecular networking and t-SNE based projections.
MetGem uses the Qt-Advanced-Docking-System to manage docks and to create independent
molecular network views.
[read more...](https://metgem.github.io/)
![MetGem](doc/showcase_metgem.png)
### [PRE Workbench](https://luelista.github.io/pre_workbench/)
Protocol Reverse Engineering Workbench is a software to support researchers in reverse engineering protocols and documenting the results. It supports various sources to import protocol traffic from, helps the discovery process by displaying different views and heuristic-based highlighting on data, and aids in documenting and sharing findings.
PRE Workbench is a Python software and uses the ADS PyQt integration.
[read more...](https://luelista.github.io/pre_workbench/)
[![PRE Workbench](doc/showcase_pre_workbench.png)](https://youtu.be/U3op5UreV1Q)
### [RDE Robox Development Environment](https://www.robox.it/en/product/rde-robox-development-environment/)
This software is a development environment for PAC (Programmable Automation Controllers)
from ROBOX. It offers a lot of tools to write, compile and debug machine control
and application software. The Advanced Docking System helps to organize all the tools and
windows (Project window, Shell window, Monitor windows, Oscilloscope window...)
on the screen to provide a easy to use, highly configurable and visual pleasing
development experience.
[read more...](https://www.robox.it/en/product/rde-robox-development-environment/)
![RDE](doc/showcase_robox_ide.png)
### [ResInsight](https://www.ceetronsolutions.com/projects/resinsight)
ResInsight as a software from Ceetron Solutions for visualization of oil and
gas reservoir simulation data. It allows reservoir models, simulation results,
and measurements to be visualized with very high performance. Optimized use of
graphics technology and simultaneous processing on multiple CPU cores have been
vital to enhance the performance and capacity of ResInsight for large data sets.
The Advanced Docking System has empowered Ceetron to build a much more intuitive
user interface for its ResInsight users.
[read more...](https://resinsight.org/)
[![ResInsight](doc/showcase_resinsight.png)](https://www.youtube.com/watch?v=HzLaQ1p6AUc)
### [ADTF 3](https://www.digitalwerk.net/adtf/)
The Automotive Data and Time-Triggered Framework was designed as a Rapid Prototyping Toolset, Simulation Framework and Test- and Measurement Tool. It is meant for:
- Developing and testing ADAS and HAD components
- Recording of vehicle data for visualisation
- Simulation of complex scenarios in SIL/HIL test environments
The software features time-based processing of multiple data streams and graphical editing of dynamic filter graphs. It also includes an SDK for custom plug-ins and reusable components, as well as components for data visualization in both 2D and 3D. This is was the
[manual](https://support.digitalwerk.net/adtf/v3/adtf_html/page_adtf_xsystem_plugin.html)
says about the switch to Qt Advanced Docking:
> After several minor improvements the Qt5 ADTF XSystem uses the Advanced Docking System for Qt since ADTF 3.10.0 for more convenience and usability regarding layouting, docking and embedding several widgets.
[read more...](https://support.digitalwerk.net/adtf/v3/adtf_html/index.html)
![ADTF](doc/showcase_adtf.png)
### [DREAM.3D NX](https://github.com/BlueQuartzSoftware/DREAM3D)
DREAM.3D *(Digital Representation Environment for Analysis of Materials in 3D)* is an open source, cross-platform and modular, software suite that allows users to prepare, reconstruct, quantify, instantiate, and mesh, multidimensional, multimodal microstructural data, as well as many other applications.
[BlueQuartz Software](http://www.bluequartz.net/) is currently completely rewriting the DREAM.3D application. For the upcoming version **[DREAM3D NX](http://www.dream3d.io/)** they improved the UI by using the Advanced Docking System. An [early version](http://www.dream3d.io/) of **DREAM3D NX** with ADS is already available to any user who would like to take the brand new version out for a spin.
![DREAM.3D NX](doc/showcase_dream3d_nx.png)
[read more...](http://dream3d.bluequartz.net/)
### [LabPlot](https://labplot.kde.org/)
KDE LabPlot is the ultimate free, open source and cross-platform tool for scientists, engineers, and students who need to analyze and visualize data. With its intuitive interface and powerful features, you can create stunning plots and diagrams with ease. Whether you're working with CSV, FITS, or HDF5 data, KDE LabPlot makes it simple to import and analyze your data.
The LabPlot project recently switched to the Qt Advanced Docking System for their user interface. This switch represents a significant improvement to the LabPlot software, allowing users to create and manage complex data visualization layouts with ease.
![LabPlot](doc/showcase_labplot.png)
[read more...](https://labplot.kde.org/)
## Alternative Docking System Implementations
If this Qt Advanced Docking System does not fit to your needs you may consider some of the alternative docking system solutions for Qt.
### KDDockWidgets
This is an advanced docking framework for Qt from [KDAB](https://www.kdab.com/). The interesting thing is, that they separated GUI code from logic, so they can easily provide a QtQuick backend in the future.
- [Blog post about KDDockWidgets](https://www.kdab.com/kddockwidgets/)
- [GitHub project](https://github.com/KDAB/KDDockWidgets)
**License:** dual-licensed, available under both commercial and GPL license.
### QtitanDocking
This is a commercial component from [Developer Machines](https://www.devmachines.com/) for Qt Framework that allows to create a Microsoft like dockable user interface. They also offer a lot of other interesting and useful components for Qt. The library is available
- [Product page](https://www.devmachines.com/qtitandocking-overview.html)
**License:** Commercial license
### DockingPanes
DockingPanes is a library for Qt Widgets that implements docking windows that have the look and feel of Visual Studio. It provides a simple API which allows an application to make use of docking windows with a few calls.
- [GitHub project](https://github.com/KestrelRadarSensors/dockingpanes)
**License:** GPL

28
QtADS/ads.pri Normal file
View File

@ -0,0 +1,28 @@
CONFIG(debug, debug|release){
win32-g++ {
versionAtLeast(QT_VERSION, 5.15.0) {
LIBS += -lqtadvanceddocking
}
else {
LIBS += -lqtadvanceddockingd
}
}
else:msvc {
LIBS += -lqtadvanceddockingd
}
else:mac {
LIBS += -lqtadvanceddocking_debug
}
else {
LIBS += -lqtadvanceddocking
}
}
else{
LIBS += -lqtadvanceddocking
}
unix:!macx {
LIBS += -lxcb
}

9
QtADS/ads.pro Normal file
View File

@ -0,0 +1,9 @@
TEMPLATE = subdirs
SUBDIRS = \
src \
demo \
examples
demo.depends = src
examples.depends = src

5
QtADS/adsConfig.cmake Normal file
View File

@ -0,0 +1,5 @@
include(CMakeFindDependencyMacro)
find_dependency(Qt5Core ${REQUIRED_QT_VERSION} REQUIRED)
find_dependency(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED)
find_dependency(Qt5Widgets ${REQUIRED_QT_VERSION} REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/adsTargets.cmake")

View File

@ -0,0 +1,172 @@
# - Returns a version string from Git
#
# These functions force a re-configure on each git commit so that you can
# trust the values of the variables in your build system.
#
# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
#
# Returns the refspec and sha hash of the current head revision
#
# git_describe(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe on the source tree, and adjusting
# the output so that it tests false if an error occurs.
#
# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe --exact-match on the source tree,
# and adjusting the output so that it tests false if there was no exact
# matching tag.
#
# git_local_changes(<var>)
#
# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes.
# Uses the return code of "git diff-index --quiet HEAD --".
# Does not regard untracked files.
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__get_git_revision_description)
return()
endif()
set(__get_git_revision_description YES)
# We must run the following at "include" time, not at function call time,
# to find the path to this module rather than the path to a calling list file
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
function(get_git_head_revision _refspecvar _hashvar)
set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories
set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
# We have reached the root directory, we are not in git
set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
return()
endif()
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
endwhile()
# check if this is a submodule
if(NOT IS_DIRECTORY ${GIT_DIR})
file(READ ${GIT_DIR} submodule)
string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
endif()
if(NOT IS_DIRECTORY "${GIT_DIR}")
file(READ ${GIT_DIR} worktree)
string(REGEX REPLACE "gitdir: (.*)worktrees(.*)\n$" "\\1" GIT_DIR ${worktree})
endif()
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
if(NOT EXISTS "${GIT_DATA}")
file(MAKE_DIRECTORY "${GIT_DATA}")
endif()
if(NOT EXISTS "${GIT_DIR}/HEAD")
return()
endif()
set(HEAD_FILE "${GIT_DATA}/HEAD")
configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
"${GIT_DATA}/grabRef.cmake"
@ONLY)
include("${GIT_DATA}/grabRef.cmake")
set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
endfunction()
function(git_describe _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
return()
endif()
if(NOT hash)
set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
return()
endif()
# TODO sanitize
#if((${ARGN}" MATCHES "&&") OR
# (ARGN MATCHES "||") OR
# (ARGN MATCHES "\\;"))
# message("Please report the following error to the project!")
# message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
#endif()
#message(STATUS "Arguments to execute_process: ${ARGN}")
execute_process(COMMAND
"${GIT_EXECUTABLE}"
describe
${hash}
${ARGN}
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE
res
OUTPUT_VARIABLE
out
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(out "${out}-${res}-NOTFOUND")
endif()
set(${_var} "${out}" PARENT_SCOPE)
endfunction()
function(git_get_exact_tag _var)
git_describe(out --exact-match ${ARGN})
set(${_var} "${out}" PARENT_SCOPE)
endfunction()
function(git_local_changes _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
return()
endif()
if(NOT hash)
set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
return()
endif()
execute_process(COMMAND
"${GIT_EXECUTABLE}"
diff-index --quiet HEAD --
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE
res
OUTPUT_VARIABLE
out
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(res EQUAL 0)
set(${_var} "CLEAN" PARENT_SCOPE)
else()
set(${_var} "DIRTY" PARENT_SCOPE)
endif()
endfunction()

View File

@ -0,0 +1,41 @@
#
# Internal file for GetGitRevisionDescription.cmake
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
set(HEAD_HASH)
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
if(HEAD_CONTENTS MATCHES "ref")
# named branch
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
if(EXISTS "@GIT_DIR@/${HEAD_REF}")
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
else()
configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY)
file(READ "@GIT_DATA@/packed-refs" PACKED_REFS)
if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}")
set(HEAD_HASH "${CMAKE_MATCH_1}")
endif()
endif()
else()
# detached HEAD
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
endif()
if(NOT HEAD_HASH)
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
string(STRIP "${HEAD_HASH}" HEAD_HASH)
endif()

47
QtADS/demo/CMakeLists.txt Normal file
View File

@ -0,0 +1,47 @@
cmake_minimum_required(VERSION 3.5...4.3)
project(ads_demo VERSION ${VERSION_SHORT})
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} 5.5 COMPONENTS Core Gui Widgets Quick QuickWidgets REQUIRED)
if(WIN32 AND QT_VERSION_MAJOR LESS 6)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS AxContainer REQUIRED)
endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(ads_demo_SRCS
main.cpp
MainWindow.cpp
mainwindow.ui
StatusDialog.cpp
StatusDialog.ui
ImageViewer.cpp
RenderWidget.cpp
demo.qrc
)
add_executable(AdvancedDockingSystemDemo WIN32 ${ads_demo_SRCS})
target_include_directories(AdvancedDockingSystemDemo PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src")
target_link_libraries(AdvancedDockingSystemDemo PUBLIC Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Quick
Qt${QT_VERSION_MAJOR}::QuickWidgets)
if(WIN32 AND QT_VERSION_MAJOR LESS 6)
target_link_libraries(AdvancedDockingSystemDemo PUBLIC Qt${QT_VERSION_MAJOR}::AxContainer)
endif()
target_link_libraries(AdvancedDockingSystemDemo PRIVATE qt${QT_VERSION_MAJOR}advanceddocking)
set_target_properties(AdvancedDockingSystemDemo PROPERTIES
AUTOMOC ON
AUTORCC ON
AUTOUIC ON
CXX_STANDARD 14
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
VERSION ${VERSION_SHORT}
EXPORT_NAME "Qt Advanced Docking System Demo"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${ads_PlatformDir}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${ads_PlatformDir}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${ads_PlatformDir}/bin/ads"
)
#if(BUILD_STATIC)
# target_compile_definitions(AdvancedDockingSystemDemo PRIVATE ADS_STATIC)
#endif()

Some files were not shown because too many files have changed in this diff Show More