From 11e8387f4efcfad6fe65d070c14a057a27216e16 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 11:54:17 +0300 Subject: [PATCH 01/24] cleaned: PM::Property API --- examples/property_grid_showcase/main.cpp | 20 +--- src/Property.cpp | 20 +++- src/Property.h | 84 +--------------- src/PropertyEditor.cpp | 30 +++--- src/PropertyGrid.cpp | 10 +- src/PropertyGridTreeItem.cpp | 4 +- src/PropertyGridTreeModel.cpp | 120 +---------------------- 7 files changed, 44 insertions(+), 244 deletions(-) diff --git a/examples/property_grid_showcase/main.cpp b/examples/property_grid_showcase/main.cpp index aa6abae..5e81eec 100644 --- a/examples/property_grid_showcase/main.cpp +++ b/examples/property_grid_showcase/main.cpp @@ -1,6 +1,4 @@ #include -#include -#include #include #include @@ -16,20 +14,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 +22,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 +153,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/Property.cpp b/src/Property.cpp index c6500d1..9948978 100644 --- a/src/Property.cpp +++ b/src/Property.cpp @@ -14,16 +14,26 @@ QVariant PM::internal::createDefaultVariantForType(int type) #endif } -Property::Property() : type(QMetaType::UnknownType) +QString Property::name() const { + return m_name; } -Property::Property(const Property &other) : name(other.name), type(other.type) +int Property::type() const +{ + return m_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 +42,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..282ce8d 100644 --- a/src/Property.h +++ b/src/Property.h @@ -65,84 +65,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 +90,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 +119,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 diff --git a/src/PropertyEditor.cpp b/src/PropertyEditor.cpp index 2a84978..eb3e3f8 100644 --- a/src/PropertyEditor.cpp +++ b/src/PropertyEditor.cpp @@ -66,7 +66,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 +80,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 +146,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; @@ -284,7 +284,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, QMetaType(context.property().type()).name()); return QVariant(); } @@ -319,7 +319,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 +339,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 +363,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 +383,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 +407,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 +544,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 +583,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 +606,14 @@ bool ImagesPropertyEditor::canHandle(const PropertyContext &context) const QString ImagesPropertyEditor::toString(const PropertyContext &context) const { - return QMetaType(context.property().type).name(); + return QMetaType(context.property().type()).name(); } QPixmap ImagesPropertyEditor::getPreviewIcon(const PropertyContext &context) const { const QVariant value = context.value(); - switch (context.property().type) + switch (context.property().type()) { case qMetaTypeId(): { @@ -655,7 +655,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 +667,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..56799b2 100644 --- a/src/PropertyGrid.cpp +++ b/src/PropertyGrid.cpp @@ -601,10 +601,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 +708,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/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/PropertyGridTreeModel.cpp b/src/PropertyGridTreeModel.cpp index f5121eb..53fb890 100644 --- a/src/PropertyGridTreeModel.cpp +++ b/src/PropertyGridTreeModel.cpp @@ -24,16 +24,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); @@ -166,7 +156,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 +225,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 +258,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; -// } -// } From f33d707bb9ea5d8308eb38a392c048b68bc39303 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 12:14:07 +0300 Subject: [PATCH 02/24] Fixed: build errors with clang and gcc --- src/PropertyEditor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PropertyEditor.cpp b/src/PropertyEditor.cpp index eb3e3f8..0493a9c 100644 --- a/src/PropertyEditor.cpp +++ b/src/PropertyEditor.cpp @@ -203,7 +203,7 @@ QVariant PropertyEditor::fromString(const QString &value, const PropertyContext if (!_ok) break; - return longValue; + return QVariant::fromValue(longValue); } case qMetaTypeId(): @@ -213,7 +213,7 @@ QVariant PropertyEditor::fromString(const QString &value, const PropertyContext if (!_ok) break; - return ulongValue; + return QVariant::fromValue(ulongValue); } case qMetaTypeId(): From 706a2b5a57b36652f460a0284002a85b480b16a8 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 12:22:20 +0300 Subject: [PATCH 03/24] Update cmake-multi-platform.yml --- .github/workflows/cmake-multi-platform.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index ffc046d..f957c07 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/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: From 61eb85c2ffefc61ff27da4957d28f65957aaac56 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 12:30:15 +0300 Subject: [PATCH 04/24] Added: MacOS --- .github/workflows/cmake-multi-platform.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index f957c07..ac8cbdc 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -33,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 From 3416aa1684ef2fd4dad7a9239130d4141188f1ee Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:00:31 +0300 Subject: [PATCH 05/24] modularized build workflows --- .github/workflows/build-shared.yml | 47 ++++++++++++++++++++++++++++++ .github/workflows/linux.yml | 26 +++++++++++++++++ .github/workflows/macos.yml | 26 +++++++++++++++++ .github/workflows/windows.yml | 28 ++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 .github/workflows/build-shared.yml create mode 100644 .github/workflows/linux.yml create mode 100644 .github/workflows/macos.yml create mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/build-shared.yml b/.github/workflows/build-shared.yml new file mode 100644 index 0000000..3e5fd01 --- /dev/null +++ b/.github/workflows/build-shared.yml @@ -0,0 +1,47 @@ +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 + +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: '6.8.3' + 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..7fb708d --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,26 @@ +name: Linux Build + +on: + workflow_dispatch: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + build-linux: + strategy: + fail-fast: false + matrix: + c_compiler: [gcc, clang] + 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 }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000..5ea99aa --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,26 @@ +name: macOS Build + +on: + workflow_dispatch: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + build-macos: + strategy: + fail-fast: false + matrix: + c_compiler: [gcc, clang] + include: + - c_compiler: gcc + cpp_compiler: g++ + - c_compiler: clang + cpp_compiler: clang++ + + uses: ./.github/workflows/build-shared.yml + with: + os: macos-latest + c_compiler: ${{ matrix.c_compiler }} + cpp_compiler: ${{ matrix.cpp_compiler }} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000..cfe58e5 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,28 @@ +name: Windows Build + +on: + workflow_dispatch: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + build-windows: + strategy: + fail-fast: false + matrix: + c_compiler: [cl, gcc, clang] + 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 }} From 9e40752a9d640a002b225e8b9bbc1fb5d8e1980e Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:07:30 +0300 Subject: [PATCH 06/24] removed multi-platform-build pipeline --- .github/workflows/{ => old}/cmake-multi-platform.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{ => old}/cmake-multi-platform.yml (100%) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/old/cmake-multi-platform.yml similarity index 100% rename from .github/workflows/cmake-multi-platform.yml rename to .github/workflows/old/cmake-multi-platform.yml From a2aee09809a8a8d479ce6054e260a39b221b7173 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:13:17 +0300 Subject: [PATCH 07/24] Add CI build status badges to README Added Windows, macOS, and Linux GitHub Actions build status badges to the README for better project visibility and build monitoring. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e870780..02ec1f7 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. From 9b3d2995d46022fc3ea1e5d21ceeee66733b577c Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:26:49 +0300 Subject: [PATCH 08/24] Centralize CI matrix config and add Qt version matrix Introduces a shared matrix-config.yml workflow to define compiler and Qt version matrices for all platforms. Updates build workflows for Linux, macOS, and Windows to use this centralized configuration, enabling consistent and extensible matrix builds, including multiple Qt versions. --- .github/workflows/build-shared.yml | 5 ++++- .github/workflows/linux.yml | 8 +++++++- .github/workflows/macos.yml | 8 +++++++- .github/workflows/matrix-config.yml | 26 ++++++++++++++++++++++++++ .github/workflows/windows.yml | 8 +++++++- 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/matrix-config.yml diff --git a/.github/workflows/build-shared.yml b/.github/workflows/build-shared.yml index 3e5fd01..b6f8d8b 100644 --- a/.github/workflows/build-shared.yml +++ b/.github/workflows/build-shared.yml @@ -12,6 +12,9 @@ on: cpp_compiler: required: true type: string + qt_version: + required: true + type: string jobs: build: @@ -27,7 +30,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v4 with: - version: '6.8.3' + version: ${{ inputs.qt_version }} install-deps: 'true' set-env: 'true' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7fb708d..ece7030 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -8,11 +8,16 @@ on: branches: [main, develop] jobs: + get-matrix: + uses: ./.github/workflows/matrix-config.yml + build-linux: + needs: get-matrix strategy: fail-fast: false matrix: - c_compiler: [gcc, clang] + 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++ @@ -24,3 +29,4 @@ jobs: 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 index 5ea99aa..0306e02 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -8,11 +8,16 @@ on: branches: [main, develop] jobs: + get-matrix: + uses: ./.github/workflows/matrix-config.yml + build-macos: + needs: get-matrix strategy: fail-fast: false matrix: - c_compiler: [gcc, clang] + 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++ @@ -24,3 +29,4 @@ jobs: os: macos-latest 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..e502800 --- /dev/null +++ b/.github/workflows/matrix-config.yml @@ -0,0 +1,26 @@ +name: Matrix Configuration + +on: + workflow_call: + outputs: + qt_versions: + description: "Qt versions to test against" + value: '["5.15.2", "6.5.3", "6.8.3"]' + 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/windows.yml b/.github/workflows/windows.yml index cfe58e5..ec8ec8e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -8,11 +8,16 @@ on: branches: [main, develop] jobs: + get-matrix: + uses: ./.github/workflows/matrix-config.yml + build-windows: + needs: get-matrix strategy: fail-fast: false matrix: - c_compiler: [cl, gcc, clang] + 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 @@ -26,3 +31,4 @@ jobs: os: windows-latest c_compiler: ${{ matrix.c_compiler }} cpp_compiler: ${{ matrix.cpp_compiler }} + qt_version: ${{ matrix.qt_version }} From f75b2428ba7af67f83b4a8d0011a50cb6d798031 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:36:01 +0300 Subject: [PATCH 09/24] Fix Qt5 macOS build error in propertyValueChanged handler Wrapped qInfo() logging in a preprocessor check to avoid build failures on Qt5 under macOS. This ensures compatibility across different Qt versions and platforms. --- examples/property_grid_showcase/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/property_grid_showcase/main.cpp b/examples/property_grid_showcase/main.cpp index 5e81eec..56725ad 100644 --- a/examples/property_grid_showcase/main.cpp +++ b/examples/property_grid_showcase/main.cpp @@ -152,8 +152,10 @@ int main(int argc, char **argv) QObject::connect(&propertyGrid, &PM::PropertyGrid::propertyValueChanged, &propertyGrid, [](const PM::PropertyContext &context) { - // + // for some reson this line causes builds to fail in Qt5 under macOS +#if !(defined(Q_OS_MACOS) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) qInfo() << "propertyValueChanged" << context.property().name() << context.value(); +#endif }); return app.exec(); From 1285010fce1dbcb2db8531a0ae2cfe51e6cb6128 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:38:21 +0300 Subject: [PATCH 10/24] Update main.cpp --- examples/property_grid_showcase/main.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/property_grid_showcase/main.cpp b/examples/property_grid_showcase/main.cpp index 56725ad..f26d7ac 100644 --- a/examples/property_grid_showcase/main.cpp +++ b/examples/property_grid_showcase/main.cpp @@ -149,14 +149,14 @@ int main(int argc, char **argv) propertyGrid.layout()->addWidget(&checkbox); - QObject::connect(&propertyGrid, &PM::PropertyGrid::propertyValueChanged, &propertyGrid, - [](const PM::PropertyContext &context) - { - // for some reson this line causes builds to fail in Qt5 under macOS -#if !(defined(Q_OS_MACOS) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) - qInfo() << "propertyValueChanged" << context.property().name() << context.value(); -#endif - }); +// QObject::connect(&propertyGrid, &PM::PropertyGrid::propertyValueChanged, &propertyGrid, +// [](const PM::PropertyContext &context) +// { +// // for some reson this line causes builds to fail in Qt5 under macOS +// #if !(defined(Q_OS_MACOS) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +// qInfo() << "propertyValueChanged" << context.property().name() << context.value(); +// #endif +// }); return app.exec(); } From 5e3c53b2ff99419ad070e6510e67ab59f4edff65 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:45:51 +0300 Subject: [PATCH 11/24] Enable propertyValueChanged logging and add QString include --- examples/property_grid_showcase/main.cpp | 14 ++++++-------- src/PropertyGridTreeModel.cpp | 1 + 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/property_grid_showcase/main.cpp b/examples/property_grid_showcase/main.cpp index f26d7ac..5e81eec 100644 --- a/examples/property_grid_showcase/main.cpp +++ b/examples/property_grid_showcase/main.cpp @@ -149,14 +149,12 @@ int main(int argc, char **argv) propertyGrid.layout()->addWidget(&checkbox); -// QObject::connect(&propertyGrid, &PM::PropertyGrid::propertyValueChanged, &propertyGrid, -// [](const PM::PropertyContext &context) -// { -// // for some reson this line causes builds to fail in Qt5 under macOS -// #if !(defined(Q_OS_MACOS) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) -// qInfo() << "propertyValueChanged" << context.property().name() << context.value(); -// #endif -// }); + QObject::connect(&propertyGrid, &PM::PropertyGrid::propertyValueChanged, &propertyGrid, + [](const PM::PropertyContext &context) + { + // + qInfo() << "propertyValueChanged" << context.property().name() << context.value(); + }); return app.exec(); } diff --git a/src/PropertyGridTreeModel.cpp b/src/PropertyGridTreeModel.cpp index 53fb890..2732d41 100644 --- a/src/PropertyGridTreeModel.cpp +++ b/src/PropertyGridTreeModel.cpp @@ -3,6 +3,7 @@ #include "PropertyGrid_p.h" #include +#include #include namespace From 89892303a84fb494f5b8795081cdecc03c1c06fe Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:50:47 +0300 Subject: [PATCH 12/24] Fix Qt5 macOS build error in propertyValueChanged handler Wrapped qInfo() logging in a preprocessor check to avoid build failures on Qt5 under macOS. This ensures compatibility across different Qt versions and platforms. --- examples/property_grid_showcase/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/property_grid_showcase/main.cpp b/examples/property_grid_showcase/main.cpp index 5e81eec..56725ad 100644 --- a/examples/property_grid_showcase/main.cpp +++ b/examples/property_grid_showcase/main.cpp @@ -152,8 +152,10 @@ int main(int argc, char **argv) QObject::connect(&propertyGrid, &PM::PropertyGrid::propertyValueChanged, &propertyGrid, [](const PM::PropertyContext &context) { - // + // for some reson this line causes builds to fail in Qt5 under macOS +#if !(defined(Q_OS_MACOS) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) qInfo() << "propertyValueChanged" << context.property().name() << context.value(); +#endif }); return app.exec(); From 8d6e8dd888575b4cae4c3507a6b27fa25bda77f3 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:54:10 +0300 Subject: [PATCH 13/24] Update PropertyGridTreeModel.h --- src/PropertyGridTreeModel.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PropertyGridTreeModel.h b/src/PropertyGridTreeModel.h index 27c4be5..fc9a0e7 100644 --- a/src/PropertyGridTreeModel.h +++ b/src/PropertyGridTreeModel.h @@ -6,6 +6,7 @@ #include #include #include +#include namespace PM { From acbf3a7988acf74fa9b2e6c81fc2b55b598ff36e Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:03:17 +0300 Subject: [PATCH 14/24] Add Qt::Core to CMake and create PM::PropertyGrid alias Updated CMakeLists.txt files to require Qt Core in addition to Widgets, and linked Qt::Core in targets. Added PM::PropertyGrid alias for PmPropertyGrid to standardize target naming. --- examples/property_grid_showcase/CMakeLists.txt | 7 ++++--- src/CMakeLists.txt | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/property_grid_showcase/CMakeLists.txt b/examples/property_grid_showcase/CMakeLists.txt index 1939c6c..ccdba83 100644 --- a/examples/property_grid_showcase/CMakeLists.txt +++ b/examples/property_grid_showcase/CMakeLists.txt @@ -5,8 +5,8 @@ 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 +14,7 @@ add_executable(property_grid_showcase ) target_link_libraries(property_grid_showcase + Qt::Core Qt::Widgets - PmPropertyGrid + PM::PropertyGrid ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 89d7b7f..2b4597b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,8 +7,8 @@ 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 @@ -29,6 +29,9 @@ add_library(PmPropertyGrid STATIC ${PUBLIC_HEADERS} ) +add_library(PM::PropertyGrid ALIAS PmPropertyGrid) + target_link_libraries(PmPropertyGrid + Qt::Core Qt::Widgets ) From 3def42dfb27d99472f11f8517eeeec698e0061a2 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:07:03 +0300 Subject: [PATCH 15/24] Refactor PropertyGridTreeModel includes and cleanup Replaces forward declaration of PropertyGridTreeItem with an include directive for its header. Removes commented-out code and unnecessary struct declaration to improve code clarity and maintainability. --- src/PropertyGridTreeModel.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/PropertyGridTreeModel.h b/src/PropertyGridTreeModel.h index fc9a0e7..7f4928d 100644 --- a/src/PropertyGridTreeModel.h +++ b/src/PropertyGridTreeModel.h @@ -2,6 +2,7 @@ #define PROPERTYGRIDTREEMODEL_H #include "PropertyEditor.h" +#include "PropertyGridTreeItem.h" #include #include @@ -14,8 +15,6 @@ class PropertyGrid; namespace internal { - struct PropertyGridTreeItem; - class PropertyGridTreeModel : public QAbstractItemModel { Q_OBJECT @@ -31,7 +30,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; @@ -41,10 +39,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); From 2148c479e5ceebc9017dc04507ce38f5ba5806fe Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:14:14 +0300 Subject: [PATCH 16/24] Update matrix-config.yml --- .github/workflows/matrix-config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/matrix-config.yml b/.github/workflows/matrix-config.yml index e502800..0ca42e6 100644 --- a/.github/workflows/matrix-config.yml +++ b/.github/workflows/matrix-config.yml @@ -4,8 +4,8 @@ on: workflow_call: outputs: qt_versions: - description: "Qt versions to test against" - value: '["5.15.2", "6.5.3", "6.8.3"]' + description: "Qt versions to test against (Usually LTS versions)" + value: '["5.6.*", "5.9.*", "5.12.*", "5.15.*", "6.2.*", "6.5.*", "6.8.*"]' compilers_linux: description: "Compilers for Linux" value: '["gcc", "clang"]' From 9d1970f6c7000be8ab641beb78d0d15056637f43 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:26:15 +0300 Subject: [PATCH 17/24] Update Qt target linking for compatibility with older Qt versions Replaces versionless Qt::Core and Qt::Widgets with versioned Qt${QT_VERSION_MAJOR}::Core and Qt${QT_VERSION_MAJOR}::Widgets in src/CMakeLists.txt to ensure compatibility with Qt versions prior to 5.15. Also removes redundant C++ standard settings and unused Qt linking in the example's CMakeLists.txt. --- examples/property_grid_showcase/CMakeLists.txt | 5 ----- src/CMakeLists.txt | 5 +++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/property_grid_showcase/CMakeLists.txt b/examples/property_grid_showcase/CMakeLists.txt index ccdba83..f6b9f20 100644 --- a/examples/property_grid_showcase/CMakeLists.txt +++ b/examples/property_grid_showcase/CMakeLists.txt @@ -2,9 +2,6 @@ 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 Core Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Widgets REQUIRED) @@ -14,7 +11,5 @@ add_executable(property_grid_showcase ) target_link_libraries(property_grid_showcase - Qt::Core - Qt::Widgets PM::PropertyGrid ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2b4597b..2720433 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ add_library(PmPropertyGrid STATIC add_library(PM::PropertyGrid ALIAS PmPropertyGrid) target_link_libraries(PmPropertyGrid - Qt::Core - 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 ) From d3680671904ff0c0159328fb0a35d4edb3dfe2b3 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:40:43 +0300 Subject: [PATCH 18/24] Add Qt version compatibility for QMetaType name retrieval Introduced PM::internal::getMetaTypeName to handle differences in QMetaType name retrieval between Qt versions, ensuring compatibility with Qt 5.9 and above. Updated usages in PropertyEditor.cpp to use this new function. Also updated matrix-config.yml to remove unsupported Qt 5.6 from test matrix, clarifying the minimum supported version. --- .github/workflows/matrix-config.yml | 4 +++- src/PropertyEditor.cpp | 24 ++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/matrix-config.yml b/.github/workflows/matrix-config.yml index 0ca42e6..c5b8373 100644 --- a/.github/workflows/matrix-config.yml +++ b/.github/workflows/matrix-config.yml @@ -5,7 +5,9 @@ on: outputs: qt_versions: description: "Qt versions to test against (Usually LTS versions)" - value: '["5.6.*", "5.9.*", "5.12.*", "5.15.*", "6.2.*", "6.5.*", "6.8.*"]' + # 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"]' diff --git a/src/PropertyEditor.cpp b/src/PropertyEditor.cpp index 0493a9c..c09b194 100644 --- a/src/PropertyEditor.cpp +++ b/src/PropertyEditor.cpp @@ -37,6 +37,26 @@ const QString &boolToString(bool value) return value ? trueKey : falseKey; } +namespace PM +{ +namespace internal +{ + // NOTE: this is a convenience function that is compatible with different minor versions of Qt5 + inline QString getMetaTypeName(int typeId) + { + QString result; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + result = QMetaType(typeId).name(); +#else + result = QMetaType::typeName(typeId); +#endif + + return result; + } +} // namespace internal +} // namespace PM + Property PropertyContext::property() const { return m_property; @@ -284,7 +304,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(); } @@ -606,7 +626,7 @@ 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 From 8fe6ea54271d512556550b5dc30a107cbbfaa603 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:59:00 +0300 Subject: [PATCH 19/24] Refactor Qt compatibility helpers into QtCompat_p.h Moved Qt version compatibility functions (such as getMetaTypeName, getVariantTypeId, canConvert, and createDefaultVariantForType) from Property.h/cpp and PropertyEditor.cpp into a new internal header QtCompat_p.h. Updated includes throughout the codebase to use the new header, improving maintainability and separation of concerns. --- examples/property_grid_showcase/main.cpp | 2 + src/CMakeLists.txt | 1 + src/Property.cpp | 9 --- src/Property.h | 28 ---------- src/PropertyEditor.cpp | 22 +------- src/PropertyGrid.cpp | 1 + src/PropertyGrid.h | 1 + src/PropertyGridTreeItem.h | 1 - src/QtCompat_p.h | 71 ++++++++++++++++++++++++ 9 files changed, 78 insertions(+), 58 deletions(-) create mode 100644 src/QtCompat_p.h diff --git a/examples/property_grid_showcase/main.cpp b/examples/property_grid_showcase/main.cpp index 56725ad..2471b95 100644 --- a/examples/property_grid_showcase/main.cpp +++ b/examples/property_grid_showcase/main.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include #include diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2720433..f5e61bc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,7 @@ set(PUBLIC_HEADERS Property.h PropertyGrid.h PropertyEditor.h + QtCompat_p.h ) add_library(PmPropertyGrid STATIC diff --git a/src/Property.cpp b/src/Property.cpp index 9948978..f6140a7 100644 --- a/src/Property.cpp +++ b/src/Property.cpp @@ -5,15 +5,6 @@ using namespace PM; // FIXME: move to a more appropriate place std::unordered_map Property::s_attributesRegistry; -QVariant PM::internal::createDefaultVariantForType(int type) -{ -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - return QVariant(QMetaType(type), nullptr); -#else - return QVariant(QVariant::Type(type)); -#endif -} - QString Property::name() const { return m_name; diff --git a/src/Property.h b/src/Property.h index 282ce8d..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 @@ -255,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 c09b194..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 @@ -37,26 +39,6 @@ const QString &boolToString(bool value) return value ? trueKey : falseKey; } -namespace PM -{ -namespace internal -{ - // NOTE: this is a convenience function that is compatible with different minor versions of Qt5 - inline QString getMetaTypeName(int typeId) - { - QString result; - -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - result = QMetaType(typeId).name(); -#else - result = QMetaType::typeName(typeId); -#endif - - return result; - } -} // namespace internal -} // namespace PM - Property PropertyContext::property() const { return m_property; diff --git a/src/PropertyGrid.cpp b/src/PropertyGrid.cpp index 56799b2..5f9e47a 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 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.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/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 From 4bf76c139ae795d56e3e24c1b2304cc58f7dc7c9 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:02:39 +0300 Subject: [PATCH 20/24] Replace siblingAtColumn with PM::internal::siblingAtColumn Updated calls to QModelIndex::siblingAtColumn to use the PM::internal::siblingAtColumn function to support Qt5.12 --- src/PropertyGrid.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PropertyGrid.cpp b/src/PropertyGrid.cpp index 5f9e47a..6028889 100644 --- a/src/PropertyGrid.cpp +++ b/src/PropertyGrid.cpp @@ -435,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; } @@ -585,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); From 3f8109d268808ef3e88074f9780211a1fd7f87a8 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:06:34 +0300 Subject: [PATCH 21/24] Replace siblingAtColumn with PM::internal::siblingAtColumn Updated calls to QModelIndex::siblingAtColumn to use the PM::internal::siblingAtColumn function to support Qt5.9 --- src/PropertyGridTreeModel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PropertyGridTreeModel.cpp b/src/PropertyGridTreeModel.cpp index 2732d41..1ae1f99 100644 --- a/src/PropertyGridTreeModel.cpp +++ b/src/PropertyGridTreeModel.cpp @@ -3,8 +3,8 @@ #include "PropertyGrid_p.h" #include -#include #include +#include namespace { @@ -76,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 From b246afd8942422890391d4d695cb1895a3955914 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:19:23 +0300 Subject: [PATCH 22/24] Add separate macOS Intel and ARM build jobs Split the macOS build workflow into two jobs: - one targeting Intel-based macOS (macos-13) - the other for Apple Silicon (macos-latest). The ARM build excludes Qt5 versions as support for apple silicon started from Qt6 --- .github/workflows/macos.yml | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 0306e02..7641327 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -11,7 +11,7 @@ jobs: get-matrix: uses: ./.github/workflows/matrix-config.yml - build-macos: + build-macos-intel: needs: get-matrix strategy: fail-fast: false @@ -26,7 +26,32 @@ jobs: uses: ./.github/workflows/build-shared.yml with: - os: macos-latest + os: macos-13 # Intel-based macOS + c_compiler: ${{ matrix.c_compiler }} + cpp_compiler: ${{ matrix.cpp_compiler }} + qt_version: ${{ matrix.qt_version }} + + build-macos-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 }} From 615317af8b9e3d8621f927c9e223fcba680edeef Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:32:41 +0300 Subject: [PATCH 23/24] Add dynamic job names to CI workflows Set descriptive job names in Linux, macOS, and Windows GitHub Actions workflows using matrix variables for compiler and Qt version. The naming follows the vcpkg ports naming convention --- .github/workflows/linux.yml | 1 + .github/workflows/macos.yml | 2 ++ .github/workflows/windows.yml | 1 + 3 files changed, 4 insertions(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ece7030..7579cd6 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -12,6 +12,7 @@ jobs: uses: ./.github/workflows/matrix-config.yml build-linux: + name: linux-${{ matrix.c_compiler }}-qt${{ matrix.qt_version }} needs: get-matrix strategy: fail-fast: false diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 7641327..9a742a3 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -12,6 +12,7 @@ jobs: 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 @@ -32,6 +33,7 @@ jobs: 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 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index ec8ec8e..22883ae 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -12,6 +12,7 @@ jobs: uses: ./.github/workflows/matrix-config.yml build-windows: + name: windows-${{ matrix.c_compiler }}-qt${{ matrix.qt_version }} needs: get-matrix strategy: fail-fast: false From 5bf07cf36fb2fb0747dd7b5da62baa2b4f55ec60 Mon Sep 17 00:00:00 2001 From: Mostafa Nanticock <47303284+MostafaNanticock@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:54:39 +0300 Subject: [PATCH 24/24] Update README and example for improved Qt compatibility Expanded README with clearer build instructions, CMake integration steps, and updated Qt version requirements (now Qt 5.9+). --- README.md | 89 ++++++++++++++++++------ examples/property_grid_showcase/main.cpp | 4 +- src/PropertyGridTreeModel.h | 5 +- 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 02ec1f7..592fbf7 100644 --- a/README.md +++ b/README.md @@ -50,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[]) @@ -78,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")); @@ -106,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 @@ -140,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/main.cpp b/examples/property_grid_showcase/main.cpp index 2471b95..6bc57de 100644 --- a/examples/property_grid_showcase/main.cpp +++ b/examples/property_grid_showcase/main.cpp @@ -154,10 +154,8 @@ int main(int argc, char **argv) QObject::connect(&propertyGrid, &PM::PropertyGrid::propertyValueChanged, &propertyGrid, [](const PM::PropertyContext &context) { - // for some reson this line causes builds to fail in Qt5 under macOS -#if !(defined(Q_OS_MACOS) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + // qInfo() << "propertyValueChanged" << context.property().name() << context.value(); -#endif }); return app.exec(); diff --git a/src/PropertyGridTreeModel.h b/src/PropertyGridTreeModel.h index 7f4928d..ae3345e 100644 --- a/src/PropertyGridTreeModel.h +++ b/src/PropertyGridTreeModel.h @@ -2,12 +2,9 @@ #define PROPERTYGRIDTREEMODEL_H #include "PropertyEditor.h" -#include "PropertyGridTreeItem.h" #include #include -#include -#include namespace PM { @@ -15,6 +12,8 @@ class PropertyGrid; namespace internal { + class PropertyGridTreeItem; + class PropertyGridTreeModel : public QAbstractItemModel { Q_OBJECT