diff --git a/.github/workflows/build-shared.yml b/.github/workflows/build-shared.yml new file mode 100644 index 0000000..b6f8d8b --- /dev/null +++ b/.github/workflows/build-shared.yml @@ -0,0 +1,50 @@ +name: Shared Build Logic + +on: + workflow_call: + inputs: + os: + required: true + type: string + c_compiler: + required: true + type: string + cpp_compiler: + required: true + type: string + qt_version: + required: true + type: string + +jobs: + build: + runs-on: ${{ inputs.os }} + steps: + - uses: actions/checkout@v4 + + - name: Set reusable strings + id: strings + shell: bash + run: echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: ${{ inputs.qt_version }} + install-deps: 'true' + set-env: 'true' + + - name: Configure CMake + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_C_COMPILER=${{ inputs.c_compiler }} + -DCMAKE_CXX_COMPILER=${{ inputs.cpp_compiler }} + -DCMAKE_BUILD_TYPE=Release + -S ${{ github.workspace }} + + - name: Build + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + run: ctest --build-config Release diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml new file mode 100644 index 0000000..7579cd6 --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,33 @@ +name: Linux Build + +on: + workflow_dispatch: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + get-matrix: + uses: ./.github/workflows/matrix-config.yml + + build-linux: + name: linux-${{ matrix.c_compiler }}-qt${{ matrix.qt_version }} + needs: get-matrix + strategy: + fail-fast: false + matrix: + c_compiler: ${{ fromJson(needs.get-matrix.outputs.compilers_linux) }} + qt_version: ${{ fromJson(needs.get-matrix.outputs.qt_versions) }} + include: + - c_compiler: gcc + cpp_compiler: g++ + - c_compiler: clang + cpp_compiler: clang++ + + uses: ./.github/workflows/build-shared.yml + with: + os: ubuntu-latest + c_compiler: ${{ matrix.c_compiler }} + cpp_compiler: ${{ matrix.cpp_compiler }} + qt_version: ${{ matrix.qt_version }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000..9a742a3 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,59 @@ +name: macOS Build + +on: + workflow_dispatch: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + get-matrix: + uses: ./.github/workflows/matrix-config.yml + + build-macos-intel: + name: macos-${{ matrix.c_compiler }}-qt${{ matrix.qt_version }}-intel + needs: get-matrix + strategy: + fail-fast: false + matrix: + c_compiler: ${{ fromJson(needs.get-matrix.outputs.compilers_macos) }} + qt_version: ${{ fromJson(needs.get-matrix.outputs.qt_versions) }} + include: + - c_compiler: gcc + cpp_compiler: g++ + - c_compiler: clang + cpp_compiler: clang++ + + uses: ./.github/workflows/build-shared.yml + with: + os: macos-13 # Intel-based macOS + c_compiler: ${{ matrix.c_compiler }} + cpp_compiler: ${{ matrix.cpp_compiler }} + qt_version: ${{ matrix.qt_version }} + + build-macos-arm: + name: macos-${{ matrix.c_compiler }}-qt${{ matrix.qt_version }}-arm + needs: get-matrix + strategy: + fail-fast: false + matrix: + c_compiler: ${{ fromJson(needs.get-matrix.outputs.compilers_macos) }} + qt_version: ${{ fromJson(needs.get-matrix.outputs.qt_versions) }} + include: + - c_compiler: gcc + cpp_compiler: g++ + - c_compiler: clang + cpp_compiler: clang++ + exclude: + # Exclude Qt5 versions for ARM builds + - qt_version: "5.9.*" + - qt_version: "5.12.*" + - qt_version: "5.15.*" + + uses: ./.github/workflows/build-shared.yml + with: + os: macos-latest # Apple Silicon macOS + c_compiler: ${{ matrix.c_compiler }} + cpp_compiler: ${{ matrix.cpp_compiler }} + qt_version: ${{ matrix.qt_version }} diff --git a/.github/workflows/matrix-config.yml b/.github/workflows/matrix-config.yml new file mode 100644 index 0000000..c5b8373 --- /dev/null +++ b/.github/workflows/matrix-config.yml @@ -0,0 +1,28 @@ +name: Matrix Configuration + +on: + workflow_call: + outputs: + qt_versions: + description: "Qt versions to test against (Usually LTS versions)" + # lowest LTS version that we can get through aqt is 5.9, so we start from there + # that's the reason why "5.6.*" is not included + value: '["5.9.*", "5.12.*", "5.15.*", "6.2.*", "6.5.*", "6.8.*"]' + compilers_linux: + description: "Compilers for Linux" + value: '["gcc", "clang"]' + compilers_windows: + description: "Compilers for Windows" + value: '["cl", "gcc", "clang"]' + compilers_macos: + description: "Compilers for macOS" + value: '["gcc", "clang"]' + +jobs: + # This job exists only to make the workflow valid + # The outputs are defined statically above + placeholder: + runs-on: ubuntu-latest + steps: + - name: Configuration ready + run: echo "Matrix configuration is ready" diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/old/cmake-multi-platform.yml similarity index 84% rename from .github/workflows/cmake-multi-platform.yml rename to .github/workflows/old/cmake-multi-platform.yml index ffc046d..ac8cbdc 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/old/cmake-multi-platform.yml @@ -4,9 +4,19 @@ name: CMake on multiple platforms on: push: - branches: [ "main" ] + branches: [ "main", "develop" ] pull_request: - branches: [ "main" ] + branches: [ "main", "develop" ] + workflow_dispatch: + inputs: + environment: + description: "Select environment" + required: true + type: choice + options: + - linux + - windows + - macos jobs: build: @@ -23,24 +33,38 @@ jobs: # # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest, windows-latest, macos-latest] build_type: [Release] c_compiler: [gcc, clang, cl] include: + # windows - os: windows-latest c_compiler: cl cpp_compiler: cl + - os: windows-latest + c_compiler: gcc + cpp_compiler: g++ + - os: windows-latest + c_compiler: clang + cpp_compiler: clang++ + # linux - os: ubuntu-latest c_compiler: gcc cpp_compiler: g++ - os: ubuntu-latest c_compiler: clang cpp_compiler: clang++ - exclude: - - os: windows-latest + # macos + - os: macos-latest c_compiler: gcc - - os: windows-latest + cpp_compiler: g++ + - os: macos-latest c_compiler: clang + cpp_compiler: clang++ + + exclude: + - os: macos-latest + c_compiler: cl - os: ubuntu-latest c_compiler: cl diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000..22883ae --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,35 @@ +name: Windows Build + +on: + workflow_dispatch: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + get-matrix: + uses: ./.github/workflows/matrix-config.yml + + build-windows: + name: windows-${{ matrix.c_compiler }}-qt${{ matrix.qt_version }} + needs: get-matrix + strategy: + fail-fast: false + matrix: + qt_version: ${{ fromJson(needs.get-matrix.outputs.qt_versions) }} + c_compiler: ${{ fromJson(needs.get-matrix.outputs.compilers_windows) }} + include: + - c_compiler: cl + cpp_compiler: cl + - c_compiler: gcc + cpp_compiler: g++ + - c_compiler: clang + cpp_compiler: clang++ + + uses: ./.github/workflows/build-shared.yml + with: + os: windows-latest + c_compiler: ${{ matrix.c_compiler }} + cpp_compiler: ${{ matrix.cpp_compiler }} + qt_version: ${{ matrix.qt_version }} diff --git a/README.md b/README.md index e870780..592fbf7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # PmPropertyGrid +[![Windows Build](https://github.com/Nanticock/PmPropertyGrid/actions/workflows/windows.yml/badge.svg)](https://github.com/Nanticock/PmPropertyGrid/actions/workflows/windows.yml) +[![macOS Build](https://github.com/Nanticock/PmPropertyGrid/actions/workflows/macos.yml/badge.svg)](https://github.com/Nanticock/PmPropertyGrid/actions/workflows/macos.yml) +[![Linux Build](https://github.com/Nanticock/PmPropertyGrid/actions/workflows/linux.yml/badge.svg)](https://github.com/Nanticock/PmPropertyGrid/actions/workflows/linux.yml) + A modern, feature-rich property grid widget for C++ applications (currently supporting Qt, but with more GUI frameworks support planned for the future).
This library provides a flexible and customizable property editor that displays object properties in a tree-like structure, similar to property editors found in IDEs and game engines. @@ -46,27 +50,20 @@ This library provides a flexible and customizable property editor that displays - **Cross-Platform**: Compatible with Qt 5.15+ and Qt 6.x 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.15 or Qt 6.x+ (Widgets module required) +- **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 -## Building - -### Using CMake - -```bash -mkdir build -cd build -cmake .. -cmake --build . -``` ## Quick Start ```cpp #include + #include int main(int argc, char *argv[]) @@ -74,6 +71,14 @@ 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")); @@ -102,15 +107,6 @@ int main(int argc, char *argv[]) } ``` -## Examples - -The `examples/property_grid_showcase` directory contains a comprehensive demonstration of the library's capabilities, showcasing: - -- All supported property types -- Property attributes and categorization -- Interactive property editing -- Real-time value change handling - ## API Overview ### Core Classes @@ -136,6 +132,63 @@ propertyGrid.addProperty("Object ID", 12345, PM::ReadOnlyAttribute()); ``` +## Building + +### Using CMake + +```bash +mkdir build +cd build +cmake .. +cmake --build . +``` + +### Integrating into Your Project + +You can easily integrate PmPropertyGrid into your CMake project using `add_subdirectory`: + +```cmake +# Add PmPropertyGrid as a subdirectory +add_subdirectory(path/to/PmPropertyGrid) + +# Link against your target +target_link_libraries(your_target_name PRIVATE PmPropertyGrid) +``` + +**Note**: Currently, the library only supports static linking. Shared library support will be added in the future. + +#### Complete Example + +```cmake +cmake_minimum_required(VERSION 3.10) +project(MyProject) + +# Find Qt +find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) + +# Add PmPropertyGrid +add_subdirectory(third_party/PmPropertyGrid) + +# Create your executable +add_executable(MyApp main.cpp) + +# Link Qt and PmPropertyGrid +target_link_libraries(MyApp PRIVATE + Qt${QT_VERSION_MAJOR}::Widgets + PmPropertyGrid +) +``` + +## Examples + +The `examples/property_grid_showcase` directory contains a comprehensive demonstration of the library's capabilities, showcasing: + +- All supported property types +- Property attributes and categorization +- Interactive property editing +- Real-time value change handling + ## Project Structure ``` diff --git a/examples/property_grid_showcase/CMakeLists.txt b/examples/property_grid_showcase/CMakeLists.txt index 1939c6c..f6b9f20 100644 --- a/examples/property_grid_showcase/CMakeLists.txt +++ b/examples/property_grid_showcase/CMakeLists.txt @@ -2,11 +2,8 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) -find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Widgets REQUIRED) add_executable(property_grid_showcase resources.qrc @@ -14,6 +11,5 @@ add_executable(property_grid_showcase ) target_link_libraries(property_grid_showcase - Qt::Widgets - PmPropertyGrid + PM::PropertyGrid ) diff --git a/examples/property_grid_showcase/main.cpp b/examples/property_grid_showcase/main.cpp index aa6abae..6bc57de 100644 --- a/examples/property_grid_showcase/main.cpp +++ b/examples/property_grid_showcase/main.cpp @@ -1,6 +1,6 @@ #include -#include -#include + +#include #include #include @@ -16,20 +16,6 @@ #include #include -void printTree(const PM::internal::PropertyGridTreeModel *model, bool showTransientItems) -{ - int rowCount = model->rootItem()->childrenCount(showTransientItems); - - qInfo() << rowCount; - - for (int i = 0; i < rowCount; ++i) - { - auto current = model->rootItem()->getChild(i, showTransientItems); - - qInfo() << current->context.property().name << current->childrenCount(showTransientItems); - } -} - PM::Property createProperty(const QString &name, int type, const QString &description = "", const QVariant &defaultValue = QVariant(), const QString &category = "") { @@ -38,7 +24,7 @@ PM::Property createProperty(const QString &name, int type, const QString &descri if (!description.isEmpty()) result.addAttribute(PM::DescriptionAttribute(description)); - if (defaultValue.isValid() && result.type == PM::internal::getVariantTypeId(defaultValue)) + if (defaultValue.isValid() && result.type() == PM::internal::getVariantTypeId(defaultValue)) result.addAttribute(PM::DefaultValueAttribute(defaultValue)); if (!category.isEmpty()) @@ -169,7 +155,7 @@ int main(int argc, char **argv) [](const PM::PropertyContext &context) { // - qInfo() << "propertyValueChanged" << context.property().name << context.value(); + qInfo() << "propertyValueChanged" << context.property().name() << context.value(); }); return app.exec(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 89d7b7f..f5e61bc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,13 +7,14 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) -find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Widgets REQUIRED) set(PUBLIC_HEADERS Property.h PropertyGrid.h PropertyEditor.h + QtCompat_p.h ) add_library(PmPropertyGrid STATIC @@ -29,6 +30,10 @@ add_library(PmPropertyGrid STATIC ${PUBLIC_HEADERS} ) +add_library(PM::PropertyGrid ALIAS PmPropertyGrid) + target_link_libraries(PmPropertyGrid - Qt::Widgets + # using versionless aliasses like Qt::Core doesn't work prior to Qt5.15 + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Widgets ) diff --git a/src/Property.cpp b/src/Property.cpp index c6500d1..f6140a7 100644 --- a/src/Property.cpp +++ b/src/Property.cpp @@ -5,25 +5,26 @@ using namespace PM; // FIXME: move to a more appropriate place std::unordered_map Property::s_attributesRegistry; -QVariant PM::internal::createDefaultVariantForType(int type) +QString Property::name() const { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - return QVariant(QMetaType(type), nullptr); -#else - return QVariant(QVariant::Type(type)); -#endif + return m_name; } -Property::Property() : type(QMetaType::UnknownType) +int Property::type() const { + return m_type; } -Property::Property(const Property &other) : name(other.name), type(other.type) +Property::Property() : m_type(QMetaType::UnknownType) +{ +} + +Property::Property(const Property &other) : m_type(other.m_type), m_name(other.m_name) { setAttributesFromOther(other); } -Property::Property(const QString &name, int type) : name(name), type(type) +Property::Property(const QString &name, int type) : m_type(type), m_name(name) { } @@ -32,8 +33,8 @@ Property &Property::operator=(const Property &other) if (this == &other) return *this; - name = other.name; - type = other.type; + m_name = other.m_name; + m_type = other.m_type; setAttributesFromOther(other); diff --git a/src/Property.h b/src/Property.h index ca64300..f9bcbdd 100644 --- a/src/Property.h +++ b/src/Property.h @@ -47,14 +47,7 @@ namespace internal template bool isRegisteredAttribute(); - // NOTE: this is a convenience function that is compatible with both Qt5 and Qt6 - int getVariantTypeId(const QVariant &value); - bool canConvert(const QVariant &value, int typeId); - bool isReadOnly(const Property &property); - - // TODO: is this function really needed?!! - QVariant createDefaultVariantForType(int type); } // namespace internal struct Attribute @@ -65,84 +58,12 @@ struct Attribute // TODO: should this be renamed to PropertyDescriptor?!! struct Property { - QString name; - int type; // TODO: maybe make this readonly?!! - - // TODO: add an extra read-only member to hold the typeId. - // this is important because the type information should be preserved even if the value was invalid. - // currently if the value was set to an invalid QVariant all type information will be lost. - // - // The type of this variable can be int, or QMetaType. - // int is how Qt5 used to store the type info. - // the drawback is that strong type checks in tings like function parameters doesn't work out-of-the-box - // in Qt6 this was replaced with using QMetaType instead. - // this incurs additional memory and processing overhead but ensures that strong type checks work properly - // - // this is an example code that demonstrates this: - /* - * QVariant value; - * - * if (!value.isValid()) - * #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - * result.value = QVariant(value.metaType(), nullptr); // ##Qt6 value.metaType() returns QMetaType - * #else - * result.value = QVariant(value.userType(), nullptr); // ##Qt5 value.userType() returns int - * #endif - */ - // another possible solution would be to use std::type_index. - // this will add the ability to support data types that are not registered as in the qmetatype system - // like std::strings and std::vectors and the added benefit of being independant of the changes that happen to Qt. - // but such decision will require us to implement some reliant / lightweight translation method - // that is able to map both systems to each other. - // - // such method can be as simple as two static maps like this: - /* - * std::unordered_map s_typeIndexToQMetaTypeId = { - * {PM::internal::getTypeId(), qMetaTypeId()}, - * {PM::internal::getTypeId(), qMetaTypeId()}, - * {PM::internal::getTypeId(), qMetaTypeId()}, - * {PM::internal::getTypeId(), qMetaTypeId()}, - * }; - * - * // use a lambda function to automatically fill the inverse map - * std::unordered_map s_qMetaTypeIdToTypeIndex = [&] { - * std::unordered_map map; - * for (const auto& pair : s_typeIndexToQMetaTypeId) - * map[pair.second] = pair.first; - * - * return map; - * }(); - * - * std::type_index getTypeId(int metaTypeId) - * { - * if (s_qMetaTypeIdToTypeIndex.find(metaTypeId) == s_qMetaTypeIdToTypeIndex.end()) - * return typeid(void); - * - * return s_qMetaTypeIdToTypeIndex[metaTypeId]; - * } - * - * int getMetaTypeId(std::type_index typeId) - * { - * if (s_typeIndexToQMetaTypeId.find(typeId) == s_typeIndexToQMetaTypeId.end()) - * return QMetaType::UnknownType; - * - * return s_typeIndexToQMetaTypeId[typeId]; - * } - */ + QString name() const; + int type() const; Property(); Property(const Property &other); Property(const QString &name, int type); - // TODO: add move constructor - - // TODO: add a templated constructor when the interface is finalized - // the user has the option to either let the constructor set the type based on the value - // or explicitly provide both the type and the value - // - - // Property(const QString &name, int typeId, const QVariant &value) : name(name), typeId(typeId), value(value) - // { - // } template Property(const QString &name, int typeId, Attributes &&...attributes) : Property(name, typeId) @@ -162,12 +83,6 @@ struct Property Q_UNUSED(dummy) } - // template - // Property(const QString &name, int typeId, const QVariant &value, const Attributes &...attributes) : Property(name, typeId, value) - // { - // addAttribute(attributes...); - // } - Property &operator=(const Property &other); template () && internal::isCopyable()>> @@ -197,6 +112,8 @@ struct Property friend bool internal::isRegisteredAttribute(); private: + int m_type; + QString m_name; std::unordered_map> m_attributes; // FIXME: move to a more appropriate place @@ -331,27 +248,6 @@ inline bool PM::internal::isRegisteredAttribute() return Property::s_attributesRegistry.find(PM::internal::getTypeId()) != Property::s_attributesRegistry.end(); } -inline int PM::internal::getVariantTypeId(const QVariant &value) -{ -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - return value.typeId(); -#else - return value.type(); -#endif -} - -inline bool PM::internal::canConvert(const QVariant &value, int typeId) -{ -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - const QMetaType variantType = value.metaType(); - const QMetaType targetType(typeId); - - return QMetaType::canConvert(variantType, targetType); -#else - return value.canConvert(typeId); -#endif -} - inline bool PM::internal::isReadOnly(const Property &property) { return property.hasAttribute() && property.getAttribute().value == true; diff --git a/src/PropertyEditor.cpp b/src/PropertyEditor.cpp index 2a84978..1ed4310 100644 --- a/src/PropertyEditor.cpp +++ b/src/PropertyEditor.cpp @@ -1,6 +1,8 @@ #include "PropertyEditor.h" #include "PropertyGrid_p.h" +#include "QtCompat_p.h" + #include #include #include @@ -66,7 +68,7 @@ PropertyContext::PropertyContext(const Property &property, const QVariant &value m_value(value), m_object(object), m_propertyGrid(propertyGrid), - m_isValid(!property.name.isEmpty()), + m_isValid(!property.name().isEmpty()), m_valueChangedSlot(PropertyContextPrivate::defaultValueChangedSlot()) { } @@ -80,7 +82,7 @@ QString PropertyEditor::toString(const PropertyContext &context) const { const QVariant value = context.value(); - switch (context.property().type) + switch (context.property().type()) { case qMetaTypeId(): return value.toString(); @@ -146,7 +148,7 @@ QVariant PropertyEditor::fromString(const QString &value, const PropertyContext static const char errorMessageTemplate[] = "%1 is not a valid value for %2."; bool _ok; - switch (context.property().type) + switch (context.property().type()) { case qMetaTypeId(): return value; @@ -203,7 +205,7 @@ QVariant PropertyEditor::fromString(const QString &value, const PropertyContext if (!_ok) break; - return longValue; + return QVariant::fromValue(longValue); } case qMetaTypeId(): @@ -213,7 +215,7 @@ QVariant PropertyEditor::fromString(const QString &value, const PropertyContext if (!_ok) break; - return ulongValue; + return QVariant::fromValue(ulongValue); } case qMetaTypeId(): @@ -284,7 +286,7 @@ QVariant PropertyEditor::fromString(const QString &value, const PropertyContext } if (errorMessage != nullptr) - *errorMessage = QString(errorMessageTemplate).arg(value, QMetaType(context.property().type).name()); + *errorMessage = QString(errorMessageTemplate).arg(value, PM::internal::getMetaTypeName(context.property().type())); return QVariant(); } @@ -319,7 +321,7 @@ void PropertyEditor::editValue(const PropertyContext &context, const QVariant &n bool SizePropertyEditor::canHandle(const PropertyContext &context) const { - switch (context.property().type) + switch (context.property().type()) { case qMetaTypeId(): return true; @@ -339,7 +341,7 @@ QString SizePropertyEditor::toString(const PropertyContext &context) const static const char stringTemplate[] = "%1, %2"; const QVariant value = context.value(); - switch (context.property().type) + switch (context.property().type()) { case qMetaTypeId(): { @@ -363,7 +365,7 @@ QString SizePropertyEditor::toString(const PropertyContext &context) const bool RectPropertyEditor::canHandle(const PropertyContext &context) const { - switch (context.property().type) + switch (context.property().type()) { case qMetaTypeId(): return true; @@ -383,7 +385,7 @@ QString RectPropertyEditor::toString(const PropertyContext &context) const static const char stringTemplate[] = "%1, %2, %3, %4"; const QVariant value = context.value(); - switch (context.property().type) + switch (context.property().type()) { case qMetaTypeId(): { @@ -407,7 +409,7 @@ QString RectPropertyEditor::toString(const PropertyContext &context) const bool FontPropertyEditor::canHandle(const PropertyContext &context) const { - return context.property().type == qMetaTypeId(); + return context.property().type() == qMetaTypeId(); } QString FontPropertyEditor::toString(const PropertyContext &context) const @@ -544,7 +546,7 @@ QPixmap FontPropertyEditor::getPreviewIcon(const PropertyContext &context) const bool ColorPropertyEditor::canHandle(const PropertyContext &context) const { - return context.property().type == qMetaTypeId(); + return context.property().type() == qMetaTypeId(); } QString ColorPropertyEditor::toString(const PropertyContext &context) const @@ -583,7 +585,7 @@ QPixmap ColorPropertyEditor::getPreviewIcon(const PropertyContext &context) cons bool ImagesPropertyEditor::canHandle(const PropertyContext &context) const { - switch (context.property().type) + switch (context.property().type()) { case qMetaTypeId(): return true; @@ -606,14 +608,14 @@ bool ImagesPropertyEditor::canHandle(const PropertyContext &context) const QString ImagesPropertyEditor::toString(const PropertyContext &context) const { - return QMetaType(context.property().type).name(); + return PM::internal::getMetaTypeName(context.property().type()); } QPixmap ImagesPropertyEditor::getPreviewIcon(const PropertyContext &context) const { const QVariant value = context.value(); - switch (context.property().type) + switch (context.property().type()) { case qMetaTypeId(): { @@ -655,7 +657,7 @@ QString CursorPropertyEditor::cursorShapeName(Qt::CursorShape shape) bool CursorPropertyEditor::canHandle(const PropertyContext &context) const { - return context.property().type == qMetaTypeId(); + return context.property().type() == qMetaTypeId(); } QString CursorPropertyEditor::toString(const PropertyContext &context) const @@ -667,7 +669,7 @@ QString CursorPropertyEditor::toString(const PropertyContext &context) const bool BoolPropertyEditor::canHandle(const PropertyContext &context) const { - return context.property().type == qMetaTypeId(); + return context.property().type() == qMetaTypeId(); } QString BoolPropertyEditor::toString(const PropertyContext &context) const diff --git a/src/PropertyGrid.cpp b/src/PropertyGrid.cpp index 708dea0..6028889 100644 --- a/src/PropertyGrid.cpp +++ b/src/PropertyGrid.cpp @@ -3,6 +3,7 @@ #include "PropertyGridTreeItem.h" #include "PropertyGridTreeModel.h" +#include "QtCompat_p.h" #include #include @@ -434,7 +435,7 @@ QModelIndex internal::ModelIndexHelperFunctions::firstIndexInRow(const QModelInd QModelIndex result = index; if (result.column() != 0) - result = index.siblingAtColumn(0); + result = PM::internal::siblingAtColumn(index, 0); return result; } @@ -584,7 +585,7 @@ void PropertyGridPrivate::updatePropertyValue(const QModelIndex &index, const QV PropertyContext &context = item->context; const PropertyEditor *editor = getEditorForProperty(context); - QModelIndex valueIndex = index.siblingAtColumn(1); + QModelIndex valueIndex = PM::internal::siblingAtColumn(index, 1); internal::PropertyContextPrivate::setValue(context, value); @@ -601,10 +602,10 @@ void PropertyGridPrivate::updatePropertyValue(const QModelIndex &index, const QV bool PropertyGridPrivate::setPropertyValue(const PropertyContext &context, const QVariant &value) { const Property &property = context.property(); - if (internal::getVariantTypeId(value) != property.type && !internal::canConvert(value, property.type)) + if (internal::getVariantTypeId(value) != property.type() && !internal::canConvert(value, property.type())) return false; - internal::PropertyGridTreeItem *propertyItem = m_model.getPropertyItem(property.name); + internal::PropertyGridTreeItem *propertyItem = m_model.getPropertyItem(property.name()); if (propertyItem == nullptr) return false; @@ -708,13 +709,13 @@ PropertyGrid::~PropertyGrid() void PropertyGrid::addProperty(const Property &property, const QVariant &value, void *object) { // NEVER EVER allow any properties with empty names - if (property.name.isEmpty()) + if (property.name().isEmpty()) return; // property name is a unique identifier. duplicates are not allowed - if (d->m_model.getPropertyItem(property.name) != nullptr) + if (d->m_model.getPropertyItem(property.name()) != nullptr) { - qWarning() << "property" << property.name << "alrready exists!"; + qWarning() << "property" << property.name() << "alrready exists!"; return; } diff --git a/src/PropertyGrid.h b/src/PropertyGrid.h index 0393272..8c8bedb 100644 --- a/src/PropertyGrid.h +++ b/src/PropertyGrid.h @@ -1,6 +1,7 @@ #ifndef PROPERTYGRID_H #define PROPERTYGRID_H +#include "QtCompat_p.h" #include "PropertyEditor.h" #include diff --git a/src/PropertyGridTreeItem.cpp b/src/PropertyGridTreeItem.cpp index 4568cdd..21452a1 100644 --- a/src/PropertyGridTreeItem.cpp +++ b/src/PropertyGridTreeItem.cpp @@ -200,7 +200,7 @@ void internal::PropertyGridTreeItem::setValue(const QVariant &newValue) QVariant internal::PropertyGridTreeItem::getColumnData(int columnIndex, Qt::ItemDataRole role) const { if (columnIndex == 0 && role == Qt::DisplayRole) - return context.property().name; + return context.property().name(); // in case the user didn't provide any data for the display role, we return the value of the edit role if (role == Qt::DisplayRole && !columns[columnIndex].data.contains(Qt::DisplayRole)) // FIXME: stop using that @@ -213,7 +213,7 @@ void internal::PropertyGridTreeItem::setColumnData(int columnIndex, Qt::ItemData { if (columnIndex == 0 && role == Qt::DisplayRole) { - context.property().name = newValue.toString(); + context.property().name() = newValue.toString(); return; } diff --git a/src/PropertyGridTreeItem.h b/src/PropertyGridTreeItem.h index 3e81555..01df53a 100644 --- a/src/PropertyGridTreeItem.h +++ b/src/PropertyGridTreeItem.h @@ -1,7 +1,6 @@ #ifndef PROPERTYGRIDTREEITEM_H #define PROPERTYGRIDTREEITEM_H -#include "Property.h" #include "PropertyEditor.h" #include diff --git a/src/PropertyGridTreeModel.cpp b/src/PropertyGridTreeModel.cpp index f5121eb..1ae1f99 100644 --- a/src/PropertyGridTreeModel.cpp +++ b/src/PropertyGridTreeModel.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace { @@ -24,16 +25,6 @@ internal::PropertyGridTreeModel::~PropertyGridTreeModel() delete m_rootItem; } -// TreeModel::TreeModel(const QStringList &headers, const QString &data, QObject *parent) : QAbstractItemModel(parent) -// { -// QVector rootData; -// for (const QString &header : headers) -// rootData << header; - -// rootItem = new TreeItem(rootData); -// setupModelData(data.split('\n'), rootItem); -// } - int internal::PropertyGridTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); @@ -85,7 +76,7 @@ void internal::PropertyGridTreeModel::setDataForAllColumns(const QModelIndex &in const int numberOfColumns = columnCount(index); for (int i = 0; i < numberOfColumns; ++i) - setData(index.siblingAtColumn(i), value, role); + setData(PM::internal::siblingAtColumn(index, i), value, role); } Qt::ItemFlags internal::PropertyGridTreeModel::flags(const QModelIndex &index) const @@ -166,7 +157,7 @@ QModelIndex internal::PropertyGridTreeModel::addProperty(const PropertyContext & internal::PropertyGridTreeItem *categoryItem = getCategoryItem(category); internal::PropertyGridTreeItem *PropertyItem = categoryItem->addChild(context); - m_propertiesMap[context.property().name] = PropertyItem; + m_propertiesMap[context.property().name()] = PropertyItem; bool readOnly = context.property().hasAttribute() && context.property().getAttribute().value == true; @@ -235,15 +226,6 @@ QModelIndex internal::PropertyGridTreeModel::index(int row, int column, const QM return QModelIndex(); } -// bool TreeModel::insertColumns(int position, int columns, const QModelIndex &parent) -// { -// beginInsertColumns(parent, position, position + columns - 1); -// const bool success = rootItem->insertColumns(position, columns); -// endInsertColumns(); - -// return success; -// } - bool internal::PropertyGridTreeModel::insertRows(int position, int rows, const QModelIndex &parent) { PropertyGridTreeItem *parentItem = getItem(parent); @@ -277,102 +259,3 @@ QModelIndex internal::PropertyGridTreeModel::parent(const QModelIndex &index) co return createIndex(int(indexInParent), 0, parentItem); } - -// bool TreeModel::removeColumns(int position, int columns, const QModelIndex &parent) -// { -// beginRemoveColumns(parent, position, position + columns - 1); -// const bool success = rootItem->removeColumns(position, columns); -// endRemoveColumns(); - -// if (rootItem->columnCount() == 0) -// removeRows(0, rowCount()); - -// return success; -// } - -// bool TreeModel::removeRows(int position, int rows, const QModelIndex &parent) -// { -// TreeItem *parentItem = getItem(parent); -// if (!parentItem) -// return false; - -// beginRemoveRows(parent, position, position + rows - 1); -// const bool success = parentItem->removeChildren(position, rows); -// endRemoveRows(); - -// return success; -// } - -// bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) -// { -// if (role != Qt::EditRole || orientation != Qt::Horizontal) -// return false; - -// const bool result = rootItem->setData(section, value); - -// if (result) -// emit headerDataChanged(orientation, section, section); - -// return result; -// } - -// void TreeModel::setupModelData(const QStringList &lines, TreeItem *parent) -// { -// QList parents; -// QList indentations; -// parents << parent; -// indentations << 0; - -// int number = 0; - -// while (number < lines.count()) -// { -// int position = 0; -// while (position < lines[number].length()) -// { -// if (lines[number].at(position) != ' ') -// break; - -// ++position; -// } - -// const QString lineData = lines[number].mid(position).trimmed(); - -// if (!lineData.isEmpty()) -// { -// // Read the column data from the rest of the line. -// const QStringList columnStrings = lineData.split(QLatin1Char('\t'), Qt::SkipEmptyParts); -// QList columnData; -// columnData.reserve(columnStrings.size()); -// for (const QString &columnString : columnStrings) -// columnData << columnString; - -// if (position > indentations.last()) -// { -// // The last child of the current parent is now the new parent -// // unless the current parent has no children. - -// if (parents.last()->childCount() > 0) -// { -// parents << parents.last()->child(parents.last()->childCount() - 1); -// indentations << position; -// } -// } -// else -// { -// while (position < indentations.last() && parents.count() > 0) -// { -// parents.pop_back(); -// indentations.pop_back(); -// } -// } - -// // Append a new item to the current parent's list of children. -// TreeItem *parent = parents.last(); -// parent->insertChildren(parent->childCount(), 1, rootItem->columnCount()); -// for (int column = 0; column < columnData.size(); ++column) -// parent->child(parent->childCount() - 1)->setData(column, columnData[column]); -// } -// ++number; -// } -// } diff --git a/src/PropertyGridTreeModel.h b/src/PropertyGridTreeModel.h index 27c4be5..ae3345e 100644 --- a/src/PropertyGridTreeModel.h +++ b/src/PropertyGridTreeModel.h @@ -5,7 +5,6 @@ #include #include -#include namespace PM { @@ -13,7 +12,7 @@ class PropertyGrid; namespace internal { - struct PropertyGridTreeItem; + class PropertyGridTreeItem; class PropertyGridTreeModel : public QAbstractItemModel { @@ -30,7 +29,6 @@ namespace internal void setDataForAllColumns(const QModelIndex &index, const QVariant &value, Qt::ItemDataRole role); QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - // bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; @@ -40,10 +38,7 @@ namespace internal Qt::ItemFlags flags(const QModelIndex &index) const override; - // bool insertColumns(int position, int columns, const QModelIndex &parent = QModelIndex()) override; - // bool removeColumns(int position, int columns, const QModelIndex &parent = QModelIndex()) override; bool insertRows(int position, int rows, const QModelIndex &parent = QModelIndex()) override; - // bool removeRows(int position, int rows, const QModelIndex &parent = QModelIndex()) override; bool showCategories() const; void setShowCategories(bool newShowCategories); diff --git a/src/QtCompat_p.h b/src/QtCompat_p.h new file mode 100644 index 0000000..d56ffc3 --- /dev/null +++ b/src/QtCompat_p.h @@ -0,0 +1,71 @@ +#ifndef QTCOMPAT_P_H +#define QTCOMPAT_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 + +namespace PM +{ +namespace internal +{ + inline QModelIndex siblingAtColumn(const QModelIndex &index, int column) + { +#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) + return index.siblingAtColumn(column); +#else + return index.sibling(index.row(), column); +#endif + } + + inline QString getMetaTypeName(int typeId) + { +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + return QMetaType(typeId).name(); +#else + return QMetaType::typeName(typeId); +#endif + } + + inline int getVariantTypeId(const QVariant &value) + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return value.typeId(); +#else + return value.type(); +#endif + } + + inline bool canConvert(const QVariant &value, int typeId) + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const QMetaType variantType = value.metaType(); + const QMetaType targetType(typeId); + + return QMetaType::canConvert(variantType, targetType); +#else + return value.canConvert(typeId); +#endif + } + + // TODO: is this function really needed?!! + inline QVariant createDefaultVariantForType(int type) + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QVariant(QMetaType(type), nullptr); +#else + return QVariant(QVariant::Type(type)); +#endif + } +} // namespace internal +} // namespace PM + +#endif // QTCOMPAT_P_H