diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 16af935..0000000 --- a/.appveyor.yml +++ /dev/null @@ -1,298 +0,0 @@ -version: '0.0.1.{build}' -branches: - only: - - master - - dev - - appveyor - -image: Visual Studio 2015 -clone_depth: 1 -environment: - global: - LatestLTSQtVersion: 5.9 - matrix: - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: x86 - use_mingw: "false" - use_gui: "true" - use_widgets: "true" - use_static: "true" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: x86 - use_mingw: "false" - use_gui: "true" - use_widgets: "true" - use_static: "false" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: x86 - use_mingw: "false" - use_gui: "true" - use_widgets: "false" - use_static: "true" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: x86 - use_mingw: "false" - use_gui: "true" - use_widgets: "false" - use_static: "false" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: x86 - use_mingw: "false" - use_gui: "false" - use_widgets: "false" - use_static: "true" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: x86 - use_mingw: "false" - use_gui: "false" - use_widgets: "false" - use_static: "false" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015_64 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: amd64 - use_mingw: "false" - use_gui: "true" - use_widgets: "true" - use_static: "true" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015_64 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: amd64 - use_mingw: "false" - use_gui: "true" - use_widgets: "true" - use_static: "false" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015_64 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: amd64 - use_mingw: "false" - use_gui: "true" - use_widgets: "false" - use_static: "true" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015_64 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: amd64 - use_mingw: "false" - use_gui: "true" - use_widgets: "false" - use_static: "false" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015_64 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: amd64 - use_mingw: "false" - use_gui: "false" - use_widgets: "false" - use_static: "true" - - QT5: C:\Qt\%LatestLTSQtVersion%\msvc2015_64 - COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC - platform: amd64 - use_mingw: "false" - use_gui: "false" - use_widgets: "false" - use_static: "false" - - QT5: C:\Qt\%LatestLTSQtVersion%\mingw53_32 - COMPILER: C:\Qt\Tools\mingw530_32 - platform: x86 - use_mingw: "true" - use_gui: "true" - use_widgets: "true" - use_static: "true" - - QT5: C:\Qt\%LatestLTSQtVersion%\mingw53_32 - COMPILER: C:\Qt\Tools\mingw530_32 - platform: x86 - use_mingw: "true" - use_gui: "true" - use_widgets: "true" - use_static: "false" - - QT5: C:\Qt\%LatestLTSQtVersion%\mingw53_32 - COMPILER: C:\Qt\Tools\mingw530_32 - platform: x86 - use_mingw: "true" - use_gui: "true" - use_widgets: "false" - use_static: "true" - - QT5: C:\Qt\%LatestLTSQtVersion%\mingw53_32 - COMPILER: C:\Qt\Tools\mingw530_32 - platform: x86 - use_mingw: "true" - use_gui: "true" - use_widgets: "false" - use_static: "false" - - QT5: C:\Qt\%LatestLTSQtVersion%\mingw53_32 - COMPILER: C:\Qt\Tools\mingw530_32 - platform: x86 - use_mingw: "true" - use_gui: "false" - use_widgets: "false" - use_static: "true" - - QT5: C:\Qt\%LatestLTSQtVersion%\mingw53_32 - COMPILER: C:\Qt\Tools\mingw530_32 - platform: x86 - use_mingw: "true" - use_gui: "false" - use_widgets: "false" - use_static: "false" - -matrix: - fast_finish: true - -before_build: -- set PATH=%COMPILER%\bin;%QT5%\bin;%PATH% -- if not %use_mingw%==true call "%COMPILER%\vcvarsall.bat" %platform% -- if %use_static%==true (set USESTATIC=ON) else (set USESTATIC=OFF) -- if %use_gui%==true (set USEGUI=OFF) else (set USEGUI=ON) -- if %use_widgets%==true (set USEWIDGETS=OFF) else (set USEWIDGETS=ON) -- if %use_mingw%==true (set CMAKEGENERATOR="MinGW Makefiles") else (set CMAKEGENERATOR="NMake Makefiles") -- if %use_mingw%==true set PATH=%PATH:C:\Program Files\Git\usr\bin;=% - -build_script: -- mkdir .\build -- cd .\build -- set PATH=%cd%\x86\lib;%cd%\x64\lib;%PATH% -- set PATH=%cd%\x86\bin;%cd%\x64\bin;%PATH% -- cmake --version -- cmake -G %CMAKEGENERATOR% -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_TESTING=ON -DTEST_OUTPUT_XML=ON -DBUILD_EXAMPLES=OFF -DCMAKE_DEBUG_POSTFIX=d -DBUILD_STATIC_LIBS=%USESTATIC% -DCMAKE_INSTALL_PREFIX="./installed" -DNO_WIDGETS=%USEWIDGETS% -DNO_GUI=%USEGUI% ../ -- cmake --build . -- cmake --build . --target install -- cmake --build . --target test & set preventFail = 1 -- cmake -G %CMAKEGENERATOR% -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_TESTING=ON -DTEST_OUTPUT_XML=ON -DBUILD_EXAMPLES=ON -DBUILD_STATIC_LIBS=%USESTATIC% -DCMAKE_INSTALL_PREFIX="./installed" -DNO_WIDGETS=%USEWIDGETS% -DNO_GUI=%USEGUI% ../ -- cmake --build . -- cmake --build . --target install -- cmake --build . --target test & set preventFail = 1 - -after_build: -- if %use_mingw%==true set PATH=C:\Program Files\Git\usr\bin;%PATH% -- if %use_mingw%==true (set archivename=mingw) else (set archivename=msvc2015) -- if %platform%==x86 (set archivename=%archivename%_x86) else (set archivename=%archivename%_x64) -- if %APPVEYOR_REPO_BRANCH%==master if %use_gui%==true if %use_widgets%==true if %use_static%==false cpack -G ZIP - -test_script: -- cd TestResults -- ps: '$xmlOut = new-object System.Xml.XmlDocument; - $xmlOut.AppendChild($xmlOut.CreateXmlDeclaration("1.0","UTF-8",$null)); - $rootXmlOut = $xmlOut.CreateElement("testsuites"); - $xmlOut.AppendChild($rootXmlOut); - $totalPass = 0; - $totalFail = 0; - $totalError = 0; - $totalSkip = 0; - $totalTime = 0; - $crashMessage = $null; - Get-ChildItem ".\" -Filter "*_tstres.xml" | Foreach-Object { - $XmlDocument = $null; - $localPass = 0; - $localFail = 0; - $localError = 0; - $localSkip = 0; - $localTime = 0; - $currentFilePath = $_.FullName; - $testSuiteName = $_.BaseName.subString(0,$_.BaseName.Length - 7); - if([bool]((Get-Content -Path $currentFilePath) -as [xml])){ - [xml]$XmlDocument = (Get-Content -Path $currentFilePath) -as [xml]; - } - else{ - $localError = 1; - $rawFilecontent = [IO.File]::ReadAllText($currentFilePath); - if([string]::IsNullOrEmpty($rawFilecontent)){ - $crashMessage = "Output file is empty: " + $currentFilePath; - } - else{ - $crashMessage = $rawFilecontent; - $rawFileMatch = [regex]::match($rawFilecontent,"(?s)(.+<\/TestCase>)(.*)"); - if($rawFileMatch.Success){ - if([bool](($rawFileMatch.captures.groups[1].value) -as [xml])){ - [xml]$XmlDocument = ($rawFileMatch.captures.groups[1].value) -as [xml]; - $crashMessage = $rawFileMatch.captures.groups[2].value; - } - } - } - } - $testSuiteXmlOut = $rootXmlOut.AppendChild($xmlOut.CreateElement("testsuite")); - if($XmlDocument -ne $null){ - $testClassName = $XmlDocument.TestCase.name; - $testSuiteXmlOut.SetAttribute("name",$testSuiteName); - $testSuitePropertiesXmlOut = $testSuiteXmlOut.AppendChild($xmlOut.CreateElement("properties")); - $testSuitePropertiesPropertyXmlOut = $testSuitePropertiesXmlOut.AppendChild($xmlOut.CreateElement("property")); - $testSuitePropertiesPropertyXmlOut.SetAttribute("name","QtVersion"); - $testSuitePropertiesPropertyXmlOut.SetAttribute("value",($XmlDocument.TestCase.Environment.QtVersion)); - $testSuitePropertiesPropertyXmlOut = $testSuitePropertiesXmlOut.AppendChild($xmlOut.CreateElement("property")); - $testSuitePropertiesPropertyXmlOut.SetAttribute("name","QtBuild"); - $testSuitePropertiesPropertyXmlOut.SetAttribute("value",($XmlDocument.TestCase.Environment.QtBuild)); - $testSuitePropertiesPropertyXmlOut = $testSuitePropertiesXmlOut.AppendChild($xmlOut.CreateElement("property")); - $testSuitePropertiesPropertyXmlOut.SetAttribute("name","QTestVersion"); - $testSuitePropertiesPropertyXmlOut.SetAttribute("value",($XmlDocument.TestCase.Environment.QTestVersion)); - foreach($testFunction in $XmlDocument.SelectNodes("//TestFunction")){ - $testFunctionName = $testFunction.name; - $countIncidents = $testFunction.ChildNodes.Count; - $testFunctionTime = [decimal]$testFunction.Duration.msecs; - $localTime = $localTime +$testFunctionTime; - foreach($incident in $testFunction.ChildNodes){ - if($incident.Name -ne "Incident" -and $incident.Name -ne "Message"){ - continue; - } - $incidentName = $testFunctionName; - if($incident.DataTag -ne $null){ - $incidentName = $incidentName + " - " + $incident.DataTag.InnerText; - } - $incidentName = ($incidentName); - $testSuitetestcaseXmlOut = $testSuiteXmlOut.AppendChild($xmlOut.CreateElement("testcase")); - $testSuitetestcaseXmlOut.SetAttribute("name",$incidentName); - $testSuitetestcaseXmlOut.SetAttribute("classname",$testClassName); - $testSuitetestcaseXmlOut.SetAttribute("time",$testFunctionTime/(1000*$countIncidents)); - if($incident.type -eq "skip"){ - ++$localSkip; - $testSuitetestcaseSkipXmlOut = $testSuitetestcaseXmlOut.AppendChild($xmlOut.CreateElement("skipped")); - $testSuitetestcaseSkipXmlOut.SetAttribute("message","file: " + ($incident.file + "`nline: " + $incident.line + "`n" + $incident.Description.InnerText)); - } - ElseIf ($incident.type -eq "fail"){ - ++$localFail; - $testSuitetestcaseSkipXmlOut = $testSuitetestcaseXmlOut.AppendChild($xmlOut.CreateElement("failure")); - $testSuitetestcaseSkipXmlOut.SetAttribute("message",("file: " + $incident.file + "`nline: " + $incident.line + "`n" + $incident.Description.InnerText)); - } - ElseIf ($incident.type -eq "qdebug" -or $incident.type -eq "qwarn" -or $incident.type -eq "system" -or $incident.type -eq "qfatal"){ - $testSuitetestcaseCerrXmlOut = $testSuitetestcaseXmlOut.AppendChild($xmlOut.CreateElement("system-err")); - $testSuitetestcaseCerrXmlOut.AppendChild($xmlOut.CreateTextNode(($incident.Description.InnerText))); - } - else{ - ++$localPass; - } - }; - }; - } - if($localError -eq 1){ - $testSuitetestcaseXmlOut = $testSuiteXmlOut.AppendChild($xmlOut.CreateElement("testcase")); - $testSuitetestcaseXmlOut.SetAttribute("name","SystemError"); - $testSuitetestcaseXmlOut.SetAttribute("classname",$testClassName); - $testSuitetestcaseErrorXmlOut = $testSuitetestcaseXmlOut.AppendChild($xmlOut.CreateElement("error")); - $testSuitetestcaseErrorXmlOut.SetAttribute("message",($crashMessage)); - } - $testSuiteXmlOut.SetAttribute("time",$localTime/1000); - $testSuiteXmlOut.SetAttribute("skipped",$localSkip); - $testSuiteXmlOut.SetAttribute("tests",$localSkip+$localFail+$localError+$localPass); - $testSuiteXmlOut.SetAttribute("failures",$localFail); - $testSuiteXmlOut.SetAttribute("errors",$localError); - $totalTime = $totalTime + $localTime; - $totalSkip = $totalSkip + $localSkip; - $totalError = $totalError + $localError; - $totalFail = $totalFail + $localFail; - $totalPass = $totalPass + $localPass; - }; - $rootXmlOut.SetAttribute("time",$totalTime/1000); - $rootXmlOut.SetAttribute("failures",$totalFail); - $rootXmlOut.SetAttribute("errors",$totalError); - $rootXmlOut.SetAttribute("tests",$totalPass); - $xmlOut.save($pwd.Path + "\testResults.xml"); - if($totalError+$totalFail -gt 0){ - throw; - }' - -artifacts: -- path: '.\build\*.zip' - -on_finish: -- ps: if (Test-Path ".\testResults.xml") {(new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\testResults.xml));} diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b8c6fae --- /dev/null +++ b/.clang-format @@ -0,0 +1,92 @@ +# Copyright (C) 2016 Olivier Goffart +# +# You may use this file under the terms of the 3-clause BSD license. +# See the file LICENSE from this package for details. + +# This is the clang-format configuration style to be used by Qt, +# based on the rules from https://wiki.qt.io/Qt_Coding_Style and +# https://wiki.qt.io/Coding_Conventions + +--- +# Webkit style was loosely based on the Qt style +BasedOnStyle: WebKit + +Standard: c++11 + +# Column width is limited to 100 in accordance with Qt Coding Style. +# https://wiki.qt.io/Qt_Coding_Style +# Note that this may be changed at some point in the future. +ColumnLimit: 150 +# How much weight do extra characters after the line length limit have. +# PenaltyExcessCharacter: 4 + +# Disable reflow of qdoc comments: indentation rules are different. +# Translation comments are also excluded. +CommentPragmas: "^!|^:" + +# We want a space between the type and the star for pointer types. +PointerBindsToType: false + +# We use template< without space. +SpaceAfterTemplateKeyword: false + +# We want to break before the operators, but not before a '='. +BreakBeforeBinaryOperators: NonAssignment + +# Braces are usually attached, but not after functions or class declarations. +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + +# When constructor initializers do not fit on one line, put them each on a new line. +ConstructorInitializerAllOnOneLineOrOnePerLine: false +# Indent initializers by 4 spaces +ConstructorInitializerIndentWidth: 4 + +# Indent width for line continuations. +ContinuationIndentWidth: 8 + +# No indentation for namespaces. +NamespaceIndentation: None + +# Allow indentation for preprocessing directives (if/ifdef/endif). https://reviews.llvm.org/rL312125 +IndentPPDirectives: AfterHash + +# Horizontally align arguments after an open bracket. +# The coding style does not specify the following, but this is what gives +# results closest to the existing code. +AlignAfterOpenBracket: true +AlwaysBreakTemplateDeclarations: true + +# Ideally we should also allow less short function in a single line, but +# clang-format does not handle that. +AllowShortFunctionsOnASingleLine: Inline + +# The coding style specifies some include order categories, but also tells to +# separate categories with an empty line. It does not specify the order within +# the categories. Since the SortInclude feature of clang-format does not +# re-order includes separated by empty lines, the feature is not used. +SortIncludes: false + +# macros for which the opening brace stays attached. +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ] + +# Break constructor initializers before the colon and after the commas. +BreakConstructorInitializers: BeforeComma + +Cpp11BracedListStyle: true +SpaceBeforeCpp11BracedList: false + diff --git a/.github/workflows/buildstandalone.yml b/.github/workflows/buildstandalone.yml new file mode 100644 index 0000000..cbcbb26 --- /dev/null +++ b/.github/workflows/buildstandalone.yml @@ -0,0 +1,101 @@ +name: Standalone Modules +on: + push: + branches: + - master + - dev + paths-ignore: + - '.travis.yml' + - 'docs/**' + - '**.markdown' + - '**.md' + - 'LICENSE' + pull_request: + branches: + - master + - dev + paths-ignore: + - '.travis.yml' + - 'docs/**' + - '**.markdown' + - '**.md' + - 'LICENSE' +jobs: + build: + name: Build and Test ${{ matrix.modules.friendly_name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + qt_ver: [5.15.2,6.0.2] + no_widgets: [OFF,ON] + no_gui: [OFF,ON] + many_roles: [OFF,ON] + modules: + - { friendly_name: RoleMaskProxyModel, rolemaskproxy: ON, insertproxy: OFF, modelserialisation: OFF} + - { friendly_name: InsertProxyModel, rolemaskproxy: OFF, insertproxy: ON, modelserialisation: OFF} + - { friendly_name: ModelSerialisation, rolemaskproxy: OFF, insertproxy: OFF, modelserialisation: ON} + exclude: + - no_gui: ON + no_widgets: OFF + steps: + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: ${{ matrix.qt_ver }} + - name: Git Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Create Build Directory + shell: pwsh + run: | + mkdir build + cd build + mkdir debug + mkdir release + - name: Install Linux Dependencies + run: | + sudo apt-get update -y + sudo apt-get install libxcb-icccm4 libxcb-xkb1 libxcb-icccm4 libxcb-image0 libxcb-render-util0 libxcb-randr0 libxcb-keysyms1 libxcb-xinerama0 libxcb-xinput-dev + export PATH=$PATH:$PWD/build/installed/lib + export PATH=$PATH:$PWD/build/installed/bin + - name: ${{ matrix.modules.friendly_name }} Qt${{ matrix.qt_ver }} No Gui:${{ matrix.no_gui }} No Widgets:${{ matrix.no_widgets }} Many Roles:${{ matrix.many_roles }} + shell: pwsh + run: | + cd build/debug + cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_DEBUG_POSTFIX=d -DBUILD_TESTING=ON -DTEST_OUTPUT_XML=ON -DBUILD_EXAMPLES=ON -DBUILD_ROLEMASKPROXY=${{ matrix.modules.rolemaskproxy }} -DBUILD_INSERTPROXY=${{ matrix.modules.insertproxy }} -DBUILD_MODELSERIALISATION=${{ matrix.modules.modelserialisation }} -DBUILD_STATIC_LIBS=OFF -DCMAKE_INSTALL_PREFIX="../installed" -DNO_WIDGETS=${{ matrix.no_widgets }} -DNO_GUI=${{ matrix.no_gui }} -DOPTIMISE_FOR_MANY_ROLES=${{ matrix.many_roles }} ../../ + cmake --build . + cmake --build . --target install + cd ../release + cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_TESTING=ON -DTEST_OUTPUT_XML=ON -DBUILD_EXAMPLES=ON -DBUILD_ROLEMASKPROXY=${{ matrix.modules.rolemaskproxy }} -DBUILD_INSERTPROXY=${{ matrix.modules.insertproxy }} -DBUILD_MODELSERIALISATION=${{ matrix.modules.modelserialisation }} -DBUILD_STATIC_LIBS=OFF -DCMAKE_INSTALL_PREFIX="../installed" -DNO_WIDGETS=${{ matrix.no_widgets }} -DNO_GUI=${{ matrix.no_gui }} -DOPTIMISE_FOR_MANY_ROLES=${{ matrix.many_roles }} ../../ + cmake --build . + cmake --build . --target install + - name: Debug Test + id: rundebugtests + continue-on-error: true + uses: GabrielBB/xvfb-action@v1 + with: + working-directory: ./build/debug + run: cmake --build . --target test + - name: Release Test + id: runreleasetests + uses: GabrielBB/xvfb-action@v1 + with: + working-directory: ./build/release + run: cmake --build . --target test + - name: Prepare Test Report + if: ${{ always() && (steps.rundebugtests.outcome == 'failure' || steps.runreleasetests.outcome == 'failure') }} + uses: ./ci/processtestresults + with: + qt-tests: build/TestResults + junit-output: build/TestResults/junitresult.xml + html-output: build/TestResults/testsreport.html + - name: Publish Test Report + if: ${{ always() && (steps.rundebugtests.outcome == 'failure' || steps.runreleasetests.outcome == 'failure') }} + uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.modules.friendly_name }}-Qt${{ matrix.qt_ver }}-NoGui${{ matrix.no_gui }}-NoWidgets${{ matrix.no_widgets }}-ManyRoles${{ matrix.many_roles }} + path: | + build/TestResults/junitresult.xml + build/TestResults/testsreport.html \ No newline at end of file diff --git a/.github/workflows/buildtest.yml b/.github/workflows/buildtest.yml new file mode 100644 index 0000000..aa0de50 --- /dev/null +++ b/.github/workflows/buildtest.yml @@ -0,0 +1,128 @@ +name: Main Platforms +on: + push: + branches: + - master + - dev + paths-ignore: + - '.travis.yml' + - 'docs/**' + - '**.markdown' + - '**.md' + - 'LICENSE' + pull_request: + branches: + - master + - dev + paths-ignore: + - '.travis.yml' + - 'docs/**' + - '**.markdown' + - '**.md' + - 'LICENSE' +jobs: + build: + name: Build and Test ${{ matrix.platforms.friendly_name }} + runs-on: ${{ matrix.platforms.os }} + strategy: + fail-fast: false + matrix: + qt_ver: [5.15.2,6.0.2] + static_lib: [OFF,ON] + no_widgets: [OFF,ON] + no_gui: [OFF,ON] + many_roles: [OFF,ON] + platforms: + - { os: windows-latest, generator: "NMake Makefiles", friendly_name: MSVC } + - { os: windows-latest, generator: "MinGW Makefiles", friendly_name: MinGW } + - { os: ubuntu-latest, generator: "Unix Makefiles", friendly_name: Ubuntu } + - { os: macos-latest, generator: "Unix Makefiles", friendly_name: MacOS } + exclude: + - no_gui: ON + no_widgets: OFF + steps: + - name: Install Qt + if: ${{ matrix.platforms.friendly_name != 'MinGW' }} + uses: jurplel/install-qt-action@v2 + with: + version: ${{ matrix.qt_ver }} + - name: Install Qt MinGW + if: ${{ matrix.platforms.friendly_name == 'MinGW' }} + uses: jurplel/install-qt-action@v2 + with: + version: ${{ matrix.qt_ver }} + arch: win64_mingw81 + - name: Git Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Create Build Directory + shell: pwsh + run: | + mkdir build + cd build + mkdir debug + mkdir release + - name: Set up MSVC environment + if: ${{ matrix.platforms.friendly_name == 'MSVC' }} + uses: ilammy/msvc-dev-cmd@v1 + - name: Install Linux Dependencies + if: ${{ runner.os == 'Linux' }} + run: | + sudo apt-get update -y + sudo apt-get install libxcb-icccm4 libxcb-xkb1 libxcb-icccm4 libxcb-image0 libxcb-render-util0 libxcb-randr0 libxcb-keysyms1 libxcb-xinerama0 libxcb-xinput-dev + export PATH=$PATH:$PWD/build/installed/lib + export PATH=$PATH:$PWD/build/installed/bin + - name: ${{ matrix.platforms.friendly_name }} Qt${{ matrix.qt_ver }} Static:${{ matrix.static_lib }} No Gui:${{ matrix.no_gui }} No Widgets:${{ matrix.no_widgets }} Many Roles:${{ matrix.many_roles }} + shell: pwsh + run: | + cd build/debug + cmake -G"${{ matrix.platforms.generator }}" -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_DEBUG_POSTFIX=d -DBUILD_TESTING=ON -DTEST_OUTPUT_XML=ON -DBUILD_EXAMPLES=ON -DBUILD_STATIC_LIBS=${{ matrix.static_lib }} -DCMAKE_INSTALL_PREFIX="../installed" -DNO_WIDGETS=${{ matrix.no_widgets }} -DNO_GUI=${{ matrix.no_gui }} -DOPTIMISE_FOR_MANY_ROLES=${{ matrix.many_roles }} ../../ + cmake --build . + cmake --build . --target install + cd ../release + cmake -G"${{ matrix.platforms.generator }}" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_TESTING=ON -DTEST_OUTPUT_XML=ON -DBUILD_EXAMPLES=ON -DBUILD_STATIC_LIBS=${{ matrix.static_lib }} -DCMAKE_INSTALL_PREFIX="../installed" -DNO_WIDGETS=${{ matrix.no_widgets }} -DNO_GUI=${{ matrix.no_gui }} -DOPTIMISE_FOR_MANY_ROLES=${{ matrix.many_roles }} ../../ + cmake --build . + cmake --build . --target install + - name: Linux Debug Test + if: ${{ runner.os == 'Linux' }} + id: runlinuxdebugtests + continue-on-error: true + uses: GabrielBB/xvfb-action@v1 + with: + working-directory: ./build/debug + run: cmake --build . --target test + - name: Linux Release Test + if: ${{ runner.os == 'Linux' }} + id: runlinuxreleasetests + uses: GabrielBB/xvfb-action@v1 + with: + working-directory: ./build/release + run: cmake --build . --target test + - name: Test + if: ${{ runner.os != 'Linux' }} + id: runtests + shell: pwsh + run: | + cd build + $OldPath = [Environment]::GetEnvironmentVariable("PATH") + [Environment]::SetEnvironmentVariable("PATH","$pwd/installed/lib;$pwd/installed/bin;$OldPath") + cd debug + cmake --build . --target test + cd ../release + cmake --build . --target test + - name: Prepare Test Report + if: ${{ always() && (steps.runtests.outcome == 'failure' || steps.runlinuxdebugtests.outcome == 'failure' || steps.runlinuxreleasetests.outcome == 'failure') }} + uses: ./ci/processtestresults + with: + qt-tests: build/TestResults + junit-output: build/TestResults/junitresult.xml + html-output: build/TestResults/testsreport.html + - name: Publish Test Report + if: ${{ always() && (steps.runtests.outcome == 'failure' || steps.runlinuxdebugtests.outcome == 'failure' || steps.runlinuxreleasetests.outcome == 'failure') }} + uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.platforms.friendly_name }}-Qt${{ matrix.qt_ver }}-Static${{ matrix.static_lib }}-NoGui${{ matrix.no_gui }}-NoWidgets${{ matrix.no_widgets }}-ManyRoles${{ matrix.many_roles }} + path: | + build/TestResults/junitresult.xml + build/TestResults/testsreport.html \ No newline at end of file diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml new file mode 100644 index 0000000..04c9d56 --- /dev/null +++ b/.github/workflows/clang-format-check.yml @@ -0,0 +1,38 @@ +name: clang-format Check +on: + push: + branches: + - master + - dev + paths-ignore: + - '.travis.yml' + - '.appveyor.yml' + - 'modelutilities.pri' + - 'docs/**' + - '**.markdown' + - '**.md' + - 'LICENSE' + - '**CMakeLists.txt' + - 'cmake/**' + pull_request: + branches: + - master + - dev + paths-ignore: + - '.travis.yml' + - '.appveyor.yml' + - 'modelutilities.pri' + - 'docs/**' + - '**.markdown' + - '**.md' + - 'LICENSE' + - '**CMakeLists.txt' + - 'cmake/**' +jobs: + formatting-check: + name: Formatting Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Run clang-format style check for C/C++ programs. + uses: jidicula/clang-format-action@v3.2.0 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6b5d9e3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,79 @@ +name: Releases +on: + push: + tags: + - "*" +jobs: + release: + name: ${{ matrix.platforms.friendly_name }} Qt ${{ matrix.qt_ver }} Release + runs-on: ${{ matrix.platforms.os }} + strategy: + max-parallel: 1 + matrix: + qt_ver: [5.15.2,6.0.2] + platforms: + - { os: windows-latest, generator: "NMake Makefiles", debug_postfix: d, compression: ZIP, friendly_name: MSVC } + - { os: windows-latest, generator: "MinGW Makefiles", debug_postfix: d, compression: ZIP, friendly_name: MinGW } + - { os: ubuntu-latest, generator: "Unix Makefiles", debug_postfix: d, compression: TGZ, friendly_name: Ubuntu } + - { os: macos-latest, generator: "Unix Makefiles", debug_postfix: _debug, compression: ZIP, friendly_name: MacOS } + steps: + - name: Install Qt + if: ${{ matrix.platforms.friendly_name != 'MinGW' }} + uses: jurplel/install-qt-action@v2 + with: + version: ${{ matrix.qt_ver }} + - name: Install Qt MinGW + if: ${{ matrix.platforms.friendly_name == 'MinGW' }} + uses: jurplel/install-qt-action@v2 + with: + version: ${{ matrix.qt_ver }} + arch: win64_mingw81 + - name: Git Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Create Build Directory + run: | + mkdir build + cd build + mkdir shared-Debug + mkdir shared-Release + - name: Set up MSVC environment + if: ${{ matrix.platforms.friendly_name == 'MSVC' }} + uses: ilammy/msvc-dev-cmd@v1 + - name: Install Linux Dependencies + if: ${{ runner.os == 'Linux' }} + run: | + sudo apt-get update -y + sudo apt-get install libxcb-icccm4 libxcb-xkb1 libxcb-icccm4 libxcb-image0 libxcb-render-util0 libxcb-randr0 libxcb-keysyms1 libxcb-xinerama0 libxcb-xinput-dev + - name: Build ${{ matrix.platforms.friendly_name }} Qt ${{ matrix.qt_ver }} + run: | + cd build/shared-Debug + cmake -G"${{ matrix.platforms.generator }}" -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_TESTING=OFF -DBUILD_EXAMPLES=OFF -DCMAKE_DEBUG_POSTFIX=${{ matrix.platforms.debug_postfix }} -DCMAKE_INSTALL_PREFIX="../installed" ../../ + cmake --build . + cd ../shared-Release + cmake -G"${{ matrix.platforms.generator }}" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_TESTING=OFF -DBUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX="../installed" ../../ + cmake --build . + - name: Create Package + run: | + cd build + cpack -G ${{ matrix.platforms.compression }} --config ../cmake/modules/package.cmake + - name: Publish Release + uses: ncipollo/release-action@v1 + with: + artifacts: "build/QtModelUtilities*.*" + token: ${{ secrets.GITHUB_TOKEN }} + allowUpdates: true + artifactErrorsFailBuild: true + draft: true + removedraft: + name: Remove Draft from Release + runs-on: ubuntu-latest + needs: release + steps: + - name: Publish Release + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + allowUpdates: true + draft: false \ No newline at end of file diff --git a/.github/workflows/ubuntuLTS.yml b/.github/workflows/ubuntuLTS.yml new file mode 100644 index 0000000..7765155 --- /dev/null +++ b/.github/workflows/ubuntuLTS.yml @@ -0,0 +1,92 @@ +name: Old LTS Versions +on: + push: + branches: + - master + - dev + paths-ignore: + - '.travis.yml' + - 'docs/**' + - '**.markdown' + - '**.md' + - 'LICENSE' + pull_request: + branches: + - master + - dev + paths-ignore: + - '.travis.yml' + - 'docs/**' + - '**.markdown' + - '**.md' + - 'LICENSE' +jobs: + build512: + name: Qt Old LTS Builds + runs-on: ${{ matrix.platforms.os }} + strategy: + fail-fast: false + matrix: + platforms: + - { os: ubuntu-latest, qt_ver: 5.12.10} + - { os: ubuntu-16.04, qt_ver: 5.9.9 } + steps: + - name: Install Dependencies + run: | + sudo apt-get update -y + sudo apt-get install libxcb-icccm4 libxcb-xkb1 libxcb-icccm4 libxcb-image0 libxcb-render-util0 libxcb-randr0 libxcb-keysyms1 libxcb-xinerama0 + export PATH=$PATH:$PWD/build/installed/lib + export PATH=$PATH:$PWD/build/installed/bin + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: ${{ matrix.platforms.qt_ver }} + - name: Git Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Create Build Directory + run: | + mkdir build + cd build + mkdir debug + mkdir release + - name: Debug Build Qt ${{ matrix.qt_ver }} + run: | + cd build/debug + cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_DEBUG_POSTFIX=d -DBUILD_TESTING=ON -DTEST_OUTPUT_XML=ON -DBUILD_EXAMPLES=ON -DBUILD_STATIC_LIBS=OFF -DCMAKE_INSTALL_PREFIX="../installed" ../../ + cmake --build . + cmake --build . --target install + cd ../release + cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_TESTING=ON -DTEST_OUTPUT_XML=ON -DBUILD_EXAMPLES=ON -DBUILD_STATIC_LIBS=OFF -DCMAKE_INSTALL_PREFIX="../installed" ../../ + cmake --build . + cmake --build . --target install + - name: Test Debug + id: rundebugtests + continue-on-error: true + uses: GabrielBB/xvfb-action@v1 + with: + working-directory: ./build/debug + run: cmake --build . --target test + - name: Test Release + id: runreleasetests + uses: GabrielBB/xvfb-action@v1 + with: + working-directory: ./build/release + run: cmake --build . --target test + - name: Prepare Test Report + if: ${{ always() && (steps.rundebugtests.outcome == 'failure' || steps.runreleasetests.outcome == 'failure') }} + uses: ./ci/processtestresults + with: + qt-tests: build/TestResults + junit-output: build/TestResults/junitresult.xml + html-output: build/TestResults/testsreport.html + - name: Publish Test Report + if: ${{ always() && (steps.rundebugtests.outcome == 'failure' || steps.runreleasetests.outcome == 'failure') }} + uses: actions/upload-artifact@v2 + with: + name: Qt${{ matrix.platforms.qt_ver }} + path: | + build/TestResults/junitresult.xml + build/TestResults/testsreport.html + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 78217da..9ed3edd 100644 --- a/.gitignore +++ b/.gitignore @@ -433,3 +433,11 @@ target_wrapper.* # QtCtreator CMake CMakeLists.txt.user* /docs/htmldocs.html + +# Auto License Header files +*.licenseheader + +# Model Test From Qt +modeltest.cpp +modeltest.h +/RunClangFormat.bat diff --git a/.travis.yml b/.travis.yml index f36b867..4f0d7d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,164 +1,50 @@ language: cpp -git: - depth: 1 - branches: only: - master - dev - travis -matrix: - fast_finish: true - include: - - os: linux - dist: trusty - sudo: required - compiler: gcc - env: - - USESTATIC=ON NOWIDGETS=OFF NOGUI=OFF QTVERSIONMINOR=59 QTVERSIONPATCH=594 OPTIMISE_FOR_MANY_ROLES=OFF - - os: linux - dist: trusty - sudo: required - compiler: gcc - env: - - USESTATIC=OFF NOWIDGETS=OFF NOGUI=OFF QTVERSIONMINOR=59 QTVERSIONPATCH=594 OPTIMISE_FOR_MANY_ROLES=OFF - - os: linux - dist: trusty - sudo: required - compiler: gcc - env: - - USESTATIC=ON NOWIDGETS=ON NOGUI=OFF QTVERSIONMINOR=59 QTVERSIONPATCH=594 OPTIMISE_FOR_MANY_ROLES=OFF - - os: linux - dist: trusty - sudo: required - compiler: gcc - env: - - USESTATIC=OFF NOWIDGETS=ON NOGUI=OFF QTVERSIONMINOR=59 QTVERSIONPATCH=594 OPTIMISE_FOR_MANY_ROLES=OFF - - os: linux - dist: trusty - sudo: required - compiler: gcc - env: - - USESTATIC=ON NOWIDGETS=ON NOGUI=ON QTVERSIONMINOR=59 QTVERSIONPATCH=594 OPTIMISE_FOR_MANY_ROLES=OFF - - os: linux - dist: trusty - sudo: required - compiler: gcc - env: - - USESTATIC=OFF NOWIDGETS=ON NOGUI=ON QTVERSIONMINOR=59 QTVERSIONPATCH=594 OPTIMISE_FOR_MANY_ROLES=OFF - - os: osx - compiler: clang - env: - - USESTATIC=ON NOWIDGETS=OFF NOGUI=OFF OPTIMISE_FOR_MANY_ROLES=OFF - - os: osx - compiler: clang - env: - - USESTATIC=OFF NOWIDGETS=OFF NOGUI=OFF OPTIMISE_FOR_MANY_ROLES=OFF - - os: osx - compiler: clang - env: - - USESTATIC=ON NOWIDGETS=ON NOGUI=OFF OPTIMISE_FOR_MANY_ROLES=OFF - - os: osx - compiler: clang - env: - - USESTATIC=OFF NOWIDGETS=ON NOGUI=OFF OPTIMISE_FOR_MANY_ROLES=OFF - - os: osx - compiler: clang - env: - - USESTATIC=ON NOWIDGETS=ON NOGUI=ON OPTIMISE_FOR_MANY_ROLES=OFF - - os: osx - compiler: clang - env: - - USESTATIC=OFF NOWIDGETS=ON NOGUI=ON OPTIMISE_FOR_MANY_ROLES=OFF - - os: linux - dist: trusty - sudo: required - compiler: gcc - env: - - USESTATIC=ON NOWIDGETS=OFF NOGUI=OFF QTVERSIONMINOR=51 QTVERSIONPATCH=511 OPTIMISE_FOR_MANY_ROLES=OFF - - os: linux - dist: trusty - sudo: required - compiler: gcc - env: - - USESTATIC=ON NOWIDGETS=OFF NOGUI=OFF QTVERSIONMINOR=51 QTVERSIONPATCH=511 OPTIMISE_FOR_MANY_ROLES=ON - - os: linux - dist: trusty - sudo: required - compiler: gcc - env: - - USESTATIC=ON NOWIDGETS=OFF NOGUI=OFF QTVERSIONMINOR=56 QTVERSIONPATCH=562 OPTIMISE_FOR_MANY_ROLES=OFF - -install: - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then - lsb_release -a - && sudo apt-add-repository -y ppa:ubuntu-toolchain-r/test - && sudo apt-add-repository -y ppa:beineri/opt-qt${QTVERSIONPATCH}-trusty - && sudo apt-get -qq update - && sudo apt-get -qq install g++-6 libc6-i386 qt${QTVERSIONMINOR}base qt${QTVERSIONMINOR}tools - && export CXX="g++-6" - && export CC="gcc-6" - ; - else - brew update > /dev/null - && brew install qt5 - && chmod -R 755 /usr/local/opt/qt5/* - ; - fi - -before_script: -- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then - export DISPLAY=:99.0 - && sh -e /etc/init.d/xvfb start - && sleep 3 - ; - fi - -script: -- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then - QTDIR="/opt/qt${QTVERSIONMINOR}" - && PATH="$QTDIR/bin:$PATH" - && qt${QTVERSIONMINOR}-env.sh - ; - else - QTDIR="/usr/local/opt/qt5" - && PATH="$QTDIR/bin:$PATH" - && PATH="$QTDIR/lib:$PATH" - && LDFLAGS=-L$QTDIR/lib - && CPPFLAGS=-I$QTDIR/include - ; - fi -- mkdir ./build -- cd ./build -- export PATH=$PATH:$PWD/x64/lib -- export PATH=$PATH:$PWD/x64/bin -- cmake --version -- cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_TESTING=ON -DBUILD_EXAMPLES=ON -DBUILD_STATIC_LIBS=$USESTATIC -DCMAKE_INSTALL_PREFIX="./installed" -DNO_WIDGETS=$NOWIDGETS -DNO_GUI=$NOGUI -DOPTIMISE_FOR_MANY_ROLES=$OPTIMISE_FOR_MANY_ROLES ../ -- cmake --build . -- cmake --build . --target install -- ctest --verbose -- cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_TESTING=ON -DBUILD_EXAMPLES=OFF -DCMAKE_DEBUG_POSTFIX=d -DBUILD_STATIC_LIBS=$USESTATIC -DCMAKE_INSTALL_PREFIX="./installed" -DNO_WIDGETS=$NOWIDGETS -DNO_GUI=$NOGUI -DOPTIMISE_FOR_MANY_ROLES=$OPTIMISE_FOR_MANY_ROLES ../ -- cmake --build . -- cmake --build . --target install -- ctest --verbose - -before_deploy: -- cpack -G TGZ - -deploy: - skip_cleanup: true - api_key: "${GitHubToken}" - provider: releases - file_glob: true - file: "build/*.tar.gz" - on: - tags: true - branch: master - condition: $USESTATIC=OFF - condition: $NOWIDGETS=OFF - condition: $NOGUI=OFF - -notifications: - email: false +jobs: + fast_finish: true + include: + - name: Ubuntu Qt Version 5.1 + os: linux + dist: trusty + group: stable + compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - sourceline: 'ppa:beineri/opt-qt511-trusty' + update: true + packages: + - qt51base + - qt51tools + - gcc-6 + - g++-6 + - libc6-i386 + before_script: + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + - sleep 3 # give xvfb some time to start + script: + - PATH="/opt/qt51/bin:$PATH" + - CXX="g++-6" + - CC="gcc-6" + - qt51-env.sh + - mkdir ./build + - cd ./build + - export PATH=$PATH:$PWD/x64/lib + - export PATH=$PATH:$PWD/x64/bin + - cmake --version + - cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_TESTING=ON -DBUILD_EXAMPLES=ON -DBUILD_STATIC_LIBS=OFF -DCMAKE_INSTALL_PREFIX="./installed" ../ + - cmake --build . + - cmake --build . --target install + - ctest --verbose + - cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_TESTING=ON -DBUILD_EXAMPLES=ON -DBUILD_STATIC_LIBS=OFF -DCMAKE_INSTALL_PREFIX="./installed" ../ + - cmake --build . + - cmake --build . --target install + - ctest --verbose diff --git a/CMakeLists.txt b/CMakeLists.txt index ee1db12..c96f6e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,115 +1,32 @@ -cmake_minimum_required(VERSION 3.3) -set(modelutilities_VERSION_MAJOR 0) -set(modelutilities_VERSION_MINOR 0) -set(modelutilities_VERSION_PATCH 1) -set(modelutilities_VERSION "${modelutilities_VERSION_MAJOR}.${modelutilities_VERSION_MINOR}.${modelutilities_VERSION_PATCH}") -if(POLICY CMP0025) - cmake_policy(SET CMP0025 NEW) -endif() -if(POLICY CMP0048) - cmake_policy(SET CMP0048 NEW) -endif() -if(POLICY CMP0057) - cmake_policy(SET CMP0057 NEW) -endif() -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_AUTOMOC ON) -project(ModelUtilitiesLib VERSION ${modelutilities_VERSION}) -include(CTest) -set(REQUIRED_QT_VERSION 5.0.0) +cmake_minimum_required(VERSION 3.2) +project(QtModelUtilities LANGUAGES CXX) +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) + option(BUILD_STATIC_LIBS "Build the static library" OFF) option(BUILD_EXAMPLES "Build the examples" OFF) option(NO_GUI "Disable all features requiring QtGui and QtWidgets" OFF) option(NO_WIDGETS "Disable all features requiring QtWidgets" OFF) -option(BUILD_DOCS "Enables or disables the build of the documentation" ON) +option(BUILD_DOCS "Enables or disables the build of the documentation" OFF) option(BUILD_ROLEMASKPROXY "Enables or disables the build of Role Mask Proxy Model" ON) option(BUILD_MODELSERIALISATION "Enables or disables the build of Model Serialisation" ON) option(BUILD_INSERTPROXY "Enables or disables the build of Insert Proxy Model" ON) option(OPTIMISE_FOR_MANY_ROLES "Set this property to ON if you plan to store more than 20 different roles in the models to optimise performance" OFF) -find_package(Qt5Core ${REQUIRED_QT_VERSION} REQUIRED) -find_package(Qt5Gui ${REQUIRED_QT_VERSION}) -find_package(Qt5Widgets ${REQUIRED_QT_VERSION}) -find_package(Doxygen OPTIONAL_COMPONENTS mscgen dia dot) -message(STATUS "Found Qt Core ${Qt5Core_VERSION}") -set(modelutilities_INCLUDE ${modelutilities_INCLUDE} ${Qt5Core_INCLUDE_DIRS}) -set(modelutilities_INCLUDE ${modelutilities_INCLUDE} "${CMAKE_CURRENT_SOURCE_DIR}/src") -set(modelutilities_INCLUDE ${modelutilities_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}) -set(modelutilities_LIBS ${modelutilities_LIBS} ${Qt5Core_LIBRARIES}) -set(modelutilities_COMPILE_DEFINE ${modelutilities_COMPILE_DEFINE} ${Qt5Core_COMPILE_DEFINITIONS}) -if(OPTIMISE_FOR_MANY_ROLES) - set(modelutilities_COMPILE_DEFINE ${modelutilities_COMPILE_DEFINE} OPTIMISE_FOR_MANY_ROLES) -endif() -if(NOT Qt5Gui_FOUND) - set(NO_GUI ON) -endif() -if(NOT Qt5Widgets_FOUND) - set(NO_WIDGETS ON) -endif() -if(NOT NO_GUI) - message(STATUS "Found Qt Gui ${Qt5Gui_VERSION}") - set(modelutilities_LIBS ${modelutilities_LIBS} ${Qt5Gui_LIBRARIES}) - set(modelutilities_INCLUDE ${modelutilities_INCLUDE} ${Qt5Gui_INCLUDE_DIRS}) - set(modelutilities_COMPILE_DEFINE ${modelutilities_COMPILE_DEFINE} ${Qt5Gui_COMPILE_DEFINITIONS}) -endif() -if(NOT NO_WIDGETS) - message(STATUS "Found Qt Widgets ${Qt5Widgets_VERSION}") - set(modelutilities_LIBS ${modelutilities_LIBS} ${Qt5Widgets_LIBRARIES}) - set(modelutilities_INCLUDE ${modelutilities_INCLUDE} ${Qt5Widgets_INCLUDE_DIRS}) - set(modelutilities_COMPILE_DEFINE ${modelutilities_COMPILE_DEFINE} ${Qt5Widgets_COMPILE_DEFINITIONS}) -endif() -if(DOXYGEN_FOUND) - message(STATUS "Found Doxygen ${DOXYGEN_VERSION}") -else() - set(BUILD_DOCS OFF) -endif() -if(CMAKE_BUILD_TYPE MATCHES DEBUG) - set(BUILD_DOCS OFF) -endif() -if(BUILD_STATIC_LIBS) - set(CMAKE_STATIC_LIBRARY_SUFFIX "_static${CMAKE_STATIC_LIBRARY_SUFFIX}") -endif() -set(modelutilities_COMPILE_DEFINE ${modelutilities_COMPILE_DEFINE} MODELUTILITIES_LIB) -if(BUILD_ROLEMASKPROXY) - set(modelutilities_SRCS src/rolemaskproxymodel.cpp ${modelutilities_SRCS}) - set(modelutilities_INSTALL_INCLUDE src/rolemaskproxymodel.h src/includes/RoleMaskProxyModel ${modelutilities_INSTALL_INCLUDE}) -endif() -if(BUILD_INSERTPROXY) - set(modelutilities_SRCS src/insertproxymodel.cpp ${modelutilities_SRCS}) - set(modelutilities_INSTALL_INCLUDE src/insertproxymodel.h src/includes/InsertProxyModel ${modelutilities_INSTALL_INCLUDE}) -endif() -if(BUILD_MODELSERIALISATION) - set(modelutilities_SRCS - src/abstractmodelserialiser.cpp - src/abstractmultiroleserialiser.cpp - src/abstractsingleroleserialiser.cpp - src/binarymodelserialiser.cpp - src/csvmodelserialiser.cpp - src/htmlmodelserialiser.cpp - src/jsonmodelserialiser.cpp - src/xmlmodelserialiser.cpp - ${modelutilities_SRCS} - ) - set(modelutilities_INSTALL_INCLUDE - src/abstractmodelserialiser.h - src/abstractmultiroleserialiser.h - src/abstractsingleroleserialiser.h - src/binarymodelserialiser.h - src/csvmodelserialiser.h - src/htmlmodelserialiser.h - src/jsonmodelserialiser.h - src/xmlmodelserialiser.h - src/includes/BinaryModelSerialiser - src/includes/CsvModelSerialiser - src/includes/HtmlModelSerialiser - src/includes/JsonModelSerialiser - src/includes/ModelSerialisers - src/includes/XmlModelSerialiser - ${modelutilities_INSTALL_INCLUDE} - ) -endif() +include(CTest) +include(GetGitRevisionDescription) +git_describe(GitTagVersion --tags) +string(REGEX REPLACE "^([0-9]+)\\..*" "\\1" VERSION_MAJOR "${GitTagVersion}") +string(REGEX REPLACE "^[0-9]+\\.([0-9]+).*" "\\1" VERSION_MINOR "${GitTagVersion}") +string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" VERSION_PATCH "${GitTagVersion}") +if("${VERSION_MAJOR}" STREQUAL "-128-NOTFOUND") + set(VERSION_MAJOR 0) + set(VERSION_MINOR 0) + set(VERSION_PATCH 0) +endif() +set(VERSION_SHORT "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") +message("QtModelUtilities Version ${VERSION_SHORT}") + if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") set(modelutilities_IS64BITS OFF) else() @@ -120,49 +37,12 @@ if(modelutilities_IS64BITS) else() set(modelutilities_PlatformDir "x86") endif() -if(BUILD_STATIC_LIBS) - add_library(modelutilities STATIC ${modelutilities_SRCS}) - set(modelutilities_COMPILE_DEFINE ${modelutilities_COMPILE_DEFINE} MODELUTILITIES_STATIC) -else() - add_library(modelutilities SHARED ${modelutilities_SRCS}) -endif() -install(FILES ${modelutilities_INSTALL_INCLUDE} - DESTINATION include - COMPONENT headers -) -install(TARGETS modelutilities - EXPORT modelutilitiesBinary - RUNTIME DESTINATION bin COMPONENT library - LIBRARY DESTINATION lib COMPONENT library - ARCHIVE DESTINATION lib COMPONENT library -) -install(FILES modelutilities-config.cmake DESTINATION cmake) -install(EXPORT modelutilitiesBinary DESTINATION cmake) -target_include_directories(modelutilities PUBLIC - $ - $ - ) -target_link_libraries(modelutilities PUBLIC ${modelutilities_LIBS}) -target_compile_definitions(modelutilities PRIVATE ${modelutilities_COMPILE_DEFINE}) -set_target_properties(modelutilities PROPERTIES - VERSION ${modelutilities_VERSION} - EXPORT_NAME "ModelUtilities" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin" -) -if(BUILD_DOCS) - set(htmlDocRedirect "

Redirect

") - file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/docs/htmldocs.html" ${htmlDocRedirect}) - set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/DoxygenConfig.doxyfile) - add_custom_target( doc_doxygen ALL - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_IN} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT "Generating API documentation with Doxygen" - VERBATIM ) - install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs/html DESTINATION doc COMPONENT documentation) - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/docs/htmldocs.html DESTINATION doc COMPONENT documentation) + +if(CMAKE_BUILD_TYPE MATCHES DEBUG) + set(BUILD_DOCS OFF) endif() +add_subdirectory(src) + if(BUILD_EXAMPLES) add_subdirectory(examples) endif() @@ -170,15 +50,16 @@ if(BUILD_TESTING) enable_testing() option(TEST_OUTPUT_XML "Redirects the test output to xml files in the TestResults folder" OFF) if(TEST_OUTPUT_XML) - file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/TestResults") + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/../TestResults") endif() add_subdirectory(tests) endif() -SET(CPACK_PACKAGE_VERSION_MAJOR modelutilities_VERSION_MAJOR) -SET(CPACK_PACKAGE_VERSION_MINOR modelutilities_VERSION_MINOR) -SET(CPACK_PACKAGE_VERSION_PATCH modelutilities_VERSION_PATCH) -SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.markdown") -SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") -SET(CPACK_PACKAGE_FILE_NAME "modelutilities-${modelutilities_VERSION}-${CMAKE_SYSTEM_NAME}-${modelutilities_PlatformDir}") +install(FILES "${CMAKE_SOURCE_DIR}/LICENSE" DESTINATION licenses ) +SET(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR}) +SET(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR}) +SET(CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH}) +SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.markdown") +SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") +SET(CPACK_PACKAGE_FILE_NAME "QtModelUtilities-${VERSION_SHORT}-${CMAKE_SYSTEM_NAME}-${CMAKE_CXX_COMPILER_ID}-Qt${QT_VERSION_MAJOR}-${modelutilities_PlatformDir}") include(CPack) diff --git a/INSTALL.markdown b/INSTALL.markdown new file mode 100644 index 0000000..f0fcd40 --- /dev/null +++ b/INSTALL.markdown @@ -0,0 +1,39 @@ +# Building from sources +The build system of this library is [CMake](https://cmake.org/), the minimum supported version is 3.3 + +Once you installed CMake, open a terminal window with the C++ compiler and Qt environment set up. The procedure is platform dependant. +On Windows MSVC, for example, it consist in calling `vcvarsall.bat` from the compiler folder and `qtenv2.bat` from the bin folder of Qt. + +Now you can call CMake passing arguments to customise the build. The first choice is the generator. It is passed via the `-G "Generator"` argument +You can find a list of generators in the [CMake documentation](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html); +the most commonly used ones are `-G "Unix Makefiles"` on linux and macOS, `-G "MinGW Makefiles"` on Windows MinGW and `-G "NMake Makefiles"` on Windows MSVC. +It's also advisable to create a "build" folder inside the downloaded source tree to separate the build artefacts from the original sources. + +The next option is the build type, this is decided by passing the `-DCMAKE_BUILD_TYPE=DEBUG` or `-DCMAKE_BUILD_TYPE=RELEASE` argument. +For the debug build you can also specify the `-DCMAKE_DEBUG_POSTFIX=d` or `-DCMAKE_DEBUG_POSTFIX=_debug` option to append `d` or `_debug` to the name of the built library. + +The final almost-mandatory argument is `-DCMAKE_INSTALL_PREFIX="path/to/installed/library"` where you can specify the path where the library will be installed. +Using, again, Windows MSVC as an example, a minimal CMake invocation would look like this: +`cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DCMAKE_INSTALL_PREFIX="./installed" ../` + +You can also specify an number of arguments to customise the build even further: + +| Argument | Effect | +|----------|--------| +| `-DBUILD_STATIC_LIBS=ON` | Builds a statically linked library instead of a dynamic one | +| `-DBUILD_TESTING=ON` | Compiles the unit tests included in the sources | +| `-DBUILD_EXAMPLES=ON` | Compiles the examples of use included in the sources | +| `-DNO_GUI=ON` | Disables all functionalities that require the QtGUI module. This option is automatically set if the module is not installed | +| `-DNO_WIDGETS=ON` | Disables all functionalities that require the QtWidgets module. This option is automatically set if the module is not installed | +| `-DBUILD_DOCS=OFF` | Disables the creation of the documentation in HTML format. This option is automatically set if [Doxygen](www.doxygen.org) is not installed | +| `-DBUILD_ROLEMASKPROXY=OFF` | Exclude the Role Mask Proxy Model module of the library | +| `-DBUILD_MODELSERIALISATION=OFF` | Exclude the Model Serialisation module of the library | +| `-DBUILD_INSERTPROXY=OFF` | Exclude the Insert Proxy Model module of the library | +| `-DOPTIMISE_FOR_MANY_ROLES=ON` | Some of the models are optimised so that they work best if every cell in the model holds no more than around 20 different roles. This is in line with how Qt's native models are optimised. If you plan to store more roles per cell you can enable this option to improve performance | +| `-DTEST_OUTPUT_XML=ON` | This is mainly used by the CI. If this option is set, the tests will generate an xml file with results rather than printing them to the console | + +After running CMake you can call `cmake --build .` to compile the project. +You can call `cmake --build . --target test` or `ctest` to run the unit tests. +Finally you can call `cmake --build . --target install` to install the library in the destination specified by `-DCMAKE_INSTALL_PREFIX=`. + +To use the library add the path to the directory where you installed the library to `CMAKE_PREFIX_PATH`, the you can use `find_package(QtModelUtilities)`/`target_link_libraries(MyApp PRIVATE QtModelUtilities::QtModelUtilities)` to use it in your CMake project. \ No newline at end of file diff --git a/LICENSE b/LICENSE index 16c9512..ecbfc77 100644 --- a/LICENSE +++ b/LICENSE @@ -1,18 +1,174 @@ -Copyright 2018 Luca Beldi - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/README Insert Proxy Model.markdown b/README Insert Proxy Model.markdown index bca5dc4..cf7346b 100644 --- a/README Insert Proxy Model.markdown +++ b/README Insert Proxy Model.markdown @@ -9,6 +9,9 @@ By default the row/column is committed as soon as there is any data in any cell Only flat models are supported. Branches of a tree will be hidden by the proxy +### Class Documentation ++ InsertProxyModel + ### Dependencies + Qt Core \ No newline at end of file diff --git a/README Model Serialisation.markdown b/README Model Serialisation.markdown index 5ae795e..3aa255a 100644 --- a/README Model Serialisation.markdown +++ b/README Model Serialisation.markdown @@ -1,19 +1,25 @@ # Model Serialisation -Implements a general method to serialise QAbstractItemModel based models to various common formats. +Implements a general method to serialise `QAbstractItemModel` based models to various common formats. ### Custom Types To save custom classes stored in the model as `QVariant`s you will need to define and register their stream operators. For example, for class `MyClass` you will need to implement -```C++ -QDataStream& operator<<(QDataStream &stream, const MyClass& val); -QDataStream& operator>>(QDataStream & stream, MyClass& val); -``` + + QDataStream& operator<<(QDataStream &stream, const MyClass& val); + QDataStream& operator>>(QDataStream & stream, MyClass& val); + And resgister them before using any save/load functionality by calling -```C++ -qRegisterMetaType("MyClass"); -qRegisterMetaTypeStreamOperators("MyClass"); -``` + + qRegisterMetaType("MyClass"); + qRegisterMetaTypeStreamOperators("MyClass"); + +### Class Documentation ++ BinaryModelSerialiser ++ CsvModelSerialiser ++ HtmlModelSerialiser ++ JsonModelSerialiser ++ XmlModelSerialiser ### Dependencies diff --git a/README Role Mask Proxy Model.markdown b/README Role Mask Proxy Model.markdown index 272101a..9d4e77a 100644 --- a/README Role Mask Proxy Model.markdown +++ b/README Role Mask Proxy Model.markdown @@ -5,6 +5,9 @@ This proxy will act as a mask on top of the source model to intercept data. An example usage of this proxy is to enable the use of special roles in read-only models. To change the background brush of a `QSqlQueryModel` for example, you just need to create this proxy, call `addMaskedRole(Qt::BackgroundRole)` and now you can use `setData` as the model was writable for that role. +### Class Documentation ++ RoleMaskProxyModel + ### Dependencies + Qt Core diff --git a/README.markdown b/README.markdown index f4dddaf..7385f65 100644 --- a/README.markdown +++ b/README.markdown @@ -3,45 +3,43 @@ This library is a collection of utilities for Qt's Model/View framework. ### Status -| **CI** | **master** | **dev** | -|--------|------------|---------| -| [Appveyor](https://ci.appveyor.com/project/VSRonin/qtmodelutilities) | ![Appveyor Build status](https://ci.appveyor.com/api/projects/status/3x8h2laxlbh9wc7c/branch/master?svg=true) | ![Appveyor Build status](https://ci.appveyor.com/api/projects/status/3x8h2laxlbh9wc7c/branch/dev?svg=true)| -| [Travis](https://travis-ci.org/VSRonin/QtModelUtilities) | ![Travis Build Status](https://travis-ci.org/VSRonin/QtModelUtilities.svg?branch=master) | ![Travis Build Status](https://travis-ci.org/VSRonin/QtModelUtilities.svg?branch=dev) | +[![Build Status](https://github.com/VSRonin/QtModelUtilities/actions/workflows/buildtest.yml/badge.svg?branch=master)](https://github.com/VSRonin/QtModelUtilities/actions) +[![Qt 5.1 Build Status](https://travis-ci.com/VSRonin/QtModelUtilities.svg?branch=master)](https://travis-ci.com/VSRonin/QtModelUtilities) ### Contents + [Role Mask Proxy Model](https://vsronin.github.io/QtModelUtilities/md__r_e_a_d_m_e__role__mask__proxy__model.html): A proxy that will act as a mask on top of the source model to intercept data. + [Insert Proxy Model](https://vsronin.github.io/QtModelUtilities/md__r_e_a_d_m_e__insert__proxy__model.html): A proxy to add an extra row, column or both to allow users to insert new sections with a familiar interface. + [Model Serialisation](https://vsronin.github.io/QtModelUtilities/md__r_e_a_d_m_e__model__serialisation.html): Implements a general method to serialise QAbstractItemModel based models to various common formats. ++ ~~Transpose Proxy Model: A proxy model to [transpose](https://en.wikipedia.org/wiki/Transpose#Examples) the original model.~~ Now part of Qt: QTransposeProxyModel ### Installation #### Official Binary Release -If your system is compatible with one of the 5 main platforms you can download the pre-compiled dinamically linked library from [the releases page](https://github.com/VSRonin/QtModelUtilities/releases). +If your system is compatible with one of the main platforms you can download the pre-compiled dinamically linked library from [the releases page](https://github.com/VSRonin/QtModelUtilities/releases). #### Build from Source -This library uses [CMake](https://cmake.org/) as build system and allows usage both using static and dynamic linking. -See INSTALL for detailed build instructions. - -#### Use the source directly -If you use `qmake` (Qt Creator), you can include the entire source of the library directly in your code by adding `include(path/to/source/modelutilities.pri)` in your `.pro` file. +This library uses [CMake](https://cmake.org/) as build system and allows usage both using static and dynamic linking as well as customising what parts of the library you want to build. +See [INSTALL](https://vsronin.github.io/QtModelUtilities/md__i_n_s_t_a_l_l.html) for detailed build instructions. ### Platforms The library should be compatible with all platform supported by Qt, the CI will build, test and deploy the following configurations: -+ Windows MSVC 2015 32bit Qt 5.9 -+ Windows MSVC 2015 64bit Qt 5.9 -+ Windows MinGW 5.3.0 32bit Qt 5.9 -+ Ubuntu Trusty gcc 6 64bit Qt 5.9 -+ Mac OS X LLVM 8.1 64bit Qt 5.10 ++ Windows MSVC 2019 64bit Qt 5.15 ++ Windows MSVC 2019 64bit Qt 6.0 ++ Windows MinGW 8.1 64bit Qt 5.15 ++ Windows MinGW 8.1 64bit Qt 6.0 ++ Ubuntu g++ 64bit Qt 5.15 ++ Ubuntu g++ 64bit Qt 6.0 ++ Mac OS X Clang 64bit Qt 5.15 ++ Mac OS X Clang64bit Qt 6.0 A reduced suite of tests, to ensure compatibility, is also run on: ++ Ubuntu g++ 64bit Qt 5.12 ++ Ubuntu Xenial g++ 64bit Qt 5.9 + Ubuntu Trusty gcc 6 64bit Qt 5.1 -+ Ubuntu Trusty gcc 6 64bit Qt 5.6 -+ Windows MinGW 4.7.0 32bit Qt 5.0 (manual) -+ Windows MSVC 2013 32bit Qt 5.8 (manual) ### Examples @@ -55,7 +53,7 @@ Docs can, alternatively, be built using Doxygen and the DoxygenConfig.doxyfile f ### Requirements: -+ Qt 5 ++ Qt 5.1 or later + C++11 + Doxygen (optional to build documentation) diff --git a/ci/processtestresults/action.yml b/ci/processtestresults/action.yml new file mode 100644 index 0000000..864684d --- /dev/null +++ b/ci/processtestresults/action.yml @@ -0,0 +1,145 @@ +name: Qt Test to JUnit +description: Converts the xml output of Qt Test to a detailed JUnit format and a human readable report +branding: + icon: zap + color: green +inputs: + qt-tests: + description: Folder of Qt test results + required: true + default: 'test-results' + junit-output: + description: Output filename including optional path for the JUnit xml + required: true + default: JUnitResults.xml + html-output: + description: Output filename including optional path for the human readable report + required: true + default: TestResults.html +runs: + using: "composite" + steps: + - shell: pwsh + run: | + pushd "${{ inputs.qt-tests }}" + $xmlOut = new-object System.Xml.XmlDocument; + $xmlOut.AppendChild($xmlOut.CreateXmlDeclaration("1.0","UTF-8",$null)); + $rootXmlOut = $xmlOut.CreateElement("testsuites"); + $xmlOut.AppendChild($rootXmlOut); + $totalPass = 0; + $totalFail = 0; + $totalError = 0; + $totalSkip = 0; + $totalTime = 0; + $crashMessage = $null; + Get-ChildItem ".\" -Filter "*.xml" | Foreach-Object { + $XmlDocument = $null; + $localPass = 0; + $localFail = 0; + $localError = 0; + $localSkip = 0; + $localTime = 0; + $currentFilePath = $_.FullName; + $testSuiteName = $_.BaseName; + if([bool]((Get-Content -Path $currentFilePath) -as [xml])){ + [xml]$XmlDocument = (Get-Content -Path $currentFilePath) -as [xml]; + } + else{ + $localError = 1; + $rawFilecontent = [IO.File]::ReadAllText($currentFilePath); + if([string]::IsNullOrEmpty($rawFilecontent)){ + $crashMessage = "Output file is empty: " + $currentFilePath; + } + else{ + $crashMessage = $rawFilecontent; + $rawFileMatch = [regex]::match($rawFilecontent,"(?s)(.+<\/TestCase>)(.*)"); + if($rawFileMatch.Success){ + if([bool](($rawFileMatch.captures.groups[1].value) -as [xml])){ + [xml]$XmlDocument = ($rawFileMatch.captures.groups[1].value) -as [xml]; + $crashMessage = $rawFileMatch.captures.groups[2].value; + } + } + } + } + $testSuiteXmlOut = $rootXmlOut.AppendChild($xmlOut.CreateElement("testsuite")); + if($XmlDocument -ne $null){ + $testClassName = $XmlDocument.TestCase.name; + $testSuiteXmlOut.SetAttribute("name",$testSuiteName); + $testSuitePropertiesXmlOut = $testSuiteXmlOut.AppendChild($xmlOut.CreateElement("properties")); + $testSuitePropertiesPropertyXmlOut = $testSuitePropertiesXmlOut.AppendChild($xmlOut.CreateElement("property")); + $testSuitePropertiesPropertyXmlOut.SetAttribute("name","QtVersion"); + $testSuitePropertiesPropertyXmlOut.SetAttribute("value",($XmlDocument.TestCase.Environment.QtVersion)); + $testSuitePropertiesPropertyXmlOut = $testSuitePropertiesXmlOut.AppendChild($xmlOut.CreateElement("property")); + $testSuitePropertiesPropertyXmlOut.SetAttribute("name","QtBuild"); + $testSuitePropertiesPropertyXmlOut.SetAttribute("value",($XmlDocument.TestCase.Environment.QtBuild)); + $testSuitePropertiesPropertyXmlOut = $testSuitePropertiesXmlOut.AppendChild($xmlOut.CreateElement("property")); + $testSuitePropertiesPropertyXmlOut.SetAttribute("name","QTestVersion"); + $testSuitePropertiesPropertyXmlOut.SetAttribute("value",($XmlDocument.TestCase.Environment.QTestVersion)); + foreach($testFunction in $XmlDocument.SelectNodes("//TestFunction")){ + $testFunctionName = $testFunction.name; + $countIncidents = $testFunction.ChildNodes.Count; + $testFunctionTime = [decimal]$testFunction.Duration.msecs; + $localTime = $localTime +$testFunctionTime; + foreach($incident in $testFunction.ChildNodes){ + if($incident.Name -ne "Incident" -and $incident.Name -ne "Message"){ + continue; + } + $incidentName = $testFunctionName; + if($incident.DataTag -ne $null){ + $incidentName = $incidentName + " - " + $incident.DataTag.InnerText; + } + $incidentName = ($incidentName); + $testSuitetestcaseXmlOut = $testSuiteXmlOut.AppendChild($xmlOut.CreateElement("testcase")); + $testSuitetestcaseXmlOut.SetAttribute("name",$incidentName); + $testSuitetestcaseXmlOut.SetAttribute("classname",$testClassName); + $testSuitetestcaseXmlOut.SetAttribute("time",[math]::Round($testFunctionTime/(1000*$countIncidents),3)); + if($incident.type -eq "skip"){ + ++$localSkip; + $testSuitetestcaseSkipXmlOut = $testSuitetestcaseXmlOut.AppendChild($xmlOut.CreateElement("skipped")); + $testSuitetestcaseSkipXmlOut.SetAttribute("message","file: " + ($incident.file + "`nline: " + $incident.line + "`n" + $incident.Description.InnerText)); + } + ElseIf ($incident.type -eq "fail"){ + ++$localFail; + $testSuitetestcaseSkipXmlOut = $testSuitetestcaseXmlOut.AppendChild($xmlOut.CreateElement("failure")); + $testSuitetestcaseSkipXmlOut.SetAttribute("message",("file: " + $incident.file + "`nline: " + $incident.line + "`n" + $incident.Description.InnerText)); + } + ElseIf ($incident.type -eq "qdebug" -or $incident.type -eq "qwarn" -or $incident.type -eq "system" -or $incident.type -eq "qfatal"){ + $testSuitetestcaseCerrXmlOut = $testSuitetestcaseXmlOut.AppendChild($xmlOut.CreateElement("system-err")); + $testSuitetestcaseCerrXmlOut.AppendChild($xmlOut.CreateTextNode(($incident.Description.InnerText))); + } + else{ + ++$localPass; + } + }; + }; + } + if($localError -eq 1){ + $testSuitetestcaseXmlOut = $testSuiteXmlOut.AppendChild($xmlOut.CreateElement("testcase")); + $testSuitetestcaseXmlOut.SetAttribute("name","SystemError"); + $testSuitetestcaseXmlOut.SetAttribute("classname",$testClassName); + $testSuitetestcaseErrorXmlOut = $testSuitetestcaseXmlOut.AppendChild($xmlOut.CreateElement("error")); + $testSuitetestcaseErrorXmlOut.SetAttribute("message",($crashMessage)); + } + $testSuiteXmlOut.SetAttribute("time",[math]::Round($localTime/1000,3)); + $testSuiteXmlOut.SetAttribute("skipped",$localSkip); + $testSuiteXmlOut.SetAttribute("tests",$localSkip+$localFail+$localError+$localPass); + $testSuiteXmlOut.SetAttribute("failures",$localFail); + $testSuiteXmlOut.SetAttribute("errors",$localError); + $totalTime = $totalTime + $localTime; + $totalSkip = $totalSkip + $localSkip; + $totalError = $totalError + $localError; + $totalFail = $totalFail + $localFail; + $totalPass = $totalPass + $localPass; + }; + $rootXmlOut.SetAttribute("time",[math]::Round($totalTime/1000,3)); + $rootXmlOut.SetAttribute("failures",$totalFail); + $rootXmlOut.SetAttribute("errors",$totalError); + $rootXmlOut.SetAttribute("tests",$totalPass); + popd + $xmlOut.save("${{ inputs.junit-output }}"); + - shell: pwsh + run: | + $npmBinPath = npm bin + npm install xunit-viewer + & $npmBinPath/xunit-viewer -r "${{ inputs.junit-output }}" -o "${{ inputs.html-output }}" -t "${{ github.repository }}" + Start-Sleep 10 \ No newline at end of file diff --git a/cmake/modules/GetGitRevisionDescription.cmake b/cmake/modules/GetGitRevisionDescription.cmake new file mode 100644 index 0000000..87f691a --- /dev/null +++ b/cmake/modules/GetGitRevisionDescription.cmake @@ -0,0 +1,284 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_describe_working_tree( [ ...]) +# +# Returns the results of git describe on the working tree (--dirty option), +# and adjusting the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# git_local_changes() +# +# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. +# Uses the return code of "git diff-index --quiet HEAD --". +# Does not regard untracked files. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2020 Ryan Pavlik +# http://academic.cleardefinition.com +# +# Copyright 2009-2013, Iowa State University. +# Copyright 2013-2020, Ryan Pavlik +# Copyright 2013-2020, Contributors +# SPDX-License-Identifier: BSL-1.0 +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +# Function _git_find_closest_git_dir finds the next closest .git directory +# that is part of any directory in the path defined by _start_dir. +# The result is returned in the parent scope variable whose name is passed +# as variable _git_dir_var. If no .git directory can be found, the +# function returns an empty string via _git_dir_var. +# +# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and +# neither foo nor bar contain a file/directory .git. This wil return +# C:/bla/.git +# +function(_git_find_closest_git_dir _start_dir _git_dir_var) + set(cur_dir "${_start_dir}") + set(git_dir "${_start_dir}/.git") + while(NOT EXISTS "${git_dir}") + # .git dir not found, search parent directories + set(git_previous_parent "${cur_dir}") + get_filename_component(cur_dir ${cur_dir} DIRECTORY) + if(cur_dir STREQUAL git_previous_parent) + # We have reached the root directory, we are not in git + set(${_git_dir_var} + "" + PARENT_SCOPE) + return() + endif() + set(git_dir "${cur_dir}/.git") + endwhile() + set(${_git_dir_var} + "${git_dir}" + PARENT_SCOPE) +endfunction() + +function(get_git_head_revision _refspecvar _hashvar) + _git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR) + + if("${ARGN}" STREQUAL "ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR") + set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR TRUE) + else() + set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR FALSE) + endif() + if(NOT "${GIT_DIR}" STREQUAL "") + file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_SOURCE_DIR}" + "${GIT_DIR}") + if("${_relative_to_source_dir}" MATCHES "[.][.]" AND NOT ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR) + # We've gone above the CMake root dir. + set(GIT_DIR "") + endif() + endif() + if("${GIT_DIR}" STREQUAL "") + set(${_refspecvar} + "GITDIR-NOTFOUND" + PARENT_SCOPE) + set(${_hashvar} + "GITDIR-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + # Check if the current source dir is a git submodule or a worktree. + # In both cases .git is a file instead of a directory. + # + if(NOT IS_DIRECTORY ${GIT_DIR}) + # The following git command will return a non empty string that + # points to the super project working tree if the current + # source dir is inside a git submodule. + # Otherwise the command will return an empty string. + # + execute_process( + COMMAND "${GIT_EXECUTABLE}" rev-parse + --show-superproject-working-tree + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT "${out}" STREQUAL "") + # If out is empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE + ${submodule}) + string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} + ABSOLUTE) + set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") + else() + # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree + file(READ ${GIT_DIR} worktree_ref) + # The .git directory contains a path to the worktree information directory + # inside the parent git repo of the worktree. + # + string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir + ${worktree_ref}) + string(STRIP ${git_worktree_dir} git_worktree_dir) + _git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR) + set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") + endif() + else() + set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") + endif() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${HEAD_SOURCE_FILE}") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} + "${HEAD_REF}" + PARENT_SCOPE) + set(${_hashvar} + "${HEAD_HASH}" + PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} + "HEAD-HASH-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process( + COMMAND "${GIT_EXECUTABLE}" describe --tags --always ${hash} ${ARGN} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_describe_working_tree _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + execute_process( + COMMAND "${GIT_EXECUTABLE}" describe --dirty ${ARGN} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_get_exact_tag _var) + git_describe(out --exact-match ${ARGN}) + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_local_changes _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} + "HEAD-HASH-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + execute_process( + COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(res EQUAL 0) + set(${_var} + "CLEAN" + PARENT_SCOPE) + else() + set(${_var} + "DIRTY" + PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/modules/GetGitRevisionDescription.cmake.in b/cmake/modules/GetGitRevisionDescription.cmake.in new file mode 100644 index 0000000..116efc4 --- /dev/null +++ b/cmake/modules/GetGitRevisionDescription.cmake.in @@ -0,0 +1,43 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright 2009-2012, Iowa State University +# Copyright 2011-2015, Contributors +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# SPDX-License-Identifier: BSL-1.0 + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + else() + configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) + file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) + if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") + set(HEAD_HASH "${CMAKE_MATCH_1}") + endif() + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/cmake/modules/TestMacro.cmake b/cmake/modules/TestMacro.cmake new file mode 100644 index 0000000..930b813 --- /dev/null +++ b/cmake/modules/TestMacro.cmake @@ -0,0 +1,68 @@ +macro(BasicTest TestName) + set(targetName "tst_${TestName}") + set(testProjectName "tst${TestName}") + string(TOLOWER ${TestName} TestSourceFileName) + project(${testProjectName} LANGUAGES CXX) + set(CMAKE_INCLUDE_CURRENT_DIR ON) + find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Test REQUIRED) + find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Test REQUIRED) + if(NOT NO_GUI) + find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Gui) + if(NOT "${Qt${QT_VERSION_MAJOR}Gui_FOUND}") + set(NO_GUI ON) + else() + message(STATUS "Found Qt Gui ${Qt${QT_VERSION_MAJOR}Gui_VERSION}") + endif() + endif() + message(STATUS "Found Qt Test ${Qt${QT_VERSION_MAJOR}Test_VERSION}") + add_executable(${targetName} + main.cpp + "tst_${TestSourceFileName}.cpp" + "tst_${TestSourceFileName}.h" + ) + if("${Qt${QT_VERSION_MAJOR}Test_VERSION}" VERSION_LESS "5.11.0") + file(DOWNLOAD "https://code.qt.io/cgit/qt-creator/qt-creator.git/plain/src/shared/modeltest/modeltest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/modeltest.cpp" STATUS ModelTestDownloadStatus) + list(GET ModelTestDownloadStatus 0 ModelTestDownloadStatusCode) + if(ModelTestDownloadStatusCode EQUAL 0) + file(DOWNLOAD "https://code.qt.io/cgit/qt-creator/qt-creator.git/plain/src/shared/modeltest/modeltest.h" "${CMAKE_CURRENT_SOURCE_DIR}/modeltest.h" STATUS ModelTestDownloadStatus) + list(GET ModelTestDownloadStatus 0 ModelTestDownloadStatusCode) + if(ModelTestDownloadStatusCode EQUAL 0) + target_sources(${targetName} PRIVATE modeltest.cpp modeltest.h) + else() + target_compile_definitions(${targetName} PRIVATE MOC_MODEL_TEST) + endif() + else() + target_compile_definitions(${targetName} PRIVATE MOC_MODEL_TEST) + endif() + endif() + if(BUILD_STATIC_LIBS) + target_compile_definitions(${targetName} PRIVATE MODELUTILITIES_STATIC) + endif() + target_include_directories(${targetName} PRIVATE ../../src) + target_link_libraries(${targetName} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Test modelutilities) + if(NOT NO_GUI) + target_link_libraries(${targetName} PRIVATE Qt${QT_VERSION_MAJOR}::Gui) + endif() + target_compile_definitions(${targetName} PRIVATE QT_NO_CAST_TO_ASCII) + set_target_properties(${targetName} PROPERTIES + AUTOMOC ON + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON + VERSION "1.0" + SOVERSION 1 + EXPORT_NAME ${testProjectName} + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/test" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/test" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin/test" + ) + if(TEST_OUTPUT_XML) + set(Test_Output_FileName ${testProjectName}) + if(CMAKE_BUILD_TYPE MATCHES DEBUG) + set(Test_Output_FileName "${Test_Output_FileName}_debug") + endif() + set(Test_Output_FileName "${Test_Output_FileName}_tstres.xml") + add_test(NAME ${testProjectName} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/../TestResults" COMMAND $ -o ${Test_Output_FileName},xml) + else() + add_test(NAME ${testProjectName} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" COMMAND $) + endif() +endmacro() diff --git a/cmake/modules/package.cmake b/cmake/modules/package.cmake new file mode 100644 index 0000000..68301b1 --- /dev/null +++ b/cmake/modules/package.cmake @@ -0,0 +1,6 @@ +include("shared-Release/CPackConfig.cmake") + +set(CPACK_INSTALL_CMAKE_PROJECTS + shared-Debug ALL / + shared-Release ALL / +) \ No newline at end of file diff --git a/docs/DoxygenConfig.doxyfile b/docs/DoxygenConfig.doxyfile index f956669..836f389 100644 --- a/docs/DoxygenConfig.doxyfile +++ b/docs/DoxygenConfig.doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.9.1 +# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -17,11 +17,11 @@ # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 @@ -51,7 +51,7 @@ PROJECT_BRIEF = "A set of utilities for the model/view framework of Qt" # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is @@ -93,6 +93,14 @@ ALLOW_UNICODE_NAMES = NO OUTPUT_LANGUAGE = English +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. @@ -118,7 +126,7 @@ REPEAT_BRIEF = YES # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. -ABBREVIATE_BRIEF = +ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief @@ -152,7 +160,7 @@ FULL_PATH_NAMES = YES # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = +STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -161,7 +169,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -179,6 +187,16 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus @@ -199,6 +217,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -226,17 +252,17 @@ TAB_SIZE = 4 # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. - -ALIASES = "license=\par License\n" \ - "accessors=\par Accessors\n" \ - "reimp=Reimplemented from base class" - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = "license=\par License
" \ + "accessors=\par Access functions:
" \ + "reimp=Reimplemented from base class" \ + "notifier=\par Notifier signal:
" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -266,28 +292,40 @@ OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. -EXTENSION_MAPPING = +EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. +# documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. @@ -295,6 +333,15 @@ EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -320,7 +367,7 @@ BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. @@ -345,6 +392,13 @@ IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent @@ -399,6 +453,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -419,6 +486,12 @@ EXTRACT_ALL = NO EXTRACT_PRIVATE = NO +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. @@ -456,6 +529,13 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -473,8 +553,8 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO @@ -493,11 +573,18 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. # The default value is: system dependent. CASE_SENSE_NAMES = NO @@ -624,7 +711,7 @@ GENERATE_DEPRECATEDLIST= YES # sections, marked by \if ... \endif and \cond # ... \endcond blocks. -ENABLED_SECTIONS = +ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the @@ -666,7 +753,7 @@ SHOW_NAMESPACES = YES # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. -FILE_VERSION_FILTER = +FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated @@ -679,17 +766,17 @@ FILE_VERSION_FILTER = # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = +LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. -CITE_BIB_FILES = +CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages @@ -729,11 +816,21 @@ WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. # The default value is: NO. WARN_NO_PARAMDOC = NO +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated @@ -748,7 +845,7 @@ WARN_FORMAT = "$file:$line: $text" # messages should be written. If left blank the output is written to standard # error (stderr). -WARN_LOGFILE = +WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files @@ -757,7 +854,7 @@ WARN_LOGFILE = # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with -# spaces. +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = ./src \ @@ -766,20 +863,29 @@ INPUT = ./src \ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank the -# following patterns are tested:*.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, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, -# *.qsf, *.as and *.js. +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.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 (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cpp \ @@ -800,7 +906,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -832,20 +938,20 @@ EXCLUDE_PATTERNS = */GeneratedFiles \ # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. -EXAMPLE_PATTERNS = +EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands @@ -858,7 +964,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = +IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program @@ -874,8 +980,12 @@ IMAGE_PATH = # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. -INPUT_FILTER = +INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the @@ -883,8 +993,12 @@ INPUT_FILTER = # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. -FILTER_PATTERNS = +FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for @@ -899,7 +1013,7 @@ FILTER_SOURCE_FILES = NO # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. -FILTER_SOURCE_PATTERNS = +FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page @@ -935,7 +1049,7 @@ INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. +# entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO @@ -967,12 +1081,12 @@ SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version +# (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # @@ -995,23 +1109,42 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. # Note: The availability of this option depends on whether or not doxygen was -# compiled with the --with-libclang option. +# generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. -CLANG_OPTIONS = +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index @@ -1024,20 +1157,13 @@ CLANG_OPTIONS = ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. -IGNORE_PREFIX = +IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output @@ -1081,7 +1207,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1091,7 +1217,7 @@ HTML_HEADER = # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = +HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1103,7 +1229,7 @@ HTML_FOOTER = # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = +HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets @@ -1116,7 +1242,7 @@ HTML_STYLESHEET = # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1126,12 +1252,12 @@ HTML_EXTRA_STYLESHEET = # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = +HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. @@ -1160,12 +1286,24 @@ HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: YES. +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. @@ -1189,13 +1327,14 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1234,8 +1373,8 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1254,7 +1393,7 @@ GENERATE_HTMLHELP = NO # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_FILE = +CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, @@ -1262,10 +1401,10 @@ CHM_FILE = # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -HHC_LOCATION = +HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1275,7 +1414,7 @@ GENERATE_CHI = NO # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_INDEX_ENCODING = +CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it @@ -1306,11 +1445,12 @@ GENERATE_QHP = NO # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. -QCH_FILE = +QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1318,8 +1458,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1327,33 +1467,33 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_ATTRS = +QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_SECT_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. -QHG_LOCATION = +QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To @@ -1427,6 +1567,17 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1436,7 +1587,7 @@ EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # @@ -1447,8 +1598,14 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1460,7 +1617,7 @@ USE_MATHJAX = YES # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. @@ -1475,8 +1632,8 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest @@ -1486,15 +1643,16 @@ MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_EXTENSIONS = +MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_CODEFILE = +MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and @@ -1518,7 +1676,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH # setting. When disabled, doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing @@ -1537,7 +1695,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1550,11 +1709,12 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. -SEARCHENGINE_URL = +SEARCHENGINE_URL = # When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed # search data is written to a file for indexing by an external tool. With the @@ -1570,7 +1730,7 @@ SEARCHDATA_FILE = searchdata.xml # projects and redirect the results back to the right project. # This tag requires that the tag SEARCHENGINE is set to YES. -EXTERNAL_SEARCH_ID = +EXTERNAL_SEARCH_ID = # The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen # projects other than the one defined by this configuration file, but that are @@ -1580,7 +1740,7 @@ EXTERNAL_SEARCH_ID = # EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... # This tag requires that the tag SEARCHENGINE is set to YES. -EXTRA_SEARCH_MAPPINGS = +EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # Configuration options related to the LaTeX output @@ -1602,21 +1762,35 @@ LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. # -# Note that when enabling USE_PDFLATEX this option is only used for generating -# bitmaps for formulas in the HTML output, but not in the Makefile that is -# written to the output directory. -# The default file is: latex. +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). # The default file is: makeindex. # This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = makeindex +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + # If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. @@ -1635,13 +1809,16 @@ COMPACT_LATEX = NO PAPER_TYPE = a4 # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names -# that should be included in the LaTeX output. To get the times font for -# instance you can specify -# EXTRA_PACKAGES=times +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. -EXTRA_PACKAGES = +EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for the # generated LaTeX document. The header should contain everything until the first @@ -1657,7 +1834,7 @@ EXTRA_PACKAGES = # to HTML_HEADER. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_HEADER = +LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the # generated LaTeX document. The footer should contain everything after the last @@ -1668,7 +1845,7 @@ LATEX_HEADER = # Note: Only use a user-defined footer if you know what you are doing! # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_FOOTER = +LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created @@ -1679,7 +1856,7 @@ LATEX_FOOTER = # list). # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_STYLESHEET = # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the LATEX_OUTPUT output @@ -1687,7 +1864,7 @@ LATEX_EXTRA_STYLESHEET = # markers available. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_FILES = +LATEX_EXTRA_FILES = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is # prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will @@ -1698,9 +1875,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1734,12 +1913,28 @@ LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. # The default value is: plain. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BIB_STYLE = plain +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- @@ -1779,22 +1974,22 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's config -# file, i.e. a series of assignments. You only have to provide replacements, -# missing definitions are set to their default value. +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the # default style sheet that doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_STYLESHEET_FILE = +RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's config file. A template extensions file can be generated -# using doxygen -e rtf extensionFile. +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_EXTENSIONS_FILE = +RTF_EXTENSIONS_FILE = # If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code # with syntax highlighting in the RTF output. @@ -1839,7 +2034,7 @@ MAN_EXTENSION = .3 # MAN_EXTENSION with the initial . removed. # This tag requires that the tag GENERATE_MAN is set to YES. -MAN_SUBDIR = +MAN_SUBDIR = # If the MAN_LINKS tag is set to YES and doxygen generates man output, then it # will generate one additional man file for each entity documented in the real @@ -1877,6 +2072,13 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- @@ -1909,13 +2111,17 @@ DOCBOOK_PROGRAMLISTING = NO #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sf.net) file that captures the -# structure of the code including all documentation. Note that this feature is -# still experimental and incomplete at the moment. +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -1952,7 +2158,7 @@ PERLMOD_PRETTY = YES # overwrite each other's variables. # This tag requires that the tag GENERATE_PERLMOD is set to YES. -PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor @@ -1993,7 +2199,7 @@ SEARCH_INCLUDES = YES # preprocessor. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = +INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the @@ -2001,7 +2207,7 @@ INCLUDE_PATH = # used. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -INCLUDE_FILE_PATTERNS = +INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that are # defined before the preprocessor is started (similar to the -D option of e.g. @@ -2011,7 +2217,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = MODEL_UTILITIES_DOCS # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2020,7 +2226,8 @@ PREDEFINED = # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = ModelVersionNumber +EXPAND_AS_DEFINED = Q_DECL_OVERRIDE \ + Q_NULLPTR # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have @@ -2049,13 +2256,13 @@ SKIP_FUNCTION_MACROS = YES # the path). If a tag file is not located in the directory in which doxygen is # run, you must also specify the path to the tagfile here. -TAGFILES = +TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. -GENERATE_TAGFILE = +GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES, all external class will be listed in # the class index. If set to NO, only the inherited external classes will be @@ -2078,12 +2285,6 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- @@ -2097,21 +2298,12 @@ PERL_PATH = /usr/bin/perl CLASS_DIAGRAMS = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. -DIA_PATH = +DIA_PATH = # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. @@ -2160,7 +2352,7 @@ DOT_FONTSIZE = 10 # the path where dot can find it using this tag. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTPATH = +DOT_FONTPATH = # If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for # each documented class showing the direct and indirect inheritance relations. @@ -2203,10 +2395,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2238,7 +2452,8 @@ INCLUDED_BY_GRAPH = YES # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2249,7 +2464,8 @@ CALL_GRAPH = NO # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2272,11 +2488,15 @@ GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). -# Possible values are: png, jpg, gif and svg. +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2298,26 +2518,26 @@ INTERACTIVE_SVG = NO # found. If left blank, it is assumed the dot tool can be found in the path. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_PATH = +DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the \dotfile # command). # This tag requires that the tag HAVE_DOT is set to YES. -DOTFILE_DIRS = +DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the \mscfile # command). -MSCFILE_DIRS = +MSCFILE_DIRS = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile # command). -DIAFILE_DIRS = +DIAFILE_DIRS = # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the # path where java can find the plantuml.jar file. If left blank, it is assumed @@ -2325,12 +2545,17 @@ DIAFILE_DIRS = # generate a warning when it encounters a \startuml command in this case and # will not generate output for the diagram. -PLANTUML_JAR_PATH = +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = # When using plantuml, the specified paths are searched for files specified by # the !include statement in a plantuml block. -PLANTUML_INCLUDE_PATH = +PLANTUML_INCLUDE_PATH = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes @@ -2385,9 +2610,11 @@ DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc and +# plantuml temporary files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9c921be..71848e9 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,9 @@ if(BUILD_MODELSERIALISATION) - add_subdirectory(exem_ModelSerialisation) + add_subdirectory(exam_ModelSerialisation) endif() if(BUILD_INSERTPROXY) - add_subdirectory(exem_InsertProxyModel) -endif() \ No newline at end of file + add_subdirectory(exam_InsertProxyModel) +endif() +if(BUILD_ROLEMASKPROXY) + add_subdirectory(exam_RoleMaskProxyModel) +endif() diff --git a/examples/exam_InsertProxyModel/CMakeLists.txt b/examples/exam_InsertProxyModel/CMakeLists.txt new file mode 100644 index 0000000..1bac02f --- /dev/null +++ b/examples/exam_InsertProxyModel/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.2) +project(exam_InsertProxyModel LANGUAGES CXX) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets REQUIRED) + +add_executable(exam_InsertProxyModel_Subclass WIN32 exam_inserproxysubclass.cpp) +add_executable(exam_InsertProxyModel_Commit WIN32 exam_inserproxycommit.cpp) +if(BUILD_STATIC_LIBS) + target_compile_definitions(exam_InsertProxyModel_Subclass PRIVATE MODELUTILITIES_STATIC) + target_compile_definitions(exam_InsertProxyModel_Commit PRIVATE MODELUTILITIES_STATIC) +endif() +target_include_directories(exam_InsertProxyModel_Subclass PRIVATE ../../src ../../src/includes) +target_include_directories(exam_InsertProxyModel_Commit PRIVATE ../../src ../../src/includes) +target_link_libraries(exam_InsertProxyModel_Subclass PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets modelutilities) +target_link_libraries(exam_InsertProxyModel_Commit PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets modelutilities) +set_target_properties(exam_InsertProxyModel_Subclass PROPERTIES + AUTOMOC ON + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON + VERSION "1.0" + EXPORT_NAME "examInsertProxyModelSubclass" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin/examples" +) +set_target_properties(exam_InsertProxyModel_Commit PROPERTIES + AUTOMOC ON + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON + VERSION "1.0" + EXPORT_NAME "examInsertProxyModelSubclass" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin/examples" +) +install(TARGETS exam_InsertProxyModel_Subclass + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) +install(TARGETS exam_InsertProxyModel_Commit + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) diff --git a/examples/exem_InsertProxyModel/exem_inserproxycommit.cpp b/examples/exam_InsertProxyModel/exam_inserproxycommit.cpp similarity index 68% rename from examples/exem_InsertProxyModel/exem_inserproxycommit.cpp rename to examples/exam_InsertProxyModel/exam_inserproxycommit.cpp index 531c35b..cfea3c2 100644 --- a/examples/exem_InsertProxyModel/exem_inserproxycommit.cpp +++ b/examples/exam_InsertProxyModel/exam_inserproxycommit.cpp @@ -14,7 +14,7 @@ int main(int argc, char *argv[]) QApplication a(argc, argv); QWidget mainWid; mainWid.setWindowTitle(QStringLiteral("InsertProxyModel Example Using Commit")); - QStandardItemModel* baseModel = new QStandardItemModel(&mainWid); + QStandardItemModel *baseModel = new QStandardItemModel(&mainWid); // fill the model with example data baseModel->insertColumns(0, 3); baseModel->insertRows(0, 5); @@ -23,46 +23,47 @@ int main(int argc, char *argv[]) baseModel->setData(baseModel->index(i, j), QStringLiteral("%1;%2").arg(i + 1).arg(j + 1)); } // Create the proxy and assign the source - InsertProxyModel* insertProxy = new InsertProxyModel(&mainWid); + InsertProxyModel *insertProxy = new InsertProxyModel(&mainWid); insertProxy->setSourceModel(baseModel); // create and connect the buttons to commit row/column - QPushButton* commitRowButton = new QPushButton(QStringLiteral("Commit Row"),&mainWid); + QPushButton *commitRowButton = new QPushButton(QStringLiteral("Commit Row"), &mainWid); commitRowButton->setEnabled(false); QObject::connect(commitRowButton, &QPushButton::clicked, insertProxy, &InsertProxyModel::commitRow); - QPushButton* commitColumnButton = new QPushButton(QStringLiteral("Commit Column"), &mainWid); + QPushButton *commitColumnButton = new QPushButton(QStringLiteral("Commit Column"), &mainWid); commitColumnButton->setEnabled(false); QObject::connect(commitColumnButton, &QPushButton::clicked, insertProxy, &InsertProxyModel::commitColumn); // Create a combobox to select the insert direction - QComboBox* insertDirectionCombo = new QComboBox(&mainWid); + QComboBox *insertDirectionCombo = new QComboBox(&mainWid); insertDirectionCombo->addItem("No Insertion", InsertProxyModel::NoInsert); insertDirectionCombo->addItem("Insert Rows", InsertProxyModel::InsertRow); insertDirectionCombo->addItem("Insert Columns", InsertProxyModel::InsertColumn); insertDirectionCombo->addItem("Insert Both", int(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow)); - QObject::connect(insertDirectionCombo, static_cast(&QComboBox::currentIndexChanged), [commitRowButton, commitColumnButton, insertProxy, insertDirectionCombo](int idx)->void { - const auto newDirection = InsertProxyModel::InsertDirection(insertDirectionCombo->itemData(idx).toInt()); - insertProxy->setInsertDirection(newDirection); - commitRowButton->setEnabled(newDirection & InsertProxyModel::InsertRow); - commitColumnButton->setEnabled(newDirection & InsertProxyModel::InsertColumn); - }); + QObject::connect(insertDirectionCombo, static_cast(&QComboBox::currentIndexChanged), + [commitRowButton, commitColumnButton, insertProxy, insertDirectionCombo](int idx) -> void { + const auto newDirection = InsertProxyModel::InsertDirection(insertDirectionCombo->itemData(idx).toInt()); + insertProxy->setInsertDirection(newDirection); + commitRowButton->setEnabled(newDirection & InsertProxyModel::InsertRow); + commitColumnButton->setEnabled(newDirection & InsertProxyModel::InsertColumn); + }); // Create a view for the base model and a view for the proxy - QTableView* baseView = new QTableView(&mainWid); + QTableView *baseView = new QTableView(&mainWid); baseView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); baseView->setEditTriggers(QTableView::NoEditTriggers); baseView->setModel(baseModel); - QTableView* proxyView = new QTableView(&mainWid); + QTableView *proxyView = new QTableView(&mainWid); proxyView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); proxyView->setModel(insertProxy); // lay out the widget - QHBoxLayout* topLay = new QHBoxLayout; + QHBoxLayout *topLay = new QHBoxLayout; topLay->addWidget(new QLabel(QStringLiteral("Insert Direction"), &mainWid)); topLay->addWidget(insertDirectionCombo); topLay->addWidget(commitRowButton); topLay->addWidget(commitColumnButton); topLay->addStretch(); - QGridLayout* mainLay = new QGridLayout(&mainWid); + QGridLayout *mainLay = new QGridLayout(&mainWid); mainLay->addLayout(topLay, 0, 0, 1, 2); mainLay->addWidget(baseView, 1, 0); mainLay->addWidget(proxyView, 1, 1); diff --git a/examples/exem_InsertProxyModel/exem_inserproxysubclass.cpp b/examples/exam_InsertProxyModel/exam_inserproxysubclass.cpp similarity index 75% rename from examples/exem_InsertProxyModel/exem_inserproxysubclass.cpp rename to examples/exam_InsertProxyModel/exam_inserproxysubclass.cpp index 2273128..8da4761 100644 --- a/examples/exem_InsertProxyModel/exem_inserproxysubclass.cpp +++ b/examples/exam_InsertProxyModel/exam_inserproxysubclass.cpp @@ -14,14 +14,15 @@ class InsertWhenAllDone : public InsertProxyModel { Q_DISABLE_COPY(InsertWhenAllDone) public: - explicit InsertWhenAllDone(QObject* parent = Q_NULLPTR) - : InsertProxyModel(parent) - {} + explicit InsertWhenAllDone(QObject *parent = Q_NULLPTR) + : InsertProxyModel(parent) + { } + protected: bool validRow() const Q_DECL_OVERRIDE { if (!sourceModel()) - return false; + return false; const int sourceCols = sourceModel()->columnCount(); const int sourceRows = sourceModel()->rowCount(); for (int i = 0; i < sourceCols; ++i) { @@ -30,10 +31,10 @@ class InsertWhenAllDone : public InsertProxyModel } return true; } - bool validColumn() const Q_DECL_OVERRIDE + bool validColumn() const Q_DECL_OVERRIDE { if (!sourceModel()) - return false; + return false; const int sourceCols = sourceModel()->columnCount(); const int sourceRows = sourceModel()->rowCount(); for (int i = 0; i < sourceRows; ++i) { @@ -49,7 +50,7 @@ int main(int argc, char *argv[]) QApplication a(argc, argv); QWidget mainWid; mainWid.setWindowTitle(QStringLiteral("InsertProxyModel Example Using validRow/validColumn")); - QStandardItemModel* baseModel = new QStandardItemModel(&mainWid); + QStandardItemModel *baseModel = new QStandardItemModel(&mainWid); // fill the model with example data baseModel->insertColumns(0, 3); baseModel->insertRows(0, 5); @@ -58,36 +59,37 @@ int main(int argc, char *argv[]) baseModel->setData(baseModel->index(i, j), QStringLiteral("%1;%2").arg(i + 1).arg(j + 1)); } // Create the proxy and assign the source - InsertWhenAllDone* insertProxy = new InsertWhenAllDone(&mainWid); + InsertWhenAllDone *insertProxy = new InsertWhenAllDone(&mainWid); insertProxy->setSourceModel(baseModel); - + // Create a combobox to select the insert direction - QComboBox* insertDirectionCombo = new QComboBox(&mainWid); + QComboBox *insertDirectionCombo = new QComboBox(&mainWid); insertDirectionCombo->addItem("No Insertion", InsertProxyModel::NoInsert); insertDirectionCombo->addItem("Insert Rows", InsertProxyModel::InsertRow); insertDirectionCombo->addItem("Insert Columns", InsertProxyModel::InsertColumn); insertDirectionCombo->addItem("Insert Both", int(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow)); - QObject::connect(insertDirectionCombo, static_cast(&QComboBox::currentIndexChanged), [insertProxy, insertDirectionCombo](int idx)->void { - const auto newDirection = InsertProxyModel::InsertDirection(insertDirectionCombo->itemData(idx).toInt()); - insertProxy->setInsertDirection(newDirection); - }); + QObject::connect(insertDirectionCombo, static_cast(&QComboBox::currentIndexChanged), + [insertProxy, insertDirectionCombo](int idx) -> void { + const auto newDirection = InsertProxyModel::InsertDirection(insertDirectionCombo->itemData(idx).toInt()); + insertProxy->setInsertDirection(newDirection); + }); // Create a view for the base model and a view for the proxy - QTableView* baseView = new QTableView(&mainWid); + QTableView *baseView = new QTableView(&mainWid); baseView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); baseView->setEditTriggers(QTableView::NoEditTriggers); baseView->setModel(baseModel); - QTableView* proxyView = new QTableView(&mainWid); + QTableView *proxyView = new QTableView(&mainWid); proxyView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); proxyView->setModel(insertProxy); // lay out the widget - QHBoxLayout* topLay = new QHBoxLayout; + QHBoxLayout *topLay = new QHBoxLayout; topLay->addWidget(new QLabel(QStringLiteral("Insert Direction"), &mainWid)); topLay->addWidget(insertDirectionCombo); topLay->addStretch(); - QGridLayout* mainLay = new QGridLayout(&mainWid); + QGridLayout *mainLay = new QGridLayout(&mainWid); mainLay->addLayout(topLay, 0, 0, 1, 2); mainLay->addWidget(baseView, 1, 0); mainLay->addWidget(proxyView, 1, 1); diff --git a/examples/exam_ModelSerialisation/CMakeLists.txt b/examples/exam_ModelSerialisation/CMakeLists.txt new file mode 100644 index 0000000..d019c8e --- /dev/null +++ b/examples/exam_ModelSerialisation/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.2) +project(exam_ModelSerialisation LANGUAGES CXX) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets REQUIRED) + +add_executable(exam_ModelSerialisation WIN32 exam_modelserialisation.cpp) +if(BUILD_STATIC_LIBS) + target_compile_definitions(exam_ModelSerialisation PRIVATE MODELUTILITIES_STATIC) +endif() +target_include_directories(exam_ModelSerialisation PRIVATE ../../src ../../src/includes) +target_link_libraries(exam_ModelSerialisation PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets modelutilities) +set_target_properties(exam_ModelSerialisation PROPERTIES + AUTOMOC ON + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON + VERSION "1.0" + EXPORT_NAME "examModelSerialisation" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin/examples" +) +install(TARGETS exam_ModelSerialisation + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) diff --git a/examples/exam_ModelSerialisation/exam_modelserialisation.cpp b/examples/exam_ModelSerialisation/exam_modelserialisation.cpp new file mode 100644 index 0000000..4f55910 --- /dev/null +++ b/examples/exam_ModelSerialisation/exam_modelserialisation.cpp @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + // prepare a dummy tree model + QPixmap bluePix(20, 20); + bluePix.fill(Qt::blue); + QStandardItemModel baseModel; + baseModel.insertRows(0, 3); + baseModel.insertColumns(0, 2); + baseModel.setHeaderData(0, Qt::Horizontal, "Col1"); + baseModel.setHeaderData(1, Qt::Horizontal, "Col2"); + baseModel.setHeaderData(0, Qt::Vertical, "Row1"); + baseModel.setHeaderData(1, Qt::Vertical, "Row2"); + baseModel.setHeaderData(2, Qt::Vertical, "Row3"); + baseModel.setData(baseModel.index(0, 0), "0.0"); + baseModel.setData(baseModel.index(0, 0), bluePix, Qt::DecorationRole); + baseModel.setData(baseModel.index(0, 1), "0.1"); + baseModel.setData(baseModel.index(1, 0), "1.0"); + baseModel.setData(baseModel.index(1, 1), "1.1"); + baseModel.setData(baseModel.index(2, 0), "2.0"); + baseModel.setData(baseModel.index(2, 1), "2.1 A \"very, \"odd\" one < to \"\"\"\"\" man&age"); + baseModel.insertRows(0, 2, baseModel.index(0, 0)); + baseModel.insertColumns(0, 2, baseModel.index(0, 0)); + baseModel.setData(baseModel.index(0, 0, baseModel.index(0, 0)), "0.0.0"); + baseModel.setData(baseModel.index(0, 1, baseModel.index(0, 0)), "0.0.1"); + baseModel.setData(baseModel.index(1, 0, baseModel.index(0, 0)), "0.1.0"); + baseModel.setData(baseModel.index(1, 1, baseModel.index(0, 0)), "0.1.1"); + QStandardItemModel loadedModel; + + QWidget mainWidget; + // selector for the format to use to serialise + QComboBox *formatSelector = new QComboBox(&mainWidget); + // displays our dummy model that will be saved to file + QTreeView *savedView = new QTreeView(&mainWidget); + savedView->setModel(&baseModel); + // displays the model that gets loaded from file + QTreeView *loadedView = new QTreeView(&mainWidget); + loadedView->setModel(&loadedModel); + loadedView->setEditTriggers(QAbstractItemView::NoEditTriggers); + // displays the content of the saved file as rich text (Qt HTML) + QTextBrowser *savedViewer = new QTextBrowser(&mainWidget); + // displays the content of the saved file as plain text + QPlainTextEdit *sourceViewer = new QPlainTextEdit(&mainWidget); + sourceViewer->setReadOnly(true); + QGridLayout *mainLay = new QGridLayout(&mainWidget); + mainLay->addWidget(new QLabel(QStringLiteral("Select Format"), &mainWidget), 0, 0); + mainLay->addWidget(formatSelector, 0, 1); + mainLay->addWidget(new QLabel(QStringLiteral("Saved Model"), &mainWidget), 1, 0); + mainLay->addWidget(new QLabel(QStringLiteral("Loaded Model"), &mainWidget), 1, 1); + mainLay->addWidget(new QLabel(QStringLiteral("Saved File, Rich Text"), &mainWidget), 1, 2); + mainLay->addWidget(new QLabel(QStringLiteral("Saved File, Plain Text"), &mainWidget), 1, 3); + mainLay->addWidget(savedView, 2, 0); + mainLay->addWidget(loadedView, 2, 1); + mainLay->addWidget(savedViewer, 2, 2); + mainLay->addWidget(sourceViewer, 2, 3); + // save and load the model when the combo changes + QObject::connect(formatSelector, static_cast(&QComboBox::currentIndexChanged), + [&mainWidget, &baseModel, &loadedModel, sourceViewer, savedViewer](int idx) -> void { + AbstractModelSerialiser *serial = nullptr; + switch (idx) { + case 0: + serial = new BinaryModelSerialiser; + break; + case 1: + serial = new XmlModelSerialiser; + break; + case 2: + serial = new HtmlModelSerialiser; + break; + case 3: + serial = new CsvModelSerialiser; + break; + case 4: + serial = new JsonModelSerialiser; + break; + default: + Q_UNREACHABLE(); + } + serial->setModel(&baseModel); // set the model to save + QTemporaryFile tempFile("TestSave"); // prepare the file to save + if (tempFile.exists()) + tempFile.remove(); + if (!tempFile.open()) // open the file + QMessageBox::critical(&mainWidget, QStringLiteral("Error"), QStringLiteral("Impossible to open the file")); + if (!serial->saveModel(&tempFile)) // save the model to fille + QMessageBox::critical(&mainWidget, QStringLiteral("Error"), QStringLiteral("Model Saving Failed")); + tempFile.seek(0); // go back to the beginning of the file + serial->setModel(&loadedModel); // set the model to load + if (!serial->loadModel(&tempFile)) // load the model + QMessageBox::critical(&mainWidget, QStringLiteral("Error"), QStringLiteral("Model Loading Failed")); + tempFile.seek(0); // go back to the beginning of the file + sourceViewer->setPlainText(QTextStream(&tempFile).readAll()); // load the plain text + savedViewer->setSource(QUrl::fromLocalFile(QFileInfo(tempFile).absoluteFilePath())); // load the rich text (Qt HTML) + serial->deleteLater(); + }); + formatSelector->addItems( + QStringList{QStringLiteral("Binary"), QStringLiteral("XML"), QStringLiteral("HTML"), QStringLiteral("CSV"), QStringLiteral("JSON")}); + mainWidget.show(); + return a.exec(); +} diff --git a/examples/exam_RoleMaskProxyModel/CMakeLists.txt b/examples/exam_RoleMaskProxyModel/CMakeLists.txt new file mode 100644 index 0000000..b903444 --- /dev/null +++ b/examples/exam_RoleMaskProxyModel/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.5) +project(exam_RoleMaskProxyModel LANGUAGES CXX) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Sql) + +set(exam_RoleMaskProxyModel_EditableSQL_SRCS + exam_rolemaskeditablesql.cpp + dbresource.qrc +) +add_executable(exam_RoleMaskProxyModel_Highlight WIN32 exam_rolemaskhighlight.cpp) + +if(BUILD_STATIC_LIBS) + target_compile_definitions(exam_RoleMaskProxyModel_Highlight PRIVATE MODELUTILITIES_STATIC) +endif() + +target_include_directories(exam_RoleMaskProxyModel_Highlight PRIVATE ../../src ../../src/includes) +target_link_libraries(exam_RoleMaskProxyModel_Highlight PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets modelutilities) +set_target_properties(exam_RoleMaskProxyModel_Highlight PROPERTIES + AUTOMOC ON + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON + VERSION "1.0" + EXPORT_NAME "examRoleMaskProxyModelHighlight" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin/examples" +) +install(TARGETS exam_RoleMaskProxyModel_Highlight + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +if("${Qt${QT_VERSION_MAJOR}Sql_FOUND}") + add_executable(exam_RoleMaskProxyModel_EditableSQL WIN32 exam_rolemaskeditablesql.cpp dbresource.qrc) + target_include_directories(exam_RoleMaskProxyModel_EditableSQL PRIVATE ../../src ../../src/includes) + target_link_libraries(exam_RoleMaskProxyModel_EditableSQL PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql modelutilities) + set_target_properties(exam_RoleMaskProxyModel_EditableSQL PROPERTIES + AUTOMOC ON + AUTORCC ON + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON + VERSION "1.0" + SOVERSION 1 + EXPORT_NAME "examRoleMaskProxyModelEditableSQL" + ARCHIVE_OUTPUT_DIRECTORY "../../${modelutilities_PlatformDir}/lib/examples" + LIBRARY_OUTPUT_DIRECTORY "../../${modelutilities_PlatformDir}/lib/examples" + RUNTIME_OUTPUT_DIRECTORY "../../${modelutilities_PlatformDir}/bin/examples" + ) + install(TARGETS exam_RoleMaskProxyModel_EditableSQL + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) +endif() diff --git a/examples/exam_RoleMaskProxyModel/dbresource.qrc b/examples/exam_RoleMaskProxyModel/dbresource.qrc new file mode 100644 index 0000000..585c64b --- /dev/null +++ b/examples/exam_RoleMaskProxyModel/dbresource.qrc @@ -0,0 +1,5 @@ + + + exampledb.sqlite3 + + diff --git a/examples/exam_RoleMaskProxyModel/exam_rolemaskeditablesql.cpp b/examples/exam_RoleMaskProxyModel/exam_rolemaskeditablesql.cpp new file mode 100644 index 0000000..b0c3968 --- /dev/null +++ b/examples/exam_RoleMaskProxyModel/exam_rolemaskeditablesql.cpp @@ -0,0 +1,72 @@ +// This example will demonstrate how to make a QSqlQueryModel editable using RoleMaskProxyModel. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// We subclass RoleMaskProxyModel and reimplement the flags() method to allow editing +class EditableFlagMask : public RoleMaskProxyModel +{ + Q_DISABLE_COPY(EditableFlagMask) +public: + explicit EditableFlagMask(QObject *parent = Q_NULLPTR) + : RoleMaskProxyModel(parent) + { } + Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE { return RoleMaskProxyModel::flags(index) | Qt::ItemIsEditable; } +}; + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + // we copy the dummy sqlite db from the program resources to a temporary file + const QString dbPath = QDir::tempPath() + "/exampledb.sqlite3"; + QFile::copy(QStringLiteral(":/db/exampledb.sqlite3"), dbPath); + // open the sqlite db + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName(dbPath); + if (!db.open()) { + qDebug("Unable to open Db"); + QFile::remove(dbPath); + return 1; + } + + // we load a QSqlQueryModel from the dummy db + QSqlQueryModel baseModel; + baseModel.setQuery(QStringLiteral("select * from Persons"), db); + + // we instantiate our RoleMaskProxyModel subclass + EditableFlagMask proxyModel; + // we make Qt::DisplayRole equivalent to Qt::EditRole, this is the default in all of Qt's models + proxyModel.setMergeDisplayEdit(true); + // if the proxy did not overwrite the data we set it to show the data in the source model + proxyModel.setTransparentIfEmpty(true); + // we intercept all the changes in the Qt::DisplayRole in the proxy model rather than passing them on to the source model + proxyModel.addMaskedRole(Qt::DisplayRole); + // we set the source model + proxyModel.setSourceModel(&baseModel); + + // we create the ui to show both the source and the proxy model in 2 table views + QWidget mainWid; + mainWid.setWindowTitle(QStringLiteral("RoleMaskProxyModel Example - Editable SqlQueryModel")); + QTableView *sourceView = new QTableView(&mainWid); + sourceView->setModel(&baseModel); + sourceView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + QTableView *proxyView = new QTableView(&mainWid); + proxyView->setModel(&proxyModel); + proxyView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + QGridLayout *mainLay = new QGridLayout(&mainWid); + mainLay->addWidget(new QLabel(QStringLiteral("Original Model")), 0, 0); + mainLay->addWidget(sourceView, 1, 0); + mainLay->addWidget(new QLabel(QStringLiteral("Editable Model")), 0, 1); + mainLay->addWidget(proxyView, 1, 1); + mainWid.show(); + const int appRes = app.exec(); + QFile::remove(dbPath); + return appRes; +} \ No newline at end of file diff --git a/examples/exam_RoleMaskProxyModel/exam_rolemaskhighlight.cpp b/examples/exam_RoleMaskProxyModel/exam_rolemaskhighlight.cpp new file mode 100644 index 0000000..b4c6c18 --- /dev/null +++ b/examples/exam_RoleMaskProxyModel/exam_rolemaskhighlight.cpp @@ -0,0 +1,252 @@ +// This example will demonstrate how to use RoleMaskProxyModel to leverage roles not supported by the source model. +// In the specific we will use Qt::BackgroundRole on a QStringListModel to highlight all elements that contain a certain string + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + // Create the source model, in this case a list of countries + QStringListModel baseModel(QStringList({"Afghanistan", + "Albania", + "Algeria", + "Andorra", + "Angola", + "Anguilla", + "Antigua & Barbuda", + "Argentina", + "Armenia", + "Australia", + "Austria", + "Azerbaijan", + "Bahamas", + "Bahrain", + "Bangladesh", + "Barbados", + "Belarus", + "Belgium", + "Belize", + "Benin", + "Bermuda", + "Bhutan", + "Bolivia", + "Bosnia & Herzegovina", + "Botswana", + "Brazil", + "Brunei Darussalam", + "Bulgaria", + "Burkina Faso", + "Myanmar/Burma", + "Burundi", + "Cambodia", + "Cameroon", + "Canada", + "Cape Verde", + "Cayman Islands", + "Central African Republic", + "Chad", + "Chile", + "China", + "Colombia", + "Comoros", + "Congo", + "Costa Rica", + "Croatia", + "Cuba", + "Cyprus", + "Czech Republic", + "Democratic Republic of the Congo", + "Denmark", + "Djibouti", + "Dominica", + "Dominican Republic", + "Ecuador", + "Egypt", + "El Salvador", + "Equatorial Guinea", + "Eritrea", + "Estonia", + "Ethiopia", + "Fiji", + "Finland", + "France", + "French Guiana", + "Gabon", + "Gambia", + "Georgia", + "Germany", + "Ghana", + "Great Britain", + "Greece", + "Grenada", + "Guadeloupe", + "Guatemala", + "Guinea", + "Guinea-Bissau", + "Guyana", + "Haiti", + "Honduras", + "Hungary", + "Iceland", + "India", + "Indonesia", + "Iran", + "Iraq", + "Israel and the Occupied Territories", + "Italy", + "Ivory Coast (Cote d'Ivoire)", + "Jamaica", + "Japan", + "Jordan", + "Kazakhstan", + "Kenya", + "Kosovo", + "Kuwait", + "Kyrgyz Republic (Kyrgyzstan)", + "Laos", + "Latvia", + "Lebanon", + "Lesotho", + "Liberia", + "Libya", + "Liechtenstein", + "Lithuania", + "Luxembourg", + "Republic of Macedonia", + "Madagascar", + "Malawi", + "Malaysia", + "Maldives", + "Mali", + "Malta", + "Martinique", + "Mauritania", + "Mauritius", + "Mayotte", + "Mexico", + "Moldova, Republic of", + "Monaco", + "Mongolia", + "Montenegro", + "Montserrat", + "Morocco", + "Mozambique", + "Namibia", + "Nepal", + "Netherlands", + "New Zealand", + "Nicaragua", + "Niger", + "Nigeria", + "Korea, Democratic Republic of (North Korea)", + "Norway", + "Oman", + "Pacific Islands", + "Pakistan", + "Panama", + "Papua New Guinea", + "Paraguay", + "Peru", + "Philippines", + "Poland", + "Portugal", + "Puerto Rico", + "Qatar", + "Reunion", + "Romania", + "Russian Federation", + "Rwanda", + "Saint Kitts and Nevis", + "Saint Lucia", + "Saint Vincent's & Grenadines", + "Samoa", + "Sao Tome and Principe", + "Saudi Arabia", + "Senegal", + "Serbia", + "Seychelles", + "Sierra Leone", + "Singapore", + "Slovak Republic (Slovakia)", + "Slovenia", + "Solomon Islands", + "Somalia", + "South Africa", + "Korea, Republic of (South Korea)", + "South Sudan", + "Spain", + "Sri Lanka", + "Sudan", + "Suriname", + "Swaziland", + "Sweden", + "Switzerland", + "Syria", + "Tajikistan", + "Tanzania", + "Thailand", + "Timor Leste", + "Togo", + "Trinidad & Tobago", + "Tunisia", + "Turkey", + "Turkmenistan", + "Turks & Caicos Islands", + "Uganda", + "Ukraine", + "United Arab Emirates", + "United States of America (USA)", + "Uruguay", + "Uzbekistan", + "Venezuela", + "Vietnam", + "Virgin Islands (UK)", + "Virgin Islands (US)", + "Yemen", + "Zambia", + "Zimbabwe"})); + + // create the proxy model + RoleMaskProxyModel highlightProxy; + // set the source model + highlightProxy.setSourceModel(&baseModel); + // we will intercept all data in Qt::BackgroundRole and handle it in the proxy rather than passing it on to the source model + highlightProxy.addMaskedRole(Qt::BackgroundRole); + // we create a display the ui + // it will contain a list view with the contents of our proxy model and a line edit + QWidget mainWid; + mainWid.setWindowTitle(QStringLiteral("RoleMaskProxyModel Example - Highlighted QStringListModel")); + QLineEdit *highlighterEdit = new QLineEdit(&mainWid); + QListView *mainView = new QListView(&mainWid); + mainView->setModel(&highlightProxy); + QVBoxLayout *mainLay = new QVBoxLayout(&mainWid); + mainLay->addWidget(new QLabel(QStringLiteral("Highlight cells containing:"))); + mainLay->addWidget(highlighterEdit); + mainLay->addWidget(mainView); + mainWid.show(); + + // we implement the highlighting functionality + QObject::connect(highlighterEdit, &QLineEdit::textEdited, [&highlightProxy](const QString &filterString) { + // every time the lineedit text changes + for (int i = 0; i < highlightProxy.rowCount(); ++i) { + // iterate over all elements of the model + const QModelIndex currentIdx = highlightProxy.index(i, 0); + // if the element contains the input string + if (!filterString.isEmpty() && currentIdx.data().toString().contains(filterString, Qt::CaseInsensitive)) { + // highlight it + highlightProxy.setData(currentIdx, QColor(Qt::yellow), Qt::BackgroundRole); + } else { + // otherwise reset the background color + highlightProxy.setData(currentIdx, QVariant(), Qt::BackgroundRole); + } + } + }); + + return app.exec(); +} \ No newline at end of file diff --git a/examples/exam_RoleMaskProxyModel/exampledb.sqlite3 b/examples/exam_RoleMaskProxyModel/exampledb.sqlite3 new file mode 100644 index 0000000..81263ed Binary files /dev/null and b/examples/exam_RoleMaskProxyModel/exampledb.sqlite3 differ diff --git a/examples/exem_InsertProxyModel/CMakeLists.txt b/examples/exem_InsertProxyModel/CMakeLists.txt deleted file mode 100644 index 1cef181..0000000 --- a/examples/exem_InsertProxyModel/CMakeLists.txt +++ /dev/null @@ -1,68 +0,0 @@ -cmake_minimum_required(VERSION 3.3) -if(POLICY CMP0025) - cmake_policy(SET CMP0025 NEW) -endif() -if(POLICY CMP0048) - cmake_policy(SET CMP0048 NEW) -endif() -if(POLICY CMP0057) - cmake_policy(SET CMP0057 NEW) -endif() -set (CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_AUTOMOC ON) -project(exem_InsertProxyModel VERSION "1.0") -set(REQUIRED_QT_VERSION 5.0.0) -find_package(Qt5Core ${REQUIRED_QT_VERSION} REQUIRED) -find_package(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED) -find_package(Qt5Widgets ${REQUIRED_QT_VERSION} REQUIRED) - -set(exem_InsertProxyModel_LIBS ${exem_InsertProxyModel_LIBS} ${Qt5Core_LIBRARIES}) -set(exem_InsertProxyModel_INCLUDE ${exem_InsertProxyModel_INCLUDE} ${Qt5Core_INCLUDE_DIRS}) -set(exem_InsertProxyModel_COMPILE_DEFINE ${exem_InsertProxyModel_COMPILE_DEFINE} ${Qt5Core_COMPILE_DEFINITIONS} ) - -set(exem_InsertProxyModel_LIBS ${exem_InsertProxyModel_LIBS} ${Qt5Gui_LIBRARIES}) -set(exem_InsertProxyModel_INCLUDE ${exem_InsertProxyModel_INCLUDE} ${Qt5Gui_INCLUDE_DIRS}) -set(exem_InsertProxyModel_COMPILE_DEFINE ${exem_InsertProxyModel_COMPILE_DEFINE} ${Qt5Gui_COMPILE_DEFINITIONS}) - -set(exem_InsertProxyModel_LIBS ${exem_InsertProxyModel_LIBS} ${Qt5Widgets_LIBRARIES}) -set(exem_InsertProxyModel_INCLUDE ${exem_InsertProxyModel_INCLUDE} ${Qt5Widgets_INCLUDE_DIRS}) -set(exem_InsertProxyModel_COMPILE_DEFINE ${exem_InsertProxyModel_COMPILE_DEFINE} ${Qt5Widgets_COMPILE_DEFINITIONS}) - -set(exem_InsertProxyModel_INCLUDE ${exem_InsertProxyModel_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}) -set(exem_InsertProxyModel_Subclass_SRCS - exem_inserproxysubclass.cpp -) -set(exem_InsertProxyModel_Commit_SRCS - exem_inserproxycommit.cpp -) -add_executable(exem_InsertProxyModel_Subclass WIN32 ${exem_InsertProxyModel_Subclass_SRCS}) -add_executable(exem_InsertProxyModel_Commit WIN32 ${exem_InsertProxyModel_Commit_SRCS}) -if(BUILD_STATIC_LIBS) - set(exem_InsertProxyModel_DEFINE ${exem_InsertProxyModel_DEFINE} MODELUTILITIES_STATIC) -endif() -add_dependencies(exem_InsertProxyModel_Subclass modelutilities) -add_dependencies(exem_InsertProxyModel_Commit modelutilities) -target_include_directories(exem_InsertProxyModel_Subclass PRIVATE ../../src ../../src/includes ${exem_InsertProxyModel_INCLUDE}) -target_link_libraries(exem_InsertProxyModel_Subclass PRIVATE modelutilities ${exem_InsertProxyModel_LIBS}) -target_compile_definitions(exem_InsertProxyModel_Subclass PRIVATE ${exem_InsertProxyModel_DEFINE}) -set_target_properties(exem_InsertProxyModel_Subclass PROPERTIES - VERSION "1.0" - SOVERSION 1 - EXPORT_NAME "exemInsertProxyModelSubclass" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin/examples" -) -target_include_directories(exem_InsertProxyModel_Commit PRIVATE ../../src ../../src/includes ${exem_InsertProxyModel_INCLUDE}) -target_link_libraries(exem_InsertProxyModel_Commit PRIVATE modelutilities ${exem_InsertProxyModel_LIBS}) -target_compile_definitions(exem_InsertProxyModel_Commit PRIVATE ${exem_InsertProxyModel_DEFINE}) -set_target_properties(exem_InsertProxyModel_Commit PROPERTIES - VERSION "1.0" - SOVERSION 1 - EXPORT_NAME "exemInsertProxyModelCommit" - ARCHIVE_OUTPUT_DIRECTORY "../../${modelutilities_PlatformDir}/lib/examples" - LIBRARY_OUTPUT_DIRECTORY "../../${modelutilities_PlatformDir}/lib/examples" - RUNTIME_OUTPUT_DIRECTORY "../../${modelutilities_PlatformDir}/bin/examples" -) diff --git a/examples/exem_ModelSerialisation/CMakeLists.txt b/examples/exem_ModelSerialisation/CMakeLists.txt deleted file mode 100644 index a89e0ee..0000000 --- a/examples/exem_ModelSerialisation/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -cmake_minimum_required(VERSION 3.3) -if(POLICY CMP0025) - cmake_policy(SET CMP0025 NEW) -endif() -if(POLICY CMP0048) - cmake_policy(SET CMP0048 NEW) -endif() -if(POLICY CMP0057) - cmake_policy(SET CMP0057 NEW) -endif() -set (CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_AUTOMOC ON) -project(exem_ModelSerialisation VERSION "1.0") -set(REQUIRED_QT_VERSION 5.0.0) -find_package(Qt5Core ${REQUIRED_QT_VERSION} REQUIRED) -find_package(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED) -find_package(Qt5Widgets ${REQUIRED_QT_VERSION} REQUIRED) - -set(exem_ModelSerialisation_LIBS ${exem_ModelSerialisation_LIBS} ${Qt5Core_LIBRARIES}) -set(exem_ModelSerialisation_INCLUDE ${exem_ModelSerialisation_INCLUDE} ${Qt5Core_INCLUDE_DIRS}) -set(exem_ModelSerialisation_COMPILE_DEFINE ${exem_ModelSerialisation_COMPILE_DEFINE} ${Qt5Core_COMPILE_DEFINITIONS} ) - -set(exem_ModelSerialisation_LIBS ${exem_ModelSerialisation_LIBS} ${Qt5Gui_LIBRARIES}) -set(exem_ModelSerialisation_INCLUDE ${exem_ModelSerialisation_INCLUDE} ${Qt5Gui_INCLUDE_DIRS}) -set(exem_ModelSerialisation_COMPILE_DEFINE ${exem_ModelSerialisation_COMPILE_DEFINE} ${Qt5Gui_COMPILE_DEFINITIONS}) - -set(exem_ModelSerialisation_LIBS ${exem_ModelSerialisation_LIBS} ${Qt5Widgets_LIBRARIES}) -set(exem_ModelSerialisation_INCLUDE ${exem_ModelSerialisation_INCLUDE} ${Qt5Widgets_INCLUDE_DIRS}) -set(exem_ModelSerialisation_COMPILE_DEFINE ${exem_ModelSerialisation_COMPILE_DEFINE} ${Qt5Widgets_COMPILE_DEFINITIONS}) - -set(exem_ModelSerialisation_INCLUDE ${exem_ModelSerialisation_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}) -set(exem_ModelSerialisation_SRCS - exem_modelserialisation.cpp -) -add_executable(exem_ModelSerialisation WIN32 ${exem_ModelSerialisation_SRCS}) -if(BUILD_STATIC_LIBS) - set(exem_ModelSerialisation_DEFINE ${exem_ModelSerialisation_DEFINE} MODELUTILITIES_STATIC) -endif() -add_dependencies(exem_ModelSerialisation modelutilities) -target_include_directories(exem_ModelSerialisation PRIVATE ../../src ../../src/includes ${exem_ModelSerialisation_INCLUDE}) -target_link_libraries(exem_ModelSerialisation PRIVATE modelutilities ${exem_ModelSerialisation_LIBS}) -target_compile_definitions(exem_ModelSerialisation PRIVATE ${exem_ModelSerialisation_DEFINE}) -set_target_properties(exem_ModelSerialisation PROPERTIES - VERSION "1.0" - SOVERSION 1 - EXPORT_NAME "exemModelSerialisation" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/examples" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin/examples" -) diff --git a/examples/exem_ModelSerialisation/exem_modelserialisation.cpp b/examples/exem_ModelSerialisation/exem_modelserialisation.cpp deleted file mode 100644 index 0fcc00a..0000000 --- a/examples/exem_ModelSerialisation/exem_modelserialisation.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - - // prepare a dummy tree model - QPixmap bluePix(20, 20); - bluePix.fill(Qt::blue); - QStandardItemModel baseModel; - baseModel.insertRows(0, 3); - baseModel.insertColumns(0, 2); - baseModel.setHeaderData(0, Qt::Horizontal, "Col1"); - baseModel.setHeaderData(1, Qt::Horizontal, "Col2"); - baseModel.setData(baseModel.index(0, 0), "0.0"); - baseModel.setData(baseModel.index(0, 0), bluePix, Qt::DecorationRole); - baseModel.setData(baseModel.index(0, 1), "0.1"); - baseModel.setData(baseModel.index(1, 0), "1.0"); - baseModel.setData(baseModel.index(1, 1), "1.1"); - baseModel.setData(baseModel.index(2, 0), "2.0"); - baseModel.setData(baseModel.index(2, 1), "2.1 A \"very, \"odd\" one < to \"\"\"\"\" man&age"); - baseModel.insertRows(0, 2, baseModel.index(0, 0)); - baseModel.insertColumns(0, 2, baseModel.index(0, 0)); - baseModel.setData(baseModel.index(0, 0, baseModel.index(0, 0)), "0.0.0"); - baseModel.setData(baseModel.index(0, 1, baseModel.index(0, 0)), "0.0.1"); - baseModel.setData(baseModel.index(1, 0, baseModel.index(0, 0)), "0.1.0"); - baseModel.setData(baseModel.index(1, 1, baseModel.index(0, 0)), "0.1.1"); - QStandardItemModel loadedModel; - - QWidget mainWidget; - // selector for the format to use to serialise - QComboBox* formatSelector = new QComboBox(&mainWidget); - // displays our dummy model that will be saved to file - QTreeView* savedView = new QTreeView(&mainWidget); - savedView->setModel(&baseModel); - // displays the model that gets loaded from file - QTreeView* loadedView = new QTreeView(&mainWidget); - loadedView->setModel(&loadedModel); - // displays the content of the saved file as rich text (Qt HTML) - QTextBrowser* savedViewer = new QTextBrowser(&mainWidget); - // displays the content of the saved file as plain text - QPlainTextEdit* sourceViewer = new QPlainTextEdit(&mainWidget); - sourceViewer->setReadOnly(true); - QGridLayout* mainLay = new QGridLayout(&mainWidget); - mainLay->addWidget(new QLabel(QStringLiteral("Select Format"), &mainWidget), 0, 0); - mainLay->addWidget(formatSelector, 0, 1); - mainLay->addWidget(new QLabel(QStringLiteral("Saved Model"), &mainWidget), 1, 0); - mainLay->addWidget(new QLabel(QStringLiteral("Loaded Model"), &mainWidget), 1, 1); - mainLay->addWidget(new QLabel(QStringLiteral("Saved File, Rich Text"), &mainWidget), 1, 2); - mainLay->addWidget(new QLabel(QStringLiteral("Saved File, Plain Text"), &mainWidget), 1, 3); - mainLay->addWidget(savedView, 2, 0); - mainLay->addWidget(loadedView, 2, 1); - mainLay->addWidget(savedViewer, 2, 2); - mainLay->addWidget(sourceViewer, 2, 3); - // save and load the model when the combo changes - QObject::connect(formatSelector, static_cast(&QComboBox::currentIndexChanged), [&mainWidget, &baseModel, &loadedModel, sourceViewer, savedViewer](int idx)->void { - AbstractModelSerialiser* serial=nullptr; - switch (idx){ - case 0: - serial = new BinaryModelSerialiser; - break; - case 1: - serial = new XmlModelSerialiser; - break; - case 2: - serial = new HtmlModelSerialiser; - break; - case 3: - serial = new CsvModelSerialiser; - break; - default: - Q_UNREACHABLE(); - } - serial->setModel(&baseModel); // set the model to save - QTemporaryFile tempFile("TestSave"); // prepare the file to save - if (tempFile.exists()) - tempFile.remove(); - if (!tempFile.open()) // open the file - QMessageBox::critical(&mainWidget, QStringLiteral("Error"), QStringLiteral("Impossible to open the file")); - if (!serial->saveModel(&tempFile)) // save the model to fille - QMessageBox::critical(&mainWidget, QStringLiteral("Error"), QStringLiteral("Model Saving Failed")); - tempFile.seek(0); // go back to the beginning of the file - serial->setModel(&loadedModel); // set the model to load - if (!serial->loadModel(&tempFile)) // load the model - QMessageBox::critical(&mainWidget, QStringLiteral("Error"), QStringLiteral("Model Loading Failed")); - tempFile.seek(0); // go back to the beginning of the file - sourceViewer->setPlainText(QTextStream(&tempFile).readAll()); // load the plain text - savedViewer->setSource(QUrl::fromLocalFile(QFileInfo(tempFile).absoluteFilePath())); // load the rich text (Qt HTML) - delete serial; - }); - formatSelector->addItems(QStringList() << QStringLiteral("Binary") << QStringLiteral("XML") << QStringLiteral("HTML") << QStringLiteral("CSV")); - mainWidget.show(); - return a.exec(); -} \ No newline at end of file diff --git a/modelutilities-config.cmake b/modelutilities-config.cmake deleted file mode 100644 index 0098ec2..0000000 --- a/modelutilities-config.cmake +++ /dev/null @@ -1,2 +0,0 @@ -get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) -include(${SELF_DIR}/modelutilitiesBinary.cmake) \ No newline at end of file diff --git a/modelutilities.pri b/modelutilities.pri deleted file mode 100644 index 4a0b936..0000000 --- a/modelutilities.pri +++ /dev/null @@ -1,10 +0,0 @@ -SOURCES += $$files($$PWD/src/*.cpp) -HEADERS += $$files($$PWD/src/*.h) -HEADERS += $$files($$PWD/src/*.hpp) -SOURCES += $$files($$PWD/src/private/*.cpp) -HEADERS += $$files($$PWD/src/private/*.h) -HEADERS += $$files($$PWD/src/private/*.hpp) -DEFINES += MODELUTILITIES_STATIC -INCLUDEPATH += $$PWD/src -INCLUDEPATH += $$PWD/src/includes -INCLUDEPATH += $$PWD/src/private \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..df95368 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,159 @@ +cmake_minimum_required(VERSION 3.2) +project(ModelUtilitiesLib LANGUAGES CXX) + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Gui) +message(STATUS "Found Qt Core ${Qt5Core_VERSION}") +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +if(NOT "${Qt${QT_VERSION_MAJOR}Gui_FOUND}") + set(NO_GUI ON) +endif() +if(NOT "${Qt${QT_VERSION_MAJOR}Widgets_FOUND}") + set(NO_WIDGETS ON) +endif() +if(NOT NO_GUI) + message(STATUS "Found Qt Gui ${Qt${QT_VERSION_MAJOR}Gui_VERSION}") +else() + set(NO_WIDGETS ON) +endif() +if(NOT NO_WIDGETS) + message(STATUS "Found Qt Widgets ${Qt${QT_VERSION_MAJOR}Widgets_VERSION}") +endif() +if(BUILD_DOCS) + find_package(Doxygen OPTIONAL_COMPONENTS mscgen dia dot) + if(DOXYGEN_FOUND) + message(STATUS "Found Doxygen ${DOXYGEN_VERSION}") + else() + set(BUILD_DOCS OFF) + endif() +endif() +set(modelutilities_SRCS modelutilities_global.h) +set(modelutilities_INSTALL_INCLUDE modelutilities_global.h) +if(BUILD_ROLEMASKPROXY) + set(rolemaskproxy_SRCS rolemaskproxymodel.cpp rolemaskproxymodel.h private/rolemaskproxymodel_p.h) + set(modelutilities_SRCS ${rolemaskproxy_SRCS} ${modelutilities_SRCS}) + set(modelutilities_INSTALL_INCLUDE rolemaskproxymodel.h includes/RoleMaskProxyModel ${modelutilities_INSTALL_INCLUDE}) + source_group(RoleMaskProxyModel FILES ${rolemaskproxy_SRCS}) +endif() +if(BUILD_INSERTPROXY) + set(insertproxy_SRCS insertproxymodel.cpp insertproxymodel.h private/insertproxymodel_p.h) + set(modelutilities_SRCS ${insertproxy_SRCS} ${modelutilities_SRCS}) + set(modelutilities_INSTALL_INCLUDE insertproxymodel.h includes/InsertProxyModel ${modelutilities_INSTALL_INCLUDE}) + source_group(InsertProxyModel FILES ${insertproxy_SRCS}) +endif() +if(BUILD_MODELSERIALISATION) + set(modelserialisation_SRCS + abstractmodelserialiser.cpp + abstractstringserialiser.cpp + abstractsingleroleserialiser.cpp + binarymodelserialiser.cpp + csvmodelserialiser.cpp + htmlmodelserialiser.cpp + jsonmodelserialiser.cpp + xmlmodelserialiser.cpp + abstractmodelserialiser.h + abstractstringserialiser.h + abstractsingleroleserialiser.h + binarymodelserialiser.h + csvmodelserialiser.h + htmlmodelserialiser.h + jsonmodelserialiser.h + xmlmodelserialiser.h + private/abstractmodelserialiser_p.h + private/abstractstringserialiser_p.h + private/abstractsingleroleserialiser_p.h + private/binarymodelserialiser_p.h + private/csvmodelserialiser_p.h + private/htmlmodelserialiser_p.h + private/jsonmodelserialiser_p.h + private/xmlmodelserialiser_p.h + ) + set(modelutilities_SRCS ${modelserialisation_SRCS} ${modelutilities_SRCS}) + set(modelutilities_INSTALL_INCLUDE + abstractmodelserialiser.h + abstractstringserialiser.h + abstractsingleroleserialiser.h + binarymodelserialiser.h + csvmodelserialiser.h + htmlmodelserialiser.h + jsonmodelserialiser.h + xmlmodelserialiser.h + includes/BinaryModelSerialiser + includes/CsvModelSerialiser + includes/HtmlModelSerialiser + includes/JsonModelSerialiser + includes/ModelSerialisers + includes/XmlModelSerialiser + ${modelutilities_INSTALL_INCLUDE} + ) + source_group(ModelSerialisation FILES ${modelserialisation_SRCS}) +endif() + +if(BUILD_STATIC_LIBS) + set(CMAKE_STATIC_LIBRARY_SUFFIX "_static${CMAKE_STATIC_LIBRARY_SUFFIX}") + add_library(modelutilities STATIC ${modelutilities_SRCS}) + target_compile_definitions(modelutilities PUBLIC MODELUTILITIES_STATIC) +else() + add_library(modelutilities SHARED ${modelutilities_SRCS}) +endif() +target_compile_definitions(modelutilities PRIVATE MODELUTILITIES_LIB) +if(OPTIMISE_FOR_MANY_ROLES) + target_compile_definitions(modelutilities PRIVATE OPTIMISE_FOR_MANY_ROLES) +endif() +target_link_libraries(modelutilities PUBLIC Qt${QT_VERSION_MAJOR}::Core) +if(NOT NO_GUI) + target_link_libraries(modelutilities PUBLIC Qt${QT_VERSION_MAJOR}::Gui) +endif() +if(NOT NO_WIDGETS) + target_link_libraries(modelutilities PUBLIC Qt${QT_VERSION_MAJOR}::Widgets) +endif() + +if(BUILD_DOCS) + set(htmlDocRedirect "Redirect

Click here if you are not redirected automatically

") + file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/docs/htmldocs.html" ${htmlDocRedirect}) + set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/DoxygenConfig.doxyfile) + add_custom_target( doc_doxygen ALL + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_IN} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM ) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs/html DESTINATION doc COMPONENT documentation) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/docs/htmldocs.html DESTINATION doc COMPONENT documentation) +endif() + +set_target_properties(modelutilities PROPERTIES + AUTOMOC ON + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON + VERSION ${VERSION_SHORT} + EXPORT_NAME "QtModelUtilities" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin" +) +include(CMakePackageConfigHelpers) +write_basic_package_version_file("QtModelUtilitiesConfigVersion.cmake" + VERSION ${VERSION_SHORT} + COMPATIBILITY SameMajorVersion +) +install(FILES ${modelutilities_INSTALL_INCLUDE} + DESTINATION include +) +install(TARGETS modelutilities + EXPORT modelutilitiesTargets + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + INCLUDES DESTINATION include +) +install(EXPORT modelutilitiesTargets + FILE QtModelUtilitiesTargets.cmake + NAMESPACE QtModelUtilities:: + DESTINATION lib/cmake/QtModelUtilities +) +install(FILES "QtModelUtilitiesConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/QtModelUtilitiesConfigVersion.cmake" + DESTINATION lib/cmake/QtModelUtilities +) + diff --git a/src/QtModelUtilitiesConfig.cmake b/src/QtModelUtilitiesConfig.cmake new file mode 100644 index 0000000..0f30fe9 --- /dev/null +++ b/src/QtModelUtilitiesConfig.cmake @@ -0,0 +1,2 @@ +include(CMakeFindDependencyMacro) +include("${CMAKE_CURRENT_LIST_DIR}/QtModelUtilitiesTargets.cmake") \ No newline at end of file diff --git a/src/abstractmodelserialiser.cpp b/src/abstractmodelserialiser.cpp index a5c2b40..dacd52b 100644 --- a/src/abstractmodelserialiser.cpp +++ b/src/abstractmodelserialiser.cpp @@ -1,247 +1,243 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #include "abstractmodelserialiser.h" #include "private/abstractmodelserialiser_p.h" -#include -#include -#include -#include -#ifdef QT_GUI_LIB -#include -#include -#include -#endif -#include -#include +#include -/*! -\class AbstractModelSerialiser - -\brief The interface for model serialisers. - -This class serve as a base for all serialisers -*/ - -AbstractModelSerialiserPrivate::AbstractModelSerialiserPrivate(AbstractModelSerialiser* q) - : q_ptr(q) +AbstractModelSerialiserPrivate::AbstractModelSerialiserPrivate(AbstractModelSerialiser *q) + : m_streamVersion(static_cast(QDataStream().version())) + , m_rolesToSave(AbstractModelSerialiser::modelDefaultRoles()) , m_model(Q_NULLPTR) , m_constModel(Q_NULLPTR) + , q_ptr(q) { Q_ASSERT(q_ptr); } -int AbstractModelSerialiserPrivate::guessDecimals(double val) -{ - int precision = 0; - for (double junk = 0; !qFuzzyIsNull(std::modf(val, &junk)); ++precision) - val *= 10.0; - return precision; -} -QString AbstractModelSerialiserPrivate::guessDecimalsString(double val, QLocale* loca) -{ - if (loca) - return loca->toString(val, 'f', guessDecimals(val)); - return QString::number(val, 'f', guessDecimals(val)); -} -QString AbstractModelSerialiserPrivate::variantToString(const QVariant& val) -{ - QString result; - QByteArray data; - QDataStream outStream(&data, QIODevice::WriteOnly); -#ifdef MS_DATASTREAM_VERSION - outStream.setVersion(MS_DATASTREAM_VERSION); -#endif // MS_DATASTREAM_VERSION - outStream << val; - data = qCompress(data); - return QString::fromLatin1(data.toBase64()); -} - -QVariant AbstractModelSerialiserPrivate::stringToVariant(const QString& val) -{ - QByteArray data = QByteArray::fromBase64(val.toLatin1()); - data = qUncompress(data); - QDataStream inStream(data); -#ifdef MS_DATASTREAM_VERSION - inStream.setVersion(MS_DATASTREAM_VERSION); -#endif // MS_DATASTREAM_VERSION - QVariant result; - inStream >> result; - return result; -} - -QVariant AbstractModelSerialiserPrivate::loadVariant(int type, const QString& val) -{ - if (val.isEmpty()) - return QVariant(); - switch (type) { - case QMetaType::UnknownType: - Q_ASSERT_X(false, "ModelSerialisation::loadVariant", "Trying to load unregistered type."); - return QVariant(); - case QMetaType::Bool: return val.toInt() == 1; - case QMetaType::Long: - case QMetaType::Int: return val.toInt(); - case QMetaType::ULong: - case QMetaType::UInt: return val.toUInt(); - case QMetaType::LongLong: return val.toLongLong(); - case QMetaType::ULongLong: return val.toULongLong(); - case QMetaType::Double: return val.toDouble(); - case QMetaType::Short: return static_cast(val.toInt()); - case QMetaType::SChar: - case QMetaType::Char: return static_cast(val.toInt()); - case QMetaType::UShort: return static_cast(val.toUInt()); - case QMetaType::UChar: return static_cast(val.toUInt()); - case QMetaType::Float: return val.toFloat(); - case QMetaType::QChar: return val.at(0); - case QMetaType::QString: return val; - case QMetaType::QByteArray: return QByteArray::fromBase64(val.toLatin1()); - case QMetaType::QDate: return QDate::fromString(val, Qt::ISODate); - case QMetaType::QTime: return QTime::fromString(val, Qt::ISODate); - case QMetaType::QDateTime: return QDateTime::fromString(val, Qt::ISODate); -#ifdef QT_GUI_LIB - case QMetaType::QImage: return loadImageVariant(type, val); - case QMetaType::QPixmap: return QPixmap::fromImage(loadImageVariant(type, val)); - case QMetaType::QBitmap: return QBitmap::fromImage(loadImageVariant(type, val)); -#endif - default: - return stringToVariant(val); - } -} -#ifdef QT_GUI_LIB -QString AbstractModelSerialiserPrivate::saveImageVariant(const QImage& imageData) -{ - QByteArray byteArray; - QBuffer buffer(&byteArray); - imageData.save(&buffer, "PNG"); - return QString::fromLatin1(byteArray.toBase64().constData()); -} -QImage AbstractModelSerialiserPrivate::loadImageVariant(int type, const QString& val) -{ - Q_UNUSED(type) - QByteArray byteArray = QByteArray::fromBase64(val.toLatin1()); - QBuffer buffer(&byteArray); - QImage imageData; - imageData.load(&buffer, "PNG"); - return imageData; -} -#endif -QString AbstractModelSerialiserPrivate::saveVariant(const QVariant& val) -{ - if (val.isNull()) - return QString(); - switch (val.type()) { - case QMetaType::UnknownType: - Q_ASSERT_X(false, "ModelSerialisation::saveVariant", "Trying to save unregistered type."); - return QString(); - case QMetaType::Bool: return val.toBool() ? QStringLiteral("1") : QStringLiteral("0"); - case QMetaType::Long: - case QMetaType::Short: - case QMetaType::Char: - case QMetaType::SChar: - case QMetaType::Int: return QString::number(val.toInt()); - case QMetaType::ULong: - case QMetaType::UShort: - case QMetaType::UChar: - case QMetaType::UInt: return QString::number(val.toUInt()); - case QMetaType::LongLong: return QString::number(val.toLongLong()); - case QMetaType::ULongLong: return QString::number(val.toULongLong()); - case QMetaType::Double: - case QMetaType::Float: return guessDecimalsString(val.toDouble()); - case QMetaType::QChar: return QString(val.toChar()); - case QMetaType::QString: return val.toString(); - case QMetaType::QByteArray: return QString::fromLatin1(val.toByteArray().toBase64()); - case QMetaType::QDate: return val.toDate().toString(Qt::ISODate); - case QMetaType::QTime: return val.toTime().toString(Qt::ISODate); - case QMetaType::QDateTime: return val.toDateTime().toString(Qt::ISODate); -#ifdef QT_GUI_LIB - case QMetaType::QImage: return saveImageVariant(val.value()); - case QMetaType::QPixmap: return saveImageVariant(val.value().toImage()); - case QMetaType::QBitmap: return saveImageVariant(val.value().toImage()); -#endif - default: - return variantToString(val); - } -} -/*! - Constructs a serialiser operating over \a model - - \sa isEmpty() -*/ -AbstractModelSerialiser::AbstractModelSerialiser(QAbstractItemModel* model) - :d_ptr(new AbstractModelSerialiserPrivate(this)) + +/*! +\brief The datastream version used to serialise binary data +\details This will be used to serialise variants that have a binary-only representation. +By default this property is set to the latest version avaliable in Qt. +*/ +QDataStream::Version AbstractModelSerialiser::streamVersion() const +{ + Q_D(const AbstractModelSerialiser); + return d->m_streamVersion; +} + +/*! +\brief Set the datastream version used to serialise binary data +\details This will be used to serialise variants that have a binary-only representation. +By default this property is set to the latest version avaliable in Qt. +*/ +void AbstractModelSerialiser::setStreamVersion(QDataStream::Version ver) +{ + Q_D(AbstractModelSerialiser); + d->m_streamVersion = ver; +} + +/*! +Constructs a serialiser operating over \a model +*/ +AbstractModelSerialiser::AbstractModelSerialiser(QAbstractItemModel *model, QObject *parent) + : QObject(parent) + , d_ptr(new AbstractModelSerialiserPrivate(this)) { setModel(model); } + /*! - \overload +\overload - loadModel will always fail as the model is not editable +the model will only be allowed to be saved, not loaded */ -AbstractModelSerialiser::AbstractModelSerialiser(const QAbstractItemModel* model) - : d_ptr(new AbstractModelSerialiserPrivate(this)) +AbstractModelSerialiser::AbstractModelSerialiser(const QAbstractItemModel *model, QObject *parent) + : QObject(parent) + , d_ptr(new AbstractModelSerialiserPrivate(this)) { setModel(model); } -/*! - \internal + +/*! +\internal +*/ +AbstractModelSerialiser::AbstractModelSerialiser(AbstractModelSerialiserPrivate &d, QObject *parent) + : d_ptr(&d) +{ } + +/*! +Destructor +*/ +AbstractModelSerialiser::~AbstractModelSerialiser() = default; + +/*! +\property AbstractModelSerialiser::rolesToSave +\accessors %rolesToSave(), setRoleToSave() +\brief The roles that will be serialised +\details By default this property is set to all non obsolete Qt::ItemDataRole values +*/ + +const QList &AbstractModelSerialiser::rolesToSave() const +{ + Q_D(const AbstractModelSerialiser); + + return d->m_rolesToSave; +} + +void AbstractModelSerialiser::setRoleToSave(const QList &val) +{ + Q_D(AbstractModelSerialiser); + + d->m_rolesToSave = val; +} + +/*! +\brief Appends \a role to the list of roles to save +*/ +void AbstractModelSerialiser::addRoleToSave(int role) +{ + Q_D(AbstractModelSerialiser); + + if (!d->m_rolesToSave.contains(role)) + d->m_rolesToSave.append(role); +} +/*! +\brief Removes \a role from the list of roles to save */ -AbstractModelSerialiser::AbstractModelSerialiser(AbstractModelSerialiserPrivate& d) - :d_ptr(&d) -{} +void AbstractModelSerialiser::removeRoleToSave(int role) +{ + Q_D(AbstractModelSerialiser); + d->m_rolesToSave.removeAll(role); +} /*! - Destroys the object. +\brief empties the list of roles to save */ -AbstractModelSerialiser::~AbstractModelSerialiser() +void AbstractModelSerialiser::clearRoleToSave() { - if (d_ptr) - delete d_ptr; + Q_D(AbstractModelSerialiser); + + d->m_rolesToSave.clear(); } +/*! +\brief resetter of rolesToSave property +fills the list fo roles to save with all non obsolete Qt::ItemDataRole values +*/ +void AbstractModelSerialiser::resetRoleToSave() +{ + Q_D(AbstractModelSerialiser); + + d->m_rolesToSave = AbstractModelSerialiser::modelDefaultRoles(); +} /*! - \property AbstractModelSerialiser::model - \brief the model over which the serialiser will operate +Returns a list of all non-obsolete Qt::ItemDataRole values */ +QList AbstractModelSerialiser::modelDefaultRoles() +{ + return QList{Qt::EditRole, + Qt::DecorationRole, + Qt::ToolTipRole, + Qt::StatusTipRole, + Qt::WhatsThisRole, + Qt::SizeHintRole, + Qt::FontRole, + Qt::TextAlignmentRole, + Qt::BackgroundRole, + Qt::ForegroundRole, + Qt::CheckStateRole, + Qt::InitialSortOrderRole, + Qt::AccessibleTextRole, + Qt::AccessibleDescriptionRole, + Qt::UserRole}; +} /*! - \brief getter of model property +\property AbstractModelSerialiser::model +\accessors %model(), setModel() +\brief The model over which the serialiser will operate for reading/writing +\details If setModel(const QAbstractItemModel*) was used to se the model, this method will return \c nullptr */ -QAbstractItemModel* AbstractModelSerialiser::model() const + +QAbstractItemModel *AbstractModelSerialiser::model() const { Q_D(const AbstractModelSerialiser); - + return d->m_model; } /*! - \brief the model over which the serialiser will operate - - loadModel will always fail as the model is not editable +\property AbstractModelSerialiser::constModel +\accessors %constModel(), setModel() +\brief The model over which the serialiser will operate in read mode */ -const QAbstractItemModel* AbstractModelSerialiser::constModel() const + +const QAbstractItemModel *AbstractModelSerialiser::constModel() const { Q_D(const AbstractModelSerialiser); - + return d->m_constModel; } -/*! - \brief setter of model property -*/ -void AbstractModelSerialiser::setModel(QAbstractItemModel* val) +void AbstractModelSerialiser::setModel(QAbstractItemModel *val) { Q_D(AbstractModelSerialiser); - + d->m_model = val; d->m_constModel = val; } /*! - \brief set the model over which the serialiser will operate +Sets the model over which the serialiser will operate in read mode - loadModel will always fail as the model is not editable +Using this method will only allow the model to be saved, not loaded */ -void AbstractModelSerialiser::setModel(const QAbstractItemModel* val) +void AbstractModelSerialiser::setModel(const QAbstractItemModel *val) { Q_D(AbstractModelSerialiser); - + d->m_model = Q_NULLPTR; d->m_constModel = val; } + +/*! +\class AbstractModelSerialiser +\brief The interface for model serialisers. + +This class serve as a base for all serialisers +*/ + +/*! + \fn bool AbstractModelSerialiser::loadModel(const QByteArray &source) + Loads the model from the given \a source + + Data previously stored in the model will be removed +*/ + +/*! + \fn bool AbstractModelSerialiser::loadModel(QIODevice *source) + Loads the model from the given \a source + + Data previously stored in the model will be removed +*/ + +/*! + \fn bool AbstractModelSerialiser::saveModel(QByteArray *destination) const + Saves the model to the given \a destination +*/ + +/*! + \fn bool AbstractModelSerialiser::saveModel(QIODevice *destination) const + Saves the model to the given \a destination +*/ diff --git a/src/abstractmodelserialiser.h b/src/abstractmodelserialiser.h index ee4f364..51ca10e 100644 --- a/src/abstractmodelserialiser.h +++ b/src/abstractmodelserialiser.h @@ -1,33 +1,59 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ + +#ifndef abstractmultiroleserialiser_h__ +#define abstractmultiroleserialiser_h__ -#ifndef abstractmodelserialiser_h__ -#define abstractmodelserialiser_h__ #include "modelutilities_global.h" #include +#include +#include +#include class AbstractModelSerialiserPrivate; -class QAbstractItemModel; class QIODevice; -class MODELUTILITIES_EXPORT AbstractModelSerialiser +class MODELUTILITIES_EXPORT AbstractModelSerialiser : public QObject { - Q_GADGET - Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel) - Q_PROPERTY(const QAbstractItemModel* constModel READ constModel WRITE setModel) + Q_OBJECT + Q_PROPERTY(QList rolesToSave READ rolesToSave WRITE setRoleToSave RESET resetRoleToSave) + Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel) + Q_PROPERTY(const QAbstractItemModel *constModel READ constModel WRITE setModel) Q_DECLARE_PRIVATE(AbstractModelSerialiser) Q_DISABLE_COPY(AbstractModelSerialiser) public: - AbstractModelSerialiser(QAbstractItemModel* model = Q_NULLPTR); - AbstractModelSerialiser(const QAbstractItemModel* model); + AbstractModelSerialiser(QAbstractItemModel *model = Q_NULLPTR, QObject *parent = Q_NULLPTR); + AbstractModelSerialiser(const QAbstractItemModel *model, QObject *parent = Q_NULLPTR); virtual ~AbstractModelSerialiser() = 0; - virtual QAbstractItemModel* model() const; - virtual const QAbstractItemModel* constModel() const; - void setModel(QAbstractItemModel* val); - void setModel(const QAbstractItemModel* val); - Q_INVOKABLE virtual bool saveModel(QIODevice* destination) const = 0; - Q_INVOKABLE virtual bool saveModel(QByteArray* destination) const = 0; - Q_INVOKABLE virtual bool loadModel(QIODevice* source) = 0; - Q_INVOKABLE virtual bool loadModel(const QByteArray& source) = 0; + virtual const QList &rolesToSave() const; + virtual void setRoleToSave(const QList &val); + Q_INVOKABLE virtual void addRoleToSave(int role); + Q_INVOKABLE virtual void removeRoleToSave(int role); + Q_INVOKABLE virtual void clearRoleToSave(); + virtual void resetRoleToSave(); + static QList modelDefaultRoles(); + virtual QAbstractItemModel *model() const; + virtual const QAbstractItemModel *constModel() const; + void setModel(QAbstractItemModel *val); + void setModel(const QAbstractItemModel *val); + Q_INVOKABLE QDataStream::Version streamVersion() const; + Q_INVOKABLE virtual bool saveModel(QIODevice *destination) const = 0; + Q_INVOKABLE virtual bool saveModel(QByteArray *destination) const = 0; + Q_INVOKABLE virtual bool loadModel(QIODevice *source) = 0; + Q_INVOKABLE virtual bool loadModel(const QByteArray &source) = 0; +public Q_SLOTS: + void setStreamVersion(QDataStream::Version ver); + protected: - AbstractModelSerialiserPrivate* d_ptr; - AbstractModelSerialiser(AbstractModelSerialiserPrivate& d); + AbstractModelSerialiser(AbstractModelSerialiserPrivate &d, QObject *parent); + QScopedPointer d_ptr; }; - -#endif // abstractmodelserialiser_h__ \ No newline at end of file +#endif // abstractmultiroleserialiser_h__ diff --git a/src/abstractmultiroleserialiser.cpp b/src/abstractmultiroleserialiser.cpp deleted file mode 100644 index 423f30c..0000000 --- a/src/abstractmultiroleserialiser.cpp +++ /dev/null @@ -1,134 +0,0 @@ - -#include "abstractmultiroleserialiser.h" -#include "private/abstractmultiroleserialiser_p.h" - -/*! -\class AbstractMultiRoleSerialiser - -\brief The interface for model serialisers saving multiple roles. -*/ - -AbstractMultiRoleSerialiserPrivate::AbstractMultiRoleSerialiserPrivate(AbstractMultiRoleSerialiser* q) - : AbstractModelSerialiserPrivate(q) - , m_rolesToSave(AbstractMultiRoleSerialiser::modelDefaultRoles()) -{} - -/*! -Constructs a serialiser operating over \a model - -\sa isEmpty() -*/ -AbstractMultiRoleSerialiser::AbstractMultiRoleSerialiser(QAbstractItemModel* model) - : AbstractModelSerialiser(*new AbstractMultiRoleSerialiserPrivate(this)) -{ - setModel(model); -} -/*! -\overload - -loadModel will always fail as the model is not editable -*/ -AbstractMultiRoleSerialiser::AbstractMultiRoleSerialiser(const QAbstractItemModel* model) - : AbstractModelSerialiser(*new AbstractMultiRoleSerialiserPrivate(this)) -{ - setModel(model); -} -/*! -\internal -*/ -AbstractMultiRoleSerialiser::AbstractMultiRoleSerialiser(AbstractMultiRoleSerialiserPrivate& d) - :AbstractModelSerialiser(d) -{} - -/*! -Destroys the object. -*/ -AbstractMultiRoleSerialiser::~AbstractMultiRoleSerialiser() = default; -/*! -\property AbstractSingleRoleSerialiser::rolesToSave -\brief the roles that will be serialised - -by default this property is set to all non obsolete Qt::ItemDataRole values -*/ - -/*! -\brief getter of rolesToSave property -*/ -const QList& AbstractMultiRoleSerialiser::rolesToSave() const -{ - Q_D(const AbstractMultiRoleSerialiser); - - return d->m_rolesToSave; -} -/*! -\brief setter of rolesToSave property -*/ -void AbstractMultiRoleSerialiser::setRoleToSave(const QList& val) -{ - Q_D(AbstractMultiRoleSerialiser); - - d->m_rolesToSave = val; -} -/*! -\brief appends \a role to the list of roles to save -*/ -void AbstractMultiRoleSerialiser::addRoleToSave(int role) -{ - Q_D(AbstractMultiRoleSerialiser); - - if (!d->m_rolesToSave.contains(role)) - d->m_rolesToSave.append(role); -} -/*! -\brief removes \a role from the list of roles to save -*/ -void AbstractMultiRoleSerialiser::removeRoleToSave(int role) -{ - Q_D(AbstractMultiRoleSerialiser); - - d->m_rolesToSave.removeAll(role); -} -/*! -\brief empties the list of roles to save -*/ -void AbstractMultiRoleSerialiser::clearRoleToSave() -{ - Q_D(AbstractMultiRoleSerialiser); - - d->m_rolesToSave.clear(); -} -/*! -\brief resetter of rolesToSave property - -fills the list fo roles to save with all non obsolete Qt::ItemDataRole values -*/ -void AbstractMultiRoleSerialiser::resetRoleToSave() -{ - Q_D(AbstractMultiRoleSerialiser); - - d->m_rolesToSave = AbstractMultiRoleSerialiser::modelDefaultRoles(); -} -/*! -\brief returns a list of all non obsolete Qt::ItemDataRole values -*/ -QList AbstractMultiRoleSerialiser::modelDefaultRoles() -{ - return QList() - << Qt::DisplayRole - << Qt::EditRole - << Qt::DecorationRole - << Qt::ToolTipRole - << Qt::StatusTipRole - << Qt::WhatsThisRole - << Qt::SizeHintRole - << Qt::FontRole - << Qt::TextAlignmentRole - << Qt::BackgroundRole - << Qt::ForegroundRole - << Qt::CheckStateRole - << Qt::InitialSortOrderRole - << Qt::AccessibleTextRole - << Qt::AccessibleDescriptionRole - << Qt::UserRole - ; -} diff --git a/src/abstractmultiroleserialiser.h b/src/abstractmultiroleserialiser.h deleted file mode 100644 index 24de1fe..0000000 --- a/src/abstractmultiroleserialiser.h +++ /dev/null @@ -1,28 +0,0 @@ - -#ifndef abstractmultiroleserialiser_h__ -#define abstractmultiroleserialiser_h__ - -#include "modelutilities_global.h" -#include "abstractmodelserialiser.h" -class AbstractMultiRoleSerialiserPrivate; -class MODELUTILITIES_EXPORT AbstractMultiRoleSerialiser : public AbstractModelSerialiser -{ - Q_GADGET - Q_PROPERTY(QList rolesToSave READ rolesToSave WRITE setRoleToSave RESET resetRoleToSave) - Q_DECLARE_PRIVATE(AbstractMultiRoleSerialiser) - Q_DISABLE_COPY(AbstractMultiRoleSerialiser) -public: - AbstractMultiRoleSerialiser(QAbstractItemModel* model = Q_NULLPTR); - AbstractMultiRoleSerialiser(const QAbstractItemModel* model); - ~AbstractMultiRoleSerialiser() = 0; - const QList& rolesToSave() const; - void setRoleToSave(const QList& val); - Q_INVOKABLE void addRoleToSave(int role); - Q_INVOKABLE void removeRoleToSave(int role); - Q_INVOKABLE void clearRoleToSave(); - void resetRoleToSave(); - static QList modelDefaultRoles(); -protected: - AbstractMultiRoleSerialiser(AbstractMultiRoleSerialiserPrivate& d); -}; -#endif // abstractmultiroleserialiser_h__ diff --git a/src/abstractsingleroleserialiser.cpp b/src/abstractsingleroleserialiser.cpp index 5836a94..9740660 100644 --- a/src/abstractsingleroleserialiser.cpp +++ b/src/abstractsingleroleserialiser.cpp @@ -1,76 +1,127 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #include "abstractsingleroleserialiser.h" #include "private/abstractsingleroleserialiser_p.h" -/*! -\class AbstractSingleRoleSerialiser -\brief The interface for model serialisers saving only one role. -*/ -AbstractSingleRoleSerialiserPrivate::AbstractSingleRoleSerialiserPrivate(AbstractSingleRoleSerialiser* q) - : AbstractModelSerialiserPrivate(q) - , m_roleToSave(Qt::DisplayRole) +AbstractSingleRoleSerialiserPrivate::AbstractSingleRoleSerialiserPrivate(AbstractSingleRoleSerialiser *q) + : AbstractStringSerialiserPrivate(q) { Q_ASSERT(q_ptr); } /*! -Constructs a serialiser operating over \a model - -\sa isEmpty() +Construct a read/write serialiser */ -AbstractSingleRoleSerialiser::AbstractSingleRoleSerialiser(QAbstractItemModel* model) - : AbstractModelSerialiser(*new AbstractSingleRoleSerialiserPrivate(this)) +AbstractSingleRoleSerialiser::AbstractSingleRoleSerialiser(QAbstractItemModel *model, QObject *parent) + : AbstractStringSerialiser(*new AbstractSingleRoleSerialiserPrivate(this), parent) { + AbstractSingleRoleSerialiser::setRoleToSave(Qt::DisplayRole); setModel(model); } -/*! -\overload -loadModel will always fail as the model is not editable +/*! +Construct a write-only serialiser */ -AbstractSingleRoleSerialiser::AbstractSingleRoleSerialiser(const QAbstractItemModel* model) - : AbstractModelSerialiser(*new AbstractSingleRoleSerialiserPrivate(this)) +AbstractSingleRoleSerialiser::AbstractSingleRoleSerialiser(const QAbstractItemModel *model, QObject *parent) + : AbstractStringSerialiser(*new AbstractSingleRoleSerialiserPrivate(this), parent) { + setRoleToSave(Qt::DisplayRole); setModel(model); } + /*! -\internal +Constructor used only while subclassing the private class. +Not part of the public API */ -AbstractSingleRoleSerialiser::AbstractSingleRoleSerialiser(AbstractSingleRoleSerialiserPrivate& d) - :AbstractModelSerialiser(d) -{} - - +AbstractSingleRoleSerialiser::AbstractSingleRoleSerialiser(AbstractSingleRoleSerialiserPrivate &d, QObject *parent) + : AbstractStringSerialiser(d, parent) +{ } /*! -Destroys the object. +Destructor */ AbstractSingleRoleSerialiser::~AbstractSingleRoleSerialiser() = default; - /*! \property AbstractSingleRoleSerialiser::roleToSave -\brief the role that will be serialised +\accessors %roleToSave(), setRoleToSave() +\brief The role that will be serialised -by default this property is set to Qt::DisplayRole +By default this property is set to Qt::DisplayRole */ +int AbstractSingleRoleSerialiser::roleToSave() const +{ + return rolesToSave().first(); +} + +void AbstractSingleRoleSerialiser::setRoleToSave(int val) +{ + AbstractSingleRoleSerialiser::setRoleToSave(QList{val}); +} + /*! -\brief getter of roleToSave property +\reimp +If \a val contains more than one value only the first one is considered */ -int AbstractSingleRoleSerialiser::roleToSave() const +void AbstractSingleRoleSerialiser::setRoleToSave(const QList &val) { - Q_D(const AbstractSingleRoleSerialiser); - - return d->m_roleToSave; + if (val.size() > 1) + return AbstractModelSerialiser::setRoleToSave(QList{val.first()}); + return AbstractModelSerialiser::setRoleToSave(val); } /*! -\brief setter of roleToSave property +\reimp +Equivalent to setRoleToSave */ -void AbstractSingleRoleSerialiser::setRoleToSave(int val) +void AbstractSingleRoleSerialiser::addRoleToSave(int role) { - Q_D(AbstractSingleRoleSerialiser); - - d->m_roleToSave = val; + setRoleToSave(role); } + +/*! +\reimp +If \a role is the currently saved role, resets the role to save to Qt::DisplayRole +*/ +void AbstractSingleRoleSerialiser::removeRoleToSave(int role) +{ + AbstractModelSerialiser::removeRoleToSave(role); + if (rolesToSave().isEmpty()) + setRoleToSave(Qt::DisplayRole); +} + +/*! +\reimp +Resets the role to save to Qt::DisplayRole +*/ +void AbstractSingleRoleSerialiser::clearRoleToSave() +{ + setRoleToSave(Qt::DisplayRole); +} + +/*! +\reimp +Resets the role to save to Qt::DisplayRole +*/ +void AbstractSingleRoleSerialiser::resetRoleToSave() +{ + setRoleToSave(Qt::DisplayRole); +} + +/*! +\class AbstractSingleRoleSerialiser + +\brief The interface for model serialisers saving only one role. +*/ diff --git a/src/abstractsingleroleserialiser.h b/src/abstractsingleroleserialiser.h index dc526e8..0749d6f 100644 --- a/src/abstractsingleroleserialiser.h +++ b/src/abstractsingleroleserialiser.h @@ -1,23 +1,41 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef abstractsingleroleserialiser_h__ #define abstractsingleroleserialiser_h__ #include "modelutilities_global.h" -#include "abstractmodelserialiser.h" +#include "abstractstringserialiser.h" class AbstractSingleRoleSerialiserPrivate; -class MODELUTILITIES_EXPORT AbstractSingleRoleSerialiser : public AbstractModelSerialiser +class MODELUTILITIES_EXPORT AbstractSingleRoleSerialiser : public AbstractStringSerialiser { - Q_GADGET + Q_OBJECT Q_PROPERTY(int roleToSave READ roleToSave WRITE setRoleToSave) Q_DECLARE_PRIVATE(AbstractSingleRoleSerialiser) Q_DISABLE_COPY(AbstractSingleRoleSerialiser) public: - AbstractSingleRoleSerialiser(QAbstractItemModel* model = Q_NULLPTR); - AbstractSingleRoleSerialiser(const QAbstractItemModel* model); + AbstractSingleRoleSerialiser(QAbstractItemModel *model = Q_NULLPTR, QObject *parent = Q_NULLPTR); + AbstractSingleRoleSerialiser(const QAbstractItemModel *model, QObject *parent = Q_NULLPTR); ~AbstractSingleRoleSerialiser() = 0; int roleToSave() const; void setRoleToSave(int val); + void setRoleToSave(const QList &val) Q_DECL_OVERRIDE; + void addRoleToSave(int role) Q_DECL_OVERRIDE; + void removeRoleToSave(int role) Q_DECL_OVERRIDE; + void clearRoleToSave() Q_DECL_OVERRIDE; + void resetRoleToSave() Q_DECL_OVERRIDE; + protected: - AbstractSingleRoleSerialiser(AbstractSingleRoleSerialiserPrivate& d); + AbstractSingleRoleSerialiser(AbstractSingleRoleSerialiserPrivate &d, QObject *parent); }; #endif // abstractsingleroleserialiser_h__ diff --git a/src/abstractstringserialiser.cpp b/src/abstractstringserialiser.cpp new file mode 100644 index 0000000..e426eec --- /dev/null +++ b/src/abstractstringserialiser.cpp @@ -0,0 +1,281 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ + +#include "abstractstringserialiser.h" +#include "private/abstractstringserialiser_p.h" +#include +#include +#include +#include +#ifdef QT_GUI_LIB +# include +# include +# include +#endif +#include +#include + +AbstractStringSerialiserPrivate::AbstractStringSerialiserPrivate(AbstractStringSerialiser *q) + : AbstractModelSerialiserPrivate(q) +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + , m_textCodec(QTextCodec::codecForName("UTF-8")) +#endif +{ } + +int AbstractStringSerialiserPrivate::guessDecimals(double val) +{ + int precision = 0; + for (double junk = 0; !qFuzzyIsNull(std::modf(val, &junk)); ++precision) + val *= 10.0; + return precision; +} + +QString AbstractStringSerialiserPrivate::guessDecimalsString(double val, QLocale *loca) +{ + if (loca) + return loca->toString(val, 'f', guessDecimals(val)); + return QString::number(val, 'f', guessDecimals(val)); +} + +QString AbstractStringSerialiserPrivate::variantToString(const QVariant &val) +{ + QByteArray data; + QDataStream outStream(&data, QIODevice::WriteOnly); +#ifdef MS_DATASTREAM_VERSION + outStream.setVersion(MS_DATASTREAM_VERSION); +#endif // MS_DATASTREAM_VERSION + outStream << val; + data = qCompress(data); + return QString::fromLatin1(data.toBase64()); +} + +QVariant AbstractStringSerialiserPrivate::stringToVariant(const QString &val) +{ + QByteArray data = QByteArray::fromBase64(val.toLatin1()); + data = qUncompress(data); + QDataStream inStream(data); +#ifdef MS_DATASTREAM_VERSION + inStream.setVersion(MS_DATASTREAM_VERSION); +#endif // MS_DATASTREAM_VERSION + QVariant result; + inStream >> result; + return result; +} + +QVariant AbstractStringSerialiserPrivate::loadVariant(int type, const QString &val) +{ + if (val.isEmpty()) + return QVariant(); + switch (type) { + case QMetaType::UnknownType: + Q_ASSERT_X(false, "ModelSerialisation::loadVariant", "Trying to load unregistered type."); + return QVariant(); + case QMetaType::Bool: + return val.toInt() == 1; + case QMetaType::Long: + case QMetaType::Int: + return val.toInt(); + case QMetaType::ULong: + case QMetaType::UInt: + return val.toUInt(); + case QMetaType::LongLong: + return val.toLongLong(); + case QMetaType::ULongLong: + return val.toULongLong(); + case QMetaType::Double: + return val.toDouble(); + case QMetaType::Short: + return static_cast(val.toInt()); + case QMetaType::SChar: + case QMetaType::Char: + return static_cast(val.toInt()); + case QMetaType::UShort: + return static_cast(val.toUInt()); + case QMetaType::UChar: + return static_cast(val.toUInt()); + case QMetaType::Float: + return val.toFloat(); + case QMetaType::QChar: + return val.at(0); + case QMetaType::QString: + return val; + case QMetaType::QByteArray: + return QByteArray::fromBase64(val.toLatin1()); + case QMetaType::QDate: + return QDate::fromString(val, Qt::ISODate); + case QMetaType::QTime: + return QTime::fromString(val, Qt::ISODate); + case QMetaType::QDateTime: + return QDateTime::fromString(val, Qt::ISODate); +#ifdef QT_GUI_LIB + case QMetaType::QImage: + return loadImageVariant(type, val); + case QMetaType::QPixmap: + return QPixmap::fromImage(loadImageVariant(type, val)); + case QMetaType::QBitmap: + return QBitmap::fromImage(loadImageVariant(type, val)); +#endif + default: + return stringToVariant(val); + } +} +#ifdef QT_GUI_LIB + +QString AbstractStringSerialiserPrivate::saveImageVariant(const QImage &imageData) +{ + QByteArray byteArray; + QBuffer buffer(&byteArray); + imageData.save(&buffer, "PNG"); + return QString::fromLatin1(byteArray.toBase64().constData()); +} + +QImage AbstractStringSerialiserPrivate::loadImageVariant(int type, const QString &val) +{ + Q_UNUSED(type) + QByteArray byteArray = QByteArray::fromBase64(val.toLatin1()); + QBuffer buffer(&byteArray); + QImage imageData; + imageData.load(&buffer, "PNG"); + return imageData; +} +#endif + +QString AbstractStringSerialiserPrivate::saveVariant(const QVariant &val) +{ + if (val.isNull()) + return QString(); + switch (val.userType()) { + case QMetaType::UnknownType: + Q_ASSERT_X(false, "ModelSerialisation::saveVariant", "Trying to save unregistered type."); + return QString(); + case QMetaType::Bool: + return val.toBool() ? QStringLiteral("1") : QStringLiteral("0"); + case QMetaType::Long: + case QMetaType::Short: + case QMetaType::Char: + case QMetaType::SChar: + case QMetaType::Int: + return QString::number(val.toInt()); + case QMetaType::ULong: + case QMetaType::UShort: + case QMetaType::UChar: + case QMetaType::UInt: + return QString::number(val.toUInt()); + case QMetaType::LongLong: + return QString::number(val.toLongLong()); + case QMetaType::ULongLong: + return QString::number(val.toULongLong()); + case QMetaType::Double: + case QMetaType::Float: + return guessDecimalsString(val.toDouble()); + case QMetaType::QChar: + return QString(val.toChar()); + case QMetaType::QString: + return val.toString(); + case QMetaType::QByteArray: + return QString::fromLatin1(val.toByteArray().toBase64()); + case QMetaType::QDate: + return val.toDate().toString(Qt::ISODate); + case QMetaType::QTime: + return val.toTime().toString(Qt::ISODate); + case QMetaType::QDateTime: + return val.toDateTime().toString(Qt::ISODate); +#ifdef QT_GUI_LIB + case QMetaType::QImage: + return saveImageVariant(val.value()); + case QMetaType::QPixmap: + return saveImageVariant(val.value().toImage()); + case QMetaType::QBitmap: + return saveImageVariant(val.value().toImage()); +#endif + default: + return variantToString(val); + } +} + +/*! +Construct a read/write serialiser +*/ +AbstractStringSerialiser::AbstractStringSerialiser(QAbstractItemModel *model, QObject *parent) + : AbstractModelSerialiser(*new AbstractStringSerialiserPrivate(this), parent) +{ + setModel(model); +} + +/*! +Construct a write-only serialiser +*/ +AbstractStringSerialiser::AbstractStringSerialiser(const QAbstractItemModel *model, QObject *parent) + : AbstractModelSerialiser(*new AbstractStringSerialiserPrivate(this), parent) +{ + setModel(model); +} + +/*! +Constructor used only while subclassing the private class. +Not part of the public API +*/ +AbstractStringSerialiser::AbstractStringSerialiser(AbstractStringSerialiserPrivate &d, QObject *parent) + : AbstractModelSerialiser(d, parent) +{ } + +/*! +Destructor +*/ +AbstractStringSerialiser::~AbstractStringSerialiser() = default; + +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +QTextCodec *AbstractStringSerialiser::textCodec() const +{ + Q_D(const AbstractStringSerialiser); + return d->m_textCodec; +} + +bool AbstractStringSerialiser::setTextCodec(QTextCodec *val) +{ + if (!val) + return false; + Q_D(AbstractStringSerialiser); + d->m_textCodec = val; + return true; +} +#endif + +/*! +Loads the model from the given \a source + +Data previously stored in the model will be removed +*/ +bool AbstractStringSerialiser::loadModel(const QString &source) +{ + QString sourceCopy(source); + return loadModel(&sourceCopy); +} + +/*! +\class AbstractStringSerialiser + +\brief The interface for model serialisers saving to strings +*/ + +/*! +\fn bool AbstractStringSerialiser::loadModel(QString *source) +Loads the model from the given \a source + +Data previously stored in the model will be removed +*/ + +/*! +\fn bool AbstractStringSerialiser::saveModel(QString *destination) +Saves the model to the given \a destination +*/ diff --git a/src/abstractstringserialiser.h b/src/abstractstringserialiser.h new file mode 100644 index 0000000..fd586bf --- /dev/null +++ b/src/abstractstringserialiser.h @@ -0,0 +1,43 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ + +#ifndef abstractmodelserialiser_h__ +#define abstractmodelserialiser_h__ +#include "modelutilities_global.h" +#include "abstractmodelserialiser.h" +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +# include +#endif +class AbstractStringSerialiserPrivate; +class MODELUTILITIES_EXPORT AbstractStringSerialiser : public AbstractModelSerialiser +{ + Q_OBJECT + Q_DECLARE_PRIVATE(AbstractStringSerialiser) + Q_DISABLE_COPY(AbstractStringSerialiser) +public: + AbstractStringSerialiser(QAbstractItemModel *model = Q_NULLPTR, QObject *parent = Q_NULLPTR); + AbstractStringSerialiser(const QAbstractItemModel *model, QObject *parent = Q_NULLPTR); + virtual ~AbstractStringSerialiser() = 0; +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + QTextCodec *textCodec() const; + bool setTextCodec(QTextCodec *val); +#endif + Q_INVOKABLE virtual bool saveModel(QString *destination) const = 0; + Q_INVOKABLE virtual bool loadModel(QString *source) = 0; + Q_INVOKABLE bool loadModel(const QString &source); + +protected: + AbstractStringSerialiser(AbstractStringSerialiserPrivate &d, QObject *parent); +}; + +#endif // abstractmodelserialiser_h__ diff --git a/src/binarymodelserialiser.cpp b/src/binarymodelserialiser.cpp index b0329b3..80b2615 100644 --- a/src/binarymodelserialiser.cpp +++ b/src/binarymodelserialiser.cpp @@ -1,13 +1,26 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #include "binarymodelserialiser.h" #include "private/binarymodelserialiser_p.h" #include +#include -BinaryModelSerialiserPrivate::BinaryModelSerialiserPrivate(BinaryModelSerialiser* q) - :AbstractMultiRoleSerialiserPrivate(q) -{} +BinaryModelSerialiserPrivate::BinaryModelSerialiserPrivate(BinaryModelSerialiser *q) + : AbstractModelSerialiserPrivate(q) +{ } -void BinaryModelSerialiserPrivate::writeBinaryElement(QDataStream& destination, const QModelIndex& parent) const +void BinaryModelSerialiserPrivate::writeBinaryElement(QDataStream &destination, const QModelIndex &parent) const { Q_ASSERT(m_constModel); if (m_constModel->rowCount(parent) + m_constModel->columnCount(parent) == 0) @@ -30,18 +43,22 @@ void BinaryModelSerialiserPrivate::writeBinaryElement(QDataStream& destination, } } -bool BinaryModelSerialiserPrivate::readBinaryElement(QDataStream& source, const QModelIndex& parent) +bool BinaryModelSerialiserPrivate::readBinaryElement(QDataStream &source, const QModelIndex &parent) { Q_ASSERT(m_model); qint32 rowCount, colCount; source >> rowCount >> colCount; m_model->insertRows(0, rowCount, parent); m_model->insertColumns(0, colCount, parent); + if (m_model->rowCount(parent) != rowCount) + return false; + if (m_model->columnCount(parent) != colCount) + return false; qint32 tempRole = -1; QVariant tempData; bool hasChild = false; - for (int i = 0; i < rowCount&& source.status() == QDataStream::Ok; ++i) { - for (int j = 0; j < colCount&& source.status() == QDataStream::Ok; ++j) { + for (int i = 0; i < rowCount && source.status() == QDataStream::Ok; ++i) { + for (int j = 0; j < colCount && source.status() == QDataStream::Ok; ++j) { const QModelIndex currIdx = m_model->index(i, j, parent); for (source >> tempRole; tempRole != -1 && source.status() == QDataStream::Ok; source >> tempRole) { source >> tempData; @@ -70,8 +87,7 @@ bool BinaryModelSerialiserPrivate::readBinaryElement(QDataStream& source, const return true; } - -bool BinaryModelSerialiserPrivate::readBinary(QDataStream& reader) +bool BinaryModelSerialiserPrivate::readBinary(QDataStream &reader) { if (!m_model) return false; @@ -83,6 +99,12 @@ bool BinaryModelSerialiserPrivate::readBinary(QDataStream& reader) reader.setVersion(QDataStream::Qt_5_0); qint32 steramVersion; reader >> steramVersion; + if (steramVersion > QDataStream().version()) { +#if QT_VERSION >= 0x050700 + reader.rollbackTransaction(); +#endif + return false; + } reader.setVersion(steramVersion); QString header; reader >> header; @@ -140,11 +162,11 @@ bool BinaryModelSerialiserPrivate::readBinary(QDataStream& reader) #endif } -bool BinaryModelSerialiserPrivate::writeBinary(QDataStream& writer) const +bool BinaryModelSerialiserPrivate::writeBinary(QDataStream &writer) const { if (!m_constModel) return false; - const qint32 writerVersion = writer.version(); + const qint32 writerVersion = writer.version(); writer.setVersion(QDataStream::Qt_5_0); writer << writerVersion; writer.setVersion(writerVersion); @@ -164,7 +186,6 @@ bool BinaryModelSerialiserPrivate::writeBinary(QDataStream& writer) const const QVariant roleData = m_constModel->headerData(i, Qt::Vertical, *singleRole); if (roleData.isNull()) continue; - const QString roleString = saveVariant(roleData); writer << static_cast(*singleRole) << roleData; } writer << static_cast(-1); @@ -172,7 +193,10 @@ bool BinaryModelSerialiserPrivate::writeBinary(QDataStream& writer) const return writer.status() == QDataStream::Ok; } -bool BinaryModelSerialiser::saveModel(QIODevice* destination) const +/*! +\reimp +*/ +bool BinaryModelSerialiser::saveModel(QIODevice *destination) const { if (!destination) return false; @@ -183,32 +207,43 @@ bool BinaryModelSerialiser::saveModel(QIODevice* destination) const if (!destination->isWritable()) return false; Q_D(const BinaryModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; QDataStream witer(destination); -#ifdef MS_DATASTREAM_VERSION - witer.setVersion(MS_DATASTREAM_VERSION); -#endif // MS_DATASTREAM_VERSION + witer.setVersion(d->m_streamVersion); return d->writeBinary(witer); } -bool BinaryModelSerialiser::saveModel(QByteArray* destination) const +/*! +\reimp +*/ +bool BinaryModelSerialiser::saveModel(QByteArray *destination) const { if (!destination) return false; Q_D(const BinaryModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; QDataStream witer(destination, QIODevice::WriteOnly); -#ifdef MS_DATASTREAM_VERSION - witer.setVersion(MS_DATASTREAM_VERSION); -#endif // MS_DATASTREAM_VERSION + witer.setVersion(d->m_streamVersion); return d->writeBinary(witer); } -bool BinaryModelSerialiser::loadModel(QIODevice* source) +/*! +Saves the model to the given \a stream +*/ +bool BinaryModelSerialiser::saveModel(QDataStream &stream) const +{ + Q_D(const BinaryModelSerialiser); + return d->writeBinary(stream); +} + +/*! +\reimp +*/ +bool BinaryModelSerialiser::loadModel(QIODevice *source) { if (!source) return false; @@ -219,65 +254,90 @@ bool BinaryModelSerialiser::loadModel(QIODevice* source) if (!source->isReadable()) return false; Q_D(BinaryModelSerialiser); - if (!d->m_model) return false; QDataStream reader(source); -#ifdef MS_DATASTREAM_VERSION - reader.setVersion(MS_DATASTREAM_VERSION); -#endif // MS_DATASTREAM_VERSION + reader.setVersion(d->m_streamVersion); return d->readBinary(reader); } -bool BinaryModelSerialiser::loadModel(const QByteArray& source) + +/*! +\reimp +*/ +bool BinaryModelSerialiser::loadModel(const QByteArray &source) { Q_D(BinaryModelSerialiser); - + if (!d->m_model) return false; QDataStream reader(source); -#ifdef MS_DATASTREAM_VERSION - reader.setVersion(MS_DATASTREAM_VERSION); -#endif // MS_DATASTREAM_VERSION + reader.setVersion(streamVersion()); return d->readBinary(reader); } +/*! +Loads the model from the given \a stream - -BinaryModelSerialiser::BinaryModelSerialiser(QAbstractItemModel* model) - : AbstractMultiRoleSerialiser(*new BinaryModelSerialiserPrivate(this)) +Data previously stored in the model will be removed +*/ +bool BinaryModelSerialiser::loadModel(QDataStream &stream) { - setModel(model); + Q_D(BinaryModelSerialiser); + return d->readBinary(stream); } -BinaryModelSerialiser::BinaryModelSerialiser(const QAbstractItemModel* model) - : AbstractMultiRoleSerialiser(*new BinaryModelSerialiserPrivate(this)) + +/*! +Constructs a serialiser operating over \a model +*/ +BinaryModelSerialiser::BinaryModelSerialiser(QAbstractItemModel *model, QObject *parent) + : AbstractModelSerialiser(*new BinaryModelSerialiserPrivate(this), parent) { setModel(model); } +/*! +\overload -BinaryModelSerialiser::BinaryModelSerialiser(BinaryModelSerialiserPrivate& d) - :AbstractMultiRoleSerialiser(d) -{} +the model will only be allowed to be saved, not loaded +*/ +BinaryModelSerialiser::BinaryModelSerialiser(const QAbstractItemModel *model, QObject *parent) + : AbstractModelSerialiser(*new BinaryModelSerialiserPrivate(this), parent) +{ + setModel(model); +} +/*! +\internal +*/ +BinaryModelSerialiser::BinaryModelSerialiser(BinaryModelSerialiserPrivate &d, QObject *parent) + : AbstractModelSerialiser(d, parent) +{ } +/*! +Destructor +*/ BinaryModelSerialiser::~BinaryModelSerialiser() = default; - - #ifdef MS_DECLARE_STREAM_OPERATORS -QDataStream& operator<<(QDataStream & stream, const QAbstractItemModel& model) +QDataStream &operator<<(QDataStream &stream, const QAbstractItemModel &model) { const QModelSerialiser mSer(&model); mSer.d_func()->writeBinary(stream); return stream; } -QDataStream& operator>>(QDataStream & stream, QAbstractItemModel& model) +QDataStream &operator>>(QDataStream &stream, QAbstractItemModel &model) { QModelSerialiser mSer(&model); mSer.d_func()->readBinary(stream); return stream; } -#endif \ No newline at end of file +#endif + +/*! +\class BinaryModelSerialiser + +\brief Serialiser to save and load models in binary format +*/ diff --git a/src/binarymodelserialiser.h b/src/binarymodelserialiser.h index 97dee7b..7d2569e 100644 --- a/src/binarymodelserialiser.h +++ b/src/binarymodelserialiser.h @@ -1,34 +1,48 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef binarymodelserialiser_h__ #define binarymodelserialiser_h__ - #include "modelutilities_global.h" -#include "abstractmultiroleserialiser.h" +#include "abstractmodelserialiser.h" class BinaryModelSerialiserPrivate; -class QDataStream; -class MODELUTILITIES_EXPORT BinaryModelSerialiser : public AbstractMultiRoleSerialiser +class MODELUTILITIES_EXPORT BinaryModelSerialiser : public AbstractModelSerialiser { - Q_GADGET + Q_OBJECT + Q_DECLARE_PRIVATE(BinaryModelSerialiser) Q_DISABLE_COPY(BinaryModelSerialiser) public: - BinaryModelSerialiser(QAbstractItemModel* model = Q_NULLPTR); - BinaryModelSerialiser(const QAbstractItemModel* model); + BinaryModelSerialiser(QAbstractItemModel *model = Q_NULLPTR, QObject *parent = Q_NULLPTR); + BinaryModelSerialiser(const QAbstractItemModel *model, QObject *parent = Q_NULLPTR); ~BinaryModelSerialiser(); - Q_INVOKABLE bool saveModel(QIODevice* destination) const Q_DECL_OVERRIDE; - Q_INVOKABLE bool saveModel(QByteArray* destination) const Q_DECL_OVERRIDE; - Q_INVOKABLE bool loadModel(QIODevice* source) Q_DECL_OVERRIDE; - Q_INVOKABLE bool loadModel(const QByteArray& source) Q_DECL_OVERRIDE; + virtual bool saveModel(QDataStream &stream) const; + Q_INVOKABLE bool saveModel(QIODevice *destination) const Q_DECL_OVERRIDE; + Q_INVOKABLE bool saveModel(QByteArray *destination) const Q_DECL_OVERRIDE; + Q_INVOKABLE bool loadModel(QIODevice *source) Q_DECL_OVERRIDE; + Q_INVOKABLE bool loadModel(const QByteArray &source) Q_DECL_OVERRIDE; + virtual bool loadModel(QDataStream &stream); + protected: - BinaryModelSerialiser(BinaryModelSerialiserPrivate& d); + BinaryModelSerialiser(BinaryModelSerialiserPrivate &d, QObject *parent = Q_NULLPTR); #ifdef MS_DECLARE_STREAM_OPERATORS - friend QDataStream& operator<<(QDataStream & stream, const QAbstractItemModel& model); - friend QDataStream& operator>>(QDataStream & stream, QAbstractItemModel& model); + friend QDataStream &operator<<(QDataStream &stream, const QAbstractItemModel &model); + friend QDataStream &operator>>(QDataStream &stream, QAbstractItemModel &model); #endif }; #ifdef MS_DECLARE_STREAM_OPERATORS -QDataStream& operator<<(QDataStream & stream, const QAbstractItemModel& model); -QDataStream& operator>>(QDataStream & stream, QAbstractItemModel& model); +QDataStream &operator<<(QDataStream &stream, const QAbstractItemModel &model); +QDataStream &operator>>(QDataStream &stream, QAbstractItemModel &model); #endif -#endif // binarymodelserialiser_h__ \ No newline at end of file +#endif // binarymodelserialiser_h__ diff --git a/src/csvmodelserialiser.cpp b/src/csvmodelserialiser.cpp index c17b6cb..ca02a49 100644 --- a/src/csvmodelserialiser.cpp +++ b/src/csvmodelserialiser.cpp @@ -1,33 +1,45 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #include "csvmodelserialiser.h" #include "private/csvmodelserialiser_p.h" #include #include #include -#include #include +#include +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +# include +#endif -CsvModelSerialiserPrivate::CsvModelSerialiserPrivate(CsvModelSerialiser* q) - :AbstractSingleRoleSerialiserPrivate(q) - , m_csvSeparator(QStringLiteral(",")) +CsvModelSerialiserPrivate::CsvModelSerialiserPrivate(CsvModelSerialiser *q) + : AbstractSingleRoleSerialiserPrivate(q) , m_firstRowIsHeader(true) , m_firstColumnIsHeader(true) -{} + , m_csvSeparator(QLatin1Char(',')) +{ } QString CsvModelSerialiserPrivate::escapedCSV(QString unexc) const { if (!unexc.contains(m_csvSeparator)) return unexc; - if (unexc.contains(QChar('\"'))) - unexc.replace(QChar('\"'), QStringLiteral("\"\"")); - return '\"' % unexc % '\"'; - + return '\"' % unexc.replace(QLatin1Char('\"'), QStringLiteral("\"\"")) % '\"'; } QString CsvModelSerialiserPrivate::unescapedCSV(QString exc) const { if (exc.isEmpty()) return exc; - if (*exc.constBegin() == QChar('\"') && *(exc.constEnd() - 1) == QChar('\"')) { + if (*exc.constBegin() == QLatin1Char('\"') && *(exc.constEnd() - 1) == QLatin1Char('\"')) { exc.chop(1); exc.remove(0, 1); exc.replace(QStringLiteral("\"\""), QStringLiteral("\"")); @@ -35,7 +47,7 @@ QString CsvModelSerialiserPrivate::unescapedCSV(QString exc) const return exc; } -int CsvModelSerialiserPrivate::guessVarType(const QString& val) +int CsvModelSerialiserPrivate::guessVarType(const QString &val) { bool checkConversion; val.toInt(&checkConversion); @@ -53,64 +65,63 @@ int CsvModelSerialiserPrivate::guessVarType(const QString& val) return QMetaType::QString; } -bool CsvModelSerialiserPrivate::writeCsv(QTextStream& writer) const +bool CsvModelSerialiserPrivate::writeCsv(QTextStream &writer) const { if (!m_constModel) return false; - QTextCodec* const oldCodec = writer.codec(); - Q_ASSERT(QTextCodec::availableCodecs().contains("UTF-8")); - writer.setCodec("UTF-8"); + Q_Q(const CsvModelSerialiser); if (m_firstRowIsHeader) { for (int j = 0; j < m_constModel->columnCount(); ++j) { - const QVariant headData = m_constModel->headerData(j, Qt::Horizontal, m_roleToSave); - if(j>0 || m_firstColumnIsHeader) + const QVariant headData = m_constModel->headerData(j, Qt::Horizontal, q->roleToSave()); + if (j > 0 || m_firstColumnIsHeader) writer << m_csvSeparator; if (!headData.isNull()) writer << escapedCSV(saveVariant(headData)); } writer << '\n'; } - for (int i = 0; i < m_constModel->rowCount(); ++i) { + for (int i = 0, maxRow = m_constModel->rowCount(); i < maxRow; ++i) { if (m_firstColumnIsHeader) { - const QVariant headData = m_constModel->headerData(i, Qt::Vertical, m_roleToSave); + const QVariant headData = m_constModel->headerData(i, Qt::Vertical, q->roleToSave()); if (!headData.isNull()) writer << escapedCSV(saveVariant(headData)); } - for (int j = 0; j < m_constModel->columnCount(); ++j) { - const QVariant roleData = m_constModel->data(m_constModel->index(i, j), m_roleToSave); - if (j>0 || m_firstColumnIsHeader) + for (int j = 0, maxCol = m_constModel->columnCount(); j < maxCol; ++j) { + const QVariant roleData = m_constModel->index(i, j).data(q->roleToSave()); + if (j > 0 || m_firstColumnIsHeader) writer << m_csvSeparator; - if (!roleData.isNull()) { + if (!roleData.isNull()) writer << escapedCSV(saveVariant(roleData)); - } } writer << '\n'; } - writer.setCodec(oldCodec); return writer.status() == QTextStream::Ok; } - -bool CsvModelSerialiserPrivate::readCsv(QTextStream& reader) +bool CsvModelSerialiserPrivate::readCsv(QTextStream &reader) { if (!m_model) return false; - QTextCodec* const oldCodec = reader.codec(); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + QTextCodec *const oldCodec = reader.codec(); Q_ASSERT(QTextCodec::availableCodecs().contains("UTF-8")); reader.setCodec("UTF-8"); +#endif m_model->removeColumns(0, m_model->columnCount()); m_model->removeRows(0, m_model->rowCount()); QString line; QStringList fields; QString currentField; - for (bool firstRow = true; ; firstRow = false) { + for (bool firstRow = true;; firstRow = false) { line = reader.readLine(); if (line.isNull()) break; if (line.count(QChar('\"')) % 2 > 0) { m_model->removeColumns(0, m_model->columnCount()); m_model->removeRows(0, m_model->rowCount()); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) reader.setCodec(oldCodec); +#endif return false; } fields.clear(); @@ -121,7 +132,7 @@ bool CsvModelSerialiserPrivate::readCsv(QTextStream& reader) inQuoted = !inQuoted; currentField.append(*i); if (!inQuoted) { - if (currentField.rightRef(m_csvSeparator.size()).compare(m_csvSeparator) == 0) { + if (currentField.right(m_csvSeparator.size()).compare(m_csvSeparator) == 0) { currentField.chop(m_csvSeparator.size()); fields.append(currentField); currentField.clear(); @@ -133,110 +144,156 @@ bool CsvModelSerialiserPrivate::readCsv(QTextStream& reader) const int startI = (m_firstColumnIsHeader ? 1 : 0); if (m_model->columnCount() == 0) { m_model->insertColumns(0, fields.size() - startI); - } - else if (m_model->columnCount() != fields.size() - startI) { + if (m_model->columnCount() != fields.size() - startI) + return false; + } else if (m_model->columnCount() != fields.size() - startI) { m_model->removeColumns(0, m_model->columnCount()); m_model->removeRows(0, m_model->rowCount()); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) reader.setCodec(oldCodec); +#endif return false; } + Q_Q(const CsvModelSerialiser); if (firstRow && m_firstRowIsHeader) { for (int i = startI; i < fields.size(); ++i) - m_model->setHeaderData(i - startI, Qt::Horizontal, loadVariant(guessVarType(fields.at(i)), unescapedCSV(fields.at(i))), m_roleToSave); - } - else { + m_model->setHeaderData(i - startI, Qt::Horizontal, loadVariant(guessVarType(fields.at(i)), unescapedCSV(fields.at(i))), + q->roleToSave()); + } else { const int newRow = m_model->rowCount(); - m_model->insertRow(newRow); + if (!m_model->insertRow(newRow)) + return false; for (int i = 0; i < fields.size(); ++i) { if (i == 0 && m_firstColumnIsHeader) - m_model->setHeaderData(newRow, Qt::Vertical, loadVariant(guessVarType(fields.at(i)), unescapedCSV(fields.at(i))), m_roleToSave); + m_model->setHeaderData(newRow, Qt::Vertical, loadVariant(guessVarType(fields.at(i)), unescapedCSV(fields.at(i))), + q->roleToSave()); else - m_model->setData(m_model->index(newRow, i - startI), loadVariant(guessVarType(fields.at(i)), unescapedCSV(fields.at(i))), m_roleToSave); + m_model->setData(m_model->index(newRow, i - startI), loadVariant(guessVarType(fields.at(i)), unescapedCSV(fields.at(i))), + q->roleToSave()); } } } +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) reader.setCodec(oldCodec); +#endif return true; } -const QString& CsvModelSerialiser::csvSeparator() const +/*! +\property CsvModelSerialiser::csvSeparator +\accessors %csvSeparator(), setCsvSeparator() +\brief The column separator used in the CSV +\details This is the string that will be used to separate items in the same row but different columns + +By default the separator is set to a comma: , +*/ +const QString &CsvModelSerialiser::csvSeparator() const { Q_D(const CsvModelSerialiser); - + return d->m_csvSeparator; } -void CsvModelSerialiser::setCsvSeparator(const QString& val) +void CsvModelSerialiser::setCsvSeparator(const QString &val) { Q_D(CsvModelSerialiser); - + d->m_csvSeparator = val; } - +/*! +\property CsvModelSerialiser::firstRowIsHeader +\accessors %firstRowIsHeader(), setFirstRowIsHeader() +\brief Should the first row contain headers +\details If this property is set to true (the default), the serialisation will the write + the horizontal headerData of the model as the first row of the csv file + and will load the first row of the csv file as the model's horizontal headerData +*/ bool CsvModelSerialiser::firstRowIsHeader() { Q_D(const CsvModelSerialiser); - - return d->m_firstRowIsHeader; -} -bool CsvModelSerialiser::firstColumnIsHeader() -{ - Q_D(const CsvModelSerialiser); - - return d->m_firstColumnIsHeader; + return d->m_firstRowIsHeader; } void CsvModelSerialiser::setFirstRowIsHeader(bool val) { Q_D(CsvModelSerialiser); - + d->m_firstRowIsHeader = val; } +/*! +\property CsvModelSerialiser::firstColumnIsHeader +\accessors %firstColumnIsHeader(), setFirstColumnIsHeader() +\brief Should the first column contain headers +\details If this property is set to true (the default), the serialisation will the write + the vertical headerData of the model as the first column of the csv file + and will load the first column of the csv file as the model's vertical headerData +*/ +bool CsvModelSerialiser::firstColumnIsHeader() +{ + Q_D(const CsvModelSerialiser); + + return d->m_firstColumnIsHeader; +} + void CsvModelSerialiser::setFirstColumnIsHeader(bool val) { Q_D(CsvModelSerialiser); - + d->m_firstColumnIsHeader = val; } - -CsvModelSerialiser::CsvModelSerialiser(QAbstractItemModel* model) - : AbstractSingleRoleSerialiser(*new CsvModelSerialiserPrivate(this)) +/*! +Constructs a serialiser operating over \a model +*/ +CsvModelSerialiser::CsvModelSerialiser(QAbstractItemModel *model, QObject *parent) + : AbstractSingleRoleSerialiser(*new CsvModelSerialiserPrivate(this), parent) { setModel(model); } -CsvModelSerialiser::CsvModelSerialiser(const QAbstractItemModel* model) - : AbstractSingleRoleSerialiser(*new CsvModelSerialiserPrivate(this)) + +/*! +\overload + +the model will only be allowed to be saved, not loaded + */ +CsvModelSerialiser::CsvModelSerialiser(const QAbstractItemModel *model, QObject *parent) + : AbstractSingleRoleSerialiser(*new CsvModelSerialiserPrivate(this), parent) { setModel(model); } -CsvModelSerialiser::CsvModelSerialiser(CsvModelSerialiserPrivate& d) - :AbstractSingleRoleSerialiser(d) -{} - - -CsvModelSerialiser::~CsvModelSerialiser() = default; - - - +/*! +\internal +*/ +CsvModelSerialiser::CsvModelSerialiser(CsvModelSerialiserPrivate &d, QObject *parent) + : AbstractSingleRoleSerialiser(d, parent) +{ } -bool CsvModelSerialiser::saveModel(QString* destination) const +/*! +\reimp +*/ +bool CsvModelSerialiser::saveModel(QString *destination) const { if (!destination) return false; Q_D(const CsvModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; - QTextStream witer(destination, QIODevice::WriteOnly | QIODevice::Text); - return d->writeCsv(witer); + QTextStream writer(destination, QIODevice::WriteOnly | QIODevice::Text); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + return d->writeCsv(writer); } -bool CsvModelSerialiser::saveModel(QIODevice* destination) const +/*! +\reimp +*/ +bool CsvModelSerialiser::saveModel(QIODevice *destination) const { if (!destination) return false; @@ -247,27 +304,48 @@ bool CsvModelSerialiser::saveModel(QIODevice* destination) const if (!destination->isWritable()) return false; Q_D(const CsvModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; destination->setTextModeEnabled(true); - QTextStream witer(destination); - return d->writeCsv(witer); + QTextStream writer(destination); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + return d->writeCsv(writer); } -bool CsvModelSerialiser::saveModel(QByteArray* destination) const +/*! +\reimp +*/ +bool CsvModelSerialiser::saveModel(QByteArray *destination) const { if (!destination) return false; Q_D(const CsvModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; - QTextStream witer(destination, QIODevice::WriteOnly | QIODevice::Text); - return d->writeCsv(witer); + QTextStream writer(destination, QIODevice::WriteOnly | QIODevice::Text); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + return d->writeCsv(writer); +} + +/*! +Saves the model to the given \a stream +*/ +bool CsvModelSerialiser::saveModel(QTextStream &stream) const +{ + Q_D(const CsvModelSerialiser); + return d->writeCsv(stream); } -bool CsvModelSerialiser::loadModel(QIODevice* source) +/*! +\reimp +*/ +bool CsvModelSerialiser::loadModel(QIODevice *source) { if (!source) return false; @@ -278,47 +356,86 @@ bool CsvModelSerialiser::loadModel(QIODevice* source) if (!source->isReadable()) return false; Q_D(CsvModelSerialiser); - + if (!d->m_model) return false; source->setTextModeEnabled(true); QTextStream reader(source); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + reader.setCodec(textCodec()); +#endif return d->readCsv(reader); } -bool CsvModelSerialiser::loadModel(const QByteArray& source) + +/*! +\reimp +*/ +bool CsvModelSerialiser::loadModel(const QByteArray &source) { Q_D(CsvModelSerialiser); - + if (!d->m_model) return false; QTextStream reader(source, QIODevice::ReadOnly | QIODevice::Text); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + reader.setCodec(textCodec()); +#endif return d->readCsv(reader); } -bool CsvModelSerialiser::loadModel(QString* source) + +/*! +\reimp +*/ +bool CsvModelSerialiser::loadModel(QString *source) { if (!source) return false; Q_D(CsvModelSerialiser); - + if (!d->m_model) return false; QTextStream reader(source, QIODevice::ReadOnly | QIODevice::Text); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + reader.setCodec(textCodec()); +#endif return d->readCsv(reader); } +/*! +Loads the model from the given \a stream + +Data previously stored in the model will be removed +*/ +bool CsvModelSerialiser::loadModel(QTextStream &stream) +{ + Q_D(CsvModelSerialiser); + return d->readCsv(stream); +} + +/*! +\class CsvModelSerialiser + +\brief Serialiser to save and load models in csv (comma separated values) format +*/ #ifdef MS_DECLARE_STREAM_OPERATORS -QTextStream& operator<<(QTextStream & stream, const QAbstractItemModel& model) +/*! +Text stream operator to write a model +*/ +QTextStream &operator<<(QTextStream &stream, const QAbstractItemModel &model) { const QModelSerialiser mSer(&model); mSer.d_func()->writeCsv(stream); return stream; } -QTextStream& operator>>(QTextStream & stream, QAbstractItemModel& model) +/*! +Text stream operator to read a model +*/ +QTextStream &operator>>(QTextStream &stream, QAbstractItemModel &model) { QModelSerialiser mSer(&model); mSer.d_func()->readCsv(stream); return stream; } -#endif // MS_DECLARE_STREAM_OPERATORS \ No newline at end of file +#endif // MS_DECLARE_STREAM_OPERATORS diff --git a/src/csvmodelserialiser.h b/src/csvmodelserialiser.h index 085f3fd..f5035bb 100644 --- a/src/csvmodelserialiser.h +++ b/src/csvmodelserialiser.h @@ -1,3 +1,15 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef csvmodelserialiser_h__ #define csvmodelserialiser_h__ @@ -8,38 +20,40 @@ class CsvModelSerialiserPrivate; class QTextStream; class MODELUTILITIES_EXPORT CsvModelSerialiser : public AbstractSingleRoleSerialiser { - Q_GADGET + Q_OBJECT Q_PROPERTY(QString csvSeparator READ csvSeparator WRITE setCsvSeparator) Q_PROPERTY(bool firstRowIsHeader READ firstRowIsHeader WRITE setFirstRowIsHeader) Q_PROPERTY(bool firstColumnIsHeader READ firstColumnIsHeader WRITE setFirstColumnIsHeader) Q_DECLARE_PRIVATE(CsvModelSerialiser) Q_DISABLE_COPY(CsvModelSerialiser) public: - CsvModelSerialiser(QAbstractItemModel* model = Q_NULLPTR); - CsvModelSerialiser(const QAbstractItemModel* model); - ~CsvModelSerialiser(); - const QString& csvSeparator() const; - void setCsvSeparator(const QString& val); + CsvModelSerialiser(QAbstractItemModel *model = Q_NULLPTR, QObject *parent = Q_NULLPTR); + CsvModelSerialiser(const QAbstractItemModel *model, QObject *parent = Q_NULLPTR); + const QString &csvSeparator() const; + void setCsvSeparator(const QString &val); bool firstRowIsHeader(); bool firstColumnIsHeader(); void setFirstRowIsHeader(bool val); void setFirstColumnIsHeader(bool val); - Q_INVOKABLE bool saveModel(QIODevice* destination) const Q_DECL_OVERRIDE; - Q_INVOKABLE bool saveModel(QByteArray* destination) const Q_DECL_OVERRIDE; - Q_INVOKABLE virtual bool saveModel(QString* destination) const; - Q_INVOKABLE bool loadModel(QIODevice* source) Q_DECL_OVERRIDE; - Q_INVOKABLE bool loadModel(const QByteArray& source) Q_DECL_OVERRIDE; - Q_INVOKABLE virtual bool loadModel(QString* source); + virtual bool saveModel(QTextStream &stream) const; + bool saveModel(QIODevice *destination) const Q_DECL_OVERRIDE; + bool saveModel(QByteArray *destination) const Q_DECL_OVERRIDE; + bool saveModel(QString *destination) const Q_DECL_OVERRIDE; + bool loadModel(QString *source) Q_DECL_OVERRIDE; + bool loadModel(QIODevice *source) Q_DECL_OVERRIDE; + bool loadModel(const QByteArray &source) Q_DECL_OVERRIDE; + virtual bool loadModel(QTextStream &stream); + protected: - CsvModelSerialiser(CsvModelSerialiserPrivate& d); + CsvModelSerialiser(CsvModelSerialiserPrivate &d, QObject *parent); #ifdef MS_DECLARE_STREAM_OPERATORS - friend QTextStream& operator<<(QTextStream & stream, const QAbstractItemModel& model); - friend QTextStream& operator>>(QTextStream & stream, QAbstractItemModel& model); + friend QTextStream &operator<<(QTextStream &stream, const QAbstractItemModel &model); + friend QTextStream &operator>>(QTextStream &stream, QAbstractItemModel &model); #endif }; #ifdef MS_DECLARE_STREAM_OPERATORS -QTextStream& operator<<(QTextStream & stream, const QAbstractItemModel& model); -QTextStream& operator>>(QTextStream & stream, QAbstractItemModel& model); +QTextStream &operator<<(QTextStream &stream, const QAbstractItemModel &model); +QTextStream &operator>>(QTextStream &stream, QAbstractItemModel &model); #endif #endif // csvmodelserialiser_h__ \ No newline at end of file diff --git a/src/htmlmodelserialiser.cpp b/src/htmlmodelserialiser.cpp index 0b60b6f..27ca57f 100644 --- a/src/htmlmodelserialiser.cpp +++ b/src/htmlmodelserialiser.cpp @@ -1,22 +1,37 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #include "htmlmodelserialiser.h" #include "private/htmlmodelserialiser_p.h" #include #include -HtmlModelSerialiserPrivate::HtmlModelSerialiserPrivate(HtmlModelSerialiser* q) - :AbstractMultiRoleSerialiserPrivate(q) + +HtmlModelSerialiserPrivate::HtmlModelSerialiserPrivate(HtmlModelSerialiser *q) + : AbstractStringSerialiserPrivate(q) , m_printStartDocument(true) -{} +{ } -void HtmlModelSerialiserPrivate::writeHtmlElement(QXmlStreamWriter& destination, const QModelIndex& parent) const +void HtmlModelSerialiserPrivate::writeHtmlElement(QXmlStreamWriter &destination, const QModelIndex &parent) const { Q_ASSERT(m_constModel); - if (m_constModel->rowCount(parent) + m_constModel->columnCount(parent) == 0) + const int rowCount = m_constModel->rowCount(parent); + const int colCount = m_constModel->columnCount(parent); + if (rowCount + colCount == 0) return; bool foundVhead = false; bool foundHhead = false; if (!parent.isValid()) { - for (int i = 0; i < m_constModel->rowCount() && !foundVhead; ++i) { + for (int i = 0; i < rowCount && !foundVhead; ++i) { for (QList::const_iterator roleIter = m_rolesToSave.constBegin(); roleIter != m_rolesToSave.constEnd(); ++roleIter) { if (!m_constModel->headerData(i, Qt::Vertical, *roleIter).isNull()) { foundVhead = true; @@ -24,7 +39,7 @@ void HtmlModelSerialiserPrivate::writeHtmlElement(QXmlStreamWriter& destination, } } } - for (int i = 0; i < m_constModel->columnCount() && !foundHhead; ++i) { + for (int i = 0; i < colCount && !foundHhead; ++i) { for (QList::const_iterator roleIter = m_rolesToSave.constBegin(); roleIter != m_rolesToSave.constEnd(); ++roleIter) { if (!m_constModel->headerData(i, Qt::Horizontal, *roleIter).isNull()) { foundHhead = true; @@ -35,17 +50,14 @@ void HtmlModelSerialiserPrivate::writeHtmlElement(QXmlStreamWriter& destination, } destination.writeStartElement(QStringLiteral("table")); - destination.writeAttribute(QStringLiteral("data-rowcount"), QString::number(m_constModel->rowCount(parent))); - destination.writeAttribute(QStringLiteral("data-colcount"), QString::number(m_constModel->columnCount(parent))); -#ifdef QT_DEBUG - destination.writeAttribute("border", "1"); -#endif // QT_DEBUG + destination.writeAttribute(QStringLiteral("data-rowcount"), QString::number(rowCount)); + destination.writeAttribute(QStringLiteral("data-colcount"), QString::number(colCount)); if (foundHhead) { destination.writeStartElement(QStringLiteral("tr")); if (foundVhead) { destination.writeStartElement(QStringLiteral("th")); - destination.writeAttribute(QStringLiteral("scope"), QStringLiteral("col")); - destination.writeEndElement(); //th + destination.writeCharacters(QString()); + destination.writeEndElement(); // th } for (int i = 0; i < m_constModel->columnCount(); ++i) { destination.writeStartElement(QStringLiteral("th")); @@ -55,16 +67,16 @@ void HtmlModelSerialiserPrivate::writeHtmlElement(QXmlStreamWriter& destination, if (!headData.isNull()) { destination.writeStartElement("div"); destination.writeAttribute(QStringLiteral("data-rolecode"), QString::number(*roleIter)); - destination.writeAttribute(QStringLiteral("data-varianttype"), QString::number(headData.type())); + destination.writeAttribute(QStringLiteral("data-varianttype"), QString::number(headData.userType())); writeHtmlVariant(destination, headData); - destination.writeEndElement(); //div + destination.writeEndElement(); // div } } - destination.writeEndElement(); //th + destination.writeEndElement(); // th } - destination.writeEndElement(); //tr + destination.writeEndElement(); // tr } - for (int i = 0; i < m_constModel->rowCount(parent); ++i) { + for (int i = 0; i < rowCount; ++i) { destination.writeStartElement(QStringLiteral("tr")); if (foundVhead) { @@ -75,14 +87,14 @@ void HtmlModelSerialiserPrivate::writeHtmlElement(QXmlStreamWriter& destination, if (!headData.isNull()) { destination.writeStartElement("div"); destination.writeAttribute(QStringLiteral("data-rolecode"), QString::number(*roleIter)); - destination.writeAttribute(QStringLiteral("data-varianttype"), QString::number(headData.type())); + destination.writeAttribute(QStringLiteral("data-varianttype"), QString::number(headData.userType())); writeHtmlVariant(destination, headData); - destination.writeEndElement(); //div + destination.writeEndElement(); // div } } - destination.writeEndElement(); //th + destination.writeEndElement(); // th } - for (int j = 0; j < m_constModel->columnCount(parent); ++j) { + for (int j = 0; j < colCount; ++j) { destination.writeStartElement(QStringLiteral("td")); const QModelIndex currIndex = m_constModel->index(i, j, parent); for (QList::const_iterator roleIter = m_rolesToSave.constBegin(); roleIter != m_rolesToSave.constEnd(); ++roleIter) { @@ -90,47 +102,31 @@ void HtmlModelSerialiserPrivate::writeHtmlElement(QXmlStreamWriter& destination, if (!roleData.isNull()) { destination.writeStartElement("div"); destination.writeAttribute(QStringLiteral("data-rolecode"), QString::number(*roleIter)); - destination.writeAttribute(QStringLiteral("data-varianttype"), QString::number(roleData.type())); + destination.writeAttribute(QStringLiteral("data-varianttype"), QString::number(roleData.userType())); writeHtmlVariant(destination, roleData); - destination.writeEndElement(); //div + destination.writeEndElement(); // div } } if (m_constModel->hasChildren(currIndex)) writeHtmlElement(destination, currIndex); - destination.writeEndElement(); //td + destination.writeEndElement(); // td } - destination.writeEndElement(); //tr + destination.writeEndElement(); // tr } destination.writeEndElement(); // table } -bool HtmlModelSerialiserPrivate::readHtmlElement(QXmlStreamReader& source, const QModelIndex& parent) +bool HtmlModelSerialiserPrivate::readHtmlElement(QXmlStreamReader &source, const QModelIndex &parent) { - enum SableHeadCode - { - None = -1 - , Col = Qt::Horizontal - , Row = Qt::Vertical - }; + enum SableHeadCode { None = -1, Col = Qt::Horizontal, Row = Qt::Vertical }; Q_ASSERT(m_model); if (source.name() != QStringLiteral("table")) return false; const QXmlStreamAttributes tableAttributes = source.attributes(); - if ( - !tableAttributes.hasAttribute(QStringLiteral("data-rowcount")) - || !tableAttributes.hasAttribute(QStringLiteral("data-colcount")) - ) + if (!tableAttributes.hasAttribute(QStringLiteral("data-rowcount")) || !tableAttributes.hasAttribute(QStringLiteral("data-colcount"))) return false; - m_model->insertRows(0, tableAttributes.value(QStringLiteral("data-rowcount")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(), parent); - m_model->insertColumns(0, tableAttributes.value(QStringLiteral("data-colcount")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(), parent); + m_model->insertRows(0, tableAttributes.value(QStringLiteral("data-rowcount")).toInt(), parent); + m_model->insertColumns(0, tableAttributes.value(QStringLiteral("data-colcount")).toInt(), parent); bool rowStarted = false; bool cellStarted = false; int currentRow = 0; @@ -144,75 +140,52 @@ bool HtmlModelSerialiserPrivate::readHtmlElement(QXmlStreamReader& source, const if (currentRow >= m_model->rowCount(parent)) return false; rowStarted = true; - } - else if (rowStarted && !parent.isValid() && source.name() == QStringLiteral("th")) { + } else if (rowStarted && !parent.isValid() && source.name() == QStringLiteral("th")) { const QXmlStreamAttributes headAttributes = source.attributes(); - if (!headAttributes.hasAttribute(QStringLiteral("scope"))) - return false; - const QStringRef headScope = headAttributes.value(QStringLiteral("scope")); - if (headScope.compare(QStringLiteral("row")) == 0) - headCode = Row; - else if (headScope.compare(QStringLiteral("col")) == 0) - headCode = Col; - else - return false; - } - else if (rowStarted && source.name() == QStringLiteral("td")) { + if (headAttributes.hasAttribute(QStringLiteral("scope"))) { + const auto headScope = headAttributes.value(QStringLiteral("scope")); + if (headScope.compare(QStringLiteral("row")) == 0) + headCode = Row; + else if (headScope.compare(QStringLiteral("col")) == 0) + headCode = Col; + } + } else if (rowStarted && source.name() == QStringLiteral("td")) { tdFound = true; if (currentCol >= m_model->columnCount(parent)) return false; cellStarted = true; - } - else if (source.name() == QStringLiteral("div")) { + } else if (source.name() == QStringLiteral("div")) { const QXmlStreamAttributes cellAttributes = source.attributes(); - if ( - !cellAttributes.hasAttribute(QStringLiteral("data-varianttype")) - || !cellAttributes.hasAttribute(QStringLiteral("data-rolecode")) - ) + if (!cellAttributes.hasAttribute(QStringLiteral("data-varianttype")) || !cellAttributes.hasAttribute(QStringLiteral("data-rolecode"))) return false; - const int cellRole = cellAttributes.value(QStringLiteral("data-rolecode")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(); - const int cellType = cellAttributes.value(QStringLiteral("data-varianttype")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(); + const int cellRole = cellAttributes.value(QStringLiteral("data-rolecode")).toInt(); + const int cellType = cellAttributes.value(QStringLiteral("data-varianttype")).toInt(); if (cellStarted) { m_model->setData(m_model->index(currentRow, currentCol, parent), readHtmlVariant(source, cellType), cellRole); - } - else if (headCode == Row) { + } else if (headCode == Row) { m_model->setHeaderData(currentRow, Qt::Vertical, readHtmlVariant(source, cellType), cellRole); - } - else if (headCode == Col) { + } else if (headCode == Col) { m_model->setHeaderData(currentCol, Qt::Horizontal, readHtmlVariant(source, cellType), cellRole); } - } - else if (cellStarted && source.name() == QStringLiteral("table")) { + } else if (cellStarted && source.name() == QStringLiteral("table")) { if (!readHtmlElement(source, m_model->index(currentRow, currentCol, parent))) return false; } - } - else if (source.isEndElement()) { + } else if (source.isEndElement()) { if (source.name() == QStringLiteral("tr")) { rowStarted = false; currentCol = 0; if (tdFound) ++currentRow; tdFound = false; - } - else if (rowStarted && source.name() == QStringLiteral("td")) { + } else if (rowStarted && source.name() == QStringLiteral("td")) { ++currentCol; cellStarted = false; - } - else if (rowStarted && source.name() == QStringLiteral("th")) { + } else if (rowStarted && source.name() == QStringLiteral("th")) { if (headCode == Col) ++currentCol; headCode = None; - } - else if (source.name() == QStringLiteral("table")) { + } else if (source.name() == QStringLiteral("table")) { return true; } } @@ -220,28 +193,42 @@ bool HtmlModelSerialiserPrivate::readHtmlElement(QXmlStreamReader& source, const return false; } -bool HtmlModelSerialiserPrivate::writeHtml(QXmlStreamWriter& writer) const +bool HtmlModelSerialiserPrivate::writeHtml(QXmlStreamWriter &writer) const { if (!m_constModel) return false; if (m_printStartDocument) { writer.writeDTD(QStringLiteral("")); writer.writeStartElement(QStringLiteral("html")); + writer.writeStartElement(QStringLiteral("head")); + writer.writeEmptyElement(QStringLiteral("meta")); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.writeAttribute(QStringLiteral("charset"), writer.codec()->name()); +#else + writer.writeAttribute(QStringLiteral("charset"), QStringLiteral("UTF-8")); +#endif + writer.writeStartElement(QStringLiteral("title")); + const QString modelName = m_constModel->objectName(); + writer.writeCharacters(modelName.isEmpty() ? QStringLiteral("Model") : modelName); + writer.writeEndElement(); // title + writer.writeStartElement(QStringLiteral("style")); + writer.writeCharacters(QStringLiteral("table, th, td { border: 1px solid black; } table { border-collapse: collapse; width: 100%; }")); + writer.writeEndElement(); // style + writer.writeEndElement(); // head writer.writeStartElement(QStringLiteral("body")); } writer.writeStartElement("div"); writer.writeAttribute(QStringLiteral("data-modelcode"), Magic_Model_Header); writeHtmlElement(writer); - writer.writeEndElement(); //div + writer.writeEndElement(); // div if (m_printStartDocument) { - writer.writeEndElement(); //body - writer.writeEndElement(); //html + writer.writeEndElement(); // body + writer.writeEndElement(); // html } return !writer.hasError(); } - -bool HtmlModelSerialiserPrivate::readHtml(QXmlStreamReader& reader) +bool HtmlModelSerialiserPrivate::readHtml(QXmlStreamReader &reader) { if (!m_model) return false; @@ -258,16 +245,14 @@ bool HtmlModelSerialiserPrivate::readHtml(QXmlStreamReader& reader) if (mainDivAttribute.value(QStringLiteral("data-modelcode")).compare(Magic_Model_Header) != 0) return false; mainDivStarted = true; - } - else if (mainDivStarted&& reader.name() == QStringLiteral("table")) { + } else if (mainDivStarted && reader.name() == QStringLiteral("table")) { if (!readHtmlElement(reader)) { m_model->removeColumns(0, m_model->columnCount()); m_model->removeRows(0, m_model->rowCount()); return false; } } - } - else if (reader.isEndElement()) { + } else if (reader.isEndElement()) { if (reader.name() == QStringLiteral("div")) { mainDivStarted = false; } @@ -281,10 +266,9 @@ bool HtmlModelSerialiserPrivate::readHtml(QXmlStreamReader& reader) return true; } - -void HtmlModelSerialiserPrivate::writeHtmlVariant(QXmlStreamWriter& writer, const QVariant& val) +void HtmlModelSerialiserPrivate::writeHtmlVariant(QXmlStreamWriter &writer, const QVariant &val) { - if (isImageType(val.type())) { + if (isImageType(val.userType())) { writer.writeEmptyElement(QStringLiteral("img")); writer.writeAttribute(QStringLiteral("src"), "data:image/png;base64," + saveVariant(val)); writer.writeAttribute(QStringLiteral("alt"), QStringLiteral("modelimage.png")); @@ -293,7 +277,7 @@ void HtmlModelSerialiserPrivate::writeHtmlVariant(QXmlStreamWriter& writer, cons writer.writeCharacters(saveVariant(val)); } -QVariant HtmlModelSerialiserPrivate::readHtmlVariant(QXmlStreamReader& reader, int valType) +QVariant HtmlModelSerialiserPrivate::readHtmlVariant(QXmlStreamReader &reader, int valType) { if (isImageType(valType)) { if (!reader.readNextStartElement()) @@ -308,54 +292,76 @@ QVariant HtmlModelSerialiserPrivate::readHtmlVariant(QXmlStreamReader& reader, i return loadVariant(valType, reader.readElementText()); } - -HtmlModelSerialiser::HtmlModelSerialiser(QAbstractItemModel* model) - : AbstractMultiRoleSerialiser(*new HtmlModelSerialiserPrivate(this)) +/*! +Constructs a serialiser operating over \a model +*/ +HtmlModelSerialiser::HtmlModelSerialiser(QAbstractItemModel *model, QObject *parent) + : AbstractStringSerialiser(*new HtmlModelSerialiserPrivate(this), parent) { setModel(model); } -HtmlModelSerialiser::HtmlModelSerialiser(const QAbstractItemModel* model) - : AbstractMultiRoleSerialiser(*new HtmlModelSerialiserPrivate(this)) + +/*! +\overload + +the model will only be allowed to be saved, not loaded +*/ +HtmlModelSerialiser::HtmlModelSerialiser(const QAbstractItemModel *model, QObject *parent) + : AbstractStringSerialiser(*new HtmlModelSerialiserPrivate(this), parent) { setModel(model); } -HtmlModelSerialiser::HtmlModelSerialiser(HtmlModelSerialiserPrivate& d) - :AbstractMultiRoleSerialiser(d) -{} - - -HtmlModelSerialiser::~HtmlModelSerialiser() = default; - +/*! +\internal +*/ +HtmlModelSerialiser::HtmlModelSerialiser(HtmlModelSerialiserPrivate &d, QObject *parent) + : AbstractStringSerialiser(d, parent) +{ } +/*! +\property HtmlModelSerialiser::printStartDocument +\accessors %printStartDocument(), setPrintStartDocument() +\brief This property determines if the start of the html document should be written +\details If this property is set to \c true (the default) the serialiser will write the \c starting block. +Set this to false to save the model as part of a larger html document or if you want to specify your custom css in the \c +*/ bool HtmlModelSerialiser::printStartDocument() const { Q_D(const HtmlModelSerialiser); - + return d->m_printStartDocument; } void HtmlModelSerialiser::setPrintStartDocument(bool val) { Q_D(HtmlModelSerialiser); - + d->m_printStartDocument = val; } - -bool HtmlModelSerialiser::saveModel(QString* destination) const +/*! +\reimp +*/ +bool HtmlModelSerialiser::saveModel(QString *destination) const { if (!destination) return false; Q_D(const HtmlModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; - QXmlStreamWriter witer(destination); - return d->writeHtml(witer); + QXmlStreamWriter writer(destination); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + return d->writeHtml(writer); } -bool HtmlModelSerialiser::saveModel(QIODevice* destination) const +/*! +\reimp +*/ +bool HtmlModelSerialiser::saveModel(QIODevice *destination) const { if (!destination) return false; @@ -366,26 +372,38 @@ bool HtmlModelSerialiser::saveModel(QIODevice* destination) const if (!destination->isWritable()) return false; Q_D(const HtmlModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; - QXmlStreamWriter witer(destination); - return d->writeHtml(witer); + QXmlStreamWriter writer(destination); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + return d->writeHtml(writer); } -bool HtmlModelSerialiser::saveModel(QByteArray* destination) const +/*! +\reimp +*/ +bool HtmlModelSerialiser::saveModel(QByteArray *destination) const { if (!destination) return false; Q_D(const HtmlModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; - QXmlStreamWriter witer(destination); - return d->writeHtml(witer); + QXmlStreamWriter writer(destination); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + return d->writeHtml(writer); } -bool HtmlModelSerialiser::loadModel(QIODevice* source) +/*! +\reimp +*/ +bool HtmlModelSerialiser::loadModel(QIODevice *source) { if (!source) return false; @@ -396,29 +414,43 @@ bool HtmlModelSerialiser::loadModel(QIODevice* source) if (!source->isReadable()) return false; Q_D(HtmlModelSerialiser); - + if (!d->m_model) return false; QXmlStreamReader reader(source); return d->readHtml(reader); } -bool HtmlModelSerialiser::loadModel(const QByteArray& source) + +/*! +\reimp +*/ +bool HtmlModelSerialiser::loadModel(const QByteArray &source) { Q_D(HtmlModelSerialiser); - + if (!d->m_model) return false; QXmlStreamReader reader(source); return d->readHtml(reader); } -bool HtmlModelSerialiser::loadModel(QString* source) + +/*! +\reimp +*/ +bool HtmlModelSerialiser::loadModel(QString *source) { if (!source) return false; Q_D(HtmlModelSerialiser); - + if (!d->m_model) return false; QXmlStreamReader reader(*source); return d->readHtml(reader); } + +/*! +\class HtmlModelSerialiser + +\brief Serialiser to save and load models in HTML format +*/ diff --git a/src/htmlmodelserialiser.h b/src/htmlmodelserialiser.h index ca79452..eafb603 100644 --- a/src/htmlmodelserialiser.h +++ b/src/htmlmodelserialiser.h @@ -1,28 +1,40 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef htmlmodelserialiser_h__ #define htmlmodelserialiser_h__ #include "modelutilities_global.h" -#include "abstractmultiroleserialiser.h" +#include "abstractstringserialiser.h" class HtmlModelSerialiserPrivate; -class MODELUTILITIES_EXPORT HtmlModelSerialiser : public AbstractMultiRoleSerialiser +class MODELUTILITIES_EXPORT HtmlModelSerialiser : public AbstractStringSerialiser { - Q_GADGET + Q_OBJECT Q_PROPERTY(bool printStartDocument READ printStartDocument WRITE setPrintStartDocument) Q_DECLARE_PRIVATE(HtmlModelSerialiser) Q_DISABLE_COPY(HtmlModelSerialiser) public: - HtmlModelSerialiser(QAbstractItemModel* model = Q_NULLPTR); - HtmlModelSerialiser(const QAbstractItemModel* model); - ~HtmlModelSerialiser(); + HtmlModelSerialiser(QAbstractItemModel *model = Q_NULLPTR, QObject *parent = Q_NULLPTR); + HtmlModelSerialiser(const QAbstractItemModel *model, QObject *parent = Q_NULLPTR); bool printStartDocument() const; void setPrintStartDocument(bool val); - Q_INVOKABLE bool saveModel(QIODevice* destination) const Q_DECL_OVERRIDE; - Q_INVOKABLE bool saveModel(QByteArray* destination) const Q_DECL_OVERRIDE; - Q_INVOKABLE virtual bool saveModel(QString* destination) const; - Q_INVOKABLE bool loadModel(QIODevice* source) Q_DECL_OVERRIDE; - Q_INVOKABLE bool loadModel(const QByteArray& source) Q_DECL_OVERRIDE; - Q_INVOKABLE virtual bool loadModel(QString* source); + bool saveModel(QIODevice *destination) const Q_DECL_OVERRIDE; + bool saveModel(QByteArray *destination) const Q_DECL_OVERRIDE; + bool saveModel(QString *destination) const Q_DECL_OVERRIDE; + bool loadModel(QString *source) Q_DECL_OVERRIDE; + bool loadModel(QIODevice *source) Q_DECL_OVERRIDE; + bool loadModel(const QByteArray &source) Q_DECL_OVERRIDE; + protected: - HtmlModelSerialiser(HtmlModelSerialiserPrivate& d); + HtmlModelSerialiser(HtmlModelSerialiserPrivate &d, QObject *parent); }; #endif // htmlmodelserialiser_h__ \ No newline at end of file diff --git a/src/insertproxymodel.cpp b/src/insertproxymodel.cpp index 6ccebfa..486b8b4 100644 --- a/src/insertproxymodel.cpp +++ b/src/insertproxymodel.cpp @@ -1,3 +1,15 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #include "private/insertproxymodel_p.h" #include "insertproxymodel.h" #include @@ -5,10 +17,7 @@ #include #include -/*! -\internal -*/ -QVector InsertProxyModelPrivate::setDataInContainer(RolesContainer& baseHash, int role, const QVariant& value) +QVector InsertProxyModelPrivate::setDataInContainer(RolesContainer &baseHash, int role, const QVariant &value) { QVector changedRoles; QVector adjRoles; @@ -24,14 +33,12 @@ QVector InsertProxyModelPrivate::setDataInContainer(RolesContainer& baseHas continue; baseHash.insert(*adjRole, value); changedRoles.append(*adjRole); - } - else { + } else { Q_ASSERT(dataIter.value().isValid()); - if (!value.isValid()){ + if (!value.isValid()) { baseHash.erase(dataIter); changedRoles.append(*adjRole); - } - else { + } else { if (dataIter.value() != value) { dataIter.value() = value; changedRoles.append(*adjRole); @@ -42,10 +49,7 @@ QVector InsertProxyModelPrivate::setDataInContainer(RolesContainer& baseHas return changedRoles; } -/*! -\internal -*/ -void InsertProxyModelPrivate::checkExtraDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector &roles) +void InsertProxyModelPrivate::checkExtraDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { Q_UNUSED(topLeft) Q_UNUSED(roles) @@ -54,29 +58,22 @@ void InsertProxyModelPrivate::checkExtraDataChanged(const QModelIndex& topLeft, return; const int sourceCols = q->sourceModel()->columnCount(); const int sourceRows = q->sourceModel()->rowCount(); - if(bottomRight.column() >= sourceCols && bottomRight.row() >= sourceRows) + if (bottomRight.column() >= sourceCols && bottomRight.row() >= sourceRows) return; // the corner changed if (bottomRight.column() >= sourceCols) { if (q->validColumn()) commitColumn(); - } - else if (bottomRight.row() >= sourceRows) { + } else if (bottomRight.row() >= sourceRows) { if (q->validRow()) commitRow(); } } -/*! -\internal -*/ bool InsertProxyModelPrivate::commitRow() { return commitToSource(true); } -/*! -\internal -*/ bool InsertProxyModelPrivate::commitToSource(const bool isRow) { Q_Q(InsertProxyModel); @@ -92,18 +89,13 @@ bool InsertProxyModelPrivate::commitToSource(const bool isRow) return false; const int loopEnd = isRow ? sourceCols : sourceRows; for (int i = 0; i < loopEnd; ++i) { - RolesContainer& allRoles = m_extraData[isRow][i]; + RolesContainer &allRoles = m_extraData[isRow][i]; if (allRoles.isEmpty()) continue; - const auto endRoles = allRoles.constEnd(); - const QModelIndex currentIdx = - isRow ? - q->sourceModel()->index(sourceRows, i) - : q->sourceModel()->index(i, sourceCols) - ; + const QModelIndex currentIdx = isRow ? q->sourceModel()->index(sourceRows, i) : q->sourceModel()->index(i, sourceCols); Q_ASSERT(m_separateEditDisplay || (allRoles.contains(Qt::DisplayRole) == allRoles.contains(Qt::EditRole))); Q_ASSERT(m_separateEditDisplay || (allRoles.value(Qt::DisplayRole) == allRoles.value(Qt::EditRole))); - if(!q->sourceModel()->setItemData(currentIdx, convertFromContainer >(allRoles))) + if (!q->sourceModel()->setItemData(currentIdx, convertFromContainer>(allRoles))) return false; QVector aggregateRoles; aggregateRoles.reserve(allRoles.size()); @@ -116,27 +108,22 @@ bool InsertProxyModelPrivate::commitToSource(const bool isRow) return true; } -/*! -\internal -*/ -int InsertProxyModelPrivate::mergeEditDisplayHash(RolesContainer& singleHash) +int InsertProxyModelPrivate::mergeEditDisplayHash(RolesContainer &singleHash) { const auto displayIter = singleHash.constFind(Qt::DisplayRole); const auto editIter = m_extraHeaderData->find(Qt::EditRole); - if (displayIter == singleHash.cend()){ + if (displayIter == singleHash.cend()) { if (editIter != singleHash.end()) { m_extraHeaderData->insert(Qt::DisplayRole, editIter.value()); return Qt::DisplayRole; } - } - else { - if (editIter != singleHash.end()){ - if (editIter.value() != displayIter.value()){ + } else { + if (editIter != singleHash.end()) { + if (editIter.value() != displayIter.value()) { editIter.value() = displayIter.value(); return Qt::EditRole; } - } - else { + } else { m_extraHeaderData->insert(Qt::EditRole, displayIter.value()); return Qt::EditRole; } @@ -144,9 +131,6 @@ int InsertProxyModelPrivate::mergeEditDisplayHash(RolesContainer& singleHash) return -1; } -/*! -\internal -*/ void InsertProxyModelPrivate::onInserted(bool isRow, const QModelIndex &parent, int first, int last) { if (parent.isValid()) @@ -157,9 +141,6 @@ void InsertProxyModelPrivate::onInserted(bool isRow, const QModelIndex &parent, isRow ? q->endInsertRows() : q->endInsertColumns(); } -/*! -\internal -*/ void InsertProxyModelPrivate::onMoved(bool isRow, const QModelIndex &parent, int start, int end, const QModelIndex &destination, int destIdx) { if (parent.isValid()) @@ -172,9 +153,6 @@ void InsertProxyModelPrivate::onMoved(bool isRow, const QModelIndex &parent, int isRow ? q->endMoveRows() : q->endMoveColumns(); } -/*! -\internal -*/ void InsertProxyModelPrivate::onRemoved(bool isRow, const QModelIndex &parent, int first, int last) { if (parent.isValid()) @@ -185,19 +163,13 @@ void InsertProxyModelPrivate::onRemoved(bool isRow, const QModelIndex &parent, i isRow ? q->endRemoveRows() : q->endRemoveColumns(); } -/*! -\internal -*/ bool InsertProxyModelPrivate::commitColumn() { return commitToSource(false); } -/*! -\internal -*/ -InsertProxyModelPrivate::InsertProxyModelPrivate(InsertProxyModel* q) - :q_ptr(q) +InsertProxyModelPrivate::InsertProxyModelPrivate(InsertProxyModel *q) + : q_ptr(q) , m_insertDirection(InsertProxyModel::NoInsert) , m_separateEditDisplay(false) { @@ -206,16 +178,30 @@ InsertProxyModelPrivate::InsertProxyModelPrivate(InsertProxyModel* q) starHeader.insert(Qt::DisplayRole, QVariant::fromValue(QStringLiteral("*"))); m_extraHeaderData[true] = starHeader; m_extraHeaderData[false] = starHeader; - QObject::connect(q_ptr, &InsertProxyModel::extraDataChanged, [this](const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles)->void {checkExtraDataChanged(topLeft, bottomRight, roles); }); + QObject::connect(q_ptr, &InsertProxyModel::extraDataChanged, + [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) -> void { + checkExtraDataChanged(topLeft, bottomRight, roles); + }); } /*! Constructs a new proxy model with the given \a parent. */ -InsertProxyModel::InsertProxyModel(QObject* parent) +InsertProxyModel::InsertProxyModel(QObject *parent) : QAbstractProxyModel(parent) , m_dptr(new InsertProxyModelPrivate(this)) -{} +{ } + +/*! +Constructor used only while subclassing the private class. +Not part of the public API +*/ +InsertProxyModel::InsertProxyModel(InsertProxyModelPrivate &dptr, QObject *parent) + : QAbstractProxyModel(parent) + , m_dptr(&dptr) +{ + Q_ASSERT(m_dptr); +} /*! Destructor @@ -231,71 +217,98 @@ InsertProxyModel::~InsertProxyModel() /*! \reimp */ -void InsertProxyModel::setSourceModel(QAbstractItemModel* newSourceModel) +void InsertProxyModel::setSourceModel(QAbstractItemModel *newSourceModel) { + if (newSourceModel == sourceModel()) + return; Q_D(InsertProxyModel); beginResetModel(); if (sourceModel()) { for (auto discIter = d->m_sourceConnections.cbegin(); discIter != d->m_sourceConnections.cend(); ++discIter) - Q_ASSUME(QObject::disconnect(*discIter)); + QObject::disconnect(*discIter); } QAbstractProxyModel::setSourceModel(newSourceModel); d->m_sourceConnections.clear(); - for(auto& extraData : d->m_extraData) + for (auto &extraData : d->m_extraData) extraData.clear(); if (sourceModel()) { d->m_sourceConnections - << QObject::connect(sourceModel(), &QAbstractItemModel::modelAboutToBeReset, this, &QAbstractItemModel::modelAboutToBeReset) - << QObject::connect(sourceModel(), &QAbstractItemModel::modelReset, this, &QAbstractItemModel::modelReset) - << QObject::connect(sourceModel(), &QAbstractItemModel::dataChanged, [this](const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) { - dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight), roles); - }) - << QObject::connect(sourceModel(), &QAbstractItemModel::headerDataChanged, this, &QAbstractItemModel::headerDataChanged) - << QObject::connect(sourceModel(), &QAbstractItemModel::layoutAboutToBeChanged, [d](const QList &parents, QAbstractItemModel::LayoutChangeHint hint) {d->beforeLayoutChange(parents, hint); }) - << QObject::connect(sourceModel(), &QAbstractItemModel::layoutChanged, [d](const QList &parents, QAbstractItemModel::LayoutChangeHint hint) {d->afetrLayoutChange(parents, hint);}) - << QObject::connect(sourceModel(), &QAbstractItemModel::columnsAboutToBeInserted, [this](const QModelIndex &parent, int first, int last) { - if (!parent.isValid()) { - beginInsertColumns(QModelIndex(), first, last); - } - }) - << QObject::connect(sourceModel(), &QAbstractItemModel::columnsAboutToBeMoved, [this](const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn) { - if (sourceParent.isValid()) - return; - if (destinationParent.isValid()) - beginRemoveColumns(QModelIndex(), sourceStart, sourceEnd); - else - beginMoveColumns(QModelIndex(), sourceStart, sourceEnd, QModelIndex(), destinationColumn); - }) - << QObject::connect(sourceModel(), &QAbstractItemModel::columnsAboutToBeRemoved, [this](const QModelIndex &parent, int first, int last) { - if (!parent.isValid()) { - beginRemoveColumns(QModelIndex(), first, last); - } - }) - << QObject::connect(sourceModel(), &QAbstractItemModel::columnsInserted,[d](const QModelIndex &parent, int first, int last)->void{d->onColumnsInserted(parent,first,last);}) - << QObject::connect(sourceModel(), &QAbstractItemModel::columnsRemoved,[d](const QModelIndex &parent, int first, int last)->void{d->onColumnsRemoved(parent,first,last);}) - << QObject::connect(sourceModel(), &QAbstractItemModel::columnsMoved, [d](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column)->void{d->onColumnsMoved(parent,start,end,destination,column);}) - << QObject::connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeInserted, [this](const QModelIndex &parent, int first, int last) { - if (!parent.isValid()) { - beginInsertRows(QModelIndex(), first, last); - } - }) - << QObject::connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeMoved, [this](const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) { - if (sourceParent.isValid()) - return; - if (destinationParent.isValid()) - beginRemoveRows(QModelIndex(), sourceStart, sourceEnd); - else - beginMoveRows(QModelIndex(), sourceStart, sourceEnd, QModelIndex(), destinationRow); - }) - << QObject::connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved, [this](const QModelIndex &parent, int first, int last) { - if (!parent.isValid()) { - beginRemoveRows(QModelIndex(), first, last); - } - }) - << QObject::connect(sourceModel(), &QAbstractItemModel::rowsInserted, [d](const QModelIndex &parent, int first, int last)->void{d->onRowsInserted(parent,first,last);}) - << QObject::connect(sourceModel(), &QAbstractItemModel::rowsRemoved, [d](const QModelIndex &parent, int first, int last)->void{d->onRowsRemoved(parent,first,last);}) - << QObject::connect(sourceModel(), &QAbstractItemModel::rowsMoved, [d](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)->void{d->onRowsMoved(parent,start,end,destination,row);}) - ; + << QObject::connect(sourceModel(), &QAbstractItemModel::destroyed, [this]() -> void { setSourceModel(Q_NULLPTR); }) + << QObject::connect(sourceModel(), &QAbstractItemModel::dataChanged, + [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { + dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight), roles); + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::headerDataChanged, this, &QAbstractItemModel::headerDataChanged) + << QObject::connect(sourceModel(), &QAbstractItemModel::layoutAboutToBeChanged, + [d](const QList &parents, QAbstractItemModel::LayoutChangeHint hint) { + d->beforeLayoutChange(parents, hint); + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::layoutChanged, + [d](const QList &parents, QAbstractItemModel::LayoutChangeHint hint) { + d->afetrLayoutChange(parents, hint); + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::columnsAboutToBeInserted, + [this](const QModelIndex &parent, int first, int last) { + if (!parent.isValid()) { + beginInsertColumns(QModelIndex(), first, last); + } + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::columnsAboutToBeMoved, + [this](const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, + int destinationColumn) { + if (sourceParent.isValid()) + return; + if (destinationParent.isValid()) + beginRemoveColumns(QModelIndex(), sourceStart, sourceEnd); + else + beginMoveColumns(QModelIndex(), sourceStart, sourceEnd, QModelIndex(), destinationColumn); + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::columnsAboutToBeRemoved, + [this](const QModelIndex &parent, int first, int last) { + if (!parent.isValid()) { + beginRemoveColumns(QModelIndex(), first, last); + } + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::columnsInserted, + [d](const QModelIndex &parent, int first, int last) -> void { d->onColumnsInserted(parent, first, last); }) + << QObject::connect(sourceModel(), &QAbstractItemModel::columnsRemoved, + [d](const QModelIndex &parent, int first, int last) -> void { d->onColumnsRemoved(parent, first, last); }) + << QObject::connect(sourceModel(), &QAbstractItemModel::columnsMoved, + [d](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column) -> void { + d->onColumnsMoved(parent, start, end, destination, column); + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeInserted, + [this](const QModelIndex &parent, int first, int last) { + if (!parent.isValid()) { + beginInsertRows(QModelIndex(), first, last); + } + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeMoved, + [this](const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, + int destinationRow) { + if (sourceParent.isValid()) + return; + if (destinationParent.isValid()) + beginRemoveRows(QModelIndex(), sourceStart, sourceEnd); + else + beginMoveRows(QModelIndex(), sourceStart, sourceEnd, QModelIndex(), destinationRow); + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved, + [this](const QModelIndex &parent, int first, int last) { + if (!parent.isValid()) { + beginRemoveRows(QModelIndex(), first, last); + } + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::rowsInserted, + [d](const QModelIndex &parent, int first, int last) -> void { d->onRowsInserted(parent, first, last); }) + << QObject::connect(sourceModel(), &QAbstractItemModel::rowsRemoved, + [d](const QModelIndex &parent, int first, int last) -> void { d->onRowsRemoved(parent, first, last); }) + << QObject::connect(sourceModel(), &QAbstractItemModel::rowsMoved, + [d](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) -> void { + d->onRowsMoved(parent, start, end, destination, row); + }) + << QObject::connect(sourceModel(), &QAbstractItemModel::modelAboutToBeReset, [this]() -> void { beginResetModel(); }) + << QObject::connect(sourceModel(), &QAbstractItemModel::modelReset, [d]() -> void { d->afterReset(); }); const int sourceCols = sourceModel()->columnCount(); const int sourceRows = sourceModel()->rowCount(); for (int i = 0; i < sourceRows; ++i) @@ -303,10 +316,27 @@ void InsertProxyModel::setSourceModel(QAbstractItemModel* newSourceModel) for (int i = 0; i < sourceCols; ++i) d->m_extraData[1].append(RolesContainer()); } - + endResetModel(); } +/*! +\internal +*/ +void InsertProxyModelPrivate::afterReset() +{ + Q_Q(InsertProxyModel); + for (auto &extraData : m_extraData) + extraData.clear(); + const int sourceCols = q->sourceModel()->columnCount(); + const int sourceRows = q->sourceModel()->rowCount(); + for (int i = 0; i < sourceRows; ++i) + m_extraData[0].append(RolesContainer()); + for (int i = 0; i < sourceCols; ++i) + m_extraData[1].append(RolesContainer()); + q->endResetModel(); +} + /*! \reimp */ @@ -327,7 +357,7 @@ QModelIndex InsertProxyModel::buddy(const QModelIndex &index) const */ bool InsertProxyModel::hasChildren(const QModelIndex &parent) const { - return !parent.isValid(); + return rowCount(parent) > 0 && columnCount(parent) > 0; } /*! @@ -371,10 +401,8 @@ QVariant InsertProxyModel::headerData(int section, Qt::Orientation orientation, return QVariant(); Q_D(const InsertProxyModel); const int adjRole = (!d->m_separateEditDisplay && (role == Qt::EditRole)) ? Qt::DisplayRole : role; - if ( - (orientation == Qt::Horizontal && (d->m_insertDirection & InsertColumn) && section == sourceModel()->columnCount()) - || (orientation == Qt::Vertical && (d->m_insertDirection & InsertRow) && section == sourceModel()->rowCount()) - ) + if ((orientation == Qt::Horizontal && (d->m_insertDirection & InsertColumn) && section == sourceModel()->columnCount()) + || (orientation == Qt::Vertical && (d->m_insertDirection & InsertRow) && section == sourceModel()->rowCount())) return d->m_extraHeaderData[orientation == Qt::Vertical].value(adjRole); return sourceModel()->headerData(section, orientation, role); } @@ -387,11 +415,8 @@ bool InsertProxyModel::setHeaderData(int section, Qt::Orientation orientation, c Q_D(InsertProxyModel); if (!sourceModel()) return false; - if ( - (orientation == Qt::Horizontal && (d->m_insertDirection & InsertColumn) && section == sourceModel()->columnCount()) - || (orientation == Qt::Vertical && (d->m_insertDirection & InsertRow) && section == sourceModel()->rowCount()) - ) - { + if ((orientation == Qt::Horizontal && (d->m_insertDirection & InsertColumn) && section == sourceModel()->columnCount()) + || (orientation == Qt::Vertical && (d->m_insertDirection & InsertRow) && section == sourceModel()->rowCount())) { const QVector changedRoles = d->setDataInContainer(d->m_extraHeaderData[orientation == Qt::Vertical], role, value); if (!changedRoles.isEmpty()) headerDataChanged(orientation, section, section); @@ -434,16 +459,12 @@ bool InsertProxyModel::setData(const QModelIndex &index, const QVariant &value, const int sourceRows = sourceModel()->rowCount(); const int sourceCols = sourceModel()->columnCount(); Q_D(InsertProxyModel); - if ( - index.parent().isValid() - || index.row() > sourceRows - || index.column() > sourceCols + if (index.parent().isValid() || index.row() > sourceRows || index.column() > sourceCols || (index.row() == sourceRows && !(d->m_insertDirection & InsertRow)) - || (index.column() == sourceCols && !(d->m_insertDirection & InsertColumn)) - ) + || (index.column() == sourceCols && !(d->m_insertDirection & InsertColumn))) return false; - const bool isExtraRow = index.row() == sourceRows && d->m_insertDirection & InsertRow; - const bool isExtraCol = index.column() == sourceCols && d->m_insertDirection & InsertColumn; + const bool isExtraRow = index.row() == sourceRows && d->m_insertDirection & InsertRow; + const bool isExtraCol = index.column() == sourceCols && d->m_insertDirection & InsertColumn; if (isExtraRow && isExtraCol) { setDataForCorner(value, role); return true; @@ -452,7 +473,7 @@ bool InsertProxyModel::setData(const QModelIndex &index, const QVariant &value, return sourceModel()->setData(sourceModel()->index(index.row(), index.column()), value, role); const int sectionIdx = isExtraRow ? index.column() : index.row(); QVector changedRoles = d->setDataInContainer(d->m_extraData[isExtraRow][sectionIdx], role, value); - if (!changedRoles.isEmpty()){ + if (!changedRoles.isEmpty()) { dataChanged(index, index, changedRoles); extraDataChanged(index, index, changedRoles); return true; @@ -462,6 +483,8 @@ bool InsertProxyModel::setData(const QModelIndex &index, const QVariant &value, /*! \reimp +\details If the map contains both Qt::DisplayRole and Qt::EditRole and separateEditDisplay is set to false, +the value of Qt::DisplayRole will prevail. */ bool InsertProxyModel::setItemData(const QModelIndex &index, const QMap &roles) { @@ -472,63 +495,78 @@ bool InsertProxyModel::setItemData(const QModelIndex &index, const QMaprowCount(); const int sourceCols = sourceModel()->columnCount(); Q_D(InsertProxyModel); - if ( - index.parent().isValid() - || index.row() > sourceRows - || index.column() > sourceCols + if (index.parent().isValid() || index.row() > sourceRows || index.column() > sourceCols || (index.row() == sourceRows && !(d->m_insertDirection & InsertRow)) - || (index.column() == sourceCols && !(d->m_insertDirection & InsertColumn)) - ) + || (index.column() == sourceCols && !(d->m_insertDirection & InsertColumn))) return false; - const bool isExtraRow = index.row() == sourceRows && d->m_insertDirection & InsertRow; - const bool isExtraCol = index.column() == sourceCols && d->m_insertDirection & InsertColumn; + const bool isExtraRow = index.row() == sourceRows && d->m_insertDirection & InsertRow; + const bool isExtraCol = index.column() == sourceCols && d->m_insertDirection & InsertColumn; if (!(isExtraRow || isExtraCol)) return sourceModel()->setItemData(sourceModel()->index(index.row(), index.column()), roles); const int sectionIdx = isExtraRow ? index.column() : index.row(); - const RolesContainer& oldData = (isExtraRow && isExtraCol) ? d->m_dataForCorner : d->m_extraData[isExtraRow][sectionIdx]; - RolesContainer newData; + RolesContainer *const dataContainer = (isExtraRow && isExtraCol) ? &d->m_dataForCorner : &d->m_extraData[isExtraRow][sectionIdx]; QVector changedRoles; - bool hasDisplay = false; - bool hasEdit = false; for (auto i = roles.cbegin(); i != roles.cend(); ++i) { if (i.value().isValid()) { - newData.insert(i.key(), i.value()); - const auto oldDataIter = oldData.constFind(i.key()); - if (oldDataIter == oldData.constEnd() || oldDataIter.value() != i.value()) + const auto oldDataIter = dataContainer->find(i.key()); + if (oldDataIter == dataContainer->end()) { + if (i.value().isValid()) { + changedRoles << i.key(); + dataContainer->insert(i.key(), i.value()); + } + } else if (!i.value().isValid()) { changedRoles << i.key(); - if (i.key() == Qt::DisplayRole) - hasDisplay = true; - if (i.key() == Qt::EditRole) - hasEdit = true; - } - } - if (!d->m_separateEditDisplay && hasDisplay != hasEdit) { - if(hasDisplay){ - const QVariant displayData = newData.value(Qt::DisplayRole); - newData.insert(Qt::EditRole, displayData); - const auto oldDataIter = oldData.constFind(Qt::EditRole); - if (oldDataIter == oldData.constEnd() || oldDataIter.value() != displayData) { - changedRoles << Qt::EditRole; + dataContainer->erase(oldDataIter); + continue; + } else if (oldDataIter.value() != i.value()) { + changedRoles << i.key(); + oldDataIter.value() = i.value(); } } - else { - const QVariant editData = newData.value(Qt::EditRole); - newData.insert(Qt::DisplayRole, editData); - const auto oldDataIter = oldData.constFind(Qt::DisplayRole); - if (oldDataIter == oldData.constEnd() || oldDataIter.value() != editData) { + } + if (changedRoles.isEmpty()) + return true; + Q_ASSERT(std::is_sorted(changedRoles.cbegin(), changedRoles.cend())); + const bool hasDisplay = std::binary_search(changedRoles.cbegin(), changedRoles.cend(), Qt::DisplayRole); + const bool hasEdit = std::binary_search(changedRoles.cbegin(), changedRoles.cend(), Qt::EditRole); + if (!d->m_separateEditDisplay) { + if (hasDisplay && hasEdit) { + const auto displayIter = dataContainer->constFind(Qt::DisplayRole); + const auto editIter = dataContainer->find(Qt::EditRole); + // in QStandardItemModel DisplayRole wins over EditRole so we do the same + if (displayIter == dataContainer->cend()) { + if (editIter != dataContainer->end()) { + dataContainer->erase(editIter); + } + } else { + if (editIter == dataContainer->end()) + dataContainer->insert(Qt::EditRole, displayIter.value()); + else + editIter.value() = displayIter.value(); // assign without checking should optimise memory use due to implicit sharing + } + } else if (hasDisplay != hasEdit) { + if (hasDisplay) { + const auto displayIter = dataContainer->constFind(Qt::DisplayRole); + if (displayIter == dataContainer->constEnd()) { + Q_ASSERT(dataContainer->contains(Qt::EditRole)); + dataContainer->remove(Qt::EditRole); + } else { + dataContainer->operator[](Qt::EditRole) = displayIter.value(); + } + changedRoles << Qt::EditRole; + } else { + const auto editIter = dataContainer->constFind(Qt::EditRole); + if (editIter == dataContainer->constEnd()) { + Q_ASSERT(dataContainer->contains(Qt::DisplayRole)); + dataContainer->remove(Qt::DisplayRole); + } else { + dataContainer->operator[](Qt::DisplayRole) = editIter.value(); + } changedRoles << Qt::DisplayRole; } } } - for (auto i = oldData.cbegin(); i != oldData.cend(); ++i) { - if (!newData.contains(i.key())) - changedRoles << i.key(); - } - if (isExtraRow && isExtraCol) - d->m_dataForCorner = newData; - else - d->m_extraData[isExtraRow][sectionIdx] = newData; dataChanged(index, index, changedRoles); extraDataChanged(index, index, changedRoles); return true; @@ -543,23 +581,18 @@ QMap InsertProxyModel::itemData(const QModelIndex &index) const const int sourceRows = sourceModel()->rowCount(); const int sourceCols = sourceModel()->columnCount(); Q_D(const InsertProxyModel); - if ( - index.parent().isValid() - || index.row() > sourceRows - || index.column() > sourceCols + if (index.parent().isValid() || index.row() > sourceRows || index.column() > sourceCols || (index.row() == sourceRows && !(d->m_insertDirection & InsertRow)) - || (index.column() == sourceCols && !(d->m_insertDirection & InsertColumn)) - ) + || (index.column() == sourceCols && !(d->m_insertDirection & InsertColumn))) return QMap(); - const bool isExtraRow = index.row() == sourceRows && d->m_insertDirection & InsertRow; - const bool isExtraCol = index.column() == sourceCols && d->m_insertDirection & InsertColumn; + const bool isExtraRow = index.row() == sourceRows && d->m_insertDirection & InsertRow; + const bool isExtraCol = index.column() == sourceCols && d->m_insertDirection & InsertColumn; if (!(isExtraRow || isExtraCol)) return sourceModel()->itemData(sourceModel()->index(index.row(), index.column())); if (isExtraRow && isExtraCol) - return convertFromContainer >(d->m_dataForCorner); + return convertFromContainer>(d->m_dataForCorner); const int sectionIdx = isExtraRow ? index.column() : index.row(); - return convertFromContainer >(d->m_extraData[isExtraRow][sectionIdx]); - + return convertFromContainer>(d->m_extraData[isExtraRow][sectionIdx]); } /*! @@ -591,8 +624,9 @@ QModelIndex InsertProxyModel::mapToSource(const QModelIndex &proxyIndex) const } /*! -\property InsertProxy::insertDirection +\property InsertProxyModel::insertDirection \accessors %insertDirection(), setInsertDirection() +\notifier insertDirectionChanged() \brief This property determines if the extra row, column or both are displayed */ InsertProxyModel::InsertDirections InsertProxyModel::insertDirection() const @@ -616,22 +650,10 @@ Qt::ItemFlags InsertProxyModel::flags(const QModelIndex &index) const if (isExtraRow && isExtraCol) return Qt::NoItemFlags; if (isExtraRow) - return flagForExtra(true, index.column()) - #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) - | Qt::ItemNeverHasChildren - #endif - ; + return flagForExtra(true, index.column()) | Qt::ItemNeverHasChildren; if (isExtraCol) - return flagForExtra(false, index.row()) - #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) - | Qt::ItemNeverHasChildren - #endif - ; - return sourceModel()->flags(sourceModel()->index(index.row(), index.column())) - #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) - | Qt::ItemNeverHasChildren - #endif - ; + return flagForExtra(false, index.row()) | Qt::ItemNeverHasChildren; + return sourceModel()->flags(sourceModel()->index(index.row(), index.column())) | Qt::ItemNeverHasChildren; } /*! @@ -648,7 +670,7 @@ QModelIndex InsertProxyModel::parent(const QModelIndex &index) const */ QModelIndex InsertProxyModel::index(int row, int column, const QModelIndex &parent) const { - if (parent.isValid()) + if (parent.isValid() || row < 0 || column < 0 || row >= rowCount() || column >= columnCount()) return QModelIndex(); return createIndex(row, column); } @@ -661,7 +683,7 @@ bool InsertProxyModel::insertRows(int row, int count, const QModelIndex &parent) return false; if (!sourceModel()) return false; - if (row<0 || row>sourceModel()->rowCount()) + if (row < 0 || row > sourceModel()->rowCount()) return false; return sourceModel()->insertRows(row, count); } @@ -674,7 +696,7 @@ bool InsertProxyModel::removeRows(int row, int count, const QModelIndex &parent) return false; if (!sourceModel()) return false; - if (row < 0 || row >= sourceModel()->rowCount()) + if (row < 0 || row > sourceModel()->rowCount()) return false; return sourceModel()->removeRows(row, count); } @@ -704,7 +726,7 @@ bool InsertProxyModel::insertColumns(int column, int count, const QModelIndex &p return false; if (!sourceModel()) return false; - if (column < 0 || column >= sourceModel()->columnCount()) + if (column < 0 || column > sourceModel()->columnCount()) return false; return sourceModel()->insertColumns(column, count); } @@ -718,7 +740,7 @@ bool InsertProxyModel::removeColumns(int column, int count, const QModelIndex &p return false; if (!sourceModel()) return false; - if (column<0 || column >= sourceModel()->columnCount()) + if (column < 0 || column > sourceModel()->columnCount()) return false; return sourceModel()->removeColumns(column, count); } @@ -726,7 +748,8 @@ bool InsertProxyModel::removeColumns(int column, int count, const QModelIndex &p /*! \reimp */ -bool InsertProxyModel::moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count, const QModelIndex &destinationParent, int destinationChild) +bool InsertProxyModel::moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count, const QModelIndex &destinationParent, + int destinationChild) { if (sourceParent.isValid() || destinationParent.isValid()) return false; @@ -742,11 +765,11 @@ bool InsertProxyModel::moveColumns(const QModelIndex &sourceParent, int sourceCo /*! \reimp */ -void InsertProxyModel::sort(int column, Qt::SortOrder order) +void InsertProxyModel::sort(int column, Qt::SortOrder order) { if (!sourceModel()) return; - if (column<0 || column>=sourceModel()->columnCount()) + if (column < 0 || column >= sourceModel()->columnCount()) return; sourceModel()->sort(column, order); } @@ -756,7 +779,7 @@ void InsertProxyModel::sort(int column, Qt::SortOrder order) */ void InsertProxyModelPrivate::beforeLayoutChange(const QList &parents, QAbstractItemModel::LayoutChangeHint hint) { - if (!parents.isEmpty() && std::all_of(parents.cbegin(), parents.cend(), [](const QPersistentModelIndex& idx) {return idx.isValid(); })) + if (!parents.isEmpty() && std::all_of(parents.cbegin(), parents.cend(), [](const QPersistentModelIndex &idx) { return idx.isValid(); })) return; Q_Q(InsertProxyModel); Q_ASSERT(q->sourceModel()); @@ -767,8 +790,8 @@ void InsertProxyModelPrivate::beforeLayoutChange(const QListsourceModel()->rowCount(); const int maxCol = q->sourceModel()->columnCount(); - m_layoutChangeProxyIndexes.reserve(maxRow*maxCol); - m_layoutChangePersistentIndexes.reserve(maxRow*maxCol); + m_layoutChangeProxyIndexes.reserve(maxRow * maxCol); + m_layoutChangePersistentIndexes.reserve(maxRow * maxCol); for (int i = 0; i < maxRow; ++i) { for (int j = 0; j < maxCol; ++j) { m_layoutChangeProxyIndexes << q->index(i, j); @@ -777,7 +800,7 @@ void InsertProxyModelPrivate::beforeLayoutChange(const QListlayoutAboutToBeChanged(QList({ QPersistentModelIndex() }), hint); + q->layoutAboutToBeChanged(QList({QPersistentModelIndex()}), hint); } /*! @@ -785,12 +808,12 @@ void InsertProxyModelPrivate::beforeLayoutChange(const QList &parents, QAbstractItemModel::LayoutChangeHint hint) { - - if (!parents.isEmpty() && std::all_of(parents.cbegin(), parents.cend(), [](const QPersistentModelIndex& idx) {return idx.isValid(); })) + + if (!parents.isEmpty() && std::all_of(parents.cbegin(), parents.cend(), [](const QPersistentModelIndex &idx) { return idx.isValid(); })) return; if (hint == QAbstractItemModel::VerticalSortHint) afterSortRows(); - else if (hint == QAbstractItemModel::HorizontalSortHint) + else if (hint == QAbstractItemModel::HorizontalSortHint) afterSortCols(); // Not much we can do otherwise Q_Q(InsertProxyModel); @@ -803,7 +826,7 @@ void InsertProxyModelPrivate::afetrLayoutChange(const QListchangePersistentIndexList(m_layoutChangeProxyIndexes, toList); m_layoutChangeProxyIndexes.clear(); m_layoutChangePersistentIndexes.clear(); - q->layoutChanged(QList({ QPersistentModelIndex() }), hint); + q->layoutChanged(QList({QPersistentModelIndex()}), hint); } /*! @@ -828,7 +851,7 @@ void InsertProxyModelPrivate::beforeSort(bool isRow) return; const int maxSortedIdx = isRow ? q->sourceModel()->rowCount() : q->sourceModel()->columnCount(); const InsertProxyModel::InsertDirections directionCheck = isRow ? InsertProxyModel::InsertColumn : InsertProxyModel::InsertRow; - if (m_insertDirection & directionCheck){ + if (m_insertDirection & directionCheck) { Q_ASSERT(m_layoutChangeProxyIndexes.isEmpty()); Q_ASSERT(m_layoutChangePersistentIndexes.isEmpty()); m_layoutChangeProxyIndexes.reserve(maxSortedIdx); @@ -837,7 +860,6 @@ void InsertProxyModelPrivate::beforeSort(bool isRow) for (int i = 0; i < maxSortedIdx; ++i) { m_layoutChangePersistentIndexes.append(isRow ? q->sourceModel()->index(i, 0) : q->sourceModel()->index(0, i)); m_layoutChangeProxyIndexes.append(isRow ? q->index(i, maxSecondaryIdx) : q->index(maxSecondaryIdx, i)); - } } } @@ -850,8 +872,8 @@ void InsertProxyModelPrivate::afterSort(bool isRow) Q_Q(InsertProxyModel); const int maxSortedIdx = isRow ? q->sourceModel()->rowCount() : q->sourceModel()->columnCount(); const InsertProxyModel::InsertDirections directionCheck = isRow ? InsertProxyModel::InsertColumn : InsertProxyModel::InsertRow; - if (m_insertDirection & directionCheck){ - const QList oldlayout = m_extraData[!isRow]; + if (m_insertDirection & directionCheck) { + const auto oldlayout = m_extraData[!isRow]; const int maxSecondaryIdx = isRow ? q->sourceModel()->columnCount() : q->sourceModel()->rowCount(); QModelIndexList toList; toList.reserve(maxSortedIdx); @@ -866,17 +888,16 @@ void InsertProxyModelPrivate::afterSort(bool isRow) m_layoutChangePersistentIndexes.erase(m_layoutChangePersistentIndexes.begin(), m_layoutChangePersistentIndexes.begin() + maxSortedIdx); } - /*! \internal */ void InsertProxyModelPrivate::afterSortRows() { - return afterSort(true); + return afterSort(true); } /*! -\internal +\internal */ void InsertProxyModelPrivate::beforeSortCols() { @@ -891,7 +912,6 @@ void InsertProxyModelPrivate::afterSortCols() return afterSort(false); } - /*! \brief flag function for the extra row or column \details If \a isRow is true this method is referring to the extra row, otherwise it will be the extra column, @@ -904,20 +924,19 @@ Qt::ItemFlags InsertProxyModel::flagForExtra(bool isRow, int section) const return (Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); } -void InsertProxyModel::setInsertDirection(const InsertDirections& direction) +void InsertProxyModel::setInsertDirection(const InsertDirections &direction) { Q_D(InsertProxyModel); if (d->m_insertDirection == direction) return; if (sourceModel()) { - const InsertDirections oldDirection{ std::move(d->m_insertDirection) }; + const InsertDirections oldDirection{std::move(d->m_insertDirection)}; if ((oldDirection & InsertRow) && !(direction & InsertRow)) { const int rowToRemove = sourceModel()->rowCount(); beginRemoveRows(QModelIndex(), rowToRemove, rowToRemove); d->m_insertDirection &= ~InsertRow; endRemoveRows(); - } - else if (!(oldDirection & InsertRow) && (direction & InsertRow)) { + } else if (!(oldDirection & InsertRow) && (direction & InsertRow)) { const int rowToInsert = sourceModel()->rowCount(); beginInsertRows(QModelIndex(), rowToInsert, rowToInsert); d->m_insertDirection |= InsertRow; @@ -935,8 +954,7 @@ void InsertProxyModel::setInsertDirection(const InsertDirections& direction) d->m_insertDirection |= InsertColumn; endInsertColumns(); } - } - else{ + } else { d->m_insertDirection = direction; } Q_ASSERT(direction == d->m_insertDirection); @@ -955,14 +973,14 @@ QVariant InsertProxyModel::dataForCorner(int role) const /*! Set the data for the corner cell at the intersection between the extra row and extra column */ -void InsertProxyModel::setDataForCorner(const QVariant& value, int role) +void InsertProxyModel::setDataForCorner(const QVariant &value, int role) { Q_D(InsertProxyModel); - const QVector changedRoles = d->setDataInContainer(d->m_dataForCorner,role,value); + const QVector changedRoles = d->setDataInContainer(d->m_dataForCorner, role, value); if (changedRoles.isEmpty()) return; dataForCornerChanged(changedRoles); - if (sourceModel() && (d->m_insertDirection & (InsertProxyModel::InsertRow | InsertProxyModel::InsertColumn))){ + if (sourceModel() && (d->m_insertDirection & (InsertProxyModel::InsertRow | InsertProxyModel::InsertColumn))) { const QModelIndex cornerIndex = index(sourceModel()->rowCount(), sourceModel()->columnCount()); dataChanged(cornerIndex, cornerIndex, changedRoles); } @@ -987,8 +1005,9 @@ bool InsertProxyModel::commitColumn() } /*! -\property InsertProxy::separateEditDisplay -\accessors %separateEditDisplay(), setSeparateEditDisplay() +\property InsertProxyModel::mergeDisplayEdit +\accessors %mergeDisplayEdit(), setMergeDisplayEdit() +\notifier mergeDisplayEditChanged() \brief This property determines if the Qt::DisplayRole and Qt::EditRole should be merged in the extra row/column \details By default the two roles are one and the same you can use this property to separate them. If there's any data in the role when you set this property to true it will be duplicated for both roles. @@ -996,23 +1015,23 @@ If there is data both in Qt::DisplayRole and Qt::EditRole when you set this prop This property only affects the extra row/column. Data in the source model is not affected. */ -bool InsertProxyModel::separateEditDisplay() const +bool InsertProxyModel::mergeDisplayEdit() const { Q_D(const InsertProxyModel); - return d->m_separateEditDisplay; + return !d->m_separateEditDisplay; } -void InsertProxyModel::setSeparateEditDisplay(bool val) +void InsertProxyModel::setMergeDisplayEdit(bool val) { Q_D(InsertProxyModel); - if (d->m_separateEditDisplay != val) { - d->m_separateEditDisplay = val; - if (sourceModel() && !val) { + if (d->m_separateEditDisplay == val) { + d->m_separateEditDisplay = !val; + if (sourceModel() && val) { const int sourceCol = sourceModel()->columnCount(); const int sourceRows = sourceModel()->rowCount(); - //orientation == Qt::Vertical + // orientation == Qt::Vertical int roleChange = d->mergeEditDisplayHash(d->m_extraHeaderData[1]); - if (roleChange>=0){ + if (roleChange >= 0) { headerDataChanged(Qt::Vertical, sourceRows, sourceRows); } roleChange = d->mergeEditDisplayHash(d->m_extraHeaderData[0]); @@ -1030,9 +1049,9 @@ void InsertProxyModel::setSeparateEditDisplay(bool val) } // 0 rows 1 cols - for (int i = 0; i < sourceRows;++i){ + for (int i = 0; i < sourceRows; ++i) { roleChange = d->mergeEditDisplayHash(d->m_extraData[0][i]); - if (roleChange >= 0){ + if (roleChange >= 0) { const QVector roleChangeVect(1, roleChange); const QModelIndex currentIdx = index(i, sourceCol); extraDataChanged(currentIdx, currentIdx, roleChangeVect); @@ -1043,14 +1062,14 @@ void InsertProxyModel::setSeparateEditDisplay(bool val) for (int i = 0; i < sourceCol; ++i) { if (roleChange >= 0) { const QVector roleChangeVect(1, roleChange); - const QModelIndex currentIdx = index(sourceRows,i); + const QModelIndex currentIdx = index(sourceRows, i); extraDataChanged(currentIdx, currentIdx, roleChangeVect); if (d->m_insertDirection & InsertRow) dataChanged(currentIdx, currentIdx, roleChangeVect); } } } - separateEditDisplayChanged(val); + mergeDisplayEditChanged(val); } } @@ -1058,18 +1077,12 @@ void InsertProxyModel::setSeparateEditDisplay(bool val) \brief Returns true if the extra row can be merged in the main model \details The default implementation never commits the row to the model. -If, for example, you want the row to be added to the source model as soon as there's any data in the Qt::DisplayRole of any cell you can repliement this method as: -\code{.cpp} -if (!sourceModel()) - return false; -const int sourceCols = sourceModel()->columnCount(); -const int sourceRows = sourceModel()->rowCount(); -for (int i = 0; i < sourceCols; ++i) { - if (index(sourceRows,i).data().isValid()) - return true; +If, for example, you want the row to be added to the source model as soon as there's any data in the Qt::DisplayRole of any cell you can repliement +this method as: \code{.cpp} if (!sourceModel()) return false; const int sourceCols = sourceModel()->columnCount(); const int sourceRows = +sourceModel()->rowCount(); for (int i = 0; i < sourceCols; ++i) { if (index(sourceRows,i).data().isValid()) return true; } return false; -\code +\endcode */ bool InsertProxyModel::validRow() const { @@ -1080,25 +1093,18 @@ bool InsertProxyModel::validRow() const \brief Returns true if the extra column can be merged in the main model \details The default implementation never commits the column to the model. -If, for example, you want the row to be added to the source model as soon as there's any data in the Qt::DisplayRole of any cell you can repliement this method as: -\code{.cpp} -if (!sourceModel()) - return false; -const int sourceCols = sourceModel()->columnCount(); -const int sourceRows = sourceModel()->rowCount(); -for (int i = 0; i < sourceRows; ++i) { - if (index(i, sourceCols).data().isValid()) - return true; +If, for example, you want the row to be added to the source model as soon as there's any data in the Qt::DisplayRole of any cell you can repliement +this method as: \code{.cpp} if (!sourceModel()) return false; const int sourceCols = sourceModel()->columnCount(); const int sourceRows = +sourceModel()->rowCount(); for (int i = 0; i < sourceRows; ++i) { if (index(i, sourceCols).data().isValid()) return true; } return false; -\code +\endcode */ bool InsertProxyModel::validColumn() const { return false; } - /*! \class InsertProxyModel \brief This proxy model provides an extra row and column to handle user insertions @@ -1112,14 +1118,29 @@ You can either call commitRow/commitColumn or reimplement validRow/validColumn t */ /*! -\fn void dataForCornerChanged(int role) +\fn void InsertProxyModel::dataForCornerChanged(int role) This signal is emitted whenever the data for the corner at the intersection of the extra row and column is changed */ /*! -\fn void extraDataChanged(int role) +\fn void InsertProxyModel::extraDataChanged(int role) Same as dataChanged but is emitted only for the extra row/column */ +/*! \enum InsertProxyModel::InsertDirection +Direction of the Insertion +*/ + +/*! \var InsertProxyModel::InsertDirection InsertProxyModel::NoInsert +No Insertion +*/ + +/*! \var InsertProxyModel::InsertDirection InsertProxyModel::InsertRow +Insert new rows +*/ + +/*! \var InsertProxyModel::InsertDirection InsertProxyModel::InsertColumn +Insert new columns +*/ diff --git a/src/insertproxymodel.h b/src/insertproxymodel.h index 809cf8c..bc17214 100644 --- a/src/insertproxymodel.h +++ b/src/insertproxymodel.h @@ -1,3 +1,15 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef INSERTPROXY_H #define INSERTPROXY_H @@ -5,29 +17,24 @@ #include #include class InsertProxyModelPrivate; -class MODELUTILITIES_EXPORT InsertProxyModel : public QAbstractProxyModel +class MODELUTILITIES_EXPORT InsertProxyModel : public QAbstractProxyModel { Q_OBJECT Q_PROPERTY(InsertDirections insertDirection READ insertDirection WRITE setInsertDirection NOTIFY insertDirectionChanged) - Q_PROPERTY(bool separateEditDisplay READ separateEditDisplay WRITE setSeparateEditDisplay NOTIFY separateEditDisplayChanged) + Q_PROPERTY(bool mergeDisplayEdit READ mergeDisplayEdit WRITE setMergeDisplayEdit NOTIFY mergeDisplayEditChanged) Q_DISABLE_COPY(InsertProxyModel) Q_DECLARE_PRIVATE_D(m_dptr, InsertProxyModel) public: - enum InsertDirection - { - NoInsert = 0x0 - , InsertRow = 0x1 - , InsertColumn = 0x2 - }; + enum InsertDirection { NoInsert = 0x0, InsertRow = 0x1, InsertColumn = 0x2 }; Q_DECLARE_FLAGS(InsertDirections, InsertDirection) - #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) Q_FLAG(InsertDirections) - #else +#else Q_FLAGS(InsertDirections) - #endif - explicit InsertProxyModel(QObject* parent = Q_NULLPTR); +#endif + explicit InsertProxyModel(QObject *parent = Q_NULLPTR); ~InsertProxyModel(); - void setSourceModel(QAbstractItemModel* newSourceModel) Q_DECL_OVERRIDE; + void setSourceModel(QAbstractItemModel *newSourceModel) Q_DECL_OVERRIDE; QModelIndex buddy(const QModelIndex &index) const Q_DECL_OVERRIDE; bool hasChildren(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; @@ -45,31 +52,36 @@ class MODELUTILITIES_EXPORT InsertProxyModel : public QAbstractProxyModel QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE; bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE; - bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) Q_DECL_OVERRIDE; + bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, + int destinationChild) Q_DECL_OVERRIDE; bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE; bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE; - bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count, const QModelIndex &destinationParent, int destinationChild) Q_DECL_OVERRIDE; + bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count, const QModelIndex &destinationParent, + int destinationChild) Q_DECL_OVERRIDE; void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) Q_DECL_OVERRIDE; InsertDirections insertDirection() const; - void setInsertDirection(const InsertDirections& direction); + void setInsertDirection(const InsertDirections &direction); virtual QVariant dataForCorner(int role = Qt::DisplayRole) const; - virtual void setDataForCorner(const QVariant& value, int role = Qt::EditRole); - bool separateEditDisplay() const; - void setSeparateEditDisplay(bool val); + virtual void setDataForCorner(const QVariant &value, int role = Qt::EditRole); + bool mergeDisplayEdit() const; + void setMergeDisplayEdit(bool val); public Q_SLOTS: bool commitRow(); bool commitColumn(); Q_SIGNALS: - void dataForCornerChanged(const QVector& roles); + void dataForCornerChanged(const QVector &roles); void extraDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()); - void separateEditDisplayChanged(bool separate); - void insertDirectionChanged(const InsertDirections& direction); + void mergeDisplayEditChanged(bool separate); + void insertDirectionChanged(InsertDirections direction); + protected: virtual bool validRow() const; virtual bool validColumn() const; virtual Qt::ItemFlags flagForExtra(bool isRow, int section) const; + InsertProxyModel(InsertProxyModelPrivate &dptr, QObject *parent); + private: - InsertProxyModelPrivate* m_dptr; + InsertProxyModelPrivate *m_dptr; }; Q_DECLARE_METATYPE(InsertProxyModel::InsertDirections) Q_DECLARE_OPERATORS_FOR_FLAGS(InsertProxyModel::InsertDirections) diff --git a/src/jsonmodelserialiser.cpp b/src/jsonmodelserialiser.cpp index e69de29..f278f00 100644 --- a/src/jsonmodelserialiser.cpp +++ b/src/jsonmodelserialiser.cpp @@ -0,0 +1,457 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ +#include "jsonmodelserialiser.h" +#include "private/jsonmodelserialiser_p.h" +#include +#include +#include +#include +#include + +JsonModelSerialiserPrivate::JsonModelSerialiserPrivate(JsonModelSerialiser *q) + : AbstractStringSerialiserPrivate(q) + , m_format(QJsonDocument::Compact) +{ } + +bool JsonModelSerialiserPrivate::fromJsonObject(const QJsonObject &source, const QModelIndex &parent) +{ + if (source.isEmpty() || !m_model) + return false; + QJsonValue tempValue = source.value(QLatin1String("rows")); + if (tempValue.isUndefined() || !tempValue.isDouble()) + return false; + const int maxRow = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + tempValue.toInt(); +#else + static_cast(tempValue.toDouble()); +#endif + m_model->insertRows(0, maxRow, parent); + if (m_model->rowCount(parent) != maxRow) + return false; + tempValue = source.value(QLatin1String("columns")); + if (tempValue.isUndefined() || !tempValue.isDouble()) + return false; + const int maxCol = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + tempValue.toInt(); +#else + static_cast(tempValue.toDouble()); +#endif + m_model->insertColumns(0, maxCol, parent); + if (m_model->columnCount(parent) != maxCol) + return false; + if (!parent.isValid()) { + for (auto orientat : + {std::make_pair(QLatin1String("verticalHeader"), Qt::Vertical), std::make_pair(QLatin1String("horizontalHeader"), Qt::Horizontal)}) { + tempValue = source.value(orientat.first); + if (!tempValue.isUndefined()) { + if (!tempValue.isArray()) + return false; + const QJsonArray headerArray = tempValue.toArray(); + for (int i = 0, maxHeaderArray = headerArray.size(); i < maxHeaderArray; ++i) { + tempValue = headerArray.at(i); + if (!tempValue.isObject()) + return false; + const QJsonObject sectionObject = tempValue.toObject(); + tempValue = sectionObject.value(QLatin1String("section")); + if (tempValue.isUndefined() || !tempValue.isDouble()) + return false; + const int sectIdx = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + tempValue.toInt(); +#else + static_cast(tempValue.toDouble()); +#endif + tempValue = sectionObject.value(QLatin1String("values")); + if (tempValue.isUndefined() || !tempValue.isArray()) + return false; + const QJsonArray valuesArray = tempValue.toArray(); + for (int j = 0, maxValuesArray = valuesArray.size(); j < maxValuesArray; ++j) { + tempValue = valuesArray.at(j); + if (tempValue.isUndefined() || !tempValue.isObject()) + return false; + const QJsonObject roleObject = tempValue.toObject(); + tempValue = roleObject.value(QLatin1String("role")); + if (tempValue.isUndefined() || !tempValue.isDouble()) + return false; + const int tempRole = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + tempValue.toInt(); +#else + static_cast(tempValue.toDouble()); +#endif + tempValue = roleObject.value(QLatin1String("type")); + if (tempValue.isUndefined() || !tempValue.isDouble()) + return false; + const int tempType = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + tempValue.toInt(); +#else + static_cast(tempValue.toDouble()); +#endif + tempValue = roleObject.value(QLatin1String("value")); + if (tempValue.isUndefined() || !tempValue.isString()) + return false; + if (!m_model->setHeaderData(sectIdx, orientat.second, loadVariant(tempType, tempValue.toString()), tempRole)) + return false; + } + } + } + } + } + + tempValue = source.value(QLatin1String("data")); + if (tempValue.isUndefined()) + return true; + if (!tempValue.isArray()) + return false; + const QJsonArray dataArray = tempValue.toArray(); + for (int i = 0, maxDataArray = dataArray.size(); i < maxDataArray; ++i) { + tempValue = dataArray.at(i); + if (tempValue.isUndefined() || !tempValue.isObject()) + return false; + const QJsonObject dataObject = tempValue.toObject(); + tempValue = dataObject.value(QLatin1String("row")); + if (tempValue.isUndefined() || !tempValue.isDouble()) + return false; + const int currRow = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + tempValue.toInt(); +#else + static_cast(tempValue.toDouble()); +#endif + if (currRow >= maxRow || currRow < 0) + return false; + tempValue = dataObject.value(QLatin1String("col")); + if (tempValue.isUndefined() || !tempValue.isDouble()) + return false; + const int currCol = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + tempValue.toInt(); +#else + static_cast(tempValue.toDouble()); +#endif + if (currCol >= maxCol || currCol < 0) + return false; + const QModelIndex currIdx = m_model->index(currRow, currCol, parent); + tempValue = dataObject.value(QLatin1String("values")); + if (!tempValue.isUndefined()) { + if (!tempValue.isArray()) + return false; + const QJsonArray valuesArray = tempValue.toArray(); + for (int j = 0, maxValuesArray = valuesArray.size(); j < maxValuesArray; ++j) { + tempValue = valuesArray.at(j); + if (tempValue.isUndefined() || !tempValue.isObject()) + return false; + const QJsonObject roleObject = tempValue.toObject(); + if (!roleForObject(roleObject, currIdx)) + return false; + } + } + tempValue = dataObject.value(QLatin1String("children")); + if (!tempValue.isUndefined()) { + if (!tempValue.isObject()) + return false; + if (!fromJsonObject(tempValue.toObject(), currIdx)) + return false; + } + } + + return true; +} + +bool JsonModelSerialiserPrivate::roleForObject(const QJsonObject &source, const QModelIndex &destination) +{ + QJsonValue tempValue = source.value(QLatin1String("role")); + if (tempValue.isUndefined() || !tempValue.isDouble()) + return false; + const int tempRole = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + tempValue.toInt(); +#else + static_cast(tempValue.toDouble()); +#endif + tempValue = source.value(QLatin1String("type")); + if (tempValue.isUndefined() || !tempValue.isDouble()) + return false; + const int tempType = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + tempValue.toInt(); +#else + static_cast(tempValue.toDouble()); +#endif + tempValue = source.value(QLatin1String("value")); + if (tempValue.isUndefined() || !tempValue.isString()) + return false; + if (!m_model->setData(destination, loadVariant(tempType, tempValue.toString()), tempRole)) + return false; + return true; +} + +QJsonObject JsonModelSerialiserPrivate::objectForRole(int role, const QVariant &value) const +{ + QJsonObject roleObject; + roleObject[QLatin1String("role")] = role; + roleObject[QLatin1String("type")] = static_cast(value.userType()); + roleObject[QLatin1String("value")] = saveVariant(value); + return roleObject; +} + +QJsonObject JsonModelSerialiserPrivate::toJsonObject(const QModelIndex &parent) const +{ + QJsonObject result; + if (!m_constModel) + return result; + const int maxRow = m_constModel->rowCount(parent); + const int maxCol = m_constModel->columnCount(parent); + result[QLatin1String("rows")] = maxRow; + result[QLatin1String("columns")] = maxCol; + if (!parent.isValid()) { + QJsonArray verticalHeadData; + for (int i = 0; i < maxRow; ++i) { + QJsonObject headerObject; + QJsonArray rolesArray; + for (auto roleIter = m_rolesToSave.constBegin(); roleIter != m_rolesToSave.constEnd(); ++roleIter) { + const QVariant roleVariant = m_constModel->headerData(i, Qt::Vertical, *roleIter); + if (roleVariant.isValid()) + rolesArray.append(objectForRole(*roleIter, roleVariant)); + } + if (!rolesArray.isEmpty()) { + headerObject[QLatin1String("section")] = i; + headerObject[QLatin1String("values")] = rolesArray; + verticalHeadData.append(headerObject); + } + } + if (!verticalHeadData.isEmpty()) + result[QLatin1String("verticalHeader")] = verticalHeadData; + QJsonArray horizontalHeadData; + for (int i = 0; i < maxCol; ++i) { + QJsonObject headerObject; + QJsonArray rolesArray; + for (auto roleIter = m_rolesToSave.constBegin(); roleIter != m_rolesToSave.constEnd(); ++roleIter) { + const QVariant roleVariant = m_constModel->headerData(i, Qt::Horizontal, *roleIter); + if (roleVariant.isValid()) + rolesArray.append(objectForRole(*roleIter, roleVariant)); + } + if (!rolesArray.isEmpty()) { + headerObject[QLatin1String("section")] = i; + headerObject[QLatin1String("values")] = rolesArray; + horizontalHeadData.append(headerObject); + } + } + if (!horizontalHeadData.isEmpty()) + result[QLatin1String("horizontalHeader")] = horizontalHeadData; + } + QJsonArray dataArray; + for (int i = 0; i < maxRow; ++i) { + for (int j = 0; j < maxCol; ++j) { + QJsonObject dataObject; + dataObject[QLatin1String("row")] = i; + dataObject[QLatin1String("col")] = j; + const QModelIndex currIdx = m_constModel->index(i, j, parent); + QJsonArray valuesArray; + for (auto roleIter = m_rolesToSave.constBegin(); roleIter != m_rolesToSave.constEnd(); ++roleIter) { + const QVariant roleVariant = currIdx.data(*roleIter); + if (roleVariant.isValid()) + valuesArray.append(objectForRole(*roleIter, roleVariant)); + } + if (!valuesArray.isEmpty()) + dataObject[QLatin1String("values")] = valuesArray; + if (m_constModel->hasChildren(currIdx)) + dataObject[QLatin1String("children")] = toJsonObject(currIdx); + dataArray.append(dataObject); + } + } + if (!dataArray.isEmpty()) + result[QLatin1String("data")] = dataArray; + return result; +} + +/*! +Constructs a serialiser operating over \a model +*/ +JsonModelSerialiser::JsonModelSerialiser(QAbstractItemModel *model, QObject *parent) + : AbstractStringSerialiser(*new JsonModelSerialiserPrivate(this), parent) +{ + setModel(model); +} + +/*! +\overload + +the model will only be allowed to be saved, not loaded +*/ +JsonModelSerialiser::JsonModelSerialiser(const QAbstractItemModel *model, QObject *parent) + : AbstractStringSerialiser(*new JsonModelSerialiserPrivate(this), parent) +{ + setModel(model); +} + +/*! +\internal +*/ +JsonModelSerialiser::JsonModelSerialiser(JsonModelSerialiserPrivate &d, QObject *parent) + : AbstractStringSerialiser(d, parent) +{ } + +/*! +\reimp +*/ +bool JsonModelSerialiser::saveModel(QString *destination) const +{ + if (!destination) + return false; + Q_D(const JsonModelSerialiser); + if (!d->m_constModel) + return false; + const QJsonDocument jDoc(toJsonObject()); + if (jDoc.isNull()) + return false; + *destination = QString::fromUtf8(jDoc.toJson(d->m_format)); + return true; +} + +/*! +\reimp +*/ +bool JsonModelSerialiser::saveModel(QByteArray *destination) const +{ + if (!destination) + return false; + QString tempString; + if (!saveModel(&tempString)) + return false; + QTextStream writer(destination); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + writer << tempString; + return true; +} + +/*! +\reimp +*/ +bool JsonModelSerialiser::saveModel(QIODevice *destination) const +{ + if (!destination) + return false; + QString tempString; + if (!saveModel(&tempString)) + return false; + QTextStream writer(destination); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + writer << tempString; + return true; +} + +/*! +Saves the model to a JSON object and returns it +*/ +QJsonObject JsonModelSerialiser::toJsonObject() const +{ + Q_D(const JsonModelSerialiser); + return d->toJsonObject(); +} + +/*! +\reimp +*/ +bool JsonModelSerialiser::loadModel(QString *source) +{ + Q_D(const JsonModelSerialiser); + if (!d->m_model || !source) + return false; + QJsonParseError parseErr; + const QJsonDocument jDoc = QJsonDocument::fromJson(source->toUtf8(), &parseErr); + if (parseErr.error != QJsonParseError::NoError) + return false; + return fromJsonObject(jDoc.object()); +} + +/*! +\reimp +*/ +bool JsonModelSerialiser::loadModel(QIODevice *source) +{ + if (!source) + return false; + if (!source->isOpen()) { + if (!source->open(QIODevice::ReadOnly | QIODevice::Text)) + return false; + } + if (!source->isReadable()) + return false; + source->setTextModeEnabled(true); + QTextStream reader(source); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + reader.setCodec(textCodec()); +#endif + QString loadString = reader.readAll(); + return loadModel(&loadString); +} + +/*! +\reimp +*/ +bool JsonModelSerialiser::loadModel(const QByteArray &source) +{ + QTextStream reader(source, QIODevice::ReadOnly | QIODevice::Text); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + reader.setCodec(textCodec()); +#endif + QString loadString = reader.readAll(); + return loadModel(&loadString); +} + +/*! +Loads the model from a \a source JSON object +*/ +bool JsonModelSerialiser::fromJsonObject(const QJsonObject &source) +{ + Q_D(JsonModelSerialiser); + if (!d->m_model) + return false; + d->m_model->removeColumns(0, d->m_model->columnCount()); + d->m_model->removeRows(0, d->m_model->rowCount()); + return d->fromJsonObject(source); +} + +/*! +\brief The JSON format to use +\details Defaults to \c Compact +*/ +QJsonDocument::JsonFormat JsonModelSerialiser::format() const +{ + Q_D(const JsonModelSerialiser); + return d->m_format; +} + +/*! +\brief Sets the JSON format to use +\details Defaults to \c Compact +*/ +void JsonModelSerialiser::setFormat(QJsonDocument::JsonFormat val) +{ + Q_D(JsonModelSerialiser); + d->m_format = val; +} + +/*! +\class JsonModelSerialiser + +\brief Serialiser to save and load models in JSON format +*/ diff --git a/src/jsonmodelserialiser.h b/src/jsonmodelserialiser.h index e1d47bb..c19f416 100644 --- a/src/jsonmodelserialiser.h +++ b/src/jsonmodelserialiser.h @@ -1,29 +1,45 @@ -/* +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ + #ifndef jsonmodelserialiser_h__ #define jsonmodelserialiser_h__ #include "modelutilities_global.h" -#include "abstractmultiroleserialiser.h" +#include "abstractstringserialiser.h" #include +#include class JsonModelSerialiserPrivate; -class MODELUTILITIES_EXPORT JsonModelSerialiser : public AbstractMultiRoleSerialiser +class MODELUTILITIES_EXPORT JsonModelSerialiser : public AbstractStringSerialiser { - Q_GADGET + Q_OBJECT Q_DECLARE_PRIVATE(JsonModelSerialiser) Q_DISABLE_COPY(JsonModelSerialiser) public: - JsonModelSerialiser(QAbstractItemModel* model = Q_NULLPTR); - JsonModelSerialiser(const QAbstractItemModel* model); - ~JsonModelSerialiser(); - Q_INVOKABLE bool saveModel(QIODevice* destination) const Q_DECL_OVERRIDE; - Q_INVOKABLE bool saveModel(QByteArray* destination) const Q_DECL_OVERRIDE; - Q_INVOKABLE virtual bool saveModel(QString* destination) const; + JsonModelSerialiser(QAbstractItemModel *model = Q_NULLPTR, QObject *parent = Q_NULLPTR); + JsonModelSerialiser(const QAbstractItemModel *model, QObject *parent = Q_NULLPTR); + bool saveModel(QIODevice *destination) const Q_DECL_OVERRIDE; + bool saveModel(QByteArray *destination) const Q_DECL_OVERRIDE; + bool saveModel(QString *destination) const Q_DECL_OVERRIDE; Q_INVOKABLE virtual QJsonObject toJsonObject() const; - Q_INVOKABLE bool loadModel(QIODevice* source) Q_DECL_OVERRIDE; - Q_INVOKABLE bool loadModel(const QByteArray& source) Q_DECL_OVERRIDE; - Q_INVOKABLE virtual bool loadModel(QString* source); - Q_INVOKABLE virtual bool fromJsonObject(const QJsonObject& source); + bool loadModel(QIODevice *source) Q_DECL_OVERRIDE; + bool loadModel(const QByteArray &source) Q_DECL_OVERRIDE; + bool loadModel(QString *source) Q_DECL_OVERRIDE; + Q_INVOKABLE virtual bool fromJsonObject(const QJsonObject &source); + Q_INVOKABLE QJsonDocument::JsonFormat format() const; +public Q_SLOTS: + void setFormat(QJsonDocument::JsonFormat val); + protected: - JsonModelSerialiser(JsonModelSerialiserPrivate& d); + JsonModelSerialiser(JsonModelSerialiserPrivate &d, QObject *parent); }; -#endif // jsonmodelserialiser_h__*/ \ No newline at end of file +#endif // jsonmodelserialiser_h__*/ diff --git a/src/modelutilities_global.h b/src/modelutilities_global.h index bc9cada..c086b0a 100644 --- a/src/modelutilities_global.h +++ b/src/modelutilities_global.h @@ -1,13 +1,25 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef modelutilities_global_h__ #define modelutilities_global_h__ #include #ifndef MODELUTILITIES_STATIC -# if defined(MODELUTILITIES_LIB) -# define MODELUTILITIES_EXPORT Q_DECL_EXPORT -# else -# define MODELUTILITIES_EXPORT Q_DECL_IMPORT -# endif +# if defined(MODELUTILITIES_LIB) +# define MODELUTILITIES_EXPORT Q_DECL_EXPORT +# else +# define MODELUTILITIES_EXPORT Q_DECL_IMPORT +# endif #else -# define MODELUTILITIES_EXPORT +# define MODELUTILITIES_EXPORT #endif #endif // modelutilities_global_h__ diff --git a/src/private/abstractmodelserialiser_p.h b/src/private/abstractmodelserialiser_p.h index 61c9a8e..bed524b 100644 --- a/src/private/abstractmodelserialiser_p.h +++ b/src/private/abstractmodelserialiser_p.h @@ -1,28 +1,31 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ -#ifndef abstractmodelserialiser_p_h__ -#define abstractmodelserialiser_p_h__ - +#ifndef abstractmultiroleserialiser_p_h__ +#define abstractmultiroleserialiser_p_h__ #include "abstractmodelserialiser.h" #define Magic_Model_Header QStringLiteral("808FC674-78A0-4682-9C17-E05B18A0CDD3") // magic string to mark models +class QAbstractItemModel; class AbstractModelSerialiserPrivate { Q_DISABLE_COPY(AbstractModelSerialiserPrivate); Q_DECLARE_PUBLIC(AbstractModelSerialiser) protected: - QAbstractItemModel* m_model; - const QAbstractItemModel* m_constModel; - AbstractModelSerialiser* q_ptr; - AbstractModelSerialiserPrivate(AbstractModelSerialiser* q); - static QString variantToString(const QVariant& val); - static QVariant stringToVariant(const QString& val); -#ifdef QT_GUI_LIB - static QImage loadImageVariant(int type, const QString& val); - static QString saveImageVariant(const QImage& val); -#endif - static QVariant loadVariant(int type, const QString& val); - static QString saveVariant(const QVariant& val); - static int guessDecimals(double val); - static QString guessDecimalsString(double val, QLocale* loca = Q_NULLPTR); + AbstractModelSerialiserPrivate(AbstractModelSerialiser *q); + QDataStream::Version m_streamVersion; + QList m_rolesToSave; + QAbstractItemModel *m_model; + const QAbstractItemModel *m_constModel; + AbstractModelSerialiser *q_ptr; }; - -#endif // abstractmodelserialiser_p_h__ \ No newline at end of file +#endif // abstractmultiroleserialiser_p_h__ diff --git a/src/private/abstractmultiroleserialiser_p.h b/src/private/abstractmultiroleserialiser_p.h deleted file mode 100644 index 8ab5611..0000000 --- a/src/private/abstractmultiroleserialiser_p.h +++ /dev/null @@ -1,14 +0,0 @@ - -#ifndef abstractmultiroleserialiser_p_h__ -#define abstractmultiroleserialiser_p_h__ - -#include "private/abstractmodelserialiser_p.h" -class AbstractMultiRoleSerialiserPrivate : public AbstractModelSerialiserPrivate -{ - Q_DISABLE_COPY(AbstractMultiRoleSerialiserPrivate); - Q_DECLARE_PUBLIC(AbstractMultiRoleSerialiser) -protected: - AbstractMultiRoleSerialiserPrivate(AbstractMultiRoleSerialiser* q); - QList m_rolesToSave; -}; -#endif // abstractmultiroleserialiser_p_h__ \ No newline at end of file diff --git a/src/private/abstractsingleroleserialiser_p.h b/src/private/abstractsingleroleserialiser_p.h index 9cdb449..0312c26 100644 --- a/src/private/abstractsingleroleserialiser_p.h +++ b/src/private/abstractsingleroleserialiser_p.h @@ -1,14 +1,25 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef abstractsingleroleserialiser_p_h__ #define abstractsingleroleserialiser_p_h__ -#include "private/abstractmodelserialiser_p.h" +#include "private/abstractstringserialiser_p.h" #include "abstractsingleroleserialiser.h" -class AbstractSingleRoleSerialiserPrivate : public AbstractModelSerialiserPrivate +class AbstractSingleRoleSerialiserPrivate : public AbstractStringSerialiserPrivate { Q_DISABLE_COPY(AbstractSingleRoleSerialiserPrivate); Q_DECLARE_PUBLIC(AbstractSingleRoleSerialiser) protected: - AbstractSingleRoleSerialiserPrivate(AbstractSingleRoleSerialiser* q); - int m_roleToSave; + AbstractSingleRoleSerialiserPrivate(AbstractSingleRoleSerialiser *q); }; #endif // abstractsingleroleserialiser_p_h__ \ No newline at end of file diff --git a/src/private/abstractstringserialiser_p.h b/src/private/abstractstringserialiser_p.h new file mode 100644 index 0000000..2cb8180 --- /dev/null +++ b/src/private/abstractstringserialiser_p.h @@ -0,0 +1,43 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ + +#ifndef abstractmodelserialiser_p_h__ +#define abstractmodelserialiser_p_h__ + +#include "abstractstringserialiser.h" +#include "abstractmodelserialiser_p.h" +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +class QTextCodec; +#endif +class AbstractStringSerialiserPrivate : public AbstractModelSerialiserPrivate +{ + Q_DISABLE_COPY(AbstractStringSerialiserPrivate); + Q_DECLARE_PUBLIC(AbstractStringSerialiser) +protected: + AbstractStringSerialiserPrivate(AbstractStringSerialiser *q); + static QString variantToString(const QVariant &val); + static QVariant stringToVariant(const QString &val); +#ifdef QT_GUI_LIB + static QImage loadImageVariant(int type, const QString &val); + static QString saveImageVariant(const QImage &val); +#endif + static QVariant loadVariant(int type, const QString &val); + static QString saveVariant(const QVariant &val); + static int guessDecimals(double val); + static QString guessDecimalsString(double val, QLocale *loca = Q_NULLPTR); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + QTextCodec *m_textCodec; +#endif +}; + +#endif // abstractmodelserialiser_p_h__ diff --git a/src/private/binarymodelserialiser_p.h b/src/private/binarymodelserialiser_p.h index 330b164..216138d 100644 --- a/src/private/binarymodelserialiser_p.h +++ b/src/private/binarymodelserialiser_p.h @@ -1,24 +1,35 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef binarymodelserialiser_p_h__ #define binarymodelserialiser_p_h__ -#include "private/abstractmultiroleserialiser_p.h" +#include "private/abstractmodelserialiser_p.h" #include "binarymodelserialiser.h" #include -class BinaryModelSerialiserPrivate : public AbstractMultiRoleSerialiserPrivate +class BinaryModelSerialiserPrivate : public AbstractModelSerialiserPrivate { Q_DISABLE_COPY(BinaryModelSerialiserPrivate); Q_DECLARE_PUBLIC(BinaryModelSerialiser) protected: - BinaryModelSerialiserPrivate(BinaryModelSerialiser* q); - bool writeBinary(QDataStream& writer) const; - bool readBinary(QDataStream& reader); - void writeBinaryElement(QDataStream& destination, const QModelIndex& parent = QModelIndex()) const; - bool readBinaryElement(QDataStream& source, const QModelIndex& parent = QModelIndex()); - + BinaryModelSerialiserPrivate(BinaryModelSerialiser *q); + bool writeBinary(QDataStream &writer) const; + bool readBinary(QDataStream &reader); + void writeBinaryElement(QDataStream &destination, const QModelIndex &parent = QModelIndex()) const; + bool readBinaryElement(QDataStream &source, const QModelIndex &parent = QModelIndex()); #ifdef MS_DECLARE_STREAM_OPERATORS - friend QDataStream& operator<<(QDataStream & stream, const QAbstractItemModel& model); - friend QDataStream& operator>>(QDataStream & stream, QAbstractItemModel& model); + friend QDataStream &operator<<(QDataStream &stream, const QAbstractItemModel &model); + friend QDataStream &operator>>(QDataStream &stream, QAbstractItemModel &model); #endif }; #endif // binarymodelserialiser_p_h__ diff --git a/src/private/csvmodelserialiser_p.h b/src/private/csvmodelserialiser_p.h index 1817c87..281d116 100644 --- a/src/private/csvmodelserialiser_p.h +++ b/src/private/csvmodelserialiser_p.h @@ -1,3 +1,15 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef csvmodelserialiser_p_h__ #define csvmodelserialiser_p_h__ @@ -9,18 +21,18 @@ class CsvModelSerialiserPrivate : public AbstractSingleRoleSerialiserPrivate Q_DISABLE_COPY(CsvModelSerialiserPrivate); Q_DECLARE_PUBLIC(CsvModelSerialiser) protected: - CsvModelSerialiserPrivate(CsvModelSerialiser* q); + CsvModelSerialiserPrivate(CsvModelSerialiser *q); bool m_firstRowIsHeader; bool m_firstColumnIsHeader; QString m_csvSeparator; - bool writeCsv(QTextStream& writer) const; - bool readCsv(QTextStream& reader); + bool writeCsv(QTextStream &writer) const; + bool readCsv(QTextStream &reader); QString escapedCSV(QString unexc) const; QString unescapedCSV(QString exc) const; - static int guessVarType(const QString& val); + static int guessVarType(const QString &val); #ifdef MS_DECLARE_STREAM_OPERATORS - friend QTextStream& operator<<(QTextStream & stream, const QAbstractItemModel& model); - friend QTextStream& operator>>(QTextStream & stream, QAbstractItemModel& model); + friend QTextStream &operator<<(QTextStream &stream, const QAbstractItemModel &model); + friend QTextStream &operator>>(QTextStream &stream, QAbstractItemModel &model); #endif }; #endif // csvmodelserialiser_p_h__ \ No newline at end of file diff --git a/src/private/htmlmodelserialiser_p.h b/src/private/htmlmodelserialiser_p.h index 0168c9c..5a07301 100644 --- a/src/private/htmlmodelserialiser_p.h +++ b/src/private/htmlmodelserialiser_p.h @@ -1,30 +1,40 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef htmlmodelserialiser_p_h__ #define htmlmodelserialiser_p_h__ -#include "private/abstractmultiroleserialiser_p.h" +#include "private/abstractstringserialiser_p.h" #include "htmlmodelserialiser.h" #include class QXmlStreamWriter; class QXmlStreamReader; -class HtmlModelSerialiserPrivate : public AbstractMultiRoleSerialiserPrivate +class HtmlModelSerialiserPrivate : public AbstractStringSerialiserPrivate { Q_DISABLE_COPY(HtmlModelSerialiserPrivate); Q_DECLARE_PUBLIC(HtmlModelSerialiser) protected: - HtmlModelSerialiserPrivate(HtmlModelSerialiser* q); - void writeHtmlElement(QXmlStreamWriter& destination, const QModelIndex& parent = QModelIndex()) const; - bool readHtmlElement(QXmlStreamReader& source, const QModelIndex& parent = QModelIndex()); - bool writeHtml(QXmlStreamWriter& writer) const; - bool readHtml(QXmlStreamReader& reader); - static Q_DECL_CONSTEXPR bool isImageType(int val) Q_DECL_NOEXCEPT{ - return val == QMetaType::QImage - || val == QMetaType::QPixmap - || val == QMetaType::QBitmap - ; + HtmlModelSerialiserPrivate(HtmlModelSerialiser *q); + void writeHtmlElement(QXmlStreamWriter &destination, const QModelIndex &parent = QModelIndex()) const; + bool readHtmlElement(QXmlStreamReader &source, const QModelIndex &parent = QModelIndex()); + bool writeHtml(QXmlStreamWriter &writer) const; + bool readHtml(QXmlStreamReader &reader); + static Q_DECL_CONSTEXPR bool isImageType(int val) Q_DECL_NOEXCEPT + { + return val == QMetaType::QImage || val == QMetaType::QPixmap || val == QMetaType::QBitmap; } - static void writeHtmlVariant(QXmlStreamWriter& writer, const QVariant& val); - static QVariant readHtmlVariant(QXmlStreamReader& reader, int valType); + static void writeHtmlVariant(QXmlStreamWriter &writer, const QVariant &val); + static QVariant readHtmlVariant(QXmlStreamReader &reader, int valType); bool m_printStartDocument; }; #endif // htmlmodelserialiser_p_h__ diff --git a/src/private/insertproxymodel_p.h b/src/private/insertproxymodel_p.h index 9355b8d..7f43df5 100644 --- a/src/private/insertproxymodel_p.h +++ b/src/private/insertproxymodel_p.h @@ -1,32 +1,51 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef insertproxy_p_h__ #define insertproxy_p_h__ #include "insertproxymodel.h" #include "private/modelutilities_common_p.h" #include #include +#include class InsertProxyModelPrivate { Q_DECLARE_PUBLIC(InsertProxyModel) Q_DISABLE_COPY(InsertProxyModelPrivate) - InsertProxyModelPrivate(InsertProxyModel* q); + InsertProxyModelPrivate(InsertProxyModel *q); InsertProxyModel::InsertDirections m_insertDirection; - QList m_layoutChangePersistentIndexes; + QVector m_layoutChangePersistentIndexes; QModelIndexList m_layoutChangeProxyIndexes; QList m_extraData[2]; RolesContainer m_extraHeaderData[2]; RolesContainer m_dataForCorner; bool m_separateEditDisplay; - QList m_sourceConnections; - void checkExtraDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles); + QVector m_sourceConnections; + void checkExtraDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); bool commitColumn(); bool commitRow(); bool commitToSource(const bool isRow); - int mergeEditDisplayHash(RolesContainer& singleHash); + int mergeEditDisplayHash(RolesContainer &singleHash); void onColumnsInserted(const QModelIndex &parent, int first, int last) { onInserted(false, parent, first, last); } - void onColumnsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column) { onMoved(false, parent, start, end, destination, column); } + void onColumnsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column) + { + onMoved(false, parent, start, end, destination, column); + } void onColumnsRemoved(const QModelIndex &parent, int first, int last) { onRemoved(false, parent, first, last); } void onRowsInserted(const QModelIndex &parent, int first, int last) { onInserted(true, parent, first, last); } - void onRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) { onMoved(true, parent, start, end, destination, row); } + void onRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) + { + onMoved(true, parent, start, end, destination, row); + } void onRowsRemoved(const QModelIndex &parent, int first, int last) { onRemoved(true, parent, first, last); } void onInserted(bool isRow, const QModelIndex &parent, int first, int last); void onMoved(bool isRow, const QModelIndex &parent, int start, int end, const QModelIndex &destination, int destIdx); @@ -39,8 +58,9 @@ class InsertProxyModelPrivate void afterSort(bool isRow); void beforeLayoutChange(const QList &parents, QAbstractItemModel::LayoutChangeHint hint); void afetrLayoutChange(const QList &parents, QAbstractItemModel::LayoutChangeHint hint); - QVector setDataInContainer(RolesContainer& baseHash, int role, const QVariant& value); - InsertProxyModel* q_ptr; + void afterReset(); + QVector setDataInContainer(RolesContainer &baseHash, int role, const QVariant &value); + InsertProxyModel *q_ptr; }; #endif // insertproxy_p_h__ diff --git a/src/private/jsonmodelserialiser_p.h b/src/private/jsonmodelserialiser_p.h index e69de29..06bc441 100644 --- a/src/private/jsonmodelserialiser_p.h +++ b/src/private/jsonmodelserialiser_p.h @@ -0,0 +1,33 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ + +#ifndef jsonmodelserialiser_p_h__ +#define jsonmodelserialiser_p_h__ +#include "private/abstractstringserialiser_p.h" +#include "jsonmodelserialiser.h" +#include +#include +class JsonModelSerialiserPrivate : public AbstractStringSerialiserPrivate +{ + Q_DISABLE_COPY(JsonModelSerialiserPrivate); + Q_DECLARE_PUBLIC(JsonModelSerialiser) +protected: + JsonModelSerialiserPrivate(JsonModelSerialiser *q); + QJsonObject toJsonObject(const QModelIndex &parent = QModelIndex()) const; + bool fromJsonObject(const QJsonObject &source, const QModelIndex &parent = QModelIndex()); + QJsonObject objectForRole(int role, const QVariant &value) const; + bool roleForObject(const QJsonObject &source, const QModelIndex &destination); + QJsonDocument::JsonFormat m_format; +}; + +#endif // jsonmodelserialiser_p_h__ diff --git a/src/private/modelutilities_common_p.h b/src/private/modelutilities_common_p.h index 2f9de93..d9bc339 100644 --- a/src/private/modelutilities_common_p.h +++ b/src/private/modelutilities_common_p.h @@ -1,29 +1,45 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef modelutilities_common_p_h__ #define modelutilities_common_p_h__ #include #ifdef OPTIMISE_FOR_MANY_ROLES -#include -using RolesContainer = QHash; +# include +using RolesContainer = QHash; #else -#include +# include using RolesContainer = QMap; #endif // OPTIMISE_FOR_MANY_ROLES -inline const RolesContainer& convertToContainer(const RolesContainer& other) { return other; } -template < class T > -RolesContainer convertToContainer(const T& other){ +inline const RolesContainer &convertToContainer(const RolesContainer &other) +{ + return other; +} +template +RolesContainer convertToContainer(const T &other) +{ RolesContainer result; for (auto i = other.begin(), otherEnd = other.end(); i != otherEnd; ++i) result.insert(i.key(), i.value()); return result; } -template < class T, class = typename std::enable_if::value>::type> -const RolesContainer& convertFromContainer(const RolesContainer& other) +template::value>::type> +const RolesContainer &convertFromContainer(const RolesContainer &other) { return other; } -template < class T, class = typename std::enable_if::value>::type > -T convertFromContainer(const RolesContainer& other) +template::value>::type> +T convertFromContainer(const RolesContainer &other) { T result; for (auto i = other.begin(), otherEnd = other.end(); i != otherEnd; ++i) diff --git a/src/private/rolemaskproxymodel_p.h b/src/private/rolemaskproxymodel_p.h index fcccbda..55d168f 100644 --- a/src/private/rolemaskproxymodel_p.h +++ b/src/private/rolemaskproxymodel_p.h @@ -1,26 +1,39 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef rolemaskproxymodel_p_h__ #define rolemaskproxymodel_p_h__ #include #include #include #include +#include #include "private/modelutilities_common_p.h" #include "rolemaskproxymodel.h" class RoleMaskProxyModelPrivate { Q_DECLARE_PUBLIC(RoleMaskProxyModel) - RoleMaskProxyModel* q_ptr; - RoleMaskProxyModelPrivate(RoleMaskProxyModel* q); + RoleMaskProxyModel *q_ptr; + RoleMaskProxyModelPrivate(RoleMaskProxyModel *q); QSet m_maskedRoles; - QHash m_maskedData; + QHash m_maskedData; bool m_transparentIfEmpty; bool m_mergeDisplayEdit; - QMetaObject::Connection m_sourceDataChangedConnection; - void clearUnusedMaskedRoles(const QSet& roles); - bool removeRole(const QPersistentModelIndex& idx, int role); - bool removeRole(const QHash::iterator& idxIter, int role); - void signalAllChanged(const QVector& roles = QVector(), const QModelIndex& parent = QModelIndex()); - void interceptDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles); + QVector m_sourceConnections; + void clearUnusedMaskedRoles(const QSet &roles); + bool removeRole(const QPersistentModelIndex &idx, int role); + bool removeRole(const QHash::iterator &idxIter, int role); + void signalAllChanged(const QVector &roles = QVector(), const QModelIndex &parent = QModelIndex()); + void interceptDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); }; #endif // rolemaskproxymodel_p_h__ \ No newline at end of file diff --git a/src/private/xmlmodelserialiser_p.h b/src/private/xmlmodelserialiser_p.h index 69af8b4..eb06764 100644 --- a/src/private/xmlmodelserialiser_p.h +++ b/src/private/xmlmodelserialiser_p.h @@ -1,27 +1,37 @@ - +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef xmlmodelserialiser_p_h__ #define xmlmodelserialiser_p_h__ -#include "private/abstractmultiroleserialiser_p.h" +#include "private/abstractstringserialiser_p.h" #include "xmlmodelserialiser.h" #include -class XmlModelSerialiserPrivate : public AbstractMultiRoleSerialiserPrivate +class XmlModelSerialiserPrivate : public AbstractStringSerialiserPrivate { Q_DISABLE_COPY(XmlModelSerialiserPrivate); Q_DECLARE_PUBLIC(XmlModelSerialiser) protected: - XmlModelSerialiserPrivate(XmlModelSerialiser* q); - bool writeXml(QXmlStreamWriter& writer) const; - bool readXml(QXmlStreamReader& reader); - void writeXmlElement(QXmlStreamWriter& destination, const QModelIndex& parent = QModelIndex()) const; - bool readXmlElement(QXmlStreamReader& source, const QModelIndex& parent = QModelIndex()); + XmlModelSerialiserPrivate(XmlModelSerialiser *q); + bool writeXml(QXmlStreamWriter &writer) const; + bool readXml(QXmlStreamReader &reader); + void writeXmlElement(QXmlStreamWriter &destination, const QModelIndex &parent = QModelIndex()) const; + bool readXmlElement(QXmlStreamReader &source, const QModelIndex &parent = QModelIndex()); bool m_printStartDocument; #ifdef MS_DECLARE_STREAM_OPERATORS - friend QXmlStreamWriter& operator<<(QXmlStreamWriter& stream, const QAbstractItemModel& model); - friend QXmlStreamReader& operator>>(QXmlStreamReader& stream, QAbstractItemModel& model); + friend QXmlStreamWriter &operator<<(QXmlStreamWriter &stream, const QAbstractItemModel &model); + friend QXmlStreamReader &operator>>(QXmlStreamReader &stream, QAbstractItemModel &model); #endif }; - #endif // xmlmodelserialiser_p_h__ diff --git a/src/rolemaskproxymodel.cpp b/src/rolemaskproxymodel.cpp index 13a26cf..37f0fcc 100644 --- a/src/rolemaskproxymodel.cpp +++ b/src/rolemaskproxymodel.cpp @@ -1,14 +1,34 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #include "rolemaskproxymodel.h" #include "private/rolemaskproxymodel_p.h" #include -RoleMaskProxyModelPrivate::RoleMaskProxyModelPrivate(RoleMaskProxyModel* q) - :q_ptr(q) + +/*! +\internal +*/ +RoleMaskProxyModelPrivate::RoleMaskProxyModelPrivate(RoleMaskProxyModel *q) + : q_ptr(q) , m_transparentIfEmpty(true) , m_mergeDisplayEdit(true) { Q_ASSERT(q_ptr); } -void RoleMaskProxyModelPrivate::interceptDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) + +/*! +\internal +*/ +void RoleMaskProxyModelPrivate::interceptDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { Q_Q(RoleMaskProxyModel); Q_ASSERT(topLeft.isValid() ? topLeft.model() == q->sourceModel() : true); @@ -19,21 +39,23 @@ void RoleMaskProxyModelPrivate::interceptDataChanged(const QModelIndex& topLeft, } QVector filteredRoles; filteredRoles.reserve(roles.size()); - for (int singleRole : roles){ - if (m_mergeDisplayEdit && singleRole == Qt::EditRole) + for (int singleRole : roles) { + if (m_mergeDisplayEdit && singleRole == Qt::EditRole) { + if (filteredRoles.contains(Qt::DisplayRole)) + continue; singleRole = Qt::DisplayRole; - if(m_maskedRoles.contains(singleRole)){ - if(m_transparentIfEmpty){ + } + if (m_maskedRoles.contains(singleRole)) { + if (m_transparentIfEmpty) { bool allOpaque = true; for (int i = topLeft.row(); allOpaque && i <= bottomRight.row(); ++i) { - for (int j = topLeft.column(); allOpaque &&j <= bottomRight.column(); ++j) { + for (int j = topLeft.column(); allOpaque && j <= bottomRight.column(); ++j) { allOpaque = m_maskedData.value(q->sourceModel()->index(i, j, topLeft.parent())).value(singleRole).isValid(); } } if (allOpaque) continue; - } - else { + } else { continue; } } @@ -46,7 +68,10 @@ void RoleMaskProxyModelPrivate::interceptDataChanged(const QModelIndex& topLeft, q->dataChanged(q->mapFromSource(topLeft), q->mapFromSource(bottomRight), filteredRoles); } -void RoleMaskProxyModelPrivate::clearUnusedMaskedRoles(const QSet& newRoles) +/*! +\internal +*/ +void RoleMaskProxyModelPrivate::clearUnusedMaskedRoles(const QSet &newRoles) { if (newRoles.isEmpty()) { m_maskedData.clear(); @@ -68,13 +93,19 @@ void RoleMaskProxyModelPrivate::clearUnusedMaskedRoles(const QSet& newRoles } } -bool RoleMaskProxyModelPrivate::removeRole(const QPersistentModelIndex& idx, int role) +/*! +\internal +*/ +bool RoleMaskProxyModelPrivate::removeRole(const QPersistentModelIndex &idx, int role) { const auto idxIter = m_maskedData.find(idx); return removeRole(idxIter, role); } -bool RoleMaskProxyModelPrivate::removeRole(const QHash::iterator& idxIter, int role) +/*! +\internal +*/ +bool RoleMaskProxyModelPrivate::removeRole(const QHash::iterator &idxIter, int role) { if (idxIter == m_maskedData.end()) return false; @@ -86,30 +117,42 @@ bool RoleMaskProxyModelPrivate::removeRole(const QHash& roles, const QModelIndex& parent) +/*! +\internal +*/ +void RoleMaskProxyModelPrivate::signalAllChanged(const QVector &roles, const QModelIndex &parent) { Q_Q(RoleMaskProxyModel); + if (!q->sourceModel()) + return; const int rowCnt = q->rowCount(parent); const int colCnt = q->columnCount(parent); - for (int i = 0; i < rowCnt;++i){ + for (int i = 0; i < rowCnt; ++i) { for (int j = 0; j < colCnt; ++j) { const QModelIndex currPar = q->index(i, j, parent); if (q->hasChildren(currPar)) signalAllChanged(roles, currPar); } } - q->dataChanged(q->index(0, 0, parent), q->index(rowCnt, colCnt, parent), roles); + q->dataChanged(q->index(0, 0, parent), q->index(rowCnt - 1, colCnt - 1, parent), roles); } /*! Constructs a new proxy model with the given \a parent. */ -RoleMaskProxyModel::RoleMaskProxyModel(QObject* parent) - :QIdentityProxyModel(parent) +RoleMaskProxyModel::RoleMaskProxyModel(QObject *parent) + : QIdentityProxyModel(parent) , d_ptr(new RoleMaskProxyModelPrivate(this)) -{} +{ } + +/*! +Constructor used only while subclassing the private class. +Not part of the public API +*/ +RoleMaskProxyModel::RoleMaskProxyModel(RoleMaskProxyModelPrivate &dptr, QObject *parent) + : QIdentityProxyModel(parent) + , d_ptr(&dptr) +{ } /*! Destructor @@ -117,32 +160,48 @@ Destructor RoleMaskProxyModel::~RoleMaskProxyModel() { Q_D(RoleMaskProxyModel); - if (sourceModel()) - QObject::disconnect(d->m_sourceDataChangedConnection); + for (auto discIter = d->m_sourceConnections.cbegin(); discIter != d->m_sourceConnections.cend(); ++discIter) + QObject::disconnect(*discIter); delete d_ptr; } +/*! +\property RoleMaskProxyModel::maskedRoles +\accessors %maskedRoles() +\brief Returns the list of roles managed by the proxy model +*/ QList RoleMaskProxyModel::maskedRoles() const { Q_D(const RoleMaskProxyModel); +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) return d->m_maskedRoles.toList(); +#else + return QList(d->m_maskedRoles.begin(), d->m_maskedRoles.end()); +#endif } -void RoleMaskProxyModel::setMaskedRoles(const QList& newRoles) +/*! +Set the list of roles managed by the proxy model +*/ +void RoleMaskProxyModel::setMaskedRoles(const QList &newRoles) { Q_D(RoleMaskProxyModel); +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) const QSet roles = newRoles.toSet(); - if (d->m_maskedRoles!=roles){ +#else + const QSet roles(newRoles.begin(), newRoles.end()); +#endif + if (d->m_maskedRoles != roles) { QVector changedRoles; QSet changedRolesSet = roles; changedRolesSet.unite(d->m_maskedRoles); if (d->m_mergeDisplayEdit && (changedRolesSet.contains(Qt::EditRole) || changedRolesSet.contains(Qt::DisplayRole))) changedRolesSet << Qt::EditRole << Qt::DisplayRole; changedRoles.reserve(changedRolesSet.size()); - for (auto i = changedRolesSet.cbegin(); i != changedRolesSet.cend();++i) + for (auto i = changedRolesSet.cbegin(); i != changedRolesSet.cend(); ++i) changedRoles.append(*i); d->m_maskedRoles = roles; - if (d->m_mergeDisplayEdit && d->m_maskedRoles.contains(Qt::EditRole)){ + if (d->m_mergeDisplayEdit && d->m_maskedRoles.contains(Qt::EditRole)) { d->m_maskedRoles.remove(Qt::EditRole); d->m_maskedRoles.insert(Qt::DisplayRole); } @@ -152,15 +211,18 @@ void RoleMaskProxyModel::setMaskedRoles(const QList& newRoles) } } +/*! +Removes all roles managed by the proxy model +*/ void RoleMaskProxyModel::clearMaskedRoles() { Q_D(RoleMaskProxyModel); - if (!d->m_maskedRoles.isEmpty()){ + if (!d->m_maskedRoles.isEmpty()) { QVector changedRoles; - changedRoles.reserve(d->m_maskedRoles.size()+1); + changedRoles.reserve(d->m_maskedRoles.size() + 1); const bool hasEdit = d->m_maskedRoles.contains(Qt::EditRole); const bool hasDisplay = d->m_maskedRoles.contains(Qt::DisplayRole); - for (auto i = d->m_maskedRoles.cbegin(); i != d->m_maskedRoles.cend();++i) + for (auto i = d->m_maskedRoles.cbegin(); i != d->m_maskedRoles.cend(); ++i) changedRoles.append(*i); if (hasEdit != hasDisplay) changedRoles.append(hasEdit ? Qt::DisplayRole : Qt::EditRole); @@ -171,6 +233,9 @@ void RoleMaskProxyModel::clearMaskedRoles() } } +/*! +Adds \a role to the list of roles managed by the proxy model +*/ void RoleMaskProxyModel::addMaskedRole(int role) { Q_D(RoleMaskProxyModel); @@ -183,10 +248,13 @@ void RoleMaskProxyModel::addMaskedRole(int role) if (d->m_mergeDisplayEdit && adjRole == Qt::DisplayRole) d->signalAllChanged(QVector{{Qt::EditRole, Qt::DisplayRole}}); else - d->signalAllChanged(QVector(1,adjRole)); + d->signalAllChanged(QVector(1, adjRole)); } } +/*! +Removes \a role from the list of roles managed by the proxy model +*/ void RoleMaskProxyModel::removeMaskedRole(int role) { Q_D(RoleMaskProxyModel); @@ -199,7 +267,7 @@ void RoleMaskProxyModel::removeMaskedRole(int role) if (d->m_mergeDisplayEdit && adjRole == Qt::DisplayRole) d->signalAllChanged(QVector{{Qt::EditRole, Qt::DisplayRole}}); else - d->signalAllChanged(QVector(1,adjRole)); + d->signalAllChanged(QVector(1, adjRole)); } } @@ -208,16 +276,21 @@ void RoleMaskProxyModel::removeMaskedRole(int role) */ void RoleMaskProxyModel::setSourceModel(QAbstractItemModel *sourceMdl) { - Q_D(RoleMaskProxyModel); if (sourceMdl == sourceModel()) - return; + return; + Q_D(RoleMaskProxyModel); + for (auto discIter = d->m_sourceConnections.cbegin(); discIter != d->m_sourceConnections.cend(); ++discIter) + QObject::disconnect(*discIter); + d->m_sourceConnections.clear(); d->m_maskedData.clear(); - if (sourceModel()) - Q_ASSUME(QObject::disconnect(d->m_sourceDataChangedConnection)); QIdentityProxyModel::setSourceModel(sourceMdl); if (sourceModel()) { Q_ASSUME(sourceModel()->disconnect(SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector)), this)); - d->m_sourceDataChangedConnection = connect(sourceModel(), &QAbstractItemModel::dataChanged, [d](const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles)->void{d->interceptDataChanged(topLeft,bottomRight,roles);}); + d->m_sourceConnections << QObject::connect(sourceModel(), &QAbstractItemModel::dataChanged, + [d](const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles) -> void { d->interceptDataChanged(topLeft, bottomRight, roles); }) + << QObject::connect(sourceModel(), &QAbstractItemModel::modelReset, [d]() -> void { d->m_maskedData.clear(); }) + << QObject::connect(sourceModel(), &QAbstractItemModel::destroyed, [this]() -> void { setSourceModel(Q_NULLPTR); }); } } @@ -234,12 +307,12 @@ QVariant RoleMaskProxyModel::data(const QModelIndex &proxyIndex, int role) const return QIdentityProxyModel::data(proxyIndex, role); const QModelIndex sourceIndex = mapToSource(proxyIndex); const auto idxIter = d->m_maskedData.constFind(sourceIndex); - if (idxIter != d->m_maskedData.constEnd()){ + if (idxIter != d->m_maskedData.constEnd()) { const auto roleIter = idxIter->constFind(adjRole); if (roleIter != idxIter->constEnd()) return roleIter.value(); } - if(d->m_transparentIfEmpty) + if (d->m_transparentIfEmpty) return QIdentityProxyModel::data(proxyIndex, role); return QVariant(); } @@ -254,39 +327,166 @@ bool RoleMaskProxyModel::setData(const QModelIndex &proxyIndex, const QVariant & return false; if (d->m_mergeDisplayEdit && role == Qt::EditRole) role = Qt::DisplayRole; - const QVector changedRolesVector = ((d->m_mergeDisplayEdit && role == Qt::DisplayRole) ? QVector{ { Qt::EditRole, Qt::DisplayRole } } : QVector(1, role)); + const QVector changedRolesVector = + ((d->m_mergeDisplayEdit && role == Qt::DisplayRole) ? QVector{{Qt::EditRole, Qt::DisplayRole}} : QVector(1, role)); if (!d->m_maskedRoles.contains(role)) return QIdentityProxyModel::setData(proxyIndex, value, role); const QModelIndex sourceIndex = mapToSource(proxyIndex); Q_ASSERT(sourceIndex.isValid()); auto idxIter = d->m_maskedData.find(sourceIndex); - if (idxIter == d->m_maskedData.end()){ + if (idxIter == d->m_maskedData.end()) { if (!value.isValid()) { return true; - } - else { + } else { idxIter = d->m_maskedData.insert(sourceIndex, RolesContainer()); idxIter->insert(role, value); + maskedDataChanged(proxyIndex, proxyIndex, changedRolesVector); dataChanged(proxyIndex, proxyIndex, changedRolesVector); return true; } } - if (!value.isValid()){ - if (d->removeRole(idxIter, role)) + if (!value.isValid()) { + if (d->removeRole(idxIter, role)) { + maskedDataChanged(proxyIndex, proxyIndex, changedRolesVector); dataChanged(proxyIndex, proxyIndex, changedRolesVector); + } return true; } const auto roleIter = idxIter->find(role); - if (roleIter == idxIter->end()) + if (roleIter == idxIter->end()) idxIter->insert(role, value); else if (roleIter.value() != value) roleIter.value() = value; else return true; + maskedDataChanged(proxyIndex, proxyIndex, changedRolesVector); dataChanged(proxyIndex, proxyIndex, changedRolesVector); return true; } +/*! +\reimp +\details Due to limitations in the architecture, the model might emit 2 separate dataChanged signals, one for the roles that were masked and one for +the roles that are managed by the sorce model +*/ +bool RoleMaskProxyModel::setItemData(const QModelIndex &index, const QMap &roles) +{ + Q_D(RoleMaskProxyModel); + QMap adjustedRoles = roles; + if (d->m_mergeDisplayEdit) { + const auto editIter = adjustedRoles.find(Qt::EditRole); + const auto displayIter = adjustedRoles.constFind(Qt::DisplayRole); + if (editIter != adjustedRoles.end()) { + if (displayIter == adjustedRoles.cend()) { + adjustedRoles.insert(Qt::DisplayRole, editIter.value()); + } + adjustedRoles.erase(editIter); + } + } + const QModelIndex sourceIndex = mapToSource(index); + Q_ASSERT(sourceIndex.isValid()); + QMap rolesForSource; + QVector changedRoles; + changedRoles.reserve(adjustedRoles.size()); + for (auto i = adjustedRoles.cbegin(), rolesEnd = adjustedRoles.cend(); i != rolesEnd; ++i) { + if (!d->m_maskedRoles.contains(i.key())) { + rolesForSource.insert(i.key(), i.value()); + continue; + } + auto idxIter = d->m_maskedData.find(sourceIndex); + if (idxIter == d->m_maskedData.end()) { + if (i->isValid()) { + idxIter = d->m_maskedData.insert(sourceIndex, RolesContainer()); + idxIter->insert(i.key(), i.value()); + changedRoles << i.key(); + } + continue; + } + if (!i->isValid()) { + if (d->removeRole(idxIter, i.key())) + changedRoles << i.key(); + continue; + } + const auto roleIter = idxIter->find(i.key()); + if (roleIter == idxIter->end()) + idxIter->insert(i.key(), i.value()); + else if (roleIter.value() != i.value()) + roleIter.value() = i.value(); + else + continue; + changedRoles << i.key(); + } + if (d->m_mergeDisplayEdit && changedRoles.contains(Qt::DisplayRole)) + changedRoles << Qt::EditRole; + if (!changedRoles.isEmpty()) { + maskedDataChanged(index, index, changedRoles); + dataChanged(index, index, changedRoles); + } + if (!rolesForSource.isEmpty()) + return QIdentityProxyModel::setItemData(index, rolesForSource); + return true; +} + +/*! +\reimp +*/ +QMap RoleMaskProxyModel::itemData(const QModelIndex &index) const +{ + Q_D(const RoleMaskProxyModel); + RolesContainer result; + const auto maskedIter = d->m_maskedData.constFind(index); + if (maskedIter != d->m_maskedData.cend()) { + result = maskedIter.value(); + const auto displayIter = result.constFind(Qt::DisplayRole); + if (d->m_mergeDisplayEdit && displayIter != result.cend()) + result.insert(Qt::EditRole, displayIter.value()); + } + if (!d->m_transparentIfEmpty) { + const QMap baseData = QIdentityProxyModel::itemData(index); + for (auto i = baseData.cbegin(), baseEnd = baseData.cend(); i != baseEnd; ++i) { + if (!result.contains(i.key())) + result.insert(i.key(), i.value()); + } + } + return convertFromContainer>(result); +} + +/*! +Returns all the data managed by the proxy model for a certain \a index. The key of the map is the corresponding role +*/ +QMap RoleMaskProxyModel::maskedItemData(const QModelIndex &index) const +{ + Q_D(const RoleMaskProxyModel); + const auto maskedIter = d->m_maskedData.find(index); + if (maskedIter == d->m_maskedData.end()) + return QMap(); + return convertFromContainer>(maskedIter.value()); +} + +/*! +Removes all the data managed by the proxy model for a certain \a index. +*/ +void RoleMaskProxyModel::clearMaskedData(const QModelIndex &index) +{ + Q_D(RoleMaskProxyModel); + const auto maskedIter = d->m_maskedData.find(index); + if (maskedIter == d->m_maskedData.end()) + return; + Q_ASSERT(!d->m_maskedData.isEmpty()); + const QList changedRolesList = maskedIter->keys(); + const QVector changedRoles = changedRolesList.toVector(); + d->m_maskedData.erase(maskedIter); + maskedDataChanged(index, index, changedRoles); + dataChanged(index, index, changedRoles); +} + +/*! +\property RoleMaskProxyModel::transparentIfEmpty +\accessors %transparentIfEmpty(), setTransparentIfEmpty() +\notifier transparentIfEmptyChanged() +\brief This property determines if a mapped role containing no data should be transparent +\details If this property is set to true, roles managed by the proxy will show the source model data unless it gets ovewritten in the proxy +*/ bool RoleMaskProxyModel::transparentIfEmpty() const { Q_D(const RoleMaskProxyModel); @@ -303,6 +503,16 @@ void RoleMaskProxyModel::setTransparentIfEmpty(bool val) } } +/*! +\property RoleMaskProxyModel::mergeDisplayEdit +\accessors %mergeDisplayEdit(), setMergeDisplayEdit() +\notifier mergeDisplayEditChanged() +\brief This property determines if the Qt::DisplayRole and Qt::EditRole should be merged in the extra row/column +\details By default the two roles are one and the same you can use this property to separate them. +If there's any data in the role when you set this property to true it will be duplicated for both roles. +If there is data both in Qt::DisplayRole and Qt::EditRole when you set this property to false Qt::DisplayRole will prevail. +This property only has effect if Qt::DisplayRole and/or Qt::EditRole are masked by the proxy. Data in the source model is not affected. +*/ bool RoleMaskProxyModel::mergeDisplayEdit() const { Q_D(const RoleMaskProxyModel); @@ -312,24 +522,22 @@ bool RoleMaskProxyModel::mergeDisplayEdit() const void RoleMaskProxyModel::setMergeDisplayEdit(bool val) { Q_D(RoleMaskProxyModel); - if (d->m_mergeDisplayEdit!=val) { - QVector changedRoles({ Qt::DisplayRole, Qt::EditRole }); - d->m_mergeDisplayEdit=val; + if (d->m_mergeDisplayEdit != val) { + QVector changedRoles({Qt::DisplayRole, Qt::EditRole}); + d->m_mergeDisplayEdit = val; const auto idxEnd = d->m_maskedData.end(); for (auto idxIter = d->m_maskedData.begin(); idxIter != idxEnd; ++idxIter) { - if (val){ + if (val) { if (idxIter->contains(Qt::DisplayRole)) { idxIter->remove(Qt::EditRole); - } - else { + } else { const auto roleIter = idxIter->constFind(Qt::EditRole); if (roleIter != idxIter->constEnd()) { idxIter->insert(Qt::DisplayRole, *roleIter); idxIter->remove(Qt::EditRole); } } - } - else{ + } else { const auto roleIter = idxIter->constFind(Qt::DisplayRole); if (roleIter != idxIter->constEnd()) { idxIter->insert(Qt::EditRole, *roleIter); @@ -345,16 +553,28 @@ void RoleMaskProxyModel::setMergeDisplayEdit(bool val) } } -const QSet& RoleMaskProxyModel::maskedRolesSets() const +/*! +Same as maskedRoles but returns it in the way it is stored internally rather than converting it to QList +*/ +const QSet &RoleMaskProxyModel::maskedRolesSets() const { Q_D(const RoleMaskProxyModel); return d->m_maskedRoles; } - /*! \class RoleMaskProxyModel \brief This proxy will act as a mask on top of the source model to intercept data. -\details This proxy model will mask +\details This proxy model can be used to manage data in roles not supported by the source model. +The user can select what roles should be managed by the proxy model and the rest will be left to the source model. +*/ +/*! +\fn RoleMaskProxyModel::maskedDataChanged() +This signal is emitted whenever a dataChanged() signal is emitted for a role managed by the proxy +*/ + +/*! +\fn RoleMaskProxyModel::maskedRolesChanged() +This signal is emitted whenever the list of roles managed by the proxy changes */ diff --git a/src/rolemaskproxymodel.h b/src/rolemaskproxymodel.h index 725ac06..1249446 100644 --- a/src/rolemaskproxymodel.h +++ b/src/rolemaskproxymodel.h @@ -1,3 +1,15 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef rolemaskproxymodel_h__ #define rolemaskproxymodel_h__ #include "modelutilities_global.h" @@ -14,16 +26,20 @@ class MODELUTILITIES_EXPORT RoleMaskProxyModel : public QIdentityProxyModel Q_DISABLE_COPY(RoleMaskProxyModel) Q_DECLARE_PRIVATE(RoleMaskProxyModel) public: - explicit RoleMaskProxyModel(QObject* parent = Q_NULLPTR); + explicit RoleMaskProxyModel(QObject *parent = Q_NULLPTR); ~RoleMaskProxyModel(); QList maskedRoles() const; - void setMaskedRoles(const QList& roles); + void setMaskedRoles(const QList &roles); void clearMaskedRoles(); void addMaskedRole(int role); void removeMaskedRole(int role); void setSourceModel(QAbstractItemModel *sourceModel) Q_DECL_OVERRIDE; QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE; + bool setItemData(const QModelIndex &index, const QMap &roles) Q_DECL_OVERRIDE; + QMap itemData(const QModelIndex &index) const Q_DECL_OVERRIDE; + QMap maskedItemData(const QModelIndex &index) const; + void clearMaskedData(const QModelIndex &index); bool transparentIfEmpty() const; void setTransparentIfEmpty(bool val); bool mergeDisplayEdit() const; @@ -32,9 +48,13 @@ class MODELUTILITIES_EXPORT RoleMaskProxyModel : public QIdentityProxyModel void mergeDisplayEditChanged(bool val); void transparentIfEmptyChanged(bool val); void maskedRolesChanged(); + void maskedDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); + protected: - const QSet& maskedRolesSets() const; + const QSet &maskedRolesSets() const; + RoleMaskProxyModel(RoleMaskProxyModelPrivate &dptr, QObject *parent); + private: - RoleMaskProxyModelPrivate* d_ptr; + RoleMaskProxyModelPrivate *d_ptr; }; #endif // rolemaskproxymodel_h__ diff --git a/src/xmlmodelserialiser.cpp b/src/xmlmodelserialiser.cpp index 9eb053f..2104190 100644 --- a/src/xmlmodelserialiser.cpp +++ b/src/xmlmodelserialiser.cpp @@ -1,15 +1,27 @@ +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #include "xmlmodelserialiser.h" #include "private/xmlmodelserialiser_p.h" #include #include -XmlModelSerialiserPrivate::XmlModelSerialiserPrivate(XmlModelSerialiser* q) - :AbstractMultiRoleSerialiserPrivate(q) +XmlModelSerialiserPrivate::XmlModelSerialiserPrivate(XmlModelSerialiser *q) + : AbstractStringSerialiserPrivate(q) , m_printStartDocument(true) -{} +{ } -void XmlModelSerialiserPrivate::writeXmlElement(QXmlStreamWriter& destination, const QModelIndex& parent) const +void XmlModelSerialiserPrivate::writeXmlElement(QXmlStreamWriter &destination, const QModelIndex &parent) const { Q_ASSERT(m_constModel); if (m_constModel->rowCount(parent) + m_constModel->columnCount(parent) == 0) @@ -35,7 +47,7 @@ void XmlModelSerialiserPrivate::writeXmlElement(QXmlStreamWriter& destination, c continue; // Skip unhandled types destination.writeStartElement(QStringLiteral("DataPoint")); destination.writeAttribute(QStringLiteral("Role"), QString::number(*singleRole)); - destination.writeAttribute(QStringLiteral("Type"), QString::number(roleData.type())); + destination.writeAttribute(QStringLiteral("Type"), QString::number(roleData.userType())); destination.writeCharacters(roleString); destination.writeEndElement(); // DataPoint } @@ -48,28 +60,17 @@ void XmlModelSerialiserPrivate::writeXmlElement(QXmlStreamWriter& destination, c destination.writeEndElement(); // Element } -bool XmlModelSerialiserPrivate::readXmlElement(QXmlStreamReader& source, const QModelIndex& parent) +bool XmlModelSerialiserPrivate::readXmlElement(QXmlStreamReader &source, const QModelIndex &parent) { Q_ASSERT(m_model); if (source.name() != QStringLiteral("Element")) return false; int rowCount, colCount; const QXmlStreamAttributes tableSizeAttribute = source.attributes(); - if (!( - tableSizeAttribute.hasAttribute(QStringLiteral("RowCount")) - && tableSizeAttribute.hasAttribute(QStringLiteral("ColumnCount")) - )) + if (!(tableSizeAttribute.hasAttribute(QStringLiteral("RowCount")) && tableSizeAttribute.hasAttribute(QStringLiteral("ColumnCount")))) return false; - rowCount = tableSizeAttribute.value(QStringLiteral("RowCount")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(); - colCount = tableSizeAttribute.value(QStringLiteral("ColumnCount")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(); + rowCount = tableSizeAttribute.value(QStringLiteral("RowCount")).toInt(); + colCount = tableSizeAttribute.value(QStringLiteral("ColumnCount")).toInt(); if (rowCount <= 0 || colCount <= 0) return false; if (m_model->rowCount(parent) < rowCount) @@ -84,60 +85,41 @@ bool XmlModelSerialiserPrivate::readXmlElement(QXmlStreamReader& source, const Q if (source.isStartElement()) { if (source.name() == QStringLiteral("Cell")) { cellStarted = true; - } - else if (source.name() == QStringLiteral("Row") && cellStarted) { + } else if (source.name() == QStringLiteral("Row") && cellStarted) { rowIndex = source.readElementText().toInt(); - } - else if (source.name() == QStringLiteral("Column") && cellStarted) { + } else if (source.name() == QStringLiteral("Column") && cellStarted) { colIndex = source.readElementText().toInt(); - } - else if (source.name() == QStringLiteral("DataPoint") && cellStarted) { + } else if (source.name() == QStringLiteral("DataPoint") && cellStarted) { if (rowIndex < 0 || colIndex < 0) return false; const QXmlStreamAttributes dataPointTattributes = source.attributes(); - if (!( - dataPointTattributes.hasAttribute(QStringLiteral("Role")) - && dataPointTattributes.hasAttribute(QStringLiteral("Type")) - )) + if (!(dataPointTattributes.hasAttribute(QStringLiteral("Role")) && dataPointTattributes.hasAttribute(QStringLiteral("Type")))) return false; - int dataRole = dataPointTattributes.value(QStringLiteral("Role")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(); - int dataType = dataPointTattributes.value(QStringLiteral("Type")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(); + const int dataRole = dataPointTattributes.value(QStringLiteral("Role")).toInt(); + const int dataType = dataPointTattributes.value(QStringLiteral("Type")).toInt(); const QVariant roleVariant = loadVariant(dataType, source.readElementText()); if (!roleVariant.isNull()) // skip unhandled types m_model->setData(m_model->index(rowIndex, colIndex, parent), roleVariant, dataRole); - } - else if (source.name() == QStringLiteral("Element") && cellStarted) { + } else if (source.name() == QStringLiteral("Element") && cellStarted) { if (rowIndex < 0 || colIndex < 0) return false; readXmlElement(source, m_model->index(rowIndex, colIndex, parent)); } - } - else if (source.isEndElement()) { + } else if (source.isEndElement()) { if (source.name() == QStringLiteral("Cell")) { cellStarted = false; rowIndex = -1; colIndex = -1; - } - else if (source.name() == QStringLiteral("Element")) { + } else if (source.name() == QStringLiteral("Element")) { if (!cellStarted) return true; } } - } return false; } - -bool XmlModelSerialiserPrivate::writeXml(QXmlStreamWriter& writer) const +bool XmlModelSerialiserPrivate::writeXml(QXmlStreamWriter &writer) const { if (!m_constModel) return false; @@ -158,7 +140,7 @@ bool XmlModelSerialiserPrivate::writeXml(QXmlStreamWriter& writer) const writer.writeStartElement(QStringLiteral("HeaderDataPoint")); writer.writeAttribute(QStringLiteral("Section"), QString::number(i)); writer.writeAttribute(QStringLiteral("Role"), QString::number(*singleRole)); - writer.writeAttribute(QStringLiteral("Type"), QString::number(roleData.type())); + writer.writeAttribute(QStringLiteral("Type"), QString::number(roleData.userType())); writer.writeCharacters(roleString); writer.writeEndElement(); // HeaderDataPoint } @@ -176,7 +158,7 @@ bool XmlModelSerialiserPrivate::writeXml(QXmlStreamWriter& writer) const writer.writeStartElement(QStringLiteral("HeaderDataPoint")); writer.writeAttribute(QStringLiteral("Section"), QString::number(i)); writer.writeAttribute(QStringLiteral("Role"), QString::number(*singleRole)); - writer.writeAttribute(QStringLiteral("Type"), QString::number(roleData.type())); + writer.writeAttribute(QStringLiteral("Type"), QString::number(roleData.userType())); writer.writeCharacters(roleString); writer.writeEndElement(); // HeaderDataPoint } @@ -189,7 +171,7 @@ bool XmlModelSerialiserPrivate::writeXml(QXmlStreamWriter& writer) const return !writer.hasError(); } -bool XmlModelSerialiserPrivate::readXml(QXmlStreamReader& reader) +bool XmlModelSerialiserPrivate::readXml(QXmlStreamReader &reader) { if (!m_model) return false; @@ -207,59 +189,37 @@ bool XmlModelSerialiserPrivate::readXml(QXmlStreamReader& reader) m_model->removeRows(0, m_model->rowCount()); return false; } - } - else if (reader.name() == QStringLiteral("HeaderData")) { + } else if (reader.name() == QStringLiteral("HeaderData")) { headerDataStarted = true; - } - else if (reader.name() == QStringLiteral("Vertical") && headerDataStarted) { + } else if (reader.name() == QStringLiteral("Vertical") && headerDataStarted) { if (hHeaderDataStarted) return false; vHeaderDataStarted = true; - } - else if (reader.name() == QStringLiteral("Horizontal") && headerDataStarted) { + } else if (reader.name() == QStringLiteral("Horizontal") && headerDataStarted) { if (vHeaderDataStarted) return false; hHeaderDataStarted = true; - } - else if (reader.name() == QStringLiteral("HeaderDataPoint") && headerDataStarted) { + } else if (reader.name() == QStringLiteral("HeaderDataPoint") && headerDataStarted) { if (!(vHeaderDataStarted || hHeaderDataStarted)) return false; const QXmlStreamAttributes headDataAttribute = reader.attributes(); - if (!( - headDataAttribute.hasAttribute(QStringLiteral("Section")) - && headDataAttribute.hasAttribute(QStringLiteral("Role")) - && headDataAttribute.hasAttribute(QStringLiteral("Type")) - )) + if (!(headDataAttribute.hasAttribute(QStringLiteral("Section")) && headDataAttribute.hasAttribute(QStringLiteral("Role")) + && headDataAttribute.hasAttribute(QStringLiteral("Type")))) return false; - int headerSection = headDataAttribute.value(QStringLiteral("Section")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(); - int headerRole = headDataAttribute.value(QStringLiteral("Role")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(); - int headerType = headDataAttribute.value(QStringLiteral("Type")) - #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) - .toString() - #endif - .toInt(); + int headerSection = headDataAttribute.value(QStringLiteral("Section")).toInt(); + int headerRole = headDataAttribute.value(QStringLiteral("Role")).toInt(); + int headerType = headDataAttribute.value(QStringLiteral("Type")).toInt(); const QVariant roleVariant = loadVariant(headerType, reader.readElementText()); if (!roleVariant.isNull()) // skip unhandled types m_model->setHeaderData(headerSection, (vHeaderDataStarted ? Qt::Vertical : Qt::Horizontal), roleVariant, headerRole); } - } - else if (reader.isEndElement()) { + } else if (reader.isEndElement()) { if (reader.name() == QStringLiteral("HeaderData")) { headerDataStarted = false; - } - else if (reader.name() == QStringLiteral("Vertical") && headerDataStarted) { + } else if (reader.name() == QStringLiteral("Vertical") && headerDataStarted) { vHeaderDataStarted = false; - } - else if (reader.name() == QStringLiteral("Horizontal") && headerDataStarted) { + } else if (reader.name() == QStringLiteral("Horizontal") && headerDataStarted) { hHeaderDataStarted = false; } } @@ -272,34 +232,50 @@ bool XmlModelSerialiserPrivate::readXml(QXmlStreamReader& reader) return true; } +/*! +\property XmlModelSerialiser::printStartDocument +\accessors %printStartDocument(), setPrintStartDocument() +\brief This property determines if the start of the xml document should be written +\details If this property is set to \c true (the default) the serialiser will write the \c starting block. +Set this to false to save the model as part of a larger xml document +*/ + bool XmlModelSerialiser::printStartDocument() const { Q_D(const XmlModelSerialiser); - + return d->m_printStartDocument; } void XmlModelSerialiser::setPrintStartDocument(bool val) { Q_D(XmlModelSerialiser); - + d->m_printStartDocument = val; } - -bool XmlModelSerialiser::saveModel(QString* destination) const +/*! +\reimp +*/ +bool XmlModelSerialiser::saveModel(QString *destination) const { if (!destination) return false; Q_D(const XmlModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; - QXmlStreamWriter witer(destination); - return d->writeXml(witer); + QXmlStreamWriter writer(destination); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + return d->writeXml(writer); } -bool XmlModelSerialiser::saveModel(QIODevice* destination) const +/*! +\reimp +*/ +bool XmlModelSerialiser::saveModel(QIODevice *destination) const { if (!destination) return false; @@ -310,26 +286,47 @@ bool XmlModelSerialiser::saveModel(QIODevice* destination) const if (!destination->isWritable()) return false; Q_D(const XmlModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; - QXmlStreamWriter witer(destination); - return d->writeXml(witer); + QXmlStreamWriter writer(destination); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + return d->writeXml(writer); } -bool XmlModelSerialiser::saveModel(QByteArray* destination) const +/*! +\reimp +*/ +bool XmlModelSerialiser::saveModel(QByteArray *destination) const { if (!destination) return false; Q_D(const XmlModelSerialiser); - - if (!d->m_model) + + if (!d->m_constModel) return false; - QXmlStreamWriter witer(destination); - return d->writeXml(witer); + QXmlStreamWriter writer(destination); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + writer.setCodec(textCodec()); +#endif + return d->writeXml(writer); } -bool XmlModelSerialiser::loadModel(QIODevice* source) +/*! +Saves the model to the given \a stream +*/ +bool XmlModelSerialiser::saveModel(QXmlStreamWriter &stream) const +{ + Q_D(const XmlModelSerialiser); + return d->writeXml(stream); +} + +/*! +\reimp +*/ +bool XmlModelSerialiser::loadModel(QIODevice *source) { if (!source) return false; @@ -340,61 +337,92 @@ bool XmlModelSerialiser::loadModel(QIODevice* source) if (!source->isReadable()) return false; Q_D(XmlModelSerialiser); - + if (!d->m_model) return false; QXmlStreamReader reader(source); return d->readXml(reader); } -bool XmlModelSerialiser::loadModel(const QByteArray& source) + +/*! +\reimp +*/ +bool XmlModelSerialiser::loadModel(const QByteArray &source) { Q_D(XmlModelSerialiser); - + if (!d->m_model) return false; QXmlStreamReader reader(source); return d->readXml(reader); } -bool XmlModelSerialiser::loadModel(QString* source) + +/*! +\reimp +*/ +bool XmlModelSerialiser::loadModel(QString *source) { if (!source) return false; Q_D(XmlModelSerialiser); - + QXmlStreamReader reader(*source); return d->readXml(reader); } -XmlModelSerialiser::XmlModelSerialiser(QAbstractItemModel* model) - : AbstractMultiRoleSerialiser(*new XmlModelSerialiserPrivate(this)) +/*! +Loads the model from the given \a stream + +Data previously stored in the model will be removed + */ +bool XmlModelSerialiser::loadModel(QXmlStreamReader &stream) { - setModel(model); + Q_D(XmlModelSerialiser); + return d->readXml(stream); } -XmlModelSerialiser::XmlModelSerialiser(const QAbstractItemModel* model) - : AbstractMultiRoleSerialiser(*new XmlModelSerialiserPrivate(this)) + +/*! +Constructs a serialiser operating over \a model +*/ +XmlModelSerialiser::XmlModelSerialiser(QAbstractItemModel *model, QObject *parent) + : AbstractStringSerialiser(*new XmlModelSerialiserPrivate(this), parent) { setModel(model); } +/*! +\overload -XmlModelSerialiser::XmlModelSerialiser(XmlModelSerialiserPrivate& d) - :AbstractMultiRoleSerialiser(d) -{} +the model will only be allowed to be saved, not loaded +*/ +XmlModelSerialiser::XmlModelSerialiser(const QAbstractItemModel *model, QObject *parent) + : AbstractStringSerialiser(*new XmlModelSerialiserPrivate(this), parent) +{ + setModel(model); +} +/*! +\internal +*/ +XmlModelSerialiser::XmlModelSerialiser(XmlModelSerialiserPrivate &d, QObject *parent) + : AbstractStringSerialiser(d, parent) +{ } +/*! +Destructor +*/ XmlModelSerialiser::~XmlModelSerialiser() = default; - #ifdef MS_DECLARE_STREAM_OPERATORS -QXmlStreamWriter& operator<<(QXmlStreamWriter & stream, const QAbstractItemModel& model) +QXmlStreamWriter &operator<<(QXmlStreamWriter &stream, const QAbstractItemModel &model) { const QModelSerialiser mSer(&model); mSer.d_func()->writeXml(stream); return stream; } -QXmlStreamReader& operator>>(QXmlStreamReader & stream, QAbstractItemModel& model) +QXmlStreamReader &operator>>(QXmlStreamReader &stream, QAbstractItemModel &model) { QModelSerialiser mSer(&model); mSer.d_func()->readXml(stream); @@ -402,3 +430,9 @@ QXmlStreamReader& operator>>(QXmlStreamReader & stream, QAbstractItemModel& mode } #endif // MS_DECLARE_STREAM_OPERATORS + +/*! +\class XmlModelSerialiser + +\brief Serialiser to save and load models in XML format +*/ diff --git a/src/xmlmodelserialiser.h b/src/xmlmodelserialiser.h index 4fede93..71003f7 100644 --- a/src/xmlmodelserialiser.h +++ b/src/xmlmodelserialiser.h @@ -1,41 +1,55 @@ - +/****************************************************************************\ + Copyright 2018 Luca Beldi + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +\****************************************************************************/ #ifndef xmlmodelserialiser_h__ #define xmlmodelserialiser_h__ #include "modelutilities_global.h" -#include "abstractmultiroleserialiser.h" +#include "abstractstringserialiser.h" class XmlModelSerialiserPrivate; class QXmlStreamWriter; class QXmlStreamReader; -class MODELUTILITIES_EXPORT XmlModelSerialiser : public AbstractMultiRoleSerialiser +class MODELUTILITIES_EXPORT XmlModelSerialiser : public AbstractStringSerialiser { - Q_GADGET + Q_OBJECT Q_PROPERTY(bool printStartDocument READ printStartDocument WRITE setPrintStartDocument) Q_DECLARE_PRIVATE(XmlModelSerialiser) Q_DISABLE_COPY(XmlModelSerialiser) public: - XmlModelSerialiser(QAbstractItemModel* model = Q_NULLPTR); - XmlModelSerialiser(const QAbstractItemModel* model); + XmlModelSerialiser(QAbstractItemModel *model = Q_NULLPTR, QObject *parent = Q_NULLPTR); + XmlModelSerialiser(const QAbstractItemModel *model, QObject *parent = Q_NULLPTR); ~XmlModelSerialiser(); bool printStartDocument() const; void setPrintStartDocument(bool val); - Q_INVOKABLE bool saveModel(QIODevice* destination) const Q_DECL_OVERRIDE; - Q_INVOKABLE bool saveModel(QByteArray* destination) const Q_DECL_OVERRIDE; - Q_INVOKABLE virtual bool saveModel(QString* destination) const; - Q_INVOKABLE bool loadModel(QIODevice* source) Q_DECL_OVERRIDE; - Q_INVOKABLE bool loadModel(const QByteArray& source) Q_DECL_OVERRIDE; - Q_INVOKABLE virtual bool loadModel(QString* source); + virtual bool saveModel(QXmlStreamWriter &stream) const; + bool saveModel(QIODevice *destination) const Q_DECL_OVERRIDE; + bool saveModel(QByteArray *destination) const Q_DECL_OVERRIDE; + bool saveModel(QString *destination) const Q_DECL_OVERRIDE; + bool loadModel(QString *source) Q_DECL_OVERRIDE; + bool loadModel(QIODevice *source) Q_DECL_OVERRIDE; + bool loadModel(const QByteArray &source) Q_DECL_OVERRIDE; + virtual bool loadModel(QXmlStreamReader &stream); + protected: - XmlModelSerialiser(XmlModelSerialiserPrivate& d); + XmlModelSerialiser(XmlModelSerialiserPrivate &d, QObject *parent); #ifdef MS_DECLARE_STREAM_OPERATORS - friend QXmlStreamWriter& operator<<(QXmlStreamWriter & stream, const QAbstractItemModel& model); - friend QXmlStreamReader& operator>>(QXmlStreamReader & stream, QAbstractItemModel& model); + friend QXmlStreamWriter &operator<<(QXmlStreamWriter &stream, const QAbstractItemModel &model); + friend QXmlStreamReader &operator>>(QXmlStreamReader &stream, QAbstractItemModel &model); #endif }; #ifdef MS_DECLARE_STREAM_OPERATORS -QXmlStreamWriter& operator<<(QXmlStreamWriter & stream, const QAbstractItemModel& model); -QXmlStreamReader& operator>>(QXmlStreamReader & stream, QAbstractItemModel& model); +QXmlStreamWriter &operator<<(QXmlStreamWriter &stream, const QAbstractItemModel &model); +QXmlStreamReader &operator>>(QXmlStreamReader &stream, QAbstractItemModel &model); #endif #endif // xmlmodelserialiser_h__ \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ba92b67..e6bf086 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,4 +3,11 @@ if(BUILD_ROLEMASKPROXY) endif() if(BUILD_INSERTPROXY) add_subdirectory(tst_InsertProxyModel) -endif() \ No newline at end of file +endif() +if(BUILD_MODELSERIALISATION) + add_subdirectory(tst_BinaryModelSerialiser) + add_subdirectory(tst_CsvModelSerialiser) + add_subdirectory(tst_HtmlModelSerialiser) + add_subdirectory(tst_JsonModelSerialiser) + add_subdirectory(tst_XmlModelSerialiser) +endif() diff --git a/tests/modeltestmanager.h b/tests/modeltestmanager.h new file mode 100644 index 0000000..1314be8 --- /dev/null +++ b/tests/modeltestmanager.h @@ -0,0 +1,30 @@ +#ifndef MODELTESTMANAGER_H +#define MODELTESTMANAGER_H +#include +#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) +# ifdef MOC_MODEL_TEST +class ModelTest : public QObject +{ + Q_DISABLE_COPY(ModelTest) +public: + ModelTest(QAbstractItemModel *model, QObject *parent = Q_NULLPTR) + : QObject(parent) + { + Q_UNUSED(model) + } +}; +# else +# include "modeltest.h" +# endif +#else +# include +class ModelTest : public QAbstractItemModelTester +{ + Q_DISABLE_COPY(ModelTest) +public: + ModelTest(QAbstractItemModel *model, QObject *parent) + : QAbstractItemModelTester(model, QAbstractItemModelTester::FailureReportingMode::QtTest, parent) + { } +}; +#endif +#endif diff --git a/tests/tst_BinaryModelSerialiser/CMakeLists.txt b/tests/tst_BinaryModelSerialiser/CMakeLists.txt new file mode 100644 index 0000000..ab9b7bc --- /dev/null +++ b/tests/tst_BinaryModelSerialiser/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.2) +include(TestMacro) +BasicTest(BinaryModelSerialiser) +target_sources(tst_BinaryModelSerialiser PRIVATE + ../tst_SerialisersCommon/tst_serialiserscommon.h + ../tst_SerialisersCommon/tst_serialiserscommon.cpp +) +target_include_directories(tst_BinaryModelSerialiser PRIVATE ../tst_SerialisersCommon/) diff --git a/tests/tst_BinaryModelSerialiser/main.cpp b/tests/tst_BinaryModelSerialiser/main.cpp new file mode 100644 index 0000000..0eb67f0 --- /dev/null +++ b/tests/tst_BinaryModelSerialiser/main.cpp @@ -0,0 +1,3 @@ +#include "tst_binarymodelserialiser.h" +#include +QTEST_MAIN(tst_BinaryModelSerialiser) \ No newline at end of file diff --git a/tests/tst_BinaryModelSerialiser/tst_binarymodelserialiser.cpp b/tests/tst_BinaryModelSerialiser/tst_binarymodelserialiser.cpp new file mode 100644 index 0000000..60c9328 --- /dev/null +++ b/tests/tst_BinaryModelSerialiser/tst_binarymodelserialiser.cpp @@ -0,0 +1,40 @@ +#include +#include "tst_binarymodelserialiser.h" +#include +#include +#include +#include + +void tst_BinaryModelSerialiser::basicSaveLoadByteArray() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + BinaryModelSerialiser serialiser; + saveLoadFile(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_BinaryModelSerialiser::basicSaveLoadFile() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + BinaryModelSerialiser serialiser; + saveLoadByteArray(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_BinaryModelSerialiser::basicSaveLoadStream() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + BinaryModelSerialiser serialiser(sourceModel); + serialiser.addRoleToSave(Qt::UserRole + 1); + QByteArray dataArray; + QDataStream writeStream(&dataArray, QIODevice::WriteOnly); + QVERIFY(serialiser.saveModel(writeStream)); + QDataStream readStream(dataArray); + serialiser.setModel(destinationModel); + QVERIFY(serialiser.loadModel(readStream)); + checkModelEqual(sourceModel, destinationModel); + destinationModel->deleteLater(); +} diff --git a/tests/tst_BinaryModelSerialiser/tst_binarymodelserialiser.h b/tests/tst_BinaryModelSerialiser/tst_binarymodelserialiser.h new file mode 100644 index 0000000..07825a7 --- /dev/null +++ b/tests/tst_BinaryModelSerialiser/tst_binarymodelserialiser.h @@ -0,0 +1,17 @@ +#ifndef tst_binarymodelserialiser_h__ +#define tst_binarymodelserialiser_h__ +#include +#include +class QAbstractItemModel; +class tst_BinaryModelSerialiser : public QObject, public tst_SerialiserCommon +{ + Q_OBJECT +private Q_SLOTS: + void basicSaveLoadByteArray(); + void basicSaveLoadFile(); + void basicSaveLoadStream(); + void basicSaveLoadByteArray_data() { basicSaveLoadData(this); } + void basicSaveLoadFile_data() { basicSaveLoadData(this); } + void basicSaveLoadStream_data() { basicSaveLoadData(this); } +}; +#endif diff --git a/tests/tst_CsvModelSerialiser/CMakeLists.txt b/tests/tst_CsvModelSerialiser/CMakeLists.txt new file mode 100644 index 0000000..92bd038 --- /dev/null +++ b/tests/tst_CsvModelSerialiser/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.2) +include(TestMacro) +BasicTest(CsvModelSerialiser) +target_sources(tst_CsvModelSerialiser PRIVATE + ../tst_SerialisersCommon/tst_serialiserscommon.h + ../tst_SerialisersCommon/tst_serialiserscommon.cpp +) +target_include_directories(tst_CsvModelSerialiser PRIVATE ../tst_SerialisersCommon/) \ No newline at end of file diff --git a/tests/tst_CsvModelSerialiser/main.cpp b/tests/tst_CsvModelSerialiser/main.cpp new file mode 100644 index 0000000..c912529 --- /dev/null +++ b/tests/tst_CsvModelSerialiser/main.cpp @@ -0,0 +1,3 @@ +#include "tst_csvmodelserialiser.h" +#include +QTEST_MAIN(tst_CsvModelSerialiser) \ No newline at end of file diff --git a/tests/tst_CsvModelSerialiser/tst_csvmodelserialiser.cpp b/tests/tst_CsvModelSerialiser/tst_csvmodelserialiser.cpp new file mode 100644 index 0000000..aeec5ff --- /dev/null +++ b/tests/tst_CsvModelSerialiser/tst_csvmodelserialiser.cpp @@ -0,0 +1,172 @@ +#include +#include "tst_csvmodelserialiser.h" +#include +#include +#include +#include + +void tst_CsvModelSerialiser::basicSaveLoadByteArray() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser; + saveLoadByteArray(&serialiser, sourceModel, destinationModel, false); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadFile() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser; + saveLoadFile(&serialiser, sourceModel, destinationModel, false); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadString() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser; + saveLoadString(&serialiser, sourceModel, destinationModel, false); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadStream() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser(sourceModel); + QByteArray dataArray; + QBuffer serialisedCsvStream(&dataArray); + QVERIFY(serialisedCsvStream.open(QIODevice::WriteOnly)); + QTextStream writeStream(&serialisedCsvStream); + QVERIFY(serialiser.saveModel(writeStream)); + serialisedCsvStream.close(); + QVERIFY(serialisedCsvStream.open(QIODevice::ReadOnly)); + QTextStream readStream(&serialisedCsvStream); + serialiser.setModel(destinationModel); + QVERIFY(serialiser.loadModel(readStream)); + checkModelEqual(sourceModel, destinationModel); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadByteArrayNoHeader() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser; + serialiser.setFirstColumnIsHeader(false); + serialiser.setFirstRowIsHeader(false); + saveLoadByteArray(&serialiser, sourceModel, destinationModel, false, false); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadFileNoHeader() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser; + serialiser.setFirstColumnIsHeader(false); + serialiser.setFirstRowIsHeader(false); + saveLoadFile(&serialiser, sourceModel, destinationModel, false, false); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadStringNoHeader() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser; + serialiser.setFirstColumnIsHeader(false); + serialiser.setFirstRowIsHeader(false); + saveLoadString(&serialiser, sourceModel, destinationModel, false, false); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadStreamNoHeader() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser(sourceModel); + serialiser.setFirstColumnIsHeader(false); + serialiser.setFirstRowIsHeader(false); + QByteArray dataArray; + QBuffer serialisedCsvStream(&dataArray); + QVERIFY(serialisedCsvStream.open(QIODevice::WriteOnly)); + QTextStream writeStream(&serialisedCsvStream); + QVERIFY(serialiser.saveModel(writeStream)); + serialisedCsvStream.close(); + QVERIFY(serialisedCsvStream.open(QIODevice::ReadOnly)); + QTextStream readStream(&serialisedCsvStream); + serialiser.setModel(destinationModel); + QVERIFY(serialiser.loadModel(readStream)); + checkModelEqual(sourceModel, destinationModel, QModelIndex(), QModelIndex(), false); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadByteArrayCustomSeparator() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser; + serialiser.setCsvSeparator(QStringLiteral("\t")); + saveLoadByteArray(&serialiser, sourceModel, destinationModel, false); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadFileCustomSeparator() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser; + serialiser.setCsvSeparator(QStringLiteral("\t")); + saveLoadFile(&serialiser, sourceModel, destinationModel, false); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadStringCustomSeparator() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser; + serialiser.setCsvSeparator(QStringLiteral("\t")); + saveLoadString(&serialiser, sourceModel, destinationModel, false); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadStreamCustomSeparator() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + CsvModelSerialiser serialiser(sourceModel); + serialiser.setCsvSeparator(QStringLiteral("\t")); + QByteArray dataArray; + QBuffer serialisedCsvStream(&dataArray); + QVERIFY(serialisedCsvStream.open(QIODevice::WriteOnly)); + QTextStream writeStream(&serialisedCsvStream); + QVERIFY(serialiser.saveModel(writeStream)); + serialisedCsvStream.close(); + QVERIFY(serialisedCsvStream.open(QIODevice::ReadOnly)); + QTextStream readStream(&serialisedCsvStream); + serialiser.setModel(destinationModel); + QVERIFY(serialiser.loadModel(readStream)); + checkModelEqual(sourceModel, destinationModel); + destinationModel->deleteLater(); +} + +void tst_CsvModelSerialiser::basicSaveLoadData(QObject *parent) +{ + Q_UNUSED(parent) + QTest::addColumn("sourceModel"); + QTest::addColumn("destinationModel"); + QTest::newRow("List Single Role") << static_cast(createStringModel(this)) + << static_cast(new QStringListModel(this)); + QTest::newRow("List Single Role Overwrite") << static_cast(createStringModel(this)) << createStringModel(this); +#ifdef QT_GUI_LIB + QTest::newRow("Table Single Role") << static_cast(createComplexModel(false, false, this)) + << static_cast(new QStandardItemModel(this)); + QTest::newRow("Table Single Role Overwrite") << static_cast(createComplexModel(false, false, this)) + << createComplexModel(false, false, this); +#endif +} diff --git a/tests/tst_CsvModelSerialiser/tst_csvmodelserialiser.h b/tests/tst_CsvModelSerialiser/tst_csvmodelserialiser.h new file mode 100644 index 0000000..3711f1f --- /dev/null +++ b/tests/tst_CsvModelSerialiser/tst_csvmodelserialiser.h @@ -0,0 +1,41 @@ +#ifndef tst_csvmodelserialiser_h__ +#define tst_csvmodelserialiser_h__ +#include +#include +class QAbstractItemModel; +class CsvModelSerialiser; +class tst_CsvModelSerialiser : public QObject, public tst_SerialiserCommon +{ + Q_OBJECT +private Q_SLOTS: + void basicSaveLoadByteArray(); + void basicSaveLoadFile(); + void basicSaveLoadString(); + void basicSaveLoadStream(); + void basicSaveLoadByteArray_data() { basicSaveLoadData(); } + void basicSaveLoadFile_data() { basicSaveLoadData(); } + void basicSaveLoadString_data() { basicSaveLoadData(); } + void basicSaveLoadStream_data() { basicSaveLoadData(); } + + void basicSaveLoadByteArrayNoHeader(); + void basicSaveLoadFileNoHeader(); + void basicSaveLoadStringNoHeader(); + void basicSaveLoadStreamNoHeader(); + void basicSaveLoadByteArrayNoHeader_data() { basicSaveLoadData(); } + void basicSaveLoadFileNoHeader_data() { basicSaveLoadData(); } + void basicSaveLoadStringNoHeader_data() { basicSaveLoadData(); } + void basicSaveLoadStreamNoHeader_data() { basicSaveLoadData(); } + + void basicSaveLoadByteArrayCustomSeparator(); + void basicSaveLoadFileCustomSeparator(); + void basicSaveLoadStringCustomSeparator(); + void basicSaveLoadStreamCustomSeparator(); + void basicSaveLoadByteArrayCustomSeparator_data() { basicSaveLoadData(); } + void basicSaveLoadFileCustomSeparator_data() { basicSaveLoadData(); } + void basicSaveLoadStringCustomSeparator_data() { basicSaveLoadData(); } + void basicSaveLoadStreamCustomSeparator_data() { basicSaveLoadData(); } + +protected: + void basicSaveLoadData(QObject *parent = nullptr) override; +}; +#endif diff --git a/tests/tst_HtmlModelSerialiser/CMakeLists.txt b/tests/tst_HtmlModelSerialiser/CMakeLists.txt new file mode 100644 index 0000000..00d6cb8 --- /dev/null +++ b/tests/tst_HtmlModelSerialiser/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.2) +include(TestMacro) +BasicTest(HtmlModelSerialiser) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Network) +if("${Qt${QT_VERSION_MAJOR}Network_FOUND}") + message(STATUS "Found Qt Network ${Qt${QT_VERSION_MAJOR}Gui_VERSION}") +endif() +target_link_libraries(tst_HtmlModelSerialiser PRIVATE Qt${QT_VERSION_MAJOR}::Network) +target_sources(tst_HtmlModelSerialiser PRIVATE + ../tst_SerialisersCommon/tst_serialiserscommon.h + ../tst_SerialisersCommon/tst_serialiserscommon.cpp +) +target_include_directories(tst_HtmlModelSerialiser PRIVATE ../tst_SerialisersCommon/) diff --git a/tests/tst_HtmlModelSerialiser/main.cpp b/tests/tst_HtmlModelSerialiser/main.cpp new file mode 100644 index 0000000..f4445a0 --- /dev/null +++ b/tests/tst_HtmlModelSerialiser/main.cpp @@ -0,0 +1,3 @@ +#include "tst_htmlmodelserialiser.h" +#include +QTEST_MAIN(tst_HtmlModelSerialiser) \ No newline at end of file diff --git a/tests/tst_HtmlModelSerialiser/tst_htmlmodelserialiser.cpp b/tests/tst_HtmlModelSerialiser/tst_htmlmodelserialiser.cpp new file mode 100644 index 0000000..76be816 --- /dev/null +++ b/tests/tst_HtmlModelSerialiser/tst_htmlmodelserialiser.cpp @@ -0,0 +1,140 @@ +#include +#include "tst_htmlmodelserialiser.h" +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef QT_NETWORK_LIB +# include +# include +# include +#endif + +void tst_HtmlModelSerialiser::initTestCase() +{ +#ifdef QT_NETWORK_LIB + qnam = new QNetworkAccessManager(this); +#endif +} + +void tst_HtmlModelSerialiser::basicSaveLoadByteArray() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + HtmlModelSerialiser serialiser; + saveLoadByteArray(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_HtmlModelSerialiser::basicSaveLoadFile() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + HtmlModelSerialiser serialiser; + saveLoadFile(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_HtmlModelSerialiser::basicSaveLoadString() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + HtmlModelSerialiser serialiser; + saveLoadString(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_HtmlModelSerialiser::basicSaveLoadNested() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + HtmlModelSerialiser serialiser(sourceModel); + serialiser.addRoleToSave(Qt::UserRole + 1); + QByteArray dataArray; + QBuffer serialisedHtmlNested(&dataArray); + QVERIFY(serialisedHtmlNested.open(QIODevice::WriteOnly)); + serialiser.setPrintStartDocument(false); + QTextStream nestStream(&serialisedHtmlNested); + nestStream << QStringLiteral("Nested Model

Actual Model

"); + QString modelString; + QVERIFY(serialiser.saveModel(&modelString)); + nestStream << modelString << QStringLiteral(""); + serialisedHtmlNested.close(); + QVERIFY(serialisedHtmlNested.open(QIODevice::ReadOnly)); + serialiser.setModel(destinationModel); + QVERIFY(serialiser.loadModel(&serialisedHtmlNested)); + checkModelEqual(sourceModel, destinationModel); + + destinationModel->deleteLater(); +} + +void tst_HtmlModelSerialiser::validateHtmlOutput() +{ +#ifdef QT_NETWORK_LIB + QFETCH(const QAbstractItemModel *, sourceModel); + HtmlModelSerialiser serialiser(sourceModel); + serialiser.addRoleToSave(Qt::UserRole + 1); + QByteArray htmlData; + QVERIFY(serialiser.saveModel(&htmlData)); + QNetworkReply *validateReply = nullptr; + for (int attempt = 3; attempt > 0; --attempt) { + QNetworkRequest validateReq(QUrl(QStringLiteral("https://validator.w3.org/nu/?out=json"))); + validateReq.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/html; charset=utf-8")); + validateReply = qnam->post(validateReq, htmlData); + QEventLoop replyWaitLoop; + QObject::connect(validateReply, &QNetworkReply::finished, &replyWaitLoop, &QEventLoop::quit); +# if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + QObject::connect(validateReply, static_cast(&QNetworkReply::error), &replyWaitLoop, + &QEventLoop::quit); +# else + QObject::connect(validateReply, &QNetworkReply::errorOccurred, &replyWaitLoop, &QEventLoop::quit); +# endif + QObject::connect(validateReply, &QNetworkReply::sslErrors, validateReply, + static_cast(&QNetworkReply::ignoreSslErrors)); + replyWaitLoop.exec(); + if (validateReply->error() == QNetworkReply::NoError) + break; + } + if (validateReply->error() != QNetworkReply::NoError) + QSKIP("Communication with online html validator failed"); + const QByteArray replyData = validateReply->readAll(); + QJsonParseError parseError; + const QJsonDocument replyDoc = QJsonDocument::fromJson(replyData, &parseError); + QCOMPARE(parseError.error, QJsonParseError::NoError); + QVERIFY(replyDoc.isObject()); + const QJsonObject replyObj = replyDoc.object(); + QVERIFY(replyObj.contains(QStringLiteral("messages"))); + QVERIFY(replyObj.value(QStringLiteral("messages")).isArray()); + const QJsonArray msgArr = replyObj.value(QStringLiteral("messages")).toArray(); + for (auto &&i : msgArr) { + QVERIFY(i.isObject()); + const QJsonObject msgObject = i.toObject(); + QVERIFY(msgObject.contains(QStringLiteral("type"))); + QVERIFY(msgObject.value(QStringLiteral("type")).isString()); + const QString typeString = msgObject.value(QStringLiteral("type")).toString(); + QVERIFY(typeString.compare(QStringLiteral("error"), Qt::CaseInsensitive) != 0); + QVERIFY(typeString.compare(QStringLiteral("non-document-error"), Qt::CaseInsensitive) != 0); + } +#else + QSKIP("This test requires the Qt Network module"); +#endif +} + +void tst_HtmlModelSerialiser::validateHtmlOutput_data() +{ + QTest::addColumn("sourceModel"); + QTest::newRow("List Single Role") << static_cast(createStringModel(this)); +#ifdef QT_GUI_LIB + QTest::newRow("Tree Multi Roles") << static_cast(createComplexModel(true, true, this)); + QTest::newRow("Table Single Role") << static_cast(createComplexModel(false, false, this)); + QTest::newRow("Table Multi Roles") << static_cast(createComplexModel(false, true, this)); + QTest::newRow("Tree Single Role") << static_cast(createComplexModel(true, false, this)); + QTest::newRow("abvbavole") << static_cast(createStringModel(this)); +#endif +} diff --git a/tests/tst_HtmlModelSerialiser/tst_htmlmodelserialiser.h b/tests/tst_HtmlModelSerialiser/tst_htmlmodelserialiser.h new file mode 100644 index 0000000..a36737d --- /dev/null +++ b/tests/tst_HtmlModelSerialiser/tst_htmlmodelserialiser.h @@ -0,0 +1,26 @@ +#ifndef tst_htmlmodelserialiser_h__ +#define tst_htmlmodelserialiser_h__ +#include +#include +class QAbstractItemModel; +class QNetworkAccessManager; +class tst_HtmlModelSerialiser : public QObject, public tst_SerialiserCommon +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void basicSaveLoadByteArray(); + void basicSaveLoadFile(); + void basicSaveLoadString(); + void basicSaveLoadNested(); + void basicSaveLoadByteArray_data() { basicSaveLoadData(this); } + void basicSaveLoadFile_data() { basicSaveLoadData(this); } + void basicSaveLoadString_data() { basicSaveLoadData(this); } + void basicSaveLoadNested_data() { basicSaveLoadData(this); } + void validateHtmlOutput(); + void validateHtmlOutput_data(); + +protected: + QNetworkAccessManager *qnam; +}; +#endif diff --git a/tests/tst_InsertProxyModel/CMakeLists.txt b/tests/tst_InsertProxyModel/CMakeLists.txt index 2c9feb4..00f1614 100644 --- a/tests/tst_InsertProxyModel/CMakeLists.txt +++ b/tests/tst_InsertProxyModel/CMakeLists.txt @@ -1,63 +1,3 @@ -cmake_minimum_required(VERSION 3.3) -if(POLICY CMP0025) - cmake_policy(SET CMP0025 NEW) -endif() -if(POLICY CMP0048) - cmake_policy(SET CMP0048 NEW) -endif() -if(POLICY CMP0057) - cmake_policy(SET CMP0057 NEW) -endif() -set (CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_AUTOMOC ON) -project(tst_InsertProxyModel VERSION "1.0") -set(REQUIRED_QT_VERSION 5.0.0) -find_package(Qt5Test ${REQUIRED_QT_VERSION} REQUIRED) -find_package(Qt5Gui ${REQUIRED_QT_VERSION}) -if(NOT Qt5Gui_FOUND) - set(NO_GUI ON) -else() - message(STATUS "Found Qt Gui ${Qt5Gui_VERSION}") -endif() -message(STATUS "Found Qt Test ${Qt5Test_VERSION}") -set(tst_InsertProxyModel_LIBS ${tst_InsertProxyModel_LIBS} ${Qt5Test_LIBRARIES}) -set(tst_InsertProxyModel_INCLUDE ${tst_InsertProxyModel_INCLUDE} ${Qt5Test_INCLUDE_DIRS}) -set(tst_InsertProxyModel_COMPILE_DEFINE ${tst_InsertProxyModel_COMPILE_DEFINE} ${Qt5Test_COMPILE_DEFINITIONS} ) -if(NOT NO_GUI) -set(tst_InsertProxyModel_LIBS ${tst_InsertProxyModel_LIBS} ${Qt5Gui_LIBRARIES}) -set(tst_InsertProxyModel_INCLUDE ${tst_InsertProxyModel_INCLUDE} ${Qt5Gui_INCLUDE_DIRS}) -set(tst_InsertProxyModel_COMPILE_DEFINE ${tst_InsertProxyModel_COMPILE_DEFINE} ${Qt5Gui_COMPILE_DEFINITIONS}) -endif() -set(tst_InsertProxyModel_INCLUDE ${tst_InsertProxyModel_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}) -set(tst_InsertProxyModel_SRCS - main.cpp - tst_insertproxymodel.cpp -) -add_executable(tst_InsertProxyModel ${tst_InsertProxyModel_SRCS}) -if(BUILD_STATIC_LIBS) - set(tst_InsertProxyModel_DEFINE ${tst_InsertProxyModel_DEFINE} MODELUTILITIES_STATIC) -endif() -add_dependencies(tst_InsertProxyModel modelutilities) -target_include_directories(tst_InsertProxyModel PRIVATE ../../src ${tst_InsertProxyModel_INCLUDE}) -target_link_libraries(tst_InsertProxyModel PRIVATE modelutilities ${tst_InsertProxyModel_LIBS}) -target_compile_definitions(tst_InsertProxyModel PRIVATE ${tst_InsertProxyModel_DEFINE}) -set_target_properties(tst_InsertProxyModel PROPERTIES - VERSION "1.0" - SOVERSION 1 - EXPORT_NAME "tstInsertProxyModel" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/tests" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/tests" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin/tests" -) -if(TEST_OUTPUT_XML) - set(Test_Output_FileName "tst_InsertProxyModel") - if(CMAKE_BUILD_TYPE MATCHES DEBUG) - set(Test_Output_FileName "${Test_Output_FileName}_debug") - endif() - set(Test_Output_FileName "${Test_Output_FileName}_tstres.xml") - add_test(NAME tstInsertProxyModel WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/TestResults" COMMAND $ -o ${Test_Output_FileName},xml) -else() - add_test(NAME tstInsertProxyModel WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" COMMAND $) -endif() +cmake_minimum_required(VERSION 3.2) +include(TestMacro) +BasicTest(InsertProxyModel) diff --git a/tests/tst_InsertProxyModel/tst_insertproxymodel.cpp b/tests/tst_InsertProxyModel/tst_insertproxymodel.cpp index f75ab71..c56ffcf 100644 --- a/tests/tst_InsertProxyModel/tst_insertproxymodel.cpp +++ b/tests/tst_InsertProxyModel/tst_insertproxymodel.cpp @@ -2,23 +2,45 @@ #include #include #ifdef QT_GUI_LIB -#include +# include #endif #include #include +#include "../modeltestmanager.h" +#include -QAbstractItemModel* createNullModel(QObject* parent){ +QAbstractItemModel *createNullModel(QObject *parent) +{ Q_UNUSED(parent) return nullptr; } -QAbstractItemModel* createListModel(QObject* parent) + +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +using StringListModel = QStringListModel; +#else +class StringListModel : public QStringListModel +{ +public: + using QStringListModel::QStringListModel; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + const QVariant baseData = QStringListModel::data(index, role); + if (baseData.toString().isEmpty()) + return QVariant(); + return baseData; + } +}; +#endif + +QAbstractItemModel *createListModel(QObject *parent) { - return new QStringListModel(QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3") << QStringLiteral("4") << QStringLiteral("5"), parent); + return new StringListModel( + QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3") << QStringLiteral("4") << QStringLiteral("5"), parent); } -QAbstractItemModel* createTableModel(QObject* parent) +QAbstractItemModel *createTableModel(QObject *parent) { - QAbstractItemModel* result = nullptr; + QAbstractItemModel *result = nullptr; #ifdef QT_GUI_LIB result = new QStandardItemModel(parent); result->insertRows(0, 5); @@ -34,9 +56,9 @@ QAbstractItemModel* createTableModel(QObject* parent) return result; } -QAbstractItemModel* createTreeModel(QObject* parent) +QAbstractItemModel *createTreeModel(QObject *parent) { - QAbstractItemModel* result = nullptr; + QAbstractItemModel *result = nullptr; #ifdef QT_GUI_LIB result = new QStandardItemModel(parent); result->insertRows(0, 5); @@ -63,29 +85,30 @@ QAbstractItemModel* createTreeModel(QObject* parent) } void tst_InsertProxyModel::testCommitSubclass_data() { - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::addColumn("insertDirection"); QTest::addColumn("canInsertColumns"); QTest::newRow("List Row") << createListModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertRow) << false; QTest::newRow("List Column") << createListModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertColumn) << false; QTest::newRow("List Row Column") << createListModel(this) << (InsertProxyModel::InsertRow | InsertProxyModel::InsertColumn) << false; -#ifdef QT_GUI_LIB QTest::newRow("Table Row") << createTableModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertRow) << true; QTest::newRow("Table Column") << createTableModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertColumn) << true; QTest::newRow("Table Row Column") << createTableModel(this) << (InsertProxyModel::InsertRow | InsertProxyModel::InsertColumn) << true; QTest::newRow("Tree Row") << createTreeModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertRow) << true; QTest::newRow("Tree Column") << createTreeModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertColumn) << true; QTest::newRow("Tree Row Column") << createTreeModel(this) << (InsertProxyModel::InsertRow | InsertProxyModel::InsertColumn) << true; -#endif } void tst_InsertProxyModel::testCommitSlot() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; QFETCH(InsertProxyModel::InsertDirections, insertDirection); QFETCH(bool, canInsertColumns); InsertProxyModel proxy; + new ModelTest(&proxy, baseModel); proxy.setSourceModel(baseModel); proxy.setInsertDirection(insertDirection); const int originalColCount = baseModel->columnCount(); @@ -156,7 +179,7 @@ void tst_InsertProxyModel::testCommitSlot() baseRowsInsertedSpy.clear(); QCOMPARE(proxyRowsInsertedSpy.count(), 1); proxyRowsInsertedSpy.clear(); - QVERIFY(baseDataChangedSpy.count()>0); // change to QCOMPARE(baseDataChangedSpy.count(),1) once QTBUG-67511 is fixed + QVERIFY(baseDataChangedSpy.count() > 0); // change to QCOMPARE(baseDataChangedSpy.count(),1) once QTBUG-67511 is fixed baseDataChangedSpy.clear(); QCOMPARE(proxy.rowCount(), baseModel->rowCount() + 1); QCOMPARE(baseModel->rowCount(), originalRowCount + 1); @@ -171,32 +194,29 @@ void tst_InsertProxyModel::testCommitSlot() void tst_InsertProxyModel::testCommitSlot_data() { - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::addColumn("insertDirection"); QTest::addColumn("canInsertColumns"); QTest::newRow("List Row") << createListModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertRow) << false; QTest::newRow("List Column") << createListModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertColumn) << false; QTest::newRow("List Row Column") << createListModel(this) << (InsertProxyModel::InsertRow | InsertProxyModel::InsertColumn) << false; -#ifdef QT_GUI_LIB QTest::newRow("Table Row") << createTableModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertRow) << true; QTest::newRow("Table Column") << createTableModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertColumn) << true; QTest::newRow("Table Row Column") << createTableModel(this) << (InsertProxyModel::InsertRow | InsertProxyModel::InsertColumn) << true; QTest::newRow("Tree Row") << createTreeModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertRow) << true; QTest::newRow("Tree Column") << createTreeModel(this) << InsertProxyModel::InsertDirections(InsertProxyModel::InsertColumn) << true; QTest::newRow("Tree Row Column") << createTreeModel(this) << (InsertProxyModel::InsertRow | InsertProxyModel::InsertColumn) << true; -#endif } void tst_InsertProxyModel::testSourceInsertCol() { -#ifndef QT_GUI_LIB - QSKIP("This test requires the Qt GUI module"); -#else - QFETCH(QAbstractItemModel*, baseModel); +#if defined(QT_GUI_LIB) + QFETCH(QAbstractItemModel *, baseModel); QFETCH(InsertProxyModel::InsertDirections, insertDirection); QFETCH(int, indexToInsert); QFETCH(bool, addViaProxy); InsertProxyModel proxy; + new ModelTest(&proxy, baseModel); proxy.setSourceModel(baseModel); proxy.setInsertDirection(insertDirection); QSignalSpy proxyColsInsertedSpy(&proxy, SIGNAL(columnsInserted(QModelIndex, int, int))); @@ -210,7 +230,8 @@ void tst_InsertProxyModel::testSourceInsertCol() if (insertDirection & InsertProxyModel::InsertRow) { if (indexToInsert > 0) QVERIFY(proxy.setData(proxy.index(originalRowCount, indexToInsert - 1), QStringLiteral("TestRowBefore"))); - QVERIFY(proxy.setData(proxy.index(originalRowCount, indexToInsert), QStringLiteral("TestRowAfter"))); + if (indexToInsert < originalColCount) + QVERIFY(proxy.setData(proxy.index(originalRowCount, indexToInsert), QStringLiteral("TestRowAfter"))); } if (insertDirection & InsertProxyModel::InsertColumn) { QVERIFY(proxy.setData(proxy.index(0, originalColCount), QStringLiteral("TestColumn"))); @@ -236,25 +257,26 @@ void tst_InsertProxyModel::testSourceInsertCol() QCOMPARE(baseModel->columnCount(), originalColCount + 1); QCOMPARE(baseModel->rowCount(), originalRowCount); QCOMPARE(proxy.rowCount(), originalRowCount + bool(insertDirection & InsertProxyModel::InsertRow)); - QCOMPARE(proxy.columnCount(), originalColCount +1 + bool(insertDirection & InsertProxyModel::InsertColumn)); + QCOMPARE(proxy.columnCount(), originalColCount + 1 + bool(insertDirection & InsertProxyModel::InsertColumn)); for (int i = 0; i < originalRowCount; ++i) { - for (int j = 0; j < originalColCount+1; ++j) { + for (int j = 0; j < originalColCount + 1; ++j) { QCOMPARE(proxy.index(i, j).data(), baseModel->index(i, j).data()); } } for (int j = 0; j < originalRowCount; ++j) { - QVERIFY(!proxy.index(j,indexToInsert).data().isValid()); - QVERIFY(!baseModel->index(j,indexToInsert).data().isValid()); + QVERIFY(!proxy.index(j, indexToInsert).data().isValid()); + QVERIFY(!baseModel->index(j, indexToInsert).data().isValid()); } if (insertDirection & InsertProxyModel::InsertRow) { - QCOMPARE(proxy.index(originalRowCount,indexToInsert + 1).data().toString(), QStringLiteral("TestRowAfter")); + if (indexToInsert < originalColCount) + QCOMPARE(proxy.index(originalRowCount, indexToInsert + 1).data().toString(), QStringLiteral("TestRowAfter")); if (indexToInsert > 0) QCOMPARE(proxy.index(originalRowCount, indexToInsert - 1).data().toString(), QStringLiteral("TestRowBefore")); } QCOMPARE(proxy.commitRow(), bool(insertDirection & InsertProxyModel::InsertRow)); QCOMPARE(proxy.commitColumn(), bool(insertDirection & InsertProxyModel::InsertColumn)); - for (int i = 0; i < originalRowCount + bool(insertDirection & InsertProxyModel::InsertRow); ++i) { - for (int j = 0; j < originalColCount+1 + bool(insertDirection & InsertProxyModel::InsertColumn); ++j) { + for (int i = 0; i < originalRowCount + bool(insertDirection & InsertProxyModel::InsertRow); ++i) { + for (int j = 0; j < originalColCount + 1 + bool(insertDirection & InsertProxyModel::InsertColumn); ++j) { QCOMPARE(proxy.index(i, j).data(), baseModel->index(i, j).data()); } } @@ -264,22 +286,26 @@ void tst_InsertProxyModel::testSourceInsertCol() QCOMPARE(proxy.index(i, baseModel->columnCount()).data(), QVariant()); } if (insertDirection & InsertProxyModel::InsertRow) { - QCOMPARE(baseModel->index(originalRowCount, indexToInsert + 1).data().toString(), QStringLiteral("TestRowAfter")); + if (indexToInsert < originalColCount) + QCOMPARE(baseModel->index(originalRowCount, indexToInsert + 1).data().toString(), QStringLiteral("TestRowAfter")); if (indexToInsert > 0) QCOMPARE(baseModel->index(originalRowCount, indexToInsert - 1).data().toString(), QStringLiteral("TestRowBefore")); for (int i = 0; i < proxy.columnCount(); ++i) QCOMPARE(proxy.index(i, baseModel->columnCount()).data(), QVariant()); } baseModel->deleteLater(); +#else + QSKIP("This test requires the Qt GUI module"); #endif } void tst_InsertProxyModel::testSourceInsertRow() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); QFETCH(InsertProxyModel::InsertDirections, insertDirection); QFETCH(int, indexToInsert); QFETCH(bool, addViaProxy); InsertProxyModel proxy; + new ModelTest(&proxy, baseModel); proxy.setSourceModel(baseModel); proxy.setInsertDirection(insertDirection); QSignalSpy proxyRowsInsertedSpy(&proxy, SIGNAL(rowsInserted(QModelIndex, int, int))); @@ -290,16 +316,17 @@ void tst_InsertProxyModel::testSourceInsertRow() const int originalRowCount = baseModel->rowCount(); QCOMPARE(proxy.rowCount(), originalRowCount + bool(insertDirection & InsertProxyModel::InsertRow)); QCOMPARE(proxy.columnCount(), originalColCount + bool(insertDirection & InsertProxyModel::InsertColumn)); - if (insertDirection & InsertProxyModel::InsertRow){ + if (insertDirection & InsertProxyModel::InsertRow) { QVERIFY(proxy.setData(proxy.index(originalRowCount, 0), QStringLiteral("TestRow"))); QVERIFY(!proxy.insertRow(proxy.rowCount())); } if (insertDirection & InsertProxyModel::InsertColumn) { - if (indexToInsert>0) - QVERIFY(proxy.setData(proxy.index(indexToInsert-1, originalColCount), QStringLiteral("TestColumnBefore"))); - QVERIFY(proxy.setData(proxy.index(indexToInsert, originalColCount), QStringLiteral("TestColumnAfter"))); + if (indexToInsert > 0) + QVERIFY(proxy.setData(proxy.index(indexToInsert - 1, originalColCount), QStringLiteral("TestColumnBefore"))); + if (indexToInsert < originalRowCount) + QVERIFY(proxy.setData(proxy.index(indexToInsert, originalColCount), QStringLiteral("TestColumnAfter"))); } - for (int i = 0; i < originalRowCount;++i){ + for (int i = 0; i < originalRowCount; ++i) { for (int j = 0; j < originalColCount; ++j) { QCOMPARE(proxy.index(i, j).data(), baseModel->index(i, j).data()); } @@ -320,7 +347,7 @@ void tst_InsertProxyModel::testSourceInsertRow() QCOMPARE(baseModel->columnCount(), originalColCount); QCOMPARE(proxy.rowCount(), originalRowCount + 1 + bool(insertDirection & InsertProxyModel::InsertRow)); QCOMPARE(proxy.columnCount(), originalColCount + bool(insertDirection & InsertProxyModel::InsertColumn)); - for (int i = 0; i < originalRowCount+1; ++i) { + for (int i = 0; i < originalRowCount + 1; ++i) { for (int j = 0; j < originalColCount; ++j) { QCOMPARE(proxy.index(i, j).data(), baseModel->index(i, j).data()); } @@ -330,24 +357,26 @@ void tst_InsertProxyModel::testSourceInsertRow() QVERIFY(baseModel->index(indexToInsert, j).data().isNull()); } if (insertDirection & InsertProxyModel::InsertColumn) { - QCOMPARE(proxy.index(indexToInsert + 1, originalColCount).data().toString(), QStringLiteral("TestColumnAfter")); + if (indexToInsert < originalRowCount) + QCOMPARE(proxy.index(indexToInsert + 1, originalColCount).data().toString(), QStringLiteral("TestColumnAfter")); if (indexToInsert > 0) QCOMPARE(proxy.index(indexToInsert - 1, originalColCount).data().toString(), QStringLiteral("TestColumnBefore")); } QCOMPARE(proxy.commitRow(), bool(insertDirection & InsertProxyModel::InsertRow)); QCOMPARE(proxy.commitColumn(), bool(insertDirection & InsertProxyModel::InsertColumn)); - for (int i = 0; i < originalRowCount +1+ bool(insertDirection & InsertProxyModel::InsertRow); ++i) { + for (int i = 0; i < originalRowCount + 1 + bool(insertDirection & InsertProxyModel::InsertRow); ++i) { for (int j = 0; j < originalColCount + bool(insertDirection & InsertProxyModel::InsertColumn); ++j) { QCOMPARE(proxy.index(i, j).data(), baseModel->index(i, j).data()); } } if (insertDirection & InsertProxyModel::InsertRow) { - QCOMPARE(baseModel->index(originalRowCount+1, 0).data().toString(), QStringLiteral("TestRow")); - for (int i = 0; i < proxy.columnCount();++i) + QCOMPARE(baseModel->index(originalRowCount + 1, 0).data().toString(), QStringLiteral("TestRow")); + for (int i = 0; i < proxy.columnCount(); ++i) QCOMPARE(proxy.index(baseModel->rowCount(), i).data(), QVariant()); } if (insertDirection & InsertProxyModel::InsertColumn) { - QCOMPARE(baseModel->index(indexToInsert + 1, originalColCount).data().toString(), QStringLiteral("TestColumnAfter")); + if (indexToInsert < originalRowCount) + QCOMPARE(baseModel->index(indexToInsert + 1, originalColCount).data().toString(), QStringLiteral("TestColumnAfter")); if (indexToInsert > 0) QCOMPARE(baseModel->index(indexToInsert - 1, originalColCount).data().toString(), QStringLiteral("TestColumnBefore")); for (int i = 0; i < proxy.rowCount(); ++i) @@ -358,42 +387,34 @@ void tst_InsertProxyModel::testSourceInsertRow() void tst_InsertProxyModel::testSourceInsertRow_data() { - - QTest::addColumn("baseModel"); + + QTest::addColumn("baseModel"); QTest::addColumn("insertDirection"); QTest::addColumn("indexToInsert"); QTest::addColumn("addViaProxy"); - for (auto&& insertDirection : { - std::make_pair(InsertProxyModel::InsertRow, QByteArrayLiteral("Extra Row")) - , std::make_pair(InsertProxyModel::InsertColumn, QByteArrayLiteral("Extra Column")) - , std::make_pair(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow, QByteArrayLiteral("Extra Row and Column")) - }){ - - for (auto&& baseModel : { - std::make_pair(insertDirection.first & InsertProxyModel::InsertColumn ? &createNullModel : &createListModel, QByteArrayLiteral("List")) - , std::make_pair(&createTableModel, QByteArrayLiteral("Table")) - , std::make_pair(&createTreeModel, QByteArrayLiteral("Tree")) - }){ - QAbstractItemModel* tempModel = baseModel.first(this); - if (tempModel){ - for (auto&& indexToInsert : { - std::make_pair(0, QByteArrayLiteral("Begin")) - , std::make_pair(tempModel->rowCount(), QByteArrayLiteral("End")) - , std::make_pair(tempModel->rowCount() / 2, QByteArrayLiteral("Middle")) - }) { - for (auto&& addViaProxy : { - std::make_pair(true, QByteArrayLiteral("via Proxy")) - , std::make_pair(false, QByteArrayLiteral("via Base")) - }) { - QTest::newRow((baseModel.second + ' ' + insertDirection.second + ' ' + addViaProxy.second + ' ' + indexToInsert.second).constData()) - << tempModel - << insertDirection.first - << indexToInsert.first - << addViaProxy.first - ; + for (auto &&insertDirection : + {std::make_pair(InsertProxyModel::InsertRow, QByteArrayLiteral("Extra Row")), + std::make_pair(InsertProxyModel::InsertColumn, QByteArrayLiteral("Extra Column")), + std::make_pair(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow, + QByteArrayLiteral("Extra Row and Column"))}) { + for (auto &&baseModel : + {std::make_pair(insertDirection.first & InsertProxyModel::InsertColumn ? &createNullModel : &createListModel, QByteArrayLiteral("List")), + std::make_pair(&createTableModel, QByteArrayLiteral("Table")), std::make_pair(&createTreeModel, QByteArrayLiteral("Tree"))}) { + QAbstractItemModel *const tempModel = baseModel.first(this); + if (tempModel) { + for (auto &&addViaProxy : + {std::make_pair(true, QByteArrayLiteral("via Proxy")), std::make_pair(false, QByteArrayLiteral("via Base"))}) { + + for (auto &&indexToInsert : + {std::make_pair(0, QByteArrayLiteral("Begin")), std::make_pair(tempModel->rowCount(), QByteArrayLiteral("End")), + std::make_pair(tempModel->rowCount() / 2, QByteArrayLiteral("Middle"))}) { + QTest::newRow( + (baseModel.second + ' ' + insertDirection.second + ' ' + addViaProxy.second + ' ' + indexToInsert.second).constData()) + << baseModel.first(this) << insertDirection.first << indexToInsert.first << addViaProxy.first; } } + tempModel->deleteLater(); } } } @@ -402,40 +423,32 @@ void tst_InsertProxyModel::testSourceInsertRow_data() void tst_InsertProxyModel::testSourceInsertCol_data() { - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::addColumn("insertDirection"); QTest::addColumn("indexToInsert"); QTest::addColumn("addViaProxy"); - for (auto&& insertDirection : { - std::make_pair(InsertProxyModel::InsertRow, QByteArrayLiteral("Extra Row")) - , std::make_pair(InsertProxyModel::InsertColumn, QByteArrayLiteral("Extra Column")) - , std::make_pair(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow, QByteArrayLiteral("Extra Row and Column")) - }) { - - for (auto&& baseModel : { - std::make_pair(&createTableModel, QByteArrayLiteral("Table")) - , std::make_pair(&createTreeModel, QByteArrayLiteral("Tree")) - }) { - QAbstractItemModel* tempModel = baseModel.first(this); + for (auto &&insertDirection : + {std::make_pair(InsertProxyModel::InsertRow, QByteArrayLiteral("Extra Row")), + std::make_pair(InsertProxyModel::InsertColumn, QByteArrayLiteral("Extra Column")), + std::make_pair(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow, + QByteArrayLiteral("Extra Row and Column"))}) { + + for (auto &&baseModel : + {std::make_pair(&createTableModel, QByteArrayLiteral("Table")), std::make_pair(&createTreeModel, QByteArrayLiteral("Tree"))}) { + QAbstractItemModel *const tempModel = baseModel.first(this); if (tempModel) { - for (auto&& indexToInsert : { - std::make_pair(0, QByteArrayLiteral("Begin")) - , std::make_pair(tempModel->columnCount(), QByteArrayLiteral("End")) - , std::make_pair(tempModel->columnCount() / 2, QByteArrayLiteral("Middle")) - }) { - for (auto&& addViaProxy : { - std::make_pair(true, QByteArrayLiteral("via Proxy")) - , std::make_pair(false, QByteArrayLiteral("via Base")) - }) { - QTest::newRow((baseModel.second + ' ' + insertDirection.second + ' ' + addViaProxy.second + ' ' + indexToInsert.second).constData()) - << tempModel - << insertDirection.first - << indexToInsert.first - << addViaProxy.first - ; + for (auto &&indexToInsert : + {std::make_pair(0, QByteArrayLiteral("Begin")), std::make_pair(tempModel->columnCount(), QByteArrayLiteral("End")), + std::make_pair(tempModel->columnCount() / 2, QByteArrayLiteral("Middle"))}) { + for (auto &&addViaProxy : + {std::make_pair(true, QByteArrayLiteral("via Proxy")), std::make_pair(false, QByteArrayLiteral("via Base"))}) { + QTest::newRow( + (baseModel.second + ' ' + insertDirection.second + ' ' + addViaProxy.second + ' ' + indexToInsert.second).constData()) + << baseModel.first(this) << insertDirection.first << indexToInsert.first << addViaProxy.first; } } + tempModel->deleteLater(); } } } @@ -444,6 +457,7 @@ void tst_InsertProxyModel::testSourceInsertCol_data() void tst_InsertProxyModel::testNullModel() { InsertProxyModel proxyModel; + new ModelTest(&proxyModel, this); proxyModel.setInsertDirection(InsertProxyModel::InsertRow); proxyModel.setSourceModel(nullptr); QVERIFY(!proxyModel.setData(proxyModel.index(0, 0), QStringLiteral("1"))); @@ -465,16 +479,19 @@ void tst_InsertProxyModel::testNullModel() void tst_InsertProxyModel::testProperties() { InsertProxyModel proxyModel; + new ModelTest(&proxyModel, this); QVERIFY(proxyModel.setProperty("insertDirection", QVariant::fromValue(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow))); - QCOMPARE(proxyModel.property("insertDirection").value(), InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow); - QVERIFY(proxyModel.setProperty("separateEditDisplay", true)); - QCOMPARE(proxyModel.property("separateEditDisplay").toBool(), true); + QCOMPARE(proxyModel.property("insertDirection").value(), + InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow); + QVERIFY(proxyModel.setProperty("mergeDisplayEdit", false)); + QCOMPARE(proxyModel.property("mergeDisplayEdit").toBool(), false); } void tst_InsertProxyModel::testDataForCorner() { - QAbstractItemModel* const baseModel = createListModel(this); + QAbstractItemModel *const baseModel = createListModel(this); InsertProxyModel proxyModel; + new ModelTest(&proxyModel, baseModel); QSignalSpy cornerChangeSpy(&proxyModel, SIGNAL(dataForCornerChanged(QVector))); QSignalSpy cornerDataChangeSpy(&proxyModel, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector))); QVERIFY(cornerChangeSpy.isValid()); @@ -486,13 +503,13 @@ void tst_InsertProxyModel::testDataForCorner() QCOMPARE(proxyModel.dataForCorner().toInt(), 5689); QCOMPARE(cornerChangeSpy.count(), 1); QCOMPARE(cornerDataChangeSpy.count(), 1); - auto roles = cornerChangeSpy.value(0).value(0).value >(); + auto roles = cornerChangeSpy.value(0).value(0).value>(); cornerChangeSpy.clear(); cornerDataChangeSpy.clear(); QCOMPARE(roles.size(), 2); QVERIFY(roles.contains(Qt::EditRole)); QVERIFY(roles.contains(Qt::DisplayRole)); - proxyModel.setSeparateEditDisplay(true); + proxyModel.setMergeDisplayEdit(false); proxyModel.setDataForCorner(8741); QCOMPARE(proxyModel.index(baseModel->rowCount(), baseModel->columnCount()).data().toInt(), 5689); QCOMPARE(proxyModel.dataForCorner().toInt(), 5689); @@ -500,7 +517,7 @@ void tst_InsertProxyModel::testDataForCorner() QCOMPARE(proxyModel.dataForCorner(Qt::EditRole).toInt(), 8741); QCOMPARE(cornerChangeSpy.count(), 1); QCOMPARE(cornerDataChangeSpy.count(), 1); - roles = cornerChangeSpy.value(0).value(0).value >(); + roles = cornerChangeSpy.value(0).value(0).value>(); cornerChangeSpy.clear(); cornerDataChangeSpy.clear(); QCOMPARE(roles.size(), 1); @@ -511,8 +528,9 @@ void tst_InsertProxyModel::testDataForCorner() void tst_InsertProxyModel::testSort() { QFETCH(bool, sortProxy); - QStringListModel baseModel(QStringList({ QStringLiteral("b"), QStringLiteral("a"), QStringLiteral("d"), QStringLiteral("c") })); + QStringListModel baseModel(QStringList({QStringLiteral("b"), QStringLiteral("a"), QStringLiteral("d"), QStringLiteral("c")})); InsertProxyModel proxyModel; + new ModelTest(&proxyModel, this); proxyModel.setSourceModel(&baseModel); proxyModel.setInsertDirection(InsertProxyModel::InsertColumn); QVERIFY(proxyModel.setData(proxyModel.index(0, 1), 1)); @@ -536,7 +554,6 @@ void tst_InsertProxyModel::testSort() QCOMPARE(persistInside.data().toString(), persistBase.data().toString()); for (int i = 0; i < baseModel.rowCount(); ++i) QCOMPARE(proxyModel.index(i, 1).data().toInt(), i); - } void tst_InsertProxyModel::testSort_data() @@ -546,11 +563,84 @@ void tst_InsertProxyModel::testSort_data() QTest::newRow("Sort via Base") << false; } +void tst_InsertProxyModel::testInsertOnEmptyModel() +{ + QStringListModel baseModel1; + InsertProxyModel proxyModel1; + new ModelTest(&proxyModel1, this); + proxyModel1.setSourceModel(&baseModel1); + proxyModel1.setInsertDirection(InsertProxyModel::InsertRow); + proxyModel1.setData(proxyModel1.index(0, 0), QStringLiteral("London")); + QCOMPARE(proxyModel1.rowCount(), 1); + QCOMPARE(proxyModel1.index(0, 0).data().toString(), QStringLiteral("London")); + proxyModel1.commitRow(); + QCOMPARE(baseModel1.rowCount(), 1); + QCOMPARE(baseModel1.index(0, 0).data().toString(), QStringLiteral("London")); +#if defined(QT_GUI_LIB) + QStandardItemModel baseModel2; + baseModel2.insertColumns(0, 2); + InsertProxyModel proxyModel2; + new ModelTest(&proxyModel2, this); + proxyModel2.setSourceModel(&baseModel2); + proxyModel2.setInsertDirection(InsertProxyModel::InsertRow); + proxyModel2.setData(proxyModel2.index(0, 1), QStringLiteral("London")); + QCOMPARE(proxyModel2.rowCount(), 1); + QCOMPARE(proxyModel2.index(0, 1).data().toString(), QStringLiteral("London")); + QVERIFY(!proxyModel2.index(0, 0).data().isValid()); + proxyModel2.commitRow(); + QCOMPARE(baseModel2.rowCount(), 1); + QCOMPARE(baseModel2.index(0, 1).data().toString(), QStringLiteral("London")); + QVERIFY(!baseModel2.index(0, 0).data().isValid()); +# if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) \ + && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0) || QT_VERSION > QT_VERSION_CHECK(6, 0, 2))) // QTBUG-92220 + QStandardItemModel baseModel3; + InsertProxyModel proxyModel3; + new ModelTest(&proxyModel3, this); + proxyModel3.setSourceModel(&baseModel3); + proxyModel3.setInsertDirection(InsertProxyModel::InsertRow); + baseModel3.insertColumns(0, 2); + proxyModel3.setData(proxyModel3.index(0, 1), QStringLiteral("London")); + QCOMPARE(proxyModel3.rowCount(), 1); + QCOMPARE(proxyModel3.index(0, 1).data().toString(), QStringLiteral("London")); + QVERIFY(!proxyModel3.index(0, 0).data().isValid()); + proxyModel3.commitRow(); + QCOMPARE(baseModel3.rowCount(), 1); + QCOMPARE(baseModel3.index(0, 1).data().toString(), QStringLiteral("London")); + QVERIFY(!baseModel3.index(0, 0).data().isValid()); +# endif +#endif +} + +void tst_InsertProxyModel::testResetModel() +{ + QStringListModel baseModel1(QStringList({QStringLiteral("London"), QStringLiteral("Berlin"), QStringLiteral("Paris")})); + QStringListModel baseModel2(QStringList({QStringLiteral("Rome"), QStringLiteral("Madrid")})); + QSortFilterProxyModel middleProxy; + middleProxy.setSourceModel(&baseModel1); + InsertProxyModel insertProxy; + new ModelTest(&insertProxy, this); + insertProxy.setSourceModel(&middleProxy); + insertProxy.setInsertDirection(InsertProxyModel::InsertRow); + insertProxy.setData(insertProxy.index(3, 0), QStringLiteral("Tokyo")); + QSignalSpy insertProxyAboutToResetSpy(&insertProxy, SIGNAL(modelAboutToBeReset())); + QVERIFY(insertProxyAboutToResetSpy.isValid()); + QSignalSpy insertProxyResetSpy(&insertProxy, SIGNAL(modelReset())); + QVERIFY(insertProxyResetSpy.isValid()); + middleProxy.setSourceModel(&baseModel2); + QCOMPARE(insertProxy.rowCount(), 3); + QCOMPARE(insertProxy.index(0, 0).data(0).toString(), QStringLiteral("Rome")); + QCOMPARE(insertProxy.index(1, 0).data(0).toString(), QStringLiteral("Madrid")); + QVERIFY(!insertProxy.index(2, 0).data(0).isValid()); + QCOMPARE(insertProxyAboutToResetSpy.count(), 1); + QCOMPARE(insertProxyResetSpy.count(), 1); +} + void tst_InsertProxyModel::testDisconnectedModel() { - QStringListModel baseModel1(QStringList({ QStringLiteral("London"), QStringLiteral("Berlin"), QStringLiteral("Paris") })); - QStringListModel baseModel2(QStringList({ QStringLiteral("Rome"), QStringLiteral("Madrid"), QStringLiteral("Prague") })); + QStringListModel baseModel1(QStringList({QStringLiteral("London"), QStringLiteral("Berlin"), QStringLiteral("Paris")})); + QStringListModel baseModel2(QStringList({QStringLiteral("Rome"), QStringLiteral("Madrid"), QStringLiteral("Prague")})); InsertProxyModel proxyModel; + new ModelTest(&proxyModel, this); proxyModel.setSourceModel(&baseModel1); QSignalSpy proxyDataChangeSpy(&proxyModel, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector))); baseModel1.setData(baseModel1.index(0, 0), QStringLiteral("New York")); @@ -566,18 +656,21 @@ void tst_InsertProxyModel::testDisconnectedModel() void tst_InsertProxyModel::initTestCase() { - qRegisterMetaType >(); + qRegisterMetaType>(); } void tst_InsertProxyModel::testData() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; InsertProxyModel proxyModel; + new ModelTest(&proxyModel, baseModel); proxyModel.setInsertDirection(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow); proxyModel.setSourceModel(baseModel); const int sourceRows = baseModel->rowCount(); const int sourceCols = baseModel->columnCount(); - for (int i = 0; i < sourceRows;++i){ + for (int i = 0; i < sourceRows; ++i) { for (int j = 0; j < sourceCols; ++j) { QCOMPARE(proxyModel.index(i, j).data(), baseModel->index(i, j).data()); } @@ -586,33 +679,34 @@ void tst_InsertProxyModel::testData() QVERIFY(!proxyModel.index(i, sourceCols).data().isValid()); } for (int i = 0; i < sourceCols; ++i) { - QVERIFY(!proxyModel.index(sourceRows,i).data().isValid()); + QVERIFY(!proxyModel.index(sourceRows, i).data().isValid()); } baseModel->deleteLater(); } void tst_InsertProxyModel::testData_data() { - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::newRow("List") << createListModel(this); -#ifdef QT_GUI_LIB QTest::newRow("Table") << createTableModel(this); QTest::newRow("Tree") << createTreeModel(this); -#endif } void tst_InsertProxyModel::testSetData() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; QFETCH(int, idxRow); QFETCH(int, idxCol); InsertProxyModel proxyModel; - proxyModel.setSeparateEditDisplay(false); + new ModelTest(&proxyModel, baseModel); + proxyModel.setMergeDisplayEdit(true); proxyModel.setInsertDirection(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow); proxyModel.setSourceModel(baseModel); const QModelIndex proxyIdX = proxyModel.index(idxRow, idxCol); const QString idxData = QStringLiteral("Test"); - QVERIFY(proxyModel.setData(proxyIdX, idxData,Qt::DisplayRole)); + QVERIFY(proxyModel.setData(proxyIdX, idxData, Qt::DisplayRole)); QCOMPARE(proxyModel.data(proxyIdX, Qt::DisplayRole).toString(), idxData); QCOMPARE(proxyModel.data(proxyIdX, Qt::EditRole).toString(), idxData); baseModel->deleteLater(); @@ -620,102 +714,116 @@ void tst_InsertProxyModel::testSetData() void tst_InsertProxyModel::testSetData_data() { - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::addColumn("idxRow"); QTest::addColumn("idxCol"); QTest::newRow("List Inside Base Model") << createListModel(this) << 0 << 0; - QAbstractItemModel* baseModel = createListModel(this); + QAbstractItemModel *baseModel = createListModel(this); QTest::newRow("List Extra Row") << baseModel << baseModel->rowCount() << 0; baseModel = createListModel(this); QTest::newRow("List Extra Col") << baseModel << 0 << baseModel->columnCount(); baseModel = createListModel(this); QTest::newRow("List Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); -#ifdef QT_GUI_LIB QTest::newRow("Table Inside Base Model") << createTableModel(this) << 0 << 0; baseModel = createTableModel(this); - QTest::newRow("Table Extra Row") << baseModel << baseModel->rowCount() << 0; + if (baseModel) + QTest::newRow("Table Extra Row") << baseModel << baseModel->rowCount() << 0; baseModel = createTableModel(this); - QTest::newRow("Table Extra Col") << baseModel << 0 << baseModel->columnCount(); + if (baseModel) + QTest::newRow("Table Extra Col") << baseModel << 0 << baseModel->columnCount(); baseModel = createTableModel(this); - QTest::newRow("Table Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); + if (baseModel) + QTest::newRow("Table Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); QTest::newRow("Tree Inside Base Model") << createTreeModel(this) << 0 << 0; baseModel = createTreeModel(this); - QTest::newRow("Tree Extra Row") << baseModel << baseModel->rowCount() << 0; + if (baseModel) + QTest::newRow("Tree Extra Row") << baseModel << baseModel->rowCount() << 0; baseModel = createTreeModel(this); - QTest::newRow("Tree Extra Col") << baseModel << 0 << baseModel->columnCount(); + if (baseModel) + QTest::newRow("Tree Extra Col") << baseModel << 0 << baseModel->columnCount(); baseModel = createTreeModel(this); - QTest::newRow("Tree Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); -#endif + if (baseModel) + QTest::newRow("Tree Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); } void tst_InsertProxyModel::testSetItemData_data() { - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::addColumn("idxRow"); QTest::addColumn("idxCol"); - QAbstractItemModel* baseModel = createListModel(this); + QAbstractItemModel *baseModel = createListModel(this); QTest::newRow("List Extra Row") << baseModel << baseModel->rowCount() << 0; baseModel = createListModel(this); QTest::newRow("List Extra Col") << baseModel << 0 << baseModel->columnCount(); baseModel = createListModel(this); QTest::newRow("List Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); -#ifdef QT_GUI_LIB QTest::newRow("Table Inside Base Model") << createTableModel(this) << 0 << 0; baseModel = createTableModel(this); - QTest::newRow("Table Extra Row") << baseModel << baseModel->rowCount() << 0; + if (baseModel) + QTest::newRow("Table Extra Row") << baseModel << baseModel->rowCount() << 0; baseModel = createTableModel(this); - QTest::newRow("Table Extra Col") << baseModel << 0 << baseModel->columnCount(); + if (baseModel) + QTest::newRow("Table Extra Col") << baseModel << 0 << baseModel->columnCount(); baseModel = createTableModel(this); - QTest::newRow("Table Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); + if (baseModel) + QTest::newRow("Table Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); QTest::newRow("Tree Inside Base Model") << createTreeModel(this) << 0 << 0; baseModel = createTreeModel(this); - QTest::newRow("Tree Extra Row") << baseModel << baseModel->rowCount() << 0; + if (baseModel) + QTest::newRow("Tree Extra Row") << baseModel << baseModel->rowCount() << 0; baseModel = createTreeModel(this); - QTest::newRow("Tree Extra Col") << baseModel << 0 << baseModel->columnCount(); + if (baseModel) + QTest::newRow("Tree Extra Col") << baseModel << 0 << baseModel->columnCount(); baseModel = createTreeModel(this); - QTest::newRow("Tree Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); -#endif + if (baseModel) + QTest::newRow("Tree Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); } void tst_InsertProxyModel::testSetItemDataDataChanged_data() { - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::addColumn("idxRow"); QTest::addColumn("idxCol"); - QAbstractItemModel* baseModel = createListModel(this); + QAbstractItemModel *baseModel = createListModel(this); QTest::newRow("List Extra Row") << baseModel << baseModel->rowCount() << 0; baseModel = createListModel(this); QTest::newRow("List Extra Col") << baseModel << 0 << baseModel->columnCount(); baseModel = createListModel(this); QTest::newRow("List Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); -#ifdef QT_GUI_LIB baseModel = createTableModel(this); - QTest::newRow("Table Extra Row") << baseModel << baseModel->rowCount() << 0; + if (baseModel) + QTest::newRow("Table Extra Row") << baseModel << baseModel->rowCount() << 0; baseModel = createTableModel(this); - QTest::newRow("Table Extra Col") << baseModel << 0 << baseModel->columnCount(); + if (baseModel) + QTest::newRow("Table Extra Col") << baseModel << 0 << baseModel->columnCount(); baseModel = createTableModel(this); - QTest::newRow("Table Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); + if (baseModel) + QTest::newRow("Table Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); baseModel = createTreeModel(this); - QTest::newRow("Tree Extra Row") << baseModel << baseModel->rowCount() << 0; + if (baseModel) + QTest::newRow("Tree Extra Row") << baseModel << baseModel->rowCount() << 0; baseModel = createTreeModel(this); - QTest::newRow("Tree Extra Col") << baseModel << 0 << baseModel->columnCount(); + if (baseModel) + QTest::newRow("Tree Extra Col") << baseModel << 0 << baseModel->columnCount(); baseModel = createTreeModel(this); - QTest::newRow("Tree Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); -#endif + if (baseModel) + QTest::newRow("Tree Corner") << baseModel << baseModel->rowCount() << baseModel->columnCount(); } void tst_InsertProxyModel::testSetItemDataDataChanged() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; QFETCH(int, idxRow); QFETCH(int, idxCol); - QMap itemDataSet{ { //TextAlignmentRole - std::make_pair(Qt::UserRole, 5) - , std::make_pair(Qt::DisplayRole, QStringLiteral("Test")) - , std::make_pair(Qt::ToolTipRole, QStringLiteral("ToolTip")) - } }; + QMap itemDataSet{{// TextAlignmentRole + std::make_pair(Qt::UserRole, 5), + std::make_pair(Qt::DisplayRole, QStringLiteral("Test")), + std::make_pair(Qt::ToolTipRole, QStringLiteral("ToolTip"))}}; InsertProxyModel proxyModel; - proxyModel.setSeparateEditDisplay(false); + new ModelTest(&proxyModel, baseModel); + proxyModel.setMergeDisplayEdit(true); proxyModel.setInsertDirection(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow); proxyModel.setSourceModel(baseModel); const QModelIndex proxyIdX = proxyModel.index(idxRow, idxCol); @@ -726,9 +834,12 @@ void tst_InsertProxyModel::testSetItemDataDataChanged() auto argList = proxyDataChangeSpy.takeFirst(); QCOMPARE(argList.at(0).value(), proxyIdX); QCOMPARE(argList.at(1).value(), proxyIdX); - auto rolesVector = argList.at(2).value >(); - QCOMPARE(rolesVector.size(), 5); - QVERIFY(rolesVector.contains(Qt::TextAlignmentRole)); + auto rolesVector = argList.at(2).value>(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + // bug fixed by Qt commit 1382374deaa4a854aeb542e6c8f7e1841f2abb10 + QCOMPARE(rolesVector.size(), 4); + QVERIFY(!rolesVector.contains(Qt::TextAlignmentRole)); +#endif QVERIFY(rolesVector.contains(Qt::ToolTipRole)); QVERIFY(rolesVector.contains(Qt::EditRole)); QVERIFY(rolesVector.contains(Qt::DisplayRole)); @@ -739,36 +850,32 @@ void tst_InsertProxyModel::testSetItemDataDataChanged() argList = proxyDataChangeSpy.takeFirst(); QCOMPARE(argList.at(0).value(), proxyIdX); QCOMPARE(argList.at(1).value(), proxyIdX); - rolesVector = argList.at(2).value >(); + rolesVector = argList.at(2).value>(); QCOMPARE(rolesVector.size(), 1); QVERIFY(rolesVector.contains(Qt::UserRole)); itemDataSet.clear(); itemDataSet[Qt::UserRole] = 6; QVERIFY(proxyModel.setItemData(proxyIdX, itemDataSet)); - QCOMPARE(proxyDataChangeSpy.size(), 1); - argList = proxyDataChangeSpy.takeFirst(); - QCOMPARE(argList.at(0).value(), proxyIdX); - QCOMPARE(argList.at(1).value(), proxyIdX); - rolesVector = argList.at(2).value >(); - QCOMPARE(rolesVector.size(), 3); - QVERIFY(rolesVector.contains(Qt::ToolTipRole)); - QVERIFY(rolesVector.contains(Qt::EditRole)); - QVERIFY(rolesVector.contains(Qt::DisplayRole)); +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + // bug fixed by Qt commit 1382374deaa4a854aeb542e6c8f7e1841f2abb10 + QCOMPARE(proxyDataChangeSpy.size(), 0); +#endif baseModel->deleteLater(); } void tst_InsertProxyModel::testSetItemData() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; QFETCH(int, idxRow); QFETCH(int, idxCol); - const QMap itemDataSet{{ //TextAlignmentRole - std::make_pair(Qt::UserRole, 5) - ,std::make_pair(Qt::DisplayRole, QStringLiteral("Test")) - , std::make_pair(Qt::ToolTipRole, QStringLiteral("ToolTip")) - }}; + const QMap itemDataSet{{std::make_pair(Qt::UserRole, 5), + std::make_pair(Qt::DisplayRole, QStringLiteral("Test")), + std::make_pair(Qt::ToolTipRole, QStringLiteral("ToolTip"))}}; InsertProxyModel proxyModel; - proxyModel.setSeparateEditDisplay(false); + new ModelTest(&proxyModel, baseModel); + proxyModel.setMergeDisplayEdit(true); proxyModel.setInsertDirection(InsertProxyModel::InsertColumn | InsertProxyModel::InsertRow); proxyModel.setSourceModel(baseModel); const QModelIndex proxyIdX = proxyModel.index(idxRow, idxCol); @@ -778,15 +885,22 @@ void tst_InsertProxyModel::testSetItemData() QCOMPARE(proxyModel.data(proxyIdX, Qt::EditRole).toString(), QStringLiteral("Test")); QCOMPARE(proxyModel.data(proxyIdX, Qt::UserRole).toInt(), 5); QCOMPARE(proxyModel.data(proxyIdX, Qt::ToolTipRole).toString(), QStringLiteral("ToolTip")); - QVERIFY(!proxyModel.data(proxyIdX, Qt::TextAlignmentRole).isValid()); +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + // bug fixed by Qt commit 1382374deaa4a854aeb542e6c8f7e1841f2abb10 + QCOMPARE(proxyModel.data(proxyIdX, Qt::TextAlignmentRole).toInt(), Qt::AlignRight); +#endif baseModel->deleteLater(); } void tst_InsertProxyModel::testCommitSubclass() { - class InsertProxyModelCommit : public InsertProxyModel{ + class InsertProxyModelCommit : public InsertProxyModel + { public: - explicit InsertProxyModelCommit(QObject* parent = Q_NULLPTR) : InsertProxyModel(parent) {} + explicit InsertProxyModelCommit(QObject *parent = Q_NULLPTR) + : InsertProxyModel(parent) + { } + protected: bool validRow() const Q_DECL_OVERRIDE { @@ -795,12 +909,12 @@ void tst_InsertProxyModel::testCommitSubclass() const int sourceCols = sourceModel()->columnCount(); const int sourceRows = sourceModel()->rowCount(); for (int i = 0; i < sourceCols; ++i) { - if (index(sourceRows,i).data().isValid()) + if (index(sourceRows, i).data().isValid()) return true; } return false; } - bool validColumn() const Q_DECL_OVERRIDE + bool validColumn() const Q_DECL_OVERRIDE { if (!sourceModel()) return false; @@ -813,10 +927,13 @@ void tst_InsertProxyModel::testCommitSubclass() return false; } }; - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; QFETCH(InsertProxyModel::InsertDirections, insertDirection); QFETCH(bool, canInsertColumns); InsertProxyModelCommit proxy; + new ModelTest(&proxy, baseModel); proxy.setSourceModel(baseModel); proxy.setInsertDirection(insertDirection); const int originalColCount = baseModel->columnCount(); @@ -861,18 +978,18 @@ void tst_InsertProxyModel::testCommitSubclass() QCOMPARE(baseModel->index(0, originalColCount).data().toString(), QStringLiteral("Test")); for (int i = 1; i < originalRowCount; ++i) QCOMPARE(baseModel->index(i, originalColCount).data(), QVariant()); - + for (int i = 0; i < originalRowCount; ++i) - QCOMPARE(proxy.index(i, originalColCount+1).data(), QVariant()); + QCOMPARE(proxy.index(i, originalColCount + 1).data(), QVariant()); } proxyDataChangedSpy.clear(); proxyExtraDataChangedSpy.clear(); baseDataChangedSpy.clear(); if (insertDirection & InsertProxyModel::InsertRow) { for (int i = 0; i < originalColCount; ++i) - QCOMPARE(proxy.index(originalRowCount,i).data(), QVariant()); - QVERIFY(proxy.setData(proxy.index(originalRowCount,0), QStringLiteral("Test"))); - QVERIFY(proxyDataChangedSpy.count() >=3);// change to QCOMPARE(proxyDataChangedSpy.count(),3) once QTBUG-67511 is fixed + QCOMPARE(proxy.index(originalRowCount, i).data(), QVariant()); + QVERIFY(proxy.setData(proxy.index(originalRowCount, 0), QStringLiteral("Test"))); + QVERIFY(proxyDataChangedSpy.count() >= 3); // change to QCOMPARE(proxyDataChangedSpy.count(),3) once QTBUG-67511 is fixed proxyDataChangedSpy.clear(); QCOMPARE(proxyExtraDataChangedSpy.count(), 2); proxyExtraDataChangedSpy.clear(); @@ -880,15 +997,15 @@ void tst_InsertProxyModel::testCommitSubclass() baseRowsInsertedSpy.clear(); QCOMPARE(proxyRowsInsertedSpy.count(), 1); proxyRowsInsertedSpy.clear(); - QVERIFY(baseDataChangedSpy.count() >= 1);// change to QCOMPARE(baseDataChangedSpy.count(),1) once QTBUG-67511 is fixed + QVERIFY(baseDataChangedSpy.count() >= 1); // change to QCOMPARE(baseDataChangedSpy.count(),1) once QTBUG-67511 is fixed baseDataChangedSpy.clear(); QCOMPARE(proxy.rowCount(), baseModel->rowCount() + 1); QCOMPARE(baseModel->rowCount(), originalRowCount + 1); - QCOMPARE(baseModel->index(originalRowCount,0).data().toString(), QStringLiteral("Test")); + QCOMPARE(baseModel->index(originalRowCount, 0).data().toString(), QStringLiteral("Test")); for (int i = 1; i < originalColCount; ++i) - QCOMPARE(baseModel->index(originalRowCount,i).data(), QVariant()); + QCOMPARE(baseModel->index(originalRowCount, i).data(), QVariant()); for (int i = 0; i < originalColCount; ++i) - QCOMPARE(proxy.index(originalRowCount+1,i).data(), QVariant()); + QCOMPARE(proxy.index(originalRowCount + 1, i).data(), QVariant()); } baseModel->deleteLater(); } diff --git a/tests/tst_InsertProxyModel/tst_insertproxymodel.h b/tests/tst_InsertProxyModel/tst_insertproxymodel.h index f13b814..0e225fb 100644 --- a/tests/tst_InsertProxyModel/tst_insertproxymodel.h +++ b/tests/tst_InsertProxyModel/tst_insertproxymodel.h @@ -4,7 +4,8 @@ #include #include class QAbstractItemModel; -class tst_InsertProxyModel : public QObject{ +class tst_InsertProxyModel : public QObject +{ Q_OBJECT private Q_SLOTS: void initTestCase(); @@ -30,5 +31,7 @@ private Q_SLOTS: void testDataForCorner(); void testSort(); void testSort_data(); + void testInsertOnEmptyModel(); + void testResetModel(); }; -#endif // tst_insertproxymodel_h__ \ No newline at end of file +#endif // tst_insertproxymodel_h__ diff --git a/tests/tst_JsonModelSerialiser/CMakeLists.txt b/tests/tst_JsonModelSerialiser/CMakeLists.txt new file mode 100644 index 0000000..d42efc7 --- /dev/null +++ b/tests/tst_JsonModelSerialiser/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.2) +include(TestMacro) +BasicTest(JsonModelSerialiser) +target_sources(tst_JsonModelSerialiser PRIVATE + ../tst_SerialisersCommon/tst_serialiserscommon.h + ../tst_SerialisersCommon/tst_serialiserscommon.cpp +) +target_include_directories(tst_JsonModelSerialiser PRIVATE ../tst_SerialisersCommon/) diff --git a/tests/tst_JsonModelSerialiser/main.cpp b/tests/tst_JsonModelSerialiser/main.cpp new file mode 100644 index 0000000..05274ea --- /dev/null +++ b/tests/tst_JsonModelSerialiser/main.cpp @@ -0,0 +1,3 @@ +#include "tst_jsonmodelserialiser.h" +#include +QTEST_MAIN(tst_JsonModelSerialiser) \ No newline at end of file diff --git a/tests/tst_JsonModelSerialiser/tst_jsonmodelserialiser.cpp b/tests/tst_JsonModelSerialiser/tst_jsonmodelserialiser.cpp new file mode 100644 index 0000000..d382319 --- /dev/null +++ b/tests/tst_JsonModelSerialiser/tst_jsonmodelserialiser.cpp @@ -0,0 +1,70 @@ +#include +#include "tst_jsonmodelserialiser.h" +#include +#include +#include +#include + +void tst_JsonModelSerialiser::basicSaveLoadByteArray() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + JsonModelSerialiser serialiser; + saveLoadByteArray(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_JsonModelSerialiser::basicSaveLoadFile() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + JsonModelSerialiser serialiser; + saveLoadFile(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_JsonModelSerialiser::basicSaveLoadString() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + JsonModelSerialiser serialiser; + saveLoadString(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_JsonModelSerialiser::validateJsonOutput() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QByteArray modelData; + JsonModelSerialiser serialiser(sourceModel); + serialiser.saveModel(&modelData); + QJsonParseError parseErr; + QJsonDocument::fromJson(modelData, &parseErr); + QCOMPARE(parseErr.error, QJsonParseError::NoError); +} + +void tst_JsonModelSerialiser::validateJsonOutput_data() +{ + QTest::addColumn("sourceModel"); + QTest::newRow("List Single Role") << static_cast(createStringModel(this)); +#ifdef QT_GUI_LIB + QTest::newRow("Table Single Role") << static_cast(createComplexModel(false, false, this)); + QTest::newRow("Table Multi Roles") << static_cast(createComplexModel(false, true, this)); + QTest::newRow("Tree Single Role") << static_cast(createComplexModel(true, false, this)); + QTest::newRow("Tree Multi Roles") << static_cast(createComplexModel(true, true, this)); +#endif +} + +void tst_JsonModelSerialiser::basicSaveLoadObject() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + JsonModelSerialiser serialiser(sourceModel); + serialiser.addRoleToSave(Qt::UserRole + 1); + QJsonObject writeObject = serialiser.toJsonObject(); + QVERIFY(!writeObject.isEmpty()); + serialiser.setModel(destinationModel); + QVERIFY(serialiser.fromJsonObject(writeObject)); + checkModelEqual(sourceModel, destinationModel); + destinationModel->deleteLater(); +} diff --git a/tests/tst_JsonModelSerialiser/tst_jsonmodelserialiser.h b/tests/tst_JsonModelSerialiser/tst_jsonmodelserialiser.h new file mode 100644 index 0000000..a4f1e4e --- /dev/null +++ b/tests/tst_JsonModelSerialiser/tst_jsonmodelserialiser.h @@ -0,0 +1,21 @@ +#ifndef tst_jsonmodelserialiser_h__ +#define tst_jsonmodelserialiser_h__ +#include +#include +class QAbstractItemModel; +class tst_JsonModelSerialiser : public QObject, public tst_SerialiserCommon +{ + Q_OBJECT +private Q_SLOTS: + void basicSaveLoadByteArray(); + void basicSaveLoadFile(); + void basicSaveLoadObject(); + void basicSaveLoadString(); + void basicSaveLoadByteArray_data() { basicSaveLoadData(this); } + void basicSaveLoadFile_data() { basicSaveLoadData(this); } + void basicSaveLoadObject_data() { basicSaveLoadData(this); } + void basicSaveLoadString_data() { basicSaveLoadData(this); } + void validateJsonOutput(); + void validateJsonOutput_data(); +}; +#endif diff --git a/tests/tst_RoleMaskProxyModel/CMakeLists.txt b/tests/tst_RoleMaskProxyModel/CMakeLists.txt index 94242f7..335c202 100644 --- a/tests/tst_RoleMaskProxyModel/CMakeLists.txt +++ b/tests/tst_RoleMaskProxyModel/CMakeLists.txt @@ -1,63 +1,3 @@ -cmake_minimum_required(VERSION 3.3) -if(POLICY CMP0025) - cmake_policy(SET CMP0025 NEW) -endif() -if(POLICY CMP0048) - cmake_policy(SET CMP0048 NEW) -endif() -if(POLICY CMP0057) - cmake_policy(SET CMP0057 NEW) -endif() -set (CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_AUTOMOC ON) -project(tst_RoleMaskProxyModel VERSION "1.0") -set(REQUIRED_QT_VERSION 5.0.0) -find_package(Qt5Test ${REQUIRED_QT_VERSION} REQUIRED) -find_package(Qt5Gui ${REQUIRED_QT_VERSION}) -if(NOT Qt5Gui_FOUND) - set(NO_GUI ON) -else() - message(STATUS "Found Qt Gui ${Qt5Gui_VERSION}") -endif() -message(STATUS "Found Qt Test ${Qt5Test_VERSION}") -set(tst_RoleMaskProxyModel_LIBS ${tst_RoleMaskProxyModel_LIBS} ${Qt5Test_LIBRARIES}) -set(tst_RoleMaskProxyModel_INCLUDE ${tst_RoleMaskProxyModel_INCLUDE} ${Qt5Test_INCLUDE_DIRS}) -set(tst_RoleMaskProxyModel_COMPILE_DEFINE ${tst_RoleMaskProxyModel_COMPILE_DEFINE} ${Qt5Test_COMPILE_DEFINITIONS} ) -if(NOT NO_GUI) -set(tst_RoleMaskProxyModel_LIBS ${tst_RoleMaskProxyModel_LIBS} ${Qt5Gui_LIBRARIES}) -set(tst_RoleMaskProxyModel_INCLUDE ${tst_RoleMaskProxyModel_INCLUDE} ${Qt5Gui_INCLUDE_DIRS}) -set(tst_RoleMaskProxyModel_COMPILE_DEFINE ${tst_RoleMaskProxyModel_COMPILE_DEFINE} ${Qt5Gui_COMPILE_DEFINITIONS}) -endif() -set(tst_RoleMaskProxyModel_INCLUDE ${tst_RoleMaskProxyModel_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}) -set(tst_RoleMaskProxyModel_SRCS - main.cpp - tst_rolemaskproxymodel.cpp -) -add_executable(tst_RoleMaskProxyModel ${tst_RoleMaskProxyModel_SRCS}) -if(BUILD_STATIC_LIBS) - set(tst_RoleMaskProxyModel_DEFINE ${tst_RoleMaskProxyModel_DEFINE} MODELUTILITIES_STATIC) -endif() -add_dependencies(tst_RoleMaskProxyModel modelutilities) -target_include_directories(tst_RoleMaskProxyModel PRIVATE ../../src ${tst_RoleMaskProxyModel_INCLUDE}) -target_link_libraries(tst_RoleMaskProxyModel PRIVATE modelutilities ${tst_RoleMaskProxyModel_LIBS}) -target_compile_definitions(tst_RoleMaskProxyModel PRIVATE ${tst_RoleMaskProxyModel_DEFINE}) -set_target_properties(tst_RoleMaskProxyModel PROPERTIES - VERSION "1.0" - SOVERSION 1 - EXPORT_NAME "tstRoleMaskProxyModel" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/tests" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/lib/tests" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${modelutilities_PlatformDir}/bin/tests" -) -if(TEST_OUTPUT_XML) - set(Test_Output_FileName "tst_RoleMaskProxyModel") - if(CMAKE_BUILD_TYPE MATCHES DEBUG) - set(Test_Output_FileName "${Test_Output_FileName}_debug") - endif() - set(Test_Output_FileName "${Test_Output_FileName}_tstres.xml") - add_test(NAME tstRoleMaskProxyModel WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/TestResults" COMMAND $ -o ${Test_Output_FileName},xml) -else() - add_test(NAME tstRoleMaskProxyModel WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" COMMAND $) -endif() +cmake_minimum_required(VERSION 3.2) +include(TestMacro) +BasicTest(RoleMaskProxyModel) diff --git a/tests/tst_RoleMaskProxyModel/tst_rolemaskproxymodel.cpp b/tests/tst_RoleMaskProxyModel/tst_rolemaskproxymodel.cpp index aec43d8..0403625 100644 --- a/tests/tst_RoleMaskProxyModel/tst_rolemaskproxymodel.cpp +++ b/tests/tst_RoleMaskProxyModel/tst_rolemaskproxymodel.cpp @@ -2,91 +2,125 @@ #include #include #ifdef QT_GUI_LIB -#include +# include #endif +#include #include #include #include +#include #include "tst_rolemaskproxymodel.h" -void tst_RoleMaskProxyModel::initTestCase() +#include <../modeltestmanager.h> + +QAbstractItemModel *createNullModel(QObject *parent) +{ + Q_UNUSED(parent) + return nullptr; +} +QAbstractItemModel *createListModel(QObject *parent) { - qRegisterMetaType >(); - m_models.append(new QStringListModel(QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3") << QStringLiteral("4") << QStringLiteral("5"),this)); + return new QStringListModel( + QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3") << QStringLiteral("4") << QStringLiteral("5"), parent); +} + +QAbstractItemModel *createTableModel(QObject *parent) +{ + QAbstractItemModel *result = nullptr; #ifdef QT_GUI_LIB - m_models.append(new QStandardItemModel(this)); - m_models.last()->insertRows(0, 5); - m_models.last()->insertColumns(0, 3); - for (int i = 0; i < m_models.last()->rowCount(); ++i) { - for (int j = 0; j < m_models.last()->columnCount(); ++j) { - m_models.last()->setData(m_models.last()->index(i, j), QStringLiteral("%1,%2").arg(i).arg(j), Qt::EditRole); - m_models.last()->setData(m_models.last()->index(i, j), i, Qt::UserRole); - m_models.last()->setData(m_models.last()->index(i, j), j, Qt::UserRole + 1); + result = new QStandardItemModel(parent); + result->insertRows(0, 5); + result->insertColumns(0, 3); + for (int i = 0; i < result->rowCount(); ++i) { + for (int j = 0; j < result->columnCount(); ++j) { + result->setData(result->index(i, j), QStringLiteral("%1,%2").arg(i).arg(j), Qt::EditRole); + result->setData(result->index(i, j), i, Qt::UserRole); + result->setData(result->index(i, j), j, Qt::UserRole + 1); } } +#endif + return result; +} - m_models.append(new QStandardItemModel(this)); - m_models.last()->insertRows(0, 5); - m_models.last()->insertColumns(0, 3); - for (int i = 0; i < m_models.last()->rowCount(); ++i) { - for (int j = 0; j < m_models.last()->columnCount(); ++j) { - m_models.last()->setData(m_models.last()->index(i, j), QStringLiteral("%1,%2").arg(i).arg(j), Qt::EditRole); - m_models.last()->setData(m_models.last()->index(i, j), i, Qt::UserRole); - m_models.last()->setData(m_models.last()->index(i, j), j, Qt::UserRole + 1); +QAbstractItemModel *createTreeModel(QObject *parent) +{ + QAbstractItemModel *result = nullptr; +#ifdef QT_GUI_LIB + result = new QStandardItemModel(parent); + result->insertRows(0, 5); + result->insertColumns(0, 3); + for (int i = 0; i < result->rowCount(); ++i) { + for (int j = 0; j < result->columnCount(); ++j) { + result->setData(result->index(i, j), QStringLiteral("%1,%2").arg(i).arg(j), Qt::EditRole); + result->setData(result->index(i, j), i, Qt::UserRole); + result->setData(result->index(i, j), j, Qt::UserRole + 1); } - const QModelIndex parIdx = m_models.last()->index(i, 0); - m_models.last()->insertRows(0, 5, parIdx); - m_models.last()->insertColumns(0, 3, parIdx); - for (int k = 0; k < m_models.last()->rowCount(parIdx); ++k) { - for (int h = 0; h < m_models.last()->columnCount(parIdx); ++h) { - m_models.last()->setData(m_models.last()->index(k, h, parIdx), QStringLiteral("%1,%2,%3").arg(i).arg(k).arg(h), Qt::EditRole); - m_models.last()->setData(m_models.last()->index(k, h, parIdx), h, Qt::UserRole); - m_models.last()->setData(m_models.last()->index(k, h, parIdx), k, Qt::UserRole + 1); + const QModelIndex parIdx = result->index(i, 0); + result->insertRows(0, 5, parIdx); + result->insertColumns(0, 3, parIdx); + for (int k = 0; k < result->rowCount(parIdx); ++k) { + for (int h = 0; h < result->columnCount(parIdx); ++h) { + result->setData(result->index(k, h, parIdx), QStringLiteral("%1,%2,%3").arg(i).arg(k).arg(h), Qt::EditRole); + result->setData(result->index(k, h, parIdx), h, Qt::UserRole); + result->setData(result->index(k, h, parIdx), k, Qt::UserRole + 1); } } } -#endif // QT_GUI_LIB +#endif + return result; } -void tst_RoleMaskProxyModel::cleanupTestCase() +void tst_RoleMaskProxyModel::initTestCase() { - while (!m_models.isEmpty()) - m_models.takeLast()->deleteLater(); + qRegisterMetaType>(); } void tst_RoleMaskProxyModel::testUseRoleMask() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; QFETCH(QModelIndexList, magicNumerIndexes); QFETCH(bool, userRoleEditable); RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, baseModel); proxyModel.addMaskedRole(Qt::UserRole); proxyModel.setSourceModel(baseModel); const int magicNumber = 785874; - for(const QModelIndex& singleIdx : magicNumerIndexes){ + for (const QModelIndex &singleIdx : magicNumerIndexes) { QVERIFY(proxyModel.setData(proxyModel.mapFromSource(singleIdx), magicNumber, Qt::UserRole)); QVERIFY(userRoleEditable == baseModel->setData(singleIdx, ~magicNumber, Qt::UserRole)); } testUseRoleMaskRecurse(magicNumber, baseModel, &proxyModel, magicNumerIndexes, userRoleEditable); + baseModel->deleteLater(); } void tst_RoleMaskProxyModel::testUseRoleMask_data() { - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::addColumn("magicNumerIndexes"); QTest::addColumn("userRoleEditable"); - QTest::newRow("QStringListModel") << m_models.at(0) << QModelIndexList({ m_models.at(0)->index(0, 0) }) << false; -#ifdef QT_GUI_LIB - QTest::newRow("QStandadItemModel Table") << m_models.at(1) << QModelIndexList({ m_models.at(1)->index(1, 0), m_models.at(1)->index(0, 1) }) << true; - QTest::newRow("QStandadItemModel Tree") << m_models.at(2) << QModelIndexList({ m_models.at(2)->index(1, 0), m_models.at(2)->index(0, 1), m_models.at(2)->index(0, 1, m_models.at(2)->index(0, 0)) }) << true; -#endif + QAbstractItemModel *baseModel = createListModel(this); + QTest::newRow("QStringListModel") << baseModel << QModelIndexList({baseModel->index(0, 0)}) << false; + baseModel = createTableModel(this); + if (baseModel) + QTest::newRow("QStandadItemModel Table") << baseModel << QModelIndexList({baseModel->index(1, 0), baseModel->index(0, 1)}) << true; + baseModel = createTreeModel(this); + if (baseModel) + QTest::newRow("QStandadItemModel Tree") << baseModel + << QModelIndexList({baseModel->index(1, 0), baseModel->index(0, 1), + baseModel->index(0, 1, baseModel->index(0, 0))}) + << true; } void tst_RoleMaskProxyModel::testInsertRow() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; QFETCH(int, insertIndex); QFETCH(QModelIndex, parentIndex); RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, baseModel); proxyModel.addMaskedRole(Qt::UserRole); proxyModel.setSourceModel(baseModel); const int magicNumber = 785874; @@ -98,44 +132,51 @@ void tst_RoleMaskProxyModel::testInsertRow() for (int i = 0; i < baseModel->rowCount(parentIndex); ++i) { if (i == insertIndex) QVERIFY(!proxyModel.index(i, 0, proxyParent).data(Qt::UserRole).isValid()); - else if (i>insertIndex) - QCOMPARE(proxyModel.index(i, 0, proxyParent).data(Qt::UserRole).toInt(), magicNumber + i-1); + else if (i > insertIndex) + QCOMPARE(proxyModel.index(i, 0, proxyParent).data(Qt::UserRole).toInt(), magicNumber + i - 1); else QCOMPARE(proxyModel.index(i, 0, proxyParent).data(Qt::UserRole).toInt(), magicNumber + i); } QVERIFY(baseModel->removeRow(insertIndex, parentIndex)); + baseModel->deleteLater(); } void tst_RoleMaskProxyModel::testInsertRow_data() { - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::addColumn("insertIndex"); QTest::addColumn("parentIndex"); - QTest::newRow("List Insert Begin") << m_models.at(0) << 0 << QModelIndex(); -#ifdef QT_GUI_LIB - QTest::newRow("Table Insert Begin") << m_models.at(1) << 0 << QModelIndex(); - QTest::newRow("Tree Insert Begin") << m_models.at(2) << 0 << QModelIndex(); -#endif - QTest::newRow("List Insert End") << m_models.at(0) << m_models.at(0)->rowCount() << QModelIndex(); -#ifdef QT_GUI_LIB - QTest::newRow("Table Insert End") << m_models.at(1) << m_models.at(1)->rowCount() << QModelIndex(); - QTest::newRow("Tree Insert End") << m_models.at(2) << m_models.at(2)->rowCount() << QModelIndex(); -#endif - QTest::newRow("List Insert Middle") << m_models.at(0) << 2 << QModelIndex(); -#ifdef QT_GUI_LIB - QTest::newRow("Table Insert Middle") << m_models.at(1) << 2 << QModelIndex(); - QTest::newRow("Tree Insert Middle") << m_models.at(2) << 2 << QModelIndex(); + QTest::newRow("List Insert Begin") << createListModel(this) << 0 << QModelIndex(); + QTest::newRow("Table Insert Begin") << createTableModel(this) << 0 << QModelIndex(); + QTest::newRow("Tree Insert Begin") << createTreeModel(this) << 0 << QModelIndex(); + QAbstractItemModel *baseModel = createListModel(this); + QTest::newRow("List Insert End") << baseModel << baseModel->rowCount() << QModelIndex(); + baseModel = createTableModel(this); + if (baseModel) + QTest::newRow("Table Insert End") << baseModel << baseModel->rowCount() << QModelIndex(); + baseModel = createTreeModel(this); + if (baseModel) + QTest::newRow("Tree Insert End") << baseModel << baseModel->rowCount() << QModelIndex(); + QTest::newRow("List Insert Middle") << createListModel(this) << 2 << QModelIndex(); + QTest::newRow("Table Insert Middle") << createTableModel(this) << 2 << QModelIndex(); + QTest::newRow("Tree Insert Middle") << createTreeModel(this) << 2 << QModelIndex(); - QTest::newRow("Tree Insert Begin Child") << m_models.at(2) << 0 << m_models.at(2)->index(0, 0); - QTest::newRow("Tree Insert Middle Child") << m_models.at(2) << 2 << m_models.at(2)->index(0, 0); - QTest::newRow("Tree Insert End Child") << m_models.at(2) << m_models.at(2)->rowCount(m_models.at(2)->index(0, 0)) << m_models.at(2)->index(0, 0); -#endif + baseModel = createTreeModel(this); + if (baseModel) + QTest::newRow("Tree Insert Begin Child") << baseModel << 0 << baseModel->index(0, 0); + baseModel = createTreeModel(this); + if (baseModel) + QTest::newRow("Tree Insert Middle Child") << baseModel << 2 << baseModel->index(0, 0); + baseModel = createTreeModel(this); + if (baseModel) + QTest::newRow("Tree Insert End Child") << baseModel << baseModel->rowCount(baseModel->index(0, 0)) << baseModel->index(0, 0); } void tst_RoleMaskProxyModel::testProperties() { RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, this); QVERIFY(proxyModel.setProperty("maskedRoles", QVariant::fromValue(QList({Qt::UserRole, Qt::DisplayRole})))); - const auto roleList = proxyModel.property("maskedRoles").value >(); + const auto roleList = proxyModel.property("maskedRoles").value>(); QVERIFY(roleList.contains(Qt::UserRole)); QVERIFY(roleList.contains(Qt::DisplayRole)); QVERIFY(proxyModel.setProperty("transparentIfEmpty", false)); @@ -145,13 +186,14 @@ void tst_RoleMaskProxyModel::testProperties() } void tst_RoleMaskProxyModel::testInsertColumn() { -#ifndef QT_GUI_LIB - QSKIP("This test requires the Qt GUI module"); -#else - QFETCH(QAbstractItemModel*, baseModel); +#if defined(QT_GUI_LIB) + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; QFETCH(int, insertIndex); QFETCH(QModelIndex, parentIndex); RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, baseModel); proxyModel.addMaskedRole(Qt::UserRole); proxyModel.setSourceModel(baseModel); const int magicNumber = 785874; @@ -169,33 +211,46 @@ void tst_RoleMaskProxyModel::testInsertColumn() QCOMPARE(proxyModel.index(0, i, proxyParent).data(Qt::UserRole).toInt(), magicNumber + i); } QVERIFY(baseModel->removeColumn(insertIndex, parentIndex)); + baseModel->deleteLater(); +#else + QSKIP("This test requires the Qt GUI module"); #endif } void tst_RoleMaskProxyModel::testInsertColumn_data() { -#ifdef QT_GUI_LIB - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::addColumn("insertIndex"); QTest::addColumn("parentIndex"); - QTest::newRow("Table Insert Begin") << m_models.at(1) << 0 << QModelIndex(); - QTest::newRow("Tree Insert Begin") << m_models.at(2) << 0 << QModelIndex(); - QTest::newRow("Table Insert End") << m_models.at(1) << m_models.at(1)->columnCount() << QModelIndex(); - QTest::newRow("Tree Insert End") << m_models.at(2) << m_models.at(2)->columnCount() << QModelIndex(); + QTest::newRow("Table Insert Begin") << createTableModel(this) << 0 << QModelIndex(); + QTest::newRow("Tree Insert Begin") << createTreeModel(this) << 0 << QModelIndex(); - QTest::newRow("Table Insert Middle") << m_models.at(1) << 2 << QModelIndex(); - QTest::newRow("Tree Insert Middle") << m_models.at(2) << 2 << QModelIndex(); + QAbstractItemModel *baseModel = createTableModel(this); + if (baseModel) + QTest::newRow("Table Insert End") << baseModel << baseModel->columnCount() << QModelIndex(); + baseModel = createTreeModel(this); + if (baseModel) + QTest::newRow("Tree Insert End") << baseModel << baseModel->columnCount() << QModelIndex(); - QTest::newRow("Tree Insert Begin Child") << m_models.at(2) << 0 << m_models.at(2)->index(0, 0); - QTest::newRow("Tree Insert Middle Child") << m_models.at(2) << 2 << m_models.at(2)->index(0, 0); - QTest::newRow("Tree Insert End Child") << m_models.at(2) << m_models.at(2)->columnCount(m_models.at(2)->index(0, 0)) << m_models.at(2)->index(0, 0); -#endif + QTest::newRow("Table Insert Middle") << createTableModel(this) << 2 << QModelIndex(); + QTest::newRow("Tree Insert Middle") << createTreeModel(this) << 2 << QModelIndex(); + + baseModel = createTreeModel(this); + if (baseModel) + QTest::newRow("Tree Insert Begin Child") << baseModel << 0 << baseModel->index(0, 0); + baseModel = createTreeModel(this); + if (baseModel) + QTest::newRow("Tree Insert Middle Child") << baseModel << 2 << baseModel->index(0, 0); + baseModel = createTreeModel(this); + if (baseModel) + QTest::newRow("Tree Insert End Child") << baseModel << baseModel->columnCount(baseModel->index(0, 0)) << baseModel->index(0, 0); } void tst_RoleMaskProxyModel::testNullModel() { RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, this); proxyModel.addMaskedRole(Qt::UserRole); proxyModel.setSourceModel(nullptr); QVERIFY(!proxyModel.setData(proxyModel.index(0, 0), 1, Qt::UserRole)); @@ -212,46 +267,47 @@ void tst_RoleMaskProxyModel::testNullModel() void tst_RoleMaskProxyModel::testDataChangeSignals_data() { - QTest::addColumn("baseModel"); + QTest::addColumn("baseModel"); QTest::addColumn("implementsRoles"); - QTest::newRow("List") << m_models.at(0) << bool(QT_VERSION >= QT_VERSION_CHECK(5, 0, 2)); -#ifdef QT_GUI_LIB - QTest::newRow("Table") << m_models.at(1) << bool(QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)); - QTest::newRow("Tree") << m_models.at(2) << bool(QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)); -#endif + QTest::newRow("List") << createListModel(this) << bool(QT_VERSION >= QT_VERSION_CHECK(5, 0, 2)); + QTest::newRow("Table") << createTableModel(this) << bool(QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)); + QTest::newRow("Tree") << createTreeModel(this) << bool(QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)); } void tst_RoleMaskProxyModel::testDataChangeSignals() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; QFETCH(bool, implementsRoles); RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, baseModel); proxyModel.setTransparentIfEmpty(true); proxyModel.setMergeDisplayEdit(true); proxyModel.addMaskedRole(Qt::UserRole); proxyModel.setSourceModel(baseModel); - QSignalSpy baseDataChangeSpy(baseModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector))); + QSignalSpy baseDataChangeSpy(baseModel, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector))); QVERIFY(baseDataChangeSpy.isValid()); - QSignalSpy proxyDataChangeSpy(&proxyModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector))); + QSignalSpy proxyDataChangeSpy(&proxyModel, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector))); QVERIFY(baseDataChangeSpy.isValid()); int timesFired = 0; - connect(&proxyModel, &QAbstractItemModel::dataChanged, [×Fired](const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles)->void { - Q_UNUSED(topLeft) - Q_UNUSED(bottomRight) - Q_UNUSED(roles) - ++timesFired; - }); + connect(&proxyModel, &QAbstractItemModel::dataChanged, + [×Fired](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) -> void { + Q_UNUSED(topLeft) + Q_UNUSED(bottomRight) + Q_UNUSED(roles) + ++timesFired; + }); const QModelIndex proxyDataIdx = proxyModel.index(0, 0); const QModelIndex baseDataIdx = baseModel->index(0, 0); QVERIFY(proxyModel.setData(proxyDataIdx, 1, Qt::UserRole)); QCOMPARE(baseDataChangeSpy.count(), 0); QCOMPARE(proxyDataChangeSpy.count(), 1); - QList arguments = proxyDataChangeSpy.value(0); - proxyDataChangeSpy.clear(); + QList arguments = proxyDataChangeSpy.takeFirst(); QCOMPARE(arguments.at(0).value(), proxyDataIdx); QCOMPARE(arguments.at(1).value(), proxyDataIdx); - QVector rolesVector = arguments.at(2).value >(); + QVector rolesVector = arguments.at(2).value>(); QCOMPARE(rolesVector.size(), 1); QCOMPARE(rolesVector.first(), int(Qt::UserRole)); if (baseModel->setData(baseDataIdx, 1, Qt::UserRole)) { @@ -273,14 +329,14 @@ void tst_RoleMaskProxyModel::testDataChangeSignals() proxyDataChangeSpy.clear(); QCOMPARE(arguments.at(0).value(), proxyDataIdx); QCOMPARE(arguments.at(1).value(), proxyDataIdx); - rolesVector = arguments.at(2).value >(); + rolesVector = arguments.at(2).value>(); QCOMPARE(rolesVector.size(), 2); QVERIFY(rolesVector.contains(Qt::DisplayRole)); QVERIFY(rolesVector.contains(Qt::EditRole)); - if (baseModel->setData(baseDataIdx, 6, Qt::EditRole)){ + if (baseModel->setData(baseDataIdx, 6, Qt::EditRole)) { QCOMPARE(baseDataChangeSpy.count(), 1); baseDataChangeSpy.clear(); - QCOMPARE(proxyDataChangeSpy.count(), implementsRoles ? 0:1); + QCOMPARE(proxyDataChangeSpy.count(), implementsRoles ? 0 : 1); proxyDataChangeSpy.clear(); } proxyModel.setTransparentIfEmpty(false); @@ -294,11 +350,11 @@ void tst_RoleMaskProxyModel::testDataChangeSignals() proxyDataChangeSpy.clear(); QCOMPARE(arguments.at(0).value(), proxyDataIdx); QCOMPARE(arguments.at(1).value(), proxyDataIdx); - rolesVector = arguments.at(2).value >(); + rolesVector = arguments.at(2).value>(); QCOMPARE(rolesVector.size(), 2); QVERIFY(rolesVector.contains(Qt::DisplayRole)); QVERIFY(rolesVector.contains(Qt::EditRole)); - if (baseModel->setData(baseDataIdx, 86, Qt::EditRole)){ + if (baseModel->setData(baseDataIdx, 86, Qt::EditRole)) { QCOMPARE(baseDataChangeSpy.count(), 1); baseDataChangeSpy.clear(); QCOMPARE(proxyDataChangeSpy.count(), implementsRoles ? 0 : 1); @@ -321,7 +377,7 @@ void tst_RoleMaskProxyModel::testDataChangeSignals() proxyDataChangeSpy.clear(); QCOMPARE(arguments.at(0).value(), proxyDataIdx); QCOMPARE(arguments.at(1).value(), proxyDataIdx); - rolesVector = arguments.at(2).value >(); + rolesVector = arguments.at(2).value>(); QCOMPARE(rolesVector.size(), 1); QVERIFY(rolesVector.contains(Qt::EditRole)); proxyDataChangeSpy.clear(); @@ -334,23 +390,27 @@ void tst_RoleMaskProxyModel::testDataChangeSignals() if (baseModel->setData(baseDataIdx, 147, Qt::EditRole)) { QCOMPARE(baseDataChangeSpy.count(), 1); baseDataChangeSpy.clear(); - if (proxyDataChangeSpy.count()>0){ + if (proxyDataChangeSpy.count() > 0) { arguments = proxyDataChangeSpy.value(0); QCOMPARE(arguments.at(0).value(), proxyModel.mapFromSource(baseDataIdx)); QCOMPARE(arguments.at(1).value(), proxyModel.mapFromSource(baseDataIdx)); - rolesVector = arguments.at(2).value >(); + rolesVector = arguments.at(2).value>(); QVERIFY(rolesVector.size() < 2); if (rolesVector.size()) QCOMPARE(rolesVector.first(), int(Qt::DisplayRole)); } proxyDataChangeSpy.clear(); } + baseModel->deleteLater(); } void tst_RoleMaskProxyModel::testTransparentIfEmpty() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, baseModel); proxyModel.setMergeDisplayEdit(true); proxyModel.setTransparentIfEmpty(true); proxyModel.addMaskedRole(Qt::DisplayRole); @@ -365,9 +425,12 @@ void tst_RoleMaskProxyModel::testTransparentIfEmpty() testTransparentIfEmptyRecurse(baseModel, &proxyModel, proxyActionIndex, 777888999, true); proxyModel.setTransparentIfEmpty(false); QCOMPARE(proxyTransparentChangeSpy.count(), 1); + baseModel->deleteLater(); } -void tst_RoleMaskProxyModel::testTransparentIfEmptyRecurse(const QAbstractItemModel* const baseModel, const RoleMaskProxyModel* const proxyModel, const QModelIndex& maskedIdx, const QVariant& maskedVal, bool nonMaskedIsNull, const QModelIndex& sourceParent) +void tst_RoleMaskProxyModel::testTransparentIfEmptyRecurse(const QAbstractItemModel *const baseModel, const RoleMaskProxyModel *const proxyModel, + const QModelIndex &maskedIdx, const QVariant &maskedVal, bool nonMaskedIsNull, + const QModelIndex &sourceParent) { for (int i = 0; i < baseModel->rowCount(sourceParent); ++i) { for (int j = 0; j < baseModel->columnCount(sourceParent); ++j) { @@ -375,16 +438,14 @@ void tst_RoleMaskProxyModel::testTransparentIfEmptyRecurse(const QAbstractItemMo const QModelIndex currProxIdx = proxyModel->mapFromSource(currBsaIdx); if (currProxIdx == maskedIdx) { QCOMPARE(currProxIdx.data(), maskedVal); - QVERIFY(currBsaIdx.data()!= maskedVal); - } - else if(nonMaskedIsNull){ + QVERIFY(currBsaIdx.data() != maskedVal); + } else if (nonMaskedIsNull) { QVERIFY(!currProxIdx.data().isValid()); QVERIFY(currBsaIdx.data().isValid()); - } - else{ + } else { QCOMPARE(currProxIdx.data(), currBsaIdx.data()); } - if (baseModel->hasChildren(currBsaIdx)){ + if (baseModel->hasChildren(currBsaIdx)) { testTransparentIfEmptyRecurse(baseModel, proxyModel, maskedIdx, maskedVal, nonMaskedIsNull, currBsaIdx); } } @@ -393,18 +454,19 @@ void tst_RoleMaskProxyModel::testTransparentIfEmptyRecurse(const QAbstractItemMo void tst_RoleMaskProxyModel::testTransparentIfEmpty_data() { - QTest::addColumn("baseModel"); - QTest::newRow("List") << m_models.at(0); -#ifdef QT_GUI_LIB - QTest::newRow("Table") << m_models.at(1); - QTest::newRow("Tree") << m_models.at(2); -#endif + QTest::addColumn("baseModel"); + QTest::newRow("List") << createListModel(this); + QTest::newRow("Table") << createTableModel(this); + QTest::newRow("Tree") << createTreeModel(this); } void tst_RoleMaskProxyModel::testMergeDisplayEdit() { - QFETCH(QAbstractItemModel*, baseModel); + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, baseModel); proxyModel.setMergeDisplayEdit(true); proxyModel.setTransparentIfEmpty(false); proxyModel.addMaskedRole(Qt::DisplayRole); @@ -424,25 +486,25 @@ void tst_RoleMaskProxyModel::testMergeDisplayEdit() QCOMPARE(proxyActionIndex.data(Qt::EditRole), QVariant()); proxyModel.setMergeDisplayEdit(false); QCOMPARE(proxyMergeDisplayEditChangeSpy.count(), 1); + baseModel->deleteLater(); } void tst_RoleMaskProxyModel::testMergeDisplayEdit_data() { - QTest::addColumn("baseModel"); - QTest::newRow("List") << m_models.at(0); -#ifdef QT_GUI_LIB - QTest::newRow("Table") << m_models.at(1); - QTest::newRow("Tree") << m_models.at(2); -#endif + QTest::addColumn("baseModel"); + QTest::newRow("List") << createListModel(this); + QTest::newRow("Table") << createTableModel(this); + QTest::newRow("Tree") << createTreeModel(this); } void tst_RoleMaskProxyModel::testManageMaskedRoles() { RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, this); proxyModel.setMergeDisplayEdit(true); QVERIFY(proxyModel.maskedRoles().isEmpty()); proxyModel.addMaskedRole(Qt::EditRole); - QCOMPARE(proxyModel.maskedRoles().size(),1); + QCOMPARE(proxyModel.maskedRoles().size(), 1); QCOMPARE(proxyModel.maskedRoles().value(0), int(Qt::DisplayRole)); proxyModel.addMaskedRole(Qt::DisplayRole); QCOMPARE(proxyModel.maskedRoles().size(), 1); @@ -457,7 +519,7 @@ void tst_RoleMaskProxyModel::testManageMaskedRoles() QVERIFY(proxyModel.maskedRoles().contains(Qt::UserRole)); proxyModel.clearMaskedRoles(); QCOMPARE(proxyModel.maskedRoles().size(), 0); - proxyModel.setMaskedRoles({ Qt::UserRole, Qt::EditRole }); + proxyModel.setMaskedRoles({Qt::UserRole, Qt::EditRole}); QCOMPARE(proxyModel.maskedRoles().size(), 2); QVERIFY(proxyModel.maskedRoles().contains(Qt::DisplayRole)); QVERIFY(proxyModel.maskedRoles().contains(Qt::UserRole)); @@ -466,7 +528,7 @@ void tst_RoleMaskProxyModel::testManageMaskedRoles() QCOMPARE(proxyModel.maskedRoles().value(0), int(Qt::DisplayRole)); proxyModel.removeMaskedRole(Qt::EditRole); QVERIFY(proxyModel.maskedRoles().isEmpty()); - proxyModel.setMaskedRoles({ Qt::DisplayRole, Qt::EditRole }); + proxyModel.setMaskedRoles({Qt::DisplayRole, Qt::EditRole}); QCOMPARE(proxyModel.maskedRoles().size(), 1); QCOMPARE(proxyModel.maskedRoles().value(0), int(Qt::DisplayRole)); proxyModel.removeMaskedRole(Qt::DisplayRole); @@ -492,7 +554,7 @@ void tst_RoleMaskProxyModel::testManageMaskedRoles() QVERIFY(proxyModel.maskedRoles().contains(Qt::UserRole)); proxyModel.clearMaskedRoles(); QCOMPARE(proxyModel.maskedRoles().size(), 0); - proxyModel.setMaskedRoles({ Qt::UserRole, Qt::EditRole, Qt::DisplayRole }); + proxyModel.setMaskedRoles({Qt::UserRole, Qt::EditRole, Qt::DisplayRole}); QCOMPARE(proxyModel.maskedRoles().size(), 3); QVERIFY(proxyModel.maskedRoles().contains(Qt::DisplayRole)); QVERIFY(proxyModel.maskedRoles().contains(Qt::UserRole)); @@ -510,13 +572,14 @@ void tst_RoleMaskProxyModel::testManageMaskedRoles() void tst_RoleMaskProxyModel::testDisconnectedModel() { - QStringListModel baseModel1(QStringList({ QStringLiteral("London"), QStringLiteral("Berlin"), QStringLiteral("Paris") })); - QStringListModel baseModel2(QStringList({ QStringLiteral("Rome"), QStringLiteral("Madrid"), QStringLiteral("Prague") })); + QStringListModel baseModel1(QStringList({QStringLiteral("London"), QStringLiteral("Berlin"), QStringLiteral("Paris")})); + QStringListModel baseModel2(QStringList({QStringLiteral("Rome"), QStringLiteral("Madrid"), QStringLiteral("Prague")})); RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, this); proxyModel.setMergeDisplayEdit(true); proxyModel.setTransparentIfEmpty(true); proxyModel.setSourceModel(&baseModel1); - QSignalSpy proxyDataChangeSpy(&proxyModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector))); + QSignalSpy proxyDataChangeSpy(&proxyModel, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector))); baseModel1.setData(baseModel1.index(0, 0), QStringLiteral("New York")); QCOMPARE(proxyDataChangeSpy.count(), 1); proxyDataChangeSpy.clear(); @@ -528,7 +591,7 @@ void tst_RoleMaskProxyModel::testDisconnectedModel() QCOMPARE(proxyDataChangeSpy.count(), 1); } -int tst_RoleMaskProxyModel::countChildren(const QAbstractItemModel* const baseModel, const QModelIndex& parIdx) +int tst_RoleMaskProxyModel::countChildren(const QAbstractItemModel *const baseModel, const QModelIndex &parIdx) { const int rowCnt = baseModel->rowCount(parIdx); const int colCnt = baseModel->columnCount(parIdx); @@ -543,7 +606,9 @@ int tst_RoleMaskProxyModel::countChildren(const QAbstractItemModel* const baseMo return result; } -void tst_RoleMaskProxyModel::testUseRoleMaskRecurse(const int magicNumber, const QAbstractItemModel* const baseModel, const RoleMaskProxyModel* const proxyModel, const QModelIndexList& magicNumerIndexes, const bool userRoleEditable, const QModelIndex& srcParent, const QModelIndex& prxParent) +void tst_RoleMaskProxyModel::testUseRoleMaskRecurse(const int magicNumber, const QAbstractItemModel *const baseModel, + const RoleMaskProxyModel *const proxyModel, const QModelIndexList &magicNumerIndexes, + const bool userRoleEditable, const QModelIndex &srcParent, const QModelIndex &prxParent) { const int rowCnt = baseModel->rowCount(srcParent); const int colCnt = baseModel->columnCount(srcParent); @@ -557,8 +622,7 @@ void tst_RoleMaskProxyModel::testUseRoleMaskRecurse(const int magicNumber, const QCOMPARE(baseParent.data(Qt::UserRole).toInt(), ~magicNumber); else QVERIFY(baseParent.data(Qt::UserRole).toInt() != magicNumber); - } - else { + } else { QCOMPARE(proxyParent.data(Qt::UserRole), baseParent.data(Qt::UserRole)); } QCOMPARE(proxyParent.data(), baseParent.data()); @@ -569,3 +633,160 @@ void tst_RoleMaskProxyModel::testUseRoleMaskRecurse(const int magicNumber, const } } +void tst_RoleMaskProxyModel::testSetItemData_data() +{ + QTest::addColumn("baseModel"); + QTest::addColumn("idxRow"); + QTest::addColumn("idxCol"); + QTest::newRow("List") << createListModel(this) << 0 << 0; + QTest::newRow("Table") << createTableModel(this) << 1 << 1; + QTest::newRow("Tree Root") << createTreeModel(this) << 1 << 0; +} + +void tst_RoleMaskProxyModel::testSetItemDataDataChanged_data() +{ + QTest::addColumn("baseModel"); + QTest::addColumn("idxRow"); + QTest::addColumn("idxCol"); + QTest::newRow("List") << createListModel(this) << 0 << 0; + QTest::newRow("Table") << createTableModel(this) << 1 << 1; + QTest::newRow("Tree Root") << createTreeModel(this) << 1 << 0; +} + +void tst_RoleMaskProxyModel::testSetItemDataDataChanged() +{ + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; + QFETCH(int, idxRow); + QFETCH(int, idxCol); + QMap itemDataSet{{// TextAlignmentRole + std::make_pair(Qt::UserRole, 5), + std::make_pair(Qt::DisplayRole, QStringLiteral("Test")), + std::make_pair(Qt::ToolTipRole, QStringLiteral("ToolTip"))}}; + RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, baseModel); + proxyModel.setMergeDisplayEdit(true); + proxyModel.setTransparentIfEmpty(true); + proxyModel.setMaskedRoles({Qt::UserRole, Qt::ToolTipRole, Qt::TextAlignmentRole}); + proxyModel.setSourceModel(baseModel); + const QModelIndex proxyIdX = proxyModel.index(idxRow, idxCol); + QVERIFY(proxyModel.setData(proxyIdX, Qt::AlignRight, Qt::TextAlignmentRole)); + QSignalSpy proxyDataChangeSpy(&proxyModel, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector))); + QVERIFY(proxyModel.setItemData(proxyIdX, itemDataSet)); + QCOMPARE(proxyDataChangeSpy.size(), 2); + auto argList = proxyDataChangeSpy.takeFirst(); + QCOMPARE(argList.at(0).value(), proxyIdX); + QCOMPARE(argList.at(1).value(), proxyIdX); + auto rolesVector = argList.at(2).value>(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + // bug fixed by Qt commit 1382374deaa4a854aeb542e6c8f7e1841f2abb10 + QCOMPARE(rolesVector.size(), 2); + QVERIFY(!rolesVector.contains(Qt::TextAlignmentRole)); + QVERIFY(rolesVector.contains(Qt::ToolTipRole)); + QVERIFY(rolesVector.contains(Qt::UserRole)); +#endif + argList = proxyDataChangeSpy.takeFirst(); + QCOMPARE(argList.at(0).value(), proxyIdX); + QCOMPARE(argList.at(1).value(), proxyIdX); +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + // bug fixed by Qt commit 1382374deaa4a854aeb542e6c8f7e1841f2abb10 + rolesVector = argList.at(2).value>(); + QCOMPARE(rolesVector.size(), 2); + QVERIFY(rolesVector.contains(Qt::DisplayRole)); + QVERIFY(rolesVector.contains(Qt::EditRole)); +#endif + itemDataSet[Qt::UserRole] = 6; + QVERIFY(proxyModel.setItemData(proxyIdX, itemDataSet)); + QVERIFY(proxyDataChangeSpy.size() >= 1); // the source model signal depends on the version of Qt + argList = proxyDataChangeSpy.takeFirst(); + QCOMPARE(argList.at(0).value(), proxyIdX); + QCOMPARE(argList.at(1).value(), proxyIdX); + rolesVector = argList.at(2).value>(); + QCOMPARE(rolesVector.size(), 1); + QVERIFY(rolesVector.contains(Qt::UserRole)); + itemDataSet.clear(); + itemDataSet[Qt::UserRole] = 6; + QVERIFY(proxyModel.setItemData(proxyIdX, itemDataSet)); + // requures QTBUG-67511 to be fixed before it works on models other than QStandardItemModel + // QCOMPARE(proxyDataChangeSpy.size(), 0); + baseModel->deleteLater(); +} + +void tst_RoleMaskProxyModel::testSort_data() +{ + QTest::addColumn("sortViaProxy"); + QTest::newRow("Sort via Base") << false; + QTest::newRow("Sort via Proxy") << true; +} + +void tst_RoleMaskProxyModel::testSort() +{ + QFETCH(bool, sortViaProxy); + QStringList sequence; + sequence.reserve(100); + for (int i = 0; i < 100; ++i) + sequence.append(QStringLiteral("%1").arg(i, 3, 10, QLatin1Char('0'))); + std::shuffle(sequence.begin(), sequence.end(), std::mt19937(88)); + QStringListModel baseModel(sequence); + RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, this); + proxyModel.setSourceModel(&baseModel); + proxyModel.addMaskedRole(Qt::UserRole); + proxyModel.setTransparentIfEmpty(true); + for (int i = 0; i < 100; ++i) { + QVERIFY(proxyModel.setData(proxyModel.index(i, 0), proxyModel.index(i, 0).data().toString().toInt(), Qt::UserRole)); + } + if (sortViaProxy) + proxyModel.sort(0, Qt::AscendingOrder); + else + baseModel.sort(0, Qt::AscendingOrder); + for (int i = 1; i < 100; ++i) { + QCOMPARE(proxyModel.index(i, 0).data().toString().toInt(), proxyModel.index(i - 1, 0).data().toString().toInt() + 1); + QCOMPARE(proxyModel.index(i, 0).data(Qt::UserRole).toInt(), proxyModel.index(i - 1, 0).data(Qt::UserRole).toInt() + 1); + } +} + +void tst_RoleMaskProxyModel::testEmptyProxy() +{ + QSortFilterProxyModel emptyProxy; + RoleMaskProxyModel maskProxyModel; + new ModelTest(&maskProxyModel, this); + maskProxyModel.setMaskedRoles({Qt::DisplayRole, Qt::ToolTipRole, Qt::BackgroundRole}); + maskProxyModel.setMergeDisplayEdit(true); + maskProxyModel.setTransparentIfEmpty(true); + maskProxyModel.setSourceModel(&emptyProxy); + QCOMPARE(maskProxyModel.rowCount(), 0); + QCOMPARE(maskProxyModel.columnCount(), 0); + QCOMPARE(maskProxyModel.sourceModel(), &emptyProxy); +} + +void tst_RoleMaskProxyModel::testSetItemData() +{ + QFETCH(QAbstractItemModel *, baseModel); + if (!baseModel) + return; + QFETCH(int, idxRow); + QFETCH(int, idxCol); + const QMap itemDataSet{{std::make_pair(Qt::UserRole, 5), + std::make_pair(Qt::DisplayRole, QStringLiteral("Test")), + std::make_pair(Qt::ToolTipRole, QStringLiteral("ToolTip"))}}; + RoleMaskProxyModel proxyModel; + new ModelTest(&proxyModel, baseModel); + proxyModel.setMergeDisplayEdit(true); + proxyModel.setTransparentIfEmpty(true); + proxyModel.setSourceModel(baseModel); + proxyModel.setMaskedRoles({Qt::UserRole, Qt::ToolTipRole, Qt::TextAlignmentRole}); + const QModelIndex proxyIdX = proxyModel.index(idxRow, idxCol); + QVERIFY(proxyModel.setData(proxyIdX, Qt::AlignRight, Qt::TextAlignmentRole)); + QVERIFY(proxyModel.setItemData(proxyIdX, itemDataSet)); + QCOMPARE(proxyModel.data(proxyIdX, Qt::DisplayRole).toString(), QStringLiteral("Test")); + QCOMPARE(proxyModel.data(proxyIdX, Qt::EditRole).toString(), QStringLiteral("Test")); + QCOMPARE(proxyModel.data(proxyIdX, Qt::UserRole).toInt(), 5); + QCOMPARE(proxyModel.data(proxyIdX, Qt::ToolTipRole).toString(), QStringLiteral("ToolTip")); +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + // bug fixed by Qt commit 1382374deaa4a854aeb542e6c8f7e1841f2abb10 + QCOMPARE(proxyModel.data(proxyIdX, Qt::TextAlignmentRole).toInt(), Qt::AlignRight); +#endif + baseModel->deleteLater(); +} diff --git a/tests/tst_RoleMaskProxyModel/tst_rolemaskproxymodel.h b/tests/tst_RoleMaskProxyModel/tst_rolemaskproxymodel.h index 1b0e869..5544153 100644 --- a/tests/tst_RoleMaskProxyModel/tst_rolemaskproxymodel.h +++ b/tests/tst_RoleMaskProxyModel/tst_rolemaskproxymodel.h @@ -6,11 +6,11 @@ #include class QAbstractItemModel; class RoleMaskProxyModel; -class tst_RoleMaskProxyModel :public QObject{ +class tst_RoleMaskProxyModel : public QObject +{ Q_OBJECT private Q_SLOTS: void initTestCase(); - void cleanupTestCase(); void testUseRoleMask(); void testUseRoleMask_data(); void testInsertRow(); @@ -27,10 +27,21 @@ private Q_SLOTS: void testMergeDisplayEdit_data(); void testManageMaskedRoles(); void testDisconnectedModel(); + void testSetItemData_data(); + void testSetItemData(); + void testSetItemDataDataChanged_data(); + void testSetItemDataDataChanged(); + void testSort_data(); + void testSort(); + void testEmptyProxy(); + private: - void testTransparentIfEmptyRecurse(const QAbstractItemModel* const baseModel, const RoleMaskProxyModel* const proxyModel, const QModelIndex& maskedIdx, const QVariant& maskedVal, bool nonMaskedIsNull, const QModelIndex& sourceParent = QModelIndex()); - int countChildren(const QAbstractItemModel* const baseModel, const QModelIndex& parIdx = QModelIndex()); - void testUseRoleMaskRecurse(const int magicNumber, const QAbstractItemModel* const baseModel, const RoleMaskProxyModel* const proxyModel, const QModelIndexList& magicNumerIndexes, const bool userRoleEditable, const QModelIndex& sourceParent = QModelIndex(), const QModelIndex& proxyParent = QModelIndex()); - QList m_models; + void testTransparentIfEmptyRecurse(const QAbstractItemModel *const baseModel, const RoleMaskProxyModel *const proxyModel, + const QModelIndex &maskedIdx, const QVariant &maskedVal, bool nonMaskedIsNull, + const QModelIndex &sourceParent = QModelIndex()); + int countChildren(const QAbstractItemModel *const baseModel, const QModelIndex &parIdx = QModelIndex()); + void testUseRoleMaskRecurse(const int magicNumber, const QAbstractItemModel *const baseModel, const RoleMaskProxyModel *const proxyModel, + const QModelIndexList &magicNumerIndexes, const bool userRoleEditable, + const QModelIndex &sourceParent = QModelIndex(), const QModelIndex &proxyParent = QModelIndex()); }; #endif // tst_rolemaskproxymodel_h__ diff --git a/tests/tst_SerialisersCommon/tst_serialiserscommon.cpp b/tests/tst_SerialisersCommon/tst_serialiserscommon.cpp new file mode 100644 index 0000000..34e729f --- /dev/null +++ b/tests/tst_SerialisersCommon/tst_serialiserscommon.cpp @@ -0,0 +1,168 @@ +#include "tst_serialiserscommon.h" +#include +#include +#include +#include +#include +void tst_SerialiserCommon::saveLoadByteArray(AbstractModelSerialiser *serialiser, const QAbstractItemModel *sourceModel, + QAbstractItemModel *destinationModel, bool multiRole, bool checkHeaders) const +{ + serialiser->setModel(sourceModel); + if (multiRole) + serialiser->addRoleToSave(Qt::UserRole + 1); + QByteArray dataArray; + QVERIFY(serialiser->saveModel(&dataArray)); + serialiser->setModel(destinationModel); + QVERIFY(serialiser->loadModel(dataArray)); + checkModelEqual(sourceModel, destinationModel, QModelIndex(), QModelIndex(), checkHeaders); +} + +void tst_SerialiserCommon::saveLoadFile(AbstractModelSerialiser *serialiser, const QAbstractItemModel *sourceModel, + QAbstractItemModel *destinationModel, bool multiRole, bool checkHeaders) const +{ + serialiser->setModel(sourceModel); + if (multiRole) + serialiser->addRoleToSave(Qt::UserRole + 1); + QTemporaryFile tempFile; + QVERIFY(tempFile.open()); + QVERIFY(serialiser->saveModel(&tempFile)); + QVERIFY(tempFile.seek(0)); + serialiser->setModel(destinationModel); + QVERIFY(serialiser->loadModel(&tempFile)); + checkModelEqual(sourceModel, destinationModel, QModelIndex(), QModelIndex(), checkHeaders); +} + +void tst_SerialiserCommon::saveLoadString(AbstractStringSerialiser *serialiser, const QAbstractItemModel *sourceModel, + QAbstractItemModel *destinationModel, bool multiRole, bool checkHeaders) const +{ + serialiser->setModel(sourceModel); + if (multiRole) + serialiser->addRoleToSave(Qt::UserRole + 1); + QString dataString; + QVERIFY(serialiser->saveModel(&dataString)); + serialiser->setModel(destinationModel); + QVERIFY(serialiser->loadModel(dataString)); + checkModelEqual(sourceModel, destinationModel, QModelIndex(), QModelIndex(), checkHeaders); +} + +void tst_SerialiserCommon::checkModelEqual(const QAbstractItemModel *a, const QAbstractItemModel *b, const QModelIndex &aParent, + const QModelIndex &bParent, bool checkHeaders) const +{ + Q_ASSERT(a); + Q_ASSERT(b); + const bool aParentValid = aParent.isValid(); + Q_ASSERT(!aParentValid || aParent.model() == a); + Q_ASSERT(!bParent.isValid() || bParent.model() == b); + Q_ASSERT(aParentValid == bParent.isValid()); + const int rowC = a->rowCount(aParent); + QCOMPARE(b->rowCount(bParent), rowC); + const int colC = a->columnCount(aParent); + QCOMPARE(b->columnCount(bParent), colC); + if (!aParentValid && checkHeaders) { + for (int i = 0; i < rowC; ++i) { + QCOMPARE(a->headerData(i, Qt::Vertical), b->headerData(i, Qt::Vertical)); + QCOMPARE(a->headerData(i, Qt::Vertical, Qt::UserRole), b->headerData(i, Qt::Vertical, Qt::UserRole)); + } + for (int i = 0; i < colC; ++i) { + QCOMPARE(a->headerData(i, Qt::Horizontal), b->headerData(i, Qt::Horizontal)); + QCOMPARE(a->headerData(i, Qt::Horizontal, Qt::UserRole), b->headerData(i, Qt::Horizontal, Qt::UserRole)); + } + } + for (int i = 0; i < rowC; ++i) { + for (int j = 0; j < colC; ++j) { + const QModelIndex aIdx = a->index(i, j, aParent); + const QModelIndex bIdx = b->index(i, j, bParent); + const auto aItemData = a->itemData(aIdx); + const auto bItemData = b->itemData(bIdx); + QCOMPARE(bItemData.keys(), aItemData.keys()); + QCOMPARE(bItemData, aItemData); + const bool hasChildren = a->hasChildren(aIdx); + QCOMPARE(b->hasChildren(bIdx), hasChildren); + if (hasChildren) { + checkModelEqual(a, b, aIdx, bIdx, checkHeaders); + } + } + } +} + +QAbstractItemModel *tst_SerialiserCommon::createStringModel(QObject *parent) +{ + std::uniform_int_distribution rowsDist(20, 50); + std::uniform_int_distribution itemsDist(0, 1000); + + QStringList itmesList; + for (int i = rowsDist(generator); i > 0; --i) + itmesList.append(QStringLiteral("Item ") + QString::number(itemsDist(generator))); + return new QStringListModel(itmesList, parent); +} +#ifdef QT_GUI_LIB +void tst_SerialiserCommon::insertBranch(QAbstractItemModel *model, const QModelIndex &parent, bool multiRoles, int subBranches) +{ + Q_ASSERT(model); + Q_ASSERT(!parent.isValid() || parent.model() == model); + const QBrush randomBrushes[] = {QBrush(Qt::red), QBrush(Qt::blue), QBrush(Qt::green), QBrush(Qt::yellow), QBrush(Qt::magenta), QBrush(Qt::cyan)}; + std::uniform_int_distribution colorDistribution(0, (sizeof(randomBrushes) / sizeof(randomBrushes[0])) - 1); + std::uniform_int_distribution coulmnsDist(2, 5); + std::uniform_int_distribution rowsDist(3, 6); + Q_ASSUME(model->insertColumns(0, coulmnsDist(generator), parent)); + Q_ASSUME(model->insertRows(0, rowsDist(generator), parent)); + const QString baseString = parent.isValid() ? (parent.data(Qt::EditRole).toString() + QStringLiteral("->")) : QString(); + for (int i = 0; i < model->rowCount(parent); ++i) { + for (int j = 0; j < model->columnCount(parent); ++j) { + const QModelIndex idx = model->index(i, j, parent); + model->setData(idx, baseString + QStringLiteral("%1,%2[%3]").arg(i).arg(j).arg(colorDistribution(generator)), Qt::EditRole); + if (multiRoles) { + model->setData(idx, randomBrushes[colorDistribution(generator)], Qt::BackgroundRole); + model->setData(idx, randomBrushes[colorDistribution(generator)], Qt::ForegroundRole); + model->setData(idx, i, Qt::UserRole); + model->setData(idx, j, Qt::UserRole + 1); + } + if (subBranches > 0) + insertBranch(model, idx, multiRoles, subBranches - 1); + } + } +} + +QAbstractItemModel *tst_SerialiserCommon::createComplexModel(bool tree, bool multiRoles, QObject *parent) +{ + QStandardItemModel *result = new QStandardItemModel(parent); + insertBranch(result, QModelIndex(), multiRoles, tree ? 2 : 0); + for (int i = 0; i < result->rowCount(); ++i) { + result->setHeaderData(i, Qt::Vertical, QStringLiteral("Row %1").arg(i)); + if (multiRoles) + result->setHeaderData(i, Qt::Vertical, i, Qt::UserRole); + } + for (int i = 0; i < result->columnCount(); ++i) { + result->setHeaderData(i, Qt::Horizontal, QStringLiteral("Column %1").arg(i)); + if (multiRoles) + result->setHeaderData(i, Qt::Horizontal, i, Qt::UserRole); + } + return result; +} +#endif +void tst_SerialiserCommon::basicSaveLoadData(QObject *parent) +{ + QTest::addColumn("sourceModel"); + QTest::addColumn("destinationModel"); + QTest::newRow("List Single Role") << static_cast(createStringModel(parent)) + << static_cast(new QStringListModel(parent)); + QTest::newRow("List Single Role Overwrite") << static_cast(createStringModel(parent)) << createStringModel(parent); +#ifdef QT_GUI_LIB + QTest::newRow("Table Single Role") << static_cast(createComplexModel(false, false, parent)) + << static_cast(new QStandardItemModel(parent)); + QTest::newRow("Table Multi Roles") << static_cast(createComplexModel(false, true, parent)) + << static_cast(new QStandardItemModel(parent)); + QTest::newRow("Table Single Role Overwrite") << static_cast(createComplexModel(false, false, parent)) + << createComplexModel(false, false, parent); + QTest::newRow("Table Multi Roles Overwrite") << static_cast(createComplexModel(false, true, parent)) + << createComplexModel(false, true, parent); + QTest::newRow("Tree Single Role") << static_cast(createComplexModel(true, false, parent)) + << static_cast(new QStandardItemModel(parent)); + QTest::newRow("Tree Multi Roles") << static_cast(createComplexModel(true, true, parent)) + << static_cast(new QStandardItemModel(parent)); + QTest::newRow("Tree Single Role Overwrite") << static_cast(createComplexModel(true, false, parent)) + << createComplexModel(true, false, parent); + QTest::newRow("Tree Multi Roles Overwrite") << static_cast(createComplexModel(true, true, parent)) + << createComplexModel(true, true, parent); +#endif +} diff --git a/tests/tst_SerialisersCommon/tst_serialiserscommon.h b/tests/tst_SerialisersCommon/tst_serialiserscommon.h new file mode 100644 index 0000000..5bd9d2f --- /dev/null +++ b/tests/tst_SerialisersCommon/tst_serialiserscommon.h @@ -0,0 +1,33 @@ +#ifndef tst_serialisers_common_h__ +#define tst_serialisers_common_h__ +#include +#include +#include +#ifdef QT_GUI_LIB +# include +#endif +class AbstractModelSerialiser; +class AbstractStringSerialiser; +class tst_SerialiserCommon +{ +protected: + virtual void basicSaveLoadData(QObject *parent); + void saveLoadByteArray(AbstractModelSerialiser *serialiser, const QAbstractItemModel *sourceModel, QAbstractItemModel *destinationModel, + bool multiRole = true, bool checkHeaders = true) const; + void saveLoadFile(AbstractModelSerialiser *serialiser, const QAbstractItemModel *sourceModel, QAbstractItemModel *destinationModel, + bool multiRole = true, bool checkHeaders = true) const; + void saveLoadString(AbstractStringSerialiser *serialiser, const QAbstractItemModel *sourceModel, QAbstractItemModel *destinationModel, + bool multiRole = true, bool checkHeaders = true) const; + void checkModelEqual(const QAbstractItemModel *a, const QAbstractItemModel *b, const QModelIndex &aParent = QModelIndex(), + const QModelIndex &bParent = QModelIndex(), bool checkHeaders = true) const; + QAbstractItemModel *createStringModel(QObject *parent = nullptr); +#ifdef QT_GUI_LIB + void insertBranch(QAbstractItemModel *model, const QModelIndex &parent, bool multiRoles, int subBranches); + QAbstractItemModel *createComplexModel(bool tree, bool multiRoles, QObject *parent = nullptr); +#endif + std::default_random_engine generator; +}; +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +Q_DECLARE_METATYPE(const QAbstractItemModel *); +#endif +#endif diff --git a/tests/tst_XmlModelSerialiser/CMakeLists.txt b/tests/tst_XmlModelSerialiser/CMakeLists.txt new file mode 100644 index 0000000..d7f2492 --- /dev/null +++ b/tests/tst_XmlModelSerialiser/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.2) +include(TestMacro) +BasicTest(XmlModelSerialiser) +target_sources(tst_XmlModelSerialiser PRIVATE + ../tst_SerialisersCommon/tst_serialiserscommon.h + ../tst_SerialisersCommon/tst_serialiserscommon.cpp +) +target_include_directories(tst_XmlModelSerialiser PRIVATE ../tst_SerialisersCommon/) \ No newline at end of file diff --git a/tests/tst_XmlModelSerialiser/main.cpp b/tests/tst_XmlModelSerialiser/main.cpp new file mode 100644 index 0000000..92d3674 --- /dev/null +++ b/tests/tst_XmlModelSerialiser/main.cpp @@ -0,0 +1,3 @@ +#include "tst_xmlmodelserialiser.h" +#include +QTEST_MAIN(tst_XmlModelSerialiser) \ No newline at end of file diff --git a/tests/tst_XmlModelSerialiser/tst_xmlmodelserialiser.cpp b/tests/tst_XmlModelSerialiser/tst_xmlmodelserialiser.cpp new file mode 100644 index 0000000..27f1751 --- /dev/null +++ b/tests/tst_XmlModelSerialiser/tst_xmlmodelserialiser.cpp @@ -0,0 +1,100 @@ +#include +#include "tst_xmlmodelserialiser.h" +#include +#include +#include +#include +#include + +void tst_XmlModelSerialiser::basicSaveLoadByteArray() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + XmlModelSerialiser serialiser; + saveLoadByteArray(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_XmlModelSerialiser::basicSaveLoadFile() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + XmlModelSerialiser serialiser; + saveLoadFile(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_XmlModelSerialiser::basicSaveLoadString() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + XmlModelSerialiser serialiser; + saveLoadString(&serialiser, sourceModel, destinationModel, true); + destinationModel->deleteLater(); +} + +void tst_XmlModelSerialiser::basicSaveLoadStream() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + XmlModelSerialiser serialiser(sourceModel); + serialiser.addRoleToSave(Qt::UserRole + 1); + QTemporaryFile serialisedXmlStream; + QVERIFY(serialisedXmlStream.open()); + QXmlStreamWriter writeStream(&serialisedXmlStream); + QVERIFY(serialiser.saveModel(writeStream)); + QVERIFY(serialisedXmlStream.seek(0)); + QXmlStreamReader readStream(&serialisedXmlStream); + serialiser.setModel(destinationModel); + QVERIFY(serialiser.loadModel(readStream)); + serialisedXmlStream.close(); + checkModelEqual(sourceModel, destinationModel); + destinationModel->deleteLater(); +} + +void tst_XmlModelSerialiser::basicSaveLoadNested() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QFETCH(QAbstractItemModel *, destinationModel); + XmlModelSerialiser serialiser(sourceModel); + serialiser.addRoleToSave(Qt::UserRole + 1); + QTemporaryFile serialisedXmlNested; + QVERIFY(serialisedXmlNested.open()); + QXmlStreamWriter writeNestedStream(&serialisedXmlNested); + writeNestedStream.writeStartDocument(); + writeNestedStream.writeStartElement(QStringLiteral("NestForModel")); + serialiser.setPrintStartDocument(false); + QVERIFY(serialiser.saveModel(writeNestedStream)); + writeNestedStream.writeEndElement(); // NestForModel + QVERIFY(serialisedXmlNested.seek(0)); + QXmlStreamReader readNestedStream(&serialisedXmlNested); + serialiser.setModel(destinationModel); + QVERIFY(serialiser.loadModel(readNestedStream)); + serialisedXmlNested.close(); + checkModelEqual(sourceModel, destinationModel); + destinationModel->deleteLater(); +} + +void tst_XmlModelSerialiser::validateXmlOutput() +{ + QFETCH(const QAbstractItemModel *, sourceModel); + QByteArray modelData; + XmlModelSerialiser serialiser(sourceModel); + serialiser.saveModel(&modelData); + QXmlStreamReader xmlReader(modelData); + while (!xmlReader.atEnd()) + xmlReader.readNext(); + QVERIFY(!xmlReader.hasError()); +} + +void tst_XmlModelSerialiser::validateXmlOutput_data() +{ + QTest::addColumn("sourceModel"); + QTest::newRow("List Single Role") << static_cast(createStringModel(this)); +#ifdef QT_GUI_LIB + QTest::newRow("Table Single Role") << static_cast(createComplexModel(false, false, this)); + QTest::newRow("Table Multi Roles") << static_cast(createComplexModel(false, true, this)); + QTest::newRow("Tree Single Role") << static_cast(createComplexModel(true, false, this)); + QTest::newRow("Tree Multi Roles") << static_cast(createComplexModel(true, true, this)); +#endif +} diff --git a/tests/tst_XmlModelSerialiser/tst_xmlmodelserialiser.h b/tests/tst_XmlModelSerialiser/tst_xmlmodelserialiser.h new file mode 100644 index 0000000..9c9e832 --- /dev/null +++ b/tests/tst_XmlModelSerialiser/tst_xmlmodelserialiser.h @@ -0,0 +1,23 @@ +#ifndef tst_xmlmodelserialiser_h__ +#define tst_xmlmodelserialiser_h__ +#include +#include +class QAbstractItemModel; +class tst_XmlModelSerialiser : public QObject, public tst_SerialiserCommon +{ + Q_OBJECT +private Q_SLOTS: + void basicSaveLoadByteArray(); + void basicSaveLoadFile(); + void basicSaveLoadString(); + void basicSaveLoadStream(); + void basicSaveLoadNested(); + void basicSaveLoadByteArray_data() { basicSaveLoadData(this); } + void basicSaveLoadFile_data() { basicSaveLoadData(this); } + void basicSaveLoadString_data() { basicSaveLoadData(this); } + void basicSaveLoadNested_data() { basicSaveLoadData(this); } + void basicSaveLoadStream_data() { basicSaveLoadData(this); } + void validateXmlOutput(); + void validateXmlOutput_data(); +}; +#endif