[
  {
    "path": ".envrc",
    "content": "use flake"
  },
  {
    "path": ".gitignore",
    "content": "*.pro.user\nresult\n"
  },
  {
    "path": ".gitlab-ci.yml",
    "content": "stages:\n  - build\n  - test\n  - package\n  - publish\n\ndefault:\n  interruptible: true\n\n.cache:\n  variables:\n    CCACHE_DIR: \"$CI_PROJECT_DIR/.ccache\"\n    CCACHE_MAXSIZE: \"100M\"\n  cache:\n    key: \"$CI_JOB_STAGE:$CI_JOB_NAME\"\n    paths: [.ccache]\n\n# ----------- Build MiniZincIDE -----------\n\nbuild:linux:\n  extends: .cache\n  stage: build\n  image: ghcr.io/minizinc/docker-build-environment:qt\n  variables:\n    GIT_SUBMODULE_STRATEGY: recursive\n    GIT_STRATEGY: clone\n    DISABLE_COPYRIGHT_FILES_DEPLOYMENT: \"1\"\n    DEBUG: \"1\"\n  script:\n    - mkdir -p build; cd build\n    - qmake -makefile \"CONFIG+=bundled\" \"CXX_PREFIX=ccache\" \"DEFINES+=MINIZINC_IDE_BUILD=\\\\\\\\\\\\\\\"\\\"${CI_PIPELINE_ID}\\\\\\\\\\\\\\\"\\\"\" PREFIX=/usr ../MiniZincIDE/MiniZincIDE.pro\n    - make -j4\n    - make -j4 INSTALL_ROOT=../ide install; find ../ide/\n    - cd ..\n    # Download Gecode Gist so linuxdeploy can include its dependencies\n    - sh ${VENDOR_SCRIPT} minizinc-vendor master gecode_gist:linux vendor.zip\n    - unzip -q vendor.zip\n    - linuxdeploy --appdir ide --executable vendor/gecode_gist/bin/fzn-gecode --plugin qt -v0\n    - rm -rf ide/usr/share ide/usr/translations ide/usr/bin/fzn-gecode\n  tags: [linux, docker]\n  artifacts:\n    paths: [ide/]\n\nbuild:osx:\n  extends: .cache\n  stage: build\n  variables:\n    GIT_SUBMODULE_STRATEGY: recursive\n    GIT_STRATEGY: clone\n  script:\n    - mkdir -p build; cd build\n    - qmake -makefile \"CONFIG+=bundled\" \"CXX_PREFIX=ccache\" \"DEFINES+=MINIZINC_IDE_BUILD=\\\\\\\\\\\\\\\"\\\"${CI_PIPELINE_ID}\\\\\\\\\\\\\\\"\\\"\" QMAKE_APPLE_DEVICE_ARCHS=\"x86_64 arm64\" ../MiniZincIDE/MiniZincIDE.pro\n    - make -j4\n    - cp -r MiniZincIDE.app ..\n  tags: [osx, cpp, qt]\n  artifacts:\n    paths: [MiniZincIDE.app]\n\nbuild:win64:\n  stage: build\n  variables:\n    GIT_SUBMODULE_STRATEGY: recursive\n    MZNARCH: \"win64\"\n    BUILDCACHE_DIR: \"$CI_PROJECT_DIR/.ccache\"\n    BUILDCACHE_MAX_CACHE_SIZE: \"104857600\"\n  script:\n    - if not exist \"build\" mkdir build\n    - if not exist \"ide\" mkdir ide\n    - cd build\n    - qmake \"CONFIG+=bundled\" \"CXX_PREFIX=buildcache\" \"DEFINES+=MINIZINC_IDE_BUILD=\\\\\\\\\\\\\\\"\\\"%CI_PIPELINE_ID%\\\\\\\\\\\\\\\"\\\"\" ../MiniZincIDE/MiniZincIDE.pro\n    - jom -j4\n    - cp release/MiniZincIDE.exe ../ide\n    - cd ../\n    # Download Gecode Gist so windeployqt can include its dependencies\n    - curl --silent -o vendor.zip --location --header \"PRIVATE-TOKEN:%ACCESS_TOKEN%\" \"https://gitlab.com/api/v4/projects/minizinc%%2Fminizinc-vendor/jobs/artifacts/master/download?job=gecode_gist:%MZNARCH%\"\n    - unzip -q vendor.zip\n    - cd ide\n    - windeployqt --no-translations --no-compiler-runtime --no-system-d3d-compiler MiniZincIDE.exe ../vendor/gecode_gist/bin/fzn-gecode.exe\n  tags: [win64, cpp, qt]\n  artifacts:\n    paths: [ide/]\n  cache:\n    key: \"build_win64\"\n    paths: [.ccache]\n\n# ----------- Test MiniZincIDE -----------\n\n.tests_template:\n  image: ghcr.io/minizinc/docker-build-environment:qt\n  variables:\n    GIT_SUBMODULE_STRATEGY: recursive\n    GIT_STRATEGY: clone\n    QT_QPA_PLATFORM: offscreen\n    MZN_SOLVER_PATH: ${CI_PROJECT_DIR}/vendor/gecode/share/minizinc/solvers/:${CI_PROJECT_DIR}/vendor/chuffed/share/minizinc/solvers\n  before_script:\n    - 'if [ -n \"$CI_COMMIT_TAG\" ]; then MZNREF=\"$CI_COMMIT_TAG\"; elif [ \"$CI_COMMIT_REF_NAME\" = \"master\" ]; then MZNREF=\"master\"; else MZNREF=\"develop\"; fi'\n    ### Download Dependencies\n    - curl --silent -o minizinc.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2Fminizinc/jobs/artifacts/$MZNREF/download?job=build:$MZNARCH\"\n    - unzip -q minizinc.zip\n    - curl --silent -o vendor.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2Fminizinc-vendor/jobs/artifacts/master/download?job=bundle:$MZNARCH\"\n    - unzip -q vendor.zip\n    ### Add MiniZinc to path\n    - export PATH=$CI_PROJECT_DIR/minizinc/bin:$PATH\n  script:\n    ### Build tests\n    - mkdir -p test; cd test\n    - qmake -makefile \"CXX_PREFIX=ccache\" ../tests/tests.pro\n    - make -j4\n    ### Run tests\n    - make check\n  needs: []\n\ntest:linux:\n  extends: [.cache, .tests_template]\n  variables:\n    MZNARCH: linux\n  tags: [linux, docker]\n\ntest:osx:\n  extends: [.cache, .tests_template]\n  variables:\n    MZNARCH: osx\n  tags: [osx, cpp, qt]\n\ntest:win64:\n  extends: [.cache, .tests_template]\n  variables:\n    MZNARCH: win64\n    MZN_SOLVER_PATH: ${CI_PROJECT_DIR}/vendor/gecode/share/minizinc/solvers/;${CI_PROJECT_DIR}/vendor/chuffed/share/minizinc/solvers\n    BUILDCACHE_DIR: \"$CI_PROJECT_DIR/.ccache\"\n    BUILDCACHE_MAX_CACHE_SIZE: \"104857600\"\n  before_script:\n    ### Choose the MiniZinc compiler branch\n    - if defined CI_COMMIT_TAG (set MZNREF=%CI_COMMIT_TAG%) else if %CI_COMMIT_REF_NAME%==master (set MZNREF=master) else (set MZNREF=develop)\n    ### Download Dependencies\n    - curl --silent -o minizinc.zip --location --header \"PRIVATE-TOKEN:%ACCESS_TOKEN%\" \"https://gitlab.com/api/v4/projects/minizinc%%2Fminizinc/jobs/artifacts/%MZNREF%/download?job=build:%MZNARCH%\"\n    - unzip -q minizinc.zip\n    - curl --silent -o vendor.zip --location --header \"PRIVATE-TOKEN:%ACCESS_TOKEN%\" \"https://gitlab.com/api/v4/projects/minizinc%%2Fminizinc-vendor/jobs/artifacts/master/download?job=bundle:%MZNARCH%\"\n    - unzip -q vendor.zip\n    ### Add MiniZinc to path\n    - set PATH=%CI_PROJECT_DIR%/minizinc/bin;%PATH%\n  script:\n    ### Build tests\n    - if not exist \"test\" mkdir test\n    - cd test\n    - qmake \"CXX_PREFIX=buildcache\" ../tests/tests.pro\n    - jom -j4\n    ### Run tests\n    - jom check\n  tags: [win64, cpp, qt]\n  cache:\n    key: \"test_win64\"\n    paths: [.ccache]\n\n# ----------- MiniZinc Packaging -----------\n.packaging_setup: &packaging_setup\n  before_script:\n    ### Set the MZNVERSION variable\n    - 'if [ -n \"$CI_COMMIT_TAG\" ]; then MZNVERSION=\"$CI_COMMIT_TAG\"; else MZNVERSION=\"build$CI_PIPELINE_ID\"; fi'\n    ### Choose the MiniZinc compiler branch\n    - 'if [ -n \"$CI_COMMIT_TAG\" ]; then MZNREF=\"$CI_COMMIT_TAG\"; elif [ \"$CI_COMMIT_REF_NAME\" = \"master\" ]; then MZNREF=\"master\"; else MZNREF=\"develop\"; fi'\n    ### Choose the FindMUS branch\n    - 'if  [ -n \"$CI_COMMIT_TAG\" ] || [ \"$CI_COMMIT_REF_NAME\" = \"master\" ]; then FINDMUSREF=\"master\"; else FINDMUSREF=\"develop\"; fi'\n    ### Choose the FindMUS branch\n    - 'if  [ -n \"$CI_COMMIT_TAG\" ] || [ \"$CI_COMMIT_REF_NAME\" = \"master\" ]; then ANALYSEREF=\"master\"; else ANALYSEREF=\"develop\"; fi'\n    ### Download Dependencies\n    - curl --silent -o minizinc.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2Fminizinc/jobs/artifacts/$MZNREF/download?job=build:$MZNARCH\"\n    - unzip -q minizinc.zip\n    - '[ ${DOWNLOAD_SOLVERS:-1} -eq 1 ] && curl --silent -o vendor.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2Fminizinc-vendor/jobs/artifacts/master/download?job=bundle:$MZNARCH\" && unzip -q vendor.zip'\n    - '[ ${DOWNLOAD_GLOBALIZER:-0} -eq 1 ] && curl --silent -o globalizer.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2FGlobalizer/jobs/artifacts/master/download?job=build:$MZNARCH\" && unzip -q globalizer.zip'\n    - '[ ${DOWNLOAD_FINDMUS:-0} -eq 1 ] && curl --silent -o findmus.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2FFindMUS/jobs/artifacts/$FINDMUSREF/download?job=build:$MZNARCH\" && unzip -q findmus.zip'\n    - '[ ${DOWNLOAD_ANALYSE:-0} -eq 1 ] && curl --silent -o mzn-analyse.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2Fmzn-analyse/jobs/artifacts/$ANALYSEREF/download?job=build:$MZNARCH\" && unzip -q mzn-analyse.zip'\n\npackage:check_version:\n  stage: package\n  image: ghcr.io/minizinc/docker-build-environment:qt\n  variables:\n    MZNARCH: \"linux\"\n    DOWNLOAD_SOLVERS: 0\n  <<: *packaging_setup\n  script:\n    - mkdir -p build; cd build\n    - qmake -makefile \"CONFIG+=output_version\" ../MiniZincIDE/MiniZincIDE.pro\n    - IDE_VERSION=$(tr -s ' ' < version)\n    - MZN_VERSION=$(../minizinc/bin/minizinc --version | grep -Po '(?<=version )[^,]+')\n    - echo IDE is version ${IDE_VERSION}\n    - echo MiniZinc is version ${MZN_VERSION}\n    - if [ \"$IDE_VERSION\" != \"$MZN_VERSION\" ]; then echo 'Version mismatch!'; exit 1; fi\n    - if [ -n \"$CI_COMMIT_TAG\" ]; then echo Tag is \"$CI_COMMIT_TAG\"; if [ \"$CI_COMMIT_TAG\" != \"$MZN_VERSION\" ]; then echo 'Version mismatch!'; exit 1; fi; fi\n  needs: []\n  tags: [linux, docker]\n\npackage:linux:\n  stage: package\n  image: ghcr.io/minizinc/docker-build-environment:package\n  variables:\n    MZNARCH: \"linux\"\n    DOWNLOAD_GLOBALIZER: 1\n    DOWNLOAD_FINDMUS: 1\n    DOWNLOAD_ANALYSE: 1\n  <<: *packaging_setup\n  script:\n    - PACKAGE=MiniZincIDE-${MZNVERSION}-bundle-linux-x86_64\n    - mkdir -p $PACKAGE/lib/\n    ### Package IDE\n    - mv ide/usr/* $PACKAGE/\n    - cp resources/scripts/MiniZincIDE.sh $PACKAGE/\n    ### Package MiniZinc\n    - mv minizinc/bin/* $PACKAGE/bin/\n    - mv minizinc/share $PACKAGE/share\n    ### Package vendor solvers\n    - mv vendor/gecode_gist/bin/fzn-gecode $PACKAGE/bin/\n    - patchelf --set-rpath '$ORIGIN/../lib' $PACKAGE/bin/fzn-gecode\n    - cp -r vendor/gecode_gist/share/minizinc/* $PACKAGE/share/minizinc/\n    - mv vendor/chuffed/bin/fzn-chuffed $PACKAGE/bin/\n    - cp -r vendor/chuffed/share/minizinc/* $PACKAGE/share/minizinc/\n    - mv vendor/or-tools/bin/fzn-cp-sat $PACKAGE/bin/\n    - cp -r vendor/or-tools/share/minizinc/* $PACKAGE/share/minizinc/\n    - cp vendor/highs/lib64/libhighs.so $PACKAGE/lib/\n    ### Package Globalizer\n    - mv globalizer/bin/minizinc-globalizer $PACKAGE/bin/\n    - cp -r globalizer/share/minizinc/* $PACKAGE/share/minizinc/\n    ### Package findMUS\n    - mv findMUS/bin/findMUS $PACKAGE/bin/\n    - cp -r findMUS/share/minizinc/* $PACKAGE/share/minizinc/\n    ### Package mzn-analyse\n    - mv mzn-analyse/bin/mzn-analyse $PACKAGE/bin/\n    ### Strip included binaries\n    - (cd $PACKAGE/bin; strip minizinc fzn-gecode fzn-chuffed fzn-cp-sat findMUS mzn-analyse minizinc-globalizer mzn2doc)\n    - cp resources/misc/README $PACKAGE\n    ### Compress package\n    - tar -czf $PACKAGE.tgz $PACKAGE\n    ### Generate checksum\n    - sha256sum $PACKAGE.tgz > $PACKAGE.sha256\n  artifacts:\n    name: \"minizinc_bundle_linux_${CI_PIPELINE_ID}\"\n    paths: [MiniZincIDE*.tgz, MiniZincIDE*.sha256]\n  needs: [\"build:linux\"]\n  tags: [linux, docker]\n\npackage:osx:\n  stage: package\n  variables:\n    MZNARCH: \"osx\"\n    DOWNLOAD_GLOBALIZER: 1\n    DOWNLOAD_FINDMUS: 1\n    DOWNLOAD_ANALYSE: 1\n  <<: *packaging_setup\n  script:\n    - \"DIR=MiniZincIDE.app/Contents/Resources; MZNDIR=$DIR/share/minizinc\"\n    - mkdir -p $MZNDIR/solvers\n    ### Package MiniZinc\n    - mv minizinc/bin/* $DIR/\n    - mv minizinc/share/minizinc/* $MZNDIR/\n    ### Package vendor solvers\n    - mkdir -p $DIR/bin/\n    - mkdir -p $DIR/lib/\n    - mv vendor/gecode_gist/bin/fzn-gecode $DIR/bin/fzn-gecode\n    - cp -r vendor/gecode_gist/share/minizinc/* $MZNDIR/\n    - cp resources/misc/osx-gecode-qt.conf $DIR/bin/qt.conf\n    - mv vendor/chuffed/bin/fzn-chuffed $DIR/bin/\n    - cp -r vendor/chuffed/share/minizinc/* $MZNDIR/\n    - mv vendor/or-tools/bin/fzn-cp-sat $DIR/bin/\n    - cp -r vendor/or-tools/share/minizinc/* $MZNDIR/\n    - cp vendor/highs/lib/libhighs.dylib $DIR/lib/\n    ### Package Globalizer\n    - mv globalizer/bin/minizinc-globalizer $DIR/bin/\n    - cp -r globalizer/share/minizinc/* $MZNDIR/\n    ### Package findMUS\n    - mv findMUS/bin/findMUS $DIR/bin/\n    - cp -r findMUS/share/minizinc/* $MZNDIR/\n    ### Package mzn-analyse\n    - mv mzn-analyse/bin/mzn-analyse $DIR/bin/\n    ### Strip included binaries\n    - (cd $DIR; strip minizinc mzn2doc)\n    - (cd $DIR/bin; strip fzn-gecode fzn-chuffed fzn-cp-sat findMUS mzn-analyse minizinc-globalizer)\n    ### Run automated Qt deployment tool\n    - macdeployqt ./MiniZincIDE.app -executable=$DIR/bin/fzn-gecode\n    ### Sign package\n    - if [ -z \"$OSX_DEVELOPER_ID\" ]; then\n    - exit 0\n    - fi\n    - security unlock-keychain -p \"$OSX_KEYCHAIN_PASSWORD\" \"$OSX_KEYCHAIN_PATH\"\n    - security set-key-partition-list -S 'apple-tool:,apple:' -s -k \"$OSX_KEYCHAIN_PASSWORD\" \"$OSX_KEYCHAIN_PATH\"\n    - security list-keychains -d user -s \"$OSX_KEYCHAIN_PATH\" login.keychain\n    - for f in MiniZincIDE.app/Contents/Frameworks/*.framework; do \\\n    - codesign --options runtime --force --sign \"$OSX_DEVELOPER_ID\" $f/Versions/*/`basename $f .framework`; \\\n    - done\n    - find MiniZincIDE.app/Contents/PlugIns MiniZincIDE.app/Contents/Frameworks -type f -name '*.dylib' -exec codesign --options runtime --force --sign \"$OSX_DEVELOPER_ID\" {} \\;\n    - find MiniZincIDE.app/Contents/Resources -type f -perm +111 -exec codesign --options runtime --entitlements resources/misc/entitlements.xml --force --sign \"$OSX_DEVELOPER_ID\" {} \\;\n    - codesign --options runtime --force --sign \"$OSX_DEVELOPER_ID\" MiniZincIDE.app\n    - ditto -c -k --sequesterRsrc --keepParent MiniZincIDE.app signed_bundle.zip\n    - xcrun notarytool submit signed_bundle.zip --team-id \"$OSX_NOTARY_TOOL_TEAM_ID\" --apple-id \"$OSX_NOTARY_TOOL_APPLE_ID\" --password \"$OSX_NOTARY_TOOL_PASSWORD\" --wait\n    - xcrun stapler staple MiniZincIDE.app\n    - cp \"$OSX_TEMPLATE_IMAGE\" signed_bundle.sparseimage\n    - hdiutil detach -quiet signed_bundle || true\n    - hdiutil attach -mountpoint signed_bundle signed_bundle.sparseimage\n    - diskutil rename signed_bundle \"MiniZinc IDE $MZNVERSION (bundled)\"\n    - rm -rf signed_bundle/MiniZincIDE.app\n    - mv MiniZincIDE.app signed_bundle/\n    - hdiutil detach signed_bundle\n    - hdiutil convert -format UDZO -o MiniZincIDE-$MZNVERSION-bundled.dmg signed_bundle.sparseimage\n  after_script:\n    - hdiutil detach -quiet signed_bundle || true\n    - security lock-keychain \"$OSX_KEYCHAIN_PATH\"\n  artifacts:\n    name: \"minizinc_bundle_mac_${CI_PIPELINE_ID}\"\n    paths: [MiniZincIDE.app, MiniZincIDE-*-bundled.dmg]\n  needs: [\"build:osx\"]\n  tags: [osx, qt]\n\npackage:osx:ide_only:\n  stage: package\n  variables:\n    MZNARCH: \"osx\"\n  script:\n    ### Run automated Qt deployment tool\n    - macdeployqt ./MiniZincIDE.app\n  artifacts:\n    name: \"minizinc_ide_mac_${CI_PIPELINE_ID}\"\n    paths: [MiniZincIDE.app]\n  needs: [\"build:osx\"]\n  tags: [osx, qt]\n\npackage:win64:\n  stage: package\n  variables:\n    MZNARCH: \"win64\"\n    ISSARCH: \"x64\"\n    ISSARCHALLOWED: \"x64\"\n    CODESIGN_CMD: signtool.exe sign /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 /dlib $WIN_CODESIGN_DLIB /dmdf $WIN_CODESIGN_METADATA\n  before_script:\n    ### Set redist variables\n    - for /d %%a in (\"%VCToolsRedistDir%\\\\x64\\\\*.CRT\") do set MSVCREDIST=%%a\n    - set UCRTREDIST=%WindowsSdkDir%\\Redist\\ucrt\\DLLs\\x64\n    ### Set the MZNVERSION variable\n    - if defined CI_COMMIT_TAG (set MZNVERSION=%CI_COMMIT_TAG%) else (set MZNVERSION=%CI_PIPELINE_ID%)\n    ### Choose the MiniZinc compiler branch\n    - if defined CI_COMMIT_TAG (set MZNREF=%CI_COMMIT_TAG%) else if %CI_COMMIT_REF_NAME%==master (set MZNREF=master) else (set MZNREF=develop)\n    - if defined CI_COMMIT_TAG (set FINDMUSREF=master) else if %CI_COMMIT_REF_NAME%==master (set FINDMUSREF=master) else (set FINDMUSREF=develop)\n    - if defined CI_COMMIT_TAG (set ANALYSEREF=master) else if %CI_COMMIT_REF_NAME%==master (set ANALYSEREF=master) else (set ANALYSEREF=develop)\n    ### Download Dependencies\n    - curl --silent -o minizinc.zip --location --header \"PRIVATE-TOKEN:%ACCESS_TOKEN%\" \"https://gitlab.com/api/v4/projects/minizinc%%2Fminizinc/jobs/artifacts/%MZNREF%/download?job=build:%MZNARCH%\"\n    - unzip -q minizinc.zip\n    - curl --silent -o vendor.zip --location --header \"PRIVATE-TOKEN:%ACCESS_TOKEN%\" \"https://gitlab.com/api/v4/projects/minizinc%%2Fminizinc-vendor/jobs/artifacts/master/download?job=bundle:%MZNARCH%\"\n    - unzip -q vendor.zip\n    - curl --silent -o globalizer.zip --location --header \"PRIVATE-TOKEN:%ACCESS_TOKEN%\" \"https://gitlab.com/api/v4/projects/minizinc%%2Fglobalizer/jobs/artifacts/master/download?job=build:%MZNARCH%\"\n    - unzip -q globalizer.zip\n    - curl --silent -o findmus.zip --location --header \"PRIVATE-TOKEN:%ACCESS_TOKEN%\" \"https://gitlab.com/api/v4/projects/minizinc%%2FFindMus/jobs/artifacts/%FINDMUSREF%/download?job=build:%MZNARCH%\"\n    - unzip -q findmus.zip\n    - curl --silent -o mzn-analyse.zip --location --header \"PRIVATE-TOKEN:%ACCESS_TOKEN%\" \"https://gitlab.com/api/v4/projects/minizinc%%2Fmzn-analyse/jobs/artifacts/%ANALYSEREF%/download?job=build:%MZNARCH%\"\n    - unzip -q mzn-analyse.zip\n    ### Turn on code signing if variables present\n    - if defined WIN_CODESIGN_METADATA (set DOCODESIGN=1) else (set DOCODESIGN=0)\n  script:\n    - '\"C:/Program Files (x86)/Inno Setup 6/ISCC.exe\" /dMyAppVersion=\"%MZNVERSION%\" /dMyMZNVersion=\"%MZNVERSION%\" /dMyAppDirectory=\"%CI_PROJECT_DIR%\" /dMyMSVCRedist=\"%MSVCREDIST%\" /dMyUCRTRedist=\"%UCRTREDIST%\" /dMyAPPArch=\"%MZNARCH%\" /dMyApp64Bit=\"%ISSARCH%\" /dMyAppArchitectures=\"%ISSARCHALLOWED%\" /dDoCodeSign=\"%DOCODESIGN%\" /SMyCodeSignTool=\"%CODESIGN_CMD% $f\" /O\"%CI_PROJECT_DIR%\" resources/pkg_config/minizinc-bundle.iss'\n    ### Generate checksum\n    - certutil -hashfile MiniZincIDE-%MZNVERSION%-bundled-setup-%MZNARCH%.exe SHA256 > MiniZincIDE-%MZNVERSION%-bundled-setup-%MZNARCH%.sha256\n  artifacts:\n    name: \"minizinc_bundle_windows_%CI_PIPELINE_ID%\"\n    paths: [MiniZincIDE*.exe, MiniZincIDE*.sha256]\n  needs: [\"build:win64\"]\n  tags: [win64]\n\npackage:appimage:\n  stage: package\n  image: ghcr.io/minizinc/docker-build-environment:qt\n  variables:\n    MZNARCH: \"linux\"\n    DOWNLOAD_GLOBALIZER: 1\n    DOWNLOAD_FINDMUS: 1\n    DOWNLOAD_ANALYSE: 1\n    PACKAGE: \"MiniZinc.AppDir\"\n  <<: *packaging_setup\n  script:\n    - mkdir -p $PACKAGE/usr/lib\n    ### Package IDE\n    - mv ide/usr/* $PACKAGE/usr/\n    ### Package MiniZinc\n    - mv minizinc/bin/* $PACKAGE/usr/bin/\n    - mv minizinc/share $PACKAGE/usr/share\n    ### Package vendor solvers\n    - mv vendor/gecode_gist/bin/fzn-gecode $PACKAGE/usr/bin/\n    - cp -r vendor/gecode_gist/share/minizinc/* $PACKAGE/usr/share/minizinc/\n    - mv vendor/chuffed/bin/fzn-chuffed $PACKAGE/usr/bin/\n    - cp -r vendor/chuffed/share/minizinc/* $PACKAGE/usr/share/minizinc/\n    - mv vendor/or-tools/bin/fzn-cp-sat $PACKAGE/usr/bin/\n    - cp -r vendor/or-tools/share/minizinc/* $PACKAGE/usr/share/minizinc/\n    - cp vendor/highs/lib64/libhighs.so $PACKAGE/usr/lib/\n    ### Package Globalizer\n    - mv globalizer/bin/minizinc-globalizer $PACKAGE/usr/bin/\n    - cp -r globalizer/share/minizinc/* $PACKAGE/usr/share/minizinc/\n    ### Package findMUS\n    - mv findMUS/bin/findMUS $PACKAGE/usr/bin/\n    - cp -r findMUS/share/minizinc/* $PACKAGE/usr/share/minizinc/\n    ### Package mzn-analyse\n    - mv mzn-analyse/bin/mzn-analyse $PACKAGE/usr/bin/\n    ### Strip included binaries\n    - (cd $PACKAGE/usr/bin; strip minizinc fzn-gecode fzn-chuffed fzn-cp-sat findMUS mzn-analyse minizinc-globalizer mzn2doc)\n    - cp resources/misc/README $PACKAGE\n    ### Assemble AppImage\n    - cp resources/scripts/AppRun $PACKAGE\n    - cp resources/misc/minizinc.desktop $PACKAGE/minizinc.desktop\n    - cp resources/icon.png $PACKAGE/minizinc.png\n    - ARCH=x86_64 appimagetool $PACKAGE MiniZincIDE-${MZNVERSION}-x86_64.AppImage\n    ### Generate checksum\n    - sha256sum MiniZincIDE*.AppImage > MiniZincIDE-${MZNVERSION}-x86_64.sha256\n  artifacts:\n    name: \"minizinc_appimage_${CI_PIPELINE_ID}\"\n    paths: [MiniZincIDE*.AppImage, MiniZincIDE*.sha256]\n  needs: [\"build:linux\"]\n  tags: [linux, docker]\n\n.docker_setup: &docker_setup\n  image: ghcr.io/minizinc/docker-build-environment:docker-cli\n  before_script:\n    ### Set the MZNVERSION variable\n    - 'if [ -n \"$CI_COMMIT_TAG\" ]; then MZNVERSION=\"$CI_COMMIT_TAG\"; else MZNVERSION=\"build$CI_PIPELINE_ID\"; fi'\n    ### Choose the MiniZinc compiler branch\n    - 'if [ -n \"$CI_COMMIT_TAG\" ]; then MZNREF=\"$CI_COMMIT_TAG\"; elif [ \"$CI_COMMIT_REF_NAME\" = \"master\" ]; then MZNREF=\"master\"; else MZNREF=\"develop\"; fi'\n    ### Download Dependencies\n    - mkdir -p linux/amd64 && cd linux/amd64\n    - curl --silent -o minizinc.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2Fminizinc/jobs/artifacts/$MZNREF/download?job=build:$MZNARCH\" && unzip -q minizinc.zip\n    - curl --silent -o vendor.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2Fminizinc-vendor/jobs/artifacts/master/download?job=bundle:$MZNARCH\" && unzip -q vendor.zip\n    - mkdir -p ../arm64 && cd ../arm64\n    - curl --silent -o minizinc.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2Fminizinc/jobs/artifacts/$MZNREF/download?job=build:$MZNARCH-arm64\" && unzip -q minizinc.zip\n    - curl --silent -o vendor.zip --location --header \"PRIVATE-TOKEN:$ACCESS_TOKEN\" \"https://gitlab.com/api/v4/projects/minizinc%2Fminizinc-vendor/jobs/artifacts/master/download?job=bundle:$MZNARCH-arm64\" && unzip -q vendor.zip\n    - cd ../../\n\npackage:docker_alpine:\n  stage: package\n  variables:\n    MZNARCH: \"musl\"\n  <<: *docker_setup\n  script:\n    - echo \"Building image based on \\\"alpine:latest\\\" with tag extension \\\"-alpine\\\"\"\n    - if [ -n \"$CI_COMMIT_TAG\" ]; then IMAGE_TAG=\"minizinc/minizinc:${CI_COMMIT_TAG}-alpine\"; fi\n    - if [ \"$CI_COMMIT_REF_NAME\" = \"master\" ]; then IMAGE_TAG=minizinc/minizinc:latest-alpine; fi\n    - if [ \"$CI_COMMIT_REF_NAME\" = \"develop\" ]; then IMAGE_TAG=minizinc/minizinc:edge-alpine; fi\n    - if [ -n \"$IMAGE_TAG\" ]; then BUILDX_ARGS=\"--push -t $IMAGE_TAG\"; fi\n    - docker buildx build $BUILDX_ARGS --pull --builder=container --platform=linux/amd64,linux/arm64 -f resources/pkg_config/Dockerfile --build-arg BASE='alpine:latest' .\n  needs: []\n  tags: [linux-arm64, mac-mini]\n\npackage:docker_ubuntu:\n  stage: package\n  parallel: 3\n  variables:\n    MZNARCH: \"linux\"\n  <<: *docker_setup\n  script:\n    - BASES=(\"null\" \"ubuntu:latest\" \"ubuntu:noble\" \"ubuntu:jammy\")\n    - EXTS=(\"null\" \"\" \"-noble\" \"-jammy\")\n    - echo \"Building image based on \\\"${BASES[$CI_NODE_INDEX]}\\\" with tag extension \\\"${EXTS[$CI_NODE_INDEX]}\\\"\"\n    - if [ -n \"$CI_COMMIT_TAG\" ]; then IMAGE_TAG=minizinc/minizinc:${CI_COMMIT_TAG}${EXTS[$CI_NODE_INDEX]}; fi\n    - if [ \"$CI_COMMIT_REF_NAME\" = \"master\" ]; then IMAGE_TAG=minizinc/minizinc:latest${EXTS[$CI_NODE_INDEX]}; fi\n    - if [ \"$CI_COMMIT_REF_NAME\" = \"develop\" ]; then IMAGE_TAG=minizinc/minizinc:edge${EXTS[$CI_NODE_INDEX]}; fi\n    - if [ -n \"$IMAGE_TAG\" ]; then BUILDX_ARGS=\"--push -t $IMAGE_TAG\"; fi\n    - docker buildx build $BUILDX_ARGS --pull --builder=container --platform=linux/amd64,linux/arm64 -f resources/pkg_config/Dockerfile --build-arg BASE=${BASES[$CI_NODE_INDEX]} .\n  needs: []\n  tags: [linux-arm64, mac-mini]\n\nminizinc:linux:nogui:\n  image: ghcr.io/minizinc/docker-build-environment:package\n  stage: package\n  variables:\n    MZNARCH: \"linux\"\n  <<: *packaging_setup\n  script:\n    - PACKAGE=MiniZinc-${MZNVERSION}-linux-x86_64\n    - mkdir -p $PACKAGE/bin\n    - mkdir -p $PACKAGE/lib\n    ### Package MiniZinc\n    - mv minizinc/bin/* $PACKAGE/bin/\n    - mv minizinc/share $PACKAGE/share\n    ### Package vendor solvers\n    - mv vendor/gecode/bin/fzn-gecode $PACKAGE/bin/\n    - cp -r vendor/gecode/share/minizinc/* $PACKAGE/share/minizinc/\n    - mv vendor/chuffed/bin/fzn-chuffed $PACKAGE/bin/\n    - cp -r vendor/chuffed/share/minizinc/* $PACKAGE/share/minizinc/\n    - mv vendor/or-tools/bin/fzn-cp-sat $PACKAGE/bin/\n    - cp -r vendor/or-tools/share/minizinc/* $PACKAGE/share/minizinc/\n    - cp vendor/highs/lib64/libhighs.so $PACKAGE/lib/\n    ### Strip included binaries\n    - (cd $PACKAGE/bin; strip minizinc fzn-gecode fzn-chuffed fzn-cp-sat mzn2doc)\n    ### Compress package\n    - tar -czf $PACKAGE.tar.gz $PACKAGE\n  artifacts:\n    name: \"minizinc_linux_nogui_${CI_PIPELINE_ID}\"\n    paths: [MiniZinc*.tar.gz]\n  needs: []\n  tags: [linux, docker]\n\n.snap_job: &snap_job\n  stage: package\n  image: ghcr.io/minizinc/docker-build-environment:snap\n  variables:\n    GIT_SUBMODULE_STRATEGY: recursive\n    MZNARCH: \"linux\"\n    DOWNLOAD_GLOBALIZER: 1\n    DOWNLOAD_FINDMUS: 1\n    DOWNLOAD_ANALYSE: 1\n  <<: *packaging_setup\n  script:\n    - strip minizinc/bin/minizinc vendor/gecode_gist/bin/fzn-gecode vendor/chuffed/bin/fzn-chuffed vendor/or-tools/bin/fzn-cp-sat findMUS/bin/findMUS mzn-analyse/bin/mzn-analyse globalizer/bin/minizinc-globalizer minizinc/bin/mzn2doc\n    - cp resources/pkg_config/snapcraft.yaml .\n    - apt-get update -y\n    - snapcraft --destructive-mode\n  artifacts:\n    name: \"minizinc_snap_${CI_PIPELINE_ID}\"\n    paths: [minizinc*.snap]\n  needs: [\"build:linux\"]\n  tags: [linux, docker]\n\npackage:snap:\n  <<: *snap_job\n  only: [tags, master, develop]\n\npackage:snap_manual:\n  <<: *snap_job\n  except: [tags, master, develop]\n  when: manual\n\n.snap_publish: &snap_publish\n  stage: publish\n  image: ghcr.io/minizinc/docker-build-environment:snap\n  tags: [linux, docker]\n  script:\n    - snapcraft upload minizinc*.snap --release $SNAPCRAFT_CHANNEL\n  needs: [\"package:snap\"]\n\npublish:snap_edge:\n  variables:\n    SNAPCRAFT_CHANNEL: edge\n  <<: *snap_publish\n  only: [develop]\n\npublish:snap_stable:\n  variables:\n    SNAPCRAFT_CHANNEL: stable\n  <<: *snap_publish\n  only: [tags]\n  when: manual # Don't publish to stable channel automatically\n\npublish:github_edge:\n  stage: publish\n  image: ghcr.io/minizinc/docker-build-environment:package\n  variables:\n    REMOTE_URL: https://minizinc-ci:${ACCESS_TOKEN}@gitlab.com/minizinc/minizinc-ide.git\n  script:\n    # Update edge tag\n    - git config user.name \"$GITLAB_USER_NAME\"\n    - git config user.email \"$GITLAB_USER_EMAIL\"\n    - git remote add gitlab_remote \"${REMOTE_URL}\" || git remote set-url gitlab_remote \"${REMOTE_URL}\"\n    - git tag -f -a -m \"Development build of MiniZinc\" edge\n    - git push -f gitlab_remote edge -o ci.skip\n    # Remove old assets\n    - gh release --repo MiniZinc/MiniZincIDE view edge --json assets --jq '.assets[].name' | xargs -n 1 -r gh release --repo MiniZinc/MiniZincIDE delete-asset -y edge\n    # Upload new assets\n    - gh release --repo MiniZinc/MiniZincIDE upload edge MiniZincIDE*.dmg MiniZincIDE*.exe MiniZincIDE*.AppImage MiniZincIDE*.tgz MiniZincIDE*.sha256\n  needs: [\"package:osx\", \"package:win64\", \"package:appimage\", \"package:linux\"]\n  only: [develop]\n  tags: [linux, docker]\n\npublish:github_stable:\n  stage: publish\n  image: ghcr.io/minizinc/docker-build-environment:package\n  script:\n    - NOTES=\"This release\"\n    - '[ \"$(echo \"$CI_COMMIT_TAG\" | cut -d. -f3)\" == \"0\" ] && NOTES=\"$NOTES adds several new features and\"'\n    - NOTES=\"$NOTES fixes a number of bugs, see https://docs.minizinc.dev/en/${CI_COMMIT_TAG}/changelog.html for a full change log. Binary releases for different platforms are available in the bundled packages of the MiniZinc IDE at https://github.com/minizinc/minizincide/releases.\"\n    - gh release --repo MiniZinc/libminizinc create --draft --title \"MiniZinc $CI_COMMIT_TAG\" --notes \"$NOTES\" \"$CI_COMMIT_TAG\"\n    - echo \"Please refer to the change log for details:\" > ide_notes\n    - echo \"https://docs.minizinc.dev/en/${CI_COMMIT_TAG}/changelog.html\" >> ide_notes\n    - gh release --repo MiniZinc/MiniZincIDE create --draft --title \"MiniZinc $CI_COMMIT_TAG\" --notes-file ide_notes \"$CI_COMMIT_TAG\" MiniZincIDE*.dmg MiniZincIDE*.exe MiniZincIDE*.AppImage MiniZincIDE*.tgz MiniZincIDE*.sha256\n  needs: [\"package:osx\", \"package:win64\", \"package:appimage\", \"package:linux\"]\n  only: [tags]\n  except: [edge]\n  tags: [linux, docker]\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in \n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "MiniZincIDE/CHANGES",
    "content": "2026-04-30\n  v2.9.7\n-  Update to MiniZinc 2.9.7.\n\n2026-04-24\n  v2.9.6\n-  Fix syntax highlighting of the `list` keyword (#227).\n\n2026-01-23\n  v2.9.5\n-  Update to MiniZinc 2.9.5.\n\n2025-09-29\n  v2.9.4\n-  Update to MiniZinc 2.9.4.\n\n2025-05-23\n  v2.9.3\n-  Suppress warnings generated when running compiled solution checkers.\n\n2025-03-06\n  v2.9.2\n-  Fix the packaging of the OR Tools solver on linux distributions.\n\n2025-03-03\n  v2.9.1\n-  Ensure locations for warnings and errors are shown when the stack dump is empty (#212).\n-  Use scroll buttons when the tab bar overflows to ensure the side panels can be resized (#213).\n\n2025-02-11\n  v2.9.0\n-  Fix broken documentation link in the help menu item.\n\n2024-10-02\n  v2.8.7\n-  Update to MiniZinc 2.8.7.\n\n2024-09-25\n  v2.8.6\n-  Update to MiniZinc 2.8.6.\n\n2024-05-03\n  v2.8.5\n-  Update to MiniZinc 2.8.5.\n\n2024-05-10\n  v2.8.4\n-  Fix bundled OpenSSL libraries on Windows.\n-  Allow MOOC submission window to be scrolled.\n-  Increase hard process termination timeout to 1s.\n\n2024-02-01\n  v2.8.3\n-  Increase maximum number of threads from default Qt limit (#196).\n\n2023-12-15\n  v2.8.2\n-  Make process handling more robust to failures.\n\n2023-11-27\n  v2.8.1\n-  Fix command used to run findMUS and Globalizer.\n-  Add ability to set the ports used for the visualisation server.\n-  Add option for printing the visualisation server URL for debugging purposes.\n-  Add more information to subprocess error messages.\n\n2023-11-16\n  v2.8.0\n-  Fix unreadable cheat sheet font colour in dark mode (#191).\n-  Add option to output objective value and enable by default.\n-  Show manually input parameters in output window.\n-  Fix missing checker messages (#192).\n-  Fix incorrect OpenSSL version in Linux packages (#189).\n\n2023-05-20\n  v2.7.6\n-  Update to MiniZinc 2.7.6.\n\n2023-06-07\n  v2.7.5\n-  Use native Qt dark mode on Windows where supported.\n-  Improve behaviour of the custom solver parameter dialog.\n\n2023-05-11\n  v2.7.4\n-  Update to MiniZinc 2.7.4.\n\n2023-04-20\n  v2.7.3\n-  Only show MOOC error code when response is actually an error (#176).\n\n2023-04-05\n  v2.7.2\n-  Fix patching of `RPATH` for binaries and libraries in Snap package.\n\n2023-03-31\n  v2.7.1\n-  Fix highlighting of multiline comments starting with /*/ (#172).\n-  Fix bundling of incompatible OpenSSL version in linux packages.\n-  Remove support for glibc 2.27 and earlier from AppImage and tarball linux\n   packages. The Snap package may be used instead on such systems.\n\n2023-02-23\n  v2.7.0\n-  Fix a bug where model selection dialog could run the wrong model.\n-  Fix a bug where the same data file could be added to the MiniZinc command twice.\n-  Ensure user config directory is created when modifying solver search paths (#167).\n-  Ensure that IDE windows cannot spawn off-screen.\n-  Add tooltips to the CP-Profiler status bar legend.\n-  Add support for mooc submissions which include file editing history.\n\n2022-06-23\n  v2.6.4\n-  Ensure the extra parameter filter is cleared when the textbox is cleared.\n\n2022-05-06\n  v2.6.3\n-  Improve UI and dark mode for CP Profiler.\n-  Fix CP Profiler tree-builder signal/slot connection (#160).\n-  Fix deadlock in CP Profiler tree building (#162).\n-  Make project loading more resilient to errors (#165).\n\n2022-03-22\n  v2.6.2\n-  Don't print expected error messages for MOOC submissions.\n-  Fix custom parameter widget dark mode CSS.\n\n2022-03-03\n  v2.6.1\n-  Fix crash when the solver for an unsaved configuration is removed.\n-  Fix bug where the selected solver could incorrectly change when a\n    configuration option is altered.\n\n2022-02-18\n  v2.6.0\n-  Add support for specifying submission terms for MOOC.\n-  Ensure newly loaded configs override synced options (#144).\n-  Fix check for empty project to avoid incorrect warnings when closing.\n-  Maintain modified solver configurations when using preferences dialog.\n-  Support using arm64 version of MiniZinc with x86_64 IDE build on macOS.\n-  Fix crash when no solver configurations are available.\n-  Remove WebEngine-based visualisation feature and implement HTTP/WebSocket\n   server based visualisations.\n-  Add support for dark mode detection on Windows.\n-  Implement foldable output widget supporting output sections.\n-  Support both Qt 5 and Qt 6.\n-  Allow tab to shift multiple lines right.\n-  Re-implement support for detached solver processes.\n-  Allow the project/solver configuration panes to take the full height of the\n   main window.\n-  Implement new multi-tabbed preferences dialog.\n-  Ignore errors in non-current files during background code checking.\n-  Fix undefined behaviour in main window event filter (#154).\n-  Fix crash when terminating solvers due to closing the main window.\n-  Confirm before removing files from project (#149).\n\n2021-03-19\n  v2.5.5\n-  Fix editing of custom string parameters so they don't get converted to\n   floats.\n-  Fix crash on Windows caused when the PATH environment contains unicode\n   characters.\n\n2021-03-16\n  v2.5.4\n-  Fix possible crash due to incorrect use of WriteFile on Windows.\n-  Ensure Gecode Gist dependencies are present in the Linux bundle and AppImage\n   (#132).\n-  Fix crash when stopping solver during exit.\n-  Don't show irrelevant context menu entries in the project explorer.\n-  Add support for HTTP/S links in the output pane.\n-  Fix crash when saving CP Profiler executions where there is no info\n   associated with a node.\n-  Show a warning when there are open files which are not part of a MOOC\n   submission.\n-  Fix double spinbox precision issues (#134).\n-  Include Gecode Gist and CP Profiler dependencies in Snap package.\n-  Allow opening of multiple files through the open file menu option.\n-  Ensure file dialogs save last path when opening files.\n-  Make the escape key close the find/replace dialog when focussed on any child\n   widget.\n-  Allow setting MOOC submission items as mandatory.\n\n2020-24-06\n  v2.5.3\n-  Only reset config window item focus if it is still focused, preventing spurious\n   changes in focus during code checking.\n-  Fix handling of final statuses, including UNSAT (#123).\n-  Remove -s flag support from Gecode Gist solver configuration (#125).\n-  Fix crash when saving a project with no solver selected (#127).\n-  Correctly remove temporary parameter configuration files after use (#128, #129).\n-  Fix the time limit readout in the status bar when solving.\n\n2020-11-06\n  v2.5.2\n  - Properly resize extra flags table after adding parameters (#119).\n  - Use the minimal configuration to check the model interface (#118).\n  - Allow omitting builtin solver version in project JSON.\n  - Don't mark as modified when loading non-synced solver configurations.\n  - Ensure the last open configuration in a project is selected when loaded.\n  - Fix the default values of solution truncation and output window clearing.\n  - Process unrecognised extra flags from old project configurations.\n  - Fix watching for modification of the additional data box.\n  - Fix the alignment of line numbers.\n  - Make behaviour controls more narrow to accommodate smaller window sizes.\n  - Defocus config window widgets when updating solver config so values of\n    currently edited fields are updated.\n  - Pass user input data correctly during compilation.\n  - Remove solns2out options from MiniZinc call when compiling.\n\n2020-10-22\n  v2.5.1\n  - Fix typo when passing solver statistics option to minizinc (#112).\n  - Fix missing statistics output (#112).\n  - Add support for colour themes (#110).\n  - Don't prompt for saving after adding/removing files from the Untitled\n    project.\n  - Fix running of compiled FlatZinc files.\n  - Show error message when trying to load an invalid configuration file.\n  - Ensure all output is sent to the output console, and that fragments in\n    standard error output appear when a newline is written to standard output\n    (#114).\n  - Fix running of solver configurations from the project explorer.\n  - Improve performance of adding a large number of extra flags at once.\n  - Add support for 64-bit integer extra flags.\n  - Add support for setting both solver backend flags and MiniZinc command flags\n    (#113).\n  - Improve interface for adding extra parameters, allowing search/filter and\n    multiselection of known parameters.\n\n2020-09-25\n  v2.5.0\n  - Add fallback libraries if user does not have libnss3.\n  - Remove support for the old binary storage format of projects used prior to\n    version 2.2.0.\n    These must be opened and re-saved with version 2.4.3 to remain compatible.\n  - Include experimental CP-profiler through the *MiniZinc* > *Profile search*\n    option for supported solvers.\n  - Redesign the solver configuration window.\n  - Use parameter configuration files rather than passing command-line options\n    directly.\n  - Show solver configurations and checkers in their own sections in the\n    project explorer.\n  - Allow multiselection in the project explorer for running particular sets\n    of files.\n  - Allow MiniZinc to manage subprocesses by itself.\n  - Allow non-privileged installs of the IDE on Windows.\n  - Correctly remove files from old installations of the IDE on Windows.\n  - Enable scroll bars in the preferences dialog to allow for low resolution\n    displays.\n  - Prompt to save modified files before performing MOOC submissions or running\n    models.\n  - Fix infinite recursion when a model file for a MOOC submission doesn't\n    exist.\n  - Use --output-mode checker for MOOC solution submission where supported.\n  - Fully support unicode on Windows.\n\n2020-03-03\n  v2.4.3\n  - Disable menu items that don't make sense when all tabs are closed,\n    fix behaviour of stop button when all tabs closed (fixes several crashes).\n  - Add x86_64 suffix to linux package name (#96).\n  - Make boolean extra solver options with a default of true functional.\n  - Only read linter results if it exited normally (#97).\n  - Update alpine version to 3.11\n  - Resolve paths in _mooc to paths\n    (allowing submission of models in subdirectories).\n2020-01-10\n  v2.4.2\n  - Fix syntax highlighting of keywords, and add syntax highlighting for\n    interpolated strings.\n  - Redraw when switching to/from dark mode, and fix dark mode header colours.\n  - Fix \"Select all\" menu item.\n2019-12-09\n  v2.4.1\n  - Display error message when submission to MOOC provider fails.\n  - Fix shift left and shift right indentation behaviour when selecting\n    text backwards.\n  - Make \"previous tab\" and \"next tab\" actions cycle rather than stop at\n    first/last tab.\n  - Fix OpenSSL library in binary distribution to enable update checks and\n    submission to MOOCs again.\n  v2.4.0\n  - Parse timing and statistics output produced by compiler, and display as\n    profiling information next to each line in the model.\n  - Enable run/compile action on data files. This automatically selects the\n    model file if there is only one, or presents a dialog for selecting the\n    model if there are multiple.\n  - Select first data file in parameter dialog if there was no previous\n    selection, and always focus parameter dialog.\n  - Fix dark mode detection on macOS 10.15, improve dark mode colors a bit\n    and fixed some dark mode bugs.\n  - Make background compilation of a model (used to display syntax and type\n    errors) a bit more stable.\n  - Highlight current line.\n  - Support .json as file extension for data files.\n  - Remember whether wrap around, case sensitivity and regular expression\n    was selected in find/replace dialog, pre-select the find/replace text\n    when find/replace widget is openend, and close find/replace widget when\n    ESC is pressed while editor has focus.\n  - Avoid infinite loop in wrap around replace all.\n  - Fix memory management for HTML visualisation windows, and resize docked\n    HTML visualisation widgets to take up equal space.\n2019-09-12\n  v2.3.2\n  - Update to MiniZinc 2.3.2.\n2019-07-10\n  v2.3.1\n  - Remove incorrect symbolic link and fix qt.conf for some bundled distributions.\n  - Fix check for availability of dark mode on older versions of macOS.\n  - Fix a typo in the cheat sheet.\n  - Provide more robust solution for checking the model parameters, which will get\n    rid of some \"internal error\" messages.\n  - Always show directory selection dialog in the Windows installer. Addresses #89.\n  - Improved the configuration files for some bundled solvers, provides nicer\n    configuration interface.\n2019-06-26\n  v2.3.0\n  - The IDE will now check MiniZinc code for syntax and type errors\n  - The editor performs simple code completion for MiniZinc keywords\n  - Ensure cursor is visible (editor scrolls to cursor position) when pressing\n    tab or enter. Fixes #71.\n  - Replace find dialog with inline widget and incremental search.\n  - Support dark mode on macOS.\n  - Add support for extra solver flags (parsed from solver configuration).\n  - IDE now only uses minizinc executable (not mzn2fzn and solns2out).\n  - Re-dock configuration editor when closing un-docked window.\n  - Handle quotes when parsing additional solver command line arguments.\n    Fixes #77.\n  - Add workaround for the missing libnss requirements\n  - Allow spaces in $DIR in MiniZincIDE.sh (Fixes #81)\n2018-10-31\n  v2.2.3\n  - Only run solution checker if it is enabled in the solver configuration dialog.\n2018-10-26\n  v2.2.2\n  - Add line/column display in status bar. Fixes #65.\n  - Optional parameters don't have to be defined in input dialog.\n  - Fix race condition in constructor of HTMLWindow. Fixes #64.\n  - Provide mzn-json-init / mzn-json-init-end handlers to initialise HTML window\n    before first solution is produced.\n  - Add version information and minimum system version into Info.plist on macOS.\n    Fixes #66.\n  - Manage multiple open visualisation windows, and implement re-solve function\n    that can be initiated from a visualisation.\n2018-09-06\n  v2.2.1\n  - Improve dark mode by changing line numbers to dark background.\n  - Make parameter input dialog scrollable.\n  - Fix solution compression limit, and output one solution per\n    block of compressed solutions.\n2018-08-24\n  v2.2.0\n  - Update to MiniZinc 2.2.0.\n  - Change solver configuration interface to work with new MiniZinc 2.2.0\n    solver configurations.\n  - Add support for solution checker models.\n  - Better process management (to make sure solvers are terminated).\n  - Change project files to be based on JSON format.\n  - Better support for solver HTML output (used e.g. for Globalizer and FindMUS).\n  - Fix shift left/shift right functionality.\n  - Support for running models without saving them first.\n  - Fix file dialogs to use correct file extensions.\n2018-01-10\n  v2.1.7\n  - Update to MiniZinc 2.1.7.\n  - Fix problem where files with a . in the filename could not be run (bug #44).\n  - Fix font settings (were not saved reliably on some platforms).\n  - Enable generic interface for submitting assignments (not just to Coursera).\n  - Fix output handling for solvers that do not run mzn2fzn.\n  - Fix hidden solution display when there are exactly as many solutions as the\n    configured threshold for hiding solutions (bug #42).\n  - Add configuration option to print timing information for each solution.\n2017-09-22\n  v2.1.6\n  - Update to MiniZinc 2.1.6.\n2017-05-17\n  v2.1.5\n  - Update to MiniZinc 2.1.5.\n  - Fix an issue where solver output may not get printed if it occurs too\n    quickly after the solver has started.\n2017-03-16\n  v2.1.4\n  - Update to MiniZinc 2.1.4.\n  - Fix major race condition that would crash the IDE when it didn't detect\n    that a solver process had finished.\n  - Improve HTML output by making sure every line is terminated by a newline.\n2017-02-06\n  v2.1.3\n  - Update to MiniZinc 2.1.3.\n  - Avoid crashes and print error messages when mzn2fzn subprocess crashes.\n  - Changed meaning of \"User-defined behavior\" options, to have a clear\n    distinction between optimisation and satisfaction problems.\n  - Fix buffering of error output from mzn2fzn process (which would sometimes\n    not be printed to the output window).\n  - Suppress output after configurable number of solutions (to avoid\n    overloading the IDE output box).\n2016-12-20\n  v2.1.2\n  - Update to MiniZinc 2.1.2.\n2016-12-14\n  v2.1.1\n  - Add option to print mzn2fzn statistics to project configuration.\n  - Update to MiniZinc 2.1.1.\n2016-11-16\n  v2.1.0\n  - Add new bundled solvers: Chuffed, CBC, Gurobi\n  - Change update check to use Google Analytics (opt-in)\n  - Fix a crash in the syntax highlighter when changing documents (e.g when\n    saving a previously unsaved file).\n  - Fix buffering problems on Windows (could lead to solver output not being\n    shown).\n  - Fix a crash when stopping a long-running compilation.\n2016-08-30\n  v2.0.97\n  - Update to include MiniZinc 2.0.97 beta release.\n2016-07-31\n  v2.0.14\n  - Implement new Coursera submission system.\n  - Reload list of data files after removing a file through the project view,\n    and use persistent indices to fix file removal. Fixes #11.\n  - Fix renaming of files through the project explorer. Renaming should now\n    work using the usual platform editing key, and using the context menu\n    option. Fixes #12.\n  - Don't add empty file name to list of data files when user cancels file\n    dialog. Fixes #13.\n  - Add same leading white space as on current line when pressing return\n    (maintain indenting).\n  - Add support for new Qt WebEngine framework (since Qt WebKit is not available\n    in Qt 5.6).\n2016-03-26\n  v2.0.13\n  - Flush output more consistently when process finished, hopefully\n    fixing problem where solutions were missing from output.\n  - Updated to include MiniZinc 2.0.13\n2016-02-26\n  v2.0.12\n  - Fix link to MiniZinc issue tracker.\n  - Add configuration option to clear solver output for each run.\n  - Remember whether previous run used data file or manual data input.\n  - Updated to include MiniZinc 2.0.12\n2016-01-15\n  v2.0.11\n  - Updated to include MiniZinc 2.0.11\n2015-12-10\n  v2.0.10\n  - Updated to include MiniZinc 2.0.10\n2015-12-07\n  v2.0.9\n  - Remove (unimplemented) menu item for adding files to a project.\n  - Fix version number comparison to work for multi-digit minor and patch\n    versions.\n2015-10-19\n  v2.0.8\n  - Only disable the run and compile actions when a solving process is currently\n    running (keep editor and rest of the user interface enabled).\n  - Keep editor font setting synchronised across different IDE windows.\n2015-10-06\n  v2.0.7\n  - Changed version number scheme to coincide with MiniZinc version.\n  - Disable all editing while the solver is running (avoids race conditions).\n  - Fix behaviour of stop button: avoid race condition when pressing it twice,\n    and signal that process has stopped when the initial SIGINT was successful.\n  - Split up extra mzn2fzn command line arguments so they get passed correctly.\n  - Changed behaviour of MiniZinc path input (now doesn't check for presence\n    of mzn2fzn every time the cursor leaves the input box).\n  - Add configuration option to use \"default behaviour\" when running models,\n    which is to output all intermediate solutions for optimisation problems,\n    and stop after one solution for satisfaction problems.\n  - Fix \"Add solver\" dialog (sometimes the option to add solvers would\n    disappear from the drop-down menu).\n  - On Windows, configure Gecode/Gist to run the correct batch file.\n  - Fix a crash when activating the (un)comment or go to line actions while\n    on the configuration tab.\n  - Avoid opening multiple \"File modified\" dialogs for the same file.\n2015-07-30\n  v0.9.9\n  - Fix for clicking on error messages on Windows\n  - Fix syntax highlighting, used to turn itself off when saving file\n    under a different name\n  - Set default font more consistently on different platforms\n2015-07-01\n  v0.9.8\n  - Add \"dark mode\" to change text colours\n  - Various bug fixes\n  - Add integration with Coursera MiniZinc course\n2015-05-12\n  v0.9.7\n  - Various bug fixes\n  - Improved compatibility with MiniZinc 2.0.2\n  - Bundled binary release (includes MiniZinc and some solvers)\n2015-01-10\n  v0.9.6\n  - When killing a process (using the Stop action or timeout), send CTRL-C\n    first, which allows the solver to exit gracefully. Solvers that do not\n    react to CTRL-C are killed as before.\n  - Fix copy/paste actions to also work in output window and to create\n    rich text (including syntax colouring)\n  - Avoid creating an empty .fzn file during compilation\n  - Accept drag-and-drop to main window, opens dragged files\n  - Quit menu action on Mac OS actually quits instead of just closing all\n    open windows\n  - Warn when loading large FlatZinc files\n  - Assume UTF8 text encoding when loading and saving files\n  - Keep selection of .dzn file in configuration tab when opening another\n    .dzn file\n2014-09-18\n  v0.9.5\n  - load new projects or files into existing project if it is empty\n    and unmodified (avoids opening a new window)\n  - fixed a bug when right-clicking into an empty area in the project\n    explorer\n  - fixed a bug when renaming files (in particular data files) in the\n    project explorer\n  - add support for multi-line (C-style) comments in the syntax\n    highlighting\n  - on Mac OS, show a Window menu to make it easy to switch between\n    open projects, and open all files dragged onto the dock icon\n    in the same project\n  - fixed the hard-coded library path for the G12 lazyfd solver\n  - fixed a bug that could cause a crash when closing and re-opening\n    a project\n2014-07-31\n  v0.9.4\n  - Project \"drawer\", better handling of projects\n  - Tool bar\n  - Limit additional arguments dialog to at most 10 parameters\n  - Show MiniZinc version information in configuration dialog\n  - More consistent handling of PATHs to MiniZinc and solvers\n  - Print name of data files and additional arguments when running\n    a solver\n  - On Mac OS, enable QuickLook for MiniZinc files\n  - Add recent files and projects to file menu\n  - File dialogs open in the previously used directory\n2014-04-01\n  v0.9.3\n  - Add elapsed time to output\n  - Fix crash for files given as command line arguments\n  - Make output window undockable\n  - Add check for updates on startup\n  - Fix bugs #4, #5, #8, #9\n2014-02-03\n  v0.9.2\n  - Add verbose solver option\n  - Save currently open project tab in project file\n  - Ignore empty additional model parameters\n  - Fix crash when closing modified Untitled tabs without saving\n  - Fix crash after error message from solver\n  - Fix project file format\n  - Ask for parameter input when compiling (not just when solving)\n2014-01-30\n  v0.9.1\n  Initial release\n"
  },
  {
    "path": "MiniZincIDE/MiniZincIDE.pri",
    "content": "QT       += core gui widgets websockets\n\nVERSION = 2.9.7\nDEFINES += MINIZINC_IDE_VERSION=\\\\\\\"$$VERSION\\\\\\\"\n\nbundled {\n    DEFINES += MINIZINC_IDE_BUNDLED\n}\n\noutput_version {\n    write_file($$OUT_PWD/version, VERSION)\n}\n\n!isEmpty(CXX_PREFIX) {\n    message(Using CXX_PREFIX = $$CXX_PREFIX)\n    QMAKE_CXX = $$CXX_PREFIX $$QMAKE_CXX\n}\n\nCONFIG += c++11\n\nmacx {\n    ICON = $$PWD/mznide.icns\n    OBJECTIVE_SOURCES += \\\n        $$PWD/darkmodenotifier_macos.mm\n    LIBS += -framework Cocoa\n    macx-xcode {\n      QMAKE_INFO_PLIST = $$PWD/mznide-xcode.plist\n    } else {\n      QMAKE_INFO_PLIST = $$PWD/mznide-makefile.plist\n    }\n}\n\nwin32 {\n    LIBS += -ladvapi32\n}\n\nRC_ICONS = $$PWD/mznide.ico\n\nCONFIG += embed_manifest_exe\n\nSOURCES += \\\n    $$PWD/codechecker.cpp \\\n    $$PWD/configwindow.cpp \\\n    $$PWD/darkmodenotifier.cpp \\\n    $$PWD/elapsedtimer.cpp \\\n    $$PWD/extraparamdialog.cpp \\\n    $$PWD/history.cpp \\\n    $$PWD/ide.cpp \\\n    $$PWD/ideutils.cpp \\\n    $$PWD/mainwindow.cpp \\\n    $$PWD/codeeditor.cpp \\\n    $$PWD/highlighter.cpp \\\n    $$PWD/fzndoc.cpp \\\n    $$PWD/outputwidget.cpp \\\n    $$PWD/preferencesdialog.cpp \\\n    $$PWD/process.cpp \\\n    $$PWD/profilecompilation.cpp \\\n    $$PWD/projectbrowser.cpp \\\n    $$PWD/server.cpp \\\n    $$PWD/gotolinedialog.cpp \\\n    $$PWD/paramdialog.cpp \\\n    $$PWD/outputdockwidget.cpp \\\n    $$PWD/checkupdatedialog.cpp \\\n    $$PWD/project.cpp \\\n    $$PWD/moocsubmission.cpp \\\n    $$PWD/esclineedit.cpp \\\n    $$PWD/solver.cpp \\\n    $$PWD/theme.cpp\n\nHEADERS += \\\n    $$PWD/history.h \\\n    $$PWD/mainwindow.h \\\n    $$PWD/codechecker.h \\\n    $$PWD/codeeditor.h \\\n    $$PWD/configwindow.h \\\n    $$PWD/darkmodenotifier.h \\\n    $$PWD/elapsedtimer.h \\\n    $$PWD/exception.h \\\n    $$PWD/extraparamdialog.h \\\n    $$PWD/highlighter.h \\\n    $$PWD/fzndoc.h \\\n    $$PWD/ide.h \\\n    $$PWD/ideutils.h \\\n    $$PWD/outputwidget.h \\\n    $$PWD/preferencesdialog.h \\\n    $$PWD/process.h \\\n    $$PWD/profilecompilation.h \\\n    $$PWD/projectbrowser.h \\\n    $$PWD/server.h \\\n    $$PWD/gotolinedialog.h \\\n    $$PWD/paramdialog.h \\\n    $$PWD/outputdockwidget.h \\\n    $$PWD/checkupdatedialog.h \\\n    $$PWD/project.h \\\n    $$PWD/moocsubmission.h \\\n    $$PWD/esclineedit.h \\\n    $$PWD/solver.h \\\n    $$PWD/theme.h\n\nFORMS += \\\n    $$PWD/configwindow.ui \\\n    $$PWD/extraparamdialog.ui \\\n    $$PWD/mainwindow.ui \\\n    $$PWD/outputwidget.ui \\\n    $$PWD/preferencesdialog.ui \\\n    $$PWD/projectbrowser.ui \\\n    $$PWD/gotolinedialog.ui \\\n    $$PWD/paramdialog.ui \\\n    $$PWD/checkupdatedialog.ui \\\n    $$PWD/moocsubmission.ui\n\nRESOURCES += \\\n    $$PWD/minizincide.qrc\n\ninclude($$PWD/../cp-profiler/cp-profiler.pri)\nQT += network sql\n"
  },
  {
    "path": "MiniZincIDE/MiniZincIDE.pro",
    "content": "TARGET = MiniZincIDE\nTEMPLATE = app\n\nINCLUDEPATH += $$PWD/../\n\nSOURCES += main.cpp\n\ninclude($$PWD/MiniZincIDE.pri)\n\ntarget.path = $$PREFIX/bin\nINSTALLS += target\n"
  },
  {
    "path": "MiniZincIDE/README.txt",
    "content": "================================================================\nMiniZinc IDE\n================================================================\n\nhttp://www.minizinc.org\n\nThe MiniZinc IDE is copyright 2013-2019 Monash University, NICTA, Data61/CSIRO.\nPlease see LICENSE.txt for license information.\nDetailed installation instructions for Windows, Linux and macOS can be found\nat https://www.minizinc.org/doc-latest/en/installation.html.\n"
  },
  {
    "path": "MiniZincIDE/cheat_sheet.mzn",
    "content": "                      %%%%%%%%%%%%%%%%%%%%%%%%\n                      %                      %\n                      % MiniZinc cheat sheet %\n                      %                      %\n                      %%%%%%%%%%%%%%%%%%%%%%%%\n\n/******************\n* Model structure *\n******************/\n\ninclude \"globals.mzn\";        % use globals library\n\nint: n;                       % fixed integer parameter\nvar 1..n: x;                  % integer decision variable\narray[1..n] of var 1..n: y;   % array of integer decision variables\nconstraint sum(y) <= x;       % constraint\n\nsolve satisfy;                % find satisfying solution\n\noutput [\"Solution:\\n\", \"x = \", show(x),  % output list of strings\n        \"y = \\(y)\"];                     % interpolation \\(expression)\n\n/***********************\n* Basic types          *\n***********************/\n\nint: i;\n3..5: j;                     % integer\n\nfloat: f;\n3.0..5.0: g;                 % floating point number\n\nbool: b;                     % boolean\n\nset of int: s;               % set\nset of 3..5: t;\n\nenum E = { Boat, Airplane }; % enumerated type\n\n/***********************\n* Type-insts           *\n***********************/\n\nvar int: x;           % declare variable \n                      % (also with float, bool, and set of int)\nvar 3..7: y;          % declare variable with domain (also 3.0..7.0)\nvar set of 10..20: s; % declare set variable (only fixed set of int!)\n\narray[1..4,1..10] of var 0.0..100.0: f;\n                      % declare 2d array of float variables\n\nvar opt 1..10: ox;    % declare optional int variable (can be 1..10 or\n                      % \"absent\", written <>)\n\n/*********************\n* Basic Constraints  *\n*********************/\nconstraint x = y;\nconstraint x < y;\nconstraint x <= y;\nconstraint x > y;\nconstraint x >= y;\nconstraint x != y;    % not equals\n   \n/**********************\n* Logical Connectives *\n**********************/\n                                   % conditionals\nint: d = if i > 10 then a elseif i > 0 then b else c endif;\nconstraint if x < y then y < z else y > z endif;\n   \nconstraint x < y \\/ y != z;        % logical \"or\"\nconstraint x < y /\\ y != z;        % logical \"and\"\nconstraint x < y -> y != z;        % logical implication\nconstraint not (x < y /\\ y > z);   % logical negation\n\n/******************\n* Set Constraints *\n******************/\n   \nconstraint s subset t;             % non-strict subset relation\nconstraint s intersect t subset w; % intersection\nconstraint s union t subset w;     % union\n\n/***************************\n* Predicates and Functions *\n***************************/\n   \nconstraint all_different(x);       % predicate call\nconstraint mydiv(x,y) = 2;         % function call\n\n/********************************\n* Comprehensions and generators *\n********************************/\n\narray[int] of int: a = [ i | i in 1..10];\n                      % create array [1,2,3,4,5,6,7,8,9,10]\n\narray[int] of int: a = [ i | i in 1..10 where i mod 3=0];\n                      % create array [3, 6, 9]\n\nconstraint forall (i,j in 1..n where i<j) (x[i] != x[j]);\n                      % is the same as\nconstraint forall ([ x[i] != x[j] | i,j in 1..n where i<j ]);\n\n/****************************\n* Array index set coercions *\n****************************/\n\narray[20..30] of int: i = array1d(20..30, x);\n\narray[int,int] of float: x =\n  array2d(1..10, 1..10, [ 0.0 | i,j in 1..10 ]);\n\n/********************************\n* Search and search annotations *\n********************************/\n\nsolve maximize sum(x);        % optimization\nsolve minimize sum(x);\n\nsolve ::int_search([x,y,z], input_order, indomain_min) satisfy;\n                      % search annotation\n                      % variable selection (examples):\n                      %   input_order, first_fail, max_regret, smallest\n                      % value selection (examples):\n                      %   indomain_min, indomain_max, indomain,\n                      %   indomain_split, indomain_reverse_split\n                      % similar for bool_search, set_search, float_search\n\nsolve ::seq_search([int_search(x, first_fail, indomain),\n                    int_search(y, input_order, indomain_min)])\n      satisfy;\n                      % first search x, then y\n\n\n\n/*********************\n* Strings and output *\n**********************/\n\n                      % concatenation using ++\n                      % join arrays using string \", \"\n                      % convert expression into string using show\nstring: s = \"a\" ++ join(\", \",[show(x[i]) | i in 1..3]);\n\n/****************************************\n* User-defined predicates and functions *\n****************************************/\n\npredicate no_overlap(var int: start0, var int: duration0,\n                     var int: start1, var int: duration1) =\n  start0 + duration0 <= start1 \\/ start1 + duration1 <= start0;\n\nfunction var float: average(array[int] of var int: x) =\n  sum(x) / length(x);\n\n"
  },
  {
    "path": "MiniZincIDE/checkupdatedialog.cpp",
    "content": "#include \"checkupdatedialog.h\"\n#include \"ui_checkupdatedialog.h\"\n\n#include <QPushButton>\n\nCheckUpdateDialog::CheckUpdateDialog(QWidget *parent) :\n    QDialog(parent),\n    ui(new Ui::CheckUpdateDialog)\n{\n    ui->setupUi(this);\n}\n\nCheckUpdateDialog::~CheckUpdateDialog()\n{\n    delete ui;\n}\n"
  },
  {
    "path": "MiniZincIDE/checkupdatedialog.h",
    "content": "#ifndef CHECKUPDATEDIALOG_H\n#define CHECKUPDATEDIALOG_H\n\n#include <QDialog>\n\nnamespace Ui {\nclass CheckUpdateDialog;\n}\n\nclass CheckUpdateDialog : public QDialog\n{\n    Q_OBJECT\n\npublic:\n    explicit CheckUpdateDialog(QWidget *parent = 0);\n    ~CheckUpdateDialog();\nprivate:\n    Ui::CheckUpdateDialog *ui;\n};\n\n#endif // CHECKUPDATEDIALOG_H\n"
  },
  {
    "path": "MiniZincIDE/checkupdatedialog.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>CheckUpdateDialog</class>\n <widget class=\"QDialog\" name=\"CheckUpdateDialog\">\n  <property name=\"windowModality\">\n   <enum>Qt::ApplicationModal</enum>\n  </property>\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>480</width>\n    <height>299</height>\n   </rect>\n  </property>\n  <property name=\"sizePolicy\">\n   <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Expanding\">\n    <horstretch>0</horstretch>\n    <verstretch>0</verstretch>\n   </sizepolicy>\n  </property>\n  <property name=\"minimumSize\">\n   <size>\n    <width>387</width>\n    <height>226</height>\n   </size>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Automatic check for updates</string>\n  </property>\n  <property name=\"modal\">\n   <bool>true</bool>\n  </property>\n  <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n   <property name=\"sizeConstraint\">\n    <enum>QLayout::SetFixedSize</enum>\n   </property>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n     <property name=\"sizeConstraint\">\n      <enum>QLayout::SetMaximumSize</enum>\n     </property>\n     <item>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n       <property name=\"sizeConstraint\">\n        <enum>QLayout::SetMinimumSize</enum>\n       </property>\n       <property name=\"topMargin\">\n        <number>4</number>\n       </property>\n       <item>\n        <widget class=\"QLabel\" name=\"label_3\">\n         <property name=\"sizePolicy\">\n          <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n           <horstretch>0</horstretch>\n           <verstretch>0</verstretch>\n          </sizepolicy>\n         </property>\n         <property name=\"minimumSize\">\n          <size>\n           <width>100</width>\n           <height>100</height>\n          </size>\n         </property>\n         <property name=\"maximumSize\">\n          <size>\n           <width>100</width>\n           <height>100</height>\n          </size>\n         </property>\n         <property name=\"text\">\n          <string/>\n         </property>\n         <property name=\"pixmap\">\n          <pixmap resource=\"minizincide.qrc\">:/images/mznicon.png</pixmap>\n         </property>\n         <property name=\"scaledContents\">\n          <bool>true</bool>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <spacer name=\"verticalSpacer\">\n         <property name=\"orientation\">\n          <enum>Qt::Vertical</enum>\n         </property>\n         <property name=\"sizeHint\" stdset=\"0\">\n          <size>\n           <width>20</width>\n           <height>40</height>\n          </size>\n         </property>\n        </spacer>\n       </item>\n      </layout>\n     </item>\n     <item>\n      <spacer name=\"horizontalSpacer_2\">\n       <property name=\"orientation\">\n        <enum>Qt::Horizontal</enum>\n       </property>\n       <property name=\"sizeType\">\n        <enum>QSizePolicy::Preferred</enum>\n       </property>\n       <property name=\"sizeHint\" stdset=\"0\">\n        <size>\n         <width>10</width>\n         <height>10</height>\n        </size>\n       </property>\n      </spacer>\n     </item>\n     <item>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n       <property name=\"sizeConstraint\">\n        <enum>QLayout::SetMinimumSize</enum>\n       </property>\n       <property name=\"leftMargin\">\n        <number>0</number>\n       </property>\n       <property name=\"topMargin\">\n        <number>4</number>\n       </property>\n       <property name=\"rightMargin\">\n        <number>4</number>\n       </property>\n       <property name=\"bottomMargin\">\n        <number>4</number>\n       </property>\n       <item>\n        <widget class=\"QLabel\" name=\"label\">\n         <property name=\"sizePolicy\">\n          <sizepolicy hsizetype=\"MinimumExpanding\" vsizetype=\"MinimumExpanding\">\n           <horstretch>0</horstretch>\n           <verstretch>0</verstretch>\n          </sizepolicy>\n         </property>\n         <property name=\"text\">\n          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The MiniZinc IDE can check automatically once a day whether any updates are available.&lt;/p&gt;&lt;p&gt;Please note that every update check sends anonymised statistics to Google Analytics. We only use these statistics to better understand our user base and to justify funding for further development!&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Do you want to enable the automatic update check?&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n         </property>\n         <property name=\"wordWrap\">\n          <bool>true</bool>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n         <property name=\"sizeConstraint\">\n          <enum>QLayout::SetMinimumSize</enum>\n         </property>\n         <item>\n          <spacer name=\"horizontalSpacer\">\n           <property name=\"orientation\">\n            <enum>Qt::Horizontal</enum>\n           </property>\n           <property name=\"sizeHint\" stdset=\"0\">\n            <size>\n             <width>40</width>\n             <height>20</height>\n            </size>\n           </property>\n          </spacer>\n         </item>\n         <item>\n          <widget class=\"QPushButton\" name=\"nobutton\">\n           <property name=\"text\">\n            <string>No</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QPushButton\" name=\"yesbutton\">\n           <property name=\"text\">\n            <string>Yes</string>\n           </property>\n           <property name=\"default\">\n            <bool>true</bool>\n           </property>\n          </widget>\n         </item>\n        </layout>\n       </item>\n       <item>\n        <widget class=\"QLabel\" name=\"label_2\">\n         <property name=\"sizePolicy\">\n          <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Expanding\">\n           <horstretch>0</horstretch>\n           <verstretch>0</verstretch>\n          </sizepolicy>\n         </property>\n         <property name=\"text\">\n          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can disable the update check at any time in the preferences dialog.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n         </property>\n         <property name=\"wordWrap\">\n          <bool>true</bool>\n         </property>\n        </widget>\n       </item>\n      </layout>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <resources>\n  <include location=\"minizincide.qrc\"/>\n </resources>\n <connections>\n  <connection>\n   <sender>yesbutton</sender>\n   <signal>clicked()</signal>\n   <receiver>CheckUpdateDialog</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>485</x>\n     <y>215</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>270</x>\n     <y>119</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>nobutton</sender>\n   <signal>clicked()</signal>\n   <receiver>CheckUpdateDialog</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>415</x>\n     <y>215</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>270</x>\n     <y>119</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "MiniZincIDE/codechecker.cpp",
    "content": "#include \"codechecker.h\"\n#include \"process.h\"\n\nCodeChecker::~CodeChecker()\n{\n    cancel();\n}\n\nvoid CodeChecker::connectSignals()\n{\n    connect(&p, &MznProcess::started, this, &CodeChecker::onStarted);\n    connect(&p, &MznProcess::outputStdOut, this, &CodeChecker::onLine);\n    connect(&p, &MznProcess::outputStdError, this, &CodeChecker::onLine);\n    connect(&p, &MznProcess::finished, this, &CodeChecker::onFinished);\n}\n\nvoid CodeChecker::start(const QString& modelContents, SolverConfiguration& sc, const QString& wd)\n{\n    cancel();\n    connectSignals();\n    inRelevantError = false;\n    curError = MiniZincError();\n    mznErrors.clear();\n    SolverConfiguration checkSc(sc.solverDefinition);\n    checkSc.additionalData = sc.additionalData;\n    checkSc.extraOptions = sc.extraOptions;\n    QStringList args;\n    args << \"--model-check-only\" << \"-\";\n    input = modelContents;\n    p.start(checkSc, args, wd);\n}\n\nvoid CodeChecker::cancel()\n{\n    p.disconnect();\n    p.terminate();\n}\n\nvoid CodeChecker::onStarted()\n{\n    p.writeStdIn(input);\n    p.closeStdIn();\n}\n\nvoid CodeChecker::onLine(const QString& l)\n{\n    QJsonParseError error;\n    auto json = QJsonDocument::fromJson(l.toUtf8(), &error);\n    if (json.isNull()) {\n        return;\n    }\n    auto msg = json.object();\n    if (msg[\"type\"] != \"error\" && msg[\"type\"] != \"warning\") {\n        return;\n    }\n    if (!msg[\"location\"].isObject()) {\n        return;\n    }\n    auto loc = msg[\"location\"].toObject();\n    MiniZincError e;\n    e.isWarning = msg[\"type\"] == \"warning\";\n    e.filename = loc[\"filename\"].toString();\n    e.first_line = loc[\"firstLine\"].toInt();\n    e.first_col = loc[\"firstColumn\"].toInt();\n    e.last_line = loc[\"lastLine\"].toInt();\n    e.last_col = loc[\"lastColumn\"].toInt();\n    e.msg = msg[\"message\"].toString();\n    if (e.filename == \"stdin\") {\n        // Ignore errors that aren't from this file\n        mznErrors.push_back(e);\n    }\n}\n\nvoid CodeChecker::onFinished()\n{\n    emit finished(mznErrors);\n}\n"
  },
  {
    "path": "MiniZincIDE/codechecker.h",
    "content": "#ifndef CHECKCODE_H\n#define CHECKCODE_H\n\n#include <QObject>\n\n#include \"codeeditor.h\"\n#include \"process.h\"\n\nclass CodeChecker : public QObject\n{\n    Q_OBJECT\npublic:\n    explicit CodeChecker(QObject *parent = nullptr) : QObject(parent), p(this) {}\n    ~CodeChecker();\n\n    void start(const QString& modelContents, SolverConfiguration& sc, const QString& wd);\n    void cancel(void);\nsignals:\n    void finished(const QVector<MiniZincError>& mznErrors);\n\nprivate slots:\n    void onStarted(void);\n    void onLine(const QString& data);\n    void onFinished();\n\nprivate:\n    MznProcess p;\n    QString input;\n\n    bool inRelevantError = false;\n    MiniZincError curError;\n    QVector<MiniZincError> mznErrors;\n\n    void connectSignals();\n};\n\n#endif // CHECKCODE_H\n"
  },
  {
    "path": "MiniZincIDE/codeeditor.cpp",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#include <QtWidgets>\n#include \"codeeditor.h\"\n#include \"ide.h\"\n#include \"mainwindow.h\"\n\nvoid\nCodeEditor::initUI(QFont& font)\n{\n    setFont(font);\n    QFontMetrics metrics(font);\n    setTabStopDistance(metrics.horizontalAdvance(' ') * indentSize);\n\n    lineNumbers= new LineNumbers(this);\n    debugInfo = new DebugInfo(this);\n    editorHeader = new EditorHeader(this);\n    debugInfo->hide();\n    connect(this, &QPlainTextEdit::blockCountChanged, this, &CodeEditor::setViewportWidth);\n    connect(this, &QPlainTextEdit::updateRequest, this, &CodeEditor::setLineNumbers);\n    connect(this, &QPlainTextEdit::updateRequest, this, &CodeEditor::setDebugInfoPos);\n    connect(this, &QPlainTextEdit::cursorPositionChanged, this, &CodeEditor::cursorChange);\n    connect(document(), &QTextDocument::modificationChanged, this, &CodeEditor::docChanged);\n    connect(document(), &QTextDocument::contentsChanged, this, &CodeEditor::contentsChanged);\n\n    setViewportWidth(0);\n    cursorChange();\n\n    highlighter = new Highlighter(font,theme,darkMode,document());\n    setTheme(theme, darkMode);\n\n    QTextCursor cursor(textCursor());\n    cursor.movePosition(QTextCursor::Start);\n    setTextCursor(cursor);\n    ensureCursorVisible();\n    setFocus();\n}\n\nCodeEditor::CodeEditor(QTextDocument* doc, const QString& path, bool isNewFile, bool large,\n                       QFont& font, int indentSize0, bool useTabs0, const Theme& theme0, bool darkMode0,\n                       QTabWidget* t, QWidget *parent) :\n    QPlainTextEdit(parent), loadContentsButton(nullptr), tabs(t),\n    indentSize(indentSize0), useTabs(useTabs0), theme(theme0), darkMode(darkMode0)\n{\n    if (doc) {\n        QPlainTextEdit::setDocument(doc);\n    }\n    initUI(font);\n    if (isNewFile) {\n        filepath = \"\";\n        filename = path;\n    } else {\n        filepath = QFileInfo(path).absoluteFilePath();\n        filename = QFileInfo(path).fileName();\n    }\n    if (large) {\n        setReadOnly(true);\n        QPushButton* pb = new QPushButton(\"Big file. Load contents?\", this);\n        connect(pb, &QPushButton::clicked, this, &CodeEditor::loadContents);\n        loadContentsButton = pb;\n    }\n    completer = new QCompleter(this);\n    QStringList completionList;\n    completionList << \"annotation\" << \"array\" << \"bool\" << \"constraint\"\n                   << \"diff\" << \"else\" << \"elseif\" << \"endif\" << \"enum\" << \"float\"\n                   << \"function\" << \"include\" << \"intersect\" << \"maximize\" << \"minimize\"\n                   << \"output\" << \"predicate\" << \"satisfy\" << \"solve\" << \"string\"\n                   << \"subset\" << \"superset\" << \"symdiff\" << \"test\" << \"then\"\n                   << \"union\" << \"where\";\n    completionList.sort();\n    completionModel.setStringList(completionList);\n    completer->setModel(&completionModel);\n    completer->setCaseSensitivity(Qt::CaseSensitive);\n    completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);\n    completer->setWrapAround(false);\n    completer->setWidget(this);\n    completer->setCompletionMode(QCompleter::PopupCompletion);\n    QObject::connect(completer, QOverload<const QString&>::of(&QCompleter::activated), this, &CodeEditor::insertCompletion);\n\n    modificationTimer.setSingleShot(true);\n    QObject::connect(&modificationTimer, &QTimer::timeout, this, &CodeEditor::contentsChangedWithTimeout);\n\n    auto* mw = qobject_cast<MainWindow*>(parent);\n    if (mw != nullptr) {\n        QObject::connect(this, &CodeEditor::escPressed, mw, &MainWindow::closeFindWidget);\n    }\n\n    setAcceptDrops(false);\n    installEventFilter(this);\n}\n\nvoid CodeEditor::loadContents()\n{\n    static_cast<IDE*>(qApp)->loadLargeFile(filepath,this);\n}\n\nvoid CodeEditor::insertCompletion(const QString &completion)\n{\n    QTextCursor tc = textCursor();\n    int extra = completion.length() - completer->completionPrefix().length();\n    tc.movePosition(QTextCursor::Left);\n    tc.movePosition(QTextCursor::EndOfWord);\n    tc.insertText(completion.right(extra));\n    setTextCursor(tc);\n}\n\nvoid CodeEditor::loadedLargeFile()\n{\n    setReadOnly(false);\n    delete loadContentsButton;\n    loadContentsButton = nullptr;\n}\n\nvoid CodeEditor::setDocument(QTextDocument *document)\n{\n    if (document) {\n        delete highlighter;\n        highlighter = nullptr;\n    }\n    disconnect(this, &QPlainTextEdit::blockCountChanged, this, &CodeEditor::setViewportWidth);\n    disconnect(this, &QPlainTextEdit::updateRequest, this, &CodeEditor::setLineNumbers);\n    disconnect(this, &QPlainTextEdit::updateRequest, this, &CodeEditor::setDebugInfoPos);\n    disconnect(this, &QPlainTextEdit::cursorPositionChanged, this, &CodeEditor::cursorChange);\n    disconnect(this->document(), &QTextDocument::modificationChanged, this, &CodeEditor::docChanged);\n    QList<QTextEdit::ExtraSelection> noSelections;\n    setExtraSelections(noSelections);\n    QPlainTextEdit::setDocument(document);\n    if (document) {\n        QFont f= font();\n        highlighter = new Highlighter(f, theme, darkMode, document);\n    }\n    connect(this, &QPlainTextEdit::blockCountChanged, this, &CodeEditor::setViewportWidth);\n    connect(this, &QPlainTextEdit::updateRequest, this, &CodeEditor::setLineNumbers);\n    connect(this, &QPlainTextEdit::updateRequest, this, &CodeEditor::setDebugInfoPos);\n    connect(this, &QPlainTextEdit::cursorPositionChanged, this, &CodeEditor::cursorChange);\n    connect(this->document(), &QTextDocument::modificationChanged, this, &CodeEditor::docChanged);\n}\n\nvoid CodeEditor::setTheme(const Theme& _theme, bool _darkMode)\n{\n    darkMode = _darkMode;\n    theme = _theme;\n    highlighter->setTheme(theme, darkMode);\n    highlighter->rehighlight();\n\n    auto palette = this->palette();\n    palette.setColor(QPalette::Text, theme.textColor.get(darkMode));\n    palette.setColor(QPalette::Base, theme.backgroundColor.get(darkMode));\n    palette.setColor(QPalette::Highlight, theme.textHighlightColor.get(darkMode));\n    palette.setColor(QPalette::HighlightedText, theme.textColor.get(darkMode));\n    this->setPalette(palette);\n\n    viewport()->setStyleSheet(theme.styleSheet(darkMode));\n    cursorChange(); // Ensure extra selections are the correct colours\n}\n\nvoid CodeEditor::setIndentSize(int size)\n{\n    indentSize = size;\n    QFontMetrics metrics(font());\n    setTabStopDistance(metrics.horizontalAdvance(' ') * size);\n}\n\nHighlighter& CodeEditor::getHighlighter() {\n    return *highlighter;\n}\n\nvoid CodeEditor::docChanged(bool c)\n{\n    int t = tabs == nullptr ? -1 : tabs->indexOf(this);\n    if (t != -1) {\n        QString title = tabs->tabText(t);\n        title = title.mid(0, title.lastIndexOf(\" *\"));\n        if (c)\n            title += \" *\";\n        tabs->setTabText(t,title);\n    }\n}\n\nvoid CodeEditor::contentsChanged()\n{\n    modificationTimer.start(500);\n}\n\nvoid CodeEditor::contentsChangedWithTimeout()\n{\n    emit changedDebounced();\n}\n\nvoid CodeEditor::keyPressEvent(QKeyEvent *e)\n{\n    if (completer->popup()->isVisible()) {\n        switch (e->key()) {\n        case Qt::Key_Enter:\n        case Qt::Key_Return:\n        case Qt::Key_Escape:\n        case Qt::Key_Tab:\n        case Qt::Key_Backtab:\n            e->ignore();\n            return; // let the completer do default behavior\n        default:\n            break;\n        }\n    }\n    if (e->key() == Qt::Key_Backtab) {\n        e->accept();\n        shiftLeft();\n        ensureCursorVisible();\n    } else if (e->key() == Qt::Key_Tab) {\n        e->accept();\n        auto cursor = textCursor();\n        auto startBlock = document()->findBlock(cursor.selectionStart());\n        auto endBlock = document()->findBlock(cursor.selectionEnd());\n        auto partialLineSelection = startBlock.position() != cursor.selectionStart()\n                || endBlock.position() + endBlock.length() - 1 != cursor.selectionEnd();\n        if (!cursor.hasSelection() || (startBlock == endBlock && partialLineSelection)) {\n            if (useTabs) {\n                cursor.insertText(\"\\t\");\n            } else {\n                auto posInLine = cursor.selectionStart() - startBlock.position();\n                auto distanceFromTabStop = 0;\n                auto line = startBlock.text();\n                for (int i = 0; i < posInLine; i++) {\n                    if (line[i] == '\\t') {\n                        distanceFromTabStop = 0;\n                    } else if (line[i].isSpace()) {\n                        distanceFromTabStop++;\n                    } else {\n                        break;\n                    }\n                }\n                auto toAdd = distanceFromTabStop % indentSize;\n                if (toAdd == 0) {\n                    toAdd = indentSize;\n                }\n                cursor.insertText(QString(\" \").repeated(toAdd));\n            }\n        } else {\n            shiftRight();\n        }\n        ensureCursorVisible();\n    } else if (e->key() == Qt::Key_Return) {\n        e->accept();\n        QTextCursor cursor(textCursor());\n        QString curLine = cursor.block().text();\n        QRegularExpression leadingWhitespace(\"^(\\\\s*)\");\n        cursor.insertText(\"\\n\");\n        auto leadingWhitespace_match = leadingWhitespace.match(curLine);\n        if (leadingWhitespace_match.hasMatch()) {\n            cursor.insertText(leadingWhitespace_match.captured(1));\n        }\n        ensureCursorVisible();\n    } else if (e->key() == Qt::Key_Escape) {\n        e->accept();\n        emit escPressed();\n    } else {\n        bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E\n        if (!isShortcut) // do not process the shortcut when we have a completer\n            QPlainTextEdit::keyPressEvent(e);\n        const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);\n        if (ctrlOrShift && e->text().isEmpty())\n            return;\n        static QString eow(\"~!@#$%^&*()_+{}|:\\\"<>?,./;'[]\\\\-=\"); // end of word\n        bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;\n\n        QTextCursor tc = textCursor();\n        tc.select(QTextCursor::WordUnderCursor);\n        QString completionPrefix = tc.selectedText();\n        if (!isShortcut && (hasModifier || e->text().isEmpty()|| completionPrefix.length() < 3\n                            || eow.contains(e->text().right(1)))) {\n            completer->popup()->hide();\n            return;\n        }\n        if (completionPrefix != completer->completionPrefix()) {\n            completer->setCompletionPrefix(completionPrefix);\n            completer->popup()->setCurrentIndex(completer->completionModel()->index(0, 0));\n        }\n        QRect cr = cursorRect();\n        cr.setWidth(completer->popup()->sizeHintForColumn(0)\n                    + completer->popup()->verticalScrollBar()->sizeHint().width());\n        completer->complete(cr); // popup it up!\n    }\n}\n\nint CodeEditor::lineNumbersWidth()\n{\n    int width = 1;\n    int bc = blockCount();\n    while (bc >= 10) {\n        bc /= 10;\n        ++width;\n    }\n    width = std::max(width,3);\n    return 3 + fontMetrics().boundingRect(QLatin1Char('9')).width() * width;\n}\n\nint CodeEditor::debugInfoWidth()\n{\n    return !debugInfo->isVisible()?0:(3*DEBUG_TAB_SIZE);\n}\n\nint CodeEditor::debugInfoOffset()\n{\n    int heightOffset = 0;\n    if(debugInfo->isVisible()){\n        QFont lineNoFont = font();\n        QFontMetrics fm(lineNoFont);\n        heightOffset = fm.height();\n    }\n    return heightOffset;\n}\n\nvoid CodeEditor::showDebugInfo(bool show)\n{\n    if (filepath!=\"\" && !filepath.endsWith(\".mzn\"))\n        show = false;\n    if (show) {\n        if(debugInfo->isHidden()){\n            debugInfo->show();\n            setViewportWidth(0);\n        }\n    } else {\n        if(!debugInfo->isHidden()){\n            debugInfo->hide();\n            setViewportWidth(0);\n        }\n    }\n}\n\nvoid CodeEditor::setViewportWidth(int)\n{\n    setViewportMargins(lineNumbersWidth(), debugInfoOffset(), debugInfoWidth(), 0);\n}\n\n\n\nvoid CodeEditor::setLineNumbers(const QRect &rect, int dy)\n{\n    if (dy)\n        lineNumbers->scroll(0, dy);\n    else\n        lineNumbers->update(0, rect.y(), lineNumbers->width(), rect.height());\n\n    if (rect.contains(viewport()->rect()))\n        setViewportWidth(0);\n}\n\nvoid CodeEditor::setDebugInfoPos(const QRect &rect, int dy)\n{\n    if (dy)\n        debugInfo->scroll(0, dy);\n    else\n        debugInfo->update(0, rect.y(), debugInfo->width(), rect.height());\n\n\n    if (rect.contains(viewport()->rect()))\n        setViewportWidth(0);\n}\n\n\n\n\n\nvoid CodeEditor::resizeEvent(QResizeEvent *e)\n{\n    QPlainTextEdit::resizeEvent(e);\n\n    QRect cr = contentsRect();\n    lineNumbers->setGeometry(QRect(cr.left(), cr.top()+debugInfoOffset(), lineNumbersWidth(), cr.height()));\n    if (loadContentsButton) {\n        loadContentsButton->move(cr.left()+lineNumbersWidth(), cr.top());\n    }\n\n    debugInfo->setGeometry(QRect(cr.right()-debugInfoWidth(), cr.top()+debugInfoOffset(), debugInfoWidth(), cr.height()));\n\n    editorHeader->setGeometry(QRect(cr.left(), cr.top(), cr.width(), debugInfoOffset()));\n}\n\nvoid CodeEditor::showEvent(QShowEvent *event)\n{\n    setViewportWidth(0);\n}\n\n\n\nvoid CodeEditor::cursorChange()\n{\n    QList<QTextEdit::ExtraSelection> allExtraSels = extraSelections();\n\n    BracketData* bd = static_cast<BracketData*>(textCursor().block().userData());\n    QList<QTextEdit::ExtraSelection> extraSelections;\n\n    {\n        QTextEdit::ExtraSelection highlightLineSelection;\n        QColor lineColor = theme.lineHighlightColor.get(darkMode);\n        highlightLineSelection.format.setBackground(lineColor);\n        highlightLineSelection.format.setProperty(QTextFormat::FullWidthSelection, true);\n        highlightLineSelection.cursor = textCursor();\n        highlightLineSelection.cursor.clearSelection();\n        extraSelections.append(highlightLineSelection);\n    }\n\n    auto ec = theme.errorColor.get(darkMode);\n    auto wc = theme.warningColor.get(darkMode);\n    foreach (QTextEdit::ExtraSelection sel, allExtraSels) {\n        if (sel.format.underlineColor() == ec || sel.format.underlineColor() == wc) {\n            extraSelections.append(sel);\n        }\n    }\n\n    if (bd) {\n        QVector<Bracket>& brackets = bd->brackets;\n        int pos = textCursor().block().position();\n        for (int i=0; i<brackets.size(); i++) {\n            int curPos = textCursor().position()-textCursor().block().position();\n            Bracket& b = brackets[i];\n            int parenPos0 = -1;\n            int parenPos1 = -1;\n            int errPos = -1;\n            if (b.pos == curPos-1 && (b.b == '(' || b.b == '{' || b.b == '[')) {\n                parenPos1 = matchLeft(textCursor().block(), b.b, i+1, 0);\n                if (parenPos1 != -1) {\n                    parenPos0 = pos+b.pos;\n                } else {\n                    errPos = pos+b.pos;\n                }\n            } else if (b.pos == curPos-1 && (b.b == ')' || b.b == '}' || b.b == ']')) {\n                parenPos0 = matchRight(textCursor().block(), b.b, i-1, 0);\n                if (parenPos0 != -1) {\n                    parenPos1 = pos+b.pos;\n                } else {\n                    errPos = pos+b.pos;\n                }\n            }\n            if (parenPos0 != -1 && parenPos1 != -1) {\n                QTextEdit::ExtraSelection sel;\n                QTextCharFormat format = sel.format;\n                format.setBackground(theme.bracketsMatchColor.get(darkMode));\n                sel.format = format;\n                QTextCursor cursor = textCursor();\n                cursor.setPosition(parenPos0);\n                cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);\n                sel.cursor = cursor;\n                extraSelections.append(sel);\n                cursor.setPosition(parenPos1);\n                cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);\n                sel.cursor = cursor;\n                extraSelections.append(sel);\n            }\n            if (errPos != -1) {\n                QTextEdit::ExtraSelection sel;\n                QTextCharFormat format = sel.format;\n                format.setBackground(theme.bracketsNoMatchColor.get(darkMode));\n                sel.format = format;\n                QTextCursor cursor = textCursor();\n                cursor.setPosition(errPos);\n                cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);\n                sel.cursor = cursor;\n                extraSelections.append(sel);\n            }\n        }\n    }\n\n    setExtraSelections(extraSelections);\n}\n\nint CodeEditor::matchLeft(QTextBlock block, QChar b, int i, int nLeft)\n{\n    QChar match;\n    switch (b.toLatin1()) {\n    case '(' : match = ')'; break;\n    case '{' : match = '}'; break;\n    case '[' : match = ']'; break;\n    default: break; // should not happen\n    }\n\n    while (block.isValid()) {\n        BracketData* bd = static_cast<BracketData*>(block.userData());\n        QVector<Bracket>& brackets = bd->brackets;\n        int docPos = block.position();\n        for (; i<brackets.size(); i++) {\n            Bracket& b = brackets[i];\n            if (b.b=='(' || b.b=='{' || b.b=='[') {\n                nLeft++;\n            } else if (b.b==match && nLeft==0) {\n                return docPos+b.pos;\n            } else {\n                nLeft--;\n            }\n        }\n        block = block.next();\n        i = 0;\n    }\n    return -1;\n}\n\nint CodeEditor::matchRight(QTextBlock block, QChar b, int i, int nRight)\n{\n    QChar match;\n    switch (b.toLatin1()) {\n    case ')' : match = '('; break;\n    case '}' : match = '{'; break;\n    case ']' : match = '['; break;\n    default: break; // should not happen\n    }\n    if (i==-1)\n        block = block.previous();\n    while (block.isValid()) {\n        BracketData* bd = static_cast<BracketData*>(block.userData());\n        QVector<Bracket>& brackets = bd->brackets;\n        if (i==-1)\n            i = brackets.size()-1;\n        int docPos = block.position();\n        for (; i>-1 && brackets.size()>0; i--) {\n            Bracket& b = brackets[i];\n            if (b.b==')' || b.b=='}' || b.b==']') {\n                nRight++;\n            } else if (b.b==match && nRight==0) {\n                return docPos+b.pos;\n            } else {\n                nRight--;\n            }\n        }\n        block = block.previous();\n        i = -1;\n    }\n    return -1;\n}\n\nvoid CodeEditor::paintLineNumbers(QPaintEvent *event)\n{\n    QPainter painter(lineNumbers);\n    QFont lineNoFont = font();\n    QFontMetrics fm(lineNoFont);\n    lineNoFont.setPointSizeF(lineNoFont.pointSizeF()*0.8);\n    QFontMetrics fm2(lineNoFont);\n    int ascentDiff = fontMetrics().ascent() - fm2.ascent();\n    painter.setFont(lineNoFont);\n    painter.fillRect(event->rect(), theme.lineNumberbackground.get(darkMode));\n\n    QTextBlock block = firstVisibleBlock();\n    int blockNumber = block.blockNumber();\n\n    int top = static_cast<int>(blockBoundingGeometry(block).translated(contentOffset()).top());\n    int bottom = top + static_cast<int>(blockBoundingRect(block).height());\n\n//    painter.fillRect(event->rect(), QColor::fromRgb(QRandomGenerator::global()->generate()));\n\n    int curLine = textCursor().blockNumber();\n\n    while (block.isValid() && top <= event->rect().bottom()) {\n        if (block.isVisible() && bottom >= event->rect().top()) {\n            QString number = QString::number(blockNumber + 1);\n            int textTop = top + ascentDiff;\n\n            if (errorLines.contains(blockNumber)) {\n                painter.setPen(theme.errorColor.get(darkMode));\n            } else if (warningLines.contains(blockNumber)) {\n                painter.setPen(theme.warningColor.get(darkMode));\n            } else if (blockNumber == curLine) {\n                painter.setPen(theme.foregroundActiveColor.get(darkMode));\n            } else {\n                painter.setPen(theme.foregroundInactiveColor.get(darkMode));\n            }\n\n            painter.drawText(0, textTop, lineNumbers->width(), fm2.height(),\n                             Qt::AlignRight, number);\n        }\n\n        block = block.next();\n        top = bottom;\n        bottom = top + static_cast<int>(blockBoundingRect(block).height());\n        ++blockNumber;\n    }\n}\n\nQColor CodeEditor::interpolate(QColor start,QColor end,double ratio)\n{\n    //From https://stackoverflow.com/questions/3306786/get-intermediate-color-from-a-gradient\n    int r = static_cast<int>(ratio*start.red() + (1-ratio)*end.red());\n    int g = static_cast<int>(ratio*start.green() + (1-ratio)*end.green());\n    int b = static_cast<int>(ratio*start.blue() + (1-ratio)*end.blue());\n    int a = static_cast<int>(ratio*start.alpha() + (1-ratio)*end.alpha());\n    QColor c = QColor::fromRgb(r,g,b);\n    c.setAlpha(a); // Strangely there seems to be no clean way of creating a rgba color, fromRgba does not take 4 parameters as one would expect.\n    return c;\n}\n\nQColor CodeEditor::heatColor(double ratio)\n{\n//return interpolate(darkMode?QColor(191, 0, 0):QColor(Qt::red).lighter(110), QColor(Qt::transparent), ratio);\n//    QColor bg =  darkMode?QColor(0x26, 0x26, 0x26):Qt::white;\n    QColor bg = theme.backgroundColor.get(darkMode);\n    bg.setAlpha(50);\n    return interpolate(darkMode?QColor(191, 0, 0):QColor(Qt::red).lighter(110), bg, ratio);\n}\n\nvoid CodeEditor::paintDebugInfo(QPaintEvent *event)\n{\n    QPainter painter(debugInfo);\n    QFont lineNoFont = font();\n    QFontMetrics fm(lineNoFont);\n    int origFontHeight = fm.height();\n    lineNoFont.setPointSizeF(lineNoFont.pointSizeF()*0.8);\n    QFontMetrics fm2(lineNoFont);\n    int heightDiff = (origFontHeight-fm2.height());\n    painter.setFont(lineNoFont);\n\n    painter.fillRect(event->rect(), theme.backgroundColor.get(darkMode));\n\n    // TODO: This should be pre-computed only once and stored in each block.\n    // Statistics should not be recounted at eack redraw...\n\n    QTextBlock block = firstVisibleBlock();\n    int blockNumber = block.blockNumber();\n    int top = static_cast<int>(blockBoundingGeometry(block).translated(contentOffset()).top());\n    int bottom = top + static_cast<int>(blockBoundingRect(block).height());\n\n    int curLine = textCursor().blockNumber();\n\n    painter.setPen(theme.foregroundActiveColor.get(darkMode));\n    while (block.isValid() && top <= event->rect().bottom()) {\n        BracketData* bd = static_cast<BracketData*>(block.userData());\n        if (block.isVisible() && bottom >= event->rect().top() && bd->d.hasData()) {\n//            int textTop = top+fontMetrics().leading()+heightDiff;\n            int textTop = top+heightDiff;\n            // num constraints\n            painter.fillRect(0, top, DEBUG_TAB_SIZE, static_cast<int>(blockBoundingRect(block).height()),\n                             heatColor(static_cast<double>(bd->d.con)/bd->d.totalCon));\n            QString numConstraints = QString().number(bd->d.con);\n            painter.drawText(0, textTop, DEBUG_TAB_SIZE, fm2.height(), Qt::AlignCenter, numConstraints);\n            // num vars\n            painter.fillRect(DEBUG_TAB_SIZE, top, DEBUG_TAB_SIZE, static_cast<int>(blockBoundingRect(block).height()),\n                              heatColor(static_cast<double>(bd->d.var)/bd->d.totalVar));\n            QString numVars = QString().number(bd->d.var);\n            painter.drawText(DEBUG_TAB_SIZE, textTop, DEBUG_TAB_SIZE, fm2.height(), Qt::AlignCenter, numVars);\n            // flatten time\n            painter.fillRect(DEBUG_TAB_SIZE*2, top, DEBUG_TAB_SIZE, static_cast<int>(blockBoundingRect(block).height()),\n                              heatColor(static_cast<double>(bd->d.ms)/bd->d.totalMs));\n            QString flattenTime = QString().number(bd->d.ms);\n            painter.drawText(DEBUG_TAB_SIZE*2, textTop, DEBUG_TAB_SIZE, fm2.height(), Qt::AlignCenter, flattenTime+\"ms\");\n            //            painter.drawText(0, textTop, debugInfo->width(), fm2.height(),\n//                             Qt::AlignLeft, number);\n        }\n\n        block = block.next();\n        top = bottom;\n        bottom = top + static_cast<int>(blockBoundingRect(block).height());\n        ++blockNumber;\n    }\n\n    painter.setPen(theme.foregroundInactiveColor.get(darkMode));\n    painter.drawLine(0,0,0,event->rect().bottom());\n}\n\nvoid CodeEditor::paintHeader(QPaintEvent *event)\n{\n    QPainter painter(editorHeader);\n    QFont lineNoFont = font();\n    QFontMetrics fm(lineNoFont);\n    int origFontHeight = fm.height();\n    lineNoFont.setPointSizeF(lineNoFont.pointSizeF()*0.8);\n    QFontMetrics fm2(lineNoFont);\n    int heightDiff = (origFontHeight-fm2.height());\n    painter.setFont(lineNoFont);\n\n\n    painter.fillRect(event->rect(), theme.backgroundColor.get(darkMode));\n    int baseX = debugInfo->geometry().x();\n    painter.setPen(theme.textColor.get(darkMode));\n    painter.drawText(baseX, heightDiff/2, DEBUG_TAB_SIZE, debugInfoOffset(), Qt::AlignCenter, \"Cons\");\n    painter.drawText(baseX + DEBUG_TAB_SIZE, heightDiff/2, DEBUG_TAB_SIZE, debugInfoOffset(), Qt::AlignCenter, \"Vars\");\n    painter.drawText(baseX + DEBUG_TAB_SIZE*2, heightDiff/2, DEBUG_TAB_SIZE, debugInfoOffset(), Qt::AlignCenter, \"Time\");\n}\n\nvoid CodeEditor::setEditorFont(QFont& font)\n{\n    setFont(font);\n    document()->setDefaultFont(font);\n    highlighter->setEditorFont(font);\n    setIndentSize(indentSize);\n    highlighter->rehighlight();\n}\n\nbool CodeEditor::eventFilter(QObject *, QEvent *ev)\n{\n    if (ev->type() == QEvent::KeyPress) {\n        QKeyEvent *keyEvent = static_cast<QKeyEvent*>(ev);\n        if (keyEvent == QKeySequence::Copy) {\n            copy();\n            return true;\n        } else if (keyEvent == QKeySequence::Cut) {\n            cut();\n            return true;\n        }\n    } else if (ev->type() == QEvent::ToolTip) {\n        QHelpEvent* helpEvent = static_cast<QHelpEvent*>(ev);\n        QPoint evPos = helpEvent->pos();\n        evPos = QPoint(evPos.x()-lineNumbersWidth(),evPos.y());\n        if (evPos.x() >= 0) {\n            QTextCursor cursor = cursorForPosition(evPos);\n            bool foundError = false;\n            foreach (CodeEditorError cee, errors) {\n                if (cursor.position() >= cee.startPos && cursor.position() <= cee.endPos) {\n                    QToolTip::showText(helpEvent->globalPos(), cee.msg);\n                    foundError = true;\n                    break;\n                }\n            }\n            if (!foundError) {\n                cursor.select(QTextCursor::WordUnderCursor);\n                QString loc(filename+\":\");\n                loc += QString().number(cursor.block().blockNumber()+1);\n                loc += \".\";\n                loc += QString().number(cursor.selectionStart()-cursor.block().position()+1);\n                QHash<QString,QString>::iterator idMapIt = idMap.find(loc);\n                if (idMapIt != idMap.end()) {\n                    QToolTip::showText(helpEvent->globalPos(), idMapIt.value());\n                } else {\n                    QToolTip::hideText();\n                }\n            }\n        } else {\n            QToolTip::hideText();\n        }\n        return true;\n    }\n    return false;\n}\n\nvoid CodeEditor::copy()\n{\n    highlighter->copyHighlightedToClipboard(textCursor());\n}\n\n\nvoid CodeEditor::cut()\n{\n    highlighter->copyHighlightedToClipboard(textCursor());\n    textCursor().removeSelectedText();\n}\n\nvoid CodeEditor::checkFile(const QVector<MiniZincError>& mznErrors)\n{\n    auto errorColor = theme.errorColor.get(darkMode);\n    auto warningColor = theme.warningColor.get(darkMode);\n\n    QList<QTextEdit::ExtraSelection> allExtraSels = extraSelections();\n\n    QList<QTextEdit::ExtraSelection> extraSelections;\n    foreach (QTextEdit::ExtraSelection sel, allExtraSels) {\n        if (sel.format.underlineColor() != errorColor && sel.format.underlineColor() != warningColor) {\n            extraSelections.append(sel);\n        }\n    }\n\n    errors.clear();\n    errorLines.clear();\n    warningLines.clear();\n    for (auto& it : mznErrors) {\n        QTextEdit::ExtraSelection sel;\n        QTextCharFormat format = sel.format;\n        format.setUnderlineStyle(QTextCharFormat::WaveUnderline);\n        format.setUnderlineColor(it.isWarning ? warningColor : errorColor);\n        QTextBlock block = document()->findBlockByNumber(it.first_line-1);\n        QTextBlock endblock = document()->findBlockByNumber(it.last_line-1);\n        if (block.isValid() && endblock.isValid()) {\n            QTextCursor cursor = textCursor();\n            cursor.setPosition(block.position());\n            int firstCol = it.first_col < it.last_col ? (it.first_col-1) : (it.last_col-1);\n            int lastCol = it.first_col < it.last_col ? (it.last_col) : (it.first_col);\n            cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, firstCol);\n            int startPos = cursor.position();\n            cursor.setPosition(endblock.position(), QTextCursor::KeepAnchor);\n            cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, lastCol);\n            int endPos = cursor.position();\n            sel.cursor = cursor;\n            sel.format = format;\n            extraSelections.append(sel);\n            CodeEditorError cee(startPos, endPos, QString(it.isWarning ? \"Warning: %1\" : \"Error: %1\").arg(it.msg));\n            errors.append(cee);\n            for (int j=it.first_line; j<=it.last_line; j++) {\n                if (it.isWarning) {\n                    warningLines.insert(j - 1);\n                } else {\n                    errorLines.insert(j - 1);\n                }\n            }\n        }\n    }\n    setExtraSelections(extraSelections);\n}\n\nvoid CodeEditor::shiftLeft()\n{\n    shiftSelected(-1);\n}\n\nvoid CodeEditor::shiftRight()\n{\n    shiftSelected(1);\n}\n\nvoid CodeEditor::shiftSelected(int amount)\n{\n    auto origCursor = textCursor();\n    origCursor.setKeepPositionOnInsert(true);\n    QTextCursor cursor = textCursor();\n    QTextBlock block = document()->findBlock(cursor.selectionStart());\n    QTextBlock endblock = document()->findBlock(cursor.selectionEnd());\n    bool atBlockStart = cursor.selectionEnd() == endblock.position();\n    if (block==endblock || !atBlockStart)\n        endblock = endblock.next();\n    cursor.beginEditBlock();\n    do {\n        auto position = block.position();\n        cursor.setPosition(position);\n        auto indentation = 0;\n        for (auto c : block.text()) {\n            if (c == '\\t') {\n                indentation = indentSize * ((indentation + indentSize) / indentSize);\n            } else if (c.isSpace()) {\n                indentation++;\n            } else {\n                break;\n            }\n            position++;\n        }\n        cursor.setPosition(position, QTextCursor::KeepAnchor);\n        auto newIndent = std::max(0, indentation + indentSize * amount);\n        if (useTabs) {\n            cursor.insertText(\n                        QString(\"\\t\").repeated(newIndent / indentSize) +\n                        QString(\" \").repeated(newIndent % indentSize));\n        } else {\n            cursor.insertText(QString(\" \").repeated(newIndent));\n        }\n        block = block.next();\n    } while (block.isValid() && block != endblock);\n    cursor.endEditBlock();\n    origCursor.setKeepPositionOnInsert(false);\n    setTextCursor(origCursor);\n}\n"
  },
  {
    "path": "MiniZincIDE/codeeditor.h",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#ifndef CODEEDITOR_H\n#define CODEEDITOR_H\n\n#include <QPlainTextEdit>\n#include <QTabWidget>\n#include <QCompleter>\n#include <QStringListModel>\n#include <QTimer>\n\n#include \"highlighter.h\"\n#include \"theme.h\"\n\nclass CodeEditorError {\npublic:\n    int startPos;\n    int endPos;\n    QString msg;\n    CodeEditorError(int startPos0, int endPos0, const QString& msg0)\n        : startPos(startPos0), endPos(endPos0), msg(msg0) {}\n};\n\nstruct MiniZincError {\n    bool isWarning;\n    QString filename;\n    int first_line;\n    int last_line;\n    int first_col;\n    int last_col;\n    QString msg;\n};\n\nclass EditorHeader;\n\nclass CodeEditor : public QPlainTextEdit\n{\n    Q_OBJECT\npublic:\n    explicit CodeEditor(QTextDocument* doc, const QString& path, bool isNewFile, bool large,\n                        QFont& font, int indentSize, bool useTabs, const Theme& theme, bool darkMode,\n                        QTabWidget* tabs, QWidget *parent);\n    void paintLineNumbers(QPaintEvent *event);\n    int lineNumbersWidth();\n    void paintDebugInfo(QPaintEvent *event);\n    void paintHeader(QPaintEvent *event);\n    int debugInfoWidth();\n    int debugInfoOffset();\n    QString filepath;\n    QString filename;\n    QString playgroundTempFile;\n    void setEditorFont(QFont& font);\n    void setDocument(QTextDocument *document);\n    void setTheme(const Theme& t, bool d);\n    void setIndentSize(int size);\n    void setIndentTab(bool useTabs0) { useTabs = useTabs0; }\n    Highlighter& getHighlighter();\n    void checkFile(const QVector<MiniZincError>& errors);\n    void showDebugInfo(bool show);\n    const static int DEBUG_TAB_SIZE = 70;\n    void shiftLeft();\n    void shiftRight();\nprotected:\n    void resizeEvent(QResizeEvent *event);\n    void showEvent(QShowEvent *event);\n    void initUI(QFont& font);\n    virtual void keyPressEvent(QKeyEvent *e);\n    bool eventFilter(QObject *, QEvent *);\nprivate slots:\n    void setViewportWidth(int newBlockCount);\n    void cursorChange();\n    void setLineNumbers(const QRect &, int);\n    void setDebugInfoPos(const QRect &, int);\n    void docChanged(bool);\n    void contentsChanged();\n    void contentsChangedWithTimeout();\n    void loadContents();\n    void insertCompletion(const QString& completion);\nprivate:\n    QWidget* lineNumbers;\n    QWidget* debugInfo;\n    EditorHeader* editorHeader;\n    QWidget* loadContentsButton;\n    QTabWidget* tabs;\n    Highlighter* highlighter;\n    QCompleter* completer;\n    QStringListModel completionModel;\n    int indentSize;\n    bool useTabs;\n    QList<CodeEditorError> errors;\n    QSet<int> errorLines;\n    QSet<int> warningLines;\n    QHash<QString,QString> idMap;\n    QTimer modificationTimer;\n    Theme theme;\n    bool darkMode;\n    int matchLeft(QTextBlock block, QChar b, int i, int n);\n    int matchRight(QTextBlock block, QChar b, int i, int n);\n\n    void shiftSelected(int amount);\n\n    QColor interpolate(QColor start,QColor end,double ratio); // This should not go here\n    QColor heatColor(double ratio); // This should not go here\n\nsignals:\n    void escPressed();\n    void changedDebounced();\npublic slots:\n    void loadedLargeFile();\n    void copy();\n    void cut();\n};\n\nclass LineNumbers: public QWidget\n{\npublic:\n    LineNumbers(CodeEditor *e) : QWidget(e), codeEditor(e) {}\n\n    QSize sizeHint() const {\n        return QSize(codeEditor->lineNumbersWidth(), 0);\n    }\n\nprotected:\n    void paintEvent(QPaintEvent *event) {\n        codeEditor->paintLineNumbers(event);\n    }\n\nprivate:\n    CodeEditor *codeEditor;\n};\n\nclass DebugInfo: public QWidget\n{\npublic:\n    DebugInfo(CodeEditor *e) : QWidget(e), codeEditor(e) {}\n\n    QSize sizeHint() const {\n        return QSize(codeEditor->debugInfoWidth(), 0);\n    }\n\nprotected:\n    void paintEvent(QPaintEvent *event) {\n        codeEditor->paintDebugInfo(event);\n    }\n\nprivate:\n    CodeEditor *codeEditor;\n};\n\nclass EditorHeader: public QWidget\n{\n    Q_OBJECT\npublic:\n    EditorHeader(CodeEditor *e) : QWidget(e), codeEditor(e) {\n        setMouseTracking(true);\n    }\n\n    QSize sizeHint() const {\n        return QSize(0, codeEditor->debugInfoOffset());\n    }\n\nprotected:\n    void paintEvent(QPaintEvent *event) {\n        codeEditor->paintHeader(event);\n    }\nprivate:\n    CodeEditor *codeEditor;\n\n};\n\n#endif // CODEEDITOR_H\n"
  },
  {
    "path": "MiniZincIDE/configwindow.cpp",
    "content": "/*\n *  Main authors:\n *     Jason Nguyen <jason.nguyen@monash.edu>\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#include \"configwindow.h\"\n#include \"ui_configwindow.h\"\n\n#include <QFile>\n#include <QFileDialog>\n#include <QTextStream>\n#include <QMessageBox>\n#include <QMenu>\n#include <QScrollBar>\n#include <QValidator>\n#include <QWidgetAction>\n\n#include \"process.h\"\n#include \"exception.h\"\n#include \"ideutils.h\"\n\n#include <algorithm>\n#include <limits>\n#include <cfloat>\n\nConfigWindow::ConfigWindow(QWidget *parent) :\n    QWidget(parent),\n    ui(new Ui::ConfigWindow)\n{\n    ui->setupUi(this);\n    ui->userDefinedBehavior_frame->setVisible(false);\n\n    extraParamDialog = new ExtraParamDialog;\n\n    auto* widgetAction = new QWidgetAction(nullptr);\n    widgetAction->setDefaultWidget(extraParamDialog);\n\n    auto* menu = new QMenu(this);\n    menu->addAction(widgetAction);\n    ui->addExtraParam_toolButton->setMenu(menu);\n\n    connect(extraParamDialog, &ExtraParamDialog::addParams, this, [=](const QList<SolverFlag>& flags) {\n        for (auto& f : flags) {\n            addExtraParam(f);\n            extraParamDialog->setParamEnabled(f, false);\n        }\n        resizeExtraFlagsTable();\n        invalidate(false);\n        menu->hide();\n    });\n\n    connect(extraParamDialog, &ExtraParamDialog::addCustomParam, this, [=]() {\n        addExtraParam();\n        resizeExtraFlagsTable();\n        invalidate(false);\n        menu->hide();\n    });\n\n    ui->extraParams_tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);\n    ui->extraParams_tableWidget->setItemDelegateForColumn(3, new ExtraOptionDelegate);\n\n    IDEUtils::watchChildChanges(ui->options_groupBox, this, std::bind(&ConfigWindow::invalidate, this, true));\n    IDEUtils::watchChildChanges(ui->advanced_groupBox, this, std::bind(&ConfigWindow::invalidate, this, false));\n}\n\nConfigWindow::~ConfigWindow()\n{\n    delete ui;\n    for (auto sc : configs) {\n        delete sc;\n    }\n}\n\nvoid ConfigWindow::init()\n{\n    loadConfigs();\n    initialized = true;\n}\n\nvoid ConfigWindow::loadConfigs(void)\n{\n    try {\n        QStringList files;\n        QStringList warnings;\n        for (auto config : configs) {\n            if (!config->paramFile.isEmpty()) {\n                files.append(config->paramFile);\n            }\n            delete config;\n        }\n        configs.clear();\n        for (auto* solver : MznDriver::get().solvers()) {\n            if (solver->hasAllRequiredFlags()) {\n                configs.append(new SolverConfiguration(*solver, true));\n            }\n        }\n        if (!files.empty()) {\n            for (auto fileName : files) {\n                configs.append(new (SolverConfiguration) (SolverConfiguration::loadJSON(fileName, warnings)));\n            }\n        }\n        populateComboBox();\n        updateGUI(true);\n        if (!warnings.empty()) {\n            QMessageBox::warning(this, \"MiniZinc IDE\", warnings.join(\"\\n\"), QMessageBox::Ok);\n        }\n    } catch (Exception& e) {\n        QMessageBox::warning(this, \"Parameter configuration error\", e.message(), QMessageBox::Ok);\n    }\n}\n\nbool ConfigWindow::addConfig(const QString &fileName)\n{\n    int index = findConfigFile(fileName);\n    if (index != -1) {\n        // Already have this config\n        setCurrentIndex(index);\n        return true;\n    }\n\n    try {\n        QStringList warnings;\n        updateSolverConfig(configs[currentIndex()]);\n        configs.append(new (SolverConfiguration) (SolverConfiguration::loadJSON(fileName, warnings)));\n        for (auto sc : configs) {\n            if (!sc->syncedOptionsMatch(*(configs.last()))) {\n                populating = true; // Make sure this doesn't mark the config as modified\n                ui->sync_checkBox->setChecked(false);\n                populating = false;\n                break;\n            }\n        }\n        populateComboBox();\n\n        if (ui->sync_checkBox->isChecked()) {\n            // Ensure that newly loaded config overrides synced options\n            populating = true; // Make sure this doesn't mark the config as modified\n            ui->sync_checkBox->setChecked(false);\n            populating = false;\n            setCurrentIndex(configs.length() - 1);\n            populating = true; // Make sure this doesn't mark the config as modified\n            ui->sync_checkBox->setChecked(true);\n            populating = false;\n        }\n\n        if (!warnings.empty()) {\n            QMessageBox::warning(this, \"MiniZinc IDE\", warnings.join(\"\\n\"), QMessageBox::Ok);\n        }\n\n        return true;\n    } catch (Exception& e) {\n        QMessageBox::warning(this, \"Parameter configuration error\", e.message(), QMessageBox::Ok);\n        return false;\n    }\n}\n\nvoid ConfigWindow::mergeConfigs(const QList<SolverConfiguration*> confs)\n{\n    for (auto sc : confs) {\n        int swapAt = sc->isBuiltin ?\n                    findBuiltinConfig(sc->solverDefinition.id, sc->solverDefinition.version) :\n                    findConfigFile(sc->paramFile);\n\n        sc->modified = true;\n        if (swapAt == -1) {\n            sc->isBuiltin = false;\n            configs.append(sc);\n        } else {\n            delete configs[swapAt];\n            configs.replace(swapAt, sc);\n        }\n    }\n    updateGUI(true);\n    populateComboBox();\n}\n\nint ConfigWindow::findBuiltinConfig(const QString &id, const QString &version)\n{\n    int matchId = -1;\n    int i = 0;\n    for (auto sc : configs) {\n        if (sc->isBuiltin && sc->solverDefinition.id == id) {\n            matchId = i;\n            if (sc->solverDefinition.version == version) {\n                return i;\n            }\n        }\n        i++;\n    }\n    return matchId;\n}\n\nint ConfigWindow::findConfigFile(const QString &file)\n{\n    if (file.isEmpty()) {\n        // Unsaved configuration\n        return -1;\n    }\n    int i = 0;\n    for (auto sc : configs) {\n        if (sc->paramFile == file) {\n            return i;\n        }\n        i++;\n    }\n    return -1;\n}\n\nvoid ConfigWindow::stashModifiedConfigs()\n{\n    auto* selected = currentSolverConfig();\n    for (auto* sc : configs) {\n        if (sc->modified) {\n            if (sc == selected) {\n                stashSelectedModifiedConfig = stash.length();\n            }\n            stash.append(StashItem(sc));\n        }\n    }\n    if (selected != nullptr) {\n        if (selected->isBuiltin) {\n            stashSelectedBuiltinSolverId = selected->solverDefinition.id;\n            stashSelectedBuiltinSolverVersion = selected->solverDefinition.version;\n        }\n        stashSelectedParamFile = selected->paramFile;\n    }\n}\n\nvoid ConfigWindow::unstashModifiedConfigs()\n{\n    lastIndex = -1;\n\n    QList<SolverConfiguration*> merge;\n    for (auto& item : stash) {\n        try {\n            merge.append(item.consume());\n        }\n        catch (...) {\n            // Ignore\n        }\n    }\n    mergeConfigs(merge);\n\n    int i = -1;\n    if (stashSelectedModifiedConfig >= 0 && stashSelectedModifiedConfig < merge.size()) {\n        i = configs.indexOf(merge[stashSelectedModifiedConfig]);\n    } else if (!stashSelectedParamFile.isEmpty()) {\n        i = findConfigFile(stashSelectedParamFile);\n    } else if (!stashSelectedBuiltinSolverId.isEmpty()) {\n        i = findBuiltinConfig(stashSelectedBuiltinSolverId, stashSelectedBuiltinSolverVersion);\n    }\n    if (i != -1) {\n        setCurrentIndex(i);\n    }\n\n    stash.clear();\n    stashSelectedBuiltinSolverId = \"\";\n    stashSelectedBuiltinSolverVersion = \"\";\n    stashSelectedParamFile = \"\";\n    stashSelectedModifiedConfig = -1;\n}\n\nQString ConfigWindow::saveConfig(int index)\n{\n    auto sc = configs[index];\n    auto oldSc = sc;\n    updateSolverConfig(configs[currentIndex()]);\n\n    if (sc->isBuiltin) {\n        // Must clone built-in configs\n        sc = new SolverConfiguration(*sc);\n    }\n\n    QString target = sc->paramFile;\n    if (sc->paramFile.isEmpty()) {\n        QFileInfo fi(sc->paramFile);\n        target = QFileDialog::getSaveFileName(this,\n                                              \"Save configuration\",\n                                              fi.canonicalFilePath(),\n                                              \"Solver configuration files (*.mpc)\");\n    }\n\n    if (target.isEmpty()) {\n        return \"\";\n    }\n\n    QFile f(target);\n    f.open(QFile::WriteOnly | QFile::Truncate);\n    f.write(sc->toJSON());\n    f.close();\n\n    oldSc->modified = false;\n    sc->modified = false;\n    sc->paramFile = target;\n    populateComboBox();\n\n    emit configSaved(target);\n    return target;\n}\n\nQString ConfigWindow::saveConfig()\n{\n    return saveConfig(currentIndex());\n}\n\nSolverConfiguration* ConfigWindow::currentSolverConfig(void)\n{\n    int i = currentIndex();\n    if (i < 0 || i >= configs.length()) {\n        return nullptr;\n    }\n    auto sc = configs[i];\n    updateSolverConfig(sc);\n    return sc;\n}\n\nconst QList<SolverConfiguration*>& ConfigWindow::solverConfigs()\n{\n    currentSolverConfig();\n    return configs;\n}\n\nint ConfigWindow::currentIndex(void)\n{\n    return ui->config_comboBox->currentIndex();\n}\n\nvoid ConfigWindow::setCurrentIndex(int i)\n{\n    ui->config_comboBox->setCurrentIndex(qBound(0, i, configs.length()));\n}\n\nvoid ConfigWindow::on_numSolutions_checkBox_stateChanged(int arg1)\n{\n    ui->numSolutions_spinBox->setEnabled(arg1);\n}\n\nvoid ConfigWindow::on_numOptimal_checkBox_stateChanged(int arg1)\n{\n    ui->numOptimal_spinBox->setEnabled(arg1);\n}\n\nvoid ConfigWindow::on_timeLimit_checkBox_stateChanged(int arg1)\n{\n    ui->timeLimit_doubleSpinBox->setEnabled(arg1);\n}\n\nvoid ConfigWindow::on_randomSeed_checkBox_stateChanged(int arg1)\n{\n    ui->randomSeed_lineEdit->setEnabled(arg1);\n}\n\nvoid ConfigWindow::on_defaultBehavior_radioButton_toggled(bool checked)\n{\n    ui->defaultBehavior_frame->setVisible(checked);\n}\n\nvoid ConfigWindow::on_userDefinedBehavior_radioButton_toggled(bool checked)\n{\n    ui->userDefinedBehavior_frame->setVisible(checked);\n}\n\nvoid ConfigWindow::on_config_comboBox_currentIndexChanged(int index)\n{\n    if (!initialized) {\n        return;\n    }\n\n    if (lastIndex >= 0) {\n        updateSolverConfig(configs[lastIndex]);\n    }\n\n    lastIndex = index;\n\n    updateGUI();\n\n    if (index >= 0 && index < configs.length()) {\n        emit selectedIndexChanged(index);\n        emit selectedSolverConfigurationChanged(configs[index]);\n    }\n}\n\nvoid ConfigWindow::updateGUI(bool overrideSync)\n{\n    int index = currentIndex();\n\n    if (configs.isEmpty()) {\n        ui->solver_controls->setVisible(false);\n        ui->options_groupBox->setVisible(false);\n        ui->advanced_groupBox->setVisible(false);\n        return;\n    }\n\n    auto sc = configs[index];\n\n    ui->solver_controls->setVisible(true);\n    ui->options_groupBox->setVisible(true);\n    ui->advanced_groupBox->setVisible(true);\n\n    populating = true;\n\n    ui->solver_label->setText(sc->solverDefinition.name + \" \" + sc->solverDefinition.version);\n\n    ui->makeConfigDefault_pushButton->setEnabled(sc->isBuiltin && !sc->solverDefinition.isDefaultSolver);\n\n    ui->reset_pushButton->setEnabled(sc->isBuiltin);\n\n    ui->intermediateSolutions_checkBox->setEnabled(sc->supports(\"-a\") || sc->supports(\"-i\"));\n\n    ui->numSolutions_checkBox->setEnabled(sc->supports(\"-a\"));\n    ui->numOptimal_checkBox->setEnabled(sc->supports(\"-a-o\"));\n    ui->verboseSolving_checkBox->setEnabled(sc->supports(\"-v\"));\n    ui->solvingStats_checkBox->setEnabled(sc->supports(\"-s\"));\n\n    if (overrideSync || !ui->sync_checkBox->isChecked()) {\n        ui->timeLimit_checkBox->setChecked(sc->timeLimit != 0);\n        ui->timeLimit_doubleSpinBox->setValue(sc->timeLimit / 1000.0);\n        ui->timeLimit_doubleSpinBox->setEnabled(ui->timeLimit_checkBox->isChecked());\n\n        if (sc->printIntermediate && sc->numSolutions == 1) {\n            ui->defaultBehavior_radioButton->setChecked(true);\n        } else {\n            ui->userDefinedBehavior_radioButton->setChecked(true);\n        }\n\n        ui->intermediateSolutions_checkBox->setChecked(sc->printIntermediate);\n        ui->numSolutions_spinBox->setValue(sc->numSolutions);\n        ui->numSolutions_checkBox->setChecked(sc->numSolutions != 0);\n        ui->numOptimal_spinBox->setValue(sc->numOptimal);\n        ui->numOptimal_checkBox->setChecked(sc->numOptimal != 0);\n        ui->verboseCompilation_checkBox->setChecked(sc->verboseCompilation);\n\n        if (ui->verboseSolving_checkBox->isEnabled()) {\n            ui->verboseSolving_checkBox->setChecked(sc->verboseSolving);\n        }\n        ui->compilationStats_checkBox->setChecked(sc->compilationStats);\n\n        if (ui->solvingStats_checkBox->isEnabled()) {\n            ui->solvingStats_checkBox->setChecked(sc->solvingStats);\n        }\n\n        ui->timingInfo_checkBox->setChecked(sc->outputTiming);\n        ui->outputObjective_checkBox->setChecked(sc->outputObjective);\n    }\n\n    ui->outputObjective_checkBox->setEnabled(sc->solverDefinition.inputType != Solver::I_MZN || sc->supports(\"--output-objective\"));\n\n    ui->numSolutions_spinBox->setEnabled(ui->numSolutions_checkBox->isEnabled() && ui->numSolutions_checkBox->isChecked());\n    ui->numOptimal_spinBox->setEnabled(ui->numOptimal_checkBox->isEnabled() && ui->numOptimal_checkBox->isChecked());\n\n    ui->optimizationLevel_comboBox->setCurrentIndex(sc->optimizationLevel);\n    ui->additionalData_plainTextEdit->setPlainText(sc->additionalData.join(\"\\n\"));\n\n    ui->numThreads_spinBox->setEnabled(sc->supports(\"-p\"));\n    ui->numThreads_spinBox->setValue(sc->numThreads);\n\n    bool enableRandomSeed = sc->supports(\"-r\");\n    ui->randomSeed_lineEdit->setText(sc->randomSeed.toString());\n    ui->randomSeed_checkBox->setChecked(!sc->randomSeed.isNull());\n    ui->randomSeed_checkBox->setEnabled(enableRandomSeed);\n    ui->randomSeed_lineEdit->setEnabled(enableRandomSeed && !sc->randomSeed.isNull());\n\n    bool enableFreeSearch = sc->supports(\"-f\");\n    ui->freeSearch_checkBox->setEnabled(enableFreeSearch);\n    ui->freeSearch_checkBox->setChecked(sc->freeSearch);\n\n    extraParamDialog->setParams(sc->solverDefinition.extraFlags);\n\n    while (ui->extraParams_tableWidget->rowCount() > 0) {\n        ui->extraParams_tableWidget->removeRow(0);\n    }\n\n    for (auto it = sc->extraOptions.begin(); it != sc->extraOptions.end(); it++) {\n        bool matched = false;\n        for (auto& f : sc->solverDefinition.extraFlags) {\n            if (f.name == it.key()) {\n                extraParamDialog->setParamEnabled(f, false);\n                addExtraParam(f, it.value());\n                matched = true;\n                break;\n            }\n        }\n        if (!matched) {\n            addExtraParam(it.key(), false, it.value());\n        }\n    }\n\n    for (auto it = sc->solverBackendOptions.begin(); it != sc->solverBackendOptions.end(); it++) {\n        addExtraParam(it.key(), true, it.value());\n    }\n\n    resizeExtraFlagsTable();\n\n    populating = false;\n}\n\n\nvoid ConfigWindow::updateSolverConfig(SolverConfiguration* sc)\n{\n    // Make sure if anything was being edited, we defocus to update the value\n    auto* focus = focusWidget();\n    if (focus && focus->hasFocus()) {\n        focus->clearFocus();\n        focus->setFocus();\n    }\n\n    if (!sc->modified) {\n        return;\n    }\n\n    auto cfgs = ui->sync_checkBox->isChecked() ? configs : (QList<SolverConfiguration*>() << sc);\n    for (auto s : cfgs) {\n        s->timeLimit = ui->timeLimit_checkBox->isChecked() ? static_cast<int>(ui->timeLimit_doubleSpinBox->value() * 1000.0) : 0;\n        if (ui->defaultBehavior_radioButton->isChecked()) {\n            s->printIntermediate = true;\n            s->numSolutions = 1;\n            s->numOptimal = 1;\n        } else {\n            s->printIntermediate = ui->intermediateSolutions_checkBox->isChecked();\n            s->numSolutions = ui->numSolutions_checkBox->isChecked() ? ui->numSolutions_spinBox->value() : 0;\n            s->numOptimal = ui->numOptimal_checkBox->isChecked() ? ui->numOptimal_spinBox->value() : 0;\n        }\n\n        s->verboseCompilation = ui->verboseCompilation_checkBox->isChecked();\n        s->verboseSolving = ui->verboseSolving_checkBox->isChecked();\n        s->compilationStats = ui->compilationStats_checkBox->isChecked();\n        s->solvingStats = ui->solvingStats_checkBox->isChecked();\n        s->outputTiming = ui->timingInfo_checkBox->isChecked();\n        s->outputObjective = ui->outputObjective_checkBox->isChecked();\n    }\n\n    sc->optimizationLevel = ui->optimizationLevel_comboBox->currentIndex();\n    QString additionalData = ui->additionalData_plainTextEdit->toPlainText();\n    sc->additionalData = additionalData.length() > 0 ? additionalData.split(\"\\n\") : QStringList();\n    sc->numThreads = ui->numThreads_spinBox->value();\n    sc->randomSeed = ui->randomSeed_checkBox->isChecked() ? ui->randomSeed_lineEdit->text() : QVariant();\n    sc->freeSearch = ui->freeSearch_checkBox->isChecked();\n\n    sc->extraOptions.clear();\n    for (int row = 0; row < ui->extraParams_tableWidget->rowCount(); row++) {\n        auto keyItem = ui->extraParams_tableWidget->item(row, 0);\n        auto flagTypeWidget = static_cast<QComboBox*>(ui->extraParams_tableWidget->cellWidget(row, 1));\n        auto valueItem = ui->extraParams_tableWidget->item(row, 3);\n        if (keyItem && valueItem) {\n            auto key = keyItem->data(Qt::UserRole).isNull() ?\n                        keyItem->data(Qt::DisplayRole).toString() :\n                        qvariant_cast<SolverFlag>(keyItem->data(Qt::UserRole)).name;\n            auto value = valueItem->data(Qt::DisplayRole);\n            if (flagTypeWidget && flagTypeWidget->currentIndex() == 1) {\n                sc->solverBackendOptions.insert(key, value);\n            } else {\n                sc->extraOptions.insert(key, value);\n            }\n        }\n    }\n}\n\nvoid ConfigWindow::on_removeExtraParam_toolButton_clicked()\n{\n    QVector<int> toBeRemoved;\n    for (auto& range : ui->extraParams_tableWidget->selectedRanges()) {\n        for (int i = range.topRow(); i <= range.bottomRow(); i++) {\n            toBeRemoved.append(i);\n        }\n    }\n    std::sort(toBeRemoved.begin(), toBeRemoved.end(), std::greater<int>());\n    for (auto i : toBeRemoved) {\n        auto data = ui->extraParams_tableWidget->item(i, 0)->data(Qt::UserRole);\n        if (!data.isNull()) {\n            // Re-enable adding of this flag\n            extraParamDialog->setParamEnabled(qvariant_cast<SolverFlag>(data), true);\n        }\n        ui->extraParams_tableWidget->removeRow(i);\n    }\n    if (!toBeRemoved.isEmpty()) {\n        resizeExtraFlagsTable();\n        invalidate(false);\n    }\n}\n\nvoid ConfigWindow::on_extraParams_tableWidget_itemSelectionChanged()\n{\n    bool hasSelection = ui->extraParams_tableWidget->selectedRanges().length();\n    ui->removeExtraParam_toolButton->setEnabled(hasSelection);\n}\n\nvoid ConfigWindow::addExtraParam(const SolverFlag& f, const QVariant& value)\n{\n    int i = ui->extraParams_tableWidget->rowCount();\n    ui->extraParams_tableWidget->insertRow(i);\n\n    auto keyItem = new QTableWidgetItem(f.description);\n    keyItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);\n    keyItem->setData(Qt::UserRole, QVariant::fromValue(f));\n    keyItem->setToolTip(f.name);\n\n    auto flagTypeItem = new QTableWidgetItem;\n    flagTypeItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);\n\n    auto typeItem = new QTableWidgetItem;\n    typeItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);\n\n    auto valueItem = new QTableWidgetItem;\n    valueItem->setData(Qt::UserRole, QVariant::fromValue(f));\n    if (value.isNull()) {\n        valueItem->setData(Qt::DisplayRole, f.def);\n    } else {\n        valueItem->setData(Qt::DisplayRole, value);\n    }\n    if (f.t == SolverFlag::T_BOOL && f.def.toBool()) {\n        // Non switched booleans which are default true cannot be turned off\n        // TODO: Maybe this should send the flag when different to default?\n        valueItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);\n    }\n\n    ui->extraParams_tableWidget->setItem(i, 0, keyItem);\n    ui->extraParams_tableWidget->setItem(i, 1, flagTypeItem);\n    ui->extraParams_tableWidget->setItem(i, 2, typeItem);\n    ui->extraParams_tableWidget->setItem(i, 3, valueItem);\n\n    invalidate(false);\n}\n\nvoid ConfigWindow::addExtraParam(const QString& key, bool backend, const QVariant& value)\n{\n    int i = ui->extraParams_tableWidget->rowCount();\n    ui->extraParams_tableWidget->insertRow(i);\n\n    auto flag = key.startsWith(\"--\") ? key.right(key.length() - 2) : key;\n    auto keyItem = new QTableWidgetItem(flag);\n    auto valItem = new QTableWidgetItem;\n    valItem->setData(Qt::DisplayRole, value);\n    ui->extraParams_tableWidget->setItem(i, 0, keyItem);\n    ui->extraParams_tableWidget->setItem(i, 3, valItem);\n\n    auto flagTypeComboBox = new QComboBox;\n    flagTypeComboBox->addItems({\"MiniZinc\", \"Solver backend\"});\n    flagTypeComboBox->setCurrentIndex(backend ? 1 : 0);\n    flagTypeComboBox->setToolTip(\"Controls whether the flag is sent to the underlying solver executable (if available), or to the minizinc command.\");\n    ui->extraParams_tableWidget->setCellWidget(i, 1, flagTypeComboBox);\n\n    auto typeComboBox = new QComboBox;\n    typeComboBox->setToolTip(\"Controls the data type of the flag value\");\n    typeComboBox->addItems({\"String\", \"Boolean\", \"Integer\", \"Float\"});\n    switch (value.type()) {\n    case QVariant::String:\n        typeComboBox->setCurrentIndex(0);\n        break;\n    case QVariant::Bool:\n        typeComboBox->setCurrentIndex(1);\n        break;\n    case QVariant::Int:\n        typeComboBox->setCurrentIndex(2);\n        break;\n    case QVariant::Double:\n        typeComboBox->setCurrentIndex(3);\n        break;\n    default:\n        typeComboBox->setCurrentIndex(0);\n        break;\n    }\n\n    connect(typeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [=] (int typeIndex) mutable {\n        QVariant::Type types[] = {QVariant::String, QVariant::Bool, QVariant::Int, QVariant::Double};\n        auto target = types[typeIndex];\n        auto data = valItem->data(Qt::DisplayRole);\n        data.convert(target);\n        auto newValItem = new QTableWidgetItem;\n        newValItem->setData(Qt::DisplayRole, data);\n        ui->extraParams_tableWidget->setItem(valItem->row(), 3, newValItem);\n        valItem = newValItem;\n    });\n\n    ui->extraParams_tableWidget->setCellWidget(i, 2, typeComboBox);\n}\n\nvoid ConfigWindow::invalidate(bool all)\n{\n    if (populating) {\n        // Ignore changes while populating the configuration widgets\n        return;\n    }\n\n    bool refreshComboBox = false;\n\n    int i = currentIndex();\n    if (i >= 0 && i < configs.length() && !configs[i]->modified) {\n        configs[i]->modified = true;\n        refreshComboBox = true;\n    }\n\n    if (all && ui->sync_checkBox->isChecked()) {\n        for (auto sc : configs) {\n            // Only non-builtin configs really need to be saved after modifying basic options\n            if (!sc->isBuiltin && !sc->modified) {\n                sc->modified = true;\n                refreshComboBox = true;\n            }\n        }\n    }\n\n    if (refreshComboBox) {\n        populateComboBox();\n    }\n}\n\nvoid ConfigWindow::populateComboBox()\n{\n    bool oldInitialized = initialized;\n    int oldIndex = currentIndex();\n    QStringList items;\n    for (auto sc : configs) {\n        items << sc->name();\n    }\n\n    initialized = false;\n    ui->config_comboBox->clear();\n    ui->config_comboBox->addItems(items);\n    if (oldIndex >= 0 && oldIndex < configs.length()) {\n        setCurrentIndex(oldIndex);\n    } else {\n        // Index no longer valid, just go back to default solver\n        int i = 0;\n        for (auto config : configs) {\n            if (config->solverDefinition.isDefaultSolver) {\n                setCurrentIndex(i);\n                break;\n            }\n            i++;\n        }\n    }\n    emit itemsChanged(items);\n    initialized = oldInitialized;\n}\n\nvoid ConfigWindow::removeConfig(int i)\n{\n    bool targetIndex = i < currentIndex() ? currentIndex() - 1 : currentIndex();\n    delete configs[i];\n    configs.removeAt(i);\n    populateComboBox();\n    setCurrentIndex(targetIndex);\n}\n\nvoid ConfigWindow::on_clone_pushButton_clicked()\n{\n    auto sc = configs[currentIndex()];\n    updateSolverConfig(sc);\n    auto clone = new SolverConfiguration(*sc);\n    clone->paramFile = \"\";\n    clone->isBuiltin = false;\n    clone->modified = true;\n    configs << clone;\n    populateComboBox();\n    setCurrentIndex(configs.length() - 1);\n}\n\nvoid ConfigWindow::resizeExtraFlagsTable()\n{\n    auto* hScroll = ui->extraParams_tableWidget->horizontalScrollBar();\n    // To avoid creating a vertical scrollbar if there's a horizontal one\n    int padding = hScroll ? hScroll->height() : 0;\n    int total_height = ui->extraParams_tableWidget->horizontalHeader()->height() + padding;\n    if (!ui->extraParams_tableWidget->horizontalScrollBar()->isHidden()) {\n        total_height += ui->extraParams_tableWidget->horizontalScrollBar()->height();\n    }\n    for (int row = 0; row < ui->extraParams_tableWidget->rowCount(); row++) {\n        total_height += ui->extraParams_tableWidget->rowHeight(row);\n    }\n    ui->extraParams_tableWidget->setMinimumHeight(total_height);\n}\n\nQWidget* ExtraOptionDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const\n{\n    if (index.data(Qt::UserRole).canConvert<SolverFlag>()) {\n        auto f = qvariant_cast<SolverFlag>(index.data(Qt::UserRole));\n        switch (f.t) {\n        case SolverFlag::T_INT:\n        {\n            auto field = new QLineEdit(parent);\n            field->setAlignment(Qt::AlignRight);\n            field->setValidator(new LongLongValidator());\n            return field;\n        }\n        case SolverFlag::T_INT_RANGE:\n        {\n            if (f.min_ll < std::numeric_limits<int>::min() || f.max_ll > std::numeric_limits<int>::max()) {\n                auto field = new QLineEdit(parent);\n                field->setAlignment(Qt::AlignRight);\n                field->setValidator(new LongLongValidator(f.min_ll, f.max_ll));\n                return field;\n            } else {\n                auto field = new QSpinBox(parent);\n                field->setRange(static_cast<int>(f.min_ll), static_cast<int>(f.max_ll));\n                return field;\n            }\n        }\n        case SolverFlag::T_FLOAT:\n        {\n            auto field = new QLineEdit(parent);\n            field->setAlignment(Qt::AlignRight);\n            field->setValidator(new QDoubleValidator());\n            return field;\n        }\n        case SolverFlag::T_FLOAT_RANGE:\n        {\n            auto field = new QDoubleSpinBox(parent);\n            field->setDecimals(DBL_MAX_10_EXP + DBL_DIG);\n            field->setRange(f.min, f.max);\n            return field;\n        }\n        case SolverFlag::T_OPT:\n        {\n            auto field = new QComboBox(parent);\n            field->addItems(f.options);\n            return field;\n        }\n        case SolverFlag::T_SOLVER:\n        {\n            auto field = new QComboBox(parent);\n            for (auto* solver : MznDriver::get().solvers()) {\n                field->addItem(solver->name);\n            }\n            return field;\n        }\n        case SolverFlag::T_BOOL:\n        case SolverFlag::T_BOOL_ONOFF:\n        case SolverFlag::T_STRING:\n            return QStyledItemDelegate::createEditor(parent, option, index);\n        }\n    } else if (index.data().type() == QVariant::Double) {\n        auto field = new QLineEdit(parent);\n        field->setAlignment(Qt::AlignRight);\n        field->setValidator(new QDoubleValidator());\n        return field;\n    } else {\n        return QStyledItemDelegate::createEditor(parent, option, index);\n    }\n}\n\nvoid ExtraOptionDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const\n{\n    if (index.data(Qt::UserRole).canConvert<SolverFlag>()) {\n        auto f = qvariant_cast<SolverFlag>(index.data(Qt::UserRole));\n        switch (f.t) {\n        case SolverFlag::T_INT:\n            static_cast<QLineEdit*>(editor)->setText(QString::number(index.data().toLongLong()));\n            break;\n        case SolverFlag::T_INT_RANGE: {\n            auto* e = qobject_cast<QSpinBox*>(editor);\n            if (e) {\n                e->setValue(index.data().toLongLong());\n            } else {\n                static_cast<QLineEdit*>(editor)->setText(QString::number(index.data().toLongLong()));\n            }\n            break;\n        }\n        case SolverFlag::T_FLOAT:\n            static_cast<QLineEdit*>(editor)->setText(QString::number(index.data().toDouble()));\n            break;\n        case SolverFlag::T_FLOAT_RANGE:\n            static_cast<QDoubleSpinBox*>(editor)->setValue(index.data().toDouble());\n            break;\n        case SolverFlag::T_OPT:\n        case SolverFlag::T_SOLVER:\n            static_cast<QComboBox*>(editor)->setCurrentText(index.data().toString());\n            break;\n        case SolverFlag::T_BOOL:\n        case SolverFlag::T_BOOL_ONOFF:\n        case SolverFlag::T_STRING:\n            QStyledItemDelegate::setEditorData(editor, index);\n            break;\n        }\n    } else if (index.data().type() == QVariant::Double) {\n        static_cast<QLineEdit*>(editor)->setText(QString::number(index.data().toDouble()));\n    } else {\n        QStyledItemDelegate::setEditorData(editor, index);\n    }\n}\n\nvoid ExtraOptionDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const\n{\n    if (index.data(Qt::UserRole).canConvert<SolverFlag>()) {\n        auto f = qvariant_cast<SolverFlag>(index.data(Qt::UserRole));\n        switch (f.t) {\n        case SolverFlag::T_INT:\n            model->setData(index, static_cast<QLineEdit*>(editor)->text().toInt());\n            break;\n        case SolverFlag::T_INT_RANGE: {\n            auto* e = qobject_cast<QSpinBox*>(editor);\n            if (e) {\n                model->setData(index, e->value());\n            } else {\n                model->setData(index, static_cast<QLineEdit*>(editor)->text().toLongLong());\n            }\n            break;\n        }\n        case SolverFlag::T_BOOL:\n        case SolverFlag::T_BOOL_ONOFF:\n            QStyledItemDelegate::setModelData(editor, model, index);\n            break;\n        case SolverFlag::T_FLOAT:\n            model->setData(index, static_cast<QLineEdit*>(editor)->text().toDouble());\n            break;\n        case SolverFlag::T_FLOAT_RANGE:\n            model->setData(index, static_cast<QDoubleSpinBox*>(editor)->value());\n            break;\n        case SolverFlag::T_STRING:\n            model->setData(index, static_cast<QLineEdit*>(editor)->text());\n            break;\n        case SolverFlag::T_OPT:\n        case SolverFlag::T_SOLVER:\n            model->setData(index, static_cast<QComboBox*>(editor)->currentText());\n            break;\n        }\n    } else if (index.data().type() == QVariant::Double) {\n        model->setData(index, static_cast<QLineEdit*>(editor)->text().toDouble());\n    } else {\n        QStyledItemDelegate::setModelData(editor, model, index);\n    }\n}\n\nvoid ConfigWindow::on_makeConfigDefault_pushButton_clicked()\n{\n    auto sc = currentSolverConfig();\n    if (!sc) {\n        return;\n    }\n    try {\n        MznDriver::get().setDefaultSolver(sc->solverDefinition);\n        ui->makeConfigDefault_pushButton->setDisabled(true);\n    } catch (Exception& e) {\n        QMessageBox::warning(this, \"MiniZinc IDE\", e.message(), QMessageBox::Ok);\n    }\n}\n\nvoid ConfigWindow::on_reset_pushButton_clicked()\n{\n    auto sc = currentSolverConfig();\n    if (!sc || !sc->isBuiltin) {\n        return;\n    }\n\n    auto* newSc = new SolverConfiguration(sc->solverDefinition, true);\n    int i = currentIndex();\n    delete configs[i];\n    configs.replace(i, newSc);\n    updateGUI(true);\n    populateComboBox();\n}\n\nConfigWindow::StashItem::StashItem(SolverConfiguration* sc) :\n    json(sc->toJSONObject()),\n    isBuiltIn(sc->isBuiltin),\n    paramFile(sc->paramFile)\n{}\n\nSolverConfiguration* ConfigWindow::StashItem::consume() {\n    QStringList warnings;\n    auto* sc = new (SolverConfiguration) (SolverConfiguration::loadJSON(QJsonDocument(json), warnings));\n    sc->isBuiltin = isBuiltIn;\n    sc->paramFile = paramFile;\n    return sc;\n}\n"
  },
  {
    "path": "MiniZincIDE/configwindow.h",
    "content": "/*\n *  Main authors:\n *     Jason Nguyen <jason.nguyen@monash.edu>\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#ifndef CONFIGWINDOW_H\n#define CONFIGWINDOW_H\n\n#include <functional>\n\n#include <QWidget>\n#include <QVariant>\n#include <QJsonDocument>\n#include <QFormLayout>\n#include <QMenu>\n#include <QStyledItemDelegate>\n\n#include \"solver.h\"\n#include \"extraparamdialog.h\"\n\nnamespace Ui {\nclass ConfigWindow;\n}\n\nclass ConfigWindow : public QWidget\n{\n    Q_OBJECT\n    friend class TestIDE;\n\npublic:\n    explicit ConfigWindow(QWidget *parent = nullptr);\n    ~ConfigWindow();\n\n    void init(void);\n\n    ///\n    /// \\brief ConfigWindow::loadConfigs\n    /// Loads or reloads solver configs, populating default solver configs, and reloading non-default configs\n    ///\n    void loadConfigs(void);\n\n    ///\n    /// \\brief Loads a configuration from a JSON file\n    /// \\param fileName The path to the JSON file\n    /// \\return True if loading the config was successful\n    ///\n    bool addConfig(const QString& fileName);\n\n    ///\n    /// \\brief Adds/merges in existing solver configurations\n    /// \\param configs The configurations to add\n    ///\n    void mergeConfigs(const QList<SolverConfiguration*> configs);\n\n    int findBuiltinConfig(const QString& id, const QString& version);\n    int findConfigFile(const QString& file);\n\n    void removeConfig(int i);\n\n    ///\n    /// \\brief Stash modified configs so they can be re-added later\n    /// Stash modified configs and record the currently selected one\n    ///\n    void stashModifiedConfigs();\n\n    ///\n    /// \\brief Unstash previously stashed modified configs\n    /// Unstash modified configs and select the config that was active during stashing\n    ///\n    void unstashModifiedConfigs();\n\n    ///\n    /// \\brief Saves the solver configuration with the given index\n    /// \\param index The index of the solver configuration to save\n    /// \\return The location of the saved file or empty if cancelled\n    ///\n    QString saveConfig(int index);\n\n    ///\n    /// \\brief Saves the currently solver configuration\n    /// \\return The location of the saved file or empty if cancelled\n    ///\n    QString saveConfig(void);\n\n    ///\n    /// \\brief Gets the index of the current solver configuration\n    /// \\return The current solver configuration index\n    ///\n    int currentIndex(void);\n\n    ///\n    /// \\brief Sets the current solver configuration index\n    /// \\param i The new solver configuration index\n    ///\n    void setCurrentIndex(int i);\n\n    ///\n    /// \\brief Gets the active solver configuration\n    /// \\return The current solver config or nullptr\n    ///\n    SolverConfiguration* currentSolverConfig(void);\n\n    ///\n    /// \\brief Gets a list of all loaded solver configurations\n    /// \\return A list of solver configurations\n    ///\n    const QList<SolverConfiguration*>& solverConfigs(void);\nsignals:\n    void selectedIndexChanged(int index);\n    void selectedSolverConfigurationChanged(const SolverConfiguration* sc);\n    void itemsChanged(const QStringList& items);\n    void configSaved(const QString& filename);\n\nprivate slots:\n    void on_numSolutions_checkBox_stateChanged(int arg1);\n\n    void on_timeLimit_checkBox_stateChanged(int arg1);\n\n    void on_randomSeed_checkBox_stateChanged(int arg1);\n\n    void on_defaultBehavior_radioButton_toggled(bool checked);\n\n    void on_userDefinedBehavior_radioButton_toggled(bool checked);\n\n    void on_config_comboBox_currentIndexChanged(int index);\n\n    void on_removeExtraParam_toolButton_clicked();\n\n    void on_extraParams_tableWidget_itemSelectionChanged();\n\n    void on_numOptimal_checkBox_stateChanged(int arg1);\n\n    void on_clone_pushButton_clicked();\n\n    void on_makeConfigDefault_pushButton_clicked();\n\n    void on_reset_pushButton_clicked();\n\nprivate:\n    class StashItem {\n    public:\n        StashItem() {}\n        StashItem(SolverConfiguration* sc);\n        SolverConfiguration* consume();\n    private:\n        QJsonObject json;\n        bool isBuiltIn;\n        QString paramFile;\n    };\n\n    Ui::ConfigWindow *ui;\n\n    QList<SolverConfiguration*> configs;\n\n    QVector<StashItem> stash;\n    QString stashSelectedBuiltinSolverId;\n    QString stashSelectedBuiltinSolverVersion;\n    QString stashSelectedParamFile;\n    int stashSelectedModifiedConfig = -1;\n\n    int lastIndex = -1;\n    bool initialized = false;\n    bool populating = false;\n\n    void updateGUI(bool overrideSync = false);\n    void updateSolverConfig(SolverConfiguration* sc);\n    void addExtraParam(const QString& key = \"\", bool backend = false, const QVariant& value = \"\");\n    void addExtraParam(const SolverFlag& f, const QVariant& value = QVariant());\n    void invalidate(bool all);\n    void populateComboBox(void);\n    void resizeExtraFlagsTable(void);\n\n    ExtraParamDialog* extraParamDialog;\n};\n\nclass ExtraOptionDelegate : public QStyledItemDelegate {\n    Q_OBJECT\npublic:\n    using QStyledItemDelegate::QStyledItemDelegate;\n\n    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;\n    void setEditorData(QWidget* editor, const QModelIndex& index) const override;\n    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;\n};\n\nclass LongLongValidator : public QValidator {\n    Q_OBJECT\npublic:\n    LongLongValidator(qlonglong min, qlonglong max, QObject *parent = nullptr)\n        : QValidator(parent), _min(min), _max(max) {}\n    LongLongValidator(QObject *parent = nullptr) : QValidator(parent) {}\n    qlonglong bottom() const { return _min; }\n    qlonglong top() const { return _max; }\n    void setBottom(qlonglong min){\n        if (_min != min) {\n            _min = min;\n            changed();\n        }\n    }\n    void setTop(qlonglong max){\n        if (_max != max) {\n            _max = max;\n            changed();\n        }\n    }\n    void setRange(qlonglong min, qlonglong max){\n        setBottom(min);\n        setTop(max);\n    }\n    QValidator::State validate(QString &input, int&) const override{\n        bool ok = false;\n        qlonglong n = input.toLongLong(&ok);\n        if (!ok) {\n            return QValidator::Invalid;\n        }\n        if (n < _min || n > _max) {\n            return QValidator::Intermediate;\n        }\n        return QValidator::Acceptable;\n    }\nprivate:\n    qlonglong _min = std::numeric_limits<qlonglong>::min();\n    qlonglong _max = std::numeric_limits<qlonglong>::max();\n};\n\n\n\n#endif // CONFIGWINDOW_H\n"
  },
  {
    "path": "MiniZincIDE/configwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ConfigWindow</class>\n <widget class=\"QWidget\" name=\"ConfigWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>688</width>\n    <height>1140</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout_5\">\n   <item>\n    <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n     <item>\n      <widget class=\"QGroupBox\" name=\"config_groupBox\">\n       <property name=\"title\">\n        <string>Configuration</string>\n       </property>\n       <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n        <property name=\"leftMargin\">\n         <number>0</number>\n        </property>\n        <property name=\"topMargin\">\n         <number>0</number>\n        </property>\n        <property name=\"rightMargin\">\n         <number>0</number>\n        </property>\n        <property name=\"bottomMargin\">\n         <number>0</number>\n        </property>\n        <item>\n         <widget class=\"QWidget\" name=\"solver_controls\" native=\"true\">\n          <layout class=\"QHBoxLayout\" name=\"horizontalLayout_9\">\n           <item>\n            <layout class=\"QGridLayout\" name=\"gridLayout_4\">\n             <item row=\"0\" column=\"0\">\n              <widget class=\"QComboBox\" name=\"config_comboBox\">\n               <property name=\"sizePolicy\">\n                <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n                 <horstretch>0</horstretch>\n                 <verstretch>0</verstretch>\n                </sizepolicy>\n               </property>\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The currently selected solver configuration&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"0\" column=\"1\">\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_4\">\n               <item>\n                <widget class=\"QPushButton\" name=\"clone_pushButton\">\n                 <property name=\"text\">\n                  <string>Clone</string>\n                 </property>\n                </widget>\n               </item>\n               <item>\n                <widget class=\"QPushButton\" name=\"makeConfigDefault_pushButton\">\n                 <property name=\"toolTip\">\n                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make this solver configuration the default.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n                 </property>\n                 <property name=\"text\">\n                  <string>Set as default</string>\n                 </property>\n                </widget>\n               </item>\n              </layout>\n             </item>\n             <item row=\"1\" column=\"0\" colspan=\"2\">\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n               <item>\n                <widget class=\"QLabel\" name=\"label_4\">\n                 <property name=\"text\">\n                  <string>Solver:</string>\n                 </property>\n                </widget>\n               </item>\n               <item>\n                <widget class=\"QLabel\" name=\"solver_label\">\n                 <property name=\"text\">\n                  <string>&lt;unknown&gt;</string>\n                 </property>\n                </widget>\n               </item>\n               <item>\n                <spacer name=\"horizontalSpacer\">\n                 <property name=\"orientation\">\n                  <enum>Qt::Horizontal</enum>\n                 </property>\n                 <property name=\"sizeHint\" stdset=\"0\">\n                  <size>\n                   <width>40</width>\n                   <height>20</height>\n                  </size>\n                 </property>\n                </spacer>\n               </item>\n               <item>\n                <widget class=\"QPushButton\" name=\"reset_pushButton\">\n                 <property name=\"text\">\n                  <string>Reset to defaults</string>\n                 </property>\n                </widget>\n               </item>\n              </layout>\n             </item>\n            </layout>\n           </item>\n          </layout>\n         </widget>\n        </item>\n       </layout>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QGroupBox\" name=\"options_groupBox\">\n       <property name=\"title\">\n        <string>Options</string>\n       </property>\n       <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n        <item>\n         <widget class=\"QCheckBox\" name=\"sync_checkBox\">\n          <property name=\"toolTip\">\n           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Preserve these options when switching solver configurations.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n          </property>\n          <property name=\"text\">\n           <string>Maintain these options across solver configurations</string>\n          </property>\n          <property name=\"checked\">\n           <bool>true</bool>\n          </property>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QGroupBox\" name=\"solving_groupBox\">\n          <property name=\"title\">\n           <string>Solving</string>\n          </property>\n          <layout class=\"QVBoxLayout\" name=\"verticalLayout_9\">\n           <item>\n            <layout class=\"QHBoxLayout\" name=\"horizontalLayout_7\">\n             <item>\n              <widget class=\"QCheckBox\" name=\"timeLimit_checkBox\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Limit running time to the given amount in seconds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Time limit:</string>\n               </property>\n              </widget>\n             </item>\n             <item>\n              <widget class=\"QDoubleSpinBox\" name=\"timeLimit_doubleSpinBox\">\n               <property name=\"enabled\">\n                <bool>false</bool>\n               </property>\n               <property name=\"sizePolicy\">\n                <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n                 <horstretch>0</horstretch>\n                 <verstretch>0</verstretch>\n                </sizepolicy>\n               </property>\n               <property name=\"alignment\">\n                <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n               </property>\n               <property name=\"suffix\">\n                <string>s</string>\n               </property>\n               <property name=\"decimals\">\n                <number>3</number>\n               </property>\n               <property name=\"maximum\">\n                <double>99999.998999999996158</double>\n               </property>\n              </widget>\n             </item>\n            </layout>\n           </item>\n           <item>\n            <layout class=\"QHBoxLayout\" name=\"horizontalLayout_8\">\n             <item>\n              <widget class=\"QRadioButton\" name=\"defaultBehavior_radioButton\">\n               <property name=\"text\">\n                <string>Default behavior</string>\n               </property>\n               <property name=\"checked\">\n                <bool>true</bool>\n               </property>\n              </widget>\n             </item>\n             <item>\n              <widget class=\"QRadioButton\" name=\"userDefinedBehavior_radioButton\">\n               <property name=\"text\">\n                <string>User defined behavior</string>\n               </property>\n              </widget>\n             </item>\n            </layout>\n           </item>\n           <item>\n            <widget class=\"QFrame\" name=\"defaultBehavior_frame\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Preferred\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <property name=\"frameShape\">\n              <enum>QFrame::Panel</enum>\n             </property>\n             <property name=\"frameShadow\">\n              <enum>QFrame::Raised</enum>\n             </property>\n             <layout class=\"QGridLayout\" name=\"gridLayout_6\">\n              <item row=\"0\" column=\"0\">\n               <widget class=\"QLabel\" name=\"label_7\">\n                <property name=\"text\">\n                 <string>Optimization problems:</string>\n                </property>\n               </widget>\n              </item>\n              <item row=\"0\" column=\"1\">\n               <widget class=\"QLabel\" name=\"label_16\">\n                <property name=\"enabled\">\n                 <bool>false</bool>\n                </property>\n                <property name=\"sizePolicy\">\n                 <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Preferred\">\n                  <horstretch>0</horstretch>\n                  <verstretch>0</verstretch>\n                 </sizepolicy>\n                </property>\n                <property name=\"text\">\n                 <string>print all intermediate solutions</string>\n                </property>\n               </widget>\n              </item>\n              <item row=\"1\" column=\"0\">\n               <widget class=\"QLabel\" name=\"label_12\">\n                <property name=\"text\">\n                 <string>Satisfaction problems: </string>\n                </property>\n               </widget>\n              </item>\n              <item row=\"1\" column=\"1\">\n               <widget class=\"QLabel\" name=\"label_17\">\n                <property name=\"enabled\">\n                 <bool>false</bool>\n                </property>\n                <property name=\"sizePolicy\">\n                 <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Preferred\">\n                  <horstretch>0</horstretch>\n                  <verstretch>0</verstretch>\n                 </sizepolicy>\n                </property>\n                <property name=\"text\">\n                 <string>stop after first solution</string>\n                </property>\n               </widget>\n              </item>\n             </layout>\n            </widget>\n           </item>\n           <item>\n            <widget class=\"QFrame\" name=\"userDefinedBehavior_frame\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Preferred\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <property name=\"frameShape\">\n              <enum>QFrame::Panel</enum>\n             </property>\n             <property name=\"frameShadow\">\n              <enum>QFrame::Raised</enum>\n             </property>\n             <layout class=\"QVBoxLayout\" name=\"verticalLayout_6\">\n              <item>\n               <widget class=\"QGroupBox\" name=\"groupBox\">\n                <property name=\"title\">\n                 <string>Optimization problems</string>\n                </property>\n                <layout class=\"QVBoxLayout\" name=\"verticalLayout_7\">\n                 <item>\n                  <widget class=\"QCheckBox\" name=\"intermediateSolutions_checkBox\">\n                   <property name=\"toolTip\">\n                    <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Print intermediate solutions with &lt;span style=&quot; font-family:'monospace';&quot;&gt;-i&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n                   </property>\n                   <property name=\"text\">\n                    <string>Print intermediate solutions</string>\n                   </property>\n                   <property name=\"checked\">\n                    <bool>true</bool>\n                   </property>\n                  </widget>\n                 </item>\n                 <item>\n                  <widget class=\"QCheckBox\" name=\"numOptimal_checkBox\">\n                   <property name=\"toolTip\">\n                    <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Limit number of optimal  solutions with &lt;span style=&quot; font-family:'monospace';&quot;&gt;-n-o&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n                   </property>\n                   <property name=\"text\">\n                    <string>Stop after this many optimal solutions (uncheck for all)</string>\n                   </property>\n                   <property name=\"checked\">\n                    <bool>true</bool>\n                   </property>\n                  </widget>\n                 </item>\n                 <item>\n                  <widget class=\"QSpinBox\" name=\"numOptimal_spinBox\">\n                   <property name=\"sizePolicy\">\n                    <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n                     <horstretch>0</horstretch>\n                     <verstretch>0</verstretch>\n                    </sizepolicy>\n                   </property>\n                   <property name=\"minimum\">\n                    <number>1</number>\n                   </property>\n                  </widget>\n                 </item>\n                </layout>\n               </widget>\n              </item>\n              <item>\n               <widget class=\"QGroupBox\" name=\"groupBox_2\">\n                <property name=\"title\">\n                 <string>Satisfaction problems</string>\n                </property>\n                <layout class=\"QVBoxLayout\" name=\"verticalLayout_8\">\n                 <item>\n                  <widget class=\"QCheckBox\" name=\"numSolutions_checkBox\">\n                   <property name=\"toolTip\">\n                    <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Limit number of solutions with &lt;span style=&quot; font-family:'monospace';&quot;&gt;-n&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n                   </property>\n                   <property name=\"text\">\n                    <string>Stop after this many solutions (uncheck for all)</string>\n                   </property>\n                   <property name=\"checked\">\n                    <bool>true</bool>\n                   </property>\n                  </widget>\n                 </item>\n                 <item>\n                  <widget class=\"QSpinBox\" name=\"numSolutions_spinBox\">\n                   <property name=\"sizePolicy\">\n                    <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n                     <horstretch>0</horstretch>\n                     <verstretch>0</verstretch>\n                    </sizepolicy>\n                   </property>\n                   <property name=\"minimum\">\n                    <number>1</number>\n                   </property>\n                   <property name=\"maximum\">\n                    <number>999999</number>\n                   </property>\n                   <property name=\"value\">\n                    <number>1</number>\n                   </property>\n                  </widget>\n                 </item>\n                </layout>\n               </widget>\n              </item>\n             </layout>\n            </widget>\n           </item>\n          </layout>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QGroupBox\" name=\"output_groupBox\">\n          <property name=\"title\">\n           <string>Output</string>\n          </property>\n          <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n           <item>\n            <layout class=\"QGridLayout\" name=\"gridLayout\">\n             <item row=\"0\" column=\"0\">\n              <widget class=\"QCheckBox\" name=\"verboseCompilation_checkBox\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable verbose compilation with &lt;span style=&quot; font-family:'monospace';&quot;&gt;--verbose-compilation&lt;/span&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Verbose compilation</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"2\" column=\"1\">\n              <widget class=\"QCheckBox\" name=\"solvingStats_checkBox\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Output solving statistics with &lt;span style=&quot; font-family:'monospace';&quot;&gt;--solver-statistics&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Output solving statistics</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"0\" column=\"1\">\n              <widget class=\"QCheckBox\" name=\"verboseSolving_checkBox\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable verbose compilation with &lt;span style=&quot; font-family:'monospace';&quot;&gt;--verbose-solving&lt;/span&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Verbose Solving</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"2\" column=\"0\">\n              <widget class=\"QCheckBox\" name=\"compilationStats_checkBox\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Output compilation statistics with &lt;span style=&quot; font-family: monospace&quot;&gt;--compiler-statistics&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Output compilation statistics</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"3\" column=\"0\">\n              <widget class=\"QCheckBox\" name=\"timingInfo_checkBox\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Output timing information with &lt;span style=&quot; font-family:'monospace';&quot;&gt;--output-time&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Output timing information</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"1\" column=\"0\" colspan=\"2\">\n              <widget class=\"Line\" name=\"line_2\">\n               <property name=\"orientation\">\n                <enum>Qt::Horizontal</enum>\n               </property>\n              </widget>\n             </item>\n             <item row=\"3\" column=\"1\">\n              <widget class=\"QCheckBox\" name=\"outputObjective_checkBox\">\n               <property name=\"text\">\n                <string>Output objective value</string>\n               </property>\n              </widget>\n             </item>\n            </layout>\n           </item>\n          </layout>\n         </widget>\n        </item>\n       </layout>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QGroupBox\" name=\"advanced_groupBox\">\n       <property name=\"title\">\n        <string>Advanced options</string>\n       </property>\n       <layout class=\"QVBoxLayout\" name=\"verticalLayout_14\">\n        <item>\n         <widget class=\"QGroupBox\" name=\"compiling_groupBox\">\n          <property name=\"title\">\n           <string>Compiling</string>\n          </property>\n          <layout class=\"QHBoxLayout\" name=\"horizontalLayout_5\">\n           <item>\n            <layout class=\"QGridLayout\" name=\"gridLayout_3\">\n             <item row=\"0\" column=\"0\">\n              <widget class=\"QLabel\" name=\"label_21\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Compiler optimization level&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Optimization level:</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"0\" column=\"1\">\n              <widget class=\"QComboBox\" name=\"optimizationLevel_comboBox\">\n               <property name=\"currentIndex\">\n                <number>1</number>\n               </property>\n               <item>\n                <property name=\"text\">\n                 <string>-O0 (no optimization)</string>\n                </property>\n               </item>\n               <item>\n                <property name=\"text\">\n                 <string>-O1 (default)</string>\n                </property>\n               </item>\n               <item>\n                <property name=\"text\">\n                 <string>-O2 (two pass compilation)</string>\n                </property>\n               </item>\n               <item>\n                <property name=\"text\">\n                 <string>-O3 (two pass compilation with Gecode)</string>\n                </property>\n               </item>\n               <item>\n                <property name=\"text\">\n                 <string>-O4 (as -O3 with shaving)</string>\n                </property>\n               </item>\n               <item>\n                <property name=\"text\">\n                 <string>-O5 (as -O3 with singleton arc consistency)</string>\n                </property>\n               </item>\n              </widget>\n             </item>\n             <item row=\"1\" column=\"0\">\n              <widget class=\"QLabel\" name=\"label_22\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Additional string data input with &lt;span style=&quot; font-family:monospace;&quot;&gt;-D&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Additional data:</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"1\" column=\"1\">\n              <widget class=\"QPlainTextEdit\" name=\"additionalData_plainTextEdit\">\n               <property name=\"maximumSize\">\n                <size>\n                 <width>16777215</width>\n                 <height>40</height>\n                </size>\n               </property>\n              </widget>\n             </item>\n            </layout>\n           </item>\n          </layout>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QGroupBox\" name=\"solving_groupBox_2\">\n          <property name=\"title\">\n           <string>Solving</string>\n          </property>\n          <layout class=\"QHBoxLayout\" name=\"horizontalLayout_6\">\n           <item>\n            <layout class=\"QGridLayout\" name=\"gridLayout_5\">\n             <item row=\"0\" column=\"0\">\n              <widget class=\"QLabel\" name=\"label_23\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sets the number of solver threads with &lt;span style=&quot; font-family:monospace;&quot;&gt;-p&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Number of threads:</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"0\" column=\"1\">\n              <widget class=\"QSpinBox\" name=\"numThreads_spinBox\">\n               <property name=\"alignment\">\n                <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n               </property>\n               <property name=\"minimum\">\n                <number>1</number>\n               </property>\n               <property name=\"maximum\">\n                <number>9999</number>\n               </property>\n               <property name=\"value\">\n                <number>1</number>\n               </property>\n              </widget>\n             </item>\n             <item row=\"1\" column=\"0\">\n              <widget class=\"QCheckBox\" name=\"randomSeed_checkBox\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sets the random seed for the solver with &lt;span style=&quot; font-family:monospace;&quot;&gt;-r&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Random seed:</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"1\" column=\"1\">\n              <widget class=\"QLineEdit\" name=\"randomSeed_lineEdit\">\n               <property name=\"enabled\">\n                <bool>false</bool>\n               </property>\n              </widget>\n             </item>\n             <item row=\"2\" column=\"0\">\n              <widget class=\"QCheckBox\" name=\"freeSearch_checkBox\">\n               <property name=\"toolTip\">\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable free search with &lt;span style=&quot; font-family:'monospace';&quot;&gt;-f&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n               </property>\n               <property name=\"text\">\n                <string>Free search</string>\n               </property>\n              </widget>\n             </item>\n            </layout>\n           </item>\n          </layout>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QGroupBox\" name=\"unknownOptions_groupBox\">\n          <property name=\"toolTip\">\n           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Extra parameters passed to MiniZinc&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n          </property>\n          <property name=\"title\">\n           <string>Extra configuration parameters</string>\n          </property>\n          <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n           <item>\n            <widget class=\"QTableWidget\" name=\"extraParams_tableWidget\">\n             <property name=\"verticalScrollBarPolicy\">\n              <enum>Qt::ScrollBarAsNeeded</enum>\n             </property>\n             <property name=\"sizeAdjustPolicy\">\n              <enum>QAbstractScrollArea::AdjustToContents</enum>\n             </property>\n             <property name=\"editTriggers\">\n              <set>QAbstractItemView::AllEditTriggers</set>\n             </property>\n             <property name=\"selectionBehavior\">\n              <enum>QAbstractItemView::SelectRows</enum>\n             </property>\n             <attribute name=\"horizontalHeaderStretchLastSection\">\n              <bool>true</bool>\n             </attribute>\n             <attribute name=\"verticalHeaderVisible\">\n              <bool>false</bool>\n             </attribute>\n             <column>\n              <property name=\"text\">\n               <string>Parameter</string>\n              </property>\n             </column>\n             <column>\n              <property name=\"text\">\n               <string>Flag Type</string>\n              </property>\n             </column>\n             <column>\n              <property name=\"text\">\n               <string>Data Type</string>\n              </property>\n             </column>\n             <column>\n              <property name=\"text\">\n               <string>Value</string>\n              </property>\n             </column>\n            </widget>\n           </item>\n           <item>\n            <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n             <item>\n              <spacer name=\"horizontalSpacer_2\">\n               <property name=\"orientation\">\n                <enum>Qt::Horizontal</enum>\n               </property>\n               <property name=\"sizeHint\" stdset=\"0\">\n                <size>\n                 <width>40</width>\n                 <height>20</height>\n                </size>\n               </property>\n              </spacer>\n             </item>\n             <item>\n              <widget class=\"QToolButton\" name=\"addExtraParam_toolButton\">\n               <property name=\"text\">\n                <string>Add parameter</string>\n               </property>\n               <property name=\"popupMode\">\n                <enum>QToolButton::InstantPopup</enum>\n               </property>\n               <property name=\"toolButtonStyle\">\n                <enum>Qt::ToolButtonTextOnly</enum>\n               </property>\n               <property name=\"arrowType\">\n                <enum>Qt::DownArrow</enum>\n               </property>\n              </widget>\n             </item>\n             <item>\n              <widget class=\"QToolButton\" name=\"removeExtraParam_toolButton\">\n               <property name=\"enabled\">\n                <bool>false</bool>\n               </property>\n               <property name=\"text\">\n                <string>Remove parameter</string>\n               </property>\n               <property name=\"toolButtonStyle\">\n                <enum>Qt::ToolButtonTextOnly</enum>\n               </property>\n              </widget>\n             </item>\n            </layout>\n           </item>\n          </layout>\n         </widget>\n        </item>\n       </layout>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <spacer name=\"verticalSpacer\">\n     <property name=\"orientation\">\n      <enum>Qt::Vertical</enum>\n     </property>\n     <property name=\"sizeHint\" stdset=\"0\">\n      <size>\n       <width>20</width>\n       <height>40</height>\n      </size>\n     </property>\n    </spacer>\n   </item>\n  </layout>\n  <action name=\"actionCustom_Parameter\">\n   <property name=\"text\">\n    <string>Custom parameter</string>\n   </property>\n   <property name=\"toolTip\">\n    <string>Add custom parameter to configuration</string>\n   </property>\n  </action>\n  <action name=\"actionAdd_all_known_parameters\">\n   <property name=\"text\">\n    <string>Add all known parameters</string>\n   </property>\n  </action>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "MiniZincIDE/dark_mode.css",
    "content": "*,\nQTableView::item {\n    border-color: #444444;\n    color: #FFFFFF;\n}\n\nQMainWindow, QDialog, QToolBar, QAbstractScrollArea, QAction, QDockWidget {\n    background-color: #333333;\n    alternate-background-color: #444444;\n}\n\n*:disabled {\n    background-color: #333333;\n    color: #666666;\n    border-color: #666666;\n}\n\nQMenuBar, QPushButton, QToolButton,\nQTabWidget::pane QMenuBar, QTabWidget::pane QPushButton, QTabWidget::pane QToolButton {\n    background-color: #444444;\n}\n\nQToolBar::separator {\n    background-color: #444444;\n    width: 1px;\n    margin: 6px;\n}\n\nQMenuBar::item:selected, QMenu::item:selected {\n    background-color: #555555;\n}\n\nQMenuBar::item:pressed {\n    background-color: #666666;\n}\n\nQMenu {\n    background-color: #3D3D3D;\n}\n\nQTabWidget::tab-bar, QTabWidget::pane, QTabBar {\n    border: 0;\n}\n\n\nQTabWidget::tab-bar, QTabBar {\n    background-color: #444444;\n}\n\nQTabBar::tab {\n    background-color: #3A3A3A;\n}\n\nQTabBar::tab:selected {\n    background-color: #262626;\n}\n\nQTabBar::tab:hover {\n    background-color: #2D2D2D;\n}\n\nQTabWidget::pane, QTabWidget::pane * {\n    background-color: #262626;\n}\n\nQPlainTextEdit,\nQTextEdit,\nQLineEdit,\nQComboBox,\nQAbstractSlider,\nQAbstractSpinBox,\nQListView,\nQTreeView,\nQTableView,\nQTableView::item\n{\n    background-color: #222222;\n    padding: 3px;\n    border: 1px solid #666666;\n}\n\n\nQTabWidget::pane QPlainTextEdit,\nQTabWidget::pane QTextEdit,\nQTabWidget::pane QLineEdit,\nQTabWidget::pane QComboBox,\nQTabWidget::pane QAbstractSlider,\nQTabWidget::pane QAbstractSpinBox,\nQTabWidget::pane QListView,\nQTabWidget::pane QTreeView,\nQTabWidget::pane QTableView,\nQTabWidget::pane QTableView::item\n{\n    background-color: #1D1D1D;\n    padding: 3px;\n    border: 1px solid #666666;\n}\n\nQGroupBox {\n    border: 1px solid #666666;\n    margin-top: 1.5ex;\n    margin-bottom: 1.5ex;\n    padding: 1ex;\n    padding-top: 2ex;\n}\n\nQGroupBox::title {\n    subcontrol-origin: margin;\n    subcontrol-position: top left;\n    left: 5px;\n    padding: 0 1ex;\n}\n\nQToolTip {\n    color: #000000;\n}\n\nQDockWidget::title {\n    background-color: #222222;\n    text-align: left center;\n}\n\nQDockWidget::close-button, QDockWidget::float-button {\n    background-color: #000000;\n}\n\nQStatusBar QLabel {\n    padding-left: 1ex;\n    padding-right: 1ex;\n}\n\nQStatusBar::item {\n    border: 0;\n}\n\nQScrollBar {\n    color: #555555;\n}\n\nQScrollBar:vertical {\n    margin-top: 16px;\n    margin-bottom: 16px;\n}\n\nQScrollBar:horizontal {\n    margin-left: 16px;\n    margin-right: 16px;\n}\n\nQScrollBar::handle {\n    border: 0;\n    background: #444444;\n}\n\nQScrollBar::handle:vertical {\n    min-height: 20px;\n}\n\nQScrollBar::handle:horizontal {\n    min-width: 20px;\n}\n\nQScrollBar::add-page, QScrollBar::sub-page {\n    background: none;\n}\n\nQScrollBar::add-line:vertical {\n    height: 16px;\n    subcontrol-position: bottom;\n    subcontrol-origin: margin;\n}\n\nQScrollBar::sub-line:vertical {\n    height: 16px;\n    subcontrol-position: top;\n    subcontrol-origin: margin;\n}\n\nQScrollBar::add-line:horizontal {\n    width: 16px;\n    subcontrol-position: right;\n    subcontrol-origin: margin;\n    border: 0;\n}\n\nQScrollBar::sub-line:horizontal {\n    width: 16px;\n    subcontrol-position: left;\n    subcontrol-origin: margin;\n    border: 0;\n}\n\nQHeaderView::section {\n    background-color: #333333;\n}\n"
  },
  {
    "path": "MiniZincIDE/darkmodenotifier.cpp",
    "content": "#include \"darkmodenotifier.h\"\r\n\r\n#include <QApplication>\r\n#include <QSettings>\r\n\r\nDarkModeNotifier::DarkModeNotifier(QObject *parent) : QObject(parent)\r\n{\r\n    init();\r\n\r\n    if (!hasSystemSetting()) {\r\n        QSettings settings;\r\n        settings.beginGroup(\"MainWindow\");\r\n        _darkMode = settings.value(\"darkMode\", false).value<bool>();\r\n    }\r\n}\r\n\r\nvoid DarkModeNotifier::requestChangeDarkMode(bool enable)\r\n{\r\n    if (hasSystemSetting()) {\r\n        // Can't change dark mode since we're using system setting\r\n        return;\r\n    }\r\n    if (enable != _darkMode) {\r\n        _darkMode = enable;\r\n        emit darkModeChanged(_darkMode);\r\n    }\r\n}\r\n\r\n#ifdef Q_OS_WIN\r\n\r\n#include <QWinEventNotifier>\r\n\r\n#define NOMINMAX\r\n#include <Windows.h>\r\n\r\nclass DarkModeNotifier::Internal\r\n{\r\npublic:\r\n    QWinEventNotifier* notifier = nullptr;\r\n\r\n    HKEY hKey = nullptr;\r\n    HANDLE hEvent = nullptr;\r\n\r\n    bool ok = true;\r\n};\r\n\r\nvoid DarkModeNotifier::init()\r\n{\r\n    auto getDarkMode = [=] () {\r\n        if (!_internal->ok) { return false; }\r\n        DWORD value = 1;\r\n        DWORD size = sizeof(DWORD);\r\n        auto result = RegGetValue(_internal->hKey, nullptr, L\"AppsUseLightTheme\", RRF_RT_REG_DWORD, nullptr, &value, &size);\r\n        if (result != ERROR_SUCCESS) {\r\n            _internal->ok = false;\r\n            return false;\r\n        }\r\n        return value == 0;\r\n    };\r\n\r\n    _internal = new Internal;\r\n\r\n    // Get dark mode registry key\r\n    auto result = RegOpenKey(HKEY_CURRENT_USER, L\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Themes\\\\Personalize\", &_internal->hKey);\r\n    _internal->ok = result == ERROR_SUCCESS;\r\n    if (!_internal->ok) { return; }\r\n\r\n    // Get initial dark mode\r\n    _darkMode = getDarkMode();\r\n    if (!_internal->ok) { return; }\r\n\r\n    // Setup event to be triggered on registry change\r\n    _internal->hEvent = CreateEvent(nullptr, true, false, nullptr);\r\n    _internal->ok = _internal->hEvent != nullptr;\r\n    if (!_internal->ok) { return; }\r\n\r\n    // Listen for event\r\n    _internal->notifier = new QWinEventNotifier(_internal->hEvent);\r\n    connect(_internal->notifier, &QWinEventNotifier::activated, this, [=] () {\r\n        bool newDarkMode = getDarkMode();\r\n        if (!_internal->ok) { return; }\r\n        if (newDarkMode != _darkMode) {\r\n            _darkMode = newDarkMode;\r\n            emit darkModeChanged(newDarkMode);\r\n        }\r\n        _internal->ok = ResetEvent(_internal->hEvent) != 0;\r\n        if (!_internal->ok) { return; }\r\n        auto result = RegNotifyChangeKeyValue(_internal->hKey,\r\n                                              true,\r\n                                              REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,\r\n                                              _internal->hEvent,\r\n                                              true);\r\n        _internal->ok = result == ERROR_SUCCESS;\r\n    });\r\n\r\n    // Start notifier\r\n    result = RegNotifyChangeKeyValue(_internal->hKey,\r\n                                     true,\r\n                                     REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,\r\n                                     _internal->hEvent,\r\n                                     true);\r\n    _internal->ok = result == ERROR_SUCCESS;\r\n}\r\n\r\nDarkModeNotifier::~DarkModeNotifier()\r\n{\r\n    delete _internal->notifier;\r\n    CloseHandle(_internal->hEvent);\r\n    RegCloseKey(_internal->hKey);\r\n    delete _internal;\r\n}\r\n\r\nbool DarkModeNotifier::hasSystemSetting() const\r\n{\r\n    return _internal->ok;\r\n}\r\n\r\nbool DarkModeNotifier::hasNativeDarkMode() const\r\n{\r\n#if QT_VERSION < 0x060500\r\n    // Have to style using CSS, no native dark widgets\r\n    return false;\r\n#else\r\n    if (hasSystemSetting()) {\r\n        QApplication::setStyle(\"Fusion\");\r\n        return true;\r\n    }\r\n    return false;\r\n#endif\r\n}\r\n\r\n#elif !defined(Q_OS_MAC)\r\n\r\n// No support for system dark mode\r\n\r\nvoid DarkModeNotifier::init() {}\r\n\r\nDarkModeNotifier::~DarkModeNotifier() {}\r\n\r\nbool DarkModeNotifier::hasSystemSetting() const\r\n{\r\n    return false;\r\n}\r\n\r\nbool DarkModeNotifier::hasNativeDarkMode() const\r\n{\r\n    // Have to style using CSS, no native dark widgets\r\n    return false;\r\n}\r\n\r\n#endif\r\n"
  },
  {
    "path": "MiniZincIDE/darkmodenotifier.h",
    "content": "#ifndef DARKMODENOTIFIER_H\r\n#define DARKMODENOTIFIER_H\r\n\r\n#include <QObject>\r\n\r\nclass DarkModeNotifier : public QObject\r\n{\r\n    Q_OBJECT\r\npublic:\r\n    explicit DarkModeNotifier(QObject *parent = nullptr);\r\n    ~DarkModeNotifier();\r\n    bool hasSystemSetting() const;\r\n    bool hasNativeDarkMode() const;\r\n    bool darkMode() const { return _darkMode; }\r\npublic slots:\r\n    void requestChangeDarkMode(bool enable);\r\n\r\nsignals:\r\n    void darkModeChanged(bool darkMode);\r\nprivate:\r\n    void init();\r\n    class Internal;\r\n    Internal* _internal = nullptr;\r\n    bool _darkMode = false;\r\n};\r\n\r\n#endif // DARKMODENOTIFIER_H\r\n"
  },
  {
    "path": "MiniZincIDE/darkmodenotifier_macos.mm",
    "content": "#import <Cocoa/Cocoa.h>\r\n \r\n#include \"darkmodenotifier.h\"\r\n\r\n#include <functional>\r\n\r\n@interface DarkModeObserver : NSObject\r\n@property (readonly, nonatomic) std::function<void()> handler;\r\n- (instancetype)initWithHandler:(std::function<void()>)handler;\r\n@end\r\n \r\n@implementation DarkModeObserver\r\n- (instancetype)initWithHandler:(std::function<void()>)handler\r\n{\r\n    if ((self = [super init])) {\r\n        _handler = handler;\r\n        if (@available(macOS 10.14, *)) {\r\n            [NSApp addObserver:self\r\n                    forKeyPath:@\"effectiveAppearance\"\r\n                       options:0\r\n                       context:nullptr\r\n            ];\r\n        }\r\n    }\r\n    return self;\r\n}\r\n \r\n- (void)dealloc\r\n{\r\n    if (@available(macOS 10.14, *)) {\r\n        [NSApp removeObserver:self\r\n               forKeyPath:@\"effectiveAppearance\"\r\n        ];\r\n    }\r\n    [super dealloc];\r\n}\r\n \r\n- (void)observeValueForKeyPath:(NSString *)keyPath\r\n                      ofObject:(id)object\r\n                        change:(NSDictionary *)change\r\n                       context:(void *)context\r\n{\r\n    self.handler();\r\n}\r\n@end\r\n\r\nclass DarkModeNotifier::Internal {\r\npublic:\r\n    DarkModeObserver* darkModeObserver = nullptr;\r\n};\r\n\r\nvoid DarkModeNotifier::init()\r\n{\r\n    auto getDarkMode = [=] () {\r\n        if (@available(macOS 10.14, *)) {\r\n            id appObjects[] = { NSAppearanceNameAqua, NSAppearanceNameDarkAqua };\r\n            NSArray* appearances = [NSArray arrayWithObjects: appObjects count:2];\r\n            NSAppearanceName appearance = [[NSApp effectiveAppearance] bestMatchFromAppearancesWithNames: appearances];\r\n            if ([appearance isEqualToString:NSAppearanceNameDarkAqua]) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    };\r\n\r\n    _internal = new Internal;\r\n\r\n    _darkMode = getDarkMode();\r\n\r\n    auto handler = [=] () {\r\n        bool newDarkMode = getDarkMode();\r\n        if (_darkMode != newDarkMode) {\r\n            _darkMode = newDarkMode;\r\n            emit darkModeChanged(newDarkMode);\r\n        }\r\n    };\r\n\r\n    _internal->darkModeObserver = [[DarkModeObserver alloc] initWithHandler:handler];\r\n}\r\n\r\nDarkModeNotifier::~DarkModeNotifier()\r\n{\r\n    [_internal->darkModeObserver release];\r\n    delete _internal;\r\n}\r\n\r\nbool DarkModeNotifier::hasSystemSetting() const\r\n{\r\n    if (@available(macOS 10.14, *)) {\r\n        return true;\r\n    }\r\n    return false;\r\n}\r\n\r\nbool DarkModeNotifier::hasNativeDarkMode() const\r\n{\r\n    return hasSystemSetting();\r\n}\r\n"
  },
  {
    "path": "MiniZincIDE/elapsedtimer.cpp",
    "content": "#include \"elapsedtimer.h\"\n\nElapsedTimer::ElapsedTimer(QObject* parent) : QObject(parent) {\n    connect(&_interval, &QTimer::timeout, this, [=] () {\n        emit timeElapsed(_elapsed.elapsed());\n    });\n}\n\nqint64 ElapsedTimer::elapsed()\n{\n    if (_elapsed.isValid()) {\n        return _elapsed.elapsed();\n    }\n    return finalTime;\n}\n\nbool ElapsedTimer::isRunning() {\n    return _elapsed.isValid();\n}\n\nvoid ElapsedTimer::start(int updateRate) {\n    _interval.start(updateRate);\n    _elapsed.start();\n}\n\nvoid ElapsedTimer::stop() {\n    _interval.stop();\n    if (_elapsed.isValid()) {\n        finalTime = _elapsed.elapsed();\n        _elapsed.invalidate();\n    }\n}\n"
  },
  {
    "path": "MiniZincIDE/elapsedtimer.h",
    "content": "#ifndef ELAPSEDTIMER_H\n#define ELAPSEDTIMER_H\n\n#include <QObject>\n\n#include <QTimer>\n#include <QElapsedTimer>\n\nclass ElapsedTimer: public QObject\n{\n    Q_OBJECT\npublic:\n    ElapsedTimer(QObject* parent = nullptr);\n\n    qint64 elapsed();\n    bool isRunning();\n\nsignals:\n    void timeElapsed(qint64 time);\n\npublic slots:\n    void start(int updateRate);\n    void stop();\n\nprivate:\n    QTimer _interval;\n    QElapsedTimer _elapsed;\n    qint64 finalTime;\n};\n\n#endif // ELAPSEDTIMER_H\n"
  },
  {
    "path": "MiniZincIDE/esclineedit.cpp",
    "content": "#include \"esclineedit.h\"\n\n#include <QKeyEvent>\n#include <QDebug>\n\nEscLineEdit::EscLineEdit(QWidget* parent) : QLineEdit(parent)\n{\n\n}\n\nvoid EscLineEdit::keyReleaseEvent(QKeyEvent* event)\n{\n    if(event->key() == Qt::Key_Escape) {\n        emit(escPressed());\n    }\n}\n"
  },
  {
    "path": "MiniZincIDE/esclineedit.h",
    "content": "#ifndef ESCLINEEDIT_H\n#define ESCLINEEDIT_H\n\n#include <QLineEdit>\n\nclass EscLineEdit : public QLineEdit\n{\n    Q_OBJECT\npublic:\n    EscLineEdit(QWidget* parent = 0);\nsignals:\n    void escPressed(void);\nprotected:\n    void keyReleaseEvent(QKeyEvent *);\n};\n\n#endif // ESCLINEEDIT_H\n"
  },
  {
    "path": "MiniZincIDE/exception.h",
    "content": "#ifndef EXCEPTION_H\n#define EXCEPTION_H\n\n#include <QException>\n\nclass Exception : public QException\n{\npublic:\n    Exception(const QString& _msg) : msg(_msg) {}\n\n    const QString& message(void) const { return msg; }\n\n    void raise() const override { throw *this; }\n    Exception *clone() const override { return new Exception(*this); }\n\nprotected:\n    QString msg;\n};\n\nclass InternalError : public Exception\n{\npublic:\n    InternalError(const QString& _msg) : Exception(_msg) {}\n\n    void raise() const override { throw *this; }\n    Exception *clone() const override { return new InternalError(*this); }\n};\n\nclass ProcessError : public Exception\n{\npublic:\n    ProcessError(const QString& _msg) : Exception(_msg) {}\n\n    void raise() const override { throw *this; }\n    Exception *clone() const override { return new ProcessError(*this); }\n};\n\nclass ProjectError : public Exception\n{\npublic:\n    ProjectError(const QString& _msg) : Exception(_msg) {}\n\n    void raise() const override { throw *this; }\n    Exception *clone() const override { return new ProjectError(*this); }\n};\n\nclass MoocError : public Exception\n{\npublic:\n    MoocError(const QString& _msg) : Exception(_msg) {}\n\n    void raise() const override { throw *this; }\n    Exception *clone() const override { return new MoocError(*this); }\n};\n\nclass ConfigError : public Exception\n{\npublic:\n    ConfigError(const QString& _msg) : Exception(_msg) {}\n\n    void raise() const override { throw *this; }\n    Exception *clone() const override { return new ConfigError(*this); }\n};\n\nclass DriverError : public Exception\n{\npublic:\n    DriverError(const QString& _msg) : Exception(_msg) {}\n\n    void raise() const override { throw *this; }\n    Exception *clone() const override { return new DriverError(*this); }\n};\n\nclass FileError : public Exception\n{\npublic:\n    FileError(const QString& _msg) : Exception(_msg) {}\n\n    void raise() const override { throw *this; }\n    Exception *clone() const override { return new FileError(*this); }\n};\n\nclass ServerError : public Exception\n{\npublic:\n    ServerError(const QString& _msg) : Exception(_msg) {}\n\n    void raise() const override { throw *this; }\n    Exception *clone() const override { return new ServerError(*this); }\n};\n\n#endif // EXCEPTION_H\n"
  },
  {
    "path": "MiniZincIDE/extraparamdialog.cpp",
    "content": "#include \"extraparamdialog.h\"\n#include \"ui_extraparamdialog.h\"\n\n#include <QMouseEvent>\n\nExtraParamDialog::ExtraParamDialog(QWidget* parent) :\n    QWidget(parent),\n    ui(new Ui::ExtraParamDialog)\n{\n    ui->setupUi(this);\n    _sourceModel = new QStandardItemModel(this);\n    _filterModel = new QSortFilterProxyModel(this);\n    _filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);\n    _filterModel->setSourceModel(_sourceModel);\n    _filterModel->setFilterKeyColumn(1);\n    ui->params_listView->setModel(_filterModel);\n\n    connect(ui->params_listView->selectionModel(), &QItemSelectionModel::selectionChanged,\n            this, &ExtraParamDialog::on_selectionChanged);\n}\n\nExtraParamDialog::~ExtraParamDialog()\n{\n    delete ui;\n}\n\nvoid ExtraParamDialog::setParams(const QList<SolverFlag>& params) {\n    _sourceModel->clear();\n    _sourceModel->setRowCount(params.length());\n    _sourceModel->setColumnCount(2);\n\n    for (int i = 0; i < params.length(); i++) {\n        auto* item = new QStandardItem(params[i].description);\n        item->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);\n        item->setData(QVariant::fromValue(params[i]));\n        item->setToolTip(params[i].name);\n        _sourceModel->setItem(i, 0, item);\n\n        // Allow filtering to search by flag name and description\n        auto* searchItem = new QStandardItem(params[i].name + \" \" + params[i].description);\n        _sourceModel->setItem(i, 1, searchItem);\n    }\n}\n\nvoid ExtraParamDialog::setParamEnabled(const SolverFlag& param, bool enabled) {\n    for (int i = 0; i < _sourceModel->rowCount(); i++) {\n        auto* item = _sourceModel->item(i);\n        auto flag = qvariant_cast<SolverFlag>(item->data());\n        if (flag.name == param.name) {\n            item->setFlags(enabled ? Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled : Qt::ItemFlag::NoItemFlags);\n            return;\n        }\n    }\n}\n\nvoid ExtraParamDialog::on_selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {\n    // The selected argument is wrong for some reason, so just check the selected indexes manually\n    bool hasSelection = ui->params_listView->selectionModel()->selectedIndexes().empty();\n    ui->add_pushButton->setDisabled(hasSelection);\n}\n\nvoid ExtraParamDialog::showEvent(QShowEvent* event) {\n    QWidget::showEvent(event);\n    ui->filter_lineEdit->setFocus();\n}\n\nvoid ExtraParamDialog::on_filter_lineEdit_textEdited(const QString& pattern)\n{\n    _filterModel->setFilterFixedString(pattern);\n}\n\nvoid ExtraParamDialog::on_addAll_pushButton_clicked()\n{\n    QList<SolverFlag> flags;\n    for (int i = 0; i < _sourceModel->rowCount(); i++) {\n        auto* item = _sourceModel->item(i);\n        if (item->isEnabled()) {\n            auto sf = qvariant_cast<SolverFlag>(item->data());\n            flags << sf;\n        }\n    }\n    emit addParams(flags);\n    resetControls();\n}\n\nvoid ExtraParamDialog::on_customParameter_pushButton_clicked()\n{\n    emit addCustomParam();\n    resetControls();\n}\n\nvoid ExtraParamDialog::on_add_pushButton_clicked()\n{\n    QList<SolverFlag> flags;\n    for (auto item : ui->params_listView->selectionModel()->selectedIndexes()) {\n        if (item.column() == 0) {\n            auto sf = qvariant_cast<SolverFlag>(item.data(Qt::UserRole + 1));\n            flags << sf;\n        }\n    }\n    emit addParams(flags);\n    resetControls();\n}\n\nvoid ExtraParamDialog::resetControls()\n{\n    ui->filter_lineEdit->clear();\n    ui->params_listView->selectionModel()->clear();\n    _filterModel->setFilterFixedString(\"\");\n}\n\nvoid ExtraParamDialog::mousePressEvent(QMouseEvent* event)\n{\n    // Hack which stops the menu from closing when clicking on the widget empty space\n    event->accept();\n}\n\nvoid ExtraParamDialog::mouseReleaseEvent(QMouseEvent* event)\n{\n    // Hack which stops the menu from closing when clicking on the widget empty space\n    event->accept();\n}\n\nvoid ExtraParamDialog::on_params_listView_activated(const QModelIndex &index)\n{\n    on_add_pushButton_clicked();\n}\n\n"
  },
  {
    "path": "MiniZincIDE/extraparamdialog.h",
    "content": "#ifndef EXTRAPARAMDIALOG_H\n#define EXTRAPARAMDIALOG_H\n\n#include <QWidget>\n#include <QStandardItemModel>\n#include <QSortFilterProxyModel>\n\n#include \"solver.h\"\n\nnamespace Ui {\nclass ExtraParamDialog;\n}\n\nclass ExtraParamDialog : public QWidget\n{\n    Q_OBJECT\n\npublic:\n    explicit ExtraParamDialog(QWidget* parent = nullptr);\n    ~ExtraParamDialog() override;\n\n    void showEvent(QShowEvent* event) override;\n\nsignals:\n    void addParams(const QList<SolverFlag>& params);\n    void addCustomParam();\n\npublic slots:\n    void setParams(const QList<SolverFlag>& params);\n    void setParamEnabled(const SolverFlag& param, bool enabled);\n\nprotected:\n    void mousePressEvent(QMouseEvent* event) override;\n    void mouseReleaseEvent(QMouseEvent* event) override;\n\nprivate slots:\n    void on_filter_lineEdit_textEdited(const QString& pattern);\n    void on_selectionChanged(const QItemSelection& selected, const QItemSelection& deselected);\n\n    void on_addAll_pushButton_clicked();\n\n    void on_customParameter_pushButton_clicked();\n\n    void on_add_pushButton_clicked();\n\n    void resetControls();\n\n    void on_params_listView_activated(const QModelIndex &index);\n\nprivate:\n    Ui::ExtraParamDialog* ui;\n    QStandardItemModel* _sourceModel;\n    QSortFilterProxyModel* _filterModel;\n};\n\n#endif // EXTRAPARAMDIALOG_H\n"
  },
  {
    "path": "MiniZincIDE/extraparamdialog.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ExtraParamDialog</class>\n <widget class=\"QWidget\" name=\"ExtraParamDialog\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>371</width>\n    <height>352</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QLineEdit\" name=\"filter_lineEdit\">\n     <property name=\"placeholderText\">\n      <string>Filter</string>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QListView\" name=\"params_listView\">\n     <property name=\"editTriggers\">\n      <set>QAbstractItemView::NoEditTriggers</set>\n     </property>\n     <property name=\"alternatingRowColors\">\n      <bool>true</bool>\n     </property>\n     <property name=\"selectionMode\">\n      <enum>QAbstractItemView::ExtendedSelection</enum>\n     </property>\n     <property name=\"selectionBehavior\">\n      <enum>QAbstractItemView::SelectRows</enum>\n     </property>\n     <property name=\"wordWrap\">\n      <bool>true</bool>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"add_pushButton\">\n     <property name=\"enabled\">\n      <bool>false</bool>\n     </property>\n     <property name=\"toolTip\">\n      <string>Add the selected parameters.</string>\n     </property>\n     <property name=\"text\">\n      <string>Add parameter</string>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"addAll_pushButton\">\n     <property name=\"toolTip\">\n      <string>Add all available parameters.</string>\n     </property>\n     <property name=\"text\">\n      <string>Add all known parameters</string>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"customParameter_pushButton\">\n     <property name=\"toolTip\">\n      <string>Add a custom command line parameter to the minizinc command or to the solver executable.</string>\n     </property>\n     <property name=\"text\">\n      <string>Add custom parameter</string>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "MiniZincIDE/fzndoc.cpp",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#include \"fzndoc.h\"\n\nFznDoc::FznDoc(QObject *parent) :\n    QObject(parent)\n{\n}\n"
  },
  {
    "path": "MiniZincIDE/fzndoc.h",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#ifndef FZNDOC_H\n#define FZNDOC_H\n\n#include <QObject>\n\nclass FznDoc : public QObject\n{\n    Q_OBJECT\n    Q_PROPERTY(QString str READ str WRITE setstr)\npublic:\n    explicit FznDoc(QObject *parent = 0);\n\n    void setstr(const QString& s) {\n        m_str =s ;\n    }\n    QString str(void) {\n        return m_str;\n    }\n\nprivate:\n    QString m_str;\n};\n\n#endif // FZNDOC_H\n"
  },
  {
    "path": "MiniZincIDE/gotolinedialog.cpp",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#include \"gotolinedialog.h\"\n#include \"ui_gotolinedialog.h\"\n\nGoToLineDialog::GoToLineDialog(QWidget *parent) :\n    QDialog(parent),\n    ui(new Ui::GoToLineDialog)\n{\n    ui->setupUi(this);\n    ui->line->setFocus();\n}\n\nGoToLineDialog::~GoToLineDialog()\n{\n    delete ui;\n}\n\nint GoToLineDialog::getLine(bool *ok) {\n    return ui->line->text().toInt(ok);\n}\n"
  },
  {
    "path": "MiniZincIDE/gotolinedialog.h",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#ifndef GOTOLINEDIALOG_H\n#define GOTOLINEDIALOG_H\n\n#include <QDialog>\n\nnamespace Ui {\nclass GoToLineDialog;\n}\n\nclass GoToLineDialog : public QDialog\n{\n    Q_OBJECT\n\npublic:\n    explicit GoToLineDialog(QWidget *parent = 0);\n    ~GoToLineDialog();\n\n    int getLine(bool* ok);\n\nprivate:\n    Ui::GoToLineDialog *ui;\n};\n\n#endif // GOTOLINEDIALOG_H\n"
  },
  {
    "path": "MiniZincIDE/gotolinedialog.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>GoToLineDialog</class>\n <widget class=\"QDialog\" name=\"GoToLineDialog\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>179</width>\n    <height>78</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Go to line</string>\n  </property>\n  <property name=\"modal\">\n   <bool>true</bool>\n  </property>\n  <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n   <property name=\"geometry\">\n    <rect>\n     <x>10</x>\n     <y>40</y>\n     <width>161</width>\n     <height>32</height>\n    </rect>\n   </property>\n   <property name=\"orientation\">\n    <enum>Qt::Horizontal</enum>\n   </property>\n   <property name=\"standardButtons\">\n    <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\n   </property>\n  </widget>\n  <widget class=\"QLineEdit\" name=\"line\">\n   <property name=\"geometry\">\n    <rect>\n     <x>10</x>\n     <y>10</y>\n     <width>161</width>\n     <height>21</height>\n    </rect>\n   </property>\n  </widget>\n </widget>\n <tabstops>\n  <tabstop>line</tabstop>\n  <tabstop>buttonBox</tabstop>\n </tabstops>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>GoToLineDialog</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>248</x>\n     <y>254</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>157</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>GoToLineDialog</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>316</x>\n     <y>260</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>286</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "MiniZincIDE/highlighter.cpp",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#include <QDebug>\n\n#include \"highlighter.h\"\n#include \"ideutils.h\"\n\nHighlighter::Highlighter(QFont& font, const Theme& theme, bool dm, QTextDocument *parent)\n    : QSyntaxHighlighter(parent), keywordColor(Qt::darkGreen), functionColor(Qt::blue), stringColor(Qt::darkRed), commentColor(Qt::red)\n\n{\n    Rule rule;\n\n    commentFormat.setForeground(commentColor);\n\n    QStringList patterns;\n    patterns << \"\\\\bann\\\\b\" << \"\\\\bannotation\\\\b\" << \"\\\\bany\\\\b\"\n             << \"\\\\barray\\\\b\" << \"\\\\bbool\\\\b\" << \"\\\\bcase\\\\b\"\n             << \"\\\\bconstraint\\\\b\" << \"\\\\bdefault\\\\b\" << \"\\\\bdiv\\\\b\"\n             << \"\\\\bdiff\\\\b\" << \"\\\\belse\\\\b\" << \"\\\\belseif\\\\b\"\n             << \"\\\\bendif\\\\b\" << \"\\\\benum\\\\b\" << \"\\\\bfloat\\\\b\"\n             << \"\\\\bfunction\\\\b\" << \"\\\\bif\\\\b\" << \"\\\\binclude\\\\b\"\n             << \"\\\\bintersect\\\\b\" << \"\\\\bin\\\\b\" << \"\\\\bint\\\\b\"\n             << \"\\\\blet\\\\b\" << \"\\\\blist\\\\b\" << \"\\\\bmaximize\\\\b\" << \"\\\\bminimize\\\\b\"\n             << \"\\\\bmod\\\\b\" << \"\\\\bnot\\\\b\" << \"\\\\bof\\\\b\" << \"\\\\boutput\\\\b\"\n             << \"\\\\bopt\\\\b\" << \"\\\\bpar\\\\b\" << \"\\\\bpredicate\\\\b\"\n             << \"\\\\brecord\\\\b\" << \"\\\\bsatisfy\\\\b\" << \"\\\\bset\\\\b\"\n             << \"\\\\bsolve\\\\b\" << \"\\\\bstring\\\\b\" << \"\\\\bsubset\\\\b\"\n             << \"\\\\bsuperset\\\\b\" << \"\\\\bsymdiff\\\\b\" << \"\\\\btest\\\\b\"\n             << \"\\\\bthen\\\\b\" << \"\\\\btuple\\\\b\" << \"\\\\btype\\\\b\"\n             << \"\\\\bunion\\\\b\" << \"\\\\bvar\\\\b\" << \"\\\\bvariant_record\\\\b\"\n             << \"\\\\bwhere\\\\b\" << \"\\\\bxor\\\\b\";\n    QTextCharFormat format;\n\n    format.setForeground(functionColor);\n    rule.pattern = QRegularExpression(\"\\\\b[A-Za-z0-9_]+(?=\\\\s*\\\\()\");\n    rule.format = format;\n    rules.append(rule);\n\n    format = QTextCharFormat();\n    format.setFontWeight(QFont::Bold);\n    format.setForeground(keywordColor);\n    for (int i=0; i<patterns.size(); i++) {\n        rule.pattern = QRegularExpression(patterns[i]);\n        rule.format = format;\n        rules.append(rule);\n    }\n\n\n    setEditorFont(font);\n\n    commentStartExp = QRegularExpression(\"/\\\\*\");\n    commentEndExp = QRegularExpression(\"\\\\*/\");\n\n    setTheme(theme, dm);\n\n}\n\nvoid Highlighter::setEditorFont(QFont& font)\n{\n    commentFormat.setFont(font);\n    for (int i=0; i<rules.size(); i++) {\n        rules[i].format.setFont(font);\n    }\n}\n\nuint qHash(const FixedBg& key) {\n    return qHash(key.sl) ^ qHash(key.sc) ^ qHash(key.el) ^ qHash(key.ec);\n}\n\nbool fg_contains(const FixedBg& a, const FixedBg& b) {\n  return (a.sl < b.sl || (a.sl == b.sl && a.sc <= b.sc))\n      && (a.el > b.el || (a.el == b.el && a.ec >= b.ec));\n}\n\nvoid Highlighter::addFixedBg(\n    unsigned int sl, unsigned int sc, unsigned int el, unsigned ec,\n    QColor colour, QString tip) {\n\n  FixedBg ifb {sl, sc, el, ec};\n\n  for(BgMap::iterator it = fixedBg.begin();\n      it != fixedBg.end();) {\n    const FixedBg& fb = it.key();\n\n    if (fg_contains(fb, ifb)) {\n      it = fixedBg.erase(it);\n    } else {\n      ++it;\n    }\n  }\n  fixedBg.insert(ifb, QPair<QColor, QString>(colour, tip));\n\n}\n\nvoid Highlighter::clearFixedBg() {\n    fixedBg.clear();\n}\n\nvoid Highlighter::highlightBlock(const QString &text)\n{\n    QTextBlock block = currentBlock();\n\n    BracketData* bd = new BracketData;\n    if (currentBlockUserData()) {\n        bd->d = static_cast<BracketData*>(currentBlockUserData())->d;\n    }\n    if (block.previous().userData()) {\n        bd->highlightingState = static_cast<BracketData*>(block.previous().userData())->highlightingState;\n    }\n\n    QRegularExpression noneRegExp(\"\\\"|%|/\\\\*\");\n    QRegularExpression stringRegExp(\"\\\"|\\\\\\\\\\\\(\");\n    QRegularExpression commentRegexp(\"\\\\*/\");\n    QRegularExpression interpolateRegexp(\"[)\\\"%(]|/\\\\*\");\n\n    QTextCharFormat stringFormat;\n    stringFormat.setForeground(stringColor);\n    stringFormat.setFontItalic(true);\n    QTextCharFormat interpolateFormat;\n    interpolateFormat.setFontItalic(true);\n\n    // Stage 1: find strings (including interpolations) and comments\n    QVector<HighlightingState>& highlightingState = bd->highlightingState;\n    int curPosition = 0;\n    HighlightingState currentState;\n    while (curPosition >= 0) {\n        currentState = highlightingState.empty() ? HS_NONE : highlightingState.back();\n        switch (currentState) {\n        case HS_NONE:\n            {\n                int nxt = noneRegExp.match(text, curPosition).capturedStart();\n                if (nxt==-1) {\n                    curPosition = -1;\n                } else {\n                    if (text[nxt]=='\"') {\n                        highlightingState.push_back(HS_STRING);\n                        curPosition = nxt+1;\n                    } else if (text[nxt]=='%') {\n                        setFormat(nxt, text.size()-nxt, commentFormat);\n                        curPosition = -1;\n                    } else {\n                        // /*\n                        highlightingState.push_back(HS_COMMENT);\n                        curPosition = nxt+2;\n                    }\n                }\n            }\n            break;\n        case HS_STRING:\n            {\n                int nxt = stringRegExp.match(text, curPosition).capturedStart();\n                int stringStartIdx = curPosition==0 ? 0 : curPosition-1;\n                if (nxt==-1) {\n                    setFormat(stringStartIdx, text.size()-stringStartIdx, stringFormat);\n                    highlightingState.clear(); // this is an error, reset to NONE state\n                    curPosition = -1;\n                } else {\n                    if (text[nxt]=='\"') {\n                        setFormat(stringStartIdx, nxt-stringStartIdx+1, stringFormat);\n                        curPosition = nxt+1;\n                        highlightingState.pop_back();\n                    } else {\n                        setFormat(stringStartIdx, nxt-stringStartIdx+2, stringFormat);\n                        curPosition = nxt+2;\n                        highlightingState.push_back(HS_INTERPOLATE);\n                    }\n                }\n            }\n            break;\n        case HS_COMMENT:\n            {\n                int nxt = commentRegexp.match(text, curPosition).capturedStart();\n                int commentStartIdx = curPosition <= 1 ? 0 : curPosition - 2;\n                if (nxt==-1) {\n                    // EOL -> stay in COMMENT state\n                    setFormat(commentStartIdx, text.size()-commentStartIdx, commentFormat);\n                    curPosition = -1;\n                } else {\n                    // finish comment\n                    setFormat(commentStartIdx, nxt-commentStartIdx+2, commentFormat);\n                    curPosition = nxt+2;\n                    highlightingState.pop_back();\n                }\n            }\n            break;\n        case HS_INTERPOLATE:\n            {\n                int nxt = interpolateRegexp.match(text, curPosition).capturedStart();\n                if (nxt==-1) {\n                    // EOL -> stay in INTERPOLATE state\n                    setFormat(curPosition, text.size()-curPosition, interpolateFormat);\n                    curPosition = -1;\n                } else {\n                    setFormat(curPosition, nxt-curPosition+1, interpolateFormat);\n                    if (text[nxt]==')') {\n                        curPosition = nxt+1;\n                        highlightingState.pop_back();\n                    } else if (text[nxt]=='(') {\n                        curPosition = nxt+1;\n                        highlightingState.push_back(HS_INTERPOLATE);\n                    } else if (text[nxt]=='%') {\n                        setFormat(nxt, text.size()-nxt, commentFormat);\n                        curPosition = -1;\n                    } else if (text[nxt]=='\"') {\n                        curPosition = nxt+1;\n                        highlightingState.push_back(HS_STRING);\n                    } else {\n                        // /*\n                        highlightingState.push_back(HS_COMMENT);\n                        curPosition = nxt+1;\n                    }\n                }\n            }\n            break;\n        }\n    }\n    currentState = highlightingState.empty() ? HS_NONE : highlightingState.back();\n    setCurrentBlockState(currentState);\n\n    // Stage 2: find keywords and functions\n    for (int i=0; i<rules.size(); i++) {\n        const Rule& rule = rules[i];\n        QRegularExpression expression(rule.pattern);\n        auto match = expression.match(text);\n        int index = match.capturedStart();\n        while (index >= 0) {\n            int length = match.capturedLength();\n            if (format(index)!=commentFormat && format(index)!=stringFormat) {\n                if (format(index)==interpolateFormat) {\n                    QTextCharFormat interpolateRule = rule.format;\n                    interpolateRule.setFontItalic(true);\n                    setFormat(index, length, interpolateRule);\n                } else {\n                    setFormat(index, length, rule.format);\n                }\n            }\n            match = expression.match(text, index + length);\n            index = match.capturedStart();\n        }\n    }\n\n    // Stage 3: update bracket data\n    QRegularExpression re(\"\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\");\n    auto re_match = re.match(text);\n    int pos = re_match.capturedStart();\n    while (pos != -1) {\n        if (format(pos)!=commentFormat) {\n            Bracket b;\n            b.b = text.at(pos);\n            b.pos = pos;\n            bd->brackets.append(b);\n        }\n        re_match = re.match(text, pos+1);\n        pos = re_match.capturedStart();\n    }\n    setCurrentBlockUserData(bd);\n\n    // Stage 4: apply highlighting\n    for(QHash<FixedBg, QPair<QColor, QString> >::iterator it = fixedBg.begin();\n        it != fixedBg.end(); ++it) {\n      const FixedBg& fb = it.key();\n      QPair<QColor, QString> val = it.value();\n      QColor colour = val.first;\n      QString tip = val.second;\n\n      unsigned int blockNumber = block.blockNumber() + 1;\n      if(fb.sl <= blockNumber && fb.el >= blockNumber) {\n        int index = 0;\n        int length = block.length();\n\n        if(fb.sl == blockNumber) {\n          index = fb.sc - 1;\n          if(fb.sl == fb.el) {\n            length = fb.ec - index;\n          }\n        } else if(fb.el == blockNumber) {\n          length = fb.ec;\n        }\n\n        int endpos = index + length;\n        foreach(const QTextLayout::FormatRange& fr, block.textFormats()) {\n          //if(index >= fr.start && index <= fr.start + length) {\n            int local_index = fr.start < index ? index : fr.start;\n            int fr_endpos = fr.start + fr.length;\n            int local_len = (fr_endpos < endpos ? fr_endpos : endpos) - local_index;\n            QTextCharFormat fmt = fr.format;\n            fmt.setBackground(colour);\n            fmt.setToolTip(tip);\n            setFormat(local_index, local_len, fmt);\n          //}\n        }\n      }\n    }\n}\n\n#include <QTextCursor>\n#include <QTextDocumentFragment>\n#include <QTextLayout>\n#include <QTextEdit>\n#include <QApplication>\n#include <QClipboard>\n#include <QMimeData>\n\nvoid Highlighter::copyHighlightedToClipboard(QTextCursor cursor)\n{\n    QTextDocument* tempDocument(new QTextDocument);\n    Q_ASSERT(tempDocument);\n    QTextCursor tempCursor(tempDocument);\n\n    tempCursor.insertFragment(cursor.selection());\n    tempCursor.select(QTextCursor::Document);\n\n    QTextCharFormat textfmt = cursor.charFormat();\n    textfmt.setFont(commentFormat.font());\n    tempCursor.setCharFormat(textfmt);\n\n    QTextBlock start = document()->findBlock(cursor.selectionStart());\n    QTextBlock end = document()->findBlock(cursor.selectionEnd());\n    end = end.next();\n    const int selectionStart = cursor.selectionStart();\n    const int endOfDocument = tempDocument->characterCount() - 1;\n    for(QTextBlock current = start; current.isValid() && current != end; current = current.next()) {\n        const QTextLayout* layout(current.layout());\n\n        foreach(const QTextLayout::FormatRange &range, layout->formats()) {\n            const int start = current.position() + range.start - selectionStart;\n            const int end = start + range.length;\n            if(end <= 0 || start >= endOfDocument)\n                continue;\n            tempCursor.setPosition(qMax(start, 0));\n            tempCursor.setPosition(qMin(end, endOfDocument), QTextCursor::KeepAnchor);\n            tempCursor.setCharFormat(range.format);\n        }\n    }\n\n    for(QTextBlock block = tempDocument->begin(); block.isValid(); block = block.next())\n        block.setUserState(-1);\n\n    tempCursor.select(QTextCursor::Document);\n    QTextBlockFormat blockFormat = cursor.blockFormat();\n    blockFormat.setNonBreakableLines(true);\n    tempCursor.setBlockFormat(blockFormat);\n\n    IDEUtils::MimeDataExporter te;\n    te.setDocument(tempDocument);\n    te.selectAll();\n    QMimeData* mimeData = te.md();\n    QApplication::clipboard()->setMimeData(mimeData);\n    delete tempDocument;\n}\n\nvoid Highlighter::setTheme(const Theme& theme, bool darkMode)\n{\n    keywordColor = theme.keywordColor.get(darkMode);\n    functionColor = theme.functionColor.get(darkMode);\n    stringColor = theme.stringColor.get(darkMode);\n    commentColor = theme.commentColor.get(darkMode);\n    commentFormat.setForeground(commentColor);\n    rules[0].format.setForeground(functionColor);\n    for (int i=1; i<rules.size(); i++) {\n        rules[i].format.setForeground(keywordColor);\n    }\n}\n"
  },
  {
    "path": "MiniZincIDE/highlighter.h",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#ifndef HIGHLIGHTER_H\n#define HIGHLIGHTER_H\n\n#include \"theme.h\"\n#include <QSyntaxHighlighter>\n#include <QTextCharFormat>\n#include <QTextDocument>\n#include <QRegularExpression>\n\nstruct Bracket {\n    QChar b;\n    int pos;\n};\n\nclass DebugInfoData {\npublic:\n    int con; // DebugInfo: number of constraints\n    int var; // DebugInfo: number of variables\n    int ms;  // DebugInfo: milliseconds\n    int totalCon;\n    int totalVar;\n    int totalMs;\n    DebugInfoData(void) : con(0), var(0), ms(0), totalCon(0), totalVar(0), totalMs(1) {}\n    bool hasData(void) { return con!=0 || var!=0 || ms!=0; }\n    void reset(void) { con=0; var=0; ms=0; totalCon=0; totalVar=0; totalMs=1; }\n    QString toString(void) {\n        return QString().number(ms)+\"ms,\"+QString().number(con)+\",\"+QString().number(var);\n    }\n};\n\nenum HighlightingState { HS_NONE=-1, HS_STRING, HS_INTERPOLATE, HS_COMMENT };\n\nclass BracketData : public QTextBlockUserData\n{\npublic:\n    QVector<Bracket> brackets;\n    DebugInfoData d;\n    QVector<HighlightingState> highlightingState;\n};\n\nstruct FixedBg {\n  unsigned int sl;\n  unsigned int sc;\n  unsigned int el;\n  unsigned int ec;\n};\ntypedef QHash<FixedBg, QPair<QColor, QString> > BgMap;\nuint qHash(const FixedBg& key);\n\ninline bool operator==(const FixedBg &A, const FixedBg &B) {\n    return A.sl == B.sl && A.sc == B.sc && A.el == B.el && A.ec == B.ec;\n}\n\nclass Highlighter : public QSyntaxHighlighter\n{\n    Q_OBJECT\n\npublic:\n    Highlighter(QFont& font, const Theme& theme, bool darkMode, QTextDocument *parent = 0);\n    void setEditorFont(QFont& font);\n    void copyHighlightedToClipboard(QTextCursor selectionCursor);\n    void setTheme(const Theme& theme, bool darkMode);\n    void addFixedBg(unsigned int sl, unsigned int sc, unsigned int el, unsigned ec, QColor colour, QString tip);\n    void clearFixedBg();\nprotected:\n    void highlightBlock(const QString &text);\n\nprivate:\n    struct Rule\n    {\n        QRegularExpression pattern;\n        QTextCharFormat format;\n    };\n    QVector<Rule> rules;\n\n    BgMap fixedBg;\n\n    QColor keywordColor;\n    QColor functionColor;\n    QColor stringColor;\n    QColor commentColor;\n\n    QTextCharFormat commentFormat;\n    QRegularExpression commentStartExp;\n    QRegularExpression commentEndExp;\n\n};\n\n#endif // HIGHLIGHTER_H\n"
  },
  {
    "path": "MiniZincIDE/history.cpp",
    "content": "#include \"history.h\"\r\n\r\n#include <QDateTime>\r\n#include <QJsonArray>\r\n#include <QJsonObject>\r\n#include <QJsonValue>\r\n#include <QUuid>\r\n\r\nFileDiff::FileDiff(const QString& origText, const QString& newText):\r\n    _orig(origText.split(\"\\n\")),\r\n    _new(newText.split(\"\\n\"))\r\n{\r\n    _offset = 0;\r\n    _origLength = _orig.size();\r\n    _newLength = _new.size();\r\n\r\n    // Trim start lines if same\r\n    while (_offset < _orig.size() - 1 && _offset < _new.size() - 1 && _orig[_offset] == _new[_offset]) {\r\n        _offset++;\r\n        _origLength--;\r\n        _newLength--;\r\n    }\r\n\r\n    // Trim end lines if same\r\n    while (_origLength > 0 && _newLength > 0 && _orig[_offset + _origLength - 1] == _new[_offset + _newLength - 1]) {\r\n        _origLength--;\r\n        _newLength--;\r\n    }\r\n}\r\n\r\nQVector<FileDiff::Entry> FileDiff::diff()\r\n{\r\n    if (_origLength == 0 && _newLength == 0) {\r\n        return {};\r\n    }\r\n\r\n    auto m = _origLength + 1;\r\n    auto n = _newLength + 1;\r\n    QVector<int> c;\r\n    c.resize(m * n);\r\n    for (auto i = 0; i < _origLength; i++) {\r\n        for (auto j = 0; j < _newLength; j++) {\r\n            if (_orig[i + _offset] == _new[j + _offset]) {\r\n                c[(j + 1) * m + i + 1] = c[j * m + i] + 1;\r\n            } else {\r\n                auto a = c[j * m + i + 1];\r\n                auto b = c[(j + 1) * m + i];\r\n                c[(j + 1) * m + i + 1] = qMax(a, b);\r\n            }\r\n        }\r\n    }\r\n    QVector<FileDiff::Entry> result;\r\n    auto i = _origLength;\r\n    auto j = _newLength;\r\n    while (true) {\r\n        if (i > 0 && j > 0\r\n                && c[j * m + i] == c[(j - 1) * m + i - 1] + 1\r\n                && c[(j - 1) * m + i] == c[j * m + i - 1]) {\r\n            i--;\r\n            j--;\r\n        } else if (j > 0 && (i == 0 || c[(j - 1) * m + i] >= c[j * m + i - 1])) {\r\n            result.prepend(FileDiff::Entry(\r\n                               FileDiff::EntryType::Insertion,\r\n                               i + 1 + _offset,\r\n                               _new[j - 1 + _offset]\r\n                           ));\r\n            j--;\r\n        } else if (i > 0 && (j == 0 || c[(j - 1) * m + i] < c[j * m + i - 1])) {\r\n            result.prepend(FileDiff::Entry(\r\n                               FileDiff::EntryType::Deletion,\r\n                               i + _offset,\r\n                               _orig[i - 1 + _offset]\r\n                           ));\r\n            i--;\r\n        } else {\r\n            break;\r\n        }\r\n    }\r\n    return result;\r\n}\r\n\r\nQString FileDiff::apply(const QString& source, const QVector<FileDiff::Entry>& diff)\r\n{\r\n    auto split = source.split(\"\\n\");\r\n    QStringList text;\r\n    auto it = diff.begin();\r\n    for (auto i = 0; i < split.size(); i++) {\r\n        bool deleteOrig = false;\r\n        while (it != diff.end() && it->line == i + 1) {\r\n            if (it->kind == FileDiff::EntryType::Insertion) {\r\n                text.append(it->text);\r\n            } else {\r\n                assert(!deleteOrig);\r\n                deleteOrig = true;\r\n            }\r\n            it++;\r\n        }\r\n        if (!deleteOrig) {\r\n            text.append(split[i]);\r\n        }\r\n    }\r\n    while (it != diff.end()) {\r\n        assert(it->kind == FileDiff::EntryType::Insertion);\r\n        assert(it->line == split.size() + 1);\r\n        text.append(it->text);\r\n        it++;\r\n    }\r\n    return text.join('\\n');\r\n}\r\n\r\nHistory::History(const QJsonObject& obj, const QDir& relativeTo, QObject* parent): QObject(parent), _rootDir(relativeTo)\r\n{\r\n    _uuid = obj[\"uuid\"].toString();\r\n    _parent = obj[\"parent\"].toString();\r\n    auto files = obj[\"files\"].toObject();\r\n    for (auto it = files.begin(); it != files.end(); it++) {\r\n        auto file = _rootDir.absoluteFilePath(it.key());\r\n        auto entry = it.value().toObject();\r\n        FileHistory h;\r\n        h.snapshot = entry[\"snapshot\"].toString();\r\n        for (auto change : entry[\"changes\"].toArray()) {\r\n            auto changeObj = change.toObject();\r\n            History::Change c;\r\n            c.timestamp = changeObj[\"timestamp\"].toDouble();\r\n            for (auto edit : changeObj[\"edits\"].toArray()) {\r\n                auto editObj = edit.toObject();\r\n                auto type = editObj[\"type\"].toString() == \"insertion\" ? FileDiff::EntryType::Insertion : FileDiff::EntryType::Deletion;\r\n                auto line = editObj[\"line\"].toInt();\r\n                auto text = editObj[\"text\"].toString();\r\n                c.edits.append(FileDiff::Entry(type, line, text));\r\n            }\r\n            h.changes.append(c);\r\n        }\r\n        _history.insert(file, h);\r\n    }\r\n}\r\n\r\nHistory::History(const QString& uuid, QObject* parent): QObject(parent), _uuid(uuid)\r\n{\r\n}\r\n\r\nvoid History::addFile(const QString& file, const QString& contents)\r\n{\r\n    FileHistory h;\r\n    h.snapshot = contents;\r\n    _history.insert(file, h);\r\n}\r\n\r\nQJsonObject History::toJSON() const\r\n{\r\n    QJsonObject root;\r\n    root[\"uuid\"] = _uuid;\r\n    root[\"parent\"] = _parent.isEmpty() ? QJsonValue(QJsonValue::Type::Null) : QJsonValue(_parent);\r\n    QJsonObject files;\r\n    for (auto it = _history.begin(); it != _history.end(); it++) {\r\n        auto& val = it.value();\r\n        auto file = _rootDir.relativeFilePath(it.key());\r\n        QJsonObject entry;\r\n        entry[\"snapshot\"] = val.snapshot;\r\n        QJsonArray changes;\r\n        for (auto& change : val.changes) {\r\n            QJsonObject c;\r\n            c[\"timestamp\"] = change.timestamp;\r\n            QJsonArray edits;\r\n            for (auto& edit : change.edits) {\r\n                QJsonObject e;\r\n                e[\"type\"] = edit.kind == FileDiff::EntryType::Insertion ? \"insertion\" : \"deletion\";\r\n                e[\"line\"] = edit.line;\r\n                e[\"text\"] = edit.text;\r\n                edits.append(e);\r\n            }\r\n            c[\"edits\"] = edits;\r\n            changes.append(c);\r\n        }\r\n        entry[\"changes\"] = changes;\r\n        files.insert(file, entry);\r\n    }\r\n    root[\"files\"] = files;\r\n    return root;\r\n}\r\n\r\nvoid History::updateFileContents(const QString& file, const QString& contents)\r\n{\r\n    auto it = _history.find(file);\r\n    if (it == _history.end()) {\r\n        return;\r\n    }\r\n    FileDiff d(it.value().snapshot, contents);\r\n    auto diff = d.diff();\r\n    if (diff.empty()) {\r\n        return;\r\n    }\r\n    History::Change c;\r\n    c.edits = diff;\r\n    c.timestamp = static_cast<double>(QDateTime::currentMSecsSinceEpoch()) / 1000.0;\r\n    it.value().changes.append(std::move(c));\r\n    it.value().snapshot = contents;\r\n    emit historyChanged();\r\n}\r\n\r\nvoid History::commit() {\r\n    for (auto it = _history.begin(); it != _history.end(); it++) {\r\n        it.value().changes.clear();\r\n    }\r\n    _parent = _uuid;\r\n    _uuid = QUuid::createUuid().toString(QUuid::StringFormat::WithoutBraces);\r\n    emit historyChanged();\r\n}\r\n"
  },
  {
    "path": "MiniZincIDE/history.h",
    "content": "#ifndef HISTORY_H\r\n#define HISTORY_H\r\n\r\n#include <QObject>\r\n#include <QString>\r\n#include <QStringList>\r\n#include <QMap>\r\n#include <QVector>\r\n#include <QJsonObject>\r\n#include <QDir>\r\n\r\nclass FileDiff\r\n{\r\npublic:\r\n    enum class EntryType {\r\n        Insertion,\r\n        Deletion\r\n    };\r\n\r\n    struct Entry {\r\n        EntryType kind;\r\n        qsizetype line;\r\n        QString text;\r\n\r\n        Entry() {}\r\n\r\n        Entry(EntryType _kind, qsizetype _line, const QString& _text):\r\n            kind(_kind),\r\n            line(_line),\r\n            text(_text) {}\r\n    };\r\n\r\n    FileDiff(const QString& origText, const QString& newText);\r\n\r\n    QVector<Entry> diff();\r\n\r\n    static QString apply(const QString& source, const QVector<Entry>& diff);\r\n\r\nprivate:\r\n    QStringList _orig;\r\n    QStringList _new;\r\n\r\n    qsizetype _offset;\r\n    qsizetype _origLength;\r\n    qsizetype _newLength;\r\n};\r\n\r\nclass History : public QObject {\r\n    Q_OBJECT\r\n\r\npublic:\r\n    ///\r\n    /// \\brief Create a new history tracker\r\n    /// \\param doc The JSON history data\r\n    /// \\param parent The parent object\r\n    ///\r\n    explicit History(const QJsonObject& obj, const QDir& relativeTo, QObject* parent = nullptr);\r\n\r\n    explicit History(const QString& uuid, QObject* parent = nullptr);\r\n\r\n    ///\r\n    /// \\brief Output history as JSON\r\n    /// \\return The JSON document\r\n    ///\r\n    QJsonObject toJSON() const;\r\n\r\n    void addFile(const QString& file, const QString& contents);\r\nsignals:\r\n    ///\r\n    /// \\brief Emitted when the history has changed\r\n    ///\r\n    void historyChanged();\r\n\r\npublic slots:\r\n    ///\r\n    /// \\brief Update the history of the given file if it is currently being tracked\r\n    /// \\param file The file path\r\n    /// \\param contents The new file contents\r\n    ///\r\n    void updateFileContents(const QString& file, const QString& contents);\r\n\r\n    ///\r\n    /// \\brief Remove stored changes and create new history node\r\n    ///\r\n    void commit();\r\n\r\nprivate:\r\n    struct Change {\r\n        double timestamp;\r\n        QVector<FileDiff::Entry> edits;\r\n    };\r\n\r\n    struct FileHistory {\r\n        QString snapshot;\r\n        QVector<Change> changes;\r\n    };\r\n\r\n    QString _uuid;\r\n    QString _parent;\r\n    QMap<QString, FileHistory> _history;\r\n    QDir _rootDir;\r\n};\r\n\r\n#endif // HISTORY_H\r\n"
  },
  {
    "path": "MiniZincIDE/ide.cpp",
    "content": "#include <QSettings>\n#include <QMessageBox>\n#include <QDesktopServices>\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QUuid>\n#include <QUrlQuery>\n#include <QNetworkReply>\n#include <QFileInfo>\n#include <QFileDialog>\n#include <QPushButton>\n#include <QRegularExpression>\n\n#include \"ide.h\"\n#include \"ideutils.h\"\n#include \"mainwindow.h\"\n#include \"ui_mainwindow.h\"\n#include \"checkupdatedialog.h\"\n\n#ifdef MINIZINC_IDE_TESTING\n#include \"tests/testide.h\"\n#endif\n\nIDEStatistics::IDEStatistics(void)\n    : errorsShown(0), errorsClicked(0), modelsRun(0) {}\n\nvoid IDEStatistics::init(QVariant v) {\n    if (v.isValid()) {\n        QMap<QString,QVariant> m = v.toMap();\n        errorsShown = m[\"errorsShown\"].toInt();\n        errorsClicked = m[\"errorsClicked\"].toInt();\n        modelsRun = m[\"modelsRun\"].toInt();\n        solvers = m[\"solvers\"].toStringList();\n    }\n}\n\nQVariantMap IDEStatistics::toVariantMap(void) {\n    QMap<QString,QVariant> m;\n    m[\"errorsShown\"] = errorsShown;\n    m[\"errorsClicked\"] = errorsClicked;\n    m[\"modelsRun\"] = modelsRun;\n    m[\"solvers\"] = solvers;\n    return m;\n}\n\nQByteArray IDEStatistics::toJson(void) {\n    QJsonDocument jd(QJsonObject::fromVariantMap(toVariantMap()));\n    return jd.toJson();\n}\n\nvoid IDEStatistics::resetCounts(void) {\n    errorsShown = 0;\n    errorsClicked = 0;\n    modelsRun = 0;\n}\n\nstruct IDE::Doc {\n    QTextDocument td;\n    QSet<CodeEditor*> editors;\n    bool large;\n    Doc() {\n        td.setDocumentLayout(new QPlainTextDocumentLayout(&td));\n    }\n};\n\nbool IDE::event(QEvent *e)\n{\n    switch (e->type()) {\n    case QEvent::FileOpen:\n    {\n        QString file = static_cast<QFileOpenEvent*>(e)->file();\n        if (file.endsWith(\".mzp\")) {\n            PMap::iterator it = projects.find(file);\n            if (it==projects.end()) {\n                MainWindow* mw = qobject_cast<MainWindow*>(activeWindow());\n                if (mw==nullptr) {\n                    mw = new MainWindow(file);\n                    mw->show();\n                } else {\n                    mw->openProject(file);\n                }\n            } else {\n                it.value()->raise();\n                it.value()->activateWindow();\n            }\n        } else {\n            MainWindow* curw = qobject_cast<MainWindow*>(activeWindow());\n            if (curw != nullptr && (curw->isEmptyProject() || curw==lastDefaultProject)) {\n                curw->openFile(file);\n                lastDefaultProject = curw;\n            } else {\n                QStringList files;\n                files << file;\n                MainWindow* mw = new MainWindow(files);\n                if (curw!=nullptr) {\n                    QPoint p = curw->pos();\n                    mw->move(p.x()+20, p.y()+20);\n                }\n                mw->show();\n                lastDefaultProject = mw;\n            }\n        }\n        return true;\n    }\n    default:\n        return QApplication::event(e);\n    }\n}\n\nvoid IDE::checkUpdate(void) {\n    QSettings settings;\n    settings.sync();\n\n    settings.beginGroup(\"ide\");\n    if (settings.value(\"checkforupdates21\",false).toBool()) {\n        QDate lastCheck = QDate::fromString(settings.value(\"lastCheck21\",\n                                                           QDate::currentDate().addDays(-2).toString()).toString());\n        if (lastCheck < QDate::currentDate()) {\n            // Prepare Google Analytics event\n            QUrlQuery gaQuery;\n            gaQuery.addQueryItem(\"v\",\"1\"); // version 1 of the protocol\n            gaQuery.addQueryItem(\"tid\",\"UA-63390311-1\"); // the MiniZinc ID\n            gaQuery.addQueryItem(\"cid\",settings.value(\"uuid\",\"unknown\").toString()); // identifier for this installation\n            gaQuery.addQueryItem(\"aip\",\"1\"); // anonymize IP address\n            gaQuery.addQueryItem(\"t\",\"event\"); // feedback type\n            gaQuery.addQueryItem(\"ec\",\"check\"); // event type\n            gaQuery.addQueryItem(\"ea\",\"checkUpdate\"); // event action\n            gaQuery.addQueryItem(\"el\",applicationVersion()); // event label (IDE version)\n            QNetworkRequest gaRequest(QUrl(\"http://www.google-analytics.com/collect\"));\n            gaRequest.setHeader(QNetworkRequest::ContentTypeHeader, \"application/x-www-form-urlencoded\");\n            networkManager->post(gaRequest, gaQuery.toString().toLocal8Bit());\n\n            // Check if an update is available\n            QNetworkRequest request(QUrl(\"http://www.minizinc.org/version-info.php\"));\n            versionCheckReply = networkManager->get(request);\n            connect(versionCheckReply, &QNetworkReply::finished, this, &IDE::versionCheckFinished);\n        }\n        QTimer::singleShot(24*60*60*1000, this, SLOT(checkUpdate()));\n    }\n    settings.endGroup();\n}\n\n\nIDE::IDE(int& argc, char* argv[]) : QApplication(argc,argv) {\n    setApplicationVersion(MINIZINC_IDE_VERSION);\n    setOrganizationName(\"MiniZinc\");\n    setOrganizationDomain(\"minizinc.org\");\n#ifdef MINIZINC_IDE_TESTING\n    setApplicationName(\"MiniZinc IDE Test Suite\");\n    TestMocker::resetSettings();\n#elif MINIZINC_IDE_BUNDLED\n    setApplicationName(\"MiniZinc IDE (bundled)\");\n#else\n    setApplicationName(\"MiniZinc IDE\");\n#endif\n\n    networkManager = new QNetworkAccessManager(this);\n\n    setAttribute(Qt::AA_UseHighDpiPixmaps);\n\n    QSettings settings;\n    settings.sync();\n    settings.beginGroup(\"ide\");\n    if (settings.value(\"lastCheck21\",QString()).toString().isEmpty()) {\n        settings.setValue(\"uuid\", QUuid::createUuid().toString());\n\n        CheckUpdateDialog cud;\n        int result = cud.exec();\n\n        settings.setValue(\"lastCheck21\",QDate::currentDate().addDays(-2).toString());\n        settings.setValue(\"checkforupdates21\",result==QDialog::Accepted);\n        settings.sync();\n    }\n    bool wordWrap = settings.value(\"wordWrap\", true).toBool();\n    settings.endGroup();\n    settings.beginGroup(\"Recent\");\n    recentFiles = settings.value(\"files\",QStringList()).toStringList();\n    recentProjects = settings.value(\"projects\",QStringList()).toStringList();\n    settings.endGroup();\n\n    stats.init(settings.value(\"statistics\"));\n\n    lastDefaultProject = nullptr;\n\n    darkModeNotifier = new DarkModeNotifier(this);\n    themeManager = new ThemeManager(this);\n\n    { // Load cheat sheet\n        QString fileContents;\n        QFile file(\":/cheat_sheet.mzn\");\n        if (file.open(QFile::ReadOnly)) {\n            fileContents = file.readAll();\n        } else {\n            qDebug() << \"internal error: cannot open cheat sheet.\";\n        }\n\n        QSettings settings;\n        settings.beginGroup(\"MainWindow\");\n\n        QFont defaultFont;\n        defaultFont.setFamily(\"Menlo\");\n        if (!defaultFont.exactMatch()) {\n            defaultFont.setFamily(\"Consolas\");\n        }\n        if (!defaultFont.exactMatch()) {\n            defaultFont.setFamily(\"Courier New\");\n        }\n        defaultFont.setStyleHint(QFont::TypeWriter);\n        defaultFont.setPointSize(13);\n        auto editorFont = IDEUtils::fontFromString(settings.value(\"editorFont\").toString());\n        int zoom = settings.value(\"zoom\", 100).toInt();\n        editorFont.setPointSize(editorFont.pointSize() * zoom / 100);\n        bool darkMode = darkModeNotifier->darkMode();\n        auto themeIdx = settings.value(\"theme\", 0).toInt();\n        themeManager->current(themeIdx);\n        auto& theme = themeManager->current();\n        settings.endGroup();\n\n        cheatSheet = new QMainWindow;\n        cheatSheet->setWindowTitle(\"MiniZinc Cheat Sheet\");\n        CodeEditor* ce = new CodeEditor(nullptr,\":/cheat_sheet.mzn\",false,false,editorFont,2,false,theme,darkMode,nullptr,nullptr);\n        ce->setWordWrapMode(wordWrap ?\n                                QTextOption::WrapAtWordBoundaryOrAnywhere :\n                                QTextOption::NoWrap);\n        ce->document()->setPlainText(fileContents);\n        QTextCursor cursor = ce->textCursor();\n        cursor.movePosition(QTextCursor::Start);\n        ce->setTextCursor(cursor);\n\n        ce->setReadOnly(true);\n        cheatSheet->setCentralWidget(ce);\n        cheatSheet->resize(800, 600);\n    }\n\n    connect(darkModeNotifier, &DarkModeNotifier::darkModeChanged, this, &IDE::onDarkModeChanged);\n    onDarkModeChanged(darkModeNotifier->darkMode());\n\n    connect(&fsWatch, &QFileSystemWatcher::fileChanged, this, &IDE::fileModified);\n    connect(this, &IDE::focusChanged, this, &IDE::handleFocusChange);\n    connect(&modifiedTimer, &QTimer::timeout, this, &IDE::fileModifiedTimeout);\n\n#ifdef Q_OS_MAC\n    MainWindow* mw = new MainWindow(QString());\n    const QMenuBar* mwb = mw->ui->menubar;\n    defaultMenuBar = new QMenuBar(0);\n    recentFilesMenu = new QMenu(\"Recent files\");\n    recentProjectsMenu = new QMenu(\"Recent projects\");\n    connect(recentFilesMenu, &QMenu::triggered, this, &IDE::recentFileMenuAction);\n    connect(recentProjectsMenu, &QMenu::triggered, this, &IDE::recentProjectMenuAction);\n    addRecentFile(\"\");\n    addRecentProject(\"\");\n\n    QList<QObject*> lst = mwb->children();\n    foreach (QObject* mo, lst) {\n        if (QMenu* m = qobject_cast<QMenu*>(mo)) {\n            if (m->title()==\"&File\" || m->title()==\"Help\") {\n                QMenu* nm = defaultMenuBar->addMenu(m->title());\n                foreach (QAction* a, m->actions()) {\n                    if (a->isSeparator()) {\n                        nm->addSeparator();\n                    } else {\n                        if (a->text()==\"Recent Files\") {\n                            nm->addMenu(recentFilesMenu);\n                        } else if (a->text()==\"Recent Projects\") {\n                            nm->addMenu(recentProjectsMenu);\n                        } else {\n                            QAction* na = nm->addAction(a->text());\n                            na->setShortcut(a->shortcut());\n                            if (a==mw->ui->actionQuit) {\n                                connect(na, &QAction::triggered, this, &IDE::quit);\n                            } else if (a==mw->ui->actionNewModel_file || a==mw->ui->actionNew_project) {\n                                connect(na, &QAction::triggered, this, &IDE::newProject);\n                            } else if (a==mw->ui->actionOpen) {\n                                connect(na, &QAction::triggered, this, [=] () { openFile(); });\n                            } else if (a==mw->ui->actionHelp) {\n                                connect(na, &QAction::triggered, this, &IDE::help);\n                            } else {\n                                na->setEnabled(false);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n    mainWindows.remove(mw);\n    delete mw;\n#endif\n\n    checkUpdate();\n}\n\n#ifdef Q_OS_MAC\n\nvoid IDE::recentFileMenuAction(QAction* a) {\n    if (a->text()==\"Clear Menu\") {\n        recentFiles.clear();\n        recentFilesMenu->clear();\n        recentFilesMenu->addSeparator();\n        recentFilesMenu->addAction(\"Clear Menu\");\n    } else {\n        openFile(a->data().toString());\n    }\n}\n\nvoid IDE::recentProjectMenuAction(QAction* a) {\n    if (a->text()==\"Clear Menu\") {\n        IDE::instance()->recentProjects.clear();\n        recentProjectsMenu->clear();\n        recentProjectsMenu->addSeparator();\n        recentProjectsMenu->addAction(\"Clear Menu\");\n    } else {\n        openFile(a->data().toString());\n    }\n}\n\n#endif\n\nvoid IDE::handleFocusChange(QWidget *old, QWidget *newW)\n{\n    if (old==nullptr && newW!=nullptr && !modifiedFiles.empty()) {\n        fileModifiedTimeout();\n    }\n}\n\nvoid IDE::fileModifiedTimeout(void)\n{\n    QSet<QString> modCopy = modifiedFiles;\n    modifiedFiles.clear();\n    for (QSet<QString>::iterator s_it = modCopy.begin(); s_it != modCopy.end(); ++s_it) {\n        DMap::iterator it = documents.find(*s_it);\n        if (it != documents.end()) {\n            QFileInfo fi(*s_it);\n            QMessageBox msg;\n\n            if (!fi.exists()) {\n                msg.setText(\"The file \"+fi.fileName()+\" has been removed or renamed outside MiniZinc IDE.\");\n                msg.setStandardButtons(QMessageBox::Ok);\n            } else {\n                msg.setText(\"The file \"+fi.fileName()+\" has been modified outside MiniZinc IDE.\");\n\n                bool isModified = it.value()->td.isModified();\n                if (!isModified) {\n                    QFile newFile(fi.absoluteFilePath());\n                    if (newFile.open(QFile::ReadOnly | QFile::Text)) {\n                        QString newFileContents = newFile.readAll();\n                        isModified = (newFileContents != it.value()->td.toPlainText());\n                    } else {\n                        isModified = true;\n                    }\n                }\n\n                if (isModified) {\n                    if (it.value()->td.isModified()) {\n                        msg.setInformativeText(\"Do you want to reload the file and discard your changes?\");\n                    } else {\n                        msg.setInformativeText(\"Do you want to reload the file?\");\n                    }\n                    QPushButton* cancelButton = msg.addButton(QMessageBox::Cancel);\n                    msg.addButton(\"Reload\", QMessageBox::AcceptRole);\n                    msg.exec();\n                    if (msg.clickedButton()==cancelButton) {\n                        it.value()->td.setModified(true);\n                    } else {\n                        QFile file(*s_it);\n                        if (file.open(QFile::ReadOnly | QFile::Text)) {\n                            QString contents = file.readAll();\n                            it.value()->td.setPlainText(contents);\n                            it.value()->td.setModified(false);\n                            emit reloadedFile(*s_it, contents);\n                        } else {\n                            QMessageBox::warning(nullptr, \"MiniZinc IDE\",\n                                                 \"Could not reload file \"+*s_it,\n                                                 QMessageBox::Ok);\n                            it.value()->td.setModified(true);\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid IDE::fileModified(const QString &f)\n{\n    modifiedFiles.insert(f);\n    if (activeWindow()!=nullptr) {\n        modifiedTimer.setSingleShot(true);\n        modifiedTimer.start(3000);\n    }\n}\n\nvoid IDE::addRecentProject(const QString& p) {\n    if (p != \"\") {\n        recentProjects.removeAll(p);\n        recentProjects.insert(0,p);\n        while (recentProjects.size() > 12)\n            recentProjects.pop_back();\n    }\n#ifdef Q_OS_MAC\n    recentProjectsMenu->clear();\n    for (int i=0; i<recentProjects.size(); i++) {\n        QAction* na = recentProjectsMenu->addAction(recentProjects[i]);\n        na->setData(recentProjects[i]);\n    }\n    recentProjectsMenu->addSeparator();\n    recentProjectsMenu->addAction(\"Clear Menu\");\n#endif\n}\n\nvoid IDE::addRecentFile(const QString& f) {\n    if (f != \"\") {\n        recentFiles.removeAll(f);\n        recentFiles.insert(0,f);\n        while (recentFiles.size() > 12)\n            recentFiles.pop_back();\n    }\n#ifdef Q_OS_MAC\n    recentFilesMenu->clear();\n    for (int i=0; i<recentFiles.size(); i++) {\n        QAction* na = recentFilesMenu->addAction(recentFiles[i]);\n        na->setData(recentFiles[i]);\n    }\n    recentFilesMenu->addSeparator();\n    recentFilesMenu->addAction(\"Clear Menu\");\n#endif\n}\n\nvoid IDE::newProject()\n{\n    MainWindow* mw = new MainWindow(QString());\n    mw->show();\n}\n\nQString IDE::getLastPath(void) {\n    QSettings settings;\n    settings.beginGroup(\"Path\");\n    return settings.value(\"lastPath\",\"\").toString();\n}\n\nvoid IDE::setLastPath(const QString& path) {\n    QSettings settings;\n    settings.beginGroup(\"Path\");\n    settings.setValue(\"lastPath\", path);\n    settings.endGroup();\n}\n\nvoid IDE::setEditorFont(QFont font)\n{\n    for (auto* mw : qAsConst(mainWindows)) {\n        mw->setEditorFont(font);\n    }\n    static_cast<CodeEditor*>(cheatSheet->centralWidget())->setEditorFont(font);\n}\n\nvoid IDE::setEditorIndent(int indentSize, bool useTabs)\n{\n    for (auto* mw : qAsConst(mainWindows)) {\n        mw->setEditorIndent(indentSize, useTabs);\n    }\n}\n\nvoid IDE::setEditorWordWrap(QTextOption::WrapMode mode)\n{\n    for (auto* mw : qAsConst(mainWindows)) {\n        mw->setEditorWordWrap(mode);\n    }\n    static_cast<CodeEditor*>(cheatSheet->centralWidget())->setWordWrapMode(mode);\n}\n\nvoid IDE::openFile(const QString& fileName0)\n{\n    QStringList fileNames;\n    if (fileName0.isEmpty()) {\n        fileNames = QFileDialog::getOpenFileNames(nullptr, tr(\"Open File\"), getLastPath(), \"MiniZinc Files (*.mzn *.dzn *.fzn *.json *.mzp *.mzc *.mpc);;Other (*)\");\n        if (!fileNames.isEmpty()) {\n            setLastPath(QFileInfo(fileNames.last()).absolutePath() + fileDialogSuffix);\n        }\n    } else {\n        fileNames << fileName0;\n    }\n    if (!fileNames.isEmpty()) {\n        MainWindow* mw = new MainWindow(fileNames);\n        mw->show();\n    }\n}\n\nvoid IDE::help()\n{\n    QDesktopServices::openUrl(QUrl(QString(\"http://docs.minizinc.dev/en/\")+MINIZINC_IDE_VERSION+\"/minizinc_ide.html\"));\n}\n\nIDE::~IDE(void) {\n    QSettings settings;\n    settings.setValue(\"statistics\",stats.toVariantMap());\n    settings.beginGroup(\"Recent\");\n    settings.setValue(\"files\",recentFiles);\n    settings.setValue(\"projects\",recentProjects);\n    settings.endGroup();\n    settings.beginGroup(\"ide\");\n    settings.endGroup();\n}\n\nbool IDE::hasFile(const QString& path)\n{\n    return documents.find(path) != documents.end();\n}\n\nQTextDocument* IDE::addDocument(const QString& path, QTextDocument *doc, CodeEditor *ce)\n{\n    Doc* d = new Doc;\n    d->td.setDefaultFont(ce->font());\n    d->td.setPlainText(doc->toPlainText());\n    d->editors.insert(ce);\n    d->large = false;\n    documents.insert(path,d);\n    fsWatch.addPath(path);\n    return &d->td;\n}\n\nQPair<QTextDocument*,bool> IDE::loadFile(const QString& path, QWidget* parent)\n{\n    DMap::iterator it = documents.find(path);\n    if (it==documents.end()) {\n        QFile file(path);\n        if (file.open(QFile::ReadOnly | QFile::Text)) {\n            Doc* d = new Doc;\n            if ( (path.endsWith(\".dzn\") || path.endsWith(\".fzn\") || path.endsWith(\".json\")) && file.size() > 5*1024*1024) {\n                d->large = true;\n            } else {\n                d->td.setPlainText(file.readAll());\n                d->large = false;\n            }\n            d->td.setModified(false);\n            documents.insert(path,d);\n            if (!d->large)\n                fsWatch.addPath(path);\n            return qMakePair(&d->td,d->large);\n        } else {\n            QMessageBox::warning(parent, \"MiniZinc IDE\",\n                                 \"Could not open file \"+path,\n                                 QMessageBox::Ok);\n            QTextDocument* nd = nullptr;\n            return qMakePair(nd,false);\n        }\n\n    } else {\n        return qMakePair(&it.value()->td,it.value()->large);\n    }\n}\n\nvoid IDE::loadLargeFile(const QString &path, QWidget* parent)\n{\n    DMap::iterator it = documents.find(path);\n    if (it.value()->large) {\n        QFile file(path);\n        if (file.open(QFile::ReadOnly | QFile::Text)) {\n            QTextStream file_stream(&file);\n#if QT_VERSION < 0x060000\n            file_stream.setCodec(\"UTF-8\");\n#endif\n            it.value()->td.setPlainText(file_stream.readAll());\n            it.value()->large = false;\n            it.value()->td.setModified(false);\n            QSet<CodeEditor*>::iterator ed = it.value()->editors.begin();\n            for (; ed != it.value()->editors.end(); ++ed) {\n                (*ed)->loadedLargeFile();\n            }\n            fsWatch.addPath(path);\n        } else {\n            QMessageBox::warning(parent, \"MiniZinc IDE\",\n                                 \"Could not open file \"+path,\n                                 QMessageBox::Ok);\n        }\n    }\n}\n\nvoid IDE::registerEditor(const QString& path, CodeEditor* ce)\n{\n    DMap::iterator it = documents.find(path);\n    QSet<CodeEditor*>& editors = it.value()->editors;\n    editors.insert(ce);\n}\n\nvoid IDE::removeEditor(const QString& path, CodeEditor* ce)\n{\n    DMap::iterator it = documents.find(path);\n    if (it == documents.end()) {\n        qDebug() << \"internal error: document \" << path << \" not found\";\n    } else {\n        QSet<CodeEditor*>& editors = it.value()->editors;\n        editors.remove(ce);\n        if (editors.empty()) {\n            delete it.value();\n            documents.remove(path);\n            fsWatch.removePath(path);\n        }\n    }\n}\n\nvoid IDE::renameFile(const QString& oldPath, const QString& newPath)\n{\n    DMap::iterator it = documents.find(oldPath);\n    if (it == documents.end()) {\n        qDebug() << \"internal error: document \" << oldPath << \" not found\";\n    } else {\n        Doc* doc = it.value();\n        documents.remove(oldPath);\n        fsWatch.removePath(oldPath);\n        documents.insert(newPath, doc);\n        fsWatch.addPath(newPath);\n    }\n}\n\nvoid\nIDE::versionCheckFinished(void) {\n    if (versionCheckReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()==200) {\n        QString currentVersion = versionCheckReply->readAll();\n\n        QRegularExpression versionRegExp(\"([1-9][0-9]*)\\\\.([0-9]+)\\\\.([0-9]+)\");\n\n        int curVersionMajor = 0;\n        int curVersionMinor = 0;\n        int curVersionPatch = 0;\n        bool ok = true;\n        QRegularExpressionMatch curVersionMatch = versionRegExp.match(currentVersion);\n        if (curVersionMatch.hasMatch()) {\n            curVersionMajor = curVersionMatch.captured(1).toInt(&ok);\n            if (ok)\n                curVersionMinor = curVersionMatch.captured(2).toInt(&ok);\n            if (ok)\n                curVersionPatch = curVersionMatch.captured(3).toInt(&ok);\n        }\n\n        int appVersionMajor = 0;\n        int appVersionMinor = 0;\n        int appVersionPatch = 0;\n        QRegularExpressionMatch appVersionMatch = versionRegExp.match(applicationVersion());\n        if (ok && appVersionMatch.hasMatch()) {\n            appVersionMajor = appVersionMatch.captured(1).toInt(&ok);\n            if (ok)\n                appVersionMinor = appVersionMatch.captured(2).toInt(&ok);\n            if (ok)\n                appVersionPatch = appVersionMatch.captured(3).toInt(&ok);\n        }\n\n        bool needUpdate = ok && (curVersionMajor > appVersionMajor ||\n                                 (curVersionMajor==appVersionMajor &&\n                                  (curVersionMinor > appVersionMinor ||\n                                   (curVersionMinor==appVersionMinor &&\n                                    curVersionPatch > appVersionPatch))));\n\n        if (needUpdate) {\n            int button = QMessageBox::information(nullptr,\"Update available\",\n                                     \"Version \"+currentVersion+\" of MiniZinc is now available. \"\n                                     \"You are currently using version \"+applicationVersion()+\n                                     \".\\nDo you want to open the MiniZinc web site?\",\n                                     QMessageBox::Cancel|QMessageBox::Ok,QMessageBox::Ok);\n            if (button==QMessageBox::Ok) {\n                QDesktopServices::openUrl(QUrl(\"http://www.minizinc.org/\"));\n            }\n        }\n        QSettings settings;\n        settings.beginGroup(\"ide\");\n        settings.setValue(\"lastCheck21\",QDate::currentDate().toString());\n        settings.endGroup();\n        stats.resetCounts();\n    }\n}\n\nQString IDE::appDir(void) const {\n#ifdef Q_OS_MAC\n    return applicationDirPath()+\"/../Resources/\";\n#else\n    return applicationDirPath();\n#endif\n}\n\nIDE* IDE::instance(void) {\n    return static_cast<IDE*>(qApp);\n}\n\nvoid IDE::onDarkModeChanged(bool)\n{\n    refreshTheme();\n}\n\nvoid IDE::refreshTheme()\n{\n    auto& theme = themeManager->current();\n    bool darkMode = darkModeNotifier->darkMode();\n    static_cast<CodeEditor*>(cheatSheet->centralWidget())->setTheme(theme, darkMode);\n\n    if (!darkModeNotifier->hasNativeDarkMode()) {\n        // No native dark widgets, so use stylesheet instead\n        if (darkMode) {\n            QFile sheet(\":/dark_mode.css\");\n            sheet.open(QFile::ReadOnly);\n            qApp->setStyleSheet(sheet.readAll());\n        } else {\n            qApp->setStyleSheet(\"\");\n        }\n    }\n\n    for (auto* mw : qAsConst(mainWindows)) {\n        mw->setTheme(theme, darkMode);\n    }\n}\n"
  },
  {
    "path": "MiniZincIDE/ide.h",
    "content": "#ifndef IDE_H\n#define IDE_H\n\n#include <QApplication>\n#include <QFileSystemWatcher>\n#include <QNetworkAccessManager>\n#include <QTextDocument>\n#include <QMainWindow>\n\n#include \"codeeditor.h\"\n#include \"darkmodenotifier.h\"\n#include \"theme.h\"\n\n#ifdef Q_OS_WIN\n#define pathSep \";\"\n#define fileDialogSuffix \"/\"\n#define MZNOS \"win\"\n#else\n#define pathSep \":\"\n#ifdef Q_OS_MAC\n#define fileDialogSuffix \"/*\"\n#define MZNOS \"mac\"\n#else\n#define fileDialogSuffix \"/\"\n#define MZNOS \"linux\"\n#endif\n#endif\n\nclass MainWindow;\n\nclass IDEStatistics {\npublic:\n    int errorsShown;\n    int errorsClicked;\n    int modelsRun;\n    QStringList solvers;\n    QVariantMap toVariantMap(void);\n    IDEStatistics(void);\n    void init(QVariant v);\n    QByteArray toJson(void);\n    void resetCounts(void);\n};\n\nclass IDE : public QApplication {\n    Q_OBJECT\npublic:\n    IDE(int& argc, char* argv[]);\n    ~IDE(void);\n    struct Doc;\n    typedef QMap<QString,Doc*> DMap;\n    DMap documents;\n    typedef QMap<QString,MainWindow*> PMap;\n    PMap projects;\n    QSet<MainWindow*> mainWindows;\n\n    QStringList recentFiles;\n    QStringList recentProjects;\n\n    QSet<QString> modifiedFiles;\n    QTimer modifiedTimer;\n\n    IDEStatistics stats;\n\n    MainWindow* lastDefaultProject;\n    QMainWindow* cheatSheet;\n\n    QNetworkAccessManager* networkManager;\n    QNetworkReply* versionCheckReply;\n\n    DarkModeNotifier* darkModeNotifier;\n    ThemeManager* themeManager;\n\n#ifdef Q_OS_MAC\n    QMenuBar* defaultMenuBar;\n    QMenu* recentFilesMenu;\n    QMenu* recentProjectsMenu;\npublic slots:\n    void recentFileMenuAction(QAction*);\n    void recentProjectMenuAction(QAction*);\npublic:\n#endif\n\n    QFileSystemWatcher fsWatch;\n\n    bool hasFile(const QString& path);\n    QPair<QTextDocument*,bool> loadFile(const QString& path, QWidget* parent);\n    void loadLargeFile(const QString& path, QWidget* parent);\n    QTextDocument* addDocument(const QString& path, QTextDocument* doc, CodeEditor* ce);\n    void registerEditor(const QString& path, CodeEditor* ce);\n    void removeEditor(const QString& path, CodeEditor* ce);\n    void renameFile(const QString& oldPath, const QString& newPath);\n    QString appDir(void) const;\n    static IDE* instance(void);\n    QString getLastPath(void);\n    void setLastPath(const QString& path);\n    void setEditorFont(QFont font);\n    void setEditorIndent(int indentSize, bool useTabs);\n    void setEditorWordWrap(QTextOption::WrapMode mode);\n    void addRecentFile(const QString& file);\n    void addRecentProject(const QString& file);\n    void refreshTheme();\nprotected:\n    bool event(QEvent *);\nprotected slots:\n    void versionCheckFinished(void);\n    void newProject(void);\n    void openFile(const QString& filename = QString(\"\"));\n    void fileModified(const QString&);\n    void fileModifiedTimeout(void);\n    void handleFocusChange(QWidget*,QWidget*);\n    void onDarkModeChanged(bool darkMode);\npublic slots:\n    void checkUpdate(void);\n    void help(void);\nsignals:\n    void reloadedFile(const QString& file, const QString& contents);\n};\n\n#endif // IDE_H\n"
  },
  {
    "path": "MiniZincIDE/ideutils.cpp",
    "content": "#include \"ideutils.h\"\r\n\r\n#include <QDir>\r\n#include <QFileInfo>\r\n#include <QCheckBox>\r\n#include <QLineEdit>\r\n#include <QSpinBox>\r\n#include <QDoubleSpinBox>\r\n#include <QComboBox>\r\n#include <QGroupBox>\r\n#include <QTableWidget>\r\n#include <QPlainTextEdit>\r\n\r\n\r\nnamespace IDEUtils {\r\n\r\nQString formatTime(qint64 time) {\r\n    int hours =  time / 3600000;\r\n    int minutes = (time % 3600000) / 60000;\r\n    int seconds = (time % 60000) / 1000;\r\n    int msec = (time % 1000);\r\n    QString elapsed;\r\n    if (hours > 0) {\r\n        elapsed += QString(\"%1h \").arg(hours);\r\n    }\r\n    if (hours > 0 || minutes > 0) {\r\n        elapsed += QString(\"%1m \").arg(minutes);\r\n    }\r\n    if (hours > 0 || minutes > 0 || seconds > 0) {\r\n        elapsed += QString(\"%1s\").arg(seconds);\r\n    }\r\n    if (hours==0 && minutes==0) {\r\n        if (seconds > 0) {\r\n            elapsed += \" \";\r\n        }\r\n        elapsed += QString(\"%1msec\").arg(msec);\r\n    }\r\n    return elapsed;\r\n}\r\n\r\nbool isChildPath(const QString& parent, const QString& child)\r\n{\r\n    return !QDir(parent).relativeFilePath(child).startsWith(\".\");\r\n}\r\n\r\n\r\nvoid watchChildChanges(QWidget* target, QObject* receiver, std::function<void()> action)\r\n{\r\n    for (auto widget : target->findChildren<QWidget*>()) {\r\n        QCheckBox* checkBox;\r\n        QLineEdit* lineEdit;\r\n        QSpinBox* spinBox;\r\n        QDoubleSpinBox* doubleSpinBox;\r\n        QComboBox* comboBox;\r\n        QGroupBox* groupBox;\r\n        QTableWidget* tableWidget;\r\n        QPlainTextEdit* plainTextEdit;\r\n\r\n        if ((checkBox = qobject_cast<QCheckBox*>(widget))) {\r\n            QObject::connect(checkBox, &QCheckBox::stateChanged, receiver, [=] (int) { action(); });\r\n        } else if ((lineEdit = qobject_cast<QLineEdit*>(widget))) {\r\n            QObject::connect(lineEdit, &QLineEdit::textChanged, receiver, [=] (const QString&) { action(); });\r\n        } else if ((spinBox = qobject_cast<QSpinBox*>(widget))) {\r\n            QObject::connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), receiver, [=] (int) { action(); });\r\n        } else if ((doubleSpinBox = qobject_cast<QDoubleSpinBox*>(widget))) {\r\n            QObject::connect(doubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), receiver, [=] (double) { action(); });\r\n        } else if ((comboBox = qobject_cast<QComboBox*>(widget))) {\r\n            QObject::connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), receiver, [=] (int) { action(); });\r\n        } else if ((groupBox = qobject_cast<QGroupBox*>(widget))) {\r\n            QObject::connect(groupBox, &QGroupBox::toggled, receiver, [=] (bool) { action(); });\r\n        } else if ((tableWidget = qobject_cast<QTableWidget*>(widget))) {\r\n            QObject::connect(tableWidget, &QTableWidget::cellChanged, receiver, [=] (int, int) { action(); });\r\n        } else if ((plainTextEdit = qobject_cast<QPlainTextEdit*>(widget))) {\r\n            QObject::connect(plainTextEdit, &QPlainTextEdit::textChanged,receiver, [=] () { action(); });\r\n        }\r\n    }\r\n}\r\n\r\nQFont fontFromString(const QString& s)\r\n{\r\n    QFont font;\r\n    if (!s.isEmpty() && font.fromString(s)) {\r\n        return font;\r\n    }\r\n\r\n    QStringList families({\"Menlo\", \"Consolas\", \"Courier New\"});\r\n\r\n    font.setPointSize(13);\r\n    font.setStyleHint(QFont::TypeWriter);\r\n    for (auto& family : families) {\r\n        font.setFamily(family);\r\n        if (font.exactMatch()) {\r\n            break;\r\n        }\r\n    }\r\n    return font;\r\n}\r\n\r\n}\r\n"
  },
  {
    "path": "MiniZincIDE/ideutils.h",
    "content": "#ifndef IDEUTILS_H\r\n#define IDEUTILS_H\r\n\r\n#include <QString>\r\n#include <QWidget>\r\n#include <QMimeData>\r\n#include <QTextEdit>\r\n#include <functional>\r\n\r\nnamespace IDEUtils {\r\n    QString formatTime(qint64 time);\r\n    bool isChildPath(const QString& parent, const QString& child);\r\n    void watchChildChanges(QWidget* target, QObject* receiver, std::function<void()> action);\r\n    template <class T>\r\n    void watchChildChanges(QWidget* target, T* receiver, void (T::* action)()) {\r\n        watchChildChanges(target, receiver, std::bind(action, receiver));\r\n    }\r\n    QFont fontFromString(const QString& s);\r\n\r\n    class MimeDataExporter : public QTextEdit {\r\n    public:\r\n        QMimeData* md(void) const {\r\n            QMimeData* mymd = createMimeDataFromSelection();\r\n            mymd->removeFormat(\"text/plain\");\r\n            return mymd;\r\n        }\r\n    };\r\n\r\n}\r\n\r\n#endif // IDEUTILS_H\r\n"
  },
  {
    "path": "MiniZincIDE/images/about.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\"\n\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n<html><head>\n  <style type=\"text/css\"> p, li { white-space:\npre-wrap; }\n  </style>\n</head>\n<body style=\" font-family:'.Lucida Grande UI';\nfont-size:13pt; font-weight:400; font-style:normal;\">\n<div style=\"float:left; margin-right:30px\">\n<a href=\"http://www.minizinc.org\"><img src=\":/images/mznlogo.png\" width=\"128\" height=\"108\"></a>\n</div>\n<div>\n  <h3>The MiniZinc IDE</h3>\n</div>\n<div>\n  Version $VERSION<br />\n  Author: Guido Tack<br />\n  Copyright Monash University and NICTA 2013, 2014, 2015\n</div>\n<div>\nThis program is provided under the terms of the <a href=\"http://www.minizinc.org/2.0/LICENSE.txt\">Mozilla Public License Version 2.0</a>.\nIt uses the Qt toolkit, available from <a href=\"http://qt-project.org\">qt-project.org</a>.\n</div>\n<div>lala\n</div>\n</body></html>\n"
  },
  {
    "path": "MiniZincIDE/main.cpp",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#include \"ide.h\"\n#include \"mainwindow.h\"\n\nint main(int argc, char *argv[])\n{\n    IDE a(argc, argv);\n    QStringList args = QApplication::arguments();\n    QStringList files;\n    bool hadProject = false;\n    for (int i=1; i<args.size(); i++) {\n        if (args[i].endsWith(\".mzp\")) {\n            MainWindow* mw = new MainWindow(args[i]);\n            mw->show();\n            hadProject = true;\n        } else {\n            files << args[i];\n        }\n    }\n    if (!hadProject) {\n        MainWindow* w = new MainWindow(files);\n        w->show();\n    }\n#ifdef Q_OS_MAC\n    a.setQuitOnLastWindowClosed(false);\n#endif\n    return a.exec();\n}\n"
  },
  {
    "path": "MiniZincIDE/mainwindow.cpp",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#include <QtWidgets>\n#include <QApplication>\n#include <QSet>\n#include <QNetworkRequest>\n#include <QNetworkReply>\n#include <QBoxLayout>\n#include <QTemporaryFile>\n\n#include \"mainwindow.h\"\n#include \"ui_mainwindow.h\"\n#include \"ide.h\"\n#include \"codeeditor.h\"\n#include \"fzndoc.h\"\n#include \"gotolinedialog.h\"\n#include \"paramdialog.h\"\n#include \"checkupdatedialog.h\"\n#include \"moocsubmission.h\"\n#include \"highlighter.h\"\n#include \"exception.h\"\n#include \"ideutils.h\"\n#include \"preferencesdialog.h\"\n\n#include \"cp-profiler/src/cpprofiler/execution.hh\"\n#include \"cp-profiler/src/cpprofiler/options.hh\"\n\n#include <QtGlobal>\n\n#define BUILD_YEAR  (__DATE__ + 7)\n\nMainWindow::MainWindow(const QString& project) :\n    ui(new Ui::MainWindow),\n    curEditor(nullptr),\n    code_checker(nullptr),\n    tmpDir(nullptr),\n    saveBeforeRunning(false),\n    processRunning(false)\n{\n    init(project);\n}\n\nMainWindow::MainWindow(const QStringList& files) :\n    ui(new Ui::MainWindow),\n    curEditor(nullptr),\n    code_checker(nullptr),\n    tmpDir(nullptr),\n    saveBeforeRunning(false),\n    processRunning(false)\n{\n    init(QString());\n    for (int i=0; i<files.size(); i++)\n        openFile(files[i],false);\n\n}\n\nvoid MainWindow::showWindowMenu()\n{\n    ui->menuWindow->clear();\n    ui->menuWindow->addAction(minimizeAction);\n    ui->menuWindow->addSeparator();\n    for (QSet<MainWindow*>::iterator it = IDE::instance()->mainWindows.begin();\n         it != IDE::instance()->mainWindows.end(); ++it) {\n        QAction* windowAction = ui->menuWindow->addAction((*it)->windowTitle());\n        QVariant v = QVariant::fromValue(static_cast<void*>(*it));\n        windowAction->setData(v);\n        windowAction->setCheckable(true);\n        if (*it == this) {\n            windowAction->setChecked(true);\n        }\n    }\n}\n\nvoid MainWindow::windowMenuSelected(QAction* a)\n{\n    if (a==minimizeAction) {\n        showMinimized();\n    } else {\n        QMainWindow* mw = static_cast<QMainWindow*>(a->data().value<void*>());\n        mw->showNormal();\n        mw->raise();\n        mw->activateWindow();\n    }\n}\n\nvoid MainWindow::init(const QString& projectFile)\n{\n    code_checker = new CodeChecker(this);\n\n    IDE::instance()->mainWindows.insert(this);\n    ui->setupUi(this);\n    ui->tabWidget->removeTab(0);\n#ifndef Q_OS_MAC\n    ui->menuFile->addAction(ui->actionQuit);\n#endif\n\n    setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);\n\n    // initialise find widget\n    ui->findFrame->hide();\n    ui->findWidget->installEventFilter(this);\n\n    QWidget* solverConfFrame = new QWidget;\n    QVBoxLayout* solverConfFrameLayout = new QVBoxLayout;\n\n    solverConfCombo = new QComboBox;\n    solverConfCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);\n    solverConfCombo->setMinimumWidth(100);\n    QLabel* solverConfLabel = new QLabel(\"Solver configuration:\");\n    QFont solverConfLabelFont = solverConfLabel->font();\n    solverConfLabelFont.setPointSizeF(solverConfLabelFont.pointSizeF()*0.9);\n    solverConfLabel->setFont(solverConfLabelFont);\n    solverConfFrameLayout->addWidget(solverConfLabel);\n    solverConfFrameLayout->addWidget(solverConfCombo);\n    solverConfFrame->setLayout(solverConfFrameLayout);\n    QAction* solverConfComboAction = ui->toolBar->insertWidget(ui->actionSubmit_to_MOOC, solverConfFrame);\n\n    auto runMenu = new QMenu(this);\n    runMenu->addAction(ui->actionRun);\n    runMenu->addAction(ui->actionCompile);\n    runMenu->addAction(ui->actionProfile_compilation);\n    runMenu->addAction(ui->actionProfile_search);\n\n    runButton = new QToolButton;\n    runButton->setDefaultAction(ui->actionRun);\n    runButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);\n    runButton->setPopupMode(QToolButton::MenuButtonPopup);\n    runButton->setMenu(runMenu);\n\n    ui->toolBar->insertWidget(solverConfComboAction, runButton);\n\n    setAcceptDrops(true);\n    setAttribute(Qt::WA_DeleteOnClose, true);\n    minimizeAction = new QAction(\"&Minimize\",this);\n    minimizeAction->setShortcut(Qt::CTRL | Qt::Key_M);\n#ifdef Q_OS_MAC\n    connect(ui->menuWindow, &QMenu::aboutToShow, this, &MainWindow::showWindowMenu);\n    connect(ui->menuWindow, &QMenu::triggered, this, &MainWindow::windowMenuSelected);\n    ui->menuWindow->addAction(minimizeAction);\n    ui->menuWindow->addSeparator();\n#else\n    ui->menuWindow->hide();\n    ui->menubar->removeAction(ui->menuWindow->menuAction());\n#endif\n    QWidget* toolBarSpacer = new QWidget();\n    toolBarSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);\n    ui->toolBar->insertWidget(ui->actionEditSolverConfig, toolBarSpacer);\n\n    newFileCounter = 1;\n\n    profileInfoVisible = false;\n\n    paramDialog = new ParamDialog(this);\n\n    fakeRunAction = new QAction(this);\n    fakeRunAction->setShortcut(Qt::CTRL | Qt::Key_R);\n    fakeRunAction->setEnabled(true);\n    this->addAction(fakeRunAction);\n\n    fakeCompileAction = new QAction(this);\n    fakeCompileAction->setShortcut(Qt::CTRL | Qt::Key_B);\n    fakeCompileAction->setEnabled(true);\n    this->addAction(fakeCompileAction);\n\n    fakeStopAction = new QAction(this);\n    fakeStopAction->setShortcut(Qt::CTRL | Qt::Key_E);\n    fakeStopAction->setEnabled(true);\n    this->addAction(fakeStopAction);\n\n    updateRecentProjects(\"\");\n    updateRecentFiles(\"\");\n    connect(ui->menuRecent_Files, &QMenu::triggered, this, &MainWindow::recentFileMenuAction);\n    connect(ui->menuRecent_Projects, &QMenu::triggered, this, &MainWindow::recentProjectMenuAction);\n\n    connect(ui->tabWidget, &QTabWidget::tabCloseRequested, this, &MainWindow::tabCloseRequest);\n    connect(ui->tabWidget, &QTabWidget::currentChanged, this, &MainWindow::tabChange);\n\n    progressBar = new QProgressBar;\n    progressBar->setRange(0, 100);\n    progressBar->setSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Minimum);\n    progressBar->setHidden(true);\n    statusLabel = new QLabel(\"\");\n    statusLineColLabel = new QLabel(\"\");\n    ui->statusbar->addPermanentWidget(statusLabel);\n    ui->statusbar->addPermanentWidget(progressBar);\n    ui->statusbar->addWidget(statusLineColLabel);\n    ui->actionStop->setEnabled(false);\n    QTabBar* tb = ui->tabWidget->findChild<QTabBar*>();\n    tb->setTabButton(0, QTabBar::RightSide, 0);\n    tb->setTabButton(0, QTabBar::LeftSide, 0);\n\n    ui->actionSubmit_to_MOOC->setVisible(false);\n\n    connect(ui->outputWidget, &OutputWidget::anchorClicked, this, &MainWindow::anchorClicked);\n\n    QSettings settings;\n    settings.beginGroup(\"MainWindow\");\n    editorFont = IDEUtils::fontFromString(settings.value(\"editorFont\").toString());\n    auto zoom = settings.value(\"zoom\", 100).toInt();\n    editorFont.setPointSize(editorFont.pointSize() * zoom / 100);\n    initTheme();\n    ui->outputWidget->setBrowserFont(editorFont);\n    resize(settings.value(\"size\", QSize(800, 600)).toSize());\n    move(settings.value(\"pos\", QPoint(100, 100)).toPoint());\n\n    auto frameGeom = frameGeometry();\n    bool positionValid = false;\n    for (auto* screen: QGuiApplication::screens()) {\n        auto geom = screen->availableGeometry();\n        if (geom.intersects(frameGeom)) {\n            positionValid = true;\n            break;\n        }\n    }\n    if (!positionValid) {\n        // Window is outside of view\n        resize(800, 600);\n        move(100, 100);\n    }\n\n    if (settings.value(\"toolbarHidden\", false).toBool()) {\n        on_actionHide_tool_bar_triggered();\n    }\n    if (settings.value(\"outputWindowHidden\", true).toBool()) {\n        on_actionOnly_editor_triggered();\n    }\n    ui->check_wrap->setChecked(settings.value(\"findWrapAround\", false).toBool());\n    ui->check_re->setChecked(settings.value(\"findRegularExpression\", false).toBool());\n    ui->check_case->setChecked(settings.value(\"findCaseSensitive\", false).toBool());\n    settings.endGroup();\n\n    settings.beginGroup(\"ide\");\n    indentSize = settings.value(\"indentSize\", 2).toInt();\n    useTabs = settings.value(\"indentTabs\", false).toBool();\n    settings.endGroup();\n\n    IDE::instance()->setEditorFont(editorFont);\n\n    settings.beginGroup(\"minizinc\");\n    QString mznDistribPath = settings.value(\"mznpath\",\"\").toString();\n    settings.endGroup();\n    auto& driver = MznDriver::get();\n    if (!driver.isValid()) {\n        try {\n            driver.setLocation(mznDistribPath);\n        } catch (Exception& e) {\n            int ret = QMessageBox::warning(this, \"MiniZinc IDE\",\n                                           e.message() + \"\\nDo you want to open the settings dialog?\",\n                                           QMessageBox::Ok | QMessageBox::Cancel);\n            if (ret == QMessageBox::Ok)\n                on_actionManage_solvers_triggered();\n        }\n    }\n    ui->config_window->init();\n\n    connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &MainWindow::onClipboardChanged);\n\n    ui->projectExplorerDockWidget->hide();\n    ui->configWindow_dockWidget->hide();\n\n    project = new Project(ui->config_window->solverConfigs(), this);\n    connect(ui->tabWidget, &QTabWidget::currentChanged, [=] (int i) {\n        project->openTabsChanged(getOpenFiles(), i);\n    });\n    connect(ui->config_window, &ConfigWindow::selectedSolverConfigurationChanged, project, &Project::activeSolverConfigChanged);\n    connect(ui->config_window, &ConfigWindow::configSaved, [=] (const QString& f) { openFile(f); });\n    ui->projectBrowser->project(project);\n\n    connect(project, &Project::moocChanged, this, &MainWindow::on_moocChanged);\n\n    ui->actionNext_tab->setShortcuts(QKeySequence::NextChild);\n    ui->actionPrevious_tab->setShortcuts(QKeySequence::PreviousChild);\n\n    if (!projectFile.isEmpty()) {\n        loadProject(projectFile);\n        setLastPath(QFileInfo(projectFile).absolutePath()+fileDialogSuffix);\n    } else {\n        createEditor(\"Playground\",false,true);\n        if (getLastPath().isEmpty()) {\n            setLastPath(QDir::currentPath()+fileDialogSuffix);\n        }\n    }\n\n    getProject().setModified(false);\n\n    ui->cpprofiler_dockWidget->hide();\n}\n\nvoid MainWindow::updateUiProcessRunning(bool pr)\n{\n    processRunning = pr;\n\n    if (processRunning) {\n        fakeRunAction->setEnabled(true);\n        ui->actionRun->setEnabled(false);\n        ui->actionProfile_compilation->setEnabled(false);\n        ui->actionProfile_search->setEnabled(false);\n        fakeCompileAction->setEnabled(true);\n        ui->actionCompile->setEnabled(false);\n        fakeStopAction->setEnabled(false);\n        ui->actionStop->setEnabled(true);\n        runButton->removeAction(ui->actionRun);\n        runButton->setDefaultAction(ui->actionStop);\n        ui->actionSubmit_to_MOOC->setEnabled(false);\n    } else {\n        bool isPlayground = false;\n        bool isMzn = false;\n        bool isFzn = false;\n        bool isData = false;\n        if (curEditor) {\n            isPlayground = curEditor->filepath==\"\";\n            isMzn = (isPlayground && !curEditor->filename.endsWith(\".fzn\")) || QFileInfo(curEditor->filepath).suffix()==\"mzn\";\n            isFzn = QFileInfo(curEditor->filepath).suffix()==\"fzn\" || curEditor->filename.endsWith(\".fzn\");\n            isData = QFileInfo(curEditor->filepath).suffix()==\"dzn\" || QFileInfo(curEditor->filepath).suffix()==\"json\";\n        }\n        fakeRunAction->setEnabled(! (isMzn || isFzn || isData));\n        ui->actionRun->setEnabled(isMzn || isFzn || isData);\n        ui->actionProfile_compilation->setEnabled(isMzn || isData);\n        ui->actionProfile_search->setEnabled(isMzn || isFzn || isData);\n        fakeCompileAction->setEnabled(!(isMzn||isData));\n        ui->actionCompile->setEnabled(isMzn||isData);\n        fakeStopAction->setEnabled(true);\n        ui->actionStop->setEnabled(false);\n        runButton->removeAction(ui->actionStop);\n        runButton->setDefaultAction(ui->actionRun);\n        ui->actionSubmit_to_MOOC->setEnabled(true);\n    }\n}\n\nMainWindow::~MainWindow()\n{\n    delete code_checker;\n    delete server;\n    for (int i=0; i<cleanupTmpDirs.size(); i++) {\n        delete cleanupTmpDirs[i];\n    }\n    for (int i=0; i<cleanupProcesses.size(); i++) {\n        cleanupProcesses[i]->terminate();\n        delete cleanupProcesses[i];\n    }\n    delete project;\n    delete ui;\n}\n\nvoid MainWindow::on_actionNewModel_file_triggered()\n{\n    createEditor(\".mzn\",false,true);\n}\n\nvoid MainWindow::on_actionNewData_file_triggered()\n{\n    createEditor(\".dzn\",false,true);\n}\n\nvoid MainWindow::createEditor(const QString& path, bool openAsModified, bool isNewFile, bool readOnly, bool focus) {\n    QTextDocument* doc = nullptr;\n    bool large = false;\n    QString fileContents;\n    QString absPath = QFileInfo(path).canonicalFilePath();\n    if (isNewFile && path == \"Playground\") {\n        absPath = path;\n        fileContents = \"% Use this editor as a MiniZinc scratch book\\n\";\n        openAsModified = false;\n    } else if (isNewFile && path.startsWith(\".\")) {\n        absPath = QString(\"Untitled\")+QString().setNum(newFileCounter++)+path;\n    } else if (path.isEmpty()) {\n        absPath = path;\n        // Do nothing\n    } else if (openAsModified) {\n        QFile file(path);\n        if (file.open(QFile::ReadOnly)) {\n            fileContents = file.readAll();\n        } else {\n            QMessageBox::warning(this,\"MiniZinc IDE\",\n                                 \"Could not open file \"+path,\n                                 QMessageBox::Ok);\n            return;\n        }\n        if (isNewFile) {\n            absPath = QFileInfo(path).fileName();\n        }\n    } else {\n        if (absPath.isEmpty()) {\n            QMessageBox::warning(this,\"MiniZinc IDE\",\n                                 \"Could not open file \"+path,\n                                 QMessageBox::Ok);\n            return;\n        }\n        QPair<QTextDocument*,bool> d = IDE::instance()->loadFile(absPath,this);\n        updateRecentFiles(absPath);\n        doc = d.first;\n        large = d.second;\n    }\n    if (doc || !fileContents.isEmpty() || isNewFile) {\n        int closeTab = -1;\n        if (!isNewFile && ui->tabWidget->count()==1) {\n            CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(0));\n            if (ce && ce->filepath == \"\" && !ce->document()->isModified()) {\n                closeTab = 0;\n            }\n        }\n        auto& theme = IDE::instance()->themeManager->current();\n        CodeEditor* ce = new CodeEditor(doc,absPath,isNewFile,large,editorFont,indentSize,useTabs,theme,darkMode,ui->tabWidget,this);\n        if (readOnly || ce->filename == \"_coursera\" || ce->filename.endsWith(\".mzc\"))\n            ce->setReadOnly(true);\n        int tab = ui->tabWidget->addTab(ce, ce->filename);\n        if(profileInfoVisible)\n            ce->showDebugInfo(profileInfoVisible);\n        if (focus) {\n            ui->tabWidget->setCurrentIndex(tab);\n            curEditor->setFocus();\n        }\n        if (!fileContents.isEmpty()) {\n            curEditor->filepath = \"\";\n            curEditor->document()->setPlainText(fileContents);\n            curEditor->document()->setModified(openAsModified);\n            tabChange(ui->tabWidget->currentIndex());\n            auto* history = getProject().history();\n            if (history != nullptr) {\n                history->updateFileContents(absPath, fileContents);\n            }\n        } else if (doc) {\n            if (!absPath.endsWith(\".fzn\")) {\n                getProject().add(absPath);\n                auto* history = getProject().history();\n                if (history != nullptr) {\n                    history->updateFileContents(absPath, doc->toPlainText());\n                }\n            }\n            IDE::instance()->registerEditor(absPath,curEditor);\n        }\n        if (closeTab >= 0)\n            tabCloseRequest(closeTab);\n    }\n}\n\nvoid MainWindow::setLastPath(const QString &s)\n{\n    IDE::instance()->setLastPath(s);\n}\n\nQString MainWindow::getLastPath(void)\n{\n    return IDE::instance()->getLastPath();\n}\n\nvoid MainWindow::openFile(const QString &path, bool openAsModified, bool focus)\n{\n    QStringList fileNames;\n    if (path.isEmpty()) {\n        fileNames = QFileDialog::getOpenFileNames(this, tr(\"Open File\"), getLastPath(), \"MiniZinc Files (*.mzn *.dzn *.fzn *.json *.mzp *.mzc *.mpc);;Other (*)\");\n        if (!fileNames.isEmpty()) {\n            setLastPath(QFileInfo(fileNames.last()).absolutePath() + fileDialogSuffix);\n        }\n    } else {\n        fileNames << path;\n    }\n\n    for (auto& fileName : fileNames) {\n        if (fileName.endsWith(\".mzp\")) {\n            openProject(fileName);\n        } else if (fileName.endsWith(\".mpc\")) {\n            QString absPath = QFileInfo(fileName).canonicalFilePath();\n            if (ui->config_window->addConfig(absPath)) {\n                getProject().add(absPath);\n                ui->configWindow_dockWidget->setVisible(true);\n            }\n        } else {\n            createEditor(fileName, openAsModified, false, false, focus);\n        }\n    }\n\n}\n\nvoid MainWindow::tabCloseRequest(int tab)\n{\n    CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(tab));\n    if (ce) {\n        if (ce->document()->isModified()) {\n            QMessageBox msg;\n            msg.setText(\"The document has been modified.\");\n            msg.setInformativeText(\"Do you want to save your changes?\");\n            msg.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);\n            msg.setDefaultButton(QMessageBox::Save);\n            int ret = msg.exec();\n            switch (ret) {\n            case QMessageBox::Save:\n                saveFile(ce,ce->filepath);\n                if (ce->document()->isModified())\n                    return;\n                break;\n            case QMessageBox::Discard:\n                break;\n            case QMessageBox::Cancel:\n                return;\n            default:\n                return;\n            }\n        }\n        ce->document()->setModified(false);\n        if (!ce->filepath.isEmpty()) {\n            IDE::instance()->removeEditor(ce->filepath,ce);\n        }\n        if (ce == curEditor) {\n            curEditor = nullptr;\n        }\n        ce->deleteLater();\n    } else {\n        ui->tabWidget->removeTab(tab);\n    }\n    getProject().openTabsChanged(getOpenFiles(), ui->tabWidget->currentIndex());\n}\n\nvoid MainWindow::closeEvent(QCloseEvent* e) {\n    // make sure any modifications in solver configurations are saved\n//    on_conf_solver_conf_currentIndexChanged(ui->conf_solver_conf->currentIndex());\n\n    bool modified = false;\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        auto ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce && ce->document()->isModified()) {\n            modified = true;\n            break;\n        }\n    }\n    if (modified) {\n        int ret = QMessageBox::warning(this, \"MiniZinc IDE\",\n                                       \"There are modified documents.\\nDo you want to discard the changes or cancel?\",\n                                       QMessageBox::Discard | QMessageBox::Cancel);\n        if (ret == QMessageBox::Cancel) {\n            e->ignore();\n            return;\n        }\n    }\n    for (auto& sc : ui->config_window->solverConfigs()) {\n        if (sc->modified) {\n            int ret = QMessageBox::warning(this, \"MiniZinc IDE\",\n                                           \"There are modified solver configurations.\\nDo you want to discard the changes or cancel?\",\n                                           QMessageBox::Discard | QMessageBox::Cancel);\n            if (ret == QMessageBox::Cancel) {\n                e->ignore();\n                return;\n            }\n            break;\n        }\n    }\n    if (getProject().isModified()) {\n        int ret = QMessageBox::warning(this, \"MiniZinc IDE\",\n                                       \"The project has been modified.\\nDo you want to discard the changes or cancel?\",\n                                       QMessageBox::Discard | QMessageBox::Cancel);\n        if (ret == QMessageBox::Cancel) {\n            e->ignore();\n            return;\n        }\n    }\n    if (processRunning) {\n        int ret = QMessageBox::warning(this, \"MiniZinc IDE\",\n                                       \"MiniZinc is currently running a solver.\\nDo you want to quit anyway and stop the current process?\",\n                                       QMessageBox::Yes| QMessageBox::No);\n        if (ret == QMessageBox::No) {\n            e->ignore();\n            return;\n        }\n    }\n\n    // At this point we're definitely closing\n    emit terminating();\n\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce) {\n            ce->setDocument(nullptr);\n            ce->filepath = \"\";\n//            if (ce->filepath != \"\") { // TODO: How is this possible?\n//                IDE::instance()->removeEditor(ce->filepath,ce);\n//            }\n        }\n    }\n    if (getProject().hasProjectFile()) {\n        IDE::instance()->projects.remove(getProject().projectFile());\n    }\n\n    IDE::instance()->mainWindows.remove(this);\n\n    QSettings settings;\n    settings.beginGroup(\"MainWindow\");\n    settings.setValue(\"darkMode\", darkMode);\n    settings.setValue(\"size\", size());\n    settings.setValue(\"pos\", pos());\n    settings.setValue(\"toolbarHidden\", ui->toolBar->isHidden());\n    settings.setValue(\"outputWindowHidden\", ui->outputDockWidget->isHidden());\n\n    settings.setValue(\"findWrapAround\", ui->check_wrap->isChecked());\n    settings.setValue(\"findRegularExpression\", ui->check_re->isChecked());\n    settings.setValue(\"findCaseSensitive\", ui->check_case->isChecked());\n\n    settings.endGroup();\n    e->accept();\n}\n\nvoid MainWindow::dragEnterEvent(QDragEnterEvent* event) {\n    if (event->mimeData()->hasFormat(\"text/uri-list\"))\n        event->acceptProposedAction();\n}\n\nvoid MainWindow::dropEvent(QDropEvent* event) {\n    const QMimeData* mimeData = event->mimeData();\n    if (mimeData->hasUrls()) {\n        QList<QUrl> urlList = mimeData->urls();\n        for (int i=0; i<urlList.size(); i++) {\n            openFile(urlList[i].toLocalFile());\n        }\n    }\n    event->acceptProposedAction();\n}\n\nvoid MainWindow::tabChange(int tab) {\n    if (curEditor) {\n        disconnect(ui->actionCopy, &QAction::triggered, curEditor, &CodeEditor::copy);\n        disconnect(ui->actionPaste, &QAction::triggered, curEditor, &CodeEditor::paste);\n        disconnect(ui->actionCut, &QAction::triggered, curEditor, &CodeEditor::cut);\n        disconnect(ui->actionSelect_All, &QAction::triggered, curEditor, &CodeEditor::selectAll);\n        disconnect(ui->actionUndo, &QAction::triggered, curEditor, &CodeEditor::undo);\n        disconnect(ui->actionRedo, &QAction::triggered, curEditor, &CodeEditor::redo);\n        disconnect(curEditor, &CodeEditor::copyAvailable, ui->actionCopy, &QAction::setEnabled);\n        disconnect(curEditor, &CodeEditor::copyAvailable, ui->actionCut, &QAction::setEnabled);\n        disconnect(curEditor->document(), &QTextDocument::modificationChanged,\n                   this, &MainWindow::setWindowModified);\n        disconnect(curEditor->document(), &QTextDocument::undoAvailable,\n                   ui->actionUndo, &QAction::setEnabled);\n        disconnect(curEditor->document(), &QTextDocument::redoAvailable,\n                   ui->actionRedo, &QAction::setEnabled);\n        disconnect(curEditor, &CodeEditor::cursorPositionChanged, this, &MainWindow::editor_cursor_position_changed);\n        disconnect(code_checker, &CodeChecker::finished, curEditor, &CodeEditor::checkFile);\n        disconnect(curEditor, &CodeEditor::changedDebounced, this, &MainWindow::check_code);\n    }\n    curEditor = tab == -1 ? nullptr : qobject_cast<CodeEditor*>(ui->tabWidget->widget(tab));\n    if (!curEditor) {\n        setEditorMenuItemsEnabled(false);\n        ui->findFrame->hide();\n    } else {\n        setEditorMenuItemsEnabled(true);\n        connect(ui->actionCopy, &QAction::triggered, curEditor, &CodeEditor::copy);\n        connect(ui->actionPaste, &QAction::triggered, curEditor, &CodeEditor::paste);\n        connect(ui->actionCut, &QAction::triggered, curEditor, &CodeEditor::cut);\n        connect(ui->actionSelect_All, &QAction::triggered, curEditor, &CodeEditor::selectAll);\n        connect(ui->actionUndo, &QAction::triggered, curEditor, &CodeEditor::undo);\n        connect(ui->actionRedo, &QAction::triggered, curEditor, &CodeEditor::redo);\n        connect(curEditor, &CodeEditor::copyAvailable, ui->actionCopy, &QAction::setEnabled);\n        connect(curEditor, &CodeEditor::copyAvailable, ui->actionCut, &QAction::setEnabled);\n        connect(curEditor->document(), &QTextDocument::modificationChanged,\n                this, &MainWindow::setWindowModified);\n        connect(curEditor->document(), &QTextDocument::undoAvailable,\n                ui->actionUndo, &QAction::setEnabled);\n        connect(curEditor->document(), &QTextDocument::redoAvailable,\n                ui->actionRedo, &QAction::setEnabled);\n        connect(curEditor, &CodeEditor::cursorPositionChanged, this, &MainWindow::editor_cursor_position_changed);\n        connect(code_checker, &CodeChecker::finished, curEditor, &CodeEditor::checkFile);\n        connect(curEditor, &CodeEditor::changedDebounced, this, &MainWindow::check_code);\n        setWindowModified(curEditor->document()->isModified());\n        QString p;\n        p += \" \";\n        p += QChar(0x2014);\n        p += \" \";\n        if (getProject().hasProjectFile()) {\n            QFileInfo fi(getProject().projectFile());\n            p += \"Project: \" + fi.baseName();\n        } else {\n            p += \"Untitled Project\";\n        }\n        if (curEditor->filepath.isEmpty()) {\n            setWindowFilePath(curEditor->filename);\n            setWindowTitle(curEditor->filename+p+\"[*]\");\n        } else {\n            setWindowFilePath(curEditor->filepath);\n            setWindowTitle(curEditor->filename+p+\"[*]\");\n\n            bool haveChecker = false;\n            if (curEditor->filename.endsWith(\".mzn\")) {\n                QString checkFile = curEditor->filepath;\n                checkFile.replace(checkFile.length()-1,1,\"c\");\n                haveChecker = getProject().contains(checkFile) || getProject().contains(checkFile+\".mzn\");\n            }\n            QSettings settings;\n            settings.beginGroup(\"ide\");\n            bool checkSolutions = haveChecker && settings.value(\"checkSolutions\", true).toBool();\n            if (checkSolutions) {\n                ui->actionRun->setText(\"Run + check\");\n            } else {\n                ui->actionRun->setText(\"Run\");\n            }\n\n        }\n        ui->actionSave->setEnabled(true);\n        ui->actionSave_as->setEnabled(true);\n        ui->actionSelect_All->setEnabled(true);\n        ui->actionUndo->setEnabled(curEditor->document()->isUndoAvailable());\n        ui->actionRedo->setEnabled(curEditor->document()->isRedoAvailable());\n        updateUiProcessRunning(processRunning);\n\n        ui->actionFind->setEnabled(true);\n        ui->actionFind_next->setEnabled(true);\n        ui->actionFind_previous->setEnabled(true);\n        ui->actionReplace->setEnabled(true);\n        ui->actionShift_left->setEnabled(true);\n        ui->actionShift_right->setEnabled(true);\n        curEditor->setFocus();\n\n        MainWindow::check_code();\n    }\n\n    updateProfileSearchButton();\n}\n\nvoid MainWindow::on_actionClose_triggered()\n{\n    int tab = ui->tabWidget->currentIndex();\n    tabCloseRequest(tab);\n}\n\nvoid MainWindow::on_actionOpen_triggered()\n{\n    openFile(QString());\n}\n\nvoid MainWindow::addOutput(const QString& s, bool html)\n{\n    if (html) {\n        ui->outputWidget->addHtml(s);\n    } else {\n        ui->outputWidget->addText(s);\n    }\n}\n\nvoid MainWindow::on_actionRun_triggered()\n{\n    compileOrRun(CM_RUN);\n}\n\nvoid MainWindow::compileOrRun(\n        CompileMode cm,\n        const SolverConfiguration* sc,\n        const QString& model,\n        const QStringList& data,\n        const QString& checker,\n        const QStringList& extraArgs)\n{\n    if (!promptSaveModified()) {\n        return;\n    }\n\n    if (curEditor != nullptr && curEditor->filepath.endsWith(\".mzc.mzn\") && cm == CM_COMPILE) {\n        // Compile the current checker\n        compileSolutionChecker(curEditor->filepath);\n        return;\n    }\n\n    // Use either the provided solver config, or the currently active one\n    auto solverConfig = sc ? sc : getCurrentSolverConfig();\n    if (!solverConfig) {\n        // There is no solver config\n        return;\n    }\n\n    // Use either the model given, or the currently opened model, or prompt for one\n    auto modelFile = model.isEmpty() ? currentModelFile() : model;\n    if (modelFile.isEmpty()) {\n        // There is no model file at all\n        return;\n    }\n\n    auto dataFiles = data;\n    auto checkerFile = checker;\n    auto extraArguments = extraArgs;\n    QStringList inlineData;\n\n    // If we didn't specify any data but the current file is a data file, use it\n    if (dataFiles.isEmpty() && curEditor && (curEditor->filepath.endsWith(\".dzn\") || curEditor->filepath.endsWith(\".json\"))) {\n        dataFiles << curEditor->filepath;\n    }\n\n    // Prompt for data if necessary\n    if (!getModelParameters(*solverConfig, modelFile, dataFiles, extraArguments, inlineData)) {\n        return;\n    }\n\n    // If we didn't specify a checker but the current file is one, use it\n    if (checkerFile.isEmpty() && curEditor && (curEditor->filepath.endsWith(\".mzc\") || curEditor->filepath.endsWith(\".mzc.mzn\"))) {\n        checkerFile = curEditor->filepath;\n    }\n\n    // Use checker that matches model file if it exists and checking is on\n    QSettings settings;\n    settings.beginGroup(\"ide\");\n\n    if (settings.value(\"clearOutput\", false).toBool()) {\n        on_actionClear_output_triggered();\n    }\n    on_actionSplit_triggered();\n\n    if (checkerFile.isEmpty() && settings.value(\"checkSolutions\", true).toBool()) {\n        auto mzc = modelFile;\n        mzc.replace(mzc.length() - 1, 1, \"c\");\n        if (getProject().contains(mzc)) {\n            checkerFile = mzc;\n        } else if (getProject().contains(mzc + \".mzn\")) {\n            checkerFile = mzc + \".mzn\";\n        }\n    }\n    settings.endGroup();\n\n    if (!checkerFile.isEmpty()) {\n        dataFiles << checkerFile;\n    }\n\n    if (solverConfig->solverDefinition.stdFlags.contains(\"--output-html\")) {\n        extraArguments << \"--output-html\";\n    }\n\n    // Compile/run\n    switch (cm) {\n    case CM_RUN:\n        run(*solverConfig, modelFile, dataFiles, extraArguments, inlineData);\n        break;\n    case CM_COMPILE:\n        compile(*solverConfig, modelFile, dataFiles, extraArguments, inlineData);\n        break;\n    case CM_PROFILE:\n        compile(*solverConfig, modelFile, dataFiles, extraArguments, inlineData, true);\n        break;\n    }\n}\n\nbool MainWindow::getModelParameters(const SolverConfiguration& sc, const QString& model, QStringList& data, const QStringList& extraArgs, QStringList& inlineData)\n{\n    if (!requireMiniZinc()) {\n        return false;\n    }\n\n    // Get model interface to obtain parameters\n    QStringList args;\n    args << \"-c\" << \"--model-interface-only\" << data << model << extraArgs;\n\n    MznProcess p;\n    // Passing all flags can break --model-inteface-only, so just pass the necessary ones\n    SolverConfiguration checkConfig(sc.solverDefinition);\n    checkConfig.additionalData = sc.additionalData;\n    checkConfig.extraOptions = sc.extraOptions;\n\n    try {\n        auto result = p.run(checkConfig, args, QFileInfo(model).absolutePath());\n\n        QStringList additionalDataFiles = data;\n        if (result.exitCode == 0) {\n            auto jdoc = QJsonDocument::fromJson(result.stdOut.toUtf8());\n            if (jdoc.isObject() && jdoc.object()[\"input\"].isObject() && jdoc.object()[\"method\"].isString()) {\n                QJsonObject inputArgs = jdoc.object()[\"input\"].toObject();\n                QStringList undefinedArgs = inputArgs.keys();\n                if (undefinedArgs.size() > 0) {\n                    QStringList params;\n                    paramDialog->getParams(undefinedArgs, getProject().dataFiles(), params, additionalDataFiles);\n                    if (additionalDataFiles.isEmpty()) {\n                        if (params.size()==0) {\n                            return false;\n                        }\n                        for (int i=0; i<undefinedArgs.size(); i++) {\n                            if (params[i].isEmpty()) {\n                                if (! (inputArgs[undefinedArgs[i]].isObject() && inputArgs[undefinedArgs[i]].toObject().contains(\"optional\")) ) {\n                                    QMessageBox::critical(this, \"Undefined parameter\", \"The parameter '\" + undefinedArgs[i] + \"' is undefined.\");\n                                    return false;\n                                }\n                            } else {\n                                inlineData << undefinedArgs[i] + \" = \" + params[i];\n                            }\n                        }\n                    }\n                }\n            } else {\n                ui->outputWidget->addText(\"Error when checking model parameters:\\n\", ui->outputWidget->errorCharFormat());\n                ui->outputWidget->addText(result.stdErr);\n                QMessageBox::critical(this, \"Internal error\", \"Could not determine model parameters\");\n                return false;\n            }\n        }\n        for (const auto& d : additionalDataFiles) {\n            if (!data.contains(d)) {\n                // Ensure we don't add the same data file twice\n                data << d;\n            }\n        }\n        return true;\n    } catch (ProcessError& e) {\n         QMessageBox::critical(this, \"MiniZinc IDE\", e.message());\n         return false;\n    }\n}\n\nQString MainWindow::currentModelFile()\n{\n    if (curEditor) {\n        if (curEditor->filepath.endsWith(\".fzn\") ||\n                (curEditor->filepath.endsWith(\".mzn\") && !curEditor->filepath.endsWith(\".mzc.mzn\"))) {\n            // The current file is a model\n            return curEditor->filepath;\n        }\n        if (curEditor->filepath.isEmpty()) {\n            // The current file is a playground buffer\n            QTemporaryDir* modelTmpDir = new QTemporaryDir;\n            if (!modelTmpDir->isValid()) {\n                throw InternalError(\"Could not create temporary directory for compilation.\");\n            } else {\n                cleanupTmpDirs.append(modelTmpDir);\n                QString model = modelTmpDir->path() + \"/untitled_model.\" + (curEditor->filename.endsWith(\".fzn\") ? \"fzn\" : \"mzn\");\n                QFile modelFile(model);\n                if (modelFile.open(QIODevice::ReadWrite)) {\n                    QTextStream ts(&modelFile);\n#if QT_VERSION < 0x060000\n                    ts.setCodec(\"UTF-8\");\n#endif\n                    ts << curEditor->document()->toPlainText();\n                    modelFile.close();\n                } else {\n                    throw InternalError(\"Could not write temporary model file.\");\n                }\n                curEditor->playgroundTempFile = model;\n                return model;\n            }\n        }\n    }\n\n    auto modelFiles = getProject().modelFiles();\n    if (modelFiles.count() == 0) {\n        // There are no model files\n        return \"\";\n    }\n    if (modelFiles.count() == 1) {\n        return modelFiles[0];\n    }\n\n    int selectedModel = paramDialog->getModel(modelFiles);\n    if (selectedModel == -1) {\n        return \"\";\n    }\n    return modelFiles[selectedModel];\n}\n\nbool MainWindow::promptSaveModified()\n{\n    QVector<CodeEditor*> modifiedDocs;\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce && !ce->filepath.isEmpty() && ce->document()->isModified()) {\n            modifiedDocs << ce;\n        }\n    }\n\n    // Prompt for saving modified files\n    if (!modifiedDocs.empty()) {\n        if (!saveBeforeRunning) {\n            QMessageBox msgBox;\n            if (modifiedDocs.size()==1) {\n                msgBox.setText(\"One of the files is modified. You have to save it before running.\");\n            } else {\n                msgBox.setText(\"Several files have been modified. You have to save them before running.\");\n            }\n            msgBox.setInformativeText(\"Do you want to save now and then run?\");\n            QAbstractButton *saveButton = msgBox.addButton(QMessageBox::Save);\n            msgBox.addButton(QMessageBox::Cancel);\n            QAbstractButton *alwaysButton = msgBox.addButton(\"Always save\", QMessageBox::AcceptRole);\n            msgBox.setDefaultButton(QMessageBox::Save);\n            msgBox.exec();\n            if (msgBox.clickedButton()==alwaysButton) {\n                saveBeforeRunning = true;\n            }\n            if (msgBox.clickedButton()!=saveButton && msgBox.clickedButton()!=alwaysButton) {\n                return false;\n            }\n        }\n        for (auto ce : modifiedDocs) {\n            saveFile(ce,ce->filepath);\n            if (ce->document()->isModified()) {\n                return false;\n            }\n        }\n    }\n\n    return true;\n}\n\nQString MainWindow::setElapsedTime(qint64 elapsed_t)\n{\n    auto elapsed = IDEUtils::formatTime(elapsed_t);\n    QString timeLimit;\n    auto sc = getCurrentSolverConfig();\n    if (sc && sc->timeLimit > 0) {\n        timeLimit += \" / \";\n        timeLimit += IDEUtils::formatTime(sc->timeLimit);\n    }\n    statusLabel->setText(elapsed + timeLimit);\n    return elapsed;\n}\n\nvoid MainWindow::statusTimerEvent(qint64 time)\n{\n    QString txt = \"Running.\";\n    int dots = time / 5000000000;\n    for (int i = 0; i < dots; i++) {\n        txt += \".\";\n    }\n    ui->statusbar->showMessage(txt);\n    setElapsedTime(time);\n}\n\nvoid MainWindow::compileSolutionChecker(const QString& checker)\n{\n    if (!requireMiniZinc()) {\n        return;\n    }\n\n    QFileInfo fi(checker);\n\n    QSettings settings;\n    settings.beginGroup(\"ide\");\n    bool printCommand = settings.value(\"printCommand\", false).toBool();\n    settings.endGroup();\n\n    auto proc = new MznProcess(this);\n    QStringList args;\n    args << \"--compile-solution-checker\"\n         << checker;\n    connect(this, &MainWindow::terminating, proc, [=] () {\n        proc->terminate();\n        proc->deleteLater();\n    });\n    connect(ui->actionStop, &QAction::triggered, proc, [=] () {\n        ui->actionStop->setDisabled(true);\n        proc->stop();\n        ui->outputWidget->addText(\"Stopped.\");\n    });\n    connect(proc, &MznProcess::success, [=] (bool cancelled) {\n        if (!cancelled) {\n            openCompiledFzn(checker.left(checker.size() - 4));\n        }\n        procFinished(0, proc->elapsedTime());\n        ui->outputWidget->endExecution(0, proc->elapsedTime());\n    });\n    connect(proc, &MznProcess::errorOutput, this, &MainWindow::on_minizincError);\n    connect(proc, &MznProcess::warningOutput, this, &MainWindow::on_minizincError);\n    connect(proc, &MznProcess::outputStdError, this, [=] (const QString& d) {\n        QTextCharFormat f;\n        f.setForeground(IDE::instance()->themeManager->current().commentColor.get(darkMode));\n        ui->outputWidget->addText(d, f, \"Standard Error\");\n    });\n    connect(proc, &MznProcess::finished, [=] () {\n        proc->deleteLater();\n    });\n    connect(proc, &MznProcess::failure, [=](int exitCode, MznProcess::FailureType e) {\n        if (e == MznProcess::FailedToStart) {\n            QMessageBox::critical(this, \"MiniZinc IDE\", \"Failed to start MiniZinc. Check your path settings.\");\n            exitCode = 0;\n        } else if (e != MznProcess::NonZeroExit) {\n            QMetaEnum metaEnum = QMetaEnum::fromType<MznProcess::FailureType>();\n            QMessageBox::critical(this, \"MiniZinc IDE\", \"Unknown error while executing MiniZinc: \" + QString(metaEnum.valueToKey(e)));\n        }\n        ui->outputWidget->endExecution(0, proc->elapsedTime());\n        procFinished(exitCode, proc->elapsedTime());\n    });\n    connect(proc, &MznProcess::timeUpdated, this, &MainWindow::statusTimerEvent);\n    updateUiProcessRunning(true);\n    ui->outputWidget->startExecution(QString(\"Compiling %1\").arg(fi.fileName()));\n    proc->start(args, fi.canonicalPath());\n\n    if (printCommand) {\n        auto cmdMessage = QString(\"Command: %1\\n\").arg(proc->command());\n        ui->outputWidget->addText(cmdMessage, ui->outputWidget->infoCharFormat(), \"Commands\");\n    }\n}\n\nvoid MainWindow::compile(const SolverConfiguration& sc, const QString& model, const QStringList& data, const QStringList& extraArgs, const QStringList& inlineData, bool profile)\n{\n    if (!requireMiniZinc()) {\n        return;\n    }\n\n    QFileInfo fi(model);\n\n    QSettings settings;\n    settings.beginGroup(\"ide\");\n    bool printCommand = settings.value(\"printCommand\", false).toBool();\n    settings.endGroup();\n\n    QTemporaryDir* fznTmpDir = new QTemporaryDir;\n    if (!fznTmpDir->isValid()) {\n        QMessageBox::critical(this, \"MiniZinc IDE\", \"Could not create temporary directory for compilation.\");\n        return;\n    }\n    cleanupTmpDirs.append(fznTmpDir);\n    QString fzn = fznTmpDir->path() + \"/\" + fi.baseName() + \".fzn\";\n\n    auto proc = new MznProcess(this);\n    QStringList args;\n    args << \"-c\"\n         << \"-o\" << fzn\n         << model\n         << data\n         << extraArgs;\n    for (auto dzn : inlineData) {\n        args << \"-D\" << dzn;\n    }\n    if (profile) {\n        args << \"--output-paths-to-stdout\"\n             << \"--output-detailed-timing\";\n    }\n    connect(this, &MainWindow::terminating, proc, [=] () {\n        proc->terminate();\n        proc->deleteLater();\n    });\n    connect(ui->actionStop, &QAction::triggered, proc, [=] () {\n        ui->actionStop->setDisabled(true);\n        proc->stop();\n        ui->outputWidget->addText(\"Stopped.\");\n    });\n\n    if (profile) {\n        auto* timing = new QVector<TimingEntry>;\n        auto* paths = new QVector<PathEntry>;\n\n        connect(proc, &MznProcess::profilingOutput, [=] (const QVector<TimingEntry>& t) {\n            *timing << t;\n        });\n        connect(proc, &MznProcess::pathsOutput, [=] (const QVector<PathEntry>& p) {\n            *paths << p;\n        });\n\n        connect(proc, &MznProcess::success, [=] (bool cancelled) {\n            profileCompiledFzn(*timing, *paths);\n            procFinished(0, proc->elapsedTime());\n        });\n        connect(proc, &MznProcess::finished, [=] () {\n            delete timing;\n            delete paths;\n        });\n    } else {\n        connect(proc, &MznProcess::success, [=] (bool cancelled) {\n            if (!cancelled) {\n                openCompiledFzn(fzn);\n            }\n            procFinished(0, proc->elapsedTime());\n            ui->outputWidget->endExecution(0, proc->elapsedTime());\n        });\n    }\n    connect(proc, &MznProcess::statisticsOutput, ui->outputWidget, &OutputWidget::addStatistics);\n    connect(proc, &MznProcess::errorOutput, this, &MainWindow::on_minizincError);\n    connect(proc, &MznProcess::warningOutput, this, &MainWindow::on_minizincError);\n    connect(proc, &MznProcess::progressOutput, this, &MainWindow::on_progressOutput);\n    connect(proc, &MznProcess::outputStdError, this, [=] (const QString& d) {\n        QTextCharFormat f;\n        f.setForeground(IDE::instance()->themeManager->current().commentColor.get(darkMode));\n        ui->outputWidget->addText(d, f, \"Standard Error\");\n    });\n    connect(proc, &MznProcess::finished, [=] () {\n        proc->deleteLater();\n    });\n    connect(proc, &MznProcess::failure, [=](int exitCode, MznProcess::FailureType e) {\n        if (e == MznProcess::FailedToStart) {\n            QMessageBox::critical(this, \"MiniZinc IDE\", \"Failed to start MiniZinc. Check your path settings.\");\n            exitCode = 0;\n        } else if (e != MznProcess::NonZeroExit) {\n            QMetaEnum metaEnum = QMetaEnum::fromType<MznProcess::FailureType>();\n            QMessageBox::critical(this, \"MiniZinc IDE\", \"Unknown error while executing MiniZinc: \" + QString(metaEnum.valueToKey(e)));\n        }\n        ui->outputWidget->endExecution(0, proc->elapsedTime());\n        procFinished(exitCode, proc->elapsedTime());\n    });\n    connect(proc, &MznProcess::timeUpdated, this, &MainWindow::statusTimerEvent);\n    SolverConfiguration compileSc(sc);\n    compileSc.outputTiming = false; // Remove solns2out options\n    updateUiProcessRunning(true);\n    QStringList files({QFileInfo(model).fileName()});\n    for (auto& d : data) {\n        files << QFileInfo(d).fileName();\n    }\n    auto label = files.join(\", \").prepend(\"Compiling \");\n    if (!inlineData.isEmpty()) {\n        label.append(\" with \");\n        label.append(inlineData.join(\", \"));\n    }\n    ui->outputWidget->startExecution(label);\n    proc->start(compileSc, args, fi.canonicalPath());\n\n    if (printCommand) {\n        auto cmdMessage = QString(\"Command: %1\\n\").arg(proc->command());\n        ui->outputWidget->addText(cmdMessage, ui->outputWidget->infoCharFormat(), \"Commands\");\n        auto confMessage = QString(\"Configuration:\\n%1\").arg(QString::fromUtf8(sc.toJSON()));\n        ui->outputWidget->addText(confMessage, ui->outputWidget->infoCharFormat(), \"Commands\");\n    }\n}\n\nvoid MainWindow::run(const SolverConfiguration& sc, const QString& model, const QStringList& data, const QStringList& extraArgs, const QStringList& inlineData, QTextStream* ts)\n{\n    if (!requireMiniZinc()) {\n        return;\n    }\n\n    QFileInfo fi(model);\n    auto workingDir = fi.canonicalPath();\n\n    QSettings settings;\n    settings.beginGroup(\"ide\");\n    int compressSolutions = settings.value(\"compressSolutions\", 100).toInt();\n    bool printCommand = settings.value(\"printCommand\", false).toBool();\n    settings.endGroup();\n\n    auto* proc = new MznProcess(this);\n\n    connect(this, &MainWindow::terminating, proc, [=] () {\n        proc->terminate();\n        proc->deleteLater();\n    });\n\n    QStringList args;\n    args << model\n         << data;\n    bool compiledChecker = false;\n    QStringList files;\n    for (auto& arg : args) {\n        QFileInfo fi(arg);\n        files << fi.fileName();\n        if (fi.suffix() == \"mzc\") {\n            compiledChecker = true;\n        }\n    }\n    args << extraArgs;\n    for (auto& dzn : inlineData) {\n        args << \"-D\" << dzn;\n    }\n\n    QString label = \"Running \";\n    label.append(files.join(\", \"));\n    if (!inlineData.isEmpty()) {\n        label.append(\" with \").append(inlineData.join(\", \"));\n    }\n\n    if (sc.solverDefinition.isGUIApplication) {\n        // Detach GUI app\n        auto* failureCtx = new QObject(this);\n        ui->outputWidget->startExecution(label + \" (detached)\");\n        ui->outputWidget->addText(\"Process will continue running detached from the IDE.\\n\", ui->outputWidget->commentCharFormat());\n        connect(proc, &MznProcess::started, this, [=] () {\n            ui->outputWidget->endExecution(0, proc->elapsedTime());\n            procFinished(0);\n            delete failureCtx; // Disconnect the failure handler\n        });\n        connect(proc, &MznProcess::failure, failureCtx, [=](int exitCode, MznProcess::FailureType e) {\n            if (e == MznProcess::FailedToStart) {\n                QMessageBox::critical(this, \"MiniZinc IDE\", \"Failed to start MiniZinc. Check your path settings.\");\n                exitCode = 0;\n            } else if (e != MznProcess::NonZeroExit) {\n                QMetaEnum metaEnum = QMetaEnum::fromType<MznProcess::FailureType>();\n                QMessageBox::critical(this, \"MiniZinc IDE\", \"Unknown error while executing MiniZinc: \" + QString(metaEnum.valueToKey(e)));\n            }\n            ui->outputWidget->endExecution(exitCode, proc->elapsedTime());\n            procFinished(exitCode, proc->elapsedTime());\n        });\n        connect(proc, &MznProcess::finished, proc, &QObject::deleteLater);\n        connect(proc, &MznProcess::finished, failureCtx, &QObject::deleteLater);\n\n        proc->start(sc, args, workingDir, ts == nullptr);\n        return;\n    }\n\n    connect(ui->actionStop, &QAction::triggered, proc, [=] () {\n        ui->actionStop->setDisabled(true);\n        proc->stop();\n        ui->outputWidget->addText(\"Stopped.\\n\", ui->outputWidget->noticeCharFormat());\n    });\n    if (ts) {\n        // Write to a stream for the purposes of submission\n        // (also disables JSON streaming output)\n        connect(proc, &MznProcess::outputStdOut, [=](const QString& d) { *ts << d; });\n    }\n\n    connect(proc, &MznProcess::statisticsOutput, ui->outputWidget, &OutputWidget::addStatistics);\n    connect(proc, &MznProcess::solutionOutput, ui->outputWidget, &OutputWidget::addSolution);\n    connect(proc, &MznProcess::checkerOutput, ui->outputWidget, &OutputWidget::addCheckerOutput);\n    connect(proc, &MznProcess::errorOutput, this, &MainWindow::on_minizincError);\n    connect(proc, &MznProcess::warningOutput, this, [=] (const QJsonObject& error, bool fromChecker) {\n        if (!fromChecker || !compiledChecker) {\n            // Suppress warnings from compiled checkers\n            on_minizincError(error);\n        }\n    });\n    connect(proc, &MznProcess::finalStatus, ui->outputWidget, &OutputWidget::addStatus);\n    connect(proc, &MznProcess::unknownOutput, [=](const QString& d) { ui->outputWidget->addText(d); });\n    connect(proc, &MznProcess::commentOutput, this, [=] (const QString& d) {\n        ui->outputWidget->addText(d, ui->outputWidget->commentCharFormat(), \"Comments\");\n    });\n    connect(proc, &MznProcess::progressOutput, this, &MainWindow::on_progressOutput);\n    connect(proc, &MznProcess::traceOutput, this, [=] (const QString& section, const QVariant& message) {\n        if (section == \"trace_exp\") {\n            TextLayoutLock lock(ui->outputWidget);\n            auto obj = message.toJsonObject();\n            auto msg = obj[\"message\"].toString();\n            QRegularExpression val(\"\\\\(≡.*?\\\\)\");\n            QRegularExpressionMatchIterator val_i = val.globalMatch(msg);\n            int pos = 0;\n            auto loc = obj[\"location\"].toObject();\n            auto link = locationToLink(loc[\"filename\"].toString(),\n                    loc[\"firstLine\"].toInt(),\n                    loc[\"firstColumn\"].toInt(),\n                    loc[\"lastLine\"].toInt(),\n                    loc[\"lastColumn\"].toInt(),\n                    IDE::instance()->themeManager->current().warningColor.get(darkMode)\n                    );\n            auto prevLink = ui->outputWidget->lastTraceLoc(link);\n            if (prevLink != link) {\n                ui->outputWidget->addHtml(link, \"trace\");\n                ui->outputWidget->addText(\":\\n\", \"trace\");\n            }\n\n            ui->outputWidget->addText(\"  \", ui->outputWidget->infoCharFormat(), \"trace\");\n            while (val_i.hasNext()) {\n                auto match = val_i.next();\n                if (match.capturedStart() > 0) {\n                    ui->outputWidget->addText(msg.mid(pos, match.capturedStart() - pos),\n                                              ui->outputWidget->infoCharFormat(), \"trace\");\n                }\n                ui->outputWidget->addText(match.captured(0), ui->outputWidget->commentCharFormat(), \"trace\");\n                pos = match.capturedEnd();\n            }\n            if (pos < msg.size()) {\n                ui->outputWidget->addText(msg.mid(pos, msg.size() - pos),\n                                          ui->outputWidget->infoCharFormat(), \"trace\");\n            }\n            ui->outputWidget->addText(\"\\n\", ui->outputWidget->infoCharFormat(), \"trace\");\n        } else {\n            auto text = message.toString();\n            if (!text.isEmpty()) {\n                ui->outputWidget->addTextToSection(section, text, ui->outputWidget->commentCharFormat());\n            }\n            if (vis_connector == nullptr && section.startsWith(\"mzn_vis_\")) {\n                auto obj = message.toJsonObject();\n                startVisualisation(model, data, section, obj[\"url\"].toString(), obj[\"userData\"], proc);\n            }\n        }\n    });\n    connect(proc, &MznProcess::outputStdError, this, [=] (const QString& d) {\n        ui->outputWidget->addText(d, ui->outputWidget->commentCharFormat(), \"Standard Error\");\n    });\n    connect(proc, &MznProcess::success, [=]() {\n        ui->outputWidget->endExecution(0, proc->elapsedTime());\n        procFinished(0, proc->elapsedTime());\n    });\n    connect(proc, &MznProcess::finished, proc, &QObject::deleteLater);\n    connect(proc, &MznProcess::failure, [=](int exitCode, MznProcess::FailureType e) {\n        if (e == MznProcess::FailedToStart) {\n            QMessageBox::critical(this, \"MiniZinc IDE\", \"Failed to start MiniZinc. Check your path settings.\");\n            exitCode = 0;\n        } else if (e != MznProcess::NonZeroExit) {\n            QMetaEnum metaEnum = QMetaEnum::fromType<MznProcess::FailureType>();\n            QMessageBox::critical(this, \"MiniZinc IDE\", \"Unknown error while executing MiniZinc: \" + QString(metaEnum.valueToKey(e)));\n        }\n        ui->outputWidget->endExecution(exitCode, proc->elapsedTime());\n        procFinished(exitCode, proc->elapsedTime());\n    });\n    connect(proc, &MznProcess::timeUpdated, this, &MainWindow::statusTimerEvent);\n\n    updateUiProcessRunning(true);\n\n    vis_connector = nullptr;\n    ui->outputWidget->setSolutionLimit(compressSolutions);\n    ui->outputWidget->startExecution(label);\n\n    proc->start(sc, args, workingDir, ts == nullptr);\n\n    if (printCommand) {\n        auto cmdMessage = QString(\"Command: %1\\n\").arg(proc->command());\n        ui->outputWidget->addText(cmdMessage, ui->outputWidget->infoCharFormat(), \"Commands\");\n        auto confMessage = QString(\"Configuration:\\n%1\").arg(QString::fromUtf8(sc.toJSON()));\n        ui->outputWidget->addText(confMessage, ui->outputWidget->infoCharFormat(), \"Commands\");\n    }\n}\n\nQString MainWindow::currentSolverConfigName(void) {\n    SolverConfiguration* sc = getCurrentSolverConfig();\n    return sc ? sc->name() : \"None\";\n}\n\nvoid MainWindow::procFinished(int exitCode, qint64 time) {\n    procFinished(exitCode);\n    QString elapsedTime = setElapsedTime(time);\n    ui->statusbar->clearMessage();\n}\n\nvoid MainWindow::procFinished(int exitCode) {\n    updateUiProcessRunning(false);\n    ui->statusbar->clearMessage();\n    emit(finished());\n}\n\nvoid MainWindow::saveFile(CodeEditor* ce, const QString& f)\n{\n    QString filepath = f;\n    int tabIndex = ui->tabWidget->indexOf(ce);\n    if (filepath==\"\") {\n        if (ce != curEditor) {\n            ui->tabWidget->setCurrentIndex(tabIndex);\n        }\n        QString dialogPath = ce->filepath.isEmpty() ? getLastPath()+\"/\"+ce->filename: ce->filepath;\n        QString selectedFilter = \"Other (*)\";\n        if (dialogPath.endsWith(\".mzn\") || (ce->filepath.isEmpty() && ce->filename==\"Playground\"))\n            selectedFilter = \"MiniZinc model (*.mzn)\";\n        else if (dialogPath.endsWith(\".dzn\") || dialogPath.endsWith(\".json\"))\n            selectedFilter = \"MiniZinc data (*.dzn *.json)\";\n        else if (dialogPath.endsWith(\".fzn\"))\n            selectedFilter = \"FlatZinc (*.fzn)\";\n        else if (dialogPath.endsWith(\".mzc\"))\n            selectedFilter = \"MiniZinc solution checker (*.mzc)\";\n        filepath = QFileDialog::getSaveFileName(this,\"Save file\",dialogPath,\"MiniZinc model (*.mzn);;MiniZinc data (*.dzn *.json);;MiniZinc solution checker (*.mzc);;FlatZinc (*.fzn);;Other (*)\",&selectedFilter);\n        if (!filepath.isNull()) {\n            setLastPath(QFileInfo(filepath).absolutePath()+fileDialogSuffix);\n        }\n    }\n    if (!filepath.isEmpty()) {\n        if (filepath != ce->filepath && IDE::instance()->hasFile(filepath)) {\n            QMessageBox::warning(this,\"MiniZinc IDE\",\"Cannot overwrite open file.\",\n                                 QMessageBox::Ok);\n\n        } else {\n            IDE::instance()->fsWatch.removePath(filepath);\n            QFile file(filepath);\n            if (file.open(QFile::WriteOnly | QFile::Text)) {\n                QTextStream out(&file);\n#if QT_VERSION < 0x060000\n                out.setCodec(QTextCodec::codecForName(\"UTF-8\"));\n#endif\n                auto contents = ce->document()->toPlainText();\n                out << contents;\n                file.close();\n                if (filepath != ce->filepath) {\n                    QTextDocument* newdoc =\n                            IDE::instance()->addDocument(filepath,ce->document(),ce);\n                    ce->setDocument(newdoc);\n                    if (ce->filepath != \"\") {\n                        IDE::instance()->removeEditor(ce->filepath,ce);\n                    }\n                    getProject().remove(ce->filepath);\n                    getProject().add(filepath);\n                    ce->filepath = filepath;\n                }\n                ce->document()->setModified(false);\n                ce->filename = QFileInfo(filepath).fileName();\n                ui->tabWidget->setTabText(tabIndex,ce->filename);\n                updateRecentFiles(filepath);\n                if (ce==curEditor)\n                    tabChange(tabIndex);\n                auto* history = getProject().history();\n                if (history != nullptr) {\n                    history->updateFileContents(filepath, contents);\n                }\n            } else {\n                QMessageBox::warning(this,\"MiniZinc IDE\",\"Could not save file\");\n            }\n            IDE::instance()->fsWatch.addPath(filepath);\n        }\n    }\n}\n\nvoid MainWindow::fileRenamed(const QString& oldPath, const QString& newPath)\n{\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce && ce->filepath==oldPath) {\n            ce->filepath = newPath;\n            ce->filename = QFileInfo(newPath).fileName();\n            IDE::instance()->renameFile(oldPath,newPath);\n            ui->tabWidget->setTabText(i,ce->filename);\n            updateRecentFiles(newPath);\n            if (ce==curEditor)\n                tabChange(i);\n        }\n    }\n}\n\nvoid MainWindow::on_actionSave_triggered()\n{\n    if (curEditor) {\n        saveFile(curEditor,curEditor->filepath);\n    }\n}\n\nvoid MainWindow::on_actionSave_as_triggered()\n{\n    if (curEditor) {\n        saveFile(curEditor,QString());\n    }\n}\n\nvoid MainWindow::on_actionQuit_triggered()\n{\n    qApp->closeAllWindows();\n    if (IDE::instance()->mainWindows.size()==0) {\n        IDE::instance()->quit();\n    }\n}\n\nvoid MainWindow::openCompiledFzn(const QString& fzn)\n{\n    QFile file(fzn);\n    int fsize = file.size() / (1024 * 1024);\n    if (fsize > 10) {\n        QMessageBox::StandardButton sb =\n                QMessageBox::warning(this, \"MiniZinc IDE\",\n                                     QString(\"Compilation resulted in a large FlatZinc file (\")+\n                                     QString().setNum(fsize)+\" MB). Opening \"\n                                     \"the file may slow down the IDE and potentially \"\n                                     \"affect its stability. Do you want to open it anyway, save it, or discard the file?\",\n                                     QMessageBox::Open | QMessageBox::Discard | QMessageBox::Save);\n        switch (sb) {\n        case QMessageBox::Save:\n        {\n            bool success = true;\n            do {\n                QString savepath = QFileDialog::getSaveFileName(this,\"Save FlatZinc\",getLastPath(),\"FlatZinc files (*.fzn)\");\n                if (!savepath.isNull() && !savepath.isEmpty()) {\n                    QFile oldfile(savepath);\n                    if (oldfile.exists()) {\n                        if (!oldfile.remove()) {\n                            success = false;\n                        }\n                    }\n                    file.copy(savepath);\n                }\n            } while (!success);\n            return;\n        }\n        case QMessageBox::Discard:\n            return;\n        default:\n            break;\n        }\n    }\n    createEditor(fzn, !fzn.endsWith(\".mzc\"), !fzn.endsWith(\".mzc\"), false);\n}\n\nvoid MainWindow::profileCompiledFzn(const QVector<TimingEntry>& timing, const QVector<PathEntry>& paths)\n{\n    typedef QMap<QString,QVector<BracketData*>> CoverageMap;\n    CoverageMap ce_coverage;\n\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce == nullptr) {\n            continue;\n        }\n        auto path = ce->filepath.isEmpty() ? ce->playgroundTempFile : ce->filepath;\n        if (!path.endsWith(\".mzn\")) {\n            continue;\n        }\n        QTextBlock tb = ce->document()->begin();\n        QVector<BracketData*> coverage;\n        while (tb.isValid()) {\n            BracketData* bd = static_cast<BracketData*>(tb.userData());\n            if (bd==nullptr) {\n                bd = new BracketData;\n                tb.setUserData(bd);\n            }\n            bd->d.reset();\n            coverage.push_back(bd);\n            tb = tb.next();\n        }\n        ce_coverage.insert(path,coverage);\n    }\n\n    int totalCons=1;\n    int totalVars=1;\n    int totalTime=1;\n    for (auto& it : paths) {\n        int min_line_covered = -1;\n        CoverageMap::iterator fileMatch;\n        for (auto& segment : it.path().segments()) {\n            auto tryMatch = ce_coverage.find(segment.filename);\n            if (tryMatch != ce_coverage.end()) {\n                fileMatch = tryMatch;\n                min_line_covered = segment.firstLine;\n            }\n        }\n        if (min_line_covered > 0 && min_line_covered <= fileMatch.value().size()) {\n            if (it.constraintIndex() != -1) {\n                fileMatch.value()[min_line_covered-1]->d.con++;\n                totalCons++;\n            } else {\n                fileMatch.value()[min_line_covered-1]->d.var++;\n                totalVars++;\n            }\n        }\n    }\n    for (auto& it : timing) {\n        CoverageMap::iterator fileMatch = ce_coverage.find(it.filename());\n        if (fileMatch != ce_coverage.end()) {\n            int line_no = it.line();\n            if (line_no > 0 && line_no <= fileMatch.value().size()) {\n                fileMatch.value()[line_no-1]->d.ms = it.time();\n                totalTime += fileMatch.value()[line_no-1]->d.ms;\n            }\n        }\n    }\n\n    for (auto& coverage: ce_coverage) {\n        for(auto data: coverage){\n            data->d.totalCon = totalCons;\n            data->d.totalVar = totalVars;\n            data->d.totalMs = totalTime;\n        }\n    }\n    curEditor->repaint();\n}\n\nvoid MainWindow::on_actionCompile_triggered()\n{\n    compileOrRun(CM_COMPILE);\n}\n\nvoid MainWindow::on_actionClear_output_triggered()\n{\n    progressBar->setHidden(true);\n    ui->outputWidget->clear();\n    if (server != nullptr) {\n        server->clear();\n    }\n}\n\nvoid MainWindow::setEditorFont(QFont font)\n{\n    editorFont = font;\n    ui->outputWidget->setBrowserFont(font);\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce) {\n            ce->setEditorFont(font);\n        }\n    }\n}\n\nvoid MainWindow::setEditorIndent(int indentSize, bool useTabs)\n{\n    for (int i = 0; i < ui->tabWidget->count(); i++) {\n        auto* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce) {\n            ce->setIndentSize(indentSize);\n            ce->setIndentTab(useTabs);\n        }\n    }\n}\n\nvoid MainWindow::setEditorWordWrap(QTextOption::WrapMode mode)\n{\n\n    for (int i = 0; i < ui->tabWidget->count(); i++) {\n        auto* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce) {\n            ce->setWordWrapMode(mode);\n        }\n    }\n}\n\nvoid MainWindow::on_actionBigger_font_triggered()\n{\n    QSettings settings;\n    settings.beginGroup(\"MainWindow\");\n    auto zoom = std::min(settings.value(\"zoom\", 100).toInt() + 10, 10000);\n    settings.setValue(\"zoom\", zoom);\n    auto font = IDEUtils::fontFromString(settings.value(\"editorFont\").toString());\n    settings.endGroup();\n    font.setPointSize(font.pointSize() * zoom / 100);\n    IDE::instance()->setEditorFont(font);\n}\n\nvoid MainWindow::on_actionSmaller_font_triggered()\n{\n    QSettings settings;\n    settings.beginGroup(\"MainWindow\");\n    auto zoom = std::max(10, settings.value(\"zoom\", 100).toInt() - 10);\n    settings.setValue(\"zoom\", zoom);\n    auto font = IDEUtils::fontFromString(settings.value(\"editorFont\").toString());\n    settings.endGroup();\n    font.setPointSize(font.pointSize() * zoom / 100);\n    IDE::instance()->setEditorFont(font);\n}\n\nvoid MainWindow::on_actionDefault_font_size_triggered()\n{\n    QSettings settings;\n    settings.beginGroup(\"MainWindow\");\n    settings.setValue(\"zoom\", 100);\n    auto font = IDEUtils::fontFromString(settings.value(\"editorFont\").toString());\n    settings.endGroup();\n    font.setPointSize(font.pointSize());\n    IDE::instance()->setEditorFont(font);\n}\n\n#ifndef MINIZINC_IDE_BUILD\n#define MINIZINC_IDE_BUILD \"\"\n#endif\n\nvoid MainWindow::on_actionAbout_MiniZinc_IDE_triggered()\n{\n    QString buildString(MINIZINC_IDE_BUILD);\n    if (!buildString.isEmpty())\n        buildString += \"\\n\";\n    QMessageBox::about(this, \"The MiniZinc IDE\", QString(\"The MiniZinc IDE\\n\\nVersion \")+IDE::instance()->applicationVersion()+\"\\n\"+\n                       buildString+\"\\n\"\n                       \"Copyright Monash University, NICTA, Data61 2013-\" + BUILD_YEAR + \"\\n\\n\"+\n                       \"This program is provided under the terms of the Mozilla Public License Version 2.0. It uses the Qt toolkit, available from qt-project.org.\");\n}\n\nQVector<CodeEditor*> MainWindow::collectCodeEditors(QVector<QStringList>& locs) {\n  QVector<CodeEditor*> ces;\n  ces.resize(locs.size());\n  // Open each file in the path\n  for (int p = 0; p < locs.size(); p++) {\n    QStringList& elements = locs[p];\n\n    QString filename = elements[0];\n    QUrl url = QUrl::fromLocalFile(filename);\n    QFileInfo urlinfo(url.toLocalFile());\n\n    bool notOpen = true;\n    if (filename != \"\") {\n      for (int i=0; i<ui->tabWidget->count(); i++) {\n          CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n          if (!ce) {\n              continue;\n          }\n\n          QFileInfo ceinfo(ce->filepath);\n\n          if (ceinfo.canonicalFilePath() == urlinfo.canonicalFilePath()) {\n              ces[p] = ce;\n              if(p == locs.size()-1) {\n                  ui->tabWidget->setCurrentIndex(i);\n              }\n              notOpen = false;\n              break;\n          }\n      }\n      if (notOpen && filename.size() > 0) {\n        openFile(url.toLocalFile(), false, false);\n        CodeEditor* ce = static_cast<CodeEditor*>(ui->tabWidget->widget(ui->tabWidget->count()-1));\n\n        QFileInfo ceinfo(ce->filepath);\n\n        if (ceinfo.canonicalFilePath() == urlinfo.canonicalFilePath()) {\n          ces[p] = ce;\n        } else {\n          throw InternalError(\"Code editor file path does not match URL file path\");\n        }\n      }\n    } else {\n       CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(ui->tabWidget->currentIndex()));\n       ces[p] = ce;\n    }\n  }\n  return ces;\n}\n\nvoid MainWindow::find(bool fwd, bool forceNoWrapAround)\n{\n    const QString& toFind = ui->find->text();\n    QTextDocument::FindFlags flags;\n    if (!fwd)\n        flags |= QTextDocument::FindBackward;\n    bool ignoreCase = ui->check_case->isChecked();\n    if (!ignoreCase)\n        flags |= QTextDocument::FindCaseSensitively;\n    bool wrap = !forceNoWrapAround && ui->check_wrap->isChecked();\n\n    QTextCursor cursor(curEditor->textCursor());\n    int hasWrapped = wrap ? 0 : 1;\n    while (hasWrapped < 2) {\n        if (ui->check_re->isChecked()) {\n            QRegularExpression re(toFind, ignoreCase ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption);\n            if (!re.isValid()) {\n                ui->not_found->setText(\"invalid\");\n                return;\n            }\n            cursor = curEditor->document()->find(re,cursor,flags);\n        } else {\n            cursor = curEditor->document()->find(toFind,cursor,flags);\n        }\n        if (cursor.isNull()) {\n            hasWrapped++;\n            cursor = curEditor->textCursor();\n            if (fwd) {\n                cursor.setPosition(0);\n            } else {\n                cursor.movePosition(QTextCursor::End);\n            }\n        } else {\n            ui->not_found->setText(\"\");\n            curEditor->setTextCursor(cursor);\n            break;\n        }\n    }\n    if (hasWrapped==2) {\n        ui->not_found->setText(\"not found\");\n    }\n\n}\n\n#define major_sep ';'\n#define minor_sep '|'\nQVector<QStringList> getBlocksFromPath(QString& path) {\n  QVector<QStringList> locs;\n  QStringList blocks = path.split(major_sep);\n  foreach(QString block, blocks) {\n    QStringList elements = block.split(minor_sep);\n    if(elements.size() >= 5) {\n      bool ok = false;\n      if(elements.size() > 5) elements[5].toInt(&ok);\n      elements.erase(elements.begin()+(ok ? 6 : 5), elements.end());\n      locs.append(elements);\n    }\n  }\n  return locs;\n}\n\nvoid MainWindow::highlightPath(QString& path, int index) {\n  // Build list of blocks to be highlighted\n  QVector<QStringList> locs = getBlocksFromPath(path);\n  if(locs.size() == 0) return;\n  QVector<CodeEditor*> ces = collectCodeEditors(locs);\n  if(ces.size() != locs.size()) return;\n\n  int b = Qt::red;\n  int t = Qt::yellow;\n  QColor colour = static_cast<Qt::GlobalColor>((index % (t-b)) + b);\n\n  int strans = 25;\n  int trans = strans;\n  int tstep = (250-strans) / locs.size();\n\n  for(int p = 0; p < locs.size(); p++) {\n    QStringList& elements = locs[p];\n    CodeEditor* ce = ces[p];\n    if (!ce) {\n        continue;\n    }\n    bool ok;\n    int sl = elements[1].toInt(&ok);\n    int sc = elements[2].toInt(&ok);\n    int el = elements[3].toInt(&ok);\n    int ec = elements[4].toInt(&ok);\n    if(elements.size() == 6)\n        trans = elements[5].toInt(&ok);\n    if (ok) {\n      colour.setAlpha(trans);\n      trans = trans < 250 ? trans+tstep : strans;\n\n      Highlighter& hl = ce->getHighlighter();\n      hl.addFixedBg(sl,sc,el,ec,colour,path);\n      hl.rehighlight();\n\n      ce->setTextCursor(QTextCursor(ce->document()->findBlockByLineNumber(el)));\n    }\n  }\n}\n\nvoid MainWindow::anchorClicked(const QUrl & anUrl)\n{\n    QUrl url = anUrl;\n\n    if(url.scheme() == \"highlight\") {\n      // Reset the highlighters\n      for (int i=0; i<ui->tabWidget->count(); i++) {\n          CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n          if (ce) {\n              Highlighter& hl = ce->getHighlighter();\n              hl.clearFixedBg();\n              hl.rehighlight();\n          }\n      }\n\n      QString query = url.query();\n      QStringList conflictSet = query.split(\"&\");\n\n      for(int c = 0; c<conflictSet.size(); c++) {\n        QString& Q = conflictSet[c];\n        highlightPath(Q, c);\n      }\n      return;\n    }\n\n    if (conductor != nullptr && url.scheme() == \"cpprofiler\") {\n        bool ok;\n        auto ex_id = url.query().toInt(&ok);\n        if (ok) {\n            auto* ex = conductor->getExecution(ex_id);\n            if (ex != nullptr) {\n                showExecutionWindow(conductor->getExecutionWindow(ex));\n            }\n        }\n        return;\n    }\n\n    if(url.scheme() == \"http\" || url.scheme() == \"https\") {\n      QDesktopServices::openUrl(url);\n      return;\n    }\n\n    QString query = url.query();\n    url.setQuery(\"\");\n    url.setScheme(\"file\");\n    QFileInfo urlinfo(url.toLocalFile());\n    IDE::instance()->stats.errorsClicked++;\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (!ce) {\n            continue;\n        }\n        QFileInfo ceinfo(ce->filepath.isEmpty() ? ce->playgroundTempFile : ce->filepath);\n        if (ceinfo.canonicalFilePath() == urlinfo.canonicalFilePath()) {\n            QRegularExpression re_line(\"line=([0-9]+)\");\n            auto re_line_match = re_line.match(query);\n            if (re_line_match.hasMatch()) {\n                bool ok;\n                int line = re_line_match.captured(1).toInt(&ok);\n                if (ok) {\n                    int col = 1;\n                    QRegularExpression re_col(\"column=([0-9]+)\");\n                    auto re_col_match = re_col.match(query);\n                    if (re_col_match.hasMatch()) {\n                        bool ok;\n                        col = re_col_match.captured(1).toInt(&ok);\n                        if (!ok)\n                            col = 1;\n                    }\n                    QTextBlock block = ce->document()->findBlockByNumber(line-1);\n                    if (block.isValid()) {\n                        QTextCursor cursor = ce->textCursor();\n                        cursor.setPosition(block.position()+col-1);\n                        ce->setFocus();\n                        ce->setTextCursor(cursor);\n                        ce->centerCursor();\n                        ui->tabWidget->setCurrentIndex(i);\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid MainWindow::on_actionManage_solvers_triggered(bool addNew)\n{\n    QSettings settings;\n    settings.beginGroup(\"ide\");\n    bool checkUpdates = settings.value(\"checkforupdates21\",false).toBool();\n    settings.endGroup();\n\n    ui->config_window->stashModifiedConfigs();\n\n    PreferencesDialog pd(addNew, this);\n    pd.exec();\n\n    checkDriver();\n\n    ui->config_window->loadConfigs();\n    ui->config_window->unstashModifiedConfigs();\n\n    settings.beginGroup(\"ide\");\n    if (!checkUpdates && settings.value(\"checkforupdates21\",false).toBool()) {\n        settings.setValue(\"lastCheck21\",QDate::currentDate().addDays(-2).toString());\n        IDE::instance()->checkUpdate();\n    }\n\n    indentSize = settings.value(\"indentSize\", 2).toInt();\n    useTabs = settings.value(\"indentTabs\", false).toBool();\n    IDE::instance()->setEditorIndent(indentSize, useTabs);\n    bool wordWrap = settings.value(\"wordWrap\", true).toBool();\n    IDE::instance()->setEditorWordWrap(wordWrap ?\n                                           QTextOption::WrapAtWordBoundaryOrAnywhere :\n                                           QTextOption::NoWrap);\n\n    settings.endGroup();\n\n    settings.beginGroup(\"MainWindow\");\n    editorFont = IDEUtils::fontFromString(settings.value(\"editorFont\").toString());\n    auto zoom = settings.value(\"zoom\", 100).toInt();\n    editorFont.setPointSize(editorFont.pointSize() * zoom / 100);\n    IDE::instance()->setEditorFont(editorFont);\n    settings.endGroup();\n\n    settings.beginGroup(\"minizinc\");\n    settings.setValue(\"mznpath\", MznDriver::get().mznDistribPath());\n    settings.endGroup();\n}\n\nvoid MainWindow::on_actionFind_triggered()\n{\n    incrementalFindCursor = curEditor->textCursor();\n    incrementalFindCursor.setPosition(std::min(curEditor->textCursor().anchor(), curEditor->textCursor().position()));\n    if (curEditor->textCursor().hasSelection()) {\n        ui->find->setText(curEditor->textCursor().selectedText());\n    }\n    ui->not_found->setText(\"\");\n    ui->find->selectAll();\n    ui->findFrame->raise();\n    ui->findFrame->show();\n    ui->find->setFocus();\n}\n\nvoid MainWindow::on_actionReplace_triggered()\n{\n    incrementalFindCursor = curEditor->textCursor();\n    incrementalFindCursor.setPosition(std::min(curEditor->textCursor().anchor(), curEditor->textCursor().position()));\n    if (curEditor->textCursor().hasSelection()) {\n        ui->find->setText(curEditor->textCursor().selectedText());\n    }\n    ui->not_found->setText(\"\");\n    ui->find->selectAll();\n    ui->findFrame->raise();\n    ui->findFrame->show();\n}\n\nvoid MainWindow::on_actionGo_to_line_triggered()\n{\n    if (curEditor==nullptr)\n        return;\n    GoToLineDialog gtl;\n    if (gtl.exec()==QDialog::Accepted) {\n        bool ok;\n        int line = gtl.getLine(&ok);\n        if (ok) {\n            QTextBlock block = curEditor->document()->findBlockByNumber(line-1);\n            if (block.isValid()) {\n                QTextCursor cursor = curEditor->textCursor();\n                cursor.setPosition(block.position());\n                curEditor->setTextCursor(cursor);\n            }\n        }\n    }\n}\n\nvoid MainWindow::checkDriver() {\n    auto& driver = MznDriver::get();\n    bool haveMzn = (driver.isValid() && driver.solvers().size() > 0);\n    ui->actionRun->setEnabled(haveMzn);\n    ui->actionProfile_compilation->setEnabled(haveMzn);\n    ui->actionCompile->setEnabled(haveMzn);\n    ui->actionEditSolverConfig->setEnabled(haveMzn);\n    ui->actionSubmit_to_MOOC->setEnabled(haveMzn);\n    if (!haveMzn)\n        ui->configWindow_dockWidget->hide();\n}\n\nvoid MainWindow::on_actionShift_left_triggered()\n{\n    if (curEditor != nullptr) {\n        curEditor->shiftLeft();\n    }\n}\n\nvoid MainWindow::on_actionShift_right_triggered()\n{\n    if (curEditor != nullptr) {\n        curEditor->shiftRight();\n    }\n}\n\nvoid MainWindow::on_actionHelp_triggered()\n{\n    IDE::instance()->help();\n}\n\nvoid MainWindow::on_actionNew_project_triggered()\n{\n    MainWindow* mw = new MainWindow;\n    QPoint p = pos();\n    mw->move(p.x()+20, p.y()+20);\n    mw->show();\n}\n\nbool MainWindow::isEmptyProject(void)\n{\n    if (getProject().hasProjectFile() || !getProject().files().empty()) {\n        return false;\n    }\n    for (int i = 0; i < ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce && (!ce->filepath.isEmpty() || ce->document()->isModified())) {\n            return false;\n        }\n    }\n    return true;\n}\n\nvoid MainWindow::openProject(const QString& fileName)\n{\n    if (!fileName.isEmpty()) {\n        IDE::PMap& pmap = IDE::instance()->projects;\n        IDE::PMap::iterator it = pmap.find(fileName);\n        if (it==pmap.end()) {\n            if (isEmptyProject()) {\n                int closeTab = ui->tabWidget->count()==1 ? 0 : -1;\n                loadProject(fileName);\n                if (closeTab > 0 && ui->tabWidget->count()>1) {\n                    CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(closeTab));\n                    if (ce && ce->filepath == \"\")\n                        tabCloseRequest(closeTab);\n                }\n            } else {\n                MainWindow* mw = new MainWindow(fileName);\n                QPoint p = pos();\n                mw->move(p.x()+20, p.y()+20);\n                mw->show();\n            }\n        } else {\n            it.value()->raise();\n            it.value()->activateWindow();\n        }\n    }\n}\n\nvoid MainWindow::updateRecentProjects(const QString& p) {\n    if (!p.isEmpty()) {\n        IDE::instance()->addRecentProject(p);\n    }\n    ui->menuRecent_Projects->clear();\n    for (int i=0; i<IDE::instance()->recentProjects.size(); i++) {\n        QAction* na = ui->menuRecent_Projects->addAction(IDE::instance()->recentProjects[i]);\n        na->setData(IDE::instance()->recentProjects[i]);\n    }\n    ui->menuRecent_Projects->addSeparator();\n    ui->menuRecent_Projects->addAction(\"Clear Menu\");\n}\nvoid MainWindow::updateRecentFiles(const QString& p) {\n    if (!p.isEmpty()) {\n        IDE::instance()->addRecentFile(p);\n    }\n    ui->menuRecent_Files->clear();\n    for (int i=0; i<IDE::instance()->recentFiles.size(); i++) {\n        QAction* na = ui->menuRecent_Files->addAction(IDE::instance()->recentFiles[i]);\n        na->setData(IDE::instance()->recentFiles[i]);\n    }\n    ui->menuRecent_Files->addSeparator();\n    ui->menuRecent_Files->addAction(\"Clear Menu\");\n}\n\nvoid MainWindow::recentFileMenuAction(QAction* a) {\n    if (a->text()==\"Clear Menu\") {\n        IDE::instance()->recentFiles.clear();\n        updateRecentFiles(\"\");\n    } else {\n        openFile(a->data().toString());\n    }\n}\n\nvoid MainWindow::recentProjectMenuAction(QAction* a) {\n    if (a->text()==\"Clear Menu\") {\n        IDE::instance()->recentProjects.clear();\n        updateRecentProjects(\"\");\n    } else {\n        openProject(a->data().toString());\n    }\n}\n\nvoid MainWindow::saveProject(const QString& f)\n{\n    QString filepath = f;\n    if (filepath.isEmpty()) {\n        filepath = QFileDialog::getSaveFileName(this, \"Save project\", getLastPath(), \"MiniZinc projects (*.mzp)\");\n        if (!filepath.isNull()) {\n            setLastPath(QFileInfo(filepath).absolutePath() + fileDialogSuffix);\n        }\n    }\n    if (filepath.isEmpty()) {\n        return;\n    }\n\n    auto& p = getProject();\n    if (p.projectFile() != filepath && IDE::instance()->projects.contains(filepath)) {\n        QMessageBox::warning(this,\"MiniZinc IDE\",\"Cannot overwrite existing open project.\",\n                             QMessageBox::Ok);\n        return;\n    }\n\n    if (p.projectFile() != filepath) {\n        IDE::instance()->projects.remove(p.projectFile());\n        IDE::instance()->projects.insert(filepath, this);\n        p.projectFile(filepath);\n    }\n    p.openTabsChanged(getOpenFiles(), ui->tabWidget->currentIndex());\n    p.activeSolverConfigChanged(getCurrentSolverConfig());\n    try {\n        p.saveProject();\n    } catch (FileError& f) {\n        QMessageBox::critical(this, \"MiniZinc IDE\", f.message(), QMessageBox::Ok);\n    }\n\n    updateRecentProjects(p.projectFile());\n}\n\nvoid MainWindow::loadProject(const QString& filepath)\n{\n    try {\n        auto& p = getProject();\n        auto warnings = p.loadProject(filepath, ui->config_window);\n        if (ui->projectExplorerDockWidget->isHidden()) {\n            on_actionShow_project_explorer_triggered();\n        }\n        if (!warnings.empty()) {\n            QMessageBox::warning(this, \"MiniZinc IDE\",\n                                 warnings.join(\"\\n\"),\n                                 QMessageBox::Ok);\n        }\n\n        int openTab = p.openTab();\n        auto openFiles = p.openFiles();\n        for (int i = 0; i < openFiles.count(); i++) {\n            openFile(openFiles[i], false, i == openTab);\n        }\n        p.activeSolverConfigChanged(getCurrentSolverConfig());\n        p.setModified(false);\n        IDE::instance()->projects.insert(p.projectFile(), this);\n        updateRecentProjects(p.projectFile());\n    } catch (Exception& e) {\n        QMessageBox::warning(this, \"MiniZinc IDE\",\n                             e.message(),\n                             QMessageBox::Ok);\n    }\n}\n\nvoid MainWindow::on_actionSave_project_triggered()\n{\n    saveProject(getProject().projectFile());\n}\n\nvoid MainWindow::on_actionSave_project_as_triggered()\n{\n    saveProject(QString());\n}\n\nvoid MainWindow::on_actionClose_project_triggered()\n{\n    close();\n}\n\nvoid MainWindow::on_actionFind_next_triggered()\n{\n    on_b_next_clicked();\n}\n\nvoid MainWindow::on_actionFind_previous_triggered()\n{\n    on_b_prev_clicked();\n}\n\nvoid MainWindow::on_actionSave_all_triggered()\n{\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce && ce->document()->isModified())\n            saveFile(ce,ce->filepath);\n    }\n}\n\nvoid MainWindow::on_action_Un_comment_triggered()\n{\n    if (curEditor==nullptr)\n        return;\n    QTextCursor cursor = curEditor->textCursor();\n    QTextBlock beginBlock = curEditor->document()->findBlock(cursor.anchor());\n    QTextBlock endblock = curEditor->document()->findBlock(cursor.position());\n    if (beginBlock.blockNumber() > endblock.blockNumber())\n        std::swap(beginBlock,endblock);\n    endblock = endblock.next();\n\n    QRegularExpression comment(\"^(\\\\s*%|\\\\s*$)\");\n    QRegularExpression comSpace(\"%\\\\s\");\n    QRegularExpression emptyLine(\"^\\\\s*$\");\n\n    QTextBlock block = beginBlock;\n    bool isCommented = true;\n    do {\n        if (!comment.match(block.text()).hasMatch()) {\n            isCommented = false;\n            break;\n        }\n        block = block.next();\n    } while (block.isValid() && block != endblock);\n\n    block = beginBlock;\n    cursor.beginEditBlock();\n    do {\n        cursor.setPosition(block.position());\n        QString t = block.text();\n        if (isCommented) {\n            int cpos = t.indexOf(\"%\");\n            if (cpos != -1) {\n                cursor.setPosition(block.position()+cpos);\n                bool haveSpace = (comSpace.match(t,cpos).capturedStart() == cpos);\n                cursor.movePosition(QTextCursor::Right,QTextCursor::KeepAnchor,haveSpace ? 2:1);\n                cursor.removeSelectedText();\n            }\n\n        } else {\n            if (!emptyLine.match(t).hasMatch())\n                cursor.insertText(\"% \");\n        }\n        block = block.next();\n    } while (block.isValid() && block != endblock);\n    cursor.endEditBlock();\n}\n\nvoid MainWindow::on_actionOnly_editor_triggered()\n{\n    if (!ui->outputDockWidget->isFloating())\n        ui->outputDockWidget->hide();\n}\n\nvoid MainWindow::on_actionSplit_triggered()\n{\n    if (!ui->outputDockWidget->isFloating())\n        ui->outputDockWidget->show();\n}\n\nvoid MainWindow::on_actionPrevious_tab_triggered()\n{\n    if (ui->tabWidget->currentIndex() > 0) {\n        ui->tabWidget->setCurrentIndex(ui->tabWidget->currentIndex()-1);\n    } else {\n        ui->tabWidget->setCurrentIndex(ui->tabWidget->count()-1);\n    }\n}\n\nvoid MainWindow::on_actionNext_tab_triggered()\n{\n    if (ui->tabWidget->currentIndex() < ui->tabWidget->count()-1) {\n        ui->tabWidget->setCurrentIndex(ui->tabWidget->currentIndex()+1);\n    } else {\n        ui->tabWidget->setCurrentIndex(0);\n    }\n}\n\nvoid MainWindow::on_actionHide_tool_bar_triggered()\n{\n    if (ui->toolBar->isHidden()) {\n        ui->toolBar->show();\n        ui->actionHide_tool_bar->setText(\"Hide tool bar\");\n    } else {\n        ui->toolBar->hide();\n        ui->actionHide_tool_bar->setText(\"Show tool bar\");\n    }\n}\n\nvoid MainWindow::on_actionToggle_profiler_info_triggered()\n{\n    profileInfoVisible = !profileInfoVisible;\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce) {\n            ce->showDebugInfo(profileInfoVisible);\n        }\n    }\n}\n\nvoid MainWindow::on_actionShow_project_explorer_triggered()\n{\n    if (ui->projectExplorerDockWidget->isHidden()) {\n        ui->projectExplorerDockWidget->show();\n        ui->actionShow_project_explorer->setText(\"Hide project explorer\");\n    } else {\n        ui->projectExplorerDockWidget->hide();\n        ui->actionShow_project_explorer->setText(\"Show project explorer\");\n    }\n}\n\nvoid MainWindow::onClipboardChanged()\n{\n    ui->actionPaste->setEnabled(!QApplication::clipboard()->text().isEmpty());\n}\n\nvoid MainWindow::editor_cursor_position_changed()\n{\n    if (curEditor) {\n        statusLineColLabel->setText(QString(\"Line: \")+QString().number(curEditor->textCursor().blockNumber()+1)+\", Col: \"+QString().number(curEditor->textCursor().columnNumber()+1));\n    }\n}\n\nvoid MainWindow::on_actionSubmit_to_MOOC_triggered()\n{\n    // Check if any documents need saving\n    QVector<CodeEditor*> modifiedDocs;\n    QSet<QString> files;\n    for (auto model : getProject().moocAssignment().models) {\n        files.insert(model.model);\n        files.insert(model.data);\n    }\n    for (auto problem : getProject().moocAssignment().problems) {\n        files.insert(problem.model);\n        files.insert(problem.data);\n    }\n    for (int i = 0; i < ui->tabWidget->count(); i++) {\n        auto ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce && ce->document()->isModified() && files.contains(ce->filepath)) {\n            modifiedDocs.append(ce);\n        }\n    }\n    if (!modifiedDocs.empty()) {\n        if (!saveBeforeRunning) {\n            QMessageBox msgBox;\n            if (modifiedDocs.size()==1) {\n                msgBox.setText(\"One of the files has been modified. You must save it before submitting.\");\n            } else {\n                msgBox.setText(\"Several files have been modified. You must save them before submitting.\");\n            }\n            msgBox.setInformativeText(\"Do you want to save now and then submit?\");\n            QAbstractButton *saveButton = msgBox.addButton(QMessageBox::Save);\n            msgBox.addButton(QMessageBox::Cancel);\n            QAbstractButton *alwaysButton = msgBox.addButton(\"Always save\", QMessageBox::AcceptRole);\n            msgBox.setDefaultButton(QMessageBox::Save);\n            msgBox.exec();\n            if (msgBox.clickedButton() == alwaysButton) {\n                saveBeforeRunning = true;\n            }\n            else if (msgBox.clickedButton() != saveButton) {\n                return;\n            }\n        }\n        for (auto ce : modifiedDocs) {\n            saveFile(ce,ce->filepath);\n            if (ce->document()->isModified()) {\n                return;\n            }\n        }\n    }\n\n    moocSubmission = new MOOCSubmission(this, getProject().moocAssignment());\n    connect(moocSubmission, &MOOCSubmission::finished, this, &MainWindow::moocFinished);\n    setEnabled(false);\n    moocSubmission->show();\n}\n\nvoid MainWindow::moocFinished(int) {\n    moocSubmission->deleteLater();\n    setEnabled(true);\n}\n\nbool MainWindow::eventFilter(QObject *obj, QEvent *ev)\n{\n    if (obj == ui->findWidget) {\n        if (ev->type() == QEvent::KeyPress) {\n            auto* keyEvent = static_cast<QKeyEvent*>(ev);\n            if (keyEvent->key() == Qt::Key_Escape) {\n                on_closeFindWidget_clicked();\n                return true;\n            }\n        }\n    }\n    return QMainWindow::eventFilter(obj,ev);\n}\n\nvoid MainWindow::on_actionCheat_Sheet_triggered()\n{\n    IDE::instance()->cheatSheet->show();\n    IDE::instance()->cheatSheet->raise();\n    IDE::instance()->cheatSheet->activateWindow();\n}\n\nvoid MainWindow::check_code()\n{\n    auto sc = getCurrentSolverConfig();\n    if (!MznDriver::get().isValid() || !ui->actionRun->isEnabled() ||\n            !curEditor || (!curEditor->filepath.isEmpty() && !curEditor->filepath.endsWith(\".mzn\")) ||\n            !sc) {\n        return;\n    }\n    auto contents = curEditor->document()->toPlainText();\n    auto wd = QFileInfo(curEditor->filepath).absolutePath();\n    code_checker->start(contents, *sc, wd);\n}\n\nvoid MainWindow::setTheme(const Theme& theme, bool dark)\n{\n    darkMode = dark;\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce) {\n            ce->setTheme(theme, darkMode);\n        }\n    }\n    ui->outputWidget->setTheme(theme, darkMode);\n    if (conductor != nullptr) {\n        conductor->setDarkMode(darkMode);\n    }\n}\n\nvoid MainWindow::initTheme()\n{\n    auto* tm = IDE::instance()->themeManager;\n    auto* dm = IDE::instance()->darkModeNotifier;\n    setTheme(tm->current(), dm->darkMode());\n}\n\nvoid MainWindow::on_actionEditSolverConfig_triggered()\n{\n    if (ui->configWindow_dockWidget->isHidden()) {\n        ui->configWindow_dockWidget->show();\n    } else {\n        ui->configWindow_dockWidget->hide();\n    }\n}\n\nvoid MainWindow::on_b_next_clicked()\n{\n    find(true);\n    incrementalFindCursor.setPosition(std::min(curEditor->textCursor().anchor(), curEditor->textCursor().position()));\n}\n\nvoid MainWindow::on_b_prev_clicked()\n{\n    find(false);\n    incrementalFindCursor.setPosition(std::min(curEditor->textCursor().anchor(), curEditor->textCursor().position()));\n}\n\nvoid MainWindow::on_b_replacefind_clicked()\n{\n    on_b_replace_clicked();\n    find(true);\n    incrementalFindCursor.setPosition(std::min(curEditor->textCursor().anchor(), curEditor->textCursor().position()));\n}\n\nvoid MainWindow::on_b_replace_clicked()\n{\n    QTextCursor cursor = curEditor->textCursor();\n    if (cursor.hasSelection()) {\n        cursor.insertText(ui->replace->text());\n    }\n}\n\nvoid MainWindow::on_b_replaceall_clicked()\n{\n    int counter = 0;\n    QTextCursor cursor = curEditor->textCursor();\n    QTextCursor origCursor = curEditor->textCursor();\n    cursor.movePosition(QTextCursor::Start);\n    curEditor->setTextCursor(cursor);\n    if (!cursor.hasSelection()) {\n        find(true,true);\n        cursor = curEditor->textCursor();\n    }\n    cursor.beginEditBlock();\n    while (cursor.hasSelection()) {\n        counter++;\n        cursor.insertText(ui->replace->text());\n        find(true,true);\n        cursor = curEditor->textCursor();\n    }\n    cursor.endEditBlock();\n    if (counter > 0) {\n        ui->not_found->setText(QString().number(counter)+\" replaced\");\n    } else {\n        curEditor->setTextCursor(origCursor);\n    }\n    incrementalFindCursor.setPosition(std::min(curEditor->textCursor().anchor(), curEditor->textCursor().position()));\n}\n\nvoid MainWindow::on_closeFindWidget_clicked()\n{\n    closeFindWidget();\n}\n\nvoid MainWindow::closeFindWidget()\n{\n    ui->findFrame->hide();\n    curEditor->setFocus();\n}\n\nvoid MainWindow::on_find_textEdited(const QString &)\n{\n    curEditor->setTextCursor(incrementalFindCursor);\n    find(true);\n}\n\nvoid MainWindow::on_actionProfile_compilation_triggered()\n{\n    compileOrRun(CM_PROFILE);\n    if (!profileInfoVisible) {\n        on_actionToggle_profiler_info_triggered();\n    }\n}\n\nvoid MainWindow::setEditorMenuItemsEnabled(bool enabled) {\n    // These items only need to be enabled when there is an editor tab open\n    ui->actionClose->setEnabled(enabled);\n    ui->menuFind->setEnabled(enabled);\n    ui->actionFind->setEnabled(enabled);\n    ui->actionReplace->setEnabled(enabled);\n    ui->actionFind_next->setEnabled(enabled);\n    ui->actionFind_previous->setEnabled(enabled);\n    ui->actionShift_left->setEnabled(enabled);\n    ui->actionShift_right->setEnabled(enabled);\n    ui->actionCut->setEnabled(enabled);\n    ui->actionCopy->setEnabled(enabled);\n    ui->actionPaste->setEnabled(enabled);\n    ui->actionSelect_All->setEnabled(enabled);\n    ui->actionGo_to_line->setEnabled(enabled);\n    ui->action_Un_comment->setEnabled(enabled);\n    ui->actionSave->setEnabled(enabled);\n    ui->actionSave_as->setEnabled(enabled);\n    ui->actionSave_all->setEnabled(enabled);\n    ui->actionUndo->setEnabled(enabled);\n    ui->actionRedo->setEnabled(enabled);\n    ui->actionRun->setEnabled(enabled);\n    ui->actionCompile->setEnabled(enabled);\n    ui->actionProfile_compilation->setEnabled(enabled);\n    ui->actionProfile_search->setEnabled(enabled);\n}\n\nvoid MainWindow::on_configWindow_dockWidget_visibilityChanged(bool visible)\n{\n    if (visible) {\n        ui->actionEditSolverConfig->setText(\"Hide configuration editor...\");\n    } else {\n        ui->actionEditSolverConfig->setText(\"Show configuration editor...\");\n    }\n}\n\nvoid MainWindow::on_config_window_itemsChanged(const QStringList& items)\n{\n    disconnect(solverConfCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), ui->config_window, &ConfigWindow::setCurrentIndex);\n    solverConfCombo->clear();\n    solverConfCombo->addItems(items);\n    solverConfCombo->setCurrentIndex(ui->config_window->currentIndex());\n    connect(solverConfCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), ui->config_window, &ConfigWindow::setCurrentIndex);\n\n    ui->menuSolver_configurations->clear();\n    for (auto& item: items) {\n        auto action = ui->menuSolver_configurations->addAction(item);\n        action->setCheckable(true);\n        action->setChecked(solverConfCombo->currentText() == item);\n    }\n    ui->menuSolver_configurations->addSeparator();\n    ui->menuSolver_configurations->addAction(ui->actionEditSolverConfig);\n\n    bool canSaveAll = false;\n    for (auto sc : ui->config_window->solverConfigs()) {\n        if (!sc->isBuiltin && sc->modified) {\n            if (sc->paramFile.isEmpty()) {\n                canSaveAll = false;\n                break;\n            } else {\n                canSaveAll = true;\n            }\n        }\n    }\n    ui->actionSave_all_solver_configurations->setEnabled(canSaveAll);\n}\n\nvoid MainWindow::on_config_window_selectedIndexChanged(int index)\n{\n    solverConfCombo->setCurrentIndex(index);\n\n    for (auto action : ui->menuSolver_configurations->actions()) {\n        action->setChecked(solverConfCombo->currentText() == action->text());\n    }\n\n    auto sc = getCurrentSolverConfig();\n    ui->actionSave_solver_configuration->setDisabled(!sc);\n\n    updateProfileSearchButton();\n}\n\nvoid MainWindow::on_menuSolver_configurations_triggered(QAction* action)\n{\n    if (action == ui->actionEditSolverConfig) {\n        return;\n    }\n\n    auto actions = ui->menuSolver_configurations->actions();\n    ui->config_window->setCurrentIndex(actions.indexOf(action));\n}\n\nSolverConfiguration* MainWindow::getCurrentSolverConfig()\n{\n    return ui->config_window->currentSolverConfig();\n}\n\nconst Solver* MainWindow::getCurrentSolver()\n{\n    auto sc = getCurrentSolverConfig();\n    if (!sc) {\n        return nullptr;\n    }\n    return &sc->solverDefinition;\n}\n\nbool MainWindow::requireMiniZinc()\n{\n    if (!MznDriver::get().isValid()) {\n        int ret = QMessageBox::warning(this,\n                                       \"MiniZinc IDE\",\n                                       \"Could not find the minizinc executable.\\nDo you want to open the solver settings dialog?\",\n                                       QMessageBox::Ok | QMessageBox::Cancel);\n        if (ret == QMessageBox::Ok) {\n            on_actionManage_solvers_triggered();\n        }\n        return false;\n    }\n    return true;\n}\n\nvoid MainWindow::on_moocChanged(const MOOCAssignment* mooc)\n{\n    if (mooc) {\n        ui->actionSubmit_to_MOOC->setVisible(true);\n        ui->actionSubmit_to_MOOC->setText(\"Submit to \" + mooc->moocName);\n        QIcon icon = mooc->moocName == \"Coursera\" ?\n                    QIcon(\":/icons/images/coursera.png\") :\n                    QIcon(\":/icons/images/application-certificate.png\");\n        ui->actionSubmit_to_MOOC->setIcon(icon);\n    } else {\n        ui->actionSubmit_to_MOOC->setVisible(false);\n    }\n}\n\nQStringList MainWindow::getOpenFiles()\n{\n    QStringList ret;\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce) {\n            ret << ce->filepath;\n        } else {\n            ret << \"\";\n        }\n    }\n    return ret;\n}\n\nQList<CodeEditor*> MainWindow::codeEditors()\n{\n    QList<CodeEditor*> ret;\n    for (int i=0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce) {\n            ret << ce;\n        }\n    }\n    return ret;\n}\n\nvoid MainWindow::on_projectBrowser_runRequested(const QStringList& files)\n{\n    QString model;\n    QStringList data;\n    QString checker;\n    auto sc = getCurrentSolverConfig();\n    for (auto& f : files) {\n        if (f.endsWith(\".mpc\")) {\n            int i = ui->config_window->findConfigFile(f);\n            if (i != -1) {\n                sc = ui->config_window->solverConfigs()[i];\n            }\n        } else if (f.endsWith(\".mzc.mzn\") || f.endsWith(\".mzc\")) {\n            checker = f;\n        } else if (model.isEmpty() && f.endsWith(\".mzn\")) {\n            model = f;\n        } else {\n            data << f;\n        }\n    }\n\n    compileOrRun(CM_RUN, sc, model, data, checker);\n}\n\nvoid MainWindow::on_projectBrowser_openRequested(const QStringList& files)\n{\n    QStringList openFiles = getOpenFiles();\n    for (auto& f : files) {\n        if (f.endsWith(\".mpc\")) {\n            int i = 0;\n            for (auto sc : ui->config_window->solverConfigs()) {\n                if (sc->paramFile == f) {\n                    ui->config_window->setCurrentIndex(i);\n                    break;\n                }\n                i++;\n            }\n            continue;\n        }\n\n        int index = openFiles.indexOf(f);\n        if (index == -1) {\n            createEditor(f, false, false, false, true);\n        } else {\n            ui->tabWidget->setCurrentIndex(index);\n        }\n    }\n}\n\nvoid MainWindow::on_projectBrowser_removeRequested(const QStringList& files)\n{\n    bool modified = false;\n    QList<CodeEditor*> editors;\n    for (int i = 0; i<ui->tabWidget->count(); i++) {\n        CodeEditor* ce = qobject_cast<CodeEditor*>(ui->tabWidget->widget(i));\n        if (ce && files.contains(QFileInfo(ce->filepath).absoluteFilePath())) {\n            editors << ce;\n            if (ce->document()->isModified()) {\n                modified = true;\n            }\n        }\n    }\n\n    QVector<int> scIndexes;\n    for (auto& file : files) {\n        if (file.endsWith(\".mpc\")) {\n            int i = ui->config_window->findConfigFile(file);\n            scIndexes << i;\n            if (ui->config_window->solverConfigs()[i]->modified) {\n                modified = true;\n            }\n        }\n    }\n    std::sort(scIndexes.begin(), scIndexes.end(), std::greater<int>());\n\n    if (modified) {\n        int ret = QMessageBox::warning(this, \"MiniZinc IDE\",\n                                       \"There are modified documents.\\nDo you want to discard the changes or cancel?\",\n                                       QMessageBox::Discard | QMessageBox::Cancel);\n        if (ret == QMessageBox::Cancel) {\n            return;\n        }\n    } else {\n        int ret = QMessageBox::warning(this, \"MiniZinc IDE\",\n                                       \"Are you sure you wish to remove the selected file(s) from the project?\",\n                                       QMessageBox::Ok | QMessageBox::Cancel);\n        if (ret == QMessageBox::Cancel) {\n            return;\n        }\n    }\n\n    for (auto ce : editors) {\n        ce->document()->setModified(false);\n        ui->tabWidget->removeTab(ui->tabWidget->indexOf(ce));\n        IDE::instance()->removeEditor(ce->filepath,ce);\n        delete ce;\n    }\n\n    for (auto i : scIndexes) {\n        ui->config_window->removeConfig(i);\n    }\n\n    getProject().remove(files);\n    getProject().openTabsChanged(getOpenFiles(), ui->tabWidget->currentIndex());\n}\n\nvoid MainWindow::on_actionSave_solver_configuration_triggered()\n{\n    ui->config_window->saveConfig();\n}\n\nvoid MainWindow::on_actionSave_all_solver_configurations_triggered()\n{\n    auto& configs = ui->config_window->solverConfigs();\n    for (int i = 0; i < configs.count(); i++) {\n        if (!configs[i]->isBuiltin && configs[i]->modified) {\n            ui->config_window->saveConfig(i);\n        }\n    }\n}\n\nvoid MainWindow::stop()\n{\n    ui->actionStop->trigger();\n}\n\nvoid MainWindow::on_actionShow_search_profiler_triggered()\n{\n    if (!conductor) {\n        auto profiler_layout = new QGridLayout(this);\n        ui->cpprofiler->setLayout(profiler_layout);\n\n        cpprofiler::Options opts;\n        conductor = new cpprofiler::Conductor(opts, this);\n        conductor->setDarkMode(darkMode);\n        conductor->setWindowFlags(Qt::Widget);\n        connect(conductor, &cpprofiler::Conductor::executionStart, [=] (cpprofiler::Execution* e) {\n            ui->outputWidget->associateProfilerExecution(e->id());\n        });\n        connect(conductor, &cpprofiler::Conductor::showExecutionWindow, this, &MainWindow::showExecutionWindow);\n        connect(conductor, &cpprofiler::Conductor::showMergeWindow, this, &MainWindow::showMergeWindow);\n        profiler_layout->addWidget(conductor);\n    }\n    if (ui->cpprofiler_dockWidget->isVisible()) {\n        ui->cpprofiler_dockWidget->hide();\n    } else {\n        ui->cpprofiler_dockWidget->show();\n    }\n}\n\nvoid MainWindow::on_actionProfile_search_triggered()\n{\n    if (!ui->cpprofiler_dockWidget->isVisible()) {\n        on_actionShow_search_profiler_triggered();\n    }\n    QStringList args;\n    args << \"--cp-profiler\"\n         << QString::number(conductor->getNextExecId()) + \",\" + QString::number(conductor->getListenPort());\n    compileOrRun(CM_RUN, nullptr, QString(), QStringList(), QString(), args);\n}\n\nvoid MainWindow::showExecutionWindow(cpprofiler::ExecutionWindow& e)\n{\n    e.setWindowFlags(Qt::Widget);\n    e.setParent(this);\n    for (int i = 0; i < ui->tabWidget->count(); i++) {\n        if (ui->tabWidget->widget(i) == &e) {\n            ui->tabWidget->setCurrentIndex(i);\n            return;\n        }\n    }\n    auto name = QString::fromStdString(e.execution().name());\n    ui->tabWidget->addTab(&e, name);\n    ui->tabWidget->setCurrentIndex(ui->tabWidget->count()-1);\n}\n\nvoid MainWindow::showMergeWindow(cpprofiler::analysis::MergeWindow& m)\n{\n    m.setWindowFlags(Qt::Widget);\n    m.setParent(this);\n    for (int i = 0; i < ui->tabWidget->count(); i++) {\n        if (ui->tabWidget->widget(i) == &m) {\n            ui->tabWidget->setCurrentIndex(i);\n            return;\n        }\n    }\n    auto name = QString::fromStdString(\"Merge result\");\n    ui->tabWidget->addTab(&m, name);\n    ui->tabWidget->setCurrentIndex(ui->tabWidget->count()-1);\n}\n\nvoid MainWindow::on_cpprofiler_dockWidget_visibilityChanged(bool visible)\n{\n    if (visible) {\n        ui->actionShow_search_profiler->setText(\"Hide search profiler\");\n    } else {\n        ui->actionShow_search_profiler->setText(\"Show search profiler\");\n    }\n}\n\nvoid MainWindow::updateProfileSearchButton()\n{\n    auto sc = getCurrentSolverConfig();\n    ui->actionProfile_search->setDisabled(processRunning ||\n                                          !curEditor ||\n                                          !sc ||\n                                          !sc->solverDefinition.stdFlags.contains(\"--cp-profiler\"));\n}\n\nvoid MainWindow::on_progressOutput(float progress)\n{\n    progressBar->setHidden(false);\n    progressBar->setValue(static_cast<int>(progress));\n}\n\nvoid MainWindow::on_minizincError(const QJsonObject& error) {\n    bool isError = error[\"type\"].toString() == \"error\";\n    auto& currentTheme = IDE::instance()->themeManager->current();\n    auto color = isError ? currentTheme.errorColor.get(darkMode)\n                         : currentTheme.warningColor.get(darkMode);\n    QString messageType = isError ? \"Errors\" : \"Warnings\";\n\n    auto stack = error[\"stack\"].toArray();\n    if (!stack.empty()) {\n        QString lastFile = \"\";\n        int lastLine = -1;\n        for (auto it : stack) {\n            auto entry = it.toObject();\n            auto loc = entry[\"location\"].toObject();\n            if (loc[\"filename\"].toString() != lastFile || loc[\"firstLine\"].toInt() != lastLine) {\n                lastFile = loc[\"filename\"].toString();\n                lastLine = loc[\"firstLine\"].toInt();\n\n                ui->outputWidget->addHtml(locationToLink(\n                                              loc[\"filename\"].toString(),\n                                              loc[\"firstLine\"].toInt(),\n                                              loc[\"firstColumn\"].toInt(),\n                                              loc[\"lastLine\"].toInt(),\n                                              loc[\"lastColumn\"].toInt(),\n                                              color\n                                         ), messageType);\n                ui->outputWidget->addText(\"\\n\", messageType);\n            }\n            ui->outputWidget->addText(QString(entry[\"isCompIter\"].toBool() ? \"    with \" : \"  in \") + entry[\"description\"].toString(), messageType);\n            ui->outputWidget->addText(\"\\n\", messageType);\n        }\n    } else if (error[\"location\"].isObject()) {\n        auto loc = error[\"location\"].toObject();\n        ui->outputWidget->addHtml(locationToLink(\n                                      loc[\"filename\"].toString(),\n                                      loc[\"firstLine\"].toInt(),\n                                      loc[\"firstColumn\"].toInt(),\n                                      loc[\"lastLine\"].toInt(),\n                                      loc[\"lastColumn\"].toInt(),\n                                      color\n                                 ), messageType);\n        ui->outputWidget->addText(\":\\n\", messageType);\n    }\n\n    QString what = error[\"what\"].toString();\n    QString message;\n\n    if (error[\"includedFrom\"].isArray()) {\n        for (auto it : error[\"includedFrom\"].toArray()) {\n            message += QString(\" (included from %1)\\n\").arg(it.toString());\n        }\n    }\n\n    if (isError) {\n        message += \"MiniZinc: \";\n    } else {\n        message += \"Warning: \";\n    }\n    if (error[\"what\"].isString()) {\n        message += QString(\"%1: \").arg(what);\n    }\n    message += QString(\"%1\\n\").arg(error[\"message\"].toString());\n\n    if (what == \"cyclic include error\") {\n        for (auto it : error[\"cycle\"].toArray()) {\n            message += QString(\" %1\\n\").arg(it.toString());\n        }\n    }\n\n    ui->outputWidget->addText(message, messageType);\n}\n\nQString MainWindow::locationToLink(const QString& file, int firstLine, int firstColumn, int lastLine, int lastColumn, const QColor& color)\n{\n    QString label = QFileInfo(file).baseName();\n    if (file.endsWith(\"untitled_model.mzn\")) {\n        QFileInfo fi(file);\n        for (QTemporaryDir* d : cleanupTmpDirs) {\n            QFileInfo tmpFileInfo(d->path() + \"/untitled_model.mzn\");\n            if (fi.canonicalFilePath( )== tmpFileInfo.canonicalFilePath()) {\n                label = \"Playground\";\n                break;\n            }\n        }\n    }\n    QString position;\n    if (firstLine == lastLine) {\n        if (firstColumn == lastColumn) {\n            position = QString(\"%1.%2\")\n                    .arg(firstLine)\n                    .arg(firstColumn);\n        } else {\n            position = QString(\"%1.%2-%3\")\n                    .arg(firstLine)\n                    .arg(firstColumn)\n                    .arg(lastColumn);\n        }\n    } else {\n        position = QString(\"%1-%2.%3-%4\")\n                .arg(firstLine)\n                .arg(firstColumn)\n                .arg(lastLine)\n                .arg(lastColumn);\n    }\n    QUrl url = QUrl::fromLocalFile(file);\n    url.setQuery(QString(\"line=%1&column=%2\").arg(firstLine).arg(firstColumn));\n    url.setScheme(\"err\");\n    return QString(\"<a style=\\\"color:%1\\\" href=\\\"%2\\\">%3:%4</a>\")\n                              .arg(color.name())\n                              .arg(url.toString())\n                              .arg(label)\n                              .arg(position);\n}\n\nvoid MainWindow::startVisualisation(const QString& model, const QStringList& data, const QString& key, const QString& url, const QJsonValue& userData, MznProcess* proc)\n{\n\n    QSettings settings;\n    settings.beginGroup(\"ide\");\n    bool reuseVis = settings.value(\"reuseVis\", false).toBool();\n    int httpPort = settings.value(\"visPort\", 3000).toInt();\n    int wsPort = settings.value(\"visWsPort\", 3100).toInt();\n    bool printUrl = settings.value(\"printVisUrl\", false).toBool();\n    settings.endGroup();\n\n    if (server == nullptr) {\n        server = new Server(this);\n    }\n    try {\n        server->listen(httpPort, wsPort);\n    } catch (ServerError& e) {\n        QMessageBox::warning(this, \"MiniZinc IDE\", e.message(), QMessageBox::Ok);\n        return;\n    }\n\n    QFileInfo modelFileInfo(model);\n    QStringList files({modelFileInfo.fileName()});\n    for (auto& df : data) {\n        QFileInfo fi(df);\n        files << fi.fileName();\n    }\n    auto workingDir = modelFileInfo.canonicalPath();\n\n    auto label = files.join(\", \");\n    QStringList roots({workingDir, MznDriver::get().mznStdlibDir()});\n    vis_connector = server->addConnector(label, roots);\n    connect(vis_connector, &VisConnector::solveRequested, [=] (const QString& modelFile, bool dataFilesGiven, const QStringList& dataFiles, const QVariantMap& options) {\n        auto* origSc = getCurrentSolverConfig();\n        if (origSc == nullptr) {\n            return;\n        }\n        QStringList df;\n        bool modelFileGiven = !modelFile.isEmpty();\n        auto m = modelFileGiven ? workingDir + \"/\" + modelFile : model;\n        if (!IDEUtils::isChildPath(workingDir, m)) {\n            return;\n        }\n        if (dataFilesGiven) {\n            for (const auto& it : dataFiles) {\n                auto d = workingDir + \"/\" + it;\n                if (!IDEUtils::isChildPath(workingDir, d)) {\n                    return;\n                }\n                df << d;\n            }\n        } else if (!modelFileGiven) {\n            // No model, no data, so use previous\n            df = data;\n        }\n        SolverConfiguration rsc(*origSc);\n        QMapIterator<QString, QVariant> i(options);\n        while (i.hasNext()) {\n            i.next();\n            rsc.extraOptions[i.key()] = i.value();\n        }\n\n        if (processRunning) {\n            proc->terminate();\n        }\n        run(rsc, m, df);\n    });\n    connect(proc, &MznProcess::traceOutput, this, [=] (const QString& section, const QVariant& message) {\n        if (section.startsWith(\"mzn_vis_\")) {\n            auto obj = message.toJsonObject();\n            vis_connector->addWindow(section, obj[\"url\"].toString(), obj[\"userData\"]);\n        }\n    });\n    connect(proc, &MznProcess::solutionOutput, [=](const QVariantMap& output, const QStringList& sections, qint64 time) {\n        QJsonObject solution;\n        for (auto it = output.begin(); it != output.end(); it++) {\n            if (it.key().startsWith(\"mzn_vis_\")) {\n                solution[it.key()] = it.value().toJsonValue();\n            }\n        }\n        vis_connector->addSolution(solution, time == -1 ? proc->elapsedTime() : time);\n    });\n    connect(proc, &MznProcess::finalStatus, [=](const QString& status, qint64 time) {\n        vis_connector->setFinalStatus(status, time == -1 ? proc->elapsedTime() : time);\n    });\n    connect(proc, &MznProcess::finished, vis_connector, &VisConnector::setFinished);\n    vis_connector->addWindow(key, url, userData);\n\n    ui->outputWidget->associateServerUrl(vis_connector->url().toString());\n\n    if (reuseVis) {\n        QJsonObject obj({{\"event\", \"navigate\"}, {\"url\", vis_connector->url().toString()}});\n        if (!server->sendToLastClient(QJsonDocument(obj))) {\n            QDesktopServices::openUrl(vis_connector->url());\n        }\n    } else {\n        QDesktopServices::openUrl(vis_connector->url());\n    }\n\n    if (printUrl) {\n        auto url = vis_connector->url().toString();\n        auto urlMessage = QString(\"<a href=\\\"%1\\\">%1</a>\").arg(url);\n        ui->outputWidget->addText(\"Visualisation: \", ui->outputWidget->infoCharFormat(), \"Visualisation\");\n        ui->outputWidget->addHtml(urlMessage, \"Visualisation\");\n        ui->outputWidget->addText(\"\\n\", ui->outputWidget->infoCharFormat(), \"Visualisation\");\n    }\n}\n"
  },
  {
    "path": "MiniZincIDE/mainwindow.h",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#ifndef MAINWINDOW_H\n#define MAINWINDOW_H\n\n#include <QMainWindow>\n#include <QFile>\n#include <QProcess>\n#include <QTimer>\n#include <QLabel>\n#include <QSet>\n#include <QTemporaryDir>\n#include <QMap>\n#include <QSet>\n#include <QSortFilterProxyModel>\n#include <QToolButton>\n#include <QProgressBar>\n#include <QComboBox>\n#include <QVersionNumber>\n#include <QJsonArray>\n\n#include \"codeeditor.h\"\n#include \"solver.h\"\n#include \"paramdialog.h\"\n#include \"project.h\"\n#include \"moocsubmission.h\"\n#include \"solver.h\"\n#include \"process.h\"\n#include \"codechecker.h\"\n#include \"profilecompilation.h\"\n#include \"server.h\"\n#include \"theme.h\"\n\n#include \"../cp-profiler/src/cpprofiler/conductor.hh\"\n#include \"../cp-profiler/src/cpprofiler/execution_window.hh\"\n#include \"../cp-profiler/src/cpprofiler/analysis/merge_window.hh\"\n\nnamespace Ui {\nclass MainWindow;\n}\n\nclass FindDialog;\nclass MainWindow;\nclass QNetworkReply;\nclass QTextStream;\n\nclass MainWindow : public QMainWindow\n{\n    Q_OBJECT\n    friend class IDE;\n    friend class TestIDE;\n\npublic:\n    explicit MainWindow(const QString& project = QString());\n    explicit MainWindow(const QStringList& files);\n    ~MainWindow();\n\nprivate:\n    enum CompileMode { CM_RUN, CM_COMPILE, CM_PROFILE };\n\n    void init(const QString& project);\n    void compileOrRun(\n            CompileMode cm,\n            const SolverConfiguration* sc = nullptr,\n            const QString& model = QString(),\n            const QStringList& data = QStringList(),\n            const QString& checker = QString(),\n            const QStringList& extraArgs = QStringList());\n    bool getModelParameters(const SolverConfiguration& sc, const QString& model, QStringList& data, const QStringList& extraArgs, QStringList& inlineData);\n    QString currentModelFile(void);\n    bool promptSaveModified(void);\n    void setEditorMenuItemsEnabled(bool enabled);\nsignals:\n    /// emitted when compilation and running of a model has finished\n    void finished();\n    /// emitted when the window is going to close\n    void terminating();\n\npublic slots:\n\n    void openFile(const QString &path = QString(), bool openAsModified=false, bool focus=true);\n    void stop();\n    void setTheme(const Theme& theme, bool darkMode);\n    void initTheme();\n    void closeFindWidget();\n\nprivate slots:\n\n    void on_actionClose_triggered();\n\n    void on_actionOpen_triggered();\n\n    void tabCloseRequest(int);\n\n    void tabChange(int);\n\n    void on_actionRun_triggered();\n\n    void procFinished(int, qint64 time);\n    void procFinished(int);\n\n    void on_actionSave_triggered();\n    void on_actionQuit_triggered();\n\n    void statusTimerEvent(qint64 time);\n\n    void on_actionCompile_triggered();\n\n    void openCompiledFzn(const QString& file);\n\n    void profileCompiledFzn(const QVector<TimingEntry>& timing, const QVector<PathEntry>& paths);\n\n    void on_actionSave_as_triggered();\n\n    void on_actionClear_output_triggered();\n\n    void on_actionBigger_font_triggered();\n\n    void on_actionSmaller_font_triggered();\n\n    void on_actionAbout_MiniZinc_IDE_triggered();\n\n    void anchorClicked(const QUrl&);\n\n    void on_actionDefault_font_size_triggered();\n\n    void on_actionManage_solvers_triggered(bool addNew=false);\n\n    void on_actionFind_triggered();\n\n    void on_actionReplace_triggered();\n\n    void on_actionGo_to_line_triggered();\n\n    void on_actionShift_left_triggered();\n\n    void on_actionShift_right_triggered();\n\n    void on_actionHelp_triggered();\n\n    void on_actionNew_project_triggered();\n\n    void on_actionSave_project_triggered();\n\n    void on_actionSave_project_as_triggered();\n\n    void on_actionClose_project_triggered();\n\n    void on_actionFind_next_triggered();\n\n    void on_actionFind_previous_triggered();\n\n    void on_actionSave_all_triggered();\n\n    void on_actionSave_solver_configuration_triggered();\n\n    void on_actionSave_all_solver_configurations_triggered();\n\n    void on_action_Un_comment_triggered();\n\n    void on_actionOnly_editor_triggered();\n\n    void on_actionSplit_triggered();\n\n    void on_actionPrevious_tab_triggered();\n\n    void on_actionNext_tab_triggered();\n\n    void recentFileMenuAction(QAction*);\n\n    void recentProjectMenuAction(QAction*);\n\n    void on_actionHide_tool_bar_triggered();\n\n    void on_actionToggle_profiler_info_triggered();\n\n    void on_actionShow_project_explorer_triggered();\n\n    void on_actionNewModel_file_triggered();\n\n    void on_actionNewData_file_triggered();\n\n    void on_actionSubmit_to_MOOC_triggered();\n\n    void fileRenamed(const QString&, const QString&);\n\n    void onClipboardChanged();\n\n    void editor_cursor_position_changed();\n\n    void showWindowMenu(void);\n    void windowMenuSelected(QAction*);\n    void on_actionCheat_Sheet_triggered();\n    void check_code();\n\n    void moocFinished(int);\n\n    void on_actionEditSolverConfig_triggered();\n\n    void on_b_next_clicked();\n\n    void on_b_prev_clicked();\n\n    void on_b_replacefind_clicked();\n\n    void on_b_replace_clicked();\n\n    void on_b_replaceall_clicked();\n\n    void on_closeFindWidget_clicked();\n\n    void on_find_textEdited(const QString &arg1);\n\n    void on_actionProfile_compilation_triggered();\n\n    void on_configWindow_dockWidget_visibilityChanged(bool visible);\n\n    void on_config_window_itemsChanged(const QStringList& items);\n\n    void on_config_window_selectedIndexChanged(int );\n\n    void on_moocChanged(const MOOCAssignment* mooc);\n\n    void on_projectBrowser_runRequested(const QStringList& files);\n\n    void on_projectBrowser_openRequested(const QStringList& files);\n\n    void on_projectBrowser_removeRequested(const QStringList& files);\n\n    void on_actionShow_search_profiler_triggered();\n\n    void on_actionProfile_search_triggered();\n\n    void showExecutionWindow(cpprofiler::ExecutionWindow& e);\n    void showMergeWindow(cpprofiler::analysis::MergeWindow& m);\n\n    void on_cpprofiler_dockWidget_visibilityChanged(bool visible);\n\n    void on_menuSolver_configurations_triggered(QAction* action);\n\n    void on_progressOutput(float progress);\n\n    void on_minizincError(const QJsonObject& error);\n\nprotected:\n    virtual void closeEvent(QCloseEvent*);\n    virtual void dragEnterEvent(QDragEnterEvent *);\n    virtual void dropEvent(QDropEvent *);\n    bool eventFilter(QObject *, QEvent *);\npublic:\n    QString currentSolver(void) const;\n    QString currentSolverConfigName(void);\n    bool checkSolutions(void) const;\n    void setCheckSolutions(bool b);\n    SolverConfiguration* getCurrentSolverConfig(void);\n    const Solver* getCurrentSolver(void);\n    void compileSolutionChecker(const QString& checker);\n    void compile(const SolverConfiguration& sc, const QString& model, const QStringList& data = QStringList(), const QStringList& extraArgs = QStringList(), const QStringList& inlineData = QStringList(), bool profile = false);\n    void run(const SolverConfiguration& sc, const QString& model, const QStringList& data = QStringList(), const QStringList& extraArgs = QStringList(), const QStringList& inlineData = QStringList(), QTextStream* ts = nullptr);\n    QList<CodeEditor*> codeEditors();\n    bool isDarkMode() const { return darkMode; }\nprivate:\n    Ui::MainWindow *ui;\n    CodeEditor* curEditor;\n    CodeChecker* code_checker;\n    CodeEditor* curCheckEditor;\n    QProgressBar* progressBar;\n    QLabel* statusLabel;\n    QLabel* statusLineColLabel;\n    QFont editorFont;\n    int indentSize;\n    bool useTabs;\n    bool darkMode;\n    QVector<Solver> solvers;\n    QStringList currentAdditionalDataFiles;\n    QTemporaryDir* tmpDir;\n    QVector<QTemporaryDir*> cleanupTmpDirs;\n    QVector<Process*> cleanupProcesses;\n    QTextCursor incrementalFindCursor;\n    bool saveBeforeRunning;\n    ParamDialog* paramDialog;\n    bool profileInfoVisible;\n    Project* project;\n    int newFileCounter;\n    QComboBox* solverConfCombo;\n    QAction* fakeRunAction;\n    QAction* fakeStopAction;\n    QAction* fakeCompileAction;\n    QAction* minimizeAction;\n    MOOCSubmission* moocSubmission;\n    bool processRunning;\n\n    QToolButton* runButton;\n\n    cpprofiler::Conductor* conductor = nullptr;\n    Server* server = nullptr;\n    VisConnector* vis_connector = nullptr;\n\n    void createEditor(const QString& path, bool openAsModified, bool isNewFile, bool readOnly=false, bool focus=true);\n    enum ConfMode { CONF_CHECKARGS, CONF_COMPILE, CONF_RUN };\n    QStringList parseConf(const ConfMode& confMode, const QString& modelFile);\n    void saveFile(CodeEditor* ce, const QString& filepath);\n    void saveProject(const QString& filepath);\n    void loadProject(const QString& filepath);\n    void setEditorFont(QFont font);\n    void setEditorIndent(int indentSize, bool useTabs);\n    void setEditorWordWrap(QTextOption::WrapMode mode);\n    void setLastPath(const QString& s);\n    QString getLastPath(void);\n    QString setElapsedTime(qint64 elapsed_t);\n    void checkDriver();\n    void updateRecentProjects(const QString& p);\n    void updateRecentFiles(const QString& p);\n    void updateUiProcessRunning(bool pr);\n    void highlightPath(QString& path, int index);\n    QVector<CodeEditor*> collectCodeEditors(QVector<QStringList>& locs);\n    void find(bool fwd, bool forceNoWrapAround=false);\n    bool requireMiniZinc(void);\n    void outputStdErr(const QString& line);\n    QStringList getOpenFiles(void);\n\n    void updateProfileSearchButton(void);\n\n    QString locationToLink(const QString& filename, int firstLine, int firstColumn, int lastLine, int lastColumn, const QColor& color);\n\n    void startVisualisation(const QString& model, const QStringList& data, const QString& key, const QString& url, const QJsonValue& userData, MznProcess* proc);\npublic:\n    void addOutput(const QString& s, bool html=true);\n    void openProject(const QString& fileName);\n    bool isEmptyProject(void);\n    Project& getProject(void) { return *project; }\n};\n\n#endif // MAINWINDOW_H\n"
  },
  {
    "path": "MiniZincIDE/mainwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>MainWindow</class>\n <widget class=\"QMainWindow\" name=\"MainWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>1490</width>\n    <height>1609</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string/>\n  </property>\n  <property name=\"windowIcon\">\n   <iconset resource=\"minizincide.qrc\">\n    <normaloff>:/mznide.ico</normaloff>:/mznide.ico</iconset>\n  </property>\n  <property name=\"iconSize\">\n   <size>\n    <width>256</width>\n    <height>256</height>\n   </size>\n  </property>\n  <property name=\"unifiedTitleAndToolBarOnMac\">\n   <bool>true</bool>\n  </property>\n  <widget class=\"QWidget\" name=\"centralwidget\">\n   <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n    <property name=\"leftMargin\">\n     <number>0</number>\n    </property>\n    <property name=\"topMargin\">\n     <number>0</number>\n    </property>\n    <property name=\"rightMargin\">\n     <number>0</number>\n    </property>\n    <property name=\"bottomMargin\">\n     <number>0</number>\n    </property>\n    <item>\n     <widget class=\"QTabWidget\" name=\"tabWidget\">\n      <property name=\"sizePolicy\">\n       <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Preferred\">\n        <horstretch>0</horstretch>\n        <verstretch>0</verstretch>\n       </sizepolicy>\n      </property>\n      <property name=\"focusPolicy\">\n       <enum>Qt::StrongFocus</enum>\n      </property>\n      <property name=\"currentIndex\">\n       <number>0</number>\n      </property>\n      <property name=\"usesScrollButtons\">\n       <bool>true</bool>\n      </property>\n      <property name=\"documentMode\">\n       <bool>true</bool>\n      </property>\n      <property name=\"tabsClosable\">\n       <bool>true</bool>\n      </property>\n      <property name=\"movable\">\n       <bool>true</bool>\n      </property>\n      <widget class=\"QWidget\" name=\"configuration\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <attribute name=\"title\">\n        <string>Configuration</string>\n       </attribute>\n      </widget>\n     </widget>\n    </item>\n    <item>\n     <widget class=\"QFrame\" name=\"findFrame\">\n      <property name=\"maximumSize\">\n       <size>\n        <width>16777215</width>\n        <height>80</height>\n       </size>\n      </property>\n      <property name=\"frameShape\">\n       <enum>QFrame::NoFrame</enum>\n      </property>\n      <property name=\"frameShadow\">\n       <enum>QFrame::Raised</enum>\n      </property>\n      <property name=\"lineWidth\">\n       <number>0</number>\n      </property>\n      <layout class=\"QHBoxLayout\" name=\"horizontalLayout_15\">\n       <property name=\"leftMargin\">\n        <number>0</number>\n       </property>\n       <property name=\"topMargin\">\n        <number>0</number>\n       </property>\n       <property name=\"rightMargin\">\n        <number>0</number>\n       </property>\n       <property name=\"bottomMargin\">\n        <number>0</number>\n       </property>\n       <item>\n        <widget class=\"QWidget\" name=\"findWidget\" native=\"true\">\n         <layout class=\"QGridLayout\" name=\"gridLayout\">\n          <property name=\"topMargin\">\n           <number>0</number>\n          </property>\n          <property name=\"bottomMargin\">\n           <number>0</number>\n          </property>\n          <item row=\"1\" column=\"8\">\n           <widget class=\"QPushButton\" name=\"b_replacefind\">\n            <property name=\"text\">\n             <string>Replace &amp;&amp; find</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"2\" column=\"3\">\n           <widget class=\"QCheckBox\" name=\"check_case\">\n            <property name=\"text\">\n             <string>Ignore case</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"1\" column=\"1\" colspan=\"6\">\n           <widget class=\"EscLineEdit\" name=\"replace\">\n            <property name=\"minimumSize\">\n             <size>\n              <width>300</width>\n              <height>20</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item row=\"1\" column=\"0\">\n           <widget class=\"QLabel\" name=\"label_3\">\n            <property name=\"text\">\n             <string>Replace</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"1\" column=\"7\">\n           <widget class=\"QPushButton\" name=\"b_replace\">\n            <property name=\"text\">\n             <string>Replace</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"0\" column=\"1\" colspan=\"6\">\n           <widget class=\"EscLineEdit\" name=\"find\">\n            <property name=\"minimumSize\">\n             <size>\n              <width>300</width>\n              <height>20</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item row=\"1\" column=\"10\">\n           <widget class=\"QPushButton\" name=\"b_replaceall\">\n            <property name=\"text\">\n             <string>Replace all</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"0\" column=\"0\">\n           <widget class=\"QLabel\" name=\"label\">\n            <property name=\"text\">\n             <string>Find</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"0\" column=\"10\">\n           <widget class=\"QPushButton\" name=\"closeFindWidget\">\n            <property name=\"text\">\n             <string>Close</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"2\" column=\"0\">\n           <widget class=\"QLabel\" name=\"not_found\">\n            <property name=\"text\">\n             <string>not found</string>\n            </property>\n            <property name=\"alignment\">\n             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n            </property>\n           </widget>\n          </item>\n          <item row=\"2\" column=\"1\" colspan=\"2\">\n           <widget class=\"QCheckBox\" name=\"check_re\">\n            <property name=\"sizePolicy\">\n             <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Fixed\">\n              <horstretch>0</horstretch>\n              <verstretch>0</verstretch>\n             </sizepolicy>\n            </property>\n            <property name=\"text\">\n             <string>Regular expression</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"2\" column=\"4\">\n           <widget class=\"QCheckBox\" name=\"check_wrap\">\n            <property name=\"text\">\n             <string>Wrap around</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"0\" column=\"11\">\n           <spacer name=\"horizontalSpacer_8\">\n            <property name=\"orientation\">\n             <enum>Qt::Horizontal</enum>\n            </property>\n            <property name=\"sizeHint\" stdset=\"0\">\n             <size>\n              <width>40</width>\n              <height>20</height>\n             </size>\n            </property>\n           </spacer>\n          </item>\n          <item row=\"0\" column=\"7\">\n           <widget class=\"QPushButton\" name=\"b_next\">\n            <property name=\"text\">\n             <string>Next</string>\n            </property>\n            <property name=\"default\">\n             <bool>true</bool>\n            </property>\n           </widget>\n          </item>\n          <item row=\"0\" column=\"8\">\n           <widget class=\"QPushButton\" name=\"b_prev\">\n            <property name=\"text\">\n             <string>Previous</string>\n            </property>\n           </widget>\n          </item>\n         </layout>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n    </item>\n   </layout>\n  </widget>\n  <widget class=\"QMenuBar\" name=\"menubar\">\n   <property name=\"geometry\">\n    <rect>\n     <x>0</x>\n     <y>0</y>\n     <width>1490</width>\n     <height>20</height>\n    </rect>\n   </property>\n   <property name=\"nativeMenuBar\">\n    <bool>true</bool>\n   </property>\n   <widget class=\"QMenu\" name=\"menuFile\">\n    <property name=\"title\">\n     <string>&amp;File</string>\n    </property>\n    <widget class=\"QMenu\" name=\"menuRecent_Files\">\n     <property name=\"title\">\n      <string>Recent Files</string>\n     </property>\n     <addaction name=\"separator\"/>\n     <addaction name=\"actionClear_menu\"/>\n    </widget>\n    <widget class=\"QMenu\" name=\"menuRecent_Projects\">\n     <property name=\"title\">\n      <string>Recent Projects</string>\n     </property>\n     <addaction name=\"separator\"/>\n     <addaction name=\"actionClear_menu_2\"/>\n    </widget>\n    <addaction name=\"actionNewModel_file\"/>\n    <addaction name=\"actionNewData_file\"/>\n    <addaction name=\"actionOpen\"/>\n    <addaction name=\"menuRecent_Files\"/>\n    <addaction name=\"menuRecent_Projects\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionSave\"/>\n    <addaction name=\"actionSave_as\"/>\n    <addaction name=\"actionSave_all\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionSave_solver_configuration\"/>\n    <addaction name=\"actionSave_all_solver_configurations\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionClose\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionNew_project\"/>\n    <addaction name=\"actionSave_project\"/>\n    <addaction name=\"actionSave_project_as\"/>\n    <addaction name=\"actionClose_project\"/>\n    <addaction name=\"separator\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuEdit\">\n    <property name=\"title\">\n     <string>Edit</string>\n    </property>\n    <widget class=\"QMenu\" name=\"menuFind\">\n     <property name=\"title\">\n      <string>Find</string>\n     </property>\n     <addaction name=\"actionFind\"/>\n     <addaction name=\"separator\"/>\n     <addaction name=\"actionFind_next\"/>\n     <addaction name=\"actionFind_previous\"/>\n    </widget>\n    <addaction name=\"actionUndo\"/>\n    <addaction name=\"actionRedo\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionCut\"/>\n    <addaction name=\"actionCopy\"/>\n    <addaction name=\"actionPaste\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionSelect_All\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"menuFind\"/>\n    <addaction name=\"actionGo_to_line\"/>\n    <addaction name=\"actionShift_left\"/>\n    <addaction name=\"actionShift_right\"/>\n    <addaction name=\"action_Un_comment\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuMiniZinc\">\n    <property name=\"title\">\n     <string>MiniZinc</string>\n    </property>\n    <widget class=\"QMenu\" name=\"menuSolver_configurations\">\n     <property name=\"title\">\n      <string>Solver configurations</string>\n     </property>\n     <addaction name=\"actionDefaultSolverConfig\"/>\n     <addaction name=\"actionEditSolverConfig\"/>\n    </widget>\n    <addaction name=\"actionRun\"/>\n    <addaction name=\"actionStop\"/>\n    <addaction name=\"actionCompile\"/>\n    <addaction name=\"actionProfile_compilation\"/>\n    <addaction name=\"actionProfile_search\"/>\n    <addaction name=\"actionSubmit_to_MOOC\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"menuSolver_configurations\"/>\n    <addaction name=\"actionManage_solvers\"/>\n    <addaction name=\"separator\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuView\">\n    <property name=\"title\">\n     <string>View</string>\n    </property>\n    <addaction name=\"actionBigger_font\"/>\n    <addaction name=\"actionSmaller_font\"/>\n    <addaction name=\"actionDefault_font_size\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionOnly_editor\"/>\n    <addaction name=\"actionSplit\"/>\n    <addaction name=\"actionPrevious_tab\"/>\n    <addaction name=\"actionNext_tab\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionHide_tool_bar\"/>\n    <addaction name=\"actionToggle_profiler_info\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionShow_search_profiler\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionShow_project_explorer\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionClear_output\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuHelp\">\n    <property name=\"title\">\n     <string>Help</string>\n    </property>\n    <addaction name=\"actionAbout_MiniZinc_IDE\"/>\n    <addaction name=\"actionHelp\"/>\n    <addaction name=\"actionCheat_Sheet\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuWindow\">\n    <property name=\"title\">\n     <string>Window</string>\n    </property>\n   </widget>\n   <addaction name=\"menuFile\"/>\n   <addaction name=\"menuEdit\"/>\n   <addaction name=\"menuMiniZinc\"/>\n   <addaction name=\"menuView\"/>\n   <addaction name=\"menuWindow\"/>\n   <addaction name=\"menuHelp\"/>\n  </widget>\n  <widget class=\"QStatusBar\" name=\"statusbar\"/>\n  <widget class=\"OutputDockWidget\" name=\"outputDockWidget\">\n   <property name=\"enabled\">\n    <bool>true</bool>\n   </property>\n   <property name=\"sizePolicy\">\n    <sizepolicy hsizetype=\"Preferred\" vsizetype=\"MinimumExpanding\">\n     <horstretch>0</horstretch>\n     <verstretch>0</verstretch>\n    </sizepolicy>\n   </property>\n   <property name=\"features\">\n    <set>QDockWidget::AllDockWidgetFeatures</set>\n   </property>\n   <property name=\"allowedAreas\">\n    <set>Qt::BottomDockWidgetArea|Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea</set>\n   </property>\n   <property name=\"windowTitle\">\n    <string>Output</string>\n   </property>\n   <attribute name=\"dockWidgetArea\">\n    <number>8</number>\n   </attribute>\n   <widget class=\"QWidget\" name=\"dockWidgetContents_4\">\n    <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n     <item>\n      <widget class=\"OutputWidget\" name=\"outputWidget\" native=\"true\"/>\n     </item>\n    </layout>\n   </widget>\n  </widget>\n  <widget class=\"QToolBar\" name=\"toolBar\">\n   <property name=\"windowTitle\">\n    <string>toolBar</string>\n   </property>\n   <property name=\"movable\">\n    <bool>false</bool>\n   </property>\n   <property name=\"iconSize\">\n    <size>\n     <width>32</width>\n     <height>32</height>\n    </size>\n   </property>\n   <property name=\"toolButtonStyle\">\n    <enum>Qt::ToolButtonTextUnderIcon</enum>\n   </property>\n   <property name=\"floatable\">\n    <bool>false</bool>\n   </property>\n   <attribute name=\"toolBarArea\">\n    <enum>TopToolBarArea</enum>\n   </attribute>\n   <attribute name=\"toolBarBreak\">\n    <bool>false</bool>\n   </attribute>\n   <addaction name=\"actionNewModel_file\"/>\n   <addaction name=\"actionOpen\"/>\n   <addaction name=\"actionSave\"/>\n   <addaction name=\"separator\"/>\n   <addaction name=\"actionCopy\"/>\n   <addaction name=\"actionCut\"/>\n   <addaction name=\"actionPaste\"/>\n   <addaction name=\"actionUndo\"/>\n   <addaction name=\"actionRedo\"/>\n   <addaction name=\"actionShift_left\"/>\n   <addaction name=\"actionShift_right\"/>\n   <addaction name=\"separator\"/>\n   <addaction name=\"actionSubmit_to_MOOC\"/>\n   <addaction name=\"actionEditSolverConfig\"/>\n   <addaction name=\"actionShow_project_explorer\"/>\n  </widget>\n  <widget class=\"QDockWidget\" name=\"projectExplorerDockWidget\">\n   <property name=\"floating\">\n    <bool>false</bool>\n   </property>\n   <property name=\"features\">\n    <set>QDockWidget::DockWidgetMovable</set>\n   </property>\n   <property name=\"allowedAreas\">\n    <set>Qt::BottomDockWidgetArea|Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea</set>\n   </property>\n   <property name=\"windowTitle\">\n    <string>Project</string>\n   </property>\n   <attribute name=\"dockWidgetArea\">\n    <number>2</number>\n   </attribute>\n   <widget class=\"QWidget\" name=\"dockWidgetContents\">\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout_12\">\n     <property name=\"leftMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>0</number>\n     </property>\n     <item>\n      <widget class=\"ProjectBrowser\" name=\"projectBrowser\" native=\"true\"/>\n     </item>\n    </layout>\n   </widget>\n  </widget>\n  <widget class=\"QDockWidget\" name=\"configWindow_dockWidget\">\n   <property name=\"windowTitle\">\n    <string>Configuration</string>\n   </property>\n   <attribute name=\"dockWidgetArea\">\n    <number>2</number>\n   </attribute>\n   <widget class=\"QWidget\" name=\"dockWidgetContents_2\">\n    <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n     <property name=\"leftMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>0</number>\n     </property>\n     <item>\n      <widget class=\"QScrollArea\" name=\"scrollArea_2\">\n       <property name=\"frameShape\">\n        <enum>QFrame::NoFrame</enum>\n       </property>\n       <property name=\"widgetResizable\">\n        <bool>true</bool>\n       </property>\n       <widget class=\"QWidget\" name=\"scrollAreaWidgetContents_2\">\n        <property name=\"geometry\">\n         <rect>\n          <x>0</x>\n          <y>0</y>\n          <width>68</width>\n          <height>786</height>\n         </rect>\n        </property>\n        <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n         <property name=\"leftMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"topMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"rightMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"bottomMargin\">\n          <number>0</number>\n         </property>\n         <item>\n          <widget class=\"ConfigWindow\" name=\"config_window\" native=\"true\"/>\n         </item>\n        </layout>\n       </widget>\n      </widget>\n     </item>\n    </layout>\n   </widget>\n  </widget>\n  <widget class=\"QDockWidget\" name=\"cpprofiler_dockWidget\">\n   <property name=\"windowTitle\">\n    <string>Search Profiler</string>\n   </property>\n   <attribute name=\"dockWidgetArea\">\n    <number>2</number>\n   </attribute>\n   <widget class=\"QWidget\" name=\"dockWidgetContents_3\">\n    <layout class=\"QGridLayout\" name=\"gridLayout_2\">\n     <property name=\"leftMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>0</number>\n     </property>\n     <item row=\"0\" column=\"0\">\n      <widget class=\"QWidget\" name=\"cpprofiler\" native=\"true\"/>\n     </item>\n    </layout>\n   </widget>\n  </widget>\n  <action name=\"actionOpen\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/document-open.png</normaloff>:/icons/images/document-open.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>&amp;Open...</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+O</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionSave\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/document-save.png</normaloff>:/icons/images/document-save.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Save</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+S</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionSave_as\">\n   <property name=\"text\">\n    <string>Save as...</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Shift+S</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionClose\">\n   <property name=\"text\">\n    <string>Close file</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+W</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionUndo\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/edit-undo.png</normaloff>:/icons/images/edit-undo.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Undo</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Z</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionRedo\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/edit-redo.png</normaloff>:/icons/images/edit-redo.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Redo</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Shift+Z</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionCut\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/edit-cut.png</normaloff>:/icons/images/edit-cut.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Cut</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+X</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionCopy\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/edit-copy.png</normaloff>:/icons/images/edit-copy.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Copy</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionPaste\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/edit-paste.png</normaloff>:/icons/images/edit-paste.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Paste</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+V</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionSelect_All\">\n   <property name=\"text\">\n    <string>Select All</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+A</string>\n   </property>\n  </action>\n  <action name=\"actionGo_to_line\">\n   <property name=\"text\">\n    <string>Go to line...</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+L</string>\n   </property>\n  </action>\n  <action name=\"actionRun\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/media-playback-start.png</normaloff>:/icons/images/media-playback-start.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Run</string>\n   </property>\n   <property name=\"toolTip\">\n    <string>Run</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+R</string>\n   </property>\n  </action>\n  <action name=\"actionQuit\">\n   <property name=\"text\">\n    <string>Quit</string>\n   </property>\n  </action>\n  <action name=\"actionStop\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/media-playback-stop.png</normaloff>:/icons/images/media-playback-stop.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Stop</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+E</string>\n   </property>\n  </action>\n  <action name=\"actionCompile\">\n   <property name=\"text\">\n    <string>Compile</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+B</string>\n   </property>\n  </action>\n  <action name=\"actionClear_output\">\n   <property name=\"text\">\n    <string>Clear output</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+K</string>\n   </property>\n  </action>\n  <action name=\"actionBigger_font\">\n   <property name=\"text\">\n    <string>Bigger font</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl++</string>\n   </property>\n  </action>\n  <action name=\"actionSmaller_font\">\n   <property name=\"text\">\n    <string>Smaller font</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+-</string>\n   </property>\n  </action>\n  <action name=\"actionAbout_MiniZinc_IDE\">\n   <property name=\"text\">\n    <string>About MiniZinc IDE</string>\n   </property>\n  </action>\n  <action name=\"actionDefault_font_size\">\n   <property name=\"text\">\n    <string>Default font size</string>\n   </property>\n  </action>\n  <action name=\"actionManage_solvers\">\n   <property name=\"text\">\n    <string>Preferences</string>\n   </property>\n  </action>\n  <action name=\"actionReplace\">\n   <property name=\"text\">\n    <string>Replace...</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Shift+F</string>\n   </property>\n  </action>\n  <action name=\"actionSelect_font\">\n   <property name=\"text\">\n    <string>Select font...</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+T</string>\n   </property>\n  </action>\n  <action name=\"actionOnly_editor\">\n   <property name=\"text\">\n    <string>Only editor</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+1</string>\n   </property>\n  </action>\n  <action name=\"actionSplit\">\n   <property name=\"text\">\n    <string>Show output</string>\n   </property>\n   <property name=\"toolTip\">\n    <string>Show output</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+2</string>\n   </property>\n  </action>\n  <action name=\"actionShift_left\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/format-indent-less.png</normaloff>:/icons/images/format-indent-less.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Shift selection left</string>\n   </property>\n   <property name=\"iconText\">\n    <string>Shift left</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+[</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionShift_right\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/format-indent-more.png</normaloff>:/icons/images/format-indent-more.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Shift selection right</string>\n   </property>\n   <property name=\"iconText\">\n    <string>Shift right</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+]</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionHelp\">\n   <property name=\"text\">\n    <string>Help...</string>\n   </property>\n  </action>\n  <action name=\"actionNew_project\">\n   <property name=\"text\">\n    <string>New project</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Shift+N</string>\n   </property>\n  </action>\n  <action name=\"actionSave_project\">\n   <property name=\"text\">\n    <string>Save project</string>\n   </property>\n  </action>\n  <action name=\"actionSave_project_as\">\n   <property name=\"text\">\n    <string>Save project as...</string>\n   </property>\n  </action>\n  <action name=\"actionClose_project\">\n   <property name=\"text\">\n    <string>Close project</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Shift+W</string>\n   </property>\n  </action>\n  <action name=\"actionFind\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/edit-find-replace.png</normaloff>:/icons/images/edit-find-replace.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Find...</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+F</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionFind_next\">\n   <property name=\"text\">\n    <string>Find next</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+G</string>\n   </property>\n  </action>\n  <action name=\"actionFind_previous\">\n   <property name=\"text\">\n    <string>Find previous</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Shift+G</string>\n   </property>\n  </action>\n  <action name=\"actionSave_all\">\n   <property name=\"text\">\n    <string>Save all</string>\n   </property>\n  </action>\n  <action name=\"action_Un_comment\">\n   <property name=\"text\">\n    <string>(Un)comment</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+/</string>\n   </property>\n  </action>\n  <action name=\"actionPrevious_tab\">\n   <property name=\"text\">\n    <string>Previous tab</string>\n   </property>\n  </action>\n  <action name=\"actionNext_tab\">\n   <property name=\"text\">\n    <string>Next tab</string>\n   </property>\n  </action>\n  <action name=\"actionClear_menu\">\n   <property name=\"text\">\n    <string>Clear menu</string>\n   </property>\n  </action>\n  <action name=\"actionClear_menu_2\">\n   <property name=\"text\">\n    <string>Clear menu</string>\n   </property>\n  </action>\n  <action name=\"actionHide_tool_bar\">\n   <property name=\"text\">\n    <string>Hide tool bar</string>\n   </property>\n  </action>\n  <action name=\"actionShow_project_explorer\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/package-x-generic.png</normaloff>:/icons/images/package-x-generic.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Show project explorer</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionNewModel_file\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/document-new.png</normaloff>:/icons/images/document-new.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>New model</string>\n   </property>\n   <property name=\"toolTip\">\n    <string>New model</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+N</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionNewData_file\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/document-new.png</normaloff>:/icons/images/document-new.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>New data</string>\n   </property>\n   <property name=\"toolTip\">\n    <string>New data</string>\n   </property>\n   <property name=\"iconVisibleInMenu\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionSubmit_to_MOOC\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/coursera.png</normaloff>:/icons/images/coursera.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Submit to Coursera</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+U</string>\n   </property>\n  </action>\n  <action name=\"actionCheat_Sheet\">\n   <property name=\"text\">\n    <string>Cheat Sheet...</string>\n   </property>\n  </action>\n  <action name=\"actionDefaultSolverConfig\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>default</string>\n   </property>\n  </action>\n  <action name=\"actionEditSolverConfig\">\n   <property name=\"icon\">\n    <iconset resource=\"minizincide.qrc\">\n     <normaloff>:/icons/images/applications-system.png</normaloff>:/icons/images/applications-system.png</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Show configuration editor...</string>\n   </property>\n   <property name=\"toolTip\">\n    <string>Show solver configuration editor</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Shift+C</string>\n   </property>\n  </action>\n  <action name=\"actionProfile_compilation\">\n   <property name=\"text\">\n    <string>Profile compilation</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+;</string>\n   </property>\n  </action>\n  <action name=\"actionToggle_profiler_info\">\n   <property name=\"text\">\n    <string>Profiler Info</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+.</string>\n   </property>\n  </action>\n  <action name=\"actionSave_solver_configuration\">\n   <property name=\"text\">\n    <string>Save solver configuration</string>\n   </property>\n  </action>\n  <action name=\"actionSave_all_solver_configurations\">\n   <property name=\"text\">\n    <string>Save all solver configurations</string>\n   </property>\n  </action>\n  <action name=\"actionShow_search_profiler\">\n   <property name=\"text\">\n    <string>Show search profiler</string>\n   </property>\n  </action>\n  <action name=\"actionProfile_search\">\n   <property name=\"enabled\">\n    <bool>false</bool>\n   </property>\n   <property name=\"text\">\n    <string>Profile search</string>\n   </property>\n  </action>\n  <action name=\"actionDefaultTheme\">\n   <property name=\"text\">\n    <string>Default</string>\n   </property>\n  </action>\n  <action name=\"actionBlueberryTheme\">\n   <property name=\"text\">\n    <string>Blueberry</string>\n   </property>\n  </action>\n  <action name=\"actionMangoTheme\">\n   <property name=\"text\">\n    <string>Mango</string>\n   </property>\n  </action>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>OutputDockWidget</class>\n   <extends>QDockWidget</extends>\n   <header>outputdockwidget.h</header>\n   <container>1</container>\n  </customwidget>\n  <customwidget>\n   <class>EscLineEdit</class>\n   <extends>QLineEdit</extends>\n   <header>esclineedit.h</header>\n  </customwidget>\n  <customwidget>\n   <class>ProjectBrowser</class>\n   <extends>QWidget</extends>\n   <header>projectbrowser.h</header>\n   <container>1</container>\n  </customwidget>\n  <customwidget>\n   <class>ConfigWindow</class>\n   <extends>QWidget</extends>\n   <header>configwindow.h</header>\n  </customwidget>\n  <customwidget>\n   <class>OutputWidget</class>\n   <extends>QWidget</extends>\n   <header>outputwidget.h</header>\n   <container>1</container>\n  </customwidget>\n </customwidgets>\n <tabstops>\n  <tabstop>tabWidget</tabstop>\n  <tabstop>outputWidget</tabstop>\n  <tabstop>find</tabstop>\n  <tabstop>replace</tabstop>\n  <tabstop>b_next</tabstop>\n  <tabstop>b_prev</tabstop>\n  <tabstop>closeFindWidget</tabstop>\n  <tabstop>b_replace</tabstop>\n  <tabstop>b_replacefind</tabstop>\n  <tabstop>b_replaceall</tabstop>\n  <tabstop>check_re</tabstop>\n  <tabstop>check_case</tabstop>\n  <tabstop>check_wrap</tabstop>\n </tabstops>\n <resources>\n  <include location=\"minizincide.qrc\"/>\n </resources>\n <connections/>\n</ui>\n"
  },
  {
    "path": "MiniZincIDE/minizincide.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/\">\n        <file>images/about.html</file>\n        <file>images/mznicon.png</file>\n        <file>cheat_sheet.mzn</file>\n        <file>mznide.ico</file>\n        <file>dark_mode.css</file>\n    </qresource>\n    <qresource prefix=\"/icons\">\n        <file>images/document-new.png</file>\n        <file>images/document-open.png</file>\n        <file>images/document-save.png</file>\n        <file>images/edit-find-replace.png</file>\n        <file>images/edit-paste.png</file>\n        <file>images/edit-redo.png</file>\n        <file>images/edit-undo.png</file>\n        <file>images/format-indent-less.png</file>\n        <file>images/format-indent-more.png</file>\n        <file>images/process-stop.png</file>\n        <file>images/edit-copy.png</file>\n        <file>images/edit-cut.png</file>\n        <file>images/folder.png</file>\n        <file>images/package-x-generic.png</file>\n        <file>images/coursera.png</file>\n        <file>images/coursera@2x.png</file>\n        <file>images/application-certificate.png</file>\n        <file>images/media-playback-start.png</file>\n        <file>images/media-playback-stop.png</file>\n        <file>images/applications-system.png</file>\n    </qresource>\n    <qresource prefix=\"/server\">\n        <file>server/index.html</file>\n        <file>server/connector.js</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "MiniZincIDE/moocsubmission.cpp",
    "content": "#include \"moocsubmission.h\"\n#include \"ui_moocsubmission.h\"\n#include \"ide.h\"\n#include \"mainwindow.h\"\n#include \"exception.h\"\n\n#include <QCheckBox>\n#include <QVBoxLayout>\n#include <QMessageBox>\n#include <QSettings>\n\n#include <QNetworkRequest>\n#include <QNetworkReply>\n#include <QUrlQuery>\n#include <QCryptographicHash>\n#include <QJsonDocument>\n#include <QJsonArray>\n\nMOOCAssignment::MOOCAssignment(const QString& fileName) : moocFile(fileName)\n{\n    QFile f(fileName);\n    QFileInfo fi(f);\n    if (!f.open(QFile::ReadOnly)) {\n        throw FileError(\"Failed to open mooc file\");\n    }\n    auto bytes = f.readAll();\n    auto doc = QJsonDocument::fromJson(bytes);\n    if (doc.isObject()) {\n        loadJSON(doc.object(), fi);\n    } else {\n        throw MoocError(\"Could not parse mooc file\");\n    }\n}\n\nvoid MOOCAssignment::loadJSON(const QJsonObject& obj, const QFileInfo& fi)\n{\n    if (obj.isEmpty() ||\n            !obj[\"assignmentKey\"].isString() || !obj[\"name\"].isString() || !obj[\"moocName\"].isString() ||\n            !obj[\"moocPasswordString\"].isString() || !obj[\"submissionURL\"].isString() ||\n            !obj[\"solutionAssignments\"].isArray() || !obj[\"modelAssignments\"].isArray()) {\n        throw MoocError(\"Could not parse mooc file\");\n    }\n\n    assignmentKey = obj[\"assignmentKey\"].toString();\n    name = obj[\"name\"].toString();\n    moocName = obj[\"moocName\"].toString();\n    moocPasswordString = obj[\"moocPasswordString\"].toString();\n    submissionURL = obj[\"submissionURL\"].toString();\n    submissionTerms = obj[\"submissionTerms\"].toString();\n    sendMeta = obj[\"sendMeta\"].toBool(false);\n    QJsonArray sols = obj[\"solutionAssignments\"].toArray();\n    for (int i=0; i<sols.size(); i++) {\n        QJsonObject solO = sols[i].toObject();\n        if (!sols[i].isObject() || !solO[\"id\"].isString() || !solO[\"model\"].isString() ||\n                !solO[\"data\"].isString() || (!solO[\"timeout\"].isDouble() && !solO[\"timeout\"].isString() ) || !solO[\"name\"].isString()) {\n            throw MoocError(\"Could not parse mooc file\");\n        }\n\n        QString timeout = solO[\"timeout\"].isDouble() ? QString::number(solO[\"timeout\"].toInt()) : solO[\"timeout\"].toString();\n        QFileInfo model_fi(fi.dir(), solO[\"model\"].toString());\n        QFileInfo data_fi(fi.dir(), solO[\"data\"].toString());\n        MOOCAssignmentItem item(solO[\"id\"].toString(), model_fi.absoluteFilePath(), data_fi.absoluteFilePath(),\n                                timeout, solO[\"name\"].toString(), solO[\"required\"].toBool(false));\n        problems.append(item);\n    }\n    QJsonArray modelsArray = obj[\"modelAssignments\"].toArray();\n    for (int i=0; i<modelsArray.size(); i++) {\n        QJsonObject modelO = modelsArray[i].toObject();\n        QFileInfo model_fi(fi.dir(), modelO[\"model\"].toString());\n        MOOCAssignmentItem item(modelO[\"id\"].toString(), model_fi.absoluteFilePath(), modelO[\"name\"].toString(), modelO[\"required\"].toBool(false));\n        models.append(item);\n    }\n\n    json = obj;\n    if (obj[\"history\"].isObject()) {\n        history = new History(obj[\"history\"].toObject(), fi.absoluteDir());\n        QObject::connect(IDE::instance(), &IDE::reloadedFile, history, &History::updateFileContents);\n        QObject::connect(history, &History::historyChanged, [=] () {\n            json[\"history\"] = history->toJSON();\n            QFile file(moocFile);\n            if (file.open(QFile::WriteOnly)) {\n                file.write(QJsonDocument(json).toJson());\n                file.close();\n            }\n        });\n    }\n}\n\nMOOCSubmission::MOOCSubmission(MainWindow* mw0, MOOCAssignment& cp) :\n    QDialog(nullptr), _cur_phase(S_NONE), project(cp), mw(mw0),\n    ui(new Ui::MOOCSubmission)\n{\n    ui->setupUi(this);\n\n    setWindowTitle(\"Submit to \"+cp.moocName);\n\n    ui->loginEmailLabel->setText(cp.moocName+\" login email:\");\n    ui->loginTokenLabel->setText(cp.moocPasswordString+\":\");\n    ui->courseraTokenWarningLabel->setHidden(cp.moocName!=\"Coursera\");\n\n    ui->selectedSolver->setText(mw->currentSolverConfigName());\n\n    QVBoxLayout* modelLayout = new QVBoxLayout;\n    ui->modelBox->setLayout(modelLayout);\n    QVBoxLayout* problemLayout = new QVBoxLayout;\n    ui->problemBox->setLayout(problemLayout);\n\n    QSet<QString> mooc_mzns;\n    for (auto& p : project.models) {\n        mooc_mzns << p.model;\n    }\n    for (auto& p : project.problems) {\n        mooc_mzns << p.model;\n    }\n    QStringList nonSubmissionFiles;\n    for (auto* ce : mw->codeEditors()) {\n        if (ce->filename.endsWith(\".mzn\") && !mooc_mzns.contains(ce->filepath)) {\n            nonSubmissionFiles << ce->filename;\n        }\n    }\n    if (nonSubmissionFiles.empty()) {\n        ui->nonSubmissionGroup->setVisible(false);\n    } else {\n        ui->nonSubmissionLabel->setText(nonSubmissionFiles.join(\"\\n\"));\n        ui->nonSubmissionGroup->setVisible(true);\n    }\n\n    if (project.submissionTerms.isEmpty()) {\n        ui->submissionTerms_groupBox->setVisible(false);\n        ui->runButton->setEnabled(true);\n    } else {\n        ui->submissionTerms_textBrowser->setText(cp.submissionTerms);\n        ui->submissionTerms_checkBox->setChecked(false);\n        ui->runButton->setEnabled(false);\n        ui->submissionTerms_groupBox->setVisible(true);\n    }\n\n    for (int i=0; i<project.models.size(); i++) {\n        const MOOCAssignmentItem& item = project.models.at(i);\n        QCheckBox* cb = new QCheckBox(item.name);\n        cb->setChecked(true);\n        cb->setEnabled(!item.required);\n        modelLayout->addWidget(cb);\n    }\n    for (int i=0; i<project.problems.size(); i++) {\n        const MOOCAssignmentItem& item = project.problems.at(i);\n        QCheckBox* cb = new QCheckBox(item.name);\n        cb->setChecked(true);\n        cb->setEnabled(!item.required);\n        problemLayout->addWidget(cb);\n    }\n    if (project.models.empty())\n        modelLayout->addWidget(new QLabel(\"none\"));\n    if (project.problems.empty())\n        problemLayout->addWidget(new QLabel(\"none\"));\n    _output_stream.setString(&_output_string);\n\n    QSettings settings;\n    settings.beginGroup(\"coursera\");\n    ui->storePassword->setChecked(settings.value(\"storeLogin\",false).toBool());\n    ui->login->setText(settings.value(\"courseraEmail\").toString());\n    settings.beginGroup(project.assignmentKey);\n    ui->password->setText(settings.value(\"token\").toString());\n    settings.endGroup();\n    settings.endGroup();\n\n}\n\nMOOCSubmission::~MOOCSubmission()\n{\n    QSettings settings;\n    settings.beginGroup(\"coursera\");\n    bool storeLogin = ui->storePassword->isChecked();\n    settings.setValue(\"storeLogin\", storeLogin);\n    if (storeLogin) {\n        settings.setValue(\"courseraEmail\",ui->login->text());\n        settings.beginGroup(project.assignmentKey);\n        settings.setValue(\"token\",ui->password->text());\n        settings.endGroup();\n    }\n    settings.endGroup();\n    delete ui;\n}\n\nvoid MOOCSubmission::disableUI()\n{\n    ui->loginGroup->setEnabled(false);\n    ui->modelBox->setEnabled(false);\n    ui->problemBox->setEnabled(false);\n    ui->submissionTerms_checkBox->setEnabled(false);\n    ui->runButton->setText(\"Abort\");\n}\n\nvoid MOOCSubmission::enableUI()\n{\n    ui->loginGroup->setEnabled(true);\n    ui->modelBox->setEnabled(true);\n    ui->problemBox->setEnabled(true);\n    ui->submissionTerms_checkBox->setEnabled(true);\n    ui->runButton->setText(\"Run and submit\");\n}\n\nvoid MOOCSubmission::cancelOperation()\n{\n    switch (_cur_phase) {\n    case S_NONE:\n        return;\n    case S_WAIT_PWD:\n        disconnect(mw, &MainWindow::finished, this, &MOOCSubmission::rcvLoginCheckResponse);\n        break;\n    case S_WAIT_SOLVE:\n        disconnect(mw, &MainWindow::finished, this, &MOOCSubmission::solverFinished);\n        mw->stop();\n        break;\n    case S_WAIT_SUBMIT:\n        disconnect(reply, &QNetworkReply::finished, this, &MOOCSubmission::rcvSubmissionResponse);\n        break;\n    }\n    ui->textBrowser->insertPlainText(\"Aborted.\\n\");\n    _cur_phase = S_NONE;\n    enableUI();\n}\n\nvoid MOOCSubmission::reject()\n{\n    if (_cur_phase != S_NONE &&\n            QMessageBox::warning(this, \"MiniZinc IDE\",\n                                 \"Do you want to close this window and abort the \"+project.moocName+\" submission?\",\n                                 QMessageBox::Close| QMessageBox::Cancel,\n                                 QMessageBox::Cancel) == QMessageBox::Cancel) {\n        return;\n    }\n    cancelOperation();\n    QDialog::reject();\n}\n\nvoid MOOCSubmission::solveNext() {\n    int n_problems = project.problems.size();\n\n    bool done = false;\n    do {\n        _current_model++;\n        if (_current_model < n_problems) {\n            QCheckBox* cb = qobject_cast<QCheckBox*>(ui->problemBox->layout()->itemAt(_current_model)->widget());\n            done = cb->isChecked();\n        } else {\n            done = true;\n        }\n    } while (!done);\n    if (_current_model < n_problems) {\n\n        MOOCAssignmentItem& item = project.problems[_current_model];\n        connect(mw, &MainWindow::finished, this, &MOOCSubmission::solverFinished);\n        ui->textBrowser->insertPlainText(\"Running \"+item.name+\" with time out \" + QString().setNum(item.timeout)+ \"s\\n\");\n        _cur_phase = S_WAIT_SOLVE;\n        _output_string = \"\";\n        mw->addOutput(\"<div style='color:orange;'>Running \"+project.moocName+\" submission \"+item.name+\"</div><br>\\n\");\n\n        auto sc = mw->getCurrentSolverConfig();\n        SolverConfiguration solverConfig(*sc);\n        solverConfig.timeLimit = item.timeout * 1000; // Override config time limit\n\n        QStringList files = { item.data };\n\n        // When we have a checker, use --output-mode checker\n        auto mzc = item.model;\n        mzc.replace(mzc.length() - 1, 1, \"c\");\n        if (mw->getProject().contains(mzc)) {\n            files << mzc;\n            solverConfig.extraOptions[\"output-mode\"] = \"checker\";\n        } else if (mw->getProject().contains(mzc + \".mzn\")) {\n            files << mzc + \".mzn\";\n            solverConfig.extraOptions[\"output-mode\"] = \"checker\";\n        }\n\n        mw->run(solverConfig, item.model, files, QStringList(), QStringList(), &_output_stream);\n        return;\n    } else {\n        submitToMOOC();\n    }\n}\n\nvoid MOOCSubmission::submitToMOOC()\n{\n    QUrl url(project.submissionURL);\n    QNetworkRequest request;\n    request.setUrl(url);\n    request.setRawHeader(QByteArray(\"Cache-Control\"),QByteArray(\"no-cache\"));\n    request.setHeader(QNetworkRequest::ContentTypeHeader, \"application/json\");\n\n    // add models\n    QStringList allfiles = mw->getProject().files();\n    for (int i=0; i<project.models.size(); i++) {\n        const MOOCAssignmentItem& item = project.models.at(i);\n        QCheckBox* cb = qobject_cast<QCheckBox*>(ui->modelBox->layout()->itemAt(i)->widget());\n        if (cb->isChecked()) {\n            QFile file(item.model);\n            if (file.open(QFile::ReadOnly | QFile::Text)) {\n                QJsonObject output;\n                QTextStream ts(&file);\n                output[\"output\"] = ts.readAll();\n                _parts[item.id] = output;\n            } else {\n                ui->textBrowser->insertPlainText(\"Error: could not open \"+item.name+\"\\n\");\n                ui->textBrowser->insertPlainText(\"Skipping.\\n\");\n            }\n        }\n    }\n\n    _submission[\"assignmentKey\"] = project.assignmentKey;\n    _submission[\"secret\"] = ui->password->text();\n    _submission[\"submitterEmail\"] = ui->login->text();\n    _submission[\"parts\"] = _parts;\n    if (project.sendMeta) {\n        QJsonObject metadata;\n        metadata[\"version\"] = MznDriver::get().version().toString();\n        metadata[\"versionString\"] = MznDriver::get().minizincVersionString();\n        metadata[\"solver\"] = mw->getCurrentSolver()->id + \" \" + mw->getCurrentSolver()->version;\n        metadata[\"arch\"] = QSysInfo::currentCpuArchitecture();\n        metadata[\"kernelType\"] = QSysInfo::kernelType();\n        metadata[\"kernelVersion\"] = QSysInfo::kernelVersion();\n        metadata[\"productType\"] = QSysInfo::productType();\n        metadata[\"productVersion\"] = QSysInfo::productVersion();\n        metadata[\"prettyProductName\"] = QSysInfo::prettyProductName();\n        _submission[\"metadata\"] = metadata;\n    }\n    if (project.history != nullptr) {\n        _submission[\"history\"] = project.history->toJSON();\n    }\n\n    QJsonDocument doc(_submission);\n\n    _cur_phase = S_WAIT_SUBMIT;\n    reply = IDE::instance()->networkManager->post(request,doc.toJson());\n#if QT_VERSION >= 0x060000\n    connect(reply, &QNetworkReply::errorOccurred, this, &MOOCSubmission::rcvErrorResponse);\n#else\n    connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &MOOCSubmission::rcvErrorResponse);\n#endif\n    connect(reply, &QNetworkReply::finished, this, &MOOCSubmission::rcvSubmissionResponse);\n    ui->textBrowser->insertPlainText(\"Submitting to \"+project.moocName+\" for grading...\\n\");\n}\n\nvoid MOOCSubmission::rcvSubmissionResponse()\n{\n    disconnect(reply, &QNetworkReply::finished, this, &MOOCSubmission::rcvSubmissionResponse);\n#if QT_VERSION >= 0x060000\n    disconnect(reply, &QNetworkReply::errorOccurred, this, &MOOCSubmission::rcvErrorResponse);\n#else\n    disconnect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &MOOCSubmission::rcvErrorResponse);\n#endif\n    reply->deleteLater();\n    int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();\n    if (status >= 400) {\n        ui->textBrowser->insertPlainText(QString(\"Error %1: \").arg(status));\n    } else if (project.history != nullptr) {\n        project.history->commit();\n    }\n    QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());\n    if (doc.object().contains(\"message\")) {\n        ui->textBrowser->insertPlainText(\"== \"+doc.object()[\"message\"].toString()+\"\\n\");\n    }\n    if (doc.object().contains(\"details\")) {\n        QJsonObject details = doc.object()[\"details\"].toObject();\n        if (details.contains(\"learnerMessage\")) {\n            ui->textBrowser->insertPlainText(\"== \"+details[\"learnerMessage\"].toString()+\"\\n\");\n        }\n    }\n\n    ui->textBrowser->insertPlainText(\"Done.\\n\");\n    _cur_phase = S_NONE;\n    ui->runButton->setText(\"Done.\");\n    ui->runButton->setEnabled(false);\n    ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true);\n}\n\nvoid MOOCSubmission::solverFinished()\n{\n    disconnect(mw, &MainWindow::finished, this, &MOOCSubmission::solverFinished);\n\n    QStringList solutions = _output_string.split(\"----------\");\n    if (solutions.size() >= 2) {\n        _output_string = solutions[solutions.size()-2]+\"----------\"+solutions[solutions.size()-1];\n    }\n    if (_output_string.size()==0 || _output_string[_output_string.size()-1] != '\\n')\n        _output_string += \"\\n\";\n\n    QJsonObject output;\n    output[\"output\"] = _output_string;\n    _parts[project.problems[_current_model].id] = output;\n    ui->textBrowser->insertPlainText(\"Finished\\n\");\n    solveNext();\n}\n\nvoid MOOCSubmission::on_runButton_clicked()\n{\n    if (_cur_phase==S_NONE) {\n        ui->textBrowser->clear();\n        QString email = ui->login->text();\n        if (email.isEmpty()) {\n            QMessageBox::warning(this, \"MiniZinc IDE\",\n                                 \"Enter an email address for \"+project.moocName+\" login!\");\n            return;\n        }\n        if (ui->password->text().isEmpty()) {\n            QMessageBox::warning(this, \"MiniZinc IDE\",\n                                 \"Enter an assignment key!\");\n            return;\n        }\n\n        // Send empty request to check password\n        QUrl url(project.submissionURL);\n        QNetworkRequest request;\n        request.setUrl(url);\n        request.setRawHeader(QByteArray(\"Cache-Control\"),QByteArray(\"no-cache\"));\n        request.setHeader(QNetworkRequest::ContentTypeHeader, \"application/json\");\n        QJsonObject checkPwdSubmission;\n        checkPwdSubmission[\"assignmentKey\"] = project.assignmentKey;\n        checkPwdSubmission[\"secret\"] = ui->password->text();\n        checkPwdSubmission[\"submitterEmail\"] = ui->login->text();\n        QJsonObject emptyParts;\n        checkPwdSubmission[\"parts\"] = emptyParts;\n\n        QJsonDocument doc(checkPwdSubmission);\n\n        _cur_phase = S_WAIT_PWD;\n        disableUI();\n        reply = IDE::instance()->networkManager->post(request,doc.toJson());\n#if QT_VERSION >= 0x060000\n        connect(reply, &QNetworkReply::errorOccurred, this, &MOOCSubmission::rcvErrorResponse);\n#else\n        connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &MOOCSubmission::rcvErrorResponse);\n#endif\n        connect(reply, &QNetworkReply::finished, this, &MOOCSubmission::rcvLoginCheckResponse);\n        ui->textBrowser->insertPlainText(\"Checking login and assignment token...\\n\");\n    } else {\n        cancelOperation();\n    }\n}\n\nvoid MOOCSubmission::rcvLoginCheckResponse()\n{\n    disconnect(reply, &QNetworkReply::finished, this, &MOOCSubmission::rcvLoginCheckResponse);\n#if QT_VERSION >= 0x060000\n    disconnect(reply, &QNetworkReply::errorOccurred, this, &MOOCSubmission::rcvErrorResponse);\n#else\n    disconnect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &MOOCSubmission::rcvErrorResponse);\n#endif\n    reply->deleteLater();\n\n    QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());\n    if (doc.object().contains(\"message\") && (doc.object()[\"message\"].toString().endsWith(\"but found: Set()\") || doc.object()[\"message\"].toString().endsWith(\"Success\"))) {\n        ui->textBrowser->insertPlainText(\"Done.\\n\");\n        _current_model = -1;\n        for (int i=0; i<project.problems.size(); i++) {\n            _parts[project.problems[i].id] = QJsonObject();\n        }\n        for (int i=0; i<project.models.size(); i++) {\n            _parts[project.models[i].id] = QJsonObject();\n        }\n        solveNext();\n    } else {\n        if (doc.object().contains(\"message\")) {\n            ui->textBrowser->insertPlainText(\"== \"+doc.object()[\"message\"].toString()+\"\\n\");\n        }\n        if (doc.object().contains(\"details\")) {\n            QJsonObject details = doc.object()[\"details\"].toObject();\n            if (details.contains(\"learnerMessage\")) {\n                ui->textBrowser->insertPlainText(\">> \"+details[\"learnerMessage\"].toString()+\"\\n\");\n            }\n        }\n        ui->textBrowser->insertPlainText(\"Done.\\n\");\n        _cur_phase = S_NONE;\n        enableUI();\n    }\n}\n\nvoid MOOCSubmission::rcvErrorResponse(QNetworkReply::NetworkError e)\n{\n    switch (e) {\n    case QNetworkReply::ProtocolInvalidOperationError:\n    case QNetworkReply::AuthenticationRequiredError:\n        // ProtocolInvalidOperationError expected when checking auth succeeds\n        // AuthenticationRequiredError expected when checking auth fails\n        break;\n    default:\n        // Unknown networking error\n        // TODO: can probably remove this check since it should be covered by the ProtocolInvalidOperationError\n        if (!reply->errorString().endsWith(\"Bad Request\")) {\n            ui->textBrowser->insertPlainText(\"Error:\\n\" + reply->errorString() + \"\\n\\n\");\n        }\n    }\n}\n\nvoid MOOCSubmission::on_storePassword_toggled(bool checked)\n{\n    if (!checked) {\n        QSettings settings;\n        settings.beginGroup(\"coursera\");\n        settings.remove(\"\");\n        settings.setValue(\"storeLogin\", false);\n        settings.endGroup();\n    }\n}\n\nvoid MOOCSubmission::on_submissionTerms_checkBox_stateChanged(int arg1)\n{\n    ui->runButton->setEnabled(arg1);\n}\n"
  },
  {
    "path": "MiniZincIDE/moocsubmission.h",
    "content": "#ifndef MOOCSUBMISSION_H\n#define MOOCSUBMISSION_H\n\n#include <QDialog>\n#include <QTextStream>\n#include <QJsonObject>\n#include <QNetworkReply>\n#include <QFileInfo>\n#include \"history.h\"\n\nclass QNetworkReply;\nclass MainWindow;\n\nnamespace Ui {\nclass MOOCSubmission;\n}\n\nclass MOOCAssignmentItem {\npublic:\n    QString id;\n    QString model;\n    QString data;\n    int timeout;\n    QString name;\n    bool required;\n    MOOCAssignmentItem(QString id0, QString model0, QString data0, QString timeout0, QString name0, bool required0 = false)\n        : id(id0), model(model0), data(data0), timeout(timeout0.toInt()), name(name0), required(required0) {}\n    MOOCAssignmentItem(QString id0, QString model0, QString name0, bool required0 = false)\n        : id(id0), model(model0), timeout(-1), name(name0), required(required0) {}\n};\n\nclass MOOCAssignment {\npublic:\n    QString name;\n    QString assignmentKey;\n    QString moocName;\n    QString moocPasswordString;\n    QString submissionURL;\n    QString submissionTerms;\n    bool sendMeta;\n    QList<MOOCAssignmentItem> problems;\n    QList<MOOCAssignmentItem> models;\n    History* history = nullptr;\n\n    QString moocFile;\n    QJsonObject json;\n\n    MOOCAssignment(void) {}\n    MOOCAssignment(const QString& file);\n\n    ~MOOCAssignment() { delete history; }\n\nprivate:\n    void loadJSON(const QJsonObject& obj, const QFileInfo& fi);\n};\n\nclass MOOCSubmission : public QDialog\n{\n    Q_OBJECT\n    friend class TestIDE;\n\npublic:\n    explicit MOOCSubmission(MainWindow* mw, MOOCAssignment& cp);\n    ~MOOCSubmission();\n\nprotected:\n    enum State { S_NONE, S_WAIT_PWD, S_WAIT_SUBMIT, S_WAIT_SOLVE } _cur_phase;\n    int _current_model;\n\n    QTextStream _output_stream;\n    QString _output_string;\n\n    QJsonObject _submission;\n    QJsonObject _parts;\n\n    MOOCAssignment& project;\n    MainWindow* mw;\n    QNetworkReply* reply;\n\n    void disableUI(void);\n    void enableUI(void);\n    void cancelOperation(void);\n    void solveNext(void);\n\n    bool checkerEnabled;\n\npublic slots:\n    void reject();\n\nprivate slots:\n\n    void submitToMOOC();\n    void rcvSubmissionResponse();\n    void solverFinished();\n\n    void on_runButton_clicked();\n    void rcvLoginCheckResponse();\n    void rcvErrorResponse(QNetworkReply::NetworkError);\n\n    void on_storePassword_toggled(bool checked);\n    void on_submissionTerms_checkBox_stateChanged(int arg1);\n\nprivate:\n    Ui::MOOCSubmission *ui;\n};\n\n#endif // MOOCSUBMISSION_H\n"
  },
  {
    "path": "MiniZincIDE/moocsubmission.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>MOOCSubmission</class>\n <widget class=\"QDialog\" name=\"MOOCSubmission\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>590</width>\n    <height>720</height>\n   </rect>\n  </property>\n  <property name=\"sizePolicy\">\n   <sizepolicy hsizetype=\"Preferred\" vsizetype=\"MinimumExpanding\">\n    <horstretch>0</horstretch>\n    <verstretch>0</verstretch>\n   </sizepolicy>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Submit to Coursera</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n   <property name=\"spacing\">\n    <number>0</number>\n   </property>\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"QScrollArea\" name=\"scrollArea\">\n     <property name=\"widgetResizable\">\n      <bool>true</bool>\n     </property>\n     <widget class=\"QWidget\" name=\"scrollAreaWidgetContents\">\n      <property name=\"geometry\">\n       <rect>\n        <x>0</x>\n        <y>0</y>\n        <width>574</width>\n        <height>1009</height>\n       </rect>\n      </property>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_8\">\n       <item>\n        <widget class=\"QGroupBox\" name=\"nonSubmissionGroup\">\n         <property name=\"font\">\n          <font>\n           <bold>true</bold>\n          </font>\n         </property>\n         <property name=\"styleSheet\">\n          <string notr=\"true\">QGroupBox::title { color: red; }</string>\n         </property>\n         <property name=\"title\">\n          <string>WARNING</string>\n         </property>\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_5\">\n          <item>\n           <widget class=\"QLabel\" name=\"label\">\n            <property name=\"text\">\n             <string>There are models currently open which are not part of the submission and will not be included:</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_6\">\n            <property name=\"leftMargin\">\n             <number>6</number>\n            </property>\n            <item>\n             <widget class=\"QLabel\" name=\"nonSubmissionLabel\">\n              <property name=\"text\">\n               <string/>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </item>\n         </layout>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"problemBox\">\n         <property name=\"title\">\n          <string>Problems to run and submit</string>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"modelBox\">\n         <property name=\"title\">\n          <string>Models to submit</string>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"loginGroup\">\n         <property name=\"title\">\n          <string>Login information</string>\n         </property>\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n          <item>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n            <property name=\"sizeConstraint\">\n             <enum>QLayout::SetMinimumSize</enum>\n            </property>\n            <item>\n             <widget class=\"QLabel\" name=\"loginEmailLabel\">\n              <property name=\"text\">\n               <string>Coursera login email:</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLineEdit\" name=\"login\"/>\n            </item>\n           </layout>\n          </item>\n          <item>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n            <property name=\"sizeConstraint\">\n             <enum>QLayout::SetMinimumSize</enum>\n            </property>\n            <item>\n             <widget class=\"QLabel\" name=\"loginTokenLabel\">\n              <property name=\"text\">\n               <string>Assignment token:</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLineEdit\" name=\"password\">\n              <property name=\"echoMode\">\n               <enum>QLineEdit::Password</enum>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </item>\n          <item>\n           <widget class=\"QLabel\" name=\"courseraTokenWarningLabel\">\n            <property name=\"sizePolicy\">\n             <sizepolicy hsizetype=\"Preferred\" vsizetype=\"MinimumExpanding\">\n              <horstretch>0</horstretch>\n              <verstretch>0</verstretch>\n             </sizepolicy>\n            </property>\n            <property name=\"text\">\n             <string>Note that this is your assignment submission token, not your Coursera password.</string>\n            </property>\n            <property name=\"textFormat\">\n             <enum>Qt::RichText</enum>\n            </property>\n            <property name=\"wordWrap\">\n             <bool>true</bool>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n            <property name=\"sizeConstraint\">\n             <enum>QLayout::SetMinimumSize</enum>\n            </property>\n            <item>\n             <widget class=\"QCheckBox\" name=\"storePassword\">\n              <property name=\"text\">\n               <string>Remember login details</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <spacer name=\"horizontalSpacer\">\n              <property name=\"orientation\">\n               <enum>Qt::Horizontal</enum>\n              </property>\n              <property name=\"sizeHint\" stdset=\"0\">\n               <size>\n                <width>40</width>\n                <height>20</height>\n               </size>\n              </property>\n             </spacer>\n            </item>\n           </layout>\n          </item>\n         </layout>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"groupBox\">\n         <property name=\"title\">\n          <string>Selected solver configuration for running models</string>\n         </property>\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n          <item>\n           <widget class=\"QLabel\" name=\"selectedSolver\">\n            <property name=\"text\">\n             <string>Selected solver</string>\n            </property>\n           </widget>\n          </item>\n         </layout>\n        </widget>\n       </item>\n       <item>\n        <spacer name=\"verticalSpacer\">\n         <property name=\"orientation\">\n          <enum>Qt::Vertical</enum>\n         </property>\n         <property name=\"sizeType\">\n          <enum>QSizePolicy::Minimum</enum>\n         </property>\n         <property name=\"sizeHint\" stdset=\"0\">\n          <size>\n           <width>20</width>\n           <height>10</height>\n          </size>\n         </property>\n        </spacer>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"submissionTerms_groupBox\">\n         <property name=\"title\">\n          <string>Terms of submission</string>\n         </property>\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_7\">\n          <item>\n           <widget class=\"QTextBrowser\" name=\"submissionTerms_textBrowser\"/>\n          </item>\n          <item>\n           <widget class=\"QCheckBox\" name=\"submissionTerms_checkBox\">\n            <property name=\"text\">\n             <string>I have read and accept the above terms and conditions</string>\n            </property>\n           </widget>\n          </item>\n         </layout>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QPushButton\" name=\"runButton\">\n         <property name=\"text\">\n          <string>Run and submit</string>\n         </property>\n         <property name=\"autoDefault\">\n          <bool>false</bool>\n         </property>\n         <property name=\"default\">\n          <bool>true</bool>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <spacer name=\"verticalSpacer_2\">\n         <property name=\"orientation\">\n          <enum>Qt::Vertical</enum>\n         </property>\n         <property name=\"sizeType\">\n          <enum>QSizePolicy::Minimum</enum>\n         </property>\n         <property name=\"sizeHint\" stdset=\"0\">\n          <size>\n           <width>20</width>\n           <height>10</height>\n          </size>\n         </property>\n        </spacer>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"outputGroup\">\n         <property name=\"title\">\n          <string>Submission output</string>\n         </property>\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n          <item>\n           <widget class=\"QTextBrowser\" name=\"textBrowser\"/>\n          </item>\n         </layout>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n         <property name=\"orientation\">\n          <enum>Qt::Horizontal</enum>\n         </property>\n         <property name=\"standardButtons\">\n          <set>QDialogButtonBox::Close</set>\n         </property>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>MOOCSubmission</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>248</x>\n     <y>254</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>157</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>MOOCSubmission</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>316</x>\n     <y>260</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>286</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "MiniZincIDE/mznide-makefile.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSPrincipalClass</key>\n\t<string>NSApplication</string>\n\t<key>CFBundleIconFile</key>\n\t<string>mznide.icns</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>NSHighResolutionCapable</key>\n\t<string>True</string>\n\t<key>CFBundleExecutable</key>\n\t<string>MiniZincIDE</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>MiniZincIDE</string>\n    <key>CFBundleVersion</key>\n    <string>@FULL_VERSION@</string>\n    <key>CFBundleShortVersionString</key>\n    <string>@FULL_VERSION@</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>org.minizinc.MiniZincIDE</string>\n\t<key>CFBundleDocumentTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>MiniZinc Project</string>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>mzp</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>FlatZinc instance</string>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>fzn</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>MiniZinc Data</string>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>dzn</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>MiniZinc model</string>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>mzn</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>UTExportedTypeDeclarations</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>UTTypeIdentifier</key>\n\t\t\t<string>org.minizinc.minizinc</string>\n\t\t\t<key>UTTypeReferenceURL</key>\n\t\t\t<string>http://www.minizinc.org/</string>\n\t\t\t<key>UTTypeDescription</key>\n\t\t\t<string>MiniZinc model</string>\n\t\t\t<key>UTTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>UTTypeConformsTo</key>\n\t\t\t<array>\n\t\t\t\t<string>public.plain-text</string>\n\t\t\t</array>\n\t\t\t<key>UTTypeTagSpecification</key>\n\t\t\t<dict>\n\t\t\t\t<key>public.filename-extension</key>\n\t\t\t\t<array>\n\t\t\t\t\t<string>mzn</string>\n\t\t\t\t</array>\n\t\t\t</dict>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>UTTypeIdentifier</key>\n\t\t\t<string>org.minizinc.flatzinc</string>\n\t\t\t<key>UTTypeReferenceURL</key>\n\t\t\t<string>http://www.minizinc.org/</string>\n\t\t\t<key>UTTypeDescription</key>\n\t\t\t<string>FlatZinc model</string>\n\t\t\t<key>UTTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>UTTypeConformsTo</key>\n\t\t\t<array>\n\t\t\t\t<string>public.plain-text</string>\n\t\t\t</array>\n\t\t\t<key>UTTypeTagSpecification</key>\n\t\t\t<dict>\n\t\t\t\t<key>public.filename-extension</key>\n\t\t\t\t<array>\n\t\t\t\t\t<string>fzn</string>\n\t\t\t\t</array>\n\t\t\t</dict>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>UTTypeIdentifier</key>\n\t\t\t<string>org.minizinc.minizinc-data</string>\n\t\t\t<key>UTTypeReferenceURL</key>\n\t\t\t<string>http://www.minizinc.org/</string>\n\t\t\t<key>UTTypeDescription</key>\n\t\t\t<string>MiniZinc data</string>\n\t\t\t<key>UTTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>UTTypeConformsTo</key>\n\t\t\t<array>\n\t\t\t\t<string>public.plain-text</string>\n\t\t\t</array>\n\t\t\t<key>UTTypeTagSpecification</key>\n\t\t\t<dict>\n\t\t\t\t<key>public.filename-extension</key>\n\t\t\t\t<array>\n\t\t\t\t\t<string>dzn</string>\n\t\t\t\t</array>\n\t\t\t</dict>\n\t\t</dict>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "MiniZincIDE/mznide-xcode.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSPrincipalClass</key>\n\t<string>NSApplication</string>\n\t<key>CFBundleIconFile</key>\n\t<string>mznide.icns</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>NSHighResolutionCapable</key>\n\t<string>True</string>\n\t<key>CFBundleExecutable</key>\n\t<string>MiniZincIDE</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>MiniZincIDE</string>\n    <key>CFBundleVersion</key>\n    <string>${DYLIB_CURRENT_VERSION}</string>\n    <key>CFBundleShortVersionString</key>\n    <string>${DYLIB_CURRENT_VERSION}</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>org.minizinc.MiniZincIDE</string>\n\t<key>CFBundleDocumentTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>MiniZinc Project</string>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>mzp</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>FlatZinc instance</string>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>fzn</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>MiniZinc Data</string>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>dzn</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>MiniZinc model</string>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>mzn</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>UTExportedTypeDeclarations</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>UTTypeIdentifier</key>\n\t\t\t<string>org.minizinc.minizinc</string>\n\t\t\t<key>UTTypeReferenceURL</key>\n\t\t\t<string>http://www.minizinc.org/</string>\n\t\t\t<key>UTTypeDescription</key>\n\t\t\t<string>MiniZinc model</string>\n\t\t\t<key>UTTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>UTTypeConformsTo</key>\n\t\t\t<array>\n\t\t\t\t<string>public.plain-text</string>\n\t\t\t</array>\n\t\t\t<key>UTTypeTagSpecification</key>\n\t\t\t<dict>\n\t\t\t\t<key>public.filename-extension</key>\n\t\t\t\t<array>\n\t\t\t\t\t<string>mzn</string>\n\t\t\t\t</array>\n\t\t\t</dict>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>UTTypeIdentifier</key>\n\t\t\t<string>org.minizinc.flatzinc</string>\n\t\t\t<key>UTTypeReferenceURL</key>\n\t\t\t<string>http://www.minizinc.org/</string>\n\t\t\t<key>UTTypeDescription</key>\n\t\t\t<string>FlatZinc model</string>\n\t\t\t<key>UTTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>UTTypeConformsTo</key>\n\t\t\t<array>\n\t\t\t\t<string>public.plain-text</string>\n\t\t\t</array>\n\t\t\t<key>UTTypeTagSpecification</key>\n\t\t\t<dict>\n\t\t\t\t<key>public.filename-extension</key>\n\t\t\t\t<array>\n\t\t\t\t\t<string>fzn</string>\n\t\t\t\t</array>\n\t\t\t</dict>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>UTTypeIdentifier</key>\n\t\t\t<string>org.minizinc.minizinc-data</string>\n\t\t\t<key>UTTypeReferenceURL</key>\n\t\t\t<string>http://www.minizinc.org/</string>\n\t\t\t<key>UTTypeDescription</key>\n\t\t\t<string>MiniZinc data</string>\n\t\t\t<key>UTTypeIconFile</key>\n\t\t\t<string>mznide.icns</string>\n\t\t\t<key>UTTypeConformsTo</key>\n\t\t\t<array>\n\t\t\t\t<string>public.plain-text</string>\n\t\t\t</array>\n\t\t\t<key>UTTypeTagSpecification</key>\n\t\t\t<dict>\n\t\t\t\t<key>public.filename-extension</key>\n\t\t\t\t<array>\n\t\t\t\t\t<string>dzn</string>\n\t\t\t\t</array>\n\t\t\t</dict>\n\t\t</dict>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "MiniZincIDE/outputdockwidget.cpp",
    "content": "#include \"outputdockwidget.h\"\n#include <QCloseEvent>\n\nvoid OutputDockWidget::closeEvent(QCloseEvent *event) {\n    if (isFloating())\n        setFloating(false);\n    else\n        hide();\n    event->ignore();\n}\n"
  },
  {
    "path": "MiniZincIDE/outputdockwidget.h",
    "content": "#ifndef OUTPUTDOCKWIDGET_H\n#define OUTPUTDOCKWIDGET_H\n\n#include <QDockWidget>\n\nclass OutputDockWidget : public QDockWidget\n{\n    Q_OBJECT\npublic:\n    explicit OutputDockWidget(QWidget *parent = 0)\n        : QDockWidget(parent) {}\n\nsignals:\n\npublic slots:\nprotected:\n    void closeEvent(QCloseEvent *event);\n};\n\n#endif // OUTPUTDOCKWIDGET_H\n"
  },
  {
    "path": "MiniZincIDE/outputwidget.cpp",
    "content": "#include \"outputwidget.h\"\r\n#include \"ui_outputwidget.h\"\r\n\r\n#include \"highlighter.h\"\r\n\r\n#include <cmath>\r\n\r\n#include <QTextDocumentFragment>\r\n#include <QJsonObject>\r\n#include <QTextTable>\r\n#include <QTextFrame>\r\n#include <QPainter>\r\n#include <QScrollBar>\r\n#include <QClipboard>\r\n#include <QTextStream>\r\n#include <QDebug>\r\n#include <QMenu>\r\n#include <QPainterPath>\r\n#include <QCheckBox>\r\n#include \"ideutils.h\"\r\n#include \"highlighter.h\"\r\n\r\nOutputWidget::OutputWidget(QWidget *parent) :\r\n    QWidget(parent),\r\n    ui(new Ui::OutputWidget)\r\n{\r\n    ui->setupUi(this);\r\n\r\n    auto* sectionMenu = new QMenu(this);\r\n    ui->sectionMenu_pushButton->hide();\r\n    ui->sectionMenu_pushButton->setMenu(sectionMenu);\r\n    connect(sectionMenu, &QMenu::aboutToShow, this, [=] () {\r\n        for (auto* action : sectionMenu->actions()) {\r\n            action->setChecked(isSectionVisible(action->text()));\r\n        }\r\n    });\r\n\r\n    auto* messageTypeMenu = new QMenu(this);\r\n    ui->messageTypeMenu_pushButton->hide();\r\n    ui->messageTypeMenu_pushButton->setMenu(messageTypeMenu);\r\n    connect(messageTypeMenu, &QMenu::aboutToShow, this, [=] () {\r\n        for (auto* action : messageTypeMenu->actions()) {\r\n            action->setChecked(isMessageTypeVisible(action->text()));\r\n        }\r\n    });\r\n\r\n    ui->textBrowser->installEventFilter(this);\r\n    ui->textBrowser->viewport()->installEventFilter(this);\r\n    ui->textBrowser->setOpenLinks(false);\r\n    connect(ui->textBrowser, &QTextBrowser::anchorClicked, this, &OutputWidget::onAnchorClicked);\r\n\r\n    auto* arrow = new OutputWidgetArrow;\r\n    arrow->setParent(this);\r\n    ui->textBrowser->document()->documentLayout()->registerHandler(TextObject::Arrow, arrow);\r\n    _frame = ui->textBrowser->document()->rootFrame();\r\n\r\n    _headerTableFormat.setWidth(QTextLength(QTextLength::PercentageLength, 100));\r\n    _headerTableFormat.setBorder(0);\r\n    _headerTableFormat.setCellSpacing(0);\r\n    _headerTableFormat.setProperty(Property::ToggleFrame, true);\r\n\r\n    QFontMetrics metrics(ui->textBrowser->font());\r\n    auto spaceNeeded = metrics.height() + 4;\r\n    auto cwc = { QTextLength(QTextLength::FixedLength, spaceNeeded) };\r\n    _headerTableFormat.setColumnWidthConstraints(cwc);\r\n\r\n    _arrowFormat.setObjectType(TextObject::Arrow);\r\n    _arrowFormat.setForeground(Qt::gray);\r\n\r\n    _frameFormat.setLeftMargin(spaceNeeded);\r\n    _frameFormat.setProperty(Property::Expanded, true);\r\n\r\n    _rightAlignBlockFormat.setAlignment(Qt::AlignRight);\r\n    _rightAlignBlockFormat.setLeftMargin(10);\r\n#ifdef Q_OS_MAC\r\n    ui->toggleAll_pushButton->setMinimumWidth(85);\r\n    layout()->setSpacing(8);\r\n\r\n    ui->buttons_widget->setAttribute(Qt::WA_LayoutUsesWidgetRect);\r\n    ui->sectionButtons_widget->setAttribute(Qt::WA_LayoutUsesWidgetRect);\r\n    ui->messageTypeButtons_widget->setAttribute(Qt::WA_LayoutUsesWidgetRect);\r\n    ui->sectionMenu_pushButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);\r\n    ui->messageTypeMenu_pushButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);\r\n    ui->toggleAll_pushButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);\r\n#else\r\n    setStyleSheet(\"QPushButton { padding: 2px 6px; }\");\r\n#endif\r\n    _contextMenu = new QMenu(this);\r\n    _contextMenu->addAction(\"Copy selected\", this, [=] () { copySelectionToClipboard(false); });\r\n    _contextMenu->addAction(\"Copy selected including hidden\", this, [=] () { copySelectionToClipboard(true); });\r\n    _contextMenu->addSeparator();\r\n    _contextMenu->addAction(\"Select All\", this, [=] () { ui->textBrowser->selectAll(); });\r\n\r\n    connect(ui->textBrowser, &QTextBrowser::customContextMenuRequested, this, &OutputWidget::onBrowserContextMenu);\r\n    ui->textBrowser->setContextMenuPolicy(Qt::CustomContextMenu);\r\n}\r\n\r\nOutputWidget::~OutputWidget()\r\n{\r\n    delete ui;\r\n}\r\n\r\nQString OutputWidget::lastTraceLoc(const QString &newTraceLoc)\r\n{\r\n    QString ltl = _lastTraceLoc;\r\n    _lastTraceLoc = newTraceLoc;\r\n    return ltl;\r\n}\r\n\r\nvoid OutputWidget::setTheme(const Theme& theme, bool darkMode)\r\n{\r\n    _defaultCharFormat.setForeground(theme.textColor.get(darkMode));\r\n    _noticeCharFormat.setForeground(theme.functionColor.get(darkMode));\r\n    _errorCharFormat.setForeground(theme.errorColor.get(darkMode));\r\n    _errorCharFormat.setForeground(theme.errorColor.get(darkMode));\r\n    _infoCharFormat.setForeground(Qt::gray);\r\n    _commentCharFormat.setForeground(theme.commentColor.get(darkMode));\r\n\r\n    ui->textBrowser->viewport()->setStyleSheet(theme.styleSheet(darkMode));\r\n}\r\n\r\nvoid OutputWidget::scrollToBottom()\r\n{\r\n    auto* scrollbar = ui->textBrowser->verticalScrollBar();\r\n    scrollbar->setValue(scrollbar->maximum());\r\n}\r\n\r\nvoid OutputWidget::startExecution(const QString& label)\r\n{\r\n    _checkerOutput.clear();\r\n\r\n    TextLayoutLock lock(this);\r\n    auto c = ui->textBrowser->textCursor();\r\n    c.movePosition(QTextCursor::End);\r\n\r\n    // Create table containing heading\r\n    auto* table = c.insertTable(1, 3, _headerTableFormat);\r\n\r\n    auto leftSideCursor = table->cellAt(0, 0).firstCursorPosition();\r\n    leftSideCursor.insertText(QString(QChar::ObjectReplacementCharacter), _arrowFormat);\r\n\r\n    auto headerCursor = table->cellAt(0, 1).firstCursorPosition();\r\n    headerCursor.insertText(label, noticeCharFormat());\r\n\r\n    auto rightSideCursor = table->cellAt(0, 2).firstCursorPosition();\r\n    rightSideCursor.setBlockFormat(_rightAlignBlockFormat);\r\n    _statusCursor = rightSideCursor;\r\n\r\n    // Create frame which will contain children\r\n    c.movePosition(QTextCursor::End);\r\n    _frame = c.insertFrame(_frameFormat);\r\n\r\n    _hadServerUrl = false;\r\n\r\n    _solutionCount = 0;\r\n    _effectiveSolutionLimit = _solutionLimit;\r\n}\r\n\r\nvoid OutputWidget::associateProfilerExecution(int executionId)\r\n{\r\n    _statusCursor.insertHtml(QString(\"<a href=\\\"cpprofiler://execution?%1\\\">search profiler</a> \")\r\n                             .arg(executionId));\r\n}\r\n\r\nvoid OutputWidget::associateServerUrl(const QString& url)\r\n{\r\n    if (!_hadServerUrl){\r\n        _statusCursor.insertHtml(QString(\"<a href=\\\"%1\\\">visualisation</a> \").arg(url));\r\n        _hadServerUrl = true;\r\n    }\r\n}\r\n\r\nvoid OutputWidget::addSolution(const QVariantMap& output, const QStringList& order, qint64 time)\r\n{\r\n    TextLayoutLock lock(this);\r\n    QTextCursor cursor = nextInsertAt();\r\n    if (!_checkerOutput.isEmpty()) {\r\n        cursor.insertText(\"% Solution checker report:\\n\", _commentCharFormat);\r\n        for (auto& it : _checkerOutput) {\r\n            auto section = it.first;\r\n            if (section == \"raw\" || it.second.toString().isEmpty()) {\r\n                continue;\r\n            }\r\n            addSection(section);\r\n            QTextBlockFormat format;\r\n            format.setProperty(Property::Section, section);\r\n            cursor.setBlockFormat(format);\r\n            cursor.block().setVisible(_sections[section]);\r\n            if (section == \"html\" || section.endsWith(\"_html\")) {\r\n                addHtmlToSection(section, it.second.toString());\r\n            } else {\r\n                auto lines = it.second.toString().split(\"\\n\");\r\n                if (lines.last().isEmpty()) {\r\n                    lines.pop_back();\r\n                }\r\n                bool first = true;\r\n                for (auto& line : lines) {\r\n                    addTextToSection(section, (first ? \"% \" : \"\\n% \") + line, _commentCharFormat);\r\n                    first = false;\r\n                }\r\n            }\r\n            if (!cursor.block().text().isEmpty()) {\r\n                cursor.insertBlock();\r\n            }\r\n        }\r\n        _checkerOutput.clear();\r\n    }\r\n\r\n    for (auto& section : order) {\r\n        if (section == \"raw\" || output[section].toString().isEmpty()) {\r\n            continue;\r\n        }\r\n        addSection(section);\r\n        QTextBlockFormat format;\r\n        format.setProperty(Property::Section, section);\r\n        cursor.setBlockFormat(format);\r\n        cursor.block().setVisible(_sections[section]);\r\n        if (section == \"html\" || section.endsWith(\"_html\")) {\r\n            addHtmlToSection(section, output[section].toString());\r\n        } else {\r\n            addTextToSection(section, output[section].toString());\r\n        }\r\n        if (!cursor.block().text().isEmpty()) {\r\n            cursor.insertBlock();\r\n        }\r\n    }\r\n    if (time != -1) {\r\n        QTextBlockFormat f;\r\n        f.setProperty(Property::MessageType, \"Timing\");\r\n        cursor.setBlockFormat(f);\r\n        addMessageType(\"Timing\");\r\n        if (!isMessageTypeVisible(\"Timing\")) {\r\n            cursor.block().setVisible(false);\r\n        }\r\n        cursor.insertText(QString(\"% time elapsed: \"), _noticeCharFormat);\r\n        cursor.insertText(IDEUtils::formatTime(time), _defaultCharFormat);\r\n        cursor.insertText(\"\\n\", _defaultCharFormat);\r\n    }\r\n    QTextBlockFormat format;\r\n    cursor.setBlockFormat(format);\r\n    cursor.insertText(\"----------\\n\", _defaultCharFormat);\r\n\r\n    _solutionCount++;\r\n\r\n    _lastSolutions[0] = _lastSolutions[1];\r\n    _lastSolutions[1] = cursor;\r\n    _lastSolutions[1].setKeepPositionOnInsert(true);\r\n\r\n    if (_solutionLimit > 0 && _solutionCount >= _solutionLimit) {\r\n        if (_solutionBuffer == nullptr) {\r\n            _solutionBuffer = new QTextDocument(this);\r\n            _firstCompressedSolution = _solutionCount;\r\n            _effectiveSolutionLimit *= 2;\r\n        } else if (_solutionCount >= _effectiveSolutionLimit - 1) {\r\n            auto* doc = _solutionBuffer;\r\n            _solutionBuffer = nullptr;\r\n            if (!doc->isEmpty()) {\r\n                QTextDocumentFragment contents(doc);\r\n\r\n                // Create table containing heading\r\n                auto* t = nextInsertAt().insertTable(1, 2, _headerTableFormat);\r\n                t->cellAt(0, 0).firstCursorPosition().insertText(QString(QChar::ObjectReplacementCharacter), _arrowFormat);\r\n                auto label = QString(\"[ %1 more solutions ]\").arg(_solutionCount - _firstCompressedSolution);\r\n                t->cellAt(0, 1).firstCursorPosition().insertText(label, noticeCharFormat());\r\n                \r\n                // Create frame which will contain children\r\n                auto* frame = nextInsertAt().insertFrame(_frameFormat);\r\n                frame->firstCursorPosition().insertFragment(contents);\r\n                if (frame->lastCursorPosition().block().text().isEmpty()) {\r\n                    // Remove trailing newline\r\n                    frame->lastCursorPosition().deletePreviousChar();\r\n                }\r\n                toggleFrameVisibility(frame);\r\n            }\r\n            delete doc;\r\n        }\r\n    }\r\n}\r\n\r\nvoid OutputWidget::addCheckerOutput(const QVariantMap& output, const QStringList& order)\r\n{\r\n    for (auto& it : order) {\r\n        _checkerOutput.append({it, output[it]});\r\n    }\r\n}\r\n\r\nvoid OutputWidget::addText(const QString& text, const QString& messageType) {\r\n    addText(text, QTextCharFormat(), messageType);\r\n}\r\n\r\nvoid OutputWidget::addText(const QString& text, const QTextCharFormat& format, const QString& messageType) {\r\n    TextLayoutLock lock(this);\r\n    auto cursor = nextInsertAt();\r\n    if (messageType != \"trace\") {\r\n        _lastTraceLoc = \"\";\r\n    }\r\n    if (messageType.isEmpty()) {\r\n        cursor.insertText(text, format);\r\n    } else {\r\n        addMessageType(messageType);\r\n        auto start = cursor.position();\r\n        QTextBlockFormat f;\r\n        f.setProperty(Property::MessageType, messageType);\r\n        cursor.setBlockFormat(f);\r\n        cursor.insertText(text, format);\r\n        auto end = cursor.position();\r\n        if (!isMessageTypeVisible(messageType)) {\r\n            cursor.setPosition(start);\r\n            while (cursor.position() < end) {\r\n                cursor.block().setVisible(false);\r\n                cursor.movePosition(QTextCursor::NextBlock);\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nvoid OutputWidget::addHtml(const QString& html, const QString& messageType) {\r\n    TextLayoutLock lock(this);\r\n    auto cursor = nextInsertAt();\r\n    if (messageType != \"trace\") {\r\n        _lastTraceLoc = \"\";\r\n    }\r\n    if (messageType.isEmpty()) {\r\n        cursor.insertHtml(html);\r\n    } else {\r\n        addMessageType(messageType);\r\n        auto start = cursor.position();\r\n        QTextBlockFormat f;\r\n        f.setProperty(Property::MessageType, messageType);\r\n        cursor.insertHtml(html);\r\n        auto end = cursor.position();\r\n        cursor.setPosition(start);\r\n        while (cursor.position() < end) {\r\n            cursor.mergeBlockFormat(f);\r\n            cursor.block().setVisible(isMessageTypeVisible(messageType));\r\n            cursor.movePosition(QTextCursor::NextBlock);\r\n        }\r\n    }\r\n}\r\n\r\nvoid OutputWidget::addTextToSection(const QString& section, const QString& text)\r\n{\r\n    addTextToSection(section, text, QTextCharFormat());\r\n}\r\n\r\nvoid OutputWidget::addTextToSection(const QString& section, const QString& text, const QTextCharFormat& format)\r\n{\r\n    TextLayoutLock lock(this);\r\n    auto cursor = nextInsertAt();\r\n    _lastTraceLoc = \"\";\r\n    auto start = cursor.position();\r\n    addSection(section);\r\n    QTextBlockFormat f;\r\n    f.setProperty(Property::Section, section);\r\n    cursor.setBlockFormat(f);\r\n    cursor.insertText(text, format);\r\n    auto end = cursor.position();\r\n    if (!_sections[section]) {\r\n        cursor.setPosition(start);\r\n        while (cursor.position() < end) {\r\n            cursor.block().setVisible(false);\r\n            cursor.movePosition(QTextCursor::NextBlock);\r\n        }\r\n    }\r\n}\r\n\r\nvoid OutputWidget::addHtmlToSection(const QString& section, const QString& html)\r\n{\r\n    TextLayoutLock lock(this);\r\n    auto cursor = nextInsertAt();\r\n    _lastTraceLoc = \"\";\r\n    auto start = cursor.position();\r\n    addSection(section);\r\n    QTextBlockFormat f;\r\n    f.setProperty(Property::Section, section);\r\n    cursor.insertHtml(html);\r\n    auto end = cursor.position();\r\n    cursor.setPosition(start);\r\n    while (cursor.position() < end) {\r\n        cursor.mergeBlockFormat(f);\r\n        cursor.block().setVisible(_sections[section]);\r\n        cursor.movePosition(QTextCursor::NextBlock);\r\n    }\r\n}\r\n\r\nvoid OutputWidget::addStatistics(const QVariantMap& statistics)\r\n{\r\n    TextLayoutLock lock(this);\r\n    auto cursor = nextInsertAt();\r\n    _lastTraceLoc = \"\";\r\n    QTextBlockFormat f;\r\n    f.setProperty(Property::MessageType, \"Statistics\");\r\n    cursor.setBlockFormat(f);\r\n    addMessageType(\"Statistics\");\r\n    for (auto it = statistics.begin(); it != statistics.end(); it++) {\r\n        if (!isMessageTypeVisible(\"Statistics\")) {\r\n            cursor.block().setVisible(false);\r\n        }\r\n        cursor.insertText(\"%%%mzn-stat: \", _noticeCharFormat);\r\n        cursor.insertText(it.key(), _defaultCharFormat);\r\n        cursor.insertText(\"=\", _defaultCharFormat);\r\n        cursor.insertText(it.value().toString(), _defaultCharFormat);\r\n        cursor.insertText(\"\\n\", _noticeCharFormat);\r\n    }\r\n    if (!isMessageTypeVisible(\"Statistics\")) {\r\n        cursor.block().setVisible(false);\r\n    }\r\n    cursor.insertText(\"%%%mzn-stat-end\\n\", _noticeCharFormat);\r\n}\r\n\r\nvoid OutputWidget::addStatus(const QString& status, qint64 time)\r\n{\r\n    TextLayoutLock lock(this);\r\n    _lastTraceLoc = \"\";\r\n    QMap<QString, QString> status_map = {\r\n        {\"ALL_SOLUTIONS\", \"==========\"},\r\n        {\"OPTIMAL_SOLUTION\", \"==========\"},\r\n        {\"UNSATISFIABLE\", \"=====UNSATISFIABLE=====\"},\r\n        {\"UNSAT_OR_UNBOUNDED\", \"=====UNSATorUNBOUNDED=====\"},\r\n        {\"UNBOUNDED\", \"=====UNBOUNDED=====\"},\r\n        {\"UNKNOWN\", \"=====UNKNOWN=====\"},\r\n        {\"ERROR\", \"=====ERROR=====\"}\r\n    };\r\n    auto it = status_map.find(status);\r\n    if (it != status_map.end()) {\r\n        nextInsertAt().insertText(*it + \"\\n\", _defaultCharFormat);\r\n    }\r\n}\r\n\r\nvoid OutputWidget::endExecution(int exitCode, qint64 time)\r\n{\r\n    TextLayoutLock lock(this);\r\n\r\n    if (_solutionBuffer != nullptr) {\r\n        auto* doc = _solutionBuffer;\r\n        _solutionBuffer = nullptr;\r\n\r\n        // Print compressed solutions\r\n        QTextCursor cursor(doc);\r\n        cursor.setPosition(_lastSolutions[0].position(), QTextCursor::KeepAnchor);\r\n        if (cursor.hasSelection()) {\r\n            auto* t = nextInsertAt().insertTable(1, 2, _headerTableFormat);\r\n            t->cellAt(0, 0).firstCursorPosition().insertText(QString(QChar::ObjectReplacementCharacter), _arrowFormat);\r\n            auto label = QString(\"[ %1 more solutions ]\").arg(_solutionCount - _firstCompressedSolution - 1);\r\n            t->cellAt(0, 1).firstCursorPosition().insertText(label, noticeCharFormat());\r\n            \r\n            auto* f = nextInsertAt().insertFrame(_frameFormat);\r\n            f->firstCursorPosition().insertFragment(QTextDocumentFragment(cursor));\r\n            if (f->lastCursorPosition().block().text().isEmpty()) {\r\n                // Remove trailing newline\r\n                f->lastCursorPosition().deletePreviousChar();\r\n            }\r\n            toggleFrameVisibility(f);\r\n        }\r\n\r\n        // Print last solution and final status/stats\r\n        cursor.setPosition(_lastSolutions[0].position());\r\n        cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);\r\n        nextInsertAt().insertFragment(QTextDocumentFragment(cursor));\r\n\r\n        delete doc;\r\n    }\r\n\r\n    _lastTraceLoc = \"\";\r\n    if (exitCode != 0) {\r\n        QString msg = \"Process finished with non-zero exit code %1.\\n\";\r\n        nextInsertAt().insertText(msg.arg(exitCode), errorCharFormat());\r\n    }\r\n    auto t = IDEUtils::formatTime(time);\r\n    nextInsertAt().insertText(QString(\"Finished in %1.\").arg(t), noticeCharFormat());\r\n    _statusCursor.insertText(t, infoCharFormat());\r\n    _frame = ui->textBrowser->document()->rootFrame();\r\n}\r\n\r\n\r\nvoid OutputWidget::addMessageType(const QString &messageType)\r\n{\r\n    if (_messageTypeVisible.contains(messageType)) {\r\n        return;\r\n    }\r\n    _messageTypeVisible[messageType] = true;\r\n\r\n#ifdef Q_OS_MAC\r\n    auto* button = new QCheckBox(messageType);\r\n#else\r\n    auto* button = new QPushButton(messageType);\r\n    button->setCursor(Qt::PointingHandCursor);\r\n    button->setFlat(true);\r\n    button->setCheckable(true);\r\n#endif\r\n    button->setChecked(true);\r\n\r\n    auto* menuItem = ui->messageTypeMenu_pushButton->menu()->addAction(messageType, this, [=] () {\r\n        toggleMessageTypeVisibility(messageType);\r\n    });\r\n    menuItem->setCheckable(true);\r\n    connect(button, &QAbstractButton::toggled, this, [=](bool checked) { setMessageTypeVisibility(messageType, checked); });\r\n    connect(this, &OutputWidget::messageTypeToggled, button, [=] (const QString& mt, bool visible) {\r\n        if (messageType == mt) {\r\n            button->setChecked(visible);\r\n            menuItem->setChecked(visible);\r\n        }\r\n    });\r\n\r\n    button->setMinimumWidth(1);\r\n    ui->messageTypeButtons_horizontalLayout->addWidget(button);\r\n    layoutButtons();\r\n}\r\n\r\nvoid OutputWidget::setMessageTypeVisibility(const QString& messageType, bool visible)\r\n{\r\n    if (_messageTypeVisible[messageType] == visible) {\r\n        return;\r\n    }\r\n    TextLayoutLock lock(this, false);\r\n    auto cursor = ui->textBrowser->textCursor();\r\n    cursor.movePosition(QTextCursor::Start);\r\n    auto* rootFrame = cursor.currentFrame();\r\n    auto rootFrameFormat = rootFrame->frameFormat();\r\n    while (!cursor.atEnd()) {\r\n        auto frameVisible = isFrameVisible(cursor.currentFrame());\r\n        if (!frameVisible) {\r\n            cursor = cursor.currentFrame()->lastCursorPosition();\r\n            cursor.movePosition(QTextCursor::NextBlock);\r\n            continue;\r\n        }\r\n        auto block = cursor.block();\r\n        auto blockFormat = block.blockFormat();\r\n        if (blockFormat.hasProperty(Property::MessageType)\r\n                && blockFormat.property(Property::MessageType).toString() == messageType) {\r\n            block.setVisible(visible);\r\n        }\r\n        cursor.movePosition(QTextCursor::NextBlock);\r\n    }\r\n    rootFrame->setFrameFormat(rootFrameFormat); // Force relayout\r\n    _messageTypeVisible[messageType] = visible;\r\n\r\n    emit messageTypeToggled(messageType, visible);\r\n}\r\n\r\nvoid OutputWidget::clear()\r\n{\r\n    if (_frame != ui->textBrowser->document()->rootFrame()) {\r\n        // Can't clear in middle of run\r\n        return;\r\n    }\r\n\r\n    _sections.clear();\r\n    _messageTypeVisible.clear();\r\n    ui->textBrowser->clear();\r\n    ui->toggleAll_pushButton->setEnabled(false);\r\n    ui->sectionMenu_pushButton->hide();\r\n    ui->sectionMenu_pushButton->menu()->clear();\r\n    ui->messageTypeMenu_pushButton->hide();\r\n    ui->messageTypeMenu_pushButton->menu()->clear();\r\n    _frame = ui->textBrowser->document()->rootFrame();\r\n    qDeleteAll(ui->sectionButtons_widget->findChildren<QWidget*>(\"\", Qt::FindDirectChildrenOnly));\r\n    qDeleteAll(ui->messageTypeButtons_widget->findChildren<QWidget*>(\"\", Qt::FindDirectChildrenOnly));\r\n    ui->sectionButtons_widget->show();\r\n    ui->messageTypeButtons_widget->show();\r\n}\r\n\r\nvoid OutputWidget::setBrowserFont(const QFont& font)\r\n{\r\n    ui->textBrowser->setFont(font);\r\n    QTextCharFormat format;\r\n    format.setFont(font);\r\n    auto cursor = ui->textBrowser->textCursor();\r\n    cursor.movePosition(QTextCursor::Start);\r\n    cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);\r\n    cursor.mergeCharFormat(format);\r\n\r\n    QFontMetrics metrics(font);\r\n    auto spaceNeeded = metrics.height() + 4;\r\n    auto cwc = { QTextLength(QTextLength::FixedLength, spaceNeeded) };\r\n    _headerTableFormat.setColumnWidthConstraints(cwc);\r\n\r\n    _frameFormat.setLeftMargin(spaceNeeded);\r\n}\r\n\r\nvoid OutputWidget::onAnchorClicked(const QUrl& link)\r\n{\r\n    emit anchorClicked(link);\r\n}\r\n\r\nvoid OutputWidget::onClickTable(QTextTable* table) {\r\n    TextLayoutLock lock(this, false);\r\n    auto tableFormat = table->format();\r\n    if (tableFormat.hasProperty(Property::ToggleFrame)) {\r\n        auto tableCursor = table->lastCursorPosition();\r\n        tableCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, 2);\r\n        auto* frame = tableCursor.currentFrame();\r\n        toggleFrameVisibility(frame);\r\n    }\r\n}\r\n\r\nvoid OutputWidget::toggleFrameVisibility(QTextFrame* frame)\r\n{\r\n    TextLayoutLock lock(this, false);\r\n    auto frameFormat = frame->frameFormat();\r\n    auto visible = !frameFormat.property(Property::Expanded).toBool();\r\n    frameFormat.setProperty(Property::Expanded, visible);\r\n    frame->setFrameFormat(frameFormat);\r\n\r\n    auto parentVisible = isFrameVisible(frame->parentFrame());\r\n    if (!parentVisible) {\r\n        return;\r\n    }\r\n\r\n    // Set visibility of TextBlocks\r\n    auto cursor = frame->firstCursorPosition();\r\n    auto end = frame->lastCursorPosition();\r\n    while (cursor <= end) {\r\n        auto* childFrame = cursor.currentFrame();\r\n        if (childFrame != nullptr && childFrame != frame) {\r\n            auto childFormat = frame->frameFormat();\r\n            auto childVisible = !childFormat.property(Property::Expanded).toBool();\r\n            if (childVisible == visible) {\r\n                cursor = childFrame->lastCursorPosition();\r\n                cursor.movePosition(QTextCursor::NextBlock);\r\n                continue;\r\n            }\r\n        }\r\n\r\n        auto block = cursor.block();\r\n        auto blockFormat = block.blockFormat();\r\n        auto blockVisible = true;\r\n        if (blockFormat.hasProperty(Property::Section)) {\r\n            blockVisible = _sections[blockFormat.property(Property::Section).toString()];\r\n        } else if (blockFormat.hasProperty(Property::MessageType)) {\r\n            blockVisible = _messageTypeVisible[blockFormat.property(Property::MessageType).toString()];\r\n        }\r\n        block.setVisible(isFrameVisible(cursor.currentFrame()) && blockVisible);\r\n        cursor.movePosition(QTextCursor::NextBlock);\r\n    }\r\n}\r\n\r\nvoid OutputWidget::addSection(const QString& section)\r\n{\r\n    if (_sections.contains(section)) {\r\n        return;\r\n    }\r\n    _sections[section] = true;\r\n\r\n#ifdef Q_OS_MAC\r\n    auto* button = new QCheckBox(section);\r\n#else\r\n    auto* button = new QPushButton(section);\r\n    button->setCursor(Qt::PointingHandCursor);\r\n    button->setFlat(true);\r\n    button->setCheckable(true);\r\n#endif\r\n    button->setChecked(true);\r\n\r\n    connect(button, &QAbstractButton::toggled, this, [=](bool checked) { setSectionVisibility(section, checked); });\r\n\r\n    auto* menu = new QMenu(button);\r\n    auto handler = [=] () { toggleSectionVisibility(section); };\r\n    auto* toggleSection = menu->addAction(\"Show section\", this, handler);\r\n    menu->addSeparator();\r\n    menu->addAction(\"Show only this section\", this, [=] () {\r\n        setAllSectionsVisibility(false);\r\n        setSectionVisibility(section, true);\r\n    });\r\n    menu->addAction(\"Show all but this section\", this, [=] () {\r\n        setAllSectionsVisibility(true);\r\n        setSectionVisibility(section, false);\r\n    });\r\n\r\n    button->setContextMenuPolicy(Qt::CustomContextMenu);\r\n    connect(button, &QWidget::customContextMenuRequested, this, [=] (const QPoint &p) {\r\n        toggleSection->setText(_sections[section] ? \"Hide section\" : \"Show section\");\r\n        menu->exec(button->mapToGlobal(p));\r\n    });\r\n\r\n    auto* menuItem = ui->sectionMenu_pushButton->menu()->addAction(section, this, handler);\r\n    menuItem->setCheckable(true);\r\n\r\n    connect(this, &OutputWidget::sectionToggled, button, [=] (const QString& s, bool visible) {\r\n        if (section == s) {\r\n            button->setChecked(visible);\r\n            menuItem->setChecked(visible);\r\n        }\r\n    });\r\n\r\n    ui->toggleAll_pushButton->setEnabled(true);\r\n\r\n    button->setMinimumWidth(1);\r\n\r\n    ui->sectionButtons_horizontalLayout->addWidget(button);\r\n\r\n    layoutButtons();\r\n}\r\n\r\nvoid OutputWidget::setSectionVisibility(const QString& section, bool visible)\r\n{\r\n    if (_sections[section] == visible) {\r\n        return;\r\n    }\r\n    TextLayoutLock lock(this, false);\r\n    auto cursor = ui->textBrowser->textCursor();\r\n    cursor.movePosition(QTextCursor::Start);\r\n    auto* rootFrame = cursor.currentFrame();\r\n    auto rootFrameFormat = rootFrame->frameFormat();\r\n    while (!cursor.atEnd()) {\r\n        auto frameVisible = isFrameVisible(cursor.currentFrame());\r\n        if (!frameVisible) {\r\n            cursor = cursor.currentFrame()->lastCursorPosition();\r\n            cursor.movePosition(QTextCursor::NextBlock);\r\n            continue;\r\n        }\r\n        auto block = cursor.block();\r\n        auto blockFormat = block.blockFormat();\r\n        if (blockFormat.hasProperty(Property::Section)\r\n                && blockFormat.property(Property::Section).toString() == section) {\r\n            block.setVisible(visible);\r\n        }\r\n        cursor.movePosition(QTextCursor::NextBlock);\r\n    }\r\n    _sections[section] = visible;\r\n    rootFrame->setFrameFormat(rootFrameFormat); // Force relayout\r\n\r\n    emit sectionToggled(section, visible);\r\n\r\n    for (auto v : _sections) {\r\n        if (!v) {\r\n            ui->toggleAll_pushButton->setText(\"Show all\");\r\n            return;\r\n        }\r\n    }\r\n    ui->toggleAll_pushButton->setText(\"Hide all\");\r\n}\r\n\r\nvoid OutputWidget::setAllSectionsVisibility(bool visible)\r\n{\r\n    TextLayoutLock lock(this, false);\r\n    auto cursor = ui->textBrowser->textCursor();\r\n    cursor.movePosition(QTextCursor::Start);\r\n    auto* rootFrame = cursor.currentFrame();\r\n    auto rootFrameFormat = rootFrame->frameFormat();\r\n    while (!cursor.atEnd()) {\r\n        auto frameVisible = isFrameVisible(cursor.currentFrame());\r\n        if (!frameVisible) {\r\n            cursor = cursor.currentFrame()->lastCursorPosition();\r\n            cursor.movePosition(QTextCursor::NextBlock);\r\n            continue;\r\n        }\r\n        auto block = cursor.block();\r\n        auto blockFormat = block.blockFormat();\r\n        if (blockFormat.hasProperty(Property::Section)) {\r\n            block.setVisible(visible);\r\n        }\r\n        cursor.movePosition(QTextCursor::NextBlock);\r\n    }\r\n\r\n    for (auto it = _sections.begin(); it != _sections.end(); it++) {\r\n        bool toggled = it.value() != visible;\r\n        _sections[it.key()] = visible;\r\n        if (toggled) {\r\n            emit sectionToggled(it.key(), visible);\r\n        }\r\n    }\r\n    rootFrame->setFrameFormat(rootFrameFormat); // Force relayout\r\n\r\n    ui->toggleAll_pushButton->setText(visible ? \"Hide all\" : \"Show all\");\r\n}\r\n\r\n\r\n\r\nint OutputWidget::mouseToPosition(const QPoint& mousePos, Qt::HitTestAccuracy accuracy)\r\n{\r\n    auto* hbar = ui->textBrowser->horizontalScrollBar();\r\n    auto hOffset = isRightToLeft() ? (hbar->maximum() - hbar->value()) : hbar->value();\r\n    auto vOffset = ui->textBrowser->verticalScrollBar()->value();\r\n    auto* layout = ui->textBrowser->document()->documentLayout();\r\n    return layout->hitTest(QPointF(mousePos.x() + hOffset, mousePos.y() + vOffset), accuracy);\r\n}\r\n\r\nQTextCursor OutputWidget::fragmentCursor(int position)\r\n{\r\n    if (position != -1) {\r\n        auto cursor = ui->textBrowser->textCursor();\r\n        cursor.setPosition(position);\r\n        auto block = cursor.block();\r\n        for (auto it = block.begin(); it != block.end(); it++) {\r\n            auto fragment = it.fragment();\r\n            if (fragment.contains(position)) {\r\n                cursor.setPosition(fragment.position());\r\n                cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, fragment.length());\r\n                return cursor;\r\n            }\r\n        }\r\n    }\r\n    return QTextCursor();\r\n}\r\n\r\nbool OutputWidget::eventFilter(QObject* object, QEvent* event)\r\n{\r\n    if (object == ui->textBrowser->viewport() && event->type() == QEvent::MouseButtonPress) {\r\n        auto* e = static_cast<QMouseEvent*>(event);\r\n        if (e->button() == Qt::LeftButton) {\r\n            _pressed = true;\r\n        }\r\n    } else if (object == ui->textBrowser->viewport() && event->type() == QEvent::MouseMove) {\r\n        auto* e = static_cast<QMouseEvent*>(event);\r\n        if (_pressed) {\r\n            _dragging = true;\r\n        }\r\n    } else if (object == ui->textBrowser->viewport() && event->type() == QEvent::MouseButtonRelease) {\r\n        auto* e = static_cast<QMouseEvent*>(event);\r\n        if (e->button() == Qt::LeftButton) {\r\n            _pressed = false;\r\n            if (_dragging) {\r\n                _dragging = false;\r\n            } else {\r\n                auto position = mouseToPosition(e->pos());\r\n                auto cursor = fragmentCursor(position);\r\n                if (cursor.isNull()) {\r\n                    // See if we're clicking an entire table\r\n                    position = mouseToPosition(e->pos(), Qt::FuzzyHit);\r\n                    if (position != -1) {\r\n                        cursor = ui->textBrowser->textCursor();\r\n                        cursor.setPosition(position);\r\n                        auto* table = cursor.currentTable();\r\n                        if (table != nullptr) {\r\n                            onClickTable(table);\r\n                        }\r\n                    }\r\n                } else if (!cursor.charFormat().isAnchor()) {\r\n                    auto* table = cursor.currentTable();\r\n                    if (table != nullptr) {\r\n                        onClickTable(table);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    } else if (object == ui->textBrowser && event->type() == QEvent::KeyPress) {\r\n        auto* e = static_cast<QKeyEvent*>(event);\r\n        if (e == QKeySequence::Copy || e == QKeySequence::Cut) {\r\n            copySelectionToClipboard(false);\r\n            return true;\r\n        }\r\n    }\r\n    return false;\r\n}\r\n\r\nvoid OutputWidget::copySelectionToClipboard(bool includeHidden)\r\n{\r\n    auto cursor = ui->textBrowser->textCursor();\r\n    if (!cursor.hasSelection()) {\r\n        return;\r\n    }\r\n\r\n    // Copy text to new document and remove hidden/object replacement characters\r\n    auto* doc = new QTextDocument(this);\r\n    QTextCursor c(doc);\r\n    c.insertFragment(ui->textBrowser->textCursor().selection());\r\n    c.setPosition(0);\r\n\r\n    while (!c.atEnd()) {\r\n        auto* table = c.currentTable();\r\n        if (table != nullptr) {\r\n            auto f = table->format();\r\n            if (f.hasProperty(Property::Expanded)) {\r\n                auto cell = table->cellAt(0, 0);\r\n                auto cursor = cell.firstCursorPosition();\r\n                cursor.setPosition(cell.lastPosition(), QTextCursor::KeepAnchor);\r\n                cursor.removeSelectedText();\r\n                if (!f.property(Property::Expanded).toBool()) {\r\n                    cursor.setPosition(table->lastPosition() + 2);\r\n                    cursor.setPosition(cursor.currentFrame()->lastPosition(), QTextCursor::KeepAnchor);\r\n                    cursor.removeSelectedText();\r\n                }\r\n            }\r\n        }\r\n        if (!includeHidden) {\r\n            auto format = c.block().blockFormat();\r\n            bool remove = format.hasProperty(Property::Section)\r\n                    && !isSectionVisible(format.property(Property::Section).toString());\r\n            remove |= format.hasProperty(Property::MessageType)\r\n                    && !isMessageTypeVisible(format.property(Property::MessageType).toString());\r\n            if (remove) {\r\n                c.setPosition(c.block().position());\r\n                if (!c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor)) {\r\n                    c.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);\r\n                }\r\n                c.removeSelectedText();\r\n                continue;\r\n            }\r\n        }\r\n        if (!c.movePosition(QTextCursor::NextBlock)) {\r\n            break;\r\n        }\r\n    }\r\n\r\n    IDEUtils::MimeDataExporter te;\r\n    te.setDocument(doc);\r\n    te.selectAll();\r\n    QMimeData* mimeData = te.md();\r\n    QApplication::clipboard()->setMimeData(mimeData);\r\n\r\n    delete doc;\r\n}\r\n\r\nbool OutputWidget::isFrameVisible(QTextFrame* frame)\r\n{\r\n    while (frame != nullptr) {\r\n        auto format = frame->frameFormat();\r\n        if (format.hasProperty(Property::Expanded)\r\n                && !format.property(Property::Expanded).toBool()) {\r\n            return false;\r\n        }\r\n        frame = frame->parentFrame();\r\n    }\r\n    return true;\r\n}\r\n\r\nTextLayoutLock::TextLayoutLock(OutputWidget* o, bool scroll) : _o(o), _scroll(scroll) {\r\n    if (_o->_frame != _o->ui->textBrowser->document()->rootFrame()) {\r\n        auto frameVisible = _o->isFrameVisible(_o->_frame);\r\n        if (!frameVisible) {\r\n            _o->toggleFrameVisibility(_o->_frame);\r\n        }\r\n    }\r\n    _o->ui->textBrowser->textCursor().beginEditBlock();\r\n}\r\n\r\nTextLayoutLock::~TextLayoutLock()\r\n{\r\n    _o->ui->textBrowser->textCursor().endEditBlock();\r\n    if (_scroll) {\r\n        _o->scrollToBottom();\r\n    }\r\n}\r\n\r\nQSizeF OutputWidgetArrow::intrinsicSize(QTextDocument* doc, int posInDocument, const QTextFormat& format)\r\n{\r\n    QFontMetrics metrics(format.toCharFormat().font());\r\n    return QSizeF(metrics.height(), metrics.height());\r\n}\r\n\r\nvoid OutputWidgetArrow::drawObject(QPainter* painter, const QRectF& rect, QTextDocument* doc, int posInDocument, const QTextFormat& format)\r\n{\r\n    QFontMetrics metrics(format.toCharFormat().font());\r\n\r\n    QTextCursor cursor(doc);\r\n    cursor.setPosition(posInDocument);\r\n    auto t = cursor.currentTable();\r\n    assert(t != nullptr);\r\n    cursor.setPosition(t->lastPosition() + 2);\r\n    auto* f = cursor.currentFrame();\r\n    assert(f != nullptr);\r\n\r\n    auto ff = f->frameFormat();\r\n    bool expanded = ff.property(OutputWidget::Property::Expanded).toBool();\r\n    \r\n    auto tf = t->format();\r\n    auto requiredFormat = static_cast<OutputWidget*>(parent())->headerTableFormat();\r\n    if (tf.columnWidthConstraints()[0].rawValue() !=\r\n            requiredFormat.columnWidthConstraints()[0].rawValue()) {\r\n        // Re-align\r\n        t->setFormat(requiredFormat);\r\n        f->setFrameFormat(static_cast<OutputWidget*>(parent())->frameFormat());\r\n    }\r\n\r\n    auto h2 = metrics.ascent() / 2;\r\n    auto w2 = h2 / 2;\r\n\r\n    QPainterPath p;\r\n    p.moveTo(-w2, -h2);\r\n    p.lineTo(w2, 0);\r\n    p.lineTo(-w2, h2);\r\n    p.closeSubpath();\r\n    painter->translate(rect.center());\r\n    if (expanded) {\r\n        painter->rotate(90);\r\n    }\r\n    auto infoFormat = static_cast<OutputWidget*>(parent())->infoCharFormat();\r\n    painter->fillPath(p, infoFormat.foreground());\r\n}\r\n\r\nvoid OutputWidget::on_toggleAll_pushButton_clicked()\r\n{\r\n    for (auto it = _sections.begin(); it != _sections.end(); it++) {\r\n        if (!it.value()) {\r\n            // Some invisible, so show all\r\n            setAllSectionsVisibility(true);\r\n            return;\r\n        }\r\n    }\r\n    // Hide all sections since they were all previously visible\r\n    setAllSectionsVisibility(false);\r\n}\r\n\r\nvoid OutputWidget::resizeEvent(QResizeEvent *e)\r\n{\r\n    layoutButtons();\r\n}\r\n\r\nvoid OutputWidget::layoutButtons()\r\n{\r\n    auto neededWidth = 0;\r\n    auto spacing = ui->sectionButtons_horizontalLayout->spacing();\r\n    for (auto* b : ui->sectionButtons_widget->findChildren<QAbstractButton*>()) {\r\n        neededWidth += b->sizeHint().width() + spacing;\r\n    }\r\n    for (auto* b : ui->messageTypeButtons_widget->findChildren<QAbstractButton*>()) {\r\n        neededWidth += b->sizeHint().width() + spacing;\r\n    }\r\n    auto availableWidth = ui->buttons_widget->width();\r\n\r\n    if (availableWidth > neededWidth) {\r\n        // Normal layout\r\n        if (ui->sectionMenu_pushButton->isVisible()) {\r\n            ui->sectionMenu_pushButton->hide();\r\n            ui->messageTypeMenu_pushButton->hide();\r\n            ui->sectionButtons_widget->show();\r\n            ui->messageTypeButtons_widget->show();\r\n        }\r\n    } else {\r\n        // Compact layout\r\n        ui->sectionMenu_pushButton->setVisible(!_sections.empty());\r\n        ui->messageTypeMenu_pushButton->setVisible(!_messageTypeVisible.empty());\r\n        ui->sectionButtons_widget->hide();\r\n        ui->messageTypeButtons_widget->hide();\r\n    }\r\n}\r\n\r\nQTextCursor OutputWidget::nextInsertAt() const\r\n{\r\n    if (_solutionBuffer != nullptr) {\r\n        QTextCursor cursor(_solutionBuffer);\r\n        cursor.movePosition(QTextCursor::End);\r\n        return cursor;\r\n    }\r\n\r\n    return _frame->lastCursorPosition();\r\n}\r\n\r\nvoid OutputWidget::onBrowserContextMenu(const QPoint& pos)\r\n{\r\n    _contextMenu->popup(ui->textBrowser->viewport()->mapToGlobal(pos));\r\n}\r\n"
  },
  {
    "path": "MiniZincIDE/outputwidget.h",
    "content": "#ifndef OUTPUTWIDGET_H\r\n#define OUTPUTWIDGET_H\r\n\r\n#include <QWidget>\r\n#include <QTextCursor>\r\n#include <QTextObjectInterface>\r\n#include <QTimer>\r\n#include <QElapsedTimer>\r\n#include <QMenu>\r\n#include \"theme.h\"\r\n\r\nnamespace Ui {\r\nclass OutputWidget;\r\n}\r\n\r\nclass OutputWidget : public QWidget\r\n{\r\n    Q_OBJECT\r\n    friend class TestIDE;\r\n\r\npublic:\r\n    explicit OutputWidget(QWidget *parent = nullptr);\r\n    ~OutputWidget();\r\n\r\n    enum TextObject {\r\n        Arrow = QTextFormat::UserObject + 1\r\n    };\r\n\r\n    enum Property {\r\n        Expanded = QTextFormat::UserProperty + 1,\r\n        ToggleFrame,\r\n        Section,\r\n        MessageType\r\n    };\r\n\r\n    bool isSectionVisible(const QString& section) { return _sections.value(section, false); }\r\n    bool isMessageTypeVisible(const QString& messageType) { return _messageTypeVisible.value(messageType, false); }\r\n\r\n    const QTextCharFormat& defaultCharFormat() const { return _defaultCharFormat; }\r\n    const QTextCharFormat& noticeCharFormat() const { return _noticeCharFormat; }\r\n    const QTextCharFormat& errorCharFormat() const { return _errorCharFormat; }\r\n    const QTextCharFormat& infoCharFormat() const { return _infoCharFormat; }\r\n    const QTextCharFormat& commentCharFormat() const { return _commentCharFormat; }\r\n\r\n    const QTextTableFormat headerTableFormat() const { return _headerTableFormat; }\r\n    const QTextFrameFormat frameFormat() const { return _frameFormat; }\r\n\r\n    int solutionLimit() { return _solutionLimit; }\r\n\r\n    QString lastTraceLoc(const QString& newTraceLoc);\r\n\r\npublic slots:\r\n    void setTheme(const Theme& theme, bool darkMode);\r\n\r\n    void copySelectionToClipboard(bool includeHidden = false);\r\n\r\n    void startExecution(const QString& label);\r\n    void associateProfilerExecution(int executionId);\r\n    void associateServerUrl(const QString& url);\r\n    void addSolution(const QVariantMap& output, const QStringList& order, qint64 time = -1);\r\n    void addCheckerOutput(const QVariantMap& output, const QStringList& order);\r\n    void addText(const QString& text, const QString& messageType = QString());\r\n    void addText(const QString& text, const QTextCharFormat& format, const QString& messageType = QString());\r\n    void addHtml(const QString& html, const QString& messageType = QString());\r\n    void addTextToSection(const QString& section, const QString& text);\r\n    void addTextToSection(const QString& section, const QString& text, const QTextCharFormat& format);\r\n    void addHtmlToSection(const QString& section, const QString& html);\r\n\r\n    void addStatistics(const QVariantMap& statistics);\r\n    void addStatus(const QString& status, qint64 time = -1);\r\n    void endExecution(int exitCode, qint64 time = -1);\r\n\r\n    void clear();\r\n\r\n    void setSolutionLimit(int solutionLimit) { _solutionLimit = solutionLimit; }\r\n    void setBrowserFont(const QFont& font);\r\n\r\n    void setSectionVisibility(const QString& section, bool visible);\r\n    void toggleSectionVisibility(const QString& section) { setSectionVisibility(section, !isSectionVisible(section)); }\r\n    void setAllSectionsVisibility(bool visible);\r\n\r\n    void setMessageTypeVisibility(const QString& messageType, bool visible);\r\n    void toggleMessageTypeVisibility(const QString& messageType) { setMessageTypeVisibility(messageType, !isMessageTypeVisible(messageType)); }\r\n\r\n    void scrollToBottom();\r\nsignals:\r\n    void sectionToggled(const QString& section, bool visible);\r\n    void messageTypeToggled(const QString& messageType, bool visible);\r\n    void anchorClicked(const QUrl& url);\r\n\r\nprivate:\r\n    Ui::OutputWidget *ui;\r\n\r\n    friend class TextLayoutLock;\r\n\r\n    QMenu* _contextMenu = nullptr;\r\n\r\n    bool _dragging = false;\r\n    bool _pressed = false;\r\n    int _solutionLimit = 100;\r\n    int _firstCompressedSolution = 0;\r\n\r\n    QTextCursor _statusCursor;\r\n    QTextFrame* _frame = nullptr;\r\n    QTextDocument* _solutionBuffer = nullptr;\r\n    QTextCursor _lastSolutions[2];\r\n\r\n    QMap<QString, bool> _sections;\r\n    QMap<QString, bool> _messageTypeVisible;\r\n\r\n    bool _hadServerUrl;\r\n\r\n    QTextCharFormat _defaultCharFormat;\r\n    QTextCharFormat _noticeCharFormat;\r\n    QTextCharFormat _errorCharFormat;\r\n    QTextCharFormat _infoCharFormat;\r\n    QTextCharFormat _commentCharFormat;\r\n    QTextCharFormat _arrowFormat;\r\n    QTextBlockFormat _rightAlignBlockFormat;\r\n    QTextTableFormat _headerTableFormat;\r\n    QTextFrameFormat _frameFormat;\r\n\r\n    QVector<QPair<QString, QVariant>> _checkerOutput;\r\n\r\n    int _solutionCount = 0;\r\n    int _effectiveSolutionLimit = 0;\r\n\r\n    QString _lastTraceLoc;\r\n\r\n    void addSection(const QString& section);\r\n    void addMessageType(const QString& messageType);\r\n    void onClickTable(QTextTable* table);\r\n    void toggleFrameVisibility(QTextFrame* frame);\r\n    bool eventFilter(QObject* object, QEvent* event) override;\r\n    void resizeEvent(QResizeEvent* e) override;\r\n    bool isFrameVisible(QTextFrame* frame);\r\n    void layoutButtons();\r\n\r\n    QTextCursor nextInsertAt() const;\r\n\r\n    int mouseToPosition(const QPoint& mousePos, Qt::HitTestAccuracy accuracy = Qt::ExactHit);\r\n    QTextCursor fragmentCursor(int position);\r\n\r\nprivate slots:\r\n    void onAnchorClicked(const QUrl& link);\r\n    void on_toggleAll_pushButton_clicked();\r\n    void onBrowserContextMenu(const QPoint& pos);\r\n};\r\n\r\nclass OutputWidgetArrow : public QObject, public QTextObjectInterface\r\n{\r\n    Q_OBJECT\r\n    Q_INTERFACES(QTextObjectInterface)\r\n\r\npublic:\r\n    QSizeF intrinsicSize(QTextDocument* doc, int posInDocument, const QTextFormat& format) override;\r\n    void drawObject(QPainter* painter, const QRectF& rect, QTextDocument* doc, int posInDocument, const QTextFormat& format) override;\r\n};\r\n\r\nclass TextLayoutLock {\r\npublic:\r\n    explicit TextLayoutLock(OutputWidget* o, bool scroll = true);\r\n    ~TextLayoutLock();\r\nprivate:\r\n    bool _scroll;\r\n    OutputWidget* _o;\r\n};\r\n\r\n#endif // OUTPUTWIDGET_H\r\n"
  },
  {
    "path": "MiniZincIDE/outputwidget.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<ui version=\"4.0\">\r\n <class>OutputWidget</class>\r\n <widget class=\"QWidget\" name=\"OutputWidget\">\r\n  <property name=\"geometry\">\r\n   <rect>\r\n    <x>0</x>\r\n    <y>0</y>\r\n    <width>400</width>\r\n    <height>300</height>\r\n   </rect>\r\n  </property>\r\n  <property name=\"windowTitle\">\r\n   <string>Form</string>\r\n  </property>\r\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\r\n   <property name=\"leftMargin\">\r\n    <number>0</number>\r\n   </property>\r\n   <property name=\"topMargin\">\r\n    <number>0</number>\r\n   </property>\r\n   <property name=\"rightMargin\">\r\n    <number>0</number>\r\n   </property>\r\n   <property name=\"bottomMargin\">\r\n    <number>0</number>\r\n   </property>\r\n   <item>\r\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\r\n     <item>\r\n      <widget class=\"QPushButton\" name=\"toggleAll_pushButton\">\r\n       <property name=\"enabled\">\r\n        <bool>false</bool>\r\n       </property>\r\n       <property name=\"text\">\r\n        <string>Hide all</string>\r\n       </property>\r\n       <property name=\"checked\">\r\n        <bool>false</bool>\r\n       </property>\r\n      </widget>\r\n     </item>\r\n     <item>\r\n      <widget class=\"QWidget\" name=\"buttons_widget\" native=\"true\">\r\n       <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\r\n        <property name=\"spacing\">\r\n         <number>0</number>\r\n        </property>\r\n        <property name=\"leftMargin\">\r\n         <number>0</number>\r\n        </property>\r\n        <property name=\"topMargin\">\r\n         <number>0</number>\r\n        </property>\r\n        <property name=\"rightMargin\">\r\n         <number>0</number>\r\n        </property>\r\n        <property name=\"bottomMargin\">\r\n         <number>0</number>\r\n        </property>\r\n        <item>\r\n         <widget class=\"QPushButton\" name=\"sectionMenu_pushButton\">\r\n          <property name=\"text\">\r\n           <string>Toggle section</string>\r\n          </property>\r\n         </widget>\r\n        </item>\r\n        <item>\r\n         <widget class=\"QWidget\" name=\"sectionButtons_widget\" native=\"true\">\r\n          <layout class=\"QHBoxLayout\" name=\"sectionButtons_horizontalLayout\">\r\n           <property name=\"leftMargin\">\r\n            <number>0</number>\r\n           </property>\r\n           <property name=\"topMargin\">\r\n            <number>0</number>\r\n           </property>\r\n           <property name=\"rightMargin\">\r\n            <number>0</number>\r\n           </property>\r\n           <property name=\"bottomMargin\">\r\n            <number>0</number>\r\n           </property>\r\n          </layout>\r\n         </widget>\r\n        </item>\r\n        <item>\r\n         <spacer name=\"horizontalSpacer\">\r\n          <property name=\"orientation\">\r\n           <enum>Qt::Horizontal</enum>\r\n          </property>\r\n          <property name=\"sizeHint\" stdset=\"0\">\r\n           <size>\r\n            <width>0</width>\r\n            <height>0</height>\r\n           </size>\r\n          </property>\r\n         </spacer>\r\n        </item>\r\n        <item>\r\n         <widget class=\"QWidget\" name=\"messageTypeButtons_widget\" native=\"true\">\r\n          <layout class=\"QHBoxLayout\" name=\"messageTypeButtons_horizontalLayout\">\r\n           <property name=\"leftMargin\">\r\n            <number>0</number>\r\n           </property>\r\n           <property name=\"topMargin\">\r\n            <number>0</number>\r\n           </property>\r\n           <property name=\"rightMargin\">\r\n            <number>0</number>\r\n           </property>\r\n           <property name=\"bottomMargin\">\r\n            <number>0</number>\r\n           </property>\r\n          </layout>\r\n         </widget>\r\n        </item>\r\n        <item>\r\n         <widget class=\"QPushButton\" name=\"messageTypeMenu_pushButton\">\r\n          <property name=\"text\">\r\n           <string>Toggle message</string>\r\n          </property>\r\n         </widget>\r\n        </item>\r\n       </layout>\r\n      </widget>\r\n     </item>\r\n    </layout>\r\n   </item>\r\n   <item>\r\n    <widget class=\"QTextBrowser\" name=\"textBrowser\"/>\r\n   </item>\r\n  </layout>\r\n </widget>\r\n <resources/>\r\n <connections/>\r\n</ui>\r\n"
  },
  {
    "path": "MiniZincIDE/paramdialog.cpp",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#include \"paramdialog.h\"\n#include \"ui_paramdialog.h\"\n\n#include <QLabel>\n#include <QLineEdit>\n#include <QComboBox>\n#include <QFileInfo>\n#include <QDebug>\n#include <QScrollArea>\n\nParamDialog::ParamDialog(QWidget *parent) :\n    QDialog(parent),\n    ui(new Ui::ParamDialog)\n{\n    ui->setupUi(this);\n}\n\nint ParamDialog::getModel(const QStringList& modelFiles) {\n    setWindowTitle(\"Select model to run\");\n    selectedFiles = new QListWidget(this);\n    selectedFiles->setSelectionMode(QAbstractItemView::SingleSelection);\n    for (int i=0; i<modelFiles.size(); i++) {\n        QFileInfo fi(modelFiles[i]);\n        QListWidgetItem* lwi = new QListWidgetItem(fi.fileName());\n        selectedFiles->addItem(lwi);\n        if (previousModelFile==modelFiles[i]) {\n            selectedFiles->setCurrentItem(lwi);\n        }\n    }\n    if (selectedFiles->selectedItems().size()==0) {\n        selectedFiles->item(0)->setSelected(true);\n    }\n    QFormLayout* mainLayout = new QFormLayout;\n    mainLayout->addRow(selectedFiles);\n    ui->frame->setLayout(mainLayout);\n    int selectedModel = -1;\n    selectedFiles->setFocus();\n    if (QDialog::exec()==QDialog::Accepted) {\n        selectedModel = selectedFiles->currentRow();\n        previousModelFile=modelFiles[selectedModel];\n    }\n    delete ui->frame;\n    ui->frame = new QFrame;\n    ui->frame->setFrameShape(QFrame::StyledPanel);\n    ui->frame->setFrameShadow(QFrame::Raised);\n    ui->verticalLayout->insertWidget(0, ui->frame);\n    return selectedModel;\n}\n\nvoid ParamDialog::getParams(QStringList params, const QStringList& dataFiles, QStringList &values, QStringList &additionalDataFiles)\n{\n    setWindowTitle(\"Model parameters\");\n    QVector<QLineEdit*> le;\n\n    QFormLayout* mainLayout = new QFormLayout;\n\n    QTabWidget* tw = new QTabWidget(this);\n\n    QWidget* manual = new QWidget;\n    auto* formLayout = new QFormLayout;\n    manual->setLayout(formLayout);\n    bool fillPrevious = previousParams == params;\n    for (int i=0; i<params.size(); i++) {\n        QLineEdit* e = new QLineEdit(fillPrevious ? previousValues[i] : \"\");\n        le.append(e);\n        formLayout->addRow(new QLabel(params[i]+\" =\"), le.back());\n    }\n    QScrollArea* scrollArea = new QScrollArea;\n    scrollArea->setWidget(manual);\n    tw->addTab(scrollArea, \"Enter parameters\");\n\n    selectedFiles = nullptr;\n\n    if (dataFiles.size() > 0) {\n        selectedFiles = new QListWidget(this);\n        selectedFiles->setSelectionMode(QAbstractItemView::ExtendedSelection);\n        for (int i=0; i<dataFiles.size(); i++) {\n            QFileInfo fi(dataFiles[i]);\n            QListWidgetItem* lwi = new QListWidgetItem(fi.fileName());\n            selectedFiles->addItem(lwi);\n            if (previousDataFiles.contains(dataFiles[i]) || additionalDataFiles.contains(dataFiles[i])) {\n                lwi->setSelected(true);\n                selectedFiles->setCurrentItem(lwi);\n                selectedFiles->scrollToItem(lwi);\n            }\n        }\n        tw->addTab(selectedFiles, \"\");\n        if (!fillPrevious || !previousWasManual) {\n            tw->setCurrentIndex(1);\n        }\n        // Set now to workaround Qt bug where first tab doesn't appear\n        tw->setTabText(1, \"Select data file\");\n    }\n    mainLayout->addRow(tw);\n    ui->frame->setLayout(mainLayout);\n    le[0]->setFocus();\n    if (selectedFiles && selectedFiles->selectedItems().size()==0) {\n        selectedFiles->item(0)->setSelected(true);\n    }\n    if (QDialog::exec()==QDialog::Accepted) {\n        additionalDataFiles.clear();\n        previousWasManual = (tw->currentIndex()==0);\n        if (tw->currentIndex()==1) {\n            previousDataFiles.clear();\n            for (int i=0; i<dataFiles.size(); i++) {\n                QListWidgetItem* lwi = selectedFiles->item(i);\n                if (lwi && lwi->isSelected()) {\n                    additionalDataFiles.append(dataFiles[i]);\n                    previousDataFiles.append(dataFiles[i]);\n                }\n            }\n        } else {\n            for (int i=0; i<le.size(); i++)\n                values << le[i]->text();\n            previousParams = params;\n            previousValues = values;\n        }\n    }\n    delete ui->frame;\n    ui->frame = new QFrame;\n    ui->frame->setFrameShape(QFrame::StyledPanel);\n    ui->frame->setFrameShadow(QFrame::Raised);\n    ui->verticalLayout->insertWidget(0, ui->frame);\n}\n\nParamDialog::~ParamDialog()\n{\n    delete ui;\n}\n"
  },
  {
    "path": "MiniZincIDE/paramdialog.h",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#ifndef PARAMDIALOG_H\n#define PARAMDIALOG_H\n\n#include <QDialog>\n#include <QLineEdit>\n#include <QFormLayout>\n#include <QListWidget>\n\nnamespace Ui {\nclass ParamDialog;\n}\n\nclass ParamDialog : public QDialog\n{\n    Q_OBJECT\n\npublic:\n    explicit ParamDialog(QWidget *parent = 0);\n    ~ParamDialog();\n    void getParams(QStringList params, const QStringList& dataFiles, QStringList& values, QStringList& additionalDataFiles);\n    int getModel(const QStringList& modelFiles);\nprivate:\n    Ui::ParamDialog *ui;\n    QListWidget* selectedFiles;\n    QStringList previousParams;\n    QStringList previousValues;\n    QStringList previousDataFiles;\n    QString previousModelFile;\n    bool previousWasManual;\n};\n\n#endif // PARAMDIALOG_H\n"
  },
  {
    "path": "MiniZincIDE/paramdialog.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ParamDialog</class>\n <widget class=\"QDialog\" name=\"ParamDialog\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>84</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Model Parameters</string>\n  </property>\n  <property name=\"modal\">\n   <bool>true</bool>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QFrame\" name=\"frame\">\n     <property name=\"frameShape\">\n      <enum>QFrame::NoFrame</enum>\n     </property>\n     <property name=\"frameShadow\">\n      <enum>QFrame::Raised</enum>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n     <property name=\"orientation\">\n      <enum>Qt::Horizontal</enum>\n     </property>\n     <property name=\"standardButtons\">\n      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>ParamDialog</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>248</x>\n     <y>254</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>157</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>ParamDialog</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>316</x>\n     <y>260</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>286</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "MiniZincIDE/preferencesdialog.cpp",
    "content": "#include \"preferencesdialog.h\"\r\n#include \"ui_preferencesdialog.h\"\r\n\r\n#include \"exception.h\"\r\n#include \"ideutils.h\"\r\n#include \"mainwindow.h\"\r\n#include \"ide.h\"\r\n\r\n#include <QMessageBox>\r\n#include <QSettings>\r\n#include <QJsonObject>\r\n#include <QJsonArray>\r\n#include <QFileInfo>\r\n#include <QDir>\r\n#include <QFileDialog>\r\n\r\nPreferencesDialog::PreferencesDialog(bool addNewSolver, QWidget *parent) :\r\n    QDialog(parent),\r\n    ui(new Ui::PreferencesDialog)\r\n{\r\n    ui->setupUi(this);\r\n\r\n    QSettings settings;\r\n\r\n    // Get font so we can populate preview\r\n    settings.beginGroup(\"MainWindow\");\r\n    QFont editorFont = IDEUtils::fontFromString(settings.value(\"editorFont\").toString());\r\n    auto zoom = settings.value(\"zoom\", 100).toInt();\r\n    _origDarkMode = settings.value(\"darkMode\", false).toBool();\r\n    settings.endGroup();\r\n\r\n    if (_ce == nullptr) {\r\n        // Populate preview\r\n        QString fileContents;\r\n        QFile file(\":/cheat_sheet.mzn\");\r\n        if (file.open(QFile::ReadOnly)) {\r\n            fileContents = file.readAll();\r\n        } else {\r\n            qDebug() << \"internal error: cannot open cheat sheet.\";\r\n        }\r\n        auto& origTheme = IDE::instance()->themeManager->get(_origThemeIndex);\r\n        _ce = new CodeEditor(nullptr, \":/cheat_sheet.mzn\", false, false, editorFont, 2, false, origTheme, _origDarkMode, nullptr, this);\r\n        _ce->document()->setPlainText(fileContents);\r\n        _ce->setReadOnly(true);\r\n        ui->preview_verticalLayout->addWidget(_ce);\r\n    }\r\n\r\n    // Now everything else can get populated\r\n\r\n    // Separate font family from size so that combo box stays the same size\r\n    auto font = qApp->font();\r\n    font.setFamily(editorFont.family());\r\n    ui->fontComboBox->setCurrentFont(font);\r\n    ui->fontSize_spinBox->setValue(editorFont.pointSize());\r\n    ui->zoom_spinBox->setValue(zoom);\r\n\r\n    auto& driver = MznDriver::get();\r\n    _origMznDistribPath = driver.mznDistribPath();\r\n    ui->mznDistribPath->setText(_origMznDistribPath);\r\n    ui->mzn2fzn_version->setText(driver.minizincVersionString());\r\n\r\n    settings.beginGroup(\"ide\");\r\n    ui->check_updates->setChecked(settings.value(\"checkforupdates21\",false).toBool());\r\n    ui->checkSolutions_checkBox->setChecked(settings.value(\"checkSolutions\", true).toBool());\r\n    ui->clearOutput_checkBox->setChecked(settings.value(\"clearOutput\", false).toBool());\r\n    int compressSolutions = settings.value(\"compressSolutions\", 100).toInt();\r\n    if (compressSolutions > 0) {\r\n        ui->compressSolutions_spinBox->setValue(compressSolutions);\r\n        ui->compressSolutions_checkBox->setChecked(true);\r\n    } else {\r\n        ui->compressSolutions_checkBox->setChecked(false);\r\n    }\r\n    ui->reuseVis_checkBox->setChecked(settings.value(\"reuseVis\", false).toBool());\r\n    ui->visPort_spinBox->setValue(settings.value(\"visPort\", 3000).toInt());\r\n    ui->visWsPort_spinBox->setValue(settings.value(\"visWsPort\", 3100).toInt());\r\n    ui->visUrl_checkBox->setChecked(settings.value(\"printVisUrl\", false).toBool());\r\n    ui->printCommand_checkBox->setChecked(settings.value(\"printCommand\", false).toBool());\r\n    ui->indentSize_spinBox->setValue(settings.value(\"indentSize\", 2).toInt());\r\n    bool indentTabs = settings.value(\"indentTabs\", false).toBool();\r\n    ui->indentSpaces_radioButton->setChecked(!indentTabs);\r\n    ui->indentTabs_radioButton->setChecked(indentTabs);\r\n    ui->lineWrapping_checkBox->setChecked(settings.value(\"wordWrap\", true).toBool());\r\n    _origThemeIndex = settings.value(\"theme\", 0).toInt();\r\n    ui->theme_comboBox->setCurrentIndex(_origThemeIndex);\r\n\r\n    auto* d = IDE::instance()->darkModeNotifier;\r\n    if (d->hasSystemSetting()) {\r\n        _origDarkMode = d->darkMode();\r\n        ui->darkMode_checkBox->hide();\r\n    }\r\n    auto* t = IDE::instance()->themeManager;\r\n    _ce->setTheme(t->get(_origThemeIndex), _origDarkMode);\r\n    ui->darkMode_checkBox->setChecked(_origDarkMode);\r\n    settings.endGroup();\r\n    // Load user solver search paths\r\n    ui->configuration_groupBox->setEnabled(driver.isValid());\r\n    auto& userConfigFile = driver.userConfigFile();\r\n    QFile uc(userConfigFile);\r\n    if (uc.exists() && uc.open(QFile::ReadOnly)) {\r\n        QJsonDocument doc = QJsonDocument::fromJson(uc.readAll());\r\n        auto obj = doc.object();\r\n        if (obj.contains(\"mzn_solver_path\")) {\r\n            for (auto it : obj[\"mzn_solver_path\"].toArray()) {\r\n                auto path = it.toString();\r\n                if (!path.isEmpty()) {\r\n                    ui->extraSearchPath_listWidget->addItem(path);\r\n                }\r\n            }\r\n        }\r\n        if (obj.contains(\"solverDefaults\")) {\r\n            for (auto it : obj[\"solverDefaults\"].toArray()) {\r\n                auto arr = it.toArray();\r\n                auto solverId = arr[0].toString();\r\n                auto flag = arr[1].toString();\r\n                _userDefaultFlags.insert(solverId, flag);\r\n            }\r\n        }\r\n    }\r\n\r\n    IDEUtils::watchChildChanges(ui->solverFrame, this, [=] () {\r\n        auto& driver = MznDriver::get();\r\n        auto& solvers = driver.solvers();\r\n        if (!solvers.isEmpty()) {\r\n            _editingSolverIndex = ui->solvers_combo->currentIndex();\r\n        }\r\n    });\r\n\r\n    connect(ui->name, &QLineEdit::textChanged, this, &PreferencesDialog::updateSolverLabel);\r\n    connect(ui->version, &QLineEdit::textChanged, this, &PreferencesDialog::updateSolverLabel);\r\n\r\n    if (addNewSolver) {\r\n        ui->tabWidget->setCurrentIndex(1);\r\n        ui->solvers_combo->setCurrentIndex(ui->solvers_combo->count()-1);\r\n    }\r\n}\r\n\r\nPreferencesDialog::~PreferencesDialog()\r\n{\r\n    delete ui;\r\n    delete _ce;\r\n}\r\n\r\nvoid PreferencesDialog::on_fontComboBox_currentFontChanged(const QFont &f)\r\n{\r\n    updateCodeEditorFont();\r\n}\r\n\r\n\r\nvoid PreferencesDialog::on_fontSize_spinBox_valueChanged(int size)\r\n{\r\n    updateCodeEditorFont();\r\n}\r\n\r\n\r\nvoid PreferencesDialog::on_lineWrapping_checkBox_stateChanged(int checkstate)\r\n{\r\n    bool checked = checkstate == Qt::Checked;\r\n    if (checked) {\r\n        _ce->setLineWrapMode(QPlainTextEdit::WidgetWidth);\r\n        _ce->setWordWrapMode(QTextOption::WrapAnywhere);\r\n    } else {\r\n        _ce->setLineWrapMode(QPlainTextEdit::NoWrap);\r\n        _ce->setWordWrapMode(QTextOption::NoWrap);\r\n    }\r\n}\r\n\r\nvoid PreferencesDialog::updateCodeEditorFont()\r\n{\r\n    auto editorFont = ui->fontComboBox->currentFont();\r\n    editorFont.setPointSize(ui->fontSize_spinBox->value() * ui->zoom_spinBox->value() / 100);\r\n    _ce->setEditorFont(editorFont);\r\n}\r\n\r\nvoid PreferencesDialog::on_theme_comboBox_currentIndexChanged(int index)\r\n{\r\n    auto* t = IDE::instance()->themeManager;\r\n    t->current(index);\r\n    IDE::instance()->refreshTheme();\r\n    _ce->setTheme(t->current(), ui->darkMode_checkBox->isChecked());\r\n}\r\n\r\nQByteArray PreferencesDialog::allowFileRestore(const QString& path)\r\n{\r\n    QFileInfo fi(path);\r\n    auto filePath = fi.canonicalFilePath();\r\n    if (_restore.contains(filePath)) {\r\n        // Already processed\r\n        return _restore[filePath];\r\n    }\r\n    if (_remove.contains(filePath)) {\r\n        // Already processed\r\n        return QByteArray();\r\n    }\r\n    QFile f(path);\r\n    if (f.exists()) {\r\n        // Restore to old contents on cancel\r\n        if (f.open(QIODevice::ReadOnly)) {\r\n            auto contents = f.readAll();\r\n            _restore[filePath] = contents;\r\n            f.close();\r\n            return contents;\r\n        }\r\n    } else {\r\n        // Remove on cancel\r\n        _remove << filePath;\r\n    }\r\n    return QByteArray();\r\n}\r\n\r\nvoid PreferencesDialog::loadDriver(bool showError)\r\n{\r\n    auto& driver = MznDriver::get();\r\n    auto& solvers = driver.solvers();\r\n\r\n    _solversPopulated = false;\r\n    auto dest = ui->solvers_combo->currentIndex();\r\n    bool destExists = dest < solvers.size();\r\n    bool addNew = dest == ui->solvers_combo->count() - 1;\r\n    QString matchId;\r\n    QString matchVersion;\r\n    if (dest == _editingSolverIndex) {\r\n        // Was editing, so use widget values\r\n        matchId = ui->solverId->text();\r\n        matchVersion = ui->version->text();\r\n        destExists = true;\r\n    } else if (destExists) {\r\n        // Switching to different solver\r\n        matchId = solvers[dest]->id;\r\n        matchVersion = solvers[dest]->version;\r\n    }\r\n    try {\r\n        driver.setLocation(ui->mznDistribPath->text());\r\n        ui->mzn2fzn_version->setText(driver.minizincVersionString());\r\n    } catch (Exception& e) {\r\n        if (showError) {\r\n            showMessageBox(e.message());\r\n        }\r\n        ui->mzn2fzn_version->setText(e.message());\r\n    }\r\n\r\n    // Load user solver search paths\r\n    ui->extraSearchPath_listWidget->clear();\r\n    ui->configuration_groupBox->setEnabled(driver.isValid());\r\n    _userDefaultFlags.clear();\r\n    auto& userConfigFile = driver.userConfigFile();\r\n    QFile uc(userConfigFile);\r\n    if (uc.exists() && uc.open(QFile::ReadOnly)) {\r\n        QJsonDocument doc = QJsonDocument::fromJson(uc.readAll());\r\n        auto obj = doc.object();\r\n        if (obj.contains(\"mzn_solver_path\")) {\r\n            for (auto it : obj[\"mzn_solver_path\"].toArray()) {\r\n                auto path = it.toString();\r\n                if (!path.isEmpty()) {\r\n                    ui->extraSearchPath_listWidget->addItem(path);\r\n                }\r\n            }\r\n        }\r\n        if (obj.contains(\"solverDefaults\")) {\r\n            for (auto it : obj[\"solverDefaults\"].toArray()) {\r\n                auto arr = it.toArray();\r\n                auto solverId = arr[0].toString();\r\n                auto flag = arr[1].toString();\r\n                _userDefaultFlags.insert(solverId, flag);\r\n            }\r\n        }\r\n    }\r\n\r\n    populateSolvers();\r\n    int index = addNew ? ui->solvers_combo->count() - 1 : 0;\r\n    if (destExists) {\r\n        int i = 0;\r\n        for (auto* solver : solvers) {\r\n            if (solver->id == matchId && solver->version == matchVersion) {\r\n                index = i;\r\n                break;\r\n            }\r\n            i++;\r\n        }\r\n    }\r\n\r\n    if (ui->solvers_combo->currentIndex() == index) {\r\n        on_solvers_combo_currentIndexChanged(index);\r\n    } else {\r\n        ui->solvers_combo->setCurrentIndex(index);\r\n    }\r\n}\r\n\r\nvoid PreferencesDialog::populateSolvers()\r\n{\r\n    if (_solversPopulated) {\r\n        return;\r\n    }\r\n\r\n    _editingSolverIndex = -1;\r\n\r\n    auto& driver = MznDriver::get();\r\n    auto& solvers = driver.solvers();\r\n\r\n    ui->solvers_combo->clear();\r\n\r\n    for (auto& solver : solvers) {\r\n        ui->solvers_combo->addItem(solver->name + \" \" + solver->version);\r\n    }\r\n\r\n    if (solvers.empty()) {\r\n        ui->solvers_combo->addItem(\"(No solvers found)\");\r\n    }\r\n\r\n    ui->solvers_combo->addItem(\"Add new...\");\r\n\r\n    ui->solvers_tab->setEnabled(driver.isValid());\r\n\r\n    _solversPopulated = true;\r\n}\r\n\r\nbool PreferencesDialog::updateSolver()\r\n{\r\n    if (_editingSolverIndex == -1) {\r\n        return true;\r\n    }\r\n\r\n    auto& driver = MznDriver::get();\r\n    auto& solvers = driver.solvers();\r\n    auto& userConfigFile = driver.userConfigFile();\r\n    auto& userSolverConfigDir = driver.userSolverConfigDir();\r\n\r\n    if (!ui->requiredFlags->isHidden()) {\r\n        // Update the required flags/user set flags\r\n\r\n        QGridLayout* rfLayout = static_cast<QGridLayout*>(ui->requiredFlags->layout());\r\n        QVector<QStringList> defaultFlags;\r\n        for (int row=0; row<rfLayout->rowCount(); row++) {\r\n            if (QLayoutItem* li = rfLayout->itemAtPosition(row, 0)) {\r\n                QString key = static_cast<QLabel*>(li->widget())->text();\r\n                QLayoutItem* li2 = rfLayout->itemAtPosition(row, 1);\r\n                QString val = static_cast<QLineEdit*>(li2->widget())->text();\r\n                if (!val.isEmpty()) {\r\n                    QStringList sl({ui->solverId->text(), key, val});\r\n                    defaultFlags.push_back(sl);\r\n                }\r\n            }\r\n\r\n        }\r\n\r\n        auto doc = QJsonDocument::fromJson(allowFileRestore(userConfigFile));\r\n        auto jo = doc.object();\r\n        QJsonArray a0;\r\n        if (!jo.contains(\"solverDefaults\")) {\r\n            // Fresh object, initialise\r\n            for (auto& df : defaultFlags) {\r\n                QJsonArray a1;\r\n                for (auto& f : df) {\r\n                    a1.append(f);\r\n                }\r\n                a0.append(a1);\r\n            }\r\n        } else {\r\n            QJsonArray previous = jo[\"solverDefaults\"].toArray();\r\n            // First remove all entries for this solver\r\n            for (auto triple : previous) {\r\n                if (triple.isArray()) {\r\n                    QJsonArray previousA1 = triple.toArray();\r\n                    if (previousA1.size()==3) {\r\n                        if (previousA1[0]!=ui->solverId->text()) {\r\n                            a0.append(triple);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            // Now add the new values\r\n            for (auto& df : defaultFlags) {\r\n                QJsonArray a1;\r\n                for (auto& f : df) {\r\n                    a1.append(f);\r\n                }\r\n                a0.append(a1);\r\n            }\r\n        }\r\n        jo[\"solverDefaults\"] = a0;\r\n        doc.setObject(jo);\r\n        QFileInfo uc_info(userConfigFile);\r\n        if (!QDir().mkpath(uc_info.absoluteDir().absolutePath())) {\r\n            showMessageBox(\"Cannot create user configuration directory \"+uc_info.absoluteDir().absolutePath());\r\n            return false;\r\n        }\r\n        QFile uc(userConfigFile);\r\n        if (uc.open(QFile::ReadWrite | QIODevice::Truncate)) {\r\n            uc.write(doc.toJson());\r\n            uc.close();\r\n        } else {\r\n            showMessageBox(\"Cannot write user configuration file \"+userConfigFile);\r\n            return false;\r\n        }\r\n\r\n    }\r\n\r\n    if (ui->solverFrame->isEnabled()) {\r\n        if (ui->name->text().trimmed().isEmpty()) {\r\n            showMessageBox(\"You need to specify a name for the solver.\");\r\n            return false;\r\n        }\r\n        if (ui->solverId->text().trimmed().isEmpty()) {\r\n            showMessageBox(\"You need to specify a solver ID for the solver.\");\r\n            return false;\r\n        }\r\n        if (ui->executable->text().trimmed().isEmpty()) {\r\n            showMessageBox(\"You need to specify an executable for the solver.\");\r\n            return false;\r\n        }\r\n        if (ui->version->text().trimmed().isEmpty()) {\r\n            showMessageBox(\"You need to specify a version for the solver.\");\r\n            return false;\r\n        }\r\n        int index = _editingSolverIndex;\r\n        for (int i=0; i<solvers.size(); i++) {\r\n            if (i != index && ui->solverId->text().trimmed()==solvers[i]->id) {\r\n                showMessageBox(\"A solver with that solver ID already exists.\");\r\n                return false;\r\n            }\r\n        }\r\n        if (index==solvers.size()) {\r\n            auto* s = new Solver;\r\n            s->configFile = userSolverConfigDir+\"/\"+ui->solverId->text().trimmed()+\".msc\";\r\n            if (!QDir().mkpath(userSolverConfigDir)) {\r\n                showMessageBox(\"Cannot create user configuration directory \"+userSolverConfigDir);\r\n                return false;\r\n            }\r\n            solvers.append(s);\r\n            allowFileRestore(s->configFile);\r\n        }\r\n\r\n        solvers[index]->executable = ui->executable->text().trimmed();\r\n        solvers[index]->mznlib = ui->mznpath->text();\r\n        solvers[index]->name = ui->name->text().trimmed();\r\n        solvers[index]->id = ui->solverId->text().trimmed();\r\n        solvers[index]->version = ui->version->text().trimmed();\r\n        solvers[index]->isGUIApplication= ui->detach->isChecked();\r\n        Solver::SolverInputType inputTypeMap[4] = { Solver::I_FZN, Solver::I_JSON, Solver::I_MZN, Solver::I_NL };\r\n        solvers[index]->inputType = inputTypeMap[ui->inputType_comboBox->currentIndex()];\r\n        solvers[index]->needsSolns2Out = ui->needs_solns2out->isChecked();\r\n\r\n        solvers[index]->stdFlags.removeAll(\"-a\");\r\n        if (ui->has_stdflag_a->isChecked()) {\r\n            solvers[index]->stdFlags.push_back(\"-a\");\r\n        }\r\n        solvers[index]->stdFlags.removeAll(\"-n\");\r\n        if (ui->has_stdflag_n->isChecked()) {\r\n            solvers[index]->stdFlags.push_back(\"-n\");\r\n        }\r\n        solvers[index]->stdFlags.removeAll(\"-p\");\r\n        if (ui->has_stdflag_p->isChecked()) {\r\n            solvers[index]->stdFlags.push_back(\"-p\");\r\n        }\r\n        solvers[index]->stdFlags.removeAll(\"-s\");\r\n        if (ui->has_stdflag_s->isChecked()) {\r\n            solvers[index]->stdFlags.push_back(\"-s\");\r\n        }\r\n        solvers[index]->stdFlags.removeAll(\"-v\");\r\n        if (ui->has_stdflag_v->isChecked()) {\r\n            solvers[index]->stdFlags.push_back(\"-v\");\r\n        }\r\n        solvers[index]->stdFlags.removeAll(\"-r\");\r\n        if (ui->has_stdflag_r->isChecked()) {\r\n            solvers[index]->stdFlags.push_back(\"-r\");\r\n        }\r\n        solvers[index]->stdFlags.removeAll(\"-f\");\r\n        if (ui->has_stdflag_f->isChecked()) {\r\n            solvers[index]->stdFlags.push_back(\"-f\");\r\n        }\r\n        solvers[index]->stdFlags.removeAll(\"-t\");\r\n        if (ui->has_stdflag_t->isChecked()) {\r\n            solvers[index]->stdFlags.push_back(\"-t\");\r\n        }\r\n\r\n        QJsonObject json = solvers[index]->json;\r\n        json.remove(\"extraInfo\");\r\n        json[\"executable\"] = ui->executable->text().trimmed();\r\n        json[\"mznlib\"] = ui->mznpath->text();\r\n        json[\"name\"] = ui->name->text().trimmed();\r\n        json[\"id\"] = ui->solverId->text().trimmed();\r\n        json[\"version\"] = ui->version->text().trimmed();\r\n        json[\"isGUIApplication\"] = ui->detach->isChecked();\r\n        QStringList inputTypeStringMap = { \"FZN\", \"JSON\", \"MZN\", \"NL\" };\r\n        json[\"inputType\"] = inputTypeStringMap[ui->inputType_comboBox->currentIndex()];\r\n        json.remove(\"supportsFzn\");\r\n        json.remove(\"supportsMzn\");\r\n        json.remove(\"supportsNL\");\r\n        json[\"needsSolns2Out\"] = ui->needs_solns2out->isChecked();\r\n        json[\"stdFlags\"] = QJsonArray::fromStringList(solvers[index]->stdFlags);\r\n        QJsonDocument jdoc(json);\r\n        QFile jdocFile(solvers[index]->configFile);\r\n        allowFileRestore(solvers[index]->configFile);\r\n        if (!jdocFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) {\r\n            showMessageBox(\"Cannot save configuration file \"+solvers[index]->configFile);\r\n            return false;\r\n        }\r\n        if (jdocFile.write(jdoc.toJson())==-1) {\r\n            showMessageBox(\"Cannot save configuration file \"+solvers[index]->configFile);\r\n            return false;\r\n        }\r\n    }\r\n    _editingSolverIndex = -1;\r\n    loadDriver(false);\r\n    return true;\r\n}\r\n\r\nnamespace {\r\n\r\n    void clearRequiredFlagsLayout(QGridLayout* l) {\r\n        QVector<QLayoutItem*> items;\r\n        for (int i=0; i<l->rowCount(); i++) {\r\n            for (int j=0; j<l->columnCount(); j++) {\r\n                if (QLayoutItem* li = l->itemAtPosition(i,j)) {\r\n                    if (QWidget* w = li->widget()) {\r\n                        w->hide();\r\n                        w->deleteLater();\r\n                    }\r\n                    items.push_back(li);\r\n                }\r\n            }\r\n        }\r\n        for (auto i : items) {\r\n            l->removeItem(i);\r\n            delete i;\r\n        }\r\n    }\r\n\r\n}\r\n\r\nvoid PreferencesDialog::on_deleteButton_clicked()\r\n{\r\n    auto& driver = MznDriver::get();\r\n    auto& solvers = driver.solvers();\r\n    int index = ui->solvers_combo->currentIndex();\r\n    if (index >= solvers.size()) {\r\n        _editingSolverIndex = -1;\r\n        if (solvers.size() == 0) {\r\n            // No solver to switch back to, so create dummy\r\n            ui->solvers_combo->insertItem(0, \"(No solvers found)\");\r\n            index++;\r\n        }\r\n        ui->solvers_combo->setCurrentIndex(index > 0 ? index - 1 : 0);\r\n        ui->solvers_combo->removeItem(index);\r\n        return;\r\n    }\r\n    if (QMessageBox::warning(this,\r\n                                                       \"MiniZinc IDE\",\r\n                                                       \"Delete solver \" + solvers[index]->name + \"?\",\r\n                                                       QMessageBox::Ok | QMessageBox::Cancel)\r\n            == QMessageBox::Ok) {\r\n        auto configFile = solvers[index]->configFile;\r\n        allowFileRestore(configFile);\r\n        QFile sf(configFile);\r\n        if (!sf.remove()) {\r\n            showMessageBox(\"Cannot remove configuration file \"+solvers[index]->configFile);\r\n            return;\r\n        }\r\n        solvers.removeAt(index);\r\n        if (solvers.size() == 0) {\r\n            // No solver to switch back to, so create dummy\r\n            ui->solvers_combo->insertItem(0, \"(No solvers found)\");\r\n            index++;\r\n        }\r\n        ui->solvers_combo->setCurrentIndex(index > 0 ? index - 1 : 0);\r\n        ui->solvers_combo->removeItem(index);\r\n    }\r\n}\r\n\r\nvoid PreferencesDialog::on_mznpath_select_clicked()\r\n{\r\n    QFileDialog fd(this,\"Select MiniZinc distribution path (bin directory)\");\r\n    QDir dir(ui->mznDistribPath->text());\r\n    fd.setDirectory(dir);\r\n    fd.setFileMode(QFileDialog::Directory);\r\n    fd.setOption(QFileDialog::ShowDirsOnly, true);\r\n    if (fd.exec()) {\r\n        ui->mznDistribPath->setText(fd.selectedFiles().first());\r\n        loadDriver(true);\r\n    }\r\n}\r\n\r\nvoid PreferencesDialog::on_exec_select_clicked()\r\n{\r\n    QFileDialog fd(this,\"Select solver executable\");\r\n    fd.selectFile(ui->executable->text());\r\n    fd.setFileMode(QFileDialog::ExistingFile);\r\n    if (fd.exec()) {\r\n        ui->executable->setText(fd.selectedFiles().first());\r\n    }\r\n    QFileInfo fi(ui->executable->text());\r\n    ui->exeNotFoundLabel->setHidden(fi.exists());\r\n}\r\n\r\nvoid PreferencesDialog::on_mznlib_select_clicked()\r\n{\r\n    QFileDialog fd(this,\"Select solver library path\");\r\n    QDir dir(ui->mznpath->text());\r\n    fd.setDirectory(dir);\r\n    fd.setFileMode(QFileDialog::Directory);\r\n    fd.setOption(QFileDialog::ShowDirsOnly, true);\r\n    if (fd.exec()) {\r\n        ui->mznpath->setText(fd.selectedFiles().first());\r\n    }\r\n}\r\n\r\nvoid PreferencesDialog::on_mznDistribPath_returnPressed()\r\n{\r\n    loadDriver(true);\r\n}\r\n\r\n\r\nvoid PreferencesDialog::on_check_solver_clicked()\r\n{\r\n    loadDriver(true);\r\n}\r\n\r\n\r\nvoid PreferencesDialog::on_extraSearchPathAdd_pushButton_clicked()\r\n{\r\n    QFileDialog fd(this ,\"Select path containing solver configuration (.msc) files\");\r\n    auto selected = ui->extraSearchPath_listWidget->selectedItems();\r\n    if (!selected.isEmpty()) {\r\n        QDir dir(selected.first()->text());\r\n        fd.setDirectory(dir);\r\n    }\r\n    fd.setFileMode(QFileDialog::Directory);\r\n    fd.setOption(QFileDialog::ShowDirsOnly, true);\r\n    if (fd.exec()) {\r\n        for (auto& dir : fd.selectedFiles()) {\r\n            ui->extraSearchPath_listWidget->addItem(dir);\r\n        }\r\n        _extraSearchPathsChanged = true;\r\n    }\r\n}\r\n\r\nvoid PreferencesDialog::on_extraSearchPathEdit_pushButton_clicked()\r\n{\r\n    QFileDialog fd(this, \"Select path containing solver configuration (.msc) files\");\r\n    auto selected = ui->extraSearchPath_listWidget->selectedItems();\r\n    if (selected.isEmpty()) {\r\n        return;\r\n    }\r\n    QDir dir(selected.first()->text());\r\n    fd.setDirectory(dir);\r\n    fd.setFileMode(QFileDialog::Directory);\r\n    fd.setOption(QFileDialog::ShowDirsOnly, true);\r\n    if (fd.exec()) {\r\n        selected.first()->setText(fd.selectedFiles().first());\r\n        _extraSearchPathsChanged = true;\r\n    }\r\n}\r\n\r\nvoid PreferencesDialog::on_extraSearchPathDelete_pushButton_clicked()\r\n{\r\n    auto items = ui->extraSearchPath_listWidget->selectedItems();\r\n    if (!items.isEmpty()) {\r\n        delete items.first();\r\n        _extraSearchPathsChanged = true;\r\n    }\r\n}\r\n\r\nvoid PreferencesDialog::on_extraSearchPath_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)\r\n{\r\n    bool hasSelection = current != nullptr;\r\n    ui->extraSearchPathEdit_pushButton->setEnabled(hasSelection);\r\n    ui->extraSearchPathDelete_pushButton->setEnabled(hasSelection);\r\n}\r\n\r\nvoid PreferencesDialog::updateSearchPaths()\r\n{\r\n    if (!_extraSearchPathsChanged) {\r\n        return;\r\n    }\r\n\r\n    auto& driver = MznDriver::get();\r\n    auto& userConfigFile = driver.userConfigFile();\r\n    QFile uc(userConfigFile);\r\n    auto doc = QJsonDocument::fromJson(allowFileRestore(userConfigFile));\r\n    auto obj = doc.object();\r\n    auto* list = ui->extraSearchPath_listWidget;\r\n    if (list->count() > 0) {\r\n        QJsonArray arr;\r\n        for (auto i = 0; i < list->count(); i++) {\r\n            auto path = list->item(i)->text();\r\n            if (!path.isEmpty()) {\r\n                arr.append(path);\r\n            }\r\n        }\r\n        obj[\"mzn_solver_path\"] = arr;\r\n    } else if (obj.contains(\"mzn_solver_path\")) {\r\n        obj.remove(\"mzn_solver_path\");\r\n    }\r\n\r\n    QFileInfo uc_info(userConfigFile);\r\n    if (!QDir().mkpath(uc_info.absoluteDir().absolutePath())) {\r\n        showMessageBox(\"Cannot create user configuration directory \"+uc_info.absoluteDir().absolutePath());\r\n    }\r\n    if (uc.open(QFile::ReadWrite | QIODevice::Truncate)) {\r\n        uc.write(QJsonDocument(obj).toJson());\r\n        uc.close();\r\n        _extraSearchPathsChanged = false;\r\n        loadDriver(true);\r\n    } else {\r\n        showMessageBox(\"Cannot write user configuration file \" + userConfigFile);\r\n    }\r\n}\r\n\r\nvoid PreferencesDialog::on_solvers_combo_currentIndexChanged(int index)\r\n{\r\n    if (!_solversPopulated || index == _editingSolverIndex) {\r\n        return;\r\n    }\r\n    if (_editingSolverIndex != -1) {\r\n        if (!updateSolver()) {\r\n            // Cannot edit another solver unless this one is valid\r\n            ui->solvers_combo->setCurrentIndex(_editingSolverIndex);\r\n        }\r\n        return;\r\n    }\r\n\r\n    auto& driver = MznDriver::get();\r\n    auto& solvers = driver.solvers();\r\n    QGridLayout* rfLayout = static_cast<QGridLayout*>(ui->requiredFlags->layout());\r\n    clearRequiredFlagsLayout(rfLayout);\r\n    QString userSolverConfigCanonical = QFileInfo(driver.userSolverConfigDir()).canonicalPath();\r\n    if (index<solvers.size()) {\r\n        ui->name->setText(solvers[index]->name);\r\n        ui->solverId->setText(solvers[index]->id);\r\n        ui->version->setText(solvers[index]->version);\r\n        ui->executable->setText(solvers[index]->executable);\r\n        ui->exeNotFoundLabel->setVisible(!solvers[index]->executable.isEmpty() && solvers[index]->executable_resolved.isEmpty());\r\n        ui->detach->setChecked(solvers[index]->isGUIApplication);\r\n        switch (solvers[index]->inputType) {\r\n        case Solver::I_FZN:\r\n            ui->inputType_comboBox->setCurrentIndex(0);\r\n            break;\r\n        case Solver::I_JSON:\r\n            ui->inputType_comboBox->setCurrentIndex(1);\r\n            break;\r\n        case Solver::I_MZN:\r\n            ui->inputType_comboBox->setCurrentIndex(2);\r\n            break;\r\n        case Solver::I_NL:\r\n            ui->inputType_comboBox->setCurrentIndex(3);\r\n            break;\r\n        default:\r\n            ui->inputType_comboBox->setCurrentIndex(0);\r\n            break;\r\n        }\r\n        ui->needs_solns2out->setChecked(solvers[index]->needsSolns2Out);\r\n        ui->mznpath->setText(solvers[index]->mznlib);\r\n        bool solverConfigIsUserEditable = false;\r\n        if (!solvers[index]->configFile.isEmpty()) {\r\n            QFileInfo configFileInfo(solvers[index]->configFile);\r\n            if (configFileInfo.canonicalPath().startsWith(userSolverConfigCanonical)) {\r\n                solverConfigIsUserEditable = true;\r\n            }\r\n        }\r\n\r\n        ui->deleteButton->setEnabled(solverConfigIsUserEditable);\r\n        ui->solverFrame->setEnabled(solverConfigIsUserEditable);\r\n\r\n        ui->has_stdflag_a->setChecked(solvers[index]->stdFlags.contains(\"-a\"));\r\n        ui->has_stdflag_p->setChecked(solvers[index]->stdFlags.contains(\"-p\"));\r\n        ui->has_stdflag_r->setChecked(solvers[index]->stdFlags.contains(\"-r\"));\r\n        ui->has_stdflag_n->setChecked(solvers[index]->stdFlags.contains(\"-n\"));\r\n        ui->has_stdflag_s->setChecked(solvers[index]->stdFlags.contains(\"-s\"));\r\n        ui->has_stdflag_f->setChecked(solvers[index]->stdFlags.contains(\"-f\"));\r\n        ui->has_stdflag_v->setChecked(solvers[index]->stdFlags.contains(\"-v\"));\r\n        ui->has_stdflag_t->setChecked(solvers[index]->stdFlags.contains(\"-t\"));\r\n\r\n        auto flags = _userDefaultFlags.values(solvers[index]->id);\r\n        for (auto& rf : solvers[index]->requiredFlags) {\r\n            if (!flags.contains(rf)) {\r\n                flags << rf;\r\n            }\r\n        }\r\n        if (flags.empty()) {\r\n            ui->requiredFlags->hide();\r\n        } else {\r\n            ui->requiredFlags->show();\r\n            int row = 0;\r\n            for (auto& rf : flags) {\r\n                QString val;\r\n                int foundFlag = solvers[index]->defaultFlags.indexOf(rf);\r\n                if (foundFlag != -1 && foundFlag < solvers[index]->defaultFlags.size()-1) {\r\n                    val = solvers[index]->defaultFlags[foundFlag+1];\r\n                }\r\n                rfLayout->addWidget(new QLabel(rf), row, 0);\r\n                rfLayout->addWidget(new QLineEdit(val), row, 1);\r\n                row++;\r\n            }\r\n\r\n            IDEUtils::watchChildChanges(ui->requiredFlags, this, [=] () {\r\n                auto& driver = MznDriver::get();\r\n                auto& solvers = driver.solvers();\r\n                if (!solvers.isEmpty()) {\r\n                    _editingSolverIndex = ui->solvers_combo->currentIndex();\r\n                }\r\n            });\r\n        }\r\n        _editingSolverIndex = -1;\r\n    } else {\r\n        ui->name->setText(\"\");\r\n        ui->solverId->setText(\"\");\r\n        ui->version->setText(\"\");\r\n        ui->executable->setText(\"\");\r\n        ui->detach->setChecked(false);\r\n        ui->inputType_comboBox->setCurrentIndex(0);\r\n        ui->needs_solns2out->setChecked(true);\r\n        ui->mznpath->setText(\"\");\r\n        ui->solverFrame->setEnabled(true);\r\n        ui->deleteButton->setEnabled(true);\r\n        ui->has_stdflag_a->setChecked(false);\r\n        ui->has_stdflag_p->setChecked(false);\r\n        ui->has_stdflag_r->setChecked(false);\r\n        ui->has_stdflag_n->setChecked(false);\r\n        ui->has_stdflag_s->setChecked(false);\r\n        ui->has_stdflag_v->setChecked(false);\r\n        ui->has_stdflag_f->setChecked(false);\r\n        ui->has_stdflag_t->setChecked(false);\r\n        ui->requiredFlags->hide();\r\n\r\n        bool addNew = index == ui->solvers_combo->count() - 1;\r\n\r\n        if (addNew) {\r\n            if (solvers.isEmpty()) {\r\n                // Remove dummy solver\r\n                ui->solvers_combo->removeItem(0);\r\n                return;\r\n            }\r\n            ui->solvers_combo->setItemText(index, \"New solver\");\r\n            ui->solvers_combo->addItem(\"Add new...\");\r\n            _editingSolverIndex = index;\r\n        } else {\r\n            _editingSolverIndex = -1;\r\n        }\r\n\r\n        ui->deleteButton->setEnabled(addNew);\r\n        ui->solverFrame->setEnabled(addNew);\r\n    }\r\n}\r\n\r\nvoid PreferencesDialog::showMessageBox(const QString& message)\r\n{\r\n    // Defer message box so that we can return immediately\r\n    QTimer::singleShot(0, this, [=] () {\r\n        QMessageBox::warning(this, \"MiniZinc IDE\", message, QMessageBox::Ok);\r\n    });\r\n}\r\n\r\nvoid PreferencesDialog::on_tabWidget_currentChanged(int index)\r\n{\r\n    updateSearchPaths();\r\n    if (index == 1 && !_solversPopulated) {\r\n        populateSolvers();\r\n        on_solvers_combo_currentIndexChanged(0);\r\n    }\r\n    // Cannot change out of solver tab if changes invalid\r\n    if (index != 1 && !updateSolver()) {\r\n        ui->tabWidget->setCurrentIndex(1);\r\n    }\r\n}\r\n\r\n\r\nvoid PreferencesDialog::accept()\r\n{\r\n    if (ui->tabWidget->currentIndex() == 1 && !updateSolver()) {\r\n        // Can't accept until updateSolver() succeeds\r\n        return;\r\n    }\r\n\r\n    QDialog::accept();\r\n}\r\n\r\n\r\nvoid PreferencesDialog::on_PreferencesDialog_rejected()\r\n{\r\n    // Undo theme changes\r\n    ui->theme_comboBox->setCurrentIndex(_origThemeIndex);\r\n    ui->darkMode_checkBox->setChecked(_origDarkMode);\r\n\r\n    if (_restore.empty() && _remove.empty()) {\r\n        // Nothing to do\r\n        return;\r\n    }\r\n\r\n    // Undo any file changes on cancellation\r\n    for (auto it = _restore.begin(); it != _restore.end(); it++) {\r\n        QFile f(it.key());\r\n        if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {\r\n            f.write(it.value());\r\n            f.close();\r\n        }\r\n    }\r\n    for (auto& it : _remove) {\r\n        QFile::remove(it);\r\n    }\r\n    _restore.clear();\r\n    _remove.clear();\r\n\r\n    // Reload old driver (may have updated things that have been reverted)\r\n    try {\r\n        MznDriver::get().setLocation(_origMznDistribPath);\r\n    } catch (Exception& e) {}\r\n}\r\n\r\nvoid PreferencesDialog::on_PreferencesDialog_accepted()\r\n{\r\n    updateSearchPaths();\r\n\r\n    QSettings settings;\r\n\r\n    settings.beginGroup(\"ide\");\r\n    settings.setValue(\"checkforupdates21\", ui->check_updates->isChecked());\r\n    settings.setValue(\"checkSolutions\", ui->checkSolutions_checkBox->isChecked());\r\n    settings.setValue(\"clearOutput\", ui->clearOutput_checkBox->isChecked());\r\n    settings.setValue(\"compressSolutions\", ui->compressSolutions_checkBox->isChecked()\r\n                      ? ui->compressSolutions_spinBox->value() : 0);\r\n    settings.setValue(\"printCommand\", ui->printCommand_checkBox->isChecked());\r\n    settings.setValue(\"reuseVis\", ui->reuseVis_checkBox->isChecked());\r\n    settings.setValue(\"visPort\", ui->visPort_spinBox->value());\r\n    settings.setValue(\"visWsPort\", ui->visWsPort_spinBox->value());\r\n    settings.setValue(\"printVisUrl\", ui->visUrl_checkBox->isChecked());\r\n    settings.setValue(\"theme\", ui->theme_comboBox->currentIndex());\r\n    settings.setValue(\"indentTabs\", ui->indentTabs_radioButton->isChecked());\r\n    settings.setValue(\"indentSize\", ui->indentSize_spinBox->value());\r\n    settings.setValue(\"wordWrap\", ui->lineWrapping_checkBox->isChecked());\r\n    settings.endGroup();\r\n\r\n    settings.beginGroup(\"MainWindow\");\r\n    settings.setValue(\"darkMode\", ui->darkMode_checkBox->isChecked());\r\n    auto editorFont = ui->fontComboBox->currentFont();\r\n    editorFont.setPointSize(ui->fontSize_spinBox->value());\r\n    settings.setValue(\"editorFont\", editorFont.toString());\r\n    settings.setValue(\"zoom\", ui->zoom_spinBox->value());\r\n    settings.endGroup();\r\n\r\n    settings.beginGroup(\"minizinc\");\r\n    settings.setValue(\"mznpath\", ui->mznDistribPath->text());\r\n    settings.endGroup();\r\n}\r\n\r\nvoid PreferencesDialog::updateSolverLabel()\r\n{\r\n    if (!_solversPopulated || _editingSolverIndex == -1) {\r\n        return;\r\n    }\r\n    auto name = ui->name->text().isEmpty() ? \"Untitled Solver\" : ui->name->text();\r\n    ui->solvers_combo->setItemText(_editingSolverIndex, name + \" \" + ui->version->text());\r\n}\r\n\r\n\r\nvoid PreferencesDialog::on_darkMode_checkBox_stateChanged(int checked)\r\n{\r\n    auto* d = IDE::instance()->darkModeNotifier;\r\n    auto* t = IDE::instance()->themeManager;\r\n    bool dark = checked == Qt::Checked;\r\n    d->requestChangeDarkMode(dark);\r\n    _ce->setTheme(t->current(), d->darkMode());\r\n}\r\n\r\nvoid PreferencesDialog::on_zoom_spinBox_valueChanged(int value)\r\n{\r\n    updateCodeEditorFont();\r\n}\r\n"
  },
  {
    "path": "MiniZincIDE/preferencesdialog.h",
    "content": "#ifndef PREFERENCESDIALOG_H\r\n#define PREFERENCESDIALOG_H\r\n\r\n#include <QDialog>\r\n#include <QTemporaryFile>\r\n#include <QListWidgetItem>\r\n#include \"codeeditor.h\"\r\n\r\nnamespace Ui {\r\nclass PreferencesDialog;\r\n}\r\n\r\nclass PreferencesDialog : public QDialog\r\n{\r\n    Q_OBJECT\r\n    friend class TestIDE;\r\n\r\npublic:\r\n    explicit PreferencesDialog(bool addNewSolver, QWidget *parent = nullptr);\r\n    ~PreferencesDialog();\r\n\r\n    void accept() Q_DECL_OVERRIDE;\r\n\r\nprivate slots:\r\n    void on_fontComboBox_currentFontChanged(const QFont &f);\r\n\r\n    void on_fontSize_spinBox_valueChanged(int arg1);\r\n\r\n    void on_lineWrapping_checkBox_stateChanged(int arg1);\r\n\r\n    void on_theme_comboBox_currentIndexChanged(int index);\r\n\r\n    void on_solvers_combo_currentIndexChanged(int index);\r\n\r\n    void on_tabWidget_currentChanged(int index);\r\n\r\n    void on_PreferencesDialog_rejected();\r\n\r\n    void on_deleteButton_clicked();\r\n\r\n    void on_mznpath_select_clicked();\r\n\r\n    void on_exec_select_clicked();\r\n\r\n    void on_PreferencesDialog_accepted();\r\n\r\n    void on_mznDistribPath_returnPressed();\r\n\r\n    void on_check_solver_clicked();\r\n\r\n    void on_mznlib_select_clicked();\r\n\r\n    void on_extraSearchPathAdd_pushButton_clicked();\r\n\r\n    void on_extraSearchPathEdit_pushButton_clicked();\r\n\r\n    void on_extraSearchPathDelete_pushButton_clicked();\r\n\r\n    void on_extraSearchPath_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);\r\n\r\n    void updateSolverLabel();\r\n\r\n    void on_darkMode_checkBox_stateChanged(int arg1);\r\n\r\n    void on_zoom_spinBox_valueChanged(int arg1);\r\n\r\nprivate:\r\n    Ui::PreferencesDialog *ui;\r\n\r\n    CodeEditor* _ce = nullptr;\r\n    bool _solversPopulated = false;\r\n    int _editingSolverIndex = -1;\r\n    bool _extraSearchPathsChanged = false;\r\n    QMap<QString, QByteArray> _restore;\r\n    QSet<QString> _remove;\r\n    QString _origMznDistribPath;\r\n\r\n    bool _origDarkMode = false;\r\n    int _origThemeIndex = 0;\r\n\r\n    QMultiMap<QString, QString> _userDefaultFlags;\r\n\r\n    QByteArray allowFileRestore(const QString& path);\r\n    void loadDriver(bool showError);\r\n    void populateSolvers();\r\n    bool updateSolver();\r\n    void updateSearchPaths();\r\n    void showMessageBox(const QString& message);\r\n    void updateCodeEditorFont();\r\n};\r\n\r\n#endif // PREFERENCESDIALOG_H\r\n"
  },
  {
    "path": "MiniZincIDE/preferencesdialog.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<ui version=\"4.0\">\r\n <class>PreferencesDialog</class>\r\n <widget class=\"QDialog\" name=\"PreferencesDialog\">\r\n  <property name=\"geometry\">\r\n   <rect>\r\n    <x>0</x>\r\n    <y>0</y>\r\n    <width>690</width>\r\n    <height>652</height>\r\n   </rect>\r\n  </property>\r\n  <property name=\"windowTitle\">\r\n   <string>Preferences</string>\r\n  </property>\r\n  <property name=\"sizeGripEnabled\">\r\n   <bool>true</bool>\r\n  </property>\r\n  <property name=\"modal\">\r\n   <bool>true</bool>\r\n  </property>\r\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\r\n   <item>\r\n    <widget class=\"QTabWidget\" name=\"tabWidget\">\r\n     <property name=\"currentIndex\">\r\n      <number>0</number>\r\n     </property>\r\n     <widget class=\"QWidget\" name=\"minizinc_tab\">\r\n      <attribute name=\"title\">\r\n       <string>MiniZinc</string>\r\n      </attribute>\r\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\r\n       <item>\r\n        <widget class=\"QGroupBox\" name=\"groupBox_6\">\r\n         <property name=\"title\">\r\n          <string>Compiler</string>\r\n         </property>\r\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_13\">\r\n          <item>\r\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_12\">\r\n            <item>\r\n             <layout class=\"QHBoxLayout\" name=\"horizontalLayout_9\">\r\n              <item>\r\n               <layout class=\"QHBoxLayout\" name=\"horizontalLayout_8\">\r\n                <item>\r\n                 <widget class=\"QLabel\" name=\"label_6\">\r\n                  <property name=\"text\">\r\n                   <string>MiniZinc path</string>\r\n                  </property>\r\n                 </widget>\r\n                </item>\r\n                <item>\r\n                 <widget class=\"QLineEdit\" name=\"mznDistribPath\"/>\r\n                </item>\r\n               </layout>\r\n              </item>\r\n              <item>\r\n               <widget class=\"QPushButton\" name=\"mznpath_select\">\r\n                <property name=\"text\">\r\n                 <string>Select</string>\r\n                </property>\r\n                <property name=\"flat\">\r\n                 <bool>false</bool>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item>\r\n               <widget class=\"QPushButton\" name=\"check_solver\">\r\n                <property name=\"text\">\r\n                 <string>Check</string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n             </layout>\r\n            </item>\r\n            <item>\r\n             <widget class=\"QFrame\" name=\"frame\">\r\n              <property name=\"frameShape\">\r\n               <enum>QFrame::StyledPanel</enum>\r\n              </property>\r\n              <property name=\"frameShadow\">\r\n               <enum>QFrame::Raised</enum>\r\n              </property>\r\n              <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\r\n               <item>\r\n                <widget class=\"QLabel\" name=\"label_7\">\r\n                 <property name=\"text\">\r\n                  <string>Found MiniZinc installation:</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QLabel\" name=\"mzn2fzn_version\">\r\n                 <property name=\"text\">\r\n                  <string>none</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n              </layout>\r\n             </widget>\r\n            </item>\r\n            <item>\r\n             <widget class=\"QCheckBox\" name=\"check_updates\">\r\n              <property name=\"text\">\r\n               <string>Check for updates to MiniZinc on startup once a day</string>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n           </layout>\r\n          </item>\r\n         </layout>\r\n        </widget>\r\n       </item>\r\n       <item>\r\n        <widget class=\"QGroupBox\" name=\"configuration_groupBox\">\r\n         <property name=\"title\">\r\n          <string>Configuration</string>\r\n         </property>\r\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_10\">\r\n          <item>\r\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_7\">\r\n            <item>\r\n             <widget class=\"QLabel\" name=\"label_10\">\r\n              <property name=\"text\">\r\n               <string>Extra solver search paths:</string>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item>\r\n             <widget class=\"QListWidget\" name=\"extraSearchPath_listWidget\">\r\n              <property name=\"dragDropMode\">\r\n               <enum>QAbstractItemView::NoDragDrop</enum>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item>\r\n             <layout class=\"QHBoxLayout\" name=\"horizontalLayout_13\">\r\n              <item>\r\n               <widget class=\"QPushButton\" name=\"extraSearchPathAdd_pushButton\">\r\n                <property name=\"text\">\r\n                 <string>Add new</string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item>\r\n               <widget class=\"QPushButton\" name=\"extraSearchPathEdit_pushButton\">\r\n                <property name=\"enabled\">\r\n                 <bool>false</bool>\r\n                </property>\r\n                <property name=\"text\">\r\n                 <string>Edit</string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item>\r\n               <widget class=\"QPushButton\" name=\"extraSearchPathDelete_pushButton\">\r\n                <property name=\"enabled\">\r\n                 <bool>false</bool>\r\n                </property>\r\n                <property name=\"text\">\r\n                 <string>Delete</string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item>\r\n               <spacer name=\"horizontalSpacer_2\">\r\n                <property name=\"orientation\">\r\n                 <enum>Qt::Horizontal</enum>\r\n                </property>\r\n                <property name=\"sizeHint\" stdset=\"0\">\r\n                 <size>\r\n                  <width>40</width>\r\n                  <height>20</height>\r\n                 </size>\r\n                </property>\r\n               </spacer>\r\n              </item>\r\n             </layout>\r\n            </item>\r\n           </layout>\r\n          </item>\r\n         </layout>\r\n        </widget>\r\n       </item>\r\n      </layout>\r\n     </widget>\r\n     <widget class=\"QWidget\" name=\"solvers_tab\">\r\n      <attribute name=\"title\">\r\n       <string>Solvers</string>\r\n      </attribute>\r\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_5\">\r\n       <item>\r\n        <widget class=\"QGroupBox\" name=\"groupBox_7\">\r\n         <property name=\"title\">\r\n          <string>Solver</string>\r\n         </property>\r\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_14\">\r\n          <item>\r\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\r\n            <item>\r\n             <widget class=\"QComboBox\" name=\"solvers_combo\">\r\n              <property name=\"sizePolicy\">\r\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\r\n                <horstretch>1</horstretch>\r\n                <verstretch>0</verstretch>\r\n               </sizepolicy>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item>\r\n             <widget class=\"QPushButton\" name=\"deleteButton\">\r\n              <property name=\"text\">\r\n               <string>Delete</string>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n           </layout>\r\n          </item>\r\n          <item>\r\n           <widget class=\"QFrame\" name=\"solverFrame\">\r\n            <property name=\"frameShape\">\r\n             <enum>QFrame::StyledPanel</enum>\r\n            </property>\r\n            <property name=\"frameShadow\">\r\n             <enum>QFrame::Raised</enum>\r\n            </property>\r\n            <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\r\n             <item>\r\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\r\n               <item>\r\n                <widget class=\"QLabel\" name=\"label_2\">\r\n                 <property name=\"text\">\r\n                  <string>Name</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QLineEdit\" name=\"name\"/>\r\n               </item>\r\n              </layout>\r\n             </item>\r\n             <item>\r\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_11\">\r\n               <item>\r\n                <widget class=\"QLabel\" name=\"label_8\">\r\n                 <property name=\"text\">\r\n                  <string>Solver ID</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QLineEdit\" name=\"solverId\"/>\r\n               </item>\r\n              </layout>\r\n             </item>\r\n             <item>\r\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_6\">\r\n               <item>\r\n                <widget class=\"QLabel\" name=\"label_5\">\r\n                 <property name=\"text\">\r\n                  <string>Version</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QLineEdit\" name=\"version\"/>\r\n               </item>\r\n              </layout>\r\n             </item>\r\n             <item>\r\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_4\">\r\n               <item>\r\n                <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\r\n                 <item>\r\n                  <widget class=\"QLabel\" name=\"label_3\">\r\n                   <property name=\"text\">\r\n                    <string>Executable</string>\r\n                   </property>\r\n                  </widget>\r\n                 </item>\r\n                 <item>\r\n                  <widget class=\"QLineEdit\" name=\"executable\"/>\r\n                 </item>\r\n                </layout>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QPushButton\" name=\"exec_select\">\r\n                 <property name=\"sizePolicy\">\r\n                  <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Fixed\">\r\n                   <horstretch>0</horstretch>\r\n                   <verstretch>0</verstretch>\r\n                  </sizepolicy>\r\n                 </property>\r\n                 <property name=\"text\">\r\n                  <string>Select</string>\r\n                 </property>\r\n                 <property name=\"autoDefault\">\r\n                  <bool>true</bool>\r\n                 </property>\r\n                 <property name=\"flat\">\r\n                  <bool>false</bool>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n              </layout>\r\n             </item>\r\n             <item>\r\n              <widget class=\"QLabel\" name=\"exeNotFoundLabel\">\r\n               <property name=\"text\">\r\n                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#fc0107;&quot;&gt;Executable not found!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\r\n               </property>\r\n               <property name=\"textFormat\">\r\n                <enum>Qt::RichText</enum>\r\n               </property>\r\n              </widget>\r\n             </item>\r\n             <item>\r\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_10\">\r\n               <item>\r\n                <widget class=\"QCheckBox\" name=\"detach\">\r\n                 <property name=\"text\">\r\n                  <string>Detach from IDE</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QCheckBox\" name=\"needs_solns2out\">\r\n                 <property name=\"text\">\r\n                  <string>Run with solns2out</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n              </layout>\r\n             </item>\r\n             <item>\r\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_7\">\r\n               <item>\r\n                <widget class=\"QLabel\" name=\"label_16\">\r\n                 <property name=\"text\">\r\n                  <string>Input type</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QComboBox\" name=\"inputType_comboBox\">\r\n                 <property name=\"sizePolicy\">\r\n                  <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\r\n                   <horstretch>0</horstretch>\r\n                   <verstretch>0</verstretch>\r\n                  </sizepolicy>\r\n                 </property>\r\n                 <item>\r\n                  <property name=\"text\">\r\n                   <string>FlatZinc (FZN)</string>\r\n                  </property>\r\n                 </item>\r\n                 <item>\r\n                  <property name=\"text\">\r\n                   <string>FlatZinc-JSON (JSON)</string>\r\n                  </property>\r\n                 </item>\r\n                 <item>\r\n                  <property name=\"text\">\r\n                   <string>MiniZinc (MZN)</string>\r\n                  </property>\r\n                 </item>\r\n                 <item>\r\n                  <property name=\"text\">\r\n                   <string>Non-linear (NL)</string>\r\n                  </property>\r\n                 </item>\r\n                </widget>\r\n               </item>\r\n              </layout>\r\n             </item>\r\n             <item>\r\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_5\">\r\n               <item>\r\n                <widget class=\"QLabel\" name=\"label_4\">\r\n                 <property name=\"text\">\r\n                  <string>Solver library path</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QLineEdit\" name=\"mznpath\"/>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QPushButton\" name=\"mznlib_select\">\r\n                 <property name=\"text\">\r\n                  <string>Select</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n              </layout>\r\n             </item>\r\n             <item>\r\n              <widget class=\"QLabel\" name=\"label_9\">\r\n               <property name=\"text\">\r\n                <string>Supported standard command line flags:</string>\r\n               </property>\r\n              </widget>\r\n             </item>\r\n             <item>\r\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_12\">\r\n               <item>\r\n                <widget class=\"QCheckBox\" name=\"has_stdflag_a\">\r\n                 <property name=\"text\">\r\n                  <string>-a</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QCheckBox\" name=\"has_stdflag_n\">\r\n                 <property name=\"text\">\r\n                  <string>-n</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QCheckBox\" name=\"has_stdflag_s\">\r\n                 <property name=\"text\">\r\n                  <string>-s</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QCheckBox\" name=\"has_stdflag_v\">\r\n                 <property name=\"text\">\r\n                  <string>-v</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QCheckBox\" name=\"has_stdflag_p\">\r\n                 <property name=\"text\">\r\n                  <string>-p</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QCheckBox\" name=\"has_stdflag_r\">\r\n                 <property name=\"text\">\r\n                  <string>-r</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QCheckBox\" name=\"has_stdflag_f\">\r\n                 <property name=\"text\">\r\n                  <string>-f</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n               <item>\r\n                <widget class=\"QCheckBox\" name=\"has_stdflag_t\">\r\n                 <property name=\"text\">\r\n                  <string>-t</string>\r\n                 </property>\r\n                </widget>\r\n               </item>\r\n              </layout>\r\n             </item>\r\n            </layout>\r\n           </widget>\r\n          </item>\r\n          <item>\r\n           <widget class=\"QGroupBox\" name=\"requiredFlags\">\r\n            <property name=\"title\">\r\n             <string>Required solver flags</string>\r\n            </property>\r\n            <layout class=\"QGridLayout\" name=\"gridLayout\">\r\n             <item row=\"0\" column=\"0\">\r\n              <widget class=\"QLabel\" name=\"label\">\r\n               <property name=\"text\">\r\n                <string>TextLabel</string>\r\n               </property>\r\n              </widget>\r\n             </item>\r\n            </layout>\r\n           </widget>\r\n          </item>\r\n         </layout>\r\n        </widget>\r\n       </item>\r\n       <item>\r\n        <spacer name=\"verticalSpacer\">\r\n         <property name=\"orientation\">\r\n          <enum>Qt::Vertical</enum>\r\n         </property>\r\n         <property name=\"sizeHint\" stdset=\"0\">\r\n          <size>\r\n           <width>20</width>\r\n           <height>40</height>\r\n          </size>\r\n         </property>\r\n        </spacer>\r\n       </item>\r\n      </layout>\r\n     </widget>\r\n     <widget class=\"QWidget\" name=\"editor_tab\">\r\n      <attribute name=\"title\">\r\n       <string>Editing</string>\r\n      </attribute>\r\n      <layout class=\"QGridLayout\" name=\"gridLayout_7\">\r\n       <item row=\"0\" column=\"0\">\r\n        <layout class=\"QVBoxLayout\" name=\"verticalLayout_8\">\r\n         <item>\r\n          <widget class=\"QGroupBox\" name=\"groupBox\">\r\n           <property name=\"title\">\r\n            <string>Font</string>\r\n           </property>\r\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_19\">\r\n            <item>\r\n             <layout class=\"QGridLayout\" name=\"gridLayout_2\">\r\n              <item row=\"0\" column=\"0\">\r\n               <widget class=\"QLabel\" name=\"label_13\">\r\n                <property name=\"text\">\r\n                 <string>Font family: </string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"0\" column=\"1\">\r\n               <widget class=\"QFontComboBox\" name=\"fontComboBox\">\r\n                <property name=\"sizePolicy\">\r\n                 <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\r\n                  <horstretch>0</horstretch>\r\n                  <verstretch>0</verstretch>\r\n                 </sizepolicy>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"0\" column=\"2\">\r\n               <widget class=\"QLabel\" name=\"label_14\">\r\n                <property name=\"text\">\r\n                 <string>Font size: </string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"0\" column=\"3\">\r\n               <widget class=\"QSpinBox\" name=\"fontSize_spinBox\">\r\n                <property name=\"sizePolicy\">\r\n                 <sizepolicy hsizetype=\"MinimumExpanding\" vsizetype=\"Fixed\">\r\n                  <horstretch>0</horstretch>\r\n                  <verstretch>0</verstretch>\r\n                 </sizepolicy>\r\n                </property>\r\n                <property name=\"suffix\">\r\n                 <string/>\r\n                </property>\r\n                <property name=\"minimum\">\r\n                 <number>5</number>\r\n                </property>\r\n                <property name=\"maximum\">\r\n                 <number>1000</number>\r\n                </property>\r\n                <property name=\"value\">\r\n                 <number>10</number>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"1\" column=\"0\">\r\n               <widget class=\"QLabel\" name=\"label_18\">\r\n                <property name=\"text\">\r\n                 <string>Zoom:</string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"1\" column=\"1\">\r\n               <widget class=\"QSpinBox\" name=\"zoom_spinBox\">\r\n                <property name=\"suffix\">\r\n                 <string>%</string>\r\n                </property>\r\n                <property name=\"minimum\">\r\n                 <number>10</number>\r\n                </property>\r\n                <property name=\"maximum\">\r\n                 <number>10000</number>\r\n                </property>\r\n                <property name=\"singleStep\">\r\n                 <number>10</number>\r\n                </property>\r\n                <property name=\"value\">\r\n                 <number>100</number>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n             </layout>\r\n            </item>\r\n           </layout>\r\n          </widget>\r\n         </item>\r\n         <item>\r\n          <widget class=\"QGroupBox\" name=\"groupBox_2\">\r\n           <property name=\"title\">\r\n            <string>Editing</string>\r\n           </property>\r\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_18\">\r\n            <item>\r\n             <layout class=\"QGridLayout\" name=\"gridLayout_4\">\r\n              <item row=\"0\" column=\"0\">\r\n               <widget class=\"QLabel\" name=\"label_12\">\r\n                <property name=\"text\">\r\n                 <string>Indent using: </string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"0\" column=\"1\">\r\n               <widget class=\"QRadioButton\" name=\"indentSpaces_radioButton\">\r\n                <property name=\"text\">\r\n                 <string>Spaces</string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"0\" column=\"2\">\r\n               <widget class=\"QRadioButton\" name=\"indentTabs_radioButton\">\r\n                <property name=\"text\">\r\n                 <string>Tabs</string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"1\" column=\"0\">\r\n               <widget class=\"QLabel\" name=\"label_11\">\r\n                <property name=\"text\">\r\n                 <string>Indent size: </string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"1\" column=\"1\" colspan=\"2\">\r\n               <widget class=\"QSpinBox\" name=\"indentSize_spinBox\">\r\n                <property name=\"sizePolicy\">\r\n                 <sizepolicy hsizetype=\"MinimumExpanding\" vsizetype=\"Fixed\">\r\n                  <horstretch>0</horstretch>\r\n                  <verstretch>0</verstretch>\r\n                 </sizepolicy>\r\n                </property>\r\n                <property name=\"minimum\">\r\n                 <number>1</number>\r\n                </property>\r\n                <property name=\"maximum\">\r\n                 <number>1000</number>\r\n                </property>\r\n                <property name=\"value\">\r\n                 <number>2</number>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"2\" column=\"0\" colspan=\"3\">\r\n               <widget class=\"QCheckBox\" name=\"lineWrapping_checkBox\">\r\n                <property name=\"text\">\r\n                 <string>Line wrapping</string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n             </layout>\r\n            </item>\r\n           </layout>\r\n          </widget>\r\n         </item>\r\n         <item>\r\n          <widget class=\"QGroupBox\" name=\"groupBox_3\">\r\n           <property name=\"title\">\r\n            <string>Appearance</string>\r\n           </property>\r\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_9\">\r\n            <item>\r\n             <layout class=\"QGridLayout\" name=\"gridLayout_5\" columnstretch=\"0,1\">\r\n              <item row=\"0\" column=\"0\">\r\n               <widget class=\"QLabel\" name=\"label_15\">\r\n                <property name=\"sizePolicy\">\r\n                 <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Preferred\">\r\n                  <horstretch>0</horstretch>\r\n                  <verstretch>0</verstretch>\r\n                 </sizepolicy>\r\n                </property>\r\n                <property name=\"text\">\r\n                 <string>Theme: </string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n              <item row=\"0\" column=\"1\">\r\n               <widget class=\"QComboBox\" name=\"theme_comboBox\">\r\n                <item>\r\n                 <property name=\"text\">\r\n                  <string>Default</string>\r\n                 </property>\r\n                </item>\r\n                <item>\r\n                 <property name=\"text\">\r\n                  <string>Blueberry</string>\r\n                 </property>\r\n                </item>\r\n                <item>\r\n                 <property name=\"text\">\r\n                  <string>Mango</string>\r\n                 </property>\r\n                </item>\r\n               </widget>\r\n              </item>\r\n              <item row=\"1\" column=\"0\" colspan=\"2\">\r\n               <widget class=\"QCheckBox\" name=\"darkMode_checkBox\">\r\n                <property name=\"text\">\r\n                 <string>Dark Mode</string>\r\n                </property>\r\n               </widget>\r\n              </item>\r\n             </layout>\r\n            </item>\r\n           </layout>\r\n          </widget>\r\n         </item>\r\n         <item>\r\n          <widget class=\"QGroupBox\" name=\"groupBox_4\">\r\n           <property name=\"title\">\r\n            <string>Preview</string>\r\n           </property>\r\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_11\">\r\n            <item>\r\n             <layout class=\"QVBoxLayout\" name=\"preview_verticalLayout\"/>\r\n            </item>\r\n           </layout>\r\n          </widget>\r\n         </item>\r\n        </layout>\r\n       </item>\r\n      </layout>\r\n     </widget>\r\n     <widget class=\"QWidget\" name=\"output_tab\">\r\n      <attribute name=\"title\">\r\n       <string>Output</string>\r\n      </attribute>\r\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_6\">\r\n       <item>\r\n        <widget class=\"QGroupBox\" name=\"groupBox_10\">\r\n         <property name=\"title\">\r\n          <string>Behaviour</string>\r\n         </property>\r\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_15\">\r\n          <item>\r\n           <layout class=\"QGridLayout\" name=\"gridLayout_3\">\r\n            <item row=\"0\" column=\"0\" colspan=\"2\">\r\n             <widget class=\"QCheckBox\" name=\"checkSolutions_checkBox\">\r\n              <property name=\"toolTip\">\r\n               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Run corresponding solution checker (.mzc or .mzc.mzn) if present&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\r\n              </property>\r\n              <property name=\"text\">\r\n               <string>Check solutions (if solution checker model is present in project)</string>\r\n              </property>\r\n              <property name=\"checked\">\r\n               <bool>true</bool>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item row=\"2\" column=\"1\">\r\n             <widget class=\"QSpinBox\" name=\"compressSolutions_spinBox\">\r\n              <property name=\"alignment\">\r\n               <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\r\n              </property>\r\n              <property name=\"minimum\">\r\n               <number>1</number>\r\n              </property>\r\n              <property name=\"maximum\">\r\n               <number>999999</number>\r\n              </property>\r\n              <property name=\"value\">\r\n               <number>100</number>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item row=\"2\" column=\"0\">\r\n             <widget class=\"QCheckBox\" name=\"compressSolutions_checkBox\">\r\n              <property name=\"text\">\r\n               <string>Compress solution output after this many solutions:</string>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item row=\"1\" column=\"0\" colspan=\"2\">\r\n             <widget class=\"QCheckBox\" name=\"clearOutput_checkBox\">\r\n              <property name=\"toolTip\">\r\n               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Clear the output window each time Run is pressed&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\r\n              </property>\r\n              <property name=\"text\">\r\n               <string>Clear output window before each run</string>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n           </layout>\r\n          </item>\r\n         </layout>\r\n        </widget>\r\n       </item>\r\n       <item>\r\n        <widget class=\"QGroupBox\" name=\"groupBox_5\">\r\n         <property name=\"title\">\r\n          <string>Visualisation</string>\r\n         </property>\r\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_16\">\r\n          <item>\r\n           <layout class=\"QGridLayout\" name=\"gridLayout_6\">\r\n            <item row=\"0\" column=\"0\" colspan=\"2\">\r\n             <widget class=\"QCheckBox\" name=\"reuseVis_checkBox\">\r\n              <property name=\"text\">\r\n               <string>Reuse existing visualisation window when starting a new run</string>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item row=\"1\" column=\"0\">\r\n             <widget class=\"QLabel\" name=\"label_17\">\r\n              <property name=\"text\">\r\n               <string>HTTP server port (0 for auto):</string>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item row=\"1\" column=\"1\">\r\n             <widget class=\"QSpinBox\" name=\"visPort_spinBox\">\r\n              <property name=\"maximum\">\r\n               <number>65535</number>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item row=\"2\" column=\"0\">\r\n             <widget class=\"QLabel\" name=\"label_19\">\r\n              <property name=\"text\">\r\n               <string>WebSocket server port (0 for auto):</string>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item row=\"2\" column=\"1\">\r\n             <widget class=\"QSpinBox\" name=\"visWsPort_spinBox\">\r\n              <property name=\"maximum\">\r\n               <number>65535</number>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n            <item row=\"3\" column=\"0\" colspan=\"2\">\r\n             <widget class=\"QCheckBox\" name=\"visUrl_checkBox\">\r\n              <property name=\"text\">\r\n               <string>Print the visualisation server URL in the output window</string>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n           </layout>\r\n          </item>\r\n         </layout>\r\n        </widget>\r\n       </item>\r\n       <item>\r\n        <widget class=\"QGroupBox\" name=\"groupBox_8\">\r\n         <property name=\"title\">\r\n          <string>Debugging</string>\r\n         </property>\r\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_22\">\r\n          <item>\r\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_21\">\r\n            <item>\r\n             <widget class=\"QCheckBox\" name=\"printCommand_checkBox\">\r\n              <property name=\"text\">\r\n               <string>Print the MiniZinc command used when starting each run</string>\r\n              </property>\r\n             </widget>\r\n            </item>\r\n           </layout>\r\n          </item>\r\n         </layout>\r\n        </widget>\r\n       </item>\r\n       <item>\r\n        <spacer name=\"verticalSpacer_2\">\r\n         <property name=\"orientation\">\r\n          <enum>Qt::Vertical</enum>\r\n         </property>\r\n         <property name=\"sizeHint\" stdset=\"0\">\r\n          <size>\r\n           <width>20</width>\r\n           <height>40</height>\r\n          </size>\r\n         </property>\r\n        </spacer>\r\n       </item>\r\n      </layout>\r\n     </widget>\r\n    </widget>\r\n   </item>\r\n   <item>\r\n    <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\r\n     <property name=\"orientation\">\r\n      <enum>Qt::Horizontal</enum>\r\n     </property>\r\n     <property name=\"standardButtons\">\r\n      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\r\n     </property>\r\n    </widget>\r\n   </item>\r\n  </layout>\r\n </widget>\r\n <resources/>\r\n <connections>\r\n  <connection>\r\n   <sender>buttonBox</sender>\r\n   <signal>accepted()</signal>\r\n   <receiver>PreferencesDialog</receiver>\r\n   <slot>accept()</slot>\r\n   <hints>\r\n    <hint type=\"sourcelabel\">\r\n     <x>248</x>\r\n     <y>254</y>\r\n    </hint>\r\n    <hint type=\"destinationlabel\">\r\n     <x>157</x>\r\n     <y>274</y>\r\n    </hint>\r\n   </hints>\r\n  </connection>\r\n  <connection>\r\n   <sender>buttonBox</sender>\r\n   <signal>rejected()</signal>\r\n   <receiver>PreferencesDialog</receiver>\r\n   <slot>reject()</slot>\r\n   <hints>\r\n    <hint type=\"sourcelabel\">\r\n     <x>316</x>\r\n     <y>260</y>\r\n    </hint>\r\n    <hint type=\"destinationlabel\">\r\n     <x>286</x>\r\n     <y>274</y>\r\n    </hint>\r\n   </hints>\r\n  </connection>\r\n </connections>\r\n</ui>\r\n"
  },
  {
    "path": "MiniZincIDE/process.cpp",
    "content": "/*\n *  Main authors:\n *     Jason Nguyen <jason.nguyen@monash.edu>\n *     Guido Tack <guido.tack@monash.edu>\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#include <QJsonArray>\n#include \"process.h\"\n#include \"ide.h\"\n#include \"mainwindow.h\"\n#include \"exception.h\"\n\n#ifndef Q_OS_WIN\n#include <sys/types.h>\n#include <unistd.h>\n#include <signal.h>\n#include <iostream>\n#else\n#include <VersionHelpers.h>\n#endif\n\n#ifdef Q_OS_MAC\n#include <sys/sysctl.h>\n#endif\n\n#ifdef Q_OS_WIN\n#define pathSep \";\"\n#else\n#define pathSep \":\"\n#endif\n\nvoid Process::start(const QString &program, const QStringList &arguments, const QString &path)\n{\n    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();\n    QString curPath = env.value(\"PATH\");\n    QString addPath = IDE::instance()->appDir();\n    if (!path.isEmpty())\n        addPath = path + pathSep + addPath;\n    env.insert(\"PATH\", addPath + pathSep + curPath);\n    setProcessEnvironment(env);\n#ifdef Q_OS_WIN\n    _wputenv_s(L\"PATH\", (addPath + pathSep + curPath).toStdWString().c_str());\n    if (IsWindows8OrGreater()) {\n        jobObject = CreateJobObject(nullptr, nullptr);\n        connect(this, &QProcess::started, this, &Process::attachJob);\n    } else {\n        // Workaround PCA automatically adding to a job for Windows 7\n        setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) {\n            args->flags |= CREATE_BREAKAWAY_FROM_JOB;\n        });\n    }\n#else\n#if QT_VERSION >= 0x060000\n    setChildProcessModifier(Process::setpgid);\n#endif\n    setenv(\"PATH\", (addPath + pathSep + curPath).toStdString().c_str(), 1);\n#endif\n    QProcess::start(program,arguments, QIODevice::Unbuffered | QIODevice::ReadWrite);\n#ifdef Q_OS_WIN\n    _wputenv_s(L\"PATH\", curPath.toStdWString().c_str());\n#else\n    setenv(\"PATH\", curPath.toStdString().c_str(), 1);\n#endif\n}\n\nvoid Process::terminate()\n{\n    if (state() != QProcess::NotRunning) {\n#ifdef Q_OS_WIN\n        if (IsWindows8OrGreater()) {\n            TerminateJobObject(jobObject, EXIT_FAILURE);\n        } else {\n            // We can't use job objects in Windows 7, since MiniZinc already uses them\n            QProcess::kill();\n        }\n#else\n        ::killpg(processId(), SIGKILL);\n#endif\n        if (!waitForFinished(500)) {\n            kill();\n            waitForFinished();\n        }\n    }\n}\n\nvoid Process::sendInterrupt()\n{\n    if (state() == QProcess::NotRunning) {\n        return;\n    }\n#ifdef Q_OS_WIN\n    QString pipe;\n    QTextStream ts(&pipe);\n    ts << \"\\\\\\\\.\\\\pipe\\\\minizinc-\" << processId();\n    auto pipeName = pipe.toStdString();\n    HANDLE hNamedPipe = CreateFileA(pipeName.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);\n    if (hNamedPipe != INVALID_HANDLE_VALUE) {\n        DWORD bytesWritten;\n        WriteFile(hNamedPipe, nullptr, 0, &bytesWritten, nullptr);\n        CloseHandle(hNamedPipe);\n    }\n#else\n    ::killpg(processId(), SIGINT);\n#endif\n}\n\n#if QT_VERSION >= 0x060000\nvoid Process::setpgid()\n#else\nvoid Process::setupChildProcess()\n#endif\n{\n#ifndef Q_OS_WIN\n    if (::setpgid(0,0)) {\n        std::cerr << \"Error: Failed to create sub-process\\n\";\n    }\n#endif\n}\n\n#ifdef Q_OS_WIN\nvoid Process::attachJob()\n{\n    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, true, processId());\n    if (hProcess != nullptr) {\n        AssignProcessToJobObject(jobObject, hProcess);\n    }\n}\n#endif\n\nvoid MznDriver::setLocation(const QString &mznDistribPath)\n{\n    clear();\n\n    _mznDistribPath = mznDistribPath;\n    MznProcess p;\n    QRegularExpression version_regexp(\"version (\\\\d+)\\\\.(\\\\d+)\\\\.(\\\\d+)\");\n    _minizincExecutable = QStringList({\"minizinc\"});\n\n#ifdef Q_OS_MAC\n    int isTranslated = 0;\n    {\n        size_t size = sizeof(isTranslated);\n        if (sysctlbyname(\"sysctl.proc_translated\", &isTranslated, &size, NULL, 0) == -1) {\n            isTranslated = 0;\n        }\n    }\n\n    if (isTranslated) {\n        _minizincExecutable = QStringList({\"arch\", \"-arch\", \"arm64\", \"minizinc\"});\n    }\n    for (int i = 0; i <= isTranslated; i++) {\n        if (i == 1) {\n            _minizincExecutable = QStringList({\"minizinc\"});\n        }\n        try {\n            auto result = p.run({\"--version\"});\n            _versionString = result.stdOut + result.stdErr;\n            QRegularExpressionMatch path_match = version_regexp.match(_versionString);\n            if (path_match.hasMatch()) {\n                break;\n            }\n        } catch (ProcessError& e) {\n            if (i == isTranslated) {\n                clear();\n                throw;\n            }\n        }\n    }\n#else\n    try {\n        auto result = p.run({\"--version\"});\n        _versionString = result.stdOut + result.stdErr;\n    } catch (ProcessError&) {\n        clear();\n        throw;\n    }\n#endif\n\n    QRegularExpressionMatch path_match = version_regexp.match(_versionString);\n    if (path_match.hasMatch()) {\n        _version = QVersionNumber(\n                    path_match.captured(1).toInt(),\n                    path_match.captured(2).toInt(),\n                    path_match.captured(3).toInt()\n        );\n    } else {\n        QString message = _versionString;\n        clear();\n        throw DriverError(message);\n    }\n\n    auto minVersion = QVersionNumber(2, 8, 0);\n    if (_version < minVersion) {\n        clear();\n        throw DriverError(\"Versions of MiniZinc before \" + minVersion.toString() + \" are not supported.\");\n    }\n\n    QString allOutput = p.run({\"--config-dirs\"}).stdOut;\n    QJsonDocument jd = QJsonDocument::fromJson(allOutput.toUtf8());\n    if (!jd.isNull()) {\n        QJsonObject sj = jd.object();\n        _userSolverConfigDir = sj[\"userSolverConfigDir\"].toString();\n        _userConfigFile = sj[\"userConfigFile\"].toString();\n        _mznStdlibDir = sj[\"mznStdlibDir\"].toString();\n    }\n\n    allOutput = p.run({\"--solvers-json\"}).stdOut.toUtf8();\n    jd = QJsonDocument::fromJson(allOutput.toUtf8());\n    if (!jd.isNull()) {\n        for (auto* s : _solvers) {\n            delete s;\n        }\n        _solvers.clear();\n        QJsonArray allSolvers = jd.array();\n        for (auto item : allSolvers) {\n            QJsonObject sj = item.toObject();\n            _solvers.append(new Solver(sj));\n        }\n    }\n}\n\nSolver* MznDriver::defaultSolver(void)\n{\n    for (auto* solver : solvers()) {\n        if (solver->isDefaultSolver) {\n            return solver;\n        }\n    }\n    return nullptr;\n}\n\nvoid MznDriver::setDefaultSolver(const Solver& s)\n{\n    for (auto* solver : solvers()) {\n        solver->isDefaultSolver = *solver == s;\n    }\n\n    QFile uc(userConfigFile());\n    QJsonObject jo;\n    if (uc.exists()) {\n        if (uc.open(QFile::ReadOnly)) {\n            QJsonDocument doc = QJsonDocument::fromJson(uc.readAll());\n            if (doc.isNull()) {\n                throw DriverError(\"Cannot modify user configuration file \" + userConfigFile());\n            }\n            jo = doc.object();\n            uc.close();\n        }\n    }\n    QJsonArray tagdefs = jo.contains(\"tagDefaults\") ? jo[\"tagDefaults\"].toArray() : QJsonArray();\n    bool hadDefault = false;\n    for (int i=0; i<tagdefs.size(); i++) {\n        if (tagdefs[i].isArray() && tagdefs[i].toArray()[0].isString() && tagdefs[i].toArray()[0].toString().isEmpty()) {\n            QJsonArray def = tagdefs[i].toArray();\n            def[1] = s.id;\n            tagdefs[i] = def;\n            hadDefault = true;\n            break;\n        }\n    }\n    if (!hadDefault) {\n        QJsonArray def;\n        def.append(\"\");\n        def.append(s.id);\n        tagdefs.append(def);\n    }\n    jo[\"tagDefaults\"] = tagdefs;\n    QJsonDocument doc;\n    doc.setObject(jo);\n    QFileInfo uc_info(userConfigFile());\n    if (!QDir().mkpath(uc_info.absoluteDir().absolutePath())) {\n        throw DriverError(\"Cannot create user configuration directory \" + uc_info.absoluteDir().absolutePath());\n    }\n    if (uc.open(QFile::ReadWrite | QIODevice::Truncate)) {\n        uc.write(doc.toJson());\n        uc.close();\n    } else {\n        throw DriverError(\"Cannot write user configuration file \" + userConfigFile());\n    }\n}\n\nvoid MznProcess::start(const QStringList& args, const QString& cwd)\n{\n    p.setWorkingDirectory(cwd);\n\n    connect(&timer, &ElapsedTimer::timeElapsed, this, &MznProcess::timeUpdated);\n    connect(&p, &QProcess::started, [=] () {\n        timer.start(200);\n        emit started();\n    });\n    connect(&p, QOverload<int, QProcess::ExitStatus>::of(&Process::finished), this, [=](int code, QProcess::ExitStatus e) {\n        flushOutput();\n        timer.stop();\n        if (code == 0 || cancelled) {\n            emit success(cancelled);\n        } else {\n            emit failure(code, FailureType::NonZeroExit);\n        }\n        p.disconnect();\n        emit(finished(elapsedTime()));\n    });\n    connect(&p, &QProcess::errorOccurred, this, [=](QProcess::ProcessError e) {\n        timer.stop();\n        if (!cancelled) {\n            flushOutput();\n            switch (e) {\n            case QProcess::FailedToStart:\n                emit failure(0, FailureType::FailedToStart);\n                break;\n            case QProcess::Crashed:\n                emit failure(p.exitCode(), FailureType::Crashed);\n                break;\n            default:\n                emit failure(p.exitCode(), FailureType::UnknownError);\n                break;\n            }\n            p.disconnect();\n            emit(finished(elapsedTime()));\n        }\n    });\n    connect(&p, &QProcess::readyReadStandardOutput, this, &MznProcess::readStdOut);\n    connect(&p, &QProcess::readyReadStandardError, this, &MznProcess::readStdErr);\n\n    Q_ASSERT(p.state() == QProcess::NotRunning);\n    cancelled = false;\n    const QStringList& exec = MznDriver::get().minizincExecutable();\n    QStringList execArgs = args;\n    for (unsigned int i = exec.size() - 1; i > 0; i--) {\n        execArgs.push_front(exec[i]);\n    }\n    p.start(exec[0], execArgs, MznDriver::get().mznDistribPath());\n}\n\nvoid MznProcess::start(const SolverConfiguration& sc, const QStringList& args, const QString& cwd, bool jsonStream)\n{\n    auto* temp = new QTemporaryFile(QDir::tempPath() + \"/mzn_XXXXXX.mpc\", this);\n    if (!temp->open()) {\n        emit failure(0, FailureType::FailedToStart);\n        return;\n    }\n    QString paramFile = temp->fileName();\n    temp->write(sc.toJSON());\n    temp->close();\n    connect(this, &MznProcess::finished, temp, [=] () {\n        delete temp;\n    });\n    QStringList newArgs;\n    parse = jsonStream;\n    if (jsonStream) {\n        newArgs << \"--json-stream\";\n    }\n    if (!sc.paramFile.isEmpty()) {\n        QFileInfo fi(sc.paramFile);\n        newArgs << \"--push-working-directory\"\n                << fi.canonicalPath();\n    }\n    newArgs << \"--param-file-no-push\" << paramFile;\n    if (!sc.paramFile.isEmpty()) {\n        newArgs << \"--pop-working-directory\";\n    }\n    newArgs << args;\n    if (sc.timeLimit != 0) {\n        auto* hardTimer = new QTimer(this);\n        hardTimer->setSingleShot(true);\n        connect(hardTimer, &QTimer::timeout, this, [=] () {\n            stop();\n        });\n        connect(this, &MznProcess::started, hardTimer, [=] () {\n            hardTimer->start(sc.timeLimit + 10000);\n        });\n        connect(this, &MznProcess::finished, hardTimer, [=] () {\n            delete hardTimer;\n        });\n    }\n    start(newArgs, cwd);\n}\n\n\nMznProcess::RunResult MznProcess::run(const QStringList& args, const QString& cwd)\n{\n    Q_ASSERT(p.state() == QProcess::NotRunning);\n    p.setWorkingDirectory(cwd);\n    const QStringList& exec = MznDriver::get().minizincExecutable();\n    QStringList execArgs = args;\n    for (unsigned int i = exec.size() - 1; i > 0; i--) {\n        execArgs.push_front(exec[i]);\n    }\n    p.start(exec[0], execArgs, MznDriver::get().mznDistribPath());\n    if (!p.waitForStarted()) {\n        throw ProcessError(QString(\"Failed to find or start %1 %2 in '%3'.\")\n                               .arg(exec[0])\n                               .arg(args.join(\" \"))\n                               .arg(MznDriver::get().mznDistribPath())\n                           );\n    }\n    if (!p.waitForFinished()) {\n        p.terminate();\n    }\n    return { p.exitCode(), p.readAllStandardOutput(), p.readAllStandardError() };\n}\n\n\nMznProcess::RunResult MznProcess::run(const SolverConfiguration& sc, const QStringList& args, const QString& cwd)\n{\n    QTemporaryFile temp(QDir::tempPath() + \"/mzn_XXXXXX.mpc\");\n    if (!temp.open()) {\n        throw ProcessError(\"Failed to create temporary file\");\n    }\n    QString paramFile = temp.fileName();\n    temp.write(sc.toJSON());\n    temp.close();\n    QStringList newArgs;\n    newArgs << \"--param-file\" << paramFile << args;\n    return run(newArgs, cwd);\n}\n\nvoid MznProcess::stop()\n{\n    if (p.state() == QProcess::NotRunning) {\n        return;\n    }\n    cancelled = true;\n    p.sendInterrupt();\n    auto* killTimer = new QTimer(this);\n    killTimer->setSingleShot(true);\n    connect(killTimer, &QTimer::timeout, this, [=] () {\n        if (p.state() == QProcess::Running) {\n            terminate();\n        }\n    });\n    connect(this, &MznProcess::finished, killTimer, [=] () {\n        killTimer->stop();\n        delete killTimer;\n    });\n    killTimer->start(1000);\n}\n\nvoid MznProcess::terminate()\n{\n    if (p.state() == QProcess::NotRunning) {\n        return;\n    }\n\n    p.disconnect();\n    p.terminate();\n    timer.stop();\n    emit success(cancelled);\n    emit finished(timer.elapsed());\n}\n\nqint64 MznProcess::elapsedTime()\n{\n    return timer.elapsed();\n}\n\nvoid MznProcess::writeStdIn(const QString &data)\n{\n    p.write(data.toUtf8());\n}\n\nvoid MznProcess::closeStdIn()\n{\n    p.closeWriteChannel();\n}\n\nQString MznProcess::command() const\n{\n    return p.program() + \" \" + p.arguments().join(\" \");\n}\n\nvoid MznProcess::processOutput()\n{\n    p.setReadChannel(QProcess::ProcessChannel::StandardOutput);\n    while (p.canReadLine()) {\n        auto line = QString::fromUtf8(p.readLine());\n        onStdOutLine(line);\n    }\n    p.setReadChannel(QProcess::ProcessChannel::StandardError);\n    while (p.canReadLine()) {\n        auto line = QString::fromUtf8(p.readLine());\n        emit outputStdError(line);\n    }\n}\n\nvoid MznProcess::readStdOut()\n{\n    p.setReadChannel(QProcess::ProcessChannel::StandardOutput);\n    if (p.canReadLine()) {\n        auto fragment = QString::fromUtf8(p.readAllStandardError());\n        if (!fragment.isEmpty()) {\n            emit outputStdError(fragment);\n        }\n    }\n    while (p.canReadLine()) {\n        auto line = QString::fromUtf8(p.readLine());\n        onStdOutLine(line);\n    }\n}\n\nvoid MznProcess::readStdErr()\n{\n    p.setReadChannel(QProcess::ProcessChannel::StandardError);\n    if (p.canReadLine()) {\n        auto fragment = QString::fromUtf8(p.readAllStandardOutput());\n        if (!fragment.isEmpty()) {\n            onStdOutLine(fragment);\n        }\n    }\n    while (p.canReadLine()) {\n        auto line = QString::fromUtf8(p.readLine());\n        emit outputStdError(line);\n    }\n}\n\nvoid MznProcess::flushOutput()\n{\n    readStdOut();\n    readStdErr();\n\n    auto stdOut = QString::fromUtf8(p.readAllStandardOutput());\n    if (!stdOut.isEmpty()) {\n        onStdOutLine(stdOut);\n    }\n\n    auto stdErr = QString::fromUtf8(p.readAllStandardError());\n    if (!stdErr.isEmpty()) {\n        emit outputStdError(stdOut);\n    }\n}\n\nvoid MznProcess::onStdOutLine(const QString& line)\n{\n    emit outputStdOut(line);\n\n    if (!parse) {\n        // Not running with --json-stream, so cannot parse output\n        emit unknownOutput(line);\n        return;\n    }\n\n    QJsonParseError error;\n    auto json = QJsonDocument::fromJson(line.toUtf8(), &error);\n    if (!json.isNull()) {\n        auto msg = json.object();\n        auto msg_type = msg[\"type\"].toString();\n        if (msg_type == \"solution\") {\n            auto sections = msg[\"output\"].toObject().toVariantMap();\n            QStringList order;\n            if (msg[\"sections\"].isArray()) {\n                for (auto it : msg[\"sections\"].toArray()) {\n                    order << it.toString();\n                }\n            } else {\n                order = sections.keys();\n            }\n            qint64 time = msg[\"time\"].isDouble() ? static_cast<qint64>(msg[\"time\"].toDouble()) : -1;\n            emit solutionOutput(sections, order, time);\n        } else if (msg_type == \"checker\") {\n            auto checkerSol = [&] (QJsonObject& msg) {\n                auto sections = msg[\"output\"].toObject().toVariantMap();\n                QStringList order;\n                if (msg[\"sections\"].isArray()) {\n                    for (auto it : msg[\"sections\"].toArray()) {\n                        order << it.toString();\n                    }\n                } else {\n                    order = sections.keys();\n                }\n                qint64 time = msg[\"time\"].isDouble() ? static_cast<qint64>(msg[\"time\"].toDouble()) : -1;\n                emit checkerOutput(sections, order, time);\n            };\n\n            if (msg[\"messages\"].isArray()) {\n                for (auto it : msg[\"messages\"].toArray()) {\n                    auto msg = it.toObject();\n                    auto msg_type = msg[\"type\"].toString();\n                    if (msg_type == \"solution\") {\n                        checkerSol(msg);\n                    } else if (msg_type == \"trace\") {\n                        auto section = msg[\"section\"].toString();\n                        qint64 time = msg[\"time\"].isDouble() ? static_cast<qint64>(msg[\"time\"].toDouble()) : -1;\n                        emit checkerOutput({{section, msg[\"message\"].toString()}}, {section}, time);\n                    } else if (msg_type == \"comment\") {\n                        auto comment = msg[\"comment\"].toString();\n                        emit commentOutput(comment);\n                    } else if (msg_type == \"warning\") {\n                        emit warningOutput(msg, true);\n                    } else if (msg_type == \"error\") {\n                        emit errorOutput(msg);\n                    }\n                }\n            } else {\n                checkerSol(msg);\n            }\n        } else if (msg_type == \"status\") {\n            qint64 time = msg[\"time\"].isDouble() ? static_cast<qint64>(msg[\"time\"].toDouble()) : -1;\n            emit finalStatus(msg[\"status\"].toString(), time);\n        } else if (msg_type == \"statistics\") {\n            emit statisticsOutput(msg[\"statistics\"].toObject().toVariantMap());\n        } else if (msg_type == \"comment\") {\n            auto comment = msg[\"comment\"].toString();\n            emit commentOutput(comment);\n        } else if (msg_type == \"time\") {\n            qint64 time = msg[\"time\"].isDouble() ? static_cast<qint64>(msg[\"time\"].toDouble()) : -1;\n            emit timeOutput(time);\n        } else if (msg_type == \"error\") {\n            emit errorOutput(msg);\n        } else if (msg_type == \"warning\") {\n            emit warningOutput(msg);\n        } else if (msg_type == \"progress\") {\n            emit progressOutput(msg[\"progress\"].toDouble());\n        } else if (msg_type == \"paths\") {\n            QVector<PathEntry> paths;\n            for (auto it : msg[\"paths\"].toArray()) {\n                paths << it.toObject();\n            }\n            emit pathsOutput(paths);\n        } else if (msg_type == \"profiling\") {\n            QVector<TimingEntry> t;\n            for (auto it : msg[\"entries\"].toArray()) {\n                t << it.toObject();\n            }\n            emit profilingOutput(t);\n        } else if (msg_type == \"trace\") {\n            emit traceOutput(msg[\"section\"].toString(), msg[\"message\"].toVariant());\n        } else {\n            emit unknownOutput(line);\n        }\n        return;\n    }\n\n    // Fall back to just printing everything\n    emit unknownOutput(line);\n}\n"
  },
  {
    "path": "MiniZincIDE/process.h",
    "content": "#ifndef PROCESS_H\n#define PROCESS_H\n\n#include <QProcess>\n#include <QTextStream>\n#include <QVersionNumber>\n#include <QTemporaryFile>\n#include <QJsonObject>\n\n#ifdef Q_OS_WIN\n#define NOMINMAX\n#include <Windows.h>\n#endif\n\n#include \"solver.h\"\n#include \"elapsedtimer.h\"\n#include \"profilecompilation.h\"\n\n///\n/// \\brief The Process class\n/// Extends QProcess with the ability to handle child processes.\n///\nclass Process : public QProcess {\n#ifdef Q_OS_WIN\n    Q_OBJECT\n#endif\npublic:\n    Process(QObject* parent=nullptr) : QProcess(parent) {}\n    void start(const QString& program, const QStringList& arguments, const QString& path);\n    void terminate(void);\n    void sendInterrupt();\nprotected:\n#if QT_VERSION >= 0x060000\n    static void setpgid();\n#else\n    virtual void setupChildProcess();\n#endif\n#ifdef Q_OS_WIN\nprivate:\n    HANDLE jobObject;\nprivate slots:\n    void attachJob();\n#endif\n};\n\n///\n/// \\brief The MznDriver class\n/// Central store of minizinc executable information.\n/// Singleton class instantiated with MznDriver::get().\n///\nclass MznDriver {\npublic:\n    static MznDriver& get()\n    {\n        static MznDriver d;\n        return d;\n    }\n\n    ///\n    /// \\brief Set the location of the MiniZinc executable to the given directory\n    /// \\param mznDistribPath The MiniZinc binary directory\n    ///\n    void setLocation(const QString& mznDistribPath);\n\n    ///\n    /// \\brief Returns the validity of the current MiniZinc installation\n    /// \\return Whether or not a valid MiniZinc installation was found\n    ///\n    bool isValid(void) const\n    {\n        return !minizincExecutable().isEmpty();\n    }\n\n    MznDriver(MznDriver const&) = delete;\n    void operator=(MznDriver const&) = delete;\n\n    ///\n    /// \\brief The name of the minizinc executable\n    /// \\return The executable name, or an empty string if not found\n    ///\n    const QStringList& minizincExecutable(void) const\n    {\n        return _minizincExecutable;\n    }\n    ///\n    /// \\brief The directory that contains the minizinc executable\n    /// \\return The directory as a string\n    ///\n    const QString& mznDistribPath(void) const\n    {\n        return _mznDistribPath;\n    }\n    ///\n    /// \\brief The output from running with --version\n    /// \\return The full output string including stderr\n    ///\n    const QString& minizincVersionString(void) const\n    {\n        return _versionString;\n    }\n    ///\n    /// \\brief The user solver config directory\n    /// \\return The directory as a string\n    ///\n    const QString& userSolverConfigDir(void) const\n    {\n        return _userSolverConfigDir;\n    }\n    ///\n    /// \\brief The location of the user's config file\n    /// \\return The file location as a string\n    ///\n    const QString& userConfigFile(void) const\n    {\n        return _userConfigFile;\n    }\n    ///\n    /// \\brief The directory which contains stdlib\n    /// \\return The directory as a string\n    ///\n    const QString& mznStdlibDir(void) const\n    {\n        return _mznStdlibDir;\n    }\n    ///\n    /// \\brief The built-in solvers as found by running with --solvers-json\n    /// \\return The built-in solvers\n    ///\n    QList<Solver*>& solvers(void)\n    {\n        return _solvers;\n    }\n\n    ///\n    /// \\brief Get a pointer to the default Solver\n    /// \\return The default solver or nullptr if there is none\n    ///\n    Solver* defaultSolver(void);\n\n    ///\n    /// \\brief Sets the default solver\n    /// \\param s The new default solver\n    ///\n    void setDefaultSolver(const Solver& s);\n\n    ///\n    /// \\brief Returns the version number of MiniZinc\n    /// \\return The version number\n    ///\n    const QVersionNumber& version(void)\n    {\n        return _version;\n    }\n\nprivate:\n    MznDriver() {}\n\n    QStringList _minizincExecutable;\n    QString _mznDistribPath;\n    QString _versionString;\n    QString _userSolverConfigDir;\n    QString _userConfigFile;\n    QString _mznStdlibDir;\n    QList<Solver*> _solvers;\n    QVersionNumber _version;\n\n    void clear()\n    {\n        _minizincExecutable.clear();\n        _mznDistribPath.clear();\n        _versionString.clear();\n        _userSolverConfigDir.clear();\n        _userConfigFile.clear();\n        _mznStdlibDir.clear();\n        for (auto* s : _solvers) {\n            delete s;\n        }\n        _solvers.clear();\n        _version = QVersionNumber();\n    }\n};\n\n///\n/// \\brief The MznProcess class\n/// Runs MiniZinc using the current MznDriver\n///\nclass MznProcess : public QObject {\n    Q_OBJECT\npublic:\n    struct RunResult {\n        int exitCode;\n        QString stdOut;\n        QString stdErr;\n\n        RunResult(int _exitCode, const QString& _stdOut, const QString& _stdErr)\n            : exitCode(_exitCode), stdOut(_stdOut), stdErr(_stdErr) {}\n    };\n\n    enum FailureType {\n        NonZeroExit,\n        FailedToStart,\n        Crashed,\n        UnknownError\n    };\n    Q_ENUM(FailureType)\n\n    MznProcess(QObject* parent = nullptr)\n        : QObject(parent), cancelled(false), p(nullptr), timer(nullptr) {}\n\n    ///\n    /// \\brief Start minizinc. Does not enable --json-stream.\n    /// \\param args Command line arguments\n    /// \\param cwd Working directory\n    ///\n    void start(const QStringList& args, const QString& cwd = QString());\n\n    ///\n    /// \\brief Start minizinc and use the given solver configuration.\n    /// \\param sc The solver configuration to use\n    /// \\param args Command line arguments\n    /// \\param cwd Working directory\n    /// \\param jsonStream Whether or not to enable --json-stream\n    ///\n    void start(const SolverConfiguration& sc, const QStringList& args, const QString& cwd = QString(), bool jsonStream = true);\n\n    ///\n    /// \\brief Stop minizinc (does not block)\n    ///\n    void stop();\n\n    ///\n    /// \\brief Force stop minizinc immediately (blocks, does not finishe processing output)\n    ///\n    void terminate();\n\n    ///\n    /// \\brief Run minizinc in blocking mode (only for fast commands).\n    /// \\param args Command line arguments\n    /// \\param cwd Working directory\n    /// \\return The stdout and stderr output\n    ///\n    RunResult run(const QStringList& args, const QString& cwd = QString());\n\n    ///\n    /// \\brief Run minizinc in blocking mode (only for fast commands).\n    /// \\param sc The solver configuration to use\n    /// \\param args Command line arguments\n    /// \\param cwd Working directory\n    /// \\return The stdout and stderr output\n    ///\n    RunResult run(const SolverConfiguration& sc, const QStringList& args, const QString& cwd = QString());\n\n    ///\n    /// \\brief Get the time since the process started.\n    /// \\return The elapsed time in nanoseconds\n    ///\n    qint64 elapsedTime();\n\n    ///\n    /// \\brief Write string to process stdin\n    /// \\param data String data to write\n    ///\n    void writeStdIn(const QString& data);\n\n    ///\n    /// \\brief Closes process stdin\n    ///\n    void closeStdIn();\n\n    QString command() const;\n\nsignals:\n    ///\n    /// \\brief Emitted when the process is started.\n    ///\n    void started();\n\n    ///\n    /// \\brief Emitted when a line is written to stdout.\n    /// \\param output The data in stdout.\n    ///\n    void outputStdOut(const QString& output);\n    ///\n    /// \\brief Emitted when a line is written to stderr.\n    /// \\param error The data in stderr.\n    ///\n    void outputStdError(const QString& error);\n\n    ///\n    /// \\brief Emitted regularly as time elapses\n    /// \\param time The time elapsed in nanoseconds\n    ///\n    void timeUpdated(qint64 time);\n\n    ///\n    /// \\brief Emitted on successful exit (or after stopping).\n    ///\n    void success(bool cancelled);\n\n    ///\n    /// \\brief Emitted when the process encounters an error\n    /// \\param exitCode The exit code\n    /// \\param e The error that occurred\n    ///\n    void failure(int exitCode, FailureType e);\n\n    ///\n    /// \\brief Emitted when finished regardless of success/failure.\n    /// \\param time The runtime in nanoseconds.\n    ///\n    void finished(qint64 time);\n\n    // Emitted if --json-stream is enabled\n    ///\n    /// \\brief Emitted when a solution message is produced.\n    ///\n    void solutionOutput(const QVariantMap& sections, const QStringList& sectionOrder, qint64 time = -1);\n    ///\n    /// \\brief Emitted when a checker message is produced.\n    ///\n    void checkerOutput(const QVariantMap& sections, const QStringList& sectionOrder, qint64 time = -1);\n    ///\n    /// \\brief Emitted when en error message is produced.\n    ///\n    void errorOutput(const QJsonObject& error);\n    ///\n    /// \\brief Emitted when a warning message is produced.\n    ///\n    void warningOutput(const QJsonObject& warning, bool fromChecker = false);\n    ///\n    /// \\brief Emitted when a statistics message is produced.\n    ///\n    void statisticsOutput(const QVariantMap& statistics);\n    ///\n    /// \\brief Emitted when a progress message is read.\n    /// \\param progress The progress value\n    ///\n    void progressOutput(double progress);\n    ///\n    /// \\brief Emitted when a final status string is read.\n    ///\n    void finalStatus(const QString& status, qint64 time = -1);\n    ///\n    /// \\brief Emitted when a comment is read\n    /// \\param data The data that was read\n    ///\n    void commentOutput(const QString& data);\n    ///\n    /// \\brief Emitted if a time message is output\n    /// \\param time The output time\n    ///\n    void timeOutput(qint64 time = -1);\n\n    ///\n    /// \\brief Emitted when paths are output\n    /// \\param paths The paths\n    ///\n    void pathsOutput(const QVector<PathEntry>& paths);\n    ///\n    /// \\brief Emitted when detailed timing info is produced\n    /// \\param timing The timing information\n    ///\n    void profilingOutput(const QVector<TimingEntry>& timing);\n    ///\n    /// \\brief Emitted when a non-stderr trace is produced\n    /// \\param section The trace output section\n    /// \\param message The trace message (either a string or JSON)\n    ///\n    void traceOutput(const QString& section, const QVariant& message);\n    ///\n    /// \\brief Emitted when an unknown fragment is output\n    /// \\param data The data that was read\n    ///\n    void unknownOutput(const QString& data);\n\nprivate:\n    bool cancelled;\n    bool parse;\n    Process p;\n    ElapsedTimer timer;\n\n    void readStdOut();\n    void readStdErr();\n\n    void processOutput();\n\n    void onStdOutLine(const QString& line);\n\n    void flushOutput();\n};\n\n#endif // PROCESS_H\n"
  },
  {
    "path": "MiniZincIDE/profilecompilation.cpp",
    "content": "#include \"profilecompilation.h\"\r\n#include <QFileInfo>\r\n\r\nPath::Path(const QString& path)\r\n{\r\n    auto items = path.split(';');\r\n    for (auto& it : items) {\r\n        auto parts = it.split('|');\r\n        if (parts.size() < 5) {\r\n            continue;\r\n        }\r\n        Path::Segment segment;\r\n        QFileInfo fi(parts[0]);\r\n        segment.filename = fi.canonicalFilePath();\r\n        segment.firstLine = parts[1].toInt();\r\n        segment.firstColumn = parts[2].toInt();\r\n        segment.lastLine = parts[3].toInt();\r\n        segment.lastColumn = parts[4].toInt();\r\n        QStringList rest;\r\n        for (auto i = 5; i < parts.size(); i++) {\r\n            segment.parts << parts[i];\r\n        }\r\n        _segments << segment;\r\n    }\r\n}\r\n\r\nPathEntry::PathEntry(const QJsonObject& obj) : _path(obj[\"path\"].toString())\r\n{\r\n    if (obj[\"constraintIndex\"].isUndefined()) {\r\n        _flatZincName = obj[\"flatZincName\"].toString();\r\n        _niceName = obj[\"niceName\"].toString();\r\n    } else {\r\n        _constraintIndex = obj[\"constraintIndex\"].toInt();\r\n    }\r\n}\r\n\r\nTimingEntry::TimingEntry(const QJsonObject& obj)\r\n{\r\n    QFileInfo fi(obj[\"filename\"].toString());\r\n    _filename = fi.canonicalFilePath();\r\n    _line = obj[\"line\"].toInt();\r\n    _time = obj[\"time\"].toInt();\r\n}\r\n"
  },
  {
    "path": "MiniZincIDE/profilecompilation.h",
    "content": "#ifndef PROFILE_COMPILATION_H\r\n#define PROFILE_COMPILATION_H\r\n\r\n#include <QString>\r\n#include <QStringList>\r\n#include <QVector>\r\n#include <QJsonObject>\r\n\r\n#include \"highlighter.h\"\r\n\r\nclass Path\r\n{\r\npublic:\r\n    struct Segment\r\n    {\r\n        QString filename;\r\n        int firstLine;\r\n        int firstColumn;\r\n        int lastLine;\r\n        int lastColumn;\r\n        QStringList parts;\r\n    };\r\n\r\n    Path(const QString& path);\r\n    Path() {}\r\n\r\n    const QVector<Segment>& segments() const { return _segments; }\r\n\r\nprivate:\r\n    QVector<Segment> _segments;\r\n};\r\n\r\nclass PathEntry\r\n{\r\npublic:\r\n    PathEntry(const QJsonObject& obj);\r\n    PathEntry() {}\r\n\r\n    const QString& flatZincName() const {return _flatZincName; }\r\n    const QString& niceName() const { return _niceName; }\r\n    int constraintIndex() const { return _constraintIndex; }\r\n    const Path& path() const { return _path; }\r\nprivate:\r\n    QString _flatZincName;\r\n    QString _niceName;\r\n    int _constraintIndex = -1;\r\n    Path _path;\r\n};\r\n\r\nclass TimingEntry\r\n{\r\npublic:\r\n    TimingEntry(const QJsonObject& obj);\r\n    TimingEntry() {}\r\n\r\n    const QString& filename() const { return _filename; }\r\n    int line() const { return _line; }\r\n    int time() const { return _time; }\r\n\r\nprivate:\r\n    QString _filename;\r\n    int _line;\r\n    int _time;\r\n};\r\n\r\n#endif // PROFILE_COMPILATION_H\r\n"
  },
  {
    "path": "MiniZincIDE/project.cpp",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#include \"project.h\"\n#include \"moocsubmission.h\"\n#include \"solver.h\"\n#include \"ide.h\"\n\n#include <QFileInfo>\n#include <QDir>\n#include <QDebug>\n#include <QMessageBox>\n#include <QSortFilterProxyModel>\n#include <QJsonDocument>\n#include <QJsonArray>\n\nProject::Project(const QList<SolverConfiguration*>& configs, QObject* parent) : QObject(parent), solverConfigs(configs)\n{\n    itemModel = new QStandardItemModel(this);\n    itemModel->setColumnCount(1);\n\n    connect(itemModel, &QStandardItemModel::itemChanged, this, &Project::on_itemChanged);\n\n    rootItem = new QStandardItem(QIcon(\":/images/mznicon.png\"), \"Untitled Project\");\n    rootItem->setData(NodeType::ProjectFile, Role::Type);\n    rootItem->setData(\"Untitled Project\", Role::OriginalLabel);\n    rootItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);\n\n    auto font = rootItem->font();\n    font.setBold(true);\n\n    modelsItem = new QStandardItem(\"Models\");\n    modelsItem->setData(NodeType::Group, Role::Type);\n    modelsItem->setFlags(Qt::NoItemFlags);\n    modelsItem->setFont(font);\n\n    dataItem = new QStandardItem(\"Data (right click to run)\");\n    dataItem->setData(NodeType::Group, Role::Type);\n    dataItem->setFlags(Qt::NoItemFlags);\n    dataItem->setFont(font);\n\n    checkersItem = new QStandardItem(\"Checkers (right click to run)\");\n    checkersItem->setData(NodeType::Group, Role::Type);\n    checkersItem->setFlags(Qt::NoItemFlags);\n    checkersItem->setFont(font);\n\n    configsItem = new QStandardItem(\"Solver configurations\");\n    configsItem->setData(NodeType::Group, Role::Type);\n    configsItem->setFlags(Qt::NoItemFlags);\n    configsItem->setFont(font);\n\n    otherItem = new QStandardItem(\"Other files\");\n    otherItem->setData(NodeType::Group, Role::Type);\n    otherItem->setFlags(Qt::NoItemFlags);\n    otherItem->setFont(font);\n\n    rootItem->appendRow(modelsItem);\n    rootItem->appendRow(dataItem);\n    rootItem->appendRow(checkersItem);\n    rootItem->appendRow(configsItem);\n    rootItem->appendRow(otherItem);\n    itemModel->appendRow(rootItem);\n}\n\nQStringList Project::loadProject(const QString& file, ConfigWindow* configWindow)\n{\n    clear();\n\n    projectFile(file);\n\n    QStringList warnings;\n\n    QFile f(file);\n    QFileInfo fi(f);\n    if (!f.open(QFile::ReadOnly)) {\n        throw FileError(\"Failed to open project file\");\n    }\n\n    auto doc = QJsonDocument::fromJson(f.readAll());\n\n    if (doc.isObject()) {\n        loadJSON(doc.object(), fi, configWindow, warnings);\n    } else {\n        f.reset();\n        QDataStream in(&f);\n        loadLegacy(in, fi, configWindow, warnings);\n    }\n\n    f.close();\n\n    // Save these because adding the configs can change them\n    auto projectBuiltinConfigId = selectedBuiltinConfigId;\n    auto projectBuiltinConfigVersion = selectedBuiltinConfigVersion;\n    auto projectselectedSolverConfigFile = selectedSolverConfigFile;\n\n    for (auto& sc : solverConfigurationFiles()) {\n        configWindow->addConfig(sc);\n    }\n\n    if (!projectBuiltinConfigId.isEmpty()) {\n        int index = configWindow->findBuiltinConfig(projectBuiltinConfigId, projectBuiltinConfigVersion);\n        if (index == -1) {\n            warnings << \"Could not find solver \" + projectBuiltinConfigId + \"@\" + projectBuiltinConfigVersion;\n        } else {\n            configWindow->setCurrentIndex(index);\n        }\n    } else if (!projectselectedSolverConfigFile.isEmpty()) {\n        int index = configWindow->findConfigFile(rootDir().absolutePath() + \"/\" + projectselectedSolverConfigFile);\n        configWindow->setCurrentIndex(index);\n    }\n\n    setModified(false);\n\n    return warnings;\n}\n\nvoid Project::loadJSON(const QJsonObject& obj, const QFileInfo& fi, ConfigWindow* configWindow, QStringList& warnings)\n{\n    int version = obj[\"version\"].toInt();\n\n    QString basePath = fi.absolutePath() + \"/\";\n\n    auto of = obj[\"openFiles\"].toArray();\n    for (auto file : of) {\n        auto path = basePath + file.toString();\n        if (QFileInfo(path).exists()) {\n            openTabs << path;\n        } else {\n            warnings << \"The file \" + file.toString() + \" could not be found\";\n        }\n    }\n\n    openTabIndex = obj[\"openTab\"].toInt();\n\n    QList<SolverConfiguration*> configs;\n    if (obj[\"builtinSolverConfigs\"].isArray()) {\n        for (auto config : obj[\"builtinSolverConfigs\"].toArray()) {\n            if (!config.isObject()) {\n                warnings << \"Failed to read solver builtin solver config\";\n                continue;\n            }\n            try {\n                SolverConfiguration* loaded;\n                if (version >= 106) {\n                    loaded = new (SolverConfiguration) (SolverConfiguration::loadJSON(QJsonDocument(config.toObject()), warnings));\n                } else {\n                    loaded = new (SolverConfiguration) (SolverConfiguration::loadLegacy(QJsonDocument(config.toObject()), warnings));\n                }\n                loaded->isBuiltin = true;\n                configs << loaded;\n            } catch (Exception& e) {\n                warnings << e.message();\n            }\n        }\n    }\n\n    if (obj[\"projectSolverConfigs\"].isArray()) {\n        for (auto config : obj[\"projectSolverConfigs\"].toArray()) {\n            if (!config.isObject()) {\n                warnings << \"Failed to read solver project solver config\";\n                continue;\n            }            \n            try {\n                auto loaded = new (SolverConfiguration) (SolverConfiguration::loadLegacy(QJsonDocument(config.toObject()), warnings));\n                loaded->modified = true;\n                configs << loaded;\n            } catch (Exception& e) {\n                warnings << e.message();\n            }\n        }\n    }\n\n    configWindow->mergeConfigs(configs);\n\n    if (obj[\"selectedBuiltinConfigId\"].isString()) {\n        selectedBuiltinConfigId = obj[\"selectedBuiltinConfigId\"].toString();\n        selectedBuiltinConfigVersion = obj[\"selectedBuiltinConfigVersion\"].toString();\n        selectedSolverConfigFile = \"\";\n\n    } else if (obj[\"selectedSolverConfigFile\"].isString()) {\n        selectedSolverConfigFile = obj[\"selectedSolverConfigFile\"].toString();\n        selectedBuiltinConfigId = \"\";\n        selectedBuiltinConfigVersion = \"\";\n    } /*else {\n        warnings << \"No selected solver config in project\";\n    }*/\n\n    for (auto file : obj[\"projectFiles\"].toArray()) {\n        auto path = basePath + file.toString();\n        if (QFileInfo(path).exists()) {\n            add(path);\n        } else {\n            warnings << \"The file \" + file.toString() + \" could not be found\";\n        }\n    }\n}\n\nvoid Project::loadLegacy(QDataStream& in, const QFileInfo& fi, ConfigWindow* configWindow, QStringList& warnings)\n{\n    // Load old binary format of project\n    throw InternalError(\"This project format is no longer supported. Please use MiniZinc IDE version 2.4 to upgrade it.\");\n}\n\nvoid Project::saveProject()\n{\n    QJsonObject confObject;\n    confObject[\"version\"] = 106;\n\n    // Save the currently open tabs\n    QStringList of;\n    for (auto& f: openTabs) {\n         of << relativeToProject(f);\n    }\n    confObject[\"openFiles\"] = QJsonArray::fromStringList(of);\n    confObject[\"openTab\"] = openTabIndex;\n\n    // Save paths of all project files\n    QStringList relativeFilePaths;\n    for (auto& file : files()) {\n        relativeFilePaths << relativeToProject(file);\n    }\n    confObject[\"projectFiles\"] = QJsonArray::fromStringList(relativeFilePaths);\n\n    // Save which config is currently selected\n    if (!selectedBuiltinConfigId.isEmpty() && !selectedBuiltinConfigVersion.isEmpty()) {\n        confObject[\"selectedBuiltinConfigId\"] = selectedBuiltinConfigId;\n        confObject[\"selectedBuiltinConfigVersion\"] = selectedBuiltinConfigVersion;\n    } else if (!selectedSolverConfigFile.isEmpty()){\n        confObject[\"selectedSolverConfigFile\"] = selectedSolverConfigFile;\n    }\n\n    // Write project file\n    QJsonDocument doc(confObject);\n    QFile file(projectFile());\n    if (!file.open(QFile::WriteOnly)) {\n        throw FileError(\"Failed to write file\");\n    }\n    file.write(doc.toJson());\n    file.close();\n\n    setModified(false);\n}\n\nvoid Project::add(const QString& fileName)\n{\n    auto abs = QFileInfo(fileName).absoluteFilePath();\n    auto path = relativeToProject(fileName);\n    if (contains(abs)) {\n        return;\n    }\n\n#if QT_VERSION >= 0x060000\n    auto parts = path.split(\"/\", Qt::SkipEmptyParts); // Qt always uses / as the path separator\n#else\n    auto parts = path.split(\"/\", QString::SkipEmptyParts); // Qt always uses / as the path separator\n#endif\n    auto file = parts.takeLast();\n\n    QStandardItem* node = otherItem;\n    QString icon;\n    NodeType type = NodeType::Other;\n    if (file.endsWith(\".mzc.mzn\") || file.endsWith(\".mzc\")) {\n        node = checkersItem;\n        icon = \":/images/mznicon.png\";\n        type = NodeType::Checker;\n    } else if (file.endsWith(\".mzn\")) {\n        node = modelsItem;\n        icon = \":/images/mznicon.png\";\n        type = NodeType::Model;\n    } else if (file.endsWith(\".dzn\") || file.endsWith(\".json\")) {\n        node = dataItem;\n        icon = \":/images/mznicon.png\";\n        type = NodeType::Data;\n    } else if (file.endsWith(\".mpc\")) {\n        node = configsItem;\n        icon = \":/images/mznicon.png\";\n        type = NodeType::SolverConfig;\n    } else if (file == \"_mooc\" || file == \"_coursera\") {\n        if (mooc) {\n            delete mooc;\n        }\n        mooc = new MOOCAssignment(fileName);\n        emit moocChanged(mooc);\n    }\n\n    node->setFlags(Qt::ItemIsEnabled);\n\n    // Traverse existing path items\n    int i = 0;\n    while (!parts.empty() && i < node->rowCount()) {\n        if (getType(node->child(i)->index()) == NodeType::Dir &&\n                node->child(i)->text() == parts.first()) {\n            parts.pop_front();\n            node = node->child(i);\n            i = 0;\n        } else {\n            i++;\n        }\n    }\n    // Create new path items\n    for (auto& part : parts) {\n        auto dir = new QStandardItem(QIcon(\":/icons/images/folder.png\"), part);\n        dir->setData(NodeType::Dir, Role::Type);\n        dir->setFlags(Qt::ItemIsEnabled);\n        node->appendRow(dir);\n        node->sortChildren(0);\n        node = dir;\n    }\n    // Add file item\n    auto item = new QStandardItem(QIcon(icon), file);\n    item->setData(type, Role::Type);\n    item->setData(abs, Role::Path);\n    item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);\n    node->appendRow(item);\n    node->sortChildren(0);\n\n    entries.insert(abs, item);\n\n    if (!projectFile().isEmpty()) {\n        setModified(true);\n    }\n}\n\nvoid Project::add(const QStringList& fileNames)\n{\n    for (auto& fileName : fileNames) {\n        add(fileName);\n    }\n}\n\nvoid Project::remove(const QString &fileName)\n{\n    auto path = QFileInfo(fileName).absoluteFilePath();\n    if (!contains(path)) {\n        return;\n    }\n\n    // Make sure we also remove any unused dirs\n    auto node = entries[path];\n    if (node->text() == \"_mooc\" || node->text() == \"_coursera\") {\n        delete mooc;\n        mooc = nullptr;\n        emit moocChanged(mooc);\n    }\n    while (getType(node->parent()->index()) == NodeType::Dir && node->parent()->rowCount() <= 1) {\n        node = node->parent();\n    }\n    auto parent = node->parent();\n    parent->removeRow(node->row());\n    if (parent->data(Role::Type) == \"group\" && !parent->hasChildren()) {\n        parent->setFlags(Qt::NoItemFlags);\n    }\n    entries.remove(path);\n\n    if (!projectFile().isEmpty()) {\n        setModified(true);\n    }\n}\n\nvoid Project::remove(const QStringList &fileNames)\n{\n    for (auto& fileName : fileNames) {\n        remove(fileName);\n    }\n}\n\nvoid Project::remove(const QModelIndex& index)\n{\n    remove(getFileName(index));\n}\n\nvoid Project::remove(const QModelIndexList& indexes)\n{\n    for (auto& index : indexes) {\n        remove(index);\n    }\n}\n\nvoid Project::clear()\n{\n    modelsItem->removeRows(0, modelsItem->rowCount());\n    dataItem->removeRows(0, dataItem->rowCount());\n    checkersItem->removeRows(0, checkersItem->rowCount());\n    otherItem->removeRows(0, otherItem->rowCount());\n    entries.clear();\n    setModified(true);\n}\n\nQStringList Project::files(void) const {\n    return entries.keys();\n}\n\nQStringList Project::modelFiles(void) const {\n    return getFiles(NodeType::Model);\n}\n\nQStringList Project::solverConfigurationFiles(void) const {\n    return getFiles(NodeType::SolverConfig);\n}\n\nQStringList Project::dataFiles(void) const {\n    return getFiles(NodeType::Data);\n}\n\nbool Project::contains(const QString &fileName)\n{\n    QFileInfo fi(fileName);\n    return entries.contains(fi.absoluteFilePath());\n}\n\nvoid Project::setModified(bool m)\n{\n    modified = m;\n    auto label = rootItem->data(Role::OriginalLabel).toString();\n    if (modified) {\n        rootItem->setText(label + \" *\");\n    } else {\n        rootItem->setText(label);\n    }\n}\n\nvoid Project::projectFile(const QString& fileName)\n{\n    QStringList files = entries.keys();\n    clear();\n    if (fileName.isEmpty()) {\n        rootItem->setText(\"Untitled Project\");\n        rootItem->setData(\"Untitled Project\", Role::OriginalLabel);\n        projFile = \"\";\n    } else {\n        QFileInfo fi(fileName);\n        projFile = fi.absoluteFilePath();\n        rootItem->setText(fi.fileName());\n        rootItem->setData(fi.fileName(), Role::OriginalLabel);\n    }\n    add(files);\n}\n\nQDir Project::rootDir()\n{\n    QFileInfo fi(projectFile());\n    return QDir(fi.absolutePath());\n}\n\nbool Project::hasProjectFile()\n{\n    return !projectFile().isEmpty();\n}\n\nProject::NodeType Project::getType(const QModelIndex& index)\n{\n    return static_cast<Project::NodeType>(model()->data(index, Role::Type).toInt());\n}\n\nQString Project::getFileName(const QModelIndex& index)\n{\n    return model()->data(index, Role::Path).toString();\n}\n\nQStringList Project::getFileNames(const QModelIndexList& indices)\n{\n    QStringList result;\n    for (auto& index : indices) {\n        result << getFileName(index);\n    }\n    return result;\n}\n\nQStringList Project::getFiles(NodeType type) const\n{\n    QStringList ret;\n    for (auto it = entries.begin(); it != entries.end(); it++) {\n        auto t = static_cast<NodeType>(it.value()->data(Role::Type).toInt());\n        if (t == type) {\n            ret << it.key();\n        }\n    }\n    return ret;\n}\n\nQString Project::relativeToProject(const QString& fileName)\n{\n    QFileInfo fi(fileName);\n    auto abs = fi.absoluteFilePath();\n    return hasProjectFile() ? rootDir().relativeFilePath(abs) : abs;\n}\n\nvoid Project::openTabsChanged(const QStringList& files, int currentTab)\n{\n    openTabs.clear();\n    for (auto& file : files) {\n        auto abs = QFileInfo(file).absoluteFilePath();\n        if (contains(abs)) {\n            openTabs << abs;\n        }\n    }\n\n    if (currentTab >= 0) {\n        openTabIndex = openTabs.indexOf(QFileInfo(files[currentTab]).absoluteFilePath());\n    } else {\n        openTabIndex = -1;\n    }\n\n//    setModified(true);\n}\n\nvoid Project::activeSolverConfigChanged(const SolverConfiguration* sc)\n{\n    if (!sc) {\n        selectedSolverConfigFile = \"\";\n        selectedBuiltinConfigId = \"\";\n        selectedBuiltinConfigVersion = \"\";\n    } else if (sc->isBuiltin) {\n        selectedSolverConfigFile = \"\";\n        selectedBuiltinConfigId = sc->solverDefinition.id;\n        selectedBuiltinConfigVersion = sc->solverDefinition.version;\n    } else {\n        selectedBuiltinConfigId = \"\";\n        selectedBuiltinConfigVersion = \"\";\n        selectedSolverConfigFile = relativeToProject(sc->paramFile);\n    }\n//    setModified(true);\n}\n\nvoid Project::on_itemChanged(QStandardItem* item)\n{\n    auto oldPath = item->data(Qt::UserRole + 1).toString();\n    auto newName = item->text();\n    if (oldPath.isEmpty() || oldPath.endsWith(newName)) {\n        return;\n    }\n    QFileInfo fi(oldPath);\n    auto target = fi.path() + \"/\" + item->text();\n    if (QFile::rename(oldPath, target)) {\n        remove(oldPath);\n        add(target);\n        emit fileRenamed(oldPath, target);\n    } else {\n        item->setText(fi.fileName());\n    }\n}\n\n"
  },
  {
    "path": "MiniZincIDE/project.h",
    "content": "/*\n *  Author:\n *     Guido Tack <guido.tack@monash.edu>\n *\n *  Copyright:\n *     NICTA 2013\n */\n\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n#ifndef PROJECT_H\n#define PROJECT_H\n\n#include <QSet>\n#include <QStandardItemModel>\n#include <QTreeView>\n#include \"solver.h\"\n#include \"moocsubmission.h\"\n#include \"exception.h\"\n#include \"configwindow.h\"\n#include \"history.h\"\n\nclass Project : public QObject {\n    Q_OBJECT\n\npublic:\n    enum NodeType {\n        Model = 1,\n        Data = 2,\n        Checker = 4,\n        SolverConfig = 8,\n        Other = 16,\n        Dir = 32,\n        Group = 64,\n        ProjectFile = 128\n    };\n    Q_DECLARE_FLAGS(NodeTypes, NodeType)\n\n    explicit Project(const QList<SolverConfiguration*>& configs, QObject* parent = nullptr);\n\n    ~Project()\n    {\n        delete mooc;\n        emit moocChanged(nullptr);\n    }\n\n    ///\n    /// \\brief Loads a project from an mzp file.\n    /// \\param file The mzp file to be loaded\n    /// \\param configWindow The solver configuration window\n    /// \\return A list of warnings from loading the project\n    ///\n    QStringList loadProject(const QString& file, ConfigWindow* configWindow);\n\n    ///\n    /// \\brief Save this project\n    ///\n    void saveProject(void);\n\n    ///\n    /// \\brief Add a file to the project\n    /// \\param fileName The file to be added\n    ///\n    void add(const QString& fileName);\n    ///\n    /// \\brief Adds multiple files to the project\n    /// \\param fileNames The files to be added\n    ///\n    void add(const QStringList& fileNames);\n\n    ///\n    /// \\brief Removes a files from the project\n    /// \\param fileName The file to be removed\n    ///\n    void remove(const QString& fileName);\n    ///\n    /// \\brief Removes multiple files from the project\n    /// \\param fileNames The files to be removed\n    ///\n    void remove(const QStringList& fileNames);\n    ///\n    /// \\brief Removes a file from the project\n    /// \\param index The index of the file to be removed\n    ///\n    void remove(const QModelIndex& index);\n    ///\n    /// \\brief Removes multiple files from the project\n    /// \\param indexes The indices of the files to be removed\n    ///\n    void remove(const QModelIndexList& indexes);\n\n    ///\n    /// \\brief Removes all files from the project\n    ///\n    void clear(void);\n\n    ///\n    /// \\brief Gets the files in this project\n    /// \\return The files in this project\n    ///\n    QStringList files(void) const;\n\n    ///\n    /// \\brief Gets the model files in this project (excluding checkers)\n    /// \\return The file paths of the model files\n    ///\n    QStringList modelFiles(void) const;\n\n    ///\n    /// \\brief Gets the solver configuration files in this project\n    /// \\return The file paths of the solver configurations\n    ///\n    QStringList solverConfigurationFiles(void) const;\n\n    ///\n    /// \\brief Gets the data files in this project\n    /// \\return The file paths of the data files\n    ///\n    QStringList dataFiles(void) const;\n\n    const QStringList& openFiles(void) { return openTabs; }\n\n    int openTab(void) { return openTabIndex; }\n\n    ///\n    /// \\brief Return whether this project contains the given file\n    /// \\param fileName The file to check\n    /// \\return True if the given file is in the project, and false otherwise\n    ///\n    bool contains(const QString& fileName);\n\n    ///\n    /// \\brief Return whether this project is modified\n    /// \\return True if this project was modified\n    ///\n    bool isModified(void)\n    {\n        return modified;\n    }\n    ///\n    /// \\brief Marks this project as modified or unmodified\n    /// \\param m True for modified, false for unmodified\n    ///\n    void setModified(bool m);\n\n    ///\n    /// \\brief The path to the project .mzp file\n    /// \\return The path to the project file, or empty if there is none\n    ///\n    const QString& projectFile(void) const\n    {\n        return projFile;\n    }\n    ///\n    /// \\brief Set the project .mzp file\n    /// \\param fileName The new project file path\n    ///\n    void projectFile(const QString& fileName);\n\n    ///\n    /// \\brief The root directory off of which project file paths are based\n    /// \\return The root directory\n    ///\n    QDir rootDir(void);\n    ///\n    /// \\brief Return whether this project has a .mzp file\n    /// \\return True if there is one, and false otherwise\n    ///\n    bool hasProjectFile(void);\n\n    ///\n    /// \\brief Gets the mooc assignment associated with this project\n    /// \\return The mooc assignment (throws exception if there is none)\n    ///\n    MOOCAssignment& moocAssignment(void)\n    {\n        if (!mooc) {\n            throw MoocError(\"No mooc assignment in project\");\n        }\n        return *mooc;\n    }\n\n    ///\n    /// \\brief Get the type of the node at the given index\n    /// \\param index The model index\n    /// \\return The type of the node\n    ///\n    NodeType getType(const QModelIndex& index);\n\n    ///\n    /// \\brief Gets a file path from a model index\n    /// \\param index The model index\n    /// \\return The file path as a string\n    ///\n    QString getFileName(const QModelIndex& index);\n\n    ///\n    /// \\brief Gets the file paths of the given model indexes\n    /// \\param indices The model indexes\n    /// \\return The full file paths\n    ///\n    QStringList getFileNames(const QModelIndexList& indices);\n\n    ///\n    /// \\brief Get the underlying item model associated with this project\n    /// \\return The project files as a QAbstractItemModel\n    ///\n    QAbstractItemModel* model(void)\n    {\n        return itemModel;\n    }\n\n    History* history() const\n    {\n        if (mooc == nullptr) {\n            return nullptr;\n        }\n        return mooc->history;\n    }\n\nsignals:\n    ///\n    /// \\brief Emitted when a file gets renamed\n    /// \\param oldName The previous name of the file\n    /// \\param newName The new name of the file\n    ///\n    void fileRenamed(const QString& oldName, const QString& newName);\n    void moocChanged(const MOOCAssignment* mooc);\npublic slots:\n    void openTabsChanged(const QStringList& files, int currentTab);\n    void activeSolverConfigChanged(const SolverConfiguration* sc);\nprivate slots:\n    void on_itemChanged(QStandardItem* item);\nprivate:\n    enum Role {\n        Type = Qt::UserRole,\n        Path = Qt::UserRole + 1,\n        OriginalLabel = Qt::UserRole + 2\n    };\n\n    void loadJSON(const QJsonObject& obj, const QFileInfo& fi, ConfigWindow* configWindow, QStringList& warnings);\n    void loadLegacy(QDataStream& in, const QFileInfo& fi, ConfigWindow* configWindow, QStringList& warnings);\n    QStringList getFiles(NodeType type) const;\n\n    QString relativeToProject(const QString& fileName);\n\n    bool modified = false;\n\n    QStandardItemModel* itemModel;\n\n    QString projFile;\n\n    QStandardItem* rootItem;\n    QStandardItem* modelsItem;\n    QStandardItem* checkersItem;\n    QStandardItem* dataItem;\n    QStandardItem* configsItem;\n    QStandardItem* otherItem;\n\n    MOOCAssignment* mooc = nullptr;\n\n    QStringList openTabs;\n    int openTabIndex;\n\n    QString selectedBuiltinConfigId;\n    QString selectedBuiltinConfigVersion;\n    QString selectedSolverConfigFile;\n\n    const QList<SolverConfiguration*>& solverConfigs;\n\n    QMap<QString, QStandardItem*> entries;\n};\n\nQ_DECLARE_OPERATORS_FOR_FLAGS(Project::NodeTypes)\n\n#endif // PROJECT_H\n"
  },
  {
    "path": "MiniZincIDE/projectbrowser.cpp",
    "content": "#include \"projectbrowser.h\"\n#include \"ui_projectbrowser.h\"\n#include \"ide.h\"\n#include \"exception.h\"\n\n#include <QFileDialog>\n#include <QFileInfo>\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QJsonArray>\n\nProjectBrowser::ProjectBrowser(QWidget *parent) :\n    QWidget(parent),\n    ui(new Ui::ProjectBrowser)\n{\n    ui->setupUi(this);\n\n    setupContextMenu();\n}\n\nProjectBrowser::~ProjectBrowser()\n{\n    delete ui;\n}\n\nvoid ProjectBrowser::project(Project* p)\n{\n    proj = p;\n\n    ui->treeView->setModel(proj->model());\n    ui->treeView->expandToDepth(1);\n}\n\nvoid ProjectBrowser::setupContextMenu()\n{\n    auto contextMenu = new QMenu(this);\n    contextMenu->addAction(\"Open file\", [=] () {\n        auto selected = ui->treeView->selectionModel()->selectedIndexes();\n        emit openRequested(proj->getFileNames(selected));\n    });\n\n    auto runWith = contextMenu->addAction(\"Run model with this data\", [=] () {\n        auto selected = ui->treeView->selectionModel()->selectedIndexes();\n        emit runRequested(proj->getFileNames(selected));\n    });\n\n    contextMenu->addAction(\"Add existing file(s)...\", [=] () {\n        auto fileNames = QFileDialog::getOpenFileNames(this, tr(\"Select one or more files to open\"), IDE::instance()->getLastPath(), \"MiniZinc Files (*.mzn *.dzn *.json)\");\n        proj->add(fileNames);\n        if (fileNames.count()) {\n            IDE::instance()->setLastPath(QFileInfo(fileNames.last()).absolutePath() + \"/\");\n        }\n    });\n\n    auto rename = contextMenu->addAction(\"Rename file\", [=] () {\n        ui->treeView->edit(ui->treeView->currentIndex());\n    });\n\n    contextMenu->addAction(\"Remove from project\", [=] () {\n        auto selected = ui->treeView->selectionModel()->selectedIndexes();\n        emit removeRequested(proj->getFileNames(selected));\n    });\n\n    connect(ui->treeView, &QWidget::customContextMenuRequested, [=] (const QPoint& pos) {\n        auto selected = ui->treeView->selectionModel()->selectedIndexes();\n\n        if (!proj || selected.empty()) {\n            return;\n        }\n\n        int numModelsSelected = 0;\n        int numDataSelected = 0;\n        int numCheckersSelected = 0;\n        int numSolverConfigsSelected = 0;\n        int numOthersSelected = 0;\n\n        for (auto& index : selected) {\n            switch (proj->getType(index)) {\n            case Project::Model:\n                numModelsSelected++;\n                break;\n            case Project::Data:\n                numDataSelected++;\n                break;\n            case Project::Checker:\n                numCheckersSelected++;\n                break;\n            case Project::SolverConfig:\n                numSolverConfigsSelected++;\n                break;\n            case Project::Other:\n                numOthersSelected++;\n                break;\n            default:\n                return; // No applicable actions\n            }\n        }\n\n        rename->setDisabled(selected.length() > 1);\n        runWith->setDisabled(numOthersSelected > 0 || numCheckersSelected > 1 || numSolverConfigsSelected > 1);\n\n        if (numDataSelected == 0 && numCheckersSelected == 1) {\n            runWith->setText(\"Run model with this checker\");\n        } else if (numDataSelected > 0) {\n            runWith->setText(\"Run model with this data\");\n        } else {\n            runWith->setText(\"Run model\");\n        }\n\n        contextMenu->exec(ui->treeView->mapToGlobal(pos));\n    });\n}\n\nvoid ProjectBrowser::on_treeView_activated(const QModelIndex &index)\n{\n    emit openRequested({proj->getFileName(index)});\n}\n"
  },
  {
    "path": "MiniZincIDE/projectbrowser.h",
    "content": "#ifndef PROJECTBROWSER_H\n#define PROJECTBROWSER_H\n\n#include <QMenu>\n#include <QDir>\n#include <QWidget>\n#include <QStandardItemModel>\n#include <QSortFilterProxyModel>\n\n#include \"project.h\"\n#include \"moocsubmission.h\"\n\nnamespace Ui {\nclass ProjectBrowser;\n}\n\nclass ProjectBrowser : public QWidget\n{\n    Q_OBJECT\n    friend class TestIDE;\n\npublic:\n    explicit ProjectBrowser(QWidget *parent = nullptr);\n    ~ProjectBrowser();\n\n    ///\n    /// \\brief Gets the project associated with this project browser\n    /// \\return A pointer to the project\n    ///\n    Project* project(void)\n    {\n        return proj;\n    }\n    ///\n    /// \\brief Sets the project associated with this project browser\n    /// \\param project The new project\n    ///\n    void project(Project* project);\n\nsignals:\n    void runRequested(const QStringList& files);\n    void openRequested(const QStringList& files);\n    void removeRequested(const QStringList& files);\n\nprivate slots:\n    void on_treeView_activated(const QModelIndex &index);\n\nprivate:\n    Ui::ProjectBrowser *ui;\n\n    void setupContextMenu(void);\n\n    Project* proj = nullptr;\n};\n\n#endif // PROJECTBROWSER_H\n"
  },
  {
    "path": "MiniZincIDE/projectbrowser.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ProjectBrowser</class>\n <widget class=\"QWidget\" name=\"ProjectBrowser\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>300</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QGridLayout\" name=\"gridLayout\">\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item row=\"0\" column=\"0\">\n    <widget class=\"QTreeView\" name=\"treeView\">\n     <property name=\"contextMenuPolicy\">\n      <enum>Qt::CustomContextMenu</enum>\n     </property>\n     <property name=\"editTriggers\">\n      <set>QAbstractItemView::EditKeyPressed</set>\n     </property>\n     <property name=\"selectionMode\">\n      <enum>QAbstractItemView::ExtendedSelection</enum>\n     </property>\n     <property name=\"indentation\">\n      <number>10</number>\n     </property>\n     <attribute name=\"headerVisible\">\n      <bool>false</bool>\n     </attribute>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "MiniZincIDE/server/connector.js",
    "content": "const MiniZincIDE = (() => {\r\n    const callbacks = {};\r\n    const responses = [];\r\n    const freeSlots = [];\r\n    let userData;\r\n    \r\n    window.addEventListener('message', (e) => {\r\n        switch (e.data.event) {\r\n            case 'response': {\r\n                resolveResponse(e.data.id, e.data.payload);\r\n                break;\r\n            }\r\n            case 'error': {\r\n                rejectResponse(e.data.id, e.data.message);\r\n                break;\r\n            }\r\n            default:\r\n                if (e.data.event in callbacks) {\r\n                    callbacks[e.data.event].forEach(callback => {\r\n                        callback(e.data.payload);\r\n                    });\r\n                }\r\n                break;\r\n        }\r\n    });\r\n\r\n    function resolveResponse(index, payload) {\r\n        const { resolve } = responses[index];\r\n        resolve(payload);\r\n        responses[index] = null;\r\n        freeSlots.push(index);\r\n    }\r\n\r\n    function rejectResponse(index, message) {\r\n        const { reject } = responses[index];\r\n        reject(message);\r\n        responses[index] = null;\r\n        freeSlots.push(index);\r\n    }\r\n\r\n    function createPromise(message) {\r\n        return new Promise((resolve, reject) => {\r\n            const id = freeSlots.length > 0 ? freeSlots.pop() : responses.length;\r\n            responses[id] = {resolve, reject};\r\n            window.parent.postMessage({\r\n                ...message,\r\n                id\r\n            }, '*');\r\n        });\r\n    }\r\n    \r\n    function on(event, callback) {\r\n        if (!(event in callbacks)) {\r\n            callbacks[event] = new Set();\r\n        }\r\n        callbacks[event].add(callback);\r\n    }\r\n    function off(event, callback) {\r\n        if (event in callbacks) {\r\n            callbacks[event].delete(callback);\r\n        }\r\n    }\r\n    function getUserData() {\r\n        return new Promise((resolve, reject) => {\r\n            if (userData === undefined) {\r\n                on('init', (data) => {\r\n                    userData = data;\r\n                    resolve(userData);\r\n                });\r\n            } else {\r\n                resolve(userData);\r\n            }\r\n        });\r\n    }\r\n    function goToSolution(idx) {\r\n        window.parent.postMessage({\r\n            event: 'rebroadcast',\r\n            message: {\r\n                event: 'goToSolution',\r\n                payload: idx\r\n            }\r\n        }, '*');\r\n    }\r\n    function solve(modelFile, dataFiles, options) {\r\n        window.parent.postMessage({\r\n            event: 'solve',\r\n            modelFile,\r\n            dataFiles,\r\n            options\r\n        }, '*');\r\n    }\r\n    function getNumSolutions() {\r\n        return createPromise({\r\n            event: 'getNumSolutions'\r\n        });\r\n    }\r\n    function getSolution(index) {\r\n        return createPromise({\r\n            event: 'getSolution',\r\n            index\r\n        });\r\n    }\r\n    function getAllSolutions() {\r\n        return createPromise({\r\n            event: 'getAllSolutions'\r\n        });\r\n    }\r\n    function getStatus() {\r\n        return createPromise({\r\n            event: 'getStatus'\r\n        });\r\n    }\r\n    function getFinishTime() {\r\n        return createPromise({\r\n            event: 'getFinishTime'\r\n        });\r\n    }\r\n\r\n    return {\r\n        getUserData,\r\n        on,\r\n        off,\r\n        goToSolution,\r\n        solve,\r\n        getNumSolutions,\r\n        getSolution,\r\n        getAllSolutions,\r\n        getStatus,\r\n        getFinishTime\r\n    };\r\n})();\r\n"
  },
  {
    "path": "MiniZincIDE/server/index.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n\r\n<head>\r\n    <meta charset=\"utf-8\">\r\n    <title>%3 | MiniZincIDE</title>\r\n    <style>\r\n        html,\r\n        body {\r\n            margin: 0;\r\n            padding: 0;\r\n            font-family: sans-serif;\r\n            font-size: 14px;\r\n        }\r\n\r\n        * {\r\n            box-sizing: border-box;\r\n        }\r\n\r\n        #container {\r\n            display: flex;\r\n            flex-direction: column;\r\n            align-items: stretch;\r\n            width: 100vw;\r\n            height: 100vh;\r\n        }\r\n\r\n        #top {\r\n            display: none;\r\n        }\r\n\r\n        #top.has-solutions {\r\n            display: flex;\r\n            background: #F4F4F4;\r\n            padding: 5px 10px;\r\n            align-items: center;\r\n            gap: 5px;\r\n        }\r\n\r\n        #solutionSlider {\r\n            flex: 1 1 auto;\r\n        }\r\n\r\n        #wrapper {\r\n            flex: 1 0 auto;\r\n        }\r\n\r\n        #windows {\r\n            width: 100%;\r\n            height: 100%;\r\n            display: grid;\r\n            grid-auto-rows: auto;\r\n            gap: 1px;\r\n            background: #CCC;\r\n        }\r\n\r\n        #windows>* {\r\n            background: #FFF;\r\n            border: 0;\r\n            width: 100%;\r\n            height: 100%;\r\n        }\r\n\r\n        #notice {\r\n            width: 100vw;\r\n            position: absolute;\r\n            top: 20vw;\r\n            left: 0;\r\n            text-align: center;\r\n        }\r\n\r\n        #notice:empty {\r\n            display: none;\r\n        }\r\n\r\n        .hidden {\r\n            display: none !important;\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"container\">\r\n        <div id=\"top\">\r\n            <label for=\"solutionNumber\">Solution:</label>\r\n            <input type=\"number\" id=\"solutionNumber\" min=\"0\">\r\n            <input type=\"range\" id=\"solutionSlider\" min=\"0\">\r\n            <input type=\"checkbox\" id=\"followLatest\">\r\n            <label for=\"followLatest\">Follow latest solution</label>\r\n        </div>\r\n        <div id=\"wrapper\">\r\n            <div id=\"windows\"></div>\r\n        </div>\r\n    </div>\r\n    <div id=\"notice\">Connecting...</div>\r\n    <script>\r\n        const layoutGrid = (container) => {\r\n            const numChildren = container.children.length;\r\n            const cols = Math.ceil(Math.sqrt(numChildren));\r\n            container.style = `grid-template-columns: repeat(${cols}, auto);`;\r\n        };\r\n        const windows = document.getElementById('windows');\r\n        const buffers = {};\r\n        const ws = new WebSocket('%1/%2');\r\n        const notice = document.getElementById('notice');\r\n\r\n        let numSolutions = 0;\r\n        let currentSolution = 0;\r\n\r\n        const topBar = document.getElementById('top');\r\n        const solNumberInput = document.getElementById('solutionNumber');\r\n        const solSliderInput = document.getElementById('solutionSlider');\r\n        const followLatestInput = document.getElementById('followLatest');\r\n\r\n        const updateControls = () => {\r\n            if (numSolutions > 0) {\r\n                topBar.classList.add('has-solutions');\r\n            }\r\n            if (followLatestInput.checked) {\r\n                currentSolution = numSolutions;\r\n            }\r\n            solNumberInput.min = numSolutions > 0 ? 1 : 0;\r\n            solNumberInput.max = numSolutions;\r\n            solNumberInput.value = currentSolution;\r\n            solSliderInput.min = numSolutions > 0 ? 1 : 0;\r\n            solSliderInput.max = numSolutions;\r\n            solSliderInput.value = currentSolution;\r\n        };\r\n\r\n        solNumberInput.addEventListener('change', () => {\r\n            if (solNumberInput.value !== currentSolution) {\r\n                currentSolution = solNumberInput.value;\r\n                followLatestInput.checked = false;\r\n                updateControls();\r\n                for (const buffer of Object.values(buffers)) {\r\n                    buffer.queue.push({\r\n                        event: 'goToSolution',\r\n                        payload: currentSolution - 1\r\n                    });\r\n                }\r\n            }\r\n        });\r\n        solSliderInput.addEventListener('change', () => {\r\n            if (solSliderInput.value !== currentSolution) {\r\n                currentSolution = solSliderInput.value;\r\n                followLatestInput.checked = false;\r\n                updateControls();\r\n                for (const buffer of Object.values(buffers)) {\r\n                    buffer.queue.push({\r\n                        event: 'goToSolution',\r\n                        payload: currentSolution - 1\r\n                    });\r\n                }\r\n            }\r\n        });\r\n        followLatestInput.addEventListener('change', () => {\r\n            currentSolution = numSolutions;\r\n            updateControls();\r\n            for (const buffer of Object.values(buffers)) {\r\n                buffer.queue.push({\r\n                    event: 'goToSolution',\r\n                    payload: followLatestInput.checked ? -1 : numSolutions - 1\r\n                });\r\n            }\r\n        });\r\n\r\n        const addWindow = (key, url, userData) => {\r\n            const frame = document.createElement(\"iframe\");\r\n            const buffer = {\r\n                ready: false,\r\n                frame,\r\n                queue: [{ event: 'init', payload: userData }]\r\n            };\r\n            frame.addEventListener('load', () => {\r\n                buffer.ready = true;\r\n            });\r\n            frame.src = `%2/${url}`;\r\n            windows.appendChild(frame);\r\n            buffers[key] = buffer;\r\n            layoutGrid(windows);\r\n        }\r\n\r\n        ws.onopen = () => {\r\n            notice.textContent = '';\r\n        };\r\n        ws.onerror = e => {\r\n            notice.textContent = `A WebSocket error occurred (code ${e.code}).`;\r\n            console.log('WebSocket error: ', e);\r\n        };\r\n        ws.onclose = () => {\r\n            notice.textContent = 'Disconnected. Refresh the page to try again.';\r\n        };\r\n        ws.onmessage = e => {\r\n            const message = JSON.parse(e.data);\r\n            switch (message.event) {\r\n                case 'navigate':\r\n                    window.location = message.url;\r\n                    break;\r\n                case 'init':\r\n                    for (const k in message.windows) {\r\n                        addWindow(k, message.windows[k].url, message.windows[k].userData);\r\n                    }\r\n                    numSolutions = message.numSolutions;\r\n                    followLatestInput.checked = true;\r\n                    updateControls();\r\n                    break;\r\n                case 'window':\r\n                    addWindow(message.windowId, message.url, message.userData);\r\n                    break;\r\n                case 'response':\r\n                    // Pass response to child\r\n                    buffers[message.window].queue.push({\r\n                        event: 'response',\r\n                        id: message.id,\r\n                        payload: message.payload\r\n                    });\r\n                    break;\r\n                case 'error':\r\n                    // Pass error to child\r\n                    buffers[message.window].queue.push({\r\n                        event: 'error',\r\n                        id: message.id,\r\n                        message: message.message\r\n                    });\r\n                    break;\r\n                case 'solution':\r\n                    // Pass solution to children\r\n                    for (const key in message.solution) {\r\n                        buffers[key].queue.push({\r\n                            event: 'solution',\r\n                            payload: {\r\n                                time: message.time,\r\n                                data: message.solution[key]\r\n                            }\r\n                        });\r\n                    }\r\n                    numSolutions++;\r\n                    updateControls();\r\n                    break;\r\n                case 'status':\r\n                case 'finish':\r\n                    // Pass on to all children\r\n                    for (const buffer of Object.values(buffers)) {\r\n                        buffer.queue.push(message);\r\n                    }\r\n                    break;\r\n            }\r\n        };\r\n        window.addEventListener('message', e => {\r\n            const message = e.data;\r\n            switch (message.event) {\r\n                case 'rebroadcast':\r\n                    // Rebroadcast to all children\r\n                    for (const buffer of Object.values(buffers)) {\r\n                        buffer.queue.push(message.message);\r\n                    }\r\n                    if (message.message.event === 'goToSolution') {\r\n                        followLatestInput.checked = message.message.payload === -1;\r\n                        currentSolution = message.message.payload;\r\n                        updateControls();\r\n                    }\r\n                    break;\r\n                default:\r\n                    // Pass on to IDE\r\n                    let windowId = null;\r\n                    for (const key in buffers) {\r\n                        if (buffers[key].frame.contentWindow === e.source) {\r\n                            windowId = key;\r\n                            break;\r\n                        }\r\n                    }\r\n                    if (windowId === null) {\r\n                        console.error('Could not determine source window for message');\r\n                        e.source.postMessage({\r\n                            event: 'error',\r\n                            id: message.id,\r\n                            message: 'Could not determine source window'\r\n                        });\r\n                        return;\r\n                    }\r\n                    ws.send(JSON.stringify({\r\n                        ...message,\r\n                        window: windowId\r\n                    }));\r\n                    break;\r\n            }\r\n        });\r\n        window.setInterval(() => {\r\n            for (const buffer of Object.values(buffers)) {\r\n                if (buffer.ready) {\r\n                    for (const message of buffer.queue) {\r\n                        buffer.frame.contentWindow.postMessage(message, '*');\r\n                    }\r\n                    buffer.queue = [];\r\n                }\r\n            }\r\n        }, 1);\r\n    </script>\r\n</body>\r\n\r\n</html>"
  },
  {
    "path": "MiniZincIDE/server.cpp",
    "content": "#include \"server.h\"\n\n#include <QWebSocket>\n#include <QJsonDocument>\n#include <QDesktopServices>\n\n#include \"ideutils.h\"\n#include \"exception.h\"\n\nVisConnector::~VisConnector()\n{\n    for (auto* c : _clients) {\n        delete c;\n    }\n}\n\nvoid VisConnector::newWebSocketClient(QWebSocket* socket)\n{\n    _clients << socket;\n    connect(socket, &QWebSocket::disconnected, this, &VisConnector::webSocketClientDisconnected);\n    connect(socket, &QWebSocket::textMessageReceived, this, &VisConnector::webSocketMessageReceived);\n    QJsonObject obj({{\"event\", \"init\"},\n                     {\"windows\", _windows},\n                     {\"numSolutions\", _solutions.isEmpty() ? 0 : _solutions.first().size()}});\n    auto json = QString::fromUtf8(QJsonDocument(obj).toJson());\n    socket->sendTextMessage(json);\n}\n\nvoid VisConnector::webSocketClientDisconnected()\n{\n    auto* client = qobject_cast<QWebSocket*>(sender());\n    if (client) {\n        _clients.removeAll(client);\n        client->deleteLater();\n    }\n}\n\nvoid VisConnector::addWindow(const QString& key, const QString& url, const QJsonValue& userData)\n{\n    _windows[key] = QJsonObject({{\"url\", url}, {\"userData\", userData}});\n    _solutions[key] = QJsonArray();\n    QJsonObject obj({{\"event\", \"window\"},\n                     {\"windowId\", key},\n                     {\"url\", url},\n                     {\"userData\", userData}});\n    broadcastMessage(QJsonDocument(obj));\n}\n\nvoid VisConnector::addSolution(const QJsonObject& solution, qint64 time)\n{\n    auto solIndex = _solutionCount++;\n    for (auto it = solution.begin(); it != solution.end(); it++) {\n        QJsonObject sol({{\"time\", time},\n                        {\"data\", it.value()},\n                        {\"index\", solIndex}});\n        if (_solutions.contains(it.key())) {\n            _solutions[it.key()].append(sol);\n        }\n    }\n    QJsonObject obj({{\"event\", \"solution\"},\n                     {\"time\", time},\n                     {\"solution\", solution},\n                     {\"index\", solIndex}});\n    broadcastMessage(QJsonDocument(obj));\n}\n\nvoid VisConnector::setFinalStatus(const QString& status, qint64 time)\n{\n    _finalStatus = QJsonObject({{\"time\", time}, {\"status\", status}});\n    QJsonObject obj({{\"event\", \"status\"},\n                     {\"payload\", QJsonObject({\n                        {\"time\", time},\n                        {\"status\", status}\n                     })}});\n    broadcastMessage(QJsonDocument(obj));\n}\n\nvoid VisConnector::setFinished(qint64 time)\n{\n    _finishTime = time;\n    QJsonObject obj({{\"event\", \"finish\"},\n                     {\"payload\", time}});\n    broadcastMessage(QJsonDocument(obj));\n}\n\nvoid VisConnector::broadcastMessage(const QJsonDocument& message)\n{\n    auto json = QString::fromUtf8(message.toJson());\n    for (auto* client : _clients) {\n        client->sendTextMessage(json);\n    }\n}\n\nvoid VisConnector::webSocketMessageReceived(const QString& message)\n{\n    auto* socket = qobject_cast<QWebSocket*>(sender());\n\n    QJsonParseError error;\n    auto json = QJsonDocument::fromJson(message.toUtf8(), &error);\n    auto msg = json.object();\n    auto event = msg[\"event\"].toString();\n    if (event == \"solve\") {\n        auto mf = msg[\"modelFile\"].toString();\n        QStringList dfs;\n        auto dataFiles = msg[\"dataFiles\"];\n        bool dataFilesGiven = dataFiles.isArray();\n        for (auto v : dataFiles.toArray()) {\n            dfs << v.toString();\n        }\n        auto opts = msg[\"options\"].toObject().toVariantMap();\n        emit solveRequested(mf, dataFilesGiven, dfs, opts);\n    } else if (event == \"getNumSolutions\") {\n        QJsonObject obj({{\"event\", \"response\"},\n                         {\"id\", msg[\"id\"]},\n                         {\"window\", msg[\"window\"]},\n                         {\"payload\", _solutions.isEmpty() ? 0 : _solutions.first().size()}});\n        socket->sendTextMessage(QString::fromUtf8(QJsonDocument(obj).toJson()));\n    } else if (event == \"getSolution\") {\n        auto window_id = msg[\"window\"].toString();\n        if (!_solutions.contains(window_id)) {\n            QJsonObject obj({{\"event\", \"error\"},\n                             {\"id\", msg[\"id\"]},\n                             {\"window\", msg[\"window\"]},\n                             {\"message\", \"Invalid window.\"}});\n            socket->sendTextMessage(QString::fromUtf8(QJsonDocument(obj).toJson()));\n            return;\n        }\n        auto idx = msg[\"index\"].toInt();\n        if (idx < 0) {\n            idx += _solutions[window_id].count();\n        }\n        if (idx < 0 || idx >= _solutions[window_id].count()) {\n            QJsonObject obj({{\"event\", \"error\"},\n                             {\"id\", msg[\"id\"]},\n                             {\"window\", msg[\"window\"]},\n                             {\"message\", \"Index out of range.\"}});\n            socket->sendTextMessage(QString::fromUtf8(QJsonDocument(obj).toJson()));\n            return;\n        }\n        QJsonObject obj({{\"event\", \"response\"},\n                         {\"id\", msg[\"id\"]},\n                         {\"window\", msg[\"window\"]},\n                         {\"payload\", _solutions[window_id][idx]}});\n        socket->sendTextMessage(QString::fromUtf8(QJsonDocument(obj).toJson()));\n    } else if (event == \"getAllSolutions\") {\n        auto window_id = msg[\"window\"].toString();\n        if (!_solutions.contains(window_id)) {\n            QJsonObject obj({{\"event\", \"error\"},\n                             {\"id\", msg[\"id\"]},\n                             {\"window\", msg[\"window\"]},\n                             {\"message\", \"Invalid window.\"}});\n            socket->sendTextMessage(QString::fromUtf8(QJsonDocument(obj).toJson()));\n            return;\n        }\n        QJsonObject obj({{\"event\", \"response\"},\n                         {\"id\", msg[\"id\"]},\n                         {\"window\", window_id},\n                         {\"payload\", _solutions[window_id]}});\n        socket->sendTextMessage(QString::fromUtf8(QJsonDocument(obj).toJson()));\n    } else if (event == \"getStatus\") {\n        QJsonObject obj({{\"event\", \"response\"},\n                         {\"id\", msg[\"id\"]},\n                         {\"window\", msg[\"window\"]},\n                         {\"payload\", _finalStatus}});\n        socket->sendTextMessage(QString::fromUtf8(QJsonDocument(obj).toJson()));\n    } else if (event == \"getFinishTime\") {\n        QJsonObject obj({{\"event\", \"response\"},\n                         {\"id\", msg[\"id\"]},\n                         {\"window\", msg[\"window\"]},\n                         {\"payload\", _finishTime == -1 ? QJsonValue() : _finishTime}});\n        socket->sendTextMessage(QString::fromUtf8(QJsonDocument(obj).toJson()));\n    } else {\n        QJsonObject obj({{\"event\", \"error\"},\n                         {\"id\", msg[\"id\"]},\n                         {\"window\", msg[\"window\"]},\n                         {\"message\", QString(\"Unknown event '%1'.\").arg(event)}});\n        socket->sendTextMessage(QString::fromUtf8(QJsonDocument(obj).toJson()));\n    }\n}\n\nServer::Server(QObject *parent) :\n    QObject(parent),\n    http(new QTcpServer(this)),\n    ws(new QWebSocketServer(\"MiniZincIDE\", QWebSocketServer::NonSecureMode, this))\n{\n    connect(http, &QTcpServer::newConnection, this, &Server::newHttpClient);\n    connect(ws, &QWebSocketServer::newConnection, this, &Server::newWebSocketClient);\n}\n\nServer::~Server()\n{\n    for (auto* c : connectors) {\n        delete c;\n    }\n}\n\nvoid Server::listen(quint16 httpPort, quint16 wsPort)\n{\n    if (!http->isListening() || httpPort != initialHttpPort) {\n        initialHttpPort = httpPort;\n        qint16 p = httpPort;\n        http->close();\n        for (int i = 0; i < 10; i++) {\n            if (http->listen(QHostAddress::LocalHost, p) || p == 0) {\n                break;\n            }\n            p++;\n        }\n        if (!http->isListening()) {\n            throw ServerError(\"Failed to start HTTP visualisation server\");\n        }\n    }\n\n    if (!ws->isListening() || wsPort != initialWsPort) {\n        initialWsPort = wsPort;\n        quint16 p = wsPort;\n        ws->close();\n        for (int i = 0; i < 10; i++) {\n            if (ws->listen(QHostAddress::LocalHost, p) || p == 0) {\n                break;\n            }\n            p++;\n        }\n        if (!ws->isListening()) {\n            throw ServerError(\"Failed to start WebSocket visualisation server\");\n        }\n    }\n}\n\nVisConnector* Server::addConnector(const QString& label, const QStringList& roots)\n{\n    auto* c = new VisConnector(this);\n    c->_label = label;\n    c->_roots = roots;\n    c->_url.setScheme(\"http\");\n    c->_url.setHost(address());\n    c->_url.setPort(port());\n    c->_url.setPath(QString(\"/%1\").arg(connectors.size()));\n    connectors << c;\n    return c;\n}\n\nvoid Server::clear()\n{\n    for (auto* c : connectors) {\n        delete c;\n    }\n    connectors.clear();\n}\n\nbool Server::sendToLastClient(const QJsonDocument& message)\n{\n    auto json = QString::fromUtf8(message.toJson());\n    for (auto it = clients.rbegin(); it != clients.rend(); it++) {\n        if ((*it)->isValid()) {\n            (*it)->sendTextMessage(json);\n            return true;\n        }\n    }\n    return false;\n}\n\nvoid Server::newHttpClient()\n{\n    auto* socket = http->nextPendingConnection();\n    connect(socket, &QTcpSocket::readyRead, this, &Server::handleHttpRequest);\n    connect(socket, &QTcpSocket::stateChanged, this, [=] (QAbstractSocket::SocketState s) {\n        if (s == QAbstractSocket::UnconnectedState) {\n            socket->deleteLater();\n        }\n    });\n}\n\nvoid Server::handleHttpRequest()\n{\n    auto* socket = qobject_cast<QTcpSocket*>(sender());\n    if (!socket->canReadLine()) {\n        return;\n    }\n\n    QTextStream ts(socket);\n    auto parts = ts.readLine().split(QRegularExpression(\"\\\\s+\"));\n    if (parts.length() < 3 || parts[0] != \"GET\") {\n        return;\n    }\n\n    // Connector script\n    if (parts[1] == \"/minizinc-ide.js\") {\n        QFile f(\":/server/server/connector.js\");\n        f.open(QFile::ReadOnly | QFile::Text);\n        QTextStream c(&f);\n        ts << \"HTTP/1.1 200 OK\\r\\n\"\n           << \"Content-Type: text/javascript\\r\\n\"\n           << \"\\r\\n\"\n           << c.readAll();\n        socket->close();\n        return;\n    }\n\n    QRegularExpression re(\"^/?(\\\\d+)(/.*)?$\");\n    auto p = QUrl::fromPercentEncoding(parts[1].toUtf8());\n    auto match = re.match(p);\n    if (!match.hasMatch()) {\n        ts << \"HTTP/1.1 404 OK\\r\\n\"\n           << \"\\r\\n\"\n           << \"File not found.\";\n        socket->close();\n        return;\n    }\n\n    auto index = match.captured(1).toInt();\n    if (index >= connectors.size()) {\n        ts << \"HTTP/1.1 404 OK\\r\\n\"\n           << \"\\r\\n\"\n           << \"File not found.\";\n        socket->close();\n        return;\n    }\n\n    auto path = match.captured(2);\n    // Window management page\n    if (path.isEmpty() || path == \"index.html\") {\n        QString title = connectors[index]->_label.toHtmlEscaped();\n        QFile f(\":/server/server/index.html\");\n        f.open(QFile::ReadOnly | QFile::Text);\n        QTextStream c(&f);\n        ts << \"HTTP/1.1 200 OK\\r\\n\"\n           << \"Content-Type: text/html\\r\\n\"\n           << \"\\r\\n\"\n           << c.readAll().arg(ws->serverUrl().toString()).arg(index).arg(title);\n        socket->close();\n        return;\n    }\n\n    // Static file server\n    QStringList base_dirs({\":/server/server\"});\n    base_dirs << connectors[index]->_roots;\n    for (auto& base_dir : base_dirs) {\n        auto full_path = base_dir + \"/\" + path;\n        if (IDEUtils::isChildPath(base_dir, full_path)) {\n            QFile file(full_path);\n            if (!file.exists()) {\n                continue;\n            }\n            if (!file.open(QFile::ReadOnly)) {\n                ts << \"HTTP/1.1 500 OK\\r\\n\"\n                   << \"\\r\\n\"\n                   << \"Failed to retrieve file\";\n                socket->close();\n                return;\n            }\n            QMimeDatabase db;\n            QMimeType mime = db.mimeTypeForFile(QFileInfo(file));\n            ts << \"HTTP/1.1 200 OK\\r\\n\"\n               << \"Content-Type: \" << mime.name() << \"\\r\\n\"\n               << \"\\r\\n\";\n            ts.flush();\n            socket->write(file.readAll());\n            socket->close();\n            return;\n        }\n    }\n    ts << \"HTTP/1.1 404 OK\\r\\n\"\n       << \"\\r\\n\"\n       << \"File not found.\";\n    socket->close();\n    return;\n}\n\nvoid Server::newWebSocketClient()\n{\n    auto* socket = ws->nextPendingConnection();\n    auto path = socket->requestUrl().path();\n    bool ok = false;\n    auto index = path.right(path.size() - 1).toInt(&ok);\n    if (!ok || index >= connectors.size()) {\n        socket->close();\n        return;\n    }\n    auto* c = connectors[index];\n    c->newWebSocketClient(socket);\n    clients << socket;\n    connect(socket, &QWebSocket::disconnected, this, &Server::webSocketClientDisconnected);\n}\n\nvoid Server::webSocketClientDisconnected()\n{\n    auto* client = qobject_cast<QWebSocket*>(sender());\n    if (client) {\n        clients.removeAll(client);\n    }\n}\n"
  },
  {
    "path": "MiniZincIDE/server.h",
    "content": "#ifndef SERVER_H\n#define SERVER_H\n\n#include <QObject>\n#include <QTcpServer>\n#include <QtWebSockets/QtWebSockets>\n#include <QJsonValue>\n#include <QElapsedTimer>\n\nclass VisConnector : public QObject {\n    Q_OBJECT\n\nprivate:\n    QString _label;\n    QStringList _roots;\n    QList<QWebSocket*> _clients;\n\n    QJsonObject _windows;\n    QMap<QString, QJsonArray> _solutions;\n    QJsonValue _finalStatus;\n    int _solutionCount = 0;\n    qint64 _finishTime = -1;\n\n    QUrl _url;\n\n    friend class Server;\npublic:\n    explicit VisConnector(QObject *parent = nullptr) : QObject(parent) {}\n    ~VisConnector();\n\n    QUrl url() const { return _url; }\n\nsignals:\n    void solveRequested(const QString& modelFile, bool dataFilesGiven, const QStringList& dataFiles, const QVariantMap& options);\n\npublic slots:\n    void addWindow(const QString& key, const QString& url, const QJsonValue& userData);\n    void addSolution(const QJsonObject& solution, qint64 time);\n    void setFinalStatus(const QString& status, qint64 time);\n    void setFinished(qint64 time);\n\nprivate slots:\n    void newWebSocketClient(QWebSocket* s);\n    void webSocketClientDisconnected();\n    void webSocketMessageReceived(const QString& message);\n    void broadcastMessage(const QJsonDocument& message);\n};\n\n///\n/// \\brief HTTP and WebSocket server for web visualisation\n///\nclass Server : public QObject\n{\n    Q_OBJECT\npublic:\n    explicit Server(QObject *parent = nullptr);\n    ~Server();\n\n    void listen(quint16 httpPort = 3000, quint16 wsPort = 3100);\n\n    QString address() const { return http->serverAddress().toString(); }\n    quint16 port() const { return http->serverPort(); }\n    quint16 desiredHttpPort() const { return initialHttpPort; }\n    quint16 desiredWsPort() const { return initialWsPort; }\n\n    VisConnector* addConnector(const QString& label, const QStringList& roots);\n    void clear();\n\n    bool sendToLastClient(const QJsonDocument& doc);\n\nsignals:\n    void solve(const QString& model, const QStringList& data, const QVariantMap& options);\n\nprivate slots:\n    void newHttpClient();\n    void newWebSocketClient();\n    void webSocketClientDisconnected();\n    void handleHttpRequest();\n\nprivate:\n    QTcpServer* http;\n    QWebSocketServer* ws;\n    QList<VisConnector*> connectors;\n    QList<QWebSocket*> clients;\n    quint16 initialHttpPort;\n    quint16 initialWsPort;\n};\n\n#endif // SERVER_H\n"
  },
  {
    "path": "MiniZincIDE/solver.cpp",
    "content": "#include \"solver.h\"\n#include \"process.h\"\n#include \"exception.h\"\n\n#include <QFileInfo>\n#include <QFile>\n#include <QJsonParseError>\n#include <QJsonArray>\n#include <QDir>\n\nnamespace {\n\nvoid parseArgList(const QString& s, QVariantMap& map) {\n    QStringList ret;\n    bool hadEscape = false;\n    bool inSingleQuote = false;\n    bool inDoubleQuote = false;\n    QString currentArg;\n    foreach (const QChar c, s) {\n        if (hadEscape) {\n            currentArg += c;\n            hadEscape = false;\n        } else {\n            if (c=='\\\\') {\n                hadEscape = true;\n            } else if (c=='\"') {\n                if (inDoubleQuote) {\n                    inDoubleQuote=false;\n                    ret.push_back(currentArg);\n                    currentArg = \"\";\n                } else if (inSingleQuote) {\n                    currentArg += c;\n                } else {\n                    inDoubleQuote = true;\n                }\n            } else if (c=='\\'') {\n                if (inSingleQuote) {\n                    inSingleQuote=false;\n                    ret.push_back(currentArg);\n                    currentArg = \"\";\n                } else if (inDoubleQuote) {\n                    currentArg += c;\n                } else {\n                    inSingleQuote = true;\n                }\n            } else if (!inSingleQuote && !inDoubleQuote && c==' ') {\n                if (currentArg.size() > 0) {\n                    ret.push_back(currentArg);\n                    currentArg = \"\";\n                }\n            } else {\n                currentArg += c;\n            }\n        }\n    }\n    if (currentArg.size() > 0) {\n        ret.push_back(currentArg);\n    }\n\n    QString flag;\n    for (auto& arg : ret) {\n        if (arg.startsWith(\"-\")) {\n            if (!flag.isEmpty()) {\n                // Must be a boolean switch\n                map[flag] = true;\n            }\n            flag = arg;\n        } else if (!flag.isEmpty()) {\n            // Flag with arg\n            map[flag] = arg;\n            flag.clear();\n        } else {\n            // Arg with no flag\n            map[arg] = true;\n        }\n    }\n}\n\n}\n\n\nSolver::Solver(const QJsonObject& sj) {\n    json = sj;\n    name = sj[\"name\"].toString();\n    QJsonObject extraInfo = sj[\"extraInfo\"].toObject();\n    executable_resolved = extraInfo[\"executable\"].toString(\"\");\n    mznlib_resolved = extraInfo[\"mznlib\"].toString(\"\");\n    configFile = extraInfo[\"configFile\"].toString(\"\");\n    if (extraInfo[\"defaultFlags\"].isArray()) {\n        QJsonArray ei = extraInfo[\"defaultFlags\"].toArray();\n        for (auto df : ei) {\n            defaultFlags.push_back(df.toString());\n        }\n    }\n    isDefaultSolver = (extraInfo[\"isDefault\"].isBool() && extraInfo[\"isDefault\"].toBool());\n    contact = sj[\"contact\"].toString(\"\");\n    website = sj[\"website\"].toString(\"\");\n    if (sj[\"inputType\"].isString()) {\n        auto t = sj[\"inputType\"].toString();\n        if (t == \"FZN\") {\n            inputType = SolverInputType::I_FZN;\n        } else if (t == \"MZN\") {\n            inputType = SolverInputType::I_MZN;\n        } else if (t == \"NL\") {\n            inputType = SolverInputType::I_NL;\n        } else if (t == \"JSON\") {\n            inputType = SolverInputType::I_JSON;\n        } else {\n            inputType = SolverInputType::I_UNKNOWN;\n        }\n    } else if (sj[\"supportsMzn\"].toBool()) {\n        inputType = SolverInputType::I_MZN;\n    } else if (sj[\"supportsFzn\"].toBool(true)) {\n        inputType = SolverInputType::I_FZN;\n    } else {\n        inputType = SolverInputType::I_UNKNOWN;\n    }\n\n    if (sj[\"requiredFlags\"].isArray()) {\n        QJsonArray rfs = sj[\"requiredFlags\"].toArray();\n        for (auto rf : rfs) {\n            requiredFlags.push_back(rf.toString());\n        }\n    }\n    if (sj[\"stdFlags\"].isArray()) {\n        QJsonArray sfs = sj[\"stdFlags\"].toArray();\n        for (auto sf : sfs) {\n            stdFlags.push_back(sf.toString());\n        }\n    }\n    if (sj[\"extraFlags\"].isArray()) {\n        QJsonArray sfs = sj[\"extraFlags\"].toArray();\n        for (auto sf : sfs) {\n            if (sf.isArray()) {\n                QJsonArray extraFlagA = sf.toArray();\n                if (extraFlagA.size()==4) {\n                    SolverFlag extraFlag;\n                    extraFlag.min=1.0;\n                    extraFlag.max=0.0;\n                    extraFlag.name = extraFlagA[0].toString();\n                    QRegularExpression re_opt(\"^(int|float|bool)(:([0-9a-zA-Z.-]+):([0-9a-zA-Z.-]+))?\");\n                    QRegularExpressionMatch re_opt_match = re_opt.match(extraFlagA[2].toString());\n                    if (re_opt_match.hasMatch()) {\n                        if (re_opt_match.captured(1)==\"int\") {\n                            if (re_opt_match.captured(3).isEmpty()) {\n                                extraFlag.t = SolverFlag::T_INT;\n                            } else {\n                                extraFlag.t = SolverFlag::T_INT_RANGE;\n                                extraFlag.min_ll = re_opt_match.captured(3).toLongLong();\n                                extraFlag.max_ll = re_opt_match.captured(4).toLongLong();\n                            }\n                        } else if (re_opt_match.captured(1)==\"float\") {\n                            if (re_opt_match.captured(3).isEmpty()) {\n                                extraFlag.t = SolverFlag::T_FLOAT;\n                            } else {\n                                extraFlag.t = SolverFlag::T_FLOAT_RANGE;\n                                extraFlag.min = re_opt_match.captured(3).toDouble();\n                                extraFlag.max = re_opt_match.captured(4).toDouble();\n                            }\n                        } else if (re_opt_match.captured(1)==\"bool\") {\n                            if (re_opt_match.captured(3).isEmpty()) {\n                                extraFlag.t = SolverFlag::T_BOOL;\n                            } else {\n                                extraFlag.t = SolverFlag::T_BOOL_ONOFF;\n                                extraFlag.options = QStringList({re_opt_match.captured(3),re_opt_match.captured(4)});\n                            }\n                        }\n                    } else {\n                        if (extraFlagA[2].toString()==\"string\") {\n                            extraFlag.t = SolverFlag::T_STRING;\n                        } else if (extraFlagA[2].toString().startsWith(\"opt:\")) {\n                            extraFlag.t = SolverFlag::T_OPT;\n                            extraFlag.options = extraFlagA[2].toString().mid(4).split(\":\");\n//                                        } else if (extraFlagA[2].toString()==\"solver\") {\n//                                            extraFlag.t = SolverFlag::T_SOLVER;\n                        } else {\n                            continue;\n                        }\n                    }\n                    extraFlag.description = extraFlagA[1].toString();\n                    auto def = extraFlagA[3].toString();\n                    switch (extraFlag.t) {\n                    case SolverFlag::T_INT:\n                    case SolverFlag::T_INT_RANGE:\n                        extraFlag.def = def.toLongLong();\n                        break;\n                    case SolverFlag::T_BOOL:\n                        extraFlag.def = def == \"true\";\n                        break;\n                    case SolverFlag::T_BOOL_ONOFF:\n                        if (def == extraFlag.options[0]) {\n                            extraFlag.def = true;\n                        } else if (def == extraFlag.options[1]) {\n                            extraFlag.def = false;\n                        } else {\n                            extraFlag.def = def == \"true\";\n                        }\n                        break;\n                    case SolverFlag::T_FLOAT:\n                    case SolverFlag::T_FLOAT_RANGE:\n                        extraFlag.def = def.toDouble();\n                        break;\n                    case SolverFlag::T_STRING:\n                    case SolverFlag::T_OPT:\n                    case SolverFlag::T_SOLVER:\n                        extraFlag.def = def;\n                        break;\n                    }\n                    extraFlags.push_back(extraFlag);\n                }\n            }\n        }\n    }\n    isGUIApplication = sj[\"isGUIApplication\"].toBool(false);\n    needsMznExecutable = sj[\"needsMznExecutable\"].toBool(false);\n    needsStdlibDir = sj[\"needsStdlibDir\"].toBool(false);\n    needsPathsFile = sj[\"needsPathsFile\"].toBool(false);\n    needsSolns2Out = sj[\"needsSolns2Out\"].toBool(true);\n    executable = sj[\"executable\"].toString(\"\");\n    id = sj[\"id\"].toString();\n    version = sj[\"version\"].toString();\n    mznlib = sj[\"mznlib\"].toString();\n    mznLibVersion = sj[\"mznlibVersion\"].toInt();\n}\n\nSolver& Solver::lookup(const QString& str)\n{\n    MznProcess p;\n    try {\n        auto result = p.run({ \"--solver-json\", str });\n        auto solver_doc = QJsonDocument::fromJson(result.stdOut.toUtf8());\n        if (!solver_doc.isObject()) {\n            throw ConfigError(\"Failed to find solver \" + str);\n        }\n        Solver solver(solver_doc.object());\n        auto& solvers = MznDriver::get().solvers();\n        for (auto* s: solvers) {\n            if (solver == *s) {\n                return *s;\n            }\n        }\n        solvers << new Solver(solver);\n        return *solvers.last();\n    } catch (Exception&) {\n        throw DriverError(\"Failed to lookup solver \" + str);\n    }\n}\n\nSolver& Solver::lookup(const QString& id, const QString& version, bool strict)\n{\n    if (strict) {\n        return lookup(id + \"@\" + version);\n    }\n\n    try {\n        return lookup(id + \"@\" + version);\n    } catch (ConfigError&) {\n        return lookup(id);\n    }\n}\n\nbool Solver::operator==(const Solver& s) const {\n    if (configFile.isEmpty() && s.configFile.isEmpty()) {\n        return id == s.id && version == s.version;\n    }\n    return configFile == s.configFile;\n}\n\nbool Solver::hasAllRequiredFlags()\n{\n    for (auto& rf : requiredFlags) {\n        if (!defaultFlags.contains(rf)) {\n            return false;\n        }\n    }\n    return true;\n}\n\nSolverConfiguration::SolverConfiguration(const Solver& _solver, bool builtin) :\n    solverDefinition(_solver),\n    isBuiltin(builtin),\n    timeLimit(0),\n    printIntermediate(true),\n    numSolutions(1),\n    numOptimal(1),\n    verboseCompilation(false),\n    verboseSolving(false),\n    compilationStats(false),\n    solvingStats(false),\n    outputTiming(false),\n    outputObjective(true),\n    optimizationLevel(1),\n    numThreads(1),\n    freeSearch(false),\n    modified(false)\n{\n    solver = _solver.id + \"@\" + _solver.version;\n}\n\nSolverConfiguration SolverConfiguration::loadJSON(const QString& filename, QStringList& warnings)\n{\n    QFile file(filename);\n    QFileInfo fi(file);\n    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {\n        throw FileError(\"Failed to open file \" + filename);\n    }\n    QJsonParseError error;\n    auto json = QJsonDocument::fromJson(file.readAll(), &error);\n    file.close();\n    if (json.isNull()) {\n        QString message;\n        QTextStream ts(&message);\n        ts << \"Could not parse \"\n           << fi.fileName()\n           << \". Error at \"\n           << error.offset\n           << \": \"\n           << error.errorString()\n           << \".\";\n        throw ConfigError(message);\n    }\n    auto sc = loadJSON(json, warnings);\n    sc.paramFile = fi.canonicalFilePath();\n    return sc;\n}\n\nSolverConfiguration SolverConfiguration::loadJSON(const QJsonDocument& json, QStringList& warnings)\n{\n    auto configObject = json.object();\n    QString solverValue = configObject.value(\"--solver\").isUndefined() ? configObject.value(\"solver\").toString() : configObject.value(\"--solver\").toString();\n    Solver* solver = nullptr;\n    try {\n        solver = &Solver::lookup(solverValue);\n    } catch (Exception& e) {\n        warnings << e.message();\n        warnings << \"Using default solver instead.\";\n        solver = MznDriver::get().defaultSolver();\n    }\n\n    if (solver == nullptr) {\n        throw ConfigError(\"Failed to load fallback solver.\");\n    }\n\n    SolverConfiguration sc(*solver);\n    for (auto it = configObject.begin(); it != configObject.end(); it++) {\n        QString key = (it.key().startsWith(\"-\") || it.key().startsWith(\"_\")) ? it.key() : \"--\" + it.key();\n        if (key == \"--solver\") {\n            sc.solver = it.value().toString();\n        } else if (key == \"-t\" || key == \"--time-limit\") {\n            sc.timeLimit = it.value().toInt();\n        } else if (key == \"-a\" || key == \"--all-solutions\") {\n            sc.numSolutions = 0;\n            sc.printIntermediate = true;\n        } else if (key == \"-i\" || key == \"--intermediate\" || key == \"--intermediate-solutions\") {\n            sc.printIntermediate = it.value().toBool(true);\n        } else if (key == \"--all-satisfaction\") {\n            sc.numSolutions = 0;\n        } else if (key == \"-n\" || key == \"--num-solutions\") {\n            sc.numSolutions = it.value().toInt(1);\n        } else if (key == \"-a-o\" || key == \"--all-optimal\") {\n            sc.numOptimal = 0;\n        } else if (key == \"-n-o\" || key == \"--num-optimal\") {\n            sc.numOptimal = it.value().toInt(1);\n        } else if (key == \"--verbose-compilation\" || key == \"--verbose\" || key == \"-v\") {\n            sc.verboseCompilation = it.value().toBool();\n        } else if (key == \"--verbose-solving\" || key == \"--verbose\" || key == \"-v\") {\n            sc.verboseSolving = it.value().toBool();\n        } else if (key == \"--compiler-statistics\" || key == \"--statistics\" || key == \"-s\") {\n            sc.compilationStats = it.value().toBool();\n        } else if (key == \"--solver-statistics\" || key == \"--statistics\" || key == \"-s\") {\n            sc.solvingStats = it.value().toBool();\n        } else if (key == \"--output-time\") {\n            sc.outputTiming = it.value().toBool();\n        } else if (key == \"--output-objective\") {\n            sc.outputObjective = it.value().toBool();\n        } else if (key == \"-O0\" && it.value().toBool()) {\n            sc.optimizationLevel = 0;\n        } else if (key == \"-O1\" && it.value().toBool()) {\n            sc.optimizationLevel = 1;\n        } else if (key == \"-O2\" && it.value().toBool()) {\n            sc.optimizationLevel = 2;\n        } else if (key == \"-O3\" && it.value().toBool()) {\n            sc.optimizationLevel = 3;\n        } else if (key == \"-O4\" && it.value().toBool()) {\n            sc.optimizationLevel = 4;\n        } else if (key == \"-O5\" && it.value().toBool()) {\n            sc.optimizationLevel = 5;\n        } else if (key == \"-O\") {\n            sc.optimizationLevel = it.value().toInt();\n        } else if (key == \"-D\" || key == \"--cmdline-data\") {\n            if (it.value().isArray()) {\n                for (auto v : it.value().toArray()) {\n                    sc.additionalData.push_back(v.toString());\n                }\n            } else {\n                sc.additionalData.push_back(it.value().toString());\n            }\n        } else if (key == \"-p\" || key == \"--parallel\") {\n            sc.numThreads = it.value().toInt(1);\n        } else if (key == \"-r\" || key == \"--random-seed\") {\n            sc.randomSeed = it.value().toVariant();\n        } else if (key == \"-f\" || key == \"--free-search\") {\n            sc.freeSearch = it.value().toBool();\n        } else if (key == \"--backend-flags\" ||\n                   key == \"--fzn-flags\" || key == \"--flatzinc-flags\" ||\n                   key == \"--mzn-flags\" || key == \"--minizinc-flags\") {\n            if (it.value().isString()) {\n                parseArgList(it.value().toString(), sc.solverBackendOptions);\n            } else if (it.value().isObject()) {\n                auto object = it.value().toObject();\n                for (auto it = object.begin(); it != object.end(); it++) {\n                    sc.solverBackendOptions.insert(it.key(), it.value().toVariant());\n                }\n            }\n        } else {\n            sc.extraOptions[key] = it.value().toVariant();\n        }\n    }\n\n    for (auto f : solver->extraFlags) {\n        if (f.t == SolverFlag::T_BOOL_ONOFF && sc.extraOptions.contains(f.name)) {\n            // Convert on/off string to bool (TODO: would be nice to handle this in minizinc)\n            sc.extraOptions[f.name] = sc.extraOptions[f.name] == f.options[0];\n        }\n    }\n    return sc;\n}\n\nSolverConfiguration SolverConfiguration::loadLegacy(const QJsonDocument &json, QStringList& warnings)\n{\n    auto sco = json.object();\n\n    Solver* solver = nullptr;\n    try {\n        solver = &Solver::lookup(sco[\"id\"].toString(), sco[\"version\"].toString(), false);\n    } catch (Exception& e) {\n        warnings << e.message();\n        warnings << \"Using default solver instead.\";\n        solver = MznDriver::get().defaultSolver();\n    }\n\n    if (solver == nullptr) {\n        throw ConfigError(\"Failed to load fallback solver.\");\n    }\n\n    SolverConfiguration newSc(*solver);\n//    if (sco[\"name\"].isString()) {\n//        newSc.name = sco[\"name\"].toString();\n//    }\n    if (sco[\"timeLimit\"].isDouble()) {\n        newSc.timeLimit = sco[\"timeLimit\"].toInt();\n    }\n//    if (sco[\"defaultBehavior\"].isBool()) {\n//        newSc.defaultBehaviour = sco[\"defaultBehavior\"].toBool();\n//    }\n    if (sco[\"printIntermediate\"].isBool()) {\n        newSc.printIntermediate = sco[\"printIntermediate\"].toBool();\n    }\n    if (sco[\"stopAfter\"].isDouble()) {\n        newSc.numSolutions = sco[\"stopAfter\"].toInt();\n    }\n    if (sco[\"verboseFlattening\"].isBool()) {\n        newSc.verboseCompilation = sco[\"verboseFlattening\"].toBool();\n    }\n    if (sco[\"flatteningStats\"].isBool()) {\n        newSc.compilationStats = sco[\"flatteningStats\"].toBool();\n    }\n    if (sco[\"optimizationLevel\"].isDouble()) {\n        newSc.optimizationLevel = sco[\"optimizationLevel\"].toInt();\n    }\n    if (sco[\"additionalData\"].isString() && !sco[\"additionalData\"].toString().isEmpty()) {\n        newSc.additionalData << sco[\"additionalData\"].toString();\n    }\n    if (sco[\"additionalCompilerCommandline\"].isString() && !sco[\"additionalCompilerCommandline\"].toString().isEmpty()) {\n       parseArgList(sco[\"additionalCompilerCommandline\"].toString(), newSc.extraOptions);\n    }\n    if (sco[\"nThreads\"].isDouble()) {\n        newSc.numThreads = sco[\"nThreads\"].toInt();\n    }\n    if (sco[\"randomSeed\"].isDouble()) {\n        newSc.randomSeed = sco[\"randomSeed\"].toDouble();\n    }\n    if (sco[\"solverFlags\"].isString() && !sco[\"solverFlags\"].toString().isEmpty()) {\n        parseArgList(sco[\"solverFlags\"].toString(), newSc.solverBackendOptions);\n    }\n    if (sco[\"freeSearch\"].isBool()) {\n        newSc.freeSearch = sco[\"freeSearch\"].toBool();\n    }\n    if (sco[\"verboseSolving\"].isBool()) {\n        newSc.verboseSolving = sco[\"verboseSolving\"].toBool();\n    }\n    if (sco[\"outputTiming\"].isBool()) {\n        newSc.outputTiming = sco[\"outputTiming\"].toBool();\n    }\n    if (sco[\"solvingStats\"].isBool()) {\n        newSc.solvingStats = sco[\"solvingStats\"].toBool();\n    }\n    if (sco[\"extraOptions\"].isObject() && sco[\"useExtraOptions\"].toBool()) {\n        QJsonObject extraOptions = sco[\"extraOptions\"].toObject();\n\n        for (auto& f : solver->extraFlags) {\n            if (extraOptions.contains(f.name) && f.def.toString() != extraOptions[f.name].toString()) {\n                newSc.extraOptions[f.name] = extraOptions[f.name].toString();\n                extraOptions.remove(f.name);\n            }\n        }\n\n        for (auto it = extraOptions.begin(); it != extraOptions.end(); it++) {\n            // Unrecognised, but we shouldn't ignore them\n            newSc.extraOptions[it.key()] = it.value().toVariant();\n        }\n    }\n    return newSc;\n}\n\nQString SolverConfiguration::name() const\n{\n    QStringList parts;\n\n    if (isBuiltin) {\n        parts << solverDefinition.name\n              << solverDefinition.version;\n    } else {\n        if (paramFile.isEmpty()) {\n            parts << \"Unsaved Configuration\";\n        } else {\n            parts << QFileInfo(paramFile).completeBaseName();\n        }\n        parts << \"(\" + solverDefinition.name + \" \" + solverDefinition.version + \")\";\n    }\n\n    if (modified) {\n        parts << \"*\";\n    }\n\n    return parts.join(\" \");\n}\n\nQByteArray SolverConfiguration::toJSON(void) const\n{\n    QJsonDocument jsonDoc;\n    jsonDoc.setObject(toJSONObject());\n    return jsonDoc.toJson();\n}\n\nQJsonObject SolverConfiguration::toJSONObject(void) const\n{\n    QJsonObject config;\n    config[\"solver\"] = solver;\n    if (timeLimit > 0) {\n        config[\"time-limit\"] = timeLimit;\n    }\n    if ((supports(\"-a\") || supports(\"-i\"))) {\n        config[\"intermediate-solutions\"] = printIntermediate;\n    }\n    if (numSolutions > 1 && supports(\"-n\")) {\n        config[\"num-solutions\"] = numSolutions;\n    }\n    if (numSolutions == 0 && supports(\"-a\")) {\n        config[\"all-satisfaction\"] = true;\n    }\n    if (numOptimal > 1 && supports(\"-n-o\")) {\n        config[\"num-optimal\"] = numOptimal;\n    }\n    if (numOptimal == 0 && supports(\"-a-o\")) {\n        config[\"all-optimal\"] = true;\n    }\n    if (verboseCompilation) {\n        config[\"verbose-compilation\"] = verboseCompilation;\n    }\n    if (verboseSolving && supports(\"-v\")) {\n        config[\"verbose-solving\"] = verboseSolving;\n    }\n    if (compilationStats) {\n        config[\"compiler-statistics\"] = compilationStats;\n    }\n    if (solvingStats && supports(\"-s\")) {\n        config[\"solver-statistics\"] = solvingStats;\n    }\n    if (outputTiming) {\n        config[\"output-time\"] = outputTiming;\n    }\n    if (outputObjective && (solverDefinition.inputType != Solver::I_MZN || supports(\"--output-objective\"))) {\n        config[\"output-objective\"] = true;\n    }\n    if (optimizationLevel != 1) {\n        config[\"-O\"] = optimizationLevel;\n    }\n    QJsonArray arr;\n    for (auto& d : additionalData) {\n        arr.push_back(d);\n    }\n    if (arr.size() > 0) {\n        config[\"cmdline-data\"] = arr;\n    }\n    if (numThreads > 1 && supports(\"-p\")) {\n        config[\"parallel\"] = numThreads;\n    }\n    if (!randomSeed.isNull() && supports(\"-r\")) {\n        config[\"random-seed\"] = QJsonValue::fromVariant(randomSeed);\n    }\n    if (freeSearch && supports(\"-f\")) {\n        config[\"free-search\"] = freeSearch;\n    }\n    for (auto it = extraOptions.begin(); it != extraOptions.end(); it++) {\n        config[it.key()] = QJsonValue::fromVariant(it.value());\n    }\n    if (!solverBackendOptions.empty()) {\n        config[\"backend-flags\"] = QJsonObject::fromVariantMap(solverBackendOptions);\n    }\n    for (auto f : solverDefinition.extraFlags) {\n        if (f.t == SolverFlag::T_BOOL_ONOFF && extraOptions.contains(f.name)) {\n            // Convert to on/off string instead of bool (TODO: would be nice to handle this in minizinc)\n            config[f.name] = extraOptions[f.name].toBool() ? f.options[0] : f.options[1];\n        }\n    }\n    return config;\n}\n\nbool SolverConfiguration::syncedOptionsMatch(const SolverConfiguration& sc) const\n{\n    if (isBuiltin || sc.isBuiltin) {\n        // Built-in configs always can have their options overidden\n        return true;\n    }\n\n    if (timeLimit != sc.timeLimit) {\n        return false;\n    }\n\n    if ((supports(\"-a\") || supports(\"-i\")) &&\n            (sc.supports(\"-a\") || sc.supports(\"-i\")) &&\n            printIntermediate != sc.printIntermediate) {\n        return false;\n    }\n\n    if (supports(\"-n\") && sc.supports(\"-n\") &&\n            (numSolutions > 0 || sc.numSolutions > 0) &&\n            numSolutions != sc.numSolutions) {\n        return false;\n    }\n\n    if (supports(\"-a\") && sc.supports(\"-a\") &&\n            (numSolutions == 0 || sc.numSolutions == 0) &&\n            numSolutions != sc.numSolutions) {\n        return false;\n    }\n\n    if (supports(\"-n-o\") && sc.supports(\"-n-o\") &&\n            (numOptimal > 0 || sc.numOptimal > 0) &&\n            numOptimal != sc.numOptimal) {\n        return false;\n    }\n\n    if (supports(\"-a-o\") && sc.supports(\"-a-o\") &&\n            (numOptimal == 0 || sc.numOptimal == 0) &&\n            numOptimal != sc.numOptimal) {\n        return false;\n    }\n\n    if (verboseCompilation != sc.verboseCompilation) {\n        return false;\n    }\n\n    if (supports(\"-v\") && sc.supports(\"-v\") &&\n            verboseSolving != sc.verboseSolving) {\n        return false;\n    }\n\n    if (compilationStats != sc.compilationStats) {\n        return false;\n    }\n\n    if (supports(\"-s\") && sc.supports(\"-s\") &&\n            solvingStats != sc.solvingStats) {\n        return false;\n    }\n\n    if (outputTiming != sc.outputTiming) {\n        return false;\n    }\n\n    if ((solverDefinition.inputType != Solver::I_MZN || supports(\"--output-objective\")) &&\n            (sc.solverDefinition.inputType != Solver::I_MZN || sc.supports(\"--output-objective\")) &&\n            outputObjective != sc.outputObjective) {\n        return false;\n    }\n\n    return true;\n}\n\nbool SolverConfiguration::supports(const QString &flag) const\n{\n    return solverDefinition.stdFlags.contains(flag);\n}\n\nbool SolverConfiguration::operator==(const SolverConfiguration& sc) const\n{\n    return solver == sc.solver &&\n            solverDefinition.id == sc.solverDefinition.id &&\n            solverDefinition.version == sc.solverDefinition.version &&\n            isBuiltin == sc.isBuiltin &&\n            timeLimit == sc.timeLimit &&\n            printIntermediate == sc.printIntermediate &&\n            numSolutions == sc.numSolutions &&\n            verboseCompilation == sc.verboseCompilation &&\n            verboseSolving == sc.verboseSolving &&\n            compilationStats == sc.compilationStats &&\n            solvingStats == sc.solvingStats &&\n            outputTiming == sc.outputTiming &&\n            outputObjective == sc.outputObjective &&\n            optimizationLevel == sc.optimizationLevel &&\n            additionalData == sc.additionalData &&\n            numThreads == sc.numThreads &&\n            randomSeed == sc.randomSeed &&\n            freeSearch == sc.freeSearch &&\n            extraOptions == sc.extraOptions;\n}\n"
  },
  {
    "path": "MiniZincIDE/solver.h",
    "content": "#ifndef SOLVER_H\n#define SOLVER_H\n\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QString>\n#include <QVariant>\n#include <QVector>\n\nstruct SolverFlag {\n    QString name;\n    QString description;\n    enum { T_INT, T_INT_RANGE, T_BOOL, T_BOOL_ONOFF, T_FLOAT, T_FLOAT_RANGE, T_STRING, T_OPT, T_SOLVER } t;\n    double min;\n    double max;\n    qlonglong min_ll;\n    qlonglong max_ll;\n    QStringList options;\n    QVariant def;\n};\n\nQ_DECLARE_METATYPE(SolverFlag)\n\nstruct Solver {\n    enum SolverInputType {\n        I_FZN,\n        I_MZN,\n        I_NL,\n        I_JSON,\n        I_UNKNOWN\n    };\n\n    QString configFile;\n    QString id;\n    QString name;\n    QString executable;\n    QString executable_resolved;\n    QString mznlib;\n    QString mznlib_resolved;\n    QString version;\n    int mznLibVersion;\n    QString description;\n    QString contact;\n    QString website;\n    SolverInputType inputType;\n    bool needsSolns2Out;\n    bool isGUIApplication;\n    bool needsMznExecutable;\n    bool needsStdlibDir;\n    bool needsPathsFile;\n    QStringList stdFlags;\n    QList<SolverFlag> extraFlags;\n    QStringList requiredFlags;\n    QStringList defaultFlags;\n    QStringList tags;\n    QJsonObject json;\n    bool isDefaultSolver;\n    Solver(void) {}\n\n    Solver(const QJsonObject& json);\n    static Solver& lookup(const QString& str);\n    static Solver& lookup(const QString& id, const QString& version, bool strict = true);\n\n    bool hasAllRequiredFlags(void);\n\n    bool operator==(const Solver&) const;\n};\n\nclass SolverConfiguration {\npublic:\n    SolverConfiguration(const Solver& solver, bool builtin = false);\n\n    static SolverConfiguration loadJSON(const QString& filename, QStringList& warnings);\n    static SolverConfiguration loadJSON(const QJsonDocument& json, QStringList& warnings);\n    static SolverConfiguration loadLegacy(const QJsonDocument& json, QStringList& warnings);\n\n    QString solver;\n    const Solver& solverDefinition;\n    QString paramFile;\n    bool isBuiltin;\n    int timeLimit;\n    bool printIntermediate;\n    int numSolutions;\n    int numOptimal;\n    bool verboseCompilation;\n    bool verboseSolving;\n    bool compilationStats;\n    bool solvingStats;\n    bool outputTiming;\n    bool outputObjective;\n    int optimizationLevel;\n    QStringList additionalData;\n    int numThreads;\n    QVariant randomSeed;\n    bool freeSearch;\n    QVariantMap extraOptions;\n    QVariantMap solverBackendOptions;\n    bool modified;\n\n    ///\n    /// \\brief Gets the name of this solver config\n    /// \\return The name of the config\n    ///\n    QString name(void) const;\n\n    ///\n    /// \\brief Give the JSON representation of this SolverConfiguration\n    /// \\return This solver config in JSON format\n    ///\n    QByteArray toJSON(void) const;\n\n    ///\n    /// \\brief Give the JSON representation of this SolverConfiguration\n    /// \\return This solver config as a JSON object\n    ///\n    QJsonObject toJSONObject(void) const;\n\n    ///\n    /// \\brief Determines if these two solver configs have compatible basic options\n    /// \\param sc Another solver configuration\n    /// \\return Whether the basic options match\n    ///\n    bool syncedOptionsMatch(const SolverConfiguration& sc) const;\n\n    ///\n    /// \\brief Returns whether the given stdFlag is supported by the solver for this config\n    /// \\param flag The standard flag\n    /// \\return True if the flag is supported and false otherwise\n    ///\n    bool supports(const QString& flag) const;\n\n    bool operator==(const SolverConfiguration& sc) const;\n    bool operator!=(const SolverConfiguration& sc) const { return !(*this == sc); }\n};\n\n\n#endif // SOLVER_H\n"
  },
  {
    "path": "MiniZincIDE/theme.cpp",
    "content": "#include \"theme.h\"\r\n\r\nQString Theme::styleSheet(bool darkMode) const\r\n{\r\n    auto style_sheet = QString(\"background-color: %1;\"\r\n                               \"color: %2;\")\r\n                           .arg(backgroundColor.get(darkMode).name(QColor::HexArgb))\r\n                           .arg(textColor.get(darkMode).name(QColor::HexArgb));\r\n\r\n    if (!isSystemTheme) {\r\n        // Only change highlight colour for non-system themes\r\n        // Many platforms have settings/accessibility options for this, so we should probably follow it by default\r\n        style_sheet += QString(\r\n                           \"selection-background-color: %1;\"\r\n                           \"selection-color: %2;\"\r\n                           )\r\n                           .arg(textHighlightColor.get(darkMode).name(QColor::HexArgb))\r\n                           .arg(textColor.get(darkMode).name(QColor::HexArgb));\r\n    }\r\n\r\n    return style_sheet;\r\n}\r\n\r\n\r\n\r\nThemeManager::ThemeManager(QObject* parent) : QObject(parent) {\r\n    _themes.push_back({\"Default\",\r\n                       ThemeColor(Qt::black, Qt::white), // text\r\n                       ThemeColor(Qt::darkGreen, QColor(0xbb86fc)), //keywords\r\n                       ThemeColor(Qt::blue, QColor(0x13C4F5)), // function\r\n                       ThemeColor(Qt::darkRed, QColor(0xF29F05)), // string\r\n                       ThemeColor(Qt::red, QColor(0x616161)), //comment\r\n                       ThemeColor(Qt::white,QColor(0x222222)), //background\r\n                       ThemeColor(QColor(0xF0F0F0),QColor(0x292929)), // line highlight\r\n                       ThemeColor(QColor(0xE7E3FF),QColor(0x004161)), // text highlight\r\n                       ThemeColor(Qt::red), // error color\r\n                       ThemeColor(QColor(0xd1ab13), Qt::yellow), // warning color\r\n                       true\r\n    });\r\n    _themes.push_back({\"Blueberry\",\r\n                       ThemeColor(QColor(0x0B1224), QColor(0x8EBBED)), // text\r\n                       ThemeColor(QColor(0x3354A7), QColor(0x1172A6)), //keywords\r\n                       ThemeColor(QColor(0x9147A6), QColor(0xD676CC)), // function\r\n                       ThemeColor(QColor(0x132F6B), QColor(0xA192E8)), // string\r\n                       ThemeColor(QColor(0x83B9F5), QColor(0x225773)), //comment\r\n                       ThemeColor(QColor(0xE9ECFF),QColor(0x001926)), //background\r\n                       ThemeColor(QColor(0xE7E3FF),QColor(0x001D2B)), // line highlight\r\n                       ThemeColor(QColor(0xBEBDFC),QColor(0x005580)), // text highlight\r\n                       ThemeColor(Qt::red), // error color\r\n                       ThemeColor(Qt::yellow) // warning color\r\n    });\r\n    _themes.push_back({\"Mango\",\r\n                       ThemeColor(QColor(0x375327), QColor(0xF2DC6D)), // text\r\n                       ThemeColor(QColor(0xF27B35), QColor(0xF2385A)), //keywords\r\n                       ThemeColor(QColor(0xF2385A), QColor(0xF29F05)), // function\r\n                       ThemeColor(QColor(0x51C0BF), QColor(0xF48985)), // string\r\n                       ThemeColor(QColor(0x6A8F2F), QColor(0xF4503F)), //comment\r\n                       ThemeColor(QColor(0xEFDC9B),QColor(0x030500)), //background\r\n                       ThemeColor(QColor(0xD9C78D),QColor(0x070D00)), // line highlight\r\n                       ThemeColor(QColor(0xCFB359),QColor(0x6B6033)), // text highlight\r\n                       ThemeColor(Qt::red, QColor(0x51C0BF)), // error color\r\n                       ThemeColor(Qt::yellow) // warning color\r\n    });\r\n}\r\n"
  },
  {
    "path": "MiniZincIDE/theme.h",
    "content": "#ifndef THEME_H\r\n#define THEME_H\r\n\r\n#include <QObject>\r\n#include <QColor>\r\n#include <vector>\r\n\r\nstruct ThemeColor\r\n{\r\n    QColor light;\r\n    QColor dark;\r\n\r\n    ThemeColor(QColor lightAndDark) {\r\n        light = lightAndDark;\r\n        dark = lightAndDark;\r\n    }\r\n\r\n    ThemeColor(QColor l, QColor d) {\r\n        light = l;\r\n        dark = d;\r\n    }\r\n\r\n    QColor get(bool darkMode) const {\r\n        return darkMode ? dark : light;\r\n    }\r\n};\r\n\r\nstruct Theme\r\n{\r\n    QString name;\r\n\r\n    ThemeColor textColor, // Color of normal text\r\n        keywordColor, // Color of MiniZinc keywords such as \"in\" \"where\" \"constraints\"\r\n        functionColor, // Color of words that are followed by a function call, such as \"forall()\"\r\n        stringColor, // Color of strings\r\n        commentColor, // Color of comments %\r\n        backgroundColor, // Color of background\r\n        errorColor, //Color of error messages\r\n        warningColor, // Color of warning messages\r\n        lineHighlightColor,  // Color of highlighted line\r\n        foregroundActiveColor, // color of active line number\r\n        foregroundInactiveColor, // color of inactive line number\r\n        textHighlightColor,      // color of highlighted text\r\n        bracketsMatchColor, // Color of matching brackets\r\n        bracketsNoMatchColor, //Colors of dangling brackets\r\n        lineNumberbackground // Color of line numbers background\r\n        ;\r\n\r\n    bool isSystemTheme = false;\r\n\r\n    Theme(const QString& _name,\r\n        ThemeColor _textColor,\r\n          ThemeColor _keywordColor,\r\n          ThemeColor _functionColor,\r\n          ThemeColor _stringColor,\r\n          ThemeColor _commentColor,\r\n          ThemeColor _backgroundColor,\r\n          ThemeColor _lineHighlightColor,\r\n          ThemeColor _textHighlightColor,\r\n          ThemeColor _errorColor,\r\n          ThemeColor _warningColor,\r\n          bool systemTheme = false) :\r\n        name(_name),\r\n        textColor(_textColor),\r\n        keywordColor(_keywordColor),\r\n        functionColor(_functionColor),\r\n        stringColor( _stringColor),\r\n        commentColor(_commentColor),\r\n        backgroundColor(_backgroundColor),\r\n        errorColor(_errorColor),\r\n        warningColor(_warningColor),\r\n        lineHighlightColor(_lineHighlightColor),\r\n        foregroundActiveColor(textColor),\r\n        foregroundInactiveColor(textColor.light.lighter(), textColor.dark.darker()),\r\n        textHighlightColor(_textHighlightColor),\r\n        bracketsMatchColor(keywordColor.light.lighter(200), keywordColor.dark.darker(150)),\r\n        bracketsNoMatchColor(errorColor.light, errorColor.dark),\r\n        lineNumberbackground(backgroundColor.light.darker(110), backgroundColor.dark.lighter(150)),\r\n        isSystemTheme(systemTheme) {}\r\n\r\n    QString styleSheet(bool darkMode) const;\r\n};\r\n\r\n\r\nclass ThemeManager : public QObject\r\n{\r\n    Q_OBJECT\r\npublic:\r\n    explicit ThemeManager(QObject *parent = nullptr);\r\n\r\n    const std::vector<Theme>& themes() const\r\n    {\r\n        return _themes;\r\n    }\r\n    const Theme& current() const\r\n    {\r\n        return _themes[_current];\r\n    }\r\n    void current(size_t index)\r\n    {\r\n        if (index < 0 || index >= _themes.size()) {\r\n            _current = 0;\r\n        } else {\r\n            _current = index;\r\n        }\r\n    }\r\n    const Theme& get(size_t index) const\r\n    {\r\n        if (index < 0 || index >= _themes.size()) {\r\n            return _themes[0];\r\n        }\r\n        return _themes[index];\r\n    }\r\nprivate:\r\n    size_t _current = 0;\r\n    std::vector<Theme> _themes;\r\n};\r\n\r\n#endif // THEME_H\r\n"
  },
  {
    "path": "MiniZincIDE.pro",
    "content": "TEMPLATE = subdirs\n\nSUBDIRS = \\\n    MiniZincIDE \\\n    tests\n\nlibminizinc {\n  SUBDIRS += libminizinc\n  MiniZincIDE.depends = libminizinc\n}\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://www.minizinc.org/\">\n    <img src=\"https://www.minizinc.org/MiniZn_logo.png\" alt=\"Logo\" width=\"80\" height=\"80\">\n  </a>\n\n  <h3 align=\"center\">MiniZinc IDE</h3>\n\n  <p align=\"center\">\n    Integrated development environment for the high-level constraint modelling language\n    <a href=\"https://www.minizinc.org\">MiniZinc</a>.\n    <br />\n    <br />\n    <a href=\"https://github.com/MiniZinc/libminizinc\">MiniZinc Compiler</a>\n    ·\n    <a href=\"https://www.minizinc.org/doc-latest/en/minizinc_ide.html\">Documentation</a>\n    ·\n    <a href=\"https://github.com/MiniZinc/MiniZincIDE/issues\">Report Bug</a>\n  </p>\n\n  <p align=\"center\">\n    <img src=\"https://docs.minizinc.dev/en/stable/images/mzn-ide-playground-data.jpg\" alt=\"The MiniZinc IDE\" style=\"max-width:768px;width:100%\">\n  </p>\n</p>\n\n## Getting started\n\nPackages for Linux, macOS and Windows can be found in the [releases](https://github.com/MiniZinc/MiniZincIDE/releases) or from [the MiniZinc\nwebsite](http://www.minizinc.org/software.html).\n\nThese packages contain the MiniZinc IDE, the MiniZinc compiler toolchain, as well as several solvers.\n\nFor more detailed installation instructions, see the [documentation](https://www.minizinc.org/doc-latest/en/installation.html).\n\n## Building from source\n\nThe MiniZinc IDE is a [Qt](https://www.qt.io/) project and requires:\n- A recent C++ compiler\n- [Qt](https://www.qt.io/) (we target the latest LTS Qt version)\n  - We also require the [Qt WebSockets](https://doc.qt.io/qt-6/qtwebsockets-index.html) module\n- [Make](https://www.gnu.org/software/make/)\n\nEnsure you clone the repository including submodules:\n\n```sh\ngit clone --recurse-submodules https://github.com/MiniZinc/MiniZincIDE\ncd MiniZincIDE\n```\n\nThen either build open the project (`MiniZincIDE.pro`) in Qt Creator and build, or from the command line:\n\n```sh\nmkdir build\ncd build\nqmake -makefile ../MiniZincIDE/MiniZincIDE.pro\nmake -j4\n```\n\nSee the [MiniZinc compiler project](https://github.com/MiniZinc/libminizinc) for instructions on how to build the compiler toolchain.\n\n### Running tests\n\nThe IDE has a test suite which can be compiled and run with:\n\n```sh\nmkdir test\ncd test\nqmake -makefile ../tests/tests.pro\nmake -j4\nmake check\n```\n"
  },
  {
    "path": "TODO.txt",
    "content": "Features that would be nice to have:\n * Show types\n * Integrated global constraint library browser\n * Save all data files before running\n * Auto indent\n * Column selection\n\nDone\n * Focus\n * Make dzn selection in Configuration tab work\n * Clear output window\n * Font size\n * Tabs-to-spaces (without messing up the undo buffer)\n * About dialog\n * Click on error location to jump to position in editor\n * Configuration dialog for adding other solvers\n * Global solver configurations stored somewhere system wide\n * Find/replace\n * Font selection\n * Go to line\n * Configure paths\n * Time outs\n * Mac application icon\n * Indent/outdent selection\n * Report solve time\n * Basic help\n * Windows application icon\n * Multiple windows (\"projects\")\n * Saving/loading projects (i.e. the contents of the Configuration tab + all \n   open files)\n * Do not load really large files (show button instead to load on demand)\n * Save before running\n * Remove LGPL find/replace dialog\n * Correct copyright headers\n * Handle duplicate open files\n * Save all\n * Change font also for output\n * Correct handling of open constraint graph windows\n * Packaging\n * Run fzn files\n * Use mzn2fzn and implement the solutions processing internally\n * Comment/uncomment selection\n * Detach solver (to be able to run Gecode w/ Gist)\n * Dialog for missing model parameters\n * Detect when files change on disk (auto reload + dialog if changed)\n * Colour selection\n "
  },
  {
    "path": "cp-profiler/README.md",
    "content": "# CP-Profiler\n\nCP-Profiler provides search tree visualisations for executions of constraint programming solvers.\n\n## Table of Contents\n\n- [Building](#building)\n- [Usage](#usage)\n\n### Building\n\nDependencies:\n\n- Qt5.x\n\n```\n    git submodule update --init\n    mkdir build && cd build\n    qmake .. && make\n```\n\n### Usage\n\n1. Starting CP-Profiler\n\n    `cp-profiler.app/Contents/MacOS/cp-profiler` (Mac)\n\nThis starts a local TCP server listening on one of the available ports (6565 by default).\n\n2. Executing a supported solver\n\nThe solver must implement the profiling protocol (TODO). Integration libraries are available if you wish to extend your solver to work with CP-Profiler.\n\n\n### The Protocol (high level)\n\nThe following describes the protocol that a solver must implement to communicate with the profiler.\n\nThe protocol distinguishes between the following types of messages: **`Start`**, **`Restart`**, **`Done`**, and **`Node`**.\n\n\n\nThe **`Start`** message is sent at the beginning of the execution (**TODO**: multithreaded search). The message has two optional (?) parameters:\n\n- `Name`: execution's descriptor to be presented to the user (e.g. the model's name)\n- `Execution ID`: globally unique identifier used to distinguish between different executions.\n\n**TODO**: any other parameters?\n\nThe **`Restart`** message is sent whenever a solver performs a restart in case of a restart-based search.\n\nThe **`Done`** message is sent to the profiler at the end of the execution to indicate that no further nodes should be expected.\n\nThe **`Node`** message is sent whenever a new node is explored by the solver and contains information necessary for reconstructing the search tree. The required parameters are:\n\n- `Node ID`: unique node identifier in the execution.\n- `Parent ID`: the identifier (`Node ID`) of the parent node. A root node can have an identifier of `-1`. (**TODO**: needs checking)\n- `Alternative`: the node's position relative to its siblings; for the left-most child it is `0`, for the second left-most it is `1` etc.\n- `Number of Children`: the number of children nodes. If not known, can be set to `0` and the node will be extended with extra children later on if necessary. It is, however, advisable to specify the number of children the profiler should expect (for example, the yet to arrive nodes can be visualised to give a better idea about the search).\n- `Status`: allows to distinguish between different types of nodes. Supported values are:\n     - *BRANCH*: internal node in the tree;\n     - *SOLUTION*: leaf node representing a solution;\n     - *FAILURE*: leaf node representing a failure;\n     - *SKIPPED*: leaf node representing unexplored search space due to backjumping.\n\n**Example**. The following sequence of nodes (excluding the `Start` and `Done` messages) produces the simple tree below:\n\n|  `Label` | `Node ID` | `Parent ID` | `Alternative` | `Number of Children` | `Status` |\n|:--------:|:---------:|:-----------:|:-------------:|:--------------------:|:--------:|\n|   Root   |     0     |      -1     |       -1      |           2          |  BRANCH  |\n|  Failure |     1     |      0      |       0       |           0          |  FAILED  |\n| Solution |     2     |      0      |       1       |           0          |  SOLVED  |\n\n![A simple search tree](todo)\n\n### The Protocol (low level)\n\nEach message starts with a four-byte integer encoding the size of the remainder of the message in bytes. This is followed by a single byte encoding the type of the message. The corresponding values are: **`Node`**: `0`, **`Done`**: `1`, **`Start`**: `2`, **`Restart`**: `3`.\n\n#### `Node` message\n\nIn case the message is of the type **`Node`**, the following fields are added in order: `id`, `pid`, `alt`, `children` and `status`.\n\nNode identifiers `id` and `pid` are represented using three four-byte integers: first identifies the identifier of the node within a thread, the second — the identifier of the restart (in a restart-based search), and the third — the identifier of the thread.\nThe `alt` and `children` fields are represented by a single four byte integer each.\nThe `status` field is represented by a single byte; its possible values are: *SOLVED*: 0, *FAILED*: 1, *BRANCH*: 2, *SKIPPED*: 3.\nAll multi-byte integer values are encoded using the *two's compliment* notation in the *big-endian order*.\n\nAdditionally, each node message can contain the following optional fields:\n- `label`: branching decision (or any arbitrary string to be drawn together with the node);\n- `nogood`: string representation of a newly generated nogood in a learning solver;\n- `info`: arbitrary information about the node (*TODO*).\n\nField identifiers and their sizes in bytes:\n\n| field name | field id | size (bytes) |\n|:----------:|:--------:|:------------:|\n|    `id`    |    n/a   |      12      |\n|    `pid`   |    n/a   |      12      |\n|    `alt`   |    n/a   |       4      |\n| `children` |    n/a   |       4      |\n|  `status`  |    n/a   |       1      |\n|   `label`  |     0    |      any     |\n|  `nogood`  |     1    |      any     |\n|   `info`   |     2    |      any     |\n\n**Example**. The following is a possible correspondence between a solver and the profiler that generates the simple tree above.The order in which different fields arrive is shown from top to bottom (rows are numbered for convenience).\n\n*Message 1:*\n\n| Row | Bytes                                                                              | Interpretation                |\n|-----|------------------------------------------------------------------------------------|-------------------------------|\n| 1   | `00 00 00 21`                                                                      | message size (33)             |\n| 2   | `02`                                                                               | message type (*START*)        |\n| 3   | `02`                                                                               | field (*info*)                |\n| 4   | `00 00 00 1B`                                                                      | string size (27)              |\n| 5   | `7b 22 6e 61 6d 65 22 3a 20 22 6d 69 6e 69 6d 61 6c 20 65 78 61 6d 70 6c 65 22 7d` | '{\"name\": \"minimal example\"}' |\n\n*Message 2:*\n\n| Row | Bytes         | Interpretation          |\n|-----|---------------|-------------------------|\n| 6   | `00 00 00 2B` | message size (43)       |\n| 7   | `00`          | message type (**NODE**) |\n| 8   | `00 00 00 00` | node id (0)             |\n| 9   | `FF FF FF FF` | node restart id (-1)    |\n| 10  | `FF FF FF FF` | node thread id (-1)     |\n| 11  | `FF FF FF FF` | parent id (-1)          |\n| 12  | `FF FF FF FF` | parent restart id (-1)  |\n| 13  | `FF FF FF FF` | parent thread id (-1)   |\n| 14  | `FF FF FF FF` | alternative (-1)        |\n| 15  | `00 00 00 02` | children (2)            |\n| 16  | `02`          | status (*BRANCH*)       |\n| 17  | `00`          | field (label)           |\n| 18  | `00 00 00 04` | string size (4)         |\n| 19  | `52 6f 6f 74` | 'Root'                  |\n\n*Message 3:*\n\n| Row | Bytes         | Interpretation          |\n|-----|---------------|-------------------------|\n| 20  | `00 00 00 01` | message size (1)        |\n| 21  | `01`          | message type (**DONE**) |\n\n\n### Tree Visualisations\n\n#### Traditional Tree Visualisation\n\nWhen a new execution is connected to the profiler it will be added to the list of executions displayed at the top of the main window. For example, in the image below execution *golomb6a.fzn* is shown to be added to the profiler.\nTo display the execution, select its name from the list and click the *Show Tree* button. Note that the solver can still be running the execution, in which case the profiler will draw the search tree in real time.\n\n![Profiler Conductor](https://bitbucket.org/Msgmaxim/cp-profiler2/raw/d2aad1af0805a47f89459f771f70516f29886a09/docs/images/doc_conductor1.png)\n\nThe image below shows an example of a traditional (node-link) visualisation of the search tree. Different types of nodes are shown differently: branch (internal) nodes are shown as blue circles; nodes representing failures are shown as red squares; solution nodes are shown as green diamonds.\n\nNote that the root of the tree is shown in gold colour indicating the currently selected node. Arrow keys on the keyboard allow the user to navigate the tree by changing which node is selected. `Down` navigates to the first child of the current node, `Shift+Down` — to its last child, `Up` — to its the parent, `Left` — to its next sibling on the left, `Right` — to its next sibling on the right. \nAdditionally, pressing `R` will navigate to the root of the tree.\nThe same actions are available under the **`Navigation`** menu.\n\n\n![Traditional Visualisation Interface](https://bytebucket.org/Msgmaxim/cp-profiler2/raw/d2aad1af0805a47f89459f771f70516f29886a09/docs/images/doc_traditional_interface.png)\n\nIf a subtree contains no solutions, it can be collapsed into a special single node displayed as a large red triangle. By default, the tree will be collapse failed subtrees automatically during its construction as a way to deal with large trees. The image below shows the same search tree as above, but with all failed subtrees collapsed.\n\n![Collapsed Failed Subtrees](https://bitbucket.org/Msgmaxim/cp-profiler2/raw/76bb21242ad5427b10b84f7c4cb60f9b557490a0/docs/images/doc_traditional_collapsed.png)\n\nThis view of the tree allows the user to show additional information for every node — its label, which usually represents the branching decision made by the solver to move from the parent node to its child. Pressing `L` on the keyboard will display labels for all descendants of the current node. `Shift+L` will display labels on the path to the current node.\nFor example, the visualisation above shows branching decisions on the path from the first solution (shown as the new current node) to the root of the tree.\n\nStatus bar at the bottom of the window displays node statistics: the depth of the tree and the counts of different types of nodes.\nThe scroll bar on the right lets the user to zoom in/out on the visualisation;\n\n#### Similar Subtree Analysis\n\nThis analysis allows users to find similarities within a single search tree.\n\nIt can be initiated by selecting **`Similar Subtrees`** from the menu **`Analyses`** (shortcut: `Shift+S`).\nThe image below shows the result of running the analysis on the search tree above.\nHorizontal bars on the left lists all similarities (patterns) found in the tree.\nHere, the lengths of the bars indicate are configured to indicate how many subtrees belong to a particular pattern (*count*).\nAdditionally the bars are sorted so that the patterns with subtrees of larger *size* appear at the top.\nAnother property of a pattern is its *height*, which indicates the height/depth of subtrees that the pattern represent.\n\nNote that the second from the top pattern is currently selected (shown with orange outline).\nThe view on the right shows a \"preview\" (traditional visualisation) of one of the subtrees representing the selected pattern.\nThe two rows below the show the result of computing the difference in labels on the path from the root to two of the subtrees representing the pattern (in this case it is the first two subtrees encountered when the tree is traversed in the depth-first-search order).\n\n![Similar Subtrees Summary](https://bitbucket.org/Msgmaxim/cp-profiler2/raw/dec396e2537294be8cdf18b9594441ac710e937b/docs/images/doc_ss_analysis_hist.png)\n\nChanging the configuration menu at the bottom of the window, the user can filter the list of patterns based on their *count* and *height* values.\nThey way the length of horizontal bars is determined and the sorting criteria can also be specified there.\n\nWhenever a pattern on the left hand side is selected, the corresponding subtrees will be highlighted on the traditional visualisation by drawing their .\nAdditionally, if the option *Hide not selected* is selected (top of the window), the subtrees of t\n\n![Similar Subtrees Highlighted](https://bitbucket.org/Msgmaxim/cp-profiler2/raw/dec396e2537294be8cdf18b9594441ac710e937b/docs/images/doc_ss_analysis.png)\n\n**Elimination of subsumed patterns**\n\nA pattern `P` is said to be subsumed by one or more patterns if subtrees of those patterns\ncontain all of the subtrees of `P` as descendants.\n\n**Applying filters.**\n\nShould filtered out patterns be allowed to subsume?\n\n"
  },
  {
    "path": "cp-profiler/cp-profiler.pri",
    "content": "SOURCES += \\\n    $$PWD/src/cpprofiler/core.cpp \\\n    $$PWD/src/cpprofiler/command_line_parser.cpp \\\n    $$PWD/src/cpprofiler/name_map.cpp \\\n    $$PWD/src/cpprofiler/tcp_server.cpp \\\n    $$PWD/src/cpprofiler/receiver_thread.cpp \\\n    $$PWD/src/cpprofiler/receiver_worker.cpp \\\n    $$PWD/src/cpprofiler/conductor.cpp \\\n    $$PWD/src/cpprofiler/execution.cpp \\\n    $$PWD/src/cpprofiler/user_data.cpp \\\n    $$PWD/src/cpprofiler/tree_builder.cpp \\\n    $$PWD/src/cpprofiler/execution_list.cpp \\\n    $$PWD/src/cpprofiler/execution_window.cpp \\\n    $$PWD/src/cpprofiler/utils/utils.cpp \\\n    $$PWD/src/cpprofiler/utils/string_utils.cpp \\\n    $$PWD/src/cpprofiler/utils/path_utils.cpp \\\n    $$PWD/src/cpprofiler/utils/tree_utils.cpp \\\n    $$PWD/src/cpprofiler/utils/perf_helper.cpp \\\n    $$PWD/src/cpprofiler/utils/array.cpp \\\n    $$PWD/src/cpprofiler/utils/std_ext.cpp \\\n    $$PWD/src/cpprofiler/utils/maybe_caller.cpp \\\n    $$PWD/src/cpprofiler/tree/node.cpp \\\n    $$PWD/src/cpprofiler/tree/structure.cpp \\\n    $$PWD/src/cpprofiler/tree/layout.cpp \\\n    $$PWD/src/cpprofiler/tree/layout_computer.cpp \\\n    $$PWD/src/cpprofiler/tree/shape.cpp \\\n    $$PWD/src/cpprofiler/tree/node_tree.cpp \\\n    $$PWD/src/cpprofiler/tree/node_id.cpp \\\n    $$PWD/src/cpprofiler/tree/node_info.cpp \\\n    $$PWD/src/cpprofiler/tree/visual_flags.cpp \\\n    $$PWD/src/cpprofiler/tree/traditional_view.cpp \\\n    $$PWD/src/cpprofiler/pixel_views/pt_canvas.cpp \\\n    $$PWD/src/cpprofiler/pixel_views/icicle_canvas.cpp \\\n    $$PWD/src/cpprofiler/pixel_views/pixel_image.cpp \\\n    $$PWD/src/cpprofiler/pixel_views/pixel_widget.cpp \\\n    $$PWD/src/cpprofiler/tree/tree_scroll_area.cpp \\\n    $$PWD/src/cpprofiler/tree/cursors/node_cursor.cpp \\\n    $$PWD/src/cpprofiler/tree/cursors/drawing_cursor.cpp \\\n    $$PWD/src/cpprofiler/tree/cursors/layout_cursor.cpp \\\n    $$PWD/src/cpprofiler/tree/cursors/hide_failed_cursor.cpp \\\n    $$PWD/src/cpprofiler/tree/cursors/hide_not_highlighted_cursor.cpp \\\n    $$PWD/src/cpprofiler/tree/cursors/nodevisitor.hpp \\\n    $$PWD/src/cpprofiler/analysis/similar_subtree_analysis.cpp \\\n    $$PWD/src/cpprofiler/analysis/similar_subtree_window.cpp \\\n    $$PWD/src/cpprofiler/analysis/path_comp.cpp \\\n    $$PWD/src/cpprofiler/analysis/merge_window.cpp \\\n    $$PWD/src/cpprofiler/analysis/merging/pentagon_rect.cpp \\\n    $$PWD/src/cpprofiler/analysis/tree_merger.cpp \\\n    $$PWD/src/cpprofiler/analysis/histogram_scene.cpp \\\n    $$PWD/src/cpprofiler/analysis/pattern_rect.cpp \\\n    $$PWD/src/cpprofiler/tree/node_drawing.cpp \\\n    $$PWD/src/cpprofiler/db_handler.cpp \\\n    $$PWD/src/cpprofiler/solver_data.cpp \\\n    $$PWD/src/cpprofiler/nogood_dialog.cpp \\\n\nHEADERS += \\\n    $$PWD/src/cpprofiler/config.hh \\\n    $$PWD/src/cpprofiler/options.hh \\\n    $$PWD/src/cpprofiler/command_line_parser.hh \\\n    $$PWD/src/cpprofiler/name_map.hh \\\n    $$PWD/src/cpprofiler/settings.hh \\\n    $$PWD/src/cpprofiler/conductor.hh \\\n    $$PWD/src/cpprofiler/tcp_server.hh \\\n    $$PWD/src/cpprofiler/receiver_thread.hh \\\n    $$PWD/src/cpprofiler/receiver_worker.hh \\\n    $$PWD/src/cpprofiler/execution.hh \\\n    $$PWD/src/cpprofiler/user_data.hh \\\n    $$PWD/src/cpprofiler/tree_builder.hh \\\n    $$PWD/src/cpprofiler/execution_list.hh \\\n    $$PWD/src/cpprofiler/execution_window.hh \\\n    $$PWD/src/cpprofiler/utils/utils.hh \\\n    $$PWD/src/cpprofiler/utils/string_utils.hh \\\n    $$PWD/src/cpprofiler/utils/path_utils.hh \\\n    $$PWD/src/cpprofiler/utils/tree_utils.hh \\\n    $$PWD/src/cpprofiler/utils/perf_helper.hh \\\n    $$PWD/src/cpprofiler/utils/array.hh \\\n    $$PWD/src/cpprofiler/utils/debug.hh \\\n    $$PWD/src/cpprofiler/utils/std_ext.hh \\\n    $$PWD/src/cpprofiler/utils/maybe_caller.hh \\\n    $$PWD/src/cpprofiler/tree/node.hh \\\n    $$PWD/src/cpprofiler/tree/structure.hh \\\n    $$PWD/src/cpprofiler/tree/layout.hh \\\n    $$PWD/src/cpprofiler/tree/layout_computer.hh \\\n    $$PWD/src/cpprofiler/tree/shape.hh \\\n    $$PWD/src/cpprofiler/tree/node_tree.hh \\\n    $$PWD/src/cpprofiler/tree/node_id.hh \\\n    $$PWD/src/cpprofiler/tree/node_info.hh \\\n    $$PWD/src/cpprofiler/tree/node_stats.hh \\\n    $$PWD/src/cpprofiler/tree/visual_flags.hh \\\n    $$PWD/src/cpprofiler/tree/traditional_view.hh \\\n    $$PWD/src/cpprofiler/pixel_views/pt_canvas.hh \\\n    $$PWD/src/cpprofiler/pixel_views/icicle_canvas.hh \\\n    $$PWD/src/cpprofiler/pixel_views/pixel_image.hh \\\n    $$PWD/src/cpprofiler/pixel_views/pixel_widget.hh \\\n    $$PWD/src/cpprofiler/tree/tree_scroll_area.hh \\\n    $$PWD/src/cpprofiler/tree/subtree_view.hh \\\n    $$PWD/src/cpprofiler/tree/cursors/node_cursor.hh \\\n    $$PWD/src/cpprofiler/tree/cursors/drawing_cursor.hh \\\n    $$PWD/src/cpprofiler/tree/cursors/layout_cursor.hh \\\n    $$PWD/src/cpprofiler/tree/cursors/hide_failed_cursor.hh \\\n    $$PWD/src/cpprofiler/tree/cursors/hide_not_highlighted_cursor.hh \\\n    $$PWD/src/cpprofiler/tree/cursors/nodevisitor.hh \\\n    $$PWD/src/cpprofiler/core.hh \\\n    $$PWD/src/cpprofiler/solver_id.hh \\\n    $$PWD/src/cpprofiler/utils/debug_mutex.hh \\\n    $$PWD/src/cpprofiler/analysis/similar_subtree_analysis.hh \\\n    $$PWD/src/cpprofiler/analysis/similar_subtree_window.hh \\\n    $$PWD/src/cpprofiler/analysis/merge_window.hh \\\n    $$PWD/src/cpprofiler/analysis/pentagon_counter.hpp \\\n    $$PWD/src/cpprofiler/analysis/tree_merger.hh \\\n    $$PWD/src/cpprofiler/analysis/subtree_pattern.hh \\\n    $$PWD/src/cpprofiler/analysis/path_comp.hh \\\n    $$PWD/src/cpprofiler/analysis/histogram_scene.hh \\\n    $$PWD/src/cpprofiler/analysis/pattern_rect.hh \\\n    $$PWD/src/cpprofiler/analysis/merging/pentagon_list_widget.hh \\\n    $$PWD/src/cpprofiler/analysis/merging/merge_result.hh \\\n    $$PWD/src/cpprofiler/analysis/merging/pentagon_rect.hh \\\n    $$PWD/src/cpprofiler/tree/node_widget.hh \\\n    $$PWD/src/cpprofiler/tree/node_drawing.hh \\\n    $$PWD/src/cpprofiler/db_handler.hh \\\n    $$PWD/src/cpprofiler/solver_data.hh \\\n    $$PWD/src/cpprofiler/nogood_dialog.hh \\\n    $$PWD/src/cpprofiler/analysis/nogood_analysis_dialog.hh \\\n    $$PWD/src/cpprofiler/message_wrapper.hh \\\n\nSOURCES += \\\n    $$PWD/src/cpprofiler/tests/tree_test.cpp \\\n    $$PWD/src/cpprofiler/tests/execution_test.cpp \\\n\nHEADERS += \\\n    $$PWD/src/cpprofiler/tests/tree_test.hh \\\n    $$PWD/src/cpprofiler/tests/execution_test.hh \\\n"
  },
  {
    "path": "cp-profiler/cp-profiler.pro",
    "content": "TEMPLATE = app\n\nTARGET = cp-profiler\n\nQT += widgets network sql\n\nCONFIG += c++11\n\ninclude(cp-profiler.pri)\nSOURCES += $$PWD/src/main_cpprofiler.cpp\n"
  },
  {
    "path": "cp-profiler/src/cpp-integration/README.md",
    "content": "\n#### 1. Create a connector instance\n\n```c++\nunsigned int port = 6565;\n```\n\n```c++\nConnector c(port);\n```\n\n#### 2. Establish a connection and start a new search tree\n\n```c++\n/// Establishes a socket connection using the port specified above\nc.connect();\n\n/// Tells the profiler to start a new tree\nc.restart(\"example\");\n\n/// Also used in case of a restart with restart id specified\nc.restart(\"example\", 1);\n```\n\n#### 3. Send data every time the solver branches/fails/finds a solution\n\n```c++\n/// Create a node on a stack with mandatory fields\nNode node = c.createNode(node_id, parent_id, alt, kids, status);\n```\n\n```c++\n// Specify optional fields (whichever available)\nnode.set_label(\"b\");\n```\n\n```c++\n// Send the node\nc.sendNode(node);\n```\n\nOr all in one line:\n\n```c++\nc.createNode(node_id, parent_id, alt, kids, status).set_label(\"b\").send();\n```\n\n\nThe parameters are:\n\nfield   | type | description\n------  | ---- | -----------\nnode_id   | int | current node's identifier\nparent_id | int | identifier of node's parent\nalt       | int | which of its siblings the node is (0 for the left-most)\nkids      | int | number of children\nstatus    | Profiling::NodeStatus | determines the node's type (solution, failure, branching etc)\nlabel     | std::string | some text-based information to go along with the node (ie branching decision\n\n#### 4. Finish the tree and release the socket\n\n```c++\nc.done();\nc.disconnect();\n```"
  },
  {
    "path": "cp-profiler/src/cpp-integration/connector.hpp",
    "content": "#ifndef CONNECTOR\n#define CONNECTOR\n\n#include \"message.hpp\"\n\n#include <iostream>\n#include <sstream>\n#include <vector>\n#include <cstring>\n\n#ifdef WIN32\n\n#include <winsock2.h>\n#include <ws2tcpip.h>\n#pragma comment(lib, \"Ws2_32.lib\")\n#pragma comment(lib, \"Mswsock.lib\")\n#pragma comment(lib, \"AdvApi32.lib\")\n\n#include <basetsd.h>\ntypedef SSIZE_T ssize_t;\n\n#else\n\n#include <netdb.h>\n#include <unistd.h>\n\n#endif\n\nnamespace cpprofiler {\n\ntemplate <typename T>\nclass Option {\n  T value_;\n  bool present{false};\n\npublic:\n  bool valid() const { return present; }\n  void set(const T& t) { present = true; value_ = t; }\n  void unset() { present = false; }\n  const T& value() const { assert(present); return value_; }\n  T& value() { assert(present); return value_; }\n};\n\nclass Connector;\nclass Node;\nstatic void sendNode(Connector& c, Node& node);\n\nclass Node {\n  Connector& _c;\n\n  NodeUID node_;\n  NodeUID parent_;\n  int alt_;\n  int kids_;\n\n  NodeStatus status_;\n\n  Option<std::string> label_;\n  Option<std::string> nogood_;\n  Option<std::string> info_;\n\npublic:\n  Node(NodeUID node, NodeUID parent,\n       int alt, int kids, NodeStatus status, Connector& c)\n    : _c(c), node_{node}, parent_{parent},\n      alt_(alt), kids_(kids), status_(status) {}\n\n  Node& set_node_thread_id(int tid) {\n    node_.tid = tid;\n    return *this;\n  }\n\n  const Option<std::string>& label() const { return label_; }\n\n  Node& set_label(const std::string& label) {\n    label_.set(label);\n    return *this;\n  }\n\n  const Option<std::string>& nogood() const { return nogood_; }\n\n  Node& set_nogood(const std::string& nogood) {\n    nogood_.set(nogood);\n    return *this;\n  }\n\n  const Option<std::string>& info() const { return info_; }\n\n  Node& set_info(const std::string& info) {\n    info_.set(info);\n    return *this;\n  }\n\n  int alt() const { return alt_; }\n  int kids() const { return kids_; }\n\n  NodeStatus status() const { return status_; }\n\n  NodeUID nodeUID() const { return node_; }\n  NodeUID parentUID() const { return parent_; }\n\n  int node_id() const { return node_.nid; }\n  int parent_id() const { return parent_.nid; }\n  int node_thread_id() const {  return node_.tid; }\n  int node_restart_id() const { return node_.rid; }\n  int parent_thread_id() const {  return parent_.tid; }\n  int parent_restart_id() const { return parent_.rid; }\n\n  void send() { sendNode(_c, *this); }\n};\n\n// From http://beej.us/guide/bgnet/output/html/multipage/advanced.html#sendall\nstatic int sendall(int s, const char* buf, int* len) {\n  int total = 0;         // how many bytes we've sent\n  int bytesleft = *len;  // how many we have left to send\n  ssize_t n;\n\n  while (total < *len) {\n    n = send(s, buf + total, static_cast<size_t>(bytesleft), 0);\n    if (n == -1) {\n      break;\n    }\n    total += n;\n    bytesleft -= n;\n  }\n\n  *len = total;  // return number actually sent here\n\n  return n == -1 ? -1 : 0;  // return -1 on failure, 0 on success\n}\n\nclass Connector {\nprivate:\n  MessageMarshalling marshalling;\n\n  const unsigned int port;\n\n  int sockfd;\n  bool _connected;\n\n  void sendOverSocket() {\n    if (!_connected) return;\n\n    std::vector<char> buf = marshalling.serialize();\n\n    sendRawMsg(buf);\n  }\n\npublic:\n  void sendRawMsg(const std::vector<char>& buf) {\n    uint32_t bufSize = static_cast<uint32_t>(buf.size());\n    int bufSizeLen = sizeof(uint32_t);\n    sendall(sockfd, reinterpret_cast<char*>(&bufSize), &bufSizeLen);\n    int bufSizeInt = static_cast<int>(bufSize);\n    sendall(sockfd, reinterpret_cast<const char*>(buf.data()), &bufSizeInt);\n  }\n\n  Connector(unsigned int port) : port(port), _connected(false) {}\n\n  bool connected() { return _connected; }\n\n  /// connect to a socket via port specified in the construction (6565 by\n  /// default)\n  void connect() {\n    struct addrinfo hints, *servinfo, *p;\n    int rv;\n\n#ifdef WIN32\n    // Initialise Winsock.\n    WSADATA wsaData;\n    int startupResult = WSAStartup(MAKEWORD(2, 2), &wsaData);\n    if (startupResult != 0) {\n      printf(\"WSAStartup failed with error: %d\\n\", startupResult);\n    }\n#endif\n\n    memset(&hints, 0, sizeof hints);\n    hints.ai_family = AF_UNSPEC;\n    hints.ai_socktype = SOCK_STREAM;\n\n    if ((rv = getaddrinfo(\"localhost\", std::to_string(port).c_str(), &hints,\n                          &servinfo)) != 0) {\n      std::cerr << \"getaddrinfo: \" << gai_strerror(rv) << \"\\n\";\n      goto giveup;\n    }\n\n    // loop through all the results and connect to the first we can\n    for (p = servinfo; p != NULL; p = p->ai_next) {\n      if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {\n        // errno is set here, but we don't examine it.\n        continue;\n      }\n\n      if (::connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {\n#ifdef WIN32\n        closesocket(sockfd);\n#else\n        close(sockfd);\n#endif\n        // errno is set here, but we don't examine it.\n        continue;\n      }\n\n      break;\n    }\n\n    // Connection failed; give up.\n    if (p == NULL) {\n      goto giveup;\n    }\n\n    freeaddrinfo(servinfo);  // all done with this structure\n\n    _connected = true;\n\n    return;\ngiveup:\n    _connected = false;\n    return;\n\n  }\n\n  // sends START_SENDING message to the Profiler with a model name\n  void start(const std::string& file_path = \"\",\n               int execution_id = -1, bool has_restarts = false) {\n    /// extract fzn file name\n    std::string base_name(file_path);\n    {\n      size_t pos = base_name.find_last_of('/');\n      if (pos != static_cast<size_t>(-1)) {\n        base_name = base_name.substr(pos + 1, base_name.length() - pos - 1);\n      }\n    }\n\n    std::string info{\"\"};\n    {\n      std::stringstream ss;\n      ss << \"{\";\n      ss << \"\\\"has_restarts\\\": \" << (has_restarts ? \"true\" : \"false\")  << \"\\n\";\n      ss << \",\\\"name\\\": \" << \"\\\"\" << base_name << \"\\\"\" << \"\\n\";\n      if (execution_id != -1) {\n        ss << \",\\\"execution_id\\\": \" << execution_id;\n      }\n      ss << \"}\";\n      info = ss.str();\n    }\n\n    marshalling.makeStart(info);\n    sendOverSocket();\n  }\n\n  void restart(int restart_id = -1) {\n\n    std::string info{\"\"};\n    {\n      std::stringstream ss;\n      ss << \"{\";\n      ss << \"\\\"restart_id\\\": \" << restart_id << \"\\n\";\n      ss << \"}\";\n      info = ss.str();\n    }\n\n    marshalling.makeRestart(info);\n    sendOverSocket();\n  }\n\n  void done() {\n    marshalling.makeDone();\n    sendOverSocket();\n  }\n\n  /// disconnect from a socket\n  void disconnect() {\n#ifdef WIN32\n    closesocket(sockfd);\n#else\n    close(sockfd);\n#endif\n  }\n\n  void sendNode(const Node& node) {\n    if (!_connected) return;\n\n    auto& msg = marshalling.makeNode(node.nodeUID(), node.parentUID(),\n                                     node.alt(), node.kids(), node.status());\n\n    if (node.label().valid()) msg.set_label(node.label().value());\n    if (node.nogood().valid()) msg.set_nogood(node.nogood().value());\n    if (node.info().valid()) msg.set_info(node.info().value());\n\n    sendOverSocket();\n  }\n\n  Node createNode(NodeUID node, NodeUID parent,\n                  int alt, int kids, NodeStatus status) {\n    return Node(node, parent, alt, kids, status, *this);\n  }\n};\n\nvoid sendNode(Connector& c, Node& node) { c.sendNode(node); }\n\n}\n\n#endif\n"
  },
  {
    "path": "cp-profiler/src/cpp-integration/message.hpp",
    "content": "#ifndef MESSAGE_HH\n#define MESSAGE_HH\n\n#include <vector>\n#include <string>\n#include <cassert>\n#include <cstdint>\n\nnamespace cpprofiler {\n\nstatic const int32_t PROFILER_PROTOCOL_VERSION = 3;\n\nenum NodeStatus {\n  SOLVED = 0,        ///< Node representing a solution\n  FAILED = 1,        ///< Node representing failure\n  BRANCH = 2,        ///< Node representing a branch\n  SKIPPED = 3,       ///< Skipped by backjumping\n};\n\nenum class MsgType {\n  NODE = 0,\n  DONE = 1,\n  START = 2,\n  RESTART = 3,\n};\n\n// Unique identifier for a node\nstruct NodeUID {\n  // Node number\n  int32_t nid;\n  // Restart id\n  int32_t rid;\n  // Thread id\n  int32_t tid;\n};\n\n\nclass Message {\n  MsgType _type;\n\n  NodeUID _node;\n  NodeUID _parent;\n  int32_t _alt;\n  int32_t _kids;\n  NodeStatus _status;\n\n  bool _have_label{false};\n  std::string _label;\n\n  bool _have_nogood{false};\n  std::string _nogood;\n\n  bool _have_info{false};\n  std::string _info;\n\n  bool _have_version{false};\n  int32_t _version; // PROFILER_PROTOCOL_VERSION;\n\npublic:\n  bool isNode(void) const { return _type == MsgType::NODE; }\n  bool isDone(void) const { return _type == MsgType::DONE; }\n  bool isStart(void) const { return _type == MsgType::START; }\n  bool isRestart(void) const { return _type == MsgType::RESTART; }\n\n  NodeUID nodeUID(void) const { return _node; }\n  void set_nodeUID(const NodeUID& n) { _node = n; }\n\n  NodeUID parentUID(void) const { return _parent; }\n  void set_parentUID(const NodeUID& p) { _parent = p; }\n\n  int32_t alt(void) const { return _alt; }\n  void set_alt(int32_t alt) { _alt = alt; }\n\n  int32_t kids(void) const { return _kids; }\n  void set_kids(int32_t kids) { _kids = kids; }\n\n  NodeStatus status(void) const { return _status; }\n  void set_status(NodeStatus status) { _status = status; }\n\n  void set_label(const std::string& label) {\n    _have_label = true;\n    _label = label;\n  }\n\n  void set_info(const std::string& info) {\n    _have_info = true;\n    _info = info;\n  }\n\n  void set_nogood(const std::string& nogood) {\n    _have_nogood = true;\n    _nogood = nogood;\n  }\n\n  void set_version(int32_t v) {\n    _have_version = true;\n    _version = v;\n  }\n\n  bool has_version(void) const { return _have_version; }\n  int32_t version(void) const { return _version; }\n\n  bool has_label(void) const { return _have_label; }\n  const std::string& label() const { return _label; }\n\n  bool has_nogood(void) const { return _have_nogood; }\n  const std::string& nogood(void) const { return _nogood; }\n\n  // generic optional fields\n  bool has_info(void) const { return _have_info; }\n  const std::string& info(void) const { return _info; }\n\n  void set_type(MsgType type) { _type = type; }\n  MsgType type(void) const { return _type; }\n\n  void reset(void) {\n    _have_label = false;\n    _have_nogood = false;\n    _have_info = false;\n    _have_version = false;\n  }\n};\n\n\nclass MessageMarshalling {\n\nprivate:\n  /// Only optional fields are listed here, if node (no need for field id)\n  enum Field {\n    LABEL = 0,\n    NOGOOD = 1,\n    INFO = 2,\n    VERSION = 3\n  };\n\n  Message msg;\n\n  typedef char* iter;\n\n  static void serializeType(std::vector<char>& data, MsgType f) {\n    data.push_back(static_cast<char>(f));\n  }\n\n  static void serializeField(std::vector<char>& data, Field f) {\n    data.push_back(static_cast<char>(f));\n  }\n\n  static void serialize(std::vector<char>& data, int32_t i) {\n    data.push_back(static_cast<char>((i & 0xFF000000) >> 24));\n    data.push_back(static_cast<char>((i & 0xFF0000) >> 16));\n    data.push_back(static_cast<char>((i & 0xFF00) >> 8));\n    data.push_back(static_cast<char>((i & 0xFF)));\n  }\n\n  static void serialize(std::vector<char>& data, NodeStatus s) {\n    data.push_back(static_cast<char>(s));\n  }\n\n  static void serialize(std::vector<char>& data, const std::string& s) {\n    serialize(data, static_cast<int32_t>(s.size()));\n    for (char c : s) {\n      data.push_back(c);\n    }\n  }\n\n  static MsgType deserializeMsgType(iter& it) {\n    auto m = static_cast<MsgType>(*it);\n    ++it;\n    return m;\n  }\n\n  static Field deserializeField(iter& it) {\n    auto f = static_cast<Field>(*it);\n    ++it;\n    return f;\n  }\n\n  static int32_t deserializeInt(iter& it) {\n    auto b1 = static_cast<uint32_t>(reinterpret_cast<uint8_t&>(*it++));\n    auto b2 = static_cast<uint32_t>(reinterpret_cast<uint8_t&>(*it++));\n    auto b3 = static_cast<uint32_t>(reinterpret_cast<uint8_t&>(*it++));\n    auto b4 = static_cast<uint32_t>(reinterpret_cast<uint8_t&>(*it++));\n\n    return static_cast<int32_t>(b1 << 24 | b2 << 16 | b3 << 8 | b4);\n  }\n\n  static NodeStatus deserializeStatus(iter& it) {\n    auto f = static_cast<NodeStatus>(*it);\n    ++it;\n    return f;\n  }\n\n  static std::string deserializeString(iter& it) {\n    std::string result;\n    int32_t size = deserializeInt(it);\n    result.reserve(static_cast<size_t>(size));\n    for (int32_t i = 0; i < size; i++) {\n      result += *it;\n      ++it;\n    }\n    return result;\n  }\n\npublic:\n  Message& makeNode(NodeUID node, NodeUID parent,\n                    int32_t alt, int32_t kids, NodeStatus status) {\n    msg.reset();\n    msg.set_type(MsgType::NODE);\n\n    msg.set_nodeUID(node);\n    msg.set_parentUID(parent);\n\n    msg.set_alt(alt);\n    msg.set_kids(kids);\n    msg.set_status(status);\n\n    return msg;\n  }\n\n  void makeStart(const std::string& info) {\n    msg.reset();\n    msg.set_type(MsgType::START);\n    msg.set_version(PROFILER_PROTOCOL_VERSION);\n    msg.set_info(info); /// info containts name, has_restarts, execution id\n  }\n\n  void makeRestart(const std::string& info) {\n    msg.reset();\n    msg.set_type(MsgType::RESTART);\n    msg.set_info(info); /// info contains restart_id (-1 default)\n  }\n\n  void makeDone(void) {\n    msg.reset();\n    msg.set_type(MsgType::DONE);\n  }\n\n  const Message& get_msg(void) { return msg; }\n\n  std::vector<char> serialize(void) const {\n    std::vector<char> data;\n    size_t dataSize = 1 + (msg.isNode() ? 4 * 8 + 1 : 0) +\n        (msg.has_label() ? 1 + 4 + msg.label().size() : 0) +\n        (msg.has_nogood() ? 1 + 4 + msg.nogood().size() : 0) +\n        (msg.has_info() ? 1 + 4 + msg.info().size() : 0);\n    data.reserve(dataSize);\n\n    serializeType(data, msg.type());\n    if (msg.isNode()) {\n      // serialize NodeId node\n      auto n_uid = msg.nodeUID();\n      serialize(data, n_uid.nid);\n      serialize(data, n_uid.rid);\n      serialize(data, n_uid.tid);\n      // serialize NodeId parent\n      auto p_uid = msg.parentUID();\n      serialize(data, p_uid.nid);\n      serialize(data, p_uid.rid);\n      serialize(data, p_uid.tid);\n      // Other Data\n      serialize(data, msg.alt());\n      serialize(data, msg.kids());\n      serialize(data, msg.status());\n    }\n\n    if(msg.has_version()) {\n      serializeField(data, VERSION);\n      serialize(data, msg.version());\n    }\n    if (msg.has_label()) {\n      serializeField(data, LABEL);\n      serialize(data, msg.label());\n    }\n    if (msg.has_nogood()) {\n      serializeField(data, NOGOOD);\n      serialize(data, msg.nogood());\n    }\n    if (msg.has_info()) {\n      serializeField(data, INFO);\n      serialize(data, msg.info());\n    }\n    return data;\n  }\n\n  void deserialize(char* data, size_t size) {\n    char *end = data + size;\n    msg.set_type(deserializeMsgType(data));\n    if (msg.isNode()) {\n      int32_t nid = deserializeInt(data);\n      int32_t rid = deserializeInt(data);\n      int32_t tid = deserializeInt(data);\n\n      msg.set_nodeUID({nid, rid, tid});\n\n      nid = deserializeInt(data);\n      rid = deserializeInt(data);\n      tid = deserializeInt(data);\n\n      msg.set_parentUID({nid, rid, tid});\n\n      msg.set_alt(deserializeInt(data));\n      msg.set_kids(deserializeInt(data));\n      msg.set_status(deserializeStatus(data));\n    }\n\n    msg.reset();\n\n    while (data != end) {\n      MessageMarshalling::Field f = deserializeField(data);\n      switch (f) {\n      case VERSION:\n        msg.set_version(deserializeInt(data)); break;\n      case LABEL:\n        msg.set_label(deserializeString(data)); break;\n      case NOGOOD:\n        msg.set_nogood(deserializeString(data)); break;\n      case INFO:\n        msg.set_info(deserializeString(data)); break;\n      default:\n        break;\n      }\n    }\n  }\n};\n\n}\n\n#endif  // MESSAGE_HH\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/histogram_scene.cpp",
    "content": "#include \"histogram_scene.hh\"\n#include \"pattern_rect.hh\"\n#include \"../utils/perf_helper.hh\"\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nstatic constexpr int SHAPE_RECT_HEIGHT = 16;\nstatic constexpr int NUMBER_WIDTH = 50;\nstatic constexpr int COLUMN_WIDTH = NUMBER_WIDTH + 10;\n\nstatic constexpr int ROW_HEIGHT = SHAPE_RECT_HEIGHT + V_DISTANCE;\n\nenum class Align\n{\n    CENTER,\n    RIGHT\n};\n\nQColor PatternRect::normal_outline{252, 209, 22};\nQColor PatternRect::highlighted_outline{252, 148, 77};\n\nstatic void addText(QGraphicsScene &scene, int col, int row,\n                    QGraphicsSimpleTextItem *text_item,\n                    Align alignment = Align::CENTER)\n{\n    int item_width = text_item->boundingRect().width();\n    int item_height = text_item->boundingRect().height();\n\n    // center the item vertically at y\n    int y_offset = item_height / 2;\n    int x_offset = 0;\n\n    switch (alignment)\n    {\n    case Align::CENTER:\n        x_offset = (COLUMN_WIDTH - item_width) / 2;\n        break;\n    case Align::RIGHT:\n        x_offset = COLUMN_WIDTH - item_width;\n        break;\n    }\n\n    int x = col * COLUMN_WIDTH + x_offset;\n    int y = row * ROW_HEIGHT + y_offset;\n\n    text_item->setPos(x, y - y_offset);\n    scene.addItem(text_item);\n}\n\nstatic std::shared_ptr<PatternRect> addRect(HistogramScene &hist_scene, int row, int val)\n{\n\n    int x = 0;\n    int y = row * ROW_HEIGHT;\n\n    int width = val * 40;\n\n    auto item = std::make_shared<PatternRect>(hist_scene, x, y, width, SHAPE_RECT_HEIGHT);\n    item->addToScene();\n\n    return item;\n}\n\nstatic void addText(QGraphicsScene &scene, int col, int row, const char *text)\n{\n    auto str = new QGraphicsSimpleTextItem{text};\n    addText(scene, col, row, str);\n}\n\nstatic void addText(QGraphicsScene &scene, int col, int row, int value)\n{\n    auto int_text_item = new QGraphicsSimpleTextItem{QString::number(value)};\n    addText(scene, col, row, int_text_item, Align::RIGHT);\n}\n\nvoid HistogramScene::drawPatterns(PatternProp prop)\n{\n\n    auto &scene = *scene_;\n\n    addText(scene, 0, 0, \"hight\");\n    addText(scene, 1, 0, \"count\");\n    addText(scene, 2, 0, \"size\");\n\n    int row = 1;\n\n    for (auto &&p : patterns_)\n    {\n\n        const auto nid = p->first();\n        const int count = p->count();\n        const int height = p->height();\n        /// number of nodes in the frist subtree represeting a pattern\n        const auto size = p->size();\n\n        int val;\n        switch (prop)\n        {\n        case PatternProp::HEIGHT:\n            val = height;\n            break;\n        case PatternProp::COUNT:\n            val = count;\n            break;\n        case PatternProp::SIZE:\n            val = size;\n            break;\n        }\n\n        auto item = addRect(*this, row, val);\n\n        rects_.push_back(item);\n        rect2pattern_.insert(std::make_pair(item, p));\n\n        addText(scene, 0, row, height);\n        addText(scene, 1, row, count);\n        addText(scene, 2, row, size);\n\n        ++row;\n    }\n}\n\nvoid HistogramScene::setPatterns(std::vector<SubtreePattern> &&patterns)\n{\n    patterns_.reserve(patterns.size());\n\n    for (auto &p : patterns)\n    {\n        patterns_.push_back(std::make_shared<SubtreePattern>(std::move(p)));\n    }\n}\n\nint HistogramScene::findPatternIdx(PatternRect *pattern) const\n{\n    for (auto i = 0; i < rects_.size(); ++i)\n    {\n        if (pattern == rects_[i].get())\n        {\n            return i;\n        }\n    }\n\n    return -1;\n}\n\nPatternPtr HistogramScene::rectToPattern(PatternRectPtr prect) const\n{\n    if (rect2pattern_.find(prect) != rect2pattern_.end())\n    {\n        return rect2pattern_.at(prect);\n    }\n    return nullptr;\n}\n\nvoid HistogramScene::reset()\n{\n\n    if (selected_rect_)\n    {\n        selected_rect_->setHighlighted(false);\n        selected_rect_ = nullptr;\n        selected_idx_ = -1;\n    }\n\n    rects_.clear();\n    rect2pattern_.clear();\n    scene_->clear();\n    patterns_.clear();\n}\n\nvoid HistogramScene::changeSelectedPattern(int idx)\n{\n\n    if (idx < 0 || idx >= rects_.size())\n        return;\n\n    auto prect = rects_[idx];\n\n    /// Need to find pattern to get the list of nodes\n    auto pattern = rectToPattern(prect);\n    if (!pattern)\n        return;\n\n    emit pattern_selected(pattern->first());\n    emit should_be_highlighted(pattern->nodes());\n\n    {\n        if (idx == selected_idx_)\n        {\n            selected_rect_->setHighlighted(false);\n            selected_idx_ = -1;\n            selected_rect_ = nullptr;\n        }\n        else\n        {\n            selected_idx_ = idx;\n            /// unselect the old one\n            if (selected_rect_)\n            {\n                selected_rect_->setHighlighted(false);\n            }\n            prect->setHighlighted(true);\n            selected_idx_ = idx;\n            selected_rect_ = prect.get();\n        }\n    }\n}\n\nvoid HistogramScene::findAndSelect(PatternRect *prect)\n{\n    auto idx = findPatternIdx(prect);\n\n    changeSelectedPattern(idx);\n}\n\n/// TODO: this should change scroll bar value as well\nvoid HistogramScene::prevPattern()\n{\n    if (rects_.size() == 0)\n        return;\n\n    auto new_idx = selected_idx_ - 1;\n\n    if (new_idx < 0)\n        return;\n\n    changeSelectedPattern(new_idx);\n}\n\nvoid HistogramScene::nextPattern()\n{\n    if (rects_.size() == 0)\n        return;\n\n    auto new_idx = selected_idx_ + 1;\n\n    if (new_idx >= rects_.size())\n        return;\n\n    changeSelectedPattern(new_idx);\n}\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/histogram_scene.hh",
    "content": "#ifndef CPPROFILER_ANALYSES_HISTOGRAM_SCENE\n#define CPPROFILER_ANALYSES_HISTOGRAM_SCENE\n\n#include <memory>\n#include <unordered_map>\n#include <QGraphicsScene>\n#include <QGraphicsSimpleTextItem>\n\n#include \"../core.hh\"\n#include \"subtree_pattern.hh\"\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\n/// vertical distance between two shape rectangles\nstatic constexpr int V_DISTANCE = 2;\n\nclass PatternRect;\nenum class PatternProp;\n\nusing PatternRectPtr = std::shared_ptr<PatternRect>;\nusing PatternPtr = std::shared_ptr<SubtreePattern>;\n\nclass HistogramScene : public QObject\n{\n  Q_OBJECT\n  /// Scene used for drawing\n  std::unique_ptr<QGraphicsScene> scene_;\n\n  /// Selected pattern rectangle (nullptr if no selected)\n  PatternRect *selected_rect_ = nullptr;\n  /// Index of the selected pattern\n  int selected_idx_ = -1;\n\n  /// A list of visual elements for patterns (used for navigation);\n  std::vector<PatternRectPtr> rects_;\n\n  /// The result of the analysis in a form of a list of patterns\n  std::vector<PatternPtr> patterns_;\n\n  /// Mapping from a visual element to the pattern it represents\n  std::unordered_map<PatternRectPtr, PatternPtr> rect2pattern_;\n\n  /// Find the possition of the rectangle representing `pattern`\n  int findPatternIdx(PatternRect *pattern) const;\n\n  /// Select `idx` unselecting the previous pattern\n  void changeSelectedPattern(int idx);\n\n  PatternPtr rectToPattern(PatternRectPtr prect) const;\n\npublic:\n  HistogramScene()\n  {\n    scene_.reset(new QGraphicsScene());\n  }\n\n  /// Prepare the GUI for new patterns\n  void reset();\n\n  /// Expose underlying scene\n  QGraphicsScene *scene()\n  {\n    return scene_.get();\n  }\n\n  /// Find the pattern's id and select it\n  void findAndSelect(PatternRect *prect);\n\n  /// Draw the patterns onto the scene\n  void drawPatterns(PatternProp prop);\n\n  /// Initialize patterns based on the analysis results\n  void setPatterns(std::vector<SubtreePattern> &&patterns);\n\nsignals:\n\n  void pattern_selected(NodeID);\n  void should_be_highlighted(const std::vector<NodeID> &);\n\npublic slots:\n\n  void prevPattern();\n  void nextPattern();\n};\n\n} // namespace analysis\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/merge_window.cpp",
    "content": "#include \"merge_window.hh\"\n\n#include \"../tree/traditional_view.hh\"\n#include \"merging/pentagon_list_widget.hh\"\n#include \"pentagon_counter.hpp\"\n#include \"../user_data.hh\"\n#include \"../solver_data.hh\"\n#include \"../execution.hh\"\n\n#include \"nogood_analysis_dialog.hh\"\n\n#include <QGridLayout>\n#include <QWidget>\n#include <QMenuBar>\n#include <QStatusBar>\n#include <QCheckBox>\n#include <cmath>\n#include <QHBoxLayout>\n#include <QToolButton>\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nMergeWindow::MergeWindow(Execution &ex_l, Execution &ex_r, std::shared_ptr<tree::NodeTree> nt, std::shared_ptr<MergeResult> res, QWidget* parent)\n    : QMainWindow(parent), ex_l_(ex_l), ex_r_(ex_r), nt_(nt), merge_result_(res)\n{\n\n    initOrigLocations();\n\n    user_data_.reset(new UserData);\n    solver_data_.reset(new SolverData);\n    view_.reset(new tree::TraditionalView(*nt_, *user_data_, *solver_data_));\n\n    view_->setScale(50);\n\n    auto layout = new QGridLayout();\n\n    resize(500 + pent_config::VIEW_WIDTH, 700);\n\n    pentagon_bar = new PentagonCounter(this);\n    statusBar()->addPermanentWidget(pentagon_bar);\n\n    pent_list = new PentagonListWidget(this, *merge_result_);\n\n    connect(pent_list, &PentagonListWidget::pentagonClicked, view_.get(), &tree::TraditionalView::setCurrentNode);\n    // connect(pent_list, &PentagonListWidget::pentagonClicked, view_.get(), &tree::TraditionalView::setAndCenterNode);\n\n    auto sort_cb = new QCheckBox(\"sorted\", this);\n    sort_cb->setChecked(true);\n    connect(sort_cb, &QCheckBox::stateChanged, pent_list, &PentagonListWidget::handleSortCB);\n\n    layout->addWidget(pent_list, 2, 0, 1, 1, Qt::AlignLeft);\n    layout->addWidget(sort_cb, 1, 0, 1, 1, Qt::AlignLeft);\n\n    {\n        auto widget = new QWidget();\n        setCentralWidget(widget);\n        widget->setLayout(layout);\n        layout->addWidget(view_->widget(), 1, 1, 2, 1);\n    }\n\n    auto menuBar = new QMenuBar(0);\n// Don't add the menu bar on Mac OS X\n#ifndef Q_WS_MAC\n    setMenuBar(menuBar);\n#endif\n\n    connect(nt_.get(), &tree::NodeTree::structureUpdated,\n            view_.get(), &tree::TraditionalView::setLayoutOutdated);\n\n    /// This indirection is necessary to immitate the behaviour of ExecutionWindow,\n    /// which selects nodes in all views (traditional, pixel etc.)\n    connect(view_.get(), &tree::TraditionalView::nodeSelected,\n            view_.get(), &tree::TraditionalView::setCurrentNode);\n\n    {\n        auto widget = new QWidget();\n        auto button_layout = new QHBoxLayout();\n        button_layout->setContentsMargins(0, 0, 0, 0);\n        widget->setLayout(button_layout);\n\n        {\n            auto nodeMenu = new QMenu(\"&Node\");\n\n            auto centerNode = new QAction{\"Center current node\", this};\n            centerNode->setShortcut(QKeySequence(\"C\"));\n            nodeMenu->addAction(centerNode);\n            connect(centerNode, &QAction::triggered, view_.get(), &tree::TraditionalView::centerCurrentNode);\n\n            auto navRoot = new QAction{\"Go to the root\", this};\n            navRoot->setShortcut(QKeySequence(\"R\"));\n            nodeMenu->addAction(navRoot);\n            connect(navRoot, &QAction::triggered, view_.get(), &tree::TraditionalView::navRoot);\n\n            auto navDown = new QAction{\"Go down the tree\", this};\n            navDown->setShortcut(QKeySequence(\"Down\"));\n            nodeMenu->addAction(navDown);\n            connect(navDown, &QAction::triggered, view_.get(), &tree::TraditionalView::navDown);\n\n            auto navUp = new QAction{\"Go up the tree\", this};\n            navUp->setShortcut(QKeySequence(\"Up\"));\n            nodeMenu->addAction(navUp);\n            connect(navUp, &QAction::triggered, view_.get(), &tree::TraditionalView::navUp);\n\n            auto navLeft = new QAction{\"Go left the tree\", this};\n            navLeft->setShortcut(QKeySequence(\"Left\"));\n            nodeMenu->addAction(navLeft);\n            connect(navLeft, &QAction::triggered, view_.get(), &tree::TraditionalView::navLeft);\n\n            auto navRight = new QAction{\"Go right the tree\", this};\n            navRight->setShortcut(QKeySequence(\"Right\"));\n            nodeMenu->addAction(navRight);\n            connect(navRight, &QAction::triggered, view_.get(), &tree::TraditionalView::navRight);\n\n            auto toggleShowLabel = new QAction{\"Show labels down\", this};\n            toggleShowLabel->setShortcut(QKeySequence(\"L\"));\n            nodeMenu->addAction(toggleShowLabel);\n            connect(toggleShowLabel, &QAction::triggered, view_.get(), &tree::TraditionalView::showLabelsDown);\n\n            auto toggleShowLabelsUp = new QAction{\"Show labels down\", this};\n            toggleShowLabelsUp->setShortcut(QKeySequence(\"Shift+L\"));\n            nodeMenu->addAction(toggleShowLabelsUp);\n            connect(toggleShowLabelsUp, &QAction::triggered, view_.get(), &tree::TraditionalView::showLabelsUp);\n\n            auto hideFailed = new QAction{\"Hide failed\", this};\n            hideFailed->setShortcut(QKeySequence(\"F\"));\n            nodeMenu->addAction(hideFailed);\n            connect(hideFailed, &QAction::triggered, this, &analysis::MergeWindow::hideFailed);\n\n            auto unhideAll = new QAction{\"Unhide all\", this};\n            unhideAll->setShortcut(QKeySequence(\"U\"));\n            nodeMenu->addAction(unhideAll);\n            connect(unhideAll, &QAction::triggered, view_.get(), &tree::TraditionalView::unhideAll);\n\n            auto toggleHighlighted = new QAction{\"Toggle highlight subtree\", this};\n            toggleHighlighted->setShortcut(QKeySequence(\"H\"));\n            nodeMenu->addAction(toggleHighlighted);\n            connect(toggleHighlighted, &QAction::triggered, view_.get(), &tree::TraditionalView::toggleHighlighted);\n\n            auto button = new QToolButton(widget);\n            button->setStyleSheet(\"padding: 3px;\");\n            button->setPopupMode(QToolButton::InstantPopup);\n            button->setText(\"Node\");\n            button->setToolButtonStyle(Qt::ToolButtonTextOnly);\n            button->setMenu(nodeMenu);\n            button_layout->addWidget(button);\n        }\n\n        {\n            auto debugMenu = new QMenu(\"&Debug\");\n\n            auto updateLayoutAction = new QAction{\"Update layout\", this};\n            debugMenu->addAction(updateLayoutAction);\n            connect(updateLayoutAction, &QAction::triggered, view_.get(), &tree::TraditionalView::updateLayout);\n\n            auto updateView = new QAction{\"Update view\", this};\n            debugMenu->addAction(updateView);\n            connect(updateView, &QAction::triggered, view_.get(), &tree::TraditionalView::needsRedrawing);\n\n            auto button = new QToolButton(widget);\n            button->setStyleSheet(\"padding: 3px;\");\n            button->setPopupMode(QToolButton::InstantPopup);\n            button->setText(\"Debug\");\n            button->setToolButtonStyle(Qt::ToolButtonTextOnly);\n            button->setMenu(debugMenu);\n            button_layout->addWidget(button);\n        }\n\n        {\n            auto analysisMenu = new QMenu(\"&Analysis\");\n\n            auto ngAnalysisAction = new QAction{\"Nogood analysis\", this};\n            analysisMenu->addAction(ngAnalysisAction);\n            connect(ngAnalysisAction, &QAction::triggered, this, &MergeWindow::runNogoodAnalysis);\n\n            auto button = new QToolButton(widget);\n            button->setStyleSheet(\"padding: 3px;\");\n            button->setPopupMode(QToolButton::InstantPopup);\n            button->setText(\"Analysis\");\n            button->setToolButtonStyle(Qt::ToolButtonTextOnly);\n            button->setMenu(analysisMenu);\n            button_layout->addWidget(button);\n        }\n\n        button_layout->addStretch();\n        layout->addWidget(widget, 0, 0, 1, 0);\n    }\n\n    pentagon_bar->update(merge_result_->size());\n    pent_list->updateScene();\n}\n\nMergeWindow::~MergeWindow() = default;\n\n/// Find original ids for nodes under a pentagon\nstatic void linkLocationsPentagon(NodeID n_m,\n                                  const tree::NodeTree &nt_m,\n                                  NodeID n,\n                                  const tree::NodeTree &nt,\n                                  std::vector<OriginalLoc> &locs)\n{\n    std::stack<NodeID> stack_m; /// stack for nodes of the merged tree\n    std::stack<NodeID> stack;   /// stack for nodes of the original tree\n\n    if (n_m == NodeID::NoNode && n == NodeID::NoNode)\n        return;\n\n    stack_m.push(n_m);\n    stack.push(n);\n\n    while (!stack_m.empty())\n    {\n        auto node_m = stack_m.top();\n        stack_m.pop();\n        auto node = stack.top();\n        stack.pop();\n\n        locs[n_m] = {n};\n\n        /// The trees at this point must have the same strucutre\n        for (auto alt = nt_m.childrenCount(node_m) - 1; alt >= 0; --alt)\n        {\n            stack_m.push(nt_m.getChild(node_m, alt));\n            stack.push(nt.getChild(node, alt));\n        }\n    }\n}\n\nvoid MergeWindow::initOrigLocations()\n{\n    // TODO: Perform DFS traversal of three trees in lockstep assigning ids\n    orig_locations_.resize(nt_->nodeCount());\n\n    std::stack<NodeID> stack_m; /// stack for nodes of the merged tree\n    std::stack<NodeID> stack_l; /// stack for nodes of the left tree\n    std::stack<NodeID> stack_r; /// stack for nodes of the right tree\n\n    auto &nt_l = ex_l_.tree(); /// left tree\n    auto &nt_r = ex_r_.tree(); /// right tree\n\n    stack_m.push(nt_->getRoot());\n    stack_l.push(nt_l.getRoot());\n    stack_r.push(nt_r.getRoot());\n\n    while (!stack_m.empty())\n    {\n        auto n = stack_m.top();\n        stack_m.pop();\n        auto n_l = stack_l.top();\n        stack_l.pop();\n        auto n_r = stack_r.top();\n        stack_r.pop();\n\n        if (nt_->getStatus(n) == tree::NodeStatus::MERGED)\n        {\n            auto left_child = nt_->getChild(n, 0);\n            auto right_child = nt_->getChild(n, 1);\n            linkLocationsPentagon(left_child, *nt_, n_l, nt_l, orig_locations_);\n            linkLocationsPentagon(right_child, *nt_, n_r, nt_r, orig_locations_);\n            continue;\n        }\n\n        orig_locations_[n] = {n_l}; /// arbitrarily link to the node on the left tree\n\n        /// Note: the tree above merged nodes must have the same structure\n        for (auto alt = nt_->childrenCount(n) - 1; alt >= 0; --alt)\n        {\n            stack_m.push(nt_->getChild(n, alt));\n            stack_l.push(nt_l.getChild(n_l, alt));\n            stack_r.push(nt_r.getChild(n_r, alt));\n        }\n    }\n}\n\ntree::NodeTree &MergeWindow::getTree()\n{\n    return *nt_;\n}\n\nMergeResult &MergeWindow::mergeResult()\n{\n    return *merge_result_;\n}\n\nnamespace ng_analysis\n{\n\nstruct ReductionStats\n{\n    int total_red; /// total reduction by a nogood\n    int count;     /// number of times a nogood contributed to a 1-n pentagon\n};\n\nclass ResultBuilder\n{\n\n    using ResType = std::unordered_map<NogoodID, ReductionStats>;\n\n    /// Accumulate nogood contributions here\n    ResType ng_items;\n\n  public:\n    ResultBuilder() {}\n\n    /// Account for search reduction of one 1-n pentagon\n    void addPentagonData(\n        const std::vector<NogoodID> &nogoods, // responsible nogoods\n        int red)                              // node reduction (n-1)\n    {\n        /// reduction attributed to each nogood\n        const auto rel_red = std::ceil((float)red / nogoods.size());\n\n        for (auto ng : nogoods)\n        {\n            if (ng_items.find(ng) == ng_items.end())\n            {\n                /// never seen this nogood before\n                ng_items.insert({ng, {0, 0}});\n            }\n\n            auto &ng_stats = ng_items.at(ng);\n            ng_stats.count++;\n            ng_stats.total_red += rel_red;\n        }\n    }\n\n    const ResType &result() const { return ng_items; }\n};\n\n} // namespace ng_analysis\n\nvoid MergeWindow::runNogoodAnalysis() const\n{\n\n    /// Determine which tree has nogoods\n    const bool l_has_ng = ex_l_.hasNogoods();\n    const bool r_has_ng = ex_r_.hasNogoods();\n\n    /// whether treat the execution on the left as the one with nogoods\n    const bool left = [&]() {\n        /// left execution is the default one, but\n        /// use the one on the right if it is the only\n        /// one with nogoods\n        if (r_has_ng && !l_has_ng)\n            return false;\n        else if (r_has_ng && l_has_ng)\n        {\n            print(\"NOTE: both LEFT and RIGHT executions have nogoods\");\n            return true;\n        }\n        return true;\n    }();\n\n    print(\"merge result size: {}\", merge_result_->size());\n\n    ng_analysis::ResultBuilder res_builder;\n\n    /// The tree with nogoods\n    const auto &ng_tree = left ? ex_l_.tree() : ex_r_.tree();\n\n    for (auto &item : *merge_result_)\n    {\n\n        /// check if the nogood tree contains 1 node subtree under pentagon\n        const auto subtree_size = left ? item.size_l : item.size_r;\n        if (subtree_size != 1)\n            continue;\n\n        /// See what nogoods contribute to the nogood at item.nid\n\n        /// get the sole node\n        const auto alt = left ? 0 : 1;\n        const auto kid = nt_->getChild(item.pen_nid, alt);\n        const auto orig_id = orig_locations_[kid].nid;\n\n        /// get contributing nogoods:\n        const auto *nogoods = ng_tree.solver_data().getContribNogoods(orig_id);\n\n        if (nogoods)\n        {\n            res_builder.addPentagonData(*nogoods, std::abs(item.size_r - item.size_l));\n        }\n        else\n        {\n            // print(\"no contrib nogoods for {}\", orig_locations_[kid_l].nid);\n        }\n    }\n\n    /// construct ng analysis data in the format required by ng dialog\n    std::vector<NgAnalysisItem> nga_data;\n    nga_data.reserve(res_builder.result().size());\n\n    for (auto item : res_builder.result())\n    {\n        const NogoodID id = item.first;\n        const auto &ng_str = ng_tree.getNogood(id);\n        const auto *reasons_ptr = ng_tree.solver_data().getContribConstraints(id);\n\n        std::vector<int> reasons = reasons_ptr ? *reasons_ptr : std::vector<int>{};\n\n        nga_data.push_back({id, ng_str, item.second.total_red, item.second.count, std::move(reasons)});\n    }\n\n    auto ng_window = new NogoodAnalysisDialog(std::move(nga_data));\n    ng_window->setAttribute(Qt::WA_DeleteOnClose);\n\n    connect(ng_window, &NogoodAnalysisDialog::nogoodClicked, [this](NodeID nid) {\n        const_cast<tree::TraditionalView *>(view_.get())->setAndCenterNode(nid);\n    });\n\n    ng_window->show();\n}\n\nstatic bool has_as_ancestor(const tree::NodeTree &nt, NodeID n, NodeID a)\n{\n    while (n != NodeID::NoNode)\n    {\n        if (n == a)\n            return true;\n\n        n = nt.getParent(n);\n    }\n\n    return false;\n}\n\n/// Check if the node is under some pentagon\nstatic bool under_pentagon(const tree::NodeTree &nt, NodeID n)\n{\n    /// Don't check the node itself\n    if (n != NodeID::NoNode)\n    {\n        n = nt.getParent(n);\n    }\n\n    while (n != NodeID::NoNode)\n    {\n        if (nt.getStatus(n) == tree::NodeStatus::MERGED)\n        {\n            return true;\n        }\n\n        n = nt.getParent(n);\n    }\n\n    return false;\n}\n\n/// Hide failed subtrees in a way that does not hide pentagon nodes\nvoid MergeWindow::hideFailed()\n{\n    auto cur_node = view_->node();\n\n    /// Hide if the node is under some pentagon\n    /// (meaning there are no other pentagons under the node)\n    if (under_pentagon(*nt_, cur_node))\n    {\n        view_->hideFailedAt(cur_node);\n        return;\n    }\n\n    /// Otherwise, see which pentagons are under the\n    /// node and try to hide their children\n    for (auto item : *merge_result_)\n    {\n        /// pentagon node\n        const auto pen = item.pen_nid;\n\n        /// hide failed children if pen is below cur_node\n        if (has_as_ancestor(*nt_, pen, cur_node))\n        {\n\n            for (auto alt = 0; alt < nt_->childrenCount(pen); ++alt)\n            {\n                auto kid = nt_->getChild(pen, alt);\n                view_->hideFailedAt(kid);\n            }\n        }\n    }\n}\n\n// NodeID MergeWindow::findOriginalId(NodeID nid) const\n// {\n//     auto iter = nid;\n\n//     /// For now assume the node comes from left tree\n//     bool left_tree = true;\n\n//     while (iter != NodeID::NoNode)\n//     {\n//         const auto status = nt_.getStatus(iter);\n//         if (status == tree::NodeStatus::MERGED)\n//         {\n//             print(\"found pentagon node: {}\", iter);\n\n//             for ()\n//         }\n\n//         iter = nt_.getParent(iter);\n//     }\n// }\n\n} // namespace analysis\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/merge_window.hh",
    "content": "#pragma once\n\n#include <QMainWindow>\n#include \"../tree/node_tree.hh\"\n#include \"merging/merge_result.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\nclass TraditionalView;\n}\n\nclass Execution;\nclass UserData;\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nclass PentagonCounter;\nclass PentagonListWidget;\n\n/// Original location for a node;\nstruct OriginalLoc\n{\n    NodeID nid;\n};\n\nclass MergeWindow : public QMainWindow\n{\n    Q_OBJECT\n\n    /// The two executions merged\n    Execution &ex_l_;\n    Execution &ex_r_;\n\n    std::shared_ptr<tree::NodeTree> nt_;\n    std::shared_ptr<MergeResult> merge_result_;\n\n    /// Dummy user data (required for traditional view)\n    std::unique_ptr<UserData> user_data_;\n\n    /// Dummy solver data (required for traditional view)\n    std::unique_ptr<SolverData> solver_data_;\n\n    std::unique_ptr<tree::TraditionalView> view_;\n\n    PentagonCounter *pentagon_bar;\n\n    PentagonListWidget *pent_list;\n\n    /// Original locations for node `i` (used for nogoods/labels etc)\n    std::vector<OriginalLoc> orig_locations_;\n\n  private:\n    /// find the right data for a node\n    Nogood getNogood();\n\n    /// traverse the merged tree to find origins for all nodes\n    void initOrigLocations();\n    /// Find id for a node `nid` of a merged tree\n    // NodeID findOriginalId(NodeID nid) const;\n\n    /// Hide all failed subtrees under pentagons\n    void hideFailed();\n\n  public:\n    MergeWindow(Execution &ex_l, Execution &ex_r, std::shared_ptr<tree::NodeTree> nt, std::shared_ptr<MergeResult> res, QWidget* parent = nullptr);\n    ~MergeWindow();\n\n    tree::NodeTree &getTree();\n\n    MergeResult &mergeResult();\n\n  public slots:\n\n    void runNogoodAnalysis() const;\n};\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/merging/merge_result.hh",
    "content": "#pragma once\n\n#include \"../../core.hh\"\n#include <vector>\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nstruct PentagonItem\n{\n    /// pentagon node\n    NodeID pen_nid;\n    /// left subtree size\n    int size_l;\n    /// right subtree size\n    int size_r;\n};\n\nusing MergeResult = std::vector<PentagonItem>;\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/merging/pentagon_list_widget.hh",
    "content": "#pragma once\n\n#include <QWidget>\n#include <QGraphicsView>\n#include <QVBoxLayout>\n\n#include \"merge_result.hh\"\n\n#include \"pentagon_rect.hh\"\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nclass PentagonListWidget : public QWidget\n{\n    Q_OBJECT\n\n    /// Elements for drawing primitives\n    QGraphicsView *view_;\n    QGraphicsScene *scene_;\n\n    /// Data to be visualised\n    const MergeResult &merge_res_;\n\n    /// Whether pentagon list should be sorted (by right/left difference)\n    bool needs_sorting = true;\n\n    /// Which pentagon from the list is selected\n    NodeID selected_ = NodeID::NoNode;\n\n  public:\n    /// Create specifying parent widget and the result of merging\n    PentagonListWidget(QWidget *parent, const MergeResult &res);\n\n    /// Clear the view and draw horizontal bars indicating pentagons\n    void updateScene();\n\n    /// Width available for drawing\n    int viewWidth() { return view_->viewport()->width(); }\n\n    /// Handle pentagon click\n    void handleClick(NodeID node)\n    {\n        selected_ = node;\n        emit pentagonClicked(node);\n        updateScene();\n    }\n\n  signals:\n    /// Indicate that a pentagon (associated with some NodeID) was clicked\n    void pentagonClicked(NodeID);\n\n  public slots:\n    /// Handle checkbox click from Merge Window\n    void handleSortCB(int state)\n    {\n        needs_sorting = (state == Qt::Checked);\n        updateScene();\n    }\n};\n\ninline PentagonListWidget::PentagonListWidget(QWidget *w, const MergeResult &res) : QWidget(w), merge_res_(res)\n{\n\n    auto layout = new QVBoxLayout(this);\n\n    view_ = new QGraphicsView(this);\n    view_->setAlignment(Qt::AlignLeft | Qt::AlignTop);\n\n    view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);\n    view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);\n\n    view_->setMaximumWidth(pent_config::VIEW_WIDTH);\n    view_->setMinimumWidth(pent_config::VIEW_WIDTH);\n\n    scene_ = new QGraphicsScene(this);\n    view_->setScene(scene_);\n\n    layout->addWidget(view_);\n}\n\ninline void PentagonListWidget::updateScene()\n{\n    using namespace pent_config;\n\n    scene_->clear();\n\n    /// This is used to scale the horizontal bars\n    int max_value = 0;\n    for (const auto &pen : merge_res_)\n    {\n        max_value = std::max(max_value, std::max(pen.size_l, pen.size_r));\n    }\n\n    auto displayed_items = merge_res_; /// make copy\n\n    const auto sort_function = [](const PentagonItem &p1, const PentagonItem &p2) {\n        const auto diff1 = std::abs(p1.size_r - p1.size_l);\n        const auto diff2 = std::abs(p2.size_r - p2.size_l);\n\n        if (diff1 < diff2)\n        {\n            return false;\n        }\n        else if (diff2 > diff1)\n        {\n            return true;\n        }\n        else\n        {\n            const auto sum1 = p1.size_r + p1.size_l;\n            const auto sum2 = p2.size_r + p2.size_l;\n            return sum2 < sum1;\n        }\n    };\n\n    if (needs_sorting)\n    {\n        std::sort(displayed_items.begin(), displayed_items.end(), sort_function);\n    }\n\n    for (auto i = 0; i < displayed_items.size(); ++i)\n    {\n        const auto ypos = i * (HEIGHT + PADDING) + PADDING;\n\n        const bool sel = (displayed_items[i].pen_nid == selected_);\n        new PentagonRect(scene_, *this, displayed_items[i], ypos, max_value, sel);\n    }\n}\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/merging/pentagon_rect.cpp",
    "content": "#include \"pentagon_rect.hh\"\n\n#include \"merge_result.hh\"\n#include \"pentagon_list_widget.hh\"\n\n#include <QGraphicsRectItem>\n#include <QGraphicsScene>\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nPentagonRect::PentagonRect(QGraphicsScene *scene, PentagonListWidget &listw, const PentagonItem &pen, int y, int max_val, bool selected)\n    : QGraphicsRectItem(pent_config::PADDING, y, listw.viewWidth() - 1, pent_config::HEIGHT),\n      m_pen_list_widget(listw),\n      m_node(pen.pen_nid)\n{\n    using namespace pent_config;\n\n    const int PENT_WIDTH = listw.viewWidth() - 1;\n    const int HALF_WIDTH = PENT_WIDTH / 2;\n\n    const float scale_x = (float)HALF_WIDTH / max_val;\n\n    const int value_l = pen.size_l;\n    const int value_r = pen.size_r;\n\n    const int width_l = value_l * scale_x;\n    const int width_r = value_r * scale_x;\n\n    const int cx = PADDING + listw.viewWidth() / 2;\n\n    if (selected)\n    {\n        setBrush(sel_color);\n    }\n\n    auto left = new QGraphicsRectItem(cx - width_l, y, width_l, HEIGHT);\n    left->setBrush(left_color);\n    // left->setPen(Qt::NoPen);\n    auto right = new QGraphicsRectItem(cx, y, width_r, HEIGHT);\n    right->setBrush(right_color);\n    // right->setPen(Qt::NoPen);\n\n    scene->addItem(this);\n    scene->addItem(left);\n    scene->addItem(right);\n\n    {\n        auto text_item = new QGraphicsSimpleTextItem{QString::number(value_l)};\n        text_item->setPos(TEXT_PAD, y);\n        scene->addItem(text_item);\n    }\n\n    {\n        auto text_item = new QGraphicsSimpleTextItem{QString::number(value_r)};\n        const int item_width = text_item->boundingRect().width();\n        const int x = PENT_WIDTH - TEXT_PAD - item_width;\n        text_item->setPos(x, y);\n        scene->addItem(text_item);\n    }\n}\n\nvoid PentagonRect::mousePressEvent(QGraphicsSceneMouseEvent *)\n{\n    m_pen_list_widget.handleClick(m_node);\n}\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/merging/pentagon_rect.hh",
    "content": "#pragma once\n\n#include <QGraphicsRectItem>\n#include \"../../core.hh\"\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nclass PentagonListWidget;\nstruct PentagonItem;\n\nnamespace pent_config\n{\nconstexpr int HEIGHT = 16;\nconstexpr int PADDING = 0;\nconstexpr int VIEW_WIDTH = 150;\n// constexpr int PENT_WIDTH = VIEW_WIDTH - PADDING * 2 - 3;\n// constexpr int HALF_WIDTH = PENT_WIDTH / 2;\n\nconstexpr int TEXT_PAD = 10;\n\nstatic QColor left_color{153, 204, 255};\nstatic QColor right_color{255, 153, 204};\n\n/// color for selected pentagon item\nstatic QColor sel_color{150, 150, 150};\n} // namespace pent_config\n\nclass PentagonRect : public QGraphicsRectItem\n{\n\n  private:\n    PentagonListWidget &m_pen_list_widget;\n    /// Pentagon node associated with this item\n    NodeID m_node;\n\n    void mousePressEvent(QGraphicsSceneMouseEvent *) override;\n\n  public:\n    PentagonRect(QGraphicsScene *scene, PentagonListWidget &listw, const PentagonItem &pen, int y, int max_val, bool selected);\n};\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/nogood_analysis_dialog.hh",
    "content": "#pragma once\n\n#include <QDialog>\n#include <QStandardItemModel>\n#include <QSortFilterProxyModel>\n#include <QTableView>\n#include <QHeaderView>\n#include <QVBoxLayout>\n#include <QPushButton>\n\n#include <QFile>\n#include <QFileDialog>\n\n#include <memory>\n\n#include \"../core.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace analysis\n{\n\nstruct NgAnalysisItem\n{\n    NogoodID nid;                    /// node id of the nogood\n    const Nogood &ng;                /// textual representation of the nogood\n    int total_red;                   /// total reduction by this nogood\n    int count;                       /// number of times the nogood found in a 1-n pentagon\n    std::vector<int> constraint_ids; /// reasons for the nogood\n};\n\nusing NgAnalysisData = std::vector<NgAnalysisItem>;\n\nclass NogoodProxyModel : public QSortFilterProxyModel\n{\n\n    bool lessThan(const QModelIndex &left, const QModelIndex &right) const override\n    {\n        /// TODO: extend this to work for clauses too (although this doesn't crash)\n        int lhs = sourceModel()->data(left).toInt();\n        int rhs = sourceModel()->data(right).toInt();\n\n        return lhs < rhs;\n    }\n};\n\nclass NogoodAnalysisDialog : public QDialog\n{\n    Q_OBJECT\n\n  private:\n    std::unique_ptr<QStandardItemModel> ng_model_;\n\n    QTableView *ng_table_;\n\n    NgAnalysisData ng_data_;\n\n    void init()\n    {\n        static constexpr int DEFAULT_WIDTH = 1000;\n        static constexpr int DEFAULT_HEIGHT = 600;\n\n        resize(DEFAULT_WIDTH, DEFAULT_HEIGHT);\n        auto layout = new QVBoxLayout(this);\n\n        ng_table_ = new QTableView();\n\n        ng_table_->setSortingEnabled(true);\n\n        layout->addWidget(ng_table_);\n\n        ng_model_.reset(new QStandardItemModel(0, 4));\n\n        auto proxy_model = new NogoodProxyModel();\n        proxy_model->setSourceModel(ng_model_.get());\n\n        const QStringList headers{\"NodeID\", \"Total Reduction\", \"Count\",\n                                  \"Clause\"};\n        ng_model_->setHorizontalHeaderLabels(headers);\n        ng_table_->horizontalHeader()->setStretchLastSection(true);\n        ng_table_->setSelectionBehavior(QAbstractItemView::SelectRows);\n        ng_table_->setEditTriggers(QAbstractItemView::NoEditTriggers);\n\n        ng_table_->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);\n        ng_table_->setModel(proxy_model);\n\n        connect(ng_table_, &QTableView::doubleClicked, [this](const QModelIndex &idx) {\n            const auto nid = ng_model_->item(idx.row())->text().toInt();\n            emit nogoodClicked(NodeID(nid));\n        });\n\n        auto save_ng_btn = new QPushButton(\"Save Nogoods\");\n        layout->addWidget(save_ng_btn, 0, Qt::AlignLeft);\n\n        connect(save_ng_btn, &QPushButton::clicked, this, &NogoodAnalysisDialog::saveNogoods);\n    }\n\n    void saveNogoods()\n    {\n        QString file_name = QFileDialog::getSaveFileName(this, \"Save nogoods to\");\n        /// No file was selected\n        if (file_name.isEmpty())\n            return;\n\n        QFile file(file_name);\n\n        if (!file.open(QIODevice::WriteOnly))\n        {\n            print(\"Error: could not open for writing: {}\", file_name);\n            return;\n        }\n\n        QTextStream nogood_stream(&file);\n        const char sep = '\\t';\n\n        nogood_stream << \"nid\" << sep << \"count\" << sep << \"reduction\" << sep << \"nogood\" << sep << \"reasons\" << '\\n';\n\n        for (auto &ng_item : ng_data_)\n        {\n            nogood_stream << ng_item.nid << sep;\n            nogood_stream << ng_item.count << sep;\n            nogood_stream << ng_item.total_red << sep;\n\n            nogood_stream << ng_item.ng.get().c_str() << sep;\n\n            for (auto id : ng_item.constraint_ids)\n            {\n                nogood_stream << id << ' ';\n            }\n\n            nogood_stream << '\\n';\n        }\n    }\n\n  public:\n    NogoodAnalysisDialog(NgAnalysisData nga_data) : QDialog(), ng_data_(std::move(nga_data))\n    {\n        init();\n        populate(ng_data_);\n\n        ng_table_->sortByColumn(1, Qt::SortOrder::DescendingOrder);\n    }\n\n    void populate(const NgAnalysisData &nga_data)\n    {\n        for (auto &item : nga_data)\n        {\n            const auto nid_i = new QStandardItem(QString::number(item.nid));\n            const auto left_i = new QStandardItem(QString::number(item.total_red));\n            const auto right_i = new QStandardItem(QString::number(item.count));\n            const auto ng_i = new QStandardItem(item.ng.get().c_str());\n            ng_model_->appendRow({nid_i, left_i, right_i, ng_i});\n        }\n    }\n\n  signals:\n    void nogoodClicked(NodeID nid);\n};\n\n} // namespace analysis\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/path_comp.cpp",
    "content": "#include \"path_comp.hh\"\n#include <algorithm>\n\nusing std::vector;\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\n/// A wrapper around std::set_intersection; copying is intended\nstatic vector<Label> set_intersect(vector<Label> v1, vector<Label> v2)\n{\n    /// set_intersection requires the resulting set to be\n    /// at least as large as the smallest of the two sets\n    vector<Label> res(std::min(v1.size(), v2.size()));\n\n    std::sort(begin(v1), end(v1));\n    std::sort(begin(v2), end(v2));\n\n    auto it = std::set_intersection(begin(v1), end(v1), begin(v2), end(v2),\n                                    begin(res));\n    res.resize(it - begin(res));\n\n    return res;\n}\n\n/// A wrapper around std::set_symemtric_diff; copying is intended\nstatic vector<Label> set_symmetric_diff(vector<Label> v1, vector<Label> v2)\n{\n    vector<Label> res(v1.size() + v2.size());\n\n    /// vectors must be sorted\n    std::sort(begin(v1), end(v1));\n    std::sort(begin(v2), end(v2));\n\n    /// fill `res` with elements not present in both v1 and v2\n    auto it = std::set_symmetric_difference(begin(v1), end(v1), begin(v2), end(v2),\n                                            begin(res));\n\n    res.resize(it - begin(res));\n\n    return res;\n}\n\n/// Calculate unique labels for both paths (present in one but not in the other)\nstd::pair<vector<Label>, vector<Label>>\ngetLabelDiff(const std::vector<Label> &path1,\n             const std::vector<Label> &path2)\n{\n    auto diff = set_symmetric_diff(path1, path2);\n\n    auto unique_1 = set_intersect(path1, diff);\n\n    auto unique_2 = set_intersect(path2, diff);\n\n    return std::make_pair(std::move(unique_1), std::move(unique_2));\n}\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/path_comp.hh",
    "content": "#include <vector>\n\n#include \"../core.hh\"\n\n#pragma once\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\n/// Find unique labels on each of the two paths\nstd::pair<std::vector<Label>, std::vector<Label>>\ngetLabelDiff(const std::vector<Label> &path1,\n             const std::vector<Label> &path2);\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/pattern_rect.cpp",
    "content": "#include \"pattern_rect.hh\"\n#include \"histogram_scene.hh\"\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nstatic constexpr int SELECTION_WIDTH = 500;\n\nPatternRect::PatternRect(HistogramScene &hist_scene, int x, int y, int width, int height)\n    : QGraphicsRectItem(x, y - V_DISTANCE / 2, SELECTION_WIDTH, height + V_DISTANCE), visible_rect(x, y, width, height), m_hist_scene(hist_scene)\n{\n    QColor gold(252, 209, 22);\n    setPen(Qt::NoPen);\n\n    QColor patternRectColor(255, 215, 179);\n    visible_rect.setBrush(patternRectColor);\n}\n\nvoid PatternRect::mousePressEvent(QGraphicsSceneMouseEvent *)\n{\n    m_hist_scene.findAndSelect(this);\n}\n\nvoid PatternRect::setHighlighted(bool val)\n{\n\n    QPen pen;\n    if (val)\n    {\n        pen.setWidth(3);\n        pen.setBrush(highlighted_outline);\n    }\n    else\n    {\n        // pen.setBrush(normal_outline);\n        pen.setStyle(Qt::NoPen);\n    }\n    setPen(pen);\n}\n\nvoid PatternRect::addToScene()\n{\n    m_hist_scene.scene()->addItem(&visible_rect);\n    m_hist_scene.scene()->addItem(this);\n}\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/pattern_rect.hh",
    "content": "#pragma once\n\n#include \"../core.hh\"\n#include <QGraphicsRectItem>\n#include <memory>\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nclass HistogramScene;\n\nclass PatternRect : public QGraphicsRectItem\n{\n\n    QGraphicsRectItem visible_rect;\n\n    HistogramScene &m_hist_scene;\n\n    void mousePressEvent(QGraphicsSceneMouseEvent *) override;\n\n    static QColor normal_outline;\n    static QColor highlighted_outline;\n\n  public:\n    PatternRect(HistogramScene &hist_scene, int x, int y, int width, int height);\n\n    void setHighlighted(bool val);\n\n    void addToScene();\n};\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/pentagon_counter.hpp",
    "content": "#include <QWidget>\n#include <QLabel>\n#include <QHBoxLayout>\n#include \"../tree/node_widget.hh\"\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nclass PentagonCounter : public QWidget\n{\n\n    QLabel *pentagonCount;\n\n  public:\n    PentagonCounter(QWidget *parent) : QWidget(parent)\n    {\n\n        using namespace tree;\n\n        QHBoxLayout *hbl = new QHBoxLayout{this};\n        hbl->setContentsMargins(2, 1, 2, 1);\n\n        hbl->addWidget(new NodeWidget(NodeStatus::MERGED));\n        pentagonCount = new QLabel(\"0\");\n        hbl->addWidget(pentagonCount);\n    }\n\n    void update(int count)\n    {\n        pentagonCount->setNum(count);\n    }\n};\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/similar_subtree_analysis.cpp",
    "content": "#include \"similar_subtree_analysis.hh\"\n#include \"../tree/node_tree.hh\"\n#include \"../tree/layout.hh\"\n#include \"../utils/tree_utils.hh\"\n#include \"../utils/perf_helper.hh\"\n#include \"../tree/shape.hh\"\n#include \"../tree/visual_flags.hh\"\n#include \"../tree/layout_computer.hh\"\n\n#include <set>\n\nnamespace cpprofiler\n{\n\nnamespace analysis\n{\n\nusing std::vector;\nusing tree::Layout;\nusing tree::NodeStatus;\nusing tree::NodeTree;\nusing tree::Shape;\n\nstruct ShapeInfo\n{\n    NodeID nid;\n    const Shape &shape;\n};\n\nstruct CompareShapes\n{\n  public:\n    bool operator()(const ShapeInfo &si1, const ShapeInfo &si2) const\n    {\n\n        const auto &s1 = si1.shape;\n        const auto &s2 = si2.shape;\n\n        if (s1.height() < s2.height())\n            return true;\n        if (s1.height() > s2.height())\n            return false;\n\n        for (int i = 0; i < s1.height(); ++i)\n        {\n            if (s1[i].l < s2[i].l)\n                return false;\n            if (s1[i].l > s2[i].l)\n                return true;\n            if (s1[i].r < s2[i].r)\n                return true;\n            if (s1[i].r > s2[i].r)\n                return false;\n        }\n        return false;\n    }\n};\n\nstatic void set_as_processed(const NodeTree &nt, Partition &partition, int h, std::vector<int> &height_info)\n{\n\n    /// Note that in general a group can contain subtrees of\n    /// different height; however, since the subtrees of height 'h'\n    /// have already been processed, any group that contains such a\n    /// subtree is processed and contains only subtrees of height 'h'.\n    auto should_stay = [&nt, &height_info, h](const Group &g) {\n        auto nid = g.at(0);\n\n        return height_info.at(nid) != h;\n    };\n\n    auto &rem = partition.remaining;\n\n    auto first_to_move = std::partition(rem.begin(), rem.end(), should_stay);\n\n    move(first_to_move, rem.end(), std::back_inserter(partition.processed));\n    rem.resize(distance(rem.begin(), first_to_move));\n}\n\nstatic vector<Group> separate_marked(const vector<Group> &rem_groups, const vector<NodeID> &marked)\n{\n\n    vector<Group> res_groups;\n\n    for (auto &group : rem_groups)\n    {\n\n        Group g1; /// marked\n        Group g2; /// not marked\n\n        for (auto nid : group)\n        {\n            if (std::find(marked.begin(), marked.end(), nid) != marked.end())\n            {\n                g1.push_back(nid);\n            }\n            else\n            {\n                g2.push_back(nid);\n            }\n        }\n\n        if (g1.size() > 0)\n        {\n            res_groups.push_back(std::move(g1));\n        }\n        if (g2.size() > 0)\n        {\n            res_groups.push_back(std::move(g2));\n        }\n    }\n\n    return res_groups;\n}\n\n/// go through processed groups of height 'h' and\n/// mark their parent nodes as candidates for separating\nstatic void partition_step(const NodeTree &nt, Partition &p, int h, vector<int> &height_info)\n{\n\n    for (auto &group : p.processed)\n    {\n\n        /// Check if the group should be skipped\n        /// Note that processed nodes are assumed to be of the same height\n        if (group.size() == 0 || height_info.at(group[0]) != h)\n        {\n            continue;\n        }\n\n        const auto max_kids = std::accumulate(group.begin(), group.end(), 0, [&nt](int cur, NodeID nid) {\n            auto pid = nt.getParent(nid);\n            return std::max(cur, nt.childrenCount(pid));\n        });\n\n        int alt = 0;\n        for (auto alt = 0; alt < max_kids; ++alt)\n        {\n\n            vector<NodeID> marked;\n\n            for (auto nid : group)\n            {\n                if (nt.getAlternative(nid) == alt)\n                {\n                    /// Mark the parent of nid to separate\n                    auto pid = nt.getParent(nid);\n                    marked.push_back(pid);\n                }\n            }\n\n            /// separate marked nodes from their groups\n            p.remaining = separate_marked(p.remaining, marked);\n        }\n    }\n}\n\nstatic Partition initialPartition(const NodeTree &nt)\n{\n    /// separate leaf nodes from internal\n    /// NOTE: this assumes that branch nodes have at least one child!\n    Group failed_nodes;\n    Group solution_nodes;\n    Group branch_nodes;\n    {\n        auto nodes = utils::any_order(nt);\n\n        for (auto nid : nodes)\n        {\n            auto status = nt.getStatus(nid);\n            switch (status)\n            {\n            case NodeStatus::FAILED:\n            {\n                failed_nodes.push_back(nid);\n            }\n            break;\n            case NodeStatus::SOLVED:\n            {\n                solution_nodes.push_back(nid);\n            }\n            break;\n            case NodeStatus::BRANCH:\n            {\n                branch_nodes.push_back(nid);\n            }\n            break;\n            default:\n            {\n                /// ignore other nodes for now\n            }\n            break;\n            }\n        }\n    }\n\n    Partition result{{}, {}};\n\n    if (failed_nodes.size() > 0)\n    {\n        result.processed.push_back(std::move(failed_nodes));\n    }\n\n    if (solution_nodes.size() > 0)\n    {\n        result.processed.push_back(std::move(solution_nodes));\n    }\n\n    if (branch_nodes.size() > 0)\n    {\n        result.remaining.push_back(std::move(branch_nodes));\n    }\n\n    return result;\n}\n\nstatic int calculateHeightOf(NodeID nid, const NodeTree &nt, vector<int> &height_info)\n{\n\n    int cur_max = 0;\n\n    auto kids = nt.childrenCount(nid);\n\n    if (kids == 0)\n    {\n        cur_max = 1;\n    }\n    else\n    {\n        for (auto alt = 0; alt < nt.childrenCount(nid); ++alt)\n        {\n            auto child = nt.getChild(nid, alt);\n            cur_max = std::max(cur_max, calculateHeightOf(child, nt, height_info) + 1);\n        }\n    }\n\n    height_info[nid] = cur_max;\n\n    return cur_max;\n}\n\n/// TODO: make sure the structure isn't changing anymore\n/// TODO: this does not work correctly for n-ary trees yet\nvector<SubtreePattern> runIdenticalSubtrees(const NodeTree &nt)\n{\n\n    auto label_opt = LabelOption::IGNORE_LABEL;\n\n    vector<int> height_info(nt.nodeCount());\n\n    auto max_height = calculateHeightOf(nt.getRoot(), nt, height_info);\n\n    auto max_depth = nt.depth();\n\n    auto cur_height = 1;\n\n    auto sizes = utils::calc_subtree_sizes(nt);\n\n    // 0) Initial Partition\n    auto partition = initialPartition(nt);\n\n    while (cur_height != max_height)\n    {\n\n        partition_step(nt, partition, cur_height, height_info);\n\n        /// By this point, all subtrees of height 'cur_height + 1'\n        /// should have been processed\n        set_as_processed(nt, partition, cur_height + 1, height_info);\n\n        ++cur_height;\n    }\n\n    /// Construct the result in the appropriate form\n    vector<SubtreePattern> result;\n    result.reserve(partition.processed.size());\n\n    for (auto &&group : partition.processed)\n    {\n\n        if (group.empty())\n            continue;\n\n        auto height = height_info.at(group[0]);\n\n        const auto first = group[0];\n\n        SubtreePattern pattern(height);\n\n        pattern.m_nodes = std::move(group);\n        pattern.setSize(sizes.at(first));\n\n        result.push_back(std::move(pattern));\n    }\n\n    return result;\n}\n\n/// SIMILAR SHAPE ANALYSIS\n\nstd::vector<SubtreePattern> runSimilarShapes(const NodeTree &tree, const Layout &lo)\n{\n\n    std::multiset<ShapeInfo, CompareShapes> shape_set;\n\n    auto node_order = utils::any_order(tree);\n\n    for (const auto nid : node_order)\n    {\n        shape_set.insert({nid, *lo.getShape(nid)});\n    }\n\n    auto sizes = utils::calc_subtree_sizes(tree);\n\n    std::vector<SubtreePattern> shapes;\n\n    auto it = shape_set.begin();\n    auto end = shape_set.end();\n\n    while (it != end)\n    {\n        auto upper = shape_set.upper_bound(*it);\n\n        const int height = it->shape.height();\n\n        SubtreePattern pattern(height);\n        for (; it != upper; ++it)\n        {\n            pattern.m_nodes.emplace_back(it->nid);\n        }\n\n        pattern.setSize(sizes.at(pattern.first()));\n\n        shapes.push_back(std::move(pattern));\n    }\n\n    return shapes;\n}\n\n} // namespace analysis\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/similar_subtree_analysis.hh",
    "content": "#pragma once\n\n#include <vector>\n#include \"../core.hh\"\n#include \"subtree_pattern.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace tree\n{\nclass NodeTree;\nclass Layout;\n} // namespace tree\n\nnamespace analysis\n{\n\nusing Group = std::vector<NodeID>;\n\nstruct Partition\n{\n\n    std::vector<Group> processed;\n    std::vector<Group> remaining;\n\n    Partition(std::vector<Group> &&proc, std::vector<Group> &&rem)\n        : processed(proc), remaining(rem)\n    {\n    }\n};\n\nenum class LabelOption\n{\n    IGNORE_LABEL,\n    VARS,\n    FULL\n};\n\nstruct SubtreePattern;\n\nstd::vector<SubtreePattern> runIdenticalSubtrees(const tree::NodeTree &nt);\n\nstd::vector<SubtreePattern> runSimilarShapes(const tree::NodeTree &tree, const tree::Layout &lo);\n\n} // namespace analysis\n\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/similar_subtree_window.cpp",
    "content": "#include \"similar_subtree_window.hh\"\n\n#include <QGraphicsView>\n#include <QVBoxLayout>\n#include <QSplitter>\n#include <QGraphicsSimpleTextItem>\n#include <QDebug>\n#include <QLabel>\n#include <QSpinBox>\n#include <QCheckBox>\n#include <QComboBox>\n#include <QAction>\n\n#include <iostream>\n\n#include \"../tree/shape.hh\"\n#include \"../tree/structure.hh\"\n#include \"../tree/layout.hh\"\n#include \"../tree/node_tree.hh\"\n#include \"../utils/tree_utils.hh\"\n\n#include \"path_comp.hh\"\n\n#include \"subtree_pattern.hh\"\n#include \"similar_subtree_analysis.hh\"\n#include \"../tree/subtree_view.hh\"\n\n#include \"histogram_scene.hh\"\n#include \"../utils/perf_helper.hh\"\n\nusing namespace cpprofiler::tree;\n\nusing std::vector;\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nSimilarSubtreeWindow::SimilarSubtreeWindow(QWidget *parent, const tree::NodeTree &nt)\n    : QDialog(parent), tree_(nt)\n{\n\n    histogram_.reset(new HistogramScene);\n    m_subtree_view.reset(new SubtreeView{tree_});\n\n    initInterface();\n\n    detail::PerformanceHelper phelper;\n    phelper.begin(\"analyse\");\n    analyse();\n    phelper.end();\n\n    phelper.begin(\"display patterns\");\n    displayPatterns();\n    phelper.end();\n}\n\nstatic PatternProp str2Prop(const QString &str)\n{\n    if (str == \"size\")\n        return PatternProp::SIZE;\n    if (str == \"count\")\n        return PatternProp::COUNT;\n    if (str == \"height\")\n        return PatternProp::HEIGHT;\n\n    print(\"Error:: invalid property name, fall back to defaults.\");\n\n    return defaults::SORT_TYPE;\n}\n\nstatic QString prop2str(PatternProp prop)\n{\n    switch (prop)\n    {\n    case PatternProp::COUNT:\n        return \"count\";\n    case PatternProp::HEIGHT:\n        return \"height\";\n    case PatternProp::SIZE:\n        return \"size\";\n    }\n}\n\nvoid SimilarSubtreeWindow::initInterface()\n{\n    auto globalLayout = new QVBoxLayout{this};\n\n    {\n        auto settingsLayout = new QHBoxLayout{};\n        globalLayout->addLayout(settingsLayout);\n\n        settingsLayout->addWidget(new QLabel{\"Similarity Criteria:\"}, 0, Qt::AlignRight);\n\n        auto typeChoice = new QComboBox();\n        typeChoice->addItems({\"contour\", \"subtree\"});\n        settingsLayout->addWidget(typeChoice);\n\n        connect(typeChoice, &QComboBox::currentTextChanged, [this](const QString &str) {\n            if (str == \"contour\")\n            {\n                m_sim_type = SimilarityType::SHAPE;\n            }\n            else if (str == \"subtree\")\n            {\n                m_sim_type = SimilarityType::SUBTREE;\n            }\n            analyse();\n        });\n\n        auto subsumedOption = new QCheckBox{\"Keep subsumed\"};\n        settingsLayout->addWidget(subsumedOption);\n        connect(subsumedOption, &QCheckBox::stateChanged, [this](int state) {\n            settings_.subsumed = state;\n            displayPatterns();\n        });\n\n        settingsLayout->addStretch();\n\n        settingsLayout->addWidget(new QLabel{\"Labels:\"}, 0, Qt::AlignRight);\n\n        auto labels_comp = new QComboBox{};\n        labels_comp->addItems({\"Ignore\", \"Vars only\", \"Full labels\"});\n        settingsLayout->addWidget(labels_comp);\n\n        auto hideNotHighlighted = new QCheckBox{\"Hide not selected\"};\n        hideNotHighlighted->setCheckState(Qt::Unchecked);\n        settingsLayout->addWidget(hideNotHighlighted);\n\n        connect(hideNotHighlighted, &QCheckBox::stateChanged, [this](int state) {\n            settings_.hide_subtrees = state;\n        });\n    }\n\n    auto splitter = new QSplitter{this};\n    globalLayout->addWidget(splitter, 1);\n\n    auto hist_view = new QGraphicsView{this};\n    hist_view->setAlignment(Qt::AlignLeft | Qt::AlignTop);\n    hist_view->setScene(histogram_->scene());\n\n    splitter->addWidget(hist_view);\n    splitter->addWidget(m_subtree_view->widget());\n    splitter->setSizes(QList<int>{1, 1});\n\n    {\n        globalLayout->addWidget(&path_line1_);\n        globalLayout->addWidget(&path_line2_);\n    }\n\n    /// Filters interface\n    {\n        auto filtersLayout = new QHBoxLayout{};\n        globalLayout->addLayout(filtersLayout);\n\n        /// Height Filter\n        auto heightFilter = new QSpinBox(this);\n        heightFilter->setMinimum(defaults::MIN_SUBTREE_HEIGHT);\n        heightFilter->setValue(defaults::MIN_SUBTREE_HEIGHT);\n        filtersLayout->addWidget(new QLabel(\"min height\"));\n        filtersLayout->addWidget(heightFilter);\n\n        connect(heightFilter, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this](int v) {\n            filters_.min_height = v;\n            displayPatterns();\n        });\n\n        /// Count Filter\n        auto countFilter = new QSpinBox(this);\n        countFilter->setMinimum(defaults::MIN_SUBTREE_COUNT);\n        countFilter->setValue(defaults::MIN_SUBTREE_COUNT);\n        filtersLayout->addWidget(new QLabel(\"min count\"));\n        filtersLayout->addWidget(countFilter);\n\n        connect(countFilter, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this](int v) {\n            filters_.min_count = v;\n            displayPatterns();\n        });\n\n        filtersLayout->addStretch();\n\n        /// Options for sorting\n        auto sortChoiceCB = new QComboBox();\n        sortChoiceCB->addItems({prop2str(PatternProp::SIZE),\n                                prop2str(PatternProp::HEIGHT),\n                                prop2str(PatternProp::COUNT)});\n        sortChoiceCB->setCurrentText(prop2str(defaults::SORT_TYPE));\n        filtersLayout->addWidget(new QLabel{\"sort by: \"});\n        filtersLayout->addWidget(sortChoiceCB);\n\n        connect(sortChoiceCB, &QComboBox::currentTextChanged, [this](const QString &str) {\n            settings_.sort_type = str2Prop(str);\n            displayPatterns();\n        });\n\n        /// Options for histogram\n        auto histChoiceCB = new QComboBox();\n        histChoiceCB->addItems({prop2str(PatternProp::SIZE),\n                                prop2str(PatternProp::HEIGHT),\n                                prop2str(PatternProp::COUNT)});\n        histChoiceCB->setCurrentText(prop2str(defaults::HIST_TYPE));\n        filtersLayout->addWidget(new QLabel{\"histogram: \"});\n        filtersLayout->addWidget(histChoiceCB);\n\n        connect(histChoiceCB, &QComboBox::currentTextChanged, [this](const QString &str) {\n            settings_.hist_type = str2Prop(str);\n            displayPatterns();\n        });\n    }\n\n    auto keyDown = new QAction{\"Press down\", this};\n    keyDown->setShortcuts({QKeySequence(\"Down\"), QKeySequence(\"J\")});\n    addAction(keyDown);\n\n    connect(keyDown, &QAction::triggered, histogram_.get(), &HistogramScene::nextPattern);\n\n    auto keyUp = new QAction{\"Press up\", this};\n    keyUp->setShortcuts({QKeySequence(\"Up\"), QKeySequence(\"K\")});\n    addAction(keyUp);\n\n    connect(keyUp, &QAction::triggered, histogram_.get(), &HistogramScene::prevPattern);\n\n    connect(keyUp, &QAction::triggered, [this]() {\n        update();\n    });\n\n    connect(histogram_.get(), &HistogramScene::should_be_highlighted,\n            [this](const std::vector<NodeID> &nodes) {\n                // if (settings_.hide_subtrees)\n                // {\n                emit should_be_highlighted(nodes, settings_.hide_subtrees);\n                // }\n            });\n\n    connect(histogram_.get(), &HistogramScene::pattern_selected,\n            m_subtree_view.get(), &SubtreeView::setNode);\n\n    connect(histogram_.get(), &HistogramScene::should_be_highlighted,\n            this, &SimilarSubtreeWindow::updatePathDiff);\n}\n\nSimilarSubtreeWindow::~SimilarSubtreeWindow() = default;\n\nusing std::vector;\n\nstd::ostream &operator<<(std::ostream &out, const Group &group)\n{\n\n    const auto size = group.size();\n    if (size == 0)\n    {\n        return out;\n    }\n    for (auto i = 0; i < size - 1; ++i)\n    {\n        out << (int)group[i] << \" \";\n    }\n    if (size > 0)\n    {\n        out << (int)group[size - 1];\n    }\n    return out;\n}\n\nstd::ostream &operator<<(std::ostream &out, const vector<Group> &groups)\n{\n\n    const auto size = groups.size();\n\n    if (size == 0)\n        return out;\n    for (auto i = 0; i < size - 1; ++i)\n    {\n        out << groups[i];\n        out << \"|\";\n    }\n\n    out << groups[size - 1];\n\n    return out;\n}\n\nstd::ostream &operator<<(std::ostream &out, const Partition &partition)\n{\n\n    out << \"[\";\n    out << partition.processed;\n    out << \"] [\";\n    out << partition.remaining;\n    out << \"]\";\n\n    return out;\n}\n\nstatic vector<SubtreePattern> eliminateSubsumed(const NodeTree &tree,\n                                                const vector<SubtreePattern> &patterns)\n{\n\n    std::set<NodeID> marked;\n\n    for (const auto &pattern : patterns)\n    {\n        for (auto nid : pattern.nodes())\n        {\n\n            const auto kids = tree.childrenCount(nid);\n            for (auto alt = 0; alt < kids; ++alt)\n            {\n                auto kid = tree.getChild(nid, alt);\n                marked.insert(kid);\n            }\n        }\n    }\n\n    vector<SubtreePattern> result;\n\n    for (const auto &pattern : patterns)\n    {\n\n        const auto &nodes = pattern.nodes();\n\n        /// Determine if all nodes are in 'marked':\n        auto subset = true;\n\n        for (auto nid : nodes)\n        {\n            if (marked.find(nid) == marked.end())\n            {\n                subset = false;\n                break;\n            }\n        }\n\n        if (!subset)\n        {\n            result.push_back(pattern);\n        }\n    }\n\n    return result;\n}\n\nstatic std::unique_ptr<tree::Layout> computeShapes(const NodeTree &tree)\n{\n    auto layout = std::unique_ptr<tree::Layout>(new Layout);\n    tree::VisualFlags vf;\n    tree::LayoutComputer layout_c(tree, *layout, vf);\n    layout_c.compute();\n    return std::move(layout);\n}\n\nvoid SimilarSubtreeWindow::analyse()\n{\n\n    /// TODO: make sure building is finished\n\n    result_.reset(new ss_analysis::Result);\n\n    switch (m_sim_type)\n    {\n    case SimilarityType::SUBTREE:\n    {\n        *result_ = runIdenticalSubtrees(tree_);\n    }\n    break;\n    case SimilarityType::SHAPE:\n    {\n        if (!layout_)\n        {\n            layout_ = computeShapes(tree_);\n        }\n        *result_ = runSimilarShapes(tree_, *layout_);\n    }\n    }\n\n    print(\"patterns after analysis: {}\", result_->size());\n\n    /// Always remove trivial patterns\n    detail::PerformanceHelper phelper;\n    phelper.begin(\"remove trivial\");\n    auto new_end = std::remove_if(result_->begin(), result_->end(), [this](const SubtreePattern &pattern) {\n        return pattern.count() < 2 ||\n               pattern.height() < 2;\n    });\n\n    result_->resize(std::distance(result_->begin(), new_end));\n\n    print(\"non-trivial patterns: {}\", result_->size());\n    phelper.end();\n}\n\nvoid SimilarSubtreeWindow::displayPatterns()\n{\n    /// should be initialized, but being defensive here\n    if (!result_)\n        return;\n\n    detail::PerformanceHelper phelper;\n    phelper.begin(\"make a copy\");\n    /// make a copy\n    std::vector<SubtreePattern> patterns = *result_;\n    phelper.end();\n\n    /// apply filters\n    auto new_end = std::remove_if(patterns.begin(), patterns.end(), [this](const SubtreePattern &pattern) {\n        return pattern.count() < filters_.min_count ||\n               pattern.height() < filters_.min_height;\n    });\n    patterns.resize(std::distance(patterns.begin(), new_end));\n\n    /// See if subsumed patterns should be displayed\n    if (!settings_.subsumed)\n    {\n        phelper.begin(\"subsumed elimination\");\n        /// NOTE: patterns should not contain patterns of cardinality 1\n        /// before eliminating subsumed!\n        patterns = eliminateSubsumed(tree_, patterns);\n        phelper.end();\n    }\n\n    /// sort patterns\n    switch (settings_.sort_type)\n    {\n    case PatternProp::COUNT:\n        std::sort(patterns.begin(), patterns.end(),\n                  [](const SubtreePattern &lhs, const SubtreePattern &rhs) {\n                      return lhs.count() > rhs.count();\n                  });\n        break;\n    case PatternProp::SIZE:\n        std::sort(patterns.begin(), patterns.end(),\n                  [](const SubtreePattern &lhs, const SubtreePattern &rhs) {\n                      return lhs.size() > rhs.size();\n                  });\n        break;\n    case PatternProp::HEIGHT:\n        std::sort(patterns.begin(), patterns.end(),\n                  [](const SubtreePattern &lhs, const SubtreePattern &rhs) {\n                      return lhs.height() > rhs.height();\n                  });\n        break;\n    }\n\n    histogram_->reset();\n    histogram_->setPatterns(std::move(patterns));\n    histogram_->drawPatterns(settings_.hist_type);\n}\n\n/// Walk up the tree constructing label path\nstatic std::vector<Label> labelPath(NodeID nid, const tree::NodeTree &tree)\n{\n    std::vector<Label> label_path;\n    while (nid != NodeID::NoNode)\n    {\n        auto label = tree.getLabel(nid);\n        if (label != emptyLabel)\n        {\n            label_path.push_back(label);\n        }\n        nid = tree.getParent(nid);\n    }\n\n    return label_path;\n}\n\nvoid SimilarSubtreeWindow::updatePathDiff(const std::vector<NodeID> &nodes)\n{\n    if (nodes.size() < 2)\n        return;\n\n    auto path_l = labelPath(nodes[0], tree_);\n    auto path_r = labelPath(nodes[1], tree_);\n\n    auto diff_pair = getLabelDiff(path_l, path_r);\n\n    /// unique labels on path_l concatenated\n    std::string text_l;\n\n    for (auto &label : diff_pair.first)\n    {\n        text_l += label + \" \";\n    }\n\n    std::string text_r;\n    for (auto &label : diff_pair.second)\n    {\n        text_r += label + \" \";\n    }\n\n    path_line1_.setText(text_l.c_str());\n    path_line2_.setText(text_r.c_str());\n}\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/similar_subtree_window.hh",
    "content": "\n#ifndef CPPROFILER_ANALYSIS_SIMILAR_SUBTREE_WINDOW_HH\n#define CPPROFILER_ANALYSIS_SIMILAR_SUBTREE_WINDOW_HH\n\n#include <QDialog>\n#include <QLineEdit>\n#include <memory>\n\n#include \"../core.hh\"\n#include \"subtree_pattern.hh\"\n\nclass QGraphicsScene;\n\nnamespace cpprofiler\n{\nnamespace tree\n{\nclass NodeTree;\nclass Layout;\nclass SubtreeView;\n} // namespace tree\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nstruct SubtreePattern;\n\nenum class SimilarityType\n{\n    SUBTREE,\n    SHAPE\n};\n\nclass HistogramScene;\n\n/// Text line displaying difference on the path for two subtrees\nclass PathDiffLine : public QLineEdit\n{\n  public:\n    PathDiffLine() : QLineEdit()\n    {\n        setReadOnly(true);\n    }\n};\n\nnamespace defaults\n{\nstatic constexpr int MIN_SUBTREE_COUNT = 2;\nstatic constexpr int MIN_SUBTREE_HEIGHT = 2;\nstatic constexpr PatternProp SORT_TYPE = PatternProp::SIZE;\nstatic constexpr PatternProp HIST_TYPE = PatternProp::COUNT;\n} // namespace defaults\n\nnamespace ss_analysis\n{\nusing Result = std::vector<SubtreePattern>;\n}\n\nclass SimilarSubtreeWindow : public QDialog\n{\n    Q_OBJECT\n  private:\n    // void analyse_shapes();\n\n    const tree::NodeTree &tree_;\n    std::unique_ptr<tree::Layout> layout_;\n\n    std::unique_ptr<HistogramScene> histogram_;\n\n    std::unique_ptr<tree::SubtreeView> m_subtree_view;\n\n    /// Text line displaying difference on the path for two subtrees of a pattern\n    PathDiffLine path_line1_;\n    PathDiffLine path_line2_;\n\n    // SimilarityType m_sim_type = SimilarityType::SUBTREE;\n    SimilarityType m_sim_type = SimilarityType::SHAPE;\n\n    struct SubtreeAnalysisFilters\n    {\n        int min_height = defaults::MIN_SUBTREE_HEIGHT;\n        int min_count = defaults::MIN_SUBTREE_COUNT;\n    } filters_;\n\n    struct Settings\n    {\n        /// whether to show subsumed subtrees\n        bool subsumed = false;\n        /// whether not highlighted subtrees should be hidden\n        bool hide_subtrees = false;\n        /// currently selected option for sorting\n        PatternProp sort_type = defaults::SORT_TYPE;\n        /// currently selected option for histogram drawing (rectangle length)\n        PatternProp hist_type = defaults::HIST_TYPE;\n    } settings_;\n\n    std::unique_ptr<ss_analysis::Result> result_;\n\n    void initInterface();\n\n    /// Apply filters, eliminate subsumed and display the result\n    void displayPatterns();\n\n  private slots:\n    /// Calculate the difference in label paths for the first two nodes\n    void updatePathDiff(const std::vector<NodeID> &nodes);\n\n  public:\n    SimilarSubtreeWindow(QWidget *parent, const tree::NodeTree &nt);\n\n    ~SimilarSubtreeWindow();\n\n    void analyse();\n\n  signals:\n    void should_be_highlighted(const std::vector<NodeID> &nodes, bool hide_rest);\n};\n\n} // namespace analysis\n} // namespace cpprofiler\n\n#endif\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/subtree_pattern.hh",
    "content": "#pragma once\n#include \"../core.hh\"\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\n/// Pattern properties\nenum class PatternProp\n{\n    HEIGHT,\n    COUNT,\n    SIZE\n};\n\nstruct SubtreePattern\n{\n  private:\n    int size_;\n\n  public:\n    std::vector<NodeID> m_nodes;\n    int m_height;\n    /// number of nodes in one (first) of the subtrees\n\n    explicit SubtreePattern(int height) : m_height(height)\n    {\n    }\n\n    SubtreePattern() = default;\n\n    void setSize(int size)\n    {\n        size_ = size;\n    }\n\n    int size() const\n    {\n        return size_;\n    }\n\n    int count() const\n    {\n        return static_cast<int>(m_nodes.size());\n    }\n\n    int height() const\n    {\n        return m_height;\n    }\n\n    NodeID first() const\n    {\n        return m_nodes.at(0);\n    }\n\n    const std::vector<NodeID> &nodes() const\n    {\n        return m_nodes;\n    }\n};\n} // namespace analysis\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/tree_merger.cpp",
    "content": "#include \"tree_merger.hh\"\n#include \"../execution.hh\"\n#include \"../tree/structure.hh\"\n#include \"../core.hh\"\n\n#include \"../utils/utils.hh\"\n#include \"../utils/tree_utils.hh\"\n\n#include <QStack>\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nusing namespace tree;\n\nTreeMerger::TreeMerger(const Execution &ex_l_,\n                       const Execution &ex_r_,\n                       std::shared_ptr<tree::NodeTree> tree,\n                       std::shared_ptr<analysis::MergeResult> res,\n                       std::shared_ptr<std::vector<OriginalLoc>> orig_locs)\n    : ex_l(ex_l_), ex_r(ex_r_),\n      tree_l(ex_l.tree()),\n      tree_r(ex_r.tree()),\n      res_tree(tree),\n      merge_result(res),\n      orig_locs_(orig_locs)\n{\n\n    connect(this, &QThread::finished, this, &QObject::deleteLater);\n}\n\nTreeMerger::~TreeMerger()\n{\n}\n\nstatic void find_and_replace_all(std::string &str, std::string substr_old, std::string substr_new)\n{\n    auto pos = str.find(substr_old);\n    while (pos != std::string::npos)\n    {\n        str.replace(pos, std::string(substr_old).length(), substr_new);\n        pos = str.find(substr_old);\n    }\n}\n\nstatic bool labelsEqual(std::string lhs, std::string rhs)\n{\n    /// NOTE(maxim): removes whitespaces before comparing;\n    /// this will be necessary as long as Chuffed and Gecode don't agree\n    /// on whether to put whitespaces around operators (Gecode uses ' '\n    /// for parsing logbrancher while Chuffed uses them as a delimiter\n    /// between literals)\n\n    if (lhs.substr(0, 3) == \"[i]\" || lhs.substr(0, 3) == \"[f]\")\n    {\n        lhs = lhs.substr(3);\n    }\n\n    if (rhs.substr(0, 3) == \"[i]\" || rhs.substr(0, 3) == \"[f]\")\n    {\n        rhs = rhs.substr(3);\n    }\n\n    lhs.erase(remove_if(lhs.begin(), lhs.end(), isspace), lhs.end());\n    rhs.erase(remove_if(rhs.begin(), rhs.end(), isspace), rhs.end());\n\n    find_and_replace_all(lhs, \"==\", \"=\");\n    find_and_replace_all(rhs, \"==\", \"=\");\n\n    // qDebug() << \"comparing: \" << lhs.c_str() << \" \" << rhs.c_str();\n\n    if (lhs.compare(rhs) != 0)\n    {\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool compareNodes(NodeID n1, const NodeTree &nt1,\n                         NodeID n2, const NodeTree &nt2,\n                         bool with_labels)\n{\n\n    if (n1 == NodeID::NoNode || n2 == NodeID::NoNode)\n        return false;\n\n    if (nt1.getStatus(n1) != nt2.getStatus(n2))\n        return false;\n\n    if (with_labels)\n    {\n        const auto &label1 = nt1.getLabel(n1);\n        const auto &label2 = nt2.getLabel(n2);\n        if (!labelsEqual(label1, label2))\n            return false;\n    }\n\n    return true;\n}\n\n/// Copy the subtree rooted at nid_s of nt_s as a subtree rooted at nid in nt\nstatic void copy_tree_into(NodeTree &nt, NodeID nid, const NodeTree &nt_s, NodeID nid_s)\n{\n\n    QStack<NodeID> stack;\n    QStack<NodeID> stack_s;\n    stack.push(nid);\n    stack_s.push(nid_s);\n\n    while (stack_s.size() > 0)\n    {\n        auto node_s = stack_s.pop();\n        auto node = stack.pop();\n\n        auto kids = nt_s.childrenCount(node_s);\n        auto status = nt_s.getStatus(node_s);\n\n        /// TODO: make sure only the reference to a label is stored\n        auto label = nt_s.getLabel(node_s);\n\n        nt.promoteNode(node, kids, status, label);\n\n        for (auto alt = 0; alt < kids; ++alt)\n        {\n            stack.push(nt.getChild(node, alt));\n            stack_s.push(nt_s.getChild(node_s, alt));\n        }\n    }\n}\n\nvoid create_pentagon(NodeTree &nt, NodeID nid,\n                     const NodeTree &nt_l, NodeID nid_l,\n                     const NodeTree &nt_r, NodeID nid_r)\n{\n\n    nt.promoteNode(nid, 2, NodeStatus::MERGED);\n\n    /// copy the subtree of nt_l into target_l\n    if (nid_l != NodeID::NoNode)\n    {\n        auto target_l = nt.getChild(nid, 0);\n        copy_tree_into(nt, target_l, nt_l, nid_l);\n    }\n\n    /// copy the subtree of nt_r into target_r\n    if (nid_r != NodeID::NoNode)\n    {\n        auto target_r = nt.getChild(nid, 1);\n        copy_tree_into(nt, target_r, nt_r, nid_r);\n    }\n}\n\nvoid TreeMerger::run()\n{\n\n    print(\"Merging: running...\");\n\n    /// It is quite unlikely, but this can dead-lock (needs consistent ordering)\n    utils::MutexLocker locker_l(&tree_l.treeMutex());\n    utils::MutexLocker locker_r(&tree_r.treeMutex());\n    utils::MutexLocker locker_res(&res_tree->treeMutex());\n\n    QStack<NodeID> stack_l, stack_r, stack;\n\n    auto root_l = tree_l.getRoot();\n    auto root_r = tree_r.getRoot();\n\n    stack_l.push(root_l);\n    stack_r.push(root_r);\n\n    auto root = res_tree->createRoot(0);\n\n    stack.push(root);\n\n    while (stack_l.size() > 0)\n    {\n\n        auto node_l = stack_l.pop();\n        auto node_r = stack_r.pop();\n        auto target = stack.pop();\n\n        bool equal = compareNodes(node_l, tree_l, node_r, tree_r, false);\n\n        if (equal)\n        {\n\n            const auto kids_l = tree_l.childrenCount(node_l);\n            const auto kids_r = tree_r.childrenCount(node_r);\n\n            const auto min_kids = std::min(kids_l, kids_r);\n            const auto max_kids = std::max(kids_l, kids_r);\n\n            { /// The merged tree will always have the number of children of the 'larger' tree\n                auto status = tree_l.getStatus(node_l);\n                auto label = tree_l.getLabel(node_l);\n                res_tree->promoteNode(target, max_kids, status, label);\n            }\n\n            if (min_kids == max_kids)\n            {\n                /// ----- MERGE COMPLETELY -----\n                for (auto i = max_kids - 1; i >= 0; --i)\n                {\n                    stack_l.push(tree_l.getChild(node_l, i));\n                    stack_r.push(tree_r.getChild(node_r, i));\n                    stack.push(res_tree->getChild(target, i));\n                }\n            }\n            else\n            {\n                /// ----- MERGE PARTIALLY -----\n\n                /// For every \"extra\" child\n                for (auto i = max_kids - 1; i >= min_kids; --i)\n                {\n\n                    if (kids_l > kids_r)\n                    {\n                        const auto kid_l = tree_l.getChild(node_l, i);\n                        const auto status = tree_l.getStatus(kid_l);\n\n                        /// NOTE(maxim): this is most likely the case of replaying with skipped nodes,\n                        /// so should not be compared (the same below)\n                        if (status == NodeStatus::UNDETERMINED || status == NodeStatus::SKIPPED)\n                        {\n                            continue;\n                        }\n\n                        stack_l.push(kid_l);\n                        stack_r.push(NodeID::NoNode);\n                        stack.push(res_tree->getChild(target, i));\n                    }\n                    else\n                    {\n                        const auto kid_r = tree_r.getChild(node_r, i);\n                        const auto status = tree_r.getStatus(kid_r);\n\n                        if (status == NodeStatus::UNDETERMINED || status == NodeStatus::SKIPPED)\n                        {\n                            continue;\n                        }\n\n                        stack_l.push(NodeID::NoNode);\n                        stack_r.push(kid_r);\n                        stack.push(res_tree->getChild(target, i));\n                    }\n                }\n\n                /// For every child in common\n                for (auto i = min_kids - 1; i >= 0; --i)\n                {\n                    stack_l.push(tree_l.getChild(node_l, i));\n                    stack_r.push(tree_r.getChild(node_r, i));\n                    stack.push(res_tree->getChild(target, i));\n                }\n            }\n        }\n        else\n        {\n            create_pentagon(*res_tree, target, tree_l, node_l, tree_r, node_r);\n\n            auto count_left = utils::count_descendants(tree_l, node_l);\n            auto count_right = utils::count_descendants(tree_r, node_r);\n            auto pen_item = PentagonItem{target, count_left, count_right};\n\n            merge_result->push_back(pen_item);\n        }\n    }\n\n    print(\"Merging: done\");\n}\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/analysis/tree_merger.hh",
    "content": "#pragma once\n\n#include <QThread>\n#include <memory>\n\n#include \"merging/merge_result.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace tree\n{\nclass NodeTree;\n}\n\nclass Execution;\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\nnamespace analysis\n{\n\nstruct OriginalLoc;\n\nclass TreeMerger : public QThread\n{\n\n  const Execution &ex_l;\n  const Execution &ex_r;\n\n  const tree::NodeTree &tree_l;\n  const tree::NodeTree &tree_r;\n\n  std::shared_ptr<tree::NodeTree> res_tree;\n  std::shared_ptr<MergeResult> merge_result;\n\n  std::shared_ptr<std::vector<OriginalLoc>> orig_locs_;\n\nprotected:\n  void\n  run() override;\n\npublic:\n  TreeMerger(const Execution &ex_l,\n             const Execution &ex_r,\n             std::shared_ptr<tree::NodeTree> tree,\n             std::shared_ptr<analysis::MergeResult> res,\n             std::shared_ptr<std::vector<OriginalLoc>> orig_locs);\n  ~TreeMerger();\n};\n\n} // namespace analysis\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/command_line_parser.cpp",
    "content": "\n#include \"command_line_parser.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace cl_options\n{\nQCommandLineOption paths{\"paths\", \"Use symbol table from: <file_name>.\", \"file_name\"};\nQCommandLineOption mzn{\"mzn\", \"Use MiniZinc file for tying ids to expressions: <file_name>.\", \"file_name\"};\nQCommandLineOption save_search{\"save_search\", \"Process one execution and save its search to <file_name>; terminate afterwards.\", \"file_name\"};\nQCommandLineOption save_execution{\"save_execution\", \"Process one execution and save it a database named <file_name>; terminate afterwards.\", \"file_name\"};\nQCommandLineOption save_pixel_tree{\"save_pixel_tree\", \"Process one execution and save it a database named <file_name>; terminate afterwards.\", \"file_name\"};\nQCommandLineOption pixel_tree_compression{\"pixel_tree_compression\", \"What compression factor to use for saved pixel tree. Default: 2\", \"2\"};\n} // namespace cl_options\n\nCommandLineParser::CommandLineParser()\n{\n\n    cl_parser.addHelpOption();\n    cl_parser.addOption(cl_options::paths);\n    cl_parser.addOption(cl_options::mzn);\n    cl_parser.addOption(cl_options::save_search);\n    cl_parser.addOption(cl_options::save_execution);\n    cl_parser.addOption(cl_options::save_pixel_tree);\n    cl_parser.addOption(cl_options::pixel_tree_compression);\n}\n\nvoid CommandLineParser::process(const QCoreApplication &app)\n{\n    cl_parser.process(app);\n}\n\nQString CommandLineParser::value(const QCommandLineOption &opt)\n{\n    return cl_parser.value(opt);\n}\n\nbool CommandLineParser::isSet(const QCommandLineOption &opt)\n{\n    return cl_parser.isSet(opt);\n}\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/command_line_parser.hh",
    "content": "#pragma once\n\n#include <QCommandLineParser>\n#include <QString>\n\nnamespace cpprofiler\n{\n\nnamespace cl_options\n{\nextern QCommandLineOption paths;\nextern QCommandLineOption mzn;\nextern QCommandLineOption save_search;\nextern QCommandLineOption save_execution;\nextern QCommandLineOption save_pixel_tree;\nextern QCommandLineOption pixel_tree_compression;\n} // namespace cl_options\n\nclass CommandLineParser\n{\n\n    QCommandLineParser cl_parser;\n\n  public:\n    CommandLineParser();\n\n    void process(const QCoreApplication &app);\n\n    QString value(const QCommandLineOption &opt);\n\n    bool isSet(const QCommandLineOption &opt);\n};\n\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/conductor.cpp",
    "content": "#include \"conductor.hh\"\n#include \"tcp_server.hh\"\n#include \"receiver_thread.hh\"\n#include <iostream>\n#include <thread>\n#include <QTreeView>\n#include <QGridLayout>\n#include <QPushButton>\n#include <QDebug>\n#include <QFile>\n#include <QFileDialog>\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QApplication>\n#include \"../cpp-integration/message.hpp\"\n\n#include \"execution.hh\"\n#include \"tree_builder.hh\"\n#include \"execution_list.hh\"\n#include \"execution_window.hh\"\n\n#include \"tree/node_tree.hh\"\n\n#include \"analysis/merge_window.hh\"\n#include \"analysis/tree_merger.hh\"\n\n#include \"utils/std_ext.hh\"\n#include \"utils/string_utils.hh\"\n#include \"utils/tree_utils.hh\"\n#include \"utils/path_utils.hh\"\n\n#include \"pixel_views/pt_canvas.hh\"\n\n#include <random>\n\n#include <QProgressDialog>\n\n#include \"name_map.hh\"\n#include \"db_handler.hh\"\n\nnamespace cpprofiler\n{\n\nConductor::Conductor(Options opt, QWidget* parent) : QMainWindow(parent), options_(opt)\n{\n\n    setWindowTitle(\"CP-Profiler\");\n\n    // readSettings();\n\n    auto layout = new QGridLayout();\n\n    {\n        auto window = new QWidget();\n        setCentralWidget(window);\n        window->setLayout(layout);\n    }\n\n    execution_list_.reset(new ExecutionList);\n    layout->addWidget(execution_list_->getWidget());\n\n    auto showButton = new QPushButton(\"Show Tree\");\n    layout->addWidget(showButton);\n    connect(showButton, &QPushButton::clicked, [this]() {\n        for (auto execution : execution_list_->getSelected())\n        {\n            showTraditionalView(execution);\n        }\n    });\n\n    auto mergeButton = new QPushButton(\"Merge Trees\");\n    layout->addWidget(mergeButton);\n\n    connect(mergeButton, &QPushButton::clicked, [this]() {\n        const auto selected = execution_list_->getSelected();\n\n        if (selected.size() == 2)\n        {\n            mergeTrees(selected[0], selected[1]);\n        }\n        else\n        {\n            print(\"select exactly two executions\");\n        }\n    });\n\n    auto saveButton = new QPushButton(\"Save Execution\");\n    layout->addWidget(saveButton);\n\n    /// TODO: make it clear that only the first one is saved\n    connect(saveButton, &QPushButton::clicked, [this]() {\n        for (auto e : execution_list_->getSelected())\n        {\n            saveExecution(e);\n            break;\n        }\n    });\n\n    auto loadButton = new QPushButton(\"Load Execution\");\n    layout->addWidget(loadButton);\n\n    connect(loadButton, &QPushButton::clicked, [this]() {\n        const auto fileName = QFileDialog::getOpenFileName(this, \"Open Execution\").toStdString();\n\n        if (fileName == \"\")\n            return;\n\n        const auto eid = getNextExecId();\n\n        auto ex = db_handler::load_execution(fileName.c_str(), eid);\n\n        if (!ex)\n        {\n            print(\"could not load the execution\");\n        }\n        else\n        {\n            const auto ex_id = addNewExecution(ex);\n            print(\"New execution from a database, ex_id: {}\", ex_id);\n        }\n    });\n\n    server_.reset(new TcpServer([this](intptr_t socketDesc) {\n        {\n            /// Initiate a receiver thread\n            auto receiver = new ReceiverThread(socketDesc, settings_);\n            /// Delete the receiver one the thread is finished\n            connect(receiver, &QThread::finished, receiver, &QObject::deleteLater);\n            /// Handle the start message in this connector\n            connect(receiver, &ReceiverThread::notifyStart, [this, receiver](const std::string &ex_name, int ex_id, bool restarts) {\n                handleStart(receiver, ex_name, ex_id, restarts);\n            });\n\n            receiver->start();\n        }\n    }));\n\n    listen_port_ = DEFAULT_PORT;\n\n    // See if the default port is available\n    auto res = server_->listen(QHostAddress::Any, listen_port_);\n    if (!res)\n    {\n        // If not, try any port\n        server_->listen(QHostAddress::Any, 0);\n        listen_port_ = server_->serverPort();\n    }\n\n    QString listen_message;\n    QTextStream ts(&listen_message);\n    ts << \"Listening on port \"\n       << QString::number(listen_port_)\n       << \".\";\n    auto portLabel = new QLabel(listen_message);\n    layout->addWidget(portLabel);\n\n    std::cerr << \"Ready to listen on: \" << listen_port_ << std::endl;\n}\n\nstatic int getRandomExID()\n{\n    std::mt19937 rng;\n    rng.seed(std::random_device()());\n    std::uniform_int_distribution<std::mt19937::result_type> dist(100);\n\n    return dist(rng);\n}\n\nvoid Conductor::onExecutionDone(Execution *e)\n{\n    e->tree().setDone();\n    emit executionFinish(e);\n\n    if (options_.save_search_path != \"\")\n    {\n        print(\"saving search to: {}\", options_.save_search_path);\n        saveSearch(e, options_.save_search_path.c_str());\n        QApplication::quit();\n    }\n\n    if (options_.save_execution_db != \"\")\n    {\n        print(\"saving execution to db: {}\", options_.save_execution_db);\n        db_handler::save_execution(e, options_.save_execution_db.c_str());\n\n        if(options_.save_pixel_tree_path != \"\") {\n          print(\"Saving pixel tree to file: {}\", options_.save_pixel_tree_path);\n          savePixelTree(e, options_.save_pixel_tree_path.c_str(), options_.pixel_tree_compression);\n        }\n\n        QApplication::quit();\n    }\n}\n\nint Conductor::getNextExecId() const\n{\n    int eid = getRandomExID();\n    while (executions_.find(eid) != executions_.end())\n    {\n        eid = getRandomExID();\n    }\n    return eid;\n}\n\nvoid Conductor::setMetaData(int exec_id, const std::string &group_name,\n                            const std::string &exec_name,\n                            std::shared_ptr<NameMap> nm)\n{\n    exec_meta_.insert({exec_id, {group_name, exec_name, nm}});\n\n    qDebug() << \"exec_id:\" << exec_id;\n    qDebug() << \"gr_name:\" << group_name.c_str();\n    qDebug() << \"ex_name:\" << exec_name.c_str();\n}\n\nint Conductor::getListenPort() const\n{\n    return static_cast<int>(listen_port_);\n}\n\nConductor::~Conductor() = default;\n\nvoid Conductor::handleStart(ReceiverThread *receiver, const std::string &ex_name, int ex_id, bool restarts)\n{\n\n    auto res = executions_.find(ex_id);\n\n    if (res == executions_.end() || ex_id == 0)\n    {\n\n        /// Note: metadata from MiniZinc IDE overrides that provided by the solver\n        std::string ex_name_used = ex_name;\n\n        const bool ide_used = (exec_meta_.find(ex_id) != exec_meta_.end());\n\n        if (ide_used)\n        {\n            print(\"already know metadata for this ex_id!\");\n            ex_name_used = exec_meta_[ex_id].ex_name;\n        }\n\n        /// needs a new execution\n        auto ex = addNewExecution(ex_name_used, ex_id, restarts);\n        emit executionStart(ex);\n\n        /// construct a name map\n        if (ide_used)\n        {\n            ex->setNameMap(exec_meta_[ex_id].name_map);\n            print(\"using name map for {}\", ex_id);\n        }\n        else if (options_.paths != \"\" && options_.mzn != \"\")\n        {\n            auto nm = std::make_shared<NameMap>();\n            auto success = nm->initialize(options_.paths, options_.mzn);\n            if (success)\n            {\n                ex->setNameMap(nm);\n            }\n        }\n\n        /// The builder should only be created for a new execution\n        auto builderThread = new QThread();\n        auto builder = new TreeBuilder(*ex);\n\n        builders_[ex_id] = builder;\n        builder->moveToThread(builderThread);\n\n\t// onExecutionDone must be called on the same thread as the conductor\n        connect(builder, &TreeBuilder::buildingDone, this, [this, ex]() {\n            onExecutionDone(ex);\n        });\n\n        /// is this the right time to delete the builder thread?\n        connect(builderThread, &QThread::finished, builderThread, &QObject::deleteLater);\n\n        builderThread->start();\n    }\n\n    /// obtain the builder aready assigned to this execution\n    ///(either just now or by another connection)\n    auto builder = builders_[ex_id];\n\n    connect(receiver, &ReceiverThread::newNode,\n            builder, &TreeBuilder::handleNode);\n\n    connect(receiver, &ReceiverThread::doneReceiving,\n            builder, &TreeBuilder::finishBuilding);\n}\n\nint Conductor::addNewExecution(std::shared_ptr<Execution> ex)\n{\n\n    auto ex_id = getRandomExID();\n\n    executions_[ex_id] = ex;\n    execution_list_->addExecution(*ex);\n\n    return ex_id;\n}\n\nExecution *Conductor::addNewExecution(const std::string &ex_name, int ex_id, bool restarts)\n{\n\n    if (ex_id == 0)\n    {\n        ex_id = getRandomExID();\n    }\n\n    auto ex = std::make_shared<Execution>(ex_name, ex_id, restarts);\n\n    print(\"EXECUTION_ID: {}\", ex_id);\n\n    executions_[ex_id] = ex;\n    execution_list_->addExecution(*ex);\n\n    const bool auto_show = true;\n    if (auto_show && options_.save_search_path == \"\" && options_.save_execution_db == \"\")\n    {\n        showTraditionalView(ex.get());\n    }\n\n    return ex.get();\n}\n\n\nExecution* Conductor::getExecution(int exec_id)\n{\n    auto it = executions_.find(exec_id);\n    if (it == executions_.end()) {\n        return nullptr;\n    }\n    return &*it->second;\n}\n\nExecutionWindow &Conductor::getExecutionWindow(Execution *e)\n{\n    auto maybe_view = execution_windows_.find(e);\n\n    /// create new one if doesn't already exist\n    if (maybe_view == execution_windows_.end())\n    {\n        execution_windows_[e] = new ExecutionWindow(*e, this);\n\n        auto* ex_window = execution_windows_[e];\n        ex_window->setDarkMode(dark_mode_);\n\n        connect(ex_window, &ExecutionWindow::needToSaveSearch, [this, e]() {\n            saveSearch(e);\n        });\n\n        connect(ex_window, &ExecutionWindow::nogoodsClicked, [this, e](std::vector<NodeID> ns) {\n            emit computeHeatMap(e->id(), ns);\n        });\n    }\n\n    return *execution_windows_.at(e);\n}\n\nvoid Conductor::mergeTrees(Execution *e1, Execution *e2)\n{\n\n    /// create new tree\n\n    // QProgressDialog dialog;\n\n    auto tree = std::make_shared<tree::NodeTree>();\n    auto result = std::make_shared<analysis::MergeResult>();\n\n    /// mapping from merged ids to original ids\n    auto orig_locs = std::make_shared<std::vector<analysis::OriginalLoc>>();\n\n    /// Note: TreeMerger will delete itself when finished\n    auto merger = new analysis::TreeMerger(*e1, *e2, tree, result, orig_locs);\n\n    connect(merger, &analysis::TreeMerger::finished, this,\n            [this, e1, e2, tree, result]() {\n                auto window = new analysis::MergeWindow(*e1, *e2, tree, result, this);\n                emit showMergeWindow(*window);\n                window->show();\n            });\n\n    merger->start();\n}\n\nvoid Conductor::runNogoodAnalysis(Execution *e1, Execution *e2)\n{\n\n    auto tree = std::make_shared<tree::NodeTree>();\n    auto result = std::make_shared<analysis::MergeResult>();\n\n    auto orig_locs = std::make_shared<std::vector<analysis::OriginalLoc>>();\n\n    /// Note: TreeMerger will delete itself when finished\n    auto merger = new analysis::TreeMerger(*e1, *e2, tree, result, orig_locs);\n\n    connect(merger, &analysis::TreeMerger::finished, this,\n            [this, e1, e2, tree, result]() {\n                auto window = new analysis::MergeWindow(*e1, *e2, tree, result, this);\n                emit showMergeWindow(*window);\n                window->show();\n                window->runNogoodAnalysis();\n            });\n\n    merger->start();\n}\n\nvoid Conductor::savePixelTree(Execution *e, const char* path, int compression_factor) const {\n  const auto &nt = e->tree();\n  pixel_view::PtCanvas pc(nt);\n  auto pi = pc.get_pimage();\n  pc.setCompression(compression_factor);\n  int width  = pi->pixel_size()*pc.totalSlices();\n  int height = pi->pixel_size()*nt.node_stats().maxDepth();\n  pi->resize({width,height});\n  pc.redrawAll(true);\n  pi->raw_image().save(path);\n}\n\nvoid Conductor::saveSearch(Execution *e, const char *path) const\n{\n\n    const auto &nt = e->tree();\n\n    const auto order = utils::pre_order(nt);\n\n    QFile file(path);\n    if (!file.open(QFile::WriteOnly | QFile::Truncate))\n    {\n        print(\"Error: could not open \\\"{}\\\" to save search\", path);\n        return;\n    }\n\n    QTextStream file_stream(&file);\n\n    for (auto nid : order)\n    {\n\n        /// Making sure undefined/skipped nodes are not logged\n        {\n            const auto status = nt.getStatus(nid);\n            if (status == tree::NodeStatus::SKIPPED || status == tree::NodeStatus::UNDETERMINED)\n                continue;\n        }\n\n        // Note: not every child is logged (SKIPPED and UNDET are not)\n        int kids_logged = 0;\n\n        /// Note: this temporary stream is used so that children can be\n        /// traversed first, counted, but logged after their parent\n        std::stringstream children_stream;\n\n        const auto kids = nt.childrenCount(nid);\n\n        for (auto alt = 0; alt < kids; alt++)\n        {\n\n            const auto kid = nt.getChild(nid, alt);\n\n            /// Making sure undefined/skipped children are not logged\n            const auto status = nt.getStatus(kid);\n            if (status == tree::NodeStatus::SKIPPED || status == tree::NodeStatus::UNDETERMINED)\n                continue;\n\n            ++kids_logged;\n            /// TODO: use original names in labels\n            const auto label = nt.getLabel(kid);\n\n            children_stream << \" \" << kid << \" \" << label;\n        }\n\n        file_stream << nid << \" \" << kids_logged;\n\n        /// Unexplored node on the left branch (search timed out)\n        if ((kids == 0) && (nt.getStatus(nid) == tree::NodeStatus::BRANCH))\n        {\n            file_stream << \" stop\";\n        }\n\n        file_stream << children_stream.str().c_str() << '\\n';\n    }\n}\n\nvoid Conductor::saveSearch(Execution *e) const\n{\n\n    const auto file_path = QFileDialog::getSaveFileName(nullptr, \"Save Search To a File\").toStdString();\n\n    saveSearch(e, file_path.c_str());\n}\n\nvoid Conductor::saveExecution(Execution *e)\n{\n\n    const auto file_path = QFileDialog::getSaveFileName(nullptr, \"Save Execution To a File\").toStdString();\n\n    db_handler::save_execution(e, file_path.c_str());\n\n    qDebug() << \"execution saved\";\n}\n\nvoid Conductor::readSettings()\n{\n\n    QFile file(\"settings.json\");\n\n    if (!file.exists())\n    {\n        qDebug() << \"settings.json not found\";\n        return;\n    }\n\n    file.open(QIODevice::ReadWrite | QIODevice::Text);\n\n    auto data = file.readAll();\n\n    auto json_doc = QJsonDocument::fromJson(data);\n\n    if (json_doc.isEmpty())\n    {\n        qDebug() << \"settings.json is empty\";\n        return;\n    }\n\n    if (json_doc.isObject())\n    {\n\n        auto json_obj = json_doc.object();\n\n        settings_.receiver_delay = json_obj[\"receiver_delay\"].toInt();\n    }\n\n    qDebug() << \"settings read\";\n}\n\nstatic std::string getHeatMapUrl(const NameMap &nm,\n                                 const std::unordered_map<int, int> &con_counts,\n                                 int max_count)\n{\n    /// get heat map\n\n    std::unordered_map<std::string, int> loc_intensity;\n\n    for (const auto& it : con_counts)\n    {\n        const auto con = std::to_string(it.first);\n        const auto count = it.second;\n\n        const auto &path = nm.getPath(con);\n\n        const auto path_head_elements = utils::getPathPair(path, true).model_level;\n\n        if (path_head_elements.empty())\n            continue;\n\n        const auto path_head = path_head_elements.back();\n\n        const auto location_etc = utils::split(path_head, utils::minor_sep);\n\n        // print(\"path: {}\", path);\n\n        // for (auto e : new_loc) {\n        //     print(\"element: {}\", e);\n        // }\n\n        /// path plus four ints\n        if (location_etc.size() < 5)\n            continue;\n\n        std::vector<std::string> new_loc(location_etc.begin(), location_etc.begin() + 5);\n\n        int val = static_cast<int>(std::floor(count * (255.0 / max_count)));\n\n        const auto loc_str = utils::join(new_loc, utils::minor_sep);\n\n        loc_intensity[loc_str] = std::max(loc_intensity[loc_str], val);\n    }\n\n    /// highlight url\n    std::stringstream url;\n    url << \"highlight://?\";\n\n    for (auto it : loc_intensity)\n        url << it.first << utils::minor_sep << it.second << \";\";\n\n    return url.str();\n}\n\nvoid Conductor::computeHeatMap(ExecID eid, std::vector<NodeID> ns)\n{\n\n    auto it = exec_meta_.find(eid);\n\n    if (it == exec_meta_.end())\n    {\n        print(\"No metadata for eid {} (ExecMeta)\", eid);\n        return;\n    }\n\n    /// check if namemap is there\n    const auto nm = it->second.name_map;\n\n    if (!nm)\n    {\n        print(\"no name map for eid: {}\", eid);\n        return;\n    }\n\n    const auto exec = executions_.at(eid);\n\n    const auto &sd = exec->solver_data();\n\n    std::unordered_map<int, int> con_counts;\n\n    for (const auto& n : ns)\n    {\n        const auto *cs = sd.getContribConstraints(n);\n\n        if (!cs)\n            continue;\n\n        for (int con_id : *cs)\n        {\n            con_counts[con_id]++;\n        }\n    }\n\n    int max_count = 0;\n    for (const auto& p : con_counts)\n    {\n        if (p.second > max_count)\n        {\n            max_count = p.second;\n        }\n    }\n\n    const auto url = getHeatMapUrl(*nm, con_counts, max_count);\n\n    if (url.empty())\n        return;\n\n    std::stringstream label;\n\n    for (const auto n : ns)\n    {\n        label << std::to_string(n) << ' ';\n    }\n\n    emit showNogood(url.c_str(), label.str().c_str(), false);\n}\n\nvoid Conductor::setDarkMode(bool d)\n{\n    dark_mode_ = d;\n    for (auto& it : execution_windows_) {\n        it.second->setDarkMode(d);\n    }\n}\n\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\n\nvoid Conductor::showTraditionalView(Execution *e)\n{\n\n    auto &window = getExecutionWindow(e);\n\n    emit showExecutionWindow(window);\n    window.show();\n}\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/conductor.hh",
    "content": "#ifndef CPPROFILER_CONDUCTOR_HH\n#define CPPROFILER_CONDUCTOR_HH\n\n#include <QMainWindow>\n#include <QStandardItemModel>\n#include <map>\n#include <memory>\n#include <unordered_map>\n\n#include \"core.hh\"\n#include \"options.hh\"\n#include \"settings.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace analysis\n{\nclass MergeWindow;\n}\n\nclass TcpServer;\nclass Execution;\nclass ExecutionList;\nclass ExecutionWindow;\nclass ReceiverThread;\nclass TreeBuilder;\nclass NameMap;\n\nstruct ExecMeta\n{\n    std::string group_name;\n    std::string ex_name;\n    std::shared_ptr<NameMap> name_map;\n};\n\nclass Conductor : public QMainWindow\n{\n    Q_OBJECT\n\n  public:\n    Conductor(Options opt, QWidget* parent = nullptr);\n\n    ~Conductor();\n\n    void handleStart(ReceiverThread *receiver, const std::string &ex_name,\n                     int ex_id, bool restarts);\n\n    Execution *addNewExecution(const std::string &ex_name, int ex_id = 0,\n                               bool restarts = false);\n\n    /// Add existing execution to the displayed list; return generated execution id\n    int addNewExecution(std::shared_ptr<Execution> ex);\n\n    void showTraditionalView(Execution *e);\n\n    void mergeTrees(Execution *e1, Execution *e2);\n\n    void savePixelTree(Execution *e, const char *path, int compression_factor = 2) const;\n\n    void saveSearch(Execution *e, const char *path) const;\n    void saveSearch(Execution *e) const;\n\n    void runNogoodAnalysis(Execution *e1, Execution *e2);\n\n    ExecutionWindow &getExecutionWindow(Execution *e);\n\n    int getListenPort() const;\n\n    int getNextExecId() const;\n\n    void setMetaData(int exec_id, const std::string &group_name,\n                     const std::string &execution_name,\n                     std::shared_ptr<NameMap> nm);\n\n    Execution* getExecution(int exec_id);\n\n  signals:\n\n    void executionStart(cpprofiler::Execution *e);\n    void executionFinish(cpprofiler::Execution *e);\n\n    /// For a heatmap in the IDE\n    void showNogood(QString url, QString name, bool record);\n\n    void showExecutionWindow(ExecutionWindow& e);\n\n    void showMergeWindow(analysis::MergeWindow& m);\n  private:\n    void saveExecution(Execution *e);\n\n    void readSettings();\n\n    void onExecutionDone(Execution *e);\n\n    // void getSelectedExecutions\n\n    static constexpr quint16 DEFAULT_PORT = 6565;\n\n    Settings settings_;\n\n    /// Port number opened for solvers to connect to\n    quint16 listen_port_;\n\n    std::unique_ptr<TcpServer> server_;\n\n    Options options_;\n\n    /// a map from execution id to an execution\n    std::unordered_map<int, std::shared_ptr<Execution>> executions_;\n\n    /// a map from exec_id to its builder\n    std::unordered_map<int, TreeBuilder *> builders_;\n\n    std::unique_ptr<ExecutionList> execution_list_;\n\n    std::unordered_map<ExecID, ExecMeta> exec_meta_;\n\n    std::unordered_map<const Execution *, ExecutionWindow*>\n        execution_windows_;\n\n    bool dark_mode_ = false;\n\n  public slots:\n\n    void computeHeatMap(ExecID eid, std::vector<NodeID>);\n\n    void setDarkMode(bool d);\n};\n\n} // namespace cpprofiler\n\n#endif\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/config.hh",
    "content": "#pragma once\n\n#include <algorithm>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nnamespace layout\n{\nconstexpr int dist_y = 36;\nconstexpr int min_dist_x = 16;\n} // namespace layout\n\nnamespace traditional\n{\nconstexpr int MAX_NODE_W = 22;\nconstexpr int HALF_MAX_NODE_W = MAX_NODE_W / 2;\n\nconstexpr int BRANCH_WIDTH = 18;\nconstexpr int HALF_BRANCH_W = BRANCH_WIDTH / 2;\n\nconstexpr int FAILED_WIDTH = 14;\nconstexpr int HALF_FAILED_WIDTH = FAILED_WIDTH / 2;\n\nconstexpr int SOL_WIDTH = 18;\nconstexpr int HALF_SOL_W = SOL_WIDTH / 2;\n\nconstexpr int UNDET_WIDTH = BRANCH_WIDTH;\nconstexpr int HALF_UNDET_WIDTH = BRANCH_WIDTH / 2;\n\nconstexpr int SKIPPED_WIDTH = FAILED_WIDTH;\nconstexpr int HALF_SKIPPED_WIDTH = SKIPPED_WIDTH / 2;\n\nconstexpr int COLLAPSED_WIDTH = 36;\nconstexpr int HALF_COLLAPSED_WIDTH = COLLAPSED_WIDTH / 2;\nconstexpr int COLLAPSED_DEPTH = layout::dist_y + 14;\n\nconstexpr int PENTAGON_WIDTH = BRANCH_WIDTH;\nconstexpr int PENTAGON_HALF_W = PENTAGON_WIDTH / 2;\nconstexpr int PENTAGON_THIRD_W = PENTAGON_WIDTH / 3;\n\nconstexpr int BIG_PENTAGON_WIDTH = MAX_NODE_W;\nconstexpr int BIG_PENTAGON_HALF_W = BIG_PENTAGON_WIDTH / 2;\nconstexpr int BIG_PENTAGON_THIRD_W = BIG_PENTAGON_WIDTH / 3;\n\n/// there is no constexpr std::max until C++14\nstatic_assert(MAX_NODE_W >= BRANCH_WIDTH,   \"MAX_NODE_W >= BRANCH_WIDTH\");\nstatic_assert(MAX_NODE_W >= FAILED_WIDTH,   \"MAX_NODE_W >= FAILED_WIDTH\");\nstatic_assert(MAX_NODE_W >= SOL_WIDTH,      \"MAX_NODE_W >= SOL_WIDTH\");\nstatic_assert(MAX_NODE_W >= UNDET_WIDTH,    \"MAX_NODE_W >= UNDET_WIDTH\");\nstatic_assert(MAX_NODE_W >= PENTAGON_WIDTH, \"MAX_NODE_W >= PENTAGON_WIDTH\");\n\nconstexpr int SHADOW_OFFSET = 3.0;\n} // namespace traditional\n\nnamespace lantern\n{\nstatic constexpr int BASE_HEIGHT = 14;\nstatic constexpr int HALF_WIDTH = 20;\nstatic constexpr int PRECISION = 127;\nstatic constexpr int MAX_LEVELS = 5;\n/// delta height for lanterns with respect to the size increase by 1\nstatic constexpr float K = (float)(layout::dist_y * (MAX_LEVELS - 1) - BASE_HEIGHT) / PRECISION;\n} // namespace lantern\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/core.cpp",
    "content": "#include \"core.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nstd::ostream &operator<<(std::ostream &os, const NodeStatus &ns)\n{\n    switch (ns)\n    {\n    case NodeStatus::SOLVED:\n    {\n        os << \"SOLVED\";\n    }\n    break;\n    case NodeStatus::FAILED:\n    {\n        os << \"FAILED\";\n    }\n    break;\n    case NodeStatus::BRANCH:\n    {\n        os << \"BRANCH\";\n    }\n    break;\n    case NodeStatus::SKIPPED:\n    {\n        os << \"SKIPPED\";\n    }\n    break;\n    case NodeStatus::UNDETERMINED:\n    {\n        os << \"UNDETERMINED\";\n    }\n    break;\n    case NodeStatus::MERGED:\n    {\n        os << \"MERGED\";\n    }\n    break;\n    default:\n    {\n        os << \"<unknown node status>\";\n    }\n    break;\n    }\n    return os;\n}\n\n} // namespace tree\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\nconst Nogood Nogood::empty(\"\");\n}"
  },
  {
    "path": "cp-profiler/src/cpprofiler/core.hh",
    "content": "#ifndef CPPROFILER_GLOBAL_HH\n#define CPPROFILER_GLOBAL_HH\n\n#include <ostream>\n#include \"tree/node_id.hh\"\n#include \"utils/debug_mutex.hh\"\n#include \"utils/std_ext.hh\"\n#include \"utils/debug.hh\"\n\nusing cpprofiler::tree::NodeID;\nusing Label = std::string;\nusing ExecID = int;\n\nusing NogoodID = cpprofiler::tree::NodeID;\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nenum class NodeStatus\n{\n    SOLVED = 0,\n    FAILED = 1,\n    BRANCH = 2,\n    SKIPPED = 3,\n    UNDETERMINED = 4,\n    MERGED = 5\n};\n\nstd::ostream &operator<<(std::ostream &os, const NodeStatus &ns);\n\n} // namespace tree\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\n\ntypedef std::string Info;\n\nclass Nogood\n{\n    /// whether the nogood has a renamed (nice) version\n    bool renamed_;\n    /// raw flatzinc nogood\n    std::string orig_ng_;\n    /// built using a name map\n    std::string nice_ng_;\n\n  public:\n    explicit Nogood(const std::string &orig) : renamed_(false), orig_ng_(orig) {}\n\n    Nogood(const std::string &orig, const std::string &renamed) : renamed_(true), orig_ng_(orig), nice_ng_(renamed) {}\n\n    const std::string &renamed() const\n    {\n        return nice_ng_;\n    }\n\n    bool has_renamed() const { return renamed_; }\n\n    /// Get the best name available (renamed if present)\n    const std::string &get() const { return renamed_ ? nice_ng_ : orig_ng_; }\n\n    const std::string &original() const\n    {\n        return orig_ng_;\n    }\n\n    static const Nogood empty;\n};\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/db_handler.cpp",
    "content": "#include \"db_handler.hh\"\n\n#include <fstream>\n#include \"utils/debug.hh\"\n#include <QSqlDatabase>\n#include <QSqlQuery>\n#include \"execution.hh\"\n#include \"utils/tree_utils.hh\"\n#include \"utils/perf_helper.hh\"\n\nnamespace cpprofiler\n{\nnamespace db_handler\n{\n\ntypedef int (*SQL_Callback)(void *, int, char **, char **);\n\nstruct NodeData\n{\n    NodeID nid;\n    NodeID pid;\n    int alt;\n    int kids;\n    tree::NodeStatus status;\n    std::string label;\n};\n\nstruct BookmarkItem\n{\n    NodeID nid;\n    std::string text;\n};\n\nstruct NogoodItem\n{\n    NodeID nid;\n    std::string text;\n};\n\nstruct InfoItem\n{\n    NodeID nid;\n    std::string text;\n};\n\nstatic int count_nodes(QSqlDatabase *db)\n{\n    const auto query = \"select count(*) from Nodes;\";\n    QSqlQuery count_stmt(*db);\n    count_stmt.prepare(query);\n    count_stmt.exec();\n    count_stmt.next();\n    return count_stmt.value(0).toInt();\n}\n\n/// Reads all nodes from a database and builds the tree; returns `true` on success\nstatic bool read_nodes(QSqlDatabase *db, Execution &ex)\n{\n    const auto query = \"select * from Nodes;\";\n\n    QSqlQuery select_stmt (*db);\n    select_stmt.prepare(query);\n\n    auto &tree = ex.tree();\n\n    const auto total_nodes = count_nodes(db);\n    print(\"We have {} nodes\", total_nodes);\n\n    tree.db_initialize(total_nodes);\n\n    bool success = select_stmt.exec();\n\n    if(!success) return false;\n\n    while (select_stmt.next())\n    {\n        const auto nid = NodeID(select_stmt.value(0).toInt());\n        const auto pid = NodeID(select_stmt.value(1).toInt());\n        const auto alt = select_stmt.value(2).toInt();\n        const auto kids = select_stmt.value(3).toInt();\n        const auto status = tree::NodeStatus(select_stmt.value(4).toInt());\n        const auto label = select_stmt.value(5).toString().toStdString();\n\n        if (pid == NodeID::NoNode)\n        {\n            tree.db_createRoot(nid, label);\n        }\n        else\n        {\n            tree.db_addChild(nid, pid, alt, status, label);\n        }\n    }\n\n    return success;\n}\n\n/// Read all bookmarks from the database\nstatic bool read_bookmarks(QSqlDatabase *db, Execution &ex)\n{\n    const auto query = \"select * from Bookmarks;\";\n    QSqlQuery select_bm_(*db);\n    select_bm_.prepare(query);\n\n    bool success = select_bm_.exec();\n    if(!success) return false;\n\n    auto &ud = ex.userData();\n\n    while (select_bm_.next())\n    {\n        const auto nid = NodeID(select_bm_.value(0).toInt());\n        const auto bm_text = select_bm_.value(1).toString().toStdString();\n\n        ud.setBookmark(nid, bm_text);\n    }\n\n    return success;\n}\n\n/// Read all Info from the database\nstatic bool read_info(QSqlDatabase *db, Execution &ex)\n{\n    const auto query = \"select * from Info;\";\n    QSqlQuery select_info_ (*db);\n    select_info_.prepare(query);\n\n    bool success = select_info_.exec();\n    if(!success) return false;\n\n    auto &sd = ex.solver_data();\n\n    while (select_info_.next())\n    {\n        const auto nid = NodeID(select_info_.value(0).toInt());\n        const auto info_text = select_info_.value(1).toString().toStdString();\n\n        sd.setInfo(nid, {info_text});\n    }\n\n    return success;\n}\n\n/// Read all nogoods from the database\nstatic bool read_nogoods(QSqlDatabase *db, Execution &ex)\n{\n    const auto query = \"select * from Nogoods;\";\n    QSqlQuery select_ng_(*db);\n    select_ng_.prepare(query);\n\n    bool success = select_ng_.exec();\n\n    auto &sd = ex.solver_data();\n\n    while (select_ng_.next())\n    {\n        const auto nid = NodeID(select_ng_.value(0).toInt());\n        const auto ng_text = select_ng_.value(1).toString().toStdString();\n\n        sd.setNogood(nid, {ng_text});\n    }\n\n    return success;\n}\n\nstatic void insert_node(QSqlQuery *stmt, NodeData nd)\n{\n    stmt->finish();\n\n    stmt->addBindValue(static_cast<int>(nd.nid));\n    stmt->addBindValue(static_cast<int>(nd.pid));\n    stmt->addBindValue(static_cast<int>(nd.alt));\n    stmt->addBindValue(static_cast<int>(nd.kids));\n    stmt->addBindValue(static_cast<int>(nd.status));\n    stmt->addBindValue(nd.label.c_str());\n\n    if (!stmt->exec())\n    {\n        print(\"ERROR: could not execute db statement\");\n    }\n}\n\nstatic void save_nodes(QSqlDatabase *db, const Execution *ex)\n{\n    const char *insert_query = \"INSERT INTO Nodes \\\n                         (NodeID, ParentID, Alternative, NKids, Status, Label) \\\n                         VALUES (?,?,?,?,?,?);\";\n\n    QSqlQuery insert_bm(*db);\n    insert_bm.prepare(insert_query);\n\n    const auto &tree = ex->tree();\n    const auto order = utils::pre_order(tree);\n\n    constexpr static int TRANSACTION_SIZE = 50000;\n\n    db->transaction();\n    for (auto i = 0u; i < order.size(); ++i)\n    {\n        const auto nid = order[i];\n        const auto pid = tree.getParent(nid);\n        const auto alt = tree.getAlternative(nid);\n        const auto kids = tree.childrenCount(nid);\n        const auto status = tree.getStatus(nid);\n        const auto label = tree.getLabel(nid);\n\n        insert_node(&insert_bm, {nid, pid, alt, kids, status, label});\n\n        if (i % TRANSACTION_SIZE == TRANSACTION_SIZE - 1)\n        {\n            db->commit();\n            db->transaction();\n        }\n    }\n    db->commit();\n}\n\nstatic void insert_bookmark(QSqlQuery *stmt, BookmarkItem bi)\n{\n    stmt->finish();\n\n    stmt->addBindValue(static_cast<int>(bi.nid));\n    stmt->addBindValue(bi.text.c_str());\n\n    if (!stmt->exec())\n    {\n        print(\"ERROR: could not execute DB statement\");\n    }\n}\n\nstatic void insert_nogood(QSqlQuery *stmt, NogoodItem ngi)\n{\n\n    stmt->finish();\n\n    stmt->addBindValue(static_cast<int>(ngi.nid));\n    stmt->addBindValue(ngi.text.c_str());\n\n    if (!stmt->exec())\n    {\n        print(\"ERROR: could not execute DB statement\");\n    }\n}\n\nstatic void save_nogoods(QSqlDatabase *db, const Execution *ex)\n{\n    const char *query = \"INSERT INTO Nogoods \\\n                         (NodeID, Nogood) \\\n                         VALUES (?,?);\";\n    QSqlQuery insert_ng_stmt(*db);\n    insert_ng_stmt.prepare(query);\n\n    const auto &nt = ex->tree();\n    const auto &sd = ex->solver_data();\n\n    const auto nodes = utils::any_order(nt);\n\n\n    db->transaction();\n\n    for (const auto n : nodes)\n    {\n        /// TODO: should save renamed instead?\n        const auto &text = sd.getNogood(n).original();\n        if (text != \"\")\n        {\n            insert_nogood(&insert_ng_stmt, {n, text});\n        }\n    }\n\n    db->commit();\n}\n\nstatic void insert_info(QSqlQuery *stmt, InfoItem ii)\n{\n    stmt->finish();\n\n    stmt->addBindValue(static_cast<int>(ii.nid));\n    stmt->addBindValue(ii.text.c_str());\n\n    if (!stmt->exec())\n    {\n        print(\"ERROR: could not execute DB statement\");\n    }\n}\n\nstatic void save_info(QSqlDatabase *db, const Execution *ex)\n{\n    const char *query = \"INSERT INTO Info \\\n                         (NodeID, Info) \\\n                         VALUES (?,?);\";\n\n    QSqlQuery insert_ng_stmt(*db);\n    insert_ng_stmt.prepare(query);\n\n    const auto &nt = ex->tree();\n    const auto &sd = ex->solver_data();\n\n    const auto nodes = utils::any_order(nt);\n\n    db->transaction();\n    for (const auto n : nodes)\n    {\n        const auto &text = sd.getInfo(n);\n        if (text != \"\")\n        {\n            insert_info(&insert_ng_stmt, {n, text});\n        }\n    }\n    db->commit();\n}\n\nstatic void save_user_data(QSqlDatabase *db, const Execution *ex)\n{\n\n    const char *query = \"INSERT INTO Bookmarks \\\n                         (NodeID, Bookmark) \\\n                         VALUES (?,?);\";\n\n    QSqlQuery insert_bm_stmt(*db);\n    insert_bm_stmt.prepare(query);\n\n    const auto &ud = ex->userData();\n    const auto nodes = ud.bookmarkedNodes();\n\n\n    db->transaction();\n    for (const auto n : nodes)\n    {\n        const auto &text = ud.getBookmark(n);\n        insert_bookmark(&insert_bm_stmt, {n, text});\n    }\n    db->commit();\n}\n\n/// Create a file at `path` (or overwrite it) and associate it with `db`\nstatic bool create_db(QSqlDatabase* db)\n{\n    const auto create_nodes = \"CREATE TABLE Nodes( \\\n      NodeID INTEGER PRIMARY KEY, \\\n      ParentID int NOT NULL, \\\n      Alternative int NOT NULL, \\\n      NKids int NOT NULL, \\\n      Status int, \\\n      Label varchar(256) \\\n      );\";\n\n    const auto create_bookmarks = \"Create TABLE Bookmarks( \\\n        NodeID INTEGER PRIMARY KEY, \\\n        Bookmark varchar(8) \\\n        );\";\n\n    const auto create_nogoods = \"CREATE TABLE Nogoods( \\\n        NodeID INTEGER PRIMARY KEY, \\\n        Nogood varchar(8) \\\n    );\";\n\n    const auto create_info = \"CREATE TABLE Info( \\\n        NodeID INTEGER PRIMARY KEY, \\\n        Info TEXT \\\n    );\";\n\n    QSqlQuery query (*db);\n\n    if(!query.exec(create_nodes)) return false;\n    if(!query.exec(create_bookmarks)) return false;\n    if(!query.exec(create_nogoods)) return false;\n    if(!query.exec(create_info)) return false;\n\n    return true;\n}\n\n/// this takes (without nogoods) under 2 sec for a ~1.5M nodes (golomb 10)\nvoid save_execution(const Execution *ex, const char *path)\n{\n    print(\"creating file: {}\", path);\n    std::ofstream file(path);\n    file.close();\n\n    QSqlDatabase db = QSqlDatabase::addDatabase(\"QSQLITE\");\n    db.setDatabaseName(path);\n\n    if (!db.open()) {\n        print(\"Cannot open database file.\");\n        return;\n    }\n\n    perfHelper.begin(\"save execution\");\n\n    if(!create_db(&db)) {\n        print(\"Cannot create tables in database.\");\n        return;\n    }\n\n    save_nodes(&db, ex);\n\n    save_user_data(&db, ex);\n\n    const auto &sd = ex->solver_data();\n\n    if (sd.hasNogoods())\n    {\n        save_nogoods(&db, ex);\n    }\n\n    if (sd.hasInfo())\n    {\n        save_info(&db, ex);\n    }\n\n    perfHelper.end();\n}\n\nstd::shared_ptr<Execution> load_execution(const char *path, ExecID eid)\n{\n    QSqlDatabase db = QSqlDatabase::addDatabase(\"QSQLITE\");\n    db.setDatabaseName(path);\n\n    if (!db.open())\n        return nullptr;\n\n    auto ex = std::make_shared<Execution>(path, eid);\n\n    read_nodes(&db, *ex);\n\n    read_bookmarks(&db, *ex);\n\n    read_nogoods(&db, *ex);\n\n    read_info(&db, *ex);\n\n    ex->tree().setDone();\n\n    db.close();\n\n    return ex;\n}\n} // namespace db_handler\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/db_handler.hh",
    "content": "#pragma once\n\n#include \"core.hh\"\n\nnamespace cpprofiler\n{\n\nclass Execution;\n\nnamespace db_handler\n{\n\n/// Save existing execution `ex` to a file at `path`\nvoid save_execution(const Execution *ex, const char *path);\n\n/// Create a new execution based on a db file at `path`\nstd::shared_ptr<Execution> load_execution(const char *path, ExecID eid = 0);\n} // namespace db_handler\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/execution.cpp",
    "content": "\n#include \"execution.hh\"\n#include \"name_map.hh\"\n#include \"tree/node_tree.hh\"\n#include \"user_data.hh\"\n#include \"utils/debug.hh\"\n\n#include <iostream>\n\nnamespace cpprofiler\n{\n\nstd::string Execution::name() { return name_; }\n\nExecution::Execution(const std::string &name, ExecID id, bool restarts)\n    : id_(id), name_{name}, tree_{new tree::NodeTree()},\n      solver_data_(utils::make_unique<SolverData>()),\n      user_data_(utils::make_unique<UserData>()), m_is_restarts(restarts)\n{\n    tree_->setSolverData(solver_data_);\n\n    /// need to create a dummy root node\n    if (restarts)\n    {\n        print(\"restart execution!\");\n        tree_->createRoot(0, \"root\");\n    }\n}\n\nvoid Execution::setNameMap(std::shared_ptr<const NameMap> nm)\n{\n    name_map_ = nm;\n    tree_->setNameMap(nm);\n}\n\nbool Execution::doesRestarts() const { return m_is_restarts; }\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/execution.hh",
    "content": "#ifndef CPPROFILER_EXECUTION_HH\n#define CPPROFILER_EXECUTION_HH\n\n#include \"solver_data.hh\"\n#include \"tree/node.hh\"\n#include \"tree/node_tree.hh\"\n#include \"user_data.hh\"\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <unordered_map>\n\nnamespace cpprofiler\n{\n\nclass Execution\n{\n\n    const ExecID id_;\n\n    const std::string name_;\n\n    std::unique_ptr<tree::NodeTree> tree_;\n\n    std::shared_ptr<SolverData> solver_data_;\n\n    std::shared_ptr<const NameMap> name_map_;\n\n    std::unique_ptr<UserData> user_data_;\n\n    /// Whether the execution contains restarts\n    bool m_is_restarts;\n\n  public:\n    std::string name();\n\n    ExecID id() { return id_; }\n\n    explicit Execution(const std::string &name, ExecID id = 0, bool restarts = false);\n\n    void setNameMap(std::shared_ptr<const NameMap> nm);\n\n    SolverData &solver_data() { return *solver_data_; }\n    const SolverData &solver_data() const { return *solver_data_; }\n\n    inline UserData &userData() { return *user_data_; }\n    inline const UserData &userData() const { return *user_data_; };\n\n    tree::NodeTree &tree() { return *tree_; }\n    const tree::NodeTree &tree() const { return *tree_; }\n\n    bool hasNogoods() const { return solver_data_->hasNogoods(); }\n\n    const NameMap *nameMap() const { return name_map_.get(); }\n\n    bool doesRestarts() const;\n};\n\n} // namespace cpprofiler\n\n\nQ_DECLARE_METATYPE(cpprofiler::Execution*);\n\n#endif\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/execution_list.cpp",
    "content": "#include \"execution_list.hh\"\n#include \"execution.hh\"\n\n#include <QDebug>\n#include <iostream>\n\nnamespace cpprofiler\n{\n\nExecutionItem::ExecutionItem(Execution &e)\n    : QStandardItem(e.name().c_str()), m_execution(e) {}\n\nExecution *ExecutionItem::get_execution()\n{\n    return &m_execution;\n}\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\n\nExecutionList::ExecutionList()\n{\n    m_tree_view.setHeaderHidden(true);\n    m_tree_view.setModel(&m_execution_tree_model);\n    m_tree_view.setSelectionMode(QAbstractItemView::MultiSelection);\n}\n\nvoid ExecutionList::addExecution(Execution &e)\n{\n    m_execution_tree_model.appendRow(new ExecutionItem{e});\n}\n\nstd::vector<Execution *> ExecutionList::getSelected()\n{\n    auto selected_model = m_tree_view.selectionModel();\n    auto selected = selected_model->selectedRows();\n\n    std::vector<Execution *> result;\n\n    for (auto idx : selected)\n    {\n\n        auto item = m_execution_tree_model.itemFromIndex(idx);\n        auto exec_item = dynamic_cast<ExecutionItem *>(item);\n\n        if (exec_item)\n        {\n            result.push_back(exec_item->get_execution());\n        }\n    }\n\n    return result;\n}\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/execution_list.hh",
    "content": "#ifndef CPPROFILER_EXECUTION_LIST\n#define CPPROFILER_EXECUTION_LIST\n\n#include <QStandardItemModel>\n#include <QTreeView>\n#include <memory>\n#include <vector>\n\nnamespace cpprofiler\n{\n\nclass Execution;\n\nclass ExecutionItem : public QStandardItem\n{\n    Execution &m_execution;\n\n  public:\n    ExecutionItem(Execution &e);\n    Execution *get_execution();\n};\n\nclass ExecutionList\n{\n\n    QTreeView m_tree_view;\n    QStandardItemModel m_execution_tree_model;\n\n  public:\n    ExecutionList();\n\n    QWidget *getWidget() { return &m_tree_view; }\n\n    void addExecution(Execution &e);\n\n    std::vector<Execution *> getSelected();\n};\n\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/execution_window.cpp",
    "content": "#include \"execution_window.hh\"\n\n#include \"tree/traditional_view.hh\"\n#include \"pixel_views/pt_canvas.hh\"\n#include \"pixel_views/icicle_canvas.hh\"\n\n#include \"execution.hh\"\n#include \"user_data.hh\"\n\n#include <QGridLayout>\n#include <QTableView>\n#include <QDockWidget>\n#include <QHeaderView>\n#include <QStandardItemModel>\n#include <QSlider>\n#include <QSplitter>\n#include <QDebug>\n#include <QMenuBar>\n#include <QTextEdit>\n#include <QFile>\n#include <QTimer>\n#include <QStatusBar>\n#include <QHBoxLayout>\n#include <QToolButton>\n#include <QToolBar>\n\n#include \"tree/node_tree.hh\"\n\n#include \"utils/maybe_caller.hh\"\n\n#include \"analysis/similar_subtree_window.hh\"\n\n#include \"stats_bar.hpp\"\n\nnamespace cpprofiler\n{\n\nLanternMenu::LanternMenu() : QWidget()\n{\n    auto layout = new QHBoxLayout(this);\n\n    slider_ = new QSlider(Qt::Horizontal);\n    slider_->setRange(0, 100);\n    layout->addWidget(slider_);\n\n    auto label_desc = new QLabel(\"limit:\");\n    layout->addWidget(label_desc);\n\n    auto label = new QLabel(\"0\");\n    layout->addWidget(label);\n    label->setAlignment(Qt::AlignVCenter | Qt::AlignRight);\n    label->setMinimumWidth(55);\n\n    connect(slider_, &QSlider::valueChanged, [this, label](int val) {\n        label->setText(QString::number(val));\n        emit limit_changed(val);\n    });\n}\n\nExecutionWindow::ExecutionWindow(Execution &ex, QWidget* parent)\n    : QMainWindow(parent), execution_(ex)\n{\n    const auto &tree = ex.tree();\n    traditional_view_.reset(new tree::TraditionalView(tree, ex.userData(), ex.solver_data()));\n\n    connect(traditional_view_.get(), &tree::TraditionalView::nodeSelected,\n            this, &ExecutionWindow::nodeSelected);\n\n    connect(traditional_view_.get(), &tree::TraditionalView::nogoodsClicked, this, &ExecutionWindow::nogoodsClicked);\n\n    connect(this, &ExecutionWindow::nodeSelected, traditional_view_.get(), &tree::TraditionalView::setCurrentNode);\n\n    maybe_caller_.reset(new utils::MaybeCaller(30));\n\n    auto layout = new QGridLayout();\n\n    statusBar()->showMessage(\"Ready\");\n\n    auto stats_bar = new NodeStatsBar(this, tree.node_stats());\n    statusBar()->addPermanentWidget(stats_bar);\n\n    resize(500, 700);\n\n    {\n        auto widget = new QWidget();\n        setCentralWidget(widget);\n        widget->setLayout(layout);\n    }\n\n    layout->addWidget(traditional_view_->widget(), 1, 0, 2, 1);\n\n    {\n\n        auto sb = new QSlider(Qt::Vertical);\n\n        sb->setMinimum(1);\n        sb->setMaximum(100);\n\n        constexpr int start_scale = 50;\n        sb->setValue(start_scale);\n        layout->addWidget(sb, 2, 1);\n\n        traditional_view_->setScale(start_scale);\n\n        connect(sb, &QSlider::valueChanged,\n                traditional_view_.get(), &tree::TraditionalView::setScale);\n\n        connect(&execution_.tree(), &tree::NodeTree::structureUpdated,\n                traditional_view_.get(), &tree::TraditionalView::setLayoutOutdated);\n\n        connect(&execution_.tree(), &tree::NodeTree::failedSubtreeClosed, [this](NodeID n) {\n            traditional_view_->hideNode(n);\n        });\n\n        {\n            auto statsUpdateTimer = new QTimer(this);\n            connect(statsUpdateTimer, &QTimer::timeout, stats_bar, &NodeStatsBar::update);\n            statsUpdateTimer->start(16);\n        }\n    }\n\n    {\n        lantern_widget = new LanternMenu();\n        layout->addWidget(lantern_widget, 3, 0);\n        lantern_widget->hide();\n\n        // connect(lantern_widget, &LanternMenu::limit_changed,\n        //         traditional_view_.get(), &tree::TraditionalView::hideBySize);\n        connect(lantern_widget, &LanternMenu::limit_changed, [this](int limit) {\n            maybe_caller_->call([this, limit]() {\n                traditional_view_->hideBySize(limit);\n            });\n        });\n    }\n\n    {\n//        auto menuBar = new QMenuBar(0);\n//// Don't add the menu bar on Mac OS X\n//#ifndef Q_WS_MAC\n//        /// is this needed???\n//        setMenuBar(menuBar);\n//#endif\n\n        auto toolbar = addToolBar(\"CP Profiler\");\n        toolbar->setAllowedAreas(Qt::TopToolBarArea);\n        toolbar->setFloatable(false);\n        toolbar->setMovable(false);\n\n        {\n            auto nodeMenu = new QMenu(\"&Node\");\n\n            auto centerNode = new QAction{\"Center current node\", this};\n            centerNode->setShortcut(QKeySequence(\"C\"));\n            nodeMenu->addAction(centerNode);\n            connect(centerNode, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::centerCurrentNode);\n\n            auto toggleShowLabel = new QAction{\"Show labels down\", this};\n            toggleShowLabel->setShortcut(QKeySequence(\"L\"));\n            nodeMenu->addAction(toggleShowLabel);\n            connect(toggleShowLabel, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::showLabelsDown);\n\n            auto toggleShowLabelsUp = new QAction{\"Show labels down\", this};\n            toggleShowLabelsUp->setShortcut(QKeySequence(\"Shift+L\"));\n            nodeMenu->addAction(toggleShowLabelsUp);\n            connect(toggleShowLabelsUp, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::showLabelsUp);\n\n            auto hideFailed = new QAction{\"Hide failed\", this};\n            hideFailed->setShortcut(QKeySequence(\"F\"));\n            nodeMenu->addAction(hideFailed);\n            connect(hideFailed, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::hideFailed);\n\n            auto unhideAll = new QAction{\"Unhide all below\", this};\n            unhideAll->setShortcut(QKeySequence(\"U\"));\n            nodeMenu->addAction(unhideAll);\n            connect(unhideAll, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::unhideAllAtCurrent);\n\n            auto toggleHighlighted = new QAction{\"Toggle highlight subtree\", this};\n            toggleHighlighted->setShortcut(QKeySequence(\"Shift+H\"));\n            nodeMenu->addAction(toggleHighlighted);\n            connect(toggleHighlighted, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::toggleHighlighted);\n\n            auto toggleHidden = new QAction{\"Toggle hide/unhide subtree\", this};\n            toggleHidden->setShortcut(QKeySequence(\"H\"));\n            nodeMenu->addAction(toggleHidden);\n            connect(toggleHidden, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::toggleHidden);\n\n            auto bookmarkNode = new QAction{\"Add/remove bookmark\", this};\n            bookmarkNode->setShortcut(QKeySequence(\"Shift+B\"));\n            nodeMenu->addAction(bookmarkNode);\n            connect(bookmarkNode, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::bookmarkCurrentNode);\n\n            auto showNogoods = new QAction{\"Show nogoods\", this};\n            showNogoods->setShortcut(QKeySequence(\"Shift+N\"));\n            nodeMenu->addAction(showNogoods);\n            connect(showNogoods, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::showNogoods);\n\n            auto showNodeInfo = new QAction{\"Show node info\", this};\n            showNodeInfo->setShortcut(QKeySequence(\"i\"));\n            nodeMenu->addAction(showNodeInfo);\n            connect(showNodeInfo, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::showNodeInfo);\n\n            auto button = new QToolButton;\n            button->setPopupMode(QToolButton::InstantPopup);\n            button->setText(\"&Node\");\n            button->setToolButtonStyle(Qt::ToolButtonTextOnly);\n            button->setMenu(nodeMenu);\n            toolbar->addWidget(button);\n        }\n\n        {\n\n            auto navMenu = new QMenu(\"Na&vigation\");\n\n            auto navRoot = new QAction{\"Go to the root\", this};\n            navRoot->setShortcut(QKeySequence(\"R\"));\n            navMenu->addAction(navRoot);\n            connect(navRoot, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::navRoot);\n\n            auto navDown = new QAction{\"Go to the left-most child\", this};\n            navDown->setShortcut(QKeySequence(\"Down\"));\n            navMenu->addAction(navDown);\n            connect(navDown, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::navDown);\n\n            auto navDownAlt = new QAction{\"Go to the right-most child\", this};\n            navDownAlt->setShortcut(QKeySequence(\"Alt+Down\"));\n            navMenu->addAction(navDownAlt);\n            connect(navDownAlt, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::navDownAlt);\n\n            auto navUp = new QAction{\"Go up\", this};\n            navUp->setShortcut(QKeySequence(\"Up\"));\n            navMenu->addAction(navUp);\n            connect(navUp, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::navUp);\n\n            auto navLeft = new QAction{\"Go left\", this};\n            navLeft->setShortcut(QKeySequence(\"Left\"));\n            navMenu->addAction(navLeft);\n            connect(navLeft, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::navLeft);\n\n            auto navRight = new QAction{\"Go right\", this};\n            navRight->setShortcut(QKeySequence(\"Right\"));\n            navMenu->addAction(navRight);\n            connect(navRight, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::navRight);\n\n            auto button = new QToolButton;\n            button->setPopupMode(QToolButton::InstantPopup);\n            button->setText(\"Na&vigation\");\n            button->setToolButtonStyle(Qt::ToolButtonTextOnly);\n            button->setMenu(navMenu);\n            toolbar->addWidget(button);\n        }\n\n        {\n            auto viewMenu = new QMenu(\"Vie&w\");\n\n            auto showPixelTree = new QAction{\"Pixel Tree View\", this};\n            showPixelTree->setCheckable(true);\n            showPixelTree->setShortcut(QKeySequence(\"Shift+P\"));\n            viewMenu->addAction(showPixelTree);\n            connect(showPixelTree, &QAction::triggered, this, &ExecutionWindow::showPixelTree);\n\n            auto showIcicleTree = new QAction{\"Icicle Tree View\", this};\n            showIcicleTree->setCheckable(true);\n            showIcicleTree->setShortcut(QKeySequence(\"Shift+I\"));\n            viewMenu->addAction(showIcicleTree);\n            connect(showIcicleTree, &QAction::triggered, this, &ExecutionWindow::showIcicleTree);\n\n            auto toggleLanternTree = new QAction{\"Lantern Tree View\", this};\n            toggleLanternTree->setCheckable(true);\n            toggleLanternTree->setShortcut(QKeySequence(\"Ctrl+L\"));\n            viewMenu->addAction(toggleLanternTree);\n\n            connect(toggleLanternTree, &QAction::triggered, this, &ExecutionWindow::toggleLanternView);\n\n            auto button = new QToolButton;\n            button->setPopupMode(QToolButton::InstantPopup);\n            button->setMenu(viewMenu);\n            button->setText(\"Vie&w\");\n            button->setToolButtonStyle(Qt::ToolButtonTextOnly);\n            toolbar->addWidget(button);\n        }\n\n        {\n            auto dataMenu = new QMenu(\"&Data\");\n\n            auto showBookmarks = new QAction{\"Show bookmarks\", this};\n            dataMenu->addAction(showBookmarks);\n            connect(showBookmarks, &QAction::triggered, this, &ExecutionWindow::showBookmarks);\n\n            auto button = new QToolButton;\n            button->setPopupMode(QToolButton::InstantPopup);\n            button->setText(\"&Data\");\n            button->setToolButtonStyle(Qt::ToolButtonTextOnly);\n            button->setMenu(dataMenu);\n            toolbar->addWidget(button);\n        }\n\n        {\n            auto analysisMenu = new QMenu(\"&Analyses\");\n            auto similarSubtree = new QAction{\"Similar Subtrees\", this};\n            similarSubtree->setShortcut(QKeySequence(\"Shift+S\"));\n            analysisMenu->addAction(similarSubtree);\n\n            connect(similarSubtree, &QAction::triggered, [this, &ex]() {\n                auto ssw = new analysis::SimilarSubtreeWindow(this, ex.tree());\n                ssw->show();\n\n                connect(ssw, &analysis::SimilarSubtreeWindow::should_be_highlighted, [this](const std::vector<NodeID> &nodes, bool hide_rest) {\n                    traditional_view_->highlightSubtrees(nodes, hide_rest);\n                });\n            });\n\n            auto saveSearch = new QAction{\"Save Search (for replaying)\", this};\n            analysisMenu->addAction(saveSearch);\n            connect(saveSearch, &QAction::triggered, [this]() { emit needToSaveSearch(&execution_); });\n\n            auto button = new QToolButton;\n            button->setPopupMode(QToolButton::InstantPopup);\n            button->setText(\"&Analyses\");\n            button->setToolButtonStyle(Qt::ToolButtonTextOnly);\n            button->setMenu(analysisMenu);\n            toolbar->addWidget(button);\n        }\n\n#ifdef DEBUG_MODE\n        {\n            auto debugMenu = new QMenu(\"Debu&g\");\n\n            auto updateLayoutAction = new QAction{\"Update layout\", this};\n            debugMenu->addAction(updateLayoutAction);\n            connect(updateLayoutAction, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::updateLayout);\n\n            auto dirtyNodesUp = new QAction{\"Dirty Nodes Up\", this};\n            debugMenu->addAction(dirtyNodesUp);\n            connect(dirtyNodesUp, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::dirtyCurrentNodeUp);\n\n            auto getNodeInfo = new QAction{\"Print node info\", this};\n            getNodeInfo->setShortcut(QKeySequence(\"I\"));\n            debugMenu->addAction(getNodeInfo);\n            connect(getNodeInfo, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::printNodeInfo);\n\n            auto removeNode = new QAction{\"Remove node\", this};\n            removeNode->setShortcut(QKeySequence(\"Del\"));\n            debugMenu->addAction(removeNode);\n            connect(removeNode, &QAction::triggered, this, &ExecutionWindow::removeSelectedNode);\n\n            auto checkLayout = new QAction{\"Check layout\", this};\n            debugMenu->addAction(checkLayout);\n            connect(checkLayout, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::debugCheckLayout);\n\n            auto debugMode = new QAction{\"Debug mode (show NodeIDs)\", this};\n            debugMode->setCheckable(true);\n            debugMode->setShortcut(QKeySequence(\"Shift+D\"));\n            debugMenu->addAction(debugMode);\n            connect(debugMode, &QAction::triggered, traditional_view_.get(), &tree::TraditionalView::setDebugMode);\n\n            auto button = new QToolButton;\n            button->setPopupMode(QToolButton::InstantPopup);\n            button->setText(\"Debu&g\");\n            button->setToolButtonStyle(Qt::ToolButtonTextOnly);\n            button->setMenu(debugMenu);\n            toolbar->addWidget(button);\n        }\n#endif\n\n        // auto debugText = new QTextEdit{this};\n        // // debugText->setHeight(200);\n        // debugText->setReadOnly(true);\n\n        // layout->addWidget(debugText, 2, 0);\n    }\n}\n\nExecutionWindow::~ExecutionWindow() = default;\n\ntree::TraditionalView &ExecutionWindow::traditional_view()\n{\n    return *traditional_view_;\n}\n\nvoid ExecutionWindow::showBookmarks() const\n{\n\n    auto b_window = new QDialog();\n    b_window->setAttribute(Qt::WA_DeleteOnClose);\n\n    auto lo = new QVBoxLayout(b_window);\n\n    auto bm_table = new QTableView();\n\n    auto bm_model = new QStandardItemModel(0, 2);\n    QStringList headers{\"NodeID\", \"Bookmark Text\"};\n    bm_model->setHorizontalHeaderLabels(headers);\n    bm_table->horizontalHeader()->setStretchLastSection(true);\n\n    bm_table->setModel(bm_model);\n\n    {\n        const auto &ud = execution_.userData();\n\n        const auto nodes = ud.bookmarkedNodes();\n\n        for (const auto n : nodes)\n        {\n            auto nid_item = new QStandardItem(QString::number(n));\n            auto text = ud.getBookmark(n);\n            auto text_item = new QStandardItem(text.c_str());\n            bm_model->appendRow({nid_item, text_item});\n        }\n    }\n\n    lo->addWidget(bm_table);\n\n    b_window->show();\n\n    // QTableView\n}\n\n/// TODO: this should only be active when the tree is done building\nvoid ExecutionWindow::removeSelectedNode()\n{\n\n    auto nid = execution_.userData().getSelectedNode();\n    if (nid == NodeID::NoNode)\n        return;\n\n    const auto pid = execution_.tree().getParent(nid);\n    execution_.userData().setSelectedNode(pid);\n\n    execution_.tree().removeNode(nid);\n\n    if (pid != NodeID::NoNode)\n    {\n        traditional_view_->dirtyUp(pid);\n    }\n\n    traditional_view_->setLayoutOutdated();\n    traditional_view_->redraw();\n}\n\nstatic void writeToFile(const QString &path, const QString &str)\n{\n    QFile file(path);\n\n    if (file.open(QFile::WriteOnly | QFile::Truncate))\n    {\n        QTextStream out(&file);\n\n        out << str;\n    }\n    else\n    {\n        qDebug() << \"could not open the file: \" << path;\n    }\n}\n\nvoid ExecutionWindow::showPixelTree()\n{\n    const auto &tree = execution_.tree();\n\n    /// Can only show pixel tree when the tree is fully built\n    /// TODO: change this to allow partially built trees\n    //if (!tree.isDone())\n    //    return;\n\n    if (!pt_dock_)\n    {\n        pt_dock_ = new QDockWidget(\"Pixel Tree\", this);\n        pt_dock_->setAllowedAreas(Qt::BottomDockWidgetArea);\n        addDockWidget(Qt::BottomDockWidgetArea, pt_dock_);\n        pixel_tree_.reset(new pixel_view::PtCanvas(tree));\n        pixel_tree_->setDarkMode(dark_mode_);\n        pt_dock_->setWidget(pixel_tree_.get());\n\n        connect(pixel_tree_.get(), &pixel_view::PtCanvas::nodesSelected, [this](const std::vector<NodeID> &nodes) {\n            traditional_view_->highlightSubtrees(nodes, true, false);\n        });\n    }\n\n    if (pt_dock_->isHidden())\n    {\n        pt_dock_->show();\n    }\n    else\n    {\n        pt_dock_->hide();\n    }\n}\n\nvoid ExecutionWindow::showIcicleTree()\n{\n\n    const auto &tree = execution_.tree();\n    /// Can only show icicle tree when the tree is fully built\n    /// TODO: change this to allow partially built trees\n    //if (!tree.isDone())\n    //    return;\n\n    if (!it_dock_)\n    {\n        /// Create Icicle Tree Widget\n\n        it_dock_ = new QDockWidget(\"Icicle Tree\", this);\n        it_dock_->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea);\n        addDockWidget(Qt::TopDockWidgetArea, it_dock_);\n\n        icicle_tree_.reset(new pixel_view::IcicleCanvas(tree));\n        icicle_tree_->setDarkMode(dark_mode_);\n        it_dock_->setWidget(icicle_tree_.get());\n\n        connect(icicle_tree_.get(), &pixel_view::IcicleCanvas::nodeClicked, this, &ExecutionWindow::nodeSelected);\n        connect(icicle_tree_.get(), &pixel_view::IcicleCanvas::nodeClicked, traditional_view_.get(), &tree::TraditionalView::centerCurrentNode);\n\n        /// Note: nodeSelected can be triggered by some other view\n        connect(this, &ExecutionWindow::nodeSelected, icicle_tree_.get(), &pixel_view::IcicleCanvas::selectNode);\n    }\n\n    if (it_dock_->isHidden())\n    {\n        it_dock_->show();\n    }\n    else\n    {\n        it_dock_->hide();\n    }\n}\n\nvoid ExecutionWindow::toggleLanternView(bool checked)\n{\n\n    if (checked)\n    {\n        const int max_nodes = execution_.tree().nodeCount();\n        lantern_widget->setMax(max_nodes);\n        lantern_widget->show();\n        auto old_value = lantern_widget->value();\n        traditional_view_->hideBySize(old_value);\n    }\n    else\n    {\n        /// Undo lantern tree\n        traditional_view_->undoLanterns();\n        traditional_view_->unhideAll();\n        lantern_widget->hide();\n    }\n}\n\nvoid ExecutionWindow::setDarkMode(bool d)\n{\n    dark_mode_ = d;\n    traditional_view_->setDarkMode(dark_mode_);\n    if (pixel_tree_ != nullptr) {\n        pixel_tree_->setDarkMode(dark_mode_);\n    }\n    if (icicle_tree_ != nullptr) {\n        icicle_tree_->setDarkMode(dark_mode_);\n    }\n}\n\nvoid ExecutionWindow::print_log(const std::string &str)\n{\n    writeToFile(\"debug.log\", str.c_str());\n}\n\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/execution_window.hh",
    "content": "#ifndef CPPROFILER_EXECUTION_WINDOW\n#define CPPROFILER_EXECUTION_WINDOW\n\n#include <memory>\n#include <QMainWindow>\n#include <QSlider>\n\n#include \"tree/node_id.hh\"\n#include \"core.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace utils\n{\nclass MaybeCaller;\n}\n\nnamespace tree\n{\nclass TraditionalView;\n}\n\nnamespace pixel_view\n{\nclass PtCanvas;\nclass IcicleCanvas;\n} // namespace pixel_view\n\nclass Execution;\n\nclass LanternMenu : public QWidget\n{\n  Q_OBJECT\n\nprivate:\n  QSlider *slider_;\n\npublic:\n  LanternMenu();\n\n  /// Get slider value\n  int value() { return slider_->value(); }\n\n  /// Set maximum value of the slider\n  void setMax(int v) { slider_->setMaximum(v); }\n\nsignals:\n  /// indicates that max size for a lantern node changed\n  void limit_changed(int val);\n};\n\nclass ExecutionWindow : public QMainWindow\n{\n  Q_OBJECT\n\n  Execution &execution_;\n  std::unique_ptr<tree::TraditionalView> traditional_view_;\n\n  std::unique_ptr<pixel_view::PtCanvas> pixel_tree_;\n\n  std::unique_ptr<pixel_view::IcicleCanvas> icicle_tree_;\n\n  std::unique_ptr<utils::MaybeCaller> maybe_caller_;\n\n  /// Dockable widget for the pixel tree\n  QDockWidget *pt_dock_ = nullptr;\n\n  /// Dockable widget for the icicle tree\n  QDockWidget *it_dock_ = nullptr;\n\n  /// Settings widget for lantern tree\n  LanternMenu *lantern_widget = nullptr;\n\n  bool dark_mode_ = false;\n\npublic:\n  tree::TraditionalView &traditional_view();\n\n  /// Show a window with all bookmarks\n  void showBookmarks() const;\n\n  ExecutionWindow(Execution &ex, QWidget* parent = nullptr);\n  ~ExecutionWindow();\n\n  Execution& execution()\n  {\n      return execution_;\n  }\n\npublic slots:\n\n  /// Remove currently selected node; then select its parent\n  void removeSelectedNode();\n\n  void print_log(const std::string &str);\n\n  /// Create and display pixel tree as a dockable widget\n  void showPixelTree();\n\n  /// Create and display icicle tree as a dockable widget\n  void showIcicleTree();\n\n  /// Toggle the tree lantern tree version of the visualisation\n  void toggleLanternView(bool checked);\n\n  void setDarkMode(bool d);\n\nsignals:\n\n  void needToSaveSearch(Execution *e);\n\n  void nodeSelected(NodeID n);\n\n  void nogoodsClicked(std::vector<NodeID>);\n};\n\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/message_wrapper.hh",
    "content": "#ifndef CPPROFILER_MESSAGE_WRAPPER_HH\r\n#define CPPROFILER_MESSAGE_WRAPPER_HH\r\n\r\n#include <QMetaType>\r\n#include <memory>\r\n\r\n#include \"../cpp-integration/message.hpp\"\r\n\r\nnamespace cpprofiler {\r\n\r\n/// Helper class which can be registered with Qt's metatype system\r\n///\r\n/// Allows us to pass MessageWrapper references around in multithreaded signals/slots\r\n/// For some reason sending Message* pointers doesn't work\r\nclass MessageWrapper\r\n{\r\npublic:\r\n    MessageWrapper() = default;\r\n    MessageWrapper(const MessageWrapper &other) = default;\r\n    ~MessageWrapper() = default;\r\n\r\n    MessageWrapper(const Message& msg): _msg(msg) {}\r\n\r\n    Message& msg() { return _msg; }\r\n    const Message& msg() const { return _msg; }\r\n\r\nprivate:\r\n    Message _msg;\r\n};\r\n\r\n} // namespace cpprofiler\r\n\r\nQ_DECLARE_METATYPE(cpprofiler::MessageWrapper);\r\n\r\n#endif\r\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/name_map.cpp",
    "content": "#include \"name_map.hh\"\n#include \"utils/debug.hh\"\n#include \"utils/string_utils.hh\"\n#include \"utils/path_utils.hh\"\n\n#include <QDebug>\n#include <QString>\n#include <QStringList>\n#include <fstream>\n#include <regex>\n#include <utility>\n\nusing std::stoi;\nusing std::string;\nusing std::vector;\n\nnamespace cpprofiler\n{\n\nstatic const std::regex var_name_regex(\"[A-Za-z][A-Za-z0-9_]*\");\nstatic const std::regex assignment_regex(\"[A-Za-z][A-Za-z0-9_]*=[0-9]*\");\n\nstatic vector<string> read_file_by_lines(const string &file)\n{\n    std::ifstream model_file(file, std::ifstream::in);\n\n    /// Come back here next time\n    if (!model_file.is_open())\n    {\n        print(\"ERROR: cannot open model file: {}\", file);\n        return {};\n    }\n\n    vector<string> model_lines;\n    string line;\n    while (getline(model_file, line))\n    {\n        model_lines.push_back(line);\n    }\n    model_file.close();\n\n    return model_lines;\n}\n\nstd::ostream &operator<<(std::ostream &os, const Location &l)\n{\n    return os << l.sl << \" \" << l.sc << \" \" << l.el << \" \" << l.ec;\n}\n\n/// extract location and whether it is final from a paths line\nstatic std::pair<Location, bool> getLocation(const string &path)\n{\n\n    auto model_name = path.substr(0, path.find(utils::minor_sep));\n\n    auto name_pos = path.rfind(model_name);\n    auto end_elm = path.find(utils::major_sep, name_pos);\n\n    auto element = path.substr(name_pos, end_elm - name_pos);\n\n    auto loc_parts = utils::split(element, utils::minor_sep);\n\n    auto loc = Location(stoi(loc_parts[1]), stoi(loc_parts[2]),\n                        stoi(loc_parts[3]), stoi(loc_parts[4]));\n\n    bool is_final = false;\n\n    if (end_elm == path.size() - 1)\n    {\n        is_final = true;\n    }\n    else\n    {\n        auto rem = path.substr(end_elm + 1);\n        auto loc_parts = utils::split(rem, utils::minor_sep, true);\n\n        if (stoi(loc_parts[1]) == 0 && stoi(loc_parts[2]) == 0 &&\n            stoi(loc_parts[3]) == 0 && stoi(loc_parts[4]) == 0)\n        {\n            is_final = true;\n        }\n    }\n\n    return std::make_pair(loc, is_final);\n}\n\nstatic const Location empty_location{};\nstatic const string empty_string{\"\"};\n\nstatic const Location &findLocation(const SymbolTable &st,\n                                    const string &ident)\n{\n    auto it = st.find(ident);\n    if (it != st.end())\n    {\n        return it->second.location;\n    }\n    return empty_location;\n}\n\nstatic const string findPath(const SymbolTable &st, const string &ident)\n{\n    auto it = st.find(ident);\n    if (it != st.end())\n    {\n        return it->second.path;\n    }\n    return empty_string;\n}\n\nstatic string extractExpression(const vector<string> &model,\n                                const Location &loc)\n{\n    const auto &line = model.at(loc.sl - 1);\n    return line.substr(loc.sc - 1, loc.ec - loc.sc + 1);\n}\n\nstatic string getPathUntilDecomp(const string &path)\n{\n    const auto &model_name = path.substr(0, path.find(utils::minor_sep));\n\n    const auto last_model_pos = path.rfind(model_name);\n    const auto end_pos = path.find(utils::major_sep, last_model_pos);\n    return path.substr(0, end_pos);\n}\n\n/// expression (template) element\nstruct TElem\n{\n    string str;\n    bool is_id;\n};\n\nstd::ostream &operator<<(std::ostream &os, const TElem &el)\n{\n    return os << el.str << (el.is_id ? \"(id)\" : \"\");\n}\n\nusing Expression = vector<TElem>;\n\nstd::string NameMap::replaceNames(const std::string &text, bool expand) const\n{\n\n    const auto var_names_begin =\n        std::sregex_iterator(text.begin(), text.end(), var_name_regex);\n    const auto var_names_end = std::sregex_iterator();\n\n    size_t pos = 0;\n\n    Expression expr;\n\n    /// this might require some caching\n\n    for (auto it = var_names_begin; it != var_names_end; ++it)\n    {\n        auto match = *it;\n        auto str = text.substr(pos, static_cast<size_t>(match.position()) - pos);\n\n        if (match.position() != 0)\n        {\n            const auto non_id =\n                text.substr(pos, static_cast<size_t>(match.position()) - pos);\n            expr.push_back({non_id, false});\n        }\n\n        const auto id = match.str();\n        expr.push_back({id, true});\n\n        pos = static_cast<size_t>(match.position() + match.length());\n    }\n\n    expr.push_back({text.substr(pos), false});\n\n    std::stringstream ss;\n\n    for (const auto &el : expr)\n    {\n        if (el.is_id)\n        {\n            ss << getNiceName(el.str);\n        }\n        else\n        {\n            ss << el.str;\n        }\n    }\n\n    return ss.str();\n}\n\n// static string replaceAssignments(const string& path, const string&\n// epxression) {\n//   SymbolTable st;\n\n//   auto assignment_begin = std::sregex_iterator(path.begin(), path.end(),\n//   assignment_regex); auto assignment_end = std::sregex_iterator();\n\n//   for (auto it = assignment_begin; it != assignment_end; ++it) {\n//     std::smatch match = *it;\n//     const auto& assign = match.str();\n//     const auto& left_right = utils::split(assign, '=');\n\n//     const auto lhs = left_right[0];\n//     const auto rhs = left_right[1];\n\n//     print(\"left: {}, right: {}\", lhs, rhs);\n\n//     st[lhs] = SymbolRecord(rhs, empty_string, empty_location);\n//   }\n\n//   replaceNames(st, epxression);\n\n//   return \"\";\n\n// }\n\nvoid NameMap::addIdExpressionToMap(const vector<string> &model,\n                                   const string &ident)\n{\n\n    const auto &loc = findLocation(id_map_, ident);\n\n    if (loc.sl == 0)\n        return; // default (empty) location?\n\n    const auto &expression = extractExpression(model, loc);\n    const auto &path = findPath(id_map_, ident);\n\n    const auto path_until = getPathUntilDecomp(path);\n\n    // replaceAssignments(path_until, expression);\n}\n\nNameMap::NameMap() {}\n\nbool NameMap::initialize(const std::string &path_filename,\n                         const std::string &model_filename)\n{\n    vector<string> model_lines = read_file_by_lines(model_filename);\n    vector<string> paths_lines = read_file_by_lines(path_filename);\n\n    if (model_lines.size() == 0 || paths_lines.size() == 0)\n        return false;\n\n    try\n    {\n\n        for (auto &line : paths_lines)\n        {\n            const auto parts = utils::split(line, '\\t');\n\n            const auto id = parts.at(0);\n            const auto nice_name = parts.at(1);\n            const auto path = parts.at(2);\n            const auto loc = getLocation(parts.at(2));\n\n            id_map_.insert({id, SymbolRecord(nice_name, path, loc.first)});\n\n            const auto is_final = loc.second;\n\n            /// TODO: handle complex expressions\n\n            // // if a nice name is not actually nice\n            // if (nice_name.substr(0, 12) == \"X_INTRODUCED\") {\n            //   /// example: X_INTRODUCED_16_ should become ...\n            //   addIdExpressionToMap(model_lines, parts.at(0));\n            // } else {\n            //   // addDecompIdExpressionToMap(s[0], modelText);\n            // }\n        }\n\n        return true;\n    }\n    catch (std::exception &e)\n    {\n        qDebug() << \"ERR: invalid name map\";\n        return false;\n    }\n}\n\nconst NiceName &NameMap::getNiceName(const std::string &ident) const\n{\n    auto it = id_map_.find(ident);\n    if (it != id_map_.end())\n    {\n        return it->second.nice_name;\n    }\n\n    return empty_string;\n}\n\nconst Path &NameMap::getPath(const std::string &ident) const\n{\n\n    auto it = id_map_.find(ident);\n    if (it != id_map_.end())\n    {\n        return it->second.path;\n    }\n\n    return empty_string;\n}\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/name_map.hh",
    "content": "#pragma once\n\n#include <string>\n#include <QString>\n#include <vector>\n#include <unordered_map>\n\n//// Name Map should get the paths file and model and generate a mapping from UGLY to NICE names\n\n/// A line in a paths file consists of three columns:\n/// 1. original name\n/// 2. maybe nice name (can be X_INTRODUCED_N_)\n/// 3. path\n\nnamespace cpprofiler\n{\n\nstruct Location\n{\n    int sl; // start line\n    int sc; // start column\n    int el; // end line\n    int ec; // end column\n    Location() = default;\n    Location(int sl_, int sc_, int el_, int ec_) : sl(sl_), sc(sc_), el(el_), ec(ec_) {}\n};\n\nstruct SymbolRecord\n{\n\n    std::string nice_name;\n    std::string path;\n    Location location;\n\n    SymbolRecord() = default;\n    SymbolRecord(const std::string &nname, const std::string &p, const Location &loc)\n        : nice_name(nname), path(p), location(loc) {}\n};\n\nusing SymbolTable = std::unordered_map<std::string, SymbolRecord>;\nusing ExpressionTable = std::unordered_map<std::string, std::string>;\n\nusing NiceName = std::string;\nusing Path = std::string;\n\nclass NameMap\n{\n\n    SymbolTable id_map_;\n    ExpressionTable expression_map_;\n\n    const NiceName &getNiceName(const std::string &ident) const;\n    void addIdExpressionToMap(const std::vector<std::string> &model, const std::string &ident);\n\n  public:\n\n    NameMap();\n\n    /// Read paths and the model files to construct name mapping;\n    /// Returns `true` if successful -- `false` otherwise\n    bool initialize(const std::string &path_filename, const std::string &model_filename);\n\n    std::string replaceNames(const std::string &text, bool expand = false) const;\n\n\n    const Path& getPath(const std::string &ident) const;\n\n\n\n\n};\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/nogood_dialog.cpp",
    "content": "#include \"nogood_dialog.hh\"\n#include \"tree/node_tree.hh\"\n#include \"solver_data.hh\"\n\n#include <QTableView>\n#include <QVBoxLayout>\n\n#include <QHeaderView>\n\nnamespace cpprofiler\n{\n\nNogoodDialog::NogoodDialog(const tree::NodeTree &nt, const std::vector<NodeID> &nodes)\n{\n\n    static constexpr int DEFAULT_WIDTH = 800;\n    static constexpr int DEFAULT_HEIGHT = 400;\n\n    resize(DEFAULT_WIDTH, DEFAULT_HEIGHT);\n\n    auto ng_table = new QTableView();\n\n    auto lo = new QVBoxLayout(this);\n\n    ng_model_.reset(new QStandardItemModel(0, 3));\n\n    const QStringList headers{\"NodeID\", \"SolverID\", \"Clause\"};\n    ng_model_->setHorizontalHeaderLabels(headers);\n    ng_table->horizontalHeader()->setStretchLastSection(true);\n    ng_table->setSelectionBehavior(QAbstractItemView::SelectRows);\n    ng_table->setEditTriggers(QAbstractItemView::NoEditTriggers);\n\n    ng_table->setModel(ng_model_.get());\n\n    const auto &sd = nt.solver_data();\n\n    for (auto n : nodes)\n    {\n\n        const auto nid_item = new QStandardItem(QString::number(n));\n\n        const auto sid = sd.getSolverID(n);\n        const auto sid_item = new QStandardItem(sid.toString().c_str());\n        const auto text = nt.getNogood(n).get();\n        if (text == \"\")\n            continue;\n        const auto text_item = new QStandardItem(text.c_str());\n        ng_model_->appendRow({nid_item, sid_item, text_item});\n    }\n\n    lo->addWidget(ng_table);\n\n    connect(ng_table, &QTableView::doubleClicked, [this](const QModelIndex &idx) {\n        const auto nid = ng_model_->item(idx.row())->text().toInt();\n        emit nogoodClicked(NodeID(nid));\n    });\n}\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/nogood_dialog.hh",
    "content": "#pragma once\n\n#include <QDialog>\n#include <QStandardItemModel>\n#include \"core.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace tree\n{\nclass NodeTree;\n}\n\nclass NogoodDialog : public QDialog\n{\n    Q_OBJECT\n\n    std::unique_ptr<QStandardItemModel> ng_model_;\n\n  public:\n    NogoodDialog(const tree::NodeTree &nt, const std::vector<NodeID> &nodes);\n\n  signals:\n    void nogoodClicked(NodeID nid);\n};\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/options.hh",
    "content": "#pragma once\n\nnamespace cpprofiler\n{\n\nstruct Options\n{\n    std::string paths;\n    std::string mzn;\n    std::string save_search_path;\n    std::string save_execution_db;\n    std::string save_pixel_tree_path;\n    int pixel_tree_compression;\n};\n\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/pixel_views/icicle_canvas.cpp",
    "content": "#include \"icicle_canvas.hh\"\n#include \"pixel_image.hh\"\n#include \"pixel_widget.hh\"\n\n#include \"../utils/tree_utils.hh\"\n#include \"../utils/perf_helper.hh\"\n#include \"../utils/maybe_caller.hh\"\n#include \"../tree/node_tree.hh\"\n\n#include <QVBoxLayout>\n#include <QPushButton>\n#include <QScrollBar>\n#include <QLabel>\n#include <QPoint>\n\nnamespace cpprofiler\n{\n\nnamespace pixel_view\n{\n\nnamespace colors\n{\nstatic QRgb branch = qRgb(50, 50, 230);\nstatic QRgb failure = qRgb(230, 50, 50);\nstatic QRgb solution = qRgb(50, 230, 50);\nstatic QRgb selected = qRgb(252, 209, 22);\n} // namespace colors\n\nclass IcicleLayout\n{\n  public:\n    // width for every node\n    std::vector<int> width_;\n    /// whether node should represent a solution\n    std::vector<bool> has_sol;\n\n    void resize(size_t n)\n    {\n        width_.resize(n);\n        has_sol.resize(n);\n    }\n\n    inline int width(NodeID n) const { return width_[n]; }\n    inline void setWidth(NodeID n, int v) { width_[n] = v; }\n\n    inline bool hasSol(NodeID n) const { return has_sol[n]; }\n    inline void setHasSol(NodeID n, bool v) { has_sol[n] = v; }\n};\n\nstatic std::unique_ptr<IcicleLayout> computeLayout(const tree::NodeTree &nt, int compr)\n{\n    /// TODO: hold tree mutex\n\n    /// post order traversal\n    auto order = utils::post_order(nt);\n\n    /// All leaf nodes are of generation 1, parent nodes are\n    /// the largest generation of children + 1\n    std::vector<int> generations(order.size());\n\n    {\n        for (auto n : order)\n        {\n            auto nkids = nt.childrenCount(n);\n            if (nkids == 0)\n            {\n                generations[n] = 1;\n            }\n            else\n            {\n                /// assign generation based on children\n                int max_gen = 0;\n                for (auto alt = 0; alt < nkids; ++alt)\n                {\n                    const auto kid = nt.getChild(n, alt);\n                    if (generations[kid] > max_gen)\n                    {\n                        max_gen = generations[kid];\n                    }\n                }\n                generations[n] = max_gen + 1;\n                // print(\"node {} is of generation {}\", n, generations[n]);\n            }\n        }\n    }\n\n    auto layout = std::unique_ptr<IcicleLayout>(new IcicleLayout());\n\n    layout->resize(order.size());\n\n    for (auto n : order)\n    {\n\n        if (generations[n] == compr)\n        {\n            layout->setWidth(n, 1);\n\n            if (nt.hasSolvedChildren(n))\n            {\n                layout->setHasSol(n, true);\n            }\n        }\n        else if (generations[n] < compr)\n        {\n            layout->setWidth(n, 0);\n        }\n        else\n        {\n            const auto kid1 = nt.getChild(n, 0);\n\n            int sum_width = layout->width(kid1);\n            /// extra kids\n            auto nkids = nt.childrenCount(n);\n            for (auto alt = 1; alt < nkids; ++alt)\n            {\n                const auto kid = nt.getChild(n, alt);\n                sum_width += layout->width(kid);\n            }\n            layout->setWidth(n, sum_width);\n        }\n    }\n\n    return layout;\n}\n\nstatic NodeID findNode(const tree::NodeTree &nt, const IcicleLayout &lo, int x, int y)\n{\n    auto root = nt.getRoot();\n    std::stack<NodeID> stack;\n    /// stack for starting positions (x) of icicle nodes\n    std::stack<QPoint> start_pos;\n\n    stack.push(root);\n    start_pos.push({0, 0});\n\n    while (!stack.empty())\n    {\n        auto n = stack.top();\n        stack.pop();\n\n        auto pos0 = start_pos.top();\n        start_pos.pop();\n\n        /// if within the node's horizontal boundaries\n        if ((x >= pos0.x()) && (x < pos0.x() + lo.width(n)))\n        {\n            /// is node on the right depth level?\n            if (y == pos0.y())\n            {\n                return n;\n            }\n            else if (pos0.y() < y)\n            {\n                /// check its children\n\n                const auto nkids = nt.childrenCount(n);\n                auto cur_x = pos0.x();\n\n                for (auto alt = 0; alt < nkids; ++alt)\n                {\n                    const auto kid = nt.getChild(n, alt);\n                    stack.push(kid);\n                    start_pos.push({cur_x, pos0.y() + 1});\n                    cur_x += lo.width(kid);\n                }\n            }\n        }\n    }\n\n    return NodeID::NoNode;\n}\n\nIcicleCanvas::IcicleCanvas(const tree::NodeTree &tree) : QWidget(), tree_(tree)\n{\n\n    maybe_caller_.reset(new utils::MaybeCaller(30));\n    pimage_.reset(new PixelImage());\n    pwidget_.reset(new PixelWidget(*pimage_));\n\n    auto layout = new QVBoxLayout(this);\n    layout->addWidget(pwidget_.get());\n\n    connect(pwidget_.get(), &PixelWidget::viewport_resized, [this](const QSize &size) {\n        pimage_->resize(size);\n        redrawAll();\n    });\n\n    auto controlLayout = new QHBoxLayout();\n    layout->addLayout(controlLayout);\n\n    {\n\n        controlLayout->addWidget(new QLabel(\"Zoom:\"));\n\n        auto zoomOut = new QPushButton(\"-\", this);\n        auto zoomIn = new QPushButton(\"+\", this);\n\n        zoomOut->setMaximumWidth(40);\n        controlLayout->addWidget(zoomOut);\n        connect(zoomOut, &QPushButton::clicked, [zoomOut, this]() {\n            pimage_->zoomOut();\n            if (pimage_->pixel_size() == 1)\n                zoomOut->setEnabled(false);\n            redrawAll();\n        });\n\n        zoomIn->setMaximumWidth(40);\n        controlLayout->addWidget(zoomIn);\n        connect(zoomIn, &QPushButton::clicked, [zoomOut, this]() {\n            zoomOut->setEnabled(true);\n            pimage_->zoomIn();\n            redrawAll();\n        });\n    }\n\n    controlLayout->addStretch();\n\n    {\n\n        controlLayout->addWidget(new QLabel(\"Compression:\"));\n\n        auto addCompression = new QPushButton(\"-\", this);\n        auto reduceCompression = new QPushButton(\"+\", this);\n\n        addCompression->setMaximumWidth(40);\n        controlLayout->addWidget(addCompression);\n        connect(addCompression, &QPushButton::clicked, [reduceCompression, this]() {\n            compression_ += 1;\n            reduceCompression->setEnabled(true);\n            layout_ = computeLayout(tree_, compression_);\n            redrawAll();\n        });\n\n        reduceCompression->setMaximumWidth(40);\n        reduceCompression->setEnabled(false);\n        controlLayout->addWidget(reduceCompression);\n        connect(reduceCompression, &QPushButton::clicked, [reduceCompression, this]() {\n            compression_ = std::max(1, compression_ - 1);\n\n            if (compression_ == 1)\n                reduceCompression->setEnabled(false);\n\n            layout_ = computeLayout(tree_, compression_);\n            redrawAll();\n        });\n    }\n\n    // perfHelper.begin(\"icicle layout\");\n\n    layout_ = computeLayout(tree, compression_);\n\n    connect(pwidget_->horizontalScrollBar(), &QScrollBar::valueChanged, [this]() {\n        maybe_caller_->call([this]() {\n            redrawAll();\n        });\n    });\n\n    connect(pwidget_.get(), &PixelWidget::coordinate_clicked, [this](int x, int y) {\n        auto node = findNode(tree_, *layout_, x, y);\n        if (node != NodeID::NoNode)\n        {\n            emit nodeClicked(node);\n        }\n    });\n\n    // perfHelper.end();\n}\n\nIcicleCanvas::~IcicleCanvas() = default;\n\nvoid IcicleCanvas::redrawAll()\n{\n\n    pimage_->clear();\n\n    drawIcicleTree();\n\n    {\n        const auto root = tree_.getRoot();\n        const auto total_width = layout_->width_[root];\n        /// how many \"pixels\" fit in one page\n        const auto page_width = pwidget_->width();\n\n        /// Note: page width is 1 smaller for the purpose of calculating the srollbar range,\n        /// than it is for drawing (this way some \"pixels\" can be drawn partially at the edge)\n        pwidget_->horizontalScrollBar()->setRange(0, total_width - (page_width - 1));\n        pwidget_->horizontalScrollBar()->setPageStep(page_width);\n    }\n\n    pimage_->update();\n\n    pwidget_->viewport()->update();\n}\n\nclass IcicleDrawing\n{\n\n    const tree::NodeTree &nt_;\n    const IcicleLayout &layout_;\n    PixelImage &pimage_;\n    const int viewport_width;\n\n    /// currently selected node\n    NodeID selected_;\n\n    int counter = 0;\n\n  private:\n    /// Get the color to be used in the icicle tree for this node\n    QRgb getColor(NodeID n)\n    {\n        /// Note: some nodes are not actually solution nodes, but\n        /// can represent them when the tree is compressed\n        // if (layout_.hasSol(n))\n        if (nt_.hasSolvedChildren(n))\n        {\n            return colors::solution;\n        }\n        else\n        {\n            return colors::failure;\n        }\n        const auto status = nt_.getStatus(n);\n\n        if (status == tree::NodeStatus::BRANCH)\n        {\n            return colors::branch;\n        }\n        else if (status == tree::NodeStatus::FAILED)\n        {\n            return colors::failure;\n        }\n\n        return qRgb(255, 255, 255);\n    }\n\n    void drawIcicleSubtree(NodeID n, int cur_x, int cur_y)\n    {\n        const auto width = layout_.width_[n];\n\n        /// no need to draw the node or its children\n        if (cur_x > viewport_width || cur_x + width <= 0 || width == 0)\n        {\n            return;\n        }\n\n        /// draw itself\n        auto color = getColor(n);\n\n        if (n == selected_)\n        {\n            /// disregard the normal color in favour of \"gold\" for the selected node\n            color = colors::selected;\n        }\n\n        pimage_.drawRect(cur_x, cur_y, width, color);\n\n        counter++;\n\n        /// draw children\n        for (auto alt = 0; alt < nt_.childrenCount(n); ++alt)\n        {\n            auto kid = nt_.getChild(n, alt);\n            drawIcicleSubtree(kid, cur_x, cur_y + 1);\n            cur_x += layout_.width_[kid];\n        }\n    }\n\n  public:\n    IcicleDrawing(const tree::NodeTree &nt, IcicleLayout &lo, PixelImage &pi, int viewport_w, NodeID selected)\n        : nt_(nt), layout_(lo), pimage_(pi), viewport_width(viewport_w), selected_(selected)\n    {\n    }\n\n    ~IcicleDrawing()\n    {\n    }\n\n    /// only called once\n    void run(NodeID root, int x, int y)\n    {\n        drawIcicleSubtree(root, x, y);\n    };\n};\n\nvoid IcicleCanvas::drawIcicleTree()\n{\n\n    const auto root = tree_.getRoot();\n\n    const auto x_begin = pwidget_->horizontalScrollBar()->value();\n\n    IcicleDrawing drawer(tree_, *layout_, *pimage_, pwidget_->width(), selected_);\n    drawer.run(root, -x_begin, 0);\n\n    /// provided every node knows its bounding box\n}\n\nvoid IcicleCanvas::selectNode(NodeID n)\n{\n    selected_ = n;\n    redrawAll();\n}\n\nvoid IcicleCanvas::setDarkMode(bool d)\n{\n    pimage_->setDarkMode(d);\n    redrawAll();\n}\n\n} // namespace pixel_view\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/pixel_views/icicle_canvas.hh",
    "content": "#pragma once\n\n#include <QWidget>\n#include <memory>\n\n#include \"../core.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace utils\n{\nclass MaybeCaller;\n}\n\nnamespace tree\n{\nclass NodeTree;\n}\n\nnamespace pixel_view\n{\n\nclass PixelImage;\nclass PixelWidget;\nclass IcicleLayout;\n\nclass IcicleCanvas : public QWidget\n{\n    Q_OBJECT\n    const tree::NodeTree &tree_;\n\n    std::unique_ptr<utils::MaybeCaller> maybe_caller_;\n    std::unique_ptr<PixelImage> pimage_;\n    std::unique_ptr<PixelWidget> pwidget_;\n\n    std::unique_ptr<IcicleLayout> layout_;\n\n    /// Currently selected node;\n    NodeID selected_;\n\n    /// The last \"generation\" of nodes displayed (1 being leaf nodes)\n    /// Note: the default of 1 requires the '+' button to be disabled\n    int compression_ = 1;\n\n  public:\n    IcicleCanvas(const tree::NodeTree &tree);\n\n    ~IcicleCanvas();\n\n    void redrawAll();\n\n    void drawIcicleTree();\n\n  public slots:\n\n    /// highlight the node on the view; to be called via signals/slots only!\n    void selectNode(NodeID n);\n\n    void setDarkMode(bool darkMode);\n\n  signals:\n    void nodeClicked(NodeID n);\n};\n} // namespace pixel_view\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/pixel_views/pixel_image.cpp",
    "content": "#include \"pixel_image.hh\"\n\n#include \"../utils/debug.hh\"\n\n#include <QImage>\n#include <cassert>\n\nnamespace cpprofiler\n{\n\nnamespace pixel_view\n{\n\nPixelImage::PixelImage()\n{\n    image_.reset(new QImage());\n\n    resize({width_, height_});\n}\n\nPixelImage::~PixelImage() = default;\n\nvoid PixelImage::setPixel(std::vector<uint32_t> &buffer, int x, int y,\n                          QRgb color)\n{\n    assert(x >= 0 && y >= 0);\n\n    if (x >= width_ || x < 0 || y >= height_ || y < 0)\n    {\n        return;\n    }\n\n    uint32_t r = qRed(color);\n    uint32_t g = qGreen(color);\n    uint32_t b = qBlue(color);\n\n    uint32_t pixel_color = (0xFF << 24) + (r << 16) + (g << 8) + (b);\n\n    buffer[y * width_ + x] = pixel_color;\n}\n\nvoid PixelImage::resize(const QSize &size)\n{\n\n    width_ = size.width() - 10;\n    height_ = size.height() - 10;\n\n    buffer_.clear();\n    buffer_.resize(width_ * height_);\n\n    clear();\n    update();\n}\n\nvoid PixelImage::clear()\n{\n    /// set all pixels to white\n    std::fill(buffer_.begin(), buffer_.end(), dark_mode_ ? 0x333333 : 0xFFFFFF);\n}\n\nvoid PixelImage::update()\n{\n    auto buf = reinterpret_cast<uint8_t *>(buffer_.data());\n    image_.reset(new QImage(buf, width_, height_, QImage::Format_RGB32));\n}\n\nvoid PixelImage::zoomIn()\n{\n    pixel_size_ += 1;\n}\n\nvoid PixelImage::zoomOut()\n{\n    if (pixel_size_ > 1)\n    {\n        --pixel_size_;\n    }\n}\n\nvoid PixelImage::drawRect(int x, int y, int width, QRgb color)\n{\n\n    const auto BLACK = qRgb(0, 0, 0);\n    assert(y >= 0 && width > 0);\n\n    /// the rectange\n\n    if (x < 0)\n    {\n        /// the rectangle is cut off\n        width += x;\n        x = 0;\n    }\n\n    const int x_begin = x * pixel_size_;\n    const int y_begin = y * pixel_size_;\n    const int x_end = (x + width) * pixel_size_;\n    const int y_end = (y + 1) * pixel_size_;\n\n    /// Horizontal lines\n    for (auto col = x_begin; col < x_end; ++col)\n    {\n        setPixel(buffer_, col, y_begin, BLACK);\n    }\n\n    for (auto col = x_begin; col < x_end; ++col)\n    {\n        setPixel(buffer_, col, y_end - 1, BLACK);\n    }\n\n    /// Vertical lines\n    for (auto row = y_begin; row < y_end; ++row)\n    {\n        setPixel(buffer_, x_begin, row, BLACK);\n        setPixel(buffer_, x_end - 1, row, BLACK);\n    }\n\n    /// Fill the rect\n    for (auto col = x_begin + 1; col < x_end - 1; ++col)\n    {\n        for (auto row = y_begin + 1; row < y_end - 1; ++row)\n        {\n            setPixel(buffer_, col, row, color);\n        }\n    }\n}\n\nvoid PixelImage::drawPixel(int x, int y, QRgb color)\n{\n    assert(x >= 0 && y >= 0);\n\n    int x0 = x * pixel_size_;\n    int y0 = y * pixel_size_;\n\n    /// TODO(maxim): experiment with using std::fill to draw rows\n    /// TODO(maxim): experiment with drawing row by row\n    for (int column = 0; column < pixel_size_; ++column)\n    {\n        auto x = x0 + column;\n        if (x >= width_)\n            break;\n        for (int row = 0; row < pixel_size_; ++row)\n        {\n            auto y = y0 + row;\n            if (y >= height_)\n                break;\n            setPixel(buffer_, x, y, color);\n        }\n    }\n}\n\n} // namespace pixel_view\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/pixel_views/pixel_image.hh",
    "content": "#pragma once\n\n#include <QObject>\n\n#include <cstdint>\n#include <vector>\n#include <memory>\n#include <QRgb>\n\nclass QImage;\n\nnamespace cpprofiler\n{\n\nnamespace pixel_view\n{\n\nconstexpr static int DEFAULT_PIXEL_SIZE = 10;\n\nclass PixelImage : public QObject\n{\n  Q_OBJECT\nprivate:\n  /// Buffer used to initialize QImage\n  std::vector<uint32_t> buffer_;\n\n  /// The image used for diplaying the tree\n  std::unique_ptr<QImage> image_;\n\n  int width_ = 40;\n  int height_ = 20;\n\n  int pixel_size_ = DEFAULT_PIXEL_SIZE;\n\n  bool dark_mode_ = false;\n\n  void setPixel(std::vector<uint32_t> &buffer, int x, int y, QRgb color);\n\npublic:\n  PixelImage();\n\n  ~PixelImage();\n\n  /// Set all pixels to a default color\n  void clear();\n  /// change QImage to match the buffer\n  void update();\n\n  void resize(const QSize &size);\n\n  void drawPixel(int x, int y, QRgb color);\n\n  void drawRect(int x, int y, int width, QRgb color);\n\n  const QImage &raw_image() const\n  {\n    return *image_;\n  }\n\n  void setPixelSize(int size) { pixel_size_ = size; }\n\n  int pixel_size() const { return pixel_size_; }\n\n  void setDarkMode(bool d) { dark_mode_ = d; }\n\npublic slots:\n\n  /// Decrease pixel size\n  void zoomOut();\n\n  /// Increase pixel size\n  void zoomIn();\n};\n} // namespace pixel_view\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/pixel_views/pixel_item.hh",
    "content": "\n#pragma once\n\nnamespace cpprofiler\n{\n\nnamespace pixel_view\n{\n\n} // namespace pixel_view\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/pixel_views/pixel_widget.cpp",
    "content": "#include \"pixel_widget.hh\"\n\n#include <QScrollBar>\n\nnamespace cpprofiler\n{\n\nnamespace pixel_view\n{\n\nvoid PixelWidget::mousePressEvent(QMouseEvent *e)\n{\n    const auto xoff = horizontalScrollBar()->value();\n    const auto yoff = verticalScrollBar()->value();\n\n    const int abs_x = (e->x() - PADDING) / image_.pixel_size() + xoff;\n    const int abs_y = (e->y() - PADDING) / image_.pixel_size() + yoff;\n\n    pressed_vline_ = abs_x;\n\n    emit coordinate_clicked(abs_x, abs_y);\n}\n\nvoid PixelWidget::mouseReleaseEvent(QMouseEvent *e)\n{\n    const auto xoff = horizontalScrollBar()->value();\n\n    const int vline = (e->x() - PADDING) / image_.pixel_size() + xoff;\n\n    if (pressed_vline_ < vline)\n        emit range_selected(pressed_vline_, vline);\n    else if (pressed_vline_ > vline)\n        emit range_selected(vline, pressed_vline_);\n}\n\n} // namespace pixel_view\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/pixel_views/pixel_widget.hh",
    "content": "#pragma once\n\n#include <QAbstractScrollArea>\n#include <QPainter>\n#include <QResizeEvent>\n\n#include <cmath> // std::ceil\n\n#include \"pixel_image.hh\"\n#include \"../utils/debug.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace pixel_view\n{\n\nclass PixelWidget : public QAbstractScrollArea\n{\n    Q_OBJECT\n    const PixelImage &image_;\n\n    /// record previosly clicked vline\n    int pressed_vline_ = -1;\n\n    static constexpr int PADDING = 5;\n\n  protected:\n    void paintEvent(QPaintEvent *) override\n    {\n        QPainter painter{viewport()};\n        painter.drawImage(QPoint(PADDING, PADDING), image_.raw_image());\n    }\n\n    void resizeEvent(QResizeEvent *e) override\n    {\n        viewport_resized(e->size());\n    }\n\n    void mousePressEvent(QMouseEvent *e) override;\n\n    void mouseReleaseEvent(QMouseEvent *e) override;\n\n  public:\n    PixelWidget(const PixelImage &image) : image_(image) {}\n\n    /// How many \"pixels\" fit in one page;\n    int width() const\n    {\n\n        return static_cast<int>(std::ceil(static_cast<float>(viewport()->width() - 10) / image_.pixel_size()));\n    }\n\n  signals:\n    void viewport_resized(const QSize &size);\n\n    /// notify pixel canvas that some slices have been selected;\n    /// indexes may refer to non-existing slices\n    void range_selected(int x_l, int x_r);\n\n    /// Notify that (x, y) is clicked in absolute values\n    void coordinate_clicked(int x, int y);\n};\n\n} // namespace pixel_view\n\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/pixel_views/pt_canvas.cpp",
    "content": "#include \"pt_canvas.hh\"\n#include \"pixel_image.hh\"\n#include \"pixel_widget.hh\"\n#include \"../tree/node_tree.hh\"\n\n#include \"../utils/perf_helper.hh\"\n#include \"../utils/debug.hh\"\n\n#include <cmath>     // std::ceil\n#include <algorithm> // std::min\n\n#include <QPainter>\n#include <QPushButton>\n#include <QScrollBar>\n#include <QVBoxLayout>\n\nnamespace cpprofiler\n{\n\nnamespace pixel_view\n{\n\nnamespace colors\n{\nQRgb solution = qRgb(50, 230, 50);\n} // namespace colors\n\nPtCanvas::PtCanvas(const tree::NodeTree &tree) : QWidget(), tree_(tree)\n{\n    pimage_.reset(new PixelImage());\n    pwidget_.reset(new PixelWidget(*pimage_));\n    pimage_->setPixelSize(4);\n\n    pi_seq_ = constructPixelTree();\n\n    const auto max_depth = tree_.node_stats().maxDepth();\n\n    auto layout = new QVBoxLayout(this);\n    layout->addWidget(pwidget_.get());\n\n    auto controlLayout = new QHBoxLayout();\n    layout->addLayout(controlLayout);\n\n    controlLayout->addWidget(new QLabel(\"Zoom:\"));\n\n    {\n        auto zoomOut = new QPushButton(\"-\", this);\n        zoomOut->setMaximumWidth(40);\n        controlLayout->addWidget(zoomOut);\n        connect(zoomOut, &QPushButton::clicked, [this]() {\n            pimage_->zoomOut();\n            redrawAll();\n        });\n    }\n\n    {\n        auto zoomIn = new QPushButton(\"+\", this);\n        zoomIn->setMaximumWidth(40);\n        controlLayout->addWidget(zoomIn);\n        connect(zoomIn, &QPushButton::clicked, [this]() {\n            pimage_->zoomIn();\n            redrawAll();\n        });\n    }\n\n    controlLayout->addStretch();\n\n    {\n\n        controlLayout->addWidget(new QLabel(\"Compression:\"));\n\n        auto addCompression = new QPushButton(\"-\", this);\n        auto reduceCompression = new QPushButton(\"+\", this);\n\n        addCompression->setMaximumWidth(40);\n        controlLayout->addWidget(addCompression);\n        connect(addCompression, &QPushButton::clicked, [reduceCompression, this]() {\n            compression_ += 10;\n            reduceCompression->setEnabled(true);\n            redrawAll();\n        });\n\n        reduceCompression->setMaximumWidth(40);\n        reduceCompression->setEnabled(false);\n        controlLayout->addWidget(reduceCompression);\n        connect(reduceCompression, &QPushButton::clicked, [reduceCompression, this]() {\n            compression_ = std::max(1, compression_ - 10);\n\n            if (compression_ == 1)\n                reduceCompression->setEnabled(false);\n\n            redrawAll();\n        });\n    }\n\n    connect(pwidget_.get(), &PixelWidget::viewport_resized, [this](const QSize &size) {\n        pimage_->resize(size);\n        redrawAll();\n    });\n\n    connect(pwidget_->horizontalScrollBar(), &QScrollBar::valueChanged, [this]() {\n        redrawAll();\n    });\n\n    connect(pwidget_.get(), &PixelWidget::range_selected, this, &PtCanvas::selectNodes);\n    connect(pwidget_.get(), &PixelWidget::coordinate_clicked, [this](int x, int) {\n        selectNodes(x, x);\n    });\n\n    redrawAll();\n}\n\nPtCanvas::~PtCanvas() = default;\n\nint PtCanvas::totalSlices() const\n{\n    return std::ceil((float)pi_seq_.size() / compression_);\n}\n\nstd::vector<PixelItem> PtCanvas::constructPixelTree() const\n{\n\n    print(\"pt: construct tree\");\n    /// TODO: tree mutex\n    auto root = tree_.getRoot();\n\n    std::stack<NodeID> stack;\n    std::stack<int> depth_stack;\n\n    std::vector<PixelItem> pixel_seq;\n    pixel_seq.reserve(tree_.nodeCount());\n\n    stack.push(root);\n    depth_stack.push(1);\n\n    while (!stack.empty())\n    {\n\n        auto n = stack.top();\n        stack.pop();\n\n        auto depth = depth_stack.top();\n        depth_stack.pop();\n\n        /// add pixel data\n        pixel_seq.push_back({n, depth});\n\n        int kids = tree_.childrenCount(n);\n\n        for (auto i = kids - 1; i >= 0; --i)\n        {\n            stack.push(tree_.getChild(n, i));\n            depth_stack.push(depth + 1);\n        }\n    }\n\n    stack.push(root);\n\n    return pixel_seq;\n}\n\nvoid PtCanvas::redrawAll(bool all)\n{\n    pimage_->clear();\n\n    drawPixelTree(all);\n\n    {\n        const auto total_width = totalSlices();\n        /// how many \"pixels\" fit in one page\n        const auto page_width = pwidget_->width();\n\n        /// Note: page width is 1 smaller for the purpose of calculating the srollbar range,\n        /// than it is for drawing (this way some \"pixels\" can be drawn partially at the edge)\n        pwidget_->horizontalScrollBar()->setRange(0, total_width - (page_width - 1));\n        pwidget_->horizontalScrollBar()->setPageStep(page_width);\n    }\n\n    pimage_->update();\n    pwidget_->viewport()->update();\n}\n\nvoid PtCanvas::drawPixelTree(bool all)\n{\n\n    static int times_called = 0;\n\n    times_called++;\n\n    // print(\"draw pixel tree: {}\", times_called);\n\n    /// which vertical slice to draw at x = 0\n    const auto v_begin = all ? 0 : pwidget_->horizontalScrollBar()->value();\n    /// how many slices are visible\n    const auto visible_slices = pwidget_->width();\n    const auto v_end = all ? totalSlices() : v_begin + visible_slices;\n\n    bool end_reached = false;\n\n    for (auto slice = v_begin; slice < v_end && !end_reached; ++slice)\n    {\n        int x = slice - v_begin;\n        int first_idx = slice * compression_;\n\n        /// is silce selected?\n        bool selected = selected_slices_.find(slice) != selected_slices_.end();\n\n        QRgb color = dark_mode_ ? qRgb(215, 225, 215) : qRgb(30, 40, 30);\n\n        if (selected)\n        {\n            color = qRgb(255, 0, 0);\n        }\n\n        /// See if there is a solution node (warning: duplication / bad code!)\n        /// (Note that this has to be separate from drawing, as the solution line\n        /// should go behind the actual nodes)\n        bool has_solutions = false;\n        for (auto idx = first_idx; idx < first_idx + compression_; ++idx)\n        {\n            if (idx == pi_seq_.size())\n            {\n                end_reached = true;\n                break;\n            }\n            const auto node = pi_seq_[idx].nid;\n\n            if (tree_.getStatus(node) == tree::NodeStatus::SOLVED)\n            {\n                has_solutions = true;\n                break;\n            }\n        }\n\n        if (has_solutions)\n        {\n            for (auto y = 0; y < tree_.depth(); ++y)\n            {\n                pimage_->drawPixel(x, y, colors::solution);\n            }\n        }\n\n        /// Draw a \"slice\"\n        for (auto idx = first_idx; idx < first_idx + compression_; ++idx)\n        {\n            if (idx == pi_seq_.size())\n            {\n                end_reached = true;\n                break;\n            }\n            const auto &pi = pi_seq_[idx];\n            const int y = pi.depth;\n            pimage_->drawPixel(x, y, color);\n        }\n    }\n}\n\nvoid PtCanvas::selectNodes(int vbegin, int vend)\n{\n\n    if (vend < vbegin)\n    {\n        return;\n    }\n\n    selected_slices_.clear();\n\n    vend = std::min(totalSlices() - 1, vend);\n\n    for (auto slice = vbegin; slice <= vend; ++slice)\n    {\n        selected_slices_.insert(slice);\n    }\n\n    /// recover the nodes from selected slices\n    std::vector<NodeID> selected_nodes;\n    for (auto slice : selected_slices_)\n    {\n        int first_idx = slice * compression_;\n\n        for (auto idx = first_idx; idx < first_idx + compression_; ++idx)\n        {\n            if (idx == pi_seq_.size())\n                break;\n\n            selected_nodes.push_back(pi_seq_[idx].nid);\n        }\n    }\n\n    emit nodesSelected(selected_nodes);\n\n    redrawAll();\n}\n\nvoid PtCanvas::setDarkMode(bool d)\n{\n    dark_mode_ = d;\n    pimage_->setDarkMode(dark_mode_);\n    redrawAll();\n}\n\n} // namespace pixel_view\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/pixel_views/pt_canvas.hh",
    "content": "#pragma once\n\n#include <QScrollArea>\n#include <QLabel>\n#include <QWidget>\n\n#include <memory>\n#include <set>\n\n#include \"../core.hh\"\n#include \"pixel_widget.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace tree\n{\nclass NodeTree;\n}\n\nnamespace pixel_view\n{\n\nclass PixelWidget;\n\nstruct PixelItem\n{\n    NodeID nid;\n    int depth;\n};\n\nclass PixelImage;\n\nclass PtCanvas : public QWidget\n{\n    Q_OBJECT\n    const tree::NodeTree &tree_;\n\n    std::unique_ptr<PixelImage> pimage_;\n\n    std::unique_ptr<PixelWidget> pwidget_;\n\n    /// Pixel Item DFS sequence\n    std::vector<PixelItem> pi_seq_;\n\n    /// the number of pixels per vertical line\n    int compression_ = 2;\n\n    /// which slices are currently selected\n    std::set<int> selected_slices_;\n\n    bool dark_mode_ = false;\n\n  private:\n    void drawPixelTree(bool all = false);\n\n\n    std::vector<PixelItem> constructPixelTree() const;\n\n  public:\n    PtCanvas(const tree::NodeTree &tree);\n\n    ~PtCanvas();\n\n    PixelImage* get_pimage(void) const { return pimage_.get(); }\n    void redrawAll(bool all = false);\n    /// How many vertical slices does the tree span\n    int totalSlices() const;\n    void setCompression(int c) { compression_ = c; }\n\n    void setDarkMode(bool d);\n\n  signals:\n\n    /// notify the traditional visualisation\n    void nodesSelected(const std::vector<NodeID> &nodes);\n\n  public slots:\n\n    /// Select nodes based on vertical slices (may be out of bounds)\n    void selectNodes(int vbegin, int vend);\n};\n\n} // namespace pixel_view\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/receiver_thread.cpp",
    "content": "#include \"receiver_thread.hh\"\n\n#include \"receiver_worker.hh\"\n#include \"execution.hh\"\n#include \"../cpp-integration/message.hpp\"\n\n#include <iostream>\n#include <thread>\n#include <chrono>\n#include <QTcpSocket>\n#include \"utils/debug.hh\"\n\nnamespace cpprofiler\n{\n\nReceiverThread::ReceiverThread(intptr_t socket_desc, const Settings &s)\n    : m_socket_desc(socket_desc), m_settings(s)\n{\n    std::cerr << \"socket descriptor: \" << socket_desc << std::endl;\n\n    qRegisterMetaType<MessageWrapper>();\n}\n\nvoid ReceiverThread::run()\n{\n\n    QTcpSocket socket;\n\n    m_worker.reset(new ReceiverWorker{socket, m_settings});\n\n    /// propagate the signal further upwards;\n    /// blocking connection is used to ensure that the execution is created\n    /// before any further message is processed\n    connect(m_worker.get(), &ReceiverWorker::notifyStart,\n            this, &ReceiverThread::notifyStart, Qt::BlockingQueuedConnection);\n\n    connect(m_worker.get(), &ReceiverWorker::newNode,\n            this, &ReceiverThread::newNode);\n\n    connect(m_worker.get(), &ReceiverWorker::doneReceiving,\n            this, &ReceiverThread::doneReceiving);\n\n    auto res = socket.setSocketDescriptor(m_socket_desc);\n\n    if (!res)\n    {\n        std::cerr << \"invalid socket descriptor\\n\";\n        this->quit();\n        return;\n    }\n\n    connect(&socket, &QTcpSocket::readyRead, m_worker.get(), &ReceiverWorker::doRead);\n\n    connect(&socket, &QTcpSocket::disconnected, [this]() {\n        this->quit();\n    });\n\n    exec();\n}\n\nReceiverThread::~ReceiverThread() = default;\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/receiver_thread.hh",
    "content": "#ifndef CPPROFILER_RECEIVER_HH\n#define CPPROFILER_RECEIVER_HH\n\n#include <cstdint>\n#include <memory>\n#include <QThread>\n\n#include \"message_wrapper.hh\"\n\nnamespace cpprofiler\n{\n\nclass Conductor;\nclass Execution;\nclass ReceiverWorker;\nclass Settings;\n\nclass ReceiverThread : public QThread\n{\n    Q_OBJECT\n    const intptr_t m_socket_desc;\n    std::unique_ptr<ReceiverWorker> m_worker;\n\n    const Settings &m_settings;\n\n    void run() override;\n\n  signals:\n\n    void notifyStart(const std::string &ex_name, int ex_id, bool restarts);\n    void newNode(const cpprofiler::MessageWrapper& node);\n    void doneReceiving();\n\n  public:\n    ReceiverThread(intptr_t socket_desc, const Settings &s);\n    ~ReceiverThread();\n};\n\n} // namespace cpprofiler\n\n#endif\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/receiver_worker.cpp",
    "content": "#include \"receiver_worker.hh\"\n#include \"conductor.hh\"\n#include <iostream>\n#include <string>\n#include <thread>\n#include <QTcpSocket>\n#include <QJsonObject>\n#include <QJsonDocument>\n\n#include \"execution.hh\"\n#include \"utils/utils.hh\"\n\n#include \"tree/node.hh\"\n#include \"settings.hh\"\n\nnamespace cpprofiler\n{\n\nReceiverWorker::ReceiverWorker(QTcpSocket &socket, const Settings &s)\n    : m_socket(socket), m_settings(s)\n{\n}\n\nstatic int32_t ArrayToInt(const QByteArray &ba)\n{\n    const char *p = ba.data();\n    return *(reinterpret_cast<const quint32 *>(p));\n}\n\nvoid ReceiverWorker::doRead()\n{\n    // are there enough bytes to read something\n    bool can_read_more = true;\n\n    while (m_socket.bytesAvailable() > 0 || can_read_more)\n    {\n\n        if (m_socket.bytesAvailable() > 0)\n            can_read_more = true;\n\n        m_buffer.append(m_socket.readAll());\n\n        // read the size of the next field if haven't already\n        if (!m_state.size_read)\n        {\n\n            if (m_buffer.size() < m_state.bytes_read + FIELD_SIZE_NBYTES)\n            {\n                /// can't read, need to wait for more bytes\n                can_read_more = false;\n                continue;\n            }\n\n            /// enough bytes to read size\n            m_state.msg_size = ArrayToInt(m_buffer.mid(m_state.bytes_read, 4));\n            m_state.bytes_read += 4;\n            m_state.size_read = true;\n        }\n        else\n        {\n\n            if (m_buffer.size() < m_state.bytes_read + m_state.msg_size)\n            {\n                /// can't read, need to wait for more bytes\n                can_read_more = false;\n                continue;\n            }\n\n            marshalling.deserialize(m_buffer.data() + m_state.bytes_read, m_state.msg_size);\n\n            auto msg = marshalling.get_msg();\n            handleMessage(msg);\n\n            m_state.bytes_read += m_state.msg_size;\n            m_state.size_read = false;\n\n            m_state.msg_processed++;\n\n            /// reset the buffer every MSG_PER_BUFFER messages\n            if (m_state.msg_processed == MSG_PER_BUFFER)\n            {\n                m_state.msg_processed = 0;\n                m_buffer.remove(0, m_state.bytes_read);\n                m_state.bytes_read = 0;\n            }\n        }\n    }\n}\n\nvoid ReceiverWorker::handleStart(const Message &msg)\n{\n\n    /// create new execution\n\n    std::string execution_name = \"<no name>\";\n    bool has_restarts = false;\n\n    int exec_id = -1;\n\n    if (msg.has_info())\n    {\n\n        auto info_bytes = QByteArray::fromStdString(msg.info());\n        auto json_doc = QJsonDocument::fromJson(info_bytes);\n\n        if (json_doc.isNull() || json_doc.isArray())\n        {\n            print(\"Warning: start message info is invalid or empty\");\n        }\n        else\n        {\n            QJsonObject json_obj = json_doc.object();\n\n            auto name_val = json_obj.value(\"name\");\n\n            if (name_val.isString())\n            {\n                execution_name = name_val.toString().toStdString();\n            }\n\n            auto restarts_val = json_obj.value(\"has_restarts\");\n\n            if (restarts_val.isBool())\n            {\n                has_restarts = restarts_val.toBool();\n            }\n\n            {\n                auto e_id_val = json_obj.value(\"execution_id\");\n                exec_id = e_id_val.toInt();\n            }\n        }\n    }\n\n    print(\"New execution: (name: {}, exec_id: {}, has restarts: {}\", execution_name, exec_id, has_restarts);\n\n    emit notifyStart(execution_name, exec_id, has_restarts); // blocking connection\n}\n\n/// nid, pid, alt, kids are needed during the Tree Construction\n/// after the tree is build, these fields are easier to get from the tree structure\n\n/// label, nogood and info can be quieried at any time\n\nvoid ReceiverWorker::handleMessage(const Message &msg)\n{\n\n    if (m_settings.receiver_delay > 0)\n    {\n        utils::sleep_for_ms(m_settings.receiver_delay);\n    }\n\n    switch (msg.type())\n    {\n    case cpprofiler::MsgType::NODE:\n\n        try\n        {\n            MessageWrapper m(msg);\n            emit newNode(m);\n        }\n        catch (std::exception &e)\n        {\n        }\n\n        break;\n    case cpprofiler::MsgType::START:\n        print(\"message: start\");\n        handleStart(msg);\n        break;\n    case cpprofiler::MsgType::DONE:\n        emit doneReceiving();\n        print(\"message: done\");\n        break;\n    case cpprofiler::MsgType::RESTART:\n        print(\"message: restart\");\n        break;\n    default:\n        print(\"ERROR: unknown solver message\");\n    }\n}\n\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/receiver_worker.hh",
    "content": "#ifndef CPPROFILER_RECEIVER_WORKER_HH\n#define CPPROFILER_RECEIVER_WORKER_HH\n\n#include <QObject>\n#include <memory>\n\n#include \"message_wrapper.hh\"\n\nclass QTcpSocket;\n\nnamespace cpprofiler\n{\n\nclass Conductor;\nclass Execution;\nclass Settings;\n\nclass ReceiverWorker : public QObject\n{\n    Q_OBJECT\n\n    /// number of messages to read before resetting the buffer\n    static constexpr int MSG_PER_BUFFER = 10000;\n    /// the number of bytes per field size\n    static constexpr int FIELD_SIZE_NBYTES = 4;\n\n    /// read buffer\n    QByteArray m_buffer;\n\n    QTcpSocket &m_socket;\n\n    struct ReadState\n    {\n        /// whether the size has been read\n        bool size_read = false;\n        // size of the current message in bytes\n        int msg_size = 0;\n        /// messages processed since last buffer reset\n        int msg_processed = 0;\n        /// where to read next from in the buffer\n        int bytes_read = 0;\n    } m_state;\n\n    // Execution* execution;\n\n    cpprofiler::MessageMarshalling marshalling;\n\n    void handleStart(const cpprofiler::Message &msg);\n\n    void handleMessage(const cpprofiler::Message &msg);\n\n    const Settings &m_settings;\n\n  signals:\n\n    void notifyStart(const std::string &ex_name, int ex_id, bool restarts);\n    void newNode(const cpprofiler::MessageWrapper& node);\n    void doneReceiving();\n\n  public:\n    ReceiverWorker(QTcpSocket &socket, const Settings &s);\n  public slots:\n    void doRead();\n};\n\n} // namespace cpprofiler\n\n#endif\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/settings.hh",
    "content": "#pragma once\n\nnamespace cpprofiler\n{\n\nclass Settings\n{\npublic:\n    /// delay in ms after receiving a new message\n    int receiver_delay = 0;\n    int auto_hide_failed = true;\n};\n\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/solver_data.cpp",
    "content": "#include \"solver_data.hh\"\n\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QJsonArray>\n\nnamespace cpprofiler\n{\n\n/// convert json array to std::vector\nstatic std::vector<int> parse_reasons_json(QJsonValue &json_arr)\n{\n    std::vector<int> res;\n\n    auto arr = json_arr.toArray();\n\n    for (auto el : arr)\n    {\n\n        if (el.isDouble())\n        {\n            res.push_back(el.toInt());\n        }\n    }\n\n    return res;\n}\n\n/// convert json array to std::vector\nstatic std::vector<SolverID> parse_nogoods_json(QJsonValue &json_arr)\n{\n    std::vector<SolverID> res;\n\n    auto arr = json_arr.toArray();\n\n    for (auto el : arr)\n    {\n\n        if (!el.isArray())\n        {\n            const auto obj = el.toObject();\n            /// object should be of the form {\"nid\":<>, \"rid\":<>, \"tid\":<>}\n\n            auto nid_obj = obj.value(\"nid\");\n            auto rid_obj = obj.value(\"rid\");\n            auto tid_obj = obj.value(\"tid\");\n\n            if (nid_obj.isDouble() && rid_obj.isDouble() && tid_obj.isDouble())\n            {\n\n                const auto nid = nid_obj.toInt();\n                const auto rid = rid_obj.toInt();\n                const auto tid = tid_obj.toInt();\n\n                res.push_back({nid, rid, tid});\n            }\n        }\n    }\n\n    return res;\n}\n\nvoid SolverData::processInfo(NodeID nid, const std::string &info_str)\n{\n    auto info_bytes = QByteArray::fromStdString(info_str);\n    QJsonParseError json_err;\n    auto json_doc = QJsonDocument::fromJson(info_bytes, &json_err);\n\n    if (json_err.error != QJsonParseError::NoError) {\n        print(\"QJsonDocument::fromJson() error: {}\\ninfo_str:\\n {}\\n\", json_err.errorString(), info_str);\n        return;\n    }\n\n    if (json_doc.isNull() || json_doc.isEmpty())\n    {\n        print(\"no info for node {}\", nid);\n        return;\n    }\n\n    setInfo(nid, info_str);\n\n    QJsonObject json_obj = json_doc.object();\n\n    if (json_obj.isEmpty())\n    {\n        return;\n    }\n\n    auto reasons = json_obj.value(\"reasons\");\n    if (reasons.isArray())\n    {\n        auto constraints = parse_reasons_json(reasons);\n        // print(\"constraints for {}: {}\", nid, constraints);\n        contrib_cs_.insert({nid, std::move(constraints)});\n    }\n\n    auto nogoods_json = json_obj.value(\"nogoods\");\n    if (nogoods_json.isArray())\n    {\n        auto nogoods = parse_nogoods_json(nogoods_json);\n\n        /// Nogoods contributing to the failure at `nid`\n        std::vector<NodeID> c_nogoods;\n\n        for (const auto sid : nogoods)\n        {\n            auto ng_nid = getNodeId(sid);\n\n            if (ng_nid != NodeID::NoNode)\n            {\n                c_nogoods.push_back(ng_nid);\n            }\n        }\n\n        // print(\"responsible nogoods for {}: {}\", nid, c_nogoods);\n\n        contrib_ngs_.insert({nid, std::move(c_nogoods)});\n    }\n}\n\nvoid IdMap::addPair(SolverID sid, tree::NodeID nid)\n{\n    QWriteLocker locker(&m_lock);\n\n    uid2id_.insert({sid, nid});\n    nid2uid_.insert({nid, sid});\n}\n\ntree::NodeID IdMap::get(SolverID sid) const\n{\n    QReadLocker locker(&m_lock);\n\n    const auto it = uid2id_.find(sid);\n\n    if (it != uid2id_.end())\n    {\n        return it->second;\n    }\n    else\n    {\n        return NodeID::NoNode;\n    }\n}\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/solver_data.hh",
    "content": "#pragma once\n\n#include <QReadWriteLock>\n#include <unordered_map>\n\n#include \"core.hh\"\n\n#include \"solver_id.hh\"\n\nnamespace cpprofiler\n{\n\nclass NameMap;\n\nclass IdMap\n{\n\n    mutable QReadWriteLock m_lock;\n\n    std::unordered_map<SolverID, NodeID> uid2id_;\n\n    std::unordered_map<NodeID, SolverID> nid2uid_;\n\n  public:\n    void addPair(SolverID, NodeID);\n\n    NodeID get(SolverID) const;\n\n    SolverID getUID(NodeID nid) const\n    {\n        auto it = nid2uid_.find(nid);\n\n        if (it != nid2uid_.end())\n        {\n            return nid2uid_.at(nid);\n        }\n        else\n        {\n            return {-1, -1, -1};\n        }\n    }\n};\n\nclass SolverData\n{\n\n    /// TODO:save/load id map to/from DB\n    IdMap m_id_map;\n\n    std::unordered_map<NodeID, Info> info_map_;\n\n    std::unordered_map<NodeID, Nogood> nogood_map_;\n\n    /// Constraints contributing to a no-good at NodeID\n    std::unordered_map<NodeID, std::vector<int>> contrib_cs_;\n\n    /// Nogoods contributing to the failure at node NodeID\n    std::unordered_map<NodeID, std::vector<NodeID>> contrib_ngs_;\n\n    /// Time since the beginning of solving process;\n    std::unordered_map<NodeID, int> node_time_;\n\n  public:\n    NodeID getNodeId(SolverID sid) const\n    {\n        return m_id_map.get(sid);\n    }\n\n    void setNodeId(SolverID sid, NodeID nid)\n    {\n        m_id_map.addPair(sid, nid);\n    }\n\n    SolverID getSolverID(NodeID nid) const\n    {\n        return m_id_map.getUID(nid);\n    }\n\n    /// Get the reasons (constraint ids) for the nogood at node `nid`\n    const std::vector<int> *getContribConstraints(NodeID nid) const\n    {\n        const auto it = contrib_cs_.find(nid);\n\n        if (it == contrib_cs_.end())\n            return nullptr;\n        return &(it->second);\n    }\n\n    /// Get nogoods (identified by the NodeID where they were created)\n    /// that contribute to the failure at `nid`;\n    const std::vector<NodeID> *getContribNogoods(NodeID nid) const\n    {\n        const auto it = contrib_ngs_.find(nid);\n\n        if (it == contrib_ngs_.end())\n            return nullptr;\n        return &(it->second);\n    }\n\n    /// Associate nogood `ng` with node `nid`\n    void setNogood(NodeID nid, const std::string &orig, const std::string &renamed)\n    {\n        nogood_map_.insert({nid, Nogood(orig, renamed)});\n    }\n\n    void setNogood(NodeID nid, const std::string &orig)\n    {\n        nogood_map_.insert({nid, Nogood(orig)});\n    }\n\n    const Nogood &getNogood(NodeID nid) const\n    {\n        auto it = nogood_map_.find(nid);\n        if (it != nogood_map_.end())\n        {\n            return it->second;\n        }\n        else\n        {\n            return Nogood::empty;\n        }\n    }\n\n    void setInfo(NodeID nid, const std::string &orig)\n    {\n        info_map_.insert({nid, Info(orig)});\n    }\n\n    Info getInfo(NodeID nid) const\n    {\n        auto it = info_map_.find(nid);\n        if (it != info_map_.end())\n        {\n            return it->second;\n        }\n        else\n        {\n            return Info(\"\");\n        }\n    }\n\n    /// Process node info looking for reasons, contributing nogoods for failed nodes etc.\n    void processInfo(NodeID nid, const std::string &info_str);\n\n    /// Whether the data stores at least one no-good\n    bool hasNogoods() const\n    {\n        return !nogood_map_.empty();\n    }\n\n    /// Whether the data stores at least one no-good\n    bool hasInfo() const\n    {\n        return !info_map_.empty();\n    }\n};\n\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/solver_id.hh",
    "content": "namespace cpprofiler\n{\n\n/// Mimics UID\nstruct SolverID\n{\n    // Node number\n    int32_t nid;\n    // Restart id\n    int32_t rid;\n    // Thread id\n    int32_t tid;\n\n    std::string toString() const\n    {\n        return std::string(\"{\") + std::to_string(nid) +\n               \", \" + std::to_string(rid) + \", \" + std::to_string(tid) + \"}\";\n    }\n};\n\nstatic bool operator==(const SolverID &lhs, const SolverID &rhs)\n{\n    return (lhs.nid == rhs.nid) && (lhs.tid == rhs.tid) && (lhs.rid == rhs.rid);\n}\n\n} // namespace cpprofiler\n\nnamespace std\n{\ntemplate <>\nstruct hash<cpprofiler::SolverID>\n{\n    size_t operator()(cpprofiler::SolverID const &a) const\n    {\n        const size_t h1(std::hash<int>{}(a.nid));\n        size_t const h2(std::hash<int>{}(a.tid));\n        size_t const h3(std::hash<int>{}(a.rid));\n        return h1 ^ (h2 << 1) ^ (h3 << 1);\n    }\n};\n} // namespace std"
  },
  {
    "path": "cp-profiler/src/cpprofiler/stats_bar.hpp",
    "content": "#include <QWidget>\n#include <QLabel>\n#include <thread>\n\n#include \"tree/node_tree.hh\"\n#include \"tree/node_widget.hh\"\n\nnamespace cpprofiler\n{\n\nclass NodeStatsBar : public QWidget\n{\n\n    const tree::NodeStats &stats;\n    /// Status bar label for maximum depth indicator\n    QLabel *depthLabel;\n    /// Status bar label for number of solutions\n    QLabel *solvedLabel;\n    /// Status bar label for number of failures\n    QLabel *failedLabel;\n    /// Status bar label for number of skipped nodes\n    QLabel *skippedLabel;\n    /// Status bar label for number of choices\n    QLabel *choicesLabel;\n    /// Status bar label for number of open nodes\n    QLabel *openLabel;\n\n  public:\n    NodeStatsBar(QWidget *parent, const tree::NodeStats &ns) : QWidget(parent), stats(ns)\n    {\n\n        using namespace tree;\n\n        QHBoxLayout *hbl = new QHBoxLayout{this};\n        hbl->setContentsMargins(2, 1, 2, 1);\n\n        hbl->addWidget(new QLabel(\"Depth:\"));\n        depthLabel = new QLabel(\"0\");\n        hbl->addWidget(depthLabel);\n\n        solvedLabel = new QLabel(\"0\");\n        hbl->addWidget(new NodeWidget(NodeStatus::SOLVED));\n        hbl->addWidget(solvedLabel);\n\n        failedLabel = new QLabel(\"0\");\n        hbl->addWidget(new NodeWidget(NodeStatus::FAILED));\n        hbl->addWidget(failedLabel);\n\n        skippedLabel = new QLabel(\"0\");\n        hbl->addWidget(new NodeWidget(NodeStatus::SKIPPED));\n        hbl->addWidget(skippedLabel);\n\n        choicesLabel = new QLabel(\"0\");\n        hbl->addWidget(new NodeWidget(NodeStatus::BRANCH));\n        hbl->addWidget(choicesLabel);\n\n        openLabel = new QLabel(\"0\");\n        hbl->addWidget(new NodeWidget(NodeStatus::UNDETERMINED));\n        hbl->addWidget(openLabel);\n    }\n\n  public slots:\n\n    void update()\n    {\n        depthLabel->setNum(stats.maxDepth());\n        openLabel->setNum(stats.undeterminedCount());\n        solvedLabel->setNum(stats.solvedCount());\n        failedLabel->setNum(stats.failedCount());\n        skippedLabel->setNum(stats.skippedCount());\n        choicesLabel->setNum(stats.branchCount());\n    }\n};\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tcp_server.cpp",
    "content": "#include \"tcp_server.hh\"\n\nnamespace cpprofiler\n{\n\nTcpServer::TcpServer(std::function<void(intptr_t)> callback)\n    : QTcpServer{}, m_callback(callback) {}\n\nvoid TcpServer::incomingConnection(qintptr handle)\n{\n    m_callback(handle);\n}\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tcp_server.hh",
    "content": "#ifndef CPPROFILER_TCP_SERVER_HH\n#define CPPROFILER_TCP_SERVER_HH\n\n#include <QTcpServer>\n#include <functional>\n#include <cstdint>\n\nnamespace cpprofiler\n{\n\nclass TcpServer : public QTcpServer\n{\n    Q_OBJECT\n  public:\n    TcpServer(std::function<void(intptr_t)> callback);\n\n  private:\n    void incomingConnection(qintptr socketDesc) override;\n\n    std::function<void(intptr_t)> m_callback;\n};\n\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tests/execution_test.cpp",
    "content": "#include \"execution_test.hh\"\n\n#include \"../core.hh\"\n#include \"../conductor.hh\"\n#include \"../user_data.hh\"\n\n#include \"../execution.hh\"\n#include \"../tree/structure.hh\"\n#include \"../utils/array.hh\"\n#include \"../utils/utils.hh\"\n#include \"../tree/node_info.hh\"\n#include \"../execution_window.hh\"\n#include \"../tree/traditional_view.hh\"\n\n#include \"../db_handler.hh\"\n\n#include <QDebug>\n#include <iostream>\n\nnamespace cpprofiler\n{\nnamespace tests\n{\nnamespace execution\n{\n\nvoid copy_test(utils::Array<int> arr)\n{\n    auto new_arr = arr;\n}\n\nvoid array_test()\n{\n\n    utils::Array<int> arr(1);\n\n    copy_test(arr);\n}\n\nvoid binary_tree_execution(Conductor &conductor)\n{\n\n    auto ex = conductor.addNewExecution(\"test execution\");\n\n    auto &tree = ex->tree();\n\n    auto root = tree.createRoot(2, \"0\");\n    auto n1 = tree.promoteNode(root, 0, 2, tree::NodeStatus::BRANCH, \"1\");\n    auto n2 = tree.promoteNode(root, 1, 2, tree::NodeStatus::BRANCH, \"2\");\n\n    ex->userData().setSelectedNode(n1);\n\n    auto n3 = tree.promoteNode(n1, 0, 2, tree::NodeStatus::BRANCH, \"3\");\n    auto n4 = tree.promoteNode(n1, 1, 2, tree::NodeStatus::BRANCH, \"4\");\n\n    auto n5 = tree.promoteNode(n3, 0, 2, tree::NodeStatus::BRANCH, \"5\");\n    auto n6 = tree.promoteNode(n3, 1, 2, tree::NodeStatus::BRANCH, \"6\");\n\n    tree.promoteNode(n5, 0, 0, tree::NodeStatus::SOLVED, \"7\");\n    tree.promoteNode(n5, 1, 0, tree::NodeStatus::FAILED, \"8\");\n\n    tree.promoteNode(n6, 0, 0, tree::NodeStatus::FAILED, \"9\");\n    tree.promoteNode(n6, 1, 0, tree::NodeStatus::SKIPPED, \"10\");\n\n    auto n7 = tree.promoteNode(n4, 0, 0, tree::NodeStatus::FAILED, \"11\");\n    auto n8 = tree.promoteNode(n4, 1, 0, tree::NodeStatus::FAILED, \"12\");\n\n    auto n9 = tree.promoteNode(n2, 0, 2, tree::NodeStatus::BRANCH, \"13\");\n    auto n10 = tree.promoteNode(n2, 1, 0, tree::NodeStatus::FAILED, \"14\");\n\n    auto n11 = tree.promoteNode(n9, 0, 2, tree::NodeStatus::BRANCH, \"15\");\n    auto n12 = tree.promoteNode(n9, 1, 0, tree::NodeStatus::FAILED, \"16\");\n\n    auto n13 = tree.promoteNode(n11, 0, 2, tree::NodeStatus::BRANCH, \"17\");\n    auto n14 = tree.promoteNode(n11, 1, 0, tree::NodeStatus::FAILED, \"18\");\n\n    auto n15 = tree.promoteNode(n13, 0, 2, tree::NodeStatus::BRANCH, \"19\");\n    auto n16 = tree.promoteNode(n13, 1, 0, tree::NodeStatus::FAILED, \"20\");\n\n    auto n17 = tree.promoteNode(n15, 0, 2, tree::NodeStatus::BRANCH, \"21\");\n    auto n18 = tree.promoteNode(n15, 1, 0, tree::NodeStatus::FAILED, \"22\");\n\n    auto n19 = tree.promoteNode(n17, 0, 2, tree::NodeStatus::BRANCH, \"23\");\n    auto n20 = tree.promoteNode(n17, 1, 0, tree::NodeStatus::FAILED, \"24\");\n\n    auto n21 = tree.promoteNode(n19, 0, 2, tree::NodeStatus::BRANCH, \"25\");\n    auto n22 = tree.promoteNode(n19, 1, 0, tree::NodeStatus::FAILED, \"26\");\n\n    auto n23 = tree.promoteNode(n21, 0, 2, tree::NodeStatus::BRANCH, \"27\");\n    auto n24 = tree.promoteNode(n21, 1, 0, tree::NodeStatus::UNDETERMINED, \"28\");\n\n    auto n25 = tree.promoteNode(n23, 0, 2, tree::NodeStatus::BRANCH, \"29\");\n    auto n26 = tree.promoteNode(n23, 1, 0, tree::NodeStatus::FAILED, \"30\");\n\n    auto n27 = tree.promoteNode(n25, 0, 2, tree::NodeStatus::BRANCH, \"31\");\n    auto n28 = tree.promoteNode(n25, 1, 0, tree::NodeStatus::FAILED, \"32\");\n\n    auto n29 = tree.promoteNode(n27, 0, 2, tree::NodeStatus::BRANCH, \"33\");\n    auto n30 = tree.promoteNode(n27, 1, 0, tree::NodeStatus::FAILED, \"34\");\n\n    auto n31 = tree.promoteNode(n29, 0, 2, tree::NodeStatus::BRANCH, \"35\");\n    auto n32 = tree.promoteNode(n29, 1, 0, tree::NodeStatus::FAILED, \"36\");\n\n    auto n33 = tree.promoteNode(n31, 0, 2, tree::NodeStatus::BRANCH, \"37\");\n    auto n34 = tree.promoteNode(n31, 1, 0, tree::NodeStatus::FAILED, \"38\");\n\n    auto n35 = tree.promoteNode(n33, 0, 0, tree::NodeStatus::FAILED, \"39\");\n    auto n36 = tree.promoteNode(n33, 1, 0, tree::NodeStatus::FAILED, \"40\");\n\n    conductor.showTraditionalView(ex);\n}\n\nvoid nary_execution(Conductor &conductor)\n{\n\n    auto ex = conductor.addNewExecution(\"n-ary execution\");\n\n    auto &tree = ex->tree();\n\n    auto root = tree.createRoot(4);\n    auto n1 = tree.promoteNode(root, 0, 2, tree::NodeStatus::BRANCH, \"1\");\n    auto n2 = tree.promoteNode(root, 1, 2, tree::NodeStatus::BRANCH, \"2\");\n    auto n3 = tree.promoteNode(root, 2, 0, tree::NodeStatus::FAILED, \"3\");\n    auto n4 = tree.promoteNode(root, 3, 2, tree::NodeStatus::BRANCH, \"4\");\n\n    auto n5 = tree.promoteNode(n1, 0, 0, tree::NodeStatus::FAILED, \"5\");\n    auto n6 = tree.promoteNode(n1, 1, 0, tree::NodeStatus::FAILED, \"6\");\n\n    auto n7 = tree.promoteNode(n2, 0, 0, tree::NodeStatus::FAILED, \"7\");\n    auto n8 = tree.promoteNode(n2, 1, 2, tree::NodeStatus::BRANCH, \"8\");\n\n    auto n9 = tree.promoteNode(n4, 0, 0, tree::NodeStatus::FAILED, \"9\");\n    auto n10 = tree.promoteNode(n4, 1, 0, tree::NodeStatus::FAILED, \"10\");\n\n    auto n11 = tree.promoteNode(n8, 0, 0, tree::NodeStatus::FAILED, \"11\");\n    auto n12 = tree.promoteNode(n8, 1, 0, tree::NodeStatus::FAILED, \"12\");\n}\n\nvoid larger_nary_execution(Conductor &conductor)\n{\n\n    auto ex = conductor.addNewExecution(\"n-ary execution\");\n\n    auto &tree = ex->tree();\n\n    auto root = tree.createRoot(4);\n    auto n1 = tree.promoteNode(root, 0, 2, tree::NodeStatus::BRANCH);\n    auto n2 = tree.promoteNode(root, 1, 2, tree::NodeStatus::BRANCH);\n    auto n3 = tree.promoteNode(root, 2, 0, tree::NodeStatus::FAILED);\n    auto n4 = tree.promoteNode(root, 3, 2, tree::NodeStatus::BRANCH);\n\n    auto n5 = tree.promoteNode(n1, 0, 0, tree::NodeStatus::FAILED);\n    auto n6 = tree.promoteNode(n1, 1, 12, tree::NodeStatus::BRANCH);\n\n    auto n6a = tree.promoteNode(n6, 0, 0, tree::NodeStatus::FAILED);\n    auto n6b = tree.promoteNode(n6, 1, 0, tree::NodeStatus::FAILED);\n    auto n6c = tree.promoteNode(n6, 2, 0, tree::NodeStatus::FAILED);\n    auto n6d = tree.promoteNode(n6, 3, 0, tree::NodeStatus::FAILED);\n    tree.promoteNode(n6, 4, 0, tree::NodeStatus::FAILED);\n    tree.promoteNode(n6, 5, 0, tree::NodeStatus::FAILED);\n    tree.promoteNode(n6, 6, 0, tree::NodeStatus::FAILED);\n    tree.promoteNode(n6, 7, 0, tree::NodeStatus::FAILED);\n    tree.promoteNode(n6, 8, 0, tree::NodeStatus::FAILED);\n    tree.promoteNode(n6, 9, 0, tree::NodeStatus::FAILED);\n    tree.promoteNode(n6, 10, 0, tree::NodeStatus::FAILED);\n    tree.promoteNode(n6, 11, 0, tree::NodeStatus::FAILED);\n\n    auto n7 = tree.promoteNode(n2, 0, 0, tree::NodeStatus::FAILED);\n    auto n8 = tree.promoteNode(n2, 1, 2, tree::NodeStatus::BRANCH);\n\n    auto n9 = tree.promoteNode(n4, 0, 0, tree::NodeStatus::FAILED);\n    auto n10 = tree.promoteNode(n4, 1, 0, tree::NodeStatus::FAILED);\n\n    auto n11 = tree.promoteNode(n8, 0, 0, tree::NodeStatus::FAILED);\n    auto n12 = tree.promoteNode(n8, 1, 0, tree::NodeStatus::FAILED);\n}\n\nvoid simple_nary_execution(Conductor &conductor)\n{\n\n    auto ex = conductor.addNewExecution(\"n-ary execution\");\n\n    auto &tree = ex->tree();\n\n    auto root = tree.createRoot(4);\n    auto n1 = tree.promoteNode(root, 0, 0, tree::NodeStatus::FAILED);\n    auto n2 = tree.promoteNode(root, 1, 0, tree::NodeStatus::FAILED);\n    auto n3 = tree.promoteNode(root, 2, 0, tree::NodeStatus::FAILED);\n    auto n4 = tree.promoteNode(root, 3, 0, tree::NodeStatus::FAILED);\n}\n\nvoid binary_test_1_for_identical_subtrees(Conductor &conductor)\n{\n\n    auto ex = conductor.addNewExecution(\"test for identical subtree algorithm\");\n\n    auto &tree = ex->tree();\n\n    auto root = tree.createRoot(2);\n    auto n1 = tree.promoteNode(root, 0, 2, tree::NodeStatus::BRANCH, \"a\");\n    auto n2 = tree.promoteNode(root, 1, 2, tree::NodeStatus::BRANCH, \"b\");\n\n    auto n3 = tree.promoteNode(n1, 0, 2, tree::NodeStatus::BRANCH, \"c\");\n    auto n4 = tree.promoteNode(n1, 1, 2, tree::NodeStatus::BRANCH, \"d\");\n    auto n11 = tree.promoteNode(n2, 0, 0, tree::NodeStatus::FAILED, \"e\");\n    auto n12 = tree.promoteNode(n2, 1, 2, tree::NodeStatus::BRANCH, \"f\");\n\n    auto n5 = tree.promoteNode(n3, 0, 0, tree::NodeStatus::FAILED, \"g\");\n    auto n6 = tree.promoteNode(n3, 1, 2, tree::NodeStatus::BRANCH, \"h\");\n    auto n9 = tree.promoteNode(n4, 0, 0, tree::NodeStatus::FAILED, \"i\");\n    auto n10 = tree.promoteNode(n4, 1, 0, tree::NodeStatus::FAILED, \"j\");\n    auto n13 = tree.promoteNode(n12, 0, 0, tree::NodeStatus::FAILED, \"k\");\n    auto n14 = tree.promoteNode(n12, 1, 0, tree::NodeStatus::FAILED, \"l\");\n\n    auto n7 = tree.promoteNode(n6, 0, 0, tree::NodeStatus::FAILED, \"m\");\n    auto n8 = tree.promoteNode(n6, 1, 0, tree::NodeStatus::FAILED, \"n\");\n}\n\nvoid binary_test_2_for_identical_subtrees(Conductor &conductor)\n{\n\n    auto ex = conductor.addNewExecution(\"test for identical subtree algorithm\");\n\n    auto &tree = ex->tree();\n\n    auto root = tree.createRoot(2, \"0\");\n    auto n1 = tree.promoteNode(root, 0, 2, tree::NodeStatus::BRANCH, \"1\");\n    auto n2 = tree.promoteNode(root, 1, 2, tree::NodeStatus::BRANCH, \"2\");\n\n    auto n3 = tree.promoteNode(n1, 0, 2, tree::NodeStatus::BRANCH, \"3\");\n    auto n4 = tree.promoteNode(n1, 1, 2, tree::NodeStatus::BRANCH, \"4\");\n    auto n5 = tree.promoteNode(n2, 0, 2, tree::NodeStatus::BRANCH, \"5\");\n    auto n6 = tree.promoteNode(n2, 1, 2, tree::NodeStatus::BRANCH, \"6\");\n\n    auto n7 = tree.promoteNode(n3, 0, 0, tree::NodeStatus::FAILED, \"7\");\n    auto n8 = tree.promoteNode(n3, 1, 0, tree::NodeStatus::FAILED, \"8\");\n    auto n9 = tree.promoteNode(n4, 0, 0, tree::NodeStatus::FAILED, \"9\");\n    auto n10 = tree.promoteNode(n4, 1, 2, tree::NodeStatus::BRANCH, \"10\");\n    auto n13 = tree.promoteNode(n6, 0, 2, tree::NodeStatus::BRANCH, \"13\");\n    auto n14 = tree.promoteNode(n6, 1, 2, tree::NodeStatus::BRANCH, \"14\");\n\n    auto n15 = tree.promoteNode(n10, 0, 0, tree::NodeStatus::FAILED, \"15\");\n    auto n16 = tree.promoteNode(n10, 1, 0, tree::NodeStatus::FAILED, \"16\");\n\n    auto n11 = tree.promoteNode(n5, 0, 0, tree::NodeStatus::FAILED, \"11\");\n    auto n12 = tree.promoteNode(n5, 1, 0, tree::NodeStatus::FAILED, \"12\");\n\n    auto n17 = tree.promoteNode(n13, 0, 0, tree::NodeStatus::FAILED, \"17\");\n    auto n18 = tree.promoteNode(n13, 1, 0, tree::NodeStatus::FAILED, \"18\");\n\n    auto n19 = tree.promoteNode(n14, 0, 0, tree::NodeStatus::FAILED, \"19\");\n    auto n20 = tree.promoteNode(n14, 1, 2, tree::NodeStatus::BRANCH, \"20\");\n\n    auto n21 = tree.promoteNode(n20, 0, 0, tree::NodeStatus::FAILED, \"21\");\n    auto n22 = tree.promoteNode(n20, 1, 0, tree::NodeStatus::FAILED, \"22\");\n}\n\nvoid build_for_comparison_a(tree::NodeTree &tree)\n{\n\n    auto root = tree.createRoot(2, \"0\");\n\n    auto n1 = tree.promoteNode(root, 0, 2, tree::NodeStatus::BRANCH, \"1\");\n    auto n2 = tree.promoteNode(root, 1, 2, tree::NodeStatus::BRANCH, \"2\");\n\n    auto n3 = tree.promoteNode(n1, 0, 0, tree::NodeStatus::FAILED, \"3\");\n    auto n4 = tree.promoteNode(n1, 1, 2, tree::NodeStatus::BRANCH, \"4\");\n\n    auto n7 = tree.promoteNode(n4, 0, 0, tree::NodeStatus::FAILED, \"7\");\n    auto n8 = tree.promoteNode(n4, 1, 0, tree::NodeStatus::FAILED, \"8\");\n\n    auto n5 = tree.promoteNode(n2, 0, 0, tree::NodeStatus::SOLVED, \"5\");\n    auto n6 = tree.promoteNode(n2, 1, 0, tree::NodeStatus::FAILED, \"6\");\n}\n\nvoid build_for_comparison_b(tree::NodeTree &tree)\n{\n\n    auto root = tree.createRoot(2, \"0\");\n\n    auto n1 = tree.promoteNode(root, 0, 2, tree::NodeStatus::BRANCH, \"1\");\n    auto n2 = tree.promoteNode(root, 1, 0, tree::NodeStatus::FAILED, \"2\");\n\n    auto n3 = tree.promoteNode(n1, 0, 0, tree::NodeStatus::FAILED, \"3\");\n    auto n4 = tree.promoteNode(n1, 1, 2, tree::NodeStatus::BRANCH, \"4\");\n\n    auto n7 = tree.promoteNode(n4, 0, 2, tree::NodeStatus::BRANCH, \"7\");\n    auto n8 = tree.promoteNode(n4, 1, 0, tree::NodeStatus::FAILED, \"8\");\n\n    auto n9 = tree.promoteNode(n7, 0, 0, tree::NodeStatus::FAILED, \"9\");\n    auto n10 = tree.promoteNode(n7, 1, 0, tree::NodeStatus::FAILED, \"10\");\n}\n\nvoid comparison(Conductor &c)\n{\n\n    auto ex1 = c.addNewExecution(\"Execution A\");\n    build_for_comparison_a(ex1->tree());\n\n    auto ex2 = c.addNewExecution(\"Execution B\");\n    build_for_comparison_b(ex2->tree());\n\n    c.mergeTrees(ex1, ex2);\n}\n\nvoid comparison2(Conductor &c)\n{\n\n    const auto path = \"/home/maxim/dev/cp-profiler2/golomb8.db\";\n\n    auto ex1 = db_handler::load_execution(path);\n    if (ex1)\n        c.addNewExecution(ex1);\n\n    auto ex2 = db_handler::load_execution(path);\n    if (ex2)\n        c.addNewExecution(ex2);\n\n    c.mergeTrees(ex1.get(), ex2.get());\n}\n\nvoid tree_building(Conductor &c)\n{\n\n    /// create a dummy root node\n    auto ex = c.addNewExecution(\"test tree\");\n\n    auto &tree = ex->tree();\n\n    auto root = tree.createRoot(0);\n\n    auto n1 = tree.promoteNode(NodeID::NoNode, -1, 0, tree::NodeStatus::FAILED, \"1\");\n    //     auto n2 = tree.promoteNode(root, 1, 0, tree::NodeStatus::FAILED, \"2\");\n\n    //     auto n3 = tree.promoteNode(n1, 0, 0, tree::NodeStatus::FAILED, \"3\");\n    //     auto n4 = tree.promoteNode(n1, 1, 0, tree::NodeStatus::FAILED, \"4\");\n}\n\nvoid hiding_failed_test(Conductor &c)\n{\n\n    auto ex = c.addNewExecution(\"test hiding failed\");\n\n    auto &tree = ex->tree();\n\n    auto root = tree.createRoot(2, \"0\");\n\n    auto n1 = tree.promoteNode(root, 0, 2, tree::NodeStatus::BRANCH, \"1\");\n    auto n2 = tree.promoteNode(root, 1, 2, tree::NodeStatus::BRANCH, \"2\");\n\n    auto n3 = tree.promoteNode(n1, 0, 2, tree::NodeStatus::BRANCH, \"3\");\n    auto n4 = tree.promoteNode(n1, 1, 0, tree::NodeStatus::SOLVED, \"4\");\n\n    auto n5 = tree.promoteNode(n3, 0, 0, tree::NodeStatus::FAILED, \"5\");\n    auto n6 = tree.promoteNode(n3, 1, 0, tree::NodeStatus::FAILED, \"6\");\n\n    auto n7 = tree.promoteNode(n2, 0, 0, tree::NodeStatus::UNDETERMINED, \"7\");\n    auto n8 = tree.promoteNode(n2, 1, 2, tree::NodeStatus::BRANCH, \"8\");\n\n    auto n9 = tree.promoteNode(n8, 0, 0, tree::NodeStatus::FAILED, \"9\");\n    auto n10 = tree.promoteNode(n8, 1, 0, tree::NodeStatus::FAILED, \"10\");\n}\n\nvoid restart_tree(Conductor &c)\n{\n\n    auto ex = c.addNewExecution(\"Restart Tree\");\n\n    auto &tree = ex->tree();\n\n    utils::MutexLocker locker(&tree.treeMutex());\n\n    auto root = tree.createRoot(0);\n\n    tree.addExtraChild(root);\n    // tree.addExtraChild(root);\n    // tree.addExtraChild(root);\n    // tree.addExtraChild(root);\n    // tree.addExtraChild(root);\n    // tree.addExtraChild(root);\n\n    // tree.addExtraChild(tree.getChild(root, 3));\n}\n\nstatic void load_execution(Conductor &c, const char *path)\n{\n\n    auto ex = db_handler::load_execution(path);\n\n    if (!ex)\n    {\n        print(\"could not load the execution\");\n    }\n    else\n    {\n        c.addNewExecution(ex);\n    }\n}\n\nstatic void save_and_load(Conductor &c, const char *path)\n{\n\n    auto ex1 = c.addNewExecution(\"simple execution\");\n    build_for_comparison_a(ex1->tree());\n\n    ex1->userData().setBookmark(NodeID{2}, \"Test Bookmark\");\n\n    db_handler::save_execution(ex1, path);\n\n    auto ex = db_handler::load_execution(path);\n    if (!ex)\n    {\n        print(\"could not load the execution\");\n    }\n    else\n    {\n        c.addNewExecution(ex);\n    }\n}\n\nstatic void db_create_tree(Conductor &c)\n{\n\n    auto ex = c.addNewExecution(\"Created as in DB\");\n    auto &tree = ex->tree();\n\n    tree.db_initialize(100);\n\n    tree.db_createRoot(NodeID{0});\n\n    tree.db_addChild(NodeID{1}, NodeID{0}, 0, tree::NodeStatus::BRANCH, \"a\");\n    tree.db_addChild(NodeID{2}, NodeID{0}, 1, tree::NodeStatus::BRANCH, \"b\");\n}\n\nstatic void save_search(Conductor &c)\n{\n\n    auto ex1 = c.addNewExecution(\"simple execution\");\n    build_for_comparison_a(ex1->tree());\n\n    c.saveSearch(ex1, \"test.search\");\n}\n\n/// similar subtree\nstatic void ss_analysis(Conductor &c)\n{\n\n    const char *path = \"/home/maxim/dev/cp-profiler2/golomb6.db\";\n    // const char *path = \"/home/maxim/dev/cp-profiler2/golomb10.db\";\n    auto ex = db_handler::load_execution(path);\n    if (!ex)\n    {\n        print(\"could not load the execution\");\n    }\n    else\n    {\n        c.addNewExecution(ex);\n        c.showTraditionalView(ex.get());\n    }\n}\n\nstatic void nogood_dialog(Conductor &c)\n{\n\n    const char *orig_path = \"/home/maxim/dev/cp-profiler2/nogoods.db\";\n\n    auto ex = db_handler::load_execution(orig_path);\n    if (!ex)\n    {\n        print(\"could not load the execution\");\n    }\n    else\n    {\n        c.addNewExecution(ex);\n        // c.showTraditionalView(ex.get());\n    }\n\n    const char *replayed_path = \"/home/maxim/dev/cp-profiler2/nogoods_replayed.db\";\n\n    auto ex2 = db_handler::load_execution(replayed_path);\n    if (!ex2)\n    {\n        print(\"could not load the execution\");\n    }\n    else\n    {\n        c.addNewExecution(ex2);\n        // c.showTraditionalView(ex2.get());\n    }\n\n    c.runNogoodAnalysis(ex.get(), ex2.get());\n\n    // c.mergeTrees(ex.get(), ex2.get());\n}\n\nvoid run(Conductor &c)\n{\n\n    /// this one works with db\n    // binary_test_1_for_identical_subtrees(c);\n\n    // binary_test_2_for_identical_subtrees(c);\n\n    // binary_tree_execution(c);\n    // simple_nary_execution(c);\n    // nary_execution(c);\n    // larger_nary_execution(c);\n\n    // hiding_failed_test(c);\n\n    // comparison(c);\n\n    // comparison2(c);\n\n    // tree_building(c);\n\n    // restart_tree(c);\n\n    // load_execution(c, \"/home/maxim/dev/cp-profiler2/golomb8.db\");\n\n    // save_and_load(c, \"/home/maxim/dev/cp-profiler2/test.db\");\n\n    // nogood_dialog(c);\n\n    // db_create_tree(c);\n\n    // save_search(c);\n\n    // ss_analysis(c);\n}\n\n} // namespace execution\n} // namespace tests\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tests/execution_test.hh",
    "content": "#ifndef CPPROFILER_TESTS_EXECUTION_TEST_HH\n#define CPPROFILER_TESTS_EXECUTION_TEST_HH\n\nnamespace cpprofiler\n{\nclass Conductor;\n}\n\nnamespace cpprofiler\n{\nnamespace tests\n{\nnamespace execution\n{\n\nvoid run(Conductor &c);\n}\n} // namespace tests\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tests/tree_test.cpp",
    "content": "#include \"../tree/node_tree.hh\"\n#include \"../tree/structure.hh\"\n\n#include \"../utils/array.hh\"\n#include \"../utils/debug.hh\"\n\n#include <cassert>\n\nnamespace cpprofiler\n{\nnamespace tests\n{\nnamespace tree_test\n{\n\nclass TestClass\n{\n  private:\n    int m_id;\n    static int counter;\n\n  public:\n    TestClass()\n    {\n        counter++;\n        m_id = counter;\n        print(\"TestClass(): {}\", m_id);\n    }\n\n    ~TestClass()\n    {\n        print(\"~TestClass(): {}\", m_id);\n    }\n\n    TestClass(const TestClass &other)\n    {\n        counter++;\n        m_id = counter;\n        print(\"TestClass(const T&): {}\", m_id);\n    }\n\n    TestClass(TestClass &&other)\n    {\n        m_id = other.m_id;\n        print(\"TestClass(const T&&): {}\", m_id);\n    }\n\n    TestClass &operator=(const TestClass &other)\n    {\n        m_id = other.m_id;\n        print(\"T &operator=TestClass(const T&): {}\", m_id);\n        return *this;\n    }\n};\n\nint TestClass::counter = 0;\n\nvoid array_usage()\n{\n\n    utils::Array<TestClass> arr(2);\n\n    arr[0] = TestClass{};\n    arr[1] = TestClass{};\n\n    // auto other = arr;\n\n    // TestClass a;\n    // arr[0] = a;\n}\n\nvoid growing_tree()\n{\n\n    tree::Structure str;\n\n    auto root = str.createRoot(0);\n\n    auto n1 = str.addExtraChild(root);\n\n    assert(n1 == str.getChild(root, 0));\n\n    auto n2 = str.addExtraChild(root);\n\n    assert(n1 == str.getChild(root, 0));\n    assert(n2 == str.getChild(root, 1));\n\n    auto n3 = str.addExtraChild(root);\n\n    assert(n1 == str.getChild(root, 0));\n    assert(n2 == str.getChild(root, 1));\n    assert(n3 == str.getChild(root, 2));\n}\n\nvoid run()\n{\n\n    growing_tree();\n\n    // array_usage();\n}\n\n} // namespace tree_test\n} // namespace tests\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tests/tree_test.hh",
    "content": "#ifndef CPPROFILER_TESTS_TREE_TEST_HH\n#define CPPROFILER_TESTS_TREE_TEST_HH\n\nnamespace cpprofiler\n{\n\nnamespace tests\n{\n\nnamespace tree_test\n{\nvoid run();\n}\n\n} // namespace tests\n\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/drawing_cursor.cpp",
    "content": "#include \"drawing_cursor.hh\"\n#include \"../../config.hh\"\n#include \"../layout.hh\"\n#include \"../node_info.hh\"\n#include \"../node.hh\"\n#include \"../shape.hh\"\n#include \"../../user_data.hh\"\n#include \"../../utils/tree_utils.hh\"\n#include \"../traditional_view.hh\"\n\n#include \"../node_drawing.hh\"\n\n#include \"../node_tree.hh\"\n\n#include <QDebug>\n#include <QPainter>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\n/// remove this copy\nnamespace colors\n{\n/// The color for selected nodes\nstatic QColor gold(252, 209, 22);\n/// Red color for failed nodes\nstatic QColor red(218, 37, 29);\n/// Green color for solved nodes\nstatic QColor green(11, 118, 70);\n/// Blue color for choice nodes\nstatic QColor blue(0, 92, 161);\n/// Grey color for skipped nodes\nstatic QColor grey(150, 150, 150);\n/// Orange color for best solutions\nstatic QColor orange(235, 137, 27);\n/// White color\nstatic QColor white(255, 255, 255);\n\n/// Red color for expanded failed nodes\nstatic QColor lightRed(218, 37, 29, 120);\n/// Green color for expanded solved nodes\nstatic QColor lightGreen(11, 118, 70, 120);\n/// Blue color for expanded choice nodes\nstatic QColor lightBlue(0, 92, 161, 120);\n} // namespace colors\n\nDrawingCursor::DrawingCursor(NodeID start,\n                             const NodeTree &tree,\n                             const Layout &layout,\n                             const UserData &user_data,\n                             const VisualFlags &flags,\n                             QPainter &painter,\n                             QPoint start_pos,\n                             const QRect &clip,\n                             bool debug,\n                             bool dark_mode)\n    : NodeCursor(start, tree),\n      layout_(layout),\n      user_data_(user_data),\n      vis_flags_(flags),\n      painter_(painter),\n      clippingRect(clip),\n      debug_mode_(debug),\n      dark_mode_(dark_mode)\n{\n    cur_x = start_pos.x();\n    cur_y = start_pos.y();\n}\n\nstatic void drawTriangle(QPainter &painter, int x, int y, bool selected, bool has_gradient, bool has_solutions)\n{\n    using namespace traditional;\n\n    if (selected)\n    {\n        painter.setBrush(colors::gold);\n    }\n    else\n    {\n        QColor main_color = has_solutions ? colors::green : colors::red;\n        if (has_gradient)\n        {\n            QLinearGradient gradient(x - COLLAPSED_WIDTH, y,\n                                     x + COLLAPSED_WIDTH * 1.3, y + COLLAPSED_DEPTH * 1.3);\n            gradient.setColorAt(0, colors::white);\n            gradient.setColorAt(1, main_color);\n            painter.setBrush(gradient);\n        }\n        else\n        {\n            painter.setBrush(main_color);\n        }\n    }\n\n    QPointF points[3] = {QPointF(x, y),\n                         QPointF(x + HALF_COLLAPSED_WIDTH, y + COLLAPSED_DEPTH),\n                         QPointF(x - HALF_COLLAPSED_WIDTH, y + COLLAPSED_DEPTH)};\n\n    painter.drawConvexPolygon(points, 3);\n}\n\nstatic void drawShape(QPainter &painter, int x, int y, NodeID nid, const Layout &layout)\n{\n    using namespace traditional;\n\n    auto old_pen = painter.pen();\n    auto old_brush = painter.brush();\n\n    painter.setBrush(QColor{0, 0, 0, 50});\n    painter.setPen(Qt::NoPen);\n\n    const auto &shape = *layout.getShape(nid);\n\n    const int height = shape.height();\n    QPointF *points = new QPointF[height * 2];\n\n    int l_x = x + shape[0].l;\n    int r_x = x + shape[0].r;\n    y = y + BRANCH_WIDTH / 2;\n\n    points[0] = QPointF(l_x, y);\n    points[height * 2 - 1] = QPointF(r_x, y);\n\n    for (int i = 1; i < height; i++)\n    {\n        y += static_cast<double>(layout::dist_y);\n        l_x = x + shape[i].l;\n        r_x = x + shape[i].r;\n        points[i] = QPointF(l_x, y);\n        points[height * 2 - i - 1] = QPointF(r_x, y);\n    }\n\n    painter.drawConvexPolygon(points, shape.height() * 2);\n\n    delete[] points;\n\n    painter.setPen(old_pen);\n    painter.setBrush(old_brush);\n}\n\nstatic void drawBoundingBox(QPainter &painter, int x, int y, NodeID nid, const Layout &layout)\n{\n    auto bb = layout.getBoundingBox(nid);\n    auto height = layout.getHeight(nid) * layout::dist_y;\n    painter.drawRect(x + bb.left, y, bb.right - bb.left, height);\n}\n\nvoid DrawingCursor::processCurrentNode()\n{\n    using namespace traditional;\n\n    bool phantom_node = false;\n\n    painter_.setPen(QColor{dark_mode_ ? Qt::white : Qt::black});\n\n    const auto node = cur_node();\n\n    if (node != start_node())\n    {\n        auto parent_x = cur_x - layout_.getOffset(node);\n        auto parent_y = cur_y - static_cast<double>(layout::dist_y);\n\n        painter_.drawLine(parent_x, parent_y + BRANCH_WIDTH, cur_x, cur_y);\n    }\n\n    auto status = tree_.getStatus(node);\n\n    /// NOTE: this should be consisten with the layout\n    if (vis_flags_.isLabelShown(node))\n    {\n\n        auto draw_left = !utils::is_right_most_child(tree_, node);\n        // painter_.setPen(QPen{Qt::black, 2});\n        const Label &label = debug_mode_ ? std::to_string(node) : tree_.getLabel(node);\n\n        auto fm = painter_.fontMetrics();\n        auto label_width = fm.horizontalAdvance(label.c_str());\n\n        {\n            auto font = painter_.font();\n            font.setStyleHint(QFont::Monospace);\n            painter_.setFont(font);\n        }\n\n        int label_x;\n        if (draw_left)\n        {\n            label_x = cur_x - HALF_MAX_NODE_W - label_width;\n        }\n        else\n        {\n            label_x = cur_x + HALF_MAX_NODE_W;\n        }\n\n        painter_.drawText(QPoint{label_x, cur_y}, label.c_str());\n    }\n\n    if (vis_flags_.isHighlighted(node))\n    {\n        drawShape(painter_, cur_x, cur_y, node, layout_);\n    }\n\n    const auto sel_node = user_data_.getSelectedNode();\n    const auto selected = (sel_node == node) ? true : false;\n\n    // if (selected)\n    // {\n    //     painter_.setBrush(QColor{0, 0, 0, 20});\n\n    //     drawBoundingBox(painter_, cur_x, cur_y, node, layout_);\n\n    //     drawShape(painter_, cur_x, cur_y, node, layout_);\n    // }\n\n    /// see if the node is hidden\n\n    auto hidden = vis_flags_.isHidden(node);\n\n    if (hidden)\n    {\n\n        if (status == NodeStatus::MERGED)\n        {\n            draw::big_pentagon(painter_, cur_x, cur_y, selected);\n            return;\n        }\n\n        const bool has_gradient = tree_.hasOpenChildren(node);\n        const bool has_solutions = tree_.hasSolvedChildren(node);\n\n        /// check if the node is a lantern node\n        const auto lantern_size = vis_flags_.lanternSize(node);\n        if (lantern_size == -1)\n        {\n\n            drawTriangle(painter_, cur_x, cur_y, selected, has_gradient, has_solutions);\n        }\n        else\n        {\n            draw::lantern(painter_, cur_x, cur_y, lantern_size, selected, has_gradient, has_solutions);\n        }\n\n        return;\n    }\n\n    switch (status)\n    {\n    case NodeStatus::SOLVED:\n    {\n        draw::solution(painter_, cur_x, cur_y, selected);\n    }\n    break;\n    case NodeStatus::FAILED:\n    {\n        draw::failure(painter_, cur_x, cur_y, selected);\n    }\n    break;\n    case NodeStatus::BRANCH:\n    {\n        draw::branch(painter_, cur_x, cur_y, selected);\n    }\n    break;\n    case NodeStatus::SKIPPED:\n    {\n        draw::skipped(painter_, cur_x, cur_y, selected);\n    }\n    break;\n    case NodeStatus::MERGED:\n    {\n        draw::pentagon(painter_, cur_x, cur_y, selected);\n    }\n    break;\n    default:\n    {\n        draw::unexplored(painter_, cur_x, cur_y, selected);\n    }\n    break;\n    }\n\n    if (user_data_.isBookmarked(node))\n    {\n        painter_.setBrush(Qt::black);\n        painter_.drawEllipse(cur_x - 10, cur_y, 10.0, 10.0);\n    }\n}\n\nvoid DrawingCursor::moveUpwards()\n{\n    cur_x -= layout_.getOffset(cur_node());\n    cur_y -= layout::dist_y;\n    NodeCursor::moveUpwards();\n}\n\nvoid DrawingCursor::moveDownwards()\n{\n    NodeCursor::moveDownwards();\n    cur_x += layout_.getOffset(cur_node());\n    cur_y += layout::dist_y;\n}\n\nvoid DrawingCursor::moveSidewards()\n{\n    cur_x -= layout_.getOffset(cur_node());\n    NodeCursor::moveSidewards();\n    cur_x += layout_.getOffset(cur_node());\n}\n\nbool DrawingCursor::mayMoveSidewards()\n{\n    /// whether the next node is present\n    const auto may_move = NodeCursor::mayMoveSidewards();\n\n    if (!may_move)\n        return false;\n\n    const auto parent = tree_.getParent(cur_node());\n    const auto alt = tree_.getAlternative(cur_node());\n    const auto next = tree_.getChild(parent, alt + 1);\n\n    /// wether the node is ready for drawing\n    const auto next_layout_done = layout_.getLayoutDone(next);\n\n    return next_layout_done;\n}\n\nbool DrawingCursor::mayMoveDownwards()\n{\n\n    const auto has_children = (tree_.childrenCount(cur_node()) > 0);\n    if (!has_children)\n        return false;\n\n    const auto clipped = isClipped();\n    if (clipped)\n        return false;\n\n    const auto hidden = vis_flags_.isHidden(cur_node());\n\n    if (hidden)\n        return false;\n\n    const auto kid = tree_.getChild(cur_node(), 0);\n    const auto kid_layout_done = layout_.getLayoutDone(kid);\n\n    return kid_layout_done;\n}\n\nbool DrawingCursor::mayMoveUpwards()\n{\n    return NodeCursor::mayMoveUpwards();\n}\n\nbool DrawingCursor::isClipped()\n{\n    const auto bb = layout_.getBoundingBox(cur_node());\n\n    if (\n        (cur_x + bb.left > clippingRect.x() + clippingRect.width()) ||\n        (cur_x + bb.right < clippingRect.x()) ||\n        (cur_y > clippingRect.y() + clippingRect.height()) ||\n        (cur_y + (layout_.getHeight(cur_node()) + 1) * layout::dist_y < clippingRect.y()))\n    {\n        return true;\n    }\n\n    return false;\n}\n\n} // namespace tree\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/drawing_cursor.hh",
    "content": "#ifndef CPPROFILER_TREE_CURSORS_DRAWING_CURSOR_HH\n#define CPPROFILER_TREE_CURSORS_DRAWING_CURSOR_HH\n\n#include \"node_cursor.hh\"\n#include \"../layout.hh\"\n#include <QPoint>\n#include <QRect>\n\nclass QPainter;\n\nnamespace cpprofiler\n{\nclass UserData;\n\nnamespace tree\n{\nclass VisualFlags;\n}\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass Layout;\n\n/// This uses unsafe methods for tree structure!\nclass DrawingCursor : public NodeCursor\n{\n\n    const Layout &layout_;\n\n    const UserData &user_data_;\n\n    const VisualFlags &vis_flags_;\n\n    const bool debug_mode_;\n\n    const bool dark_mode_;\n\n    QPainter &painter_;\n    const QRect clippingRect;\n\n    int cur_x, cur_y;\n\n    bool isClipped();\n\n  public:\n    DrawingCursor(NodeID start,\n                  const NodeTree &tree,\n                  const Layout &layout,\n                  const UserData &user_data,\n                  const VisualFlags &flags,\n                  QPainter &painter,\n                  QPoint start_pos,\n                  const QRect &clippingRect0,\n                  bool debug,\n                  bool darkMode);\n\n    void processCurrentNode();\n\n    bool mayMoveUpwards();\n    bool mayMoveSidewards();\n    bool mayMoveDownwards();\n\n    void moveUpwards();\n    void moveDownwards();\n    void moveSidewards();\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/hide_failed_cursor.cpp",
    "content": "#include \"hide_failed_cursor.hh\"\n#include \"../node_tree.hh\"\n#include \"../visual_flags.hh\"\n#include \"../layout_computer.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nHideFailedCursor::HideFailedCursor(NodeID start, const NodeTree &nt, VisualFlags &vf, LayoutComputer &lc, bool dirty, bool &mod)\n    : NodeCursor(start, nt), m_vf(vf), m_lc(lc), m_onlyDirty(dirty), modified(mod)\n{\n}\n\nbool HideFailedCursor::mayMoveDownwards() const\n{\n    const auto node = cur_node();\n    bool ok = NodeCursor::mayMoveDownwards() &&\n              (!m_onlyDirty || m_lc.isDirty(node)) &&\n              (tree_.hasSolvedChildren(node) || tree_.hasOpenChildren(node)) &&\n              !m_vf.isHidden(node);\n\n    return ok;\n}\n\ninline static bool should_hide(const NodeTree &nt, const VisualFlags &vf, NodeID n)\n{\n    return !nt.hasSolvedChildren(n) && // does not have solution children\n           !nt.hasOpenChildren(n) &&   // does not have open children\n           nt.childrenCount(n) > 0 &&  // not a leaf node\n           !vf.isHidden(n);            // the node is not already hidden\n}\n\nvoid HideFailedCursor::processCurrentNode()\n{\n    if (should_hide(tree_, m_vf, cur_node()))\n    {\n        modified = true;\n        m_vf.setHidden(cur_node(), true);\n        m_lc.dirtyUpLater(cur_node());\n    }\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/hide_failed_cursor.hh",
    "content": "#pragma once\n\n#include \"node_cursor.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass VisualFlags;\nclass LayoutComputer;\n\n/// To be used with post-order visitor to hide the\n/// largest subtrees that contain no solutions\nclass HideFailedCursor : public NodeCursor\n{\n\n  VisualFlags &m_vf;\n  LayoutComputer &m_lc;\n\n  /// Whether only dirty nodes should be hidden\n  bool m_onlyDirty;\n  /// Whether a the cursor modified the visualisation in any way\n  bool &modified;\n\npublic:\n  HideFailedCursor(NodeID start,       // The root of the tree/subtree\n                   const NodeTree &nt, // Tree structure\n                   VisualFlags &vf,    // Visual flags (whether a node is hidden etc.)\n                   LayoutComputer &lc, // Class for computing the layout\n                   bool onlyDirty,     // Whether non-dirty nodes should be visited\n                   bool &mod);         // out-parameter to indicate if the visualisation was modified\n\n  /// Test if the cursor may move to the first child node\n  bool mayMoveDownwards() const;\n  /// Process node\n  void processCurrentNode();\n};\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/hide_not_highlighted_cursor.cpp",
    "content": "#include \"hide_not_highlighted_cursor.hh\"\n#include \"../visual_flags.hh\"\n#include \"../layout_computer.hh\"\n#include \"../node_tree.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace tree\n{\n\nHideNotHighlightedCursor::HideNotHighlightedCursor(NodeID startNode,\n                                                   const NodeTree &tree,\n                                                   VisualFlags &vf,\n                                                   LayoutComputer &lc)\n    : NodeCursor(startNode, tree), vf_(vf), lc_(lc)\n{\n    //\n}\n\nvoid HideNotHighlightedCursor::processCurrentNode()\n{\n    const auto n = cur_node();\n\n    bool on_hp = vf_.isHighlighted(n);\n\n    if (!on_hp)\n    {\n        /// find a child that is on highlighted path\n        for (auto alt = 0; alt < tree_.childrenCount(n); ++alt)\n        {\n            const auto child = tree_.getChild(n, alt);\n            if (onHighlightPath.contains(child))\n            {\n                on_hp = true;\n                break;\n            }\n        }\n    }\n\n    if (on_hp)\n    {\n        onHighlightPath[n] = true;\n    }\n    else\n    {\n        vf_.setHidden(n, true);\n        lc_.dirtyUpLater(n);\n    }\n}\n\nbool HideNotHighlightedCursor::mayMoveDownwards() const\n{\n    const auto n = cur_node();\n    return NodeCursor::mayMoveDownwards() &&\n           !vf_.isHighlighted(n) && !vf_.isHidden(n);\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/hide_not_highlighted_cursor.hh",
    "content": "#pragma once\n\n#include \"node_cursor.hh\"\n#include <QHash>\n\nnamespace cpprofiler\n{\n\nnamespace tree\n{\n\nclass VisualFlags;\nclass LayoutComputer;\n\n/// Hide subtrees that are not highlighted\nclass HideNotHighlightedCursor : public NodeCursor\n{\n  private:\n    QHash<NodeID, bool> onHighlightPath;\n    VisualFlags &vf_;\n    LayoutComputer &lc_;\n\n  public:\n    HideNotHighlightedCursor(NodeID startNode,\n                             const NodeTree &tree,\n                             VisualFlags &vf,\n                             LayoutComputer &lc);\n    /// Hide the node if needed\n    void processCurrentNode();\n    /// Test if cursor may move to the first child node\n    bool mayMoveDownwards() const;\n};\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/layout_cursor.cpp",
    "content": "\n#include \"layout_cursor.hh\"\n#include \"../../config.hh\"\n#include \"../node_id.hh\"\n#include <iostream>\n#include <QDebug>\n#include <QPainter>\n\n#include \"../layout.hh\"\n#include \"../node_tree.hh\"\n#include \"../structure.hh\"\n#include \"../shape.hh\"\n#include \"../../config.hh\"\n#include \"../../utils/tree_utils.hh\"\n#include \"../../utils/debug.hh\"\n\n/// needed for VisualFlags\n#include \"../traditional_view.hh\"\n\n#include <numeric>\n#include <climits>\n#include <cmath>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nLayoutCursor::LayoutCursor(NodeID start, const NodeTree &tree, const VisualFlags &nf, Layout &lo, bool debug)\n    : NodeCursor(start, tree), m_layout(lo), tree_(tree), m_vis_flags(nf), debug_mode_(debug) {}\n\nbool LayoutCursor::mayMoveDownwards()\n{\n    const auto has_children = NodeCursor::mayMoveDownwards();\n    const auto hidden = m_vis_flags.isHidden(cur_node());\n    const auto dirty = m_layout.isDirty(cur_node());\n    const auto layout_done = m_layout.getLayoutDone(cur_node());\n\n    const auto mayMoveDown = has_children && !hidden && dirty;\n\n    return mayMoveDown;\n\n    // return NodeCursor::mayMoveDownwards() &&\n    //        (!m_vis_flags.isHidden(cur_node())) &&\n    //        m_layout.isDirty(cur_node());\n}\n\n/// Compute the distance between s1 and s2 (that is, how far apart should the\n/// corresponding root nodes be along X axis to avoid overlap, additionally\n/// allowing for some margin -- layout::min_dist_x)\nstatic int distance_between(const Shape &s1, const Shape &s2)\n{\n    const auto common_depth = std::min(s1.height(), s2.height());\n\n    auto max = INT_MIN;\n    for (auto i = 0; i < common_depth; ++i)\n    {\n        auto cur_dist = s1[i].r - s2[i].l;\n        if (cur_dist > max)\n            max = cur_dist;\n    }\n\n    return max + layout::min_dist_x;\n}\n\n/// Combine shapes s1 and s2 to form a shape of a shape for the parent node;\n/// offsets will contain the resulting relative distance from the parent node (along x);\nstatic ShapeUniqPtr combine_shapes(const Shape &s1, const Shape &s2, std::vector<int> &offsets)\n{\n\n    const auto depth_left = s1.height();\n    const auto depth_right = s2.height();\n\n    const auto max_depth = std::max(depth_left, depth_right);\n    const auto common_depth = std::min(depth_left, depth_right);\n\n    const auto distance = distance_between(s1, s2);\n    const auto half_dist = distance / 2;\n\n    auto combined = ShapeUniqPtr(new Shape{max_depth + 1});\n\n    {\n        const auto bb_left = std::min(s1.boundingBox().left - half_dist,\n                                      s2.boundingBox().left + half_dist);\n        const auto bb_right = std::max(s1.boundingBox().right - half_dist,\n                                       s2.boundingBox().right + half_dist);\n        combined->setBoundingBox(BoundingBox{bb_left, bb_right});\n    }\n\n    offsets[0] = -half_dist;\n    offsets[1] = half_dist;\n\n    /// Calculate extents for levels shared by both shapes\n    for (auto depth = 0; depth < common_depth; ++depth)\n    {\n        (*combined)[depth + 1] = {s1[depth].l - half_dist, s2[depth].r + half_dist};\n    }\n\n    /// Calculate extents for levels where only one subtree has nodes\n    if (max_depth != common_depth)\n    {\n        const auto &longer_shape = depth_left > depth_right ? s1 : s2;\n        const int offset = depth_left > depth_right ? -half_dist : half_dist;\n\n        for (auto depth = common_depth; depth < max_depth; ++depth)\n        {\n            (*combined)[depth + 1] = {longer_shape[depth].l + offset,\n                                      longer_shape[depth].r + offset};\n        }\n    }\n\n    return std::move(combined);\n}\n\n/// Merge two \"sibling\" shapes, without adding a new depth level;\n/// `dist` is the disntance between s2 and the left extent\nstatic Shape merge_left(const Shape &s1, const Shape &s2, int &dist)\n{\n\n    const auto depth_left = s1.height();\n    const auto depth_right = s2.height();\n\n    const auto common_depth = std::min(depth_left, depth_right);\n    const auto max_depth = std::max(depth_left, depth_right);\n\n    const auto distance = distance_between(s1, s2);\n\n    dist = distance - s1.boundingBox().left;\n\n    Shape result{max_depth};\n\n    /// compute bounding box\n    {\n        auto bb_left = std::min(s1.boundingBox().left,\n                                s2.boundingBox().left + distance);\n        auto bb_right = std::max(s1.boundingBox().right,\n                                 s2.boundingBox().right + distance);\n        result.setBoundingBox(BoundingBox{bb_left, bb_right});\n    }\n\n    /// Calculate extents for levels shared by both shapes\n    for (auto depth = 0; depth < common_depth; ++depth)\n    {\n        result[depth] = {s1[depth].l, s2[depth].r + distance};\n    }\n\n    /// Calculate extents for levels where only one subtree has nodes\n    if (max_depth != common_depth)\n    {\n        const auto &longer_shape = depth_left > depth_right ? s1 : s2;\n        const int offset = depth_left > depth_right ? 0 : distance;\n\n        for (auto depth = common_depth; depth < max_depth; ++depth)\n        {\n            result[depth] = {longer_shape[depth].l + offset,\n                             longer_shape[depth].r + offset};\n        }\n    }\n\n    return result;\n}\n\nstatic Extent calculateForSingleNode(NodeID nid, const NodeTree &nt, bool label_shown, bool hidden, bool debug)\n{\n\n    Extent result{-traditional::HALF_MAX_NODE_W, traditional::HALF_MAX_NODE_W};\n    /// see if the node dispays labels and needs its (top) extents extended\n\n    if (hidden)\n    {\n        result = {-traditional::HALF_COLLAPSED_WIDTH, traditional::HALF_COLLAPSED_WIDTH};\n    }\n\n    if (!label_shown)\n        return result;\n\n    /// TODO: use font metrics?\n    const auto &label = debug ? std::to_string(nid) : nt.getLabel(nid);\n    auto label_width = label.size() * 9;\n\n    /// Note: this assumes that the default painter used for drawing text\n    // QPainter painter;\n    // auto fm = painter.fontMetrics();\n    // auto label_width = fm.width(label.c_str());\n\n    /// Note that labels are shown on the left for all alt\n    /// except the last one (right-most)\n\n    bool draw_left = !utils::is_right_most_child(nt, nid);\n\n    if (draw_left)\n    {\n        result.l -= label_width;\n    }\n    else\n    {\n        result.r += label_width;\n    }\n    return result;\n}\n\ninline static void computeForNodeBinary(NodeID nid, Layout &layout, const NodeTree &nt, bool label_shown, bool debug)\n{\n\n    auto kid_l = nt.getChild(nid, 0);\n    auto kid_r = nt.getChild(nid, 1);\n\n    const auto &s1 = *layout.getShape(kid_l);\n    const auto &s2 = *layout.getShape(kid_r);\n\n    std::vector<int> offsets(2);\n    auto combined = combine_shapes(s1, s2, offsets);\n\n    (*combined)[0] = calculateForSingleNode(nid, nt, label_shown, false, debug);\n\n    /// Extents for root node changed -> check if bounding box is correct\n    const auto &bb = combined->boundingBox();\n\n    if (bb.left > (*combined)[0].l || bb.right < (*combined)[0].r)\n    {\n        combined->setBoundingBox({std::min(bb.left, (*combined)[0].l), std::max(bb.right, (*combined)[0].r)});\n    }\n\n    layout.setShape(nid, std::move(combined));\n\n    layout.setChildOffset(kid_l, offsets[0]);\n    layout.setChildOffset(kid_r, offsets[1]);\n}\n\ninline static std::vector<int> compute_distances(NodeID nid, int nkids, Layout &lo, const NodeTree &tree)\n{\n    std::vector<int> distances(nkids - 1);\n\n    for (auto i = 0; i < nkids - 1; ++i)\n    {\n        auto kid_l = tree.getChild(nid, i);\n        auto kid_r = tree.getChild(nid, i + 1);\n        const auto &s1 = *lo.getShape(kid_l);\n        const auto &s2 = *lo.getShape(kid_r);\n        distances[i] = distance_between(s1, s2);\n    }\n\n    return distances;\n}\n\ninline static int kids_max_depth(NodeID nid, int nkids, const Layout &lo, const NodeTree &tree)\n{\n    int max_depth = 0;\n    for (auto i = 0; i < nkids; ++i)\n    {\n        auto kid = tree.getChild(nid, i);\n        const auto &s1 = *lo.getShape(kid);\n        max_depth = std::max(max_depth, s1.height());\n    }\n    return max_depth;\n}\n\nstatic inline void computeForNodeNary(NodeID nid, int nkids, Layout &layout, const NodeTree &tree, bool debug)\n{\n\n    /// calculate all distances\n    const auto distances = compute_distances(nid, nkids, layout, tree);\n\n    /// distance between the leftmost and the rightmost nodes\n    int max_dist = 0;\n    for (auto distance : distances)\n    {\n        max_dist += distance;\n    }\n\n    /// calculate the depth of the resulting shape\n    const auto new_depth = kids_max_depth(nid, nkids, layout, tree) + 1;\n\n    auto combined = ShapeUniqPtr(new Shape{new_depth});\n\n    std::vector<int> x_offsets(nkids);\n    /// calculate offsets\n    auto cur_x = -max_dist / 2;\n    for (auto i = 0; i < nkids; ++i)\n    {\n        const auto kid = tree.getChild(nid, i);\n        layout.setChildOffset(kid, cur_x);\n        x_offsets[i] = cur_x;\n        if (i < nkids - 1) {\n            cur_x += distances[i];\n        }\n    }\n\n    /// calculate extents\n    /// TODO: does this need to take labels into account?\n    (*combined)[0] = {-traditional::HALF_MAX_NODE_W, traditional::HALF_MAX_NODE_W};\n    for (auto depth = 1; depth < new_depth; ++depth)\n    {\n\n        auto leftmost_x = INT_MAX;\n        auto rightmost_x = INT_MIN;\n        for (auto alt = 0; alt < nkids; ++alt)\n        {\n            const auto kid = tree.getChild(nid, alt);\n            const auto &shape = *layout.getShape(kid);\n            if (shape.height() > depth - 1)\n            {\n                leftmost_x = std::min(leftmost_x, shape[depth - 1].l + x_offsets[alt]);\n                rightmost_x = std::max(leftmost_x, shape[depth - 1].r + x_offsets[alt]);\n            }\n        }\n\n        (*combined)[depth].l = leftmost_x;\n        (*combined)[depth].r = rightmost_x;\n    }\n\n    /// calculate bounding box\n    int l_bound = INT_MAX;\n    int r_bound = INT_MIN;\n    for (auto depth = 0; depth < new_depth; ++depth)\n    {\n        l_bound = std::min((*combined)[depth].l, l_bound);\n        r_bound = std::max((*combined)[depth].r, r_bound);\n    }\n\n    combined->setBoundingBox({l_bound, r_bound});\n\n    layout.setShape(nid, std::move(combined));\n}\n\n/// Calculate shape for sized rectangle (lantern); size is between 0 and 127\nstatic ShapeUniqPtr calc_for_sized_rect(int size)\n{\n\n    // using namespace lantern;\n\n    int levels = std::ceil((size * lantern::K + lantern::BASE_HEIGHT) / (float)layout::dist_y) + 1;\n\n    auto shape = ShapeUniqPtr(new Shape(levels));\n\n    for (auto i = 0u; i < levels; ++i)\n    {\n        (*shape)[i] = {-lantern::HALF_WIDTH, lantern::HALF_WIDTH};\n    }\n\n    shape->setBoundingBox({-lantern::HALF_WIDTH, lantern::HALF_WIDTH});\n\n    return std::move(shape);\n}\n\n/// Computes layout for nid (shape, bounding box, offsets for its children)\nvoid LayoutCursor::computeForNode(NodeID nid)\n{\n    const bool hidden = m_vis_flags.isHidden(nid);\n    const bool label_shown = m_vis_flags.isLabelShown(nid);\n\n    /// Check if the node is hidden:\n    if (hidden)\n    {\n        const auto lsize = m_vis_flags.lanternSize(nid);\n\n        if (lsize > -1)\n        {\n            /// Lantern node\n            auto shape = calc_for_sized_rect(lsize);\n            if (label_shown)\n            {\n                /// overriting the first extent in case of a label\n                (*shape)[0] = calculateForSingleNode(nid, tree_, label_shown, true, debug_mode_);\n                shape->setBoundingBox({(*shape)[0].l, (*shape)[0].r});\n            }\n            m_layout.setShape(nid, std::move(shape));\n        }\n        else\n        {\n            /// Normal failure node (triangle)\n\n            if (!label_shown)\n            {\n                m_layout.setShape(nid, ShapeUniqPtr(&Shape::hidden));\n            }\n            else\n            {\n                auto shape = ShapeUniqPtr{new Shape{2}};\n                (*shape)[0] = calculateForSingleNode(nid, tree_, label_shown, true, debug_mode_);\n                (*shape)[1] = (*shape)[0];\n                shape->setBoundingBox({(*shape)[0].l, (*shape)[0].r});\n                m_layout.setShape(nid, std::move(shape));\n            }\n        }\n    }\n    else\n    {\n        auto nkids = tree_.childrenCount(nid);\n\n        if (nkids == 0)\n        {\n            if (!m_vis_flags.isLabelShown(nid))\n            {\n                auto other = ShapeUniqPtr(&Shape::leaf);\n                // m_layout.setShape(nid, ShapeUniqPtr(&Shape::leaf));\n                m_layout.setShape(nid, std::move(other));\n            }\n            else\n            {\n                auto shape = ShapeUniqPtr{new Shape{1}};\n                (*shape)[0] = calculateForSingleNode(nid, tree_, label_shown, false, debug_mode_);\n\n                shape->setBoundingBox({(*shape)[0].l, (*shape)[0].r});\n                m_layout.setShape(nid, std::move(shape));\n            }\n        }\n        else if (nkids == 1)\n        {\n\n            const auto kid = tree_.getChild(nid, 0);\n            const auto &kid_s = *m_layout.getShape(kid);\n\n            auto shape = ShapeUniqPtr(new Shape(kid_s.height() + 1));\n\n            (*shape)[0] = calculateForSingleNode(nid, tree_, label_shown, false, debug_mode_);\n\n            for (auto depth = 0; depth < kid_s.height(); depth++)\n            {\n                (*shape)[depth + 1] = kid_s[depth];\n            }\n\n            shape->setBoundingBox(kid_s.boundingBox());\n\n            m_layout.setChildOffset(kid, 0);\n\n            m_layout.setShape(nid, std::move(shape));\n        }\n        else if (nkids == 2)\n        {\n            computeForNodeBinary(nid, m_layout, tree_, label_shown, debug_mode_);\n        }\n        else if (nkids > 2)\n        {\n            computeForNodeNary(nid, nkids, m_layout, tree_, debug_mode_);\n        }\n    }\n\n    /// Layout is done for `nid` and its children\n    m_layout.setLayoutDone(nid, true);\n}\n\nvoid LayoutCursor::processCurrentNode()\n{\n\n    const auto dirty = m_layout.isDirty(cur_node());\n    // auto layout_done = m_layout.getLayoutDone(cur_node());\n\n    if (dirty)\n    {\n        computeForNode(cur_node());\n        m_layout.setDirty(cur_node(), false);\n    }\n}\n\nvoid LayoutCursor::finalize()\n{\n    // std::cerr << \"\\n\";\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/layout_cursor.hh",
    "content": "#ifndef CPPROFILER_TREE_CURSORS_LAYOUT_CURSOR\n#define CPPROFILER_TREE_CURSORS_LAYOUT_CURSOR\n\n#include <vector>\n#include <QPainter>\n#include \"node_cursor.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass Layout;\nclass VisualFlags;\n\nclass LayoutCursor : public NodeCursor\n{\n\n    Layout &m_layout;\n    const NodeTree &tree_;\n    const VisualFlags &m_vis_flags;\n    /// painter used for dispaying text (labels)\n    const QPainter *m_painter = nullptr;\n\n    const bool debug_mode_;\n\n  public:\n    // Constructor\n    LayoutCursor(NodeID start, const NodeTree &tree, const VisualFlags &nf, Layout &lo, bool debug);\n\n    void computeForNode(NodeID nid);\n\n    bool mayMoveDownwards();\n\n    void processCurrentNode();\n\n    void finalize();\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/node_cursor.cpp",
    "content": "#include \"node_cursor.hh\"\n\n#include \"../structure.hh\"\n#include \"../node_tree.hh\"\n#include <QDebug>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nNodeCursor::NodeCursor(NodeID start, const NodeTree &tree)\n    : tree_(tree), start_node_(start), node_(start), cur_alt_(0)\n{\n}\n\nbool NodeCursor::mayMoveDownwards() const\n{\n    auto n = tree_.childrenCount(node_);\n    return (n > 0);\n}\n\nvoid NodeCursor::moveDownwards()\n{\n    cur_alt_ = 0;\n    node_ = tree_.getChild(node_, 0);\n}\n\nbool NodeCursor::mayMoveSidewards() const\n{\n    return (node_ != start_node_) &&\n           (node_ != tree_.getRoot()) &&\n           (cur_alt_ < tree_.getNumberOfSiblings(node_) - 1);\n}\n\nvoid NodeCursor::moveSidewards()\n{\n    auto parent_nid = tree_.getParent(node_);\n    node_ = tree_.getChild(parent_nid, ++cur_alt_);\n}\n\nbool NodeCursor::mayMoveUpwards() const\n{\n    return (node_ != start_node_) && (node_ != tree_.getRoot());\n}\n\nvoid NodeCursor::moveUpwards()\n{\n    node_ = tree_.getParent(node_);\n    cur_alt_ = tree_.getAlternative(node_);\n}\n\nvoid NodeCursor::finalize()\n{\n    /// do nothing\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/node_cursor.hh",
    "content": "#ifndef CPPROFILER_TREE_CURSOR_NODE_CURSOR_HH\n#define CPPROFILER_TREE_CURSOR_NODE_CURSOR_HH\n\n#include \"../node_id.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass Structure;\nclass NodeInfo;\nclass NodeTree;\n\nclass NodeCursor\n{\n\n  private:\n    /// Current node\n    NodeID node_;\n    /// Current alternative (position among siblings)\n    int cur_alt_;\n    /// The root of the tree/subtree\n    const NodeID start_node_;\n\n  protected:\n    /// Tree Structure\n    const NodeTree &tree_;\n\n    /// Return current node\n    inline NodeID cur_node() const { return node_; }\n    /// Return current alternative\n    inline int cur_alt() const { return cur_alt_; }\n    /// Return the start (root) node\n    inline NodeID start_node() const { return start_node_; }\n\n  public:\n    NodeCursor(NodeID start, const NodeTree &tree);\n\n    /// Test if the cursor may move to the parent node\n    bool mayMoveUpwards() const;\n    /// Move cursor to the parent node\n    void moveUpwards();\n    /// Test if cursor may move to the first child node\n    bool mayMoveDownwards() const;\n    /// Move cursor to the first child node\n    void moveDownwards();\n    /// Test if cursor may move to the first sibling\n    bool mayMoveSidewards() const;\n    /// Move cursor to the first sibling\n    void moveSidewards();\n    /// Action performed at the end of traversal\n    void finalize();\n    /// Action performed per node\n    void processCurrentNode();\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/nodevisitor.hh",
    "content": "#ifndef CPPROFILER_TREE_CURSORS_NODE_VISITOR_HH\n#define CPPROFILER_TREE_CURSORS_NODE_VISITOR_HH\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\ntemplate <typename Cursor>\nclass NodeVisitor\n{\n\n  protected:\n    Cursor m_cursor;\n\n  public:\n    NodeVisitor(const Cursor &c);\n};\n\ntemplate <typename Cursor>\nclass PreorderNodeVisitor : public NodeVisitor<Cursor>\n{\n\n    using NodeVisitor<Cursor>::m_cursor;\n\n  protected:\n    /// Move cursor to next node from a leaf\n    bool backtrack();\n    /// Move cursor to the next node, return true if succeeded\n    bool next();\n\n  public:\n    PreorderNodeVisitor(const Cursor &c);\n    /// Execute visitor\n    void run();\n};\n\ntemplate <typename Cursor>\nclass PostorderNodeVisitor : public NodeVisitor<Cursor>\n{\n\n    using NodeVisitor<Cursor>::m_cursor;\n\n  protected:\n    /// Move the cursor to the left-most leaf\n    void moveToLeaf();\n\n  public:\n    /// Constructor\n    PostorderNodeVisitor(const Cursor &c);\n    /// Move cursor to next node, return true if succeeded\n    bool next();\n    /// Execute visitor\n    void run();\n};\n\n#include \"nodevisitor.hpp\"\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/cursors/nodevisitor.hpp",
    "content": "template <typename Cursor>\nNodeVisitor<Cursor>::NodeVisitor(const Cursor &c) : m_cursor(c) {}\n\ntemplate <typename Cursor>\nPreorderNodeVisitor<Cursor>::PreorderNodeVisitor(const Cursor &c)\n    : NodeVisitor<Cursor>(c) {}\n\ntemplate <typename Cursor>\nbool PreorderNodeVisitor<Cursor>::backtrack()\n{\n    while (!m_cursor.mayMoveSidewards() && m_cursor.mayMoveUpwards())\n    {\n        m_cursor.moveUpwards();\n    }\n    if (!m_cursor.mayMoveUpwards())\n    {\n        m_cursor.finalize();\n        return false;\n    }\n    else\n    {\n        m_cursor.moveSidewards();\n    }\n    return true;\n}\n\ntemplate <typename Cursor>\nbool PreorderNodeVisitor<Cursor>::next()\n{\n    m_cursor.processCurrentNode();\n    if (m_cursor.mayMoveDownwards())\n    {\n        m_cursor.moveDownwards();\n    }\n    else if (m_cursor.mayMoveSidewards())\n    {\n        m_cursor.moveSidewards();\n    }\n    else\n    {\n        return backtrack();\n    }\n    return true;\n}\n\ntemplate <typename Cursor>\nvoid PreorderNodeVisitor<Cursor>::run()\n{\n    while (next())\n    {\n    }\n}\n\ntemplate <typename Cursor>\nvoid PostorderNodeVisitor<Cursor>::moveToLeaf()\n{\n    while (m_cursor.mayMoveDownwards())\n    {\n        m_cursor.moveDownwards();\n    }\n}\n\ntemplate <typename Cursor>\nPostorderNodeVisitor<Cursor>::PostorderNodeVisitor(const Cursor &c)\n    : NodeVisitor<Cursor>(c)\n{\n    moveToLeaf();\n}\n\ntemplate <class Cursor>\nbool PostorderNodeVisitor<Cursor>::next()\n{\n    m_cursor.processCurrentNode();\n    if (m_cursor.mayMoveSidewards())\n    {\n        m_cursor.moveSidewards();\n        moveToLeaf();\n    }\n    else if (m_cursor.mayMoveUpwards())\n    {\n        m_cursor.moveUpwards();\n    }\n    else\n    {\n        m_cursor.finalize();\n        return false;\n    }\n    return true;\n}\n\ntemplate <typename Cursor>\nvoid PostorderNodeVisitor<Cursor>::run()\n{\n    while (next())\n    {\n    }\n}\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/layout.cpp",
    "content": "#include \"layout.hh\"\n\n#include <QDebug>\n#include <iostream>\n\n#include \"../utils/std_ext.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nvoid Layout::setShape(NodeID nid, std::unique_ptr<Shape, ShapeDeleter> shape)\n{\n    shapes_[nid] = std::move(shape);\n}\n\nutils::Mutex &Layout::getMutex() const\n{\n    return layout_;\n}\n\nLayout::Layout()\n{\n}\n\nLayout::~Layout() = default;\n\nint Layout::getHeight(NodeID nid) const\n{\n    return getShape(nid)->height();\n}\n\nbool Layout::ready(NodeID nid) const\n{\n    return (layout_done_.size() > nid);\n}\n\nbool Layout::getLayoutDone(NodeID nid) const\n{\n    if (layout_done_.size() <= nid)\n        return false;\n    return layout_done_[nid];\n}\n\nvoid Layout::growDataStructures(int n_nodes)\n{\n\n    if (n_nodes > shapes_.size())\n    {\n        child_offsets_.resize(n_nodes, 0);\n        shapes_.resize(n_nodes);\n        layout_done_.resize(n_nodes, false);\n        /// nodes start as dirty\n        dirty_.resize(n_nodes, true);\n    }\n}\n\n} // namespace tree\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/layout.hh",
    "content": "#ifndef CPPROFILER_TREE_LAYOUT_HH\n#define CPPROFILER_TREE_LAYOUT_HH\n\n#include <vector>\n#include <memory>\n#include <unordered_map>\n#include <QMutex>\n#include <QObject>\n\n#include \"../core.hh\"\n#include \"shape.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass Shape;\nclass LayoutComputer;\nclass Structure;\nclass BoundingBox;\nclass ShapeDeleter;\n\nusing ShapeUniqPtr = std::unique_ptr<Shape, ShapeDeleter>;\n\nclass Layout : public QObject\n{\n  Q_OBJECT\n\n  mutable utils::Mutex layout_;\n\n  /// TODO: make sure this is always protected by a mutex\n  std::vector<ShapeUniqPtr> shapes_;\n\n  /// Relative offset from the parent node along the x axis\n  std::vector<double> child_offsets_;\n\n  /// Whether layout for the node and its children is done (indexed by NodeID)\n  std::vector<bool> layout_done_;\n\n  /// Whether a node's shape need to be recomputed (indexed by NodeID)\n  std::vector<bool> dirty_;\n\npublic:\n  utils::Mutex &getMutex() const;\n\n  void setChildOffset(NodeID nid, double offset) { child_offsets_[nid] = offset; }\n\n  void setLayoutDone(NodeID nid, bool val) { layout_done_[nid] = val; }\n\n  /// Note: a node might not have a shape (nullptr)\n  /// if it was hidden before layout was run\n  const Shape *getShape(NodeID nid) const { return shapes_[nid].get(); }\n\n  void setShape(NodeID nid, ShapeUniqPtr shape);\n\n  double getOffset(NodeID nid) const { return child_offsets_[nid]; }\n\n  /// Get the height of the shape of node `nid`\n  int getHeight(NodeID nid) const;\n\n  /// Whether layout is done for the subtree associated with node `nid`;\n  /// this is used by the drawing cursor to determine where to stop going down\n  /// the tree\n  bool getLayoutDone(NodeID nid) const;\n\n  /// Whether layout is information for node exists\n  bool ready(NodeID nid) const;\n\n  /// Whether node `nid` is dirty (needs layout update)\n  bool isDirty(NodeID nid) const { return dirty_[nid]; }\n\n  /// Set node `nid` as (dirty) / (not dirty) based on `val`\n  void setDirty(NodeID nid, bool val) { dirty_[nid] = val; }\n\n  /// Get bounding box of node `nid`\n  const BoundingBox &getBoundingBox(NodeID nid) const { return getShape(nid)->boundingBox(); }\n\n  Layout();\n  ~Layout();\n\npublic slots:\n\n  void growDataStructures(int n_nodes);\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/layout_computer.cpp",
    "content": "#include \"layout_computer.hh\"\n#include \"layout.hh\"\n#include \"structure.hh\"\n#include \"node_tree.hh\"\n#include \"shape.hh\"\n#include \"../utils/std_ext.hh\"\n#include \"../utils/perf_helper.hh\"\n\n#include \"cursors/layout_cursor.hh\"\n#include \"cursors/nodevisitor.hh\"\n\n#include <QMutex>\n#include <QDebug>\n#include <thread>\n#include <iostream>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nLayoutComputer::LayoutComputer(const NodeTree &tree, Layout &layout, const VisualFlags &nf)\n    : m_tree(tree), m_layout(layout), m_vis_flags(nf)\n{\n}\n\nbool LayoutComputer::isDirty(NodeID nid)\n{\n    return m_layout.isDirty(nid);\n}\n\nvoid LayoutComputer::setDirty(NodeID nid)\n{\n\n    utils::MutexLocker lock(&m_layout.getMutex());\n\n    m_layout.setDirty(nid, true);\n}\n\nvoid LayoutComputer::dirtyUpUnconditional(NodeID n)\n{\n    while (n != NodeID::NoNode)\n    {\n        m_layout.setDirty(n, true);\n        n = m_tree.getParent(n);\n    }\n}\n\nvoid LayoutComputer::dirtyUp(NodeID nid)\n{\n    while (nid != NodeID::NoNode && !m_layout.isDirty(nid))\n    {\n        m_layout.setDirty(nid, true);\n        nid = m_tree.getParent(nid);\n    }\n}\n\nvoid LayoutComputer::dirtyUpLater(NodeID nid)\n{\n    // if (m_layout.ready(nid))\n    // {\n    // print(\"dirty up {} later\", nid);\n    du_node_set_.insert(nid);\n    // }\n}\n\nbool LayoutComputer::compute()\n{\n\n    /// do nothing if there is no nodes\n\n    if (m_tree.nodeCount() == 0)\n        return false;\n\n    /// TODO: come back here (ensure mutexes work correctly)\n    utils::MutexLocker tree_lock(&m_tree.treeMutex());\n    utils::MutexLocker layout_lock(&m_layout.getMutex());\n\n    /// Ensures that sufficient memory is allocated for every node's shape\n    m_layout.growDataStructures(m_tree.nodeCount());\n\n    // print(\"to dirty up size: {}\", du_node_set_.size());\n\n    for (auto n : du_node_set_)\n    {\n        dirtyUp(n);\n    }\n\n    du_node_set_.clear();\n\n    LayoutCursor lc(m_tree.getRoot(), m_tree, m_vis_flags, m_layout, debug_mode_);\n    PostorderNodeVisitor<LayoutCursor>(lc).run();\n\n    static int counter = 0;\n    // std::cerr << \"computed layout \" << ++counter   << \" times\\n\";\n\n    return true;\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/layout_computer.hh",
    "content": "#ifndef CPPROFILER_TREE_LAYOUT_COMPUTER_HH\n#define CPPROFILER_TREE_LAYOUT_COMPUTER_HH\n\nclass QMutex;\n\n#include \"node_id.hh\"\n\n#include <set>\n#include <vector>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass Layout;\nclass NodeTree;\nclass VisualFlags;\n\nclass LayoutComputer\n{\n\n    const NodeTree &m_tree;\n    const VisualFlags &m_vis_flags;\n    Layout &m_layout;\n\n    bool m_needs_update = true;\n\n    bool debug_mode_ = false;\n\n    /// Nodes to dirty up right before next layout update\n    std::set<NodeID> du_node_set_;\n\n    void dirtyUp(NodeID nid);\n\n  public:\n    LayoutComputer(const NodeTree &tree, Layout &layout, const VisualFlags &nf);\n\n    /// compute the layout and return where any work was required\n    bool compute();\n\n    /// Mark node's ancestors as dirty without stopping at an already dirty node\n    void dirtyUpUnconditional(NodeID nid);\n\n    void dirtyUpLater(NodeID nid);\n\n    bool isDirty(NodeID nid);\n\n    void setDirty(NodeID nid);\n\n    void setDebugMode(bool val) { debug_mode_ = val; }\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node.cpp",
    "content": "#include \"node.hh\"\n\n#include <cassert>\n#include <iostream>\n#include <exception>\n#include <QDebug>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nNode::Tag Node::getTag() const\n{\n    // obtain the right-most bit\n    return static_cast<Tag>(reinterpret_cast<ptrdiff_t>(m_childrenOrFirstChild) & 3);\n}\n\nvoid Node::setTag(Tag tag)\n{\n    auto itag = static_cast<int>(tag);\n    m_childrenOrFirstChild = reinterpret_cast<void *>(\n        (reinterpret_cast<ptrdiff_t>(m_childrenOrFirstChild) & ~(3)) | itag);\n}\n\nNodeID *Node::getPtr() const\n{\n    return reinterpret_cast<NodeID *>(\n        reinterpret_cast<ptrdiff_t>(m_childrenOrFirstChild) & ~(3));\n}\n\nvoid Node::setPtr(NodeID *ptr)\n{\n    m_childrenOrFirstChild = reinterpret_cast<void *>(ptr);\n}\n\nint Node::childrenCount() const\n{\n\n    auto tag = getTag();\n\n    switch (tag)\n    {\n    case Tag::LEAF:\n        return 0;\n    case Tag::ONE_CHILD:\n        return 1;\n    case Tag::TWO_CHILDREN:\n        return 2;\n    default:\n        return m_noOfChildren;\n    }\n}\n\nvoid Node::setNumberOfChildren(int n)\n{\n\n    const auto old_n = childrenCount();\n\n    /// Nothing to do\n    if (old_n == n)\n        return;\n\n    /// This method works for \"open\" nodes only\n    if (old_n != 0)\n        throw std::exception();\n\n    if (n == 1)\n    {\n        setTag(Tag::ONE_CHILD);\n    }\n    else if (n == 2)\n    {\n        setTag(Tag::TWO_CHILDREN);\n    }\n    else if (n > 2)\n    {\n        m_noOfChildren = n;\n        setPtr(static_cast<NodeID *>(malloc(sizeof(NodeID) * n)));\n        setTag(Tag::MORE_CHILDREN);\n    }\n}\n\nNode::Node(NodeID parent_nid, int kids) : m_parent(parent_nid)\n{\n    // debug(\"node\") << \"    Node()\\n\";\n    setTag(Tag::LEAF); /// initialize to be a leaf first\n    setNumberOfChildren(kids);\n}\n\nvoid Node::setChild(NodeID nid, int alt)\n{\n\n    auto kids = childrenCount();\n\n    if (kids <= 0)\n    {\n        throw no_child();\n    }\n    else if (kids <= 2)\n    {\n        if (alt == 0)\n        {\n            m_childrenOrFirstChild =\n                reinterpret_cast<void *>(static_cast<int>(nid) << 2);\n            setNumberOfChildren(kids);\n        }\n        else\n        {\n            if (alt != 1)\n            {\n                throw std::exception();\n            }\n            m_noOfChildren = -static_cast<int>(nid);\n        }\n    }\n    else\n    {\n        getPtr()[alt] = nid;\n    }\n\n    auto kids2 = childrenCount();\n\n    if (kids != kids2)\n        throw;\n}\n\nNodeID Node::getChild(int alt) const\n{\n    const auto kids = childrenCount();\n\n    if (kids <= 0)\n    {\n        throw std::exception();\n    }\n    else if (kids <= 2)\n    {\n        if (alt == 0)\n        {\n            return static_cast<NodeID>(static_cast<int>(reinterpret_cast<ptrdiff_t>(m_childrenOrFirstChild) >> 2));\n        }\n        else\n        {\n            return NodeID{-m_noOfChildren};\n        }\n    }\n    else\n    {\n        return getPtr()[alt];\n    }\n}\n\nvoid Node::addChild()\n{\n\n    auto kids = childrenCount();\n\n    switch (kids)\n    {\n    case 0:\n    {\n        setTag(Tag::ONE_CHILD);\n    }\n    break;\n    case 1:\n    {\n        setTag(Tag::TWO_CHILDREN);\n    }\n    break;\n    case 2:\n    {\n        /// copy existing children\n        // auto kids = new NodeID[3];\n        auto kids = static_cast<NodeID *>(malloc(sizeof(NodeID) * 3));\n        kids[0] = getChild(0);\n        kids[1] = getChild(1);\n        setPtr(kids);\n        setTag(Tag::MORE_CHILDREN);\n        m_noOfChildren = 3;\n    }\n    break;\n    default:\n    {\n        m_noOfChildren++;\n        auto ptr = static_cast<NodeID *>(realloc(getPtr(), sizeof(NodeID) * m_noOfChildren));\n        setPtr(ptr);\n        setTag(Tag::MORE_CHILDREN);\n    }\n    break;\n    }\n}\n\nNodeID Node::getParent() const\n{\n    return m_parent;\n}\n\nvoid Node::removeChild(int alt)\n{\n    auto kids = childrenCount();\n\n    /// Note: only works for two kids for now\n    if (kids == 2)\n    {\n        /// move second child to first position\n        if (alt == 0)\n        {\n            const auto rkid = getChild(1);\n            setChild(rkid, 0);\n        }\n\n        setTag(Tag::ONE_CHILD);\n    }\n}\n\nNode::~Node()\n{\n    if (getTag() == Tag::MORE_CHILDREN)\n    {\n        free(getPtr());\n    }\n}\n\nQDebug &&operator<<(QDebug &&out, NodeStatus status)\n{\n    switch (status)\n    {\n    case NodeStatus::SOLVED:\n    {\n        out << \"SOLVED\";\n    }\n    break;\n    case NodeStatus::FAILED:\n    {\n        out << \"FAILED\";\n    }\n    break;\n    case NodeStatus::BRANCH:\n    {\n        out << \"BRANCH\";\n    }\n    break;\n    case NodeStatus::SKIPPED:\n    {\n        out << \"SKIPPED\";\n    }\n    break;\n    case NodeStatus::UNDETERMINED:\n    {\n        out << \"UNDETERMINED\";\n    }\n    break;\n    }\n    return std::move(out);\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node.hh",
    "content": "#ifndef CPPROFILER_TREE_NODE\n#define CPPROFILER_TREE_NODE\n\n#include <vector>\n#include \"../core.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nstruct no_child : std::exception\n{\n    const char *what() const throw() override\n    {\n        return \"child does not exist at alt\\n\";\n    }\n};\n\nQDebug &&operator<<(QDebug &&out, NodeStatus status);\n\nclass Node\n{\n  private:\n    enum class Tag\n    {\n        LEAF = 0,\n        ONE_CHILD,\n        TWO_CHILDREN,\n        MORE_CHILDREN\n    };\n\n    /// The parent of this node\n    NodeID m_parent;\n\n    /// The children, or in case there are at most two, the first child\n    void *m_childrenOrFirstChild;\n\n    // The number of children, in case it is greater than 2, or the second child (if negative)\n    int m_noOfChildren;\n\n    Tag getTag() const;\n    void setTag(Tag);\n\n    /// Return children ptr (if nkids > 2) without the tag\n    NodeID *getPtr() const;\n\n    /// Set children ptr (if kids > 2) reseting the tag\n    void setPtr(NodeID *ptr);\n\n  public:\n    Node(NodeID pid, int kids = 0);\n\n    /// Return the number of immediate children\n    int childrenCount() const;\n\n    /// Allocate memory for children, possibly moving existing children\n    void setNumberOfChildren(int n);\n\n    /// Assign nid as the child at position alt\n    void setChild(NodeID nid, int alt);\n\n    /// Make room in the node for another child\n    void addChild();\n\n    /// Get the identifier of the child at position alt\n    NodeID getChild(int alt) const;\n\n    /// Get the identifier of the parent\n    NodeID getParent() const;\n\n    /// Remove `alt` child\n    void removeChild(int alt);\n\n    ~Node();\n    Node &operator=(const Node &) = delete;\n    Node(const Node &) = delete;\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node_drawing.cpp",
    "content": "#include \"node_drawing.hh\"\n#include \"../config.hh\"\n\n#include <QPainter>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\nnamespace draw\n{\n\nnamespace colors\n{\n/// The color for selected nodes\nstatic QColor gold(252, 209, 22);\n/// Red color for failed nodes\nstatic QColor red(218, 37, 29);\n/// Green color for solved nodes\nstatic QColor green(11, 118, 70);\n/// Blue color for choice nodes\nstatic QColor blue(0, 92, 161);\n/// Grey color for skipped nodes\nstatic QColor grey(150, 150, 150);\n/// Orange color for best solutions\nstatic QColor orange(235, 137, 27);\n/// White color\nstatic QColor white(255, 255, 255);\n\n/// Red color for expanded failed nodes\nstatic QColor lightRed(218, 37, 29, 120);\n/// Green color for expanded solved nodes\nstatic QColor lightGreen(11, 118, 70, 120);\n/// Blue color for expanded choice nodes\nstatic QColor lightBlue(0, 92, 161, 120);\n/// Color for pentagons (orange)\nstatic QColor pentagonColor(235, 137, 27);\n} // namespace colors\n\nstatic void drawDiamond(QPainter &painter, int myx, int myy, bool shadow)\n{\n    using namespace traditional;\n    QPointF points[4] = {QPointF(myx, myy),\n                         QPointF(myx + HALF_SOL_W, myy + HALF_SOL_W),\n                         QPointF(myx, myy + SOL_WIDTH),\n                         QPointF(myx - HALF_SOL_W, myy + HALF_SOL_W)};\n\n    if (shadow)\n    {\n        for (auto &&p : points)\n        {\n            p += QPointF(SHADOW_OFFSET, SHADOW_OFFSET);\n        }\n    }\n\n    painter.drawConvexPolygon(points, 4);\n}\n\nvoid solution(QPainter &painter, int x, int y, bool selected)\n{\n    using namespace traditional;\n    if (selected)\n    {\n        painter.setBrush(colors::gold);\n    }\n    else\n    {\n        painter.setBrush(colors::green);\n    }\n\n    drawDiamond(painter, x, y, false);\n}\n\nvoid failure(QPainter &painter, int x, int y, bool selected)\n{\n    using namespace traditional;\n    if (selected)\n    {\n        painter.setBrush(colors::gold);\n    }\n    else\n    {\n        painter.setBrush(colors::red);\n    }\n\n    painter.drawRect(x - HALF_FAILED_WIDTH, y, FAILED_WIDTH, FAILED_WIDTH);\n}\n\nvoid branch(QPainter &painter, int x, int y, bool selected)\n{\n\n    using namespace traditional;\n    if (selected)\n    {\n        painter.setBrush(colors::gold);\n    }\n    else\n    {\n        painter.setBrush(colors::blue);\n    }\n\n    painter.drawEllipse(x - HALF_BRANCH_W, y, BRANCH_WIDTH, BRANCH_WIDTH);\n}\n\nvoid unexplored(QPainter &painter, int x, int y, bool selected)\n{\n    using namespace traditional;\n    if (selected)\n    {\n        painter.setBrush(colors::gold);\n    }\n    else\n    {\n        painter.setBrush(colors::white);\n    }\n\n    painter.drawEllipse(x - HALF_UNDET_WIDTH, y, UNDET_WIDTH, UNDET_WIDTH);\n}\n\nvoid skipped(QPainter &painter, int x, int y, bool selected)\n{\n    using namespace traditional;\n    if (selected)\n    {\n        painter.setBrush(colors::gold);\n    }\n    else\n    {\n        painter.setBrush(colors::grey);\n    }\n\n    painter.drawRect(x - HALF_SKIPPED_WIDTH, y, SKIPPED_WIDTH, SKIPPED_WIDTH);\n}\n\nvoid pentagon(QPainter &painter, int x, int y, bool selected)\n{\n    using namespace traditional;\n    if (selected)\n    {\n        painter.setBrush(colors::gold);\n    }\n    else\n    {\n        painter.setBrush(colors::pentagonColor);\n    }\n\n    QPointF points[5] = {QPointF(x, y),\n                         QPointF(x + PENTAGON_HALF_W, y + PENTAGON_THIRD_W),\n                         QPointF(x + PENTAGON_THIRD_W, y + PENTAGON_WIDTH),\n                         QPointF(x - PENTAGON_THIRD_W, y + PENTAGON_WIDTH),\n                         QPointF(x - PENTAGON_HALF_W, y + PENTAGON_THIRD_W)};\n\n    painter.drawConvexPolygon(points, 5);\n}\n\nvoid big_pentagon(QPainter &painter, int x, int y, bool selected)\n{\n    using namespace traditional;\n    if (selected)\n    {\n        painter.setBrush(colors::gold);\n    }\n    else\n    {\n        painter.setBrush(colors::pentagonColor);\n    }\n\n    QPointF points[5] = {QPointF(x, y),\n                         QPointF(x + BIG_PENTAGON_HALF_W, y + BIG_PENTAGON_THIRD_W),\n                         QPointF(x + BIG_PENTAGON_THIRD_W, y + BIG_PENTAGON_WIDTH),\n                         QPointF(x - BIG_PENTAGON_THIRD_W, y + BIG_PENTAGON_WIDTH),\n                         QPointF(x - BIG_PENTAGON_HALF_W, y + BIG_PENTAGON_THIRD_W)};\n\n    painter.drawConvexPolygon(points, 5);\n}\n\nvoid lantern(QPainter &painter, int x, int y, int size, bool selected, bool has_gradient, bool has_solutions)\n{\n\n    using namespace lantern;\n\n    const int height = K * size;\n\n    if (selected)\n    {\n        painter.setBrush(colors::gold);\n    }\n    else\n    {\n        QColor main_color = has_solutions ? colors::green : colors::red;\n        if (has_gradient)\n        {\n            QLinearGradient gradient(x - HALF_WIDTH, y,\n                                     x + HALF_WIDTH, y + BASE_HEIGHT + height);\n            gradient.setColorAt(0, colors::white);\n            gradient.setColorAt(1, main_color);\n            painter.setBrush(gradient);\n        }\n        else\n        {\n            painter.setBrush(main_color);\n        }\n    }\n\n    QPointF points[5] = {QPointF(x, y),\n                         QPointF(x + HALF_WIDTH, y + BASE_HEIGHT),\n                         QPointF(x + HALF_WIDTH, y + BASE_HEIGHT + height),\n                         QPointF(x - HALF_WIDTH, y + BASE_HEIGHT + height),\n                         QPointF(x - HALF_WIDTH, y + BASE_HEIGHT)};\n\n    painter.drawConvexPolygon(points, 5);\n}\n\n} // namespace draw\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node_drawing.hh",
    "content": "#pragma once\n\nclass QPainter;\n\nnamespace cpprofiler\n{\nnamespace tree\n{\nnamespace draw\n{\n\nvoid solution(QPainter &painter, int x, int y, bool selected);\n\nvoid failure(QPainter &painter, int x, int y, bool selected);\n\nvoid branch(QPainter &painter, int x, int y, bool selected);\n\nvoid unexplored(QPainter &painter, int x, int y, bool selected);\n\nvoid skipped(QPainter &painter, int x, int y, bool selected);\n\nvoid pentagon(QPainter &painter, int x, int y, bool selected);\nvoid big_pentagon(QPainter &painter, int x, int y, bool selected);\n\nvoid lantern(QPainter &painter, int x, int y, int size, bool selected, bool has_gradient, bool has_solutions);\n\n} // namespace draw\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node_id.cpp",
    "content": "#include \"node_id.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nbool operator==(const NodeID &lhs, const NodeID &rhs)\n{\n    return (static_cast<int>(lhs) == static_cast<int>(rhs));\n}\n\nNodeID NodeID::NoNode = NodeID{-1};\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node_id.hh",
    "content": "#ifndef CPPROFILER_TREE_NODE_ID_HH\n#define CPPROFILER_TREE_NODE_ID_HH\n\n#include <cstdint>\n#include <functional>\n\n#include <QMetaType>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass NodeID\n{\n    /// Internal representation\n    int id;\n\n  public:\n    /// Used to indicate invalid or non-existing nodes\n    static NodeID NoNode;\n\n    /// Allow implicit conversion to int\n    operator int() const { return id; }\n    /// Allow explicit conversion from int\n    explicit NodeID(int nid = -1) : id(nid) {}\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n/// Allow signal-slot mechanism across threads to use NodeID\nQ_DECLARE_METATYPE(cpprofiler::tree::NodeID)\n\n/// Make node hashable (to be used as a key)\nnamespace std\n{\ntemplate <>\nstruct hash<cpprofiler::tree::NodeID>\n{\n    size_t operator()(const cpprofiler::tree::NodeID &nid) const\n    {\n        return std::hash<int>{}(static_cast<int>(nid));\n    }\n};\n} // namespace std\n\n/// Allow comparison between NodeIDs\nnamespace cpprofiler\n{\nnamespace tree\n{\nbool operator==(const NodeID &lhs, const NodeID &rhs);\n}\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node_info.cpp",
    "content": "#include \"node_info.hh\"\n#include \"node.hh\"\n#include <QDebug>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nconstexpr NumFlagLoc STATUS = {0, 4};\n\nvoid NodeInfoEntry::setFlag(NodeFlag flag, bool value)\n{\n    m_bitset[static_cast<int>(flag)] = value;\n}\n\nbool NodeInfoEntry::getFlag(NodeFlag flag) const\n{\n    return m_bitset[static_cast<int>(flag)];\n}\n\nvoid NodeInfoEntry::setNumericFlag(NumFlagLoc loc, int value)\n{\n    uint32_t mask = (1 << loc.len) - 1;\n    uint32_t clearmask = ~(mask << loc.pos);\n    m_bitset &= std::bitset<4>(clearmask);\n    m_bitset |= (value & mask) << loc.pos;\n}\n\nint NodeInfoEntry::getNumericFlag(NumFlagLoc loc) const\n{\n    uint32_t mask = (1 << loc.len) - 1;\n    return ((m_bitset >> loc.pos) & std::bitset<4>(mask)).to_ulong();\n}\n\n} // namespace tree\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nNodeStatus NodeInfo::getStatus(NodeID nid) const\n{\n    utils::MutexLocker lock(&m_mutex, \"node info\");\n    if (static_cast<int>(nid) >= m_flags.size())\n        throw;\n    return static_cast<NodeStatus>(m_flags[nid].getNumericFlag(STATUS));\n}\n\nvoid NodeInfo::setStatus(NodeID nid, NodeStatus status)\n{\n    utils::MutexLocker lock(&m_mutex, \"node info\");\n    if (static_cast<int>(nid) >= m_flags.size())\n        throw;\n    m_flags[nid].setNumericFlag(STATUS, static_cast<int>(status));\n}\n\nvoid NodeInfo::addEntry(NodeID nid)\n{\n    utils::MutexLocker lock(&m_mutex, \"node info\");\n    if (nid != m_flags.size())\n        throw;\n    m_flags.push_back({});\n    m_has_solved_children.push_back(false);\n    m_has_open_children.push_back(true);\n}\n\nvoid NodeInfo::setHasSolvedChildren(NodeID nid, bool val)\n{\n    m_has_solved_children[nid] = val;\n}\n\nbool NodeInfo::hasSolvedChildren(NodeID nid) const\n{\n    return m_has_solved_children[nid];\n}\n\nvoid NodeInfo::setHasOpenChildren(NodeID nid, bool val)\n{\n    m_has_open_children[nid] = val;\n}\n\nbool NodeInfo::hasOpenChildren(NodeID nid) const\n{\n    return m_has_open_children[nid];\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node_info.hh",
    "content": "#ifndef CPPROFILER_TREE_NODE_INFO\n#define CPPROFILER_TREE_NODE_INFO\n\n#include \"../core.hh\"\n\n#include <bitset>\n#include <vector>\n#include <QMutex>\n#include \"node_id.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nenum class NodeStatus;\n\nenum class NodeFlag\n{\n\n};\n\nstruct NumFlagLoc\n{\n    int pos;\n    int len;\n};\n\nclass NodeInfoEntry\n{\n\n  private:\n    std::bitset<4> m_bitset;\n\n  public:\n    void setFlag(NodeFlag flag, bool value);\n    bool getFlag(NodeFlag flag) const;\n\n    void setNumericFlag(NumFlagLoc loc, int value);\n    int getNumericFlag(NumFlagLoc loc) const;\n};\n\nclass NodeInfo\n{\n\n    mutable utils::Mutex m_mutex;\n\n    std::vector<NodeInfoEntry> m_flags;\n\n    std::vector<bool> m_has_solved_children;\n    std::vector<bool> m_has_open_children;\n\n  public:\n    NodeStatus getStatus(NodeID nid) const;\n    void setStatus(NodeID nid, NodeStatus status);\n\n    void addEntry(NodeID nid);\n\n    void setHasSolvedChildren(NodeID nid, bool val);\n    bool hasSolvedChildren(NodeID nid) const;\n\n    void setHasOpenChildren(NodeID nid, bool val);\n    bool hasOpenChildren(NodeID nid) const;\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node_stats.hh",
    "content": "#include <QObject>\n#include \"../core.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass NodeStats : public QObject\n{\n    Q_OBJECT\n    int branch_ = 0;\n    int undet_ = 0;\n    int failed_ = 0;\n    int skipped_ = 0;\n    int solved_ = 0;\n    int max_depth_ = 0;\n\n  public:\n    int branchCount() const { return branch_; }\n\n    int undeterminedCount() const { return undet_; }\n\n    int failedCount() const { return failed_; }\n\n    int skippedCount() const { return skipped_; }\n\n    int solvedCount() const { return solved_; }\n\n    int maxDepth() const { return max_depth_; }\n\n    void add_branch(int n)\n    {\n        branch_ = branch_ + n;\n    }\n\n    void subtract_undetermined(int n)\n    {\n        undet_ = undet_ - n;\n    }\n\n    void add_undetermined(int n)\n    {\n        undet_ = undet_ + n;\n    }\n\n    void add_failed(int n)\n    {\n        failed_ = failed_ + n;\n    }\n\n    void add_solved(int n)\n    {\n        solved_ = solved_ + n;\n    }\n\n    void add_skipped(int n)\n    {\n        skipped_ += n;\n    }\n\n    void addNode(NodeStatus status)\n    {\n\n        switch (status)\n        {\n        case NodeStatus::BRANCH:\n        {\n            add_branch(1);\n        }\n        break;\n        case NodeStatus::FAILED:\n        {\n            add_failed(1);\n        }\n        break;\n        case NodeStatus::SOLVED:\n        {\n            add_solved(1);\n        }\n        break;\n        case NodeStatus::SKIPPED:\n        {\n            add_skipped(1);\n        }\n        break;\n        case NodeStatus::UNDETERMINED:\n        {\n            undet_ += 1;\n        }\n        break;\n        }\n    }\n\n    /// see if max depth needs to be updated to d\n    void inform_depth(int d)\n    {\n        if (d > max_depth_)\n        {\n            max_depth_ = d;\n        }\n    }\n\n  signals:\n\n    void stats_changed();\n};\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node_tree.cpp",
    "content": "#include \"node_tree.hh\"\n\n#include \"structure.hh\"\n#include \"node_info.hh\"\n#include \"../solver_data.hh\"\n#include \"../utils/tree_utils.hh\"\n#include \"../name_map.hh\"\n#include <QDebug>\n#include <cassert>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nNodeTree::NodeTree() : structure_{new Structure()}, node_info_(new NodeInfo)\n{\n    qRegisterMetaType<NodeID>();\n}\n\nNodeTree::~NodeTree() = default;\n\nvoid NodeTree::setSolverData(std::shared_ptr<SolverData> sd)\n{\n    solver_data_ = sd;\n}\n\nvoid NodeTree::setNameMap(std::shared_ptr<const NameMap> nm)\n{\n    name_map_ = nm;\n}\n\nvoid NodeTree::addEntry(NodeID nid)\n{\n    node_info_->addEntry(nid);\n    labels_.push_back({});\n}\n\nconst NodeInfo &NodeTree::node_info() const\n{\n    return *node_info_;\n}\n\nNodeInfo &NodeTree::node_info()\n{\n    return *node_info_;\n}\n\nvoid NodeTree::promoteNode(NodeID nid, int kids, tree::NodeStatus status, Label label)\n{\n\n    /// find parent and kid's position\n    const auto pid = getParent(nid);\n    const auto alt = getAlternative(nid);\n\n    promoteNode(pid, alt, kids, status, label);\n}\n\nNodeID NodeTree::createRoot(int kids, Label label)\n{\n    auto nid = structure_->createRoot(kids);\n    addEntry(nid);\n    setLabel(nid, label);\n\n    auto depth = kids > 0 ? 2 : 1;\n\n    node_stats_.inform_depth(depth);\n    node_stats_.add_branch(1);\n    node_info_->setStatus(nid, NodeStatus::BRANCH);\n\n    for (auto i = 0; i < kids; ++i)\n    {\n        auto child_nid = structure_->getChild(nid, i);\n        addEntry(child_nid);\n        node_info_->setStatus(child_nid, NodeStatus::UNDETERMINED);\n    }\n\n    node_stats_.add_undetermined(kids);\n\n    emit structureUpdated();\n\n    return nid;\n}\n\n/// Note that this form does not create children\nvoid NodeTree::db_createRoot(NodeID nid, Label label)\n{\n\n    structure_->db_createRoot(nid);\n    addEntry(nid);\n    setLabel(nid, label);\n\n    node_stats_.inform_depth(1);\n    node_stats_.add_branch(1);\n    node_info_->setStatus(nid, NodeStatus::BRANCH);\n}\n\nstatic bool is_closing(NodeStatus status)\n{\n    return (status == NodeStatus::FAILED) ||\n           (status == NodeStatus::SOLVED) ||\n           (status == NodeStatus::SKIPPED);\n}\n\n/// Note: alt is unnecessary here\nvoid NodeTree::db_addChild(NodeID nid, NodeID pid, int alt, NodeStatus status, Label label)\n{\n    structure_->db_addChild(nid, pid, alt);\n    addEntry(nid);\n\n    node_info_->setStatus(nid, status);\n    setLabel(nid, label);\n\n    emit childrenStructureChanged(pid);\n\n    auto cur_depth = utils::calculate_depth(*this, nid);\n    node_stats_.inform_depth(cur_depth);\n\n    node_stats_.addNode(status);\n\n    if (is_closing(status))\n        closeNode(nid);\n    if (status == NodeStatus::SOLVED)\n        notifyAncestors(nid);\n\n    emit structureUpdated();\n}\n\nvoid NodeTree::addExtraChild(NodeID pid)\n{\n    const auto nid = structure_->addExtraChild(pid);\n    addEntry(nid);\n\n    node_info_->setStatus(nid, NodeStatus::UNDETERMINED);\n    node_stats_.add_undetermined(1);\n\n    emit childrenStructureChanged(pid);\n\n    emit structureUpdated();\n}\n\nNodeID NodeTree::promoteNode(NodeID parent_id, int alt, int kids, tree::NodeStatus status, Label label)\n{\n\n    NodeID nid;\n\n    if (parent_id == NodeID::NoNode)\n    {\n        /// This makes it possible to transform an undet root node into a branch node,\n        /// necessary, for example, in merging\n        nid = structure_->getRoot();\n    }\n    else\n    {\n        nid = structure_->getChild(parent_id, alt);\n    }\n\n    node_info_->setStatus(nid, status);\n    setLabel(nid, label);\n    // setLabel(nid, std::to_string(nid));\n\n    if (kids > 0)\n    {\n        structure_->addChildren(nid, kids);\n        emit childrenStructureChanged(nid); /// updates dirty status for nodes\n\n        for (auto i = 0; i < kids; ++i)\n        {\n            auto child_nid = structure_->getChild(nid, i);\n            addEntry(child_nid);\n            node_info_->setStatus(child_nid, NodeStatus::UNDETERMINED);\n        }\n\n        node_stats_.add_undetermined(kids);\n\n        auto cur_depth = utils::calculate_depth(*this, nid);\n        node_stats_.inform_depth(cur_depth + 1);\n    }\n\n    node_stats_.subtract_undetermined(1);\n    node_stats_.addNode(status);\n\n    if (status == NodeStatus::SOLVED)\n        notifyAncestors(nid);\n\n    if (is_closing(status))\n        closeNode(nid);\n\n    if (childrenCount(nid) != kids)\n    {\n        print(\"error: replacing existing node\");\n    }\n    // assert( childrenCount(nid) == kids );\n\n    emit structureUpdated();\n\n    return nid;\n}\n\nint NodeTree::nodeCount() const\n{\n    return structure_->nodeCount();\n}\n\nNodeID NodeTree::getParent(NodeID nid) const\n{\n    return structure_->getParent(nid);\n}\n\nNodeID NodeTree::getRoot() const\n{\n    return structure_->getRoot();\n}\n\nNodeID NodeTree::getChild(NodeID nid, int alt) const\n{\n    return structure_->getChild(nid, alt);\n}\n\nconst NodeStats &NodeTree::node_stats() const\n{\n    return node_stats_;\n}\n\nconst SolverData &NodeTree::solver_data() const\n{\n    return *solver_data_;\n}\n\nutils::Mutex &NodeTree::treeMutex() const\n{\n    return structure_->getMutex();\n}\n\nNodeStatus NodeTree::getStatus(NodeID nid) const\n{\n    return node_info_->getStatus(nid);\n}\n\nint NodeTree::getNumberOfSiblings(NodeID nid) const\n{\n    return structure_->getNumberOfSiblings(nid);\n}\n\nint NodeTree::depth() const\n{\n    return node_stats_.maxDepth();\n}\n\nint NodeTree::getAlternative(NodeID nid) const\n{\n    return structure_->getAlternative(nid);\n}\n\nint NodeTree::childrenCount(NodeID nid) const\n{\n    return structure_->childrenCount(nid);\n}\n\nvoid NodeTree::notifyAncestors(NodeID nid)\n{\n    while (nid != NodeID::NoNode)\n    {\n        node_info_->setHasSolvedChildren(nid, true);\n        nid = getParent(nid);\n    }\n}\n\nvoid NodeTree::setHasOpenChildren(NodeID nid, bool val)\n{\n    node_info_->setHasOpenChildren(nid, val);\n}\n\nconst Label NodeTree::getLabel(NodeID nid) const\n{\n    // return std::to_string(nid);\n\n    // auto uid = solver_data_->getSolverID(nid);\n    // return uid.toString();\n\n    auto &orig = labels_.at(nid);\n    if (name_map_)\n    {\n        return name_map_->replaceNames(orig);\n    }\n    return orig;\n}\n\nconst Nogood &NodeTree::getNogood(NodeID nid) const\n{\n    return solver_data_->getNogood(nid);\n}\n\nbool NodeTree::hasSolvedChildren(NodeID nid) const\n{\n    return node_info_->hasSolvedChildren(nid);\n}\n\nbool NodeTree::hasOpenChildren(NodeID nid) const\n{\n    return node_info_->hasOpenChildren(nid);\n}\n\nbool NodeTree::isOpen(NodeID nid) const\n{\n    return ((getStatus(nid) == NodeStatus::UNDETERMINED) ||\n            hasOpenChildren(nid));\n}\n\nvoid NodeTree::onChildClosed(NodeID nid)\n{\n\n    bool allClosed = true;\n\n    for (auto i = childrenCount(nid); i--;)\n    {\n        auto kid = getChild(nid, i);\n        if (isOpen(kid))\n        {\n            allClosed = false;\n            break;\n        }\n    }\n\n    if (allClosed)\n    {\n        closeNode(nid);\n\n        if (!hasSolvedChildren(nid))\n        {\n            emit failedSubtreeClosed(nid);\n        }\n    }\n}\n\nvoid NodeTree::closeNode(NodeID nid)\n{\n    setHasOpenChildren(nid, false);\n    auto pid = getParent(nid);\n    if (pid != NodeID::NoNode)\n    {\n        onChildClosed(pid);\n    }\n}\n\nvoid NodeTree::setLabel(NodeID nid, const Label &label)\n{\n    labels_[nid] = label;\n}\n\nvoid NodeTree::removeNode(NodeID nid)\n{\n\n    const auto pid = getParent(nid);\n    if (pid == NodeID::NoNode)\n        return;\n\n    const auto alt = getAlternative(nid);\n    /// should this really remove the node?\n    structure_->removeChild(pid, alt);\n}\n\nvoid NodeTree::db_initialize(int size)\n{\n    structure_->db_initialize(size);\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node_tree.hh",
    "content": "#ifndef CPPROFILER_TREE_NODE_TREE_HH\n#define CPPROFILER_TREE_NODE_TREE_HH\n\n#include <QObject>\n#include <memory>\n#include <string>\n#include <stack>\n#include \"node_id.hh\"\n#include \"node.hh\"\n#include \"../core.hh\"\n\n#include \"node_stats.hh\"\n\nnamespace cpprofiler\n{\nclass NameMap;\nclass SolverData;\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass Structure;\nclass NodeInfo;\n\nstatic Label emptyLabel = {};\n\n/// Node tree encapsulates tree structure, node statistics (number of nodes etc.),\n/// status for nodes (node_info_), labels\nclass NodeTree : public QObject\n{\n    Q_OBJECT\n    /// Tree structural information\n    std::unique_ptr<Structure> structure_;\n    /// Nodes' statuses and flags (has solved/open children etc.)\n    std::unique_ptr<NodeInfo> node_info_;\n    /// Mapping from ugly to nice names (if present, owned by Execution)\n    std::shared_ptr<const NameMap> name_map_;\n    /// Contains a mapping from node ids to their original solver ids (triplets)\n    std::shared_ptr<SolverData> solver_data_;\n    /// Nodes' labels\n    std::vector<Label> labels_;\n    /// Count of different types of nodes, tree depth\n    NodeStats node_stats_;\n\n    /// Indicates whether the tree is fully built\n    bool is_done_ = false;\n\n    /// Ensure all relevant data structures contain this node\n    void addEntry(NodeID nid);\n\n    /// Notify ancestor nodes of a solution\n    void notifyAncestors(NodeID nid);\n\n    /// Notify ancestor nodes that of whether they contain open nodes\n    void onChildClosed(NodeID nid);\n\n    /// Set closed and notify ancestors\n    void closeNode(NodeID nid);\n\n  public:\n    NodeTree();\n    ~NodeTree();\n\n    const NodeInfo &node_info() const;\n    NodeInfo &node_info();\n\n    const NodeStats &node_stats() const;\n\n    const SolverData &solver_data() const;\n\n    cpprofiler::utils::Mutex &treeMutex() const;\n\n    void setSolverData(std::shared_ptr<SolverData> sd);\n\n    void setNameMap(std::shared_ptr<const NameMap> nm);\n\n    void setDone() { is_done_ = true; }\n\n    bool isDone() const { return is_done_; }\n\n    /// *************************** Tree Modifiers ***************************\n\n    NodeID createRoot(int kids, Label label = emptyLabel);\n\n    /// turn a white node into some other node\n    void promoteNode(NodeID nid, int kids, NodeStatus status, Label = emptyLabel);\n\n    /// TODO: rename it to resetNode\n    /// Turn undet node into a real one, updating stats and emitting signals\n    NodeID promoteNode(NodeID parent_id, int alt, int kids, NodeStatus status, Label = emptyLabel);\n\n    void addExtraChild(NodeID pid);\n\n    /// Set the flag for open children\n    void setHasOpenChildren(NodeID nid, bool val);\n\n    void setLabel(NodeID nid, const Label &label);\n\n    /// Remove node `nid` from the tree\n    void removeNode(NodeID nid);\n\n    /// ********************************************************************\n    /// *************************** Tree Queries ***************************\n\n    /// return the depth of the tree\n    int depth() const;\n\n    NodeID getRoot() const;\n\n    /// Get the total nuber of nodes (including undetermined)\n    int nodeCount() const;\n\n    /// Get the total nuber of siblings of `nid` including the node itself\n    int getNumberOfSiblings(NodeID nid) const;\n\n    /// Get the position of node `nid` relative to its left-most sibling\n    int getAlternative(NodeID nid) const;\n\n    /// Get the total number of children of node `pid`\n    int childrenCount(NodeID nid) const;\n\n    /// Get the child of node `pid` at position `alt`\n    NodeID getChild(NodeID nid, int alt) const;\n\n    /// Get the parent of node `nid` (returns NodeID::NoNode for root)\n    NodeID getParent(NodeID nid) const;\n\n    /// Get the status of node `nid`\n    NodeStatus getStatus(NodeID nid) const;\n\n    /// Get the label of node `nid`\n    const Label getLabel(NodeID nid) const;\n\n    /// Get the nogood of node `nid`\n    const Nogood &getNogood(NodeID nid) const;\n\n    /// Check if the node `nid` has solved children (ancestors?)\n    bool hasSolvedChildren(NodeID nid) const;\n\n    /// Check if the node `nid` has open children (ancestors?)\n    bool hasOpenChildren(NodeID nid) const;\n\n    /// Check if the node `nid` is open or has open children\n    bool isOpen(NodeID nid) const;\n\n    /// ************ Building a tree from a database ************\n\n    void db_initialize(int size);\n\n    void db_createRoot(NodeID nid, Label label = emptyLabel);\n\n    void db_addChild(NodeID nid, NodeID pid, int alt, NodeStatus status, Label = emptyLabel);\n\n    /// ********************************************************************\n\n  signals:\n\n    /// Notifies that the strucutre of the tree changed in general\n    /// and triggeres layout update\n    void structureUpdated();\n\n    /// Notifies that the structure underneath the node has changed\n    /// and requires layout update\n    void childrenStructureChanged(NodeID nid);\n\n    /// Notify that all nodes in the subtree have been closed (no undetermined nodes)\n    void failedSubtreeClosed(cpprofiler::tree::NodeID nid);\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/node_widget.hh",
    "content": "#pragma once\n\n#include <QWidget>\n#include <QDebug>\n#include <QPainter>\n#include \"../config.hh\"\n#include \"../core.hh\"\n\n#include \"node_drawing.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\n/// Draws a single node\nclass NodeWidget : public QWidget\n{\n\n    constexpr static int WIDTH = traditional::MAX_NODE_W + 4;\n\n    NodeStatus m_status;\n\n  protected:\n    void paintEvent(QPaintEvent *) override\n    {\n        using namespace traditional;\n\n        QPainter painter(this);\n        painter.setRenderHint(QPainter::Antialiasing);\n\n        switch (m_status)\n        {\n        case NodeStatus::SOLVED:\n        {\n            auto cy = (WIDTH - SOL_WIDTH) / 2;\n            auto cx = cy + HALF_SOL_W;\n            draw::solution(painter, cx, cy, false);\n        }\n        break;\n        case NodeStatus::FAILED:\n        {\n            auto cy = (WIDTH - FAILED_WIDTH) / 2;\n            auto cx = cy + HALF_FAILED_WIDTH;\n            draw::failure(painter, cx, cy, false);\n        }\n        break;\n        case NodeStatus::SKIPPED:\n        {\n            auto cy = (WIDTH - SKIPPED_WIDTH) / 2;\n            auto cx = cy + HALF_SKIPPED_WIDTH;\n            draw::skipped(painter, cx, cy, false);\n        }\n        break;\n        case NodeStatus::BRANCH:\n        {\n            auto cy = (WIDTH - BRANCH_WIDTH) / 2;\n            auto cx = cy + HALF_BRANCH_W;\n            draw::branch(painter, cx, cy, false);\n        }\n        break;\n        case NodeStatus::UNDETERMINED:\n        {\n            auto cy = (WIDTH - UNDET_WIDTH) / 2;\n            auto cx = cy + HALF_UNDET_WIDTH;\n            draw::unexplored(painter, cx, cy, false);\n        }\n        break;\n        case NodeStatus::MERGED:\n        {\n            auto cy = (WIDTH - PENTAGON_WIDTH) / 2;\n            auto cx = cy + PENTAGON_HALF_W;\n            draw::pentagon(painter, cx, cy, false);\n        }\n        break;\n        default:\n            break;\n        }\n    }\n\n  public:\n    explicit NodeWidget(NodeStatus s) : m_status(s)\n    {\n        setMinimumSize(WIDTH, WIDTH);\n        setMaximumSize(WIDTH, WIDTH);\n        switch (m_status)\n        {\n        case NodeStatus::SOLVED:\n            setToolTip(\"Solutions\");\n            break;\n        case NodeStatus::FAILED:\n            setToolTip(\"Failures\");\n            break;\n        case NodeStatus::SKIPPED:\n            setToolTip(\"Skipped\");\n            break;\n        case NodeStatus::BRANCH:\n            setToolTip(\"Branches\");\n            break;\n        case NodeStatus::UNDETERMINED:\n            setToolTip(\"Undetermined\");\n            break;\n        case NodeStatus::MERGED:\n            setToolTip(\"Merged\");\n            break;\n        default:\n            break;\n        }\n    }\n\n    ~NodeWidget()\n    {\n    }\n};\n\n} // namespace tree\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/shape.cpp",
    "content": "#include \"shape.hh\"\n#include \"../config.hh\"\n#include <QDebug>\n#include <ostream>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nShape::Shape(int height) : extents_(height) {}\n\nstd::ostream &operator<<(std::ostream &os, const cpprofiler::tree::Shape &s)\n{\n    os << \"{ height: \" << s.height() << \", [ \";\n\n    for (auto i = 0; i < s.extents_.size(); ++i)\n    {\n        os << \"{\" << s.extents_[i].l << \":\" << s.extents_[i].r << \"} \";\n    }\n\n    return os << \"]}\";\n}\n\nusing namespace traditional;\n\nShape Shape::leaf({{-HALF_MAX_NODE_W, HALF_MAX_NODE_W}},\n                  BoundingBox{-HALF_MAX_NODE_W, HALF_MAX_NODE_W});\n\nShape Shape::hidden({{-HALF_MAX_NODE_W, HALF_MAX_NODE_W}, {-MAX_NODE_W, MAX_NODE_W}},\n                    BoundingBox{-MAX_NODE_W, MAX_NODE_W});\n} // namespace tree\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/shape.hh",
    "content": "#ifndef CPPROFILER_TREE_SHAPE\n#define CPPROFILER_TREE_SHAPE\n\n#include \"../utils/array.hh\"\n#include <ostream>\n#include <initializer_list>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\n/// Shape's extent on some level\nclass Extent\n{\n  public:\n    /// Left extent\n    int l;\n    /// Right extent\n    int r;\n    /// Construct with \\a l and \\a r\n    Extent(int l0, int r0) : l(l0), r(r0) {}\n\n    Extent() = default;\n};\n\n/// Shape's bounding box\nstruct BoundingBox\n{\n    int left;\n    int right;\n\n    int width() const\n    {\n        return right - left;\n    }\n};\n\n/// Subtree's shape (outline) represented by extents on each depth level\nclass Shape\n{\n\n    /// Shape's extents\n    utils::Array<Extent> extents_;\n\n    /// Shapes's bounding box\n    BoundingBox bb_;\n\n    friend std::ostream &operator<<(std::ostream &, const Shape &);\n\n  public:\n    /// Create a shape of depth/height of `height` with extents uninitialized\n    explicit Shape(int height);\n\n    /// Create a shape using initializer list and a pre-computed bounding box\n    Shape(std::initializer_list<Extent> init_list, const BoundingBox &bb)\n        : extents_{init_list}, bb_{bb} {}\n\n    /// Get the depth/height of the shape\n    int height() const { return extents_.size(); }\n\n    /// Get the extent at `depth`\n    Extent &operator[](int depth) { return extents_[depth]; }\n\n    /// Get the extent at `depth`\n    const Extent &operator[](int depth) const { return extents_[depth]; }\n\n    /// Set bounding box\n    void setBoundingBox(BoundingBox bb) { bb_ = bb; }\n\n    /// Get bounding box\n    const BoundingBox &boundingBox() const { return bb_; }\n\n    /// Shape used for all leaf nodes (that don't have labels displayed)\n    static Shape leaf;\n    /// Shape used for all hidden nodes / failed subtrees (that don't have labels displayed)\n    static Shape hidden;\n};\n\nclass ShapeDeleter\n{\n  public:\n    void operator()(Shape *s)\n    {\n        if (s != &Shape::leaf && s != &Shape::hidden)\n        {\n            delete s;\n        }\n    }\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/structure.cpp",
    "content": "#include \"structure.hh\"\n\n#include <iostream>\n#include <exception>\n#include <stack>\n#include <algorithm>\n#include <QDebug>\n\nusing cpprofiler::utils::Mutex;\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nStructure::Structure()\n{\n    nodes_.reserve(100);\n}\n\nMutex &Structure::getMutex() const\n{\n    return mutex_;\n}\n\nNodeID Structure::createRoot(int kids)\n{\n    if (nodes_.size() > 0)\n    {\n        throw invalid_tree();\n    }\n\n    const auto root_nid = createNode(NodeID::NoNode, kids);\n\n    /// create white nodes for children nodes\n    for (auto i = 0; i < kids; ++i)\n    {\n        createChild(root_nid, i, 0);\n    }\n\n    return root_nid;\n}\n\nNodeID Structure::createNode(NodeID pid, int kids)\n{\n    const auto nid = NodeID{static_cast<int>(nodes_.size())};\n    nodes_.push_back(std::unique_ptr<Node>{new Node(pid, kids)});\n    return nid;\n}\n\nvoid Structure::db_createNode(NodeID nid, NodeID pid, int kids)\n{\n    nodes_[nid] = std::unique_ptr<Node>{new Node(pid, kids)};\n}\n\nNodeID Structure::createChild(NodeID pid, int alt, int kids)\n{\n    const auto nid = createNode(pid, kids);\n    nodes_[pid]->setChild(nid, alt);\n    return nid;\n}\n\nNodeID Structure::addExtraChild(NodeID pid)\n{\n\n    const auto alt = childrenCount(pid);\n\n    /// make room for another child\n    nodes_[pid]->addChild();\n\n    auto kid = createChild(pid, alt, 0);\n    return kid;\n}\n\nvoid Structure::addChildren(NodeID nid, int kids)\n{\n    if (nodes_[nid]->childrenCount() > 0)\n        throw;\n\n    nodes_[nid]->setNumberOfChildren(kids);\n\n    for (auto i = 0; i < kids; ++i)\n    {\n        createChild(nid, i, 0);\n    }\n}\n\n/// Remove `alt` child of `pid`\nvoid Structure::removeChild(NodeID pid, int alt)\n{\n    nodes_[pid]->removeChild(alt);\n}\n\nNodeID Structure::getChild(NodeID pid, int alt) const\n{\n    return nodes_[pid]->getChild(alt);\n}\n\nNodeID Structure::getParent(NodeID nid) const\n{\n    return nodes_[nid]->getParent();\n}\n\nint Structure::childrenCount(NodeID pid) const\n{\n    return nodes_[pid]->childrenCount();\n}\n\nint Structure::getNumberOfSiblings(NodeID nid) const\n{\n    auto pid = getParent(nid);\n    return childrenCount(pid);\n}\n\nNodeID Structure::getRoot() const\n{\n    return NodeID{0};\n}\n\nint Structure::getAlternative(NodeID nid) const\n{\n    auto parent_nid = getParent(nid);\n\n    if (parent_nid == NodeID::NoNode)\n        return -1;\n\n    for (auto i = 0; i < childrenCount(parent_nid); ++i)\n    {\n        if (getChild(parent_nid, i) == nid)\n        {\n            return i;\n        }\n    }\n    throw;\n    return -1;\n}\n\nint Structure::nodeCount() const\n{\n    return nodes_.size();\n}\n\nvoid Structure::db_initialize(int size)\n{\n    nodes_.resize(size);\n}\n\nvoid Structure::db_createRoot(NodeID nid)\n{\n    db_createNode(nid, NodeID::NoNode, 0);\n}\n\nvoid Structure::db_createChild(NodeID nid, NodeID pid, int alt)\n{\n    db_createNode(nid, pid, 0);\n    nodes_[pid]->setChild(nid, alt);\n}\n\nvoid Structure::db_addChild(NodeID nid, NodeID pid, int alt)\n{\n    nodes_[pid]->addChild();\n    db_createChild(nid, pid, alt);\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/structure.hh",
    "content": "#ifndef CPPROFILER_NODE_TREE_HH\n#define CPPROFILER_NODE_TREE_HH\n\n#include \"node.hh\"\n\n#include \"../core.hh\"\n\n#include \"memory\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nstruct invalid_tree : std::exception\n{\n    const char *what() const throw() override\n    {\n        return \"invalid tree\\n\";\n    }\n};\n\nclass LayoutComputer;\n\n/// Structure is responsible for dealing with the tree's structural information.\n/// Since it is not aware of statuses, labels etc., it is the caller's responsibility\n/// to ensure that this information is stored elsewhere when, for example, new nodes\n/// are created using Structure's API.\nclass Structure\n{\n\n    /// Protects `nodes_`\n    mutable utils::Mutex mutex_;\n\n    /// Nodes that describe the structure of the tree\n    std::vector<std::unique_ptr<Node>> nodes_;\n\n    /// Allocate memory for a new node and return its Id\n    NodeID createNode(NodeID pid, int kids);\n\n    /// Create a new node with `kids` kids and add set it as the `alt`th kid of `pid`\n    NodeID createChild(NodeID pid, int alt, int kids);\n\n    void db_createNode(NodeID nid, NodeID pid, int kids);\n\n  public:\n    Structure();\n\n    /// Get mutex protecting structural information\n    utils::Mutex &getMutex() const;\n\n    /// Get the identifier of the root (should be 0)\n    NodeID getRoot() const;\n\n    /// Get the parent of node `nid` (returns NodeID::NoNode for root)\n    NodeID getParent(NodeID nid) const;\n\n    /// Get the child of node `pid` at position `alt`\n    NodeID getChild(NodeID pid, int alt) const;\n\n    /// Get the position of node `nid` relative to its left-most sibling\n    int getAlternative(NodeID nid) const;\n\n    /// Get the total nuber of siblings of `nid` including the node itself\n    int getNumberOfSiblings(NodeID nid) const;\n\n    /// Get the total number of children of node `pid`\n    int childrenCount(NodeID pid) const;\n\n    /// Get the total nuber of nodes (including undetermined)\n    int nodeCount() const;\n\n    /// ************ Modifying (building) a tree ************\n\n    /// Create a root node and `kids` children\n    NodeID createRoot(int kids);\n\n    /// Extend node's children with one more child\n    NodeID addExtraChild(NodeID nid);\n\n    /// Add `kids` children to an open node\n    void addChildren(NodeID nid, int kids);\n\n    /// Remove `alt` child of `pid`\n    void removeChild(NodeID pid, int alt);\n\n    /// ************ Building a tree from a database ************\n\n    void db_initialize(int size);\n\n    void db_createRoot(NodeID nid);\n\n    void db_createChild(NodeID nid, NodeID pid, int alt);\n\n    void db_addChild(NodeID nid, NodeID pid, int alt);\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/subtree_view.hh",
    "content": "#pragma once\n\n#include <QWidget>\n\n#include \"../user_data.hh\"\n#include \"tree_scroll_area.hh\"\n#include \"layout_computer.hh\"\n#include \"visual_flags.hh\"\n#include \"layout.hh\"\n#include \"../core.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass SubtreeView : public QWidget\n{\n    Q_OBJECT\n\n    std::unique_ptr<UserData> user_data_;\n    std::unique_ptr<Layout> m_layout;\n    std::unique_ptr<TreeScrollArea> m_scroll_area;\n\n    /// Not really used in this view?\n    VisualFlags node_flags;\n\n    /// Current node representing the root of the subtree\n    NodeID node_ = NodeID::NoNode;\n\n  public:\n    SubtreeView(const NodeTree &nt)\n        : user_data_(utils::make_unique<UserData>()), m_layout(utils::make_unique<Layout>())\n    {\n        auto layout_computer = LayoutComputer(nt, *m_layout, node_flags);\n        layout_computer.compute();\n\n        m_scroll_area.reset(new TreeScrollArea(node_, nt, *user_data_, *m_layout, node_flags));\n        m_scroll_area->setScale(50);\n        m_scroll_area->viewport()->update();\n    }\n\n    QWidget *widget()\n    {\n        return m_scroll_area.get();\n    }\n\n  public slots:\n\n    void setNode(NodeID nid)\n    {\n        node_ = nid;\n        m_scroll_area->changeStartNode(nid);\n        m_scroll_area->viewport()->update();\n    }\n};\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/traditional_view.cpp",
    "content": "#include \"traditional_view.hh\"\n#include \"tree_scroll_area.hh\"\n#include \"layout.hh\"\n#include \"structure.hh\"\n#include \"node_tree.hh\"\n\n#include <queue>\n#include <thread>\n#include <cmath>\n\n#include <QPainter>\n#include <QDebug>\n#include <QTimer>\n#include <QScrollBar>\n#include <QMouseEvent>\n#include <QInputDialog>\n#include <QLineEdit>\n#include <QTextEdit>\n#include <QThread>\n#include <QVBoxLayout>\n\n#include \"cursors/nodevisitor.hh\"\n#include \"cursors/hide_failed_cursor.hh\"\n#include \"cursors/hide_not_highlighted_cursor.hh\"\n#include \"../utils/std_ext.hh\"\n#include \"node_id.hh\"\n#include \"shape.hh\"\n#include \"../user_data.hh\"\n#include \"../solver_data.hh\"\n#include \"layout_computer.hh\"\n#include \"../config.hh\"\n\n#include \"../nogood_dialog.hh\"\n\n#include \"../utils/perf_helper.hh\"\n#include \"../utils/tree_utils.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nTraditionalView::TraditionalView(const NodeTree &tree, UserData &ud, SolverData &sd)\n    : tree_(tree),\n      user_data_(ud),\n      solver_data_(sd),\n      vis_flags_(utils::make_unique<VisualFlags>()),\n      layout_(utils::make_unique<Layout>()),\n      layout_computer_(utils::make_unique<LayoutComputer>(tree, *layout_, *vis_flags_))\n{\n    utils::DebugMutexLocker tree_lock(&tree_.treeMutex());\n\n    scroll_area_.reset(new TreeScrollArea(tree_.getRoot(), tree_, user_data_, *layout_, *vis_flags_));\n\n    // std::cerr << \"traditional view thread:\" << std::this_thread::get_id() << std::endl;\n\n    // connect(scroll_area_.get(), &TreeScrollArea::nodeClicked, this, &TraditionalView::setCurrentNode);\n    connect(scroll_area_.get(), &TreeScrollArea::nodeClicked, this, &TraditionalView::nodeSelected);\n    connect(scroll_area_.get(), &TreeScrollArea::nodeDoubleClicked, this, &TraditionalView::handleDoubleClick);\n\n    connect(this, &TraditionalView::needsRedrawing, this, &TraditionalView::redraw);\n    connect(this, &TraditionalView::needsLayoutUpdate, this, &TraditionalView::updateLayout);\n\n    connect(&tree, &NodeTree::childrenStructureChanged, [this](NodeID nid) {\n        if (nid == NodeID::NoNode)\n        {\n            return;\n        }\n        layout_computer_->dirtyUpLater(nid);\n        // layout_->setLayoutDone(nid, false);\n    });\n\n    auto autoLayoutTimer = new QTimer(this);\n\n    connect(autoLayoutTimer, &QTimer::timeout, this, &TraditionalView::autoUpdate);\n\n    /// stop this timer up when the tree is finished?\n    autoLayoutTimer->start(100);\n}\n\nTraditionalView::~TraditionalView() = default;\n\nvoid TraditionalView::redraw()\n{\n    scroll_area_->viewport()->update();\n}\n\nNodeID TraditionalView::node() const\n{\n    return user_data_.getSelectedNode();\n}\n\nvoid TraditionalView::setNode(NodeID nid)\n{\n    user_data_.setSelectedNode(nid);\n}\n\nvoid TraditionalView::navRoot()\n{\n    auto root = tree_.getRoot();\n    emit nodeSelected(root);\n    centerCurrentNode(); /// TODO: this should be needed\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::navDown()\n{\n\n    const auto nid = node();\n\n    if (nid == NodeID::NoNode)\n        return;\n\n    utils::DebugMutexLocker tree_lock(&tree_.treeMutex());\n\n    const auto kids = tree_.childrenCount(nid);\n\n    if (kids == 0 || vis_flags_->isHidden(nid))\n        return;\n\n    auto first_kid = tree_.getChild(nid, 0);\n\n    emit nodeSelected(first_kid);\n    centerCurrentNode();\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::navDownAlt()\n{\n    const auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    utils::DebugMutexLocker tree_lock(&tree_.treeMutex());\n\n    const auto kids = tree_.childrenCount(nid);\n\n    if (kids == 0 || vis_flags_->isHidden(nid))\n        return;\n\n    auto last_kid = tree_.getChild(nid, kids - 1);\n    emit nodeSelected(last_kid);\n    centerCurrentNode();\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::navUp()\n{\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    utils::DebugMutexLocker tree_lock(&tree_.treeMutex());\n\n    auto pid = tree_.getParent(nid);\n\n    if (pid != NodeID::NoNode)\n    {\n        emit nodeSelected(pid);\n        centerCurrentNode();\n        emit needsRedrawing();\n    }\n}\n\nvoid TraditionalView::navLeft()\n{\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    utils::DebugMutexLocker tree_lock(&tree_.treeMutex());\n\n    auto pid = tree_.getParent(nid);\n    if (pid == NodeID::NoNode)\n        return;\n\n    auto cur_alt = tree_.getAlternative(nid);\n\n    if (cur_alt > 0)\n    {\n        auto kid = tree_.getChild(pid, cur_alt - 1);\n        emit nodeSelected(kid);\n        centerCurrentNode();\n        emit needsRedrawing();\n    }\n}\n\nvoid TraditionalView::navRight()\n{\n    /// lock mutex\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    utils::DebugMutexLocker tree_lock(&tree_.treeMutex());\n\n    auto pid = tree_.getParent(nid);\n\n    if (pid == NodeID::NoNode)\n        return;\n\n    auto cur_alt = tree_.getAlternative(nid);\n\n    auto kids = tree_.childrenCount(pid);\n\n    if (cur_alt + 1 < kids)\n    {\n        const auto kid = tree_.getChild(pid, cur_alt + 1);\n        emit nodeSelected(kid);\n        centerCurrentNode();\n        emit needsRedrawing();\n    }\n}\n\nvoid TraditionalView::setLabelShown(NodeID nid, bool val)\n{\n    vis_flags_->setLabelShown(nid, val);\n    layout_computer_->dirtyUpLater(nid);\n}\n\nvoid TraditionalView::toggleShowLabel()\n{\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    auto val = !vis_flags_->isLabelShown(nid);\n    setLabelShown(nid, val);\n    emit needsRedrawing();\n    layout_computer_->dirtyUpLater(nid);\n}\n\nvoid TraditionalView::showLabelsDown()\n{\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    auto val = !vis_flags_->isLabelShown(nid);\n\n    utils::pre_order_apply(tree_, nid, [val, this](NodeID nid) {\n        setLabelShown(nid, val);\n    });\n\n    emit needsLayoutUpdate();\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::showLabelsUp()\n{\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    auto pid = tree_.getParent(nid);\n\n    /// if it is root, toggle for the root only\n    if (pid == NodeID::NoNode)\n    {\n        toggleShowLabel();\n        return;\n    }\n\n    auto val = !vis_flags_->isLabelShown(pid);\n\n    while (nid != NodeID::NoNode)\n    {\n        setLabelShown(nid, val);\n        nid = tree_.getParent(nid);\n    }\n\n    emit needsLayoutUpdate();\n    emit needsRedrawing();\n}\n\nstatic bool is_leaf(const NodeTree &nt, NodeID nid)\n{\n    return nt.childrenCount(nid) == 0;\n}\n\nvoid TraditionalView::hideNode(NodeID n, bool delayed)\n{\n    utils::DebugMutexLocker tree_lock(&tree_.treeMutex());\n    utils::DebugMutexLocker layout_lock(&layout_->getMutex());\n\n    if (is_leaf(tree_, n))\n        return;\n\n    vis_flags_->setHidden(n, true);\n\n    dirtyUp(n);\n\n    if (delayed)\n    {\n        setLayoutOutdated();\n    }\n    else\n    {\n        emit needsLayoutUpdate();\n    }\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::toggleHidden()\n{\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    // do not hide leaf nodes\n    if (is_leaf(tree_, nid))\n        return;\n\n    auto val = !vis_flags_->isHidden(nid);\n    vis_flags_->setHidden(nid, val);\n\n    dirtyUp(nid);\n\n    emit needsLayoutUpdate();\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::hideFailedAt(NodeID n, bool onlyDirty)\n{\n    /// Do nothing if there is no tree\n    if (tree_.nodeCount() == 0)\n        return;\n\n    bool modified = false;\n\n    HideFailedCursor hfc(n, tree_, *vis_flags_, *layout_computer_, onlyDirty, modified);\n    PostorderNodeVisitor<HideFailedCursor>(hfc).run();\n\n    if (modified)\n    {\n        emit needsLayoutUpdate();\n        emit needsRedrawing();\n    }\n}\n\nvoid TraditionalView::hideFailed(bool onlyDirty)\n{\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    hideFailedAt(nid, onlyDirty);\n}\n\nvoid TraditionalView::autoUpdate()\n{\n    if (!layout_stale_)\n        return;\n\n    const auto changed = updateLayout();\n    if (changed)\n    {\n        emit needsRedrawing();\n    }\n}\n\nvoid TraditionalView::handleDoubleClick()\n{\n\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    auto status = tree_.getStatus(nid);\n\n    if (status == NodeStatus::BRANCH)\n    {\n        unhideNode(nid);\n    }\n    else if (status == NodeStatus::MERGED)\n    {\n        toggleCollapsePentagon(nid);\n    }\n\n    /// should this happen automatically whenever the layout is changed?\n    centerCurrentNode();\n}\n\nvoid TraditionalView::toggleCollapsePentagon(NodeID nid)\n{\n    /// Use the same 'hidden' flag for now\n    auto val = !vis_flags_->isHidden(nid);\n    vis_flags_->setHidden(nid, val);\n    dirtyUp(nid);\n    emit needsLayoutUpdate();\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::setNodeHidden(NodeID n, bool val)\n{\n    vis_flags_->setHidden(n, false);\n    layout_->setLayoutDone(n, false);\n}\n\nvoid TraditionalView::unhideNode(NodeID nid)\n{\n    utils::DebugMutexLocker tree_lock(&tree_.treeMutex());\n    utils::DebugMutexLocker layout_lock(&layout_->getMutex());\n\n    auto hidden = vis_flags_->isHidden(nid);\n    if (hidden)\n    {\n        setNodeHidden(nid, false);\n\n        dirtyUp(nid);\n        emit needsLayoutUpdate();\n        emit needsRedrawing();\n    }\n}\n\nvoid TraditionalView::bookmarkCurrentNode()\n{\n    auto nid = node();\n\n    if (!user_data_.isBookmarked(nid))\n    {\n        /// Add bookmark\n        bool accepted;\n        auto text = QInputDialog::getText(nullptr, \"Add bookmark\", \"Name:\", QLineEdit::Normal, \"\", &accepted);\n        if (!accepted)\n            return;\n\n        user_data_.setBookmark(nid, text.toStdString());\n    }\n    else\n    {\n        /// Remove bookmark\n        user_data_.clearBookmark(nid);\n    }\n\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::unhideAllAt(NodeID n)\n{\n    utils::DebugMutexLocker tree_lock(&tree_.treeMutex());\n    utils::DebugMutexLocker layout_lock(&layout_->getMutex());\n\n    if (n == tree_.getRoot())\n    {\n        unhideAll();\n        return;\n    }\n\n    /// indicates if any change was made\n    bool modified = false;\n\n    const auto action = [&](NodeID n) {\n        if (vis_flags_->isHidden(n))\n        {\n            vis_flags_->setHidden(n, false);\n            layout_->setLayoutDone(n, false);\n            dirtyUp(n);\n            modified = true;\n        }\n    };\n\n    utils::apply_below(tree_, n, action);\n\n    if (modified)\n    {\n        emit needsLayoutUpdate();\n        emit needsRedrawing();\n    }\n\n    centerCurrentNode();\n}\n\nvoid TraditionalView::unhideAll()\n{\n\n    /// faster version for the entire tree\n    if (vis_flags_->hiddenCount() == 0)\n    {\n        return;\n    }\n\n    for (auto n : vis_flags_->hidden_nodes())\n    {\n        dirtyUp(n);\n        layout_->setLayoutDone(n, false);\n    }\n\n    vis_flags_->unhideAll();\n\n    emit needsLayoutUpdate();\n    emit needsRedrawing();\n    centerNode(tree_.getRoot());\n}\n\nvoid TraditionalView::unhideAllAtCurrent()\n{\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    unhideAllAt(nid);\n}\n\nvoid TraditionalView::toggleHighlighted()\n{\n    auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    auto val = !vis_flags_->isHighlighted(nid);\n    vis_flags_->setHighlighted(nid, val);\n\n    emit needsRedrawing();\n}\n\nQWidget *TraditionalView::widget()\n{\n    return scroll_area_.get();\n}\n\nconst Layout &TraditionalView::layout() const\n{\n    return *layout_;\n}\n\nvoid TraditionalView::setScale(int val)\n{\n    scroll_area_->setScale(val);\n}\n\n/// relative to the root\nstatic int global_node_x_offset(const NodeTree &tree, const Layout &layout, NodeID nid)\n{\n    auto x_off = 0;\n\n    while (nid != NodeID::NoNode)\n    {\n        x_off += layout.getOffset(nid);\n        nid = tree.getParent(nid);\n    }\n\n    return x_off;\n}\n\n/// Does this need any locking?\nvoid TraditionalView::centerNode(NodeID nid)\n{\n\n    const auto x_offset = global_node_x_offset(tree_, *layout_, nid);\n\n    const auto root_nid = tree_.getRoot();\n    const auto bb = layout_->getBoundingBox(root_nid);\n\n    const auto value_x = x_offset - bb.left;\n\n    const auto depth = utils::calculate_depth(tree_, nid);\n    const auto value_y = depth * layout::dist_y;\n\n    scroll_area_->centerPoint(value_x, value_y);\n}\n\nvoid TraditionalView::centerCurrentNode()\n{\n    centerNode(node());\n}\n\nvoid TraditionalView::setCurrentNode(NodeID nid)\n{\n    user_data_.setSelectedNode(nid);\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::setAndCenterNode(NodeID nid)\n{\n    setCurrentNode(nid);\n    centerNode(nid);\n}\n\nbool TraditionalView::updateLayout()\n{\n    const auto changed = layout_computer_->compute();\n    layout_stale_ = false;\n\n    return changed;\n}\n\nvoid TraditionalView::setLayoutOutdated()\n{\n    layout_stale_ = true;\n}\n\nvoid TraditionalView::dirtyUp(NodeID nid)\n{\n    layout_computer_->dirtyUpLater(nid);\n}\n\nvoid TraditionalView::dirtyCurrentNodeUp()\n{\n    const auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    dirtyUp(nid);\n}\n\nvoid TraditionalView::printNodeInfo()\n{\n    const auto nid = node();\n    if (nid == NodeID::NoNode)\n        return;\n\n    print(\"--- Node Info: {} ----\", nid);\n    print(\"offset: {}\", layout_->getOffset(nid));\n    auto bb = layout_->getBoundingBox(nid);\n    print(\"bb:[{},{}]\", bb.left, bb.right);\n    print(\"dirty: {}\", layout_->isDirty(nid));\n    print(\"layout done for node: {}\", layout_->getLayoutDone(nid));\n    print(\"hidden: {}\", vis_flags_->isHidden(nid));\n    print(\"total kids: {}, \", tree_.childrenCount(nid));\n    print(\"has solved kids: {}, \", tree_.hasSolvedChildren(nid));\n    print(\"has open kids: {}\", tree_.hasOpenChildren(nid));\n\n    const auto &ng = tree_.getNogood(nid);\n\n    if (ng.has_renamed())\n    {\n        print(\"nogood: {} ({})\", ng.renamed(), ng.original());\n    }\n    else\n    {\n        print(\"nogood: {}\", ng.original());\n    }\n    print(\"alt: {}\", tree_.getAlternative(nid));\n}\n\n/// Show specified subtrees and hide everyting else\nstatic void showSubtrees(const NodeTree &tree, VisualFlags &vf, LayoutComputer &lc)\n{\n    auto root = tree.getRoot();\n\n    HideNotHighlightedCursor hnhc(root, tree, vf, lc);\n    PostorderNodeVisitor<HideNotHighlightedCursor>(hnhc).run();\n}\n\nclass TreeHighlighter : public QThread\n{\n\n    const NodeTree &tree_;\n    VisualFlags &vf_;\n    Layout &layout_;\n    LayoutComputer &lc_;\n\n  public:\n    TreeHighlighter(const NodeTree &tree, VisualFlags &vf, Layout &lo, LayoutComputer &lc)\n        : tree_(tree), vf_(vf), layout_(lo), lc_(lc) {}\n\n    void run() override\n    {\n        utils::DebugMutexLocker t_locker(&tree_.treeMutex());\n        utils::DebugMutexLocker l_locker(&layout_.getMutex());\n\n        auto root = tree_.getRoot();\n\n        HideNotHighlightedCursor hnhc(root, tree_, vf_, lc_);\n        PostorderNodeVisitor<HideNotHighlightedCursor>(hnhc).run();\n    }\n};\n\nvoid TraditionalView::revealNode(NodeID n)\n{\n    utils::DebugMutexLocker t_locker(&tree_.treeMutex());\n    utils::DebugMutexLocker l_locker(&layout_->getMutex());\n\n    layout_computer_->dirtyUpUnconditional(n);\n\n    while (n != NodeID::NoNode)\n    {\n        setNodeHidden(n, false);\n        n = tree_.getParent(n);\n    }\n\n    emit needsLayoutUpdate();\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::highlightSubtrees(const std::vector<NodeID> &nodes, bool hide_rest, bool show_outline)\n{\n    vis_flags_->unhighlightAll();\n\n    detail::PerformanceHelper phelper;\n\n    for (auto nid : nodes)\n    {\n        vis_flags_->setHighlighted(nid, true);\n    }\n\n    if (hide_rest)\n    {\n        unhideAll();\n\n        auto root = tree_.getRoot();\n        HideNotHighlightedCursor hnhc(root, tree_, *vis_flags_, *layout_computer_);\n        PostorderNodeVisitor<HideNotHighlightedCursor>(hnhc).run();\n\n        emit needsLayoutUpdate();\n    }\n\n    /// Note: this duplication could probably be avoided\n    if (!show_outline)\n    {\n        for (auto nid : nodes)\n        {\n            vis_flags_->setHighlighted(nid, false);\n        }\n    }\n\n    emit needsRedrawing();\n}\n\n/// Lantern Tree Visualisation\nvoid TraditionalView::hideBySize(int size_limit)\n{\n    utils::DebugMutexLocker tree_lock(&tree_.treeMutex());\n\n    unhideAll();\n\n    vis_flags_->resetLanternSizes();\n\n    const int max_lantern = 127;\n\n    const auto sizes = utils::calc_subtree_sizes(tree_);\n\n    const auto root = tree_.getRoot();\n\n    std::stack<NodeID> stack;\n\n    stack.push(root);\n\n    while (!stack.empty())\n    {\n        const auto n = stack.top();\n        stack.pop();\n\n        const auto size = sizes.at(n);\n        const auto nkids = tree_.childrenCount(n);\n\n        if (size > size_limit)\n        {\n            /// visit children\n            for (auto alt = 0u; alt < nkids; ++alt)\n            {\n                stack.push(tree_.getChild(n, alt));\n            }\n        }\n        else\n        {\n            /// turn the node into a \"lantern\"\n            if (nkids > 0)\n            {\n                vis_flags_->setHidden(n, true);\n                /// lantern size\n                auto lsize = (size * max_lantern) / size_limit;\n                vis_flags_->setLanternSize(n, lsize);\n                layout_->setLayoutDone(n, false);\n                dirtyUp(n);\n            }\n        }\n    }\n\n    emit needsLayoutUpdate();\n    emit needsRedrawing();\n\n    centerCurrentNode();\n}\n\nvoid TraditionalView::undoLanterns()\n{\n    vis_flags_->resetLanternSizes();\n}\n\nvoid TraditionalView::showNodeInfo() const\n{\n\n    if (!solver_data_.hasInfo())\n        return;\n\n    const auto cur_nid = node();\n    if (cur_nid == NodeID::NoNode)\n        return;\n\n    auto info_dialog = new QDialog;\n    auto layout = new QVBoxLayout(info_dialog);\n\n    QTextEdit* te = new QTextEdit;\n    te->append(solver_data_.getInfo(cur_nid).c_str());\n\n    layout->addWidget(te);\n\n    info_dialog->setAttribute(Qt::WA_DeleteOnClose);\n    info_dialog->show();\n}\n\nvoid TraditionalView::showNogoods() const\n{\n\n    if (!solver_data_.hasNogoods())\n        return;\n\n    const auto cur_nid = node();\n    if (cur_nid == NodeID::NoNode)\n        return;\n\n    const auto nodes = utils::nodes_below(tree_, cur_nid);\n\n    auto ng_dialog = new NogoodDialog(tree_, nodes);\n    ng_dialog->setAttribute(Qt::WA_DeleteOnClose);\n\n    connect(ng_dialog, &NogoodDialog::nogoodClicked, [this](NodeID nid) {\n        const_cast<TraditionalView *>(this)->revealNode(nid);\n        const_cast<TraditionalView *>(this)->setAndCenterNode(nid);\n        emit nogoodsClicked({nid});\n    });\n\n    ng_dialog->show();\n}\n\nvoid TraditionalView::debugCheckLayout() const\n{\n    ///\n    const auto order = utils::any_order(tree_);\n\n    for (const auto n : order)\n    {\n        auto bb = layout_->getBoundingBox(n);\n        // print(\"bb for {} is fine\", n);\n    }\n}\n\nvoid TraditionalView::setDebugMode(bool v)\n{\n    scroll_area_->setDebugMode(true);\n    layout_computer_->setDebugMode(true);\n    emit needsLayoutUpdate();\n    emit needsRedrawing();\n}\n\nvoid TraditionalView::setDarkMode(bool d)\n{\n    scroll_area_->setDarkMode(d);\n    emit needsRedrawing();\n}\n\n} // namespace tree\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/traditional_view.hh",
    "content": "#ifndef CPPROFILER_TREE_TRADITIONAL_VIEW_HH\n#define CPPROFILER_TREE_TRADITIONAL_VIEW_HH\n\n#include <QWidget>\n\n#include <memory>\n#include <set>\n#include \"node_id.hh\"\n#include \"visual_flags.hh\"\n\nnamespace cpprofiler\n{\nclass UserData;\nclass SolverData;\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass Layout;\nclass LayoutComputer;\nclass NodeTree;\nclass NodeID;\nclass Structure;\n\nclass TreeScrollArea;\n\nclass TraditionalView : public QObject\n{\n    Q_OBJECT\n\n    /// Tree structure\n    const NodeTree &tree_;\n\n    /// User data (bookmarks etc.)\n    UserData &user_data_;\n\n    /// Nogoods, solver ids\n    SolverData &solver_data_;\n\n    /// Visual flags (hidden/highlighted etc) per node\n    std::unique_ptr<VisualFlags> vis_flags_;\n\n    /// Layout: shapes of every node\n    std::unique_ptr<Layout> layout_;\n\n    /// Responsible for keeping the layout up to date\n    std::unique_ptr<LayoutComputer> layout_computer_;\n\n    /// The area the tree is actually drawn onto\n    std::unique_ptr<TreeScrollArea> scroll_area_;\n\n    /// Whether node ids should be shown instead of true labels (for debugging)\n    bool nid_shown_ = false;\n\n    /// Only update layout if it is stale\n    bool layout_stale_ = true;\n\n    /// Sets nid as the currently selected node\n    void setNode(NodeID nid);\n\n    /// Set the node as hidden/shown based on `val`\n    void setNodeHidden(NodeID nid, bool val);\n\n  public:\n    TraditionalView(const NodeTree &tree, UserData &ud, SolverData &sd);\n    ~TraditionalView();\n\n    /// Returns currently selected node; can be NodeID::NoNode\n    NodeID node() const;\n\n    /// Return the scroll area\n    QWidget *widget();\n\n    /// Show the label for node `nid` causing layout update\n    void setLabelShown(NodeID nid, bool val);\n\n    /// Exposes layout info (i.e. shapes needed for shape analysis)\n    const Layout &layout() const;\n\n    /// Collapse/uncollapse a pentagon node based on its current state\n    void toggleCollapsePentagon(NodeID nid);\n\n    /// Set `nid` and its predecessors as requiring re-layout\n    void dirtyUp(NodeID nid);\n\n  signals:\n    /// Triggers a redraw that updates scrollarea's viewport (perhaps a direct call would suffice)\n    void needsRedrawing();\n\n    /// Triggers unconditional update of the layout\n    void needsLayoutUpdate();\n\n    /// Notify all views to change their current nodes to `n`\n    void nodeSelected(NodeID n);\n\n    void nogoodsClicked(std::vector<NodeID>) const;\n\n  public slots:\n\n    /// Update scrollarea's viewport\n    void redraw();\n\n    /// Updates the layout if it is stale (triggered by timer)\n    void autoUpdate();\n\n    /// Handle double-click on a node\n    void handleDoubleClick();\n\n    /// Change zoom level\n    void setScale(int scale);\n\n    /// Center node `nid`\n    void centerNode(NodeID nid);\n\n    /// Center currently selected node\n    void centerCurrentNode();\n\n    /// Set current node to nid\n    void setCurrentNode(NodeID nid);\n\n    /// Set and center node `nid`\n    void setAndCenterNode(NodeID nid);\n\n    /// ***** NAVIGATION BEGIN *****\n    /// Navigation to a node sets the node as\n    /// selected and centers it in the canvas\n\n    /// Navigate to the root\n    void navRoot();\n    /// Navigate to the parent of the current node\n    void navUp();\n    /// Navigate to the first child of the current node\n    void navDown();\n    /// Navigate to the last child of the current node\n    void navDownAlt();\n    /// Navigate to the next sibling on the left of the current node\n    void navLeft();\n    /// Navigate to the next sibling on the right of the current node\n    void navRight();\n\n    /// ***** NAVIGATION END *****\n\n    /// Show/hide labels for the current node\n    void toggleShowLabel();\n\n    /// Show labels for every node below current\n    void showLabelsDown();\n\n    /// Show labels for every node on the path form root to current\n    void showLabelsUp();\n\n    /// Hides node `n`; immediately updates layout if delayed is false\n    void hideNode(NodeID n, bool delayed = true);\n\n    /// Set current node as not hidden\n    void unhideNode(NodeID nid);\n\n    /// Make sure the node is not hidden behind collapsed subtrees\n    void revealNode(NodeID nid);\n\n    /// Mark a node and attach a note to it\n    void bookmarkCurrentNode();\n\n    /// Hide failed subtrees under node `nid`\n    void hideFailedAt(NodeID nid, bool onlyDirty = false);\n\n    /// Hide all failed descendants of the current node\n    void hideFailed(bool onlyDirty = false);\n\n    /// Toggle hide/unhide current node\n    void toggleHidden();\n\n    /// Unhide all nodes in the tree\n    void unhideAll();\n\n    /// Unhide all descendants of the specified node\n    void unhideAllAt(NodeID n);\n\n    /// Unhide all descendants of the current node\n    void unhideAllAtCurrent();\n\n    /// Highlight/unhighlight subtree\n    void toggleHighlighted();\n\n    /// Unconditionally update layout (ignoring if it is \"stale\")\n    /// returns false if no change was required\n    bool updateLayout();\n\n    /// Set layout as stale\n    void setLayoutOutdated();\n\n    /// Print node info for debugging\n    void printNodeInfo();\n\n    /// For debugging: set all nodes on the path from the root to the current node as dirty\n    void dirtyCurrentNodeUp();\n\n    /// Show nogoods of the nodes under the current node and the node itself\n    void showNogoods() const;\n\n    /// Show node info of the current node\n    void showNodeInfo() const;\n\n    /// Highlight the subtrees (if show_outline is true) and hide the rest (if hide_rest is true)\n    void highlightSubtrees(const std::vector<NodeID> &nodes, bool hide_rest, bool show_outline = true);\n\n    /// Transform the tree in to a lantern tree using `limit` as max lantern size\n    void hideBySize(int limit);\n\n    /// Reset lantern sizes for nodes\n    void undoLanterns();\n\n    /// Check shapes and bounding boxes of all nodes\n    void debugCheckLayout() const;\n\n    /// Whether labels on nodes are true labels or NodeIDs\n    void setDebugMode(bool v);\n\n    /// Set dark mode\n    void setDarkMode(bool d);\n};\n\n} // namespace tree\n} // namespace cpprofiler\n\n#endif\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/tree_scroll_area.cpp",
    "content": "#include \"tree_scroll_area.hh\"\n\n#include <QPainter>\n#include <QScrollBar>\n#include <QMouseEvent>\n\n#include <cmath>\n#include <stack>\n#include <queue>\n#include <thread>\n\n#include \"shape.hh\"\n#include \"node_tree.hh\"\n#include \"visual_flags.hh\"\n#include \"cursors/nodevisitor.hh\"\n#include \"cursors/drawing_cursor.hh\"\n\n#include \"../utils/perf_helper.hh\"\n#include \"../utils/utils.hh\"\n#include \"../config.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nconstexpr int y_margin = 20;\n\nstatic void drawGrid(QPainter &painter, QSize size)\n{\n\n    const int cell_w = 100;\n    const int cell_h = 100;\n\n    {\n        QPen pen = painter.pen();\n        pen.setWidth(1);\n        pen.setStyle(Qt::DotLine);\n        painter.setPen(pen);\n    }\n\n    const auto columns = std::ceil(static_cast<float>(size.width()) / cell_w);\n    const auto rows = std::ceil(static_cast<float>(size.height()) / cell_h);\n\n    for (auto row = 0; row < rows; ++row)\n    {\n        for (auto col = 0; col < columns; ++col)\n        {\n            auto x = col * cell_w;\n            auto y = row * cell_h;\n            painter.drawRect(x, y, cell_w, cell_w);\n        }\n    }\n}\n\nvoid TreeScrollArea::paintEvent(QPaintEvent *event)\n{\n\n    QPainter painter(this->viewport());\n\n    painter.setRenderHint(QPainter::Antialiasing);\n\n    if (m_start_node == NodeID::NoNode)\n    {\n        return;\n    }\n\n    painter.scale(m_options.scale, m_options.scale);\n\n    if (!m_layout.ready(m_start_node) || !m_layout.getLayoutDone(m_start_node))\n    {\n        return;\n    }\n\n    auto bb = m_layout.getBoundingBox(m_start_node);\n\n    auto tree_width = bb.right - bb.left;\n\n    auto tree_height = m_layout.getHeight(m_start_node) * layout::dist_y;\n\n    auto viewport_size = viewport()->size();\n\n    auto h_page_step = viewport_size.width() / m_options.scale;\n    auto v_page_step = viewport_size.height() / m_options.scale;\n\n    /// set range in real pixels\n    horizontalScrollBar()->setRange(0, tree_width - h_page_step);\n    horizontalScrollBar()->setPageStep(h_page_step);\n\n    verticalScrollBar()->setRange(0, tree_height + y_margin / m_options.scale - v_page_step);\n    verticalScrollBar()->setPageStep(v_page_step);\n\n    horizontalScrollBar()->setSingleStep(layout::min_dist_x);\n    verticalScrollBar()->setSingleStep(layout::dist_y);\n\n    {\n        QPen pen = painter.pen();\n        pen.setWidth(2);\n        painter.setPen(pen);\n    }\n\n    auto x_off = horizontalScrollBar()->value();\n    auto y_off = verticalScrollBar()->value();\n    painter.translate(-x_off, -y_off);\n    /// TODO: this need to aquire layout mutex\n\n    auto half_w = viewport()->width() / 2;\n    auto half_h = viewport()->height() / 2;\n\n    auto displayed_width = static_cast<int>(viewport()->size().width() / m_options.scale);\n    auto displayed_height = static_cast<int>(viewport()->size().height() / m_options.scale);\n\n    // auto start_x = m_options.x_off + displayed_width / 2;\n\n    if (displayed_width > tree_width)\n    {\n        m_options.root_x = (displayed_width - tree_width) / 2 - bb.left;\n    }\n    else\n    {\n        m_options.root_x = -bb.left;\n    }\n\n    m_options.root_y = y_margin / m_options.scale;\n    auto start_pos = QPoint{m_options.root_x, m_options.root_y};\n\n    const int clip_margin = 0 / m_options.scale;\n\n    QRect clip{QPoint{x_off + clip_margin, y_off + clip_margin},\n               QSize{displayed_width - 2 * clip_margin, displayed_height - 2 * clip_margin}};\n\n    painter.drawRect(clip);\n\n    // drawGrid(painter, {std::max(tree_width, displayed_width), std::max(tree_height, displayed_height)});\n\n    utils::MutexLocker tree_locker(&m_tree.treeMutex());\n\n    DrawingCursor dc(m_start_node, m_tree, m_layout, user_data_, m_vis_flags, painter, start_pos, clip, debug_mode_, dark_mode_);\n    PreorderNodeVisitor<DrawingCursor>(dc).run();\n}\n\nQPoint TreeScrollArea::getNodeCoordinate(NodeID nid)\n{\n    auto node = nid;\n    auto pos = QPoint{m_options.root_x, m_options.root_y};\n\n    while (node != m_tree.getRoot())\n    {\n        pos += {static_cast<int>(m_layout.getOffset(node)), layout::dist_y};\n        node = m_tree.getParent(node);\n    }\n\n    return pos;\n}\n\nstatic std::pair<int, int> getRealBB(NodeID nid, const NodeTree &tree, const Layout &layout, const DisplayState &ds)\n{\n\n    auto bb = layout.getBoundingBox(nid);\n\n    auto node = nid;\n    while (node != tree.getRoot())\n    {\n        bb.left += layout.getOffset(node);\n        bb.right += layout.getOffset(node);\n        node = tree.getParent(node);\n    }\n\n    bb.left += ds.root_x;\n    bb.right += ds.root_x;\n\n    return {bb.left, bb.right};\n}\n\nvoid TreeScrollArea::setScale(int val)\n{\n    m_options.scale = val / 50.0f;\n    viewport()->update();\n}\n\n/// Make sure the layout for nodes is done\nNodeID TreeScrollArea::findNodeClicked(int x, int y)\n{\n    utils::MutexLocker tree_lock(&m_tree.treeMutex());\n    utils::MutexLocker layout_lock(&m_layout.getMutex());\n\n    using namespace traditional;\n\n    /// TODO: disable while constructing\n    /// calculate real x and y\n    auto x_off = horizontalScrollBar()->value();\n    auto y_off = verticalScrollBar()->value();\n\n    x = x / m_options.scale + x_off;\n    y = y / m_options.scale + y_off;\n\n    std::queue<NodeID> queue;\n\n    auto root = m_tree.getRoot();\n\n    if (root == NodeID::NoNode)\n    {\n        return NodeID::NoNode;\n    }\n\n    queue.push(root);\n\n    while (!queue.empty())\n    {\n\n        auto node = queue.front();\n        queue.pop();\n        auto node_pos = getNodeCoordinate(node);\n\n        const auto hidden = m_vis_flags.isHidden(node);\n\n        QRect node_area;\n        if (hidden)\n        {\n            auto node_pos_tl = node_pos - QPoint{HALF_COLLAPSED_WIDTH, 0};\n            node_area = QRect(node_pos_tl, QSize{COLLAPSED_WIDTH, COLLAPSED_DEPTH});\n        }\n        else\n        {\n            auto node_pos_tl = node_pos - QPoint{MAX_NODE_W / 2, 0};\n            node_area = QRect(node_pos_tl, QSize{MAX_NODE_W, MAX_NODE_W});\n        }\n\n        if (node_area.contains(x, y))\n        {\n            return node;\n        }\n        else if (!hidden)\n        {\n\n            auto kids = m_tree.childrenCount(node);\n\n            for (auto i = 0; i < kids; ++i)\n            {\n                auto kid = m_tree.getChild(node, i);\n\n                /// the kid does not have bounding box yet\n                if (!m_layout.getLayoutDone(kid))\n                    continue;\n\n                auto pair = getRealBB(kid, m_tree, m_layout, m_options);\n                if (pair.first <= x && pair.second >= x)\n                {\n                    queue.push(kid);\n                }\n            }\n        }\n    }\n\n    return NodeID::NoNode;\n}\n\nvoid TreeScrollArea::mousePressEvent(QMouseEvent *me)\n{\n    auto n = findNodeClicked(me->x(), me->y());\n    if (n != NodeID::NoNode)\n    {\n        emit nodeClicked(n);\n    }\n}\n\nvoid TreeScrollArea::mouseDoubleClickEvent(QMouseEvent *me)\n{\n    auto n = findNodeClicked(me->x(), me->y());\n    if (n != NodeID::NoNode)\n    {\n        emit nodeDoubleClicked(n);\n    }\n}\n\nTreeScrollArea::TreeScrollArea(NodeID start, const NodeTree &tree, const UserData &user_data, const Layout &layout, const VisualFlags &nf)\n    : m_start_node(start), m_tree(tree), user_data_(user_data), m_layout(layout), m_vis_flags(nf)\n{\n    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);\n    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);\n}\n\nvoid TreeScrollArea::centerPoint(int x, int y)\n{\n    const auto viewport_size = viewport()->size();\n    const auto h_page_step = viewport_size.width() / m_options.scale;\n    const auto v_page_step = viewport_size.height() / m_options.scale;\n\n    const auto value_x = std::max(0, static_cast<int>(x - h_page_step / 2));\n    horizontalScrollBar()->setValue(value_x);\n\n    const auto value_y = std::max(0, static_cast<int>(y - v_page_step / 2));\n    verticalScrollBar()->setValue(value_y);\n}\n\nvoid TreeScrollArea::changeStartNode(NodeID nid)\n{\n    m_start_node = nid;\n}\n\nvoid TreeScrollArea::setDarkMode(bool d)\n{\n    dark_mode_ = d;\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/tree_scroll_area.hh",
    "content": "#pragma once\n\n#include <QAbstractScrollArea>\n\n#include \"../core.hh\"\n\nnamespace cpprofiler\n{\nclass UserData;\n}\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass NodeTree;\nclass Layout;\nclass VisualFlags;\n\nstruct DisplayState\n{\n    float scale;\n\n    int root_x = 0;\n    int root_y = 0;\n};\n\nclass TreeScrollArea : public QAbstractScrollArea\n{\n    Q_OBJECT\n    const NodeTree &m_tree;\n\n    /// Current node in the traditional view\n    const UserData &user_data_;\n    const Layout &m_layout;\n\n    DisplayState m_options;\n    const VisualFlags &m_vis_flags;\n\n    NodeID m_start_node;\n\n    bool debug_mode_ = false;\n    bool dark_mode_ = false;\n\n    QPoint getNodeCoordinate(NodeID nid);\n    NodeID findNodeClicked(int x, int y);\n\n    void paintEvent(QPaintEvent *e) override;\n    void mousePressEvent(QMouseEvent *event) override;\n    void mouseDoubleClickEvent(QMouseEvent *event) override;\n\n  signals:\n    void nodeClicked(NodeID nid);\n    void nodeDoubleClicked(NodeID nid);\n\n  public:\n    TreeScrollArea(NodeID start,\n                   const NodeTree &,\n                   const UserData &user_data,\n                   const Layout &,\n                   const VisualFlags &);\n\n    /// center the x coordinate\n    void centerPoint(int x, int y);\n\n    void setDebugMode(bool val) { debug_mode_ = val; }\n\n    void setScale(int val);\n\n    void changeStartNode(NodeID nid);\n    \n    void setDarkMode(bool d);\n};\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/visual_flags.cpp",
    "content": "#include \"visual_flags.hh\"\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nvoid VisualFlags::ensure_id_exists(NodeID nid)\n{\n    const auto id = static_cast<int>(nid);\n    if (label_shown_.size() < id + 1)\n    {\n        label_shown_.resize(id + 1);\n        node_hidden_.resize(id + 1);\n        shape_highlighted_.resize(id + 1);\n    }\n}\n\nvoid VisualFlags::setLabelShown(NodeID nid, bool val)\n{\n    ensure_id_exists(nid);\n    label_shown_[nid] = val;\n}\n\nbool VisualFlags::isLabelShown(NodeID nid) const\n{\n\n    if (label_shown_.size() <= nid)\n        return false;\n    return label_shown_.at(nid);\n}\n\nvoid VisualFlags::setHidden(NodeID nid, bool val)\n{\n    ensure_id_exists(nid);\n    node_hidden_[nid] = val;\n\n    if (val)\n    {\n        hidden_nodes_.insert(nid);\n    }\n    else\n    {\n        hidden_nodes_.erase(nid);\n    }\n}\n\nbool VisualFlags::isHidden(NodeID nid) const\n{\n    if (node_hidden_.size() <= nid)\n        return false;\n    return node_hidden_.at(nid);\n}\n\nvoid VisualFlags::unhideAll()\n{\n\n    for (auto &&n : node_hidden_)\n    {\n        n = false;\n    }\n\n    hidden_nodes_.clear();\n}\n\nint VisualFlags::hiddenCount()\n{\n    return hidden_nodes_.size();\n}\n\nvoid VisualFlags::setHighlighted(NodeID nid, bool val)\n{\n    ensure_id_exists(nid);\n\n    if (val)\n    {\n        highlighted_shapes_.insert(nid);\n    }\n    else\n    {\n        highlighted_shapes_.erase(nid);\n    }\n    shape_highlighted_[nid] = val;\n}\n\nbool VisualFlags::isHighlighted(NodeID nid) const\n{\n\n    if (shape_highlighted_.size() <= nid)\n    {\n        return false;\n    }\n    return shape_highlighted_[nid];\n}\n\nvoid VisualFlags::unhighlightAll()\n{\n    for (auto nid : highlighted_shapes_)\n    {\n        shape_highlighted_[nid] = false;\n    }\n\n    highlighted_shapes_.clear();\n}\n\nvoid VisualFlags::resetLanternSizes()\n{\n    lantern_sizes_.clear();\n}\n\nvoid VisualFlags::setLanternSize(NodeID nid, int val)\n{\n    lantern_sizes_.insert({nid, val});\n}\n\nint VisualFlags::lanternSize(NodeID nid) const\n{\n    const auto it = lantern_sizes_.find(nid);\n    if (it != lantern_sizes_.end())\n    {\n        return it->second;\n    }\n    else\n    {\n        return -1; /// for non-lantern nodes\n    }\n}\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree/visual_flags.hh",
    "content": "#pragma once\n\n#include \"../core.hh\"\n#include <set>\n#include <map>\n\nnamespace cpprofiler\n{\nnamespace tree\n{\n\nclass VisualFlags\n{\n\n    std::vector<bool> label_shown_;\n\n    std::vector<bool> node_hidden_;\n\n    std::vector<bool> shape_highlighted_;\n\n    /// Somewhat redundant given m_shape_highlighted, but it is\n    /// more efficient for unhighlighting previously highlighted subtrees\n    std::set<NodeID> highlighted_shapes_;\n\n    /// A set providing quick access to all hidden nodes (redundant)\n    std::set<NodeID> hidden_nodes_;\n\n    std::map<NodeID, int> lantern_sizes_;\n\n    void ensure_id_exists(NodeID id);\n\n  public:\n    void setLabelShown(NodeID nid, bool val);\n    bool isLabelShown(NodeID nid) const;\n\n    void setHidden(NodeID nid, bool val);\n    bool isHidden(NodeID nid) const;\n\n    /// set all nodes to not be hidden (without traversing the tree)\n    void unhideAll();\n\n    /// Return the number of hidden nodes\n    int hiddenCount();\n\n    const std::set<NodeID> &hidden_nodes() { return hidden_nodes_; }\n\n    void setHighlighted(NodeID nid, bool val);\n    bool isHighlighted(NodeID nid) const;\n\n    /// Remove all map entries about lantern sizes\n    void resetLanternSizes();\n    /// Insert a map entry for `nid` to hold `val` as its lantern size\n    void setLanternSize(NodeID nid, int val);\n    /// Get lantern size for `nid`; return 0 if no entry found (not a lantern)\n    int lanternSize(NodeID nid) const;\n\n    void unhighlightAll();\n};\n\n} // namespace tree\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree_builder.cpp",
    "content": "#include \"tree_builder.hh\"\n\n#include \"message_wrapper.hh\"\n\n#include \"utils/perf_helper.hh\"\n#include \"utils/debug.hh\"\n#include \"execution.hh\"\n\n#include \"tree/node_tree.hh\"\n#include \"name_map.hh\"\n\n#include <thread>\n\nnamespace cpprofiler\n{\n\nstatic std::ostream &operator<<(std::ostream &os, const NodeUID &uid)\n{\n    return os << \"{\" << uid.nid << \", \" << uid.rid << \", \" << uid.tid << \"}\";\n}\n\nstatic std::ostream &operator<<(std::ostream &os, const NodeStatus &status)\n{\n    switch (status)\n    {\n    case SOLVED:\n        os << \"SOLVED\";\n        break;\n    case FAILED:\n        os << \"FAILED\";\n        break;\n    case BRANCH:\n        os << \"BRANCH\";\n        break;\n    case SKIPPED:\n        os << \"SKIPPED\";\n        break;\n    }\n    return os;\n}\n\n/// works correctly for node messages only atm\nstatic std::ostream &operator<<(std::ostream &os, const Message &msg)\n{\n    os << \"nid: \" << msg.nodeUID() << \", pid: \" << msg.parentUID();\n    os << \", alt: \" << msg.alt() << \", kids: \" << msg.kids();\n    os << \", \" << msg.status();\n    // if (msg.has_label()) os << \", label: \" << msg.label();\n    // if (msg.has_nogood()) os << \", nogood: \" << msg.nogood();\n    // if (msg.has_info()) os << \", info: \" << msg.info();\n    return os;\n}\n\nTreeBuilder::TreeBuilder(Execution &ex) : m_execution(ex)\n{\n    std::cerr << \"  TreeBuilder()\\n\";\n    startBuilding();\n}\n\nvoid TreeBuilder::startBuilding()\n{\n    perfHelper.begin(\"tree building\");\n    print(\"Builder: start building\");\n}\n\nvoid TreeBuilder::finishBuilding()\n{\n    perfHelper.end();\n    print(\"Builder: done building\");\n    emit buildingDone();\n}\n\nvoid TreeBuilder::handleNode(const MessageWrapper& node)\n{\n    // print(\"node: {}\", *node);\n    auto& msg = node.msg();\n\n    const auto n_uid = msg.nodeUID();\n    const auto p_uid = msg.parentUID();\n\n    auto &tree = m_execution.tree();\n\n    tree::NodeID pid = tree::NodeID::NoNode;\n\n    if (p_uid.nid != -1)\n    {\n        /// should solver data be moved to node tree?\n        pid = m_execution.solver_data().getNodeId({p_uid.nid, p_uid.rid, p_uid.tid});\n    }\n\n    const auto kids = msg.kids();\n    const auto alt = msg.alt();\n    const auto status = static_cast<tree::NodeStatus>(msg.status());\n    const auto &label = msg.has_label() ? msg.label() : tree::emptyLabel;\n\n    NodeID nid;\n\n    {\n        utils::MutexLocker tree_lock(&tree.treeMutex(), \"builder\");\n\n        if (pid == NodeID::NoNode)\n        {\n\n            if (m_execution.doesRestarts())\n            {\n                tree.addExtraChild(NodeID{0});\n                nid = tree.promoteNode(NodeID{0}, restart_count++, kids, status, label);\n            }\n            else\n            {\n                nid = tree.createRoot(kids);\n            }\n        }\n        else\n        {\n            nid = tree.promoteNode(pid, alt, kids, status, label);\n        }\n    }\n\n    m_execution.solver_data().setNodeId({n_uid.nid, n_uid.rid, n_uid.tid}, nid);\n\n    if (msg.has_nogood())\n    {\n        const auto nm = m_execution.nameMap();\n\n        if (nm)\n        {\n            /// Construct a renamed nogood using the name map\n            const auto renamed = m_execution.nameMap()->replaceNames(msg.nogood());\n            m_execution.solver_data().setNogood(nid, msg.nogood(), renamed);\n        }\n        else\n        {\n            m_execution.solver_data().setNogood(nid, msg.nogood());\n        }\n    }\n\n    if (msg.has_info() && !msg.info().empty())\n    {\n        m_execution.solver_data().processInfo(nid, msg.info());\n    }\n}\n\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/tree_builder.hh",
    "content": "#pragma once\n\n#include \"message_wrapper.hh\"\n#include <QObject>\n\nnamespace cpprofiler\n{\n\nclass Execution;\n\nclass TreeBuilder : public QObject\n{\n    Q_OBJECT\n\n    Execution &m_execution;\n\n    /// Need to keep track of the number of restarts;\n    /// cannot rely on solvers to send correct restart id\n    /// (e.g. Chuffed doesn't do that)\n    int restart_count = 0;\n\n  public:\n    TreeBuilder(Execution &ex);\n\n    void startBuilding();\n\n    void finishBuilding();\n\n    void handleNode(const cpprofiler::MessageWrapper& node);\n\n  signals:\n\n    void buildingDone();\n};\n\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/user_data.cpp",
    "content": "#include \"user_data.hh\"\n\nnamespace cpprofiler\n{\n\nvoid UserData::setSelectedNode(tree::NodeID nid)\n{\n    m_selected_node = nid;\n}\n\ntree::NodeID UserData::getSelectedNode() const\n{\n    return m_selected_node;\n}\n\nvoid UserData::setBookmark(tree::NodeID nid, const std::string &text)\n{\n    bookmarks_.insert({nid, text});\n}\n\nconst std::string &UserData::getBookmark(tree::NodeID nid) const\n{\n    return bookmarks_.at(nid);\n}\n\nbool UserData::isBookmarked(tree::NodeID nid) const\n{\n    return bookmarks_.find(nid) != bookmarks_.end();\n}\n\nvoid UserData::clearBookmark(NodeID nid)\n{\n    bookmarks_.erase(nid);\n}\n\nstd::vector<tree::NodeID> UserData::bookmarkedNodes() const\n{\n\n    std::vector<tree::NodeID> res;\n    for (const auto &item : bookmarks_)\n    {\n        res.push_back(item.first);\n    }\n\n    return res;\n}\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/user_data.hh",
    "content": "#ifndef CPPROFILER_USER_DATA_HH\n#define CPPROFILER_USER_DATA_HH\n\n#include \"tree/node_id.hh\"\n#include \"core.hh\"\n#include <unordered_map>\n#include <string>\n#include <vector>\n\nnamespace cpprofiler\n{\n\n/// TODO: move this to Execution\nclass UserData\n{\n\n    tree::NodeID m_selected_node = tree::NodeID::NoNode;\n\n    std::unordered_map<NodeID, std::string> bookmarks_;\n\n  public:\n    void setSelectedNode(tree::NodeID nid);\n\n    /// returns currently selected node (possibly NodeID::NoNode)\n    tree::NodeID getSelectedNode() const;\n\n    void setBookmark(tree::NodeID nid, const std::string &text);\n\n    void clearBookmark(NodeID nid);\n\n    const std::string &getBookmark(tree::NodeID nid) const;\n\n    bool isBookmarked(tree::NodeID nid) const;\n\n    std::vector<tree::NodeID> bookmarkedNodes() const;\n};\n\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/array.cpp",
    "content": "#include \"array.hh\"\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n}\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/array.hh",
    "content": "#ifndef CPPROFILER_UTILS_ARRAY_HH\n#define CPPROFILER_UTILS_ARRAY_HH\n\n#include <stdlib.h>\n#include <initializer_list>\n#include <algorithm>\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\ntemplate <typename T>\nclass Array\n{\n\n    int m_size;\n    T *m_data;\n\n  public:\n    /// Creates an uninitialized array of size `size`\n    Array(int size);\n    Array(std::initializer_list<T> init_list);\n    Array();\n    ~Array();\n\n    Array(const Array &other);\n    Array &operator=(const Array &other);\n\n    Array(Array &&other);\n\n    T &operator[](int pos);\n\n    const T &operator[](int pos) const;\n\n    int size() const;\n};\n\ntemplate <typename T>\nArray<T> &Array<T>::operator=(const Array<T> &other)\n{\n    free(m_data);\n    m_size = other.m_size;\n    m_data = static_cast<T *>(malloc(m_size * sizeof(T)));\n\n    for (auto i = 0; i < m_size; ++i)\n    {\n        m_data[i] = other[i];\n    }\n\n    return *this;\n}\n\ntemplate <typename T>\nArray<T>::Array(int size) : m_size(size)\n{\n    m_data = static_cast<T *>(malloc(size * sizeof(T)));\n}\n\ntemplate <typename T>\nArray<T>::Array(std::initializer_list<T> init_list)\n    : m_size(init_list.size())\n{\n    m_data = static_cast<T *>(malloc(m_size * sizeof(T)));\n\n    int i = 0;\n    for (auto &&el : init_list)\n    {\n        m_data[i] = el;\n        ++i;\n    }\n}\n\ntemplate <typename T>\nArray<T>::Array() : m_size(0)\n{\n}\n\ntemplate <typename T>\nArray<T>::Array(const Array &other) : m_size(other.size())\n{\n    m_data = static_cast<T *>(malloc(m_size * sizeof(T)));\n\n    for (auto i = 0; i < m_size; ++i)\n    {\n        m_data[i] = other[i];\n    }\n}\n\ntemplate <typename T>\nArray<T>::Array(Array &&other) : m_size(other.size())\n{\n    m_data = other.m_data;\n    other.m_size = 0;\n    other.m_data = nullptr;\n}\n\ntemplate <typename T>\nArray<T>::~Array()\n{\n    free(m_data);\n}\n\ntemplate <typename T>\nT &Array<T>::operator[](int pos)\n{\n    return m_data[pos];\n}\n\ntemplate <typename T>\nconst T &Array<T>::operator[](int pos) const\n{\n    return m_data[pos];\n}\n\ntemplate <typename T>\nint Array<T>::size() const\n{\n    return m_size;\n}\n\n} // namespace utils\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/debug.hh",
    "content": "#pragma once\n\n#include <iostream>\n#include <vector>\n#include <QString>\n#include <QDebug>\n#include <sstream>\n\nnamespace cpprofiler\n{\n\ninline std::ostream &operator<<(std::ostream &os, const QString &str)\n{\n    return os << str.toStdString();\n}\n\ntemplate <typename T>\nstd::string to_string(const std::vector<T> &vec)\n{\n    if (vec.size() == 0)\n    {\n        return \"[]\";\n    }\n\n    std::ostringstream oss;\n    oss << \"[\";\n    for (auto i = 0u; i < vec.size() - 1; ++i)\n    {\n        oss << vec[i] << \",\";\n    }\n    oss << vec.back() << \"]\";\n    return oss.str();\n}\n\ntemplate <typename T>\nstd::ostream &operator<<(std::ostream &os, const std::vector<T> &vec)\n{\n    return os << to_string(vec);\n}\n\ninline std::string format(const char *temp)\n{\n    return temp;\n}\n\ntemplate <typename T, typename... Types>\nstd::string format(const char *temp, T value, Types... args)\n{\n    std::ostringstream oss;\n    for (; *temp != '\\0'; ++temp)\n    {\n\n        if (*temp == '{')\n        {\n            oss << value;\n            oss << format(temp + 2, args...);\n            break;\n        }\n        oss << *temp;\n    }\n\n    return oss.str();\n}\n\ntemplate <typename... Types>\nvoid print(const char *temp, Types... args)\n{\n    std::cerr << format(temp, args...) << std::endl;\n}\n\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/debug_mutex.hh",
    "content": "\n#ifndef CPPROFILER_UTILS_DEBUG_MUTEX_HH\n#define CPPROFILER_UTILS_DEBUG_MUTEX_HH\n\n#if QT_VERSION >= 0x051400\n#include <QRecursiveMutex>\n#else\n#include <QMutex>\n#endif\n#include <QDebug>\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\n#if QT_VERSION >= 0x051400\ntypedef QRecursiveMutex MyMutex;\n#else\ntypedef QMutex MyMutex;\n#endif\nclass DebugMutex : public MyMutex\n{\n  public:\n#if QT_VERSION >= 0x051400\n    DebugMutex() {}\n#else\n    DebugMutex() : QMutex(QMutex::Recursive) {}\n#endif\n\n    void lock(std::string msg)\n    {\n\n        // qDebug() << \"(\" << msg.c_str() << \") lock\";\n\n        MyMutex::lock();\n    }\n\n    bool tryLock()\n    {\n\n        // qDebug() << \"try lock\";\n        return MyMutex::tryLock();\n    }\n\n    void unlock(std::string msg)\n    {\n        // qDebug() << \"(\" << msg.c_str() << \") unlock\";\n        MyMutex::unlock();\n    }\n};\n\nclass DebugMutexLocker\n{\n\n    DebugMutex *m_mutex;\n\n    std::string m_msg;\n\n  public:\n    DebugMutexLocker(DebugMutex *m, std::string msg = \"\") : m_mutex(m), m_msg(msg)\n    {\n        m_mutex->lock(m_msg);\n    }\n\n    ~DebugMutexLocker()\n    {\n        m_mutex->unlock(m_msg);\n    }\n};\n\n// using Mutex = QMutex;\n// using MutexLocker = QMutexLocker;\nusing Mutex = utils::DebugMutex;\nusing MutexLocker = utils::DebugMutexLocker;\n\n} // namespace utils\n} // namespace cpprofiler\n\n#endif\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/maybe_caller.cpp",
    "content": "#include \"maybe_caller.hh\"\n#include <iostream>\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\nMaybeCaller::MaybeCaller(int min_ms) : min_elapsed_(min_ms)\n{\n    updateTimer.setSingleShot(true);\n    connect(&updateTimer, &QTimer::timeout, this, &MaybeCaller::callViaTimer);\n    last_call_time = std::chrono::system_clock::now();\n}\n\nvoid MaybeCaller::call(std::function<void(void)> fn)\n{\n    using namespace std::chrono;\n\n    auto now = system_clock::now();\n    auto elapsed = duration_cast<milliseconds>(now - last_call_time).count();\n\n    if (elapsed > min_elapsed_)\n    {\n        fn();\n        last_call_time = system_clock::now();\n    }\n    else\n    {\n        /// call delayed\n        delayed_fn = fn;\n        updateTimer.start(min_elapsed_);\n    }\n}\n\nvoid MaybeCaller::callViaTimer() { delayed_fn(); }\n\n} // namespace utils\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/maybe_caller.hh",
    "content": "#pragma once\n\n#include <functional>\n#include <chrono>\n#include <QTimer>\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\nclass MaybeCaller : public QObject\n{\n  Q_OBJECT\nprivate:\n  int min_elapsed_; /// milliseconds\n\n  std::chrono::system_clock::time_point last_call_time;\n\n  QTimer updateTimer;\n\n  std::function<void(void)> delayed_fn;\n\nprivate Q_SLOTS:\n\n  void callViaTimer();\n\npublic:\n  /// the minimum duration between calls\n  MaybeCaller(int min_ms);\n  void call(std::function<void(void)> fn);\n};\n\n} // namespace utils\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/path_utils.cpp",
    "content": "#include \"path_utils.hh\"\n#include \"string_utils.hh\"\n\nnamespace cpprofiler\n{\n\nnamespace utils {\n\n    using std::vector;\n    using std::string;\n\n    PathPair getPathPair(const string& path, bool omitDecomp) {\n\n        PathPair ph;\n        if(path.empty()) return ph;\n\n        vector<string> path_split = utils::split(path, major_sep);\n\n        string mzn_file;\n        size_t idx = 0;\n        for (; idx < path_split.size(); ++idx) {\n            const auto& path_head = path_split[idx];\n\n            const auto head = utils::split(path_head, minor_sep);\n\n            string head_file = head[0];\n            if (idx == 0) mzn_file = head[0];\n\n            if (head_file != mzn_file) break;\n\n            ph.model_level.push_back(path_head);\n        }\n\n        if (!omitDecomp) {\n            for (; idx < path_split.size(); ++idx) {\n                ph.decomp_level.push_back(path_split[idx]);\n            }\n        }\n\n        return ph;\n    }\n\n}\n}"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/path_utils.hh",
    "content": "#pragma once\n\n#include <vector>\n#include <string>\n\n\nnamespace cpprofiler\n{\n\nnamespace utils {\n\nstatic constexpr char minor_sep = '|';\nstatic constexpr char major_sep = ';';\n\nstruct PathPair {\n    std::vector<std::string> model_level;\n    std::vector<std::string> decomp_level;\n};\n\nPathPair getPathPair(const std::string& path, bool omitDecomp = false);\n\n}\n}"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/perf_helper.cpp",
    "content": "#include \"perf_helper.hh\"\n\ndetail::PerformanceHelper perfHelper;"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/perf_helper.hh",
    "content": "#ifndef CPPROFILER_UTILS_PERF_HELPER_HH\n#define CPPROFILER_UTILS_PERF_HELPER_HH\n\n#include <iostream>\n#include <chrono>\n\n#include <unordered_map>\n\n#include \"debug.hh\"\n\nnamespace detail\n{\nusing namespace std::chrono;\n\nstruct PerfInstance\n{\n    using TimePoint = time_point<high_resolution_clock>;\n    long long current = 0;\n    TimePoint begin;\n};\n\nclass PerformanceHelper\n{\n    using TimePoint = time_point<high_resolution_clock>;\n\n  private:\n    high_resolution_clock m_hrClock;\n    TimePoint m_begin;\n    const char *m_message;\n\n    std::unordered_map<const char *, PerfInstance> maps;\n\n  public:\n    void begin(const char *msg)\n    {\n        m_message = msg;\n        m_begin = m_hrClock.now();\n    }\n\n    void accumulate(const char *msg)\n    {\n        maps[msg].begin = m_hrClock.now();\n    }\n\n    void end(const char *msg)\n    {\n        auto now = m_hrClock.now();\n        auto duration_ns =\n            duration_cast<nanoseconds>(now - maps[msg].begin).count();\n        maps[msg].current += duration_ns;\n        maps[msg].begin = now;\n    }\n\n    void total(const char *msg)\n    {\n        auto dur = maps[msg].current;\n        std::cout << \"Duration(\" << msg << \"): \" << dur / 1000000 << \"ms\"\n                  << \" (\" << dur << \"ns)\\n\";\n    }\n\n    void end()\n    {\n        auto duration_ms =\n            duration_cast<milliseconds>(m_hrClock.now() - m_begin).count();\n        auto duration_ns =\n            duration_cast<nanoseconds>(m_hrClock.now() - m_begin).count();\n        ::cpprofiler::print(\"Duration({}): {}ms ({}ns)\", m_message, duration_ms, duration_ns);\n    }\n};\n\n} // namespace detail\n\nnamespace perf_helper\n{\n\nusing namespace std::chrono;\n\nstruct Timer\n{\n    using TimePoint = time_point<high_resolution_clock>;\n    high_resolution_clock m_hrClock;\n    TimePoint m_begin;\n\n    void begin()\n    {\n        m_begin = m_hrClock.now();\n    }\n\n    int64_t end()\n    {\n        int64_t ms =\n            duration_cast<milliseconds>(m_hrClock.now() - m_begin).count();\n        return ms;\n    }\n};\n\n} // namespace perf_helper\n\nextern detail::PerformanceHelper perfHelper;\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/std_ext.cpp",
    "content": "#include \"std_ext.hh\""
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/std_ext.hh",
    "content": "#ifndef CPPROFILER_UTILS_STD_STUBD\n#define CPPROFILER_UTILS_STD_STUBD\n\n#include <memory>\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\ntemplate <typename T, typename... Args>\nstd::unique_ptr<T> make_unique_helper(std::false_type, Args &&... args)\n{\n    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));\n}\n\ntemplate <typename T, typename... Args>\nstd::unique_ptr<T> make_unique_helper(std::true_type, Args &&... args)\n{\n    static_assert(std::extent<T>::value == 0,\n                  \"make_unique<T[N]>() is forbidden, please use make_unique<T[]>().\");\n\n    typedef typename std::remove_extent<T>::type U;\n    return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});\n}\n\ntemplate <typename T, typename... Args>\nstd::unique_ptr<T> make_unique(Args &&... args)\n{\n    return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);\n}\n\ntemplate <typename T>\nstd::unique_ptr<T> make_unique()\n{\n    return std::unique_ptr<T>(new T);\n}\n\n} // namespace utils\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/string_utils.cpp",
    "content": "#include \"string_utils.hh\"\n\n#include <sstream>\n#include <iterator>\n\nusing std::string;\nusing std::vector;\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\nvector<string> split(const string &str, char delim,\n                     bool include_empty)\n{\n\n    std::stringstream ss;\n    ss.str(str);\n    std::string item;\n\n    vector<string> result;\n\n    auto inserter = std::back_inserter(result);\n\n    while (std::getline(ss, item, delim))\n    {\n        if (!item.empty() || include_empty)\n            *(inserter++) = item;\n    }\n\n    return result;\n}\n\nstring join(const vector<string>& strs, char sep) {\n    std::stringstream ss;\n    for(size_t i=0; i<strs.size(); i++) {\n        if(i) ss << sep;\n        ss << strs[i];\n    }\n    return ss.str();\n}\n\n} // namespace utils\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/string_utils.hh",
    "content": "#include <vector>\n#include <string>\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\nstd::vector<std::string> split(const std::string &str, char delim, bool include_empty = false);\nstd::string join(const std::vector<std::string>& strs, char sep);\n}\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/tree_utils.cpp",
    "content": "#include \"tree_utils.hh\"\n#include <stack>\n#include <exception>\n#include \"../tree/node_tree.hh\"\n\nusing namespace cpprofiler::tree;\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\nint count_descendants(const NodeTree &nt, NodeID nid)\n{\n\n    /// NoNode has 0 descendants (as opposed to a leaf node, which counts itself)\n    if (nid == NodeID::NoNode)\n        return 0;\n\n    int count = 0;\n\n    auto fun = [&count](NodeID n) {\n        count++;\n    };\n\n    apply_below(nt, nid, fun);\n\n    return count;\n}\n\nint calculate_depth(const NodeTree &nt, NodeID nid)\n{\n    int depth = 0;\n    while (nid != NodeID::NoNode)\n    {\n        nid = nt.getParent(nid);\n        ++depth;\n    }\n    return depth;\n}\n\nstd::vector<NodeID> nodes_below(const tree::NodeTree &nt, NodeID nid)\n{\n\n    if (nid == NodeID::NoNode)\n    {\n        throw std::exception();\n    }\n\n    std::vector<NodeID> nodes;\n\n    std::stack<NodeID> stk;\n\n    stk.push(nid);\n\n    while (!stk.empty())\n    {\n        const auto n = stk.top();\n        stk.pop();\n        nodes.push_back(n);\n\n        const auto kids = nt.childrenCount(n);\n        for (auto alt = 0; alt < kids; ++alt)\n        {\n            stk.push(nt.getChild(n, alt));\n        }\n    }\n\n    return nodes;\n}\n\nvoid apply_below(const NodeTree &nt, NodeID nid, const NodeAction &action)\n{\n\n    if (nid == NodeID::NoNode)\n    {\n        throw std::exception();\n    }\n\n    auto nodes = nodes_below(nt, nid);\n\n    for (auto n : nodes)\n    {\n        action(n);\n    }\n}\n\nvoid pre_order_apply(const tree::NodeTree &nt, NodeID start, const NodeAction &action)\n{\n    std::stack<NodeID> stk;\n\n    stk.push(start);\n\n    while (stk.size() > 0)\n    {\n        auto nid = stk.top();\n        stk.pop();\n\n        action(nid);\n\n        for (auto i = nt.childrenCount(nid) - 1; i >= 0; --i)\n        {\n            auto child = nt.getChild(nid, i);\n            stk.push(child);\n        }\n    }\n}\n\nbool is_right_most_child(const tree::NodeTree &nt, NodeID nid)\n{\n    const auto pid = nt.getParent(nid);\n\n    /// root is treated as the left-most child\n    if (pid == NodeID::NoNode)\n        return false;\n\n    const auto kids = nt.childrenCount(pid);\n    const auto alt = nt.getAlternative(nid);\n    return (alt == kids - 1);\n}\n\nstd::vector<NodeID> pre_order(const NodeTree &tree)\n{\n    std::stack<NodeID> stk;\n    std::vector<NodeID> result;\n\n    NodeID root = NodeID{0};\n\n    stk.push(root);\n\n    while (stk.size() > 0)\n    {\n        auto nid = stk.top();\n        stk.pop();\n        result.push_back(nid);\n\n        for (auto i = tree.childrenCount(nid) - 1; i >= 0; --i)\n        {\n            auto child = tree.getChild(nid, i);\n            stk.push(child);\n        }\n    }\n\n    return result;\n}\n\nstd::vector<NodeID> any_order(const NodeTree &tree)\n{\n\n    auto count = tree.nodeCount();\n    std::vector<NodeID> result;\n    result.reserve(count);\n\n    for (auto i = 0; i < count; ++i)\n    {\n        result.push_back(NodeID(i));\n    }\n\n    return result;\n}\n\nstd::vector<NodeID> post_order(const NodeTree &tree)\n{\n    std::stack<NodeID> stk_1;\n    std::vector<NodeID> result;\n\n    result.reserve(tree.nodeCount());\n\n    auto root = tree.getRoot();\n\n    stk_1.push(root);\n\n    while (!stk_1.empty())\n    {\n        auto nid = stk_1.top();\n        stk_1.pop();\n\n        result.push_back(nid);\n\n        for (auto i = 0; i < tree.childrenCount(nid); ++i)\n        {\n            auto child = tree.getChild(nid, i);\n            stk_1.push(child);\n        }\n    }\n\n    std::reverse(result.begin(), result.end());\n\n    return result;\n}\n\nstd::vector<int> calc_subtree_sizes(const tree::NodeTree &nt)\n{\n    const int nc = nt.nodeCount();\n\n    std::vector<int> sizes(nc);\n\n    std::function<void(NodeID)> countDescendants;\n\n    /// Count descendants plus one (the node itself)\n    countDescendants = [&](NodeID n) {\n        auto nkids = nt.childrenCount(n);\n        if (nkids == 0)\n        {\n            sizes[n] = 1;\n        }\n        else\n        {\n            int count = 1; // the node itself\n            for (auto alt = 0u; alt < nkids; ++alt)\n            {\n                const auto kid = nt.getChild(n, alt);\n                countDescendants(kid);\n                count += sizes[kid];\n            }\n            sizes[n] = count;\n        }\n    };\n\n    const auto root = nt.getRoot();\n    countDescendants(root);\n\n    return sizes;\n}\n\n} // namespace utils\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/tree_utils.hh",
    "content": "#pragma once\n\n#include \"../core.hh\"\n#include <functional>\n\nusing NodeAction = std::function<void(NodeID)>;\n\nnamespace cpprofiler\n{\nnamespace tree\n{\nclass NodeTree;\n}\n} // namespace cpprofiler\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\n/// Count all descendants of `n`\nint count_descendants(const tree::NodeTree &nt, NodeID n);\n\n/// Compute the node's depth (the distance to the root)\nint calculate_depth(const tree::NodeTree &nt, NodeID nid);\n\n/// Apply `action` to `root` and to all of its descendants\nvoid apply_below(const tree::NodeTree &nt, NodeID root, const NodeAction &action);\n\n/// Apply `action` to nodes in the order that corresponds to a pre-order traversal\nvoid pre_order_apply(const tree::NodeTree &nt, NodeID start, const NodeAction &action);\n\n/// Inquire if the node is the right-most child\nbool is_right_most_child(const tree::NodeTree &nt, NodeID nid);\n\n/// Return a list of nodes under `nid` (including `nid`)\nstd::vector<NodeID> nodes_below(const tree::NodeTree &tree, NodeID nid);\n\n/// Return node identifires in an arbitrary order\nstd::vector<NodeID> any_order(const tree::NodeTree &tree);\n\n/// Return node identifires in the order that corresponds to a pre-order traversal\nstd::vector<NodeID> pre_order(const tree::NodeTree &tree);\n\n/// Return node identifires in the order that corresponds to a post-order traversal\nstd::vector<NodeID> post_order(const tree::NodeTree &tree);\n\n/// Calculate subtree sizes for every node in the tree\nstd::vector<int> calc_subtree_sizes(const tree::NodeTree &tree);\n\n} // namespace utils\n} // namespace cpprofiler"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/utils.cpp",
    "content": "#include \"utils.hh\"\n\n#include <thread>\n#include <chrono>\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\nvoid sleep_for_ms(int ms)\n{\n    std::this_thread::sleep_for(std::chrono::milliseconds(ms));\n}\n\n} // namespace utils\n} // namespace cpprofiler\n"
  },
  {
    "path": "cp-profiler/src/cpprofiler/utils/utils.hh",
    "content": "#ifndef CPPROFILER_UTILS_HH\n#define CPPROFILER_UTILS_HH\n\n#include <memory>\n#include <ostream>\n#include <unordered_map>\n\nnamespace cpprofiler\n{\nnamespace utils\n{\n\ntemplate <typename K, typename T>\nvoid print(std::ostream &os, const std::unordered_map<K, T> &map)\n{\n    for (auto &k : map)\n    {\n        os << k.first << \"\\t\" << k.second << \"\\n\";\n    }\n}\n\nvoid sleep_for_ms(int ms);\n\n} // namespace utils\n} // namespace cpprofiler\n\n#endif"
  },
  {
    "path": "cp-profiler/src/main_cpprofiler.cpp",
    "content": "#include <iostream>\n\n#include <QApplication>\n\n#include \"cpprofiler/command_line_parser.hh\"\n#include \"cpprofiler/conductor.hh\"\n#include \"cpprofiler/options.hh\"\n\n#include \"cpprofiler/tests/tree_test.hh\"\n#include \"cpprofiler/tests/execution_test.hh\"\n#include \"cpprofiler/utils/debug.hh\"\n\nint main(int argc, char *argv[])\n{\n\n    using namespace cpprofiler;\n\n#ifdef QT_OPENGL_SUPPORT\n    QGL::setPreferredPaintEngine(QPaintEngine::OpenGL);\n#endif\n\n    QApplication app(argc, argv);\n    QCoreApplication::setApplicationName(\"CP-Profiler\");\n\n    CommandLineParser cl_parser;\n    cl_parser.process(app);\n\n    Options options;\n\n    {\n        if (cl_parser.isSet(cl_options::paths))\n        {\n            options.paths = cl_parser.value(cl_options::paths).toStdString();\n            print(\"selected paths file: {}\", options.paths);\n        }\n\n        if (cl_parser.isSet(cl_options::mzn))\n        {\n            options.mzn = cl_parser.value(cl_options::mzn).toStdString();\n            print(\"selected mzn file: {}\", options.mzn);\n        }\n    }\n\n    if (cl_parser.isSet(cl_options::save_search))\n    {\n        const auto path = cl_parser.value(cl_options::save_search).toStdString();\n        options.save_search_path = path;\n    }\n\n    if (cl_parser.isSet(cl_options::save_execution))\n    {\n        const auto path = cl_parser.value(cl_options::save_execution).toStdString();\n        options.save_execution_db = path;\n    }\n\n    if(cl_parser.isSet(cl_options::save_pixel_tree)) {\n        const auto path = cl_parser.value(cl_options::save_pixel_tree).toStdString();\n        options.save_pixel_tree_path = path;\n    }\n\n    if(cl_parser.isSet(cl_options::pixel_tree_compression)) {\n        QString cs = cl_parser.value(cl_options::pixel_tree_compression);\n        options.pixel_tree_compression = cs.toInt();\n    }\n\n    Conductor conductor(std::move(options));\n\n    conductor.show();\n\n    tests::execution::run(conductor);\n\n    return app.exec();\n}\n\n/// Threads\n// Main thread\n// Receiver thread\n"
  },
  {
    "path": "default.nix",
    "content": "{ self, lib, stdenv, qt6, minizinc, makeWrapper, Cocoa }:\n\nstdenv.mkDerivation rec {\n  name = \"minizinc-ide\";\n\n  src = self;\n  nativeBuildInputs = [ qt6.qmake makeWrapper ];\n  buildInputs = [ qt6.qtbase qt6.qtwebsockets ] ++ lib.optionals stdenv.isDarwin [ Cocoa ];\n  dontWrapQtApps = true;\n\n  executableLoc = if stdenv.isDarwin then \"$out/bin/MiniZincIDE.app/Contents/MacOS/MiniZincIDE\" else \"$out/bin/MiniZincIDE\";\n  postInstall = ''\n    wrapProgram ${executableLoc} --prefix PATH \":\" ${lib.makeBinPath [ minizinc ]} --set QT_QPA_PLATFORM_PLUGIN_PATH \"${lib.makeBinPath [ qt6.qtbase ]}/../lib/qt-6/plugins/platforms\"\n  '';\n\n  meta = with lib; {\n    homepage = \"https://www.minizinc.org/\";\n    description = \"IDE for MiniZinc, a medium-level constraint modelling language\";\n    longDescription = ''\n      MiniZinc is a medium-level constraint modelling\n      language. It is high-level enough to express most\n      constraint problems easily, but low-level enough\n      that it can be mapped onto existing solvers easily and consistently.\n      It is a subset of the higher-level language Zinc.\n    '';\n    license = licenses.mpl20;\n    platforms = platforms.unix;\n  };\n}\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"IDE for MiniZinc, a medium-level constraint modelling language\";\n\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixos-unstable\";\n    flake-utils.url = \"github:numtide/flake-utils\";\n  };\n\n  outputs = { self, nixpkgs, flake-utils }:\n    flake-utils.lib.eachDefaultSystem (system:\n      let\n        pkgs = nixpkgs.legacyPackages.${system};\n      in\n      {\n        packages = {\n          minizinc-ide = pkgs.callPackage ./default.nix { inherit self; inherit (pkgs.darwin.apple_sdk.frameworks) Cocoa; };\n          default = self.packages.${system}.minizinc-ide;\n        };\n\n        devShells.default = pkgs.mkShell {\n          name = \"minizinc-ide\";\n\n          packages = with pkgs; [\n            qt6.qtbase\n            qt6.qtwebsockets\n            qt6.qmake\n            minizinc\n          ] ++ lib.optionals stdenv.isDarwin [ darwin.apple_sdk.frameworks.Cocoa ];\n        };\n      }\n    );\n}\n"
  },
  {
    "path": "resources/README.md",
    "content": "# Packaging Resources\n\nThis folder contains packaging resources for the MiniZinc bundle distributions.\nThese files are used in the continuous integration of the MiniZinc tool chain\nand are not used in the compilation of the MiniZinc IDE. Resources in this\nfolder are packaged depending on the targeted platform and are not available on\nall platforms.\n"
  },
  {
    "path": "resources/misc/COMBINED_LICENSE.txt",
    "content": "The MiniZinc IDE is copyright NICTA and Monash University.\r\nIt is distributed under the Mozilla Public License Version 2.0 (see below).\r\nThe MiniZinc IDE uses the Qt toolkit, which is distributed under the LGPL license (see below).\r\n\r\nMozilla Public License Version 2.0\r\n==================================\r\n\r\n1. Definitions\r\n--------------\r\n\r\n1.1. \"Contributor\"\r\n    means each individual or legal entity that creates, contributes to\r\n    the creation of, or owns Covered Software.\r\n\r\n1.2. \"Contributor Version\"\r\n    means the combination of the Contributions of others (if any) used\r\n    by a Contributor and that particular Contributor's Contribution.\r\n\r\n1.3. \"Contribution\"\r\n    means Covered Software of a particular Contributor.\r\n\r\n1.4. \"Covered Software\"\r\n    means Source Code Form to which the initial Contributor has attached\r\n    the notice in Exhibit A, the Executable Form of such Source Code\r\n    Form, and Modifications of such Source Code Form, in each case\r\n    including portions thereof.\r\n\r\n1.5. \"Incompatible With Secondary Licenses\"\r\n    means\r\n\r\n    (a) that the initial Contributor has attached the notice described\r\n        in Exhibit B to the Covered Software; or\r\n\r\n    (b) that the Covered Software was made available under the terms of\r\n        version 1.1 or earlier of the License, but not also under the\r\n        terms of a Secondary License.\r\n\r\n1.6. \"Executable Form\"\r\n    means any form of the work other than Source Code Form.\r\n\r\n1.7. \"Larger Work\"\r\n    means a work that combines Covered Software with other material, in \r\n    a separate file or files, that is not Covered Software.\r\n\r\n1.8. \"License\"\r\n    means this document.\r\n\r\n1.9. \"Licensable\"\r\n    means having the right to grant, to the maximum extent possible,\r\n    whether at the time of the initial grant or subsequently, any and\r\n    all of the rights conveyed by this License.\r\n\r\n1.10. \"Modifications\"\r\n    means any of the following:\r\n\r\n    (a) any file in Source Code Form that results from an addition to,\r\n        deletion from, or modification of the contents of Covered\r\n        Software; or\r\n\r\n    (b) any new file in Source Code Form that contains any Covered\r\n        Software.\r\n\r\n1.11. \"Patent Claims\" of a Contributor\r\n    means any patent claim(s), including without limitation, method,\r\n    process, and apparatus claims, in any patent Licensable by such\r\n    Contributor that would be infringed, but for the grant of the\r\n    License, by the making, using, selling, offering for sale, having\r\n    made, import, or transfer of either its Contributions or its\r\n    Contributor Version.\r\n\r\n1.12. \"Secondary License\"\r\n    means either the GNU General Public License, Version 2.0, the GNU\r\n    Lesser General Public License, Version 2.1, the GNU Affero General\r\n    Public License, Version 3.0, or any later versions of those\r\n    licenses.\r\n\r\n1.13. \"Source Code Form\"\r\n    means the form of the work preferred for making modifications.\r\n\r\n1.14. \"You\" (or \"Your\")\r\n    means an individual or a legal entity exercising rights under this\r\n    License. For legal entities, \"You\" includes any entity that\r\n    controls, is controlled by, or is under common control with You. For\r\n    purposes of this definition, \"control\" means (a) the power, direct\r\n    or indirect, to cause the direction or management of such entity,\r\n    whether by contract or otherwise, or (b) ownership of more than\r\n    fifty percent (50%) of the outstanding shares or beneficial\r\n    ownership of such entity.\r\n\r\n2. License Grants and Conditions\r\n--------------------------------\r\n\r\n2.1. Grants\r\n\r\nEach Contributor hereby grants You a world-wide, royalty-free,\r\nnon-exclusive license:\r\n\r\n(a) under intellectual property rights (other than patent or trademark)\r\n    Licensable by such Contributor to use, reproduce, make available,\r\n    modify, display, perform, distribute, and otherwise exploit its\r\n    Contributions, either on an unmodified basis, with Modifications, or\r\n    as part of a Larger Work; and\r\n\r\n(b) under Patent Claims of such Contributor to make, use, sell, offer\r\n    for sale, have made, import, and otherwise transfer either its\r\n    Contributions or its Contributor Version.\r\n\r\n2.2. Effective Date\r\n\r\nThe licenses granted in Section 2.1 with respect to any Contribution\r\nbecome effective for each Contribution on the date the Contributor first\r\ndistributes such Contribution.\r\n\r\n2.3. Limitations on Grant Scope\r\n\r\nThe licenses granted in this Section 2 are the only rights granted under\r\nthis License. No additional rights or licenses will be implied from the\r\ndistribution or licensing of Covered Software under this License.\r\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\r\nContributor:\r\n\r\n(a) for any code that a Contributor has removed from Covered Software;\r\n    or\r\n\r\n(b) for infringements caused by: (i) Your and any other third party's\r\n    modifications of Covered Software, or (ii) the combination of its\r\n    Contributions with other software (except as part of its Contributor\r\n    Version); or\r\n\r\n(c) under Patent Claims infringed by Covered Software in the absence of\r\n    its Contributions.\r\n\r\nThis License does not grant any rights in the trademarks, service marks,\r\nor logos of any Contributor (except as may be necessary to comply with\r\nthe notice requirements in Section 3.4).\r\n\r\n2.4. Subsequent Licenses\r\n\r\nNo Contributor makes additional grants as a result of Your choice to\r\ndistribute the Covered Software under a subsequent version of this\r\nLicense (see Section 10.2) or under the terms of a Secondary License (if\r\npermitted under the terms of Section 3.3).\r\n\r\n2.5. Representation\r\n\r\nEach Contributor represents that the Contributor believes its\r\nContributions are its original creation(s) or it has sufficient rights\r\nto grant the rights to its Contributions conveyed by this License.\r\n\r\n2.6. Fair Use\r\n\r\nThis License is not intended to limit any rights You have under\r\napplicable copyright doctrines of fair use, fair dealing, or other\r\nequivalents.\r\n\r\n2.7. Conditions\r\n\r\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\r\nin Section 2.1.\r\n\r\n3. Responsibilities\r\n-------------------\r\n\r\n3.1. Distribution of Source Form\r\n\r\nAll distribution of Covered Software in Source Code Form, including any\r\nModifications that You create or to which You contribute, must be under\r\nthe terms of this License. You must inform recipients that the Source\r\nCode Form of the Covered Software is governed by the terms of this\r\nLicense, and how they can obtain a copy of this License. You may not\r\nattempt to alter or restrict the recipients' rights in the Source Code\r\nForm.\r\n\r\n3.2. Distribution of Executable Form\r\n\r\nIf You distribute Covered Software in Executable Form then:\r\n\r\n(a) such Covered Software must also be made available in Source Code\r\n    Form, as described in Section 3.1, and You must inform recipients of\r\n    the Executable Form how they can obtain a copy of such Source Code\r\n    Form by reasonable means in a timely manner, at a charge no more\r\n    than the cost of distribution to the recipient; and\r\n\r\n(b) You may distribute such Executable Form under the terms of this\r\n    License, or sublicense it under different terms, provided that the\r\n    license for the Executable Form does not attempt to limit or alter\r\n    the recipients' rights in the Source Code Form under this License.\r\n\r\n3.3. Distribution of a Larger Work\r\n\r\nYou may create and distribute a Larger Work under terms of Your choice,\r\nprovided that You also comply with the requirements of this License for\r\nthe Covered Software. If the Larger Work is a combination of Covered\r\nSoftware with a work governed by one or more Secondary Licenses, and the\r\nCovered Software is not Incompatible With Secondary Licenses, this\r\nLicense permits You to additionally distribute such Covered Software\r\nunder the terms of such Secondary License(s), so that the recipient of\r\nthe Larger Work may, at their option, further distribute the Covered\r\nSoftware under the terms of either this License or such Secondary\r\nLicense(s).\r\n\r\n3.4. Notices\r\n\r\nYou may not remove or alter the substance of any license notices\r\n(including copyright notices, patent notices, disclaimers of warranty,\r\nor limitations of liability) contained within the Source Code Form of\r\nthe Covered Software, except that You may alter any license notices to\r\nthe extent required to remedy known factual inaccuracies.\r\n\r\n3.5. Application of Additional Terms\r\n\r\nYou may choose to offer, and to charge a fee for, warranty, support,\r\nindemnity or liability obligations to one or more recipients of Covered\r\nSoftware. However, You may do so only on Your own behalf, and not on\r\nbehalf of any Contributor. You must make it absolutely clear that any\r\nsuch warranty, support, indemnity, or liability obligation is offered by\r\nYou alone, and You hereby agree to indemnify every Contributor for any\r\nliability incurred by such Contributor as a result of warranty, support,\r\nindemnity or liability terms You offer. You may include additional\r\ndisclaimers of warranty and limitations of liability specific to any\r\njurisdiction.\r\n\r\n4. Inability to Comply Due to Statute or Regulation\r\n---------------------------------------------------\r\n\r\nIf it is impossible for You to comply with any of the terms of this\r\nLicense with respect to some or all of the Covered Software due to\r\nstatute, judicial order, or regulation then You must: (a) comply with\r\nthe terms of this License to the maximum extent possible; and (b)\r\ndescribe the limitations and the code they affect. Such description must\r\nbe placed in a text file included with all distributions of the Covered\r\nSoftware under this License. Except to the extent prohibited by statute\r\nor regulation, such description must be sufficiently detailed for a\r\nrecipient of ordinary skill to be able to understand it.\r\n\r\n5. Termination\r\n--------------\r\n\r\n5.1. The rights granted under this License will terminate automatically\r\nif You fail to comply with any of its terms. However, if You become\r\ncompliant, then the rights granted under this License from a particular\r\nContributor are reinstated (a) provisionally, unless and until such\r\nContributor explicitly and finally terminates Your grants, and (b) on an\r\nongoing basis, if such Contributor fails to notify You of the\r\nnon-compliance by some reasonable means prior to 60 days after You have\r\ncome back into compliance. Moreover, Your grants from a particular\r\nContributor are reinstated on an ongoing basis if such Contributor\r\nnotifies You of the non-compliance by some reasonable means, this is the\r\nfirst time You have received notice of non-compliance with this License\r\nfrom such Contributor, and You become compliant prior to 30 days after\r\nYour receipt of the notice.\r\n\r\n5.2. If You initiate litigation against any entity by asserting a patent\r\ninfringement claim (excluding declaratory judgment actions,\r\ncounter-claims, and cross-claims) alleging that a Contributor Version\r\ndirectly or indirectly infringes any patent, then the rights granted to\r\nYou by any and all Contributors for the Covered Software under Section\r\n2.1 of this License shall terminate.\r\n\r\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\r\nend user license agreements (excluding distributors and resellers) which\r\nhave been validly granted by You or Your distributors under this License\r\nprior to termination shall survive termination.\r\n\r\n************************************************************************\r\n*                                                                      *\r\n*  6. Disclaimer of Warranty                                           *\r\n*  -------------------------                                           *\r\n*                                                                      *\r\n*  Covered Software is provided under this License on an \"as is\"       *\r\n*  basis, without warranty of any kind, either expressed, implied, or  *\r\n*  statutory, including, without limitation, warranties that the       *\r\n*  Covered Software is free of defects, merchantable, fit for a        *\r\n*  particular purpose or non-infringing. The entire risk as to the     *\r\n*  quality and performance of the Covered Software is with You.        *\r\n*  Should any Covered Software prove defective in any respect, You     *\r\n*  (not any Contributor) assume the cost of any necessary servicing,   *\r\n*  repair, or correction. This disclaimer of warranty constitutes an   *\r\n*  essential part of this License. No use of any Covered Software is   *\r\n*  authorized under this License except under this disclaimer.         *\r\n*                                                                      *\r\n************************************************************************\r\n\r\n************************************************************************\r\n*                                                                      *\r\n*  7. Limitation of Liability                                          *\r\n*  --------------------------                                          *\r\n*                                                                      *\r\n*  Under no circumstances and under no legal theory, whether tort      *\r\n*  (including negligence), contract, or otherwise, shall any           *\r\n*  Contributor, or anyone who distributes Covered Software as          *\r\n*  permitted above, be liable to You for any direct, indirect,         *\r\n*  special, incidental, or consequential damages of any character      *\r\n*  including, without limitation, damages for lost profits, loss of    *\r\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\r\n*  and all other commercial damages or losses, even if such party      *\r\n*  shall have been informed of the possibility of such damages. This   *\r\n*  limitation of liability shall not apply to liability for death or   *\r\n*  personal injury resulting from such party's negligence to the       *\r\n*  extent applicable law prohibits such limitation. Some               *\r\n*  jurisdictions do not allow the exclusion or limitation of           *\r\n*  incidental or consequential damages, so this exclusion and          *\r\n*  limitation may not apply to You.                                    *\r\n*                                                                      *\r\n************************************************************************\r\n\r\n8. Litigation\r\n-------------\r\n\r\nAny litigation relating to this License may be brought only in the\r\ncourts of a jurisdiction where the defendant maintains its principal\r\nplace of business and such litigation shall be governed by laws of that\r\njurisdiction, without reference to its conflict-of-law provisions.\r\nNothing in this Section shall prevent a party's ability to bring\r\ncross-claims or counter-claims.\r\n\r\n9. Miscellaneous\r\n----------------\r\n\r\nThis License represents the complete agreement concerning the subject\r\nmatter hereof. If any provision of this License is held to be\r\nunenforceable, such provision shall be reformed only to the extent\r\nnecessary to make it enforceable. Any law or regulation which provides\r\nthat the language of a contract shall be construed against the drafter\r\nshall not be used to construe this License against a Contributor.\r\n\r\n10. Versions of the License\r\n---------------------------\r\n\r\n10.1. New Versions\r\n\r\nMozilla Foundation is the license steward. Except as provided in Section\r\n10.3, no one other than the license steward has the right to modify or\r\npublish new versions of this License. Each version will be given a\r\ndistinguishing version number.\r\n\r\n10.2. Effect of New Versions\r\n\r\nYou may distribute the Covered Software under the terms of the version\r\nof the License under which You originally received the Covered Software,\r\nor under the terms of any subsequent version published by the license\r\nsteward.\r\n\r\n10.3. Modified Versions\r\n\r\nIf you create software not governed by this License, and you want to\r\ncreate a new license for such software, you may create and use a\r\nmodified version of this License if you rename the license and remove\r\nany references to the name of the license steward (except to note that\r\nsuch modified license differs from this License).\r\n\r\n10.4. Distributing Source Code Form that is Incompatible With Secondary\r\nLicenses\r\n\r\nIf You choose to distribute Source Code Form that is Incompatible With\r\nSecondary Licenses under the terms of this version of the License, the\r\nnotice described in Exhibit B of this License must be attached.\r\n\r\nExhibit A - Source Code Form License Notice\r\n-------------------------------------------\r\n\r\n  This Source Code Form is subject to the terms of the Mozilla Public\r\n  License, v. 2.0. If a copy of the MPL was not distributed with this\r\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\r\n\r\nIf it is not possible or desirable to put the notice in a particular\r\nfile, then You may include the notice in a location (such as a LICENSE\r\nfile in a relevant directory) where a recipient would be likely to look\r\nfor such a notice.\r\n\r\nYou may add additional accurate notices of copyright ownership.\r\n\r\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\r\n---------------------------------------------------------\r\n\r\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\r\n  defined by the Mozilla Public License, v. 2.0.\r\n\r\nGNU Lesser General Public License (LGPL)\r\n========================================\r\n\r\nQt is available under the GNU Lesser General Public License version 3.\r\n\r\nThe Qt Toolkit is Copyright (C) 2018 The Qt Company Ltd. and other contributors.\r\nContact: https://www.qt.io/licensing/\r\n\r\nGNU LESSER GENERAL PUBLIC LICENSE\r\n                       Version 3, 29 June 2007\r\n\r\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\r\n Everyone is permitted to copy and distribute verbatim copies\r\n of this license document, but changing it is not allowed.\r\n\r\n  This version of the GNU Lesser General Public License incorporates\r\nthe terms and conditions of version 3 of the GNU General Public\r\nLicense, supplemented by the additional permissions listed below.\r\n\r\n  0. Additional Definitions.\r\n\r\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\r\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\r\nGeneral Public License.\r\n\r\n  \"The Library\" refers to a covered work governed by this License,\r\nother than an Application or a Combined Work as defined below.\r\n\r\n  An \"Application\" is any work that makes use of an interface provided\r\nby the Library, but which is not otherwise based on the Library.\r\nDefining a subclass of a class defined by the Library is deemed a mode\r\nof using an interface provided by the Library.\r\n\r\n  A \"Combined Work\" is a work produced by combining or linking an\r\nApplication with the Library.  The particular version of the Library\r\nwith which the Combined Work was made is also called the \"Linked\r\nVersion\".\r\n\r\n  The \"Minimal Corresponding Source\" for a Combined Work means the\r\nCorresponding Source for the Combined Work, excluding any source code\r\nfor portions of the Combined Work that, considered in isolation, are\r\nbased on the Application, and not on the Linked Version.\r\n\r\n  The \"Corresponding Application Code\" for a Combined Work means the\r\nobject code and/or source code for the Application, including any data\r\nand utility programs needed for reproducing the Combined Work from the\r\nApplication, but excluding the System Libraries of the Combined Work.\r\n\r\n  1. Exception to Section 3 of the GNU GPL.\r\n\r\n  You may convey a covered work under sections 3 and 4 of this License\r\nwithout being bound by section 3 of the GNU GPL.\r\n\r\n  2. Conveying Modified Versions.\r\n\r\n  If you modify a copy of the Library, and, in your modifications, a\r\nfacility refers to a function or data to be supplied by an Application\r\nthat uses the facility (other than as an argument passed when the\r\nfacility is invoked), then you may convey a copy of the modified\r\nversion:\r\n\r\n   a) under this License, provided that you make a good faith effort to\r\n   ensure that, in the event an Application does not supply the\r\n   function or data, the facility still operates, and performs\r\n   whatever part of its purpose remains meaningful, or\r\n\r\n   b) under the GNU GPL, with none of the additional permissions of\r\n   this License applicable to that copy.\r\n\r\n  3. Object Code Incorporating Material from Library Header Files.\r\n\r\n  The object code form of an Application may incorporate material from\r\na header file that is part of the Library.  You may convey such object\r\ncode under terms of your choice, provided that, if the incorporated\r\nmaterial is not limited to numerical parameters, data structure\r\nlayouts and accessors, or small macros, inline functions and templates\r\n(ten or fewer lines in length), you do both of the following:\r\n\r\n   a) Give prominent notice with each copy of the object code that the\r\n   Library is used in it and that the Library and its use are\r\n   covered by this License.\r\n\r\n   b) Accompany the object code with a copy of the GNU GPL and this license\r\n   document.\r\n\r\n  4. Combined Works.\r\n\r\n  You may convey a Combined Work under terms of your choice that,\r\ntaken together, effectively do not restrict modification of the\r\nportions of the Library contained in the Combined Work and reverse\r\nengineering for debugging such modifications, if you also do each of\r\nthe following:\r\n\r\n   a) Give prominent notice with each copy of the Combined Work that\r\n   the Library is used in it and that the Library and its use are\r\n   covered by this License.\r\n\r\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\r\n   document.\r\n\r\n   c) For a Combined Work that displays copyright notices during\r\n   execution, include the copyright notice for the Library among\r\n   these notices, as well as a reference directing the user to the\r\n   copies of the GNU GPL and this license document.\r\n\r\n   d) Do one of the following:\r\n\r\n       0) Convey the Minimal Corresponding Source under the terms of this\r\n       License, and the Corresponding Application Code in a form\r\n       suitable for, and under terms that permit, the user to\r\n       recombine or relink the Application with a modified version of\r\n       the Linked Version to produce a modified Combined Work, in the\r\n       manner specified by section 6 of the GNU GPL for conveying\r\n       Corresponding Source.\r\n\r\n       1) Use a suitable shared library mechanism for linking with the\r\n       Library.  A suitable mechanism is one that (a) uses at run time\r\n       a copy of the Library already present on the user's computer\r\n       system, and (b) will operate properly with a modified version\r\n       of the Library that is interface-compatible with the Linked\r\n       Version.\r\n\r\n   e) Provide Installation Information, but only if you would otherwise\r\n   be required to provide such information under section 6 of the\r\n   GNU GPL, and only to the extent that such information is\r\n   necessary to install and execute a modified version of the\r\n   Combined Work produced by recombining or relinking the\r\n   Application with a modified version of the Linked Version. (If\r\n   you use option 4d0, the Installation Information must accompany\r\n   the Minimal Corresponding Source and Corresponding Application\r\n   Code. If you use option 4d1, you must provide the Installation\r\n   Information in the manner specified by section 6 of the GNU GPL\r\n   for conveying Corresponding Source.)\r\n\r\n  5. Combined Libraries.\r\n\r\n  You may place library facilities that are a work based on the\r\nLibrary side by side in a single library together with other library\r\nfacilities that are not Applications and are not covered by this\r\nLicense, and convey such a combined library under terms of your\r\nchoice, if you do both of the following:\r\n\r\n   a) Accompany the combined library with a copy of the same work based\r\n   on the Library, uncombined with any other library facilities,\r\n   conveyed under the terms of this License.\r\n\r\n   b) Give prominent notice with the combined library that part of it\r\n   is a work based on the Library, and explaining where to find the\r\n   accompanying uncombined form of the same work.\r\n\r\n  6. Revised Versions of the GNU Lesser General Public License.\r\n\r\n  The Free Software Foundation may publish revised and/or new versions\r\nof the GNU Lesser General Public License from time to time. Such new\r\nversions will be similar in spirit to the present version, but may\r\ndiffer in detail to address new problems or concerns.\r\n\r\n  Each version is given a distinguishing version number. If the\r\nLibrary as you received it specifies that a certain numbered version\r\nof the GNU Lesser General Public License \"or any later version\"\r\napplies to it, you have the option of following the terms and\r\nconditions either of that published version or of any later version\r\npublished by the Free Software Foundation. If the Library as you\r\nreceived it does not specify a version number of the GNU Lesser\r\nGeneral Public License, you may choose any version of the GNU Lesser\r\nGeneral Public License ever published by the Free Software Foundation.\r\n\r\n  If the Library as you received it specifies that a proxy can decide\r\nwhether future versions of the GNU Lesser General Public License shall\r\napply, that proxy's public statement of acceptance of any version is\r\npermanent authorization for you to choose that version for the\r\nLibrary.\r\n"
  },
  {
    "path": "resources/misc/MiniZincIDE.desktop",
    "content": "[Desktop Entry]\nVersion=1.0\nType=Application\nTerminal=false\nExec=minizinc.ide\nName=MiniZincIDE\nIcon=${SNAP}/meta/gui/icon.png\nCategories=Science;Development;Education;\n"
  },
  {
    "path": "resources/misc/README",
    "content": "MiniZinc bundled binary distribution for Linux\n==============================================\n\nThis package contains MiniZinc binaries for Ubuntu 20.04. If you want\nto run MiniZinc on other versions of Linux that are not compatible\nwith Ubuntu 20.04, you may have to compile MiniZinc from the source\ncode which is available at http://www.minizinc.org.\n\nTo run the MiniZinc IDE, just execute MiniZincIDE.sh.\n\nTo use the MiniZinc command line tools, add this directory to your PATH.\n"
  },
  {
    "path": "resources/misc/entitlements.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.cs.disable-library-validation</key>\n\t<true/>\n</dict>\n</plist>"
  },
  {
    "path": "resources/misc/minizinc.desktop",
    "content": "[Desktop Entry]\nName=MiniZincIDE\nExec=minizinc\nIcon=minizinc\nType=Application\nCategories=Science;Development;Education;\nTerminal=false\n"
  },
  {
    "path": "resources/misc/osx-gecode-qt.conf",
    "content": "[Paths]\nPlugins = ../../PlugIns\nImports = ../../Resources/qml\nQml2Imports = ../../Resources/qml\n"
  },
  {
    "path": "resources/misc/win-gecode-qt.conf",
    "content": "[Paths]\nPlugins = ..\nImports = ..\nQml2Imports = .."
  },
  {
    "path": "resources/pkg_config/Dockerfile",
    "content": "ARG BASE=ubuntu:latest\nFROM ${BASE} AS composer\nARG TARGETPLATFORM\n\n# Install MiniZinc toolchain\nCOPY ${TARGETPLATFORM}/minizinc/bin/* /usr/local/bin/\nCOPY ${TARGETPLATFORM}/minizinc/share/minizinc /usr/local/share/minizinc\n\n# Install vendor solvers\nCOPY ${TARGETPLATFORM}/vendor/gecode /usr/local\nCOPY ${TARGETPLATFORM}/vendor/chuffed /usr/local\nCOPY ${TARGETPLATFORM}/vendor/or-tools /or-tools\nCOPY ${TARGETPLATFORM}/vendor/highs /usr/local\n\n# Strip all binaries\nRUN [ -f \"/etc/alpine-release\" ] && apk add --no-cache binutils || (apt-get update -y && apt-get install -y binutils)\nRUN cd /usr/local/bin && strip minizinc mzn2doc fzn-chuffed fzn-gecode /or-tools/bin/fzn-cp-sat\n\n# Generate resulting Docker image\nFROM ${BASE}\n\nRUN [ ! -f \"/etc/alpine-release\" ] || apk add --no-cache libstdc++\n\nCOPY --from=composer /usr/local/bin/* /usr/local/bin/\nCOPY --from=composer /usr/local/share/minizinc /usr/local/share/minizinc\nCOPY --from=composer /usr/local/lib*/libhighs.so /usr/local/lib/\nCOPY --from=composer /or-tools/bin/fzn-cp-sat /usr/local/bin/\nCOPY --from=composer /or-tools/share/minizinc /usr/local/share/minizinc\n"
  },
  {
    "path": "resources/pkg_config/minizinc-bundle.iss",
    "content": "; Script generated by the Inno Setup Script Wizard.\n; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!\n\n#define MyAppName \"MiniZinc IDE\"\n#define MyAppPublisher \"Data61 and Monash University\"\n#define MyAppURL \"http://www.minizinc.org\"\n#define MyAppExeName \"MiniZincIDE.exe\"\n\n[Setup]\n; NOTE: The value of AppId uniquely identifies this application.\n; Do not use the same AppId value in installers for other applications.\n; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)\nAppId={{80FFFA5B-5C33-442E-8C26-A8CD257EFD97}\nAppName={#MyAppName}\nAppVersion={#MyAppVersion}\n;AppVerName={#MyAppName} {#MyAppVersion}\nAppPublisher={#MyAppPublisher}\nAppPublisherURL={#MyAppURL}\nAppSupportURL={#MyAppURL}\nAppUpdatesURL={#MyAppURL}\nLicenseFile={#MyAppDirectory}\\resources\\misc\\COMBINED_LICENSE.txt\nDefaultDirName={autopf}\\MiniZinc\nDefaultGroupName={#MyAppName}\nAllowNoIcons=yes\nOutputBaseFilename=MiniZincIDE-{#MyAppVersion}-bundled-setup-{#MyAppArch}\nCompression=lzma\nSolidCompression=yes\nChangesAssociations=yes\nArchitecturesInstallIn64BitMode={#MyApp64Bit}\nArchitecturesAllowed={#MyAppArchitectures}\nDisableDirPage=no\nPrivilegesRequiredOverridesAllowed=dialog\n\n#if DoCodeSign == \"1\"\n  SignTool=MyCodeSignTool\n#endif\n\n[Languages]\nName: \"english\"; MessagesFile: \"compiler:Default.isl\"\n\n[Tasks]\nName: \"desktopicon\"; Description: \"{cm:CreateDesktopIcon}\"; GroupDescription: \"{cm:AdditionalIcons}\"; Flags: unchecked\n\n[Files]\nSource: \"{#MyAppDirectory}\\ide\\*\"; Excludes: \"{#MyAppDirectory}\\ide\\MiniZincIDE.exe\"; DestDir: \"{app}\"; Flags: ignoreversion recursesubdirs createallsubdirs\nSource: \"{#MyAppDirectory}\\ide\\MiniZincIDE.exe\"; DestDir: \"{app}\"; Flags: ignoreversion sign\n\nSource: \"{#MyAppDirectory}\\vendor\\openssl\\*.dll\"; DestDir: \"{app}\"; Flags: ignoreversion\n\nSource: \"{#MyAppDirectory}\\minizinc\\bin\\*.exe\"; DestDir: \"{app}\"; Flags: ignoreversion sign\nSource: \"{#MyAppDirectory}\\minizinc\\share\\minizinc\\*\"; DestDir: \"{app}\\share\\minizinc\"; Flags: ignoreversion recursesubdirs createallsubdirs\n\nSource: \"{#MyAppDirectory}\\vendor\\gecode_gist\\bin\\fzn-gecode.exe\"; DestDir: \"{app}\\bin\"; DestName: \"fzn-gecode.exe\"; Flags: ignoreversion sign\nSource: \"{#MyAppDirectory}\\vendor\\gecode_gist\\share\\minizinc\\*\"; DestDir: \"{app}\\share\\minizinc\\\"; Flags: ignoreversion recursesubdirs createallsubdirs\nSource: \"{#MyAppDirectory}\\resources\\misc\\win-gecode-qt.conf\"; DestDir: \"{app}\\bin\"; DestName: \"qt.conf\"; Flags: ignoreversion\n\nSource: \"{#MyAppDirectory}\\vendor\\chuffed\\bin\\fzn-chuffed.exe\"; DestDir:\"{app}\\bin\"; Flags: ignoreversion sign\nSource: \"{#MyAppDirectory}\\vendor\\chuffed\\share\\minizinc\\*\"; DestDir: \"{app}\\share\\minizinc\\\"; Flags: ignoreversion recursesubdirs createallsubdirs\n\nSource: \"{#MyAppDirectory}\\vendor\\or-tools\\bin\\fzn-cp-sat.exe\"; DestDir:\"{app}\\bin\"; Flags: ignoreversion\nSource: \"{#MyAppDirectory}\\vendor\\or-tools\\share\\minizinc\\*\"; DestDir: \"{app}\\share\\minizinc\\\"; Flags: ignoreversion recursesubdirs createallsubdirs\n\nSource: \"{#MyAppDirectory}\\vendor\\highs\\bin\\highs.dll\"; DestDir:\"{app}\\bin\"; Flags: ignoreversion\n\nSource: \"{#MyAppDirectory}\\globalizer\\bin\\minizinc-globalizer.exe\"; DestDir:\"{app}\\bin\"; Flags: ignoreversion sign\nSource: \"{#MyAppDirectory}\\globalizer\\share\\minizinc\\*\"; DestDir: \"{app}\\share\\minizinc\\\"; Flags: ignoreversion recursesubdirs createallsubdirs\n\nSource: \"{#MyAppDirectory}\\findMUS\\bin\\findMUS.exe\"; DestDir:\"{app}\\bin\"; Flags: ignoreversion sign\nSource: \"{#MyAppDirectory}\\findMUS\\share\\minizinc\\*\"; DestDir: \"{app}\\share\\minizinc\\\"; Flags: ignoreversion recursesubdirs createallsubdirs\n\nSource: \"{#MyAppDirectory}\\mzn-analyse\\bin\\mzn-analyse.exe\"; DestDir:\"{app}\\bin\"; Flags: ignoreversion sign\n\nSource: \"{#MyMSVCRedist}\\*.dll\"; DestDir: \"{app}\"; Flags: ignoreversion\nSource: \"{#MyUCRTRedist}\\*.dll\"; DestDir: \"{app}\"; Flags: ignoreversion\n; NOTE: Don't use \"Flags: ignoreversion\" on any shared system files\n\n[Icons]\nName: \"{group}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"\nName: \"{commondesktop}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"; Tasks: desktopicon\n\n[Run]\nFilename: \"{app}\\{#MyAppExeName}\"; Description: \"{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}\"; Flags: nowait postinstall skipifsilent\n\n[Registry]\nRoot: HKA; Subkey: \"Software\\Classes\\.mzp\"; ValueType: string; ValueName: \"\"; ValueData: \"MiniZincProjectFile\"; Flags: uninsdeletevalue;\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincProjectFile\"; ValueType: string; ValueName: \"\"; ValueData: \"MiniZinc project\"; Flags: uninsdeletevalue;\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincProjectFile\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\{#MyAppExeName},0\"\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincProjectFile\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\{#MyAppExeName}\"\" \"\"%1\"\"\"\n\nRoot: HKA; Subkey: \"Software\\Classes\\.mzn\"; ValueType: string; ValueName: \"\"; ValueData: \"MiniZincModelFile\"; Flags: uninsdeletevalue;\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincModelFile\"; ValueType: string; ValueName: \"\"; ValueData: \"MiniZinc model\"; Flags: uninsdeletevalue;\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincModelFile\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\{#MyAppExeName},0\"\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincModelFile\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\{#MyAppExeName}\"\" \"\"%1\"\"\"\n\nRoot: HKA; Subkey: \"Software\\Classes\\.dzn\"; ValueType: string; ValueName: \"\"; ValueData: \"MiniZincDataFile\"; Flags: uninsdeletevalue;\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincDataFile\"; ValueType: string; ValueName: \"\"; ValueData: \"MiniZinc data\"; Flags: uninsdeletevalue;\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincDataFile\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\{#MyAppExeName},0\"\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincDataFile\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\{#MyAppExeName}\"\" \"\"%1\"\"\"\n\nRoot: HKA; Subkey: \"Software\\Classes\\.fzn\"; ValueType: string; ValueName: \"\"; ValueData: \"MiniZincFlatZincFile\"; Flags: uninsdeletevalue;\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincFlatZincFile\"; ValueType: string; ValueName: \"\"; ValueData: \"FlatZinc instance\"; Flags: uninsdeletevalue;\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincFlatZincFile\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\{#MyAppExeName},0\"\nRoot: HKA; Subkey: \"Software\\Classes\\MiniZincFlatZincFile\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\{#MyAppExeName}\"\" \"\"%1\"\"\"\n\n[InstallDelete]\n; These are old files from previous MiniZincIDE installers that can be removed\nType: files; Name: \"{app}\\GecodeDriver-6-1-0-r-x64.dll\"\nType: files; Name: \"{app}\\GecodeDriver-6-1-0-r-x64.pdb\"\nType: files; Name: \"{app}\\GecodeFlatZinc-6-1-0-r-x64.dll\"\nType: files; Name: \"{app}\\GecodeFlatZinc-6-1-0-r-x64.pdb\"\nType: files; Name: \"{app}\\GecodeFloat-6-1-0-r-x64.dll\"\nType: files; Name: \"{app}\\GecodeFloat-6-1-0-r-x64.pdb\"\nType: files; Name: \"{app}\\GecodeGist-6-1-0-r-x64.dll\"\nType: files; Name: \"{app}\\GecodeGist-6-1-0-r-x64.pdb\"\nType: files; Name: \"{app}\\GecodeInt-6-1-0-r-x64.dll\"\nType: files; Name: \"{app}\\GecodeInt-6-1-0-r-x64.pdb\"\nType: files; Name: \"{app}\\GecodeKernel-6-1-0-r-x64.dll\"\nType: files; Name: \"{app}\\GecodeKernel-6-1-0-r-x64.pdb\"\nType: files; Name: \"{app}\\GecodeMinimodel-6-1-0-r-x64.dll\"\nType: files; Name: \"{app}\\GecodeMinimodel-6-1-0-r-x64.pdb\"\nType: files; Name: \"{app}\\GecodeSearch-6-1-0-r-x64.dll\"\nType: files; Name: \"{app}\\GecodeSearch-6-1-0-r-x64.pdb\"\nType: files; Name: \"{app}\\GecodeSet-6-1-0-r-x64.dll\"\nType: files; Name: \"{app}\\GecodeSet-6-1-0-r-x64.pdb\"\nType: files; Name: \"{app}\\GecodeSupport-6-1-0-r-x64.dll\"\nType: files; Name: \"{app}\\GecodeSupport-6-1-0-r-x64.pdb\"\nType: files; Name: \"{app}\\Qt5Multimedia.dll\"\nType: files; Name: \"{app}\\Qt5MultimediaWidgets.dll\"\nType: files; Name: \"{app}\\Qt5OpenGL.dll\"\nType: files; Name: \"{app}\\Qt5Sensors.dll\"\nType: files; Name: \"{app}\\Qt5WebEngine.dll\"\nType: files; Name: \"{app}\\Qt5WebKit.dll\"\nType: files; Name: \"{app}\\Qt5WebKitWidgets.dll\"\nType: files; Name: \"{app}\\Qt5WebSockets.dll\"\nType: files; Name: \"{app}\\Qt5WebView.dll\"\nType: files; Name: \"{app}\\bin\\fzn-gecode-gist.bat\"\nType: files; Name: \"{app}\\bin\\mzn-gecode.bat\"\nType: files; Name: \"{app}\\findMUS.exe\"\nType: files; Name: \"{app}\\mzn-analyse.exe\"\nType: files; Name: \"{app}\\flatzinc.exe\"\nType: files; Name: \"{app}\\fzn-chuffed.exe\"\nType: files; Name: \"{app}\\fzn-gecode-gist.bat\"\nType: files; Name: \"{app}\\fzn-gecode.exe\"\nType: files; Name: \"{app}\\icudt53.dll\"\nType: files; Name: \"{app}\\icudtl.dat\"\nType: files; Name: \"{app}\\icuin53.dll\"\nType: files; Name: \"{app}\\icuuc53.dll\"\nType: files; Name: \"{app}\\libcrypto-1_1-x64.dll\"\nType: files; Name: \"{app}\\libeay32.dll\"\nType: files; Name: \"{app}\\libssl-1_1-x64.dll\"\nType: files; Name: \"{app}\\minizinc-globalizer.exe\"\nType: files; Name: \"{app}\\msvcp120.dll\"\nType: files; Name: \"{app}\\msvcr120.dll\"\nType: files; Name: \"{app}\\mzn-cbc.exe\"\nType: files; Name: \"{app}\\mzn-chuffed.bat\"\nType: files; Name: \"{app}\\mzn-fzn.exe\"\nType: files; Name: \"{app}\\mzn-g12fd.bat\"\nType: files; Name: \"{app}\\mzn-g12lazy.bat\"\nType: files; Name: \"{app}\\mzn-g12mip.bat\"\nType: files; Name: \"{app}\\mzn-gecode,1.bat\"\nType: files; Name: \"{app}\\mzn-gecode,2.bat\"\nType: files; Name: \"{app}\\mzn-gecode.bat\"\nType: files; Name: \"{app}\\mzn-gurobi.exe\"\nType: files; Name: \"{app}\\mzn2fzn.exe\"\nType: files; Name: \"{app}\\mzn2fzn_test.exe\"\nType: files; Name: \"{app}\\mzncmd.bat\"\nType: files; Name: \"{app}\\position\\qtposition_geoclue.dll\"\nType: files; Name: \"{app}\\qtwebengine_devtools_resources.pak\"\nType: files; Name: \"{app}\\qtwebengine_resources.pak\"\nType: files; Name: \"{app}\\qtwebengine_resources_100p.pak\"\nType: files; Name: \"{app}\\qtwebengine_resources_200p.pak\"\nType: files; Name: \"{app}\\share\\examples\\functions\\warehouses.mzn\"\nType: files; Name: \"{app}\\share\\examples\\new_syntax\\array_index_set.mzn\"\nType: files; Name: \"{app}\\share\\examples\\new_syntax\\extended_let.mzn\"\nType: files; Name: \"{app}\\share\\examples\\new_syntax\\var_if.mzn\"\nType: files; Name: \"{app}\\share\\examples\\option_types\\compatible_assignment_opt.mzn\"\nType: files; Name: \"{app}\\share\\examples\\option_types\\extended_comprehensions.mzn\"\nType: files; Name: \"{app}\\share\\examples\\option_types\\fjsp.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\all_different_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\arg_max_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\at_least_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\at_most_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\circuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\cost_regular.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\count.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\cumulative.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\disjunctive.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\disjunctive_strict.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\distribute.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\exactly_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\global_cardinality_low_up.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\inverse.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\maximum_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\minimum_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\nvalue.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\regular.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\subcircuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\chuffed\\table_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\geas\\all_different_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\geas\\alldifferent_except_0.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\geas\\cumulative.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\geas\\disjunctive.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\geas\\global_cardinality.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\geas\\inverse.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\geas\\table_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\geas\\value_precede_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\all_different_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\all_different_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\all_different_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\all_equal_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\all_equal_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\all_equal_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\alldifferent_except_0.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\among,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\among,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\among.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\arg_max_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\arg_max_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\arg_max_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\arg_max_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\arg_min_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\arg_min_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\arg_min_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\arg_min_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_least_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_least_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_least_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_least_set,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_least_set,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_least_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_most_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_most_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_most_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_most_set,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_most_set,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\at_most_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\bin_packing,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\bin_packing,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\bin_packing.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\bin_packing_capa,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\bin_packing_capa,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\bin_packing_capa.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\bin_packing_load,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\bin_packing_load,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\bin_packing_load.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\circuit,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\circuit,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\circuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\count,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\count,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\count.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\cumulative,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\cumulative,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\cumulative.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\decreasing_bool,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\decreasing_bool,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\decreasing_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\decreasing_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\decreasing_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\decreasing_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\diffn,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\diffn,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\diffn.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\disjoint,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\disjoint,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\disjoint.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\disjunctive_strict.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\distribute,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\distribute,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\distribute.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\exactly_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\exactly_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\exactly_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\exactly_set,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\exactly_set,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\exactly_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\gecode,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\gecode,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality_closed,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality_closed,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality_closed.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality_low_up,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality_low_up,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality_low_up.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality_low_up_closed,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality_low_up_closed,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\global_cardinality_low_up_closed.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\increasing_bool,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\increasing_bool,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\increasing_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\increasing_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\increasing_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\increasing_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\int_set_channel,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\int_set_channel,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\int_set_channel.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\inverse,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\inverse,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\inverse.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\inverse_set,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\inverse_set,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\inverse_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_less_bool,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_less_bool,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_less_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_less_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_less_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_less_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_lesseq_bool,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_lesseq_bool,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_lesseq_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_lesseq_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_lesseq_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\lex_lesseq_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\link_set_to_booleans,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\link_set_to_booleans,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\link_set_to_booleans.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\maximum_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\maximum_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\maximum_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\member_bool,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\member_bool,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\member_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\member_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\member_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\member_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\minimum_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\minimum_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\minimum_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\nvalue,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\nvalue,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\nvalue.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\partition_set,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\partition_set,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\partition_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\precedence.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\range,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\range,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\range.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\redefinitions,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\redefinitions,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\redefinitions-2.0,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\redefinitions-2.0,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\regular,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\regular,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\regular.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\roots,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\roots,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\roots.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\sort,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\sort,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\sort.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\sum_pred,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\sum_pred,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\sum_pred.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\table_bool,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\table_bool,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\table_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\table_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\table_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\table_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\value_precede_int,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\value_precede_int,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\value_precede_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\value_precede_set,1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\value_precede_set,2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode\\value_precede_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\all_different_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\all_equal_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\among.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\arg_max_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\arg_min_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\at_least_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\at_least_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\at_most_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\at_most_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\bin_packing.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\bin_packing_capa.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\bin_packing_load.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\circuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\count.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\cumulative.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\decreasing_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\decreasing_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\diffn.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\disjoint.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\distribute.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\exactly_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\exactly_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\fzn_all_different_int_reif.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\global_cardinality.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\global_cardinality_closed.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\global_cardinality_low_up.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\global_cardinality_low_up_closed.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\increasing_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\increasing_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\int_set_channel.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\inverse.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\inverse_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\lex_less_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\lex_less_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\lex_lesseq_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\lex_lesseq_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\link_set_to_booleans.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\maximum_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\member_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\member_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\minimum_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\nvalue.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\partition_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\range.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\regular.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\roots.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\sort.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\sum_pred.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\table_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\table_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\value_precede_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\gecode_presolver\\value_precede_set.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\all_different_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\alldifferent_except_0.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\circuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\cumulative.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\if_then_else_float.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\if_then_else_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\inverse.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\lex_less_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\lex_lesseq_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\regular.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\sliding_sum.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\subcircuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear\\table_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\CHANGELOG.txt\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\all_different_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\alldifferent_except_0.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\circuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\cumulative.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\domain_encodings.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\inverse.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\lex_lesseq_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\redefinitions-2.0.2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\redefinitions-2.0.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\redefinitions.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\regular.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\subcircuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\subcircuit_wDummy.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_new\\table_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\README__SCIP.txt\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\all_different_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\alldifferent_except_0.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\circuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\cumulative.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\domain_encodings.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_all_different_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_alldifferent_except_0.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_circuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_cumulative.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_if_then_else_float.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_if_then_else_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_inverse.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_inverse_in_range.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_inverse_in_range_reif.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_inverse_reif.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_lex_less_bool_reif.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_lex_lesseq_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_lex_lesseq_bool_reif.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_regular.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_sliding_sum.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_subcircuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\fzn_table_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\if_then_else_float.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\if_then_else_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\inverse.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\lex_less_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\lex_lesseq_bool.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\options.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\redefinitions-2.0.2.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\redefinitions-2.0.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\redefinitions-2.2.1.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\redefinitions.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\redefs_bool_imp.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\redefs_bool_reifs.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\redefs_lin_halfreifs.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\redefs_lin_imp.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\redefs_lin_reifs.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\regular.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\sliding_sum.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\subcircuit.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\subcircuit_wDummy.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\linear_scip\\table_int.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\nosets\\redefinitions.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\std\\builtins.mzn\"\nType: files; Name: \"{app}\\share\\minizinc\\std\\stdlib_new.mzn\"\nType: files; Name: \"{app}\\solns2dzn.exe\"\nType: files; Name: \"{app}\\solns2out.exe\"\nType: files; Name: \"{app}\\ssleay32.dll\"\nType: files; Name: \"{app}\\vccorlib120.dll\"\nType: files; Name: \"{app}\\bin\\fzn-gecode-gist.bat\"\n"
  },
  {
    "path": "resources/pkg_config/snapcraft.yaml",
    "content": "name: minizinc\nversion: git\nsummary: The MiniZinc bundle\nicon: resources/icon.png\ndescription: |\n  MiniZinc is a free and open-source constraint modeling language. You can\n  use MiniZinc to model constraint satisfaction and optimization problems in\n  a high-level, solver-independent way, taking advantage of a large library\n  of pre-defined constraints. Your model is then compiled into FlatZinc, a\n  solver input language that is understood by a wide range of solvers.\n\nconfinement: classic\nbase: core24\n\napps:\n  minizinc:\n    command: bin/minizinc\n  ide:\n    command: bin/desktop-launch $SNAP/MiniZincIDE.sh\n    desktop: share/applications/MiniZincIDE.desktop\n    environment:\n      FONTCONFIG_PATH: $SNAP/etc/fonts\n      FONTCONFIG_FILE: $SNAP/etc/fonts/fonts.conf\nparts:\n  ide:\n    plugin: dump\n    source: ide/\n    organize:\n      usr: /\n    stage:\n      - bin/\n      - lib/\n      - plugins/\n    build-attributes:\n      - enable-patchelf\n  deps:\n    plugin: nil\n    stage-packages:\n      # Additional IDE dependencies which linuxdeployqt does not include.\n      # Cannot put in the ide part because it reorganizes the directory structure.\n      - libegl1\n      - libfontconfig1\n      - libfreetype6\n      - libgl1\n      - libx11-6\n      - libxcb1\n    stage:\n      # Drivers need no-patchelf so can't include them here\n      - -usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/dri\n  deps-mesa:\n    plugin: nil\n    stage-packages:\n      - libgl1-mesa-dri\n    build-attributes:\n      - no-patchelf\n    stage:\n      # Include no-patchelf drivers at this point\n      - usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/dri\n  minizinc:\n    plugin: dump\n    source: minizinc/\n    stage: [bin/, share/]\n    build-attributes:\n      - enable-patchelf\n  vendor:\n    plugin: dump\n    source: vendor/\n    organize:\n      gecode_gist/bin/fzn-gecode: bin/\n      gecode_gist/share/minizinc/solvers/*: share/minizinc/solvers/\n      gecode_gist/share/minizinc/gecode: share/minizinc/gecode\n      chuffed/bin/fzn-chuffed: bin/\n      chuffed/share/minizinc/solvers/*: share/minizinc/solvers/\n      chuffed/share/minizinc/chuffed: share/minizinc/chuffed\n      or-tools/bin/fzn-cp-sat: bin/\n      or-tools/share/minizinc/solvers/*: share/minizinc/solvers/\n      or-tools/share/minizinc/cp-sat: share/minizinc/cp-sat\n      highs/lib64/libhighs.so*: lib/\n    stage:\n      - bin/\n      - lib/\n      - share/minizinc/\n      # Prevent conflict with ide lib\n      - -lib/libbz2.so.1\n    build-attributes:\n      - enable-patchelf\n  globalizer:\n    plugin: dump\n    source: globalizer/\n    stage: [bin/, share/minizinc/]\n    build-attributes:\n      - enable-patchelf\n  findmus:\n    plugin: dump\n    source: findMUS/\n    stage: [bin/, share/minizinc/]\n    build-attributes:\n      - enable-patchelf\n  mzn-analyse:\n    plugin: dump\n    source: mzn-analyse/\n    stage: [bin/]\n    build-attributes:\n      - enable-patchelf\n  resources:\n    plugin: dump\n    source: resources/\n    organize:\n      misc/MiniZincIDE.desktop: share/applications/\n      scripts/MiniZincIDE.sh: /\n    stage: [share/, MiniZincIDE.sh]\n  # This part installs a `desktop-launch` script to initialise desktop-specific\n  # features such as fonts, themes and the XDG environment.\n  #\n  # It is copied straight from the snapcraft desktop helpers project.\n  # Please periodically check the source for updates and copy the changes.\n  #    https://github.com/ubuntu/snapcraft-desktop-helpers/blob/master/snapcraft.yaml\n  #\n  desktop-glib-only:\n    source: https://github.com/ubuntu/snapcraft-desktop-helpers.git\n    source-subdir: glib-only\n    plugin: make\n    build-packages:\n      - libglib2.0-dev\n    stage-packages:\n      - libglib2.0-bin\n"
  },
  {
    "path": "resources/scripts/AppRun",
    "content": "#!/bin/sh\n\nHERE=\"$(dirname \"$(readlink -f \"${0}\")\")\"\n\nexport PATH=$HERE/usr/bin:$PATH\nexport LD_LIBRARY_PATH=$HERE/usr/lib:$LD_LIBRARY_PATH\nexport QT_PLUGIN_PATH=$HERE/usr/plugins\n\nif [ ! -z $APPIMAGE ]; then\n  BINARY_NAME=$(basename \"$ARGV0\")\n  if [ -e \"$HERE/usr/bin/$BINARY_NAME\" ]; then\n    exec \"$HERE/usr/bin/$BINARY_NAME\" \"$@\"\n  elif [ \"$1\" = \"install\" ]; then\n\n    set -e\n    if [ -z $BIN_LOCATION ]; then\n      BIN_LOCATION=\"/usr/local/bin\"\n    fi\n    if [ -z $DESKTOP_LOCATION ]; then\n      DESKTOP_LOCATION=\"$HOME/.local/share\"\n    fi\n    echo \"Installing minizinc and MiniZincIDE binaries to $BIN_LOCATION (set BIN_LOCATION).\"\n    echo \"Installing MiniZinc desktop file and icon in $DESKTOP_LOCATION/{applications,icons} (set DESKTOP_LOCATION).\"\n    echo \"Files with the same name may be overwritten.\"\n    if [ ! -z $PS1 ] || [ \"${NONINTERACTIVE:-0}\" -eq \"1\" ]; then\n      echo -n \"Do you want to continue? [Y/n] \"\n      read answer\n      if [ \"${answer#[Yy]}\" != \"\" ] ;then\n        exit 1\n      fi\n    fi\n\n    BPERM=\"\"\n    if [ ! -w \"$BIN_LOCATION\" ]; then\n      BPERM=\"sudo\"\n    fi\n    eval $BPERM ln -sf $APPIMAGE $BIN_LOCATION/minizinc\n    eval $BPERM ln -sf $APPIMAGE $BIN_LOCATION/MiniZincIDE\n\n    DPERM=\"\"\n    if [ ! -w \"$DESKTOP_LOCATION\" ]; then\n      DPERM=\"sudo\"\n    fi\n    eval $DPERM sed 's/Exec=.*/Exec=MiniZincIDE/g' $HERE/minizinc.desktop > $DESKTOP_LOCATION/applications/MiniZincIDE.desktop\n    eval $DPERM mkdir -p $DESKTOP_LOCATION/icons/hicolor/256x256/apps\n    eval $DPERM cp $HERE/minizinc.png $DESKTOP_LOCATION/icons/hicolor/256x256/apps/minizinc.png\n\n  else\n    exec \"$HERE/usr/bin/MiniZincIDE\" \"$@\"\n  fi\nelse\n  exec \"$HERE/usr/bin/MiniZincIDE\" \"$@\"\nfi\n"
  },
  {
    "path": "resources/scripts/MiniZincIDE.sh",
    "content": "#!/bin/sh\nDIR=$(dirname $(readlink -f \"$0\"))\n[ -n \"$XDG_RUNTIME_DIR\" ] && mkdir -p $XDG_RUNTIME_DIR -m 700\nexport LD_LIBRARY_PATH=\"$DIR\"/lib:$LD_LIBRARY_PATH\nexport QT_PLUGIN_PATH=\"$DIR\"/plugins\n\nexec \"$DIR\"/bin/MiniZincIDE $@\n"
  },
  {
    "path": "tests/data/mooc/TestProject/TestProject.mzp",
    "content": "{\"version\":105,\"projectFiles\":[\"models/submission.mzn\",\"models/submission.mzc\",\"data/n1.dzn\",\"data/n2.dzn\",\"_mooc\"],\"openFiles\":[\"models/submission.mzn\"],\"selectedBuiltinConfigId\":\"org.gecode.gecode\",\"selectedBuiltinConfigVersion\":\"default\"}"
  },
  {
    "path": "tests/data/mooc/TestProject/_mooc",
    "content": "{\"assignmentKey\":\"0GSb2Dj7kA\",\"name\":\"TestProject\",\"moocName\":\"Test Course\",\"moocPasswordString\":\"Submission token\",\"submissionURL\":\"\",\"solutionAssignments\":[{\"id\":\"4B1rip2kJ0\",\"model\":\"models/submission.mzn\",\"data\":\"data/n1.dzn\",\"timeout\":\"15\",\"name\":\"Submission 1\"},{\"id\":\"amDEPcKA4r\",\"model\":\"models/submission.mzn\",\"data\":\"data/n2.dzn\",\"timeout\":\"15\",\"name\":\"Submission 2\"}],\"modelAssignments\":[{\"id\":\"1qJ3CBAZdY\",\"model\":\"models/submission.mzn\",\"name\":\"Submission Model 1\"}],\"sendMeta\":true}"
  },
  {
    "path": "tests/data/mooc/TestProject/data/n1.dzn",
    "content": "n = 1;\r\n"
  },
  {
    "path": "tests/data/mooc/TestProject/data/n2.dzn",
    "content": "n = 2;\r\n"
  },
  {
    "path": "tests/data/mooc/TestProject/models/submission.mzc",
    "content": "@eAE9ykEKwjAQQNF9TjHMSgl4AHMNdypSkwkdTGckmZTg6e2idPXh8Vli6YkAm5aV6qtSoszC\nxirtsvwEg+PjsVT4fahdQfaO4LTbtxvc8TYTrFPpBJphAAvYJnGm+KEK3AC9z1qXyU7j7D0+\nBJ/B/QEYzC48\n"
  },
  {
    "path": "tests/data/mooc/TestProject/models/submission.mzn",
    "content": "% This is a test submission\r\nint: n;\r\nvar n..10: x;\r\nsolve minimize x;\r\noutput [\"the value of x in the model is \\(x)\\n\"];\r\n"
  },
  {
    "path": "tests/data/mooc/TestTerms/TestTerms.mzp",
    "content": "{\"version\":105,\"projectFiles\":[\"models/submission.mzn\",\"models/submission.mzc\",\"data/n1.dzn\",\"data/n2.dzn\",\"_mooc\"],\"openFiles\":[\"models/submission.mzn\"],\"selectedBuiltinConfigId\":\"org.gecode.gecode\",\"selectedBuiltinConfigVersion\":\"default\"}"
  },
  {
    "path": "tests/data/mooc/TestTerms/_mooc",
    "content": "{\"assignmentKey\":\"0GSb2Dj7kA\",\"name\":\"TestTerms\",\"moocName\":\"Test Course\",\"moocPasswordString\":\"Submission token\",\"submissionURL\":\"\",\"submissionTerms\": \"Submission terms\",\"solutionAssignments\":[{\"id\":\"4B1rip2kJ0\",\"model\":\"models/submission.mzn\",\"data\":\"data/n1.dzn\",\"timeout\":\"15\",\"name\":\"Submission 1\"},{\"id\":\"amDEPcKA4r\",\"model\":\"models/submission.mzn\",\"data\":\"data/n2.dzn\",\"timeout\":\"15\",\"name\":\"Submission 2\"}],\"modelAssignments\":[{\"id\":\"1qJ3CBAZdY\",\"model\":\"models/submission.mzn\",\"name\":\"Submission Model 1\"}],\"sendMeta\":true}"
  },
  {
    "path": "tests/data/mooc/TestTerms/data/n1.dzn",
    "content": "n = 1;\r\n"
  },
  {
    "path": "tests/data/mooc/TestTerms/data/n2.dzn",
    "content": "n = 2;\r\n"
  },
  {
    "path": "tests/data/mooc/TestTerms/models/submission.mzc",
    "content": "@eAE9ykEKwjAQQNF9TjHMSgl4AHMNdypSkwkdTGckmZTg6e2idPXh8Vli6YkAm5aV6qtSoszC\nxirtsvwEg+PjsVT4fahdQfaO4LTbtxvc8TYTrFPpBJphAAvYJnGm+KEK3AC9z1qXyU7j7D0+\nBJ/B/QEYzC48\n"
  },
  {
    "path": "tests/data/mooc/TestTerms/models/submission.mzn",
    "content": "% This is a test submission\r\nint: n;\r\nvar n..10: x;\r\nsolve minimize x;\r\noutput [\"the value of x in the model is \\(x)\\n\"];\r\n"
  },
  {
    "path": "tests/data/project/configs/solver1.mpc",
    "content": "{\n    \"-O\": 2,\n    \"free-search\": true,\n    \"intermediate-solutions\": true,\n    \"solver\": \"org.gecode.gecode@6.3.0\"\n}\n"
  },
  {
    "path": "tests/data/project/configs/solver2.mpc",
    "content": "{\n    \"intermediate-solutions\": false,\n    \"solver\": \"org.chuffed.chuffed@0.10.4\"\n}\n"
  },
  {
    "path": "tests/data/project/configs/solver3.mpc",
    "content": "{\n    \"-O\": 2,\n    \"free-search\": true,\n    \"intermediate-solutions\": true,\n    \"solver\": \"invalid.solver@1.0.0\"\n}\n"
  },
  {
    "path": "tests/data/project/data/data1.dzn",
    "content": ""
  },
  {
    "path": "tests/data/project/data/data2.dzn",
    "content": ""
  },
  {
    "path": "tests/data/project/models/model1.mzn",
    "content": ""
  },
  {
    "path": "tests/data/project/models/model2.mzn",
    "content": ""
  },
  {
    "path": "tests/data/project/project-105-bad.mzp",
    "content": "{\n    \"builtinSolverConfigs\": [\n    ],\n    \"openFiles\": [\"models/model1.mzn\", \"data/data1.dzn\"],\n    \"openTab\": 0,\n    \"projectFiles\": [\n\t\t\"file/does/not/exist.mzn\",\n        \"data/data1.dzn\",\n        \"data/data2.dzn\",\n\t\t\"models/model1.mzn\",\n\t\t\"models/model2.mzn\"\n    ],\n    \"projectSolverConfigs\": [\n        {\n            \"additionalCompilerCommandline\": \"\",\n            \"additionalData\": \"\",\n            \"clearOutputWindow\": false,\n            \"compressSolutionOutput\": 100,\n            \"defaultBehavior\": true,\n            \"flatteningStats\": false,\n            \"freeSearch\": false,\n            \"id\": \"does.not.exist\",\n            \"nThreads\": 1,\n            \"name\": \"Project solver configuration\",\n            \"optimizationLevel\": 1,\n            \"outputTiming\": false,\n            \"printIntermediate\": false,\n            \"runSolutionChecker\": true,\n            \"solverFlags\": \"\",\n            \"solvingStats\": false,\n            \"stopAfter\": 1,\n            \"timeLimit\": 0,\n            \"verboseFlattening\": false,\n            \"verboseSolving\": false,\n            \"version\": \"0.1.0\"\n        }\n    ],\n    \"selectedProjectConfig\": 0,\n    \"version\": 105\n}\n"
  },
  {
    "path": "tests/data/project/project-105-good.mzp",
    "content": "{\n    \"builtinSolverConfigs\": [\n    ],\n    \"openFiles\": [\"models/model1.mzn\", \"data/data1.dzn\"],\n    \"openTab\": 0,\n    \"projectFiles\": [\n        \"data/data1.dzn\",\n        \"data/data2.dzn\",\n\t\t\"models/model1.mzn\",\n\t\t\"models/model2.mzn\"\n    ],\n    \"projectSolverConfigs\": [\n        {\n            \"additionalCompilerCommandline\": \"\",\n            \"additionalData\": \"\",\n            \"clearOutputWindow\": false,\n            \"compressSolutionOutput\": 100,\n            \"defaultBehavior\": true,\n            \"flatteningStats\": false,\n            \"freeSearch\": false,\n            \"id\": \"org.chuffed.chuffed\",\n            \"nThreads\": 1,\n            \"name\": \"Project solver configuration\",\n            \"optimizationLevel\": 1,\n            \"outputTiming\": false,\n            \"printIntermediate\": false,\n            \"runSolutionChecker\": true,\n            \"solverFlags\": \"\",\n            \"solvingStats\": false,\n            \"stopAfter\": 1,\n            \"timeLimit\": 0,\n            \"verboseFlattening\": false,\n            \"verboseSolving\": false,\n            \"version\": \"0.9.0\"\n        }\n    ],\n    \"selectedProjectConfig\": 0,\n    \"version\": 105\n}\n"
  },
  {
    "path": "tests/data/project/project-106-bad.mzp",
    "content": "{\n    \"openFiles\": [\"models/model1.mzn\", \"data/data1.dzn\"],\n    \"openTab\": 0,\n    \"projectFiles\": [\n\t\t\"file/does/not/exist.mzn\",\n        \"data/data1.dzn\",\n        \"data/data2.dzn\",\n\t\t\"models/model1.mzn\",\n\t\t\"models/model2.mzn\",\n        \"configs/solver1.mpc\",\n        \"configs/solver2.mpc\",\n        \"configs/solver3.mpc\"\n    ],\n    \"selectedSolverConfigFile\": \"configs/solver1.mpc\",\n    \"version\": 106\n}\n"
  },
  {
    "path": "tests/data/project/project-106-good.mzp",
    "content": "{\n    \"openFiles\": [\"models/model1.mzn\", \"data/data1.dzn\"],\n    \"openTab\": 0,\n    \"projectFiles\": [\n        \"data/data1.dzn\",\n        \"data/data2.dzn\",\n\t\t\"models/model1.mzn\",\n\t\t\"models/model2.mzn\",\n        \"configs/solver1.mpc\",\n        \"configs/solver2.mpc\"\n    ],\n    \"selectedSolverConfigFile\": \"configs/solver1.mpc\",\n    \"version\": 106\n}\n"
  },
  {
    "path": "tests/testcpprofier.cpp",
    "content": "#include <QtTest>\r\n\r\n#include \"testide.h\"\r\n\r\n#include \"mainwindow.h\"\r\n#include \"ui_mainwindow.h\"\r\n\r\n#include \"../cp-profiler/src/cpprofiler/execution.hh\"\r\n\r\n#include <QSignalSpy>\r\n\r\nvoid TestIDE::testCPProfiler()\r\n{\r\n    TestMocker mock;\r\n    MainWindow* w = mock.mw();\r\n    CodeEditor* e = w->curEditor;\r\n    QTextDocument* doc = e->document();\r\n    doc->setPlainText(\"var 1..3: x;\"\r\n                      \"solve :: int_search([x], input_order, indomain_min) maximize x;\");\r\n    doc->setModified(false);\r\n    w->on_actionShow_search_profiler_triggered();\r\n\r\n    qRegisterMetaType<cpprofiler::Execution*>();\r\n    QSignalSpy spy(w->conductor, &cpprofiler::Conductor::executionStart);\r\n    QSignalSpy spy2(w, &MainWindow::finished);\r\n    w->on_actionProfile_search_triggered();\r\n    QVERIFY(spy.wait());\r\n    auto args = spy.takeFirst();\r\n    auto* ex = args[0].value<cpprofiler::Execution*>();\r\n    QVERIFY(ex != nullptr);\r\n    QVERIFY(QTest::qWaitFor([=] () {\r\n        return ex->tree().nodeCount() == 5;\r\n    }, 30000));\r\n    QVERIFY(spy2.count() > 0 || spy2.wait());\r\n}\r\n"
  },
  {
    "path": "tests/testdiff.cpp",
    "content": "#include <QtTest>\r\n#include <QSignalSpy>\r\n\r\n#include \"testide.h\"\r\n\r\n#include \"history.h\"\r\n\r\nvoid TestIDE::testDiff()\r\n{\r\n    QString origText =\r\n            \"foo\\n\"\r\n            \"bar\\n\"\r\n            \"qux\\n\"\r\n            \"baz\";\r\n    QString newText =\r\n            \"foo\\n\"\r\n            \"hello\\n\"\r\n            \"bar\\n\"\r\n            \"baz\\n\"\r\n            \"world\";\r\n    FileDiff diff(origText, newText);\r\n    auto result = diff.diff();\r\n    QCOMPARE(result.size(), 3);\r\n    QCOMPARE(result[0].kind, FileDiff::EntryType::Insertion);\r\n    QCOMPARE(result[0].line, 2);\r\n    QCOMPARE(result[0].text, \"hello\");\r\n    QCOMPARE(result[1].kind, FileDiff::EntryType::Deletion);\r\n    QCOMPARE(result[1].line, 3);\r\n    QCOMPARE(result[1].text, \"qux\");\r\n    QCOMPARE(result[2].kind, FileDiff::EntryType::Insertion);\r\n    QCOMPARE(result[2].line, 5);\r\n    QCOMPARE(result[2].text, \"world\");\r\n}\r\n\r\nvoid TestIDE::testDiffApply()\r\n{\r\n    QFETCH(QString, origText);\r\n    QFETCH(QString, newText);\r\n    FileDiff diff(origText, newText);\r\n    auto result = diff.diff();\r\n    auto applied = FileDiff::apply(origText, result);\r\n    QCOMPARE(applied, newText);\r\n}\r\n\r\nvoid TestIDE::testDiffApply_data() {\r\n    QTest::addColumn<QString>(\"origText\");\r\n    QTest::addColumn<QString>(\"newText\");\r\n    QTest::newRow(\"test 1\") << \"a\\nb\\nc\\nd\" << \"a\\nc\\nd\\ne\";\r\n    QTest::newRow(\"test 2\") << \"a\\nb\\nc\\nd\" << \"b\\nc\\nd\";\r\n    QTest::newRow(\"test 3\") << \"a\\nb\\nc\\nd\\nb\\na\\nc\" << \"b\\na\\nc\";\r\n}\r\n\r\nvoid TestIDE::testHistory()\r\n{\r\n    History history(\"foo\");\r\n    QSignalSpy spy(&history, &History::historyChanged);\r\n    history.addFile(\"test.mzn\", \"a\\nb\\nc\\n\");\r\n    history.updateFileContents(\"test.mzn\", \"a\\nc\\nd\\n\");\r\n    history.updateFileContents(\"test.mzn\", \"f\\na\\nc\\ne\\n\");\r\n    history.commit();\r\n    QCOMPARE(spy.count(), 3);\r\n}\r\n"
  },
  {
    "path": "tests/testeditor.cpp",
    "content": "#include <QtTest>\n\n#include \"testide.h\"\n\n#include \"mainwindow.h\"\n#include \"ui_mainwindow.h\"\n\nvoid TestIDE::testStartupPlayground()\n{\n    TestMocker mock;\n    MainWindow* w = mock.mw();\n    QString text = w->curEditor->document()->toPlainText();\n    QCOMPARE(text, QString(\"% Use this editor as a MiniZinc scratch book\\n\"));\n}\n\nvoid TestIDE::testIndent()\n{\n    QFETCH(QString, initial);\n    QFETCH(int, shift_amount);\n    QFETCH(int, start);\n    QFETCH(int, end);\n    QFETCH(QString, expected);\n\n    TestMocker mock;\n    MainWindow* w = mock.mw();\n    CodeEditor* e = w->curEditor;\n    QTextDocument* doc = e->document();\n    doc->setPlainText(initial);\n    QTextCursor cursor = e->textCursor();\n    cursor.setPosition(start, QTextCursor::MoveAnchor);\n    cursor.setPosition(end, QTextCursor::KeepAnchor);\n    e->setTextCursor(cursor);\n    while (shift_amount < 0) {\n        w->on_actionShift_left_triggered();\n        shift_amount++;\n    }\n    while (shift_amount > 0) {\n        w->on_actionShift_right_triggered();\n        shift_amount--;\n    }\n    QCOMPARE(doc->toPlainText(), expected);\n    doc->setModified(false);\n}\n\nvoid TestIDE::testIndent_data() {\n    QString initial1 = \"Line 1\\n\"\n                       \"  Line 2\\n\"\n                       \"    Line 3\\n\";\n    QString initial2 = \"Line 1\\n\"\n                       \"  Line 2\\n\"\n                       \"    Line 3\";\n    QString initial3 = \"Line 1\\n\"\n                       \"Line 2\\n\"\n                       \"Line 3\\n\";\n    QString initial4 = \"Line 1\\n\"\n                       \"Line 2\\n\"\n                       \"Line 3\";\n\n    QTest::addColumn<int>(\"shift_amount\");\n    QTest::addColumn<QString>(\"initial\");\n    QTest::addColumn<int>(\"start\");\n    QTest::addColumn<int>(\"end\");\n    QTest::addColumn<QString>(\"expected\");\n\n    QTest::newRow(\"shift all left\")\n            << -1\n            << initial1\n            << 0\n            << 26\n            << \"Line 1\\n\"\n               \"Line 2\\n\"\n               \"  Line 3\\n\";\n\n    QTest::newRow(\"shift first line left\")\n            << -1\n            << initial1\n            << 0\n            << 6\n            << \"Line 1\\n\"\n               \"  Line 2\\n\"\n               \"    Line 3\\n\";\n\n    QTest::newRow(\"shift last line (back) left\")\n            << -1\n            << initial1\n            << 26\n            << 16\n            << \"Line 1\\n\"\n               \"  Line 2\\n\"\n               \"  Line 3\\n\";\n\n    QTest::newRow(\"shift last two lines (back, no trail) left\")\n            << -1\n            << initial2\n            << 26\n            << 7\n            << \"Line 1\\n\"\n               \"Line 2\\n\"\n               \"  Line 3\";\n\n    QTest::newRow(\"shift empty line left\")\n            << -1\n            << initial1\n            << 27\n            << 27\n            << initial1;\n\n    QTest::newRow(\"shift partial line left\")\n            << -1\n            << initial1\n            << 9\n            << 11\n            << \"Line 1\\n\"\n               \"Line 2\\n\"\n               \"    Line 3\\n\";\n\n    QTest::newRow(\"shift 2nd line, no selection left\")\n            << -1\n            << initial1\n            << 10\n            << 10\n            << \"Line 1\\n\"\n               \"Line 2\\n\"\n               \"    Line 3\\n\";\n\n    QTest::newRow(\"shift all left twice\")\n            << -2\n            << initial1\n            << 0\n            << 26\n            << \"Line 1\\n\"\n               \"Line 2\\n\"\n               \"Line 3\\n\";\n\n    QTest::newRow(\"shift all left three times\")\n            << -3\n            << initial1\n            << 0\n            << 26\n            << \"Line 1\\n\"\n               \"Line 2\\n\"\n               \"Line 3\\n\";\n\n    QTest::newRow(\"shift all right\")\n            << 1\n            << initial3\n            << 0\n            << 20\n            << \"  Line 1\\n\"\n               \"  Line 2\\n\"\n               \"  Line 3\\n\";\n\n    QTest::newRow(\"shift first line right\")\n            << 1\n            << initial3\n            << 0\n            << 7\n            << \"  Line 1\\n\"\n               \"Line 2\\n\"\n               \"Line 3\\n\";\n\n    QTest::newRow(\"shift last line (back)\")\n            << 1\n            << initial3\n            << 20\n            << 14\n            << \"Line 1\\n\"\n               \"Line 2\\n\"\n               \"  Line 3\\n\";\n\n    QTest::newRow(\"shift last two lines (back, no trail) right\")\n            << 1\n            << initial4\n            << 20\n            << 7\n            << \"Line 1\\n\"\n               \"  Line 2\\n\"\n               \"  Line 3\";\n\n    QTest::newRow(\"shift empty line right\")\n            << 1\n            << initial3\n            << 21\n            << 21\n            << \"Line 1\\n\"\n               \"Line 2\\n\"\n               \"Line 3\\n  \";\n\n    QTest::newRow(\"shift partial line selection right\")\n            << 1\n            << initial3\n            << 9\n            << 11\n            << \"Line 1\\n\"\n               \"  Line 2\\n\"\n               \"Line 3\\n\";\n\n\n    QTest::newRow(\"shift 2nd line, no selection\")\n            << 1\n            << initial3\n            << 10\n            << 10\n            << \"Line 1\\n\"\n               \"  Line 2\\n\"\n               \"Line 3\\n\";\n}\n\nenum FindReplaceAction {\n    NEXT,\n    PREVIOUS,\n    REPLACE,\n    REPLACE_AND_FIND,\n    REPLACE_ALL\n};\n\nQ_DECLARE_METATYPE(FindReplaceAction);\n\nvoid TestIDE::testFindReplace() {\n    QFETCH(QString, initial);\n    QFETCH(bool, regex);\n    QFETCH(bool, ignore_case);\n    QFETCH(bool, wrap_around);\n    QFETCH(QString, find);\n    QFETCH(QString, replace);\n    QFETCH(QVector<FindReplaceAction>, actions);\n    QFETCH(QString, expected);\n\n    TestMocker mock;\n    MainWindow* w = mock.mw();\n    CodeEditor* e = w->curEditor;\n    QTextDocument* doc = e->document();\n    doc->setPlainText(initial);\n    w->curEditor->moveCursor(QTextCursor::Start);\n    w->ui->find->setText(find);\n    w->ui->replace->setText(replace);\n    w->ui->check_re->setChecked(regex);\n    w->ui->check_case->setChecked(ignore_case);\n    w->ui->check_wrap->setChecked(wrap_around);\n\n    QWidget* buttons[] = {\n        w->ui->b_next,\n        w->ui->b_prev,\n        w->ui->b_replace,\n        w->ui->b_replacefind,\n        w->ui->b_replaceall\n    };\n\n    for (int i = 0; i < actions.size(); ++i) {\n        QTest::mouseClick(buttons[actions.at(i)], Qt::LeftButton);\n    }\n\n    QCOMPARE(doc->toPlainText(), expected);\n    doc->setModified(false);\n}\n\nvoid TestIDE::testFindReplace_data() {\n    QString initial = \"Foo\\n\"\n                      \"Foo bar\\n\"\n                      \"bAr fOo\";\n\n    QTest::addColumn<QString>(\"initial\");\n    QTest::addColumn<bool>(\"regex\");\n    QTest::addColumn<bool>(\"ignore_case\");\n    QTest::addColumn<bool>(\"wrap_around\");\n    QTest::addColumn<QString>(\"find\");\n    QTest::addColumn<QString>(\"replace\");\n    QTest::addColumn<QVector<FindReplaceAction> >(\"actions\");\n    QTest::addColumn<QString>(\"expected\");\n\n    QVector<FindReplaceAction> basicActions;\n    basicActions\n            << FindReplaceAction::NEXT\n            << FindReplaceAction::REPLACE;\n\n    QTest::addRow(\"find replace basic\")\n            << initial\n            << false\n            << false\n            << false\n            << \"Foo\"\n            << \"baz\"\n            << basicActions\n            << \"baz\\n\"\n               \"Foo bar\\n\"\n               \"bAr fOo\";\n\n    QVector<FindReplaceAction> basicActions2;\n    basicActions2\n            << FindReplaceAction::NEXT\n            << FindReplaceAction::REPLACE\n            << FindReplaceAction::NEXT\n            << FindReplaceAction::REPLACE;\n\n    QTest::addRow(\"find replace basic twice\")\n            << initial\n            << false\n            << false\n            << false\n            << \"Foo\"\n            << \"baz\"\n            << basicActions2\n            << \"baz\\n\"\n               \"baz bar\\n\"\n               \"bAr fOo\";\n}\n"
  },
  {
    "path": "tests/testide.cpp",
    "content": "#include <QtTest>\n#include <QSettings>\n\n#include \"testide.h\"\n#include \"ide.h\"\n\n void TestMocker::resetSettings() {\n    QSettings settings;\n    settings.clear();\n    settings.beginGroup(\"ide\");\n    // Suppress prompt for enabling version check\n    settings.setValue(\"lastCheck21\", QDate::currentDate().toString());\n    settings.setValue(\"uuid\", QUuid::createUuid().toString());\n    settings.setValue(\"checkforupdates21\", false);\n    settings.sync();\n}\n\nint main(int argc, char *argv[])\n{\n    IDE a(argc, argv);\n    return QTest::qExec(new TestIDE, argc, argv);\n}\n"
  },
  {
    "path": "tests/testide.h",
    "content": "#include <QObject>\n#include <QSettings>\n#include <QDate>\n\n#include \"mainwindow.h\"\n\nclass TestIDE : public QObject\n{\n    Q_OBJECT\n\nprivate slots:\n    void testStartupPlayground();\n    void testIndent();\n    void testIndent_data();\n    void testFindReplace();\n    void testFindReplace_data();\n\n    void testProject106Good();\n    void testProject106Bad();\n    void testProject105Good();\n    void testProject105Bad();\n\n    void testMoocProject();\n    void testMoocTerms();\n    void testMoocSubmission();\n\n    void testCPProfiler();\n\n    void testDiff();\n    void testDiffApply();\n    void testDiffApply_data();\n    void testHistory();\n};\n\nclass TestMocker {\npublic:\n    template <typename ...T>\n    TestMocker(T&& ...args): _mw(new MainWindow(std::forward<T>(args)...)) {\n        TestMocker::resetSettings();\n        _mw->show();\n    }\n\n    ~TestMocker() {\n        _mw->close();\n        _mw->deleteLater();\n    }\n\n    MainWindow* mw() const { return _mw; }\n\n    static void resetSettings();\nprivate:\n    MainWindow* _mw;\n};\n"
  },
  {
    "path": "tests/testmooc.cpp",
    "content": "#include <QtTest>\r\n\r\n#include \"testide.h\"\r\n\r\n#include \"ide.h\"\r\n\r\n#include \"mainwindow.h\"\r\n#include \"ui_mainwindow.h\"\r\n\r\n#include \"moocsubmission.h\"\r\n#include \"ui_moocsubmission.h\"\r\n\r\n#include <QTcpServer>\r\n#include <QJsonDocument>\r\n#include <QSignalSpy>\r\n\r\nvoid TestIDE::testMoocProject()\r\n{\r\n    TestMocker mock(QString(MINIZINC_IDE_PATH) + \"/data/mooc/TestProject/TestProject.mzp\");\r\n    MainWindow* w = mock.mw();\r\n    QVERIFY(w->currentModelFile().endsWith(\"submission.mzn\"));\r\n    QCOMPARE(w->ui->actionSubmit_to_MOOC->text(), \"Submit to Test Course\");\r\n    w->ui->actionSubmit_to_MOOC->trigger();\r\n    QVERIFY(w->moocSubmission->ui->runButton->isEnabled());\r\n    w->moocSubmission->close();\r\n}\r\n\r\n\r\nvoid TestIDE::testMoocTerms()\r\n{\r\n    TestMocker mock(QString(MINIZINC_IDE_PATH) + \"/data/mooc/TestTerms/TestTerms.mzp\");\r\n    MainWindow* w = mock.mw();\r\n    w->ui->actionSubmit_to_MOOC->trigger();\r\n    QCOMPARE(w->moocSubmission->ui->submissionTerms_textBrowser->toPlainText(), \"Submission terms\");\r\n    QVERIFY(!w->moocSubmission->ui->runButton->isEnabled());\r\n    w->moocSubmission->ui->submissionTerms_checkBox->setChecked(true);\r\n    QVERIFY(w->moocSubmission->ui->runButton->isEnabled());\r\n    w->moocSubmission->close();\r\n}\r\n\r\nvoid TestIDE::testMoocSubmission()\r\n{\r\n    auto* s = new QTcpServer(this);\r\n    s->listen(QHostAddress::LocalHost);\r\n\r\n    TestMocker mock(QString(MINIZINC_IDE_PATH) + \"/data/mooc/TestProject/TestProject.mzp\");\r\n    MainWindow* w = mock.mw();\r\n    w->ui->actionSubmit_to_MOOC->trigger();\r\n    QUrl url;\r\n    url.setScheme(\"http\");\r\n    url.setHost(s->serverAddress().toString());\r\n    url.setPort(s->serverPort());\r\n\r\n    w->moocSubmission->project.submissionURL = url.toString();\r\n    w->moocSubmission->ui->login->setText(\"user@example.com\");\r\n    w->moocSubmission->ui->password->setText(\"password\");\r\n    w->moocSubmission->ui->runButton->click();\r\n\r\n    QSignalSpy spy(s, &QTcpServer::newConnection);\r\n    QJsonObject parts;\r\n    for (int i = 0; i < 2; i++) {\r\n        // 1st time is auth check, 2nd time is submission\r\n        QVERIFY(spy.wait(30000));\r\n        QVERIFY(s->hasPendingConnections());\r\n        auto* client = s->nextPendingConnection();\r\n        QSignalSpy spy2(client, &QTcpSocket::readyRead);\r\n        QByteArray request;\r\n        while (spy2.wait(100)) {\r\n            // Hack for reading packet data until there's nothing for 100ms\r\n            request.append(client->readAll());\r\n        }\r\n        qDebug() << QString::fromUtf8(request);\r\n        QTextStream ts(client);\r\n        ts << \"HTTP/1.1 200 OK\\r\\n\"\r\n           << \"\\r\\n\"\r\n           << \"{\\\"message\\\": \\\"Success\\\"}\";\r\n        client->close();\r\n        QSignalSpy spy3(client, &QTcpSocket::disconnected);\r\n        QVERIFY(spy3.wait());\r\n        auto raw = request.mid(request.indexOf(\"\\r\\n\\r\\n\"));\r\n        auto body = QJsonDocument::fromJson(raw).object();\r\n        QCOMPARE(body[\"assignmentKey\"].toString(), \"0GSb2Dj7kA\");\r\n        QCOMPARE(body[\"secret\"], w->moocSubmission->ui->password->text());\r\n        QCOMPARE(body[\"submitterEmail\"], w->moocSubmission->ui->login->text());\r\n        QVERIFY(body[\"parts\"].isObject());\r\n        parts = body[\"parts\"].toObject();\r\n    }\r\n    s->deleteLater();\r\n\r\n    QString out1 = \"% Solution checker report:\\n\"\r\n                   \"% The value of x in the checker is 1\\n\"\r\n                   \"x = 1;\\n\"\r\n                   \"----------\\n\"\r\n                   \"==========\\n\";\r\n\r\n    QString out2 = \"% Solution checker report:\\n\"\r\n                   \"% The value of x in the checker is 2\\n\"\r\n                   \"x = 2;\\n\"\r\n                   \"----------\\n\"\r\n                   \"==========\\n\";\r\n\r\n    QFile f(QString(MINIZINC_IDE_PATH) + \"/data/mooc/TestProject/models/submission.mzn\");\r\n    QVERIFY(f.open(QIODevice::ReadOnly));\r\n    auto out3 = QString::fromUtf8(f.readAll());\r\n    f.close();\r\n\r\n    QVector<QPair<QString, QString>> expected({{\"4B1rip2kJ0\", out1},\r\n                                               {\"amDEPcKA4r\", out2},\r\n                                               {\"1qJ3CBAZdY\", out3}});\r\n    for (auto& it : expected) {\r\n        auto actual = parts[it.first].toObject()[\"output\"].toString();\r\n        QCOMPARE(actual.replace(\"\\r\", \"\"), it.second.replace(\"\\r\", \"\"));\r\n    }\r\n\r\n    QVERIFY(QTest::qWaitFor([=] () {\r\n        return w->moocSubmission->_cur_phase == MOOCSubmission::S_NONE;\r\n    }));\r\n    w->moocSubmission->close();\r\n}\r\n"
  },
  {
    "path": "tests/testproject.cpp",
    "content": "#include <QtTest>\n\n#include \"testide.h\"\n\n#include \"ide.h\"\n\n#include \"mainwindow.h\"\n#include \"ui_mainwindow.h\"\n\n#include <QMessageBox>\n#include <QTimer>\n\nvoid TestIDE::testProject106Good()\n{\n    TestMocker mock(QString(MINIZINC_IDE_PATH) + \"/data/project/project-106-good.mzp\");\n    MainWindow* w = mock.mw();\n    QVERIFY(w->currentModelFile().endsWith(\"model1.mzn\"));\n    QCOMPARE(w->codeEditors().size(), 2);\n    QCOMPARE(w->getProject().modelFiles().size(), 2);\n    QCOMPARE(w->getProject().dataFiles().size(), 2);\n    QCOMPARE(w->getProject().solverConfigurationFiles().size(), 2);\n}\n\nvoid TestIDE::testProject106Bad()\n{\n    QTimer::singleShot(200, this, [=] () {\n        auto* message = qobject_cast<QMessageBox*>(QApplication::activeModalWidget());\n        QVERIFY(message != nullptr);\n        QVERIFY(message->text().startsWith(\"Failed to lookup solver invalid.solver@1.0.0\"));\n        message->accept();\n\n        QTimer::singleShot(200, this, [=] () {\n            auto* message = qobject_cast<QMessageBox*>(QApplication::activeModalWidget());\n            QVERIFY(message != nullptr);\n            QVERIFY(message->text().startsWith(\"The file file/does/not/exist.mzn could not be found\"));\n            message->accept();\n        });\n    });\n\n    TestMocker mock(QString(MINIZINC_IDE_PATH) + \"/data/project/project-106-bad.mzp\");\n    MainWindow* w = mock.mw();\n    QVERIFY(w->currentModelFile().endsWith(\"model1.mzn\"));\n    QCOMPARE(w->codeEditors().size(), 2);\n    QCOMPARE(w->getProject().modelFiles().size(), 2);\n    QCOMPARE(w->getProject().dataFiles().size(), 2);\n    QCOMPARE(w->getProject().solverConfigurationFiles().size(), 3);\n}\n\nvoid TestIDE::testProject105Good()\n{\n    TestMocker mock(QString(MINIZINC_IDE_PATH) + \"/data/project/project-105-good.mzp\");\n    MainWindow* w = mock.mw();\n    QVERIFY(w->currentModelFile().endsWith(\"model1.mzn\"));\n    QCOMPARE(w->codeEditors().size(), 2);\n    QCOMPARE(w->getProject().modelFiles().size(), 2);\n    QCOMPARE(w->getProject().dataFiles().size(), 2);\n\n    QTimer::singleShot(200, this, [=] () {\n        auto* message = qobject_cast<QMessageBox*>(QApplication::activeModalWidget());\n        QVERIFY(message!= nullptr);\n        QVERIFY(message->text().startsWith(\"There are modified solver configurations\"));\n        message->accept();\n    });\n}\n\nvoid TestIDE::testProject105Bad()\n{\n    QTimer::singleShot(200, this, [=] () {\n        auto* message = qobject_cast<QMessageBox*>(QApplication::activeModalWidget());\n        QVERIFY(message != nullptr);\n        QVERIFY(message->text().contains(\"Failed to lookup solver does.not.exist@0.1.0\"));\n        QVERIFY(message->text().contains(\"The file file/does/not/exist.mzn could not be found\"));\n        message->accept();\n    });\n\n    TestMocker mock(QString(MINIZINC_IDE_PATH) + \"/data/project/project-105-bad.mzp\");\n    MainWindow* w = mock.mw();\n    QVERIFY(w->currentModelFile().endsWith(\"model1.mzn\"));\n    QCOMPARE(w->codeEditors().size(), 2);\n    QCOMPARE(w->getProject().modelFiles().size(), 2);\n    QCOMPARE(w->getProject().dataFiles().size(), 2);\n\n    QTimer::singleShot(200, this, [=] () {\n        auto* message = qobject_cast<QMessageBox*>(QApplication::activeModalWidget());\n        QVERIFY(message!= nullptr);\n        QVERIFY(message->text().startsWith(\"There are modified solver configurations\"));\n        message->accept();\n    });\n}\n"
  },
  {
    "path": "tests/tests.pro",
    "content": "QT += testlib\nCONFIG += testcase no_testcase_installs\n\nDEFINES += MINIZINC_IDE_PATH=\\\\\\\"$$PWD\\\\\\\"\nDEFINES += MINIZINC_IDE_TESTING\n\nTEMPLATE = app\n\nINCLUDEPATH += \\\n    $$PWD/.. \\\n    $$PWD/../MiniZincIDE\n\nSOURCES += \\\n    testcpprofier.cpp \\\n    testdiff.cpp \\\n    testide.cpp \\\n    testeditor.cpp \\\n    testmooc.cpp \\\n    testproject.cpp\n\nHEADERS += \\\n    testide.h\n\ninclude($$PWD/../MiniZincIDE/MiniZincIDE.pri)\n"
  }
]