diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..487ad94 --- /dev/null +++ b/.clang-format @@ -0,0 +1,25 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +ColumnLimit: 100 +BreakBeforeBraces: Attach +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignOperands: true +AlignTrailingComments: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++17 \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..b5c587c --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,33 @@ +Checks: > + clang-diagnostic-*, + clang-analyzer-*, + -clang-analyzer-alpha*, + bugprone-*, + cppcoreguidelines-*, + modernize-*, + performance-*, + portability-*, + readability-*, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-static-cast-downcast, + -modernize-use-trailing-return-type, + -readability-magic-numbers, + -readability-named-parameter, + -bugprone-macro-parentheses + +WarningsAsErrors: false + +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.FunctionCase + value: camelBack + - key: readability-identifier-naming.VariableCase + value: camelBack + - key: readability-identifier-naming.PrivateMemberPrefix + value: m_ + - key: readability-identifier-naming.ProtectedMemberPrefix + value: m_ + - key: readability-identifier-naming.MacroCase + value: UPPER_CASE \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c3b202e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,214 @@ +name: CI + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master, develop ] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + qt-version: [6.7.2] + build-type: [Release, Debug] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Cache Qt + id: cache-qt + uses: actions/cache@v4 + with: + path: ../Qt + key: ${{ runner.os }}-Qt-${{ matrix.qt-version }} + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: ${{ matrix.qt-version }} + cache: true + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} + + - name: Build + run: cmake --build build --config ${{ matrix.build-type }} + + - name: Test + working-directory: build + run: ctest --output-on-failure --build-config ${{ matrix.build-type }} + + build-mobile: + strategy: + matrix: + include: + - platform: ios + os: macos-latest + qt-target: ios + qt-arch: '' + cmake-args: -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DCMAKE_OSX_DEPLOYMENT_TARGET=12.0 + - platform: android-arm64 + os: ubuntu-latest + qt-target: android + qt-arch: android_arm64_v8a + cmake-args: -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-24 + - platform: android-x86_64 + os: ubuntu-latest + qt-target: android + qt-arch: android_x86_64 + cmake-args: -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86_64 -DANDROID_PLATFORM=android-24 + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Android NDK (Android only) + if: startsWith(matrix.platform, 'android') + uses: nttld/setup-ndk@v1 + with: + ndk-version: r25c + + - name: Cache Qt + id: cache-qt + uses: actions/cache@v4 + with: + path: ../Qt + key: ${{ runner.os }}-Qt-6.7.2-${{ matrix.qt-target }}-${{ matrix.qt-arch }} + + - name: Install Qt Desktop (for cross-compilation) + if: matrix.qt-target != 'desktop' + uses: jurplel/install-qt-action@v4 + with: + version: '6.7.2' + target: desktop + arch: ${{ runner.os == 'macOS' && 'clang_64' || 'linux_gcc_64' }} + cache: true + dir: ${{ github.workspace }}/Qt-Desktop + install-deps: 'true' + + - name: Install Qt for Mobile + uses: jurplel/install-qt-action@v4 + with: + version: '6.7.2' + target: ${{ matrix.qt-target }} + arch: ${{ matrix.qt-arch }} + dir: ${{ github.workspace }}/Qt + cache: true + install-deps: 'true' + + - name: Configure CMake for ${{ matrix.platform }} + run: | + # Set QT_HOST_PATH for cross-compilation and ensure correct Qt6_DIR + if [[ "${{ matrix.platform }}" == android* ]] || [[ "${{ matrix.platform }}" == "ios" ]]; then + if [[ "${{ runner.os }}" == "macOS" ]]; then + export QT_HOST_PATH="${{ github.workspace }}/Qt-Desktop/Qt/6.7.2/macos" + else + export QT_HOST_PATH="${{ github.workspace }}/Qt-Desktop/Qt/6.7.2/gcc_64" + fi + # For Android builds, use the Android Qt installation + if [[ "${{ matrix.platform }}" == android* ]]; then + export Qt6_DIR="${{ github.workspace }}/Qt/6.7.2/${{ matrix.qt-arch }}" + else + export Qt6_DIR="${{ github.workspace }}/Qt/6.7.2/${{ matrix.qt-target }}" + fi + echo "Using QT_HOST_PATH: $QT_HOST_PATH" + echo "Qt6_DIR for mobile build: $Qt6_DIR" + + # Debug: List what's actually installed + echo "=== Debug: Qt Desktop contents ===" + find "${{ github.workspace }}/Qt-Desktop" -maxdepth 4 -type d 2>/dev/null || true + echo "=== Debug: Qt Mobile contents ===" + find "${{ github.workspace }}/Qt" -maxdepth 4 -type d 2>/dev/null || true + fi + + cmake -B build-${{ matrix.platform }} \ + -DCMAKE_BUILD_TYPE=Release \ + -DQt6_DIR="$Qt6_DIR/lib/cmake/Qt6" \ + ${QT_HOST_PATH:+-DQT_HOST_PATH="$QT_HOST_PATH"} \ + -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH \ + ${{ matrix.cmake-args }} \ + -DBUILD_TESTING=OFF + + - name: Build for ${{ matrix.platform }} + run: cmake --build build-${{ matrix.platform }} --config Release + + code-quality: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: '6.7.2' + + - name: Install tools + run: | + sudo apt-get update + sudo apt-get install -y clang-format clang-tidy cppcheck doxygen + + - name: Check formatting + run: | + find src/ -name "*.cpp" -o -name "*.h" | xargs clang-format --dry-run --Werror + + - name: Static analysis + run: | + cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + run-clang-tidy -p build src/ + + - name: Cppcheck + run: | + set -e + echo "Running cppcheck using repository script (no build needed)" + chmod +x scripts/cppcheck.sh + ./scripts/cppcheck.sh + + - name: Generate documentation + run: | + doxygen docs/Doxyfile + + - name: Upload documentation + uses: actions/upload-artifact@v4 + with: + name: documentation + path: docs/html/ + + release: + if: startsWith(github.ref, 'refs/tags/v') + needs: [build, build-mobile, code-quality] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + version: '6.7.2' + + - name: Build Release + run: | + cmake -B build -DCMAKE_BUILD_TYPE=Release + cmake --build build + + - name: Package + run: | + cd build + cpack + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: build/*.tar.gz + draft: false + prerelease: false diff --git a/.gitignore b/.gitignore index 2e5f5d7..032a4ff 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,10 @@ src/qqmlmodels/QQmlModels_resource.rc src/qqmlmodels/QQmlModelsd_resource.rc build_5_14_2 build_5_15 +build/ +CMakeFiles/ +CMakeCache.txt +docs/html/ +docs/latex/ +*.log +.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..45203bc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "files.associations": { + "array": "cpp", + "string": "cpp", + "string_view": "cpp" + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5d7a847 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,50 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- Comprehensive CMake build system with proper installation and packaging +- GitHub Actions CI/CD pipeline for automated testing and releases +- Complete test suite with unit tests for all major components +- Doxygen documentation generation +- Code quality tools (clang-format, clang-tidy, cppcheck) +- Development scripts for building, testing, and maintenance +- Detailed README with usage examples and development guidelines +- Professional project structure with proper export headers + +### Changed +- Migrated to modern CMake as primary build system (QMake files preserved as legacy) +- Updated CMakeLists.txt to support Qt6 and proper library installation +- **Updated CI/CD pipeline to use Qt 6.7.2 (compatible with install-qt-action)** +- Improved .gitignore to exclude build artifacts +- Enhanced project structure for better maintainability + +### Fixed +- Qt6 compatibility issues +- Build system improvements for cross-platform support +- Header include paths and export definitions +- **Updated deprecated GitHub Actions (upload-artifact@v3 to v4, cache@v3 to v4)** +- **Replaced Q_SLOTS with slots to fix cppcheck unknown macro warnings** +- **Configured cppcheck to properly recognize Qt keywords and macros** +- **Fixed cppcheck command line syntax (-D instead of --define=)** + +## [1.0.0] - Previous Release + +### Added +- QQmlObjectListModel for exposing C++ object lists to QML +- QQmlVariantListModel for dynamic variant lists in QML +- Qt Super Macros for property declarations +- Basic QMake build system +- GitLab CI configuration + +### Features +- Template-based QQmlObjectListModel with type safety +- Dynamic QQmlVariantListModel with variant support +- Comprehensive property macros (writable, readonly, constant) +- Cross-platform compatibility +- QML integration ready \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bb6fea..c49c088 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,28 +3,41 @@ cmake_minimum_required(VERSION 3.22) project(CPPQmlModels VERSION 1.0 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -#set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Enable testing +option(BUILD_TESTING "Build tests" ON) +if(BUILD_TESTING) + enable_testing() +endif() -set_target_properties(PROPERTIES LINKER_LANGUAGE CXX) include(GenerateExportHeader) find_package(Qt6 COMPONENTS REQUIRED Core Qml - ) + Test +) + +# Use STATIC for iOS, SHARED for other platforms +if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + set(LIBRARY_TYPE STATIC) +else() + set(LIBRARY_TYPE SHARED) +endif() -qt_add_library(CPPQmlModels ${LIB_COMPILE_MODE} -# PUBLIC +qt_add_library(CPPQmlModels ${LIBRARY_TYPE} src/qqmlmodels_global.h src/qqmlobjectlistmodel.h src/qqmlobjectlistmodel.cpp - #I don't know how to use this.. so i coment this until future implementation -# src/qqmlobjectsortfilterlistmodel.h -# src/qqmlobjectsortfilterlistmodel.cpp src/qqmlvariantlistmodel.h src/qqmlvariantlistmodel.cpp + src/testobject.h + src/testobject.cpp + src/QtSuperMacros/qqmlautopropertyhelpers.h src/QtSuperMacros/qqmlconstrefpropertyhelpers.h src/QtSuperMacros/qqmlenumclasshelper.h @@ -35,17 +48,87 @@ qt_add_library(CPPQmlModels ${LIB_COMPILE_MODE} src/QtSuperMacros/qqmlvarpropertyhelpers.h ) +# Define that we are building the library (for export macros) add_definitions(-DQQML_EXPORT) target_include_directories(CPPQmlModels PUBLIC - src/QtSuperMacros - src + $ + $ + $ + $ + PRIVATE + src ) -#add_subdirectory(src) - target_link_libraries(CPPQmlModels PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Qml ) + +# Set library properties +set_target_properties(CPPQmlModels PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + EXPORT_NAME CPPQmlModels +) + +# Install targets +install(TARGETS CPPQmlModels + EXPORT CPPQmlModelsTargets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin +) + +install(DIRECTORY src/ + DESTINATION include/CPPQmlModels + FILES_MATCHING PATTERN "*.h" +) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/src/cppqmlmodels_export.h + DESTINATION include/CPPQmlModels +) + +# Export configuration +install(EXPORT CPPQmlModelsTargets + FILE CPPQmlModelsTargets.cmake + NAMESPACE CPPQmlModels:: + DESTINATION lib/cmake/CPPQmlModels +) + +# Create config file +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + CPPQmlModelsConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) + +configure_package_config_file( + cmake/CPPQmlModelsConfig.cmake.in + CPPQmlModelsConfig.cmake + INSTALL_DESTINATION lib/cmake/CPPQmlModels +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/CPPQmlModelsConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/CPPQmlModelsConfigVersion.cmake + DESTINATION lib/cmake/CPPQmlModels +) + +# Add tests +if(BUILD_TESTING) + add_subdirectory(tests) +endif() + +# CPack configuration +set(CPACK_PACKAGE_NAME "CPPQmlModels") +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Qt QML Models Library") +set(CPACK_PACKAGE_VENDOR "George Calugar") +set(CPACK_PACKAGE_CONTACT "george@wesell.ro") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.md") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + +include(CPack) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..245061e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,296 @@ +# Contributing to QtQMLModel + +Thank you for your interest in contributing to QtQMLModel! This document provides guidelines and information for contributors. + +## Development Setup + +### Prerequisites + +- Qt 6.7.2 or later +- CMake 3.22 or later +- C++17 compatible compiler (GCC 7+, Clang 5+, MSVC 2017+) +- Git + +### Setting Up the Development Environment + +1. Fork and clone the repository: + ```bash + git clone https://github.com/your-username/QtQMLModel.git + cd QtQMLModel + ``` + +2. Install dependencies: + ```bash + # Ubuntu/Debian + sudo apt install qt6-base-dev qt6-declarative-dev cmake build-essential \ + clang-format clang-tidy cppcheck doxygen graphviz + + # Or use the script + ./scripts/build.sh deps + ``` + +3. Build and test: + ```bash + ./scripts/build.sh all + ``` + +## Development Workflow + +### Making Changes + +1. Create a feature branch: + ```bash + git checkout -b feature/your-feature-name + ``` + +2. Make your changes following the coding standards +3. Add tests for new functionality +4. Run the full test suite: + ```bash + ./scripts/build.sh all + ``` + +5. Commit your changes: + ```bash + git commit -am "Add feature: your feature description" + ``` + +6. Push and create a pull request + +### Coding Standards + +#### Code Style + +- Follow the existing code style (enforced by `.clang-format`) +- Use 4 spaces for indentation (no tabs) +- Maximum line length: 100 characters +- Use camelCase for functions and variables +- Use PascalCase for classes +- Use UPPER_CASE for macros and constants +- Prefix private members with `m_` + +#### Documentation + +- Document all public APIs using Doxygen-style comments +- Include usage examples for complex functionality +- Update README.md for significant changes +- Add entries to CHANGELOG.md + +#### Example of well-documented code: + +```cpp +/** + * @brief Appends an item to the end of the model + * + * This function adds the specified item to the end of the model and emits + * the appropriate signals to notify views of the change. + * + * @param item The item to append (takes ownership) + * + * Example usage: + * @code + * auto model = new QQmlObjectListModel(this); + * auto person = new Person("John", 30); + * model->append(person); + * @endcode + */ +void append(ItemType* item); +``` + +### Testing + +#### Running Tests + +```bash +# Run all tests +./scripts/build.sh test + +# Run specific test +cd build +./tests/unit/test_qqmlvariantlistmodel +``` + +#### Writing Tests + +- Add unit tests for all new functionality +- Use Qt Test framework +- Follow the existing test structure in `tests/unit/` +- Test both success and failure cases +- Include edge cases and boundary conditions + +#### Test Structure + +```cpp +class TestMyFeature : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); // Called before first test + void init(); // Called before each test + void testBasicFunctionality(); + void testEdgeCases(); + void cleanup(); // Called after each test + void cleanupTestCase(); // Called after last test +}; +``` + +### Code Quality + +The project uses several tools to maintain code quality: + +#### Automated Formatting + +```bash +# Format all source files +./scripts/build.sh format + +# Check formatting without modifying files +find src/ tests/ -name "*.cpp" -o -name "*.h" | xargs clang-format --dry-run --Werror +``` + +#### Static Analysis + +```bash +# Run all static analysis tools +./scripts/build.sh lint +``` + +#### Tools Used + +- **clang-format**: Automatic code formatting +- **clang-tidy**: Static analysis and modernization +- **cppcheck**: Additional static analysis with Qt support +- **Doxygen**: Documentation generation + +#### Cppcheck Qt Configuration + +The project includes special configuration for cppcheck to properly handle Qt-specific keywords and macros. This prevents false "unknownMacro" warnings for Qt code patterns. + +**Configured Qt Keywords:** +- `slots`, `signals` - Qt access specifiers +- `Q_OBJECT`, `Q_GADGET` - Qt meta-object system +- `Q_PROPERTY`, `Q_EMIT` - Qt property and signal system +- `Q_SIGNALS`, `Q_SLOTS` - Qt macro equivalents +- Project-specific macros: `QQML_EXPORT`, `MAKE_GETTER_NAME`, etc. + +**Local Development:** +```bash +# Run cppcheck with Qt configuration +./scripts/build.sh lint + +# Manual cppcheck with Qt support +cppcheck --enable=all -Dslots= -Dsignals=public -DQ_OBJECT= src/ +``` + +**CI/CD Integration:** +The GitHub Actions workflow automatically runs cppcheck with full Qt macro definitions, ensuring all Qt-specific code patterns are properly recognized and analyzed. + +### Continuous Integration + +The project uses GitHub Actions for CI/CD: + +- **Build**: Tests compilation on Ubuntu, Windows, and macOS +- **Test**: Runs the complete test suite +- **Code Quality**: Checks formatting and runs static analysis +- **Documentation**: Generates and publishes API documentation +- **Release**: Automatically creates releases for tagged versions + +### Release Process + +#### Version Numbering + +We follow [Semantic Versioning](https://semver.org/): + +- **MAJOR**: Incompatible API changes +- **MINOR**: New functionality (backwards compatible) +- **PATCH**: Bug fixes (backwards compatible) + +#### Creating a Release + +1. Update version numbers: + - `CMakeLists.txt` (PROJECT_VERSION) + - `qpm.json` (version.label) + +2. Update `CHANGELOG.md`: + - Move unreleased changes to new version section + - Add release date + - Create new unreleased section + +3. Create and push tag: + ```bash + git tag -a v1.2.0 -m "Release version 1.2.0" + git push origin v1.2.0 + ``` + +4. GitHub Actions will automatically: + - Build release binaries + - Create GitHub release + - Upload artifacts + +#### Hotfix Process + +For critical bugs in released versions: + +1. Create hotfix branch from release tag: + ```bash + git checkout -b hotfix/v1.1.1 v1.1.0 + ``` + +2. Make minimal necessary changes +3. Update version and changelog +4. Test thoroughly +5. Create new tag and push + +### Issue Guidelines + +#### Reporting Bugs + +When reporting bugs, please include: + +- Qt version +- Operating system and version +- Compiler and version +- Minimal example that reproduces the issue +- Expected vs actual behavior +- Any error messages or stack traces + +#### Feature Requests + +For feature requests: + +- Describe the use case and motivation +- Provide examples of how the feature would be used +- Consider backwards compatibility +- Be open to alternative solutions + +#### Templates + +Use the provided issue templates when available. + +## Community + +### Code of Conduct + +- Be respectful and inclusive +- Focus on constructive feedback +- Help others learn and improve +- Maintain a professional attitude + +### Communication + +- **Issues**: For bug reports and feature requests +- **Discussions**: For questions and general discussion +- **Pull Requests**: For code contributions +- **Email**: george@wesell.ro for direct contact + +### Recognition + +Contributors are acknowledged in: + +- Git commit history +- Release notes +- Documentation credits +- Project README + +Thank you for contributing to QtQMLModel! \ No newline at end of file diff --git a/README.md b/README.md index 9d32fea..045102e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,300 @@ -Qt QML Models -============= +# Qt QML Models + +[![CI](https://github.com/CMGeorge/QtQMLModel/workflows/CI/badge.svg)](https://github.com/CMGeorge/QtQMLModel/actions) +[![License](https://img.shields.io/badge/license-MIT--like-blue.svg)](LICENSE.md) Additional data models aimed to bring more power to QML applications by using useful C++ models in back-end. -* `QQmlObjectListModel` : a much nicer way to expose C++ list to QML than the quick & dirty `QList` property . Supports all the strong model features of `QAbstractListModel` while showing the simple and well know API of QList. +## 📖 [Complete Usage Guide](USAGE.md) + +**👉 For detailed examples, API reference, and integration instructions, see the [USAGE.md](USAGE.md) guide.** + +## Features + +* **`QQmlObjectListModel`**: A much nicer way to expose C++ list to QML than the quick & dirty `QList` property. Supports all the strong model features of `QAbstractListModel` while showing the simple and well know API of QList. + +* **`QQmlVariantListModel`**: A dead-simple way to create a dynamic C++ list of any type and expose it to QML, way better than using a `QVariantList` property. + +* **Qt Super Macros**: Powerful macros for property declaration that reduce boilerplate code: + - `QML_WRITABLE_AUTO_PROPERTY` - Creates a full read/write property with getter, setter, and change signal + - `QML_READONLY_AUTO_PROPERTY` - Creates a read-only property with getter and change signal + - `QML_CONSTANT_AUTO_PROPERTY` - Creates a constant property with only a getter + +## Requirements + +- Qt 6.7.2 or later +- CMake 3.22 or later +- C++17 compatible compiler + +## Quick Start + +### Building from Source + +```bash +# Clone the repository +git clone https://github.com/CMGeorge/QtQMLModel.git +cd QtQMLModel + +# Install dependencies (Ubuntu/Debian) +sudo apt install qt6-base-dev qt6-declarative-dev cmake build-essential + +# Build the project +./scripts/build.sh release + +# Run tests +./scripts/build.sh test +``` + +### Using in Your Project + +#### CMake Integration (Recommended) + +The primary and recommended way to integrate QtQMLModel is using CMake: + +```cmake +find_package(CPPQmlModels REQUIRED) +target_link_libraries(your_target PRIVATE CPPQmlModels::CPPQmlModels) +``` + +#### QMake Integration (Legacy) + +> **Note**: QMake files are preserved for compatibility but are not actively maintained. CMake is the recommended build system. + +```qmake +include(path/to/QtQMLModel.pri) +``` + +## Usage Examples + +### QQmlObjectListModel + +```cpp +#include + +class Person : public QObject { + Q_OBJECT + QML_WRITABLE_AUTO_PROPERTY(QString, name) + QML_WRITABLE_AUTO_PROPERTY(int, age) +public: + explicit Person(QObject* parent = nullptr) : QObject(parent) {} +}; + +// In your C++ code +auto model = new QQmlObjectListModel(this); +model->append(new Person); +model->get(0)->set_name("John Doe"); +model->get(0)->set_age(30); + +// Register with QML +qmlRegisterType>("MyApp", 1, 0, "PersonModel"); +``` + +```qml +// In your QML file +import MyApp 1.0 + +ListView { + model: PersonModel { + id: personModel + } + delegate: Text { + text: model.name + " (" + model.age + ")" + } +} +``` + +### QQmlVariantListModel + +```cpp +#include + +// In your C++ code +auto model = new QQmlVariantListModel(this); +model->append("First item"); +model->append(42); +model->append(true); + +// Register with QML +qmlRegisterType("MyApp", 1, 0, "VariantListModel"); +``` + +### Qt Super Macros + +```cpp +#include + +class MyClass : public QObject { + Q_OBJECT + + QML_WRITABLE_AUTO_PROPERTY(QString, title) + QML_READONLY_AUTO_PROPERTY(int, count) + QML_CONSTANT_AUTO_PROPERTY(QString, version) + +public: + explicit MyClass(QObject* parent = nullptr) : QObject(parent) { + m_title = "Default Title"; + m_count = 0; + m_version = "1.0"; + } + + void incrementCount() { + update_count(m_count + 1); // Use update_ for readonly properties + } +}; +``` + +## Development + +### Building and Testing + +```bash +# Build in debug mode +./scripts/build.sh debug + +# Run all tests +./scripts/build.sh test + +# Generate documentation +./scripts/build.sh docs + +# Format code +./scripts/build.sh format + +# Run static analysis +./scripts/build.sh lint + +# Run everything +./scripts/build.sh all +``` + +### Code Quality Tools + +This project includes automated quality control through git hooks: + +#### Pre-commit Hook (Automatic) +Runs on every `git commit` and checks: +- ✅ Code formatting (clang-format) +- ✅ Build verification +- ✅ Complete test suite +- ✅ Static analysis (cppcheck) + +#### Pre-push Hook (Automatic) +Runs on every `git push` and performs: +- ✅ Full clean build +- ✅ Comprehensive testing +- ✅ Complete static analysis +- ✅ Code formatting verification +- ✅ TODO/FIXME reporting + +#### Setup +```bash +# Install git hooks (run once) +./scripts/setup-dev.sh + +# Manual quality checks +./scripts/build.sh format # Fix formatting +./scripts/build.sh lint # Run static analysis +./scripts/build.sh test # Run tests +``` + +The git hooks automatically ensure code quality and prevent broken commits from being pushed. + +The project uses several tools to maintain code quality: + +- **clang-format**: Code formatting (configured in `.clang-format`) +- **clang-tidy**: Static analysis (configured in `.clang-tidy`) +- **cppcheck**: Additional static analysis with Qt support (see `docs/CPPCHECK_QT_CONFIG.md`) + +#### Running Cppcheck locally + +You can run Qt-aware Cppcheck over the project sources with: + +```bash +./scripts/cppcheck.sh +``` + +This writes both a human-readable and XML report to `reports/` and returns non-zero on issues. The script config neutralizes common Qt macros (slots, signals, Q_OBJECT, etc.) and loads the `qt` library knowledge for better analysis. +- **Doxygen**: API documentation generation + +### Contributing + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/my-feature` +3. Make your changes and add tests +4. Run the full test suite: `./scripts/build.sh all` +5. Commit your changes: `git commit -am 'Add some feature'` +6. Push to the branch: `git push origin feature/my-feature` +7. Submit a pull request + +#### Code Style + +- Follow the existing code style (enforced by clang-format) +- Add documentation for public APIs +- Include unit tests for new functionality +- Ensure all tests pass before submitting + +### Release Management + +#### Creating a Release + +1. Update version numbers in: + - `CMakeLists.txt` + - `qpm.json` + - Documentation + +2. Update `CHANGELOG.md` with release notes + +3. Create and push a version tag: + ```bash + git tag -a v1.1.0 -m "Release version 1.1.0" + git push origin v1.1.0 + ``` + +4. GitHub Actions will automatically create a release with artifacts + +#### Hotfix Process + +For critical bug fixes: + +1. Create a hotfix branch from the latest release tag: + ```bash + git checkout -b hotfix/v1.0.1 v1.0.0 + ``` + +2. Make the minimal necessary changes +3. Test thoroughly +4. Update version numbers +5. Create a new tag and push + +## API Documentation + +Full API documentation is available at: [Documentation Link](docs/html/index.html) + +Generate locally with: +```bash +./scripts/build.sh docs +``` + +## License + +This project uses a very permissive license similar to WTFPL. See [LICENSE.md](LICENSE.md) for details. + +Basically: +- Use it however you want (submodule, shared library, modifications, etc.) +- No attribution required (but appreciated) +- Share improvements if you want (but not required) +- Do whatever makes sense for your project + +## Support + +- **Issues**: [GitHub Issues](https://github.com/CMGeorge/QtQMLModel/issues) +- **Discussions**: [GitHub Discussions](https://github.com/CMGeorge/QtQMLModel/discussions) +- **Email**: george@wesell.ro + +## Acknowledgments + +Original Qt QML Models project by the Qt community and contributors. -* `QQmlVariantListModel` : a dead-simple way to create a dynamic C++ list of any type and expose it to QML, way better than using a `QVariantList` property. +--- -> NOTE : If you want to donate, use this link : [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=thebootroo&url=http://gitlab.unique-conception.org/qt-qml-tricks/qt-qml-models) +> **Note**: If you want to donate, use this link: [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=thebootroo&url=http://gitlab.unique-conception.org/qt-qml-tricks/qt-qml-models) diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..03f0506 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,394 @@ +# QtQMLModel - Usage Guide + +QtQMLModel provides powerful and easy-to-use Qt/QML model classes for creating dynamic lists with automatic property binding and signal notification. + +## Table of Contents + +- [Overview](#overview) +- [QQmlVariantListModel](#qqmlvariantlistmodel) +- [QQmlObjectListModel](#qqmlobjectlistmodel) +- [QtSuperMacros](#qtsupermacros) +- [Build and Integration](#build-and-integration) +- [Examples](#examples) + +## Overview + +This library provides two main model classes that integrate seamlessly with Qt's Model/View architecture and QML: + +1. **QQmlVariantListModel** - For lists of QVariant values (strings, numbers, booleans, etc.) +2. **QQmlObjectListModel** - For lists of QObject-derived custom objects with automatic property exposure + +## QQmlVariantListModel + +A versatile model for handling lists of QVariant values, perfect for simple data types. + +### Features + +- ✅ Automatic count property with change notifications +- ✅ Full Qt Model/View compatibility +- ✅ QML integration with automatic property binding +- ✅ Support for all QVariant-compatible types +- ✅ Rich manipulation API (append, prepend, insert, move, swap, etc.) +- ✅ Batch operations for better performance + +### Basic Usage + +```cpp +#include "qqmlvariantlistmodel.h" + +// Create model +QQmlVariantListModel* model = new QQmlVariantListModel(this); + +// Add items +model->append(QVariant("Hello")); +model->append(QVariant(42)); +model->append(QVariant(true)); + +// Access items +QVariant item = model->get(0); // "Hello" +int count = model->count(); // 3 + +// Modify items +model->replace(1, QVariant(100)); // Replace 42 with 100 +model->move(0, 2); // Move "Hello" to end +model->swap(0, 1); // Swap first two items +``` + +### QML Integration + +```qml +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +ApplicationWindow { + width: 400 + height: 600 + + QQmlVariantListModel { + id: listModel + // Model automatically exposes 'count' property + } + + ListView { + anchors.fill: parent + model: listModel + + delegate: Text { + text: qtVariant // Access variant data + height: 40 + } + } + + Button { + text: "Add Item" + onClicked: listModel.append("New Item " + listModel.count) + } +} +``` + +### API Reference + +#### Adding Items +```cpp +void append(const QVariant &item); // Add to end +void prepend(const QVariant &item); // Add to beginning +void insert(int idx, const QVariant &item); // Insert at position + +// Batch operations (more efficient) +void appendList(const QVariantList &itemList); +void prependList(const QVariantList &itemList); +void insertList(int idx, const QVariantList &itemList); +``` + +#### Modifying Items +```cpp +void replace(int pos, const QVariant &item); // Replace item at position +void move(int from, int to); // Move item (shifts others) +void swap(int idx1, int idx2); // Swap two items +void remove(int idx); // Remove item +void clear(); // Remove all items +``` + +#### Accessing Data +```cpp +QVariant get(int idx) const; // Get single item +QVariantList list() const; // Get all items as list +int count() const; // Get item count +bool isEmpty() const; // Check if empty +``` + +#### Signals +```cpp +void countChanged(int count); // Emitted when count changes +``` + +## QQmlObjectListModel + +A template-based model for lists of custom QObject-derived objects with automatic property exposure. + +### Features + +- ✅ Template-based for type safety +- ✅ Automatic property exposure to QML based on Q_PROPERTY declarations +- ✅ Custom role names for property access +- ✅ Display role configuration +- ✅ UID-based lookups +- ✅ Same rich manipulation API as QQmlVariantListModel +- ✅ Automatic memory management + +### Basic Usage + +```cpp +// Define your custom object +class Person : public QObject { + Q_OBJECT + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged) + +public: + Person(QObject* parent = nullptr) : QObject(parent) {} + + QString name() const { return m_name; } + void setName(const QString& name) { + if (m_name != name) { + m_name = name; + emit nameChanged(); + } + } + + int age() const { return m_age; } + void setAge(int age) { + if (m_age != age) { + m_age = age; + emit ageChanged(); + } + } + +signals: + void nameChanged(); + void ageChanged(); + +private: + QString m_name; + int m_age = 0; +}; + +// Use with QQmlObjectListModel +QQmlObjectListModel* model = new QQmlObjectListModel(this); + +// Add objects +Person* person1 = new Person(); +person1->setName("Alice"); +person1->setAge(30); +model->append(person1); + +Person* person2 = new Person(); +person2->setName("Bob"); +person2->setAge(25); +model->append(person2); + +// Access objects +Person* first = model->get(0); +int count = model->count(); +``` + +### QML Integration + +```qml +ListView { + model: personModel // QQmlObjectListModel + + delegate: Column { + Text { text: "Name: " + name } // Direct property access + Text { text: "Age: " + age } // Properties auto-exposed + + MouseArea { + anchors.fill: parent + onClicked: { + // Modify object properties directly + age = age + 1 + } + } + } +} +``` + +### Advanced Configuration + +```cpp +// Configure display role and UID +QQmlObjectListModel* model = new QQmlObjectListModel( + this, // parent + QByteArrayLiteral("name"), // displayRole for Qt::DisplayRole + QByteArrayLiteral("id") // uidRole for unique identification +); +``` + +## QtSuperMacros + +Convenient macros for reducing boilerplate code in QObject property declarations. + +### Property Macros + +```cpp +class MyObject : public QObject { + Q_OBJECT + + // Read-write property with automatic getter/setter + QML_WRITABLE_AUTO_PROPERTY(QString, title) + + // Read-only property + QML_READONLY_AUTO_PROPERTY(int, itemCount) + + // Constant property (set once, never changes) + QML_CONSTANT_AUTO_PROPERTY(QString, version) + +public: + MyObject(QObject* parent = nullptr) : QObject(parent) { + // Initialize constant property + m_version = "1.0.0"; + } +}; +``` + +This generates: +- Private member variables (`m_title`, `m_itemCount`, `m_version`) +- Getter methods (`title()`, `itemCount()`, `version()`) +- Setter methods (for writable properties: `setTitle()`) +- Change notification signals (`titleChanged()`, `itemCountChanged()`) +- Q_PROPERTY declarations with appropriate attributes + +## Build and Integration + +### CMake Integration + +```cmake +# Find the package +find_package(CPPQmlModels REQUIRED) + +# Link to your target +target_link_libraries(your_target CPPQmlModels) +``` + +### QMake Integration + +```pro +# Add to your .pro file +include(path/to/QtQMLModel/QtQMLModel.pri) +``` + +### Manual Integration + +Simply include the source files in your project: +- `src/qqmlvariantlistmodel.h/cpp` +- `src/qqmlobjectlistmodel.h/cpp` +- `src/QtSuperMacros/*.h` + +## Examples + +### Example 1: Shopping List App + +```cpp +// Create model +QQmlVariantListModel* shoppingList = new QQmlVariantListModel(this); + +// Populate list +QVariantList items; +items << "Milk" << "Bread" << "Eggs" << "Butter"; +shoppingList->appendList(items); + +// Use in QML +engine.rootContext()->setContextProperty("shoppingList", shoppingList); +``` + +```qml +ListView { + model: shoppingList + delegate: CheckBox { + text: qtVariant + onToggled: { + if (checked) { + // Move completed item to end + shoppingList.move(index, shoppingList.count - 1) + } + } + } +} +``` + +### Example 2: Contact Manager + +```cpp +class Contact : public QObject { + Q_OBJECT + QML_WRITABLE_AUTO_PROPERTY(QString, name) + QML_WRITABLE_AUTO_PROPERTY(QString, email) + QML_WRITABLE_AUTO_PROPERTY(QString, phone) + QML_READONLY_AUTO_PROPERTY(QString, displayName) + +public: + Contact(QObject* parent = nullptr) : QObject(parent) { + // Update display name when name changes + connect(this, &Contact::nameChanged, this, [this]() { + QString display = m_name.isEmpty() ? "Unknown" : m_name; + if (m_displayName != display) { + m_displayName = display; + emit displayNameChanged(); + } + }); + } +}; + +// Create and use model +QQmlObjectListModel* contacts = new QQmlObjectListModel( + this, + QByteArrayLiteral("displayName") // Use displayName for Qt::DisplayRole +); + +// Add contacts +Contact* contact = new Contact(); +contact->setName("John Doe"); +contact->setEmail("john@example.com"); +contacts->append(contact); +``` + +### Example 3: Dynamic Data Manipulation + +```cpp +// Demonstrate various operations +QQmlVariantListModel* model = new QQmlVariantListModel(this); + +// Add initial data +model->appendList({1, 2, 3, 4, 5}); + +// Move operations +model->move(0, 4); // [2, 3, 4, 5, 1] + +// Swap operations +model->swap(0, 4); // [1, 3, 4, 5, 2] + +// Batch insertions +model->prependList({"A", "B"}); // ["A", "B", 1, 3, 4, 5, 2] + +// Replace operations +model->replace(2, "X"); // ["A", "B", "X", 3, 4, 5, 2] +``` + +## Performance Tips + +1. **Use batch operations** (`appendList`, `prependList`, `insertList`) instead of multiple single operations +2. **Minimize property changes** - batch property updates when possible +3. **Use appropriate model size** - consider pagination for very large datasets +4. **Leverage QML property bindings** - let Qt handle updates automatically + +## Thread Safety + +⚠️ **Important**: These models are **not thread-safe**. All operations must be performed on the main/GUI thread. For multi-threaded applications, use Qt's signal-slot mechanism to communicate between threads. + +## Migration Guide + +If migrating from other list models: + +- **From QStringListModel**: Replace with `QQmlVariantListModel` and convert strings to `QVariant` +- **From custom QAbstractListModel**: Use `QQmlObjectListModel` for better automatic property handling +- **From QML ListModel**: Direct replacement with better performance and C++ integration \ No newline at end of file diff --git a/cmake/CPPQmlModelsConfig.cmake.in b/cmake/CPPQmlModelsConfig.cmake.in new file mode 100644 index 0000000..1d634f9 --- /dev/null +++ b/cmake/CPPQmlModelsConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/CPPQmlModelsTargets.cmake") + +check_required_components(CPPQmlModels) \ No newline at end of file diff --git a/docs/CPPCHECK_QT_CONFIG.md b/docs/CPPCHECK_QT_CONFIG.md new file mode 100644 index 0000000..4de01d0 --- /dev/null +++ b/docs/CPPCHECK_QT_CONFIG.md @@ -0,0 +1,126 @@ +# Cppcheck Qt Configuration + +This document explains how cppcheck is configured to work with Qt code in this project. + +## Problem + +By default, cppcheck doesn't understand Qt-specific keywords and macros, leading to "unknownMacro" warnings such as: + +``` +error: There is an unknown macro here somewhere. Configuration is required. +If slots is a macro then please configure it. [unknownMacro] + public slots: // public API +``` + +## Solution + +The project configures cppcheck to recognize Qt keywords and macros through `--define` options. + +## Qt Keywords Configured + +### Access Specifiers +- `slots` → defined as empty (becomes regular public/protected/private) +- `signals` → defined as `public` +- `Q_SLOTS` → defined as empty +- `Q_SIGNALS` → defined as `public` + +### Meta-Object System +- `Q_OBJECT` → defined as empty +- `Q_GADGET` → defined as empty +- `Q_NAMESPACE` → defined as empty + +### Property System +- `Q_PROPERTY(x)` → defined as empty +- `Q_EMIT` → defined as empty +- `emit` → defined as empty + +### Common Macros +- `Q_NULLPTR` → defined as `nullptr` +- `Q_OVERRIDE` → defined as `override` +- `Q_FINAL` → defined as `final` + +### Project-Specific Macros +- `QQML_EXPORT` → defined as empty +- `MAKE_GETTER_NAME(name)` → defined as `get##name` +- `QML_WRITABLE_AUTO_PROPERTY(type,name)` → defined as empty +- `QML_READONLY_AUTO_PROPERTY(type,name)` → defined as empty +- `QML_CONSTANT_AUTO_PROPERTY(type,name)` → defined as empty + +## Usage + +### Local Development + +```bash +# Use the build script (recommended) +./scripts/build.sh lint + +# Manual cppcheck with Qt support +cppcheck \ + --enable=all \ + -Dslots= \ + -Dsignals=public \ + -DQ_OBJECT= \ + -DQ_SIGNALS=public \ + -DQ_SLOTS= \ + -DQ_EMIT= \ + -D"Q_PROPERTY(x)=" \ + -DQ_NULLPTR=nullptr \ + -DQQML_EXPORT= \ + -D"MAKE_GETTER_NAME(name)=get##name" \ + src/ +``` + +### CI/CD Integration + +The GitHub Actions workflow (`.github/workflows/ci.yml`) includes the same configuration automatically: + +```yaml +- name: Cppcheck + run: | + cppcheck \ + --enable=all \ + --error-exitcode=1 \ + --inline-suppr \ + -Dslots= \ + -Dsignals=public \ + # ... (full configuration) + src/ +``` + +## Adding New Qt Macros + +If you encounter new "unknownMacro" warnings for Qt keywords: + +1. **Update the build script**: Add the new `--define` option to `scripts/build.sh` +2. **Update the CI workflow**: Add the same option to `.github/workflows/ci.yml` +3. **Update this documentation**: Document the new macro in the appropriate section above + +### Example + +For a new Qt macro `Q_CUSTOM_MACRO(x)`: + +```bash +# Add to both build script and CI workflow +-D"Q_CUSTOM_MACRO(x)=" +``` + +## Suppressed Warnings + +Some warnings are suppressed as they're common in Qt projects: + +- `unusedFunction` - Qt MOC generates functions that may appear unused +- `missingIncludeSystem` - Qt headers sometimes have complex include patterns +- `unusedStructMember` - Qt properties may generate unused private members + +## Best Practices + +1. **Keep macros minimal**: Only define what's necessary to avoid warnings +2. **Test locally**: Run `./scripts/build.sh lint` before committing +3. **Document additions**: Update this file when adding new macro definitions +4. **Consistent configuration**: Ensure build script and CI use identical settings + +## References + +- [Cppcheck Manual](https://cppcheck.sourceforge.io/manual.html) +- [Qt Documentation - Using Qt from cmake](https://doc.qt.io/qt-6/cmake-get-started.html) +- [Qt Meta-Object System](https://doc.qt.io/qt-6/metaobjects.html) \ No newline at end of file diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 0000000..753d488 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,334 @@ +PROJECT_NAME = "QtQMLModel" +PROJECT_NUMBER = "1.0" +PROJECT_BRIEF = "Additional data models for Qt QML applications" +PROJECT_LOGO = + +OUTPUT_DIRECTORY = docs +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO + +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +TOC_INCLUDE_HEADINGS = 0 +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 + +# Build related configuration options +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = + +# Configuration options related to warning and progress messages +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +# Configuration options related to the input files +INPUT = src/ README.md +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = */build/* \ + */.* +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = README.md + +# Configuration options related to source browsing +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +CLANG_ASSISTED_PARSING = NO +CLANG_OPTIONS = + +# Configuration options related to the alphabetical class index +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = + +# Configuration options related to the HTML output +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = YES +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = + +# Configuration options related to the LaTeX output +GENERATE_LATEX = NO + +# Configuration options related to the RTF output +GENERATE_RTF = NO + +# Configuration options related to the man page output +GENERATE_MAN = NO + +# Configuration options related to the XML output +GENERATE_XML = NO + +# Configuration options related to the DOCBOOK output +GENERATE_DOCBOOK = NO + +# Configuration options related to the AutoGen Definitions output +GENERATE_AUTOGEN_DEF = NO + +# Configuration options related to the Perl module output +GENERATE_PERLMOD = NO + +# Configuration options related to the preprocessor +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = Q_OBJECT \ + Q_GADGET \ + Q_PROPERTY(x)= \ + QML_ELEMENT \ + QML_NAMED_ELEMENT(x) \ + Q_INVOKABLE= \ + Q_SIGNALS=public \ + Q_SLOTS=public \ + Q_EMIT= \ + signals=public \ + slots=public +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +# Configuration options related to external references +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +PERL_PATH = /usr/bin/perl + +# Configuration options related to the dot tool +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_CFG_FILE = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES \ No newline at end of file diff --git a/reports/cppcheck.txt b/reports/cppcheck.txt new file mode 100644 index 0000000..edfe041 --- /dev/null +++ b/reports/cppcheck.txt @@ -0,0 +1,7 @@ + + + + + + + diff --git a/reports/cppcheck.xml b/reports/cppcheck.xml new file mode 100644 index 0000000..e69de29 diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..a747e9a --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,176 @@ +#!/bin/bash + +# QtQMLModel Build Script +# Usage: ./scripts/build.sh [clean|release|debug|test|docs|format|lint] + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +BUILD_DIR="$PROJECT_DIR/build" + +echo "QtQMLModel Build Script" +echo "======================" +echo "Project directory: $PROJECT_DIR" +echo "Build directory: $BUILD_DIR" +echo "" + +# Function to clean build directory +clean_build() { + echo "Cleaning build directory..." + rm -rf "$BUILD_DIR" + echo "Clean complete." +} + +# Function to configure and build +build_project() { + local build_type=${1:-Release} + echo "Building project (${build_type})..." + + mkdir -p "$BUILD_DIR" + cd "$BUILD_DIR" + + cmake .. -DCMAKE_BUILD_TYPE="$build_type" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + make -j$(nproc) + + echo "Build complete." +} + +# Function to run tests +run_tests() { + echo "Running tests..." + cd "$BUILD_DIR" + ctest --output-on-failure + echo "Tests complete." +} + +# Function to generate documentation +generate_docs() { + echo "Generating documentation..." + cd "$PROJECT_DIR" + doxygen docs/Doxyfile + echo "Documentation generated in docs/html/" +} + +# Function to format code +format_code() { + echo "Formatting code..." + cd "$PROJECT_DIR" + find src/ tests/ -name "*.cpp" -o -name "*.h" | xargs clang-format -i + echo "Code formatting complete." +} + +# Function to run static analysis +run_lint() { + echo "Running static analysis..." + cd "$PROJECT_DIR" + + if [ ! -f "$BUILD_DIR/compile_commands.json" ]; then + echo "compile_commands.json not found. Building first..." + build_project + fi + + echo "Running clang-tidy..." + run-clang-tidy -p "$BUILD_DIR" src/ || true + + echo "Running cppcheck..." + cppcheck --enable=all --error-exitcode=0 --inline-suppr \ + -Dslots= \ + -Dsignals=public \ + -DQ_OBJECT= \ + -DQ_SIGNALS=public \ + -DQ_SLOTS= \ + -DQ_EMIT= \ + -D"Q_PROPERTY(x)=" \ + -DQ_NULLPTR=nullptr \ + -DQQML_EXPORT= \ + -D"MAKE_GETTER_NAME(name)=get##name" \ + -D"QML_WRITABLE_AUTO_PROPERTY(type,name)=" \ + -D"QML_READONLY_AUTO_PROPERTY(type,name)=" \ + -D"QML_CONSTANT_AUTO_PROPERTY(type,name)=" \ + --suppress=missingIncludeSystem \ + --suppress=unusedFunction \ + src/ || true + + echo "Static analysis complete." +} + +# Function to install dependencies +install_deps() { + echo "Installing dependencies..." + sudo apt update + sudo apt install -y qt6-base-dev qt6-declarative-dev cmake build-essential \ + clang-format clang-tidy cppcheck doxygen graphviz + echo "Dependencies installed." +} + +# Function to create release package +create_package() { + echo "Creating release package..." + cd "$BUILD_DIR" + cpack + echo "Package created." +} + +# Parse command line arguments +case "${1:-build}" in + clean) + clean_build + ;; + release) + clean_build + build_project "Release" + ;; + debug) + clean_build + build_project "Debug" + ;; + test) + build_project + run_tests + ;; + docs) + generate_docs + ;; + format) + format_code + ;; + lint) + run_lint + ;; + deps) + install_deps + ;; + package) + build_project "Release" + create_package + ;; + all) + clean_build + build_project "Release" + run_tests + generate_docs + format_code + run_lint + ;; + build) + build_project + ;; + *) + echo "Usage: $0 [clean|release|debug|test|docs|format|lint|deps|package|all|build]" + echo "" + echo "Commands:" + echo " clean - Clean build directory" + echo " release - Clean and build in Release mode" + echo " debug - Clean and build in Debug mode" + echo " test - Build and run tests" + echo " docs - Generate documentation" + echo " format - Format source code" + echo " lint - Run static analysis" + echo " deps - Install dependencies" + echo " package - Create release package" + echo " all - Run all operations" + echo " build - Build project (default)" + exit 1 + ;; +esac \ No newline at end of file diff --git a/scripts/cppcheck.sh b/scripts/cppcheck.sh new file mode 100755 index 0000000..f670b9e --- /dev/null +++ b/scripts/cppcheck.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +# Qt-aware Cppcheck runner for this repository. +# - Scans src/ and tests/ +# - Works without having Qt installed by neutralizing Qt macros +# - Writes human-readable and XML reports into reports/ +# - Exits non-zero on findings + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)" +REPORT_DIR="${ROOT_DIR}/reports" +mkdir -p "${REPORT_DIR}" + +CPPCHECK_BIN=${CPPCHECK_BIN:-cppcheck} +JOBS=${JOBS:-$(command -v nproc >/dev/null 2>&1 && nproc || echo 2)} + +# Common excludes (build trees, VCS, reports) +EXCLUDES=( + -i "${ROOT_DIR}/.git" + -i "${ROOT_DIR}/build" + -i "${ROOT_DIR}/build-*" + -i "${ROOT_DIR}/reports" + # Exclude Qt autogen and generated sources + -i "${ROOT_DIR}/**/_autogen/**" + -i "${ROOT_DIR}/**/mocs_compilation.cpp" + -i "${ROOT_DIR}/**/moc_*.cpp" + -i "${ROOT_DIR}/**/qrc_*.cpp" +) + +# User-provided Qt macro neutralizations and useful options +DEFINES=( + "-Dslots=" + "-Dsignals=public" + "-DQ_OBJECT=" + "-DQ_SIGNALS=public" + "-DQ_SLOTS=" + "-DQ_EMIT=" + "-DQ_PROPERTY(x)=" + "-DQ_NULLPTR=nullptr" + "-DQQML_EXPORT=" + "-DQ_MOC_INCLUDE" + "-DMAKE_GETTER_NAME(name)=get##name" + "-DQML_WRITABLE_AUTO_PROPERTY(type,name)=" + "-DQML_READONLY_AUTO_PROPERTY(type,name)=" + "-DQML_CONSTANT_AUTO_PROPERTY(type,name)=" +) + +SUPPRESS=( + --suppress=unusedFunction + --suppress=missingIncludeSystem +) + +# Build the command +CMD=( + "${CPPCHECK_BIN}" + --enable=all + --error-exitcode=1 + --inline-suppr + --library=qt + --std=c++17 + --language=c++ + --template=gcc + -j "${JOBS}" + --quiet + --force + --output-file="${REPORT_DIR}/cppcheck.txt" + --xml +) + +# Append arrays +CMD+=("${DEFINES[@]}") +CMD+=("${SUPPRESS[@]}") +CMD+=("${EXCLUDES[@]}") + +# Include roots to help cppcheck find headers within the project +CMD+=( + -I "${ROOT_DIR}/src" + -I "${ROOT_DIR}/src/QtSuperMacros" +) + +# Targets to analyze (allow override via args) +TARGETS=("${ROOT_DIR}/src" "${ROOT_DIR}/tests") +if [[ $# -gt 0 ]]; then + TARGETS=("$@") +fi + +CMD+=("${TARGETS[@]}") + +echo "Running: ${CMD[*]} (XML to ${REPORT_DIR}/cppcheck.xml)" >&2 +"${CMD[@]}" 2>"${REPORT_DIR}/cppcheck.xml" + +echo "Cppcheck completed. Reports written to: ${REPORT_DIR}/cppcheck.txt and cppcheck.xml" >&2 diff --git a/scripts/pre-commit b/scripts/pre-commit new file mode 100755 index 0000000..3e83cd5 --- /dev/null +++ b/scripts/pre-commit @@ -0,0 +1,89 @@ +#!/bin/bash + +# Pre-commit hook for QtQMLModel +# This script runs comprehensive code quality checks before allowing a commit + +set -e + +echo "🔍 Running pre-commit checks..." + +# Check if we're in the right directory +if [ ! -f "CMakeLists.txt" ]; then + echo "❌ Error: This doesn't appear to be the QtQMLModel root directory" + exit 1 +fi + +# Function to check if command exists +command_exists() { + command -v "$1" &> /dev/null +} + +# 1. Check code formatting with clang-format +if ! command_exists clang-format; then + echo "⚠️ Warning: clang-format not found. Skipping format check." +else + echo "📝 Checking code formatting..." + + # Get list of C++ files to check (only staged files) + FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cpp|h)$' || true) + + if [ -n "$FILES" ]; then + # Check if files are properly formatted + if ! echo "$FILES" | xargs clang-format --dry-run --Werror &> /dev/null; then + echo "❌ Error: Code is not properly formatted." + echo "Run the following command to fix formatting:" + echo " find src/ -name '*.cpp' -o -name '*.h' | xargs clang-format -i" + exit 1 + fi + + echo "✅ Code formatting is correct" + else + echo "ℹ️ No C++ files to check formatting" + fi +fi + +# 2. Build and test if there are source changes +CPP_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cpp|h)$' || true) +CMAKE_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E 'CMakeLists\.txt$' || true) + +if [ -n "$CPP_FILES" ] || [ -n "$CMAKE_FILES" ]; then + echo "🔨 Source files changed, running build and tests..." + + # Build the project + echo "🏗️ Building project..." + if ! cmake --build build --parallel $(nproc) 2>/dev/null; then + echo "❌ Error: Build failed" + exit 1 + fi + + # Run tests + echo "🧪 Running tests..." + if ! (cd build && ctest --output-on-failure) 2>/dev/null; then + echo "❌ Error: Tests failed" + exit 1 + fi + + echo "✅ Build and tests passed" +else + echo "ℹ️ No source files changed, skipping build and tests" +fi + +# 3. Run cppcheck static analysis +if [ -n "$CPP_FILES" ]; then + if ! command_exists cppcheck; then + echo "⚠️ Warning: cppcheck not found. Skipping static analysis." + else + echo "🔍 Running static analysis with cppcheck..." + if ! ./scripts/cppcheck.sh >/dev/null 2>&1; then + echo "❌ Error: Static analysis found issues" + echo "Run './scripts/cppcheck.sh' to see details" + exit 1 + fi + echo "✅ Static analysis passed" + fi +else + echo "ℹ️ No C++ files to analyze" +fi + +echo "🎉 All pre-commit checks passed!" +exit 0 \ No newline at end of file diff --git a/scripts/pre-push b/scripts/pre-push new file mode 100755 index 0000000..7be7e9d --- /dev/null +++ b/scripts/pre-push @@ -0,0 +1,99 @@ +#!/bin/bash + +# Pre-push hook for QtQMLModel +# This script runs comprehensive checks before pushing to remote + +set -e + +echo "🚀 Running pre-push checks..." + +# Check if we're in the right directory +if [ ! -f "CMakeLists.txt" ]; then + echo "❌ Error: This doesn't appear to be the QtQMLModel root directory" + exit 1 +fi + +# Function to check if command exists +command_exists() { + command -v "$1" &> /dev/null +} + +# 1. Ensure everything is committed +if ! git diff-index --quiet HEAD --; then + echo "❌ Error: You have uncommitted changes. Please commit them first." + exit 1 +fi + +# 2. Full build from clean state +echo "🏗️ Running full clean build..." +if [ -d "build" ]; then + rm -rf build +fi + +if ! cmake -B build -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1; then + echo "❌ Error: CMake configuration failed" + exit 1 +fi + +if ! cmake --build build --parallel $(nproc) >/dev/null 2>&1; then + echo "❌ Error: Full build failed" + exit 1 +fi + +echo "✅ Full build successful" + +# 3. Run complete test suite +echo "🧪 Running complete test suite..." +if ! (cd build && ctest --output-on-failure) >/dev/null 2>&1; then + echo "❌ Error: Test suite failed" + exit 1 +fi + +echo "✅ All tests passed" + +# 4. Run comprehensive static analysis +if command_exists cppcheck; then + echo "🔍 Running comprehensive static analysis..." + if ! ./scripts/cppcheck.sh >/dev/null 2>&1; then + echo "❌ Error: Static analysis found issues" + echo "Run './scripts/cppcheck.sh' to see details" + exit 1 + fi + echo "✅ Static analysis passed" +else + echo "⚠️ Warning: cppcheck not found. Skipping static analysis." +fi + +# 5. Check code formatting on all files +if command_exists clang-format; then + echo "📝 Checking code formatting on all files..." + if ! find src/ -name "*.cpp" -o -name "*.h" | xargs clang-format --dry-run --Werror >/dev/null 2>&1; then + echo "❌ Error: Code formatting issues found" + echo "Run: find src/ -name '*.cpp' -o -name '*.h' | xargs clang-format -i" + exit 1 + fi + echo "✅ Code formatting is correct" +else + echo "⚠️ Warning: clang-format not found. Skipping format check." +fi + +# 6. Check for any TODO/FIXME comments in the code +TODO_COUNT=$(find src/ -name "*.cpp" -o -name "*.h" | xargs grep -n "TODO\|FIXME" | wc -l) +if [ "$TODO_COUNT" -gt 0 ]; then + echo "⚠️ Warning: Found $TODO_COUNT TODO/FIXME comments in code:" + find src/ -name "*.cpp" -o -name "*.h" | xargs grep -n "TODO\|FIXME" || true + echo "Consider addressing these before pushing to production." +fi + +# 7. Check branch status +CURRENT_BRANCH=$(git branch --show-current) +echo "ℹ️ Pushing branch: $CURRENT_BRANCH" + +# 8. Check if this is a merge to main/master +if [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]]; then + echo "⚠️ Warning: Pushing directly to $CURRENT_BRANCH branch!" + echo "Consider using pull requests for code review." +fi + +echo "🎉 All pre-push checks passed! Ready to push." +exit 0 \ No newline at end of file diff --git a/scripts/setup-dev.sh b/scripts/setup-dev.sh new file mode 100755 index 0000000..3f2cd3b --- /dev/null +++ b/scripts/setup-dev.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Install development tools and git hooks for QtQMLModel + +echo "Setting up QtQMLModel development environment..." + +# Install git hooks +if [ -d ".git" ]; then + echo "Installing git hooks..." + + # Install pre-commit hook + cp scripts/pre-commit .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit + echo "✓ Pre-commit hook installed" + + # Install pre-push hook + cp scripts/pre-push .git/hooks/pre-push + chmod +x .git/hooks/pre-push + echo "✓ Pre-push hook installed" +else + echo "Warning: Not a git repository, skipping git hooks" +fi + +# Install dependencies if on supported platform +if command -v apt &> /dev/null; then + echo "Installing dependencies (requires sudo)..." + ./scripts/build.sh deps +elif command -v brew &> /dev/null; then + echo "Installing dependencies with Homebrew..." + brew install qt cmake doxygen graphviz clang-format +elif command -v pacman &> /dev/null; then + echo "Installing dependencies with pacman..." + sudo pacman -S qt6-base qt6-declarative cmake doxygen graphviz clang +else + echo "Unknown package manager. Please install dependencies manually:" + echo "- Qt 6.7.2+" + echo "- CMake 3.22+" + echo "- Doxygen" + echo "- Graphviz" + echo "- clang-format, clang-tidy, cppcheck" +fi + +# Run initial build and test +echo "Running initial build and test..." +./scripts/build.sh all + +echo "" +echo "Development environment setup complete!" +echo "" +echo "Next steps:" +echo "1. Read CONTRIBUTING.md for development guidelines" +echo "2. Create a feature branch: git checkout -b feature/my-feature" +echo "3. Make your changes and commit (pre-commit hook will run automatically)" +echo "4. Push and create a pull request" +echo "" +echo "Available commands:" +echo "./scripts/build.sh [clean|release|debug|test|docs|format|lint|all]" \ No newline at end of file diff --git a/src/QtQmlTricksPlugin_SmartDataModels.h b/src/QtQmlTricksPlugin_SmartDataModels.h index f4a6a12..709d14b 100644 --- a/src/QtQmlTricksPlugin_SmartDataModels.h +++ b/src/QtQmlTricksPlugin_SmartDataModels.h @@ -7,16 +7,16 @@ #include "QQmlObjectListModel.h" #include "QQmlVariantListModel.h" -static void registerQtQmlTricksSmartDataModel (QQmlEngine * engine) { - Q_UNUSED (engine) +static void registerQtQmlTricksSmartDataModel(QQmlEngine *engine) { + Q_UNUSED(engine) - const char * uri = "QtQmlTricks.SmartDataModels"; // @uri QtQmlTricks.SmartDataModels - const int maj = 2; - const int min = 0; - const char * msg = "!!!"; + const char *uri = "QtQmlTricks.SmartDataModels"; // @uri QtQmlTricks.SmartDataModels + const int maj = 2; + const int min = 0; + const char *msg = "!!!"; - qmlRegisterUncreatableType (uri, maj, min, "ObjectListModel", msg); - qmlRegisterUncreatableType (uri, maj, min, "VariantListModel", msg); + qmlRegisterUncreatableType(uri, maj, min, "ObjectListModel", msg); + qmlRegisterUncreatableType(uri, maj, min, "VariantListModel", msg); } #endif // QTQMLTRICKSPLUGIN_SMARTDATAMODELS_H diff --git a/src/QtSuperMacros/qqmlautopropertyhelpers.h b/src/QtSuperMacros/qqmlautopropertyhelpers.h index baca4ad..bf55b0f 100644 --- a/src/QtSuperMacros/qqmlautopropertyhelpers.h +++ b/src/QtSuperMacros/qqmlautopropertyhelpers.h @@ -7,80 +7,82 @@ // NOTE : individual macros for getter, setter, notifier, and member -#define QML_AUTO_GETTER(type, name) \ - CheapestType::type_def MAKE_GETTER_NAME(name) (void) const { \ - return m_##name; \ +#define QML_AUTO_GETTER(type, name) \ + CheapestType::type_def MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ } -#define QML_AUTO_SETTER(type, name, prefix) \ - bool prefix##name (CheapestType::type_def name) { \ - if (m_##name != name) { \ - m_##name = name; \ - Q_EMIT name##Changed (); \ - return true; \ - } \ - else { \ - return false; \ - } \ +#define QML_AUTO_SETTER(type, name, prefix) \ + bool prefix##name(CheapestType::type_def name) { \ + if (m_##name != name) { \ + m_##name = name; \ + Q_EMIT name##Changed(); \ + return true; \ + } else { \ + return false; \ + } \ } -#define QML_AUTO_NOTIFIER(type, name) \ - void name##Changed (void); +#define QML_AUTO_NOTIFIER(type, name) void name##Changed(void); -#define QML_AUTO_MEMBER(type, name) \ - type m_##name; +#define QML_AUTO_MEMBER(type, name) type m_##name; // NOTE : actual auto-property helpers -#define QML_WRITABLE_AUTO_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type name READ MAKE_GETTER_NAME(name) WRITE set_##name NOTIFY name##Changed) \ - private: \ - QML_AUTO_MEMBER (type, name) \ - public: \ - QML_AUTO_GETTER (type, name) \ - QML_AUTO_SETTER (type, name, set_) \ - Q_SIGNALS: \ - QML_AUTO_NOTIFIER (type, name) \ - private: - -#define QML_READONLY_AUTO_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type name READ MAKE_GETTER_NAME(name) NOTIFY name##Changed) \ - private: \ - QML_AUTO_MEMBER (type, name) \ - public: \ - QML_AUTO_GETTER (type, name) \ - QML_AUTO_SETTER (type, name, update_) \ - Q_SIGNALS: \ - QML_AUTO_NOTIFIER (type, name) \ - private: - -#define QML_CONSTANT_AUTO_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type name READ MAKE_GETTER_NAME(name) CONSTANT) \ - private: \ - QML_AUTO_MEMBER (type, name) \ - public: \ - QML_AUTO_GETTER (type, name) \ - private: +#define QML_WRITABLE_AUTO_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) WRITE set_##name NOTIFY name##Changed) \ + private: \ + QML_AUTO_MEMBER(type, name) \ + public: \ + QML_AUTO_GETTER(type, name) \ + QML_AUTO_SETTER(type, name, set_) \ + Q_SIGNALS: \ + QML_AUTO_NOTIFIER(type, name) \ + private: + +#define QML_READONLY_AUTO_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) NOTIFY name##Changed) \ + private: \ + QML_AUTO_MEMBER(type, name) \ + public: \ + QML_AUTO_GETTER(type, name) \ + QML_AUTO_SETTER(type, name, update_) \ + Q_SIGNALS: \ + QML_AUTO_NOTIFIER(type, name) \ + private: + +#define QML_CONSTANT_AUTO_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) CONSTANT) \ + private: \ + QML_AUTO_MEMBER(type, name) \ + public: \ + QML_AUTO_GETTER(type, name) \ + private: // NOTE : test class for all cases class _Test_QmlAutoProperty_ : public QObject { Q_OBJECT - QML_WRITABLE_AUTO_PROPERTY (bool, var1) - QML_WRITABLE_AUTO_PROPERTY (QString, var2) - QML_WRITABLE_AUTO_PROPERTY (QObject *, var3) - - QML_READONLY_AUTO_PROPERTY (bool, var4) - QML_READONLY_AUTO_PROPERTY (QString, var5) - QML_READONLY_AUTO_PROPERTY (QObject *, var6) - - QML_CONSTANT_AUTO_PROPERTY (bool, var7) - QML_CONSTANT_AUTO_PROPERTY (QString, var8) - QML_CONSTANT_AUTO_PROPERTY (QObject *, var9) + QML_WRITABLE_AUTO_PROPERTY(bool, var1) + QML_WRITABLE_AUTO_PROPERTY(QString, var2) + QML_WRITABLE_AUTO_PROPERTY(QObject *, var3) + + QML_READONLY_AUTO_PROPERTY(bool, var4) + QML_READONLY_AUTO_PROPERTY(QString, var5) + QML_READONLY_AUTO_PROPERTY(QObject *, var6) + + QML_CONSTANT_AUTO_PROPERTY(bool, var7) + QML_CONSTANT_AUTO_PROPERTY(QString, var8) + QML_CONSTANT_AUTO_PROPERTY(QObject *, var9) + public: + _Test_QmlAutoProperty_() + : QObject(nullptr), m_var1(false), m_var2(QString()), m_var3(Q_NULLPTR), m_var4(false), + m_var5(QString()), m_var6(Q_NULLPTR), m_var7(false), m_var8(QString()), + m_var9(Q_NULLPTR) {} }; #endif // QQMLAUTOPROPERTYHELPERS_H diff --git a/src/QtSuperMacros/qqmlconstrefpropertyhelpers.h b/src/QtSuperMacros/qqmlconstrefpropertyhelpers.h index 73d7284..365cc6c 100644 --- a/src/QtSuperMacros/qqmlconstrefpropertyhelpers.h +++ b/src/QtSuperMacros/qqmlconstrefpropertyhelpers.h @@ -5,65 +5,73 @@ #include "qqmlhelperscommon.h" -#define QML_WRITABLE_CSTREF_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type name READ MAKE_GETTER_NAME (name) WRITE set_##name NOTIFY name##Changed) \ - private: \ - type m_##name; \ - public: \ - const type & MAKE_GETTER_NAME (name) (void) const { \ - return m_##name ; \ - } \ - public Q_SLOTS: \ - bool set_##name (const type & name) { \ - bool ret = false; \ - if ((ret = (m_##name != name))) { \ - m_##name = name; \ - Q_EMIT name##Changed (m_##name); \ - } \ - return ret; \ - } \ - Q_SIGNALS: \ - void name##Changed (const type & name); \ - private: +#define QML_WRITABLE_CSTREF_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) WRITE set_##name NOTIFY name##Changed) \ + private: \ + type m_##name; \ + \ + public: \ + const type &MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ + } \ + public slots: \ + bool set_##name(const type &name) { \ + bool ret = false; \ + if ((ret = (m_##name != name))) { \ + m_##name = name; \ + Q_EMIT name##Changed(m_##name); \ + } \ + return ret; \ + } \ + Q_SIGNALS: \ + void name##Changed(const type &name); \ + \ + private: -#define QML_READONLY_CSTREF_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type name READ MAKE_GETTER_NAME (name) NOTIFY name##Changed) \ - private: \ - type m_##name; \ - public: \ - const type & MAKE_GETTER_NAME (name) (void) const { \ - return m_##name ; \ - } \ - bool update_##name (const type & name) { \ - bool ret = false; \ - if ((ret = (m_##name != name))) { \ - m_##name = name; \ - Q_EMIT name##Changed (m_##name); \ - } \ - return ret; \ - } \ - Q_SIGNALS: \ - void name##Changed (const type & name); \ - private: +#define QML_READONLY_CSTREF_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) NOTIFY name##Changed) \ + private: \ + type m_##name; \ + \ + public: \ + const type &MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ + } \ + bool update_##name(const type &name) { \ + bool ret = false; \ + if ((ret = (m_##name != name))) { \ + m_##name = name; \ + Q_EMIT name##Changed(m_##name); \ + } \ + return ret; \ + } \ + Q_SIGNALS: \ + void name##Changed(const type &name); \ + \ + private: -#define QML_CONSTANT_CSTREF_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type name READ MAKE_GETTER_NAME (name) CONSTANT) \ - private: \ - type m_##name; \ - public: \ - const type & MAKE_GETTER_NAME (name) (void) const { \ - return m_##name ; \ - } \ - private: +#define QML_CONSTANT_CSTREF_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) CONSTANT) \ + private: \ + type m_##name; \ + \ + public: \ + const type &MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ + } \ + \ + private: class _QmlCstRefProperty_ : public QObject { Q_OBJECT - QML_WRITABLE_CSTREF_PROPERTY (int, var1) - QML_READONLY_CSTREF_PROPERTY (bool, var2) - QML_CONSTANT_CSTREF_PROPERTY (QString, var3) + QML_WRITABLE_CSTREF_PROPERTY(int, var1) + QML_READONLY_CSTREF_PROPERTY(bool, var2) + QML_CONSTANT_CSTREF_PROPERTY(QString, var3) + public: + _QmlCstRefProperty_() : QObject(nullptr), m_var1(0), m_var2(false), m_var3(QString()) {} }; #endif // QQMLCONSTREFPROPERTYHELPERS diff --git a/src/QtSuperMacros/qqmlenumclasshelper.h b/src/QtSuperMacros/qqmlenumclasshelper.h index a213382..9006ca8 100644 --- a/src/QtSuperMacros/qqmlenumclasshelper.h +++ b/src/QtSuperMacros/qqmlenumclasshelper.h @@ -4,27 +4,27 @@ #include #ifdef Q_ENUM -# define QML_EXPORT_ENUM Q_ENUM +#define QML_EXPORT_ENUM Q_ENUM #else -# define QML_EXPORT_ENUM Q_ENUMS +#define QML_EXPORT_ENUM Q_ENUMS #endif -#define QML_ENUM_CLASS(NAME, ...) \ - struct NAME { \ - Q_GADGET \ - public: \ - enum Type { \ - __VA_ARGS__ \ - }; \ - QML_EXPORT_ENUM (Type) \ - private: \ - explicit NAME (void) { } \ - NAME (const NAME &); \ - NAME & operator= (const NAME &); \ - }; \ - Q_DECLARE_METATYPE (NAME::Type) +#define QML_ENUM_CLASS(NAME, ...) \ + struct NAME { \ + Q_GADGET \ + public: \ + enum Type { __VA_ARGS__ }; \ + QML_EXPORT_ENUM(Type) \ + private: \ + explicit NAME(void) { \ + } \ + NAME(const NAME &); \ + NAME &operator=(const NAME &); \ + }; \ + Q_DECLARE_METATYPE(NAME::Type) -class _Test_QmlEnumClass_ { Q_GADGET }; // NOTE : to avoid "no suitable class found" MOC note +class _Test_QmlEnumClass_ { + Q_GADGET +}; // NOTE : to avoid "no suitable class found" MOC note #endif // QQMLENUMCLASS - diff --git a/src/QtSuperMacros/qqmlhelpers.cpp b/src/QtSuperMacros/qqmlhelpers.cpp index 1cc621a..a0ebcdf 100644 --- a/src/QtSuperMacros/qqmlhelpers.cpp +++ b/src/QtSuperMacros/qqmlhelpers.cpp @@ -1,10 +1,10 @@ +#include "qqmlautopropertyhelpers.h" +#include "qqmlconstrefpropertyhelpers.h" #include "qqmlenumclasshelper.h" #include "qqmllistpropertyhelper.h" -#include "qqmlvarpropertyhelpers.h" #include "qqmlptrpropertyhelpers.h" -#include "qqmlconstrefpropertyhelpers.h" -#include "qqmlautopropertyhelpers.h" +#include "qqmlvarpropertyhelpers.h" // TODO : rewrite this doc @@ -16,7 +16,6 @@ which is largely error-prone and not productive at all. */ - /*! \def QML_WRITABLE_PROPERTY(type, name) \ingroup QT_QML_HELPERS @@ -37,7 +36,6 @@ \b Note : Any change from either C++ or QML side will trigger the notification. */ - /*! \def QML_READONLY_PROPERTY(type, name) \ingroup QT_QML_HELPERS @@ -58,7 +56,6 @@ \b Note : Any change from C++ side will trigger the notification to QML. */ - /*! \def QML_CONSTANT_PROPERTY(type, name) \ingroup QT_QML_HELPERS @@ -77,7 +74,6 @@ \b Note : There is no change notifier because value is constant. */ - /*! \def QML_ENUM_CLASS(name, ...) \ingroup QT_QML_HELPERS @@ -94,8 +90,8 @@ Example in use : \code - QML_ENUM_CLASS (DaysOfWeek, Monday = 1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday) - \endcode + QML_ENUM_CLASS (DaysOfWeek, Monday = 1, Tuesday, Wednesday, Thursday, Friday, Saturday, + Sunday) \endcode \b Note : The QML registration using \c qmlRegisterUncreatableType() will still be needed. */ diff --git a/src/QtSuperMacros/qqmlhelperscommon.h b/src/QtSuperMacros/qqmlhelperscommon.h index 4f187c4..fb40425 100644 --- a/src/QtSuperMacros/qqmlhelperscommon.h +++ b/src/QtSuperMacros/qqmlhelperscommon.h @@ -5,26 +5,52 @@ // NOTE : SFINAE trickery to find which type is the cheapest between T and const T & -template struct CheapestType { typedef const T & type_def; }; -template<> struct CheapestType { typedef bool type_def; }; -template<> struct CheapestType { typedef quint8 type_def; }; -template<> struct CheapestType { typedef quint16 type_def; }; -template<> struct CheapestType { typedef quint32 type_def; }; -template<> struct CheapestType { typedef quint64 type_def; }; -template<> struct CheapestType { typedef qint8 type_def; }; -template<> struct CheapestType { typedef qint16 type_def; }; -template<> struct CheapestType { typedef qint32 type_def; }; -template<> struct CheapestType { typedef qint64 type_def; }; -template<> struct CheapestType { typedef float type_def; }; -template<> struct CheapestType { typedef double type_def; }; -template struct CheapestType { typedef T * type_def; }; +template struct CheapestType { + typedef const T &type_def; +}; +template <> struct CheapestType { + typedef bool type_def; +}; +template <> struct CheapestType { + typedef quint8 type_def; +}; +template <> struct CheapestType { + typedef quint16 type_def; +}; +template <> struct CheapestType { + typedef quint32 type_def; +}; +template <> struct CheapestType { + typedef quint64 type_def; +}; +template <> struct CheapestType { + typedef qint8 type_def; +}; +template <> struct CheapestType { + typedef qint16 type_def; +}; +template <> struct CheapestType { + typedef qint32 type_def; +}; +template <> struct CheapestType { + typedef qint64 type_def; +}; +template <> struct CheapestType { + typedef float type_def; +}; +template <> struct CheapestType { + typedef double type_def; +}; +template struct CheapestType { + typedef T *type_def; +}; // NOTE : define to add/remove 'get_' prefix on getters #ifdef QTQMLTRICKS_NO_PREFIX_ON_GETTERS -# define MAKE_GETTER_NAME(name) name +#define MAKE_GETTER_NAME(name) name #else -# define MAKE_GETTER_NAME(name) get_##name +#define MAKE_GETTER_NAME(name) get_##name #endif #endif // QQMLHELPERSCOMMON_H diff --git a/src/QtSuperMacros/qqmllistpropertyhelper.h b/src/QtSuperMacros/qqmllistpropertyhelper.h index 00cb403..a152caf 100644 --- a/src/QtSuperMacros/qqmllistpropertyhelper.h +++ b/src/QtSuperMacros/qqmllistpropertyhelper.h @@ -6,78 +6,63 @@ #include "qqmlhelperscommon.h" -template class QQmlSmartListWrapper : public QQmlListProperty { -public: - typedef QVector CppListType; - typedef QQmlListProperty QmlListPropertyType; +template class QQmlSmartListWrapper : public QQmlListProperty { + public: + typedef QVector CppListType; + typedef QQmlListProperty QmlListPropertyType; typedef QQmlSmartListWrapper SmartListWrapperType; typedef typename CppListType::const_iterator const_iterator; - explicit QQmlSmartListWrapper (QObject * object, const int reserve = 0) - : QmlListPropertyType - (object, - &m_items, - &SmartListWrapperType::callbackAppend, - &SmartListWrapperType::callbackCount, - &SmartListWrapperType::callbackAt, - &SmartListWrapperType::callbackClear) - { + explicit QQmlSmartListWrapper(QObject *object, const int reserve = 0) + : QmlListPropertyType(object, &m_items, &SmartListWrapperType::callbackAppend, + &SmartListWrapperType::callbackCount, + &SmartListWrapperType::callbackAt, + &SmartListWrapperType::callbackClear) { if (reserve > 0) { - m_items.reserve (reserve); + m_items.reserve(reserve); } } - const CppListType & items (void) const { - return m_items; - } + const CppListType &items(void) const { return m_items; } - const_iterator begin (void) const { - return m_items.begin (); - } + const_iterator begin(void) const { return m_items.begin(); } - const_iterator end (void) const { - return m_items.end (); - } + const_iterator end(void) const { return m_items.end(); } - const_iterator constBegin (void) const { - return m_items.constBegin (); - } + const_iterator constBegin(void) const { return m_items.constBegin(); } - const_iterator constEnd (void) const { - return m_items.constEnd (); - } + const_iterator constEnd(void) const { return m_items.constEnd(); } - static int callbackCount (QmlListPropertyType * prop) { - return static_cast (prop->data)->count (); + static int callbackCount(QmlListPropertyType *prop) { + return static_cast(prop->data)->count(); } - static void callbackClear (QmlListPropertyType * prop) { - static_cast (prop->data)->clear (); + static void callbackClear(QmlListPropertyType *prop) { + static_cast(prop->data)->clear(); } - static void callbackAppend (QmlListPropertyType * prop, ObjType * obj) { - static_cast (prop->data)->append (obj); + static void callbackAppend(QmlListPropertyType *prop, ObjType *obj) { + static_cast(prop->data)->append(obj); } - static ObjType * callbackAt (QmlListPropertyType * prop, int idx) { - return static_cast (prop->data)->at (idx); + static ObjType *callbackAt(QmlListPropertyType *prop, int idx) { + return static_cast(prop->data)->at(idx); } -private: + private: CppListType m_items; }; -#define QML_LIST_PROPERTY(TYPE, NAME) \ - private: \ - Q_PROPERTY (QQmlListProperty NAME READ MAKE_GETTER_NAME (NAME) CONSTANT) \ - public: \ - const QQmlSmartListWrapper & MAKE_GETTER_NAME (NAME) (void) const { \ - return m_##NAME; \ - } \ - private: \ - QQmlSmartListWrapper m_##NAME; - +#define QML_LIST_PROPERTY(TYPE, NAME) \ + private: \ + Q_PROPERTY(QQmlListProperty NAME READ MAKE_GETTER_NAME(NAME) CONSTANT) \ + public: \ + const QQmlSmartListWrapper &MAKE_GETTER_NAME(NAME)(void) const { \ + return m_##NAME; \ + } \ + \ + private: \ + QQmlSmartListWrapper m_##NAME; #endif // QQMLLISTPROPERTYHELPER - diff --git a/src/QtSuperMacros/qqmlptrpropertyhelpers.h b/src/QtSuperMacros/qqmlptrpropertyhelpers.h index 18eb864..a7b7d03 100644 --- a/src/QtSuperMacros/qqmlptrpropertyhelpers.h +++ b/src/QtSuperMacros/qqmlptrpropertyhelpers.h @@ -5,66 +5,74 @@ #include "qqmlhelperscommon.h" -#define QML_WRITABLE_PTR_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type * name READ MAKE_GETTER_NAME (name) WRITE set_##name NOTIFY name##Changed) \ - private: \ - type * m_##name; \ - public: \ - type * MAKE_GETTER_NAME (name) (void) const { \ - return m_##name ; \ - } \ - public Q_SLOTS: \ - bool set_##name (type * name) { \ - bool ret = false; \ - if ((ret = (m_##name != name))) { \ - m_##name = name; \ - Q_EMIT name##Changed (m_##name); \ - } \ - return ret; \ - } \ - Q_SIGNALS: \ - void name##Changed (type * name); \ - private: +#define QML_WRITABLE_PTR_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type *name READ MAKE_GETTER_NAME(name) WRITE set_##name NOTIFY name##Changed) \ + private: \ + type *m_##name; \ + \ + public: \ + type *MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ + } \ + public slots: \ + bool set_##name(type *name) { \ + bool ret = false; \ + if ((ret = (m_##name != name))) { \ + m_##name = name; \ + Q_EMIT name##Changed(m_##name); \ + } \ + return ret; \ + } \ + Q_SIGNALS: \ + void name##Changed(type *name); \ + \ + private: -#define QML_READONLY_PTR_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type * name READ MAKE_GETTER_NAME (name) NOTIFY name##Changed) \ - private: \ - type * m_##name; \ - public: \ - type * MAKE_GETTER_NAME (name) (void) const { \ - return m_##name ; \ - } \ - bool update_##name (type * name) { \ - bool ret = false; \ - if ((ret = (m_##name != name))) { \ - m_##name = name; \ - Q_EMIT name##Changed (m_##name); \ - } \ - return ret; \ - } \ - Q_SIGNALS: \ - void name##Changed (type * name); \ - private: +#define QML_READONLY_PTR_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type *name READ MAKE_GETTER_NAME(name) NOTIFY name##Changed) \ + private: \ + type *m_##name; \ + \ + public: \ + type *MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ + } \ + bool update_##name(type *name) { \ + bool ret = false; \ + if ((ret = (m_##name != name))) { \ + m_##name = name; \ + Q_EMIT name##Changed(m_##name); \ + } \ + return ret; \ + } \ + Q_SIGNALS: \ + void name##Changed(type *name); \ + \ + private: -#define QML_CONSTANT_PTR_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type * name READ MAKE_GETTER_NAME (name) CONSTANT) \ - private: \ - type * m_##name; \ - public: \ - type * MAKE_GETTER_NAME (name) (void) const { \ - return m_##name ; \ - } \ - private: +#define QML_CONSTANT_PTR_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type *name READ MAKE_GETTER_NAME(name) CONSTANT) \ + private: \ + type *m_##name; \ + \ + public: \ + type *MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ + } \ + \ + private: class _QmlPtrProperty_ : public QObject { Q_OBJECT - QML_WRITABLE_PTR_PROPERTY (int, var1) - QML_READONLY_PTR_PROPERTY (bool, var2) - QML_CONSTANT_PTR_PROPERTY (QString, var3) + QML_WRITABLE_PTR_PROPERTY(int, var1) + QML_READONLY_PTR_PROPERTY(bool, var2) + QML_CONSTANT_PTR_PROPERTY(QString, var3) + public: + _QmlPtrProperty_() + : QObject(nullptr), m_var1(Q_NULLPTR), m_var2(Q_NULLPTR), m_var3(Q_NULLPTR) {} }; #endif // QQMLPTRPROPERTYHELPERS - diff --git a/src/QtSuperMacros/qqmlvarpropertyhelpers.h b/src/QtSuperMacros/qqmlvarpropertyhelpers.h index 6b93761..55d674f 100644 --- a/src/QtSuperMacros/qqmlvarpropertyhelpers.h +++ b/src/QtSuperMacros/qqmlvarpropertyhelpers.h @@ -4,125 +4,135 @@ #include #include "qqmlhelperscommon.h" -//#pragma GCC diagnostic ignored "-Wfloat-equal" +// #pragma GCC diagnostic ignored "-Wfloat-equal" -#define QML_WRITABLE_VAR_PROPERTY(type, name) \ -protected: \ - Q_PROPERTY (type name READ MAKE_GETTER_NAME (name) WRITE set_##name NOTIFY name##Changed) \ - private: \ - type m_##name; \ - public: \ - type MAKE_GETTER_NAME (name) (void) const { \ - return m_##name ; \ -} \ - public Q_SLOTS: \ - bool set_##name (type name) { \ - bool ret = false; \ - if ((ret = (m_##name != name))) { \ - m_##name = name; \ - Q_EMIT name##Changed (m_##name); \ - } \ - return ret; \ -} \ - Q_SIGNALS: \ - void name##Changed (type name); \ - private: -#define QML_WRITABLE_VAR_PROPERTY_MODIFIED(type, name) \ -protected: \ - Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) \ - WRITE set_##name NOTIFY name##Changed) \ - Q_PROPERTY(bool name##ErrorId READ MAKE_GETTER_NAME(name##ErrorId) \ - NOTIFY name##ErrorIdChanged) \ -private: \ - type m_##name; \ - type m_##name##_initial; \ - bool m_##name##_modified = false; \ - int m_##name##ErrorId = 0; \ - \ -public: \ - type MAKE_GETTER_NAME(name)(void) const { return m_##name; } \ - int MAKE_GETTER_NAME(name##ErrorId)(void) const { \ - return m_##name##ErrorId; \ - }\ - bool update_##name##ErrorId(int name##ErrorId) { \ - bool ret = false; \ - if ((ret = (m_##name##ErrorId != name##ErrorId))) { \ - m_##name##ErrorId = name##ErrorId; \ - Q_EMIT name##ErrorIdChanged(m_##name##ErrorId); \ - } \ - return ret; \ - }\ -public Q_SLOTS: \ - bool set_##name(type name) { \ - bool ret = false; \ - if ((ret = (m_##name != name))) { \ - m_##name = name; \ - Q_EMIT name##Changed(m_##name); \ - if (!m_##name##_modified) { \ - m_##name##_modified = true; \ - Q_EMIT name##Modified(m_##name); \ - } else if (m_##name##_initial == m_##name) { \ - m_##name##_modified = false; \ - Q_EMIT name##Modified(m_##name); \ - } \ -update_##name##ErrorId(0); \ - } \ - return ret; \ - } \ - bool init_##name(type name) { \ - bool ret = false; \ - m_##name##_modified = false; \ - m_##name##_initial = name; \ - if ((ret = (m_##name != name))) { \ - m_##name = name; \ - Q_EMIT name##Changed(m_##name); \ - } \ - return ret; \ - } \ - \ -Q_SIGNALS: \ - void name##Changed(type name); \ - void name##Modified(type name); \ - void name##ErrorIdChanged(bool name##ErrorId);\ -private: +#define QML_WRITABLE_VAR_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) WRITE set_##name NOTIFY name##Changed) \ + private: \ + type m_##name; \ + \ + public: \ + type MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ + } \ + public slots: \ + bool set_##name(type name) { \ + bool ret = false; \ + if ((ret = (m_##name != name))) { \ + m_##name = name; \ + Q_EMIT name##Changed(m_##name); \ + } \ + return ret; \ + } \ + Q_SIGNALS: \ + void name##Changed(type name); \ + \ + private: +#define QML_WRITABLE_VAR_PROPERTY_MODIFIED(type, name) \ + protected: \ + Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) WRITE set_##name NOTIFY name##Changed) \ + Q_PROPERTY(bool name##ErrorId READ MAKE_GETTER_NAME(name##ErrorId) \ + NOTIFY name##ErrorIdChanged) \ + private: \ + type m_##name; \ + type m_##name##_initial; \ + bool m_##name##_modified = false; \ + int m_##name##ErrorId = 0; \ + \ + public: \ + type MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ + } \ + int MAKE_GETTER_NAME(name##ErrorId)(void) const { \ + return m_##name##ErrorId; \ + } \ + bool update_##name##ErrorId(int name##ErrorId) { \ + bool ret = false; \ + if ((ret = (m_##name##ErrorId != name##ErrorId))) { \ + m_##name##ErrorId = name##ErrorId; \ + Q_EMIT name##ErrorIdChanged(m_##name##ErrorId); \ + } \ + return ret; \ + } \ + public slots: \ + bool set_##name(type name) { \ + bool ret = false; \ + if ((ret = (m_##name != name))) { \ + m_##name = name; \ + Q_EMIT name##Changed(m_##name); \ + if (!m_##name##_modified) { \ + m_##name##_modified = true; \ + Q_EMIT name##Modified(m_##name); \ + } else if (m_##name##_initial == m_##name) { \ + m_##name##_modified = false; \ + Q_EMIT name##Modified(m_##name); \ + } \ + update_##name##ErrorId(0); \ + } \ + return ret; \ + } \ + bool init_##name(type name) { \ + bool ret = false; \ + m_##name##_modified = false; \ + m_##name##_initial = name; \ + if ((ret = (m_##name != name))) { \ + m_##name = name; \ + Q_EMIT name##Changed(m_##name); \ + } \ + return ret; \ + } \ + \ + Q_SIGNALS: \ + void name##Changed(type name); \ + void name##Modified(type name); \ + void name##ErrorIdChanged(bool name##ErrorId); \ + \ + private: -#define QML_READONLY_VAR_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type name READ MAKE_GETTER_NAME (name) NOTIFY name##Changed) \ - private: \ - type m_##name; \ - public: \ - type MAKE_GETTER_NAME (name) (void) const { \ - return m_##name ; \ -} \ - bool update_##name (type name) { \ - bool ret = false; \ - if ((ret = (m_##name != name))) { \ - m_##name = name; \ - Q_EMIT name##Changed (m_##name); \ - } \ - return ret; \ -} \ - Q_SIGNALS: \ - void name##Changed (type name); \ - private: +#define QML_READONLY_VAR_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) NOTIFY name##Changed) \ + private: \ + type m_##name; \ + \ + public: \ + type MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ + } \ + bool update_##name(type name) { \ + bool ret = false; \ + if ((ret = (m_##name != name))) { \ + m_##name = name; \ + Q_EMIT name##Changed(m_##name); \ + } \ + return ret; \ + } \ + Q_SIGNALS: \ + void name##Changed(type name); \ + \ + private: -#define QML_CONSTANT_VAR_PROPERTY(type, name) \ - protected: \ - Q_PROPERTY (type name READ MAKE_GETTER_NAME (name) CONSTANT) \ - private: \ - type m_##name; \ - public: \ - type MAKE_GETTER_NAME (name) (void) const { \ - return m_##name ; \ -} \ - private: +#define QML_CONSTANT_VAR_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(type name READ MAKE_GETTER_NAME(name) CONSTANT) \ + private: \ + type m_##name; \ + \ + public: \ + type MAKE_GETTER_NAME(name)(void) const { \ + return m_##name; \ + } \ + \ + private: - class _QmlVarProperty_ : public QObject { +class _QmlVarProperty_ : public QObject { Q_OBJECT - QML_WRITABLE_VAR_PROPERTY (int, var1) - QML_READONLY_VAR_PROPERTY (bool, var2) - QML_CONSTANT_VAR_PROPERTY (QString, var3) + QML_WRITABLE_VAR_PROPERTY(int, var1) + QML_READONLY_VAR_PROPERTY(bool, var2) + QML_CONSTANT_VAR_PROPERTY(QString, var3) + public: + _QmlVarProperty_() : QObject(nullptr), m_var1(0), m_var2(false), m_var3(QString()) {} }; #endif // QQMLVARPROPERTYHELPERS diff --git a/src/qqmlmodels_global.h b/src/qqmlmodels_global.h index b8db0ab..8aad9c0 100644 --- a/src/qqmlmodels_global.h +++ b/src/qqmlmodels_global.h @@ -1,14 +1,18 @@ #ifndef QQMLMODELS_GLOBAL_H #define QQMLMODELS_GLOBAL_H +#include + #ifndef QT_STATIC #if defined(QQML_EXPORT) +/* We are building this library */ #define QQMLMODELS_EXPORT Q_DECL_EXPORT #else +/* We are using this library */ #define QQMLMODELS_EXPORT Q_DECL_IMPORT #endif #else #define QQMLMODELS_EXPORT #endif -//#define QQMLMODELS_EXPORT + #endif // QQMLMODELS_GLOBAL_H diff --git a/src/qqmlobjectlistmodel.cpp b/src/qqmlobjectlistmodel.cpp index 7f0aa0d..4df0e46 100644 --- a/src/qqmlobjectlistmodel.cpp +++ b/src/qqmlobjectlistmodel.cpp @@ -6,19 +6,22 @@ \ingroup QT_QML_MODELS - \brief Provides a generic way to generate a list model from QObject derived class, suitable for QML + \brief Provides a generic way to generate a list model from QObject derived class, suitable for + QML - QQmlObjectListModel is a convenience subclass \c QAbstractListModel that makes use of C++ templates - and Qt Meta Object to extract properties from a \c QObject derived class and create according roles - inside the model. + QQmlObjectListModel is a convenience subclass \c QAbstractListModel that makes use of C++ + templates and Qt Meta Object to extract properties from a \c QObject derived class and create + according roles inside the model. This is a far better way than to expose directly a \c QList inside a \c QVariant. - And this is far simpler than doing all Qt model stuff manually : no subclassing or reimplementing need. + And this is far simpler than doing all Qt model stuff manually : no subclassing or + reimplementing need. The class was designed so that most of the added API is really common with \c QList one. - \b Note : Simply needs that the class used for items inherits \c QObject and has Qt Meta Properties. + \b Note : Simply needs that the class used for items inherits \c QObject and has Qt Meta + Properties. \sa QQmlVariantListModel */ @@ -55,7 +58,8 @@ /*! \fn QList QQmlObjectListModel::listAs () const - \details A template method to retreive all the items as \c QList typed Qt object pointer list. + \details A template method to retreive all the items as \c QList typed Qt object pointer + list. \tparam ItemType The class as object type to use in the returned pointer list \return A strongly typed \c QList of items Qt object pointers @@ -63,7 +67,6 @@ \sa list() const */ - /*! \details Returns the data in a specific index for a given role. @@ -104,7 +107,6 @@ \return The matching role, \c -1 if not found */ - /*! \details Counts the items in the model. @@ -143,7 +145,6 @@ only if they have no parent (because the model took the ownership). */ - /*! \details Adds the given item at the end of the model. @@ -152,7 +153,6 @@ \sa prepend(QObject*), insert(int,QObject*) */ - /*! \details Adds the given item at the beginning of the model. @@ -161,7 +161,6 @@ \sa append(QObject*), insert(int,QObject*) */ - /*! \details Adds the given item at a certain position in the model. @@ -171,7 +170,6 @@ \sa append(QObject*), prepend(QObject*) */ - /*! \details Adds the given list of items at the end of the model. @@ -180,7 +178,6 @@ \sa prepend(QObjectList), insert(int, QObjectList) */ - /*! \details Adds the given list of items at the beginning of the model. @@ -189,7 +186,6 @@ \sa append(QObjectList), insert(int, QObjectList) */ - /*! \details Adds the given list of items at a certain position in the model. @@ -206,21 +202,18 @@ \param pos The position where it willl be after the move */ - /*! \details Remove an item from the model. \param item The pointer to the item object */ - /*! \details Remove an item from the model. \param idx The position of the item in the model */ - /*! \details Retreives a model item as standard Qt object pointer. @@ -273,3 +266,11 @@ \param name The name of the property / role that is used as the index key */ + +// Include TestObject for explicit template instantiation +#include "testobject.h" + +// Explicit template instantiation to ensure Windows DLL linking works correctly +// This forces the template to be instantiated in the library, making its symbols +// available for export and resolving Windows LNK2019 errors +template class QQmlObjectListModel; diff --git a/src/qqmlobjectlistmodel.h b/src/qqmlobjectlistmodel.h index f5011a1..a98e9da 100644 --- a/src/qqmlobjectlistmodel.h +++ b/src/qqmlobjectlistmodel.h @@ -15,444 +15,416 @@ #include #include #include -//#define QQMLMODELS_EXPORT +// #define QQMLMODELS_EXPORT #include "qqmlmodels_global.h" -template QList qListFromVariant (const QVariantList & list) { +template QList qListFromVariant(const QVariantList &list) { QList ret; - ret.reserve (list.size ()); - for (QVariantList::const_iterator it = list.constBegin (); it != list.constEnd (); ++it) { - const QVariant & var = static_cast(* it); - ret.append (var.value ()); + ret.reserve(list.size()); + for (QVariantList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { + const QVariant &var = static_cast(*it); + ret.append(var.value()); } return ret; } -template QVariantList qListToVariant (const QList & list) { +template QVariantList qListToVariant(const QList &list) { QVariantList ret; - ret.reserve (list.size ()); - for (typename QList::const_iterator it = list.constBegin (); it != list.constEnd (); ++it) { - const T & val = static_cast(* it); - ret.append (QVariant::fromValue (val)); + ret.reserve(list.size()); + for (typename QList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { + const T &val = static_cast(*it); + ret.append(QVariant::fromValue(val)); } return ret; } // custom foreach for QList, which uses no copy and check pointer non-null -#define FOREACH_PTR_IN_QLIST(_type_, _var_, _list_) \ - for (typename QList<_type_ *>::const_iterator it = _list_.begin (); it != _list_.end (); ++it) \ - for (_type_ * _var_ = static_cast<_type_ *> (* it); _var_ != Q_NULLPTR; _var_ = Q_NULLPTR) +#define FOREACH_PTR_IN_QLIST(_type_, _var_, _list_) \ + for (typename QList<_type_ *>::const_iterator it = _list_.begin(); it != _list_.end(); ++it) \ + for (_type_ *_var_ = static_cast<_type_ *>(*it); _var_ != Q_NULLPTR; _var_ = Q_NULLPTR) -class QQMLMODELS_EXPORT QQmlObjectListModelBase : public QAbstractListModel { // abstract Qt base class +class QQMLMODELS_EXPORT QQmlObjectListModelBase + : public QAbstractListModel { // abstract Qt base class Q_OBJECT - Q_PROPERTY (int count READ count NOTIFY countChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) -public: - explicit QQmlObjectListModelBase (QObject * parent = Q_NULLPTR) : QAbstractListModel (parent) { } + public: + explicit QQmlObjectListModelBase(QObject *parent = Q_NULLPTR) : QAbstractListModel(parent) {} -public Q_SLOTS: // virtual methods API for QML - virtual int size (void) const = 0; - virtual int count (void) const = 0; - virtual bool isEmpty (void) const = 0; - virtual bool contains (QObject * item) const = 0; - virtual int indexOf (QObject * item) const = 0; - virtual int roleForName (const QByteArray & name) const = 0; - virtual void clear (void) = 0; - virtual void append (QObject * item) = 0; - virtual void prepend (QObject * item) = 0; - virtual void insert (int idx, QObject * item) = 0; - virtual void move (int idx, int pos) = 0; - virtual void remove (QObject * item) = 0; - virtual void remove (int idx) = 0; - virtual QObject * get (int idx) const = 0; - virtual QObject * get (const QString & uid) const = 0; - virtual QObject * getFirst (void) const = 0; - virtual QObject * getLast (void) const = 0; - virtual QVariantList toVarArray (void) const = 0; + public slots: // virtual methods API for QML + virtual int size(void) const = 0; + virtual int count(void) const = 0; + virtual bool isEmpty(void) const = 0; + virtual bool contains(QObject *item) const = 0; + virtual int indexOf(QObject *item) const = 0; + virtual int roleForName(const QByteArray &name) const = 0; + virtual void clear(void) = 0; + virtual void append(QObject *item) = 0; + virtual void prepend(QObject *item) = 0; + virtual void insert(int idx, QObject *item) = 0; + virtual void move(int idx, int pos) = 0; + virtual void remove(QObject *item) = 0; + virtual void remove(int idx) = 0; + virtual QObject *get(int idx) const = 0; + virtual QObject *get(const QString &uid) const = 0; + virtual QObject *getFirst(void) const = 0; + virtual QObject *getLast(void) const = 0; + virtual QVariantList toVarArray(void) const = 0; -protected Q_SLOTS: // internal callback - virtual void onItemPropertyChanged (void) = 0; + protected slots: // internal callback + virtual void onItemPropertyChanged(void) = 0; -Q_SIGNALS: // notifier - void countChanged (void); + Q_SIGNALS: // notifier + void countChanged(void); /** Emitted when an item is about to be moved */ - void itemAboutToBeMoved(QObject* item, int src, int dest); + void itemAboutToBeMoved(QObject *item, int src, int dest); /** Emitted after an item have been moved */ - void itemMoved(QObject* item, int src, int dest); + void itemMoved(QObject *item, int src, int dest); }; -template class /*QQMLMODELS_EXPORT*/ QQmlObjectListModel : public QQmlObjectListModelBase { -public: - explicit QQmlObjectListModel (QObject * parent = Q_NULLPTR, - const QByteArray & displayRole = QByteArray (), - const QByteArray & uidRole = QByteArray ()) - : QQmlObjectListModelBase (parent) - , m_count (0) - , m_uidRoleName (uidRole) - , m_dispRoleName (displayRole) - , m_metaObj (ItemType::staticMetaObject) - { +template +class /*QQMLMODELS_EXPORT*/ QQmlObjectListModel : public QQmlObjectListModelBase { + public: + explicit QQmlObjectListModel(QObject *parent = Q_NULLPTR, + const QByteArray &displayRole = QByteArray(), + const QByteArray &uidRole = QByteArray()) + : QQmlObjectListModelBase(parent), m_count(0), m_uidRoleName(uidRole), + m_dispRoleName(displayRole), m_metaObj(ItemType::staticMetaObject) { static QSet roleNamesBlacklist; - if (roleNamesBlacklist.isEmpty ()) { - roleNamesBlacklist << QByteArrayLiteral ("id") - << QByteArrayLiteral ("index") - << QByteArrayLiteral ("class") - << QByteArrayLiteral ("model") - << QByteArrayLiteral ("modelData"); + if (roleNamesBlacklist.isEmpty()) { + roleNamesBlacklist << QByteArrayLiteral("id") << QByteArrayLiteral("index") + << QByteArrayLiteral("class") << QByteArrayLiteral("model") + << QByteArrayLiteral("modelData"); } - static const char * HANDLER = "onItemPropertyChanged()"; - m_handler = metaObject ()->method (metaObject ()->indexOfMethod (HANDLER)); - if (!displayRole.isEmpty ()) { - m_roles.insert (Qt::DisplayRole, QByteArrayLiteral ("display")); + static const char *HANDLER = "onItemPropertyChanged()"; + m_handler = metaObject()->method(metaObject()->indexOfMethod(HANDLER)); + if (!displayRole.isEmpty()) { + m_roles.insert(Qt::DisplayRole, QByteArrayLiteral("display")); } - m_roles.insert (baseRole (), QByteArrayLiteral ("qtObject")); - const int len = m_metaObj.propertyCount (); - for (int propertyIdx = 0, role = (baseRole () +1); propertyIdx < len; propertyIdx++, role++) { - QMetaProperty metaProp = m_metaObj.property (propertyIdx); - const QByteArray propName = QByteArray (metaProp.name ()); - if (!roleNamesBlacklist.contains (propName)) { - m_roles.insert (role, propName); - if (metaProp.hasNotifySignal ()) { - m_signalIdxToRole.insert (metaProp.notifySignalIndex (), role); + m_roles.insert(baseRole(), QByteArrayLiteral("qtObject")); + const int len = m_metaObj.propertyCount(); + for (int propertyIdx = 0, role = (baseRole() + 1); propertyIdx < len; + propertyIdx++, role++) { + QMetaProperty metaProp = m_metaObj.property(propertyIdx); + const QByteArray propName = QByteArray(metaProp.name()); + if (!roleNamesBlacklist.contains(propName)) { + m_roles.insert(role, propName); + if (metaProp.hasNotifySignal()) { + m_signalIdxToRole.insert(metaProp.notifySignalIndex(), role); } - } - else { - static const QByteArray CLASS_NAME = (QByteArrayLiteral ("QQmlObjectListModel<") % m_metaObj.className () % '>'); - qWarning () << "Can't have" << propName << "as a role name in" << qPrintable (QString::fromLatin1(CLASS_NAME)); + } else { + static const QByteArray CLASS_NAME = + (QByteArrayLiteral("QQmlObjectListModel<") % m_metaObj.className() % '>'); + qWarning() << "Can't have" << propName << "as a role name in" + << qPrintable(QString::fromLatin1(CLASS_NAME)); } } } - bool setData (const QModelIndex & index, const QVariant & value, int role) { + bool setData(const QModelIndex &index, const QVariant &value, int role) { bool ret = false; - ItemType * item = at (index.row ()); - const QByteArray rolename = (role != Qt::DisplayRole ? m_roles.value (role, emptyBA ()) : m_dispRoleName); - if (item != Q_NULLPTR && role != baseRole () && !rolename.isEmpty ()) { - ret = item->setProperty (rolename, value); + ItemType *item = at(index.row()); + const QByteArray rolename = + (role != Qt::DisplayRole ? m_roles.value(role, emptyBA()) : m_dispRoleName); + if (item != Q_NULLPTR && role != baseRole() && !rolename.isEmpty()) { + ret = item->setProperty(rolename, value); } return ret; } - QVariant data (const QModelIndex & index, int role) const { + QVariant data(const QModelIndex &index, int role) const { QVariant ret; - ItemType * item = at (index.row ()); - const QByteArray rolename = (role != Qt::DisplayRole ? m_roles.value (role, emptyBA ()) : m_dispRoleName); - if (item != Q_NULLPTR && !rolename.isEmpty ()) { - ret.setValue (role != baseRole () ? item->property (rolename) : QVariant::fromValue (static_cast (item))); + ItemType *item = at(index.row()); + if (item != Q_NULLPTR) { + if (role == Qt::DisplayRole) { + // For Qt::DisplayRole, use m_dispRoleName if set, otherwise return the object + // itself + if (!m_dispRoleName.isEmpty()) { + ret.setValue(item->property(m_dispRoleName)); + } else { + ret.setValue(QVariant::fromValue(static_cast(item))); + } + } else if (role == baseRole()) { + ret.setValue(QVariant::fromValue(static_cast(item))); + } else { + const QByteArray rolename = m_roles.value(role, emptyBA()); + if (!rolename.isEmpty()) { + ret.setValue(item->property(rolename)); + } + } } return ret; } - QHash roleNames (void) const { - return m_roles; - } + QHash roleNames(void) const { return m_roles; } typedef typename QList::const_iterator const_iterator; - const_iterator begin (void) const { - return m_items.begin (); - } - const_iterator end (void) const { - return m_items.end (); - } - const_iterator constBegin (void) const { - return m_items.constBegin (); - } - const_iterator constEnd (void) const { - return m_items.constEnd (); - } + const_iterator begin(void) const { return m_items.begin(); } + const_iterator end(void) const { return m_items.end(); } + const_iterator constBegin(void) const { return m_items.constBegin(); } + const_iterator constEnd(void) const { return m_items.constEnd(); } -public: // C++ API - ItemType * at (int idx) const { - ItemType * ret = Q_NULLPTR; - if (idx >= 0 && idx < m_items.size ()) { - ret = m_items.value (idx); + public: // C++ API + ItemType *at(int idx) const { + ItemType *ret = Q_NULLPTR; + if (idx >= 0 && idx < m_items.size()) { + ret = m_items.value(idx); } return ret; } - ItemType * getByUid (const QString & uid) const { - return (!m_indexByUid.isEmpty () ? m_indexByUid.value (uid, Q_NULLPTR) : Q_NULLPTR); - } - int roleForName (const QByteArray & name) const { - return m_roles.key (name, -1); - } - int count (void) const { - return m_count; - } - int size (void) const { - return m_count; - } - bool isEmpty (void) const { - return m_items.isEmpty (); - } - bool contains (ItemType * item) const { - return m_items.contains (item); - } - int indexOf (ItemType * item) const { - return m_items.indexOf (item); - } - void clear (void) { - if (!m_items.isEmpty ()) { - beginRemoveRows (noParent (), 0, m_items.count () -1); - FOREACH_PTR_IN_QLIST (ItemType, item, m_items) { - dereferenceItem (item); + ItemType *getByUid(const QString &uid) const { + return (!m_indexByUid.isEmpty() ? m_indexByUid.value(uid, Q_NULLPTR) : Q_NULLPTR); + } + int roleForName(const QByteArray &name) const override { return m_roles.key(name, -1); } + int count(void) const override { return m_count; } + int size(void) const override { return m_count; } + bool isEmpty(void) const override { return m_items.isEmpty(); } + bool contains(ItemType *item) const { return m_items.contains(item); } + int indexOf(ItemType *item) const { return m_items.indexOf(item); } + void clear(void) override { + if (!m_items.isEmpty()) { + beginRemoveRows(noParent(), 0, m_items.count() - 1); + FOREACH_PTR_IN_QLIST(ItemType, item, m_items) { + dereferenceItem(item); } - m_items.clear (); - updateCounter (); - endRemoveRows (); + m_items.clear(); + updateCounter(); + endRemoveRows(); } } - void append (ItemType * item) { + void append(ItemType *item) { if (item != Q_NULLPTR) { - const int pos = m_items.count (); - beginInsertRows (noParent (), pos, pos); - m_items.append (item); - referenceItem (item); - updateCounter (); - endInsertRows (); + const int pos = m_items.count(); + beginInsertRows(noParent(), pos, pos); + m_items.append(item); + referenceItem(item); + updateCounter(); + endInsertRows(); } } - void prepend (ItemType * item) { + void prepend(ItemType *item) { if (item != Q_NULLPTR) { - beginInsertRows (noParent (), 0, 0); - m_items.prepend (item); - referenceItem (item); - updateCounter (); - endInsertRows (); + beginInsertRows(noParent(), 0, 0); + m_items.prepend(item); + referenceItem(item); + updateCounter(); + endInsertRows(); } } - void insert (int idx, ItemType * item) { + void insert(int idx, ItemType *item) { if (item != Q_NULLPTR) { - beginInsertRows (noParent (), idx, idx); - m_items.insert (idx, item); - referenceItem (item); - updateCounter (); - endInsertRows (); + beginInsertRows(noParent(), idx, idx); + m_items.insert(idx, item); + referenceItem(item); + updateCounter(); + endInsertRows(); } } - void append (const QList & itemList) { - if (!itemList.isEmpty ()) { - const int pos = m_items.count (); - beginInsertRows (noParent (), pos, pos + itemList.count () -1); - m_items.reserve (m_items.count () + itemList.count ()); - m_items.append (itemList); - FOREACH_PTR_IN_QLIST (ItemType, item, itemList) { - referenceItem (item); + void append(const QList &itemList) { + if (!itemList.isEmpty()) { + const int pos = m_items.count(); + beginInsertRows(noParent(), pos, pos + itemList.count() - 1); + m_items.reserve(m_items.count() + itemList.count()); + m_items.append(itemList); + FOREACH_PTR_IN_QLIST(ItemType, item, itemList) { + referenceItem(item); } - updateCounter (); - endInsertRows (); + updateCounter(); + endInsertRows(); } } - void prepend (const QList & itemList) { - if (!itemList.isEmpty ()) { - beginInsertRows (noParent (), 0, itemList.count () -1); - m_items.reserve (m_items.count () + itemList.count ()); + void prepend(const QList &itemList) { + if (!itemList.isEmpty()) { + beginInsertRows(noParent(), 0, itemList.count() - 1); + m_items.reserve(m_items.count() + itemList.count()); int offset = 0; - FOREACH_PTR_IN_QLIST (ItemType, item, itemList) { - m_items.insert (offset, item); - referenceItem (item); + FOREACH_PTR_IN_QLIST(ItemType, item, itemList) { + m_items.insert(offset, item); + referenceItem(item); offset++; } - updateCounter (); - endInsertRows (); + updateCounter(); + endInsertRows(); } } - void insert (int idx, const QList & itemList) { - if (!itemList.isEmpty ()) { - beginInsertRows (noParent (), idx, idx + itemList.count () -1); - m_items.reserve (m_items.count () + itemList.count ()); + void insert(int idx, const QList &itemList) { + if (!itemList.isEmpty()) { + beginInsertRows(noParent(), idx, idx + itemList.count() - 1); + m_items.reserve(m_items.count() + itemList.count()); int offset = 0; - FOREACH_PTR_IN_QLIST (ItemType, item, itemList) { - m_items.insert (idx + offset, item); - referenceItem (item); + FOREACH_PTR_IN_QLIST(ItemType, item, itemList) { + m_items.insert(idx + offset, item); + referenceItem(item); offset++; } - updateCounter (); - endInsertRows (); + updateCounter(); + endInsertRows(); } } - void move (int idx, int pos) { - if (idx != pos && idx >=0 && pos>=0 && idx < m_items.size() && pos < m_items.size()) { + void move(int idx, int pos) override { + if (idx != pos && idx >= 0 && pos >= 0 && idx < m_items.size() && pos < m_items.size()) { itemAboutToBeMoved(m_items.at(idx), idx, pos); - beginMoveRows (noParent (), idx, idx, noParent (), (idx < pos ? pos +1 : pos)); - m_items.move (idx, pos); - endMoveRows (); + beginMoveRows(noParent(), idx, idx, noParent(), (idx < pos ? pos + 1 : pos)); + m_items.move(idx, pos); + endMoveRows(); itemMoved(m_items.at(idx), idx, pos); } } - void remove (ItemType * item) { + void remove(ItemType *item) { if (item != Q_NULLPTR) { - const int idx = m_items.indexOf (item); - remove (idx); + const int idx = m_items.indexOf(item); + remove(idx); } } - void remove (int idx) { - if (idx >= 0 && idx < m_items.size ()) { - beginRemoveRows (noParent (), idx, idx); - ItemType * item = m_items.takeAt (idx); - dereferenceItem (item); - updateCounter (); - endRemoveRows (); + void remove(int idx) override { + if (idx >= 0 && idx < m_items.size()) { + beginRemoveRows(noParent(), idx, idx); + ItemType *item = m_items.takeAt(idx); + dereferenceItem(item); + updateCounter(); + endRemoveRows(); } } - ItemType * first (void) const { - return m_items.first (); - } - ItemType * last (void) const { - return m_items.last (); - } - const QList & toList (void) const { - return m_items; - } + ItemType *first(void) const { return m_items.first(); } + ItemType *last(void) const { return m_items.last(); } + const QList &toList(void) const { return m_items; } -public: // QML slots implementation - void append (QObject * item) { - append (qobject_cast (item)); - } - void prepend (QObject * item) { - prepend (qobject_cast (item)); - } - void insert (int idx, QObject * item) { - insert (idx, qobject_cast (item)); - } - void remove (QObject * item) { - remove (qobject_cast (item)); - } - bool contains (QObject * item) const { - return contains (qobject_cast (item)); - } - int indexOf (QObject * item) const { - return indexOf (qobject_cast (item)); - } - int indexOf (const QString & uid) const { - return indexOf (get (uid)); - } - QObject * get (int idx) const { - return static_cast (at (idx)); - } - QObject * get (const QString & uid) const { - return static_cast (getByUid (uid)); - } - QObject * getFirst (void) const { - return static_cast (first ()); - } - QObject * getLast (void) const { - return static_cast (last ()); - } - QVariantList toVarArray (void) const { - return qListToVariant (m_items); - } + public: // QML slots implementation + void append(QObject *item) override { append(qobject_cast(item)); } + void prepend(QObject *item) override { prepend(qobject_cast(item)); } + void insert(int idx, QObject *item) override { insert(idx, qobject_cast(item)); } + void remove(QObject *item) override { remove(qobject_cast(item)); } + bool contains(QObject *item) const override { return contains(qobject_cast(item)); } + int indexOf(QObject *item) const override { return indexOf(qobject_cast(item)); } + int indexOf(const QString &uid) const { return indexOf(get(uid)); } + QObject *get(int idx) const override { return static_cast(at(idx)); } + QObject *get(const QString &uid) const override { + return static_cast(getByUid(uid)); + } + QObject *getFirst(void) const override { return static_cast(first()); } + QObject *getLast(void) const override { return static_cast(last()); } + QVariantList toVarArray(void) const override { return qListToVariant(m_items); } -protected: // internal stuff - static const QString & emptyStr (void) { - static const QString ret = QStringLiteral (""); + protected: // internal stuff + static const QString &emptyStr(void) { + static const QString ret = QStringLiteral(""); return ret; } - static const QByteArray & emptyBA (void) { - static const QByteArray ret = QByteArrayLiteral (""); + static const QByteArray &emptyBA(void) { + static const QByteArray ret = QByteArrayLiteral(""); return ret; } - static const QModelIndex & noParent (void) { - static const QModelIndex ret = QModelIndex (); + static const QModelIndex &noParent(void) { + static const QModelIndex ret = QModelIndex(); return ret; } - static const int & baseRole (void) { + static const int &baseRole(void) { static const int ret = Qt::UserRole; return ret; } - int rowCount (const QModelIndex & parent = QModelIndex ()) const { - return (!parent.isValid () ? m_items.count () : 0); + int rowCount(const QModelIndex &parent = QModelIndex()) const override { + return (!parent.isValid() ? m_items.count() : 0); } - void referenceItem (ItemType * item) { + void referenceItem(ItemType *item) { if (item != Q_NULLPTR) { - if (item->parent () == Q_NULLPTR) { - item->setParent (this); + if (item->parent() == Q_NULLPTR) { + item->setParent(this); } - const QList signalsIdxList = m_signalIdxToRole.keys (); - for (QList::const_iterator it = signalsIdxList.constBegin (); it != signalsIdxList.constEnd (); ++it) { - const int signalIdx = static_cast (* it); - QMetaMethod notifier = item->metaObject ()->method (signalIdx); - connect (item, notifier, this, m_handler, Qt::UniqueConnection); + const QList signalsIdxList = m_signalIdxToRole.keys(); + for (QList::const_iterator it = signalsIdxList.constBegin(); + it != signalsIdxList.constEnd(); ++it) { + const int signalIdx = static_cast(*it); + QMetaMethod notifier = item->metaObject()->method(signalIdx); + connect(item, notifier, this, m_handler, Qt::UniqueConnection); } - if (!m_uidRoleName.isEmpty ()) { - const QString key = m_indexByUid.key (item, emptyStr ()); - if (!key.isEmpty ()) { - m_indexByUid.remove (key); + if (!m_uidRoleName.isEmpty()) { + const QString key = m_indexByUid.key(item, emptyStr()); + if (!key.isEmpty()) { + m_indexByUid.remove(key); } - const QString value = item->property (m_uidRoleName).toString (); - if (!value.isEmpty ()) { - m_indexByUid.insert (value, item); + const QString value = item->property(m_uidRoleName).toString(); + if (!value.isEmpty()) { + m_indexByUid.insert(value, item); } } } } - void dereferenceItem (ItemType * item) { + void dereferenceItem(ItemType *item) { if (item != Q_NULLPTR) { - disconnect (this, Q_NULLPTR, item, Q_NULLPTR); - disconnect (item, Q_NULLPTR, this, Q_NULLPTR); - if (!m_uidRoleName.isEmpty ()) { - const QString key = m_indexByUid.key (item, emptyStr ()); - if (!key.isEmpty ()) { - m_indexByUid.remove (key); + disconnect(this, Q_NULLPTR, item, Q_NULLPTR); + disconnect(item, Q_NULLPTR, this, Q_NULLPTR); + if (!m_uidRoleName.isEmpty()) { + const QString key = m_indexByUid.key(item, emptyStr()); + if (!key.isEmpty()) { + m_indexByUid.remove(key); } } - if (item->parent () == this) { // FIXME : maybe that's not the best way to test ownership ? - item->deleteLater (); + if (item->parent() == + this) { // FIXME : maybe that's not the best way to test ownership ? + item->deleteLater(); } } } - void onItemPropertyChanged (void) { - ItemType * item = qobject_cast (sender ()); - const int row = m_items.indexOf (item); - const int sig = senderSignalIndex (); - const int role = m_signalIdxToRole.value (sig, -1); + void onItemPropertyChanged(void) override { + ItemType *item = qobject_cast(sender()); + const int row = m_items.indexOf(item); + const int sig = senderSignalIndex(); + const int role = m_signalIdxToRole.value(sig, -1); if (row >= 0 && role >= 0) { - QModelIndex index = QAbstractListModel::index (row, 0, noParent ()); + QModelIndex index = QAbstractListModel::index(row, 0, noParent()); QVector rolesList; - rolesList.append (role); - if (m_roles.value (role) == m_dispRoleName) { - rolesList.append (Qt::DisplayRole); + rolesList.append(role); + if (m_roles.value(role) == m_dispRoleName) { + rolesList.append(Qt::DisplayRole); } - Q_EMIT dataChanged (index, index, rolesList); + Q_EMIT dataChanged(index, index, rolesList); } - if (!m_uidRoleName.isEmpty ()) { - const QByteArray roleName = m_roles.value (role, emptyBA ()); - if (!roleName.isEmpty () && roleName == m_uidRoleName) { - const QString key = m_indexByUid.key (item, emptyStr ()); - if (!key.isEmpty ()) { - m_indexByUid.remove (key); + if (!m_uidRoleName.isEmpty()) { + const QByteArray roleName = m_roles.value(role, emptyBA()); + if (!roleName.isEmpty() && roleName == m_uidRoleName) { + const QString key = m_indexByUid.key(item, emptyStr()); + if (!key.isEmpty()) { + m_indexByUid.remove(key); } - const QString value = item->property (m_uidRoleName).toString (); - if (!value.isEmpty ()) { - m_indexByUid.insert (value, item); + const QString value = item->property(m_uidRoleName).toString(); + if (!value.isEmpty()) { + m_indexByUid.insert(value, item); } } } } - inline void updateCounter (void) { - if (m_count != m_items.count ()) { - m_count = m_items.count (); - Q_EMIT countChanged (); + inline void updateCounter(void) { + if (m_count != m_items.count()) { + m_count = m_items.count(); + Q_EMIT countChanged(); } } -private: // data members - int m_count; - QByteArray m_uidRoleName; - QByteArray m_dispRoleName; - QMetaObject m_metaObj; - QMetaMethod m_handler; - QHash m_roles; - QHash m_signalIdxToRole; - QList m_items; + private: // data members + int m_count; + QByteArray m_uidRoleName; + QByteArray m_dispRoleName; + QMetaObject m_metaObj; + QMetaMethod m_handler; + QHash m_roles; + QHash m_signalIdxToRole; + QList m_items; QHash m_indexByUid; - friend QDebug operator<<(QDebug debug, const QQmlObjectListModel* ret){ - debug.nospace()<<"QQmlObjectListModel Content: "<count()<<"\n"; - for (int i=0; icount();i++){ - debug.nospace() << "Item[" << i << "] = " << ret->get(i) << '\n'; - } - return debug; - } + friend QDebug operator<<(QDebug debug, const QQmlObjectListModel *ret) { + debug.nospace() << "QQmlObjectListModel Content: " << ret->count() << "\n"; + for (int i = 0; i < ret->count(); i++) { + debug.nospace() << "Item[" << i << "] = " << ret->get(i) << '\n'; + } + return debug; + } }; -#define QML_OBJMODEL_PROPERTY(type, name) \ - protected: Q_PROPERTY (QQmlObjectListModelBase * name READ get_##name CONSTANT) \ - private: QQmlObjectListModel * m_##name; \ - public: QQmlObjectListModel * get_##name (void) const { return m_##name; } \ - private: +#define QML_OBJMODEL_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(QQmlObjectListModelBase *name READ get_##name CONSTANT) \ + private: \ + QQmlObjectListModel *m_##name; \ + \ + public: \ + QQmlObjectListModel *get_##name(void) const { \ + return m_##name; \ + } \ + \ + private: #endif // QQMLOBJECTLISTMODEL_H diff --git a/src/qqmlobjectsortfilterlistmodel.cpp b/src/qqmlobjectsortfilterlistmodel.cpp index a85bca4..af78224 100644 --- a/src/qqmlobjectsortfilterlistmodel.cpp +++ b/src/qqmlobjectsortfilterlistmodel.cpp @@ -6,25 +6,29 @@ \ingroup QT_QML_MODELS - \brief Provides a generic way to generate a list model from QObject derived class, suitable for QML + \brief Provides a generic way to generate a list model from QObject derived class, suitable for + QML - QQmlObjectSortFilterListModel is a convenience subclass \c QSortFilterProxyModel that makes use of C++ templates - and Qt Meta Object to extract properties from a \c QObject derived class and create according roles - inside the model. + QQmlObjectSortFilterListModel is a convenience subclass \c QSortFilterProxyModel that makes use + of C++ templates and Qt Meta Object to extract properties from a \c QObject derived class and + create according roles inside the model. This is a far better way than to expose directly a \c QList inside a \c QVariant. - And this is far simpler than doing all Qt model stuff manually : no subclassing or reimplementing need. + And this is far simpler than doing all Qt model stuff manually : no subclassing or + reimplementing need. The class was designed so that most of the added API is really common with \c QList one. - \b Note : Simply needs that the class used for items inherits \c QObject and has Qt Meta Properties. + \b Note : Simply needs that the class used for items inherits \c QObject and has Qt Meta + Properties. \sa QQmlVariantListModel */ /*! - \fn static QQmlObjectSortFilterListModel * QQmlObjectSortFilterListModel::create (QObject * parent = Q_NULLPTR) + \fn static QQmlObjectSortFilterListModel * QQmlObjectSortFilterListModel::create (QObject * + parent = Q_NULLPTR) \details A factory to create a new model from a class that will be used as item type. @@ -34,8 +38,8 @@ This is a template method, meant to be used like this : \code - QQmlObjectSortFilterListModel * myModel = QQmlObjectSortFilterListModel::create(this); - \endcode + QQmlObjectSortFilterListModel * myModel = + QQmlObjectSortFilterListModel::create(this); \endcode No other customization in needed after that. */ @@ -55,7 +59,8 @@ /*! \fn QList QQmlObjectSortFilterListModel::listAs () const - \details A template method to retreive all the items as \c QList typed Qt object pointer list. + \details A template method to retreive all the items as \c QList typed Qt object pointer + list. \tparam ItemType The class as object type to use in the returned pointer list \return A strongly typed \c QList of items Qt object pointers @@ -63,7 +68,6 @@ \sa list() const */ - /*! \details Returns the data in a specific index for a given role. @@ -104,7 +108,6 @@ \return The matching role, \c -1 if not found */ - /*! \details Counts the items in the model. @@ -143,7 +146,6 @@ only if they have no parent (because the model took the ownership). */ - /*! \details Adds the given item at the end of the model. @@ -152,7 +154,6 @@ \sa prepend(QObject*), insert(int,QObject*) */ - /*! \details Adds the given item at the beginning of the model. @@ -161,7 +162,6 @@ \sa append(QObject*), insert(int,QObject*) */ - /*! \details Adds the given item at a certain position in the model. @@ -171,7 +171,6 @@ \sa append(QObject*), prepend(QObject*) */ - /*! \details Adds the given list of items at the end of the model. @@ -180,7 +179,6 @@ \sa prepend(QObjectList), insert(int, QObjectList) */ - /*! \details Adds the given list of items at the beginning of the model. @@ -189,7 +187,6 @@ \sa append(QObjectList), insert(int, QObjectList) */ - /*! \details Adds the given list of items at a certain position in the model. @@ -206,21 +203,18 @@ \param pos The position where it willl be after the move */ - /*! \details Remove an item from the model. \param item The pointer to the item object */ - /*! \details Remove an item from the model. \param idx The position of the item in the model */ - /*! \details Retreives a model item as standard Qt object pointer. diff --git a/src/qqmlobjectsortfilterlistmodel.h b/src/qqmlobjectsortfilterlistmodel.h index d8d5e72..f7955f6 100644 --- a/src/qqmlobjectsortfilterlistmodel.h +++ b/src/qqmlobjectsortfilterlistmodel.h @@ -1,7 +1,6 @@ #ifndef QQMLOBJECTLISTMODEL_H #define QQMLOBJECTLISTMODEL_H -#include #include #include #include @@ -11,440 +10,403 @@ #include #include #include +#include #include #include #include #include -//#define QQMLMODELS_EXPORT +// #define QQMLMODELS_EXPORT #include "qqmlmodels_global.h" -template QList qListFromVariant (const QVariantList & list) { +template QList qListFromVariant(const QVariantList &list) { QList ret; - ret.reserve (list.size ()); - for (QVariantList::const_iterator it = list.constBegin (); it != list.constEnd (); ++it) { - const QVariant & var = static_cast(* it); - ret.append (var.value ()); + ret.reserve(list.size()); + for (QVariantList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { + const QVariant &var = static_cast(*it); + ret.append(var.value()); } return ret; } -template QVariantList qListToVariant (const QList & list) { +template QVariantList qListToVariant(const QList &list) { QVariantList ret; - ret.reserve (list.size ()); - for (typename QList::const_iterator it = list.constBegin (); it != list.constEnd (); ++it) { - const T & val = static_cast(* it); - ret.append (QVariant::fromValue (val)); + ret.reserve(list.size()); + for (typename QList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { + const T &val = static_cast(*it); + ret.append(QVariant::fromValue(val)); } return ret; } // custom foreach for QList, which uses no copy and check pointer non-null -#define FOREACH_PTR_IN_QLIST(_type_, _var_, _list_) \ - for (typename QList<_type_ *>::const_iterator it = _list_.begin (); it != _list_.end (); ++it) \ - for (_type_ * _var_ = static_cast<_type_ *> (* it); _var_ != Q_NULLPTR; _var_ = Q_NULLPTR) +#define FOREACH_PTR_IN_QLIST(_type_, _var_, _list_) \ + for (typename QList<_type_ *>::const_iterator it = _list_.begin(); it != _list_.end(); ++it) \ + for (_type_ *_var_ = static_cast<_type_ *>(*it); _var_ != Q_NULLPTR; _var_ = Q_NULLPTR) -class QQMLMODELS_EXPORT QQmlObjectListSortFilterModelBase : public QSortFilterProxyModel { // abstract Qt base class +class QQMLMODELS_EXPORT QQmlObjectListSortFilterModelBase + : public QSortFilterProxyModel { // abstract Qt base class Q_OBJECT - Q_PROPERTY (int count READ count NOTIFY countChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) -public: - explicit QQmlObjectListSortFilterModelBase (QObject * parent = Q_NULLPTR) : QSortFilterProxyModel (parent) { } + public: + explicit QQmlObjectListSortFilterModelBase(QObject *parent = Q_NULLPTR) + : QSortFilterProxyModel(parent) {} -public Q_SLOTS: // virtual methods API for QML - virtual int size (void) const = 0; - virtual int count (void) const = 0; - virtual bool isEmpty (void) const = 0; - virtual bool contains (QObject * item) const = 0; - virtual int indexOf (QObject * item) const = 0; - virtual int roleForName (const QByteArray & name) const = 0; - virtual void clear (void) = 0; - virtual void append (QObject * item) = 0; - virtual void prepend (QObject * item) = 0; - virtual void insert (int idx, QObject * item) = 0; - virtual void move (int idx, int pos) = 0; - virtual void remove (QObject * item) = 0; - virtual void remove (int idx) = 0; - virtual QObject * get (int idx) const = 0; - virtual QObject * get (const QString & uid) const = 0; - virtual QObject * getFirst (void) const = 0; - virtual QObject * getLast (void) const = 0; - virtual QVariantList toVarArray (void) const = 0; + public slots: // virtual methods API for QML + virtual int size(void) const = 0; + virtual int count(void) const = 0; + virtual bool isEmpty(void) const = 0; + virtual bool contains(QObject *item) const = 0; + virtual int indexOf(QObject *item) const = 0; + virtual int roleForName(const QByteArray &name) const = 0; + virtual void clear(void) = 0; + virtual void append(QObject *item) = 0; + virtual void prepend(QObject *item) = 0; + virtual void insert(int idx, QObject *item) = 0; + virtual void move(int idx, int pos) = 0; + virtual void remove(QObject *item) = 0; + virtual void remove(int idx) = 0; + virtual QObject *get(int idx) const = 0; + virtual QObject *get(const QString &uid) const = 0; + virtual QObject *getFirst(void) const = 0; + virtual QObject *getLast(void) const = 0; + virtual QVariantList toVarArray(void) const = 0; -protected Q_SLOTS: // internal callback - virtual void onItemPropertyChanged (void) = 0; + protected slots: // internal callback + virtual void onItemPropertyChanged(void) = 0; -Q_SIGNALS: // notifier - void countChanged (void); + Q_SIGNALS: // notifier + void countChanged(void); }; -template class /*QQMLMODELS_EXPORT*/ QQmlObjectListModel : public QQmlObjectListSortFilterModelBase { -public: - explicit QQmlObjectListModel (QObject * parent = Q_NULLPTR, - const QByteArray & displayRole = QByteArray (), - const QByteArray & uidRole = QByteArray ()) - : QQmlObjectListSortFilterModelBase (parent) - , m_count (0) - , m_uidRoleName (uidRole) - , m_dispRoleName (displayRole) - , m_metaObj (ItemType::staticMetaObject) - { +template +class /*QQMLMODELS_EXPORT*/ QQmlObjectListModel : public QQmlObjectListSortFilterModelBase { + public: + explicit QQmlObjectListModel(QObject *parent = Q_NULLPTR, + const QByteArray &displayRole = QByteArray(), + const QByteArray &uidRole = QByteArray()) + : QQmlObjectListSortFilterModelBase(parent), m_count(0), m_uidRoleName(uidRole), + m_dispRoleName(displayRole), m_metaObj(ItemType::staticMetaObject) { static QSet roleNamesBlacklist; - if (roleNamesBlacklist.isEmpty ()) { - roleNamesBlacklist << QByteArrayLiteral ("id") - << QByteArrayLiteral ("index") - << QByteArrayLiteral ("class") - << QByteArrayLiteral ("model") - << QByteArrayLiteral ("modelData"); + if (roleNamesBlacklist.isEmpty()) { + roleNamesBlacklist << QByteArrayLiteral("id") << QByteArrayLiteral("index") + << QByteArrayLiteral("class") << QByteArrayLiteral("model") + << QByteArrayLiteral("modelData"); } - static const char * HANDLER = "onItemPropertyChanged()"; - m_handler = metaObject ()->method (metaObject ()->indexOfMethod (HANDLER)); - if (!displayRole.isEmpty ()) { - m_roles.insert (Qt::DisplayRole, QByteArrayLiteral ("display")); + static const char *HANDLER = "onItemPropertyChanged()"; + m_handler = metaObject()->method(metaObject()->indexOfMethod(HANDLER)); + if (!displayRole.isEmpty()) { + m_roles.insert(Qt::DisplayRole, QByteArrayLiteral("display")); } - m_roles.insert (baseRole (), QByteArrayLiteral ("qtObject")); - const int len = m_metaObj.propertyCount (); - for (int propertyIdx = 0, role = (baseRole () +1); propertyIdx < len; propertyIdx++, role++) { - QMetaProperty metaProp = m_metaObj.property (propertyIdx); - const QByteArray propName = QByteArray (metaProp.name ()); - if (!roleNamesBlacklist.contains (propName)) { - m_roles.insert (role, propName); - if (metaProp.hasNotifySignal ()) { - m_signalIdxToRole.insert (metaProp.notifySignalIndex (), role); + m_roles.insert(baseRole(), QByteArrayLiteral("qtObject")); + const int len = m_metaObj.propertyCount(); + for (int propertyIdx = 0, role = (baseRole() + 1); propertyIdx < len; + propertyIdx++, role++) { + QMetaProperty metaProp = m_metaObj.property(propertyIdx); + const QByteArray propName = QByteArray(metaProp.name()); + if (!roleNamesBlacklist.contains(propName)) { + m_roles.insert(role, propName); + if (metaProp.hasNotifySignal()) { + m_signalIdxToRole.insert(metaProp.notifySignalIndex(), role); } - } - else { - static const QByteArray CLASS_NAME = (QByteArrayLiteral ("QQmlObjectListModel<") % m_metaObj.className () % '>'); - qWarning () << "Can't have" << propName << "as a role name in" << qPrintable (QString::fromLatin1(CLASS_NAME)); + } else { + static const QByteArray CLASS_NAME = + (QByteArrayLiteral("QQmlObjectListModel<") % m_metaObj.className() % '>'); + qWarning() << "Can't have" << propName << "as a role name in" + << qPrintable(QString::fromLatin1(CLASS_NAME)); } } } - bool setData (const QModelIndex & index, const QVariant & value, int role) { + bool setData(const QModelIndex &index, const QVariant &value, int role) { bool ret = false; - ItemType * item = at (index.row ()); - const QByteArray rolename = (role != Qt::DisplayRole ? m_roles.value (role, emptyBA ()) : m_dispRoleName); - if (item != Q_NULLPTR && role != baseRole () && !rolename.isEmpty ()) { - ret = item->setProperty (rolename, value); + ItemType *item = at(index.row()); + const QByteArray rolename = + (role != Qt::DisplayRole ? m_roles.value(role, emptyBA()) : m_dispRoleName); + if (item != Q_NULLPTR && role != baseRole() && !rolename.isEmpty()) { + ret = item->setProperty(rolename, value); } return ret; } - QVariant data (const QModelIndex & index, int role) const { + QVariant data(const QModelIndex &index, int role) const { QVariant ret; - ItemType * item = at (index.row ()); - const QByteArray rolename = (role != Qt::DisplayRole ? m_roles.value (role, emptyBA ()) : m_dispRoleName); - if (item != Q_NULLPTR && !rolename.isEmpty ()) { - ret.setValue (role != baseRole () ? item->property (rolename) : QVariant::fromValue (static_cast (item))); + ItemType *item = at(index.row()); + const QByteArray rolename = + (role != Qt::DisplayRole ? m_roles.value(role, emptyBA()) : m_dispRoleName); + if (item != Q_NULLPTR && !rolename.isEmpty()) { + ret.setValue(role != baseRole() ? item->property(rolename) + : QVariant::fromValue(static_cast(item))); } return ret; } - QHash roleNames (void) const { - return m_roles; - } + QHash roleNames(void) const { return m_roles; } typedef typename QList::const_iterator const_iterator; - const_iterator begin (void) const { - return m_items.begin (); - } - const_iterator end (void) const { - return m_items.end (); - } - const_iterator constBegin (void) const { - return m_items.constBegin (); - } - const_iterator constEnd (void) const { - return m_items.constEnd (); - } + const_iterator begin(void) const { return m_items.begin(); } + const_iterator end(void) const { return m_items.end(); } + const_iterator constBegin(void) const { return m_items.constBegin(); } + const_iterator constEnd(void) const { return m_items.constEnd(); } -public: // C++ API - ItemType * at (int idx) const { - ItemType * ret = Q_NULLPTR; - if (idx >= 0 && idx < m_items.size ()) { - ret = m_items.value (idx); + public: // C++ API + ItemType *at(int idx) const { + ItemType *ret = Q_NULLPTR; + if (idx >= 0 && idx < m_items.size()) { + ret = m_items.value(idx); } return ret; } - ItemType * getByUid (const QString & uid) const { - return (!m_indexByUid.isEmpty () ? m_indexByUid.value (uid, Q_NULLPTR) : Q_NULLPTR); - } - int roleForName (const QByteArray & name) const { - return m_roles.key (name, -1); - } - int count (void) const { - return m_count; - } - int size (void) const { - return m_count; - } - bool isEmpty (void) const { - return m_items.isEmpty (); - } - bool contains (ItemType * item) const { - return m_items.contains (item); - } - int indexOf (ItemType * item) const { - return m_items.indexOf (item); - } - void clear (void) { - if (!m_items.isEmpty ()) { - beginRemoveRows (noParent (), 0, m_items.count () -1); - FOREACH_PTR_IN_QLIST (ItemType, item, m_items) { - dereferenceItem (item); + ItemType *getByUid(const QString &uid) const { + return (!m_indexByUid.isEmpty() ? m_indexByUid.value(uid, Q_NULLPTR) : Q_NULLPTR); + } + int roleForName(const QByteArray &name) const override { return m_roles.key(name, -1); } + int count(void) const override { return m_count; } + int size(void) const override { return m_count; } + bool isEmpty(void) const override { return m_items.isEmpty(); } + bool contains(ItemType *item) const { return m_items.contains(item); } + int indexOf(ItemType *item) const { return m_items.indexOf(item); } + void clear(void) override { + if (!m_items.isEmpty()) { + beginRemoveRows(noParent(), 0, m_items.count() - 1); + FOREACH_PTR_IN_QLIST(ItemType, item, m_items) { + dereferenceItem(item); } - m_items.clear (); - updateCounter (); - endRemoveRows (); + m_items.clear(); + updateCounter(); + endRemoveRows(); } } - void append (ItemType * item) { + void append(ItemType *item) { if (item != Q_NULLPTR) { - const int pos = m_items.count (); - beginInsertRows (noParent (), pos, pos); - m_items.append (item); - referenceItem (item); - updateCounter (); - endInsertRows (); + const int pos = m_items.count(); + beginInsertRows(noParent(), pos, pos); + m_items.append(item); + referenceItem(item); + updateCounter(); + endInsertRows(); } } - void prepend (ItemType * item) { + void prepend(ItemType *item) { if (item != Q_NULLPTR) { - beginInsertRows (noParent (), 0, 0); - m_items.prepend (item); - referenceItem (item); - updateCounter (); - endInsertRows (); + beginInsertRows(noParent(), 0, 0); + m_items.prepend(item); + referenceItem(item); + updateCounter(); + endInsertRows(); } } - void insert (int idx, ItemType * item) { + void insert(int idx, ItemType *item) { if (item != Q_NULLPTR) { - beginInsertRows (noParent (), idx, idx); - m_items.insert (idx, item); - referenceItem (item); - updateCounter (); - endInsertRows (); + beginInsertRows(noParent(), idx, idx); + m_items.insert(idx, item); + referenceItem(item); + updateCounter(); + endInsertRows(); } } - void append (const QList & itemList) { - if (!itemList.isEmpty ()) { - const int pos = m_items.count (); - beginInsertRows (noParent (), pos, pos + itemList.count () -1); - m_items.reserve (m_items.count () + itemList.count ()); - m_items.append (itemList); - FOREACH_PTR_IN_QLIST (ItemType, item, itemList) { - referenceItem (item); + void append(const QList &itemList) { + if (!itemList.isEmpty()) { + const int pos = m_items.count(); + beginInsertRows(noParent(), pos, pos + itemList.count() - 1); + m_items.reserve(m_items.count() + itemList.count()); + m_items.append(itemList); + FOREACH_PTR_IN_QLIST(ItemType, item, itemList) { + referenceItem(item); } - updateCounter (); - endInsertRows (); + updateCounter(); + endInsertRows(); } } - void prepend (const QList & itemList) { - if (!itemList.isEmpty ()) { - beginInsertRows (noParent (), 0, itemList.count () -1); - m_items.reserve (m_items.count () + itemList.count ()); + void prepend(const QList &itemList) { + if (!itemList.isEmpty()) { + beginInsertRows(noParent(), 0, itemList.count() - 1); + m_items.reserve(m_items.count() + itemList.count()); int offset = 0; - FOREACH_PTR_IN_QLIST (ItemType, item, itemList) { - m_items.insert (offset, item); - referenceItem (item); + FOREACH_PTR_IN_QLIST(ItemType, item, itemList) { + m_items.insert(offset, item); + referenceItem(item); offset++; } - updateCounter (); - endInsertRows (); + updateCounter(); + endInsertRows(); } } - void insert (int idx, const QList & itemList) { - if (!itemList.isEmpty ()) { - beginInsertRows (noParent (), idx, idx + itemList.count () -1); - m_items.reserve (m_items.count () + itemList.count ()); + void insert(int idx, const QList &itemList) { + if (!itemList.isEmpty()) { + beginInsertRows(noParent(), idx, idx + itemList.count() - 1); + m_items.reserve(m_items.count() + itemList.count()); int offset = 0; - FOREACH_PTR_IN_QLIST (ItemType, item, itemList) { - m_items.insert (idx + offset, item); - referenceItem (item); + FOREACH_PTR_IN_QLIST(ItemType, item, itemList) { + m_items.insert(idx + offset, item); + referenceItem(item); offset++; } - updateCounter (); - endInsertRows (); + updateCounter(); + endInsertRows(); } } - void move (int idx, int pos) { + void move(int idx, int pos) override { if (idx != pos) { - // FIXME : use begin/end MoveRows when supported by Repeater, since then use remove/insert pair - //beginMoveRows (noParent (), idx, idx, noParent (), (idx < pos ? pos +1 : pos)); - beginRemoveRows (noParent (), idx, idx); - beginInsertRows (noParent (), pos, pos); - m_items.move (idx, pos); - endRemoveRows (); - endInsertRows (); - //endMoveRows (); + // FIXME : use begin/end MoveRows when supported by Repeater, since then use + // remove/insert pair + // beginMoveRows (noParent (), idx, idx, noParent (), (idx < pos ? pos +1 : pos)); + beginRemoveRows(noParent(), idx, idx); + beginInsertRows(noParent(), pos, pos); + m_items.move(idx, pos); + endRemoveRows(); + endInsertRows(); + // endMoveRows (); } } - void remove (ItemType * item) { + void remove(ItemType *item) { if (item != Q_NULLPTR) { - const int idx = m_items.indexOf (item); - remove (idx); + const int idx = m_items.indexOf(item); + remove(idx); } } - void remove (int idx) { - if (idx >= 0 && idx < m_items.size ()) { - beginRemoveRows (noParent (), idx, idx); - ItemType * item = m_items.takeAt (idx); - dereferenceItem (item); - updateCounter (); - endRemoveRows (); + void remove(int idx) override { + if (idx >= 0 && idx < m_items.size()) { + beginRemoveRows(noParent(), idx, idx); + ItemType *item = m_items.takeAt(idx); + dereferenceItem(item); + updateCounter(); + endRemoveRows(); } } - ItemType * first (void) const { - return m_items.first (); - } - ItemType * last (void) const { - return m_items.last (); - } - const QList & toList (void) const { - return m_items; - } + ItemType *first(void) const { return m_items.first(); } + ItemType *last(void) const { return m_items.last(); } + const QList &toList(void) const { return m_items; } -public: // QML slots implementation - void append (QObject * item) { - append (qobject_cast (item)); - } - void prepend (QObject * item) { - prepend (qobject_cast (item)); - } - void insert (int idx, QObject * item) { - insert (idx, qobject_cast (item)); - } - void remove (QObject * item) { - remove (qobject_cast (item)); - } - bool contains (QObject * item) const { - return contains (qobject_cast (item)); - } - int indexOf (QObject * item) const { - return indexOf (qobject_cast (item)); - } - int indexOf (const QString & uid) const { - return indexOf (get (uid)); - } - QObject * get (int idx) const { - return static_cast (at (idx)); - } - QObject * get (const QString & uid) const { - return static_cast (getByUid (uid)); - } - QObject * getFirst (void) const { - return static_cast (first ()); - } - QObject * getLast (void) const { - return static_cast (last ()); - } - QVariantList toVarArray (void) const { - return qListToVariant (m_items); - } + public: // QML slots implementation + void append(QObject *item) override { append(qobject_cast(item)); } + void prepend(QObject *item) override { prepend(qobject_cast(item)); } + void insert(int idx, QObject *item) override { insert(idx, qobject_cast(item)); } + void remove(QObject *item) override { remove(qobject_cast(item)); } + bool contains(QObject *item) const override { return contains(qobject_cast(item)); } + int indexOf(QObject *item) const override { return indexOf(qobject_cast(item)); } + int indexOf(const QString &uid) const { return indexOf(get(uid)); } + QObject *get(int idx) const override { return static_cast(at(idx)); } + QObject *get(const QString &uid) const override { + return static_cast(getByUid(uid)); + } + QObject *getFirst(void) const override { return static_cast(first()); } + QObject *getLast(void) const override { return static_cast(last()); } + QVariantList toVarArray(void) const override { return qListToVariant(m_items); } -protected: // internal stuff - static const QString & emptyStr (void) { - static const QString ret = QStringLiteral (""); + protected: // internal stuff + static const QString &emptyStr(void) { + static const QString ret = QStringLiteral(""); return ret; } - static const QByteArray & emptyBA (void) { - static const QByteArray ret = QByteArrayLiteral (""); + static const QByteArray &emptyBA(void) { + static const QByteArray ret = QByteArrayLiteral(""); return ret; } - static const QModelIndex & noParent (void) { - static const QModelIndex ret = QModelIndex (); + static const QModelIndex &noParent(void) { + static const QModelIndex ret = QModelIndex(); return ret; } - static const int & baseRole (void) { + static const int &baseRole(void) { static const int ret = Qt::UserRole; return ret; } - int rowCount (const QModelIndex & parent = QModelIndex ()) const { - return (!parent.isValid () ? m_items.count () : 0); + int rowCount(const QModelIndex &parent = QModelIndex()) const override { + return (!parent.isValid() ? m_items.count() : 0); } - void referenceItem (ItemType * item) { + void referenceItem(ItemType *item) { if (item != Q_NULLPTR) { - if (item->parent () == Q_NULLPTR) { - item->setParent (this); + if (item->parent() == Q_NULLPTR) { + item->setParent(this); } - const QList signalsIdxList = m_signalIdxToRole.keys (); - for (QList::const_iterator it = signalsIdxList.constBegin (); it != signalsIdxList.constEnd (); ++it) { - const int signalIdx = static_cast (* it); - QMetaMethod notifier = item->metaObject ()->method (signalIdx); - connect (item, notifier, this, m_handler, Qt::UniqueConnection); + const QList signalsIdxList = m_signalIdxToRole.keys(); + for (QList::const_iterator it = signalsIdxList.constBegin(); + it != signalsIdxList.constEnd(); ++it) { + const int signalIdx = static_cast(*it); + QMetaMethod notifier = item->metaObject()->method(signalIdx); + connect(item, notifier, this, m_handler, Qt::UniqueConnection); } - if (!m_uidRoleName.isEmpty ()) { - const QString key = m_indexByUid.key (item, emptyStr ()); - if (!key.isEmpty ()) { - m_indexByUid.remove (key); + if (!m_uidRoleName.isEmpty()) { + const QString key = m_indexByUid.key(item, emptyStr()); + if (!key.isEmpty()) { + m_indexByUid.remove(key); } - const QString value = item->property (m_uidRoleName).toString (); - if (!value.isEmpty ()) { - m_indexByUid.insert (value, item); + const QString value = item->property(m_uidRoleName).toString(); + if (!value.isEmpty()) { + m_indexByUid.insert(value, item); } } } } - void dereferenceItem (ItemType * item) { + void dereferenceItem(ItemType *item) { if (item != Q_NULLPTR) { - disconnect (this, Q_NULLPTR, item, Q_NULLPTR); - disconnect (item, Q_NULLPTR, this, Q_NULLPTR); - if (!m_uidRoleName.isEmpty ()) { - const QString key = m_indexByUid.key (item, emptyStr ()); - if (!key.isEmpty ()) { - m_indexByUid.remove (key); + disconnect(this, Q_NULLPTR, item, Q_NULLPTR); + disconnect(item, Q_NULLPTR, this, Q_NULLPTR); + if (!m_uidRoleName.isEmpty()) { + const QString key = m_indexByUid.key(item, emptyStr()); + if (!key.isEmpty()) { + m_indexByUid.remove(key); } } - if (item->parent () == this) { // FIXME : maybe that's not the best way to test ownership ? - item->deleteLater (); + if (item->parent() == + this) { // FIXME : maybe that's not the best way to test ownership ? + item->deleteLater(); } } } - void onItemPropertyChanged (void) { - ItemType * item = qobject_cast (sender ()); - const int row = m_items.indexOf (item); - const int sig = senderSignalIndex (); - const int role = m_signalIdxToRole.value (sig, -1); + void onItemPropertyChanged(void) override { + ItemType *item = qobject_cast(sender()); + const int row = m_items.indexOf(item); + const int sig = senderSignalIndex(); + const int role = m_signalIdxToRole.value(sig, -1); if (row >= 0 && role >= 0) { - QModelIndex index = QSortFilterProxyModel::index (row, 0, noParent ()); + QModelIndex index = QSortFilterProxyModel::index(row, 0, noParent()); QVector rolesList; - rolesList.append (role); - if (m_roles.value (role) == m_dispRoleName) { - rolesList.append (Qt::DisplayRole); + rolesList.append(role); + if (m_roles.value(role) == m_dispRoleName) { + rolesList.append(Qt::DisplayRole); } - Q_EMIT dataChanged (index, index, rolesList); + Q_EMIT dataChanged(index, index, rolesList); } - if (!m_uidRoleName.isEmpty ()) { - const QByteArray roleName = m_roles.value (role, emptyBA ()); - if (!roleName.isEmpty () && roleName == m_uidRoleName) { - const QString key = m_indexByUid.key (item, emptyStr ()); - if (!key.isEmpty ()) { - m_indexByUid.remove (key); + if (!m_uidRoleName.isEmpty()) { + const QByteArray roleName = m_roles.value(role, emptyBA()); + if (!roleName.isEmpty() && roleName == m_uidRoleName) { + const QString key = m_indexByUid.key(item, emptyStr()); + if (!key.isEmpty()) { + m_indexByUid.remove(key); } - const QString value = item->property (m_uidRoleName).toString (); - if (!value.isEmpty ()) { - m_indexByUid.insert (value, item); + const QString value = item->property(m_uidRoleName).toString(); + if (!value.isEmpty()) { + m_indexByUid.insert(value, item); } } } } - inline void updateCounter (void) { - if (m_count != m_items.count ()) { - m_count = m_items.count (); - Q_EMIT countChanged (); + inline void updateCounter(void) { + if (m_count != m_items.count()) { + m_count = m_items.count(); + Q_EMIT countChanged(); } } -private: // data members - int m_count; - QByteArray m_uidRoleName; - QByteArray m_dispRoleName; - QMetaObject m_metaObj; - QMetaMethod m_handler; - QHash m_roles; - QHash m_signalIdxToRole; - QList m_items; + private: // data members + int m_count; + QByteArray m_uidRoleName; + QByteArray m_dispRoleName; + QMetaObject m_metaObj; + QMetaMethod m_handler; + QHash m_roles; + QHash m_signalIdxToRole; + QList m_items; QHash m_indexByUid; }; -#define QML_OBJSORTFILTERMODEL_PROPERTY(type, name) \ - protected: Q_PROPERTY (QQmlObjectListSortFilterModelBase * name READ get_##name CONSTANT) \ - private: QQmlObjectListSortFilterModel * m_##name; \ - public: QQmlObjectListSortFilterModel * get_##name (void) const { return m_##name; } \ - private: +#define QML_OBJSORTFILTERMODEL_PROPERTY(type, name) \ + protected: \ + Q_PROPERTY(QQmlObjectListSortFilterModelBase *name READ get_##name CONSTANT) \ + private: \ + QQmlObjectListSortFilterModel *m_##name; \ + \ + public: \ + QQmlObjectListSortFilterModel *get_##name(void) const { \ + return m_##name; \ + } \ + \ + private: #endif // QQMLOBJECTLISTMODEL_H diff --git a/src/qqmlvariantlistmodel.cpp b/src/qqmlvariantlistmodel.cpp index 6b94f12..96a4e8c 100644 --- a/src/qqmlvariantlistmodel.cpp +++ b/src/qqmlvariantlistmodel.cpp @@ -1,10 +1,10 @@ #include "qqmlvariantlistmodel.h" -#define NO_PARENT QModelIndex () +#define NO_PARENT QModelIndex() #define BASE_ROLE Qt::UserRole -#define EMPTY_STR QStringLiteral ("") -#define EMPTY_BA QByteArrayLiteral ("") +#define EMPTY_STR QStringLiteral("") +#define EMPTY_BA QByteArrayLiteral("") /*! \class QQmlVariantListModel @@ -13,55 +13,47 @@ \brief Provides a generic way to generate a list model from QVariant, suitable for QML - QQmlVariantListModel is a convenience subclass \c QAbstractListModel that makes use of the versatile - nature of QVariant to allow creating a list model from every type : - \li Booleans - \li Numbers - \li Strings - \li Lists - \li Maps - \li Object pointers - \li etc... + QQmlVariantListModel is a convenience subclass \c QAbstractListModel that makes use of the + versatile nature of QVariant to allow creating a list model from every type : \li Booleans \li + Numbers \li Strings \li Lists \li Maps \li Object pointers \li etc... This is a far better way than to expose directly a \c QList<____> inside a \c QVariant. - And this is far simpler than doing all Qt model stuff manually : no subclassing or reimplementing need. + And this is far simpler than doing all Qt model stuff manually : no subclassing or + reimplementing need. The class was designed so that most of the added API is really common with \c QList one. - \b Note : Simply needs that the type items inherits is handled by Qt MetaType system and \c QVariant. + \b Note : Simply needs that the type items inherits is handled by Qt MetaType system and \c + QVariant. \sa QQmlObjectListModel */ - /*! \details Constructs a new model that will hold QVariant as items. \param parent The parent object for the model memory management */ -QQmlVariantListModel::QQmlVariantListModel (QObject * parent) : QAbstractListModel (parent) - , m_count(0) - , m_items() - , m_roles() -{ - m_roles.insert (BASE_ROLE, QByteArrayLiteral ("qtVariant")); +QQmlVariantListModel::QQmlVariantListModel(QObject *parent) + : QAbstractListModel(parent), m_count(0), m_items(), m_roles() { + m_roles.insert(BASE_ROLE, QByteArrayLiteral("qtVariant")); + m_roles.insert(Qt::DisplayRole, QByteArrayLiteral("display")); } /*! \internal */ -QQmlVariantListModel::~QQmlVariantListModel (void) { - clear (); +QQmlVariantListModel::~QQmlVariantListModel(void) { + clear(); } /*! \internal */ -int QQmlVariantListModel::rowCount (const QModelIndex & parent) const -{ - Q_UNUSED (parent); - return m_items.count (); +int QQmlVariantListModel::rowCount(const QModelIndex &parent) const { + Q_UNUSED(parent); + return m_items.count(); } /*! @@ -75,12 +67,11 @@ int QQmlVariantListModel::rowCount (const QModelIndex & parent) const \b Note : the \c 0 role contains the QVariant itself. */ -QVariant QQmlVariantListModel::data (const QModelIndex & index, int role) const -{ +QVariant QQmlVariantListModel::data(const QModelIndex &index, int role) const { QVariant ret; - int idx = index.row (); - if (idx >= 0 && idx < count () && role == BASE_ROLE) { - ret = m_items.value (idx); + int idx = index.row(); + if (idx >= 0 && idx < count() && (role == BASE_ROLE || role == Qt::DisplayRole)) { + ret = m_items.value(idx); } return ret; } @@ -94,8 +85,7 @@ QVariant QQmlVariantListModel::data (const QModelIndex & index, int role) const \b Note : the only role is \c 'qtVariant'. */ -QHash QQmlVariantListModel::roleNames () const -{ +QHash QQmlVariantListModel::roleNames() const { return m_roles; } @@ -111,14 +101,13 @@ QHash QQmlVariantListModel::roleNames () const \b Note : as the only role is \c 0 ('qtVariant'), it replaces the QVariant value */ -bool QQmlVariantListModel::setData (const QModelIndex & index, const QVariant & value, int role) -{ +bool QQmlVariantListModel::setData(const QModelIndex &index, const QVariant &value, int role) { bool ret = false; - int idx = index.row (); - if (idx >= 0 && idx < count () && role == BASE_ROLE) { - m_items.replace (idx, value); - QModelIndex item = QAbstractListModel::index (idx, 0, NO_PARENT); - emit dataChanged (item, item, QVector (1, role)); + int idx = index.row(); + if (idx >= 0 && idx < count() && role == BASE_ROLE) { + m_items.replace(idx, value); + QModelIndex item = QAbstractListModel::index(idx, 0, NO_PARENT); + emit dataChanged(item, item, QVector(1, role)); ret = true; } return ret; @@ -129,9 +118,8 @@ bool QQmlVariantListModel::setData (const QModelIndex & index, const QVariant & \return The count of items in the model */ -int QQmlVariantListModel::count () const -{ - return m_items.size (); +int QQmlVariantListModel::count() const { + return m_items.size(); } /*! @@ -139,21 +127,19 @@ int QQmlVariantListModel::count () const \return Whether the model contains no item */ -bool QQmlVariantListModel::isEmpty () const -{ - return m_items.isEmpty (); +bool QQmlVariantListModel::isEmpty() const { + return m_items.isEmpty(); } /*! \details Delete all the items in the model. */ -void QQmlVariantListModel::clear () -{ - if (!m_items.isEmpty ()) { - beginRemoveRows (NO_PARENT, 0, count () -1); - m_items.clear (); - endRemoveRows (); - updateCounter (); +void QQmlVariantListModel::clear() { + if (!m_items.isEmpty()) { + beginRemoveRows(NO_PARENT, 0, count() - 1); + m_items.clear(); + endRemoveRows(); + updateCounter(); } } @@ -164,13 +150,12 @@ void QQmlVariantListModel::clear () \sa prepend(QVariant), insert(int,QVariant) */ -void QQmlVariantListModel::append (const QVariant & item) -{ - int pos = m_items.count (); - beginInsertRows (NO_PARENT, pos, pos); - m_items.append (item); - endInsertRows (); - updateCounter (); +void QQmlVariantListModel::append(const QVariant &item) { + int pos = m_items.count(); + beginInsertRows(NO_PARENT, pos, pos); + m_items.append(item); + endInsertRows(); + updateCounter(); } /*! @@ -180,12 +165,11 @@ void QQmlVariantListModel::append (const QVariant & item) \sa append(QVariant), insert(int,QVariant) */ -void QQmlVariantListModel::prepend (const QVariant & item) -{ - beginInsertRows (NO_PARENT, 0, 0); - m_items.prepend (item); - endInsertRows (); - updateCounter (); +void QQmlVariantListModel::prepend(const QVariant &item) { + beginInsertRows(NO_PARENT, 0, 0); + m_items.prepend(item); + endInsertRows(); + updateCounter(); } /*! @@ -196,12 +180,11 @@ void QQmlVariantListModel::prepend (const QVariant & item) \sa append(QVariant), prepend(QVariant) */ -void QQmlVariantListModel::insert (int idx, const QVariant & item) -{ - beginInsertRows (NO_PARENT, idx, idx); - m_items.insert (idx, item); - endInsertRows (); - updateCounter (); +void QQmlVariantListModel::insert(int idx, const QVariant &item) { + beginInsertRows(NO_PARENT, idx, idx); + m_items.insert(idx, item); + endInsertRows(); + updateCounter(); } /*! @@ -212,12 +195,11 @@ void QQmlVariantListModel::insert (int idx, const QVariant & item) \b Note : this is the regular way in C++ to modify the variant value. */ -void QQmlVariantListModel::replace (int pos, const QVariant & item) -{ - if (pos >= 0 && pos < count ()) { - m_items.replace (pos, item); - QModelIndex index = QAbstractListModel::index (pos, 0, NO_PARENT); - emit dataChanged (index, index, QVector (1, BASE_ROLE)); +void QQmlVariantListModel::replace(int pos, const QVariant &item) { + if (pos >= 0 && pos < count()) { + m_items.replace(pos, item); + QModelIndex index = QAbstractListModel::index(pos, 0, NO_PARENT); + emit dataChanged(index, index, QVector(1, BASE_ROLE)); } } @@ -228,14 +210,13 @@ void QQmlVariantListModel::replace (int pos, const QVariant & item) \sa prepend(QVariantList), insert(int, QVariantList) */ -void QQmlVariantListModel::appendList (const QVariantList & itemList) -{ - if (!itemList.isEmpty ()) { - int pos = m_items.count (); - beginInsertRows (NO_PARENT, pos, pos + itemList.count () -1); - m_items.append (itemList); - endInsertRows (); - updateCounter (); +void QQmlVariantListModel::appendList(const QVariantList &itemList) { + if (!itemList.isEmpty()) { + int pos = m_items.count(); + beginInsertRows(NO_PARENT, pos, pos + itemList.count() - 1); + m_items.append(itemList); + endInsertRows(); + updateCounter(); } } @@ -246,16 +227,16 @@ void QQmlVariantListModel::appendList (const QVariantList & itemList) \sa append(QVariantList), insert(int, QVariantList) */ -void QQmlVariantListModel::prependList (const QVariantList & itemList) -{ - if (!itemList.isEmpty ()) { - beginInsertRows (NO_PARENT, 0, itemList.count () -1); +void QQmlVariantListModel::prependList(const QVariantList &itemList) { + if (!itemList.isEmpty()) { + beginInsertRows(NO_PARENT, 0, itemList.count() - 1); int offset = 0; foreach (QVariant item, itemList) { - m_items.insert (offset, item); + m_items.insert(offset, item); + offset++; // Insert each subsequent item after the previous one } - endInsertRows (); - updateCounter (); + endInsertRows(); + updateCounter(); } } @@ -267,17 +248,16 @@ void QQmlVariantListModel::prependList (const QVariantList & itemList) \sa append(QVariantList), prepend(QVariantList) */ -void QQmlVariantListModel::insertList (int idx, const QVariantList & itemList) -{ - if (!itemList.isEmpty ()) { - beginInsertRows (NO_PARENT, idx, idx + itemList.count () -1); +void QQmlVariantListModel::insertList(int idx, const QVariantList &itemList) { + if (!itemList.isEmpty()) { + beginInsertRows(NO_PARENT, idx, idx + itemList.count() - 1); int offset = 0; foreach (QVariant item, itemList) { - m_items.insert (idx + offset, item); + m_items.insert(idx + offset, item); offset++; } - endInsertRows (); - updateCounter (); + endInsertRows(); + updateCounter(); } } @@ -287,15 +267,35 @@ void QQmlVariantListModel::insertList (int idx, const QVariantList & itemList) \param idx The current position of the item \param pos The position where it willl be after the move */ -void QQmlVariantListModel::move (int idx, int pos) -{ +void QQmlVariantListModel::move(int idx, int pos) { if (idx != pos) { - const int lowest = qMin (idx, pos); - const int highest = qMax (idx, pos); - beginMoveRows (NO_PARENT, highest, highest, NO_PARENT, lowest); + // Qt's beginMoveRows expects the destination index based on the state BEFORE the move + // When moving from lower to higher index, use pos + 1 + // When moving from higher to lower index, use pos + int destination = (idx < pos) ? pos + 1 : pos; + beginMoveRows(NO_PARENT, idx, idx, NO_PARENT, destination); + + m_items.move(idx, pos); + endMoveRows(); + } +} + +/*! + \details Swaps two items in the model. + + \param idx1 The position of the first item + \param idx2 The position of the second item +*/ +void QQmlVariantListModel::swap(int idx1, int idx2) { + if (idx1 != idx2 && idx1 >= 0 && idx1 < m_items.size() && idx2 >= 0 && idx2 < m_items.size()) { + m_items.swapItemsAt(idx1, idx2); + + // Emit dataChanged for both positions + QModelIndex index1 = QAbstractListModel::index(idx1, 0, NO_PARENT); + QModelIndex index2 = QAbstractListModel::index(idx2, 0, NO_PARENT); - m_items.move (highest, lowest); - endMoveRows (); + emit dataChanged(index1, index1, QVector(1, BASE_ROLE)); + emit dataChanged(index2, index2, QVector(1, BASE_ROLE)); } } @@ -304,13 +304,12 @@ void QQmlVariantListModel::move (int idx, int pos) \param idx The position of the item in the model */ -void QQmlVariantListModel::remove (int idx) -{ - if (idx >= 0 && idx < m_items.size ()) { - beginRemoveRows (NO_PARENT, idx, idx); - m_items.removeAt (idx); - endRemoveRows (); - updateCounter (); +void QQmlVariantListModel::remove(int idx) { + if (idx >= 0 && idx < m_items.size()) { + beginRemoveRows(NO_PARENT, idx, idx); + m_items.removeAt(idx); + endRemoveRows(); + updateCounter(); } } @@ -320,11 +319,10 @@ void QQmlVariantListModel::remove (int idx) \param idx The position of the item in the model \return A variant containing the item */ -QVariant QQmlVariantListModel::get (int idx) const -{ +QVariant QQmlVariantListModel::get(int idx) const { QVariant ret; - if (idx >= 0 && idx < m_items.size ()) { - ret = m_items.value (idx); + if (idx >= 0 && idx < m_items.size()) { + ret = m_items.value(idx); } return ret; } @@ -334,18 +332,16 @@ QVariant QQmlVariantListModel::get (int idx) const \return A \c QVariantList containing all the variants */ -QVariantList QQmlVariantListModel::list () const -{ +QVariantList QQmlVariantListModel::list() const { return m_items; } /*! \internal */ -void QQmlVariantListModel::updateCounter () -{ - if (m_count != m_items.count ()) { - m_count = m_items.count (); - emit countChanged (m_count); +void QQmlVariantListModel::updateCounter() { + if (m_count != m_items.count()) { + m_count = m_items.count(); + emit countChanged(m_count); } } diff --git a/src/qqmlvariantlistmodel.h b/src/qqmlvariantlistmodel.h index efc242f..5124f94 100644 --- a/src/qqmlvariantlistmodel.h +++ b/src/qqmlvariantlistmodel.h @@ -1,51 +1,52 @@ #ifndef QQMLVARIANTLISTMODEL_H #define QQMLVARIANTLISTMODEL_H -#include #include -#include #include +#include +#include #include "qqmlmodels_global.h" class QQMLMODELS_EXPORT QQmlVariantListModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY (int count READ count NOTIFY countChanged) - -public: - explicit QQmlVariantListModel (QObject * parent = Q_NULLPTR); - ~QQmlVariantListModel (void); - -public: // QAbstractItemModel interface reimplemented - int rowCount (const QModelIndex & parent = QModelIndex ()) const override; - bool setData (const QModelIndex & index, const QVariant & value, int role) override; - QVariant data (const QModelIndex & index, int role) const override; - QHash roleNames (void) const override; - -public Q_SLOTS: // public API - void clear (void); - int count (void) const; - bool isEmpty (void) const; - void append (const QVariant & item); - void prepend (const QVariant & item); - void insert (int idx, const QVariant & item); - void appendList (const QVariantList & itemList); - void prependList (const QVariantList & itemList); - void replace (int pos, const QVariant & item); - void insertList (int idx, const QVariantList & itemList); - void move (int idx, int pos); - void remove (int idx); - QVariant get (int idx) const; - QVariantList list (void) const; - -Q_SIGNALS: // notifiers - void countChanged (int count); - -protected: - void updateCounter (void); - -private: - int m_count; - QVariantList m_items; + Q_PROPERTY(int count READ count NOTIFY countChanged) + + public: + explicit QQmlVariantListModel(QObject *parent = Q_NULLPTR); + ~QQmlVariantListModel(void); + + public: // QAbstractItemModel interface reimplemented + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames(void) const override; + + public slots: // public API + void clear(void); + int count(void) const; + bool isEmpty(void) const; + void append(const QVariant &item); + void prepend(const QVariant &item); + void insert(int idx, const QVariant &item); + void appendList(const QVariantList &itemList); + void prependList(const QVariantList &itemList); + void replace(int pos, const QVariant &item); + void insertList(int idx, const QVariantList &itemList); + void move(int idx, int pos); + void swap(int idx1, int idx2); + void remove(int idx); + QVariant get(int idx) const; + QVariantList list(void) const; + + Q_SIGNALS: // notifiers + void countChanged(int count); + + protected: + void updateCounter(void); + + private: + int m_count; + QVariantList m_items; QHash m_roles; }; diff --git a/src/testobject.cpp b/src/testobject.cpp new file mode 100644 index 0000000..4f621bf --- /dev/null +++ b/src/testobject.cpp @@ -0,0 +1,22 @@ +#include "testobject.h" + +TestObject::TestObject(QObject *parent) : QObject(parent), m_value(0) { +} + +TestObject::TestObject(const QString &name, int value, QObject *parent) + : QObject(parent), m_name(name), m_value(value) { +} + +void TestObject::setName(const QString &name) { + if (m_name != name) { + m_name = name; + emit nameChanged(); + } +} + +void TestObject::setValue(int value) { + if (m_value != value) { + m_value = value; + emit valueChanged(); + } +} \ No newline at end of file diff --git a/src/testobject.h b/src/testobject.h new file mode 100644 index 0000000..7670902 --- /dev/null +++ b/src/testobject.h @@ -0,0 +1,34 @@ +#ifndef TESTOBJECT_H +#define TESTOBJECT_H + +#include "qqmlmodels_global.h" +#include +#include + +// Test utility class for demonstrating QQmlObjectListModel usage +// This class is exported to ensure Windows DLL linking works correctly +class QQMLMODELS_EXPORT TestObject : public QObject { + Q_OBJECT + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) + + public: + explicit TestObject(QObject *parent = nullptr); + TestObject(const QString &name, int value, QObject *parent = nullptr); + + QString name() const { return m_name; } + void setName(const QString &name); + + int value() const { return m_value; } + void setValue(int value); + + signals: + void nameChanged(); + void valueChanged(); + + private: + QString m_name; + int m_value; +}; + +#endif // TESTOBJECT_H \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..059f2a2 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(unit) \ No newline at end of file diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt new file mode 100644 index 0000000..1c85dfb --- /dev/null +++ b/tests/unit/CMakeLists.txt @@ -0,0 +1,38 @@ +qt_add_executable(test_qqmlobjectlistmodel + test_qqmlobjectlistmodel.cpp +) + +target_link_libraries(test_qqmlobjectlistmodel PRIVATE + CPPQmlModels + Qt6::Test + Qt6::Core + Qt6::Qml +) + +add_test(NAME test_qqmlobjectlistmodel COMMAND test_qqmlobjectlistmodel) + +qt_add_executable(test_qqmlvariantlistmodel + test_qqmlvariantlistmodel.cpp +) + +target_link_libraries(test_qqmlvariantlistmodel PRIVATE + CPPQmlModels + Qt6::Test + Qt6::Core + Qt6::Qml +) + +add_test(NAME test_qqmlvariantlistmodel COMMAND test_qqmlvariantlistmodel) + +qt_add_executable(test_qtsupermacros + test_qtsupermacros.cpp +) + +target_link_libraries(test_qtsupermacros PRIVATE + CPPQmlModels + Qt6::Test + Qt6::Core + Qt6::Qml +) + +add_test(NAME test_qtsupermacros COMMAND test_qtsupermacros) \ No newline at end of file diff --git a/tests/unit/test_qqmlobjectlistmodel.cpp b/tests/unit/test_qqmlobjectlistmodel.cpp new file mode 100644 index 0000000..f8c5a80 --- /dev/null +++ b/tests/unit/test_qqmlobjectlistmodel.cpp @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include + +class TestQQmlObjectListModel : public QObject { + Q_OBJECT + public: + TestQQmlObjectListModel() : model(nullptr) {} + + private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void testEmpty(); + void testAppend(); + void testPrepend(); + void testInsert(); + void testRemove(); + void testClear(); + void testGet(); + void testIndexOf(); + void testContains(); + void testRoles(); + void testData(); + void testSignals(); + + private: + QQmlObjectListModel *model; +}; + +void TestQQmlObjectListModel::initTestCase() { + // Called before the first test function is executed +} + +void TestQQmlObjectListModel::cleanupTestCase() { + // Called after the last test function was executed +} + +void TestQQmlObjectListModel::init() { + model = new QQmlObjectListModel(this); +} + +void TestQQmlObjectListModel::cleanup() { + delete model; + model = nullptr; +} + +void TestQQmlObjectListModel::testEmpty() { + QVERIFY(model != nullptr); + QCOMPARE(model->count(), 0); + QCOMPARE(model->size(), 0); + QVERIFY(model->isEmpty()); + // Don't test protected rowCount() directly +} + +void TestQQmlObjectListModel::testAppend() { + TestObject *obj = new TestObject("test", 42); + model->append(obj); + + QCOMPARE(model->count(), 1); + QCOMPARE(model->size(), 1); + QVERIFY(!model->isEmpty()); + QCOMPARE(model->get(0), obj); +} + +void TestQQmlObjectListModel::testPrepend() { + TestObject *obj1 = new TestObject("first", 1); + TestObject *obj2 = new TestObject("second", 2); + + model->append(obj2); + model->prepend(obj1); + + QCOMPARE(model->count(), 2); + QCOMPARE(model->get(0), obj1); + QCOMPARE(model->get(1), obj2); +} + +void TestQQmlObjectListModel::testInsert() { + TestObject *obj1 = new TestObject("first", 1); + TestObject *obj2 = new TestObject("second", 2); + TestObject *obj3 = new TestObject("third", 3); + + model->append(obj1); + model->append(obj3); + model->insert(1, obj2); + + QCOMPARE(model->count(), 3); + QCOMPARE(model->get(0), obj1); + QCOMPARE(model->get(1), obj2); + QCOMPARE(model->get(2), obj3); +} + +void TestQQmlObjectListModel::testRemove() { + TestObject *obj1 = new TestObject("first", 1); + TestObject *obj2 = new TestObject("second", 2); + TestObject *obj3 = new TestObject("third", 3); + + model->append(obj1); + model->append(obj2); + model->append(obj3); + + model->remove(1); + QCOMPARE(model->count(), 2); + QCOMPARE(model->get(0), obj1); + QCOMPARE(model->get(1), obj3); +} + +void TestQQmlObjectListModel::testClear() { + TestObject *obj1 = new TestObject("first", 1); + TestObject *obj2 = new TestObject("second", 2); + + model->append(obj1); + model->append(obj2); + QCOMPARE(model->count(), 2); + + model->clear(); + QCOMPARE(model->count(), 0); + QVERIFY(model->isEmpty()); +} + +void TestQQmlObjectListModel::testGet() { + TestObject *obj = new TestObject("test", 42); + model->append(obj); + + QCOMPARE(model->get(0), obj); + QCOMPARE(model->get(10), nullptr); // Invalid index +} + +void TestQQmlObjectListModel::testIndexOf() { + TestObject *obj1 = new TestObject("first", 1); + TestObject *obj2 = new TestObject("second", 2); + TestObject *obj3 = new TestObject("third", 3); + + model->append(obj1); + model->append(obj2); + + QCOMPARE(model->indexOf(obj1), 0); + QCOMPARE(model->indexOf(obj2), 1); + QCOMPARE(model->indexOf(obj3), -1); // Not in model +} + +void TestQQmlObjectListModel::testContains() { + TestObject *obj1 = new TestObject("first", 1); + TestObject *obj2 = new TestObject("second", 2); + + model->append(obj1); + + QVERIFY(model->contains(obj1)); + QVERIFY(!model->contains(obj2)); +} + +void TestQQmlObjectListModel::testRoles() { + QHash roles = model->roleNames(); + QVERIFY(roles.size() > 0); + // The exact roles depend on the TestObject properties +} + +void TestQQmlObjectListModel::testData() { + TestObject *obj = new TestObject("test", 42); + model->append(obj); + + QModelIndex index = model->index(0, 0); + QVERIFY(index.isValid()); + + // Test data retrieval for different roles + QVariant data = model->data(index, Qt::DisplayRole); + QVERIFY(data.isValid()); +} + +void TestQQmlObjectListModel::testSignals() { + QSignalSpy countSpy(model, &QQmlObjectListModelBase::countChanged); + + TestObject *obj = new TestObject("test", 42); + model->append(obj); + + QCOMPARE(countSpy.count(), 1); + + model->clear(); + QCOMPARE(countSpy.count(), 2); +} + +QTEST_MAIN(TestQQmlObjectListModel) +// cppcheck-suppress missingInclude +#include "test_qqmlobjectlistmodel.moc" \ No newline at end of file diff --git a/tests/unit/test_qqmlvariantlistmodel.cpp b/tests/unit/test_qqmlvariantlistmodel.cpp new file mode 100644 index 0000000..df1ec48 --- /dev/null +++ b/tests/unit/test_qqmlvariantlistmodel.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include + +class TestQQmlVariantListModel : public QObject { + Q_OBJECT + public: + TestQQmlVariantListModel() : model(nullptr) {} + + private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void testEmpty(); + void testAppend(); + void testPrepend(); + void testInsert(); + void testRemove(); + void testClear(); + void testGet(); + void testReplace(); + void testMove(); + void testSwap(); + void testAppendList(); + void testPrependList(); + void testInsertList(); + void testSignals(); + void testRoles(); + + private: + QQmlVariantListModel *model; +}; + +void TestQQmlVariantListModel::initTestCase() { + // Called before the first test function is executed +} + +void TestQQmlVariantListModel::cleanupTestCase() { + // Called after the last test function was executed +} + +void TestQQmlVariantListModel::init() { + model = new QQmlVariantListModel(this); +} + +void TestQQmlVariantListModel::cleanup() { + delete model; + model = nullptr; +} + +void TestQQmlVariantListModel::testEmpty() { + QVERIFY(model != nullptr); + QCOMPARE(model->count(), 0); + QVERIFY(model->isEmpty()); + QCOMPARE(model->rowCount(), 0); +} + +void TestQQmlVariantListModel::testAppend() { + model->append(QVariant("test")); + QCOMPARE(model->count(), 1); + QVERIFY(!model->isEmpty()); + QCOMPARE(model->get(0).toString(), QString("test")); + + model->append(QVariant(42)); + QCOMPARE(model->count(), 2); + QCOMPARE(model->get(1).toInt(), 42); +} + +void TestQQmlVariantListModel::testPrepend() { + model->append(QVariant("second")); + model->prepend(QVariant("first")); + + QCOMPARE(model->count(), 2); + QCOMPARE(model->get(0).toString(), QString("first")); + QCOMPARE(model->get(1).toString(), QString("second")); +} + +void TestQQmlVariantListModel::testInsert() { + model->append(QVariant("first")); + model->append(QVariant("third")); + model->insert(1, QVariant("second")); + + QCOMPARE(model->count(), 3); + QCOMPARE(model->get(0).toString(), QString("first")); + QCOMPARE(model->get(1).toString(), QString("second")); + QCOMPARE(model->get(2).toString(), QString("third")); +} + +void TestQQmlVariantListModel::testRemove() { + model->append(QVariant("first")); + model->append(QVariant("second")); + model->append(QVariant("third")); + + model->remove(1); + QCOMPARE(model->count(), 2); + QCOMPARE(model->get(0).toString(), QString("first")); + QCOMPARE(model->get(1).toString(), QString("third")); +} + +void TestQQmlVariantListModel::testClear() { + model->append(QVariant("test1")); + model->append(QVariant("test2")); + QCOMPARE(model->count(), 2); + + model->clear(); + QCOMPARE(model->count(), 0); + QVERIFY(model->isEmpty()); +} + +void TestQQmlVariantListModel::testGet() { + model->append(QVariant("test")); + QVariant value = model->get(0); + QCOMPARE(value.toString(), QString("test")); + + // Test invalid index + QVariant invalid = model->get(10); + QVERIFY(!invalid.isValid()); +} + +void TestQQmlVariantListModel::testReplace() { + model->append(QVariant("old")); + model->replace(0, QVariant("new")); + + QCOMPARE(model->count(), 1); + QCOMPARE(model->get(0).toString(), QString("new")); +} + +void TestQQmlVariantListModel::testMove() { + model->append(QVariant("first")); + model->append(QVariant("second")); + model->append(QVariant("third")); + + model->move(0, 2); // Move first to last position + + QCOMPARE(model->count(), 3); + QCOMPARE(model->get(0).toString(), QString("second")); + QCOMPARE(model->get(1).toString(), QString("third")); + QCOMPARE(model->get(2).toString(), QString("first")); +} + +void TestQQmlVariantListModel::testSwap() { + model->append(QVariant("first")); + model->append(QVariant("second")); + model->append(QVariant("third")); + + model->swap(0, 2); // Swap first and third + + QCOMPARE(model->count(), 3); + QCOMPARE(model->get(0).toString(), QString("third")); + QCOMPARE(model->get(1).toString(), QString("second")); + QCOMPARE(model->get(2).toString(), QString("first")); +} + +void TestQQmlVariantListModel::testAppendList() { + QVariantList list; + list << "item1" << "item2" << "item3"; + + model->appendList(list); + + QCOMPARE(model->count(), 3); + QCOMPARE(model->get(0).toString(), QString("item1")); + QCOMPARE(model->get(1).toString(), QString("item2")); + QCOMPARE(model->get(2).toString(), QString("item3")); +} + +void TestQQmlVariantListModel::testPrependList() { + model->append(QVariant("existing")); + + QVariantList list; + list << "item1" << "item2"; + + model->prependList(list); + + QCOMPARE(model->count(), 3); + QCOMPARE(model->get(0).toString(), QString("item1")); + QCOMPARE(model->get(1).toString(), QString("item2")); + QCOMPARE(model->get(2).toString(), QString("existing")); +} + +void TestQQmlVariantListModel::testInsertList() { + model->append(QVariant("first")); + model->append(QVariant("last")); + + QVariantList list; + list << "middle1" << "middle2"; + + model->insertList(1, list); + + QCOMPARE(model->count(), 4); + QCOMPARE(model->get(0).toString(), QString("first")); + QCOMPARE(model->get(1).toString(), QString("middle1")); + QCOMPARE(model->get(2).toString(), QString("middle2")); + QCOMPARE(model->get(3).toString(), QString("last")); +} + +void TestQQmlVariantListModel::testSignals() { + QSignalSpy countSpy(model, &QQmlVariantListModel::countChanged); + + model->append(QVariant("test")); + QCOMPARE(countSpy.count(), 1); + QCOMPARE(countSpy.takeLast().at(0).toInt(), 1); + + model->clear(); + QCOMPARE(countSpy.count(), 1); + QCOMPARE(countSpy.takeLast().at(0).toInt(), 0); +} + +void TestQQmlVariantListModel::testRoles() { + QHash roles = model->roleNames(); + QVERIFY(roles.contains(Qt::DisplayRole)); +} + +QTEST_MAIN(TestQQmlVariantListModel) +// cppcheck-suppress missingInclude +#include "test_qqmlvariantlistmodel.moc" \ No newline at end of file diff --git a/tests/unit/test_qtsupermacros.cpp b/tests/unit/test_qtsupermacros.cpp new file mode 100644 index 0000000..69cb381 --- /dev/null +++ b/tests/unit/test_qtsupermacros.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +// Test class using the macros (must be at global scope for MOC) +class TestPropertyClass : public QObject { + Q_OBJECT + + QML_WRITABLE_AUTO_PROPERTY(QString, writableProp) + QML_READONLY_AUTO_PROPERTY(int, readonlyProp) + QML_CONSTANT_AUTO_PROPERTY(bool, constantProp) + + public: + explicit TestPropertyClass(QObject *parent = nullptr) + : QObject(parent), m_writableProp(QString("initial")), m_readonlyProp(42), + m_constantProp(true) {} + + // For readonly property, we need to provide a way to update it + void updateReadonly(int value) { update_readonlyProp(value); } +}; + +class TestQtSuperMacros : public QObject { + Q_OBJECT + + private slots: + void initTestCase(); + void cleanupTestCase(); + void testWritableProperty(); + void testReadonlyProperty(); + void testConstantProperty(); +}; + +void TestQtSuperMacros::initTestCase() { + // Called before the first test function is executed +} + +void TestQtSuperMacros::cleanupTestCase() { + // Called after the last test function was executed +} + +void TestQtSuperMacros::testWritableProperty() { + TestPropertyClass obj; + + // Test initial value + QCOMPARE(obj.get_writableProp(), QString("initial")); + + // Test signal spy + QSignalSpy spy(&obj, &TestPropertyClass::writablePropChanged); + + // Test setter + bool result = obj.set_writableProp("new value"); + QVERIFY(result); // Should return true when value changes + QCOMPARE(obj.get_writableProp(), QString("new value")); + QCOMPARE(spy.count(), 1); + + // Test setting same value (should not emit signal) + result = obj.set_writableProp("new value"); + QVERIFY(!result); // Should return false when value doesn't change + QCOMPARE(spy.count(), 1); // Signal count should remain the same +} + +void TestQtSuperMacros::testReadonlyProperty() { + TestPropertyClass obj; + + // Test initial value + QCOMPARE(obj.get_readonlyProp(), 42); + + // Test signal spy + QSignalSpy spy(&obj, &TestPropertyClass::readonlyPropChanged); + + // Test updater (readonly properties use update_ prefix) + obj.updateReadonly(100); + QCOMPARE(obj.get_readonlyProp(), 100); + QCOMPARE(spy.count(), 1); +} + +void TestQtSuperMacros::testConstantProperty() { + TestPropertyClass obj; + + // Test constant value + QCOMPARE(obj.get_constantProp(), true); + + // Constant properties don't have setters or signals + // They should be accessible via getter only +} + +QTEST_MAIN(TestQtSuperMacros) +// cppcheck-suppress missingInclude +#include "test_qtsupermacros.moc" \ No newline at end of file