diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 2331bc9..0900949 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -4,8 +4,13 @@ on: workflow_dispatch: push: branches: [main, develop] + paths-ignore: + - '*.md' + pull_request: branches: [main, develop] + paths-ignore: + - '*.md' jobs: get-matrix: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 3766cf8..87c474c 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -4,8 +4,13 @@ on: workflow_dispatch: push: branches: [main, develop] + paths-ignore: + - '*.md' + pull_request: branches: [main, develop] + paths-ignore: + - '*.md' jobs: get-matrix: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d4203cd..bab5d35 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -4,8 +4,13 @@ on: workflow_dispatch: push: branches: [main, develop] + paths-ignore: + - '*.md' + pull_request: branches: [main, develop] + paths-ignore: + - '*.md' jobs: get-matrix: diff --git a/CMakeLists.txt b/CMakeLists.txt index 11e1036..88b2aca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,5 +10,10 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) +option(PM_BUILD_PROPERTY_GRID_EXAMPLES "Determines whether or not the example projects should be built with the library" ON) + add_subdirectory(src) -add_subdirectory(examples) + +if(PM_BUILD_PROPERTY_GRID_EXAMPLES) + add_subdirectory(examples) +endif() diff --git a/README.md b/README.md index 70978f3..acebaf8 100644 --- a/README.md +++ b/README.md @@ -16,49 +16,10 @@ This library provides a flexible and customizable property editor that displays > **Current Status:** > - ✅ **API is stable** - Safe to use in your projects without worrying about breaking changes > - ⚠️ **Implementation is not fully polished** - Some features may have rough edges -> - 🚧 **Work in progress** - Actively being improved in my free time to become production-ready +> - 🚧 **Work in progress** - Actively being improved to become production-ready > > Feel free to use it, report issues, and contribute! The core functionality works well, and the API won't change in ways that break your code. -## Features - -- **Comprehensive Type Support**: Built-in support for all major Qt types including: - - Basic types: `int`, `bool`, `QString`, `double` - - Geometric types: `QPoint`, `QPointF`, `QSize`, `QSizeF`, `QRect`, `QRectF`, `QLine`, `QLineF` - - Graphics types: `QColor`, `QFont`, `QIcon`, `QPixmap`, `QBitmap`, `QImage` - - Advanced types: `QVector2D`, `QVector3D`, `QVector4D`, `QMatrix4x4`, `QPolygon`, `QPolygonF` - - Date/Time types: `QDate`, `QTime`, `QDateTime` - - UI types: `QKeySequence`, `QCursor`, `QChar` - - Object types: `QObject*` and custom enums - -- **Property Attributes**: Rich attribute system for enhanced property configuration: - - `DescriptionAttribute`: Add helpful descriptions to properties - - `DefaultValueAttribute`: Define default values for properties - - `CategoryAttribute`: Organize properties into collapsible categories - - `ReadOnlyAttribute`: Mark properties as read-only - -- **Flexible API**: Multiple ways to add properties: - - Simple property addition with automatic type detection - - Detailed property configuration with attributes - - Batch property addition with categories - -- **Interactive Features**: - - Collapsible categories for better organization - - Real-time property value change notifications - - Support for custom property editors - - Responsive tree-based layout - -- **Cross-Platform**: Compatible with Qt 5.9+ on Windows, macOS, and Linux - -- **Wide Qt Compatibility**: Designed to work with all Qt versions from Qt 5.9 onward, ensuring maximum compatibility across projects - -## Requirements - -- **CMake**: 3.10 or higher -- **Qt**: 5.9+ (Widgets module required) -- **C++ Standard**: C++17 (can work on C++14 if you have a replacement implementation for `std::variant`) -- **Compiler**: MSVC 2019, GCC, or Clang with C++17 support - ## Quick Start ```cpp @@ -71,14 +32,6 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); PM::PropertyGrid propertyGrid; - // - // ONLY FOR NOW... - // - // Add editors for the property types that you're using - // This won't be required in the future - // - propertyGrid.addPropertyEditor(); - propertyGrid.addPropertyEditor(); // Add simple properties propertyGrid.addProperty("Name", QString("My Object")); @@ -107,6 +60,45 @@ int main(int argc, char *argv[]) } ``` +## Features + +- **Comprehensive Type Support**: Planned Built-in support for all major Qt types including: + - [x] **Basic types**: `int`, `bool`, `QString`, `double` + - [ ] **Geometric types**: `QPoint`, `QPointF`, `QSize`, `QSizeF`, `QRect`, `QRectF`, `QLine`, `QLineF` + - [x] **Graphics types**: `QColor`, `QFont`, `QIcon`, `QPixmap`, `QBitmap`, `QImage` + - [ ] **Advanced types**: `QVector2D`, `QVector3D`, `QVector4D`, `QMatrix4x4`, `QPolygon`, `QPolygonF` + - [x] **Date/Time types**: `QDate`, `QTime`, `QDateTime` + - [ ] **UI types** (partial): `QKeySequence`, `QCursor`, `QChar` + - [ ] **Object types**: `QObject*` and custom enums + +- **Property Attributes**: Rich attribute system for enhanced property configuration: + - `DescriptionAttribute`: Add helpful descriptions to properties + - `DefaultValueAttribute`: Define default values for properties + - `CategoryAttribute`: Organize properties into collapsible categories + - `ReadOnlyAttribute`: Mark properties as read-only + +- **Flexible API**: Multiple ways to add properties: + - Simple property addition with automatic type detection + - Detailed property configuration with attributes + - Batch property addition with categories + +- **Interactive Features**: + - Collapsible categories for better organization + - Real-time property value change notifications + - Support for custom property editors + - Responsive tree-based layout + +- **Cross-Platform**: Compatible with Qt 5.9+ on Windows, macOS, and Linux + +- **Wide Qt Compatibility**: Designed to work with all Qt versions from Qt 5.9 onward, ensuring maximum compatibility across projects + +## Requirements + +- **CMake**: 3.10 or higher +- **Qt**: 5.9+ (Widgets module required) +- **C++ Standard**: C++17 (can work on C++14 if you have a replacement implementation for `std::variant`) +- **Compiler**: MSVC 2019, GCC, or Clang with C++17 support + ## API Overview ### Core Classes @@ -148,7 +140,8 @@ cmake --build . You can easily integrate PmPropertyGrid into your CMake project using `add_subdirectory`: ```cmake -# Add PmPropertyGrid as a subdirectory +# Add PmPropertyGrid as a subdirectory and disable examples +set(PM_BUILD_PROPERTY_GRID_EXAMPLES OFF) add_subdirectory(path/to/PmPropertyGrid) # Link against your target @@ -168,6 +161,7 @@ find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) # Add PmPropertyGrid +set(PM_BUILD_PROPERTY_GRID_EXAMPLES OFF) add_subdirectory(third_party/PmPropertyGrid) # Create your executable @@ -196,6 +190,7 @@ PmPropertyGrid/ ├── src/ # Core library source code │ ├── PropertyGrid.h/cpp # Main property grid widget │ ├── Property.h/cpp # Property definition and attributes +│ ├── PropertyContext.h/cpp # Property definition, value and associated meta info │ └── PropertyEditor.h/cpp # Property editor framework ├── examples/ # Example applications │ └── property_grid_showcase/ # Comprehensive demo diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 80c261b..a36ed11 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,2 @@ -include_directories(${CMAKE_SOURCE_DIR}/src) - add_subdirectory(property_grid_showcase) +add_subdirectory(object_property_grid) diff --git a/examples/object_property_grid/CMakeLists.txt b/examples/object_property_grid/CMakeLists.txt new file mode 100644 index 0000000..fa9dee1 --- /dev/null +++ b/examples/object_property_grid/CMakeLists.txt @@ -0,0 +1,20 @@ +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) + +add_executable(object_property_grid + main.cpp + ObjectPropertyGrid.h ObjectPropertyGrid.cpp + + MainWindow.h MainWindow.cpp MainWindow.ui +) + +target_link_libraries(object_property_grid + PRIVATE + PM::PropertyGrid +) +target_link_libraries(object_property_grid PRIVATE Qt${QT_VERSION_MAJOR}::Core) diff --git a/examples/object_property_grid/MainWindow.cpp b/examples/object_property_grid/MainWindow.cpp new file mode 100644 index 0000000..2e2c238 --- /dev/null +++ b/examples/object_property_grid/MainWindow.cpp @@ -0,0 +1,75 @@ +#include "MainWindow.h" +#include "ui_MainWindow.h" + +#include +#include + +static void addWidgetToTree(QTreeWidgetItem *parentItem, QWidget *widget) +{ + if (!widget) + return; + + auto *item = new QTreeWidgetItem(parentItem, {widget->objectName()}); + item->setData(0, Qt::UserRole, QVariant::fromValue(static_cast(widget))); + + const QList children = widget->findChildren(QString(), Qt::FindDirectChildrenOnly); + for (QWidget *child : children) + addWidgetToTree(item, child); +} + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), propertyGrid(this) +{ + ui->setupUi(this); + + // Place propertyGrid inside the Properties dock + propertyGrid.layout()->setContentsMargins(0, 0, 0, 0); + ui->dockWidget_2->setWidget(&propertyGrid); + + // Populate tree with central widget hierarchy + ui->treeWidget->setHeaderLabel("Objects"); + ui->treeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + + // Instead of adding the central widget itself, add its direct children + const QList children = ui->centralwidget->findChildren(QString(), Qt::FindDirectChildrenOnly); + for (QWidget *child : children) + addWidgetToTree(ui->treeWidget->invisibleRootItem(), child); + + // Expand everything by default + ui->treeWidget->expandAll(); + + // Default selection: first item + if (ui->treeWidget->topLevelItemCount() > 0) + { + ui->treeWidget->setCurrentItem(ui->treeWidget->topLevelItem(0)); + QObject *obj = ui->treeWidget->topLevelItem(0)->data(0, Qt::UserRole).value(); + + if (obj) + propertyGrid.setSelectedObject(obj); + } + + // Sync selection with property grid + connect(ui->treeWidget, &QTreeWidget::itemSelectionChanged, this, &MainWindow::objectsTreeSelectionChanged); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::objectsTreeSelectionChanged() +{ + QList selectedObjects; + for (QTreeWidgetItem *item : ui->treeWidget->selectedItems()) + { + QObject *obj = item->data(0, Qt::UserRole).value(); + if (obj) + selectedObjects.append(obj); + } + + if (selectedObjects.isEmpty()) + propertyGrid.setSelectedObjects({}); + else if (selectedObjects.size() == 1) + propertyGrid.setSelectedObject(selectedObjects.first()); + else + propertyGrid.setSelectedObjects(selectedObjects); +} diff --git a/examples/object_property_grid/MainWindow.h b/examples/object_property_grid/MainWindow.h new file mode 100644 index 0000000..7dc62d7 --- /dev/null +++ b/examples/object_property_grid/MainWindow.h @@ -0,0 +1,30 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "ObjectPropertyGrid.h" + +#include + +namespace Ui +{ +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void objectsTreeSelectionChanged(); + +private: + Ui::MainWindow *ui; + + ObjectPropertyGrid propertyGrid; +}; + +#endif // MAINWINDOW_H diff --git a/examples/object_property_grid/MainWindow.ui b/examples/object_property_grid/MainWindow.ui new file mode 100644 index 0000000..b549c6c --- /dev/null +++ b/examples/object_property_grid/MainWindow.ui @@ -0,0 +1,222 @@ + + + MainWindow + + + + 0 + 0 + 800 + 800 + + + + ObjectsPropertyGrid example + + + + + + 30 + 20 + 191 + 22 + + + + lineEdit + + + + + + 30 + 50 + 191 + 22 + + + + + Item 1 + + + + + Item 2 + + + + + Item 3 + + + + + + + 30 + 80 + 191 + 22 + + + + + + + 30 + 110 + 75 + 24 + + + + PushButton + + + + + + 30 + 140 + 22 + 22 + + + + ... + + + + + + 30 + 170 + 76 + 20 + + + + CheckBox + + + + + + 30 + 200 + 185 + 41 + + + + CommandLinkButton + + + + + + 20 + 250 + 201 + 111 + + + + Options + + + + + 10 + 30 + 89 + 20 + + + + Option1 + + + true + + + + + + 10 + 50 + 89 + 20 + + + + Option2 + + + + + + 10 + 70 + 89 + 20 + + + + Option3 + + + + + + + + 0 + 0 + 800 + 22 + + + + + + + + 500 + 95 + + + + Objects Tree + + + 2 + + + + + 1 + + + + + + + + 500 + 40 + + + + Properties + + + 2 + + + + + + diff --git a/examples/object_property_grid/ObjectPropertyGrid.cpp b/examples/object_property_grid/ObjectPropertyGrid.cpp new file mode 100644 index 0000000..976e330 --- /dev/null +++ b/examples/object_property_grid/ObjectPropertyGrid.cpp @@ -0,0 +1,126 @@ +#include "ObjectPropertyGrid.h" + +#include + +// Return list of all common readable properties across selected objects +// only properties that are common between all objects gets returned +static QList getCommonProperties(const QObjectList &objects) +{ + QList result; + if (objects.isEmpty()) + return result; + + const QMetaObject *meta = objects.first()->metaObject(); + for (int i = 0; i < meta->propertyCount(); ++i) + { + QMetaProperty prop = meta->property(i); + if (!prop.isReadable()) + continue; + + const char *name = prop.name(); + bool common = true; + for (QObject *obj : objects) + { + if (obj->metaObject()->indexOfProperty(name) >= 0) + continue; + + common = false; + break; + } + + if (common) + result.append(prop); + } + + return result; +} + +// Return merged value for a given property across selected objects +// If the property value is the same then we return it, if it's different we return an invalid variant +static QVariant getMergedPropertyValue(const QObjectList &objects, const QMetaProperty &prop) +{ + if (objects.isEmpty()) + return QVariant(); + + const char *name = prop.name(); + const QVariant firstValue = objects.first()->property(name); + + for (QObject *obj : objects) + { + if (obj->property(name) == firstValue) + continue; + + return QVariant(); // blank if different + } + + return firstValue; +} + +static PM::Property createGridProperty(const QMetaProperty &metaObjectProperty) +{ + PM::Property result(metaObjectProperty.name(), metaObjectProperty.type()); + + if (!metaObjectProperty.isWritable()) + result.addAttribute(PM::ReadOnlyAttribute()); + + return result; +} + +ObjectPropertyGrid::ObjectPropertyGrid(QWidget *parent) : PM::PropertyGrid(parent) +{ +} + +void ObjectPropertyGrid::setSelectedObject(QObject *object) +{ + m_objects.clear(); + + if (object != nullptr) + m_objects.append(object); + + updateProperties(); +} + +void ObjectPropertyGrid::setSelectedObjects(const QObjectList &objects) +{ + m_objects = objects; + + updateProperties(); +} + +QObjectList ObjectPropertyGrid::selectedObjects() const +{ + return m_objects; +} + +QObject *ObjectPropertyGrid::selectedObject() const +{ + return m_objects.isEmpty() ? nullptr : m_objects.first(); +} + +void ObjectPropertyGrid::updateProperties() +{ + clearProperties(); + + for (const auto &connection : qAsConst(m_objectsConnections)) + disconnect(connection); + m_objectsConnections.clear(); + + if (m_objects.isEmpty()) + return; + + const QList commonProperties = getCommonProperties(m_objects); + for (const QMetaProperty &metaProperty : commonProperties) + { + const PM::Property gridProperty = createGridProperty(metaProperty); + const QVariant propertyValue = getMergedPropertyValue(m_objects, metaProperty); + addProperty(gridProperty, propertyValue); + + // make sure to propagate properties values that are changed in the UI to the underlying objects + m_objectsConnections << connect(this, &PM::PropertyGrid::propertyValueChanged, this, + [this](const PM::PropertyContext &context) + { + for (QObject *obj : qAsConst(m_objects)) + obj->setProperty(context.property().name().toUtf8().constData(), context.value()); + }); + } +} diff --git a/examples/object_property_grid/ObjectPropertyGrid.h b/examples/object_property_grid/ObjectPropertyGrid.h new file mode 100644 index 0000000..cc23cf4 --- /dev/null +++ b/examples/object_property_grid/ObjectPropertyGrid.h @@ -0,0 +1,26 @@ +#ifndef OBJECTPROPERTYGRID_H +#define OBJECTPROPERTYGRID_H + +#include + +class ObjectPropertyGrid : public PM::PropertyGrid +{ + Q_OBJECT +public: + explicit ObjectPropertyGrid(QWidget *parent = nullptr); + + QObject *selectedObject() const; + void setSelectedObject(QObject *object); + + QObjectList selectedObjects() const; + void setSelectedObjects(const QObjectList &objects); + +private: + void updateProperties(); + +private: + QObjectList m_objects; + QVector m_objectsConnections; +}; + +#endif // OBJECTPROPERTYGRID_H diff --git a/examples/object_property_grid/README.md b/examples/object_property_grid/README.md new file mode 100644 index 0000000..830f93b --- /dev/null +++ b/examples/object_property_grid/README.md @@ -0,0 +1,22 @@ +# Object Property Grid Example + +![Preview](object_property_grid_example.png) + +## Overview +This example demonstrates how to integrate a custom **ObjectPropertyGrid** into a Qt application. The property grid allows inspection and editing of common properties across multiple selected `QObject` instances. It is designed to behave similarly to the property grid found in development environments, showing shared properties and synchronizing changes back to the selected objects. + +## Features +- Uses `QMetaProperty` introspection to dynamically build the grid. +- Displays only properties that are common across all selected objects. +- Supports both single and multiple object selection. +- Updates property values directly on the underlying `QObject` instances. + +## How It Works +1. The tree view lists child widgets of the central widget. +2. Selecting one or more items updates the property grid with common properties. +3. Editing a property in the grid applies the change to all selected objects. +4. Connections are cleared and rebuilt whenever the selection changes to avoid stale signal bindings. + +## Structure +- **MainWindow**: Demonstrates usage by populating a tree of widgets and connecting selection changes to the property grid. +- **ObjectPropertyGrid**: Inherits from `PM::PropertyGrid` and manages the selected objects and property display. \ No newline at end of file diff --git a/examples/object_property_grid/main.cpp b/examples/object_property_grid/main.cpp new file mode 100644 index 0000000..acdc223 --- /dev/null +++ b/examples/object_property_grid/main.cpp @@ -0,0 +1,13 @@ +#include "MainWindow.h" + +#include + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + MainWindow mainWindow; + mainWindow.show(); + + return app.exec(); +} diff --git a/examples/object_property_grid/object_property_grid_example.png b/examples/object_property_grid/object_property_grid_example.png new file mode 100644 index 0000000..6abe378 Binary files /dev/null and b/examples/object_property_grid/object_property_grid_example.png differ diff --git a/examples/property_grid_showcase/CMakeLists.txt b/examples/property_grid_showcase/CMakeLists.txt index f6b9f20..3d61281 100644 --- a/examples/property_grid_showcase/CMakeLists.txt +++ b/examples/property_grid_showcase/CMakeLists.txt @@ -11,5 +11,6 @@ add_executable(property_grid_showcase ) target_link_libraries(property_grid_showcase - PM::PropertyGrid + PRIVATE + PM::PropertyGrid ) diff --git a/examples/property_grid_showcase/main.cpp b/examples/property_grid_showcase/main.cpp index 6bc57de..b985f14 100644 --- a/examples/property_grid_showcase/main.cpp +++ b/examples/property_grid_showcase/main.cpp @@ -46,13 +46,6 @@ int main(int argc, char **argv) QApplication app(argc, argv); PM::PropertyGrid propertyGrid; - propertyGrid.addPropertyEditor(); - propertyGrid.addPropertyEditor(); - propertyGrid.addPropertyEditor(); - propertyGrid.addPropertyEditor(); - propertyGrid.addPropertyEditor(); - propertyGrid.addPropertyEditor(); - propertyGrid.addPropertyEditor(); // add properties addProperty(propertyGrid, "Property1", "Value1", "Description of property1"); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f5e61bc..d94f164 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,9 +12,10 @@ find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Widgets REQUIRED) set(PUBLIC_HEADERS Property.h + QtCompat_p.h PropertyGrid.h PropertyEditor.h - QtCompat_p.h + PropertyContext.h ) add_library(PmPropertyGrid STATIC @@ -23,15 +24,23 @@ add_library(PmPropertyGrid STATIC PropertyGrid.ui PropertyGrid_p.h PropertyGrid.cpp - PropertyGridTreeItem.h + PropertyGridTreeItem_p.h PropertyGridTreeItem.cpp - PropertyGridTreeModel.h + PropertyGridTreeModel_p.h PropertyGridTreeModel.cpp + PropertyContext_p.h + PropertyContext.cpp + ${PUBLIC_HEADERS} ) add_library(PM::PropertyGrid ALIAS PmPropertyGrid) +target_include_directories(PmPropertyGrid + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + target_link_libraries(PmPropertyGrid # using versionless aliasses like Qt::Core doesn't work prior to Qt5.15 Qt${QT_VERSION_MAJOR}::Core diff --git a/src/PropertyContext.cpp b/src/PropertyContext.cpp new file mode 100644 index 0000000..1b9b5a3 --- /dev/null +++ b/src/PropertyContext.cpp @@ -0,0 +1,102 @@ +#include "PropertyContext.h" +#include "PropertyContext_p.h" + +#include "PropertyGrid.h" + +using namespace PM; + +PropertyContext &PropertyContextPrivate::invalidContext() +{ + static PropertyContext result; + + return result; +} + +PropertyContext PropertyContextPrivate::createContext(const Property &property) +{ + return PropertyContext(property, QVariant(), nullptr, nullptr); +} + +PropertyContext PropertyContextPrivate::createContext(const QVariant &value, bool valid) +{ + PropertyContext result; + result.m_value = value; + result.m_isValid = valid; + + return result; +} + +PropertyContext PropertyContextPrivate::createContext(const PropertyContext &other, const QVariant &newValue) +{ + PropertyContext result = other; + result.m_value = newValue; + + return result; +} + +PropertyContext PropertyContextPrivate::createContext(const Property &property, const QVariant &value, void *object, PropertyGrid *propertyGrid) +{ + return PropertyContext(property, value, object, propertyGrid); +} + +void PropertyContextPrivate::setValue(PropertyContext &context, const QVariant &value) +{ + context.m_value = value; + notifyValueChanged(context, value); +} + +PropertyContextPrivate::valueChangedSlot_t PropertyContextPrivate::defaultValueChangedSlot() +{ + static valueChangedSlot_t result = [](const QVariant &) {}; + + return result; +} + +void PropertyContextPrivate::disconnectValueChangedSlot(PropertyContext &context) +{ + context.m_valueChangedSlot = defaultValueChangedSlot(); +} + +void PropertyContextPrivate::connectValueChangedSlot(PropertyContext &context, const valueChangedSlot_t &slot) +{ + context.m_valueChangedSlot = slot; +} + +void PropertyContextPrivate::notifyValueChanged(const PropertyContext &context, const QVariant &newValue) +{ + context.m_valueChangedSlot(newValue); +} + +Property PropertyContext::property() const +{ + return m_property; +} + +QVariant PropertyContext::value() const +{ + return m_value; +} + +bool PropertyContext::isValid() const +{ + return m_isValid; +} + +PropertyGrid *PropertyContext::propertyGrid() const +{ + return m_propertyGrid; +} + +PropertyContext::PropertyContext() : PropertyContext(Property(), QVariant(), nullptr, nullptr) +{ +} + +PropertyContext::PropertyContext(const Property &property, const QVariant &value, void *object, PropertyGrid *propertyGrid) : + m_property(property), + m_value(value), + m_object(object), + m_propertyGrid(propertyGrid), + m_isValid(!property.name().isEmpty()), + m_valueChangedSlot(PropertyContextPrivate::defaultValueChangedSlot()) +{ +} diff --git a/src/PropertyContext.h b/src/PropertyContext.h new file mode 100644 index 0000000..2c6a1ee --- /dev/null +++ b/src/PropertyContext.h @@ -0,0 +1,65 @@ +#ifndef PROPERTYCONTEXT_H +#define PROPERTYCONTEXT_H + +#include "Property.h" + +#include + +namespace PM +{ +class PropertyGrid; +class PropertyContextPrivate; + +class PropertyContext +{ + friend class PM::PropertyContextPrivate; + +public: + Property property() const; + QVariant value() const; + + bool isValid() const; + + template + T object() const // WARNING: this function doesn't perform any checks for type-safety + { + return getObjectValue(std::is_pointer()); + } + + PropertyGrid *propertyGrid() const; + +private: // TODO: wouldn't this cause problems when writing unit tests for user property editors? + PropertyContext(); + PropertyContext(const Property &property, const QVariant &value, void *object, PropertyGrid *propertyGrid); + + // function for pointer types + template + T getObjectValue(std::true_type) const + { + return static_cast(m_object); + } + + // function for non-pointer types + template + T getObjectValue(std::false_type) const + { + T *result = static_cast(m_object); + if (result == nullptr) + return T(); + + return *result; + } + +private: + Property m_property; + QVariant m_value; + void *m_object; // TODO: use std::any + QPointer m_propertyGrid; + + // Meta values + bool m_isValid; + std::function m_valueChangedSlot; +}; +} // namespace PM + +#endif // PROPERTYCONTEXT_H diff --git a/src/PropertyContext_p.h b/src/PropertyContext_p.h new file mode 100644 index 0000000..0b596d5 --- /dev/null +++ b/src/PropertyContext_p.h @@ -0,0 +1,42 @@ +#ifndef PROPERTYCONTEXT_P_H +#define PROPERTYCONTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the PM::PropertyGrid API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// + +#include "PropertyContext.h" + +namespace PM +{ +class PropertyContextPrivate +{ +public: + using valueChangedSlot_t = std::function; + +public: + static PropertyContext &invalidContext(); + + static PropertyContext createContext(const Property &property); + static PropertyContext createContext(const QVariant &value, bool valid = true); + static PropertyContext createContext(const PropertyContext &other, const QVariant &newValue); + static PropertyContext createContext(const Property &property, const QVariant &value, void *object, PropertyGrid *propertyGrid); + + static void setValue(PropertyContext &context, const QVariant &value); + + static valueChangedSlot_t defaultValueChangedSlot(); + + static void disconnectValueChangedSlot(PropertyContext &context); + static void connectValueChangedSlot(PropertyContext &context, const valueChangedSlot_t &slot); + + static void notifyValueChanged(const PropertyContext &context, const QVariant &newValue); +}; +} // namespace PM + +#endif // PROPERTYCONTEXT_P_H diff --git a/src/PropertyEditor.cpp b/src/PropertyEditor.cpp index 1ed4310..1dde82a 100644 --- a/src/PropertyEditor.cpp +++ b/src/PropertyEditor.cpp @@ -1,6 +1,6 @@ #include "PropertyEditor.h" -#include "PropertyGrid_p.h" +#include "PropertyContext_p.h" #include "QtCompat_p.h" #include @@ -39,38 +39,29 @@ const QString &boolToString(bool value) return value ? trueKey : falseKey; } -Property PropertyContext::property() const +template +std::pair> createPropertyEditorMapEntry() { - return m_property; + return std::pair>(internal::getTypeId(), std::make_shared()); } -QVariant PropertyContext::value() const +const PropertyEditorsMap_t &PM::internal::defaultPropertyEditors() { - return m_value; -} - -bool PropertyContext::isValid() const -{ - return m_isValid; -} - -PropertyGrid *PropertyContext::propertyGrid() const -{ - return m_propertyGrid; -} + // TODO: should have the ability to modify this list at runtime if we ever made this API public -PropertyContext::PropertyContext() : PropertyContext(Property(), QVariant(), nullptr, nullptr) -{ -} + // clang-format off + static PropertyEditorsMap_t instance = { + createPropertyEditorMapEntry(), + createPropertyEditorMapEntry(), + createPropertyEditorMapEntry(), + createPropertyEditorMapEntry(), + createPropertyEditorMapEntry(), + createPropertyEditorMapEntry(), + createPropertyEditorMapEntry(), + }; + // clang-format on -PropertyContext::PropertyContext(const Property &property, const QVariant &value, void *object, PropertyGrid *propertyGrid) : - m_property(property), - m_value(value), - m_object(object), - m_propertyGrid(propertyGrid), - m_isValid(!property.name().isEmpty()), - m_valueChangedSlot(PropertyContextPrivate::defaultValueChangedSlot()) -{ + return instance; } bool PropertyEditor::canHandle(const PropertyContext &context) const @@ -474,7 +465,7 @@ QVariant FontPropertyEditor::fromString(const QString &value, const PropertyCont QString sizeValue = properties[1]; bool isPixelSize = sizeValue.endsWith("px"); - int fontSize = sizeValue.left(sizeValue.length() - (isPixelSize ? 2 : 0)).toInt(); + int fontSize = PM::internal::stringLeft(sizeValue, sizeValue.length() - (isPixelSize ? 2 : 0)).toInt(); QFont font(fontFamily); if (isPixelSize) @@ -488,15 +479,15 @@ QVariant FontPropertyEditor::fromString(const QString &value, const PropertyCont const QString &prop = properties[i]; if (prop.startsWith(weightKey)) - font.setWeight(static_cast(prop.mid(weightKey.length()).toInt())); + font.setWeight(static_cast(PM::internal::stringMid(prop, weightKey.length()).toInt())); else if (prop.startsWith(styleKey)) - font.setStyle(static_cast(prop.mid(styleKey.length()).toInt())); + font.setStyle(static_cast(PM::internal::stringMid(prop, styleKey.length()).toInt())); else if (prop.startsWith(underlineKey)) - font.setUnderline(prop.mid(underlineKey.length()).toInt()); + font.setUnderline(PM::internal::stringMid(prop, underlineKey.length()).toInt()); else if (prop.startsWith(strikeOutKey)) - font.setStrikeOut(prop.mid(strikeOutKey.length()).toInt()); + font.setStrikeOut(PM::internal::stringMid(prop, strikeOutKey.length()).toInt()); else if (prop.startsWith(kerningKey)) - font.setKerning(prop.mid(kerningKey.length()).toInt()); + font.setKerning(PM::internal::stringMid(prop, kerningKey.length()).toInt()); } return QVariant::fromValue(font); diff --git a/src/PropertyEditor.h b/src/PropertyEditor.h index cbb579d..877cef2 100644 --- a/src/PropertyEditor.h +++ b/src/PropertyEditor.h @@ -1,7 +1,7 @@ #ifndef PROPERTYEDITOR_H #define PROPERTYEDITOR_H -#include "Property.h" +#include "PropertyContext.h" #include @@ -15,8 +15,6 @@ class PropertyEditor; namespace internal { - class PropertyContextPrivate; - template constexpr bool isPropertyEditor() { @@ -27,58 +25,10 @@ namespace internal return isPropertyEditor; } -} // namespace internal - -class PropertyContext -{ - friend class PM::internal::PropertyContextPrivate; - -public: - Property property() const; - QVariant value() const; - - bool isValid() const; - - template - T object() const // WARNING: this function would never perform any checks for type-safety - { - return getObjectValue(std::is_pointer()); - } - - PropertyGrid *propertyGrid() const; - -private: // TODO: wouldn't this cause problems when writing unit tests for user property editors? - PropertyContext(); - PropertyContext(const Property &property, const QVariant &value, void *object, PropertyGrid *propertyGrid); - // function for pointer types - template - T getObjectValue(std::true_type) const - { - return static_cast(m_object); - } - - // function for non-pointer types - template - T getObjectValue(std::false_type) const - { - T *result = static_cast(m_object); - if (result == nullptr) - return T(); - - return *result; - } - -private: - Property m_property; - QVariant m_value; - void *m_object; // TODO: use std::any - QPointer m_propertyGrid; - - // Meta values - bool m_isValid; - std::function m_valueChangedSlot; -}; + using PropertyEditorsMap_t = std::unordered_map>; + const PropertyEditorsMap_t &defaultPropertyEditors(); +} // namespace internal class PropertyEditor { diff --git a/src/PropertyGrid.cpp b/src/PropertyGrid.cpp index 6028889..6e72d86 100644 --- a/src/PropertyGrid.cpp +++ b/src/PropertyGrid.cpp @@ -1,8 +1,9 @@ #include "PropertyGrid.h" #include "PropertyGrid_p.h" -#include "PropertyGridTreeItem.h" -#include "PropertyGridTreeModel.h" +#include "PropertyContext_p.h" +#include "PropertyGridTreeItem_p.h" +#include "PropertyGridTreeModel_p.h" #include "QtCompat_p.h" #include @@ -357,69 +358,6 @@ QString internal::PropertyEditorWidget::valueToString(const QVariant &value) con return propertyEditor()->toString(newContext); } -PropertyContext &internal::PropertyContextPrivate::invalidContext() -{ - static PropertyContext result; - - return result; -} - -PropertyContext internal::PropertyContextPrivate::createContext(const Property &property) -{ - return PropertyContext(property, QVariant(), nullptr, nullptr); -} - -PropertyContext internal::PropertyContextPrivate::createContext(const QVariant &value, bool valid) -{ - PropertyContext result; - result.m_value = value; - result.m_isValid = valid; - - return result; -} - -PropertyContext internal::PropertyContextPrivate::createContext(const PropertyContext &other, const QVariant &newValue) -{ - PropertyContext result = other; - result.m_value = newValue; - - return result; -} - -PropertyContext internal::PropertyContextPrivate::createContext(const Property &property, const QVariant &value, void *object, - PropertyGrid *propertyGrid) -{ - return PropertyContext(property, value, object, propertyGrid); -} - -void internal::PropertyContextPrivate::setValue(PropertyContext &context, const QVariant &value) -{ - context.m_value = value; - notifyValueChanged(context, value); -} - -internal::PropertyContextPrivate::valueChangedSlot_t internal::PropertyContextPrivate::defaultValueChangedSlot() -{ - static valueChangedSlot_t result = [](const QVariant &) {}; - - return result; -} - -void internal::PropertyContextPrivate::disconnectValueChangedSlot(PropertyContext &context) -{ - context.m_valueChangedSlot = defaultValueChangedSlot(); -} - -void internal::PropertyContextPrivate::connectValueChangedSlot(PropertyContext &context, const valueChangedSlot_t &slot) -{ - context.m_valueChangedSlot = slot; -} - -void internal::PropertyContextPrivate::notifyValueChanged(const PropertyContext &context, const QVariant &newValue) -{ - context.m_valueChangedSlot(newValue); -} - QString internal::ModelIndexHelperFunctions::firstValueInRowAsString(const QModelIndex &index, Qt::ItemDataRole role) { QVariant result = firstValueInRow(index, role); @@ -546,7 +484,11 @@ QSize internal::PropertyGridItemDelegate::sizeHint(const QStyleOptionViewItem &o return result; } -PropertyGridPrivate::PropertyGridPrivate(PropertyGrid *q) : q(q), ui(new Ui::PropertyGrid()), tableViewItemDelegate(q) +PropertyGridPrivate::PropertyGridPrivate(PropertyGrid *q) : + q(q), + ui(new Ui::PropertyGrid()), + tableViewItemDelegate(q), + m_propertyEditors(internal::defaultPropertyEditors()) { } @@ -587,7 +529,7 @@ void PropertyGridPrivate::updatePropertyValue(const QModelIndex &index, const QV QModelIndex valueIndex = PM::internal::siblingAtColumn(index, 1); - internal::PropertyContextPrivate::setValue(context, value); + PropertyContextPrivate::setValue(context, value); m_model.setData(valueIndex, value, Qt::EditRole); m_model.setData(valueIndex, editor->toString(context), Qt::DisplayRole); @@ -719,7 +661,7 @@ void PropertyGrid::addProperty(const Property &property, const QVariant &value, return; } - PropertyContext context = internal::PropertyContextPrivate::createContext(property, value, object, this); + PropertyContext context = PropertyContextPrivate::createContext(property, value, object, this); QModelIndex propertyIndex = d->m_model.addProperty(context); @@ -765,10 +707,52 @@ PropertyContext PropertyGrid::getPropertyContext(const QString &propertyName) co if (treeItem != nullptr) return treeItem->context; - return internal::PropertyContextPrivate::invalidContext(); + return PropertyContextPrivate::invalidContext(); +} + +void PropertyGrid::clearProperties() +{ + d->m_model.clearModel(); +} + +QStringList PropertyGrid::propertyNames() const +{ + return d->m_model.getPropertiesNames(); +} + +void PropertyGrid::replacePropertyEditor_impl(TypeId oldEditorTypeId, TypeId newEditorTypeId, std::shared_ptr &&editor) +{ + if (d->m_propertyEditors.find(oldEditorTypeId) == d->m_propertyEditors.end()) + { + qWarning() << "Cannot replace a non-existing editor"; + return; + } + + d->m_propertyEditors.erase(oldEditorTypeId); + + // no need to add a new instance of the default property editor if the user specified it explicitly + if (newEditorTypeId == internal::getTypeId()) + return; + + addPropertyEditor_impl(newEditorTypeId, std::move(editor)); } -void PropertyGrid::addPropertyEditor_impl(TypeId typeId, std::unique_ptr &&editor) +void PropertyGrid::addPropertyEditor_impl(TypeId typeId, std::shared_ptr &&editor) { + // If the new editor already exists then there is no need to add it again + if (d->m_propertyEditors.find(typeId) != d->m_propertyEditors.end()) + return; + d->m_propertyEditors.emplace(typeId, std::move(editor)); + + // Force all property entries in the view to get calculated using the updated editors list + const QStringList names = propertyNames(); + for (const QString &propertyName : names) + { + const PropertyContext context = getPropertyContext(propertyName); + internal::PropertyGridTreeItem *item = d->m_model.getPropertyItem(propertyName); + const QModelIndex propertyIndex = d->m_model.getItemIndex(item); + + d->updatePropertyValue(propertyIndex, context.value()); + } } diff --git a/src/PropertyGrid.h b/src/PropertyGrid.h index 8c8bedb..9b6f6f7 100644 --- a/src/PropertyGrid.h +++ b/src/PropertyGrid.h @@ -1,8 +1,8 @@ #ifndef PROPERTYGRID_H #define PROPERTYGRID_H -#include "QtCompat_p.h" #include "PropertyEditor.h" +#include "QtCompat_p.h" #include @@ -30,14 +30,7 @@ class PropertyGrid : public QWidget // @@ CONVENIENCE template - void addProperty(const QString &name, const QVariant &value, const Attributes &...attributes) - { - if (!value.isValid()) - qWarning() << "PropertyGrid::addProperty(): Cannot infer the type of property" << name - << "because the provided QVariant value is invalid."; - - addProperty(Property(name, internal::getVariantTypeId(value), attributes...), value); - } + void addProperty(const QString &name, const QVariant &value, const Attributes &...attributes); // TODO: change to return false if a property editor returns subProperties list with invalid names?!! template ()>> @@ -51,17 +44,47 @@ class PropertyGrid : public QWidget PropertyContext getPropertyContext(const QString &propertyName) const; +public: /* EXPERIMENTAL API */ + /**/ + template ()>, + typename = internal::templateCheck_t()> + // clang-format on + > + void replacePropertyEditor(); + + void clearProperties(); // Requested: 2 + QStringList propertyNames() const; // Requested: 2 + signals: void propertyValueChanged(const PM::PropertyContext &context); private: // stable internal functions - void addPropertyEditor_impl(TypeId, std::unique_ptr &&editor); + void replacePropertyEditor_impl(TypeId oldEditorTypeId, TypeId newEditorTypeId, std::shared_ptr &&editor); + + void addPropertyEditor_impl(TypeId typeId, std::shared_ptr &&editor); private: PropertyGridPrivate *d; }; } // namespace PM +template +inline void PM::PropertyGrid::addProperty(const QString &name, const QVariant &value, const Attributes &...attributes) +{ + if (!value.isValid()) + qWarning() << "PropertyGrid::addProperty(): Cannot infer the type of property" << name << "because the provided QVariant value is invalid."; + + addProperty(Property(name, internal::getVariantTypeId(value), attributes...), value); +} + +template +inline void PM::PropertyGrid::replacePropertyEditor() +{ + replacePropertyEditor_impl(internal::getTypeId(), internal::getTypeId(), std::make_unique()); +} + template inline void PM::PropertyGrid::addPropertyEditor() { diff --git a/src/PropertyGridTreeItem.cpp b/src/PropertyGridTreeItem.cpp index 21452a1..2d5dcaf 100644 --- a/src/PropertyGridTreeItem.cpp +++ b/src/PropertyGridTreeItem.cpp @@ -1,5 +1,6 @@ -#include "PropertyGridTreeItem.h" -#include "PropertyGrid_p.h" +#include "PropertyGridTreeItem_p.h" + +#include "PropertyContext_p.h" using namespace PM; @@ -178,7 +179,7 @@ internal::PropertyGridTreeItem *internal::PropertyGridTreeItem::getChild(size_t internal::PropertyGridTreeItem *internal::PropertyGridTreeItem::addChild(const PropertyContext &context) { - if (!insertChildren(children.size(), 1, 2)) + if (!insertChildren(static_cast(children.size()), 1, 2)) return nullptr; auto &newChild = children.back(); @@ -269,10 +270,7 @@ int internal::PropertyGridTreeItem::indexInParent(bool showTransientItems) const return -1; } -internal::PropertyGridTreeItem::PropertyGridTreeItem() : - context(PM::internal::PropertyContextPrivate::invalidContext()), - parent(nullptr), - isTransient(false) +internal::PropertyGridTreeItem::PropertyGridTreeItem() : context(PM::PropertyContextPrivate::invalidContext()), parent(nullptr), isTransient(false) { } diff --git a/src/PropertyGridTreeItem.h b/src/PropertyGridTreeItem_p.h similarity index 88% rename from src/PropertyGridTreeItem.h rename to src/PropertyGridTreeItem_p.h index 01df53a..be761d0 100644 --- a/src/PropertyGridTreeItem.h +++ b/src/PropertyGridTreeItem_p.h @@ -1,10 +1,21 @@ -#ifndef PROPERTYGRIDTREEITEM_H -#define PROPERTYGRIDTREEITEM_H +#ifndef PROPERTYGRIDTREEITEM_P_H +#define PROPERTYGRIDTREEITEM_P_H -#include "PropertyEditor.h" +// +// W A R N I N G +// ------------- +// +// This file is not part of the PM::PropertyGrid API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// + +#include "PropertyContext.h" #include #include +#include class TreeItem { @@ -106,4 +117,4 @@ namespace internal } // namespace internal } // namespace PM -#endif // PROPERTYGRIDTREEITEM_H +#endif // PROPERTYGRIDTREEITEM_P_H diff --git a/src/PropertyGridTreeModel.cpp b/src/PropertyGridTreeModel.cpp index 1ae1f99..26fdbad 100644 --- a/src/PropertyGridTreeModel.cpp +++ b/src/PropertyGridTreeModel.cpp @@ -1,6 +1,8 @@ -#include "PropertyGridTreeModel.h" -#include "PropertyGridTreeItem.h" -#include "PropertyGrid_p.h" +#include "PropertyGridTreeModel_p.h" + +#include "PropertyContext_p.h" +#include "PropertyGridTreeItem_p.h" +#include "QtCompat_p.h" #include #include @@ -114,14 +116,7 @@ void internal::PropertyGridTreeModel::setShowCategories(bool newShowCategories) m_showCategories = newShowCategories; - beginResetModel(); - - // Notify about the structural change - emit layoutAboutToBeChanged(); - emit dataChanged(createIndex(0, 0), createIndex(rowCount(), columnCount())); - emit layoutChanged(); - - endResetModel(); + update(); } internal::PropertyGridTreeItem *internal::PropertyGridTreeModel::rootItem() const @@ -173,12 +168,35 @@ QModelIndex internal::PropertyGridTreeModel::getItemIndex(PropertyGridTreeItem * return createIndex(item->indexInParent(m_showCategories), 0, item); } +void internal::PropertyGridTreeModel::clearModel() +{ + beginResetModel(); + { + m_categoriesMap.clear(); + m_propertiesMap.clear(); + m_rootItem->children.clear(); + } + endResetModel(); +} + +void internal::PropertyGridTreeModel::update() +{ + beginResetModel(); + + // Notify about the structural change + emit layoutAboutToBeChanged(); + emit dataChanged(createIndex(0, 0), createIndex(rowCount(), columnCount())); + emit layoutChanged(); + + endResetModel(); +} + internal::PropertyGridTreeItem *internal::PropertyGridTreeModel::getCategoryItem(const QString &category) { if (m_categoriesMap.contains(category)) return m_categoriesMap.value(category); - const PropertyContext tempCategoryContext = internal::PropertyContextPrivate::createContext(PM::Property(category, QMetaType::UnknownType)); + const PropertyContext tempCategoryContext = PropertyContextPrivate::createContext(PM::Property(category, QMetaType::UnknownType)); PropertyGridTreeItem *result = m_rootItem->addChild(tempCategoryContext); result->isTransient = true; // TODO: make category item expanded by default?!! diff --git a/src/PropertyGridTreeModel.h b/src/PropertyGridTreeModel_p.h similarity index 78% rename from src/PropertyGridTreeModel.h rename to src/PropertyGridTreeModel_p.h index ae3345e..a2a73f0 100644 --- a/src/PropertyGridTreeModel.h +++ b/src/PropertyGridTreeModel_p.h @@ -1,5 +1,15 @@ -#ifndef PROPERTYGRIDTREEMODEL_H -#define PROPERTYGRIDTREEMODEL_H +#ifndef PROPERTYGRIDTREEMODEL_P_H +#define PROPERTYGRIDTREEMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the PM::PropertyGrid API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// #include "PropertyEditor.h" @@ -12,7 +22,7 @@ class PropertyGrid; namespace internal { - class PropertyGridTreeItem; + struct PropertyGridTreeItem; class PropertyGridTreeModel : public QAbstractItemModel { @@ -56,6 +66,11 @@ namespace internal PropertyGridTreeItem *getItem(const QModelIndex &index) const; // FIXME: should this be public?!! QModelIndex getItemIndex(PropertyGridTreeItem *item) const; + void clearModel(); + QStringList getPropertiesNames() const; + + void update(); + private: bool m_showCategories; PropertyGridTreeItem *m_rootItem; @@ -66,4 +81,9 @@ namespace internal } // namespace internal } // namespace PM -#endif // PROPERTYGRIDTREEMODEL_H +inline QStringList PM::internal::PropertyGridTreeModel::getPropertiesNames() const +{ + return m_propertiesMap.keys(); +} + +#endif // PROPERTYGRIDTREEMODEL_P_H diff --git a/src/PropertyGrid_p.h b/src/PropertyGrid_p.h index c4f0e63..af96294 100644 --- a/src/PropertyGrid_p.h +++ b/src/PropertyGrid_p.h @@ -1,10 +1,20 @@ #ifndef PROPERTYGRID_P_H #define PROPERTYGRID_P_H +// +// W A R N I N G +// ------------- +// +// This file is not part of the PM::PropertyGrid API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// + #include "PropertyGrid.h" #include "ui_PropertyGrid.h" -#include "PropertyGridTreeModel.h" +#include "PropertyGridTreeModel_p.h" #include #include @@ -112,30 +122,6 @@ namespace internal PropertyEditorLineEdit m_propertylineEdit; }; - // FIXME: move to a more appropriate location - class PropertyContextPrivate - { - public: - using valueChangedSlot_t = std::function; - - public: - static PropertyContext &invalidContext(); - - static PropertyContext createContext(const Property &property); - static PropertyContext createContext(const QVariant &value, bool valid = true); - static PropertyContext createContext(const PropertyContext &other, const QVariant &newValue); - static PropertyContext createContext(const Property &property, const QVariant &value, void *object, PropertyGrid *propertyGrid); - - static void setValue(PropertyContext &context, const QVariant &value); - - static valueChangedSlot_t defaultValueChangedSlot(); - - static void disconnectValueChangedSlot(PropertyContext &context); - static void connectValueChangedSlot(PropertyContext &context, const valueChangedSlot_t &slot); - - static void notifyValueChanged(const PropertyContext &context, const QVariant &newValue); - }; - // TODO: can we get rid of this altogether?!! struct ModelIndexHelperFunctions { @@ -207,7 +193,7 @@ class PropertyGridPrivate internal::PropertyGridItemDelegate tableViewItemDelegate; internal::PropertyGridTreeModel m_model; - std::unordered_map> m_propertyEditors; + internal::PropertyEditorsMap_t m_propertyEditors; }; } // namespace PM diff --git a/src/QtCompat_p.h b/src/QtCompat_p.h index d56ffc3..5e99234 100644 --- a/src/QtCompat_p.h +++ b/src/QtCompat_p.h @@ -17,6 +17,14 @@ namespace PM { namespace internal { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + using StringRef_t = QStringView; +#elif QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + using StringRef_t = QStringRef; +#else + using StringRef_t = QString; +#endif + inline QModelIndex siblingAtColumn(const QModelIndex &index, int column) { #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) @@ -56,6 +64,28 @@ namespace internal #endif } + inline StringRef_t stringLeft(const QString &input, int n) + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QStringView(input).left(n); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + return input.leftRef(n); +#else + return input.left(n); +#endif + } + + inline StringRef_t stringMid(const QString &input, int position, int n = -1) + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QStringView(input).mid(position, n); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + return input.midRef(position, n); +#else + return input.mid(position, n); +#endif + } + // TODO: is this function really needed?!! inline QVariant createDefaultVariantForType(int type) {