pax_global_header00006660000000000000000000000064150340221230014502gustar00rootroot0000000000000052 comment=29ab4983d92f31fe47dc9f59d2a86843a60ccde2 sight-25.1.0/000077500000000000000000000000001503402212300127055ustar00rootroot00000000000000sight-25.1.0/.clang-tidy000066400000000000000000000117401503402212300147440ustar00rootroot00000000000000--- Checks: '*,-llvm-header-guard,-llvm-include-order,-llvmlibc-*,-fuchsia-*,-altera-struct-pack-align,-modernize-use-trailing-return-type,-misc-non-private-member-variables-in-classes,-google-readability-namespace-comments,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-google-readability-todo,-cppcoreguidelines-special-member-functions,-hicpp-special-member-functions,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-altera-unroll-loops,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-google-default-arguments,-altera-id-dependent-backward-branch,-cppcoreguidelines-pro-type-reinterpret-cast,-cert-err58-cpp,-bugprone-easily-swappable-parameters,-cppcoreguidelines-owning-memory,-misc-no-recursion,-concurrency-mt-unsafe,-abseil-*,-clang-analyzer-cplusplus.NewDelete,-bugprone-exception-escape,-cppcoreguidelines-pro-type-static-cast-downcast,-cppcoreguidelines-pro-type-union-access,-readability-redundant-access-specifiers,-readability-function-cognitive-complexity,-clang-analyzer-cplusplus.NewDeleteLeaks,-android-cloexec-fopen,-hicpp-signed-bitwise,-bugprone-unhandled-exception-at-new,-openmp-use-default-none,-performance-no-int-to-ptr,-clang-diagnostic-*,-google-readability-casting,-readability-identifier-length,-clang-analyzer-cplusplus.StringChecker,-readability-redundant-string-cstr,-performance-unnecessary-value-param,-performance-inefficient-string-concatenation,-misc-include-cleaner,-misc-const-correctness,-cppcoreguidelines-avoid-do-while,-performance-avoid-endl,-bugprone-unchecked-optional-access,-misc-use-anonymous-namespace,-readability-simplify-boolean-expr,-bugprone-empty-catch,-cppcoreguidelines-avoid-const-or-ref-data-members,-bugprone-assignment-in-if-condition,-clang-analyzer-optin.core.EnumCastOutOfRange,-readability-avoid-nested-conditional-operator,-misc-header-include-cycle' WarningsAsErrors: '*' CheckOptions: - key: readability-simplify-boolean-expr.ChainedConditionalReturn value: true - key: readability-simplify-boolean-expr.ChainedConditionalAssignment value: true - key: google-runtime-int.UnsignedTypePrefix value: std::uint - key: google-runtime-int.SignedTypePrefix value: std::int - key: google-runtime-int.TypeSuffix value: _t - key: cppcoreguidelines-init-variables.MathHeader value: - key: modernize-loop-convert.MinConfidence value: risky - key: bugprone-misplaced-widening-cast.CheckImplicitCasts value: true - key: cppcoreguidelines-macro-usage.AllowedRegexp value: ^ASSERT* - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic value: true - key: bugprone-assert-side-effect.AssertMacros value: SIGHT_ASSERT - key: bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression value: true - key: bugprone-suspicious-enum-usage.StrictMode value: 1 - key: readability-identifier-naming.ClassCase value: lower_case - key: readability-identifier-naming.ClassMemberCase value: lower_case - key: readability-identifier-naming.ClassMemberIgnoredRegexp value: (U?INT\d\d?)|FLOAT|DOUBLE - key: readability-identifier-naming.ClassMemberPrefix value: s_ - key: readability-identifier-naming.ClassMethodCase value: lower_case - key: readability-identifier-naming.ConstantMemberCase value: lower_case - key: readability-identifier-naming.EnumCase value: lower_case - key: readability-identifier-naming.EnumConstantCase value: lower_case - key: readability-identifier-naming.EnumConstantIgnoredRegexp value: (in)|(out)|(inout) - key: readability-identifier-naming.FunctionCase value: lower_case - key: readability-identifier-naming.GlobalConstantCase value: UPPER_CASE - key: readability-identifier-naming.MacroDefinitionCase value: UPPER_CASE - key: readability-identifier-naming.MemberCase value: lower_case - key: readability-identifier-naming.MethodCase value: lower_case - key: readability-identifier-naming.MethodIgnoredRegexp value: New - key: readability-identifier-naming.NamespaceCase value: lower_case - key: readability-identifier-naming.ParameterCase value: lower_case - key: readability-identifier-naming.ParameterPrefix value: _ - key: readability-identifier-naming.PrivateMemberCase value: lower_case - key: readability-identifier-naming.PrivateMemberPrefix value: m_ - key: readability-identifier-naming.StaticConstantCase value: UPPER_CASE - key: readability-identifier-naming.StaticConstantPrefix value: s_ - key: readability-identifier-naming.TypeAliasCase value: lower_case - key: readability-identifier-naming.TypeAliasIgnoredRegexp value: (c?[usw]ptr) - key: readability-identifier-naming.VariableCase value: lower_case ... sight-25.1.0/.githooks/000077500000000000000000000000001503402212300146125ustar00rootroot00000000000000sight-25.1.0/.githooks/commit-msg000077500000000000000000000017151503402212300166200ustar00rootroot00000000000000#!/usr/bin/env python import re import sys commit_msg_path = sys.argv[1] commit_msg_help = """ Required commit message: type(scope): description - type: enh, feat, fix, refactor - scope: build, ci, core, doc, filter, geometry, io, navigation, test, ui, viz or type: description - type: merge, misc, style """ with open(commit_msg_path) as commit_msg_file: commit_msg_content = commit_msg_file.readline() message_regex = r'(?:(?Penh|feat|fix|refactor)\((?Pbuild|ci|core|doc|filter|geometry|io|navigation|test|ui|viz)\)|(?Pmerge|misc|style)): (?P[a-z].*?(?:\b|[\]\)\'\"\`])$)' if not re.match(message_regex, commit_msg_content): sys.stderr.write("\n'" + commit_msg_content.strip() + "' message doesn't follow commit rules.\n") sys.stderr.write(commit_msg_help) sys.stderr.write('Take a look at the project push rules regex, and experiment https://regex101.com\n') sys.exit(1) sight-25.1.0/.gitignore000066400000000000000000000010071503402212300146730ustar00rootroot00000000000000# use glob syntax. syntax: glob # vim swap file *.swp # eclipse stuff *.cproject *.project # python *.pyc *.pyo # tmp files .*~ *.autosave # qtcreator cmake preferences *.txt.user* # reject and backup file *.rej *.orig # macos .DS_Store # VisualStudio .vs/ CMakeSettings\.json # VSCode *.code-workspace .vscode/ /build/ /.build/ # JetBrains IDEs .idea/ # Just for the CI sight-git # various logs *.log # clangd .cache/clangd compile_commands.json CMakeUserPresets.json # coverage .coverage cobertura-coverage*.xml sight-25.1.0/.gitlab-ci.yml000066400000000000000000000447501503402212300153530ustar00rootroot00000000000000stages: - lint - prebuild - build - deploy-test - deploy include: - project: "sight/sight-gitlab" ref: bb3f5b594b8a75e68e8d5d10042313a5bdbbf664 file: "/.templates/deploy.yml" - project: "sight/sight-gitlab" ref: bb3f5b594b8a75e68e8d5d10042313a5bdbbf664 file: "/.templates/build.yml" lint:ubuntu-24.04: extends: .lint variables: ENABLE_CLANG_TIDY: "ON" tags: - linux-power-runner .linux_build: extends: .linux_before stage: build needs: - job: lint:ubuntu-24.04 optional: true variables: SIGHT_BUILD_DOC: "OFF" SIGHT_BUILD_PACKAGES: "OFF" SIGHT_BUILD_SDK: "OFF" SIGHT_ENABLE_REALSENSE: "OFF" SIGHT_ENABLE_GDB: "OFF" TEST_DATA_REF: "ace14232f" script: # Reset the modified time of all files to improve ccache performance - /usr/lib/git-core/git-restore-mtime --force --skip-missing --commit-time # Print CCache statistics - ccache -svv # Launch CMake - cd $CI_PROJECT_DIR/.build - > cmake $CI_PROJECT_DIR -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CUDA_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=$CI_PROJECT_DIR/.install -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DSIGHT_ARCH=sandybridge -DSIGHT_BUILD_TESTS=ON -DSIGHT_BUILD_DOC=${SIGHT_BUILD_DOC} -DSIGHT_DEPS_ROOT_DIRECTORY=/cache/.sight-deps -DSIGHT_ENABLE_REALSENSE=${SIGHT_ENABLE_REALSENSE} -DSIGHT_ENABLE_COVERAGE=${SIGHT_ENABLE_COVERAGE} -DSIGHT_ENABLE_GDB=${SIGHT_ENABLE_GDB} -DSIGHT_BUILD_MANPAGES=ON # Touch all generated files to improve CCache performance - find . -type f -name '*.?pp' -o -name '*.?xx' -exec touch -t 197001010000 {} \; # Build - ninja -j${RUNNER_THREADS} | tee output.log # Generate metrics information # Note: beware when there is no warning, grep returns false... - nb_warnings=$(grep -c "warning:" output.log || true) - nb_deprecated=$(grep -ce "-Wdeprecated-declarations" output.log || true) - echo -e "Cpp_Warnings $nb_warnings\nCpp_Deprecated_declaration $nb_deprecated" >metrics.txt # Print CCache statistics (Cache hit rate should have raised) - ccache -svv # Clone sight-data. - SIGHT_DATA_DIR=$(cmake -DOUTPUT_FOLDER=/cache/sight-data -DBRANCH=${TEST_DATA_REF} -P "${CI_PROJECT_DIR}/cmake/build/download_test_data.cmake" 2>&1 | grep sight-data) - export FWTEST_DATA_DIR=${SIGHT_DATA_DIR} - export IMAGE_OUTPUT_PATH=$CI_PROJECT_DIR/.build/testImages - mkdir -p $IMAGE_OUTPUT_PATH # Launch tests - > if [ "$SIGHT_ENABLE_COVERAGE" = "ON" ] || [ "$BUILD_TYPE" = "Debug" ]; then TIMEOUT="1200" else TIMEOUT="600" fi - ctest -E "(uit)|(dicom)" --timeout $TIMEOUT --output-on-failure -O ctest1.log -j10 --output-junit junit1.xml --repeat until-pass:2 - ctest -R "(dicom)" --timeout $TIMEOUT --output-on-failure -O ctest2.log -j10 --output-junit junit2.xml --repeat until-pass:2 - ctest -R "(uit)" --timeout $TIMEOUT --output-on-failure -O ctest3.log -j10 --output-junit junit3.xml --repeat until-pass:2 # Build documentation if needed - > if [ "${SIGHT_BUILD_DOC}" == "ON" ]; then ninja -j${RUNNER_THREADS} doc fi - > if [ "${SIGHT_BUILD_PACKAGES}" == "ON" ]; then ninja -j${RUNNER_THREADS} sight_viewer_package rm -rf ${CI_PROJECT_DIR}/.install ninja -j${RUNNER_THREADS} sight_calibrator_package rm -rf ${CI_PROJECT_DIR}/.install fi - > if [ "${SIGHT_BUILD_SDK}" == "ON" ]; then ninja -j${RUNNER_THREADS} install ninja -j${RUNNER_THREADS} package fi - > if [ "${SIGHT_ENABLE_COVERAGE}" == "ON" ]; then ninja coverage fi artifacts: &linux_build_artifacts when: always paths: - .build/ctest*.log - .build/Documentation/Doxygen/ - .build/packages/*.tar.zst - .build/coverage - .build/bin/*.core - .build/testImages reports: junit: .build/junit*.xml metrics: .build/metrics.txt .linux_deploy_test: image: "${SIGHT_UBUNTU_24_IMAGE}" stage: deploy-test variables: BUILD_TYPE: "Debug" SIGHT_TEST_PACKAGES: "OFF" SIGHT_TEST_SDK: "OFF" script: - cd .build - > if [ "${SIGHT_TEST_PACKAGES}" == "ON" ]; then tar -xf packages/sight_viewer-*.tar.zst echo Testing sight_viewer: timeout 5s xvfb-run -a sight_viewer-*/bin/sight_viewer || [ $? -eq 124 ] tar -xf packages/sight_calibrator-*.tar.zst echo Testing sight_calibrator: timeout 5s xvfb-run -a sight_calibrator-*/bin/sight_calibrator || [ $? -eq 124 ] fi - > if [ "${SIGHT_TEST_SDK}" == "ON" ]; then tar -xf packages/sight-*.tar.zst for program in sight-*/bin/{ex*,tuto*,sight_viewer,sight_calibrator}; do echo Testing $(basename $program): timeout 5s xvfb-run -a $program || [ $? -eq 124 ] done for program in sight-*/bin/{aruco_marker,dicom_anonymizer,network_proxy}; do echo Testing $(basename $program): $program --help done fi build:ubuntu-24.04-debug-gcc: extends: .linux_build variables: BUILD_TYPE: "Debug" SIGHT_BUILD_SDK: "ON" SIGHT_ENABLE_GDB: "ON" CC: "gcc" CXX: "g++" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE !~ "/^Draft.*/" - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" build:ubuntu-24.04-release-gcc: extends: .linux_build variables: BUILD_TYPE: "Release" SIGHT_BUILD_SDK: "ON" SIGHT_BUILD_PACKAGES: "ON" SIGHT_IGNORE_UNSTABLE_TESTS: 1 CC: "gcc" CXX: "g++" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE !~ "/^Draft.*/" - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" build:ubuntu-24.04-RelWithDebInfo-clang: extends: .linux_build variables: BUILD_TYPE: "RelWithDebInfo" SIGHT_IGNORE_UNSTABLE_TESTS: 1 SIGHT_ENABLE_GDB: "ON" SIGHT_BUILD_DOC: "ON" CC: "clang" CXX: "clang++" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE !~ "/^Draft.*/" - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" build:ubuntu-24.04-coverage-gcc: extends: .linux_build variables: BUILD_TYPE: "RelWithDebInfo" SIGHT_ENABLE_COVERAGE: "ON" SIGHT_IGNORE_UNSTABLE_TESTS: 1 CC: "gcc" CXX: "g++" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" deploy-test:linux-debug: extends: .linux_deploy_test needs: - job: build:ubuntu-24.04-debug-gcc artifacts: true variables: BUILD_TYPE: "Debug" SIGHT_TEST_SDK: "ON" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE !~ "/^Draft.*/" - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" deploy:linux-debug: extends: .linux_deploy needs: - job: build:ubuntu-24.04-debug-gcc artifacts: true - job: deploy-test:linux-debug variables: APPS: "sight-" URL: "IRCAD%20-%20Open/" rules: - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" deploy-test:linux-release: extends: .linux_deploy_test needs: - job: build:ubuntu-24.04-release-gcc artifacts: true variables: BUILD_TYPE: "Release" SIGHT_TEST_PACKAGES: "ON" SIGHT_TEST_SDK: "ON" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE !~ "/^Draft.*/" - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" deploy:linux-release: extends: .linux_deploy needs: - job: build:ubuntu-24.04-release-gcc artifacts: true - job: deploy-test:linux-release variables: APPS: "sight-" URL: "IRCAD%20-%20Open/" rules: - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" deploy:apps-linux-release: extends: .linux_deploy needs: - job: build:ubuntu-24.04-release-gcc artifacts: true - job: deploy-test:linux-release variables: APPS: "sight_viewer,sight_calibrator" URL: "IRCAD%20-%20Open/" rules: - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" .windows_build: extends: .windows_before needs: - job: lint:ubuntu-24.04 optional: true - job: prebuild:windows variables: TOOLCHAIN: "scripts\\buildsystems\\vcpkg.cmake" SIGHT_BUILD_PACKAGES: "OFF" SIGHT_BUILD_SDK: "OFF" MSVC_VERSION: "17" CUDA_VERSION: "12_5" TEST_DATA_REF: "ace14232f" stage: build script: # Print CCache statistics - ccache -svv # Get the package_name name from our CMake script - $PACKAGE_NAME = cmd /c cmake -DGET_ARCHIVE_FOLDER=ON -P "${env:CI_PROJECT_DIR}\cmake\build\download_deps.cmake" '2>&1' # Configure the project. - cd "${env:CI_PROJECT_DIR}/build" - cmake "$env:CI_PROJECT_DIR" -G Ninja -DCMAKE_TOOLCHAIN_FILE="$CACHE\$PACKAGE_NAME\$TOOLCHAIN" -DCMAKE_INSTALL_PREFIX="$env:CI_PROJECT_DIR/install" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" -DSIGHT_BUILD_TESTS=ON -DCMAKE_CUDA_COMPILER_LAUNCHER="ccache" -DCMAKE_C_COMPILER_LAUNCHER="ccache" -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" # Build the project - ninja # Print CCache statistics (Cache hit rate should have raised) - ccache -svv # Clone sight-data. - cmd /c cmake -DOUTPUT_FOLDER="$CACHE\sight-data" -DBRANCH="$TEST_DATA_REF" -P "${env:CI_PROJECT_DIR}\cmake\build\download_test_data.cmake" - $env:FWTEST_DATA_DIR=cmd /c cmake -DOUTPUT_FOLDER="$CACHE\sight-data" -DBRANCH="$TEST_DATA_REF" -P "${env:CI_PROJECT_DIR}\cmake\build\download_test_data.cmake" '2>&1' | Select-String sight-data - $env:IMAGE_OUTPUT_PATH="${env:CI_PROJECT_DIR}/build/testImages" - md "${env:IMAGE_OUTPUT_PATH}" - $env:DISABLE_ABORT_DIALOG="1" - | if ("${SIGHT_ENABLE_COVERAGE}" -eq "ON" -or "${BUILD_TYPE}" -eq "Debug") { $TIMEOUT="1200" } else { $TIMEOUT="600" } # Launch tests - ctest -E "(uit)|(dicom)" --timeout $TIMEOUT --output-on-failure -O ctest1.log -j6 --output-junit junit1.xml --repeat until-pass:2 - ctest -R "(dicom)" --timeout $TIMEOUT --output-on-failure -O ctest2.log -j6 --output-junit junit2.xml --repeat until-pass:2 - ctest -R "(uit)" --timeout $TIMEOUT --output-on-failure -O ctest3.log -j6 --output-junit junit3.xml --repeat until-pass:2 - | if ("${SIGHT_BUILD_PACKAGES}" -eq "ON") { ninja sight_viewer_package (Remove-Item -Path "${env:CI_PROJECT_DIR}/install" -Force -Recurse -ErrorAction Ignore) ninja sight_calibrator_package (Remove-Item -Path "${env:CI_PROJECT_DIR}/install" -Force -Recurse -ErrorAction Ignore) } - | if ("${SIGHT_BUILD_SDK}" -eq "ON") { ninja install # Check if the package is relocatable Try { if (Select-String -Pattern "[A-Z]:[\/\\]\w+" -Path "${env:CI_PROJECT_DIR}/install/lib/cmake/sight/*.cmake") { echo "The package is not relocatable, some absolute paths are present" exit 1 } else { echo "The package is relocatable, no absolute path found." } } Catch { echo "Error when looking for absolute paths, see output below." echo $Error[0] exit 1 } ninja package } artifacts: when: always name: "${env:CI_JOB_NAME}-${env:CI_COMMIT_REF_SLUG}" paths: - build/ctest*.log - build/fwTest.log - build/packages/*.exe - build/packages/sight-*.zip - build/testImages reports: junit: build/junit.xml .windows_sight_deploy_test: extends: .windows_deploy_test script: - $PACKAGE_NAME = cmd /c cmake -DGET_ARCHIVE_FOLDER=ON -P "${env:CI_PROJECT_DIR}\cmake\build\download_deps.cmake" '2>&1' - cd build - | if("${SIGHT_TEST_PACKAGES}" -eq "ON"){ Start-Process -Wait .\packages\sight_viewer-*.exe /S,/D=${pwd}\sight_viewer Start-SightProcess -path "sight_viewer\bin\sight_viewer.bat" Start-Process -Wait .\packages\sight_calibrator-*.exe /S,/D=${pwd}\sight_calibrator Start-SightProcess -path "sight_calibrator\bin\sight_calibrator.bat" } - | Expand-Archive packages/sight-*.zip if("${SIGHT_TEST_SDK}" -eq "ON"){ if ( ${BUILD_TYPE} -eq "Debug" ) { $env:PATH = "$CACHE\$PACKAGE_NAME\installed\x64-windows\debug\bin;$env:PATH" } else { $env:PATH = "$CACHE\$PACKAGE_NAME\installed\x64-windows\bin\;$env:PATH" } cd sight-*/*/bin ForEach($program in Get-ChildItem ex*.bat,tuto*.bat,sight_viewer.bat,sight_calibrator.bat){ $programName = Split-Path -Leaf $program Write-Host Testing $programName Start-SightProcess -path $program } ForEach($program in Get-ChildItem aruco_marker.exe,dicom_anonymizer.exe){ $programName = Split-Path -Leaf $program Write-Host Testing $programName Start-Process -NoNewWindow -Wait $program --help } } build:windows-debug: extends: .windows_build variables: BUILD_TYPE: "Debug" SIGHT_BUILD_SDK: "ON" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE !~ "/^Draft.*/" - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" build:windows-release: extends: .windows_build variables: BUILD_TYPE: "Release" SIGHT_BUILD_PACKAGES: "ON" SIGHT_BUILD_SDK: "ON" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE !~ "/^Draft.*/" - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" build:windows-release-draft: extends: .windows_build variables: BUILD_TYPE: "Release" SIGHT_BUILD_PACKAGES: "OFF" SIGHT_BUILD_SDK: "OFF" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE =~ "/^Draft.*/" deploy-test:windows-debug: extends: .windows_sight_deploy_test needs: - job: build:windows-debug artifacts: true variables: BUILD_TYPE: "Debug" SIGHT_TEST_SDK: "ON" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE !~ "/^Draft.*/" - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" deploy:windows-debug: extends: .windows_deploy needs: - job: build:windows-debug artifacts: true - job: deploy-test:windows-debug variables: APPS: "sight-" URL: "IRCAD%20-%20Open/" rules: - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" deploy-test:windows-release: extends: .windows_sight_deploy_test needs: - job: build:windows-release artifacts: true variables: BUILD_TYPE: "Release" SIGHT_TEST_PACKAGES: "ON" SIGHT_TEST_SDK: "ON" rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE !~ "/^Draft.*/" - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" deploy:windows-release: extends: .windows_deploy needs: - job: build:windows-release artifacts: true - job: deploy-test:windows-release variables: APPS: "sight-" URL: "IRCAD%20-%20Open/" rules: - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" deploy:apps-windows-release: extends: .windows_deploy needs: - job: build:windows-release artifacts: true - job: deploy-test:windows-release variables: APPS: "sight_viewer,sight_calibrator" URL: "IRCAD%20-%20Open/" rules: - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev" deploy:linux-coverage-analysis: needs: - job: build:ubuntu-24.04-coverage-gcc artifacts: true image: "${COVERAGE_IMAGE}" variables: SIGHT_GIT_BRANCH: "dev" stage: deploy before_script: - git clone --depth 1 https://gitlab-ci-token:${CI_JOB_TOKEN}@git.ircad.fr/Sight/sight-git.git -b $SIGHT_GIT_BRANCH $CI_PROJECT_DIR/.build/sight-git script: - $CI_PROJECT_DIR/.build/sight-git/sight-cov.py ${CI_PROJECT_NAME} ${CI_MERGE_REQUEST_IID} --diff --html --token_config - $CI_PROJECT_DIR/.build/sight-git/coverage-commentator.py ${CI_PROJECT_NAME} ${CI_MERGE_REQUEST_IID} ${CI_JOB_ID} rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TITLE !~ "/^Draft.*/" artifacts: paths: - $CI_PROJECT_DIR/.coverage tags: - linux-power-runner when: always # Pages must be deployed in a single job, named "pages" pages: image: "${SIGHT_UBUNTU_24_IMAGE}" stage: deploy needs: - job: build:ubuntu-24.04-RelWithDebInfo-clang - job: build:ubuntu-24.04-coverage-gcc artifacts: true script: - mv .build/Documentation/Doxygen/html/ public/ - mv .build/coverage/ public/coverage artifacts: paths: - public only: - dev sight-25.1.0/.gitlab/000077500000000000000000000000001503402212300142255ustar00rootroot00000000000000sight-25.1.0/.gitlab/CODEOWNERS000066400000000000000000000003251503402212300156200ustar00rootroot00000000000000[Build] /cmake @fbridault [Visualisation] /lib/viz/ @fbridault /module/viz/ @fbridault [Serialisation] lib/io/bitmap @dweckmann lib/io/session @dweckmann module/io/bitmap @dweckmann module/io/session @dweckmann sight-25.1.0/.gitlab/issue_templates/000077500000000000000000000000001503402212300174335ustar00rootroot00000000000000sight-25.1.0/.gitlab/issue_templates/Bug.md000066400000000000000000000014451503402212300204760ustar00rootroot00000000000000## Description _Summarize the bug encountered concisely._ _Optionally, you may give directions about how to address the issue._ _Switch severity to minor if the bug is not crash or a major malfunction._ /label ~"Severity::major" ## Steps to reproduce _How one can reproduce the issue - this is very important._ _If the bug depends on a particular environment or platform, please give all relevant details._ _Paste any relevant logs. Please use code blocks (```) to format these._ ## Functional specifications _If there is any change to the workflow, UX/UI design, screenshots, etc..., please describe it here._ ## Technical specifications _Details of the implementation of the fix._ ## Test plan _Describe how you will verify that the bug is fixed._ /label ~"Type::bugfix" ~"Priority::medium" sight-25.1.0/.gitlab/issue_templates/Enhancement.md000066400000000000000000000007151503402212300222050ustar00rootroot00000000000000## Description _Describe the existing feature and what is the enhancement, use cases, benefits, and goals._ _Optionally, you may give directions about how to address the issue._ ## Functional specifications _Workflow, UX/UI design, screenshots, etc..._ ## Technical specifications _Details of the implementation._ ## Test plan _Describe how you will verify that the implementation meets specifications._ /label ~"Type::enhancement" ~"Priority::medium" sight-25.1.0/.gitlab/issue_templates/Feature.md000066400000000000000000000006741503402212300213570ustar00rootroot00000000000000## Description _Describe the need, what would be the feature, use cases, benefits, and goals._ _Optionally, you may give directions about how to address the issue._ ## Functional specifications _Workflow, UX/UI design, screenshots, etc..._ ## Technical specifications _Details of the implementation._ ## Test plan _Describe how you will verify that the implementation meets specifications._ /label ~"Type::feature" ~"Priority::medium" sight-25.1.0/.gitlab/issue_templates/Refactor.md000066400000000000000000000011021503402212300215140ustar00rootroot00000000000000## Description _Describe the existing code and why there is a need for a refactor, benefits, and goals._ _Optionally, you may give directions about how to address the issue._ ## Functional specifications _Usually a refactor does not alter functional specifications,_ _but if there is any change to the workflow, UX/UI design, screenshots, etc..., please describe it here._ ## Technical specifications _Details of the implementation._ ## Test plan _Describe how you will verify that the implementation meets specifications._ /label ~"Type::refactor" ~"Priority::medium" sight-25.1.0/.gitlab/issue_templates/Research.md000066400000000000000000000021031503402212300215050ustar00rootroot00000000000000# Goal and general information _Brief description of the goal of this algorithm research task._ # Specification _Specifications include functional, performance, language and data. The algorithms should focus on solving a specific problem with well-defined interfaces._ # Background _Background check to see what algorithms exist for solving the problem. Do they meet our needs? Have better new algorithms been published?_ # Data _Collection of data required for testing (or training in the case of learning-based algorithms). Formatting of data so that it can be inputted to the algorithm._ # Evaluation _Evaluation framework is used to test the algorithm for correctness and meeting performance specification._ # Baseline _Existing algorithm selection, implementation and evaluation._ # Prototyping _The main algorithm research cycle: evaluate performance, make improvements, repeat._ # Integration _Integration of algorithm code into production system._ # Communication _Communicate how the algorithm works (internal and/or article publication)._ /label ~"Type:research" sight-25.1.0/.gitlab/merge_request_templates/000077500000000000000000000000001503402212300211525ustar00rootroot00000000000000sight-25.1.0/.gitlab/merge_request_templates/default.md000066400000000000000000000014411503402212300231200ustar00rootroot00000000000000## Description _Briefly describe what this merge request is about_ ### Breaking change(s) _Optional, fill this section if there is a breaking change or remove it otherwise_ /label ~"Breaking change" ### Results _Optional, extensive description of results, screenshots, performances,..._ ## How to test it? _Describe how to test this feature step by step_ ### Data _Optional, reference to any specific data needed for testing_ ### Decreased code coverage _If you reduce test coverage, it is mandatory to justify it here_ ## Related issue(s) _If an issue must be closed only by this merge request, use Closes #X_ _If an issue must be closed by this merge request and at least other one, use Relates/Blocks #X._ _In this latter case, the issue should be closed manually after all merges._sight-25.1.0/.sheldonignore000066400000000000000000000004151503402212300155460ustar00rootroot00000000000000/3rd-party /lib/__/data/dicom/attribute.* /lib/__/data/dicom/sop.* /lib/__/data/test/tu/dicom/attribute_test.* /lib/__/data/test/tu/dicom/sop_test.* /lib/io/igtl/patch/ /lib/io/itk/patched_itk_compiler_detection.h /lib/io/itk/compilers/ /lib/io/vtk/patch/vtkSMPTools.h sight-25.1.0/.sight000066400000000000000000000000061503402212300140200ustar00rootroot00000000000000sight sight-25.1.0/3rd-party/000077500000000000000000000000001503402212300145325ustar00rootroot00000000000000sight-25.1.0/3rd-party/.clang-tidy000066400000000000000000000001271503402212300165660ustar00rootroot00000000000000--- # Disable all checks in this folder. Checks: '-*,cppcoreguidelines-avoid-goto' --- sight-25.1.0/3rd-party/CMakeLists.txt000066400000000000000000000000321503402212300172650ustar00rootroot00000000000000add_subdirectory(minizip) sight-25.1.0/3rd-party/minizip/000077500000000000000000000000001503402212300162115ustar00rootroot00000000000000sight-25.1.0/3rd-party/minizip/CMakeLists.txt000066400000000000000000000044671503402212300207640ustar00rootroot00000000000000file(GLOB MINIZIP_SOURCES *.c *.h) add_library(minizip STATIC ${MINIZIP_SOURCES}) target_compile_options(minizip PRIVATE "$<$:-w>$<$:/w>") include(CheckIncludeFile) include(CheckTypeSize) include(CheckFunctionExists) # Check for system includes check_include_file(stdint.h HAVE_STDINT_H) if(HAVE_STDINT_H) target_compile_definitions(minizip PRIVATE "-DHAVE_STDINT_H") endif() check_include_file(inttypes.h HAVE_INTTYPES_H) if(HAVE_INTTYPES_H) target_compile_definitions(minizip PRIVATE "-DHAVE_INTTYPES_H") endif() # Check for large file support check_type_size(off64_t OFF64_T) if(HAVE_OFF64_T) target_compile_definitions(minizip PRIVATE "-D__USE_LARGEFILE64") target_compile_definitions(minizip PRIVATE "-D_LARGEFILE64_SOURCE") endif() # Check for fseeko support check_function_exists(fseeko HAVE_FSEEKO) if(NOT HAVE_FSEEKO) target_compile_definitions(minizip PRIVATE "-DNO_FSEEKO") endif() # ZLIB find_package(ZLIB QUIET REQUIRED) target_link_libraries(minizip PRIVATE ZLIB::ZLIB) target_compile_definitions(minizip PRIVATE "-DHAVE_ZLIB") target_compile_definitions(minizip PRIVATE "-DZLIB_COMPAT") # ZSTD find_package(PkgConfig QUIET) if(PKGCONFIG_FOUND) pkg_check_modules(ZSTD libzstd) endif() if(NOT WIN32 AND ZSTD_FOUND) target_include_directories(minizip SYSTEM PRIVATE ${ZSTD_INCLUDE_DIRS}) target_link_libraries(minizip PRIVATE ${ZSTD_LINK_LIBRARIES}) else() find_package(zstd CONFIG REQUIRED) target_link_libraries(minizip PRIVATE zstd::libzstd_shared) endif() target_compile_definitions(minizip PRIVATE "-DHAVE_ZSTD") # OpenSSL find_package(OpenSSL QUIET REQUIRED COMPONENTS Crypto) target_link_libraries(minizip PRIVATE OpenSSL::Crypto) # Windows specific if(WIN32) target_compile_definitions(minizip PRIVATE "-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE") endif() # Unix specific if(UNIX) # ICONV find_package(Iconv QUIET REQUIRED) target_include_directories(minizip SYSTEM PRIVATE ${Iconv_INCLUDE_DIRS}) target_link_libraries(minizip PRIVATE ${Iconv_LIBRARIES}) target_compile_definitions(minizip PRIVATE "-DHAVE_ICONV") endif() # Setup predefined macros target_compile_definitions(minizip PRIVATE "-DMZ_ZIP_SIGNING") # Include WinZIP AES encryption target_compile_definitions(minizip PRIVATE "-DHAVE_WZAES") sight-25.1.0/3rd-party/minizip/LICENSE000066400000000000000000000015551503402212300172240ustar00rootroot00000000000000Condition of use and distribution are the same as zlib: This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgement in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. sight-25.1.0/3rd-party/minizip/mz.h000066400000000000000000000172771503402212300170260ustar00rootroot00000000000000/* mz.h -- Errors codes, zip flags and magic part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_H #define MZ_H /***************************************************************************/ /* MZ_VERSION */ #define MZ_VERSION ("4.0.1") #define MZ_VERSION_BUILD (040001) /* MZ_ERROR */ #define MZ_OK (0) /* zlib */ #define MZ_STREAM_ERROR (-1) /* zlib */ #define MZ_DATA_ERROR (-3) /* zlib */ #define MZ_MEM_ERROR (-4) /* zlib */ #define MZ_BUF_ERROR (-5) /* zlib */ #define MZ_VERSION_ERROR (-6) /* zlib */ #define MZ_END_OF_LIST (-100) #define MZ_END_OF_STREAM (-101) #define MZ_PARAM_ERROR (-102) #define MZ_FORMAT_ERROR (-103) #define MZ_INTERNAL_ERROR (-104) #define MZ_CRC_ERROR (-105) #define MZ_CRYPT_ERROR (-106) #define MZ_EXIST_ERROR (-107) #define MZ_PASSWORD_ERROR (-108) #define MZ_SUPPORT_ERROR (-109) #define MZ_HASH_ERROR (-110) #define MZ_OPEN_ERROR (-111) #define MZ_CLOSE_ERROR (-112) #define MZ_SEEK_ERROR (-113) #define MZ_TELL_ERROR (-114) #define MZ_READ_ERROR (-115) #define MZ_WRITE_ERROR (-116) #define MZ_SIGN_ERROR (-117) #define MZ_SYMLINK_ERROR (-118) /* MZ_OPEN */ #define MZ_OPEN_MODE_READ (0x01) #define MZ_OPEN_MODE_WRITE (0x02) #define MZ_OPEN_MODE_READWRITE (MZ_OPEN_MODE_READ | MZ_OPEN_MODE_WRITE) #define MZ_OPEN_MODE_APPEND (0x04) #define MZ_OPEN_MODE_CREATE (0x08) #define MZ_OPEN_MODE_EXISTING (0x10) /* MZ_SEEK */ #define MZ_SEEK_SET (0) #define MZ_SEEK_CUR (1) #define MZ_SEEK_END (2) /* MZ_COMPRESS */ #define MZ_COMPRESS_METHOD_STORE (0) #define MZ_COMPRESS_METHOD_DEFLATE (8) #define MZ_COMPRESS_METHOD_BZIP2 (12) #define MZ_COMPRESS_METHOD_LZMA (14) #define MZ_COMPRESS_METHOD_ZSTD (93) #define MZ_COMPRESS_METHOD_XZ (95) #define MZ_COMPRESS_METHOD_AES (99) #define MZ_COMPRESS_LEVEL_DEFAULT (-1) #define MZ_COMPRESS_LEVEL_FAST (2) #define MZ_COMPRESS_LEVEL_NORMAL (6) #define MZ_COMPRESS_LEVEL_BEST (9) /* MZ_ZIP_FLAG */ #define MZ_ZIP_FLAG_ENCRYPTED (1 << 0) #define MZ_ZIP_FLAG_LZMA_EOS_MARKER (1 << 1) #define MZ_ZIP_FLAG_DEFLATE_MAX (1 << 1) #define MZ_ZIP_FLAG_DEFLATE_NORMAL (0) #define MZ_ZIP_FLAG_DEFLATE_FAST (1 << 2) #define MZ_ZIP_FLAG_DEFLATE_SUPER_FAST (MZ_ZIP_FLAG_DEFLATE_FAST | \ MZ_ZIP_FLAG_DEFLATE_MAX) #define MZ_ZIP_FLAG_DATA_DESCRIPTOR (1 << 3) #define MZ_ZIP_FLAG_UTF8 (1 << 11) #define MZ_ZIP_FLAG_MASK_LOCAL_INFO (1 << 13) /* MZ_ZIP_EXTENSION */ #define MZ_ZIP_EXTENSION_ZIP64 (0x0001) #define MZ_ZIP_EXTENSION_NTFS (0x000a) #define MZ_ZIP_EXTENSION_AES (0x9901) #define MZ_ZIP_EXTENSION_UNIX1 (0x000d) #define MZ_ZIP_EXTENSION_SIGN (0x10c5) #define MZ_ZIP_EXTENSION_HASH (0x1a51) #define MZ_ZIP_EXTENSION_CDCD (0xcdcd) /* MZ_ZIP64 */ #define MZ_ZIP64_AUTO (0) #define MZ_ZIP64_FORCE (1) #define MZ_ZIP64_DISABLE (2) /* MZ_HOST_SYSTEM */ #define MZ_HOST_SYSTEM(VERSION_MADEBY) ((uint8_t)(VERSION_MADEBY >> 8)) #define MZ_HOST_SYSTEM_MSDOS (0) #define MZ_HOST_SYSTEM_UNIX (3) #define MZ_HOST_SYSTEM_WINDOWS_NTFS (10) #define MZ_HOST_SYSTEM_RISCOS (13) #define MZ_HOST_SYSTEM_OSX_DARWIN (19) /* MZ_PKCRYPT */ #define MZ_PKCRYPT_HEADER_SIZE (12) /* MZ_AES */ #define MZ_AES_VERSION (1) #define MZ_AES_MODE_ECB (0) #define MZ_AES_MODE_CBC (1) #define MZ_AES_MODE_GCM (2) #define MZ_AES_STRENGTH_128 (1) #define MZ_AES_STRENGTH_192 (2) #define MZ_AES_STRENGTH_256 (3) #define MZ_AES_KEY_LENGTH_MAX (32) #define MZ_AES_BLOCK_SIZE (16) #define MZ_AES_FOOTER_SIZE (10) /* MZ_HASH */ #define MZ_HASH_MD5 (10) #define MZ_HASH_MD5_SIZE (16) #define MZ_HASH_SHA1 (20) #define MZ_HASH_SHA1_SIZE (20) #define MZ_HASH_SHA224 (22) #define MZ_HASH_SHA224_SIZE (28) #define MZ_HASH_SHA256 (23) #define MZ_HASH_SHA256_SIZE (32) #define MZ_HASH_SHA384 (24) #define MZ_HASH_SHA384_SIZE (48) #define MZ_HASH_SHA512 (25) #define MZ_HASH_SHA512_SIZE (64) #define MZ_HASH_MAX_SIZE (256) /* MZ_ENCODING */ #define MZ_ENCODING_CODEPAGE_437 (437) #define MZ_ENCODING_CODEPAGE_932 (932) #define MZ_ENCODING_CODEPAGE_936 (936) #define MZ_ENCODING_CODEPAGE_950 (950) #define MZ_ENCODING_UTF8 (65001) /* MZ_UTILITY */ #define MZ_UNUSED(SYMBOL) ((void)SYMBOL) #if defined(_WIN32) && defined(MZ_EXPORTS) #define MZ_EXPORT __declspec(dllexport) #else #define MZ_EXPORT #endif /***************************************************************************/ #include /* size_t, NULL, malloc */ #include /* time_t, time() */ #include /* memset, strncpy, strlen */ #include #if defined(HAVE_STDINT_H) # include #elif defined(__has_include) # if __has_include() # include # endif #endif #ifndef INT8_MAX typedef signed char int8_t; #endif #ifndef INT16_MAX typedef short int16_t; #endif #ifndef INT32_MAX typedef int int32_t; #endif #ifndef INT64_MAX typedef long long int64_t; #endif #ifndef UINT8_MAX typedef unsigned char uint8_t; #endif #ifndef UINT16_MAX typedef unsigned short uint16_t; #endif #ifndef UINT32_MAX typedef unsigned int uint32_t; #endif #ifndef UINT64_MAX typedef unsigned long long uint64_t; #endif #if defined(HAVE_INTTYPES_H) # include #elif defined(__has_include) # if __has_include() # include # endif #endif #ifndef PRId8 # define PRId8 "hhd" #endif #ifndef PRIu8 # define PRIu8 "hhu" #endif #ifndef PRIx8 # define PRIx8 "hhx" #endif #ifndef PRId16 # define PRId16 "hd" #endif #ifndef PRIu16 # define PRIu16 "hu" #endif #ifndef PRIx16 # define PRIx16 "hx" #endif #ifndef PRId32 # define PRId32 "d" #endif #ifndef PRIu32 # define PRIu32 "u" #endif #ifndef PRIx32 # define PRIx32 "x" #endif #if ULONG_MAX == 0xfffffffful # ifndef PRId64 # define PRId64 "ld" # endif # ifndef PRIu64 # define PRIu64 "lu" # endif # ifndef PRIx64 # define PRIx64 "lx" # endif #else # ifndef PRId64 # define PRId64 "lld" # endif # ifndef PRIu64 # define PRIu64 "llu" # endif # ifndef PRIx64 # define PRIx64 "llx" # endif #endif #ifndef INT16_MAX # define INT16_MAX 32767 #endif #ifndef INT32_MAX # define INT32_MAX 2147483647L #endif #ifndef INT64_MAX # define INT64_MAX 9223372036854775807LL #endif #ifndef UINT16_MAX # define UINT16_MAX 65535U #endif #ifndef UINT32_MAX # define UINT32_MAX 4294967295UL #endif #ifndef UINT64_MAX # define UINT64_MAX 18446744073709551615ULL #endif /***************************************************************************/ #endif sight-25.1.0/3rd-party/minizip/mz_compat.c000066400000000000000000001232201503402212300203460ustar00rootroot00000000000000/* mz_compat.c -- Backwards compatible interface for older versions part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng Copyright (C) 1998-2010 Gilles Vollant https://www.winimage.com/zLibDll/minizip.html This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_os.h" #include "mz_strm.h" #include "mz_strm_mem.h" #include "mz_strm_os.h" #include "mz_strm_zlib.h" #include "mz_zip.h" #include /* SEEK */ #include "mz_compat.h" /***************************************************************************/ typedef struct mz_compat_s { void *stream; void *handle; uint64_t entry_index; int64_t entry_pos; int64_t total_out; } mz_compat; /***************************************************************************/ typedef struct mz_stream_ioapi_s { mz_stream stream; void *handle; zlib_filefunc_def filefunc; zlib_filefunc64_def filefunc64; } mz_stream_ioapi; /***************************************************************************/ static int32_t mz_stream_ioapi_open(void *stream, const char *path, int32_t mode); static int32_t mz_stream_ioapi_is_open(void *stream); static int32_t mz_stream_ioapi_read(void *stream, void *buf, int32_t size); static int32_t mz_stream_ioapi_write(void *stream, const void *buf, int32_t size); static int64_t mz_stream_ioapi_tell(void *stream); static int32_t mz_stream_ioapi_seek(void *stream, int64_t offset, int32_t origin); static int32_t mz_stream_ioapi_close(void *stream); static int32_t mz_stream_ioapi_error(void *stream); static void *mz_stream_ioapi_create(void); static void mz_stream_ioapi_delete(void **stream); /***************************************************************************/ static mz_stream_vtbl mz_stream_ioapi_vtbl = { mz_stream_ioapi_open, mz_stream_ioapi_is_open, mz_stream_ioapi_read, mz_stream_ioapi_write, mz_stream_ioapi_tell, mz_stream_ioapi_seek, mz_stream_ioapi_close, mz_stream_ioapi_error, mz_stream_ioapi_create, mz_stream_ioapi_delete, NULL, NULL }; /***************************************************************************/ static int32_t mz_stream_ioapi_open(void *stream, const char *path, int32_t mode) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream; int32_t ioapi_mode = 0; if ((mode & MZ_OPEN_MODE_READWRITE) == MZ_OPEN_MODE_READ) ioapi_mode = ZLIB_FILEFUNC_MODE_READ; else if (mode & MZ_OPEN_MODE_APPEND) ioapi_mode = ZLIB_FILEFUNC_MODE_EXISTING; else if (mode & MZ_OPEN_MODE_CREATE) ioapi_mode = ZLIB_FILEFUNC_MODE_CREATE; else return MZ_OPEN_ERROR; if (ioapi->filefunc64.zopen64_file) ioapi->handle = ioapi->filefunc64.zopen64_file(ioapi->filefunc64.opaque, path, ioapi_mode); else if (ioapi->filefunc.zopen_file) ioapi->handle = ioapi->filefunc.zopen_file(ioapi->filefunc.opaque, path, ioapi_mode); if (!ioapi->handle) return MZ_PARAM_ERROR; return MZ_OK; } static int32_t mz_stream_ioapi_is_open(void *stream) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream; if (!ioapi->handle) return MZ_OPEN_ERROR; return MZ_OK; } static int32_t mz_stream_ioapi_read(void *stream, void *buf, int32_t size) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream; read_file_func zread = NULL; void *opaque = NULL; if (mz_stream_ioapi_is_open(stream) != MZ_OK) return MZ_OPEN_ERROR; if (ioapi->filefunc64.zread_file) { zread = ioapi->filefunc64.zread_file; opaque = ioapi->filefunc64.opaque; } else if (ioapi->filefunc.zread_file) { zread = ioapi->filefunc.zread_file; opaque = ioapi->filefunc.opaque; } else return MZ_PARAM_ERROR; return zread(opaque, ioapi->handle, buf, size); } static int32_t mz_stream_ioapi_write(void *stream, const void *buf, int32_t size) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream; write_file_func zwrite = NULL; int32_t written = 0; void *opaque = NULL; if (mz_stream_ioapi_is_open(stream) != MZ_OK) return MZ_OPEN_ERROR; if (ioapi->filefunc64.zwrite_file) { zwrite = ioapi->filefunc64.zwrite_file; opaque = ioapi->filefunc64.opaque; } else if (ioapi->filefunc.zwrite_file) { zwrite = ioapi->filefunc.zwrite_file; opaque = ioapi->filefunc.opaque; } else return MZ_PARAM_ERROR; written = zwrite(opaque, ioapi->handle, buf, size); return written; } static int64_t mz_stream_ioapi_tell(void *stream) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream; if (mz_stream_ioapi_is_open(stream) != MZ_OK) return MZ_OPEN_ERROR; if (ioapi->filefunc64.ztell64_file) return ioapi->filefunc64.ztell64_file(ioapi->filefunc64.opaque, ioapi->handle); else if (ioapi->filefunc.ztell_file) return ioapi->filefunc.ztell_file(ioapi->filefunc.opaque, ioapi->handle); return MZ_INTERNAL_ERROR; } static int32_t mz_stream_ioapi_seek(void *stream, int64_t offset, int32_t origin) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream; if (mz_stream_ioapi_is_open(stream) != MZ_OK) return MZ_OPEN_ERROR; if (ioapi->filefunc64.zseek64_file) { if (ioapi->filefunc64.zseek64_file(ioapi->filefunc64.opaque, ioapi->handle, offset, origin) != 0) return MZ_INTERNAL_ERROR; } else if (ioapi->filefunc.zseek_file) { if (ioapi->filefunc.zseek_file(ioapi->filefunc.opaque, ioapi->handle, (int32_t)offset, origin) != 0) return MZ_INTERNAL_ERROR; } else return MZ_PARAM_ERROR; return MZ_OK; } static int32_t mz_stream_ioapi_close(void *stream) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream; close_file_func zclose = NULL; void *opaque = NULL; if (mz_stream_ioapi_is_open(stream) != MZ_OK) return MZ_OPEN_ERROR; if (ioapi->filefunc.zclose_file) { zclose = ioapi->filefunc.zclose_file; opaque = ioapi->filefunc.opaque; } else if (ioapi->filefunc64.zclose_file) { zclose = ioapi->filefunc64.zclose_file; opaque = ioapi->filefunc64.opaque; } else return MZ_PARAM_ERROR; if (zclose(opaque, ioapi->handle) != 0) return MZ_CLOSE_ERROR; ioapi->handle = NULL; return MZ_OK; } static int32_t mz_stream_ioapi_error(void *stream) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream; testerror_file_func zerror = NULL; void *opaque = NULL; if (mz_stream_ioapi_is_open(stream) != MZ_OK) return MZ_OPEN_ERROR; if (ioapi->filefunc.zerror_file) { zerror = ioapi->filefunc.zerror_file; opaque = ioapi->filefunc.opaque; } else if (ioapi->filefunc64.zerror_file) { zerror = ioapi->filefunc64.zerror_file; opaque = ioapi->filefunc64.opaque; } else return MZ_PARAM_ERROR; return zerror(opaque, ioapi->handle); } static int32_t mz_stream_ioapi_set_filefunc(void *stream, zlib_filefunc_def *filefunc) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream; memcpy(&ioapi->filefunc, filefunc, sizeof(zlib_filefunc_def)); return MZ_OK; } static int32_t mz_stream_ioapi_set_filefunc64(void *stream, zlib_filefunc64_def *filefunc) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream; memcpy(&ioapi->filefunc64, filefunc, sizeof(zlib_filefunc64_def)); return MZ_OK; } static void *mz_stream_ioapi_create(void) { mz_stream_ioapi *ioapi = (mz_stream_ioapi *)calloc(1, sizeof(mz_stream_ioapi)); if (ioapi) ioapi->stream.vtbl = &mz_stream_ioapi_vtbl; return ioapi; } static void mz_stream_ioapi_delete(void **stream) { mz_stream_ioapi *ioapi = NULL; if (!stream) return; ioapi = (mz_stream_ioapi *)*stream; if (ioapi) free(ioapi); *stream = NULL; } /***************************************************************************/ void fill_fopen_filefunc(zlib_filefunc_def *pzlib_filefunc_def) { /* For 32-bit file support only, compile with MZ_FILE32_API */ if (pzlib_filefunc_def) memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc_def)); } void fill_fopen64_filefunc(zlib_filefunc64_def *pzlib_filefunc_def) { /* All mz_stream_os_* support large files if compilation supports it */ if (pzlib_filefunc_def) memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc64_def)); } void fill_win32_filefunc(zlib_filefunc_def *pzlib_filefunc_def) { /* Handled by mz_stream_os_win32 */ if (pzlib_filefunc_def) memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc_def)); } void fill_win32_filefunc64(zlib_filefunc64_def *pzlib_filefunc_def) { /* Automatically supported in mz_stream_os_win32 */ if (pzlib_filefunc_def) memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc64_def)); } void fill_win32_filefunc64A(zlib_filefunc64_def *pzlib_filefunc_def) { /* Automatically supported in mz_stream_os_win32 */ if (pzlib_filefunc_def) memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc64_def)); } /* NOTE: fill_win32_filefunc64W is no longer necessary since wide-character support is automatically handled by the underlying os stream. Do not pass wide-characters to zipOpen or unzOpen. */ void fill_memory_filefunc(zlib_filefunc_def *pzlib_filefunc_def) { /* Use opaque to indicate which stream interface to create */ if (pzlib_filefunc_def) { memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc_def)); pzlib_filefunc_def->opaque = mz_stream_mem_get_interface(); } } /***************************************************************************/ static int32_t zipConvertAppendToStreamMode(int append) { int32_t mode = MZ_OPEN_MODE_WRITE; switch (append) { case APPEND_STATUS_CREATE: mode |= MZ_OPEN_MODE_CREATE; break; case APPEND_STATUS_CREATEAFTER: mode |= MZ_OPEN_MODE_CREATE | MZ_OPEN_MODE_APPEND; break; case APPEND_STATUS_ADDINZIP: mode |= MZ_OPEN_MODE_READ | MZ_OPEN_MODE_APPEND; break; } return mode; } zipFile zipOpen(const char *path, int append) { return zipOpen2(path, append, NULL, NULL); } zipFile zipOpen64(const void *path, int append) { return zipOpen2(path, append, NULL, NULL); } zipFile zipOpen2(const char *path, int append, const char **globalcomment, zlib_filefunc_def *pzlib_filefunc_def) { zipFile zip = NULL; int32_t mode = zipConvertAppendToStreamMode(append); void *stream = NULL; if (pzlib_filefunc_def) { if (pzlib_filefunc_def->zopen_file) { stream = mz_stream_ioapi_create(); if (!stream) return NULL; mz_stream_ioapi_set_filefunc(stream, pzlib_filefunc_def); } else if (pzlib_filefunc_def->opaque) { stream = mz_stream_create((mz_stream_vtbl *)pzlib_filefunc_def->opaque); if (!stream) return NULL; } } if (!stream) { stream = mz_stream_os_create(); if (!stream) return NULL; } if (mz_stream_open(stream, path, mode) != MZ_OK) { mz_stream_delete(&stream); return NULL; } zip = zipOpen_MZ(stream, append, globalcomment); if (!zip) { mz_stream_delete(&stream); return NULL; } return zip; } zipFile zipOpen2_64(const void *path, int append, const char **globalcomment, zlib_filefunc64_def *pzlib_filefunc_def) { zipFile zip = NULL; int32_t mode = zipConvertAppendToStreamMode(append); void *stream = NULL; if (pzlib_filefunc_def) { if (pzlib_filefunc_def->zopen64_file) { stream = mz_stream_ioapi_create(); if (!stream) return NULL; mz_stream_ioapi_set_filefunc64(stream, pzlib_filefunc_def); } else if (pzlib_filefunc_def->opaque) { stream = mz_stream_create((mz_stream_vtbl *)pzlib_filefunc_def->opaque); if (!stream) return NULL; } } if (!stream) { stream = mz_stream_os_create(); if (!stream) return NULL; } if (mz_stream_open(stream, path, mode) != MZ_OK) { mz_stream_delete(&stream); return NULL; } zip = zipOpen_MZ(stream, append, globalcomment); if (!zip) { mz_stream_delete(&stream); return NULL; } return zip; } zipFile zipOpen_MZ(void *stream, int append, const char **globalcomment) { mz_compat *compat = NULL; int32_t err = MZ_OK; int32_t mode = zipConvertAppendToStreamMode(append); void *handle = NULL; handle = mz_zip_create(); if (!handle) return NULL; err = mz_zip_open(handle, stream, mode); if (err != MZ_OK) { mz_zip_delete(&handle); return NULL; } if (globalcomment) mz_zip_get_comment(handle, globalcomment); compat = (mz_compat *)calloc(1, sizeof(mz_compat)); if (compat) { compat->handle = handle; compat->stream = stream; } else { mz_zip_delete(&handle); } return (zipFile)compat; } void* zipGetHandle_MZ(zipFile file) { mz_compat *compat = (mz_compat *)file; if (!compat) return NULL; return compat->handle; } void* zipGetStream_MZ(zipFile file) { mz_compat *compat = (mz_compat *)file; if (!compat) return NULL; return (void *)compat->stream; } int zipOpenNewFileInZip5(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel, int strategy, const char *password, unsigned long crc_for_crypting, unsigned long version_madeby, unsigned long flag_base, int zip64) { mz_compat *compat = (mz_compat *)file; mz_zip_file file_info; MZ_UNUSED(strategy); MZ_UNUSED(memLevel); MZ_UNUSED(windowBits); MZ_UNUSED(size_extrafield_local); MZ_UNUSED(extrafield_local); MZ_UNUSED(crc_for_crypting); if (!compat) return ZIP_PARAMERROR; memset(&file_info, 0, sizeof(file_info)); if (zipfi) { uint64_t dos_date = 0; if (zipfi->mz_dos_date != 0) dos_date = zipfi->mz_dos_date; else dos_date = mz_zip_tm_to_dosdate(&zipfi->tmz_date); file_info.modified_date = mz_zip_dosdate_to_time_t(dos_date); file_info.external_fa = zipfi->external_fa; file_info.internal_fa = zipfi->internal_fa; } if (!filename) filename = "-"; file_info.compression_method = (uint16_t)compression_method; file_info.filename = filename; /* file_info.extrafield_local = extrafield_local; */ /* file_info.extrafield_local_size = size_extrafield_local; */ file_info.extrafield = extrafield_global; file_info.extrafield_size = size_extrafield_global; file_info.version_madeby = (uint16_t)version_madeby; file_info.comment = comment; if (file_info.comment) file_info.comment_size = (uint16_t)strlen(file_info.comment); file_info.flag = (uint16_t)flag_base; if (zip64) file_info.zip64 = MZ_ZIP64_FORCE; else file_info.zip64 = MZ_ZIP64_DISABLE; #ifdef HAVE_WZAES if (password || (raw && (file_info.flag & MZ_ZIP_FLAG_ENCRYPTED))) file_info.aes_version = MZ_AES_VERSION; #endif return mz_zip_entry_write_open(compat->handle, &file_info, (int16_t)level, (uint8_t)raw, password); } int zipOpenNewFileInZip4_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel, int strategy, const char *password, unsigned long crc_for_crypting, unsigned long version_madeby, unsigned long flag_base, int zip64) { return zipOpenNewFileInZip5(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, compression_method, level, raw, windowBits, memLevel, strategy, password, crc_for_crypting, version_madeby, flag_base, zip64); } int zipOpenNewFileInZip4(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel, int strategy, const char *password, unsigned long crc_for_crypting, unsigned long version_madeby, unsigned long flag_base) { return zipOpenNewFileInZip4_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, compression_method, level, raw, windowBits, memLevel, strategy, password, crc_for_crypting, version_madeby, flag_base, 0); } int zipOpenNewFileInZip3(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel, int strategy, const char *password, unsigned long crc_for_crypting) { return zipOpenNewFileInZip3_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, compression_method, level, raw, windowBits, memLevel, strategy, password, crc_for_crypting, 0); } int zipOpenNewFileInZip3_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel, int strategy, const char *password, uint32_t crc_for_crypting, int zip64) { return zipOpenNewFileInZip4_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, compression_method, level, raw, windowBits, memLevel, strategy, password, crc_for_crypting, MZ_VERSION_MADEBY, 0, zip64); } int zipOpenNewFileInZip2(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw) { return zipOpenNewFileInZip3_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, compression_method, level, raw, 0, 0, 0, NULL, 0, 0); } int zipOpenNewFileInZip2_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int zip64) { return zipOpenNewFileInZip3_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, compression_method, level, raw, 0, 0, 0, NULL, 0, zip64); } int zipOpenNewFileInZip(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level) { return zipOpenNewFileInZip_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, compression_method, level, 0); } int zipOpenNewFileInZip_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int zip64) { return zipOpenNewFileInZip2_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, compression_method, level, 0, zip64); } int zipWriteInFileInZip(zipFile file, const void *buf, uint32_t len) { mz_compat *compat = (mz_compat *)file; int32_t written = 0; if (!compat || len >= INT32_MAX) return ZIP_PARAMERROR; written = mz_zip_entry_write(compat->handle, buf, (int32_t)len); if ((written < 0) || ((uint32_t)written != len)) return ZIP_ERRNO; return ZIP_OK; } int zipCloseFileInZipRaw(zipFile file, unsigned long uncompressed_size, unsigned long crc32) { return zipCloseFileInZipRaw64(file, uncompressed_size, crc32); } int zipCloseFileInZipRaw64(zipFile file, uint64_t uncompressed_size, unsigned long crc32) { mz_compat *compat = (mz_compat *)file; if (!compat) return ZIP_PARAMERROR; return mz_zip_entry_close_raw(compat->handle, (int64_t)uncompressed_size, crc32); } int zipCloseFileInZip(zipFile file) { return zipCloseFileInZip64(file); } int zipCloseFileInZip64(zipFile file) { mz_compat *compat = (mz_compat *)file; if (!compat) return ZIP_PARAMERROR; return mz_zip_entry_close(compat->handle); } int zipClose(zipFile file, const char *global_comment) { return zipClose_64(file, global_comment); } int zipClose_64(zipFile file, const char *global_comment) { return zipClose2_64(file, global_comment, MZ_VERSION_MADEBY); } int zipClose2_64(zipFile file, const char *global_comment, uint16_t version_madeby) { mz_compat *compat = (mz_compat *)file; int32_t err = MZ_OK; if (compat->handle) err = zipClose2_MZ(file, global_comment, version_madeby); if (compat->stream) { mz_stream_close(compat->stream); mz_stream_delete(&compat->stream); } free(compat); return err; } /* Only closes the zip handle, does not close the stream */ int zipClose_MZ(zipFile file, const char *global_comment) { return zipClose2_MZ(file, global_comment, MZ_VERSION_MADEBY); } /* Only closes the zip handle, does not close the stream */ int zipClose2_MZ(zipFile file, const char *global_comment, uint16_t version_madeby) { mz_compat *compat = (mz_compat *)file; int32_t err = MZ_OK; if (!compat) return ZIP_PARAMERROR; if (!compat->handle) return err; if (global_comment) mz_zip_set_comment(compat->handle, global_comment); mz_zip_set_version_madeby(compat->handle, version_madeby); err = mz_zip_close(compat->handle); mz_zip_delete(&compat->handle); return err; } /***************************************************************************/ unzFile unzOpen(const char *path) { return unzOpen64(path); } unzFile unzOpen64(const void *path) { return unzOpen2(path, NULL); } unzFile unzOpen2(const char *path, zlib_filefunc_def *pzlib_filefunc_def) { unzFile unz = NULL; void *stream = NULL; if (pzlib_filefunc_def) { if (pzlib_filefunc_def->zopen_file) { stream = mz_stream_ioapi_create(); if (!stream) return NULL; mz_stream_ioapi_set_filefunc(stream, pzlib_filefunc_def); } else if (pzlib_filefunc_def->opaque) { stream = mz_stream_create((mz_stream_vtbl *)pzlib_filefunc_def->opaque); if (!stream) return NULL; } } if (!stream) { stream = mz_stream_os_create(); if (!stream) return NULL; } if (mz_stream_open(stream, path, MZ_OPEN_MODE_READ) != MZ_OK) { mz_stream_delete(&stream); return NULL; } unz = unzOpen_MZ(stream); if (!unz) { mz_stream_close(stream); mz_stream_delete(&stream); return NULL; } return unz; } unzFile unzOpen2_64(const void *path, zlib_filefunc64_def *pzlib_filefunc_def) { unzFile unz = NULL; void *stream = NULL; if (pzlib_filefunc_def) { if (pzlib_filefunc_def->zopen64_file) { stream = mz_stream_ioapi_create(); if (!stream) return NULL; mz_stream_ioapi_set_filefunc64(stream, pzlib_filefunc_def); } else if (pzlib_filefunc_def->opaque) { stream = mz_stream_create((mz_stream_vtbl *)pzlib_filefunc_def->opaque); if (!stream) return NULL; } } if (!stream) { stream = mz_stream_os_create(); if (!stream) return NULL; } if (mz_stream_open(stream, path, MZ_OPEN_MODE_READ) != MZ_OK) { mz_stream_delete(&stream); return NULL; } unz = unzOpen_MZ(stream); if (!unz) { mz_stream_close(stream); mz_stream_delete(&stream); return NULL; } return unz; } void* unzGetHandle_MZ(unzFile file) { mz_compat *compat = (mz_compat *)file; if (!compat) return NULL; return compat->handle; } void* unzGetStream_MZ(unzFile file) { mz_compat *compat = (mz_compat *)file; if (!compat) return NULL; return compat->stream; } unzFile unzOpen_MZ(void *stream) { mz_compat *compat = NULL; int32_t err = MZ_OK; void *handle = NULL; handle = mz_zip_create(); if (!handle) return NULL; err = mz_zip_open(handle, stream, MZ_OPEN_MODE_READ); if (err != MZ_OK) { mz_zip_delete(&handle); return NULL; } compat = (mz_compat *)calloc(1, sizeof(mz_compat)); if (compat) { compat->handle = handle; compat->stream = stream; mz_zip_goto_first_entry(compat->handle); } else { mz_zip_delete(&handle); } return (unzFile)compat; } int unzClose(unzFile file) { mz_compat *compat = (mz_compat *)file; int32_t err = MZ_OK; if (!compat) return UNZ_PARAMERROR; if (compat->handle) err = unzClose_MZ(file); if (compat->stream) { mz_stream_close(compat->stream); mz_stream_delete(&compat->stream); } free(compat); return err; } /* Only closes the zip handle, does not close the stream */ int unzClose_MZ(unzFile file) { mz_compat *compat = (mz_compat *)file; int32_t err = MZ_OK; if (!compat) return UNZ_PARAMERROR; err = mz_zip_close(compat->handle); mz_zip_delete(&compat->handle); return err; } int unzGetGlobalInfo(unzFile file, unz_global_info* pglobal_info32) { mz_compat *compat = (mz_compat *)file; unz_global_info64 global_info64; int32_t err = MZ_OK; memset(pglobal_info32, 0, sizeof(unz_global_info)); if (!compat) return UNZ_PARAMERROR; err = unzGetGlobalInfo64(file, &global_info64); if (err == MZ_OK) { pglobal_info32->number_entry = (uint32_t)global_info64.number_entry; pglobal_info32->size_comment = global_info64.size_comment; pglobal_info32->number_disk_with_CD = global_info64.number_disk_with_CD; } return err; } int unzGetGlobalInfo64(unzFile file, unz_global_info64 *pglobal_info) { mz_compat *compat = (mz_compat *)file; const char *comment_ptr = NULL; int32_t err = MZ_OK; memset(pglobal_info, 0, sizeof(unz_global_info64)); if (!compat) return UNZ_PARAMERROR; err = mz_zip_get_comment(compat->handle, &comment_ptr); if (err == MZ_OK) pglobal_info->size_comment = (uint16_t)strlen(comment_ptr); if ((err == MZ_OK) || (err == MZ_EXIST_ERROR)) err = mz_zip_get_number_entry(compat->handle, &pglobal_info->number_entry); if (err == MZ_OK) err = mz_zip_get_disk_number_with_cd(compat->handle, &pglobal_info->number_disk_with_CD); return err; } int unzGetGlobalComment(unzFile file, char *comment, unsigned long comment_size) { mz_compat *compat = (mz_compat *)file; const char *comment_ptr = NULL; int32_t err = MZ_OK; if (!comment || !comment_size) return UNZ_PARAMERROR; err = mz_zip_get_comment(compat->handle, &comment_ptr); if (err == MZ_OK) { strncpy(comment, comment_ptr, comment_size - 1); comment[comment_size - 1] = 0; } return err; } int unzOpenCurrentFile3(unzFile file, int *method, int *level, int raw, const char *password) { mz_compat *compat = (mz_compat *)file; mz_zip_file *file_info = NULL; int32_t err = MZ_OK; void *stream = NULL; if (!compat) return UNZ_PARAMERROR; if (method) *method = 0; if (level) *level = 0; if (mz_zip_entry_is_open(compat->handle) == MZ_OK) { /* zlib minizip does not error out here if close returns errors */ unzCloseCurrentFile(file); } compat->total_out = 0; err = mz_zip_entry_read_open(compat->handle, (uint8_t)raw, password); if (err == MZ_OK) err = mz_zip_entry_get_info(compat->handle, &file_info); if (err == MZ_OK) { if (method) { *method = file_info->compression_method; } if (level) { *level = 6; switch (file_info->flag & 0x06) { case MZ_ZIP_FLAG_DEFLATE_SUPER_FAST: *level = 1; break; case MZ_ZIP_FLAG_DEFLATE_FAST: *level = 2; break; case MZ_ZIP_FLAG_DEFLATE_MAX: *level = 9; break; } } } if (err == MZ_OK) err = mz_zip_get_stream(compat->handle, &stream); if (err == MZ_OK) compat->entry_pos = mz_stream_tell(stream); return err; } int unzOpenCurrentFile(unzFile file) { return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); } int unzOpenCurrentFilePassword(unzFile file, const char *password) { return unzOpenCurrentFile3(file, NULL, NULL, 0, password); } int unzOpenCurrentFile2(unzFile file, int *method, int *level, int raw) { return unzOpenCurrentFile3(file, method, level, raw, NULL); } int unzReadCurrentFile(unzFile file, void *buf, uint32_t len) { mz_compat *compat = (mz_compat *)file; int32_t err = MZ_OK; if (!compat || len >= INT32_MAX) return UNZ_PARAMERROR; err = mz_zip_entry_read(compat->handle, buf, (int32_t)len); if (err > 0) compat->total_out += (uint32_t)err; return err; } int unzCloseCurrentFile(unzFile file) { mz_compat *compat = (mz_compat *)file; int32_t err = MZ_OK; if (!compat) return UNZ_PARAMERROR; err = mz_zip_entry_close(compat->handle); return err; } int unzGetCurrentFileInfo(unzFile file, unz_file_info *pfile_info, char *filename, unsigned long filename_size, void *extrafield, unsigned long extrafield_size, char *comment, unsigned long comment_size) { mz_compat *compat = (mz_compat *)file; mz_zip_file *file_info = NULL; uint16_t bytes_to_copy = 0; int32_t err = MZ_OK; if (!compat) return UNZ_PARAMERROR; err = mz_zip_entry_get_info(compat->handle, &file_info); if (err != MZ_OK) return err; if (pfile_info) { pfile_info->version = file_info->version_madeby; pfile_info->version_needed = file_info->version_needed; pfile_info->flag = file_info->flag; pfile_info->compression_method = file_info->compression_method; pfile_info->mz_dos_date = mz_zip_time_t_to_dos_date(file_info->modified_date); mz_zip_time_t_to_tm(file_info->modified_date, &pfile_info->tmu_date); pfile_info->tmu_date.tm_year += 1900; pfile_info->crc = file_info->crc; pfile_info->size_filename = file_info->filename_size; pfile_info->size_file_extra = file_info->extrafield_size; pfile_info->size_file_comment = file_info->comment_size; pfile_info->disk_num_start = (uint16_t)file_info->disk_number; pfile_info->internal_fa = file_info->internal_fa; pfile_info->external_fa = file_info->external_fa; pfile_info->compressed_size = (uint32_t)file_info->compressed_size; pfile_info->uncompressed_size = (uint32_t)file_info->uncompressed_size; } if (filename_size > 0 && filename && file_info->filename) { bytes_to_copy = (uint16_t)filename_size; if (bytes_to_copy > file_info->filename_size) bytes_to_copy = file_info->filename_size; memcpy(filename, file_info->filename, bytes_to_copy); if (bytes_to_copy < filename_size) filename[bytes_to_copy] = 0; } if (extrafield_size > 0 && extrafield) { bytes_to_copy = (uint16_t)extrafield_size; if (bytes_to_copy > file_info->extrafield_size) bytes_to_copy = file_info->extrafield_size; memcpy(extrafield, file_info->extrafield, bytes_to_copy); } if (comment_size > 0 && comment && file_info->comment) { bytes_to_copy = (uint16_t)comment_size; if (bytes_to_copy > file_info->comment_size) bytes_to_copy = file_info->comment_size; memcpy(comment, file_info->comment, bytes_to_copy); if (bytes_to_copy < comment_size) comment[bytes_to_copy] = 0; } return err; } int unzGetCurrentFileInfo64(unzFile file, unz_file_info64 * pfile_info, char *filename, unsigned long filename_size, void *extrafield, unsigned long extrafield_size, char *comment, unsigned long comment_size) { mz_compat *compat = (mz_compat *)file; mz_zip_file *file_info = NULL; uint16_t bytes_to_copy = 0; int32_t err = MZ_OK; if (!compat) return UNZ_PARAMERROR; err = mz_zip_entry_get_info(compat->handle, &file_info); if (err != MZ_OK) return err; if (pfile_info) { pfile_info->version = file_info->version_madeby; pfile_info->version_needed = file_info->version_needed; pfile_info->flag = file_info->flag; pfile_info->compression_method = file_info->compression_method; pfile_info->mz_dos_date = mz_zip_time_t_to_dos_date(file_info->modified_date); mz_zip_time_t_to_tm(file_info->modified_date, &pfile_info->tmu_date); pfile_info->tmu_date.tm_year += 1900; pfile_info->crc = file_info->crc; pfile_info->size_filename = file_info->filename_size; pfile_info->size_file_extra = file_info->extrafield_size; pfile_info->size_file_comment = file_info->comment_size; pfile_info->disk_num_start = file_info->disk_number; pfile_info->internal_fa = file_info->internal_fa; pfile_info->external_fa = file_info->external_fa; pfile_info->compressed_size = (uint64_t)file_info->compressed_size; pfile_info->uncompressed_size = (uint64_t)file_info->uncompressed_size; } if (filename_size > 0 && filename && file_info->filename) { bytes_to_copy = (uint16_t)filename_size; if (bytes_to_copy > file_info->filename_size) bytes_to_copy = file_info->filename_size; memcpy(filename, file_info->filename, bytes_to_copy); if (bytes_to_copy < filename_size) filename[bytes_to_copy] = 0; } if (extrafield_size > 0 && extrafield) { bytes_to_copy = (uint16_t)extrafield_size; if (bytes_to_copy > file_info->extrafield_size) bytes_to_copy = file_info->extrafield_size; memcpy(extrafield, file_info->extrafield, bytes_to_copy); } if (comment_size > 0 && comment && file_info->comment) { bytes_to_copy = (uint16_t)comment_size; if (bytes_to_copy > file_info->comment_size) bytes_to_copy = file_info->comment_size; memcpy(comment, file_info->comment, bytes_to_copy); if (bytes_to_copy < comment_size) comment[bytes_to_copy] = 0; } return err; } int unzGoToFirstFile(unzFile file) { mz_compat *compat = (mz_compat *)file; if (!compat) return UNZ_PARAMERROR; compat->entry_index = 0; return mz_zip_goto_first_entry(compat->handle); } int unzGoToNextFile(unzFile file) { mz_compat *compat = (mz_compat *)file; int32_t err = MZ_OK; if (!compat) return UNZ_PARAMERROR; err = mz_zip_goto_next_entry(compat->handle); if (err != MZ_END_OF_LIST) compat->entry_index += 1; return err; } int unzLocateFile(unzFile file, const char *filename, unzFileNameComparer filename_compare_func) { mz_compat *compat = (mz_compat *)file; mz_zip_file *file_info = NULL; uint64_t preserve_index = 0; int32_t err = MZ_OK; int32_t result = 0; if (!compat) return UNZ_PARAMERROR; preserve_index = compat->entry_index; err = mz_zip_goto_first_entry(compat->handle); while (err == MZ_OK) { err = mz_zip_entry_get_info(compat->handle, &file_info); if (err != MZ_OK) break; if ((intptr_t)filename_compare_func > 2) { result = filename_compare_func(file, filename, file_info->filename); } else { int32_t case_sensitive = (int32_t)(intptr_t)filename_compare_func; result = mz_path_compare_wc(filename, file_info->filename, !case_sensitive); } if (result == 0) return MZ_OK; err = mz_zip_goto_next_entry(compat->handle); } compat->entry_index = preserve_index; return err; } /***************************************************************************/ int unzGetFilePos(unzFile file, unz_file_pos *file_pos) { unz64_file_pos file_pos64; int32_t err = 0; err = unzGetFilePos64(file, &file_pos64); if (err < 0) return err; file_pos->pos_in_zip_directory = (uint32_t)file_pos64.pos_in_zip_directory; file_pos->num_of_file = (uint32_t)file_pos64.num_of_file; return err; } int unzGoToFilePos(unzFile file, unz_file_pos *file_pos) { mz_compat *compat = (mz_compat *)file; unz64_file_pos file_pos64; if (!compat || !file_pos) return UNZ_PARAMERROR; file_pos64.pos_in_zip_directory = file_pos->pos_in_zip_directory; file_pos64.num_of_file = file_pos->num_of_file; return unzGoToFilePos64(file, &file_pos64); } int unzGetFilePos64(unzFile file, unz64_file_pos *file_pos) { mz_compat *compat = (mz_compat *)file; int64_t offset = 0; if (!compat || !file_pos) return UNZ_PARAMERROR; offset = unzGetOffset64(file); if (offset < 0) return (int)offset; file_pos->pos_in_zip_directory = offset; file_pos->num_of_file = compat->entry_index; return UNZ_OK; } int unzGoToFilePos64(unzFile file, const unz64_file_pos *file_pos) { mz_compat *compat = (mz_compat *)file; int32_t err = MZ_OK; if (!compat || !file_pos) return UNZ_PARAMERROR; err = mz_zip_goto_entry(compat->handle, file_pos->pos_in_zip_directory); if (err == MZ_OK) compat->entry_index = file_pos->num_of_file; return err; } unsigned long unzGetOffset(unzFile file) { return (uint32_t)unzGetOffset64(file); } int64_t unzGetOffset64(unzFile file) { mz_compat *compat = (mz_compat *)file; if (!compat) return UNZ_PARAMERROR; return mz_zip_get_entry(compat->handle); } int unzSetOffset(unzFile file, unsigned long pos) { return unzSetOffset64(file, pos); } int unzSetOffset64(unzFile file, int64_t pos) { mz_compat *compat = (mz_compat *)file; if (!compat) return UNZ_PARAMERROR; return (int)mz_zip_goto_entry(compat->handle, pos); } int unzGetLocalExtrafield(unzFile file, void *buf, unsigned int len) { mz_compat *compat = (mz_compat *)file; mz_zip_file *file_info = NULL; int32_t err = MZ_OK; int32_t bytes_to_copy = 0; if (!compat || !buf || len >= INT32_MAX) return UNZ_PARAMERROR; err = mz_zip_entry_get_local_info(compat->handle, &file_info); if (err != MZ_OK) return err; bytes_to_copy = (int32_t)len; if (bytes_to_copy > file_info->extrafield_size) bytes_to_copy = file_info->extrafield_size; memcpy(buf, file_info->extrafield, bytes_to_copy); return MZ_OK; } int32_t unzTell(unzFile file) { return unztell(file); } int32_t unztell(unzFile file) { return (int32_t)unztell64(file); } uint64_t unzTell64(unzFile file) { return unztell64(file); } uint64_t unztell64(unzFile file) { mz_compat *compat = (mz_compat *)file; if (!compat) return UNZ_PARAMERROR; return compat->total_out; } int unzSeek(unzFile file, int32_t offset, int origin) { return unzSeek64(file, offset, origin); } int unzSeek64(unzFile file, int64_t offset, int origin) { mz_compat *compat = (mz_compat *)file; mz_zip_file *file_info = NULL; int64_t position = 0; int32_t err = MZ_OK; void *stream = NULL; if (!compat) return UNZ_PARAMERROR; err = mz_zip_entry_get_info(compat->handle, &file_info); if (err != MZ_OK) return err; if (file_info->compression_method != MZ_COMPRESS_METHOD_STORE) return UNZ_ERRNO; if (origin == SEEK_SET) position = offset; else if (origin == SEEK_CUR) position = compat->total_out + offset; else if (origin == SEEK_END) position = (int64_t)file_info->compressed_size + offset; else return UNZ_PARAMERROR; if (position > (int64_t)file_info->compressed_size) return UNZ_PARAMERROR; err = mz_zip_get_stream(compat->handle, &stream); if (err == MZ_OK) err = mz_stream_seek(stream, compat->entry_pos + position, MZ_SEEK_SET); if (err == MZ_OK) compat->total_out = position; return err; } int unzEndOfFile(unzFile file) { return unzeof(file); } int unzeof(unzFile file) { mz_compat *compat = (mz_compat *)file; mz_zip_file *file_info = NULL; int32_t err = MZ_OK; if (!compat) return UNZ_PARAMERROR; err = mz_zip_entry_get_info(compat->handle, &file_info); if (err != MZ_OK) return err; if (compat->total_out == (int64_t)file_info->uncompressed_size) return 1; return 0; } void* unzGetStream(unzFile file) { mz_compat *compat = (mz_compat *)file; if (!compat) return NULL; return (void *)compat->stream; } /***************************************************************************/ sight-25.1.0/3rd-party/minizip/mz_compat.h000066400000000000000000000437541503402212300203700ustar00rootroot00000000000000/* mz_compat.h -- Backwards compatible interface for older versions part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng Copyright (C) 1998-2010 Gilles Vollant https://www.winimage.com/zLibDll/minizip.html This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_COMPAT_H #define MZ_COMPAT_H #include "mz.h" #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ #if defined(HAVE_ZLIB) && defined(MAX_MEM_LEVEL) #ifndef DEF_MEM_LEVEL # if MAX_MEM_LEVEL >= 8 # define DEF_MEM_LEVEL 8 # else # define DEF_MEM_LEVEL MAX_MEM_LEVEL # endif #endif #endif #ifndef MAX_WBITS #define MAX_WBITS (15) #endif #ifndef DEF_MEM_LEVEL #define DEF_MEM_LEVEL (8) #endif #ifndef ZEXPORT # define ZEXPORT MZ_EXPORT #endif /***************************************************************************/ #if defined(STRICTZIP) || defined(STRICTZIPUNZIP) /* like the STRICT of WIN32, we define a pointer that cannot be converted from (void*) without cast */ typedef struct TagzipFile__ { int unused; } zip_file__; typedef zip_file__ *zipFile; #else typedef void *zipFile; #endif /***************************************************************************/ typedef uint64_t ZPOS64_T; #ifndef ZCALLBACK #define ZCALLBACK #endif typedef void* (ZCALLBACK *open_file_func) (void *opaque, const char *filename, int mode); typedef void* (ZCALLBACK *open64_file_func) (void *opaque, const void *filename, int mode); typedef unsigned long (ZCALLBACK *read_file_func) (void *opaque, void *stream, void* buf, unsigned long size); typedef unsigned long (ZCALLBACK *write_file_func) (void *opaque, void *stream, const void* buf, unsigned long size); typedef int (ZCALLBACK *close_file_func) (void *opaque, void *stream); typedef int (ZCALLBACK *testerror_file_func)(void *opaque, void *stream); typedef long (ZCALLBACK *tell_file_func) (void *opaque, void *stream); typedef ZPOS64_T (ZCALLBACK *tell64_file_func) (void *opaque, void *stream); typedef long (ZCALLBACK *seek_file_func) (void *opaque, void *stream, unsigned long offset, int origin); typedef long (ZCALLBACK *seek64_file_func) (void *opaque, void *stream, ZPOS64_T offset, int origin); typedef struct zlib_filefunc_def_s { open_file_func zopen_file; read_file_func zread_file; write_file_func zwrite_file; tell_file_func ztell_file; seek_file_func zseek_file; close_file_func zclose_file; testerror_file_func zerror_file; void* opaque; } zlib_filefunc_def; typedef struct zlib_filefunc64_def_s { open64_file_func zopen64_file; read_file_func zread_file; write_file_func zwrite_file; tell64_file_func ztell64_file; seek64_file_func zseek64_file; close_file_func zclose_file; testerror_file_func zerror_file; void* opaque; } zlib_filefunc64_def; /***************************************************************************/ #define ZLIB_FILEFUNC_SEEK_SET (0) #define ZLIB_FILEFUNC_SEEK_CUR (1) #define ZLIB_FILEFUNC_SEEK_END (2) #define ZLIB_FILEFUNC_MODE_READ (1) #define ZLIB_FILEFUNC_MODE_WRITE (2) #define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) #define ZLIB_FILEFUNC_MODE_EXISTING (4) #define ZLIB_FILEFUNC_MODE_CREATE (8) /***************************************************************************/ ZEXPORT void fill_fopen_filefunc(zlib_filefunc_def *pzlib_filefunc_def); ZEXPORT void fill_fopen64_filefunc(zlib_filefunc64_def *pzlib_filefunc_def); ZEXPORT void fill_win32_filefunc(zlib_filefunc_def *pzlib_filefunc_def); ZEXPORT void fill_win32_filefunc64(zlib_filefunc64_def *pzlib_filefunc_def); ZEXPORT void fill_win32_filefunc64A(zlib_filefunc64_def *pzlib_filefunc_def); ZEXPORT void fill_memory_filefunc(zlib_filefunc_def *pzlib_filefunc_def); /***************************************************************************/ #if MZ_COMPAT_VERSION <= 110 #define mz_dos_date dosDate #else #define mz_dos_date dos_date #endif typedef struct tm tm_unz; typedef struct tm tm_zip; typedef struct { uint32_t mz_dos_date; struct tm tmz_date; uint16_t internal_fa; /* internal file attributes 2 bytes */ uint32_t external_fa; /* external file attributes 4 bytes */ } zip_fileinfo; typedef const char *zipcharpc; /***************************************************************************/ #define ZIP_OK (0) #define ZIP_EOF (0) #define ZIP_ERRNO (-1) #define ZIP_PARAMERROR (-102) #define ZIP_BADZIPFILE (-103) #define ZIP_INTERNALERROR (-104) #ifndef Z_DEFLATED #define Z_DEFLATED (8) #endif #define Z_BZIP2ED (12) #define APPEND_STATUS_CREATE (0) #define APPEND_STATUS_CREATEAFTER (1) #define APPEND_STATUS_ADDINZIP (2) /***************************************************************************/ /* Writing a zip file */ ZEXPORT zipFile zipOpen(const char *path, int append); ZEXPORT zipFile zipOpen64(const void *path, int append); ZEXPORT zipFile zipOpen2(const char *path, int append, const char **globalcomment, zlib_filefunc_def *pzlib_filefunc_def); ZEXPORT zipFile zipOpen2_64(const void *path, int append, const char **globalcomment, zlib_filefunc64_def *pzlib_filefunc_def); ZEXPORT zipFile zipOpen_MZ(void *stream, int append, const char **globalcomment); ZEXPORT void* zipGetHandle_MZ(zipFile); ZEXPORT void* zipGetStream_MZ(zipFile file); ZEXPORT int zipOpenNewFileInZip(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level); ZEXPORT int zipOpenNewFileInZip_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int zip64); ZEXPORT int zipOpenNewFileInZip2(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw); ZEXPORT int zipOpenNewFileInZip2_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int zip64); ZEXPORT int zipOpenNewFileInZip3(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel, int strategy, const char *password, unsigned long crc_for_crypting); ZEXPORT int zipOpenNewFileInZip3_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel, int strategy, const char *password, uint32_t crc_for_crypting, int zip64); ZEXPORT int zipOpenNewFileInZip4(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel, int strategy, const char *password, unsigned long crc_for_crypting, unsigned long version_madeby, unsigned long flag_base); ZEXPORT int zipOpenNewFileInZip4_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel, int strategy, const char *password, unsigned long crc_for_crypting, unsigned long version_madeby, unsigned long flag_base, int zip64); ZEXPORT int zipOpenNewFileInZip5(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global, const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel, int strategy, const char *password, unsigned long crc_for_crypting, unsigned long version_madeby, unsigned long flag_base, int zip64); ZEXPORT int zipWriteInFileInZip(zipFile file, const void *buf, uint32_t len); ZEXPORT int zipCloseFileInZipRaw(zipFile file, unsigned long uncompressed_size, unsigned long crc32); ZEXPORT int zipCloseFileInZipRaw64(zipFile file, uint64_t uncompressed_size, unsigned long crc32); ZEXPORT int zipCloseFileInZip(zipFile file); ZEXPORT int zipCloseFileInZip64(zipFile file); ZEXPORT int zipClose(zipFile file, const char *global_comment); ZEXPORT int zipClose_64(zipFile file, const char *global_comment); ZEXPORT int zipClose2_64(zipFile file, const char *global_comment, uint16_t version_madeby); int zipClose_MZ(zipFile file, const char *global_comment); int zipClose2_MZ(zipFile file, const char *global_comment, uint16_t version_madeby); /***************************************************************************/ #if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) /* like the STRICT of WIN32, we define a pointer that cannot be converted from (void*) without cast */ typedef struct TagunzFile__ { int unused; } unz_file__; typedef unz_file__ *unzFile; #else typedef void *unzFile; #endif /***************************************************************************/ #define UNZ_OK (0) #define UNZ_END_OF_LIST_OF_FILE (-100) #define UNZ_ERRNO (-1) #define UNZ_EOF (0) #define UNZ_PARAMERROR (-102) #define UNZ_BADZIPFILE (-103) #define UNZ_INTERNALERROR (-104) #define UNZ_CRCERROR (-105) #define UNZ_BADPASSWORD (-106) /***************************************************************************/ typedef struct unz_global_info64_s { uint64_t number_entry; /* total number of entries in the central dir on this disk */ uint32_t number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP */ uint16_t size_comment; /* size of the global comment of the zipfile */ } unz_global_info64; typedef struct unz_global_info_s { uint32_t number_entry; /* total number of entries in the central dir on this disk */ uint32_t number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP */ uint16_t size_comment; /* size of the global comment of the zipfile */ } unz_global_info; typedef struct unz_file_info64_s { uint16_t version; /* version made by 2 bytes */ uint16_t version_needed; /* version needed to extract 2 bytes */ uint16_t flag; /* general purpose bit flag 2 bytes */ uint16_t compression_method; /* compression method 2 bytes */ uint32_t mz_dos_date; /* last mod file date in Dos fmt 4 bytes */ struct tm tmu_date; uint32_t crc; /* crc-32 4 bytes */ uint64_t compressed_size; /* compressed size 8 bytes */ uint64_t uncompressed_size; /* uncompressed size 8 bytes */ uint16_t size_filename; /* filename length 2 bytes */ uint16_t size_file_extra; /* extra field length 2 bytes */ uint16_t size_file_comment; /* file comment length 2 bytes */ uint32_t disk_num_start; /* disk number start 4 bytes */ uint16_t internal_fa; /* internal file attributes 2 bytes */ uint32_t external_fa; /* external file attributes 4 bytes */ uint64_t disk_offset; uint16_t size_file_extra_internal; } unz_file_info64; typedef struct unz_file_info_s { uint16_t version; /* version made by 2 bytes */ uint16_t version_needed; /* version needed to extract 2 bytes */ uint16_t flag; /* general purpose bit flag 2 bytes */ uint16_t compression_method; /* compression method 2 bytes */ uint32_t mz_dos_date; /* last mod file date in Dos fmt 4 bytes */ struct tm tmu_date; uint32_t crc; /* crc-32 4 bytes */ uint32_t compressed_size; /* compressed size 4 bytes */ uint32_t uncompressed_size; /* uncompressed size 4 bytes */ uint16_t size_filename; /* filename length 2 bytes */ uint16_t size_file_extra; /* extra field length 2 bytes */ uint16_t size_file_comment; /* file comment length 2 bytes */ uint16_t disk_num_start; /* disk number start 2 bytes */ uint16_t internal_fa; /* internal file attributes 2 bytes */ uint32_t external_fa; /* external file attributes 4 bytes */ uint64_t disk_offset; } unz_file_info; /***************************************************************************/ typedef int (*unzFileNameComparer)(unzFile file, const char *filename1, const char *filename2); typedef int (*unzIteratorFunction)(unzFile file); typedef int (*unzIteratorFunction2)(unzFile file, unz_file_info64 *pfile_info, char *filename, uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size); /***************************************************************************/ /* Reading a zip file */ ZEXPORT unzFile unzOpen(const char *path); ZEXPORT unzFile unzOpen64(const void *path); ZEXPORT unzFile unzOpen2(const char *path, zlib_filefunc_def *pzlib_filefunc_def); ZEXPORT unzFile unzOpen2_64(const void *path, zlib_filefunc64_def *pzlib_filefunc_def); unzFile unzOpen_MZ(void *stream); ZEXPORT int unzClose(unzFile file); ZEXPORT int unzClose_MZ(unzFile file); ZEXPORT void* unzGetHandle_MZ(unzFile file); ZEXPORT void* unzGetStream_MZ(zipFile file); ZEXPORT int unzGetGlobalInfo(unzFile file, unz_global_info* pglobal_info32); ZEXPORT int unzGetGlobalInfo64(unzFile file, unz_global_info64 *pglobal_info); ZEXPORT int unzGetGlobalComment(unzFile file, char *comment, unsigned long comment_size); ZEXPORT int unzOpenCurrentFile(unzFile file); ZEXPORT int unzOpenCurrentFilePassword(unzFile file, const char *password); ZEXPORT int unzOpenCurrentFile2(unzFile file, int *method, int *level, int raw); ZEXPORT int unzOpenCurrentFile3(unzFile file, int *method, int *level, int raw, const char *password); ZEXPORT int unzReadCurrentFile(unzFile file, void *buf, uint32_t len); ZEXPORT int unzCloseCurrentFile(unzFile file); ZEXPORT int unzGetCurrentFileInfo(unzFile file, unz_file_info *pfile_info, char *filename, unsigned long filename_size, void *extrafield, unsigned long extrafield_size, char *comment, unsigned long comment_size); ZEXPORT int unzGetCurrentFileInfo64(unzFile file, unz_file_info64 * pfile_info, char *filename, unsigned long filename_size, void *extrafield, unsigned long extrafield_size, char *comment, unsigned long comment_size); ZEXPORT int unzGoToFirstFile(unzFile file); ZEXPORT int unzGoToNextFile(unzFile file); ZEXPORT int unzLocateFile(unzFile file, const char *filename, unzFileNameComparer filename_compare_func); ZEXPORT int unzGetLocalExtrafield(unzFile file, void *buf, unsigned int len); /***************************************************************************/ /* Raw access to zip file */ typedef struct unz_file_pos_s { uint32_t pos_in_zip_directory; /* offset in zip file directory */ uint32_t num_of_file; /* # of file */ } unz_file_pos; ZEXPORT int unzGetFilePos(unzFile file, unz_file_pos *file_pos); ZEXPORT int unzGoToFilePos(unzFile file, unz_file_pos *file_pos); typedef struct unz64_file_pos_s { int64_t pos_in_zip_directory; /* offset in zip file directory */ uint64_t num_of_file; /* # of file */ } unz64_file_pos; ZEXPORT int unzGetFilePos64(unzFile file, unz64_file_pos *file_pos); ZEXPORT int unzGoToFilePos64(unzFile file, const unz64_file_pos *file_pos); ZEXPORT int64_t unzGetOffset64(unzFile file); ZEXPORT unsigned long unzGetOffset(unzFile file); ZEXPORT int unzSetOffset64(unzFile file, int64_t pos); ZEXPORT int unzSetOffset(unzFile file, unsigned long pos); ZEXPORT int32_t unztell(unzFile file); ZEXPORT int32_t unzTell(unzFile file); ZEXPORT uint64_t unztell64(unzFile file); ZEXPORT uint64_t unzTell64(unzFile file); ZEXPORT int unzSeek(unzFile file, int32_t offset, int origin); ZEXPORT int unzSeek64(unzFile file, int64_t offset, int origin); ZEXPORT int unzEndOfFile(unzFile file); ZEXPORT int unzeof(unzFile file); ZEXPORT void* unzGetStream(unzFile file); /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_crypt.c000066400000000000000000000164771503402212300202430ustar00rootroot00000000000000/* mz_crypt.c -- Crypto/hash functions part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_os.h" #include "mz_crypt.h" #if defined(HAVE_ZLIB) # if !defined(ZLIB_COMPAT) # include "zlib-ng.h" # define ZLIB_PREFIX(x) zng_##x # else # include "zlib.h" # define ZLIB_PREFIX(x) x # endif #elif defined(HAVE_LZMA) # include "lzma.h" #endif /***************************************************************************/ #if defined(MZ_ZIP_NO_CRYPTO) int32_t mz_crypt_rand(uint8_t *buf, int32_t size) { return mz_os_rand(buf, size); } #endif uint32_t mz_crypt_crc32_update(uint32_t value, const uint8_t *buf, int32_t size) { #if defined(HAVE_ZLIB) /* Define z_crc_t in zlib 1.2.5 and less or if using zlib-ng */ # if (ZLIB_VERNUM < 0x1270) typedef unsigned long z_crc_t; # else typedef uint32_t z_crc_t; # endif return (uint32_t)ZLIB_PREFIX(crc32)((z_crc_t)value, buf, (uInt)size); #elif defined(HAVE_LZMA) return (uint32_t)lzma_crc32(buf, (size_t)size, (uint32_t)value); #else static uint32_t crc32_table[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; value = ~value; while (size > 0) { value = (value >> 8) ^ crc32_table[(value ^ *buf) & 0xFF]; buf += 1; size -= 1; } return ~value; #endif } #if defined(HAVE_WZAES) int32_t mz_crypt_pbkdf2(uint8_t *password, int32_t password_length, uint8_t *salt, int32_t salt_length, uint32_t iteration_count, uint8_t *key, uint16_t key_length) { void *hmac1 = NULL; void *hmac2 = NULL; void *hmac3 = NULL; int32_t err = MZ_OK; uint16_t i = 0; uint32_t j = 0; uint16_t k = 0; uint16_t block_count = 0; uint8_t uu[MZ_HASH_SHA1_SIZE]; uint8_t ux[MZ_HASH_SHA1_SIZE]; if (!password || !salt || !key) return MZ_PARAM_ERROR; memset(key, 0, key_length); hmac1 = mz_crypt_hmac_create(); hmac2 = mz_crypt_hmac_create(); hmac3 = mz_crypt_hmac_create(); if (!hmac1 || !hmac2 || !hmac3) { err = MZ_MEM_ERROR; goto pbkdf2_cleanup; } mz_crypt_hmac_set_algorithm(hmac1, MZ_HASH_SHA1); mz_crypt_hmac_set_algorithm(hmac2, MZ_HASH_SHA1); mz_crypt_hmac_set_algorithm(hmac3, MZ_HASH_SHA1); err = mz_crypt_hmac_init(hmac1, password, password_length); if (err == MZ_OK) err = mz_crypt_hmac_init(hmac2, password, password_length); if (err == MZ_OK) err = mz_crypt_hmac_update(hmac2, salt, salt_length); block_count = 1 + ((uint16_t)key_length - 1) / MZ_HASH_SHA1_SIZE; for (i = 0; (err == MZ_OK) && (i < block_count); i += 1) { memset(ux, 0, sizeof(ux)); err = mz_crypt_hmac_copy(hmac2, hmac3); if (err != MZ_OK) break; uu[0] = (uint8_t)((i + 1) >> 24); uu[1] = (uint8_t)((i + 1) >> 16); uu[2] = (uint8_t)((i + 1) >> 8); uu[3] = (uint8_t)(i + 1); for (j = 0, k = 4; j < iteration_count; j += 1) { err = mz_crypt_hmac_update(hmac3, uu, k); if (err == MZ_OK) err = mz_crypt_hmac_end(hmac3, uu, sizeof(uu)); if (err != MZ_OK) break; for (k = 0; k < MZ_HASH_SHA1_SIZE; k += 1) ux[k] ^= uu[k]; err = mz_crypt_hmac_copy(hmac1, hmac3); if (err != MZ_OK) break; } if (err != MZ_OK) break; j = 0; k = i * MZ_HASH_SHA1_SIZE; while (j < MZ_HASH_SHA1_SIZE && k < key_length) key[k++] = ux[j++]; } pbkdf2_cleanup: /* hmac3 uses the same provider as hmac2, so it must be deleted before the context is destroyed. */ mz_crypt_hmac_delete(&hmac3); mz_crypt_hmac_delete(&hmac1); mz_crypt_hmac_delete(&hmac2); return err; } #endif /***************************************************************************/ sight-25.1.0/3rd-party/minizip/mz_crypt.h000066400000000000000000000053241503402212300202350ustar00rootroot00000000000000/* mz_crypt.h -- Crypto/hash functions part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_CRYPT_H #define MZ_CRYPT_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ uint32_t mz_crypt_crc32_update(uint32_t value, const uint8_t *buf, int32_t size); int32_t mz_crypt_pbkdf2(uint8_t *password, int32_t password_length, uint8_t *salt, int32_t salt_length, uint32_t iteration_count, uint8_t *key, uint16_t key_length); /***************************************************************************/ int32_t mz_crypt_rand(uint8_t *buf, int32_t size); void mz_crypt_sha_reset(void *handle); int32_t mz_crypt_sha_begin(void *handle); int32_t mz_crypt_sha_update(void *handle, const void *buf, int32_t size); int32_t mz_crypt_sha_end(void *handle, uint8_t *digest, int32_t digest_size); int32_t mz_crypt_sha_set_algorithm(void *handle, uint16_t algorithm); void* mz_crypt_sha_create(void); void mz_crypt_sha_delete(void **handle); void mz_crypt_aes_reset(void *handle); int32_t mz_crypt_aes_encrypt(void *handle, const void *aad, int32_t aad_size, uint8_t *buf, int32_t size); int32_t mz_crypt_aes_encrypt_final(void *handle, uint8_t *buf, int32_t size, uint8_t *tag, int32_t tag_size); int32_t mz_crypt_aes_decrypt(void *handle, const void *aad, int32_t aad_size, uint8_t *buf, int32_t size); int32_t mz_crypt_aes_decrypt_final(void *handle, uint8_t *buf, int32_t size, const uint8_t *tag, int32_t tag_size); int32_t mz_crypt_aes_set_encrypt_key(void *handle, const void *key, int32_t key_length, const void *iv, int32_t iv_length); int32_t mz_crypt_aes_set_decrypt_key(void *handle, const void *key, int32_t key_length, const void *iv, int32_t iv_length); void mz_crypt_aes_set_mode(void *handle, int32_t mode); void* mz_crypt_aes_create(void); void mz_crypt_aes_delete(void **handle); void mz_crypt_hmac_reset(void *handle); int32_t mz_crypt_hmac_init(void *handle, const void *key, int32_t key_length); int32_t mz_crypt_hmac_update(void *handle, const void *buf, int32_t size); int32_t mz_crypt_hmac_end(void *handle, uint8_t *digest, int32_t digest_size); int32_t mz_crypt_hmac_copy(void *src_handle, void *target_handle); void mz_crypt_hmac_set_algorithm(void *handle, uint16_t algorithm); void* mz_crypt_hmac_create(void); void mz_crypt_hmac_delete(void **handle); /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_crypt_openssl.c000066400000000000000000000436751503402212300220060ustar00rootroot00000000000000/* mz_crypt_openssl.c -- Crypto/hash functions for OpenSSL part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_crypt.h" #include #include #include #include #include #include #include #include #if OPENSSL_VERSION_NUMBER >= 0x30000000L # include #endif /***************************************************************************/ static void mz_crypt_init(void) { static int32_t openssl_initialized = 0; if (!openssl_initialized) { #if OPENSSL_VERSION_NUMBER < 0x10100000L OpenSSL_add_all_algorithms(); ERR_load_BIO_strings(); ERR_load_crypto_strings(); ENGINE_load_builtin_engines(); ENGINE_register_all_complete(); #else OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_ALL_BUILTIN, NULL); #endif openssl_initialized = 1; } } int32_t mz_crypt_rand(uint8_t *buf, int32_t size) { if (!RAND_bytes(buf, size)) return MZ_CRYPT_ERROR; return size; } /***************************************************************************/ typedef struct mz_crypt_sha_s { #if OPENSSL_VERSION_NUMBER < 0x10100000L union { SHA512_CTX ctx512; SHA256_CTX ctx256; SHA_CTX ctx1; }; #else EVP_MD_CTX *ctx; #endif int32_t initialized; int32_t error; uint16_t algorithm; } mz_crypt_sha; /***************************************************************************/ static const uint8_t mz_crypt_sha_digest_size[] = { MZ_HASH_SHA1_SIZE, 0, MZ_HASH_SHA224_SIZE, MZ_HASH_SHA256_SIZE, MZ_HASH_SHA384_SIZE, MZ_HASH_SHA512_SIZE }; /***************************************************************************/ static void mz_crypt_sha_free(void *handle) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L mz_crypt_sha *sha = (mz_crypt_sha *)handle; if (sha->ctx) EVP_MD_CTX_free(sha->ctx); sha->ctx = NULL; #else MZ_UNUSED(handle); #endif } void mz_crypt_sha_reset(void *handle) { mz_crypt_sha *sha = (mz_crypt_sha *)handle; mz_crypt_init(); mz_crypt_sha_free(handle); sha->error = 0; sha->initialized = 0; } int32_t mz_crypt_sha_begin(void *handle) { mz_crypt_sha *sha = (mz_crypt_sha *)handle; int32_t result = 0; if (!sha) return MZ_PARAM_ERROR; mz_crypt_sha_reset(handle); #if OPENSSL_VERSION_NUMBER < 0x10100000L switch (sha->algorithm) { case MZ_HASH_SHA1: result = SHA1_Init(&sha->ctx1); break; case MZ_HASH_SHA224: result = SHA224_Init(&sha->ctx256); break; case MZ_HASH_SHA256: result = SHA256_Init(&sha->ctx256); break; case MZ_HASH_SHA384: result = SHA384_Init(&sha->ctx512); break; case MZ_HASH_SHA512: result = SHA512_Init(&sha->ctx512); break; } #else const EVP_MD *md = NULL; switch (sha->algorithm) { case MZ_HASH_SHA1: md = EVP_sha1(); break; case MZ_HASH_SHA224: md = EVP_sha224(); break; case MZ_HASH_SHA256: md = EVP_sha256(); break; case MZ_HASH_SHA384: md = EVP_sha384(); break; case MZ_HASH_SHA512: md = EVP_sha512(); break; } if (!md) return MZ_PARAM_ERROR; sha->ctx = EVP_MD_CTX_new(); if (!sha->ctx) return MZ_MEM_ERROR; result = EVP_DigestInit_ex(sha->ctx, md, NULL); #endif if (!result) { sha->error = ERR_get_error(); return MZ_HASH_ERROR; } sha->initialized = 1; return MZ_OK; } int32_t mz_crypt_sha_update(void *handle, const void *buf, int32_t size) { mz_crypt_sha *sha = (mz_crypt_sha *)handle; int32_t result = 0; if (!sha || !buf || !sha->initialized) return MZ_PARAM_ERROR; #if OPENSSL_VERSION_NUMBER < 0x10100000L switch (sha->algorithm) { case MZ_HASH_SHA1: result = SHA1_Update(&sha->ctx1, buf, size); break; case MZ_HASH_SHA224: result = SHA224_Update(&sha->ctx256, buf, size); break; case MZ_HASH_SHA256: result = SHA256_Update(&sha->ctx256, buf, size); break; case MZ_HASH_SHA384: result = SHA384_Update(&sha->ctx512, buf, size); break; case MZ_HASH_SHA512: result = SHA512_Update(&sha->ctx512, buf, size); break; } #else result = EVP_DigestUpdate(sha->ctx, buf, size); #endif if (!result) { sha->error = ERR_get_error(); return MZ_HASH_ERROR; } return size; } int32_t mz_crypt_sha_end(void *handle, uint8_t *digest, int32_t digest_size) { mz_crypt_sha *sha = (mz_crypt_sha *)handle; int32_t result = 0; if (!sha || !digest || !sha->initialized) return MZ_PARAM_ERROR; if (digest_size < mz_crypt_sha_digest_size[sha->algorithm - MZ_HASH_SHA1]) return MZ_PARAM_ERROR; #if OPENSSL_VERSION_NUMBER < 0x10100000L switch (sha->algorithm) { case MZ_HASH_SHA1: result = SHA1_Final(digest, &sha->ctx1); break; case MZ_HASH_SHA224: result = SHA224_Final(digest, &sha->ctx256); break; case MZ_HASH_SHA256: result = SHA256_Final(digest, &sha->ctx256); break; case MZ_HASH_SHA384: result = SHA384_Final(digest, &sha->ctx512); break; case MZ_HASH_SHA512: result = SHA512_Final(digest, &sha->ctx512); break; } #else result = EVP_DigestFinal_ex(sha->ctx, digest, NULL); #endif if (!result) { sha->error = ERR_get_error(); return MZ_HASH_ERROR; } return MZ_OK; } int32_t mz_crypt_sha_set_algorithm(void *handle, uint16_t algorithm) { mz_crypt_sha *sha = (mz_crypt_sha *)handle; if (algorithm < MZ_HASH_SHA1 || algorithm > MZ_HASH_SHA512) return MZ_PARAM_ERROR; sha->algorithm = algorithm; return MZ_OK; } void *mz_crypt_sha_create(void) { mz_crypt_sha *sha = (mz_crypt_sha *)calloc(1, sizeof(mz_crypt_sha)); if (sha) sha->algorithm = MZ_HASH_SHA256; return sha; } void mz_crypt_sha_delete(void **handle) { mz_crypt_sha *sha = NULL; if (!handle) return; sha = (mz_crypt_sha *)*handle; if (sha) { mz_crypt_sha_free(*handle); free(sha); } *handle = NULL; } /***************************************************************************/ typedef struct mz_crypt_aes_s { int32_t mode; int32_t error; EVP_CIPHER_CTX *ctx; } mz_crypt_aes; /***************************************************************************/ static void mz_crypt_aes_free(void *handle) { mz_crypt_aes *aes = (mz_crypt_aes *)handle; if (aes->ctx) EVP_CIPHER_CTX_free(aes->ctx); aes->ctx = NULL; } void mz_crypt_aes_reset(void *handle) { mz_crypt_init(); mz_crypt_aes_free(handle); } int32_t mz_crypt_aes_encrypt(void *handle, const void *aad, int32_t aad_size, uint8_t *buf, int32_t size) { mz_crypt_aes *aes = (mz_crypt_aes *)handle; if (!aes || !buf || size % MZ_AES_BLOCK_SIZE != 0 || !aes->ctx) return MZ_PARAM_ERROR; if (aes->mode != MZ_AES_MODE_GCM && aad && aad_size > 0) return MZ_PARAM_ERROR; if (aad && aad_size > 0) { int32_t how_many = 0; if (!EVP_EncryptUpdate(aes->ctx, NULL, &how_many, aad, aad_size)) return MZ_CRYPT_ERROR; } if (!EVP_EncryptUpdate(aes->ctx, buf, &size, buf, size)) return MZ_CRYPT_ERROR; return size; } int32_t mz_crypt_aes_encrypt_final(void *handle, uint8_t *buf, int32_t size, uint8_t *tag, int32_t tag_size) { mz_crypt_aes *aes = (mz_crypt_aes *)handle; int result = 0; int out_len = 0; if (!aes || !tag || !tag_size || !aes->ctx || aes->mode != MZ_AES_MODE_GCM) return MZ_PARAM_ERROR; if (buf && size) { if (!EVP_EncryptUpdate(aes->ctx, buf, &size, buf, size)) return MZ_CRYPT_ERROR; } /* Must call EncryptFinal for tag to be calculated */ result = EVP_EncryptFinal_ex(aes->ctx, NULL, &out_len); if (result) result = EVP_CIPHER_CTX_ctrl(aes->ctx, EVP_CTRL_GCM_GET_TAG, tag_size, tag); if (!result) { aes->error = ERR_get_error(); return MZ_CRYPT_ERROR; } return size; } int32_t mz_crypt_aes_decrypt(void *handle, const void *aad, int32_t aad_size, uint8_t *buf, int32_t size) { mz_crypt_aes *aes = (mz_crypt_aes *)handle; if (!aes || !buf || size % MZ_AES_BLOCK_SIZE != 0 || !aes->ctx) return MZ_PARAM_ERROR; if (aes->mode != MZ_AES_MODE_GCM && aad && aad_size > 0) return MZ_PARAM_ERROR; if (aad && aad_size > 0) { int32_t how_many = 0; if (!EVP_DecryptUpdate(aes->ctx, NULL, &how_many, aad, aad_size)) return MZ_CRYPT_ERROR; } if (!EVP_DecryptUpdate(aes->ctx, buf, &size, buf, size)) return MZ_CRYPT_ERROR; return size; } int32_t mz_crypt_aes_decrypt_final(void *handle, uint8_t *buf, int32_t size, const uint8_t *tag, int32_t tag_length) { mz_crypt_aes *aes = (mz_crypt_aes *)handle; int out_len = 0; if (!aes || !tag || !tag_length || !aes->ctx || aes->mode != MZ_AES_MODE_GCM) return MZ_PARAM_ERROR; if (buf && size) { if (!EVP_DecryptUpdate(aes->ctx, buf, &size, buf, size)) return MZ_CRYPT_ERROR; } /* Set expected tag */ if (!EVP_CIPHER_CTX_ctrl(aes->ctx, EVP_CTRL_GCM_SET_TAG, tag_length, (void *)tag)) { aes->error = ERR_get_error(); return MZ_CRYPT_ERROR; } /* Must call DecryptFinal for tag verification */ if (!EVP_DecryptFinal_ex(aes->ctx, NULL, &out_len)) { aes->error = ERR_get_error(); return MZ_CRYPT_ERROR; } return size; } static int32_t mz_crypt_aes_set_key(void *handle, const void *key, int32_t key_length, const void *iv, int32_t iv_length, int32_t encrypt) { mz_crypt_aes *aes = (mz_crypt_aes *)handle; const EVP_CIPHER *type = NULL; switch (aes->mode) { case MZ_AES_MODE_CBC: if (key_length == 16) type = EVP_aes_128_cbc(); else if (key_length == 24) type = EVP_aes_192_cbc(); else if (key_length == 32) type = EVP_aes_256_cbc(); break; case MZ_AES_MODE_ECB: if (key_length == 16) type = EVP_aes_128_ecb(); else if (key_length == 24) type = EVP_aes_192_ecb(); else if (key_length == 32) type = EVP_aes_256_ecb(); break; case MZ_AES_MODE_GCM: if (key_length == 16) type = EVP_aes_128_gcm(); else if (key_length == 24) type = EVP_aes_192_gcm(); else if (key_length == 32) type = EVP_aes_256_gcm(); break; } if (!type) return MZ_PARAM_ERROR; aes->ctx = EVP_CIPHER_CTX_new(); if (!aes->ctx) return MZ_MEM_ERROR; if (!EVP_CipherInit_ex(aes->ctx, type, NULL, key, iv, encrypt)) { aes->error = ERR_get_error(); return MZ_HASH_ERROR; } EVP_CIPHER_CTX_set_padding(aes->ctx, aes->mode == MZ_AES_MODE_GCM); return MZ_OK; } int32_t mz_crypt_aes_set_encrypt_key(void *handle, const void *key, int32_t key_length, const void *iv, int32_t iv_length) { mz_crypt_aes *aes = (mz_crypt_aes *)handle; if (!aes || !key || !key_length) return MZ_PARAM_ERROR; if (key_length != 16 && key_length != 24 && key_length != 32) return MZ_PARAM_ERROR; if (iv && iv_length != MZ_AES_BLOCK_SIZE) return MZ_PARAM_ERROR; mz_crypt_aes_reset(handle); return mz_crypt_aes_set_key(handle, key, key_length, iv, iv_length, 1); } int32_t mz_crypt_aes_set_decrypt_key(void *handle, const void *key, int32_t key_length, const void *iv, int32_t iv_length) { mz_crypt_aes *aes = (mz_crypt_aes *)handle; if (!aes || !key || !key_length) return MZ_PARAM_ERROR; if (key_length != 16 && key_length != 24 && key_length != 32) return MZ_PARAM_ERROR; if (iv && iv_length > MZ_AES_BLOCK_SIZE) return MZ_PARAM_ERROR; mz_crypt_aes_reset(handle); return mz_crypt_aes_set_key(handle, key, key_length, iv, iv_length, 0); } void mz_crypt_aes_set_mode(void *handle, int32_t mode) { mz_crypt_aes *aes = (mz_crypt_aes *)handle; aes->mode = mode; } void *mz_crypt_aes_create(void) { mz_crypt_aes *aes = (mz_crypt_aes *)calloc(1, sizeof(mz_crypt_aes)); return aes; } void mz_crypt_aes_delete(void **handle) { mz_crypt_aes *aes = NULL; if (!handle) return; aes = (mz_crypt_aes *)*handle; if (aes) { mz_crypt_aes_free(*handle); free(aes); } *handle = NULL; } /***************************************************************************/ typedef struct mz_crypt_hmac_s { #if OPENSSL_VERSION_NUMBER < 0x30000000L HMAC_CTX *ctx; #else EVP_MAC *mac; EVP_MAC_CTX *ctx; #endif int32_t initialized; int32_t error; uint16_t algorithm; } mz_crypt_hmac; /***************************************************************************/ #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2070000fL)) static HMAC_CTX *HMAC_CTX_new(void) { HMAC_CTX *ctx = OPENSSL_malloc(sizeof(HMAC_CTX)); if (ctx) HMAC_CTX_init(ctx); return ctx; } static void HMAC_CTX_free(HMAC_CTX *ctx) { if (ctx) { HMAC_CTX_cleanup(ctx); OPENSSL_free(ctx); } } #endif /***************************************************************************/ static void mz_crypt_hmac_free(void *handle) { mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; #if OPENSSL_VERSION_NUMBER < 0x30000000L HMAC_CTX_free(hmac->ctx); #else if (hmac->ctx) EVP_MAC_CTX_free(hmac->ctx); if (hmac->mac) EVP_MAC_free(hmac->mac); hmac->mac = NULL; #endif hmac->ctx = NULL; } void mz_crypt_hmac_reset(void *handle) { mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; mz_crypt_init(); mz_crypt_hmac_free(handle); hmac->error = 0; } int32_t mz_crypt_hmac_init(void *handle, const void *key, int32_t key_length) { mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; int32_t result = 0; if (!hmac || !key) return MZ_PARAM_ERROR; mz_crypt_hmac_reset(handle); #if OPENSSL_VERSION_NUMBER < 0x30000000L const EVP_MD *evp_md = NULL; if (hmac->algorithm == MZ_HASH_SHA1) evp_md = EVP_sha1(); else evp_md = EVP_sha256(); hmac->ctx = HMAC_CTX_new(); if (!hmac->ctx) return MZ_MEM_ERROR; result = HMAC_Init_ex(hmac->ctx, key, key_length, evp_md, NULL); #else char *digest_algorithm = NULL; OSSL_PARAM params[2]; if (hmac->algorithm == MZ_HASH_SHA1) digest_algorithm = "sha1"; else digest_algorithm = "sha256"; params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, digest_algorithm, 0); params[1] = OSSL_PARAM_construct_end(); hmac->mac = EVP_MAC_fetch(NULL, "HMAC", NULL); if (!hmac->mac) return MZ_MEM_ERROR; hmac->ctx = EVP_MAC_CTX_new(hmac->mac); if (!hmac->ctx) return MZ_MEM_ERROR; result = EVP_MAC_init(hmac->ctx, key, key_length, params); #endif if (!result) { hmac->error = ERR_get_error(); return MZ_HASH_ERROR; } return MZ_OK; } int32_t mz_crypt_hmac_update(void *handle, const void *buf, int32_t size) { mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; int32_t result = 0; if (!hmac || !buf) return MZ_PARAM_ERROR; #if OPENSSL_VERSION_NUMBER < 0x30000000L result = HMAC_Update(hmac->ctx, buf, size); #else result = EVP_MAC_update(hmac->ctx, buf, size); #endif if (!result) { hmac->error = ERR_get_error(); return MZ_HASH_ERROR; } return MZ_OK; } int32_t mz_crypt_hmac_end(void *handle, uint8_t *digest, int32_t digest_size) { mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; int32_t result = 0; if (!hmac || !digest) return MZ_PARAM_ERROR; #if OPENSSL_VERSION_NUMBER < 0x30000000L if (hmac->algorithm == MZ_HASH_SHA1) { if (digest_size < MZ_HASH_SHA1_SIZE) return MZ_BUF_ERROR; result = HMAC_Final(hmac->ctx, digest, (uint32_t *)&digest_size); } else { if (digest_size < MZ_HASH_SHA256_SIZE) return MZ_BUF_ERROR; result = HMAC_Final(hmac->ctx, digest, (uint32_t *)&digest_size); } #else { size_t digest_outsize = digest_size; result = EVP_MAC_final(hmac->ctx, digest, &digest_outsize, digest_size); } #endif if (!result) { hmac->error = ERR_get_error(); return MZ_HASH_ERROR; } return MZ_OK; } void mz_crypt_hmac_set_algorithm(void *handle, uint16_t algorithm) { mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; hmac->algorithm = algorithm; } int32_t mz_crypt_hmac_copy(void *src_handle, void *target_handle) { mz_crypt_hmac *source = (mz_crypt_hmac *)src_handle; mz_crypt_hmac *target = (mz_crypt_hmac *)target_handle; if (!source || !target) return MZ_PARAM_ERROR; mz_crypt_hmac_reset(target_handle); #if OPENSSL_VERSION_NUMBER < 0x30000000L if (!target->ctx) target->ctx = HMAC_CTX_new(); if (!HMAC_CTX_copy(target->ctx, source->ctx)) { target->error = ERR_get_error(); return MZ_HASH_ERROR; } #else if (!target->ctx) target->ctx = EVP_MAC_CTX_dup(source->ctx); if (!target->ctx) return MZ_MEM_ERROR; #endif return MZ_OK; } void *mz_crypt_hmac_create(void) { mz_crypt_hmac *hmac = (mz_crypt_hmac *)calloc(1, sizeof(mz_crypt_hmac)); if (hmac) hmac->algorithm = MZ_HASH_SHA256; return hmac; } void mz_crypt_hmac_delete(void **handle) { mz_crypt_hmac *hmac = NULL; if (!handle) return; hmac = (mz_crypt_hmac *)*handle; if (hmac) { mz_crypt_hmac_free(*handle); free(hmac); } *handle = NULL; } sight-25.1.0/3rd-party/minizip/mz_os.c000066400000000000000000000214341503402212300175100ustar00rootroot00000000000000/* mz_os.c -- System functions part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng Copyright (C) 1998-2010 Gilles Vollant https://www.winimage.com/zLibDll/minizip.html This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_crypt.h" #include "mz_os.h" #include "mz_strm.h" #include "mz_strm_os.h" #include /* tolower */ #include /***************************************************************************/ int32_t mz_path_combine(char *path, const char *join, int32_t max_path) { int32_t path_len = 0; if (!path || !join || !max_path) return MZ_PARAM_ERROR; path_len = (int32_t)strlen(path); if (path_len == 0) { strncpy(path, join, max_path - 1); path[max_path - 1] = 0; } else { mz_path_append_slash(path, max_path, MZ_PATH_SLASH_PLATFORM); path_len = (int32_t)strlen(path); if (max_path > path_len) strncat(path, join, max_path - path_len - 1); } return MZ_OK; } int32_t mz_path_append_slash(char *path, int32_t max_path, char slash) { int32_t path_len = (int32_t)strlen(path); if ((path_len + 2) >= max_path) return MZ_BUF_ERROR; if (path[path_len - 1] != '\\' && path[path_len - 1] != '/') { path[path_len] = slash; path[path_len + 1] = 0; } return MZ_OK; } int32_t mz_path_remove_slash(char *path) { int32_t path_len = (int32_t)strlen(path); while (path_len > 0) { if (path[path_len - 1] == '\\' || path[path_len - 1] == '/') path[path_len - 1] = 0; else break; path_len -= 1; } return MZ_OK; } int32_t mz_path_has_slash(const char *path) { int32_t path_len = (int32_t)strlen(path); if (path[path_len - 1] != '\\' && path[path_len - 1] != '/') return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_path_convert_slashes(char *path, char slash) { int32_t i = 0; for (i = 0; i < (int32_t)strlen(path); i += 1) { if (path[i] == '\\' || path[i] == '/') path[i] = slash; } return MZ_OK; } int32_t mz_path_compare_wc(const char *path, const char *wildcard, uint8_t ignore_case) { while (*path != 0) { switch (*wildcard) { case '*': if (*(wildcard + 1) == 0) return MZ_OK; while (*path != 0) { if (mz_path_compare_wc(path, (wildcard + 1), ignore_case) == MZ_OK) return MZ_OK; path += 1; } return MZ_EXIST_ERROR; default: /* Ignore differences in path slashes on platforms */ if ((*path == '\\' && *wildcard == '/') || (*path == '/' && *wildcard == '\\')) break; if (ignore_case) { if (tolower(*path) != tolower(*wildcard)) return MZ_EXIST_ERROR; } else { if (*path != *wildcard) return MZ_EXIST_ERROR; } break; } path += 1; wildcard += 1; } if ((*wildcard != 0) && (*wildcard != '*')) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_path_resolve(const char *path, char *output, int32_t max_output) { const char *source = path; const char *check = output; char *target = output; if (max_output <= 0) return MZ_PARAM_ERROR; while (*source != 0 && max_output > 1) { check = source; if ((*check == '\\') || (*check == '/')) check += 1; if ((source == path) || (target == output) || (check != source)) { /* Skip double paths */ if ((*check == '\\') || (*check == '/')) { source += 1; continue; } if (*check == '.') { check += 1; /* Remove . if at end of string and not at the beginning */ if ((*check == 0) && (source != path && target != output)) { /* Copy last slash */ *target = *source; target += 1; max_output -= 1; source += (check - source); continue; } /* Remove . if not at end of string */ else if ((*check == '\\') || (*check == '/')) { source += (check - source); /* Skip slash if at beginning of string */ if (target == output && *source != 0) source += 1; continue; } /* Go to parent directory .. */ else if (*check == '.') { check += 1; if ((*check == 0) || (*check == '\\' || *check == '/')) { source += (check - source); /* Search backwards for previous slash */ if (target != output) { target -= 1; do { if ((*target == '\\') || (*target == '/')) break; target -= 1; max_output += 1; } while (target > output); } if ((target == output) && (*source != 0)) source += 1; if ((*target == '\\' || *target == '/') && (*source == 0)) target += 1; *target = 0; continue; } } } } *target = *source; source += 1; target += 1; max_output -= 1; } *target = 0; if (*path == 0) return MZ_INTERNAL_ERROR; return MZ_OK; } int32_t mz_path_remove_filename(char *path) { char *path_ptr = NULL; if (!path) return MZ_PARAM_ERROR; path_ptr = path + strlen(path) - 1; while (path_ptr > path) { if ((*path_ptr == '/') || (*path_ptr == '\\')) { *path_ptr = 0; break; } path_ptr -= 1; } if (path_ptr == path) *path_ptr = 0; return MZ_OK; } int32_t mz_path_remove_extension(char *path) { char *path_ptr = NULL; if (!path) return MZ_PARAM_ERROR; path_ptr = path + strlen(path) - 1; while (path_ptr > path) { if ((*path_ptr == '/') || (*path_ptr == '\\')) break; if (*path_ptr == '.') { *path_ptr = 0; break; } path_ptr -= 1; } if (path_ptr == path) *path_ptr = 0; return MZ_OK; } int32_t mz_path_get_filename(const char *path, const char **filename) { const char *match = NULL; if (!path || !filename) return MZ_PARAM_ERROR; *filename = NULL; for (match = path; *match != 0; match += 1) { if ((*match == '\\') || (*match == '/')) *filename = match + 1; } if (!*filename) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_dir_make(const char *path) { int32_t err = MZ_OK; char *current_dir = NULL; char *match = NULL; char hold = 0; current_dir = strdup(path); if (!current_dir) return MZ_MEM_ERROR; mz_path_remove_slash(current_dir); err = mz_os_make_dir(current_dir); if (err != MZ_OK) { match = current_dir + 1; while (1) { while (*match != 0 && *match != '\\' && *match != '/') match += 1; hold = *match; *match = 0; err = mz_os_make_dir(current_dir); if (err != MZ_OK) break; if (hold == 0) break; *match = hold; match += 1; } } free(current_dir); return err; } int32_t mz_file_get_crc(const char *path, uint32_t *result_crc) { void *stream = NULL; uint32_t crc32 = 0; int32_t read = 0; int32_t err = MZ_OK; uint8_t buf[16384]; stream = mz_stream_os_create(); if (!stream) return MZ_MEM_ERROR; err = mz_stream_os_open(stream, path, MZ_OPEN_MODE_READ); if (err == MZ_OK) { do { read = mz_stream_os_read(stream, buf, sizeof(buf)); if (read < 0) { err = read; break; } crc32 = mz_crypt_crc32_update(crc32, buf, read); } while ((err == MZ_OK) && (read > 0)); mz_stream_os_close(stream); } *result_crc = crc32; mz_stream_os_delete(&stream); return err; } /***************************************************************************/ sight-25.1.0/3rd-party/minizip/mz_os.h000066400000000000000000000126561503402212300175230ustar00rootroot00000000000000/* mz_os.h -- System functions part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_OS_H #define MZ_OS_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ #if defined(__APPLE__) # define MZ_VERSION_MADEBY_HOST_SYSTEM (MZ_HOST_SYSTEM_OSX_DARWIN) #elif defined(__riscos__) # define MZ_VERSION_MADEBY_HOST_SYSTEM (MZ_HOST_SYSTEM_RISCOS) #elif defined(_WIN32) # define MZ_VERSION_MADEBY_HOST_SYSTEM (MZ_HOST_SYSTEM_WINDOWS_NTFS) #else # define MZ_VERSION_MADEBY_HOST_SYSTEM (MZ_HOST_SYSTEM_UNIX) #endif #if defined(HAVE_LZMA) || defined(HAVE_LIBCOMP) # define MZ_VERSION_MADEBY_ZIP_VERSION (63) #elif defined(HAVE_WZAES) # define MZ_VERSION_MADEBY_ZIP_VERSION (51) #elif defined(HAVE_BZIP2) # define MZ_VERSION_MADEBY_ZIP_VERSION (46) #else # define MZ_VERSION_MADEBY_ZIP_VERSION (45) #endif #define MZ_VERSION_MADEBY ((MZ_VERSION_MADEBY_HOST_SYSTEM << 8) | \ (MZ_VERSION_MADEBY_ZIP_VERSION)) #define MZ_PATH_SLASH_UNIX ('/') #define MZ_PATH_SLASH_WINDOWS ('\\') #if defined(_WIN32) # define MZ_PATH_SLASH_PLATFORM (MZ_PATH_SLASH_WINDOWS) #else # define MZ_PATH_SLASH_PLATFORM (MZ_PATH_SLASH_UNIX) #endif /***************************************************************************/ #if defined(_WIN32) struct dirent { char d_name[256]; }; typedef void* DIR; #else #include #endif /***************************************************************************/ /* Shared functions */ int32_t mz_path_combine(char *path, const char *join, int32_t max_path); /* Combines two paths */ int32_t mz_path_append_slash(char *path, int32_t max_path, char slash); /* Appends a path slash on to the end of the path */ int32_t mz_path_remove_slash(char *path); /* Removes a path slash from the end of the path */ int32_t mz_path_has_slash(const char *path); /* Returns whether or not the path ends with slash */ int32_t mz_path_convert_slashes(char *path, char slash); /* Converts the slashes in a path */ int32_t mz_path_compare_wc(const char *path, const char *wildcard, uint8_t ignore_case); /* Compare two paths with wildcard */ int32_t mz_path_resolve(const char *path, char *target, int32_t max_target); /* Resolves path */ int32_t mz_path_remove_filename(char *path); /* Remove the filename from a path */ int32_t mz_path_remove_extension(char *path); /* Remove the extension from a path */ int32_t mz_path_get_filename(const char *path, const char **filename); /* Get the filename from a path */ int32_t mz_dir_make(const char *path); /* Creates a directory recursively */ int32_t mz_file_get_crc(const char *path, uint32_t *result_crc); /* Gets the crc32 hash of a file */ /***************************************************************************/ /* Platform specific functions */ wchar_t *mz_os_unicode_string_create(const char *string, int32_t encoding); /* Create unicode string from a utf8 string */ void mz_os_unicode_string_delete(wchar_t **string); /* Delete a unicode string that was created */ char *mz_os_utf8_string_create(const char *string, int32_t encoding); /* Create a utf8 string from a string with another encoding */ void mz_os_utf8_string_delete(char **string); /* Delete a utf8 string that was created */ int32_t mz_os_rand(uint8_t *buf, int32_t size); /* Random number generator (not cryptographically secure) */ int32_t mz_os_rename(const char *source_path, const char *target_path); /* Rename a file */ int32_t mz_os_unlink(const char *path); /* Delete an existing file */ int32_t mz_os_file_exists(const char *path); /* Check to see if a file exists */ int64_t mz_os_get_file_size(const char *path); /* Gets the length of a file */ int32_t mz_os_get_file_date(const char *path, time_t *modified_date, time_t *accessed_date, time_t *creation_date); /* Gets a file's modified, access, and creation dates if supported */ int32_t mz_os_set_file_date(const char *path, time_t modified_date, time_t accessed_date, time_t creation_date); /* Sets a file's modified, access, and creation dates if supported */ int32_t mz_os_get_file_attribs(const char *path, uint32_t *attributes); /* Gets a file's attributes */ int32_t mz_os_set_file_attribs(const char *path, uint32_t attributes); /* Sets a file's attributes */ int32_t mz_os_make_dir(const char *path); /* Recursively creates a directory */ DIR* mz_os_open_dir(const char *path); /* Opens a directory for listing */ struct dirent* mz_os_read_dir(DIR *dir); /* Reads a directory listing entry */ int32_t mz_os_close_dir(DIR *dir); /* Closes a directory that has been opened for listing */ int32_t mz_os_is_dir(const char *path); /* Checks to see if path is a directory */ int32_t mz_os_is_symlink(const char *path); /* Checks to see if path is a symbolic link */ int32_t mz_os_make_symlink(const char *path, const char *target_path); /* Creates a symbolic link pointing to a target */ int32_t mz_os_read_symlink(const char *path, char *target_path, int32_t max_target_path); /* Gets the target path for a symbolic link */ uint64_t mz_os_ms_time(void); /* Gets the time in milliseconds */ /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_os_posix.c000066400000000000000000000207111503402212300207270ustar00rootroot00000000000000/* mz_os_posix.c -- System functions for posix part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef WIN32 #include "mz.h" #include "mz_strm.h" #include "mz_os.h" #include /* rename */ #include #if defined(HAVE_ICONV) #include #endif #include #include #include #ifndef _WIN32 # include # include #endif #if defined(__APPLE__) # include # include #endif #if defined(HAVE_GETRANDOM) # include #endif #if defined(HAVE_LIBBSD) # include /* arc4random_buf */ #endif /***************************************************************************/ #if defined(HAVE_ICONV) char *mz_os_utf8_string_create(const char *string, int32_t encoding) { iconv_t cd; const char *from_encoding = NULL; size_t result = 0; size_t string_length = 0; size_t string_utf8_size = 0; char *string_utf8 = NULL; char *string_utf8_ptr = NULL; if (!string) return NULL; if (encoding == MZ_ENCODING_CODEPAGE_437) from_encoding = "CP437"; else if (encoding == MZ_ENCODING_CODEPAGE_932) from_encoding = "CP932"; else if (encoding == MZ_ENCODING_CODEPAGE_936) from_encoding = "CP936"; else if (encoding == MZ_ENCODING_CODEPAGE_950) from_encoding = "CP950"; else if (encoding == MZ_ENCODING_UTF8) from_encoding = "UTF-8"; else return NULL; cd = iconv_open("UTF-8", from_encoding); if (cd == (iconv_t)-1) return NULL; string_length = strlen(string); string_utf8_size = string_length * 2; string_utf8 = (char *)calloc((int32_t)(string_utf8_size + 1), sizeof(char)); string_utf8_ptr = string_utf8; if (string_utf8) { result = iconv(cd, (char **)&string, &string_length, (char **)&string_utf8_ptr, &string_utf8_size); } iconv_close(cd); if (result == (size_t)-1) { free(string_utf8); string_utf8 = NULL; } return string_utf8; } #else char *mz_os_utf8_string_create(const char *string, int32_t encoding) { return strdup(string); } #endif void mz_os_utf8_string_delete(char **string) { if (string) { free(*string); *string = NULL; } } /***************************************************************************/ #if defined(HAVE_GETRANDOM) int32_t mz_os_rand(uint8_t *buf, int32_t size) { int32_t left = size; int32_t written = 0; while (left > 0) { written = getrandom(buf, left, 0); if (written < 0) return MZ_INTERNAL_ERROR; buf += written; left -= written; } return size - left; } #elif defined(HAVE_ARC4RANDOM_BUF) int32_t mz_os_rand(uint8_t *buf, int32_t size) { if (size < 0) return 0; arc4random_buf(buf, (uint32_t)size); return size; } #elif defined(HAVE_ARC4RANDOM) int32_t mz_os_rand(uint8_t *buf, int32_t size) { int32_t left = size; for (; left > 2; left -= 3, buf += 3) { uint32_t val = arc4random(); buf[0] = (val) & 0xFF; buf[1] = (val >> 8) & 0xFF; buf[2] = (val >> 16) & 0xFF; } for (; left > 0; left--, buf++) { *buf = arc4random() & 0xFF; } return size - left; } #else int32_t mz_os_rand(uint8_t *buf, int32_t size) { static unsigned calls = 0; int32_t i = 0; /* Ensure different random header each time */ if (++calls == 1) { #define PI_SEED 3141592654UL srand((unsigned)(time(NULL) ^ PI_SEED)); } while (i < size) buf[i++] = (rand() >> 7) & 0xff; return size; } #endif int32_t mz_os_rename(const char *source_path, const char *target_path) { if (rename(source_path, target_path) == -1) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_os_unlink(const char *path) { if (unlink(path) == -1) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_os_file_exists(const char *path) { struct stat path_stat; memset(&path_stat, 0, sizeof(path_stat)); if (stat(path, &path_stat) == 0) return MZ_OK; return MZ_EXIST_ERROR; } int64_t mz_os_get_file_size(const char *path) { struct stat path_stat; memset(&path_stat, 0, sizeof(path_stat)); if (stat(path, &path_stat) == 0) { /* Stat returns size taken up by directory entry, so return 0 */ if (S_ISDIR(path_stat.st_mode)) return 0; return path_stat.st_size; } return 0; } int32_t mz_os_get_file_date(const char *path, time_t *modified_date, time_t *accessed_date, time_t *creation_date) { struct stat path_stat; char *name = NULL; int32_t err = MZ_INTERNAL_ERROR; memset(&path_stat, 0, sizeof(path_stat)); if (strcmp(path, "-") != 0) { /* Not all systems allow stat'ing a file with / appended */ name = strdup(path); mz_path_remove_slash(name); if (stat(name, &path_stat) == 0) { if (modified_date) *modified_date = path_stat.st_mtime; if (accessed_date) *accessed_date = path_stat.st_atime; /* Creation date not supported */ if (creation_date) *creation_date = 0; err = MZ_OK; } free(name); } return err; } int32_t mz_os_set_file_date(const char *path, time_t modified_date, time_t accessed_date, time_t creation_date) { struct utimbuf ut; ut.actime = accessed_date; ut.modtime = modified_date; /* Creation date not supported */ MZ_UNUSED(creation_date); if (utime(path, &ut) != 0) return MZ_INTERNAL_ERROR; return MZ_OK; } int32_t mz_os_get_file_attribs(const char *path, uint32_t *attributes) { struct stat path_stat; int32_t err = MZ_OK; memset(&path_stat, 0, sizeof(path_stat)); if (lstat(path, &path_stat) == -1) err = MZ_INTERNAL_ERROR; *attributes = path_stat.st_mode; return err; } int32_t mz_os_set_file_attribs(const char *path, uint32_t attributes) { int32_t err = MZ_OK; if (chmod(path, (mode_t)attributes) == -1) err = MZ_INTERNAL_ERROR; return err; } int32_t mz_os_make_dir(const char *path) { int32_t err = 0; err = mkdir(path, 0755); if (err != 0 && errno != EEXIST) return MZ_INTERNAL_ERROR; return MZ_OK; } DIR* mz_os_open_dir(const char *path) { return opendir(path); } struct dirent* mz_os_read_dir(DIR *dir) { if (!dir) return NULL; return readdir(dir); } int32_t mz_os_close_dir(DIR *dir) { if (!dir) return MZ_PARAM_ERROR; if (closedir(dir) == -1) return MZ_INTERNAL_ERROR; return MZ_OK; } int32_t mz_os_is_dir(const char *path) { struct stat path_stat; memset(&path_stat, 0, sizeof(path_stat)); stat(path, &path_stat); if (S_ISDIR(path_stat.st_mode)) return MZ_OK; return MZ_EXIST_ERROR; } int32_t mz_os_is_symlink(const char *path) { struct stat path_stat; memset(&path_stat, 0, sizeof(path_stat)); lstat(path, &path_stat); if (S_ISLNK(path_stat.st_mode)) return MZ_OK; return MZ_EXIST_ERROR; } int32_t mz_os_make_symlink(const char *path, const char *target_path) { if (symlink(target_path, path) != 0) return MZ_INTERNAL_ERROR; return MZ_OK; } int32_t mz_os_read_symlink(const char *path, char *target_path, int32_t max_target_path) { size_t length = 0; length = (size_t)readlink(path, target_path, max_target_path - 1); if (length == (size_t)-1) return MZ_EXIST_ERROR; target_path[length] = 0; return MZ_OK; } uint64_t mz_os_ms_time(void) { struct timespec ts; #if defined(__APPLE__) clock_serv_t cclock; mach_timespec_t mts; host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); clock_get_time(cclock, &mts); mach_port_deallocate(mach_task_self(), cclock); ts.tv_sec = mts.tv_sec; ts.tv_nsec = mts.tv_nsec; #elif !defined(_POSIX_MONOTONIC_CLOCK) || _POSIX_MONOTONIC_CLOCK < 0 clock_gettime(CLOCK_REALTIME, &ts); #elif _POSIX_MONOTONIC_CLOCK > 0 clock_gettime(CLOCK_MONOTONIC, &ts); #else if (sysconf(_SC_MONOTONIC_CLOCK) > 0) clock_gettime(CLOCK_MONOTONIC, &ts); else clock_gettime(CLOCK_REALTIME, &ts); #endif return ((uint64_t)ts.tv_sec * 1000) + ((uint64_t)ts.tv_nsec / 1000000); } #endifsight-25.1.0/3rd-party/minizip/mz_os_win32.c000066400000000000000000000461401503402212300205330ustar00rootroot00000000000000/* mz_os_win32.c -- System functions for Windows part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifdef WIN32 #include "mz.h" #include "mz_os.h" #include "mz_strm_os.h" #include #include /***************************************************************************/ #ifndef SYMBOLIC_LINK_FLAG_DIRECTORY # define SYMBOLIC_LINK_FLAG_DIRECTORY 0x1 #endif #ifndef _WIN32_WINNT_WIN8 # define _WIN32_WINNT_WIN8 0x0602 #endif /***************************************************************************/ typedef struct DIR_int_s { void *find_handle; WIN32_FIND_DATAW find_data; struct dirent entry; uint8_t end; } DIR_int; /***************************************************************************/ wchar_t *mz_os_unicode_string_create(const char *string, int32_t encoding) { wchar_t *string_wide = NULL; uint32_t string_wide_size = 0; string_wide_size = MultiByteToWideChar(encoding, 0, string, -1, NULL, 0); if (string_wide_size == 0) return NULL; string_wide = (wchar_t *)calloc(string_wide_size + 1, sizeof(wchar_t)); if (!string_wide) return NULL; MultiByteToWideChar(encoding, 0, string, -1, string_wide, string_wide_size); return string_wide; } void mz_os_unicode_string_delete(wchar_t **string) { if (string) { free(*string); *string = NULL; } } char *mz_os_utf8_string_create(const char *string, int32_t encoding) { wchar_t *string_wide = NULL; char *string_utf8 = NULL; uint32_t string_utf8_size = 0; string_wide = mz_os_unicode_string_create(string, encoding); if (string_wide) { string_utf8_size = WideCharToMultiByte(CP_UTF8, 0, string_wide, -1, NULL, 0, NULL, NULL); string_utf8 = (char *)calloc(string_utf8_size + 1, sizeof(char)); if (string_utf8) WideCharToMultiByte(CP_UTF8, 0, string_wide, -1, string_utf8, string_utf8_size, NULL, NULL); mz_os_unicode_string_delete(&string_wide); } return string_utf8; } char *mz_os_utf8_string_create_from_unicode(const wchar_t *string, int32_t encoding) { char *string_utf8 = NULL; uint32_t string_utf8_size = 0; MZ_UNUSED(encoding); string_utf8_size = WideCharToMultiByte(CP_UTF8, 0, string, -1, NULL, 0, NULL, NULL); string_utf8 = (char *)calloc(string_utf8_size + 1, sizeof(char)); if (string_utf8) WideCharToMultiByte(CP_UTF8, 0, string, -1, string_utf8, string_utf8_size, NULL, NULL); return string_utf8; } void mz_os_utf8_string_delete(char **string) { if (string) { free(*string); *string = NULL; } } /***************************************************************************/ int32_t mz_os_rand(uint8_t *buf, int32_t size) { unsigned __int64 pentium_tsc[1]; int32_t len = 0; for (len = 0; len < (int)size; len += 1) { if (len % 8 == 0) QueryPerformanceCounter((LARGE_INTEGER *)pentium_tsc); buf[len] = ((unsigned char *)pentium_tsc)[len % 8]; } return len; } int32_t mz_os_rename(const char *source_path, const char *target_path) { wchar_t *source_path_wide = NULL; wchar_t *target_path_wide = NULL; int32_t result = 0; int32_t err = MZ_OK; if (!source_path || !target_path) return MZ_PARAM_ERROR; source_path_wide = mz_os_unicode_string_create(source_path, MZ_ENCODING_UTF8); if (!source_path_wide) { err = MZ_PARAM_ERROR; } else { target_path_wide = mz_os_unicode_string_create(target_path, MZ_ENCODING_UTF8); if (!target_path_wide) err = MZ_PARAM_ERROR; } if (err == MZ_OK) { #if _WIN32_WINNT >= _WIN32_WINNT_WINXP result = MoveFileExW(source_path_wide, target_path_wide, MOVEFILE_WRITE_THROUGH); #else result = MoveFileW(source_path_wide, target_path_wide); #endif if (result == 0) err = MZ_EXIST_ERROR; } if (target_path_wide) mz_os_unicode_string_delete(&target_path_wide); if (source_path_wide) mz_os_unicode_string_delete(&source_path_wide); return err; } int32_t mz_os_unlink(const char *path) { wchar_t *path_wide = NULL; int32_t result = 0; if (!path) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; if (mz_os_is_dir(path) == MZ_OK) result = RemoveDirectoryW(path_wide); else result = DeleteFileW(path_wide); mz_os_unicode_string_delete(&path_wide); if (result == 0) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_os_file_exists(const char *path) { wchar_t *path_wide = NULL; DWORD attribs = 0; if (!path) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; attribs = GetFileAttributesW(path_wide); mz_os_unicode_string_delete(&path_wide); if (attribs == 0xFFFFFFFF) return MZ_EXIST_ERROR; return MZ_OK; } int64_t mz_os_get_file_size(const char *path) { HANDLE handle = NULL; LARGE_INTEGER large_size; wchar_t *path_wide = NULL; if (!path) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; #if _WIN32_WINNT >= _WIN32_WINNT_WIN8 handle = CreateFile2(path_wide, GENERIC_READ, 0, OPEN_EXISTING, NULL); #else handle = CreateFileW(path_wide, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); #endif mz_os_unicode_string_delete(&path_wide); large_size.QuadPart = 0; if (handle != INVALID_HANDLE_VALUE) { GetFileSizeEx(handle, &large_size); CloseHandle(handle); } return large_size.QuadPart; } static void mz_os_file_to_unix_time(FILETIME file_time, time_t *unix_time) { uint64_t quad_file_time = 0; quad_file_time = file_time.dwLowDateTime; quad_file_time |= ((uint64_t)file_time.dwHighDateTime << 32); *unix_time = (time_t)((quad_file_time - 116444736000000000LL) / 10000000); } static void mz_os_unix_to_file_time(time_t unix_time, FILETIME *file_time) { uint64_t quad_file_time = 0; quad_file_time = ((uint64_t)unix_time * 10000000) + 116444736000000000LL; file_time->dwHighDateTime = (quad_file_time >> 32); file_time->dwLowDateTime = (uint32_t)(quad_file_time); } int32_t mz_os_get_file_date(const char *path, time_t *modified_date, time_t *accessed_date, time_t *creation_date) { WIN32_FIND_DATAW ff32; HANDLE handle = NULL; wchar_t *path_wide = NULL; int32_t err = MZ_INTERNAL_ERROR; if (!path) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; handle = FindFirstFileW(path_wide, &ff32); free(path_wide); if (handle != INVALID_HANDLE_VALUE) { if (modified_date) mz_os_file_to_unix_time(ff32.ftLastWriteTime, modified_date); if (accessed_date) mz_os_file_to_unix_time(ff32.ftLastAccessTime, accessed_date); if (creation_date) mz_os_file_to_unix_time(ff32.ftCreationTime, creation_date); FindClose(handle); err = MZ_OK; } return err; } int32_t mz_os_set_file_date(const char *path, time_t modified_date, time_t accessed_date, time_t creation_date) { HANDLE handle = NULL; FILETIME ftm_creation, ftm_accessed, ftm_modified; wchar_t *path_wide = NULL; int32_t err = MZ_OK; if (!path) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; #if _WIN32_WINNT >= _WIN32_WINNT_WIN8 handle = CreateFile2(path_wide, GENERIC_READ | GENERIC_WRITE, 0, OPEN_EXISTING, NULL); #else handle = CreateFileW(path_wide, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); #endif mz_os_unicode_string_delete(&path_wide); if (handle != INVALID_HANDLE_VALUE) { GetFileTime(handle, &ftm_creation, &ftm_accessed, &ftm_modified); if (modified_date != 0) mz_os_unix_to_file_time(modified_date, &ftm_modified); if (accessed_date != 0) mz_os_unix_to_file_time(accessed_date, &ftm_accessed); if (creation_date != 0) mz_os_unix_to_file_time(creation_date, &ftm_creation); if (SetFileTime(handle, &ftm_creation, &ftm_accessed, &ftm_modified) == 0) err = MZ_INTERNAL_ERROR; CloseHandle(handle); } return err; } int32_t mz_os_get_file_attribs(const char *path, uint32_t *attributes) { wchar_t *path_wide = NULL; int32_t err = MZ_OK; if (!path || !attributes) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; *attributes = GetFileAttributesW(path_wide); mz_os_unicode_string_delete(&path_wide); if (*attributes == INVALID_FILE_ATTRIBUTES) err = MZ_INTERNAL_ERROR; return err; } int32_t mz_os_set_file_attribs(const char *path, uint32_t attributes) { wchar_t *path_wide = NULL; int32_t err = MZ_OK; if (!path) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; if (SetFileAttributesW(path_wide, attributes) == 0) err = MZ_INTERNAL_ERROR; mz_os_unicode_string_delete(&path_wide); return err; } int32_t mz_os_make_dir(const char *path) { wchar_t *path_wide = NULL; int32_t err = MZ_OK; if (!path) return MZ_PARAM_ERROR; /* Don't try to create a drive letter */ if ((path[0] != 0) && (strlen(path) <= 3) && (path[1] == ':')) return mz_os_is_dir(path); path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; if (CreateDirectoryW(path_wide, NULL) == 0) { if (GetLastError() != ERROR_ALREADY_EXISTS) err = MZ_INTERNAL_ERROR; } mz_os_unicode_string_delete(&path_wide); return err; } DIR *mz_os_open_dir(const char *path) { WIN32_FIND_DATAW find_data; DIR_int *dir_int = NULL; wchar_t *path_wide = NULL; char fixed_path[320]; void *handle = NULL; if (!path) return NULL; strncpy(fixed_path, path, sizeof(fixed_path) - 1); fixed_path[sizeof(fixed_path) - 1] = 0; mz_path_append_slash(fixed_path, sizeof(fixed_path), MZ_PATH_SLASH_PLATFORM); mz_path_combine(fixed_path, "*", sizeof(fixed_path)); path_wide = mz_os_unicode_string_create(fixed_path, MZ_ENCODING_UTF8); if (!path_wide) return NULL; handle = FindFirstFileW(path_wide, &find_data); mz_os_unicode_string_delete(&path_wide); if (handle == INVALID_HANDLE_VALUE) return NULL; dir_int = (DIR_int *)malloc(sizeof(DIR_int)); if (!dir_int) { FindClose(handle); return NULL; } dir_int->find_handle = handle; dir_int->end = 0; memcpy(&dir_int->find_data, &find_data, sizeof(dir_int->find_data)); return (DIR *)dir_int; } struct dirent* mz_os_read_dir(DIR *dir) { DIR_int *dir_int; if (!dir) return NULL; dir_int = (DIR_int *)dir; if (dir_int->end) return NULL; WideCharToMultiByte(CP_UTF8, 0, dir_int->find_data.cFileName, -1, dir_int->entry.d_name, sizeof(dir_int->entry.d_name), NULL, NULL); if (FindNextFileW(dir_int->find_handle, &dir_int->find_data) == 0) { if (GetLastError() != ERROR_NO_MORE_FILES) return NULL; dir_int->end = 1; } return &dir_int->entry; } int32_t mz_os_close_dir(DIR *dir) { DIR_int *dir_int; if (!dir) return MZ_PARAM_ERROR; dir_int = (DIR_int *)dir; if (dir_int->find_handle != INVALID_HANDLE_VALUE) FindClose(dir_int->find_handle); free(dir_int); return MZ_OK; } int32_t mz_os_is_dir(const char *path) { wchar_t *path_wide = NULL; uint32_t attribs = 0; if (!path) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; attribs = GetFileAttributesW(path_wide); mz_os_unicode_string_delete(&path_wide); if (attribs != 0xFFFFFFFF) { if (attribs & FILE_ATTRIBUTE_DIRECTORY) return MZ_OK; } return MZ_EXIST_ERROR; } int32_t mz_os_is_symlink(const char *path) { wchar_t *path_wide = NULL; uint32_t attribs = 0; if (!path) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; attribs = GetFileAttributesW(path_wide); mz_os_unicode_string_delete(&path_wide); if (attribs != 0xFFFFFFFF) { if (attribs & FILE_ATTRIBUTE_REPARSE_POINT) return MZ_OK; } return MZ_EXIST_ERROR; } int32_t mz_os_make_symlink(const char *path, const char *target_path) { typedef BOOLEAN (WINAPI *LPCREATESYMBOLICLINKW)(LPCWSTR, LPCWSTR, DWORD); MEMORY_BASIC_INFORMATION mbi; LPCREATESYMBOLICLINKW create_symbolic_link_w = NULL; HMODULE kernel32_mod = NULL; wchar_t *path_wide = NULL; wchar_t *target_path_wide = NULL; int32_t err = MZ_OK; int32_t flags = 0; if (!path) return MZ_PARAM_ERROR; // Use VirtualQuery instead of GetModuleHandleW for UWP memset(&mbi, 0, sizeof(mbi)); VirtualQuery(VirtualQuery, &mbi, sizeof(mbi)); kernel32_mod = (HMODULE)mbi.AllocationBase; if (!kernel32_mod) return MZ_SUPPORT_ERROR; create_symbolic_link_w = (LPCREATESYMBOLICLINKW)GetProcAddress(kernel32_mod, "CreateSymbolicLinkW"); if (!create_symbolic_link_w) { return MZ_SUPPORT_ERROR; } path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) { return MZ_PARAM_ERROR; } target_path_wide = mz_os_unicode_string_create(target_path, MZ_ENCODING_UTF8); if (target_path_wide) { if (mz_path_has_slash(target_path) == MZ_OK) flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; if (create_symbolic_link_w(path_wide, target_path_wide, flags) == FALSE) err = MZ_SYMLINK_ERROR; mz_os_unicode_string_delete(&target_path_wide); } else { err = MZ_PARAM_ERROR; } mz_os_unicode_string_delete(&path_wide); return err; } int32_t mz_os_read_symlink(const char *path, char *target_path, int32_t max_target_path) { typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; }; } REPARSE_DATA_BUFFER; REPARSE_DATA_BUFFER *reparse_data = NULL; DWORD length = 0; HANDLE handle = NULL; wchar_t *path_wide = NULL; wchar_t *target_path_wide = NULL; uint8_t buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; int32_t target_path_len = 0; int32_t target_path_idx = 0; int32_t err = MZ_OK; char *target_path_utf8 = NULL; if (!path) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; #if _WIN32_WINNT >= _WIN32_WINNT_WIN8 CREATEFILE2_EXTENDED_PARAMETERS extended_params; memset(&extended_params, 0, sizeof(extended_params)); extended_params.dwSize = sizeof(extended_params); extended_params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; extended_params.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT; extended_params.dwSecurityQosFlags = SECURITY_ANONYMOUS; extended_params.lpSecurityAttributes = NULL; extended_params.hTemplateFile = NULL; handle = CreateFile2(path_wide, FILE_READ_EA, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, &extended_params); #else handle = CreateFileW(path_wide, FILE_READ_EA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); #endif if (handle == INVALID_HANDLE_VALUE) { mz_os_unicode_string_delete(&path_wide); return MZ_OPEN_ERROR; } if (DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, sizeof(buffer), &length, NULL) == TRUE) { reparse_data = (REPARSE_DATA_BUFFER *)buffer; if ((IsReparseTagMicrosoft(reparse_data->ReparseTag)) && (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK)) { target_path_len = max_target_path * sizeof(wchar_t); if (target_path_len > reparse_data->SymbolicLinkReparseBuffer.PrintNameLength) target_path_len = reparse_data->SymbolicLinkReparseBuffer.PrintNameLength; target_path_wide = (wchar_t *)malloc(target_path_len + sizeof(wchar_t)); if (target_path_wide) { target_path_idx = reparse_data->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t); memcpy(target_path_wide, &reparse_data->SymbolicLinkReparseBuffer.PathBuffer[target_path_idx], target_path_len); target_path_wide[target_path_len / sizeof(wchar_t)] = 0; target_path_utf8 = mz_os_utf8_string_create_from_unicode(target_path_wide, MZ_ENCODING_UTF8); if (target_path_utf8) { strncpy(target_path, target_path_utf8, max_target_path - 1); target_path[max_target_path - 1] = 0; /* Ensure directories have slash at the end so we can recreate them later */ if (mz_os_is_dir(target_path_utf8) == MZ_OK) mz_path_append_slash(target_path, max_target_path, MZ_PATH_SLASH_PLATFORM); mz_os_utf8_string_delete(&target_path_utf8); } else { err = MZ_MEM_ERROR; } free(target_path_wide); } else { err = MZ_MEM_ERROR; } } } else { err = MZ_INTERNAL_ERROR; } CloseHandle(handle); mz_os_unicode_string_delete(&path_wide); return err; } uint64_t mz_os_ms_time(void) { SYSTEMTIME system_time; FILETIME file_time; uint64_t quad_file_time = 0; GetSystemTime(&system_time); SystemTimeToFileTime(&system_time, &file_time); quad_file_time = file_time.dwLowDateTime; quad_file_time |= ((uint64_t)file_time.dwHighDateTime << 32); return quad_file_time / 10000 - 11644473600000LL; } #endifsight-25.1.0/3rd-party/minizip/mz_strm.c000066400000000000000000000367071503402212300200650ustar00rootroot00000000000000/* mz_strm.c -- Stream interface part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_strm.h" /***************************************************************************/ #define MZ_STREAM_FIND_SIZE (1024) /***************************************************************************/ int32_t mz_stream_open(void *stream, const char *path, int32_t mode) { mz_stream *strm = (mz_stream *)stream; if (!strm || !strm->vtbl || !strm->vtbl->open) return MZ_STREAM_ERROR; return strm->vtbl->open(strm, path, mode); } int32_t mz_stream_is_open(void *stream) { mz_stream *strm = (mz_stream *)stream; if (!strm || !strm->vtbl || !strm->vtbl->is_open) return MZ_STREAM_ERROR; return strm->vtbl->is_open(strm); } int32_t mz_stream_read(void *stream, void *buf, int32_t size) { mz_stream *strm = (mz_stream *)stream; if (!strm || !strm->vtbl || !strm->vtbl->read) return MZ_PARAM_ERROR; if (mz_stream_is_open(stream) != MZ_OK) return MZ_STREAM_ERROR; return strm->vtbl->read(strm, buf, size); } static int32_t mz_stream_read_value(void *stream, uint64_t *value, int32_t len) { uint8_t buf[8]; int32_t n = 0; int32_t i = 0; *value = 0; if (mz_stream_read(stream, buf, len) == len) { for (n = 0; n < len; n += 1, i += 8) *value += ((uint64_t)buf[n]) << i; } else if (mz_stream_error(stream)) return MZ_STREAM_ERROR; else return MZ_END_OF_STREAM; return MZ_OK; } int32_t mz_stream_read_uint8(void *stream, uint8_t *value) { int32_t err = MZ_OK; uint64_t value64 = 0; *value = 0; err = mz_stream_read_value(stream, &value64, sizeof(uint8_t)); if (err == MZ_OK) *value = (uint8_t)value64; return err; } int32_t mz_stream_read_uint16(void *stream, uint16_t *value) { int32_t err = MZ_OK; uint64_t value64 = 0; *value = 0; err = mz_stream_read_value(stream, &value64, sizeof(uint16_t)); if (err == MZ_OK) *value = (uint16_t)value64; return err; } int32_t mz_stream_read_uint32(void *stream, uint32_t *value) { int32_t err = MZ_OK; uint64_t value64 = 0; *value = 0; err = mz_stream_read_value(stream, &value64, sizeof(uint32_t)); if (err == MZ_OK) *value = (uint32_t)value64; return err; } int32_t mz_stream_read_int64(void *stream, int64_t *value) { return mz_stream_read_value(stream, (uint64_t *)value, sizeof(uint64_t)); } int32_t mz_stream_read_uint64(void *stream, uint64_t *value) { return mz_stream_read_value(stream, value, sizeof(uint64_t)); } int32_t mz_stream_write(void *stream, const void *buf, int32_t size) { mz_stream *strm = (mz_stream *)stream; if (size == 0) return size; if (!strm || !strm->vtbl || !strm->vtbl->write) return MZ_PARAM_ERROR; if (mz_stream_is_open(stream) != MZ_OK) return MZ_STREAM_ERROR; return strm->vtbl->write(strm, buf, size); } static int32_t mz_stream_write_value(void *stream, uint64_t value, int32_t len) { uint8_t buf[8]; int32_t n = 0; for (n = 0; n < len; n += 1) { buf[n] = (uint8_t)(value & 0xff); value >>= 8; } if (value != 0) { /* Data overflow - hack for ZIP64 (X Roche) */ for (n = 0; n < len; n += 1) buf[n] = 0xff; } if (mz_stream_write(stream, buf, len) != len) return MZ_STREAM_ERROR; return MZ_OK; } int32_t mz_stream_write_uint8(void *stream, uint8_t value) { return mz_stream_write_value(stream, value, sizeof(uint8_t)); } int32_t mz_stream_write_uint16(void *stream, uint16_t value) { return mz_stream_write_value(stream, value, sizeof(uint16_t)); } int32_t mz_stream_write_uint32(void *stream, uint32_t value) { return mz_stream_write_value(stream, value, sizeof(uint32_t)); } int32_t mz_stream_write_int64(void *stream, int64_t value) { return mz_stream_write_value(stream, (uint64_t)value, sizeof(uint64_t)); } int32_t mz_stream_write_uint64(void *stream, uint64_t value) { return mz_stream_write_value(stream, value, sizeof(uint64_t)); } int32_t mz_stream_copy(void *target, void *source, int32_t len) { return mz_stream_copy_stream(target, NULL, source, NULL, len); } int32_t mz_stream_copy_to_end(void *target, void *source) { return mz_stream_copy_stream_to_end(target, NULL, source, NULL); } int32_t mz_stream_copy_stream(void *target, mz_stream_write_cb write_cb, void *source, mz_stream_read_cb read_cb, int32_t len) { uint8_t buf[16384]; int32_t bytes_to_copy = 0; int32_t read = 0; int32_t written = 0; if (!write_cb) write_cb = mz_stream_write; if (!read_cb) read_cb = mz_stream_read; while (len > 0) { bytes_to_copy = len; if (bytes_to_copy > (int32_t)sizeof(buf)) bytes_to_copy = sizeof(buf); read = read_cb(source, buf, bytes_to_copy); if (read <= 0) return MZ_STREAM_ERROR; written = write_cb(target, buf, read); if (written != read) return MZ_STREAM_ERROR; len -= read; } return MZ_OK; } int32_t mz_stream_copy_stream_to_end(void *target, mz_stream_write_cb write_cb, void *source, mz_stream_read_cb read_cb) { uint8_t buf[16384]; int32_t read = 0; int32_t written = 0; if (!write_cb) write_cb = mz_stream_write; if (!read_cb) read_cb = mz_stream_read; read = read_cb(source, buf, sizeof(buf)); while (read > 0) { written = write_cb(target, buf, read); if (written != read) return MZ_STREAM_ERROR; read = read_cb(source, buf, sizeof(buf)); } if (read < 0) return MZ_STREAM_ERROR; return MZ_OK; } int64_t mz_stream_tell(void *stream) { mz_stream *strm = (mz_stream *)stream; if (!strm || !strm->vtbl || !strm->vtbl->tell) return MZ_PARAM_ERROR; if (mz_stream_is_open(stream) != MZ_OK) return MZ_STREAM_ERROR; return strm->vtbl->tell(strm); } int32_t mz_stream_seek(void *stream, int64_t offset, int32_t origin) { mz_stream *strm = (mz_stream *)stream; if (!strm || !strm->vtbl || !strm->vtbl->seek) return MZ_PARAM_ERROR; if (mz_stream_is_open(stream) != MZ_OK) return MZ_STREAM_ERROR; if (origin == MZ_SEEK_SET && offset < 0) return MZ_SEEK_ERROR; return strm->vtbl->seek(strm, offset, origin); } int32_t mz_stream_find(void *stream, const void *find, int32_t find_size, int64_t max_seek, int64_t *position) { uint8_t buf[MZ_STREAM_FIND_SIZE]; int32_t buf_pos = 0; int32_t read_size = sizeof(buf); int32_t read = 0; int64_t read_pos = 0; int64_t start_pos = 0; int64_t disk_pos = 0; int32_t i = 0; uint8_t first = 1; int32_t err = MZ_OK; if (!stream || !find || !position) return MZ_PARAM_ERROR; if (find_size < 0 || find_size >= (int32_t)sizeof(buf)) return MZ_PARAM_ERROR; *position = -1; start_pos = mz_stream_tell(stream); while (read_pos < max_seek) { if (read_size > (int32_t)(max_seek - read_pos - buf_pos) && (max_seek - read_pos - buf_pos) < (int64_t)sizeof(buf)) read_size = (int32_t)(max_seek - read_pos - buf_pos); read = mz_stream_read(stream, buf + buf_pos, read_size); if ((read <= 0) || (read + buf_pos < find_size)) break; for (i = 0; i <= read + buf_pos - find_size; i += 1) { if (memcmp(&buf[i], find, find_size) != 0) continue; disk_pos = mz_stream_tell(stream); /* Seek to position on disk where the data was found */ err = mz_stream_seek(stream, disk_pos - ((int64_t)read + buf_pos - i), MZ_SEEK_SET); if (err != MZ_OK) return MZ_EXIST_ERROR; *position = start_pos + read_pos + i; return MZ_OK; } if (first) { read -= find_size; read_size -= find_size; buf_pos = find_size; first = 0; } memmove(buf, buf + read, find_size); read_pos += read; } return MZ_EXIST_ERROR; } int32_t mz_stream_find_reverse(void *stream, const void *find, int32_t find_size, int64_t max_seek, int64_t *position) { uint8_t buf[MZ_STREAM_FIND_SIZE]; int32_t buf_pos = 0; int32_t read_size = MZ_STREAM_FIND_SIZE; int64_t read_pos = 0; int32_t read = 0; int64_t start_pos = 0; int64_t disk_pos = 0; uint8_t first = 1; int32_t i = 0; int32_t err = MZ_OK; if (!stream || !find || !position) return MZ_PARAM_ERROR; if (find_size < 0 || find_size >= (int32_t)sizeof(buf)) return MZ_PARAM_ERROR; *position = -1; start_pos = mz_stream_tell(stream); while (read_pos < max_seek) { if (read_size > (int32_t)(max_seek - read_pos) && (max_seek - read_pos) < (int64_t)sizeof(buf)) read_size = (int32_t)(max_seek - read_pos); if (mz_stream_seek(stream, start_pos - (read_pos + read_size), MZ_SEEK_SET) != MZ_OK) break; read = mz_stream_read(stream, buf, read_size); if ((read <= 0) || (read + buf_pos < find_size)) break; if (read + buf_pos < MZ_STREAM_FIND_SIZE) memmove(buf + MZ_STREAM_FIND_SIZE - (read + buf_pos), buf, read); for (i = find_size; i <= (read + buf_pos); i += 1) { if (memcmp(&buf[MZ_STREAM_FIND_SIZE - i], find, find_size) != 0) continue; disk_pos = mz_stream_tell(stream); /* Seek to position on disk where the data was found */ err = mz_stream_seek(stream, disk_pos + buf_pos - i, MZ_SEEK_SET); if (err != MZ_OK) return MZ_EXIST_ERROR; *position = start_pos - (read_pos - buf_pos + i); return MZ_OK; } if (first) { read -= find_size; read_size -= find_size; buf_pos = find_size; first = 0; } if (read == 0) break; memmove(buf + read_size, buf, find_size); read_pos += read; } return MZ_EXIST_ERROR; } int32_t mz_stream_close(void *stream) { mz_stream *strm = (mz_stream *)stream; if (!strm || !strm->vtbl || !strm->vtbl->close) return MZ_PARAM_ERROR; if (mz_stream_is_open(stream) != MZ_OK) return MZ_STREAM_ERROR; return strm->vtbl->close(strm); } int32_t mz_stream_error(void *stream) { mz_stream *strm = (mz_stream *)stream; if (!strm || !strm->vtbl || !strm->vtbl->error) return MZ_PARAM_ERROR; return strm->vtbl->error(strm); } int32_t mz_stream_set_base(void *stream, void *base) { mz_stream *strm = (mz_stream *)stream; strm->base = (mz_stream *)base; return MZ_OK; } void* mz_stream_get_interface(void *stream) { mz_stream *strm = (mz_stream *)stream; if (!strm || !strm->vtbl) return NULL; return (void *)strm->vtbl; } int32_t mz_stream_get_prop_int64(void *stream, int32_t prop, int64_t *value) { mz_stream *strm = (mz_stream *)stream; if (!strm || !strm->vtbl || !strm->vtbl->get_prop_int64) return MZ_PARAM_ERROR; return strm->vtbl->get_prop_int64(stream, prop, value); } int32_t mz_stream_set_prop_int64(void *stream, int32_t prop, int64_t value) { mz_stream *strm = (mz_stream *)stream; if (!strm || !strm->vtbl || !strm->vtbl->set_prop_int64) return MZ_PARAM_ERROR; return strm->vtbl->set_prop_int64(stream, prop, value); } void *mz_stream_create(mz_stream_vtbl *vtbl) { if (!vtbl || !vtbl->create) return NULL; return vtbl->create(); } void mz_stream_delete(void **stream) { mz_stream *strm = NULL; if (!stream) return; strm = (mz_stream *)*stream; if (strm && strm->vtbl && strm->vtbl->destroy) strm->vtbl->destroy(stream); *stream = NULL; } /***************************************************************************/ typedef struct mz_stream_raw_s { mz_stream stream; int64_t total_in; int64_t total_out; int64_t max_total_in; } mz_stream_raw; /***************************************************************************/ int32_t mz_stream_raw_open(void *stream, const char *path, int32_t mode) { MZ_UNUSED(stream); MZ_UNUSED(path); MZ_UNUSED(mode); return MZ_OK; } int32_t mz_stream_raw_is_open(void *stream) { mz_stream_raw *raw = (mz_stream_raw *)stream; return mz_stream_is_open(raw->stream.base); } int32_t mz_stream_raw_read(void *stream, void *buf, int32_t size) { mz_stream_raw *raw = (mz_stream_raw *)stream; int32_t bytes_to_read = size; int32_t read = 0; if (raw->max_total_in > 0) { if ((int64_t)bytes_to_read > (raw->max_total_in - raw->total_in)) bytes_to_read = (int32_t)(raw->max_total_in - raw->total_in); } read = mz_stream_read(raw->stream.base, buf, bytes_to_read); if (read > 0) { raw->total_in += read; raw->total_out += read; } return read; } int32_t mz_stream_raw_write(void *stream, const void *buf, int32_t size) { mz_stream_raw *raw = (mz_stream_raw *)stream; int32_t written = 0; written = mz_stream_write(raw->stream.base, buf, size); if (written > 0) { raw->total_out += written; raw->total_in += written; } return written; } int64_t mz_stream_raw_tell(void *stream) { mz_stream_raw *raw = (mz_stream_raw *)stream; return mz_stream_tell(raw->stream.base); } int32_t mz_stream_raw_seek(void *stream, int64_t offset, int32_t origin) { mz_stream_raw *raw = (mz_stream_raw *)stream; return mz_stream_seek(raw->stream.base, offset, origin); } int32_t mz_stream_raw_close(void *stream) { MZ_UNUSED(stream); return MZ_OK; } int32_t mz_stream_raw_error(void *stream) { mz_stream_raw *raw = (mz_stream_raw *)stream; return mz_stream_error(raw->stream.base); } int32_t mz_stream_raw_get_prop_int64(void *stream, int32_t prop, int64_t *value) { mz_stream_raw *raw = (mz_stream_raw *)stream; switch (prop) { case MZ_STREAM_PROP_TOTAL_IN: *value = raw->total_in; return MZ_OK; case MZ_STREAM_PROP_TOTAL_OUT: *value = raw->total_out; return MZ_OK; } return MZ_EXIST_ERROR; } int32_t mz_stream_raw_set_prop_int64(void *stream, int32_t prop, int64_t value) { mz_stream_raw *raw = (mz_stream_raw *)stream; switch (prop) { case MZ_STREAM_PROP_TOTAL_IN_MAX: raw->max_total_in = value; return MZ_OK; } return MZ_EXIST_ERROR; } /***************************************************************************/ static mz_stream_vtbl mz_stream_raw_vtbl = { mz_stream_raw_open, mz_stream_raw_is_open, mz_stream_raw_read, mz_stream_raw_write, mz_stream_raw_tell, mz_stream_raw_seek, mz_stream_raw_close, mz_stream_raw_error, mz_stream_raw_create, mz_stream_raw_delete, mz_stream_raw_get_prop_int64, mz_stream_raw_set_prop_int64 }; /***************************************************************************/ void *mz_stream_raw_create(void) { mz_stream_raw *raw = (mz_stream_raw *)calloc(1, sizeof(mz_stream_raw)); if (raw) raw->stream.vtbl = &mz_stream_raw_vtbl; return raw; } void mz_stream_raw_delete(void **stream) { mz_stream_raw *raw = NULL; if (!stream) return; raw = (mz_stream_raw *)*stream; if (raw) free(raw); *stream = NULL; } sight-25.1.0/3rd-party/minizip/mz_strm.h000066400000000000000000000134231503402212300200600ustar00rootroot00000000000000/* mz_strm.h -- Stream interface part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_STREAM_H #define MZ_STREAM_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ #define MZ_STREAM_PROP_TOTAL_IN (1) #define MZ_STREAM_PROP_TOTAL_IN_MAX (2) #define MZ_STREAM_PROP_TOTAL_OUT (3) #define MZ_STREAM_PROP_TOTAL_OUT_MAX (4) #define MZ_STREAM_PROP_HEADER_SIZE (5) #define MZ_STREAM_PROP_FOOTER_SIZE (6) #define MZ_STREAM_PROP_DISK_SIZE (7) #define MZ_STREAM_PROP_DISK_NUMBER (8) #define MZ_STREAM_PROP_COMPRESS_LEVEL (9) #define MZ_STREAM_PROP_COMPRESS_METHOD (10) #define MZ_STREAM_PROP_COMPRESS_WINDOW (11) /***************************************************************************/ typedef int32_t (*mz_stream_open_cb) (void *stream, const char *path, int32_t mode); typedef int32_t (*mz_stream_is_open_cb) (void *stream); typedef int32_t (*mz_stream_read_cb) (void *stream, void *buf, int32_t size); typedef int32_t (*mz_stream_write_cb) (void *stream, const void *buf, int32_t size); typedef int64_t (*mz_stream_tell_cb) (void *stream); typedef int32_t (*mz_stream_seek_cb) (void *stream, int64_t offset, int32_t origin); typedef int32_t (*mz_stream_close_cb) (void *stream); typedef int32_t (*mz_stream_error_cb) (void *stream); typedef void* (*mz_stream_create_cb) (void); typedef void (*mz_stream_destroy_cb) (void **stream); typedef int32_t (*mz_stream_get_prop_int64_cb) (void *stream, int32_t prop, int64_t *value); typedef int32_t (*mz_stream_set_prop_int64_cb) (void *stream, int32_t prop, int64_t value); typedef int32_t (*mz_stream_find_cb) (void *stream, const void *find, int32_t find_size, int64_t max_seek, int64_t *position); /***************************************************************************/ typedef struct mz_stream_vtbl_s { mz_stream_open_cb open; mz_stream_is_open_cb is_open; mz_stream_read_cb read; mz_stream_write_cb write; mz_stream_tell_cb tell; mz_stream_seek_cb seek; mz_stream_close_cb close; mz_stream_error_cb error; mz_stream_create_cb create; mz_stream_destroy_cb destroy; mz_stream_get_prop_int64_cb get_prop_int64; mz_stream_set_prop_int64_cb set_prop_int64; } mz_stream_vtbl; typedef struct mz_stream_s { mz_stream_vtbl *vtbl; struct mz_stream_s *base; } mz_stream; /***************************************************************************/ int32_t mz_stream_open(void *stream, const char *path, int32_t mode); int32_t mz_stream_is_open(void *stream); int32_t mz_stream_read(void *stream, void *buf, int32_t size); int32_t mz_stream_read_uint8(void *stream, uint8_t *value); int32_t mz_stream_read_uint16(void *stream, uint16_t *value); int32_t mz_stream_read_uint32(void *stream, uint32_t *value); int32_t mz_stream_read_int64(void *stream, int64_t *value); int32_t mz_stream_read_uint64(void *stream, uint64_t *value); int32_t mz_stream_write(void *stream, const void *buf, int32_t size); int32_t mz_stream_write_uint8(void *stream, uint8_t value); int32_t mz_stream_write_uint16(void *stream, uint16_t value); int32_t mz_stream_write_uint32(void *stream, uint32_t value); int32_t mz_stream_write_int64(void *stream, int64_t value); int32_t mz_stream_write_uint64(void *stream, uint64_t value); int32_t mz_stream_copy(void *target, void *source, int32_t len); int32_t mz_stream_copy_to_end(void *target, void *source); int32_t mz_stream_copy_stream(void *target, mz_stream_write_cb write_cb, void *source, mz_stream_read_cb read_cb, int32_t len); int32_t mz_stream_copy_stream_to_end(void *target, mz_stream_write_cb write_cb, void *source, mz_stream_read_cb read_cb); int64_t mz_stream_tell(void *stream); int32_t mz_stream_seek(void *stream, int64_t offset, int32_t origin); int32_t mz_stream_find(void *stream, const void *find, int32_t find_size, int64_t max_seek, int64_t *position); int32_t mz_stream_find_reverse(void *stream, const void *find, int32_t find_size, int64_t max_seek, int64_t *position); int32_t mz_stream_close(void *stream); int32_t mz_stream_error(void *stream); int32_t mz_stream_set_base(void *stream, void *base); void* mz_stream_get_interface(void *stream); int32_t mz_stream_get_prop_int64(void *stream, int32_t prop, int64_t *value); int32_t mz_stream_set_prop_int64(void *stream, int32_t prop, int64_t value); void* mz_stream_create(mz_stream_vtbl *vtbl); void mz_stream_delete(void **stream); /***************************************************************************/ int32_t mz_stream_raw_open(void *stream, const char *filename, int32_t mode); int32_t mz_stream_raw_is_open(void *stream); int32_t mz_stream_raw_read(void *stream, void *buf, int32_t size); int32_t mz_stream_raw_write(void *stream, const void *buf, int32_t size); int64_t mz_stream_raw_tell(void *stream); int32_t mz_stream_raw_seek(void *stream, int64_t offset, int32_t origin); int32_t mz_stream_raw_close(void *stream); int32_t mz_stream_raw_error(void *stream); int32_t mz_stream_raw_get_prop_int64(void *stream, int32_t prop, int64_t *value); int32_t mz_stream_raw_set_prop_int64(void *stream, int32_t prop, int64_t value); void* mz_stream_raw_create(void); void mz_stream_raw_delete(void **stream); /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_strm_buf.c000066400000000000000000000315271503402212300207140ustar00rootroot00000000000000/* mz_strm_buf.c -- Stream for buffering reads/writes part of the minizip-ng project This version of ioapi is designed to buffer IO. Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_strm.h" #include "mz_strm_buf.h" /***************************************************************************/ static mz_stream_vtbl mz_stream_buffered_vtbl = { mz_stream_buffered_open, mz_stream_buffered_is_open, mz_stream_buffered_read, mz_stream_buffered_write, mz_stream_buffered_tell, mz_stream_buffered_seek, mz_stream_buffered_close, mz_stream_buffered_error, mz_stream_buffered_create, mz_stream_buffered_delete, NULL, NULL }; /***************************************************************************/ typedef struct mz_stream_buffered_s { mz_stream stream; int32_t error; char readbuf[INT16_MAX]; int32_t readbuf_len; int32_t readbuf_pos; int32_t readbuf_hits; int32_t readbuf_misses; char writebuf[INT16_MAX]; int32_t writebuf_len; int32_t writebuf_pos; int32_t writebuf_hits; int32_t writebuf_misses; int64_t position; } mz_stream_buffered; /***************************************************************************/ #if 0 # define mz_stream_buffered_print printf #else # define mz_stream_buffered_print(fmt,...) #endif /***************************************************************************/ static int32_t mz_stream_buffered_reset(void *stream) { mz_stream_buffered *buffered = (mz_stream_buffered *)stream; buffered->readbuf_len = 0; buffered->readbuf_pos = 0; buffered->writebuf_len = 0; buffered->writebuf_pos = 0; buffered->position = 0; return MZ_OK; } int32_t mz_stream_buffered_open(void *stream, const char *path, int32_t mode) { mz_stream_buffered *buffered = (mz_stream_buffered *)stream; mz_stream_buffered_print("Buffered - Open (mode %" PRId32 ")\n", mode); mz_stream_buffered_reset(buffered); return mz_stream_open(buffered->stream.base, path, mode); } int32_t mz_stream_buffered_is_open(void *stream) { mz_stream_buffered *buffered = (mz_stream_buffered *)stream; return mz_stream_is_open(buffered->stream.base); } static int32_t mz_stream_buffered_flush(void *stream, int32_t *written) { mz_stream_buffered *buffered = (mz_stream_buffered *)stream; int32_t total_bytes_written = 0; int32_t bytes_to_write = buffered->writebuf_len; int32_t bytes_left_to_write = buffered->writebuf_len; int32_t bytes_written = 0; *written = 0; while (bytes_left_to_write > 0) { bytes_written = mz_stream_write(buffered->stream.base, buffered->writebuf + (bytes_to_write - bytes_left_to_write), bytes_left_to_write); if (bytes_written != bytes_left_to_write) return MZ_WRITE_ERROR; buffered->writebuf_misses += 1; mz_stream_buffered_print("Buffered - Write flush (%" PRId32 ":%" PRId32 " len %" PRId32 ")\n", bytes_to_write, bytes_left_to_write, buffered->writebuf_len); total_bytes_written += bytes_written; bytes_left_to_write -= bytes_written; buffered->position += bytes_written; } buffered->writebuf_len = 0; buffered->writebuf_pos = 0; *written = total_bytes_written; return MZ_OK; } int32_t mz_stream_buffered_read(void *stream, void *buf, int32_t size) { mz_stream_buffered *buffered = (mz_stream_buffered *)stream; int32_t buf_len = 0; int32_t bytes_to_read = 0; int32_t bytes_to_copy = 0; int32_t bytes_left_to_read = size; int32_t bytes_read = 0; int32_t bytes_flushed = 0; mz_stream_buffered_print("Buffered - Read (size %" PRId32 " pos %" PRId64 ")\n", size, buffered->position); if (buffered->writebuf_len > 0) { int64_t position = buffered->position + buffered->writebuf_pos mz_stream_buffered_print("Buffered - Switch from write to read, flushing (pos %" PRId64 ")\n", position); mz_stream_buffered_flush(stream, &bytes_flushed); mz_stream_buffered_seek(stream, position, MZ_SEEK_SET); } while (bytes_left_to_read > 0) { if ((buffered->readbuf_len == 0) || (buffered->readbuf_pos == buffered->readbuf_len)) { if (buffered->readbuf_len == sizeof(buffered->readbuf)) { buffered->readbuf_pos = 0; buffered->readbuf_len = 0; } bytes_to_read = (int32_t)sizeof(buffered->readbuf) - (buffered->readbuf_len - buffered->readbuf_pos); bytes_read = mz_stream_read(buffered->stream.base, buffered->readbuf + buffered->readbuf_pos, bytes_to_read); if (bytes_read < 0) return bytes_read; buffered->readbuf_misses += 1; buffered->readbuf_len += bytes_read; buffered->position += bytes_read; mz_stream_buffered_print("Buffered - Filled (read %" PRId32 "/%" PRId32 " buf %" PRId32 ":%" PRId32 " pos %" PRId64 ")\n", bytes_read, bytes_to_read, buffered->readbuf_pos, buffered->readbuf_len, buffered->position); if (bytes_read == 0) break; } if ((buffered->readbuf_len - buffered->readbuf_pos) > 0) { bytes_to_copy = buffered->readbuf_len - buffered->readbuf_pos; if (bytes_to_copy > bytes_left_to_read) bytes_to_copy = bytes_left_to_read; memcpy((char *)buf + buf_len, buffered->readbuf + buffered->readbuf_pos, bytes_to_copy); buf_len += bytes_to_copy; bytes_left_to_read -= bytes_to_copy; buffered->readbuf_hits += 1; buffered->readbuf_pos += bytes_to_copy; mz_stream_buffered_print("Buffered - Emptied (copied %" PRId32 " remaining %" PRId32 " buf %" PRId32 ":%" PRId32 " pos %" PRId64 ")\n", bytes_to_copy, bytes_left_to_read, buffered->readbuf_pos, buffered->readbuf_len, buffered->position); } } return size - bytes_left_to_read; } int32_t mz_stream_buffered_write(void *stream, const void *buf, int32_t size) { mz_stream_buffered *buffered = (mz_stream_buffered *)stream; int32_t bytes_to_write = size; int32_t bytes_left_to_write = size; int32_t bytes_to_copy = 0; int32_t bytes_used = 0; int32_t bytes_flushed = 0; int32_t err = MZ_OK; mz_stream_buffered_print("Buffered - Write (size %" PRId32 " len %" PRId32 " pos %" PRId64 ")\n", size, buffered->writebuf_len, buffered->position); if (buffered->readbuf_len > 0) { buffered->position -= buffered->readbuf_len; buffered->position += buffered->readbuf_pos; buffered->readbuf_len = 0; buffered->readbuf_pos = 0; mz_stream_buffered_print("Buffered - Switch from read to write (pos %" PRId64 ")\n", buffered->position); err = mz_stream_seek(buffered->stream.base, buffered->position, MZ_SEEK_SET); if (err != MZ_OK) return err; } while (bytes_left_to_write > 0) { bytes_used = buffered->writebuf_len; if (bytes_used > buffered->writebuf_pos) bytes_used = buffered->writebuf_pos; bytes_to_copy = (int32_t)sizeof(buffered->writebuf) - bytes_used; if (bytes_to_copy > bytes_left_to_write) bytes_to_copy = bytes_left_to_write; if (bytes_to_copy == 0) { err = mz_stream_buffered_flush(stream, &bytes_flushed); if (err != MZ_OK) return err; if (bytes_flushed == 0) return 0; continue; } memcpy(buffered->writebuf + buffered->writebuf_pos, (const char *)buf + (bytes_to_write - bytes_left_to_write), bytes_to_copy); mz_stream_buffered_print("Buffered - Write copy (remaining %" PRId32 " write %" PRId32 ":%" PRId32 " len %" PRId32 ")\n", bytes_to_copy, bytes_to_write, bytes_left_to_write, buffered->writebuf_len); bytes_left_to_write -= bytes_to_copy; buffered->writebuf_pos += bytes_to_copy; buffered->writebuf_hits += 1; if (buffered->writebuf_pos > buffered->writebuf_len) buffered->writebuf_len += buffered->writebuf_pos - buffered->writebuf_len; } return size - bytes_left_to_write; } int64_t mz_stream_buffered_tell(void *stream) { mz_stream_buffered *buffered = (mz_stream_buffered *)stream; int64_t position = mz_stream_tell(buffered->stream.base); buffered->position = position; mz_stream_buffered_print("Buffered - Tell (pos %" PRId64 " readpos %" PRId32 " writepos %" PRId32 ")\n", buffered->position, buffered->readbuf_pos, buffered->writebuf_pos); if (buffered->readbuf_len > 0) position -= ((int64_t)buffered->readbuf_len - buffered->readbuf_pos); if (buffered->writebuf_len > 0) position += buffered->writebuf_pos; return position; } int32_t mz_stream_buffered_seek(void *stream, int64_t offset, int32_t origin) { mz_stream_buffered *buffered = (mz_stream_buffered *)stream; int32_t bytes_flushed = 0; int32_t err = MZ_OK; mz_stream_buffered_print("Buffered - Seek (origin %" PRId32 " offset %" PRId64 " pos %" PRId64 ")\n", origin, offset, buffered->position); switch (origin) { case MZ_SEEK_SET: if ((buffered->readbuf_len > 0) && (offset < buffered->position) && (offset >= buffered->position - buffered->readbuf_len)) { buffered->readbuf_pos = (int32_t)(offset - (buffered->position - buffered->readbuf_len)); return MZ_OK; } if (buffered->writebuf_len > 0) { if ((offset >= buffered->position) && (offset <= buffered->position + buffered->writebuf_len)) { buffered->writebuf_pos = (int32_t)(offset - buffered->position); return MZ_OK; } } err = mz_stream_buffered_flush(stream, &bytes_flushed); if (err != MZ_OK) return err; buffered->position = offset; break; case MZ_SEEK_CUR: if (buffered->readbuf_len > 0) { if (offset <= ((int64_t)buffered->readbuf_len - buffered->readbuf_pos)) { buffered->readbuf_pos += (uint32_t)offset; return MZ_OK; } offset -= ((int64_t)buffered->readbuf_len - buffered->readbuf_pos); buffered->position += offset; } if (buffered->writebuf_len > 0) { if (offset <= ((int64_t)buffered->writebuf_len - buffered->writebuf_pos)) { buffered->writebuf_pos += (uint32_t)offset; return MZ_OK; } /* offset -= (buffered->writebuf_len - buffered->writebuf_pos); */ } err = mz_stream_buffered_flush(stream, &bytes_flushed); if (err != MZ_OK) return err; break; case MZ_SEEK_END: if (buffered->writebuf_len > 0) { buffered->writebuf_pos = buffered->writebuf_len; return MZ_OK; } break; } buffered->readbuf_len = 0; buffered->readbuf_pos = 0; buffered->writebuf_len = 0; buffered->writebuf_pos = 0; return mz_stream_seek(buffered->stream.base, offset, origin); } int32_t mz_stream_buffered_close(void *stream) { mz_stream_buffered *buffered = (mz_stream_buffered *)stream; int32_t bytes_flushed = 0; mz_stream_buffered_flush(stream, &bytes_flushed); mz_stream_buffered_print("Buffered - Close (flushed %" PRId32 ")\n", bytes_flushed); if (buffered->readbuf_hits + buffered->readbuf_misses > 0) { mz_stream_buffered_print("Buffered - Read efficiency %.02f%%\n", (buffered->readbuf_hits / ((float)buffered->readbuf_hits + buffered->readbuf_misses)) * 100); } if (buffered->writebuf_hits + buffered->writebuf_misses > 0) { mz_stream_buffered_print("Buffered - Write efficiency %.02f%%\n", (buffered->writebuf_hits / ((float)buffered->writebuf_hits + buffered->writebuf_misses)) * 100); } mz_stream_buffered_reset(buffered); return mz_stream_close(buffered->stream.base); } int32_t mz_stream_buffered_error(void *stream) { mz_stream_buffered *buffered = (mz_stream_buffered *)stream; return mz_stream_error(buffered->stream.base); } void *mz_stream_buffered_create(void) { mz_stream_buffered *buffered = (mz_stream_buffered *)calloc(1, sizeof(mz_stream_buffered)); if (buffered) buffered->stream.vtbl = &mz_stream_buffered_vtbl; return buffered; } void mz_stream_buffered_delete(void **stream) { mz_stream_buffered *buffered = NULL; if (!stream) return; buffered = (mz_stream_buffered *)*stream; if (buffered) free(buffered); *stream = NULL; } void *mz_stream_buffered_get_interface(void) { return (void *)&mz_stream_buffered_vtbl; } sight-25.1.0/3rd-party/minizip/mz_strm_buf.h000066400000000000000000000024351503402212300207150ustar00rootroot00000000000000/* mz_strm_buf.h -- Stream for buffering reads/writes part of the minizip-ng project This version of ioapi is designed to buffer IO. Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_STREAM_BUFFERED_H #define MZ_STREAM_BUFFERED_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ int32_t mz_stream_buffered_open(void *stream, const char *path, int32_t mode); int32_t mz_stream_buffered_is_open(void *stream); int32_t mz_stream_buffered_read(void *stream, void *buf, int32_t size); int32_t mz_stream_buffered_write(void *stream, const void *buf, int32_t size); int64_t mz_stream_buffered_tell(void *stream); int32_t mz_stream_buffered_seek(void *stream, int64_t offset, int32_t origin); int32_t mz_stream_buffered_close(void *stream); int32_t mz_stream_buffered_error(void *stream); void* mz_stream_buffered_create(void); void mz_stream_buffered_delete(void **stream); void* mz_stream_buffered_get_interface(void); /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_strm_mem.c000066400000000000000000000155531503402212300207170ustar00rootroot00000000000000/* mz_strm_mem.c -- Stream for memory access part of the minizip-ng project This interface is designed to access memory rather than files. We do use a region of memory to put data in to and take it out of. Based on Unzip ioapi.c version 0.22, May 19th, 2003 Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng Copyright (C) 2003 Justin Fletcher Copyright (C) 1998-2003 Gilles Vollant https://www.winimage.com/zLibDll/minizip.html This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_strm.h" #include "mz_strm_mem.h" /***************************************************************************/ static mz_stream_vtbl mz_stream_mem_vtbl = { mz_stream_mem_open, mz_stream_mem_is_open, mz_stream_mem_read, mz_stream_mem_write, mz_stream_mem_tell, mz_stream_mem_seek, mz_stream_mem_close, mz_stream_mem_error, mz_stream_mem_create, mz_stream_mem_delete, NULL, NULL }; /***************************************************************************/ typedef struct mz_stream_mem_s { mz_stream stream; int32_t mode; uint8_t *buffer; /* Memory buffer pointer */ int32_t size; /* Size of the memory buffer */ int32_t limit; /* Furthest we've written */ int32_t position; /* Current position in the memory */ int32_t grow_size; /* Size to grow when full */ } mz_stream_mem; /***************************************************************************/ static int32_t mz_stream_mem_set_size(void *stream, int32_t size) { mz_stream_mem *mem = (mz_stream_mem *)stream; int32_t new_size = size; uint8_t *new_buf = NULL; new_buf = (uint8_t *)malloc((uint32_t)new_size); if (!new_buf) return MZ_BUF_ERROR; if (mem->buffer) { memcpy(new_buf, mem->buffer, mem->size); free(mem->buffer); } mem->buffer = new_buf; mem->size = new_size; return MZ_OK; } int32_t mz_stream_mem_open(void *stream, const char *path, int32_t mode) { mz_stream_mem *mem = (mz_stream_mem *)stream; int32_t err = MZ_OK; MZ_UNUSED(path); mem->mode = mode; mem->limit = 0; mem->position = 0; if (mem->mode & MZ_OPEN_MODE_CREATE) err = mz_stream_mem_set_size(stream, mem->grow_size); else mem->limit = mem->size; return err; } int32_t mz_stream_mem_is_open(void *stream) { mz_stream_mem *mem = (mz_stream_mem *)stream; if (!mem->buffer) return MZ_OPEN_ERROR; return MZ_OK; } int32_t mz_stream_mem_read(void *stream, void *buf, int32_t size) { mz_stream_mem *mem = (mz_stream_mem *)stream; if (size > mem->size - mem->position) size = mem->size - mem->position; if (mem->position + size > mem->limit) size = mem->limit - mem->position; if (size <= 0) return 0; memcpy(buf, mem->buffer + mem->position, size); mem->position += size; return size; } int32_t mz_stream_mem_write(void *stream, const void *buf, int32_t size) { mz_stream_mem *mem = (mz_stream_mem *)stream; int32_t new_size = 0; int32_t err = MZ_OK; if (!size) return size; if (size > mem->size - mem->position) { if (mem->mode & MZ_OPEN_MODE_CREATE) { new_size = mem->size; if (size < mem->grow_size) new_size += mem->grow_size; else new_size += size; err = mz_stream_mem_set_size(stream, new_size); if (err != MZ_OK) return err; } else { size = mem->size - mem->position; } } memcpy(mem->buffer + mem->position, buf, size); mem->position += size; if (mem->position > mem->limit) mem->limit = mem->position; return size; } int64_t mz_stream_mem_tell(void *stream) { mz_stream_mem *mem = (mz_stream_mem *)stream; return mem->position; } int32_t mz_stream_mem_seek(void *stream, int64_t offset, int32_t origin) { mz_stream_mem *mem = (mz_stream_mem *)stream; int64_t new_pos = 0; int32_t err = MZ_OK; switch (origin) { case MZ_SEEK_CUR: new_pos = mem->position + offset; break; case MZ_SEEK_END: new_pos = mem->limit + offset; break; case MZ_SEEK_SET: new_pos = offset; break; default: return MZ_SEEK_ERROR; } if (new_pos > mem->size) { if ((mem->mode & MZ_OPEN_MODE_CREATE) == 0) return MZ_SEEK_ERROR; err = mz_stream_mem_set_size(stream, (int32_t)new_pos); if (err != MZ_OK) return err; } else if (new_pos < 0) { return MZ_SEEK_ERROR; } mem->position = (int32_t)new_pos; return MZ_OK; } int32_t mz_stream_mem_close(void *stream) { MZ_UNUSED(stream); /* We never return errors */ return MZ_OK; } int32_t mz_stream_mem_error(void *stream) { MZ_UNUSED(stream); /* We never return errors */ return MZ_OK; } void mz_stream_mem_set_buffer(void *stream, void *buf, int32_t size) { mz_stream_mem *mem = (mz_stream_mem *)stream; mem->buffer = (uint8_t *)buf; mem->size = size; mem->limit = size; } int32_t mz_stream_mem_get_buffer(void *stream, const void **buf) { return mz_stream_mem_get_buffer_at(stream, 0, buf); } int32_t mz_stream_mem_get_buffer_at(void *stream, int64_t position, const void **buf) { mz_stream_mem *mem = (mz_stream_mem *)stream; if (!buf || position < 0 || !mem->buffer|| mem->size < position) return MZ_SEEK_ERROR; *buf = mem->buffer + position; return MZ_OK; } int32_t mz_stream_mem_get_buffer_at_current(void *stream, const void **buf) { mz_stream_mem *mem = (mz_stream_mem *)stream; return mz_stream_mem_get_buffer_at(stream, mem->position, buf); } void mz_stream_mem_get_buffer_length(void *stream, int32_t *length) { mz_stream_mem *mem = (mz_stream_mem *)stream; *length = mem->limit; } void mz_stream_mem_set_buffer_limit(void *stream, int32_t limit) { mz_stream_mem *mem = (mz_stream_mem *)stream; mem->limit = limit; } void mz_stream_mem_set_grow_size(void *stream, int32_t grow_size) { mz_stream_mem *mem = (mz_stream_mem *)stream; mem->grow_size = grow_size; } void *mz_stream_mem_create(void) { mz_stream_mem *mem = (mz_stream_mem *)calloc(1, sizeof(mz_stream_mem)); if (mem) { mem->stream.vtbl = &mz_stream_mem_vtbl; mem->grow_size = 4096; } return mem; } void mz_stream_mem_delete(void **stream) { mz_stream_mem *mem = NULL; if (!stream) return; mem = (mz_stream_mem *)*stream; if (mem) { if ((mem->mode & MZ_OPEN_MODE_CREATE) && (mem->buffer)) free(mem->buffer); free(mem); } *stream = NULL; } void *mz_stream_mem_get_interface(void) { return (void *)&mz_stream_mem_vtbl; } sight-25.1.0/3rd-party/minizip/mz_strm_mem.h000066400000000000000000000032461503402212300207200ustar00rootroot00000000000000/* mz_strm_mem.h -- Stream for memory access part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_STREAM_MEM_H #define MZ_STREAM_MEM_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ int32_t mz_stream_mem_open(void *stream, const char *filename, int32_t mode); int32_t mz_stream_mem_is_open(void *stream); int32_t mz_stream_mem_read(void *stream, void *buf, int32_t size); int32_t mz_stream_mem_write(void *stream, const void *buf, int32_t size); int64_t mz_stream_mem_tell(void *stream); int32_t mz_stream_mem_seek(void *stream, int64_t offset, int32_t origin); int32_t mz_stream_mem_close(void *stream); int32_t mz_stream_mem_error(void *stream); void mz_stream_mem_set_buffer(void *stream, void *buf, int32_t size); int32_t mz_stream_mem_get_buffer(void *stream, const void **buf); int32_t mz_stream_mem_get_buffer_at(void *stream, int64_t position, const void **buf); int32_t mz_stream_mem_get_buffer_at_current(void *stream, const void **buf); void mz_stream_mem_get_buffer_length(void *stream, int32_t *length); void mz_stream_mem_set_buffer_limit(void *stream, int32_t limit); void mz_stream_mem_set_grow_size(void *stream, int32_t grow_size); void* mz_stream_mem_create(void); void mz_stream_mem_delete(void **stream); void* mz_stream_mem_get_interface(void); /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_strm_os.h000066400000000000000000000022251503402212300205570ustar00rootroot00000000000000/* mz_sstrm_os.h -- Stream for filesystem access part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_STREAM_OS_H #define MZ_STREAM_OS_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ int32_t mz_stream_os_open(void *stream, const char *path, int32_t mode); int32_t mz_stream_os_is_open(void *stream); int32_t mz_stream_os_read(void *stream, void *buf, int32_t size); int32_t mz_stream_os_write(void *stream, const void *buf, int32_t size); int64_t mz_stream_os_tell(void *stream); int32_t mz_stream_os_seek(void *stream, int64_t offset, int32_t origin); int32_t mz_stream_os_close(void *stream); int32_t mz_stream_os_error(void *stream); void* mz_stream_os_create(void); void mz_stream_os_delete(void **stream); void* mz_stream_os_get_interface(void); /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_strm_os_posix.c000066400000000000000000000120571503402212300220000ustar00rootroot00000000000000/* mz_strm_posix.c -- Stream for filesystem access for posix/linux part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng Modifications for Zip64 support Copyright (C) 2009-2010 Mathias Svensson http://result42.com Copyright (C) 1998-2010 Gilles Vollant https://www.winimage.com/zLibDll/minizip.html This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef WIN32 #include "mz.h" #include "mz_strm.h" #include "mz_strm_os.h" #include /* fopen, fread.. */ #include /***************************************************************************/ #define fopen64 fopen #ifndef MZ_FILE32_API # ifndef NO_FSEEKO # define ftello64 ftello # define fseeko64 fseeko # elif defined(_MSC_VER) && (_MSC_VER >= 1400) # define ftello64 _ftelli64 # define fseeko64 _fseeki64 # endif #endif #ifndef ftello64 # define ftello64 ftell #endif #ifndef fseeko64 # define fseeko64 fseek #endif /***************************************************************************/ static mz_stream_vtbl mz_stream_os_vtbl = { mz_stream_os_open, mz_stream_os_is_open, mz_stream_os_read, mz_stream_os_write, mz_stream_os_tell, mz_stream_os_seek, mz_stream_os_close, mz_stream_os_error, mz_stream_os_create, mz_stream_os_delete, NULL, NULL }; /***************************************************************************/ typedef struct mz_stream_posix_s { mz_stream stream; int32_t error; FILE *handle; } mz_stream_posix; /***************************************************************************/ int32_t mz_stream_os_open(void *stream, const char *path, int32_t mode) { mz_stream_posix *posix = (mz_stream_posix *)stream; const char *mode_fopen = NULL; if (!path) return MZ_PARAM_ERROR; if ((mode & MZ_OPEN_MODE_READWRITE) == MZ_OPEN_MODE_READ) mode_fopen = "rb"; else if (mode & MZ_OPEN_MODE_APPEND) mode_fopen = "r+b"; else if (mode & MZ_OPEN_MODE_CREATE) mode_fopen = "wb"; else return MZ_OPEN_ERROR; posix->handle = fopen64(path, mode_fopen); if (!posix->handle) { posix->error = errno; return MZ_OPEN_ERROR; } if (mode & MZ_OPEN_MODE_APPEND) return mz_stream_os_seek(stream, 0, MZ_SEEK_END); return MZ_OK; } int32_t mz_stream_os_is_open(void *stream) { mz_stream_posix *posix = (mz_stream_posix *)stream; if (!posix->handle) return MZ_OPEN_ERROR; return MZ_OK; } int32_t mz_stream_os_read(void *stream, void *buf, int32_t size) { mz_stream_posix *posix = (mz_stream_posix *)stream; int32_t read = (int32_t)fread(buf, 1, (size_t)size, posix->handle); if (read < size && ferror(posix->handle)) { posix->error = errno; return MZ_READ_ERROR; } return read; } int32_t mz_stream_os_write(void *stream, const void *buf, int32_t size) { mz_stream_posix *posix = (mz_stream_posix *)stream; int32_t written = (int32_t)fwrite(buf, 1, (size_t)size, posix->handle); if (written < size && ferror(posix->handle)) { posix->error = errno; return MZ_WRITE_ERROR; } return written; } int64_t mz_stream_os_tell(void *stream) { mz_stream_posix *posix = (mz_stream_posix *)stream; int64_t position = ftello64(posix->handle); if (position == -1) { posix->error = errno; return MZ_TELL_ERROR; } return position; } int32_t mz_stream_os_seek(void *stream, int64_t offset, int32_t origin) { mz_stream_posix *posix = (mz_stream_posix *)stream; int32_t fseek_origin = 0; switch (origin) { case MZ_SEEK_CUR: fseek_origin = SEEK_CUR; break; case MZ_SEEK_END: fseek_origin = SEEK_END; break; case MZ_SEEK_SET: fseek_origin = SEEK_SET; break; default: return MZ_SEEK_ERROR; } if (fseeko64(posix->handle, offset, fseek_origin) != 0) { posix->error = errno; return MZ_SEEK_ERROR; } return MZ_OK; } int32_t mz_stream_os_close(void *stream) { mz_stream_posix *posix = (mz_stream_posix *)stream; int32_t closed = 0; if (posix->handle) { closed = fclose(posix->handle); posix->handle = NULL; } if (closed != 0) { posix->error = errno; return MZ_CLOSE_ERROR; } return MZ_OK; } int32_t mz_stream_os_error(void *stream) { mz_stream_posix *posix = (mz_stream_posix *)stream; return posix->error; } void *mz_stream_os_create(void) { mz_stream_posix *posix = (mz_stream_posix *)calloc(1, sizeof(mz_stream_posix)); if (posix) posix->stream.vtbl = &mz_stream_os_vtbl; return posix; } void mz_stream_os_delete(void **stream) { mz_stream_posix *posix = NULL; if (!stream) return; posix = (mz_stream_posix *)*stream; if (posix) free(posix); *stream = NULL; } void *mz_stream_os_get_interface(void) { return (void *)&mz_stream_os_vtbl; } #endifsight-25.1.0/3rd-party/minizip/mz_strm_os_win32.c000066400000000000000000000172201503402212300215750ustar00rootroot00000000000000/* mz_strm_win32.c -- Stream for filesystem access for windows part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng Copyright (C) 2009-2010 Mathias Svensson Modifications for Zip64 support http://result42.com Copyright (C) 1998-2010 Gilles Vollant https://www.winimage.com/zLibDll/minizip.html This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifdef WIN32 #include "mz.h" #include "mz_os.h" #include "mz_strm.h" #include "mz_strm_os.h" #include /***************************************************************************/ #ifndef INVALID_HANDLE_VALUE # define INVALID_HANDLE_VALUE 0xFFFFFFFF #endif #ifndef INVALID_SET_FILE_POINTER # define INVALID_SET_FILE_POINTER (DWORD)-1 #endif #ifndef _WIN32_WINNT_WIN8 # define _WIN32_WINNT_WIN8 0x0602 #endif /***************************************************************************/ static mz_stream_vtbl mz_stream_os_vtbl = { mz_stream_os_open, mz_stream_os_is_open, mz_stream_os_read, mz_stream_os_write, mz_stream_os_tell, mz_stream_os_seek, mz_stream_os_close, mz_stream_os_error, mz_stream_os_create, mz_stream_os_delete, NULL, NULL }; /***************************************************************************/ typedef struct mz_stream_win32_s { mz_stream stream; HANDLE handle; int32_t error; } mz_stream_win32; /***************************************************************************/ #if 0 # define mz_stream_os_print printf #else # define mz_stream_os_print(fmt, ...) #endif /***************************************************************************/ int32_t mz_stream_os_open(void *stream, const char *path, int32_t mode) { mz_stream_win32 *win32 = (mz_stream_win32 *)stream; uint32_t desired_access = 0; uint32_t creation_disposition = 0; uint32_t share_mode = FILE_SHARE_READ; uint32_t flags_attribs = FILE_ATTRIBUTE_NORMAL; wchar_t *path_wide = NULL; if (!path) return MZ_PARAM_ERROR; /* Some use cases require write sharing as well */ share_mode |= FILE_SHARE_WRITE; if ((mode & MZ_OPEN_MODE_READWRITE) == MZ_OPEN_MODE_READ) { desired_access = GENERIC_READ; creation_disposition = OPEN_EXISTING; } else if (mode & MZ_OPEN_MODE_APPEND) { desired_access = GENERIC_WRITE | GENERIC_READ; creation_disposition = OPEN_EXISTING; } else if (mode & MZ_OPEN_MODE_CREATE) { desired_access = GENERIC_WRITE | GENERIC_READ; creation_disposition = CREATE_ALWAYS; } else { return MZ_PARAM_ERROR; } mz_stream_os_print("Win32 - Open - %s (mode %" PRId32 ")\n", path); path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (!path_wide) return MZ_PARAM_ERROR; #if _WIN32_WINNT >= _WIN32_WINNT_WIN8 win32->handle = CreateFile2(path_wide, desired_access, share_mode, creation_disposition, NULL); #else win32->handle = CreateFileW(path_wide, desired_access, share_mode, NULL, creation_disposition, flags_attribs, NULL); #endif mz_os_unicode_string_delete(&path_wide); if (mz_stream_os_is_open(stream) != MZ_OK) { win32->error = GetLastError(); return MZ_OPEN_ERROR; } if (mode & MZ_OPEN_MODE_APPEND) return mz_stream_os_seek(stream, 0, MZ_SEEK_END); return MZ_OK; } int32_t mz_stream_os_is_open(void *stream) { mz_stream_win32 *win32 = (mz_stream_win32 *)stream; if (!win32->handle || win32->handle == INVALID_HANDLE_VALUE) return MZ_OPEN_ERROR; return MZ_OK; } int32_t mz_stream_os_read(void *stream, void *buf, int32_t size) { mz_stream_win32 *win32 = (mz_stream_win32 *)stream; uint32_t read = 0; if (mz_stream_os_is_open(stream) != MZ_OK) return MZ_OPEN_ERROR; if (!ReadFile(win32->handle, buf, size, (DWORD *)&read, NULL)) { win32->error = GetLastError(); if (win32->error == ERROR_HANDLE_EOF) win32->error = 0; } mz_stream_os_print("Win32 - Read - %" PRId32 "\n", read); return read; } int32_t mz_stream_os_write(void *stream, const void *buf, int32_t size) { mz_stream_win32 *win32 = (mz_stream_win32 *)stream; int32_t written = 0; if (mz_stream_os_is_open(stream) != MZ_OK) return MZ_OPEN_ERROR; if (!WriteFile(win32->handle, buf, size, (DWORD *)&written, NULL)) { win32->error = GetLastError(); if (win32->error == ERROR_HANDLE_EOF) win32->error = 0; } mz_stream_os_print("Win32 - Write - %" PRId32 "\n", written); return written; } static int32_t mz_stream_os_seekinternal(HANDLE handle, LARGE_INTEGER large_pos, LARGE_INTEGER *new_pos, uint32_t move_method) { #if _WIN32_WINNT >= _WIN32_WINNT_WINXP BOOL success = FALSE; success = SetFilePointerEx(handle, large_pos, new_pos, move_method); if ((success == FALSE) && (GetLastError() != NO_ERROR)) return MZ_SEEK_ERROR; return MZ_OK; #else LONG high_part = 0; uint32_t pos = 0; high_part = large_pos.HighPart; pos = SetFilePointer(handle, large_pos.LowPart, &high_part, move_method); if ((pos == INVALID_SET_FILE_POINTER) && (GetLastError() != NO_ERROR)) return MZ_SEEK_ERROR; if (new_pos) { new_pos->LowPart = pos; new_pos->HighPart = high_part; } return MZ_OK; #endif } int64_t mz_stream_os_tell(void *stream) { mz_stream_win32 *win32 = (mz_stream_win32 *)stream; LARGE_INTEGER large_pos; if (mz_stream_os_is_open(stream) != MZ_OK) return MZ_OPEN_ERROR; large_pos.QuadPart = 0; if (mz_stream_os_seekinternal(win32->handle, large_pos, &large_pos, FILE_CURRENT) != MZ_OK) win32->error = GetLastError(); mz_stream_os_print("Win32 - Tell - %" PRId64 "\n", large_pos.QuadPart); return large_pos.QuadPart; } int32_t mz_stream_os_seek(void *stream, int64_t offset, int32_t origin) { mz_stream_win32 *win32 = (mz_stream_win32 *)stream; uint32_t move_method = 0xFFFFFFFF; int32_t err = MZ_OK; LARGE_INTEGER large_pos; if (mz_stream_os_is_open(stream) != MZ_OK) return MZ_OPEN_ERROR; switch (origin) { case MZ_SEEK_CUR: move_method = FILE_CURRENT; break; case MZ_SEEK_END: move_method = FILE_END; break; case MZ_SEEK_SET: move_method = FILE_BEGIN; break; default: return MZ_SEEK_ERROR; } mz_stream_os_print("Win32 - Seek - %" PRId64 " (origin %" PRId32 ")\n", offset, origin); large_pos.QuadPart = offset; err = mz_stream_os_seekinternal(win32->handle, large_pos, NULL, move_method); if (err != MZ_OK) { win32->error = GetLastError(); return err; } return MZ_OK; } int32_t mz_stream_os_close(void *stream) { mz_stream_win32 *win32 = (mz_stream_win32 *)stream; if (win32->handle) CloseHandle(win32->handle); mz_stream_os_print("Win32 - Close\n"); win32->handle = NULL; return MZ_OK; } int32_t mz_stream_os_error(void *stream) { mz_stream_win32 *win32 = (mz_stream_win32 *)stream; return win32->error; } void *mz_stream_os_create(void) { mz_stream_win32 *win32 = (mz_stream_win32 *)calloc(1, sizeof(mz_stream_win32)); if (win32) win32->stream.vtbl = &mz_stream_os_vtbl; return win32; } void mz_stream_os_delete(void **stream) { mz_stream_win32 *win32 = NULL; if (!stream) return; win32 = (mz_stream_win32 *)*stream; if (win32) free(win32); *stream = NULL; } void *mz_stream_os_get_interface(void) { return (void *)&mz_stream_os_vtbl; } #endifsight-25.1.0/3rd-party/minizip/mz_strm_split.c000066400000000000000000000310331503402212300212630ustar00rootroot00000000000000/* mz_strm_split.c -- Stream for split files part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_os.h" #include "mz_strm.h" #include "mz_strm_split.h" #include /* snprintf */ #include #if defined(_MSC_VER) && (_MSC_VER < 1900) # define snprintf _snprintf #endif /***************************************************************************/ #define MZ_ZIP_MAGIC_DISKHEADER (0x08074b50) /***************************************************************************/ static mz_stream_vtbl mz_stream_split_vtbl = { mz_stream_split_open, mz_stream_split_is_open, mz_stream_split_read, mz_stream_split_write, mz_stream_split_tell, mz_stream_split_seek, mz_stream_split_close, mz_stream_split_error, mz_stream_split_create, mz_stream_split_delete, mz_stream_split_get_prop_int64, mz_stream_split_set_prop_int64 }; /***************************************************************************/ typedef struct mz_stream_split_s { mz_stream stream; int32_t is_open; int64_t disk_size; int64_t total_in; int64_t total_in_disk; int64_t total_out; int64_t total_out_disk; int32_t mode; char *path_cd; char *path_disk; uint32_t path_disk_size; int32_t number_disk; int32_t current_disk; int64_t current_disk_size; int32_t reached_end; } mz_stream_split; /***************************************************************************/ #if 0 # define mz_stream_split_print printf #else # define mz_stream_split_print(fmt, ...) #endif /***************************************************************************/ static int32_t mz_stream_split_open_disk(void *stream, int32_t number_disk) { mz_stream_split *split = (mz_stream_split *)stream; uint32_t magic = 0; int64_t position = 0; int32_t i = 0; int32_t err = MZ_OK; int16_t disk_part = 0; /* Check if we are reading or writing a disk part or the cd disk */ if (number_disk >= 0) { if ((split->mode & MZ_OPEN_MODE_WRITE) == 0) disk_part = MZ_OPEN_MODE_READ; else if (split->disk_size > 0) disk_part = MZ_OPEN_MODE_WRITE; } /* Construct disk path */ if (disk_part > 0) { for (i = (int32_t)strlen(split->path_disk) - 1; i >= 0; i -= 1) { if (split->path_disk[i] != '.') continue; snprintf(&split->path_disk[i], split->path_disk_size - (uint32_t)i, ".z%02" PRId32, number_disk + 1); break; } } else { strncpy(split->path_disk, split->path_cd, split->path_disk_size - 1); split->path_disk[split->path_disk_size - 1] = 0; } mz_stream_split_print("Split - Goto disk - %s (disk %" PRId32 ")\n", split->path_disk, number_disk); /* If disk part doesn't exist during reading then return MZ_EXIST_ERROR */ if (disk_part == MZ_OPEN_MODE_READ) err = mz_os_file_exists(split->path_disk); if (err == MZ_OK) err = mz_stream_open(split->stream.base, split->path_disk, split->mode); if (err == MZ_OK) { split->total_in_disk = 0; split->total_out_disk = 0; split->current_disk = number_disk; if (split->mode & MZ_OPEN_MODE_WRITE) { if ((split->current_disk == 0) && (split->disk_size > 0)) { err = mz_stream_write_uint32(split->stream.base, MZ_ZIP_MAGIC_DISKHEADER); split->total_out_disk += 4; split->total_out += split->total_out_disk; } } else if (split->mode & MZ_OPEN_MODE_READ) { if (split->current_disk == 0) { err = mz_stream_read_uint32(split->stream.base, &magic); if (magic != MZ_ZIP_MAGIC_DISKHEADER) err = MZ_FORMAT_ERROR; } } } if (err == MZ_OK) { /* Get the size of the current disk we are on */ position = mz_stream_tell(split->stream.base); mz_stream_seek(split->stream.base, 0, MZ_SEEK_END); split->current_disk_size = mz_stream_tell(split->stream.base); mz_stream_seek(split->stream.base, position, MZ_SEEK_SET); split->is_open = 1; } return err; } static int32_t mz_stream_split_close_disk(void *stream) { mz_stream_split *split = (mz_stream_split *)stream; if (mz_stream_is_open(split->stream.base) != MZ_OK) return MZ_OK; mz_stream_split_print("Split - Close disk\n"); return mz_stream_close(split->stream.base); } static int32_t mz_stream_split_goto_disk(void *stream, int32_t number_disk) { mz_stream_split *split = (mz_stream_split *)stream; int32_t err = MZ_OK; int32_t err_is_open = MZ_OK; err_is_open = mz_stream_is_open(split->stream.base); if ((split->disk_size == 0) && (split->mode & MZ_OPEN_MODE_WRITE)) { if (err_is_open != MZ_OK) err = mz_stream_split_open_disk(stream, number_disk); } else if ((number_disk != split->current_disk) || (err_is_open != MZ_OK)) { err = mz_stream_split_close_disk(stream); if (err == MZ_OK) { err = mz_stream_split_open_disk(stream, number_disk); if (err == MZ_OK) split->number_disk = number_disk; } } return err; } int32_t mz_stream_split_open(void *stream, const char *path, int32_t mode) { mz_stream_split *split = (mz_stream_split *)stream; int32_t number_disk = 0; split->mode = mode; split->path_cd = strdup(path); if (!split->path_cd) return MZ_MEM_ERROR; mz_stream_split_print("Split - Open - %s (disk %" PRId32 ")\n", split->path_cd, number_disk); split->path_disk_size = (uint32_t)strlen(path) + 10; split->path_disk = (char *)malloc(split->path_disk_size); if (!split->path_disk) { free(split->path_cd); return MZ_MEM_ERROR; } strncpy(split->path_disk, path, split->path_disk_size - 1); split->path_disk[split->path_disk_size - 1] = 0; if ((mode & MZ_OPEN_MODE_WRITE) && ((mode & MZ_OPEN_MODE_APPEND) == 0)) { number_disk = 0; split->current_disk = -1; } else { number_disk = -1; split->current_disk = 0; } return mz_stream_split_goto_disk(stream, number_disk); } int32_t mz_stream_split_is_open(void *stream) { mz_stream_split *split = (mz_stream_split *)stream; if (split->is_open != 1) return MZ_OPEN_ERROR; return MZ_OK; } int32_t mz_stream_split_read(void *stream, void *buf, int32_t size) { mz_stream_split *split = (mz_stream_split *)stream; int32_t bytes_left = size; int32_t read = 0; int32_t err = MZ_OK; uint8_t *buf_ptr = (uint8_t *)buf; err = mz_stream_split_goto_disk(stream, split->number_disk); if (err != MZ_OK) return err; while (bytes_left > 0) { read = mz_stream_read(split->stream.base, buf_ptr, bytes_left); mz_stream_split_print("Split - Read disk - %" PRId32 "\n", read); if (read < 0) return read; if (read == 0) { if (split->current_disk < 0) /* No more disks to goto */ break; err = mz_stream_split_goto_disk(stream, split->current_disk + 1); if (err == MZ_EXIST_ERROR) { split->current_disk = -1; break; } if (err != MZ_OK) return err; } bytes_left -= read; buf_ptr += read; split->total_in += read; split->total_in_disk += read; } return size - bytes_left; } int32_t mz_stream_split_write(void *stream, const void *buf, int32_t size) { mz_stream_split *split = (mz_stream_split *)stream; int64_t position = 0; int32_t written = 0; int32_t bytes_left = size; int32_t bytes_to_write = 0; int32_t bytes_avail = 0; int32_t number_disk = -1; int32_t err = MZ_OK; const uint8_t *buf_ptr = (const uint8_t *)buf; position = mz_stream_tell(split->stream.base); while (bytes_left > 0) { bytes_to_write = bytes_left; if (split->disk_size > 0) { if ((split->total_out_disk == split->disk_size && split->total_out > 0) || (split->number_disk == -1 && split->number_disk != split->current_disk)) { if (split->number_disk != -1) number_disk = split->current_disk + 1; err = mz_stream_split_goto_disk(stream, number_disk); if (err != MZ_OK) return err; position = 0; } if (split->number_disk != -1) { bytes_avail = (int32_t)(split->disk_size - split->total_out_disk); if (bytes_to_write > bytes_avail) bytes_to_write = bytes_avail; } } written = mz_stream_write(split->stream.base, buf_ptr, bytes_to_write); if (written != bytes_to_write) return MZ_WRITE_ERROR; mz_stream_split_print("Split - Write disk - %" PRId32 "\n", written); bytes_left -= written; buf_ptr += written; split->total_out += written; split->total_out_disk += written; position += written; if (position > split->current_disk_size) split->current_disk_size = position; } return size - bytes_left; } int64_t mz_stream_split_tell(void *stream) { mz_stream_split *split = (mz_stream_split *)stream; int32_t err = MZ_OK; err = mz_stream_split_goto_disk(stream, split->number_disk); if (err != MZ_OK) return err; return mz_stream_tell(split->stream.base); } int32_t mz_stream_split_seek(void *stream, int64_t offset, int32_t origin) { mz_stream_split *split = (mz_stream_split *)stream; int64_t disk_left = 0; int64_t position = 0; int32_t err = MZ_OK; err = mz_stream_split_goto_disk(stream, split->number_disk); if (err != MZ_OK) return err; mz_stream_split_print("Split - Seek disk - %" PRId64 " (origin %" PRId32 ")\n", offset, origin); if ((origin == MZ_SEEK_CUR) && (split->number_disk != -1)) { position = mz_stream_tell(split->stream.base); disk_left = split->current_disk_size - position; while (offset > disk_left) { err = mz_stream_split_goto_disk(stream, split->current_disk + 1); if (err != MZ_OK) return err; offset -= disk_left; disk_left = split->current_disk_size; } } return mz_stream_seek(split->stream.base, offset, origin); } int32_t mz_stream_split_close(void *stream) { mz_stream_split *split = (mz_stream_split *)stream; int32_t err = MZ_OK; err = mz_stream_split_close_disk(stream); split->is_open = 0; return err; } int32_t mz_stream_split_error(void *stream) { mz_stream_split *split = (mz_stream_split *)stream; return mz_stream_error(split->stream.base); } int32_t mz_stream_split_get_prop_int64(void *stream, int32_t prop, int64_t *value) { mz_stream_split *split = (mz_stream_split *)stream; switch (prop) { case MZ_STREAM_PROP_TOTAL_OUT: *value = split->total_out; break; case MZ_STREAM_PROP_DISK_NUMBER: *value = split->number_disk; break; case MZ_STREAM_PROP_DISK_SIZE: *value = split->disk_size; break; default: return MZ_EXIST_ERROR; } return MZ_OK; } int32_t mz_stream_split_set_prop_int64(void *stream, int32_t prop, int64_t value) { mz_stream_split *split = (mz_stream_split *)stream; switch (prop) { case MZ_STREAM_PROP_DISK_NUMBER: split->number_disk = (int32_t)value; break; case MZ_STREAM_PROP_DISK_SIZE: split->disk_size = value; break; default: return MZ_EXIST_ERROR; } return MZ_OK; } void *mz_stream_split_create(void) { mz_stream_split *split = (mz_stream_split *)calloc(1, sizeof(mz_stream_split)); if (split) split->stream.vtbl = &mz_stream_split_vtbl; return split; } void mz_stream_split_delete(void **stream) { mz_stream_split *split = NULL; if (!stream) return; split = (mz_stream_split *)*stream; if (split) { if (split->path_cd) free(split->path_cd); if (split->path_disk) free(split->path_disk); free(split); } *stream = NULL; } void *mz_stream_split_get_interface(void) { return (void *)&mz_stream_split_vtbl; } sight-25.1.0/3rd-party/minizip/mz_strm_split.h000066400000000000000000000025451503402212300212760ustar00rootroot00000000000000/* mz_strm_split.h -- Stream for split files part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_STREAM_SPLIT_H #define MZ_STREAM_SPLIT_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ int32_t mz_stream_split_open(void *stream, const char *filename, int32_t mode); int32_t mz_stream_split_is_open(void *stream); int32_t mz_stream_split_read(void *stream, void *buf, int32_t size); int32_t mz_stream_split_write(void *stream, const void *buf, int32_t size); int64_t mz_stream_split_tell(void *stream); int32_t mz_stream_split_seek(void *stream, int64_t offset, int32_t origin); int32_t mz_stream_split_close(void *stream); int32_t mz_stream_split_error(void *stream); int32_t mz_stream_split_get_prop_int64(void *stream, int32_t prop, int64_t *value); int32_t mz_stream_split_set_prop_int64(void *stream, int32_t prop, int64_t value); void* mz_stream_split_create(void); void mz_stream_split_delete(void **stream); void* mz_stream_split_get_interface(void); /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_strm_wzaes.c000066400000000000000000000265601503402212300212720ustar00rootroot00000000000000/* mz_strm_wzaes.c -- Stream for WinZip AES encryption part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng Copyright (C) 1998-2010 Brian Gladman, Worcester, UK This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_crypt.h" #include "mz_strm.h" #include "mz_strm_wzaes.h" /***************************************************************************/ #define MZ_AES_KEY_LENGTH(STRENGTH) (8 * (STRENGTH & 3) + 8) #define MZ_AES_KEYING_ITERATIONS (1000) #define MZ_AES_SALT_LENGTH(STRENGTH) (4 * (STRENGTH & 3) + 4) #define MZ_AES_SALT_LENGTH_MAX (16) #define MZ_AES_PW_LENGTH_MAX (128) #define MZ_AES_PW_VERIFY_SIZE (2) #define MZ_AES_AUTHCODE_SIZE (10) /***************************************************************************/ static mz_stream_vtbl mz_stream_wzaes_vtbl = { mz_stream_wzaes_open, mz_stream_wzaes_is_open, mz_stream_wzaes_read, mz_stream_wzaes_write, mz_stream_wzaes_tell, mz_stream_wzaes_seek, mz_stream_wzaes_close, mz_stream_wzaes_error, mz_stream_wzaes_create, mz_stream_wzaes_delete, mz_stream_wzaes_get_prop_int64, mz_stream_wzaes_set_prop_int64 }; /***************************************************************************/ typedef struct mz_stream_wzaes_s { mz_stream stream; int32_t mode; int32_t error; int16_t initialized; uint8_t buffer[UINT16_MAX]; int64_t total_in; int64_t max_total_in; int64_t total_out; uint8_t strength; const char *password; void *aes; uint32_t crypt_pos; uint8_t crypt_block[MZ_AES_BLOCK_SIZE]; void *hmac; uint8_t nonce[MZ_AES_BLOCK_SIZE]; } mz_stream_wzaes; /***************************************************************************/ int32_t mz_stream_wzaes_open(void *stream, const char *path, int32_t mode) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; uint16_t salt_length = 0; uint16_t password_length = 0; uint16_t key_length = 0; uint8_t kbuf[2 * MZ_AES_KEY_LENGTH_MAX + MZ_AES_PW_VERIFY_SIZE]; uint8_t verify[MZ_AES_PW_VERIFY_SIZE]; uint8_t verify_expected[MZ_AES_PW_VERIFY_SIZE]; uint8_t salt_value[MZ_AES_SALT_LENGTH_MAX]; const char *password = path; wzaes->total_in = 0; wzaes->total_out = 0; wzaes->initialized = 0; if (mz_stream_is_open(wzaes->stream.base) != MZ_OK) return MZ_OPEN_ERROR; if (!password) password = wzaes->password; if (!password) return MZ_PARAM_ERROR; password_length = (uint16_t)strlen(password); if (password_length > MZ_AES_PW_LENGTH_MAX) return MZ_PARAM_ERROR; if (wzaes->strength < 1 || wzaes->strength > 3) return MZ_PARAM_ERROR; key_length = MZ_AES_KEY_LENGTH(wzaes->strength); salt_length = MZ_AES_SALT_LENGTH(wzaes->strength); if (mode & MZ_OPEN_MODE_WRITE) { mz_crypt_rand(salt_value, salt_length); } else if (mode & MZ_OPEN_MODE_READ) { if (mz_stream_read(wzaes->stream.base, salt_value, salt_length) != salt_length) return MZ_READ_ERROR; } /* Derive the encryption and authentication keys and the password verifier */ mz_crypt_pbkdf2((uint8_t *)password, password_length, salt_value, salt_length, MZ_AES_KEYING_ITERATIONS, kbuf, 2 * key_length + MZ_AES_PW_VERIFY_SIZE); /* Initialize the buffer pos */ wzaes->crypt_pos = MZ_AES_BLOCK_SIZE; /* Use fixed zeroed IV/nonce for CTR mode */ memset(wzaes->nonce, 0, sizeof(wzaes->nonce)); /* Initialize for encryption using key 1 */ mz_crypt_aes_reset(wzaes->aes); mz_crypt_aes_set_encrypt_key(wzaes->aes, kbuf, key_length, NULL, 0); /* Initialize for authentication using key 2 */ mz_crypt_hmac_reset(wzaes->hmac); mz_crypt_hmac_set_algorithm(wzaes->hmac, MZ_HASH_SHA1); mz_crypt_hmac_init(wzaes->hmac, kbuf + key_length, key_length); memcpy(verify, kbuf + (2 * key_length), MZ_AES_PW_VERIFY_SIZE); if (mode & MZ_OPEN_MODE_WRITE) { if (mz_stream_write(wzaes->stream.base, salt_value, salt_length) != salt_length) return MZ_WRITE_ERROR; wzaes->total_out += salt_length; if (mz_stream_write(wzaes->stream.base, verify, MZ_AES_PW_VERIFY_SIZE) != MZ_AES_PW_VERIFY_SIZE) return MZ_WRITE_ERROR; wzaes->total_out += MZ_AES_PW_VERIFY_SIZE; } else if (mode & MZ_OPEN_MODE_READ) { wzaes->total_in += salt_length; if (mz_stream_read(wzaes->stream.base, verify_expected, MZ_AES_PW_VERIFY_SIZE) != MZ_AES_PW_VERIFY_SIZE) return MZ_READ_ERROR; wzaes->total_in += MZ_AES_PW_VERIFY_SIZE; if (memcmp(verify_expected, verify, MZ_AES_PW_VERIFY_SIZE) != 0) return MZ_PASSWORD_ERROR; } wzaes->mode = mode; wzaes->initialized = 1; return MZ_OK; } int32_t mz_stream_wzaes_is_open(void *stream) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; if (!wzaes->initialized) return MZ_OPEN_ERROR; return MZ_OK; } static int32_t mz_stream_wzaes_ctr_encrypt(void *stream, uint8_t *buf, int32_t size) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; uint32_t pos = wzaes->crypt_pos; uint32_t i = 0; int32_t err = MZ_OK; while (i < (uint32_t)size) { if (pos == MZ_AES_BLOCK_SIZE) { uint32_t j = 0; /* Increment encryption nonce */ while (j < 8 && !++wzaes->nonce[j]) j += 1; /* Encrypt the nonce using ECB mode to form next xor buffer */ memcpy(wzaes->crypt_block, wzaes->nonce, MZ_AES_BLOCK_SIZE); mz_crypt_aes_encrypt(wzaes->aes, NULL, 0, wzaes->crypt_block, sizeof(wzaes->crypt_block)); pos = 0; } buf[i++] ^= wzaes->crypt_block[pos++]; } wzaes->crypt_pos = pos; return err; } int32_t mz_stream_wzaes_read(void *stream, void *buf, int32_t size) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; int64_t max_total_in = 0; int32_t bytes_to_read = size; int32_t read = 0; max_total_in = wzaes->max_total_in - MZ_AES_FOOTER_SIZE; if ((int64_t)bytes_to_read > (max_total_in - wzaes->total_in)) bytes_to_read = (int32_t)(max_total_in - wzaes->total_in); read = mz_stream_read(wzaes->stream.base, buf, bytes_to_read); if (read > 0) { mz_crypt_hmac_update(wzaes->hmac, (uint8_t *)buf, read); mz_stream_wzaes_ctr_encrypt(stream, (uint8_t *)buf, read); wzaes->total_in += read; } return read; } int32_t mz_stream_wzaes_write(void *stream, const void *buf, int32_t size) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; const uint8_t *buf_ptr = (const uint8_t *)buf; int32_t bytes_to_write = sizeof(wzaes->buffer); int32_t total_written = 0; int32_t written = 0; if (size < 0) return MZ_PARAM_ERROR; do { if (bytes_to_write > (size - total_written)) bytes_to_write = (size - total_written); memcpy(wzaes->buffer, buf_ptr, bytes_to_write); buf_ptr += bytes_to_write; mz_stream_wzaes_ctr_encrypt(stream, (uint8_t *)wzaes->buffer, bytes_to_write); mz_crypt_hmac_update(wzaes->hmac, wzaes->buffer, bytes_to_write); written = mz_stream_write(wzaes->stream.base, wzaes->buffer, bytes_to_write); if (written < 0) return written; total_written += written; } while (total_written < size && written > 0); wzaes->total_out += total_written; return total_written; } int64_t mz_stream_wzaes_tell(void *stream) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; return mz_stream_tell(wzaes->stream.base); } int32_t mz_stream_wzaes_seek(void *stream, int64_t offset, int32_t origin) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; return mz_stream_seek(wzaes->stream.base, offset, origin); } int32_t mz_stream_wzaes_close(void *stream) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; uint8_t expected_hash[MZ_AES_AUTHCODE_SIZE]; uint8_t computed_hash[MZ_HASH_SHA1_SIZE]; mz_crypt_hmac_end(wzaes->hmac, computed_hash, sizeof(computed_hash)); if (wzaes->mode & MZ_OPEN_MODE_WRITE) { if (mz_stream_write(wzaes->stream.base, computed_hash, MZ_AES_AUTHCODE_SIZE) != MZ_AES_AUTHCODE_SIZE) return MZ_WRITE_ERROR; wzaes->total_out += MZ_AES_AUTHCODE_SIZE; } else if (wzaes->mode & MZ_OPEN_MODE_READ) { if (mz_stream_read(wzaes->stream.base, expected_hash, MZ_AES_AUTHCODE_SIZE) != MZ_AES_AUTHCODE_SIZE) return MZ_READ_ERROR; wzaes->total_in += MZ_AES_AUTHCODE_SIZE; /* If entire entry was not read this will fail */ if (memcmp(computed_hash, expected_hash, MZ_AES_AUTHCODE_SIZE) != 0) return MZ_CRC_ERROR; } wzaes->initialized = 0; return MZ_OK; } int32_t mz_stream_wzaes_error(void *stream) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; return wzaes->error; } void mz_stream_wzaes_set_password(void *stream, const char *password) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; wzaes->password = password; } void mz_stream_wzaes_set_strength(void *stream, uint8_t strength) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; wzaes->strength = strength; } int32_t mz_stream_wzaes_get_prop_int64(void *stream, int32_t prop, int64_t *value) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; switch (prop) { case MZ_STREAM_PROP_TOTAL_IN: *value = wzaes->total_in; break; case MZ_STREAM_PROP_TOTAL_OUT: *value = wzaes->total_out; break; case MZ_STREAM_PROP_TOTAL_IN_MAX: *value = wzaes->max_total_in; break; case MZ_STREAM_PROP_HEADER_SIZE: *value = MZ_AES_SALT_LENGTH((int64_t)wzaes->strength) + MZ_AES_PW_VERIFY_SIZE; break; case MZ_STREAM_PROP_FOOTER_SIZE: *value = MZ_AES_AUTHCODE_SIZE; break; default: return MZ_EXIST_ERROR; } return MZ_OK; } int32_t mz_stream_wzaes_set_prop_int64(void *stream, int32_t prop, int64_t value) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; switch (prop) { case MZ_STREAM_PROP_TOTAL_IN_MAX: wzaes->max_total_in = value; break; default: return MZ_EXIST_ERROR; } return MZ_OK; } void *mz_stream_wzaes_create(void) { mz_stream_wzaes *wzaes = (mz_stream_wzaes *)calloc(1, sizeof(mz_stream_wzaes)); if (wzaes) { wzaes->stream.vtbl = &mz_stream_wzaes_vtbl; wzaes->strength = MZ_AES_STRENGTH_256; wzaes->hmac = mz_crypt_hmac_create(); if (!wzaes->hmac) { free(wzaes); return NULL; } wzaes->aes = mz_crypt_aes_create(); if (!wzaes->aes) { mz_crypt_hmac_delete(&wzaes->hmac); free(wzaes); return NULL; } } return wzaes; } void mz_stream_wzaes_delete(void **stream) { mz_stream_wzaes *wzaes = NULL; if (!stream) return; wzaes = (mz_stream_wzaes *)*stream; if (wzaes) { mz_crypt_aes_delete(&wzaes->aes); mz_crypt_hmac_delete(&wzaes->hmac); free(wzaes); } *stream = NULL; } void *mz_stream_wzaes_get_interface(void) { return (void *)&mz_stream_wzaes_vtbl; } sight-25.1.0/3rd-party/minizip/mz_strm_wzaes.h000066400000000000000000000030121503402212300212620ustar00rootroot00000000000000/* mz_strm_wzaes.h -- Stream for WinZIP AES encryption part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_STREAM_WZAES_SHA1_H #define MZ_STREAM_WZAES_SHA1_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ int32_t mz_stream_wzaes_open(void *stream, const char *filename, int32_t mode); int32_t mz_stream_wzaes_is_open(void *stream); int32_t mz_stream_wzaes_read(void *stream, void *buf, int32_t size); int32_t mz_stream_wzaes_write(void *stream, const void *buf, int32_t size); int64_t mz_stream_wzaes_tell(void *stream); int32_t mz_stream_wzaes_seek(void *stream, int64_t offset, int32_t origin); int32_t mz_stream_wzaes_close(void *stream); int32_t mz_stream_wzaes_error(void *stream); void mz_stream_wzaes_set_password(void *stream, const char *password); void mz_stream_wzaes_set_strength(void *stream, uint8_t strength); int32_t mz_stream_wzaes_get_prop_int64(void *stream, int32_t prop, int64_t *value); int32_t mz_stream_wzaes_set_prop_int64(void *stream, int32_t prop, int64_t value); void* mz_stream_wzaes_create(void); void mz_stream_wzaes_delete(void **stream); void* mz_stream_wzaes_get_interface(void); /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_strm_zlib.c000066400000000000000000000237431503402212300211010ustar00rootroot00000000000000/* mz_strm_zlib.c -- Stream for zlib inflate/deflate part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_strm.h" #include "mz_strm_zlib.h" #if !defined(ZLIB_COMPAT) # include "zlib-ng.h" #else # include "zlib.h" #endif /***************************************************************************/ #if !defined(ZLIB_COMPAT) # define ZLIB_PREFIX(x) zng_ ## x typedef zng_stream zlib_stream; #else # define ZLIB_PREFIX(x) x typedef z_stream zlib_stream; #endif #if !defined(DEF_MEM_LEVEL) # if MAX_MEM_LEVEL >= 8 # define DEF_MEM_LEVEL 8 # else # define DEF_MEM_LEVEL MAX_MEM_LEVEL # endif #endif /***************************************************************************/ static mz_stream_vtbl mz_stream_zlib_vtbl = { mz_stream_zlib_open, mz_stream_zlib_is_open, mz_stream_zlib_read, mz_stream_zlib_write, mz_stream_zlib_tell, mz_stream_zlib_seek, mz_stream_zlib_close, mz_stream_zlib_error, mz_stream_zlib_create, mz_stream_zlib_delete, mz_stream_zlib_get_prop_int64, mz_stream_zlib_set_prop_int64 }; /***************************************************************************/ typedef struct mz_stream_zlib_s { mz_stream stream; zlib_stream zstream; uint8_t buffer[INT16_MAX]; int32_t buffer_len; int64_t total_in; int64_t total_out; int64_t max_total_in; int8_t initialized; int16_t level; int32_t window_bits; int32_t mode; int32_t error; } mz_stream_zlib; /***************************************************************************/ int32_t mz_stream_zlib_open(void *stream, const char *path, int32_t mode) { mz_stream_zlib *zlib = (mz_stream_zlib *)stream; MZ_UNUSED(path); zlib->zstream.data_type = Z_BINARY; zlib->zstream.zalloc = Z_NULL; zlib->zstream.zfree = Z_NULL; zlib->zstream.opaque = Z_NULL; zlib->zstream.total_in = 0; zlib->zstream.total_out = 0; zlib->total_in = 0; zlib->total_out = 0; if (mode & MZ_OPEN_MODE_WRITE) { #ifdef MZ_ZIP_NO_COMPRESSION return MZ_SUPPORT_ERROR; #else zlib->zstream.next_out = zlib->buffer; zlib->zstream.avail_out = sizeof(zlib->buffer); zlib->error = ZLIB_PREFIX(deflateInit2)(&zlib->zstream, (int8_t)zlib->level, Z_DEFLATED, zlib->window_bits, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); #endif } else if (mode & MZ_OPEN_MODE_READ) { #ifdef MZ_ZIP_NO_DECOMPRESSION return MZ_SUPPORT_ERROR; #else zlib->zstream.next_in = zlib->buffer; zlib->zstream.avail_in = 0; zlib->error = ZLIB_PREFIX(inflateInit2)(&zlib->zstream, zlib->window_bits); #endif } if (zlib->error != Z_OK) return MZ_OPEN_ERROR; zlib->initialized = 1; zlib->mode = mode; return MZ_OK; } int32_t mz_stream_zlib_is_open(void *stream) { mz_stream_zlib *zlib = (mz_stream_zlib *)stream; if (zlib->initialized != 1) return MZ_OPEN_ERROR; return MZ_OK; } int32_t mz_stream_zlib_read(void *stream, void *buf, int32_t size) { #ifdef MZ_ZIP_NO_DECOMPRESSION MZ_UNUSED(stream); MZ_UNUSED(buf); MZ_UNUSED(size); return MZ_SUPPORT_ERROR; #else mz_stream_zlib *zlib = (mz_stream_zlib *)stream; uint64_t total_in_before = 0; uint64_t total_in_after = 0; uint64_t total_out_before = 0; uint64_t total_out_after = 0; uint32_t total_in = 0; uint32_t total_out = 0; uint32_t in_bytes = 0; uint32_t out_bytes = 0; int32_t bytes_to_read = sizeof(zlib->buffer); int32_t read = 0; int32_t err = Z_OK; zlib->zstream.next_out = (Bytef *)buf; zlib->zstream.avail_out = (uInt)size; do { if (zlib->zstream.avail_in == 0) { if (zlib->max_total_in > 0) { if ((int64_t)bytes_to_read > (zlib->max_total_in - zlib->total_in)) bytes_to_read = (int32_t)(zlib->max_total_in - zlib->total_in); } read = mz_stream_read(zlib->stream.base, zlib->buffer, bytes_to_read); if (read < 0) return read; zlib->zstream.next_in = zlib->buffer; zlib->zstream.avail_in = read; } total_in_before = zlib->zstream.avail_in; total_out_before = zlib->zstream.total_out; err = ZLIB_PREFIX(inflate)(&zlib->zstream, Z_SYNC_FLUSH); if ((err >= Z_OK) && (zlib->zstream.msg)) { zlib->error = Z_DATA_ERROR; break; } total_in_after = zlib->zstream.avail_in; total_out_after = zlib->zstream.total_out; in_bytes = (uint32_t)(total_in_before - total_in_after); out_bytes = (uint32_t)(total_out_after - total_out_before); total_in += in_bytes; total_out += out_bytes; zlib->total_in += in_bytes; zlib->total_out += out_bytes; if (err == Z_STREAM_END) break; if (err != Z_OK) { zlib->error = err; break; } } while (zlib->zstream.avail_out > 0); MZ_UNUSED(total_in); if (zlib->error != 0) { /* Zlib errors are compatible with MZ */ return zlib->error; } return total_out; #endif } #ifndef MZ_ZIP_NO_COMPRESSION static int32_t mz_stream_zlib_flush(void *stream) { mz_stream_zlib *zlib = (mz_stream_zlib *)stream; if (mz_stream_write(zlib->stream.base, zlib->buffer, zlib->buffer_len) != zlib->buffer_len) return MZ_WRITE_ERROR; return MZ_OK; } static int32_t mz_stream_zlib_deflate(void *stream, int flush) { mz_stream_zlib *zlib = (mz_stream_zlib *)stream; uint64_t total_out_before = 0; uint64_t total_out_after = 0; int32_t out_bytes = 0; int32_t err = Z_OK; do { if (zlib->zstream.avail_out == 0) { err = mz_stream_zlib_flush(zlib); if (err != MZ_OK) return err; zlib->zstream.avail_out = sizeof(zlib->buffer); zlib->zstream.next_out = zlib->buffer; zlib->buffer_len = 0; } total_out_before = zlib->zstream.total_out; err = ZLIB_PREFIX(deflate)(&zlib->zstream, flush); total_out_after = zlib->zstream.total_out; out_bytes = (uint32_t)(total_out_after - total_out_before); zlib->buffer_len += out_bytes; zlib->total_out += out_bytes; if (err == Z_STREAM_END) break; if (err != Z_OK) { zlib->error = err; return MZ_DATA_ERROR; } } while ((zlib->zstream.avail_in > 0) || (flush == Z_FINISH && err == Z_OK)); return MZ_OK; } #endif int32_t mz_stream_zlib_write(void *stream, const void *buf, int32_t size) { #ifdef MZ_ZIP_NO_COMPRESSION MZ_UNUSED(stream); MZ_UNUSED(buf); MZ_UNUSED(size); return MZ_SUPPORT_ERROR; #else mz_stream_zlib *zlib = (mz_stream_zlib *)stream; int32_t err = MZ_OK; zlib->zstream.next_in = (Bytef *)(intptr_t)buf; zlib->zstream.avail_in = (uInt)size; err = mz_stream_zlib_deflate(stream, Z_NO_FLUSH); if (err != MZ_OK) { return err; } zlib->total_in += size; return size; #endif } int64_t mz_stream_zlib_tell(void *stream) { MZ_UNUSED(stream); return MZ_TELL_ERROR; } int32_t mz_stream_zlib_seek(void *stream, int64_t offset, int32_t origin) { MZ_UNUSED(stream); MZ_UNUSED(offset); MZ_UNUSED(origin); return MZ_SEEK_ERROR; } int32_t mz_stream_zlib_close(void *stream) { mz_stream_zlib *zlib = (mz_stream_zlib *)stream; if (zlib->mode & MZ_OPEN_MODE_WRITE) { #ifdef MZ_ZIP_NO_COMPRESSION return MZ_SUPPORT_ERROR; #else mz_stream_zlib_deflate(stream, Z_FINISH); mz_stream_zlib_flush(stream); ZLIB_PREFIX(deflateEnd)(&zlib->zstream); #endif } else if (zlib->mode & MZ_OPEN_MODE_READ) { #ifdef MZ_ZIP_NO_DECOMPRESSION return MZ_SUPPORT_ERROR; #else ZLIB_PREFIX(inflateEnd)(&zlib->zstream); #endif } zlib->initialized = 0; if (zlib->error != Z_OK) return MZ_CLOSE_ERROR; return MZ_OK; } int32_t mz_stream_zlib_error(void *stream) { mz_stream_zlib *zlib = (mz_stream_zlib *)stream; return zlib->error; } int32_t mz_stream_zlib_get_prop_int64(void *stream, int32_t prop, int64_t *value) { mz_stream_zlib *zlib = (mz_stream_zlib *)stream; switch (prop) { case MZ_STREAM_PROP_TOTAL_IN: *value = zlib->total_in; break; case MZ_STREAM_PROP_TOTAL_IN_MAX: *value = zlib->max_total_in; break; case MZ_STREAM_PROP_TOTAL_OUT: *value = zlib->total_out; break; case MZ_STREAM_PROP_HEADER_SIZE: *value = 0; break; case MZ_STREAM_PROP_COMPRESS_WINDOW: *value = zlib->window_bits; break; default: return MZ_EXIST_ERROR; } return MZ_OK; } int32_t mz_stream_zlib_set_prop_int64(void *stream, int32_t prop, int64_t value) { mz_stream_zlib *zlib = (mz_stream_zlib *)stream; switch (prop) { case MZ_STREAM_PROP_COMPRESS_LEVEL: zlib->level = (int16_t)value; break; case MZ_STREAM_PROP_TOTAL_IN_MAX: zlib->max_total_in = value; break; case MZ_STREAM_PROP_COMPRESS_WINDOW: zlib->window_bits = (int32_t)value; break; default: return MZ_EXIST_ERROR; } return MZ_OK; } void *mz_stream_zlib_create(void) { mz_stream_zlib *zlib = zlib = (mz_stream_zlib *)calloc(1, sizeof(mz_stream_zlib)); if (zlib) { zlib->stream.vtbl = &mz_stream_zlib_vtbl; zlib->level = Z_DEFAULT_COMPRESSION; zlib->window_bits = -MAX_WBITS; } return zlib; } void mz_stream_zlib_delete(void **stream) { mz_stream_zlib *zlib = NULL; if (!stream) return; zlib = (mz_stream_zlib *)*stream; if (zlib) free(zlib); *stream = NULL; } void *mz_stream_zlib_get_interface(void) { return (void *)&mz_stream_zlib_vtbl; } sight-25.1.0/3rd-party/minizip/mz_strm_zlib.h000066400000000000000000000025361503402212300211030ustar00rootroot00000000000000/* mz_strm_zlib.h -- Stream for zlib inflate/deflate part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_STREAM_ZLIB_H #define MZ_STREAM_ZLIB_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ int32_t mz_stream_zlib_open(void *stream, const char *filename, int32_t mode); int32_t mz_stream_zlib_is_open(void *stream); int32_t mz_stream_zlib_read(void *stream, void *buf, int32_t size); int32_t mz_stream_zlib_write(void *stream, const void *buf, int32_t size); int64_t mz_stream_zlib_tell(void *stream); int32_t mz_stream_zlib_seek(void *stream, int64_t offset, int32_t origin); int32_t mz_stream_zlib_close(void *stream); int32_t mz_stream_zlib_error(void *stream); int32_t mz_stream_zlib_get_prop_int64(void *stream, int32_t prop, int64_t *value); int32_t mz_stream_zlib_set_prop_int64(void *stream, int32_t prop, int64_t value); void* mz_stream_zlib_create(void); void mz_stream_zlib_delete(void **stream); void* mz_stream_zlib_get_interface(void); /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_strm_zstd.c000066400000000000000000000223461503402212300211230ustar00rootroot00000000000000/* mz_strm_zstd.c -- Stream for zstd compress/decompress part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng Authors: Force Charlie https://github.com/fcharlie This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_strm.h" #include "mz_strm_zstd.h" #include /***************************************************************************/ static mz_stream_vtbl mz_stream_zstd_vtbl = { mz_stream_zstd_open, mz_stream_zstd_is_open, mz_stream_zstd_read, mz_stream_zstd_write, mz_stream_zstd_tell, mz_stream_zstd_seek, mz_stream_zstd_close, mz_stream_zstd_error, mz_stream_zstd_create, mz_stream_zstd_delete, mz_stream_zstd_get_prop_int64, mz_stream_zstd_set_prop_int64 }; /***************************************************************************/ typedef struct mz_stream_zstd_s { mz_stream stream; ZSTD_CStream *zcstream; ZSTD_DStream *zdstream; ZSTD_outBuffer out; ZSTD_inBuffer in; int32_t mode; int32_t error; uint8_t buffer[INT16_MAX]; int32_t buffer_len; int64_t total_in; int64_t total_out; int64_t max_total_in; int64_t max_total_out; int8_t initialized; uint32_t preset; } mz_stream_zstd; /***************************************************************************/ int32_t mz_stream_zstd_open(void *stream, const char *path, int32_t mode) { mz_stream_zstd *zstd = (mz_stream_zstd *)stream; MZ_UNUSED(path); if (mode & MZ_OPEN_MODE_WRITE) { #ifdef MZ_ZIP_NO_COMPRESSION return MZ_SUPPORT_ERROR; #else zstd->zcstream = ZSTD_createCStream(); zstd->out.dst = zstd->buffer; zstd->out.size = sizeof(zstd->buffer); zstd->out.pos = 0; ZSTD_CCtx_setParameter(zstd->zcstream, ZSTD_c_compressionLevel, zstd->preset); #endif } else if (mode & MZ_OPEN_MODE_READ) { #ifdef MZ_ZIP_NO_DECOMPRESSION return MZ_SUPPORT_ERROR; #else zstd->zdstream = ZSTD_createDStream(); memset(&zstd->out, 0, sizeof(ZSTD_outBuffer)); #endif } memset(&zstd->in, 0, sizeof(ZSTD_inBuffer)); zstd->initialized = 1; zstd->mode = mode; zstd->error = MZ_OK; return MZ_OK; } int32_t mz_stream_zstd_is_open(void *stream) { mz_stream_zstd *zstd = (mz_stream_zstd *)stream; if (zstd->initialized != 1) return MZ_OPEN_ERROR; return MZ_OK; } int32_t mz_stream_zstd_read(void *stream, void *buf, int32_t size) { #ifdef MZ_ZIP_NO_DECOMPRESSION MZ_UNUSED(stream); MZ_UNUSED(buf); MZ_UNUSED(size); return MZ_SUPPORT_ERROR; #else mz_stream_zstd *zstd = (mz_stream_zstd *)stream; uint64_t total_in_before = 0; uint64_t total_in_after = 0; uint64_t total_out_before = 0; uint64_t total_out_after = 0; int32_t total_in = 0; int32_t total_out = 0; int32_t in_bytes = 0; int32_t out_bytes = 0; int32_t bytes_to_read = sizeof(zstd->buffer); int32_t read = 0; size_t result = 0; zstd->out.dst = (void *)buf; zstd->out.size = (size_t)size; zstd->out.pos = 0; do { if (zstd->in.pos == zstd->in.size) { if (zstd->max_total_in > 0) { if ((int64_t)bytes_to_read > (zstd->max_total_in - zstd->total_in)) bytes_to_read = (int32_t)(zstd->max_total_in - zstd->total_in); } read = mz_stream_read(zstd->stream.base, zstd->buffer, bytes_to_read); if (read < 0) return read; zstd->in.src = (const void *)zstd->buffer; zstd->in.pos = 0; zstd->in.size = (size_t)read; } total_in_before = zstd->in.pos; total_out_before = zstd->out.pos; result = ZSTD_decompressStream(zstd->zdstream, &zstd->out, &zstd->in); if (ZSTD_isError(result)) { zstd->error = (int32_t)result; return MZ_DATA_ERROR; } total_in_after = zstd->in.pos; total_out_after = zstd->out.pos; if ((zstd->max_total_out != -1) && (int64_t)total_out_after > zstd->max_total_out) total_out_after = (uint64_t)zstd->max_total_out; in_bytes = (int32_t)(total_in_after - total_in_before); out_bytes = (int32_t)(total_out_after - total_out_before); total_in += in_bytes; total_out += out_bytes; zstd->total_in += in_bytes; zstd->total_out += out_bytes; } while ((zstd->in.size > 0 || out_bytes > 0) && (zstd->out.pos < zstd->out.size)); return total_out; #endif } #ifndef MZ_ZIP_NO_COMPRESSION static int32_t mz_stream_zstd_flush(void *stream) { mz_stream_zstd *zstd = (mz_stream_zstd *)stream; if (mz_stream_write(zstd->stream.base, zstd->buffer, zstd->buffer_len) != zstd->buffer_len) return MZ_WRITE_ERROR; return MZ_OK; } static int32_t mz_stream_zstd_compress(void *stream, ZSTD_EndDirective flush) { mz_stream_zstd *zstd = (mz_stream_zstd *)stream; uint64_t total_out_before = 0; uint64_t total_out_after = 0; int32_t out_bytes = 0; size_t result = 0; int32_t err = 0; do { if (zstd->out.pos == zstd->out.size) { err = mz_stream_zstd_flush(zstd); if (err != MZ_OK) return err; zstd->out.dst = zstd->buffer; zstd->out.size = sizeof(zstd->buffer); zstd->out.pos = 0; zstd->buffer_len = 0; } total_out_before = zstd->out.pos; result = ZSTD_compressStream2(zstd->zcstream, &zstd->out, &zstd->in, flush); total_out_after = zstd->out.pos; out_bytes = (uint32_t)(total_out_after - total_out_before); zstd->buffer_len += out_bytes; zstd->total_out += out_bytes; if (ZSTD_isError(result)) { zstd->error = (int32_t)result; return MZ_DATA_ERROR; } } while ((zstd->in.pos < zstd->in.size) || (flush == ZSTD_e_end && result != 0)); return MZ_OK; } #endif int32_t mz_stream_zstd_write(void *stream, const void *buf, int32_t size) { #ifdef MZ_ZIP_NO_COMPRESSION MZ_UNUSED(stream); MZ_UNUSED(buf); MZ_UNUSED(size); return MZ_SUPPORT_ERROR; #else mz_stream_zstd *zstd = (mz_stream_zstd *)stream; int32_t err = MZ_OK; zstd->in.src = buf; zstd->in.pos = 0; zstd->in.size = size; err = mz_stream_zstd_compress(stream, ZSTD_e_continue); if (err != MZ_OK) { return err; } zstd->total_in += size; return size; #endif } int64_t mz_stream_zstd_tell(void *stream) { MZ_UNUSED(stream); return MZ_TELL_ERROR; } int32_t mz_stream_zstd_seek(void *stream, int64_t offset, int32_t origin) { MZ_UNUSED(stream); MZ_UNUSED(offset); MZ_UNUSED(origin); return MZ_SEEK_ERROR; } int32_t mz_stream_zstd_close(void *stream) { mz_stream_zstd *zstd = (mz_stream_zstd *)stream; if (zstd->mode & MZ_OPEN_MODE_WRITE) { #ifdef MZ_ZIP_NO_COMPRESSION return MZ_SUPPORT_ERROR; #else mz_stream_zstd_compress(stream, ZSTD_e_end); mz_stream_zstd_flush(stream); ZSTD_freeCStream(zstd->zcstream); zstd->zcstream = NULL; #endif } else if (zstd->mode & MZ_OPEN_MODE_READ) { #ifdef MZ_ZIP_NO_DECOMPRESSION return MZ_SUPPORT_ERROR; #else ZSTD_freeDStream(zstd->zdstream); zstd->zdstream = NULL; #endif } zstd->initialized = 0; return MZ_OK; } int32_t mz_stream_zstd_error(void *stream) { mz_stream_zstd *zstd = (mz_stream_zstd *)stream; return zstd->error; } int32_t mz_stream_zstd_get_prop_int64(void *stream, int32_t prop, int64_t *value) { mz_stream_zstd *zstd = (mz_stream_zstd *)stream; switch (prop) { case MZ_STREAM_PROP_TOTAL_IN: *value = zstd->total_in; break; case MZ_STREAM_PROP_TOTAL_IN_MAX: *value = zstd->max_total_in; break; case MZ_STREAM_PROP_TOTAL_OUT: *value = zstd->total_out; break; case MZ_STREAM_PROP_TOTAL_OUT_MAX: *value = zstd->max_total_out; break; case MZ_STREAM_PROP_HEADER_SIZE: *value = 0; break; default: return MZ_EXIST_ERROR; } return MZ_OK; } int32_t mz_stream_zstd_set_prop_int64(void *stream, int32_t prop, int64_t value) { mz_stream_zstd *zstd = (mz_stream_zstd *)stream; switch (prop) { case MZ_STREAM_PROP_COMPRESS_LEVEL: if (value < 0) zstd->preset = 6; else zstd->preset = (int16_t)value; return MZ_OK; case MZ_STREAM_PROP_TOTAL_IN_MAX: zstd->max_total_in = value; return MZ_OK; } return MZ_EXIST_ERROR; } void *mz_stream_zstd_create(void) { mz_stream_zstd *zstd = (mz_stream_zstd *)calloc(1, sizeof(mz_stream_zstd)); if (zstd) { zstd->stream.vtbl = &mz_stream_zstd_vtbl; zstd->max_total_out = -1; } return zstd; } void mz_stream_zstd_delete(void **stream) { mz_stream_zstd *zstd = NULL; if (!stream) return; zstd = (mz_stream_zstd *)*stream; if (zstd) free(zstd); *stream = NULL; } void *mz_stream_zstd_get_interface(void) { return (void *)&mz_stream_zstd_vtbl; } sight-25.1.0/3rd-party/minizip/mz_strm_zstd.h000066400000000000000000000026021503402212300211210ustar00rootroot00000000000000/* mz_strm_zlib.h -- Stream for zlib inflate/deflate Version 2.9.2, February 12, 2020 part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_STREAM_ZSTD_H #define MZ_STREAM_ZSTD_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ int32_t mz_stream_zstd_open(void *stream, const char *filename, int32_t mode); int32_t mz_stream_zstd_is_open(void *stream); int32_t mz_stream_zstd_read(void *stream, void *buf, int32_t size); int32_t mz_stream_zstd_write(void *stream, const void *buf, int32_t size); int64_t mz_stream_zstd_tell(void *stream); int32_t mz_stream_zstd_seek(void *stream, int64_t offset, int32_t origin); int32_t mz_stream_zstd_close(void *stream); int32_t mz_stream_zstd_error(void *stream); int32_t mz_stream_zstd_get_prop_int64(void *stream, int32_t prop, int64_t *value); int32_t mz_stream_zstd_set_prop_int64(void *stream, int32_t prop, int64_t value); void* mz_stream_zstd_create(void); void mz_stream_zstd_delete(void **stream); void* mz_stream_zstd_get_interface(void); /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/mz_zip.c000066400000000000000000003127211503402212300176730ustar00rootroot00000000000000/* zip.c -- Zip manipulation part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng Copyright (C) 2009-2010 Mathias Svensson Modifications for Zip64 support http://result42.com Copyright (C) 2007-2008 Even Rouault Modifications of Unzip for Zip64 Copyright (C) 1998-2010 Gilles Vollant https://www.winimage.com/zLibDll/minizip.html This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_crypt.h" #include "mz_strm.h" #ifdef HAVE_BZIP2 # include "mz_strm_bzip.h" #endif #ifdef HAVE_LIBCOMP # include "mz_strm_libcomp.h" #endif #ifdef HAVE_LZMA # include "mz_strm_lzma.h" #endif #include "mz_strm_mem.h" #ifdef HAVE_PKCRYPT # include "mz_strm_pkcrypt.h" #endif #ifdef HAVE_WZAES # include "mz_strm_wzaes.h" #endif #ifdef HAVE_ZLIB # include "mz_strm_zlib.h" #endif #ifdef HAVE_ZSTD # include "mz_strm_zstd.h" #endif #include "mz_zip.h" #include /* tolower */ #include /* snprintf */ #if defined(_MSC_VER) || defined(__MINGW32__) # define localtime_r(t1, t2) (localtime_s(t2, t1) == 0 ? t1 : NULL) #endif #if defined(_MSC_VER) && (_MSC_VER < 1900) # define snprintf _snprintf #endif /***************************************************************************/ #define MZ_ZIP_MAGIC_LOCALHEADER (0x04034b50) #define MZ_ZIP_MAGIC_LOCALHEADERU8 { 0x50, 0x4b, 0x03, 0x04 } #define MZ_ZIP_MAGIC_CENTRALHEADER (0x02014b50) #define MZ_ZIP_MAGIC_CENTRALHEADERU8 { 0x50, 0x4b, 0x01, 0x02 } #define MZ_ZIP_MAGIC_ENDHEADER (0x06054b50) #define MZ_ZIP_MAGIC_ENDHEADERU8 { 0x50, 0x4b, 0x05, 0x06 } #define MZ_ZIP_MAGIC_ENDHEADER64 (0x06064b50) #define MZ_ZIP_MAGIC_ENDLOCHEADER64 (0x07064b50) #define MZ_ZIP_MAGIC_DATADESCRIPTOR (0x08074b50) #define MZ_ZIP_MAGIC_DATADESCRIPTORU8 { 0x50, 0x4b, 0x07, 0x08 } #define MZ_ZIP_SIZE_LD_ITEM (30) #define MZ_ZIP_SIZE_CD_ITEM (46) #define MZ_ZIP_SIZE_CD_LOCATOR64 (20) #define MZ_ZIP_SIZE_MAX_DATA_DESCRIPTOR (24) #define MZ_ZIP_OFFSET_CRC_SIZES (14) #define MZ_ZIP_UNCOMPR_SIZE64_CUSHION (2 * 1024 * 1024) #ifndef MZ_ZIP_EOCD_MAX_BACK #define MZ_ZIP_EOCD_MAX_BACK (1 << 20) #endif /***************************************************************************/ typedef struct mz_zip_s { mz_zip_file file_info; mz_zip_file local_file_info; void *stream; /* main stream */ void *cd_stream; /* pointer to the stream with the cd */ void *cd_mem_stream; /* memory stream for central directory */ void *compress_stream; /* compression stream */ void *crypt_stream; /* encryption stream */ void *file_info_stream; /* memory stream for storing file info */ void *local_file_info_stream; /* memory stream for storing local file info */ int32_t open_mode; uint8_t recover; uint8_t data_descriptor; uint32_t disk_number_with_cd; /* number of the disk with the central dir */ int64_t disk_offset_shift; /* correction for zips that have wrong offset start of cd */ int64_t cd_start_pos; /* pos of the first file in the central dir stream */ int64_t cd_current_pos; /* pos of the current file in the central dir */ int64_t cd_offset; /* offset of start of central directory */ int64_t cd_size; /* size of the central directory */ uint32_t cd_signature; /* signature of central directory */ uint8_t entry_scanned; /* entry header information read ok */ uint8_t entry_opened; /* entry is open for read/write */ uint8_t entry_raw; /* entry opened with raw mode */ uint32_t entry_crc32; /* entry crc32 */ uint64_t number_entry; uint16_t version_madeby; char *comment; } mz_zip; /***************************************************************************/ #if 0 # define mz_zip_print printf #else # define mz_zip_print(fmt, ...) #endif /***************************************************************************/ /* Locate the end of central directory */ static int32_t mz_zip_search_eocd(void *stream, int64_t *central_pos) { int64_t file_size = 0; int64_t max_back = MZ_ZIP_EOCD_MAX_BACK; uint8_t find[4] = MZ_ZIP_MAGIC_ENDHEADERU8; int32_t err = MZ_OK; err = mz_stream_seek(stream, 0, MZ_SEEK_END); if (err != MZ_OK) return err; file_size = mz_stream_tell(stream); if (max_back <= 0 || max_back > file_size) max_back = file_size; return mz_stream_find_reverse(stream, (const void *)find, sizeof(find), max_back, central_pos); } /* Locate the end of central directory 64 of a zip file */ static int32_t mz_zip_search_zip64_eocd(void *stream, const int64_t end_central_offset, int64_t *central_pos) { int64_t offset = 0; uint32_t value32 = 0; int32_t err = MZ_OK; *central_pos = 0; /* Zip64 end of central directory locator */ err = mz_stream_seek(stream, end_central_offset - MZ_ZIP_SIZE_CD_LOCATOR64, MZ_SEEK_SET); /* Read locator signature */ if (err == MZ_OK) { err = mz_stream_read_uint32(stream, &value32); if (value32 != MZ_ZIP_MAGIC_ENDLOCHEADER64) err = MZ_FORMAT_ERROR; } /* Number of the disk with the start of the zip64 end of central directory */ if (err == MZ_OK) err = mz_stream_read_uint32(stream, &value32); /* Relative offset of the zip64 end of central directory record8 */ if (err == MZ_OK) err = mz_stream_read_uint64(stream, (uint64_t *)&offset); /* Total number of disks */ if (err == MZ_OK) err = mz_stream_read_uint32(stream, &value32); /* Goto end of central directory record */ if (err == MZ_OK) err = mz_stream_seek(stream, (int64_t)offset, MZ_SEEK_SET); /* The signature */ if (err == MZ_OK) { err = mz_stream_read_uint32(stream, &value32); if (value32 != MZ_ZIP_MAGIC_ENDHEADER64) err = MZ_FORMAT_ERROR; } if (err == MZ_OK) *central_pos = offset; return err; } #ifdef HAVE_PKCRYPT /* Get PKWARE traditional encryption verifier */ static uint16_t mz_zip_get_pk_verify(uint32_t dos_date, uint64_t crc, uint16_t flag) { /* Info-ZIP modification to ZipCrypto format: if bit 3 of the general * purpose bit flag is set, it uses high byte of 16-bit File Time. */ if (flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR) return ((dos_date >> 16) & 0xff) << 8 | ((dos_date >> 8) & 0xff); return ((crc >> 16) & 0xff) << 8 | ((crc >> 24) & 0xff); } #endif /* Get info about the current file in the zip file */ static int32_t mz_zip_entry_read_header(void *stream, uint8_t local, mz_zip_file *file_info, void *file_extra_stream) { uint64_t ntfs_time = 0; uint32_t reserved = 0; uint32_t magic = 0; uint32_t dos_date = 0; uint32_t field_pos = 0; uint16_t field_type = 0; uint16_t field_length = 0; uint32_t field_length_read = 0; uint16_t ntfs_attrib_id = 0; uint16_t ntfs_attrib_size = 0; uint16_t linkname_size; uint16_t value16 = 0; uint32_t value32 = 0; int64_t extrafield_pos = 0; int64_t comment_pos = 0; int64_t linkname_pos = 0; int64_t saved_pos = 0; int32_t err = MZ_OK; char *linkname = NULL; memset(file_info, 0, sizeof(mz_zip_file)); /* Check the magic */ err = mz_stream_read_uint32(stream, &magic); if (err == MZ_END_OF_STREAM) err = MZ_END_OF_LIST; else if (magic == MZ_ZIP_MAGIC_ENDHEADER || magic == MZ_ZIP_MAGIC_ENDHEADER64) err = MZ_END_OF_LIST; else if ((local) && (magic != MZ_ZIP_MAGIC_LOCALHEADER)) err = MZ_FORMAT_ERROR; else if ((!local) && (magic != MZ_ZIP_MAGIC_CENTRALHEADER)) err = MZ_FORMAT_ERROR; /* Read header fields */ if (err == MZ_OK) { if (!local) err = mz_stream_read_uint16(stream, &file_info->version_madeby); if (err == MZ_OK) err = mz_stream_read_uint16(stream, &file_info->version_needed); if (err == MZ_OK) err = mz_stream_read_uint16(stream, &file_info->flag); if (err == MZ_OK) err = mz_stream_read_uint16(stream, &file_info->compression_method); if (err == MZ_OK) { err = mz_stream_read_uint32(stream, &dos_date); file_info->modified_date = mz_zip_dosdate_to_time_t(dos_date); } if (err == MZ_OK) err = mz_stream_read_uint32(stream, &file_info->crc); #ifdef HAVE_PKCRYPT if (err == MZ_OK && file_info->flag & MZ_ZIP_FLAG_ENCRYPTED) { /* Use dos_date from header instead of derived from time in zip extensions */ file_info->pk_verify = mz_zip_get_pk_verify(dos_date, file_info->crc, file_info->flag); } #endif if (err == MZ_OK) { err = mz_stream_read_uint32(stream, &value32); file_info->compressed_size = value32; } if (err == MZ_OK) { err = mz_stream_read_uint32(stream, &value32); file_info->uncompressed_size = value32; } if (err == MZ_OK) err = mz_stream_read_uint16(stream, &file_info->filename_size); if (err == MZ_OK) err = mz_stream_read_uint16(stream, &file_info->extrafield_size); if (!local) { if (err == MZ_OK) err = mz_stream_read_uint16(stream, &file_info->comment_size); if (err == MZ_OK) { err = mz_stream_read_uint16(stream, &value16); file_info->disk_number = value16; } if (err == MZ_OK) err = mz_stream_read_uint16(stream, &file_info->internal_fa); if (err == MZ_OK) err = mz_stream_read_uint32(stream, &file_info->external_fa); if (err == MZ_OK) { err = mz_stream_read_uint32(stream, &value32); file_info->disk_offset = value32; } } } if (err == MZ_OK) err = mz_stream_seek(file_extra_stream, 0, MZ_SEEK_SET); /* Copy variable length data to memory stream for later retrieval */ if ((err == MZ_OK) && (file_info->filename_size > 0)) err = mz_stream_copy(file_extra_stream, stream, file_info->filename_size); mz_stream_write_uint8(file_extra_stream, 0); extrafield_pos = mz_stream_tell(file_extra_stream); if ((err == MZ_OK) && (file_info->extrafield_size > 0)) err = mz_stream_copy(file_extra_stream, stream, file_info->extrafield_size); mz_stream_write_uint8(file_extra_stream, 0); comment_pos = mz_stream_tell(file_extra_stream); if ((err == MZ_OK) && (file_info->comment_size > 0)) err = mz_stream_copy(file_extra_stream, stream, file_info->comment_size); mz_stream_write_uint8(file_extra_stream, 0); linkname_pos = mz_stream_tell(file_extra_stream); /* Overwrite if we encounter UNIX1 extra block */ mz_stream_write_uint8(file_extra_stream, 0); if ((err == MZ_OK) && (file_info->extrafield_size > 0)) { /* Seek to and parse the extra field */ err = mz_stream_seek(file_extra_stream, extrafield_pos, MZ_SEEK_SET); while ((err == MZ_OK) && (field_pos + 4 <= file_info->extrafield_size)) { err = mz_zip_extrafield_read(file_extra_stream, &field_type, &field_length); if (err != MZ_OK) break; field_pos += 4; /* Don't allow field length to exceed size of remaining extrafield */ if (field_length > (file_info->extrafield_size - field_pos)) field_length = (uint16_t)(file_info->extrafield_size - field_pos); /* Read ZIP64 extra field */ if ((field_type == MZ_ZIP_EXTENSION_ZIP64) && (field_length >= 8)) { if ((err == MZ_OK) && (file_info->uncompressed_size == UINT32_MAX)) { err = mz_stream_read_int64(file_extra_stream, &file_info->uncompressed_size); if (file_info->uncompressed_size < 0) err = MZ_FORMAT_ERROR; } if ((err == MZ_OK) && (file_info->compressed_size == UINT32_MAX)) { err = mz_stream_read_int64(file_extra_stream, &file_info->compressed_size); if (file_info->compressed_size < 0) err = MZ_FORMAT_ERROR; } if ((err == MZ_OK) && (file_info->disk_offset == UINT32_MAX)) { err = mz_stream_read_int64(file_extra_stream, &file_info->disk_offset); if (file_info->disk_offset < 0) err = MZ_FORMAT_ERROR; } if ((err == MZ_OK) && (file_info->disk_number == UINT16_MAX)) err = mz_stream_read_uint32(file_extra_stream, &file_info->disk_number); } /* Read NTFS extra field */ else if ((field_type == MZ_ZIP_EXTENSION_NTFS) && (field_length > 4)) { if (err == MZ_OK) err = mz_stream_read_uint32(file_extra_stream, &reserved); field_length_read = 4; while ((err == MZ_OK) && (field_length_read + 4 <= field_length)) { err = mz_stream_read_uint16(file_extra_stream, &ntfs_attrib_id); if (err == MZ_OK) err = mz_stream_read_uint16(file_extra_stream, &ntfs_attrib_size); field_length_read += 4; if ((err == MZ_OK) && (ntfs_attrib_id == 0x01) && (ntfs_attrib_size == 24)) { err = mz_stream_read_uint64(file_extra_stream, &ntfs_time); mz_zip_ntfs_to_unix_time(ntfs_time, &file_info->modified_date); if (err == MZ_OK) { err = mz_stream_read_uint64(file_extra_stream, &ntfs_time); mz_zip_ntfs_to_unix_time(ntfs_time, &file_info->accessed_date); } if (err == MZ_OK) { err = mz_stream_read_uint64(file_extra_stream, &ntfs_time); mz_zip_ntfs_to_unix_time(ntfs_time, &file_info->creation_date); } } else if ((err == MZ_OK) && (field_length_read + ntfs_attrib_size <= field_length)) { err = mz_stream_seek(file_extra_stream, ntfs_attrib_size, MZ_SEEK_CUR); } field_length_read += ntfs_attrib_size; } } /* Read UNIX1 extra field */ else if ((field_type == MZ_ZIP_EXTENSION_UNIX1) && (field_length >= 12)) { if (err == MZ_OK) { err = mz_stream_read_uint32(file_extra_stream, &value32); if (err == MZ_OK && file_info->accessed_date == 0) file_info->accessed_date = value32; } if (err == MZ_OK) { err = mz_stream_read_uint32(file_extra_stream, &value32); if (err == MZ_OK && file_info->modified_date == 0) file_info->modified_date = value32; } if (err == MZ_OK) err = mz_stream_read_uint16(file_extra_stream, &value16); /* User id */ if (err == MZ_OK) err = mz_stream_read_uint16(file_extra_stream, &value16); /* Group id */ /* Copy linkname to end of file extra stream so we can return null terminated string */ linkname_size = field_length - 12; if ((err == MZ_OK) && (linkname_size > 0)) { linkname = (char *)malloc(linkname_size); if (linkname) { if (mz_stream_read(file_extra_stream, linkname, linkname_size) != linkname_size) err = MZ_READ_ERROR; if (err == MZ_OK) { saved_pos = mz_stream_tell(file_extra_stream); mz_stream_seek(file_extra_stream, linkname_pos, MZ_SEEK_SET); mz_stream_write(file_extra_stream, linkname, linkname_size); mz_stream_write_uint8(file_extra_stream, 0); mz_stream_seek(file_extra_stream, saved_pos, MZ_SEEK_SET); } free(linkname); } } } #ifdef HAVE_WZAES /* Read AES extra field */ else if ((field_type == MZ_ZIP_EXTENSION_AES) && (field_length == 7)) { uint8_t value8 = 0; /* Verify version info */ err = mz_stream_read_uint16(file_extra_stream, &value16); /* Support AE-1 and AE-2 */ if (value16 != 1 && value16 != 2) err = MZ_FORMAT_ERROR; file_info->aes_version = value16; if (err == MZ_OK) err = mz_stream_read_uint8(file_extra_stream, &value8); if ((char)value8 != 'A') err = MZ_FORMAT_ERROR; if (err == MZ_OK) err = mz_stream_read_uint8(file_extra_stream, &value8); if ((char)value8 != 'E') err = MZ_FORMAT_ERROR; /* Get AES encryption strength and actual compression method */ if (err == MZ_OK) { err = mz_stream_read_uint8(file_extra_stream, &value8); file_info->aes_strength = value8; } if (err == MZ_OK) { err = mz_stream_read_uint16(file_extra_stream, &value16); file_info->compression_method = value16; } } #endif else if (field_length > 0) { err = mz_stream_seek(file_extra_stream, field_length, MZ_SEEK_CUR); } field_pos += field_length; } } /* Get pointers to variable length data */ mz_stream_mem_get_buffer(file_extra_stream, (const void **)&file_info->filename); mz_stream_mem_get_buffer_at(file_extra_stream, extrafield_pos, (const void **)&file_info->extrafield); mz_stream_mem_get_buffer_at(file_extra_stream, comment_pos, (const void **)&file_info->comment); mz_stream_mem_get_buffer_at(file_extra_stream, linkname_pos, (const void **)&file_info->linkname); /* Set to empty string just in-case */ if (!file_info->filename) file_info->filename = ""; if (!file_info->extrafield) file_info->extrafield_size = 0; if (!file_info->comment) file_info->comment = ""; if (!file_info->linkname) file_info->linkname = ""; if (err == MZ_OK) { mz_zip_print("Zip - Entry - Read header - %s (local %" PRId8 ")\n", file_info->filename, local); mz_zip_print("Zip - Entry - Read header compress (ucs %" PRId64 " cs %" PRId64 " crc 0x%08" PRIx32 ")\n", file_info->uncompressed_size, file_info->compressed_size, file_info->crc); if (!local) { mz_zip_print("Zip - Entry - Read header disk (disk %" PRIu32 " offset %" PRId64 ")\n", file_info->disk_number, file_info->disk_offset); } mz_zip_print("Zip - Entry - Read header variable (fnl %" PRId32 " efs %" PRId32 " cms %" PRId32 ")\n", file_info->filename_size, file_info->extrafield_size, file_info->comment_size); } return err; } static int32_t mz_zip_entry_read_descriptor(void *stream, uint8_t zip64, uint32_t *crc32, int64_t *compressed_size, int64_t *uncompressed_size) { uint32_t value32 = 0; int64_t value64 = 0; int32_t err = MZ_OK; err = mz_stream_read_uint32(stream, &value32); if (value32 != MZ_ZIP_MAGIC_DATADESCRIPTOR) err = MZ_FORMAT_ERROR; if (err == MZ_OK) err = mz_stream_read_uint32(stream, &value32); if (err == MZ_OK && crc32) *crc32 = value32; if (err == MZ_OK) { /* If zip 64 extension is enabled then read as 8 byte */ if (!zip64) { err = mz_stream_read_uint32(stream, &value32); value64 = value32; } else { err = mz_stream_read_int64(stream, &value64); if (value64 < 0) err = MZ_FORMAT_ERROR; } if (err == MZ_OK && compressed_size) *compressed_size = value64; } if (err == MZ_OK) { if (!zip64) { err = mz_stream_read_uint32(stream, &value32); value64 = value32; } else { err = mz_stream_read_int64(stream, &value64); if (value64 < 0) err = MZ_FORMAT_ERROR; } if (err == MZ_OK && uncompressed_size) *uncompressed_size = value64; } return err; } static int32_t mz_zip_entry_write_crc_sizes(void *stream, uint8_t zip64, uint8_t mask, mz_zip_file *file_info) { int32_t err = MZ_OK; if (mask) err = mz_stream_write_uint32(stream, 0); else err = mz_stream_write_uint32(stream, file_info->crc); /* crc */ /* For backwards-compatibility with older zip applications we set all sizes to UINT32_MAX * when zip64 is needed, instead of only setting sizes larger than UINT32_MAX. */ if (err == MZ_OK) { if (zip64) /* compr size */ err = mz_stream_write_uint32(stream, UINT32_MAX); else err = mz_stream_write_uint32(stream, (uint32_t)file_info->compressed_size); } if (err == MZ_OK) { if (mask) /* uncompr size */ err = mz_stream_write_uint32(stream, 0); else if (zip64) err = mz_stream_write_uint32(stream, UINT32_MAX); else err = mz_stream_write_uint32(stream, (uint32_t)file_info->uncompressed_size); } return err; } static int32_t mz_zip_entry_needs_zip64(mz_zip_file *file_info, uint8_t local, uint8_t *zip64) { uint32_t max_uncompressed_size = UINT32_MAX; uint8_t needs_zip64 = 0; if (!zip64) return MZ_PARAM_ERROR; *zip64 = 0; if (local) { /* At local header we might not know yet whether compressed size will overflow unsigned 32-bit integer which might happen for high entropy data so we give it some cushion */ max_uncompressed_size -= MZ_ZIP_UNCOMPR_SIZE64_CUSHION; } needs_zip64 = (file_info->uncompressed_size >= max_uncompressed_size) || (file_info->compressed_size >= UINT32_MAX); if (!local) { /* Disk offset and number only used in central directory header */ needs_zip64 |= (file_info->disk_offset >= UINT32_MAX) || (file_info->disk_number >= UINT16_MAX); } if (file_info->zip64 == MZ_ZIP64_AUTO) { /* If uncompressed size is unknown, assume zip64 for 64-bit data descriptors */ if (local && file_info->uncompressed_size == 0) { /* Don't use zip64 for local header directory entries */ if (mz_zip_attrib_is_dir(file_info->external_fa, file_info->version_madeby) != MZ_OK) { *zip64 = 1; } } *zip64 |= needs_zip64; } else if (file_info->zip64 == MZ_ZIP64_FORCE) { *zip64 = 1; } else if (file_info->zip64 == MZ_ZIP64_DISABLE) { /* Zip64 extension is required to zip file */ if (needs_zip64) return MZ_PARAM_ERROR; } return MZ_OK; } static int32_t mz_zip_entry_write_header(void *stream, uint8_t local, mz_zip_file *file_info) { uint64_t ntfs_time = 0; uint32_t reserved = 0; uint32_t dos_date = 0; uint16_t extrafield_size = 0; uint16_t field_type = 0; uint16_t field_length = 0; uint16_t field_length_zip64 = 0; uint16_t field_length_ntfs = 0; uint16_t field_length_aes = 0; uint16_t field_length_unix1 = 0; uint16_t filename_size = 0; uint16_t filename_length = 0; uint16_t linkname_size = 0; uint16_t version_needed = 0; int32_t comment_size = 0; int32_t err = MZ_OK; int32_t err_mem = MZ_OK; uint8_t zip64 = 0; uint8_t skip_aes = 0; uint8_t mask = 0; uint8_t write_end_slash = 0; const char *filename = NULL; char masked_name[64]; void *file_extra_stream = NULL; if (!file_info) return MZ_PARAM_ERROR; if ((local) && (file_info->flag & MZ_ZIP_FLAG_MASK_LOCAL_INFO)) mask = 1; /* Determine if zip64 extra field is necessary */ err = mz_zip_entry_needs_zip64(file_info, local, &zip64); if (err != MZ_OK) return err; /* Start calculating extra field sizes */ if (zip64) { /* Both compressed and uncompressed sizes must be included (at least in local header) */ field_length_zip64 = 8 + 8; if ((!local) && (file_info->disk_offset >= UINT32_MAX)) field_length_zip64 += 8; extrafield_size += 4; extrafield_size += field_length_zip64; } /* Calculate extra field size and check for duplicates */ if (file_info->extrafield_size > 0) { file_extra_stream = mz_stream_mem_create(); if (!file_extra_stream) return MZ_MEM_ERROR; mz_stream_mem_set_buffer(file_extra_stream, (void *)file_info->extrafield, file_info->extrafield_size); do { err_mem = mz_stream_read_uint16(file_extra_stream, &field_type); if (err_mem == MZ_OK) err_mem = mz_stream_read_uint16(file_extra_stream, &field_length); if (err_mem != MZ_OK) break; /* Prefer incoming aes extensions over ours */ if (field_type == MZ_ZIP_EXTENSION_AES) skip_aes = 1; /* Prefer our zip64, ntfs, unix1 extension over incoming */ if (field_type != MZ_ZIP_EXTENSION_ZIP64 && field_type != MZ_ZIP_EXTENSION_NTFS && field_type != MZ_ZIP_EXTENSION_UNIX1) extrafield_size += 4 + field_length; if (err_mem == MZ_OK) err_mem = mz_stream_seek(file_extra_stream, field_length, MZ_SEEK_CUR); } while (err_mem == MZ_OK); } #ifdef HAVE_WZAES if (!skip_aes) { if ((file_info->flag & MZ_ZIP_FLAG_ENCRYPTED) && (file_info->aes_version)) { field_length_aes = 1 + 1 + 1 + 2 + 2; extrafield_size += 4 + field_length_aes; } } #else MZ_UNUSED(field_length_aes); MZ_UNUSED(skip_aes); #endif /* NTFS timestamps */ if ((file_info->modified_date != 0) && (file_info->accessed_date != 0) && (file_info->creation_date != 0) && (!mask)) { field_length_ntfs = 8 + 8 + 8 + 4 + 2 + 2; extrafield_size += 4 + field_length_ntfs; } /* Unix1 symbolic links */ if (file_info->linkname && *file_info->linkname != 0) { linkname_size = (uint16_t)strlen(file_info->linkname); field_length_unix1 = 12 + linkname_size; extrafield_size += 4 + field_length_unix1; } if (local) err = mz_stream_write_uint32(stream, MZ_ZIP_MAGIC_LOCALHEADER); else { err = mz_stream_write_uint32(stream, MZ_ZIP_MAGIC_CENTRALHEADER); if (err == MZ_OK) err = mz_stream_write_uint16(stream, file_info->version_madeby); } /* Calculate version needed to extract */ if (err == MZ_OK) { version_needed = file_info->version_needed; if (version_needed == 0) { version_needed = 20; if (zip64) version_needed = 45; #ifdef HAVE_WZAES if ((file_info->flag & MZ_ZIP_FLAG_ENCRYPTED) && (file_info->aes_version)) version_needed = 51; #endif #if defined(HAVE_LZMA) || defined(HAVE_LIBCOMP) if ((file_info->compression_method == MZ_COMPRESS_METHOD_LZMA) || (file_info->compression_method == MZ_COMPRESS_METHOD_XZ)) version_needed = 63; #endif } err = mz_stream_write_uint16(stream, version_needed); } if (err == MZ_OK) err = mz_stream_write_uint16(stream, file_info->flag); if (err == MZ_OK) { #ifdef HAVE_WZAES if ((file_info->flag & MZ_ZIP_FLAG_ENCRYPTED) && (file_info->aes_version)) err = mz_stream_write_uint16(stream, MZ_COMPRESS_METHOD_AES); else #endif err = mz_stream_write_uint16(stream, file_info->compression_method); } if (err == MZ_OK) { if (file_info->modified_date != 0 && !mask) dos_date = mz_zip_time_t_to_dos_date(file_info->modified_date); err = mz_stream_write_uint32(stream, dos_date); } if (err == MZ_OK) err = mz_zip_entry_write_crc_sizes(stream, zip64, mask, file_info); if (mask) { snprintf(masked_name, sizeof(masked_name), "%" PRIx32 "_%" PRIx64, file_info->disk_number, file_info->disk_offset); filename = masked_name; } else { filename = file_info->filename; } filename_length = (uint16_t)strlen(filename); filename_size += filename_length; if ((mz_zip_attrib_is_dir(file_info->external_fa, file_info->version_madeby) == MZ_OK) && ((filename[filename_length - 1] != '/') && (filename[filename_length - 1] != '\\'))) { filename_size += 1; write_end_slash = 1; } if (err == MZ_OK) err = mz_stream_write_uint16(stream, filename_size); if (err == MZ_OK) err = mz_stream_write_uint16(stream, extrafield_size); if (!local) { if (file_info->comment) { comment_size = (int32_t)strlen(file_info->comment); if (comment_size > UINT16_MAX) comment_size = UINT16_MAX; } if (err == MZ_OK) err = mz_stream_write_uint16(stream, (uint16_t)comment_size); if (err == MZ_OK) err = mz_stream_write_uint16(stream, (uint16_t)file_info->disk_number); if (err == MZ_OK) err = mz_stream_write_uint16(stream, file_info->internal_fa); if (err == MZ_OK) err = mz_stream_write_uint32(stream, file_info->external_fa); if (err == MZ_OK) { if (file_info->disk_offset >= UINT32_MAX) err = mz_stream_write_uint32(stream, UINT32_MAX); else err = mz_stream_write_uint32(stream, (uint32_t)file_info->disk_offset); } } if (err == MZ_OK) { const char *backslash = NULL; const char *next = filename; int32_t left = filename_length; /* Ensure all slashes are written as forward slashes according to 4.4.17.1 */ while ((err == MZ_OK) && (backslash = strchr(next, '\\'))) { int32_t part_length = (int32_t)(backslash - next); if (mz_stream_write(stream, next, part_length) != part_length || mz_stream_write(stream, "/", 1) != 1) err = MZ_WRITE_ERROR; left -= part_length + 1; next = backslash + 1; } if (err == MZ_OK && left > 0) { if (mz_stream_write(stream, next, left) != left) err = MZ_WRITE_ERROR; } /* Ensure that directories have a slash appended to them for compatibility */ if (err == MZ_OK && write_end_slash) err = mz_stream_write_uint8(stream, '/'); } /* Write ZIP64 extra field first so we can update sizes later if data descriptor not used */ if ((err == MZ_OK) && (zip64)) { err = mz_zip_extrafield_write(stream, MZ_ZIP_EXTENSION_ZIP64, field_length_zip64); if (err == MZ_OK) { if (mask) err = mz_stream_write_int64(stream, 0); else err = mz_stream_write_int64(stream, file_info->uncompressed_size); } if (err == MZ_OK) err = mz_stream_write_int64(stream, file_info->compressed_size); if ((err == MZ_OK) && (!local) && (file_info->disk_offset >= UINT32_MAX)) err = mz_stream_write_int64(stream, file_info->disk_offset); if ((err == MZ_OK) && (!local) && (file_info->disk_number >= UINT16_MAX)) err = mz_stream_write_uint32(stream, file_info->disk_number); } /* Write NTFS extra field */ if ((err == MZ_OK) && (field_length_ntfs > 0)) { err = mz_zip_extrafield_write(stream, MZ_ZIP_EXTENSION_NTFS, field_length_ntfs); if (err == MZ_OK) err = mz_stream_write_uint32(stream, reserved); if (err == MZ_OK) err = mz_stream_write_uint16(stream, 0x01); if (err == MZ_OK) err = mz_stream_write_uint16(stream, field_length_ntfs - 8); if (err == MZ_OK) { mz_zip_unix_to_ntfs_time(file_info->modified_date, &ntfs_time); err = mz_stream_write_uint64(stream, ntfs_time); } if (err == MZ_OK) { mz_zip_unix_to_ntfs_time(file_info->accessed_date, &ntfs_time); err = mz_stream_write_uint64(stream, ntfs_time); } if (err == MZ_OK) { mz_zip_unix_to_ntfs_time(file_info->creation_date, &ntfs_time); err = mz_stream_write_uint64(stream, ntfs_time); } } /* Write UNIX extra block extra field */ if ((err == MZ_OK) && (field_length_unix1 > 0)) { err = mz_zip_extrafield_write(stream, MZ_ZIP_EXTENSION_UNIX1, field_length_unix1); if (err == MZ_OK) err = mz_stream_write_uint32(stream, (uint32_t)file_info->accessed_date); if (err == MZ_OK) err = mz_stream_write_uint32(stream, (uint32_t)file_info->modified_date); if (err == MZ_OK) /* User id */ err = mz_stream_write_uint16(stream, 0); if (err == MZ_OK) /* Group id */ err = mz_stream_write_uint16(stream, 0); if (err == MZ_OK && linkname_size > 0) { if (mz_stream_write(stream, file_info->linkname, linkname_size) != linkname_size) err = MZ_WRITE_ERROR; } } #ifdef HAVE_WZAES /* Write AES extra field */ if ((err == MZ_OK) && (!skip_aes) && (file_info->flag & MZ_ZIP_FLAG_ENCRYPTED) && (file_info->aes_version)) { err = mz_zip_extrafield_write(stream, MZ_ZIP_EXTENSION_AES, field_length_aes); if (err == MZ_OK) err = mz_stream_write_uint16(stream, file_info->aes_version); if (err == MZ_OK) err = mz_stream_write_uint8(stream, 'A'); if (err == MZ_OK) err = mz_stream_write_uint8(stream, 'E'); if (err == MZ_OK) err = mz_stream_write_uint8(stream, file_info->aes_strength); if (err == MZ_OK) err = mz_stream_write_uint16(stream, file_info->compression_method); } #endif if (file_info->extrafield_size > 0) { err_mem = mz_stream_mem_seek(file_extra_stream, 0, MZ_SEEK_SET); while (err == MZ_OK && err_mem == MZ_OK) { err_mem = mz_stream_read_uint16(file_extra_stream, &field_type); if (err_mem == MZ_OK) err_mem = mz_stream_read_uint16(file_extra_stream, &field_length); if (err_mem != MZ_OK) break; /* Prefer our zip 64, ntfs, unix1 extensions over incoming */ if (field_type == MZ_ZIP_EXTENSION_ZIP64 || field_type == MZ_ZIP_EXTENSION_NTFS || field_type == MZ_ZIP_EXTENSION_UNIX1) { err_mem = mz_stream_seek(file_extra_stream, field_length, MZ_SEEK_CUR); continue; } err = mz_stream_write_uint16(stream, field_type); if (err == MZ_OK) err = mz_stream_write_uint16(stream, field_length); if (err == MZ_OK) err = mz_stream_copy(stream, file_extra_stream, field_length); } mz_stream_mem_delete(&file_extra_stream); } if (err == MZ_OK && !local && file_info->comment) { if (mz_stream_write(stream, file_info->comment, file_info->comment_size) != file_info->comment_size) err = MZ_WRITE_ERROR; } return err; } static int32_t mz_zip_entry_write_descriptor(void *stream, uint8_t zip64, uint32_t crc32, int64_t compressed_size, int64_t uncompressed_size) { int32_t err = MZ_OK; err = mz_stream_write_uint32(stream, MZ_ZIP_MAGIC_DATADESCRIPTOR); if (err == MZ_OK) err = mz_stream_write_uint32(stream, crc32); /* Store data descriptor as 8 bytes if zip 64 extension enabled */ if (err == MZ_OK) { /* Zip 64 extension is enabled when uncompressed size is > UINT32_MAX */ if (!zip64) err = mz_stream_write_uint32(stream, (uint32_t)compressed_size); else err = mz_stream_write_int64(stream, compressed_size); } if (err == MZ_OK) { if (!zip64) err = mz_stream_write_uint32(stream, (uint32_t)uncompressed_size); else err = mz_stream_write_int64(stream, uncompressed_size); } return err; } static int32_t mz_zip_read_cd(void *handle) { mz_zip *zip = (mz_zip *)handle; uint64_t number_entry_cd64 = 0; uint64_t number_entry_cd = 0; int64_t eocd_pos = 0; int64_t eocd_pos64 = 0; int64_t value64i = 0; uint16_t value16 = 0; uint32_t value32 = 0; uint64_t value64 = 0; uint16_t comment_size = 0; int32_t comment_read = 0; int32_t err = MZ_OK; if (!zip) return MZ_PARAM_ERROR; /* Read and cache central directory records */ err = mz_zip_search_eocd(zip->stream, &eocd_pos); if (err == MZ_OK) { /* The signature, already checked */ err = mz_stream_read_uint32(zip->stream, &value32); /* Number of this disk */ if (err == MZ_OK) err = mz_stream_read_uint16(zip->stream, &value16); /* Number of the disk with the start of the central directory */ if (err == MZ_OK) err = mz_stream_read_uint16(zip->stream, &value16); zip->disk_number_with_cd = value16; /* Total number of entries in the central dir on this disk */ if (err == MZ_OK) err = mz_stream_read_uint16(zip->stream, &value16); zip->number_entry = value16; /* Total number of entries in the central dir */ if (err == MZ_OK) err = mz_stream_read_uint16(zip->stream, &value16); number_entry_cd = value16; if (number_entry_cd != zip->number_entry) err = MZ_FORMAT_ERROR; /* Size of the central directory */ if (err == MZ_OK) err = mz_stream_read_uint32(zip->stream, &value32); if (err == MZ_OK) zip->cd_size = value32; /* Offset of start of central directory with respect to the starting disk number */ if (err == MZ_OK) err = mz_stream_read_uint32(zip->stream, &value32); if (err == MZ_OK) zip->cd_offset = value32; /* Zip file global comment length */ if (err == MZ_OK) err = mz_stream_read_uint16(zip->stream, &comment_size); if ((err == MZ_OK) && (comment_size > 0)) { zip->comment = (char *)malloc(comment_size + 1); if (zip->comment) { comment_read = mz_stream_read(zip->stream, zip->comment, comment_size); /* Don't fail if incorrect comment length read, not critical */ if (comment_read < 0) comment_read = 0; zip->comment[comment_read] = 0; } } if ((err == MZ_OK) && ((number_entry_cd == UINT16_MAX) || (zip->cd_offset == UINT32_MAX))) { /* Format should be Zip64, as the central directory or file size is too large */ if (mz_zip_search_zip64_eocd(zip->stream, eocd_pos, &eocd_pos64) == MZ_OK) { eocd_pos = eocd_pos64; err = mz_stream_seek(zip->stream, eocd_pos, MZ_SEEK_SET); /* The signature, already checked */ if (err == MZ_OK) err = mz_stream_read_uint32(zip->stream, &value32); /* Size of zip64 end of central directory record */ if (err == MZ_OK) err = mz_stream_read_uint64(zip->stream, &value64); /* Version made by */ if (err == MZ_OK) err = mz_stream_read_uint16(zip->stream, &zip->version_madeby); /* Version needed to extract */ if (err == MZ_OK) err = mz_stream_read_uint16(zip->stream, &value16); /* Number of this disk */ if (err == MZ_OK) err = mz_stream_read_uint32(zip->stream, &value32); /* Number of the disk with the start of the central directory */ if (err == MZ_OK) err = mz_stream_read_uint32(zip->stream, &zip->disk_number_with_cd); /* Total number of entries in the central directory on this disk */ if (err == MZ_OK) err = mz_stream_read_uint64(zip->stream, &zip->number_entry); /* Total number of entries in the central directory */ if (err == MZ_OK) err = mz_stream_read_uint64(zip->stream, &number_entry_cd64); if (zip->number_entry != number_entry_cd64) err = MZ_FORMAT_ERROR; /* Size of the central directory */ if (err == MZ_OK) { err = mz_stream_read_int64(zip->stream, &zip->cd_size); if (zip->cd_size < 0) err = MZ_FORMAT_ERROR; } /* Offset of start of central directory with respect to the starting disk number */ if (err == MZ_OK) { err = mz_stream_read_int64(zip->stream, &zip->cd_offset); if (zip->cd_offset < 0) err = MZ_FORMAT_ERROR; } } else if ((zip->number_entry == UINT16_MAX) || (number_entry_cd != zip->number_entry) || (zip->cd_size == UINT16_MAX) || (zip->cd_offset == UINT32_MAX)) { err = MZ_FORMAT_ERROR; } } } if (err == MZ_OK) { mz_zip_print("Zip - Read cd (disk %" PRId32 " entries %" PRId64 " offset %" PRId64 " size %" PRId64 ")\n", zip->disk_number_with_cd, zip->number_entry, zip->cd_offset, zip->cd_size); /* Verify central directory signature exists at offset */ err = mz_stream_seek(zip->stream, zip->cd_offset, MZ_SEEK_SET); if (err == MZ_OK) err = mz_stream_read_uint32(zip->stream, &zip->cd_signature); if ((err == MZ_OK) && (zip->cd_signature != MZ_ZIP_MAGIC_CENTRALHEADER)) { /* If cd exists in large file and no zip-64 support, error for recover */ if (eocd_pos > UINT32_MAX && eocd_pos64 == 0) err = MZ_FORMAT_ERROR; /* If cd not found attempt to seek backward to find it */ if (err == MZ_OK) err = mz_stream_seek(zip->stream, eocd_pos - zip->cd_size, MZ_SEEK_SET); if (err == MZ_OK) err = mz_stream_read_uint32(zip->stream, &zip->cd_signature); if ((err == MZ_OK) && (zip->cd_signature == MZ_ZIP_MAGIC_CENTRALHEADER)) { /* If found compensate for incorrect locations */ value64i = zip->cd_offset; zip->cd_offset = eocd_pos - zip->cd_size; /* Assume disk has prepended data */ zip->disk_offset_shift = zip->cd_offset - value64i; } } } if (err == MZ_OK) { if (eocd_pos < zip->cd_offset) { /* End of central dir should always come after central dir */ err = MZ_FORMAT_ERROR; } else if ((uint64_t)eocd_pos < (uint64_t)zip->cd_offset + zip->cd_size) { /* Truncate size of cd if incorrect size or offset provided */ zip->cd_size = eocd_pos - zip->cd_offset; } } return err; } static int32_t mz_zip_write_cd(void *handle) { mz_zip *zip = (mz_zip *)handle; int64_t zip64_eocd_pos_inzip = 0; int64_t disk_number = 0; int64_t disk_size = 0; int32_t comment_size = 0; int32_t err = MZ_OK; if (!zip) return MZ_PARAM_ERROR; if (mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, &disk_number) == MZ_OK) zip->disk_number_with_cd = (uint32_t)disk_number; if (mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_SIZE, &disk_size) == MZ_OK && disk_size > 0) zip->disk_number_with_cd += 1; mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, -1); if ((zip->disk_number_with_cd > 0) && (zip->open_mode & MZ_OPEN_MODE_APPEND)) { // Overwrite existing central directory if using split disks mz_stream_seek(zip->stream, 0, MZ_SEEK_SET); } zip->cd_offset = mz_stream_tell(zip->stream); mz_stream_seek(zip->cd_mem_stream, 0, MZ_SEEK_END); zip->cd_size = (uint32_t)mz_stream_tell(zip->cd_mem_stream); mz_stream_seek(zip->cd_mem_stream, 0, MZ_SEEK_SET); err = mz_stream_copy(zip->stream, zip->cd_mem_stream, (int32_t)zip->cd_size); mz_zip_print("Zip - Write cd (disk %" PRId32 " entries %" PRId64 " offset %" PRId64 " size %" PRId64 ")\n", zip->disk_number_with_cd, zip->number_entry, zip->cd_offset, zip->cd_size); if (zip->cd_size == 0 && zip->number_entry > 0) { // Zip does not contain central directory, open with recovery option return MZ_FORMAT_ERROR; } /* Write the ZIP64 central directory header */ if (zip->cd_offset >= UINT32_MAX || zip->number_entry >= UINT16_MAX) { zip64_eocd_pos_inzip = mz_stream_tell(zip->stream); err = mz_stream_write_uint32(zip->stream, MZ_ZIP_MAGIC_ENDHEADER64); /* Size of this 'zip64 end of central directory' */ if (err == MZ_OK) err = mz_stream_write_uint64(zip->stream, (uint64_t)44); /* Version made by */ if (err == MZ_OK) err = mz_stream_write_uint16(zip->stream, zip->version_madeby); /* Version needed */ if (err == MZ_OK) err = mz_stream_write_uint16(zip->stream, (uint16_t)45); /* Number of this disk */ if (err == MZ_OK) err = mz_stream_write_uint32(zip->stream, zip->disk_number_with_cd); /* Number of the disk with the start of the central directory */ if (err == MZ_OK) err = mz_stream_write_uint32(zip->stream, zip->disk_number_with_cd); /* Total number of entries in the central dir on this disk */ if (err == MZ_OK) err = mz_stream_write_uint64(zip->stream, zip->number_entry); /* Total number of entries in the central dir */ if (err == MZ_OK) err = mz_stream_write_uint64(zip->stream, zip->number_entry); /* Size of the central directory */ if (err == MZ_OK) err = mz_stream_write_int64(zip->stream, zip->cd_size); /* Offset of start of central directory with respect to the starting disk number */ if (err == MZ_OK) err = mz_stream_write_int64(zip->stream, zip->cd_offset); if (err == MZ_OK) err = mz_stream_write_uint32(zip->stream, MZ_ZIP_MAGIC_ENDLOCHEADER64); /* Number of the disk with the start of the central directory */ if (err == MZ_OK) err = mz_stream_write_uint32(zip->stream, zip->disk_number_with_cd); /* Relative offset to the end of zip64 central directory */ if (err == MZ_OK) err = mz_stream_write_int64(zip->stream, zip64_eocd_pos_inzip); /* Number of the disk with the start of the central directory */ if (err == MZ_OK) err = mz_stream_write_uint32(zip->stream, zip->disk_number_with_cd + 1); } /* Write the central directory header */ /* Signature */ if (err == MZ_OK) err = mz_stream_write_uint32(zip->stream, MZ_ZIP_MAGIC_ENDHEADER); /* Number of this disk */ if (err == MZ_OK) err = mz_stream_write_uint16(zip->stream, (uint16_t)zip->disk_number_with_cd); /* Number of the disk with the start of the central directory */ if (err == MZ_OK) err = mz_stream_write_uint16(zip->stream, (uint16_t)zip->disk_number_with_cd); /* Total number of entries in the central dir on this disk */ if (err == MZ_OK) { if (zip->number_entry >= UINT16_MAX) err = mz_stream_write_uint16(zip->stream, UINT16_MAX); else err = mz_stream_write_uint16(zip->stream, (uint16_t)zip->number_entry); } /* Total number of entries in the central dir */ if (err == MZ_OK) { if (zip->number_entry >= UINT16_MAX) err = mz_stream_write_uint16(zip->stream, UINT16_MAX); else err = mz_stream_write_uint16(zip->stream, (uint16_t)zip->number_entry); } /* Size of the central directory */ if (err == MZ_OK) err = mz_stream_write_uint32(zip->stream, (uint32_t)zip->cd_size); /* Offset of start of central directory with respect to the starting disk number */ if (err == MZ_OK) { if (zip->cd_offset >= UINT32_MAX) err = mz_stream_write_uint32(zip->stream, UINT32_MAX); else err = mz_stream_write_uint32(zip->stream, (uint32_t)zip->cd_offset); } /* Write global comment */ if (zip->comment) { comment_size = (int32_t)strlen(zip->comment); if (comment_size > UINT16_MAX) comment_size = UINT16_MAX; } if (err == MZ_OK) err = mz_stream_write_uint16(zip->stream, (uint16_t)comment_size); if (err == MZ_OK) { if (mz_stream_write(zip->stream, zip->comment, comment_size) != comment_size) err = MZ_READ_ERROR; } return err; } static int32_t mz_zip_recover_cd(void *handle) { mz_zip *zip = (mz_zip *)handle; mz_zip_file local_file_info; void *local_file_info_stream = NULL; void *cd_mem_stream = NULL; uint64_t number_entry = 0; int64_t descriptor_pos = 0; int64_t next_header_pos = 0; int64_t disk_offset = 0; int64_t disk_number = 0; int64_t compressed_pos = 0; int64_t compressed_end_pos = 0; int64_t compressed_size = 0; int64_t uncompressed_size = 0; uint8_t descriptor_magic[4] = MZ_ZIP_MAGIC_DATADESCRIPTORU8; uint8_t local_header_magic[4] = MZ_ZIP_MAGIC_LOCALHEADERU8; uint8_t central_header_magic[4] = MZ_ZIP_MAGIC_CENTRALHEADERU8; uint32_t crc32 = 0; int32_t disk_number_with_cd = 0; int32_t err = MZ_OK; uint8_t zip64 = 0; uint8_t eof = 0; mz_zip_print("Zip - Recover - Start\n"); mz_zip_get_cd_mem_stream(handle, &cd_mem_stream); /* Determine if we are on a split disk or not */ mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, 0); if (mz_stream_tell(zip->stream) < 0) { mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, -1); mz_stream_seek(zip->stream, 0, MZ_SEEK_SET); } else disk_number_with_cd = 1; local_file_info_stream = mz_stream_mem_create(); if (!local_file_info_stream) return MZ_MEM_ERROR; if (mz_stream_is_open(cd_mem_stream) != MZ_OK) err = mz_stream_mem_open(cd_mem_stream, NULL, MZ_OPEN_MODE_CREATE); mz_stream_mem_open(local_file_info_stream, NULL, MZ_OPEN_MODE_CREATE); if (err == MZ_OK) { err = mz_stream_find(zip->stream, (const void *)local_header_magic, sizeof(local_header_magic), INT64_MAX, &next_header_pos); } while (err == MZ_OK && !eof) { /* Get current offset and disk number for central dir record */ disk_offset = mz_stream_tell(zip->stream); mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, &disk_number); /* Read local headers */ memset(&local_file_info, 0, sizeof(local_file_info)); err = mz_zip_entry_read_header(zip->stream, 1, &local_file_info, local_file_info_stream); if (err != MZ_OK) break; local_file_info.disk_offset = disk_offset; if (disk_number < 0) disk_number = 0; local_file_info.disk_number = (uint32_t)disk_number; compressed_pos = mz_stream_tell(zip->stream); if ((err == MZ_OK) && (local_file_info.compressed_size > 0)) { mz_stream_seek(zip->stream, local_file_info.compressed_size, MZ_SEEK_CUR); } for (;;) { /* Search for the next local header */ err = mz_stream_find(zip->stream, (const void *)local_header_magic, sizeof(local_header_magic), INT64_MAX, &next_header_pos); if (err == MZ_EXIST_ERROR) { mz_stream_seek(zip->stream, compressed_pos, MZ_SEEK_SET); /* Search for central dir if no local header found */ err = mz_stream_find(zip->stream, (const void *)central_header_magic, sizeof(central_header_magic), INT64_MAX, &next_header_pos); if (err == MZ_EXIST_ERROR) { /* Get end of stream if no central header found */ mz_stream_seek(zip->stream, 0, MZ_SEEK_END); next_header_pos = mz_stream_tell(zip->stream); } eof = 1; } if (local_file_info.flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR || local_file_info.compressed_size == 0) { /* Search backwards for the descriptor, seeking too far back will be incorrect if compressed size is small */ err = mz_stream_find_reverse(zip->stream, (const void *)descriptor_magic, sizeof(descriptor_magic), MZ_ZIP_SIZE_MAX_DATA_DESCRIPTOR, &descriptor_pos); if (err == MZ_OK) { if (mz_zip_extrafield_contains(local_file_info.extrafield, local_file_info.extrafield_size, MZ_ZIP_EXTENSION_ZIP64, NULL) == MZ_OK) zip64 = 1; err = mz_zip_entry_read_descriptor(zip->stream, zip64, &crc32, &compressed_size, &uncompressed_size); if (err == MZ_OK) { if (local_file_info.crc == 0) local_file_info.crc = crc32; if (local_file_info.compressed_size == 0) local_file_info.compressed_size = compressed_size; if (local_file_info.uncompressed_size == 0) local_file_info.uncompressed_size = uncompressed_size; } compressed_end_pos = descriptor_pos; } else if (eof) { compressed_end_pos = next_header_pos; } else if (local_file_info.flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR) { /* Wrong local file entry found, keep searching */ next_header_pos += 1; mz_stream_seek(zip->stream, next_header_pos, MZ_SEEK_SET); continue; } } else { compressed_end_pos = next_header_pos; } break; } compressed_size = compressed_end_pos - compressed_pos; if (compressed_size > UINT32_MAX) { /* Update sizes if 4GB file is written with no ZIP64 support */ if (local_file_info.uncompressed_size < UINT32_MAX) { local_file_info.compressed_size = compressed_size; local_file_info.uncompressed_size = 0; } } mz_zip_print("Zip - Recover - Entry %s (csize %" PRId64 " usize %" PRId64 " flags 0x%" PRIx16 ")\n", local_file_info.filename, local_file_info.compressed_size, local_file_info.uncompressed_size, local_file_info.flag); /* Rewrite central dir with local headers and offsets */ err = mz_zip_entry_write_header(cd_mem_stream, 0, &local_file_info); if (err == MZ_OK) number_entry += 1; err = mz_stream_seek(zip->stream, next_header_pos, MZ_SEEK_SET); } mz_stream_mem_delete(&local_file_info_stream); mz_zip_print("Zip - Recover - Complete (cddisk %" PRId32 " entries %" PRId64 ")\n", disk_number_with_cd, number_entry); if (number_entry == 0) return err; /* Set new upper seek boundary for central dir mem stream */ disk_offset = mz_stream_tell(cd_mem_stream); mz_stream_mem_set_buffer_limit(cd_mem_stream, (int32_t)disk_offset); /* Set new central directory info */ mz_zip_set_cd_stream(handle, 0, cd_mem_stream); mz_zip_set_number_entry(handle, number_entry); mz_zip_set_disk_number_with_cd(handle, disk_number_with_cd); return MZ_OK; } void *mz_zip_create(void) { mz_zip *zip = (mz_zip *)calloc(1, sizeof(mz_zip)); if (zip) zip->data_descriptor = 1; return zip; } void mz_zip_delete(void **handle) { mz_zip *zip = NULL; if (!handle) return; zip = (mz_zip *)*handle; if (zip) { free(zip); } *handle = NULL; } int32_t mz_zip_open(void *handle, void *stream, int32_t mode) { mz_zip *zip = (mz_zip *)handle; int32_t err = MZ_OK; if (!zip) return MZ_PARAM_ERROR; mz_zip_print("Zip - Open\n"); zip->stream = stream; zip->cd_mem_stream = mz_stream_mem_create(); if (!zip->cd_mem_stream) return MZ_MEM_ERROR; if (mode & MZ_OPEN_MODE_WRITE) { mz_stream_mem_open(zip->cd_mem_stream, NULL, MZ_OPEN_MODE_CREATE); zip->cd_stream = zip->cd_mem_stream; } else { zip->cd_stream = stream; } if ((mode & MZ_OPEN_MODE_READ) || (mode & MZ_OPEN_MODE_APPEND)) { if ((mode & MZ_OPEN_MODE_CREATE) == 0) { err = mz_zip_read_cd(zip); if (err != MZ_OK) { mz_zip_print("Zip - Error detected reading cd (%" PRId32 ")\n", err); if (zip->recover && mz_zip_recover_cd(zip) == MZ_OK) err = MZ_OK; } } if ((err == MZ_OK) && (mode & MZ_OPEN_MODE_APPEND)) { if (zip->cd_size > 0) { /* Store central directory in memory */ err = mz_stream_seek(zip->stream, zip->cd_offset, MZ_SEEK_SET); if (err == MZ_OK) err = mz_stream_copy(zip->cd_mem_stream, zip->stream, (int32_t)zip->cd_size); if (err == MZ_OK) err = mz_stream_seek(zip->stream, zip->cd_offset, MZ_SEEK_SET); } else { if (zip->cd_signature == MZ_ZIP_MAGIC_ENDHEADER) { /* If tiny zip then overwrite end header */ err = mz_stream_seek(zip->stream, zip->cd_offset, MZ_SEEK_SET); } else { /* If no central directory, append new zip to end of file */ err = mz_stream_seek(zip->stream, 0, MZ_SEEK_END); } } if (zip->disk_number_with_cd > 0) { /* Move to last disk to begin appending */ mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, zip->disk_number_with_cd - 1); } } else { zip->cd_start_pos = zip->cd_offset; } } if (err != MZ_OK) { mz_zip_close(zip); return err; } /* Memory streams used to store variable length file info data */ zip->file_info_stream = mz_stream_mem_create(); if (!zip->file_info_stream) return MZ_MEM_ERROR; mz_stream_mem_open(zip->file_info_stream, NULL, MZ_OPEN_MODE_CREATE); zip->local_file_info_stream = mz_stream_mem_create(); if (!zip->local_file_info_stream) { mz_stream_delete(&zip->file_info_stream); return MZ_MEM_ERROR; } mz_stream_mem_open(zip->local_file_info_stream, NULL, MZ_OPEN_MODE_CREATE); zip->open_mode = mode; return err; } int32_t mz_zip_close(void *handle) { mz_zip *zip = (mz_zip *)handle; int32_t err = MZ_OK; if (!zip) return MZ_PARAM_ERROR; mz_zip_print("Zip - Close\n"); if (mz_zip_entry_is_open(handle) == MZ_OK) err = mz_zip_entry_close(handle); if ((err == MZ_OK) && (zip->open_mode & MZ_OPEN_MODE_WRITE)) err = mz_zip_write_cd(handle); if (zip->cd_mem_stream) { mz_stream_close(zip->cd_mem_stream); mz_stream_delete(&zip->cd_mem_stream); } if (zip->file_info_stream) { mz_stream_mem_close(zip->file_info_stream); mz_stream_mem_delete(&zip->file_info_stream); } if (zip->local_file_info_stream) { mz_stream_mem_close(zip->local_file_info_stream); mz_stream_mem_delete(&zip->local_file_info_stream); } if (zip->comment) { free(zip->comment); zip->comment = NULL; } zip->stream = NULL; zip->cd_stream = NULL; return err; } int32_t mz_zip_get_comment(void *handle, const char **comment) { mz_zip *zip = (mz_zip *)handle; if (!zip || !comment) return MZ_PARAM_ERROR; if (!zip->comment) return MZ_EXIST_ERROR; *comment = zip->comment; return MZ_OK; } int32_t mz_zip_set_comment(void *handle, const char *comment) { mz_zip *zip = (mz_zip *)handle; int32_t comment_size = 0; if (!zip || !comment) return MZ_PARAM_ERROR; if (zip->comment) free(zip->comment); comment_size = (int32_t)strlen(comment); if (comment_size > UINT16_MAX) return MZ_PARAM_ERROR; zip->comment = (char *)calloc(comment_size + 1, sizeof(char)); if (!zip->comment) return MZ_MEM_ERROR; strncpy(zip->comment, comment, comment_size); return MZ_OK; } int32_t mz_zip_get_version_madeby(void *handle, uint16_t *version_madeby) { mz_zip *zip = (mz_zip *)handle; if (!zip || !version_madeby) return MZ_PARAM_ERROR; *version_madeby = zip->version_madeby; return MZ_OK; } int32_t mz_zip_set_version_madeby(void *handle, uint16_t version_madeby) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; zip->version_madeby = version_madeby; return MZ_OK; } int32_t mz_zip_set_recover(void *handle, uint8_t recover) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; zip->recover = recover; return MZ_OK; } int32_t mz_zip_set_data_descriptor(void *handle, uint8_t data_descriptor) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; zip->data_descriptor = data_descriptor; return MZ_OK; } int32_t mz_zip_get_stream(void *handle, void **stream) { mz_zip *zip = (mz_zip *)handle; if (!zip || !stream) return MZ_PARAM_ERROR; *stream = zip->stream; if (!*stream) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_zip_set_cd_stream(void *handle, int64_t cd_start_pos, void *cd_stream) { mz_zip *zip = (mz_zip *)handle; if (!zip || !cd_stream) return MZ_PARAM_ERROR; zip->cd_offset = 0; zip->cd_stream = cd_stream; zip->cd_start_pos = cd_start_pos; return MZ_OK; } int32_t mz_zip_get_cd_mem_stream(void *handle, void **cd_mem_stream) { mz_zip *zip = (mz_zip *)handle; if (!zip || !cd_mem_stream) return MZ_PARAM_ERROR; *cd_mem_stream = zip->cd_mem_stream; if (!*cd_mem_stream) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_zip_set_number_entry(void *handle, uint64_t number_entry) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; zip->number_entry = number_entry; return MZ_OK; } int32_t mz_zip_get_number_entry(void *handle, uint64_t *number_entry) { mz_zip *zip = (mz_zip *)handle; if (!zip || !number_entry) return MZ_PARAM_ERROR; *number_entry = zip->number_entry; return MZ_OK; } int32_t mz_zip_set_disk_number_with_cd(void *handle, uint32_t disk_number_with_cd) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; zip->disk_number_with_cd = disk_number_with_cd; return MZ_OK; } int32_t mz_zip_get_disk_number_with_cd(void *handle, uint32_t *disk_number_with_cd) { mz_zip *zip = (mz_zip *)handle; if (!zip || !disk_number_with_cd) return MZ_PARAM_ERROR; *disk_number_with_cd = zip->disk_number_with_cd; return MZ_OK; } static int32_t mz_zip_entry_close_int(void *handle) { mz_zip *zip = (mz_zip *)handle; if (zip->crypt_stream) mz_stream_delete(&zip->crypt_stream); zip->crypt_stream = NULL; if (zip->compress_stream) mz_stream_delete(&zip->compress_stream); zip->compress_stream = NULL; zip->entry_opened = 0; return MZ_OK; } static int32_t mz_zip_entry_open_int(void *handle, uint8_t raw, int16_t compress_level, const char *password) { mz_zip *zip = (mz_zip *)handle; int64_t max_total_in = 0; int64_t header_size = 0; int64_t footer_size = 0; int32_t err = MZ_OK; uint8_t use_crypt = 0; if (!zip) return MZ_PARAM_ERROR; switch (zip->file_info.compression_method) { case MZ_COMPRESS_METHOD_STORE: case MZ_COMPRESS_METHOD_DEFLATE: #ifdef HAVE_BZIP2 case MZ_COMPRESS_METHOD_BZIP2: #endif #ifdef HAVE_LZMA case MZ_COMPRESS_METHOD_LZMA: #endif #if defined(HAVE_LZMA) || defined(HAVE_LIBCOMP) case MZ_COMPRESS_METHOD_XZ: #endif #ifdef HAVE_ZSTD case MZ_COMPRESS_METHOD_ZSTD: #endif err = MZ_OK; break; default: return MZ_SUPPORT_ERROR; } #ifndef HAVE_WZAES if (zip->file_info.aes_version) return MZ_SUPPORT_ERROR; #endif zip->entry_raw = raw; if ((zip->file_info.flag & MZ_ZIP_FLAG_ENCRYPTED) && (password)) { if (zip->open_mode & MZ_OPEN_MODE_WRITE) { /* Encrypt only when we are not trying to write raw and password is supplied. */ if (!zip->entry_raw) use_crypt = 1; } else if (zip->open_mode & MZ_OPEN_MODE_READ) { /* Decrypt only when password is supplied. Don't error when password */ /* is not supplied as we may want to read the raw encrypted data. */ use_crypt = 1; } } if ((err == MZ_OK) && (use_crypt)) { #ifdef HAVE_WZAES if (zip->file_info.aes_version) { zip->crypt_stream = mz_stream_wzaes_create(); if (!zip->crypt_stream) return MZ_MEM_ERROR; mz_stream_wzaes_set_password(zip->crypt_stream, password); mz_stream_wzaes_set_strength(zip->crypt_stream, zip->file_info.aes_strength); } else #endif { #ifdef HAVE_PKCRYPT uint8_t verify1 = (uint8_t)((zip->file_info.pk_verify >> 8) & 0xff); uint8_t verify2 = (uint8_t)((zip->file_info.pk_verify) & 0xff); zip->crypt_stream = mz_stream_pkcrypt_create(); if (!zip->crypt_stream) return MZ_MEM_ERROR; mz_stream_pkcrypt_set_password(zip->crypt_stream, password); mz_stream_pkcrypt_set_verify(zip->crypt_stream, verify1, verify2); #endif } } if (err == MZ_OK) { if (!zip->crypt_stream) zip->crypt_stream = mz_stream_raw_create(); if (!zip->crypt_stream) return MZ_MEM_ERROR; mz_stream_set_base(zip->crypt_stream, zip->stream); err = mz_stream_open(zip->crypt_stream, NULL, zip->open_mode); } if (err == MZ_OK) { if (zip->entry_raw || zip->file_info.compression_method == MZ_COMPRESS_METHOD_STORE) zip->compress_stream = mz_stream_raw_create(); #ifdef HAVE_ZLIB else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_DEFLATE) zip->compress_stream = mz_stream_zlib_create(); #endif #ifdef HAVE_BZIP2 else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_BZIP2) zip->compress_stream = mz_stream_bzip_create(); #endif #ifdef HAVE_LIBCOMP else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_DEFLATE || zip->file_info.compression_method == MZ_COMPRESS_METHOD_XZ) { zip->compress_stream = mz_stream_libcomp_create(); if (zip->compress_stream) mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_COMPRESS_METHOD, zip->file_info.compression_method); } #endif #ifdef HAVE_LZMA else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_LZMA || zip->file_info.compression_method == MZ_COMPRESS_METHOD_XZ) { zip->compress_stream = mz_stream_lzma_create(); if (zip->compress_stream) mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_COMPRESS_METHOD, zip->file_info.compression_method); } #endif #ifdef HAVE_ZSTD else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_ZSTD) zip->compress_stream = mz_stream_zstd_create(); #endif else err = MZ_PARAM_ERROR; } if (err == MZ_OK && !zip->compress_stream) err = MZ_MEM_ERROR; if (err == MZ_OK) { if (zip->open_mode & MZ_OPEN_MODE_WRITE) { mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_COMPRESS_LEVEL, compress_level); } else { int32_t set_end_of_stream = 0; #ifndef HAVE_LIBCOMP if (zip->entry_raw || zip->file_info.compression_method == MZ_COMPRESS_METHOD_STORE || zip->file_info.flag & MZ_ZIP_FLAG_ENCRYPTED) #endif { max_total_in = zip->file_info.compressed_size; mz_stream_set_prop_int64(zip->crypt_stream, MZ_STREAM_PROP_TOTAL_IN_MAX, max_total_in); if (mz_stream_get_prop_int64(zip->crypt_stream, MZ_STREAM_PROP_HEADER_SIZE, &header_size) == MZ_OK) max_total_in -= header_size; if (mz_stream_get_prop_int64(zip->crypt_stream, MZ_STREAM_PROP_FOOTER_SIZE, &footer_size) == MZ_OK) max_total_in -= footer_size; mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_IN_MAX, max_total_in); } switch (zip->file_info.compression_method) { case MZ_COMPRESS_METHOD_LZMA: case MZ_COMPRESS_METHOD_XZ: set_end_of_stream = (zip->file_info.flag & MZ_ZIP_FLAG_LZMA_EOS_MARKER); break; case MZ_COMPRESS_METHOD_ZSTD: set_end_of_stream = 1; break; } if (set_end_of_stream) { mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_IN_MAX, zip->file_info.compressed_size); mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_OUT_MAX, zip->file_info.uncompressed_size); } } mz_stream_set_base(zip->compress_stream, zip->crypt_stream); err = mz_stream_open(zip->compress_stream, NULL, zip->open_mode); } if (err == MZ_OK) { zip->entry_opened = 1; zip->entry_crc32 = 0; } else { mz_zip_entry_close_int(handle); } return err; } int32_t mz_zip_entry_is_open(void *handle) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; if (zip->entry_opened == 0) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_zip_entry_read_open(void *handle, uint8_t raw, const char *password) { mz_zip *zip = (mz_zip *)handle; int32_t err = MZ_OK; int32_t err_shift = MZ_OK; #if defined(MZ_ZIP_NO_ENCRYPTION) if (password) return MZ_SUPPORT_ERROR; #endif if (!zip || !zip->entry_scanned) return MZ_PARAM_ERROR; if ((zip->open_mode & MZ_OPEN_MODE_READ) == 0) return MZ_PARAM_ERROR; mz_zip_print("Zip - Entry - Read open (raw %" PRId32 ")\n", raw); err = mz_zip_entry_seek_local_header(handle); if (err == MZ_OK) err = mz_zip_entry_read_header(zip->stream, 1, &zip->local_file_info, zip->local_file_info_stream); if (err == MZ_FORMAT_ERROR && zip->disk_offset_shift > 0) { /* Perhaps we didn't compensated correctly for incorrect cd offset */ err_shift = mz_stream_seek(zip->stream, zip->file_info.disk_offset, MZ_SEEK_SET); if (err_shift == MZ_OK) err_shift = mz_zip_entry_read_header(zip->stream, 1, &zip->local_file_info, zip->local_file_info_stream); if (err_shift == MZ_OK) { zip->disk_offset_shift = 0; err = err_shift; } } #ifdef MZ_ZIP_NO_DECOMPRESSION if (!raw && zip->file_info.compression_method != MZ_COMPRESS_METHOD_STORE) err = MZ_SUPPORT_ERROR; #endif if (err == MZ_OK) err = mz_zip_entry_open_int(handle, raw, 0, password); return err; } int32_t mz_zip_entry_write_open(void *handle, const mz_zip_file *file_info, int16_t compress_level, uint8_t raw, const char *password) { mz_zip *zip = (mz_zip *)handle; int64_t filename_pos = -1; int64_t extrafield_pos = 0; int64_t comment_pos = 0; int64_t linkname_pos = 0; int64_t disk_number = 0; uint8_t is_dir = 0; int32_t err = MZ_OK; #if defined(MZ_ZIP_NO_ENCRYPTION) if (password) return MZ_SUPPORT_ERROR; #endif if (!zip || !file_info || !file_info->filename) return MZ_PARAM_ERROR; if (mz_zip_entry_is_open(handle) == MZ_OK) { err = mz_zip_entry_close(handle); if (err != MZ_OK) return err; } memcpy(&zip->file_info, file_info, sizeof(mz_zip_file)); mz_zip_print("Zip - Entry - Write open - %s (level %" PRId16 " raw %" PRId8 ")\n", zip->file_info.filename, compress_level, raw); mz_stream_seek(zip->file_info_stream, 0, MZ_SEEK_SET); mz_stream_write(zip->file_info_stream, file_info, sizeof(mz_zip_file)); /* Copy filename, extrafield, and comment internally */ filename_pos = mz_stream_tell(zip->file_info_stream); if (file_info->filename) mz_stream_write(zip->file_info_stream, file_info->filename, (int32_t)strlen(file_info->filename)); mz_stream_write_uint8(zip->file_info_stream, 0); extrafield_pos = mz_stream_tell(zip->file_info_stream); if (file_info->extrafield) mz_stream_write(zip->file_info_stream, file_info->extrafield, file_info->extrafield_size); mz_stream_write_uint8(zip->file_info_stream, 0); comment_pos = mz_stream_tell(zip->file_info_stream); if (file_info->comment) mz_stream_write(zip->file_info_stream, file_info->comment, file_info->comment_size); mz_stream_write_uint8(zip->file_info_stream, 0); linkname_pos = mz_stream_tell(zip->file_info_stream); if (file_info->linkname) mz_stream_write(zip->file_info_stream, file_info->linkname, (int32_t)strlen(file_info->linkname)); mz_stream_write_uint8(zip->file_info_stream, 0); mz_stream_mem_get_buffer_at(zip->file_info_stream, filename_pos, (const void **)&zip->file_info.filename); mz_stream_mem_get_buffer_at(zip->file_info_stream, extrafield_pos, (const void **)&zip->file_info.extrafield); mz_stream_mem_get_buffer_at(zip->file_info_stream, comment_pos, (const void **)&zip->file_info.comment); mz_stream_mem_get_buffer_at(zip->file_info_stream, linkname_pos, (const void **)&zip->file_info.linkname); if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_DEFLATE) { if ((compress_level == 8) || (compress_level == 9)) zip->file_info.flag |= MZ_ZIP_FLAG_DEFLATE_MAX; if (compress_level == 2) zip->file_info.flag |= MZ_ZIP_FLAG_DEFLATE_FAST; if (compress_level == 1) zip->file_info.flag |= MZ_ZIP_FLAG_DEFLATE_SUPER_FAST; } #if defined(HAVE_LZMA) || defined(HAVE_LIBCOMP) else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_LZMA || zip->file_info.compression_method == MZ_COMPRESS_METHOD_XZ) zip->file_info.flag |= MZ_ZIP_FLAG_LZMA_EOS_MARKER; #endif if (mz_zip_attrib_is_dir(zip->file_info.external_fa, zip->file_info.version_madeby) == MZ_OK) is_dir = 1; if (!is_dir) { if (zip->data_descriptor) zip->file_info.flag |= MZ_ZIP_FLAG_DATA_DESCRIPTOR; if (password) zip->file_info.flag |= MZ_ZIP_FLAG_ENCRYPTED; } mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, &disk_number); zip->file_info.disk_number = (uint32_t)disk_number; zip->file_info.disk_offset = mz_stream_tell(zip->stream); if (zip->file_info.flag & MZ_ZIP_FLAG_ENCRYPTED) { #ifdef HAVE_PKCRYPT /* Pre-calculated CRC value is required for PKWARE traditional encryption */ uint32_t dos_date = mz_zip_time_t_to_dos_date(zip->file_info.modified_date); zip->file_info.pk_verify = mz_zip_get_pk_verify(dos_date, zip->file_info.crc, zip->file_info.flag); #endif #ifdef HAVE_WZAES if (zip->file_info.aes_version && zip->file_info.aes_strength == 0) zip->file_info.aes_strength = MZ_AES_STRENGTH_256; #endif } zip->file_info.crc = 0; zip->file_info.compressed_size = 0; if ((compress_level == 0) || (is_dir)) zip->file_info.compression_method = MZ_COMPRESS_METHOD_STORE; #ifdef MZ_ZIP_NO_COMPRESSION if (zip->file_info.compression_method != MZ_COMPRESS_METHOD_STORE) err = MZ_SUPPORT_ERROR; #endif if (err == MZ_OK) err = mz_zip_entry_write_header(zip->stream, 1, &zip->file_info); if (err == MZ_OK) err = mz_zip_entry_open_int(handle, raw, compress_level, password); return err; } int32_t mz_zip_entry_read(void *handle, void *buf, int32_t len) { mz_zip *zip = (mz_zip *)handle; int32_t read = 0; if (!zip || mz_zip_entry_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; if (UINT_MAX == UINT16_MAX && len > UINT16_MAX) /* zlib limitation */ return MZ_PARAM_ERROR; if (len == 0) return MZ_PARAM_ERROR; if (zip->file_info.compressed_size == 0) return 0; /* Read entire entry even if uncompressed_size = 0, otherwise */ /* aes encryption validation will fail if compressed_size > 0 */ read = mz_stream_read(zip->compress_stream, buf, len); if (read > 0) zip->entry_crc32 = mz_crypt_crc32_update(zip->entry_crc32, buf, read); mz_zip_print("Zip - Entry - Read - %" PRId32 " (max %" PRId32 ")\n", read, len); return read; } int32_t mz_zip_entry_write(void *handle, const void *buf, int32_t len) { mz_zip *zip = (mz_zip *)handle; int32_t written = 0; if (!zip || mz_zip_entry_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; written = mz_stream_write(zip->compress_stream, buf, len); if (written > 0) zip->entry_crc32 = mz_crypt_crc32_update(zip->entry_crc32, buf, written); mz_zip_print("Zip - Entry - Write - %" PRId32 " (max %" PRId32 ")\n", written, len); return written; } int32_t mz_zip_entry_read_close(void *handle, uint32_t *crc32, int64_t *compressed_size, int64_t *uncompressed_size) { mz_zip *zip = (mz_zip *)handle; int64_t total_in = 0; int32_t err = MZ_OK; uint8_t zip64 = 0; if (!zip || mz_zip_entry_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; mz_stream_close(zip->compress_stream); mz_zip_print("Zip - Entry - Read Close\n"); if (crc32) *crc32 = zip->file_info.crc; if (compressed_size) *compressed_size = zip->file_info.compressed_size; if (uncompressed_size) *uncompressed_size = zip->file_info.uncompressed_size; mz_stream_get_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_IN, &total_in); if ((zip->file_info.flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR) && ((zip->file_info.flag & MZ_ZIP_FLAG_MASK_LOCAL_INFO) == 0) && (crc32 || compressed_size || uncompressed_size)) { /* Check to see if data descriptor is zip64 bit format or not */ if (mz_zip_extrafield_contains(zip->local_file_info.extrafield, zip->local_file_info.extrafield_size, MZ_ZIP_EXTENSION_ZIP64, NULL) == MZ_OK) zip64 = 1; err = mz_zip_entry_seek_local_header(handle); /* Seek to end of compressed stream since we might have over-read during compression */ if (err == MZ_OK) err = mz_stream_seek(zip->stream, MZ_ZIP_SIZE_LD_ITEM + (int64_t)zip->local_file_info.filename_size + (int64_t)zip->local_file_info.extrafield_size + total_in, MZ_SEEK_CUR); /* Read data descriptor */ if (err == MZ_OK) err = mz_zip_entry_read_descriptor(zip->stream, zip64, crc32, compressed_size, uncompressed_size); } /* If entire entry was not read verification will fail */ if ((err == MZ_OK) && (total_in == zip->file_info.compressed_size) && (!zip->entry_raw)) { #ifdef HAVE_WZAES /* AES zip version AE-1 will expect a valid crc as well */ if (zip->file_info.aes_version <= 0x0001) #endif { if (zip->entry_crc32 != zip->file_info.crc) { mz_zip_print("Zip - Entry - Crc failed (actual 0x%08" PRIx32 " expected 0x%08" PRIx32 ")\n", zip->entry_crc32, zip->file_info.crc); err = MZ_CRC_ERROR; } } } mz_zip_entry_close_int(handle); return err; } int32_t mz_zip_entry_write_close(void *handle, uint32_t crc32, int64_t compressed_size, int64_t uncompressed_size) { mz_zip *zip = (mz_zip *)handle; int64_t end_disk_number = 0; int32_t err = MZ_OK; uint8_t zip64 = 0; if (!zip || mz_zip_entry_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; mz_stream_close(zip->compress_stream); if (!zip->entry_raw) crc32 = zip->entry_crc32; mz_zip_print("Zip - Entry - Write Close (crc 0x%08" PRIx32 " cs %" PRId64 " ucs %" PRId64 ")\n", crc32, compressed_size, uncompressed_size); /* If sizes are not set, then read them from the compression stream */ if (compressed_size < 0) mz_stream_get_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_OUT, &compressed_size); if (uncompressed_size < 0) mz_stream_get_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_IN, &uncompressed_size); if (zip->file_info.flag & MZ_ZIP_FLAG_ENCRYPTED) { mz_stream_set_base(zip->crypt_stream, zip->stream); err = mz_stream_close(zip->crypt_stream); mz_stream_get_prop_int64(zip->crypt_stream, MZ_STREAM_PROP_TOTAL_OUT, &compressed_size); } mz_zip_entry_needs_zip64(&zip->file_info, 1, &zip64); if ((err == MZ_OK) && (zip->file_info.flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR)) { /* Determine if we need to write data descriptor in zip64 format, if local extrafield was saved with zip64 extrafield */ if (zip->file_info.flag & MZ_ZIP_FLAG_MASK_LOCAL_INFO) err = mz_zip_entry_write_descriptor(zip->stream, zip64, 0, compressed_size, 0); else err = mz_zip_entry_write_descriptor(zip->stream, zip64, crc32, compressed_size, uncompressed_size); } /* Write file info to central directory */ mz_zip_print("Zip - Entry - Write cd (ucs %" PRId64 " cs %" PRId64 " crc 0x%08" PRIx32 ")\n", uncompressed_size, compressed_size, crc32); zip->file_info.crc = crc32; zip->file_info.compressed_size = compressed_size; zip->file_info.uncompressed_size = uncompressed_size; if (err == MZ_OK) err = mz_zip_entry_write_header(zip->cd_mem_stream, 0, &zip->file_info); /* Update local header with crc32 and sizes */ if ((err == MZ_OK) && ((zip->file_info.flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR) == 0) && ((zip->file_info.flag & MZ_ZIP_FLAG_MASK_LOCAL_INFO) == 0)) { /* Save the disk number and position we are to seek back after updating local header */ int64_t end_pos = mz_stream_tell(zip->stream); mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, &end_disk_number); err = mz_zip_entry_seek_local_header(handle); if (err == MZ_OK) { /* Seek to crc32 and sizes offset in local header */ mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, zip->file_info.disk_number); err = mz_stream_seek(zip->stream, zip->file_info.disk_offset + MZ_ZIP_OFFSET_CRC_SIZES, MZ_SEEK_SET); } if (err == MZ_OK) err = mz_zip_entry_write_crc_sizes(zip->stream, zip64, 0, &zip->file_info); /* Seek to and update zip64 extension sizes */ if ((err == MZ_OK) && (zip64)) { int64_t filename_size = zip->file_info.filename_size; if (filename_size == 0) filename_size = strlen(zip->file_info.filename); /* Since we write zip64 extension first we know its offset */ err = mz_stream_seek(zip->stream, 2 + 2 + filename_size + 4, MZ_SEEK_CUR); if (err == MZ_OK) err = mz_stream_write_uint64(zip->stream, zip->file_info.uncompressed_size); if (err == MZ_OK) err = mz_stream_write_uint64(zip->stream, zip->file_info.compressed_size); } mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, end_disk_number); mz_stream_seek(zip->stream, end_pos, MZ_SEEK_SET); } zip->number_entry += 1; mz_zip_entry_close_int(handle); return err; } int32_t mz_zip_entry_seek_local_header(void *handle) { mz_zip *zip = (mz_zip *)handle; int64_t disk_size = 0; uint32_t disk_number = zip->file_info.disk_number; if (disk_number == zip->disk_number_with_cd) { mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_SIZE, &disk_size); if ((disk_size == 0) || ((zip->open_mode & MZ_OPEN_MODE_WRITE) == 0)) disk_number = (uint32_t)-1; } mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, disk_number); mz_zip_print("Zip - Entry - Seek local (disk %" PRId32 " offset %" PRId64 ")\n", disk_number, zip->file_info.disk_offset); /* Guard against seek overflows */ if ((zip->disk_offset_shift > 0) && (zip->file_info.disk_offset > (INT64_MAX - zip->disk_offset_shift))) return MZ_FORMAT_ERROR; return mz_stream_seek(zip->stream, zip->file_info.disk_offset + zip->disk_offset_shift, MZ_SEEK_SET); } int32_t mz_zip_entry_get_compress_stream(void *handle, void **compress_stream) { mz_zip *zip = (mz_zip *)handle; if (!zip || !compress_stream) return MZ_PARAM_ERROR; *compress_stream = zip->compress_stream; if (!*compress_stream) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_zip_entry_close(void *handle) { return mz_zip_entry_close_raw(handle, UINT64_MAX, 0); } int32_t mz_zip_entry_close_raw(void *handle, int64_t uncompressed_size, uint32_t crc32) { mz_zip *zip = (mz_zip *)handle; int32_t err = MZ_OK; if (!zip || mz_zip_entry_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; if (zip->open_mode & MZ_OPEN_MODE_WRITE) err = mz_zip_entry_write_close(handle, crc32, UINT64_MAX, uncompressed_size); else err = mz_zip_entry_read_close(handle, NULL, NULL, NULL); return err; } int32_t mz_zip_entry_is_dir(void *handle) { mz_zip *zip = (mz_zip *)handle; int32_t filename_length = 0; if (!zip || !zip->entry_scanned) return MZ_PARAM_ERROR; if (mz_zip_attrib_is_dir(zip->file_info.external_fa, zip->file_info.version_madeby) == MZ_OK) return MZ_OK; filename_length = (int32_t)strlen(zip->file_info.filename); if (filename_length > 0) { if ((zip->file_info.filename[filename_length - 1] == '/') || (zip->file_info.filename[filename_length - 1] == '\\')) return MZ_OK; } return MZ_EXIST_ERROR; } int32_t mz_zip_entry_is_symlink(void *handle) { mz_zip *zip = (mz_zip *)handle; if (!zip || !zip->entry_scanned) return MZ_PARAM_ERROR; if (mz_zip_attrib_is_symlink(zip->file_info.external_fa, zip->file_info.version_madeby) != MZ_OK) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_zip_entry_get_info(void *handle, mz_zip_file **file_info) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; if ((zip->open_mode & MZ_OPEN_MODE_WRITE) == 0) { if (!zip->entry_scanned) return MZ_PARAM_ERROR; } *file_info = &zip->file_info; return MZ_OK; } int32_t mz_zip_entry_get_local_info(void *handle, mz_zip_file **local_file_info) { mz_zip *zip = (mz_zip *)handle; if (!zip || mz_zip_entry_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; *local_file_info = &zip->local_file_info; return MZ_OK; } int32_t mz_zip_entry_set_extrafield(void *handle, const uint8_t *extrafield, uint16_t extrafield_size) { mz_zip *zip = (mz_zip *)handle; if (!zip || mz_zip_entry_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; zip->file_info.extrafield = extrafield; zip->file_info.extrafield_size = extrafield_size; return MZ_OK; } static int32_t mz_zip_goto_next_entry_int(void *handle) { mz_zip *zip = (mz_zip *)handle; int32_t err = MZ_OK; if (!zip) return MZ_PARAM_ERROR; zip->entry_scanned = 0; mz_stream_set_prop_int64(zip->cd_stream, MZ_STREAM_PROP_DISK_NUMBER, -1); err = mz_stream_seek(zip->cd_stream, zip->cd_current_pos, MZ_SEEK_SET); if (err == MZ_OK) err = mz_zip_entry_read_header(zip->cd_stream, 0, &zip->file_info, zip->file_info_stream); if (err == MZ_OK) zip->entry_scanned = 1; return err; } int64_t mz_zip_get_entry(void *handle) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; return zip->cd_current_pos; } int32_t mz_zip_goto_entry(void *handle, int64_t cd_pos) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; if (cd_pos < zip->cd_start_pos || cd_pos > zip->cd_start_pos + zip->cd_size) return MZ_PARAM_ERROR; zip->cd_current_pos = cd_pos; return mz_zip_goto_next_entry_int(handle); } int32_t mz_zip_goto_first_entry(void *handle) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; zip->cd_current_pos = zip->cd_start_pos; return mz_zip_goto_next_entry_int(handle); } int32_t mz_zip_goto_next_entry(void *handle) { mz_zip *zip = (mz_zip *)handle; if (!zip) return MZ_PARAM_ERROR; zip->cd_current_pos += (int64_t)MZ_ZIP_SIZE_CD_ITEM + zip->file_info.filename_size + zip->file_info.extrafield_size + zip->file_info.comment_size; return mz_zip_goto_next_entry_int(handle); } int32_t mz_zip_locate_entry(void *handle, const char *filename, uint8_t ignore_case) { mz_zip *zip = (mz_zip *)handle; int32_t err = MZ_OK; int32_t result = 0; if (!zip || !filename) return MZ_PARAM_ERROR; /* If we are already on the current entry, no need to search */ if (zip->entry_scanned && zip->file_info.filename) { result = mz_zip_path_compare(zip->file_info.filename, filename, ignore_case); if (result == 0) return MZ_OK; } /* Search all entries starting at the first */ err = mz_zip_goto_first_entry(handle); while (err == MZ_OK) { result = mz_zip_path_compare(zip->file_info.filename, filename, ignore_case); if (result == 0) return MZ_OK; err = mz_zip_goto_next_entry(handle); } return err; } int32_t mz_zip_locate_first_entry(void *handle, void *userdata, mz_zip_locate_entry_cb cb) { mz_zip *zip = (mz_zip *)handle; int32_t err = MZ_OK; int32_t result = 0; /* Search first entry looking for match */ err = mz_zip_goto_first_entry(handle); if (err != MZ_OK) return err; result = cb(handle, userdata, &zip->file_info); if (result == 0) return MZ_OK; return mz_zip_locate_next_entry(handle, userdata, cb); } int32_t mz_zip_locate_next_entry(void *handle, void *userdata, mz_zip_locate_entry_cb cb) { mz_zip *zip = (mz_zip *)handle; int32_t err = MZ_OK; int32_t result = 0; /* Search next entries looking for match */ err = mz_zip_goto_next_entry(handle); while (err == MZ_OK) { result = cb(handle, userdata, &zip->file_info); if (result == 0) return MZ_OK; err = mz_zip_goto_next_entry(handle); } return err; } /***************************************************************************/ int32_t mz_zip_attrib_is_dir(uint32_t attrib, int32_t version_madeby) { uint32_t posix_attrib = 0; uint8_t system = MZ_HOST_SYSTEM(version_madeby); int32_t err = MZ_OK; err = mz_zip_attrib_convert(system, attrib, MZ_HOST_SYSTEM_UNIX, &posix_attrib); if (err == MZ_OK) { if ((posix_attrib & 0170000) == 0040000) /* S_ISDIR */ return MZ_OK; } return MZ_EXIST_ERROR; } int32_t mz_zip_attrib_is_symlink(uint32_t attrib, int32_t version_madeby) { uint32_t posix_attrib = 0; uint8_t system = MZ_HOST_SYSTEM(version_madeby); int32_t err = MZ_OK; err = mz_zip_attrib_convert(system, attrib, MZ_HOST_SYSTEM_UNIX, &posix_attrib); if (err == MZ_OK) { if ((posix_attrib & 0170000) == 0120000) /* S_ISLNK */ return MZ_OK; } return MZ_EXIST_ERROR; } int32_t mz_zip_attrib_convert(uint8_t src_sys, uint32_t src_attrib, uint8_t target_sys, uint32_t *target_attrib) { if (!target_attrib) return MZ_PARAM_ERROR; *target_attrib = 0; if ((src_sys == MZ_HOST_SYSTEM_MSDOS) || (src_sys == MZ_HOST_SYSTEM_WINDOWS_NTFS)) { if ((target_sys == MZ_HOST_SYSTEM_MSDOS) || (target_sys == MZ_HOST_SYSTEM_WINDOWS_NTFS)) { *target_attrib = src_attrib; return MZ_OK; } if ((target_sys == MZ_HOST_SYSTEM_UNIX) || (target_sys == MZ_HOST_SYSTEM_OSX_DARWIN) || (target_sys == MZ_HOST_SYSTEM_RISCOS)) return mz_zip_attrib_win32_to_posix(src_attrib, target_attrib); } else if ((src_sys == MZ_HOST_SYSTEM_UNIX) || (src_sys == MZ_HOST_SYSTEM_OSX_DARWIN) || (src_sys == MZ_HOST_SYSTEM_RISCOS)) { /* If high bytes are set, it contains unix specific attributes */ if ((src_attrib >> 16) != 0) src_attrib >>= 16; if ((target_sys == MZ_HOST_SYSTEM_UNIX) || (target_sys == MZ_HOST_SYSTEM_OSX_DARWIN) || (target_sys == MZ_HOST_SYSTEM_RISCOS)) { *target_attrib = src_attrib; return MZ_OK; } if ((target_sys == MZ_HOST_SYSTEM_MSDOS) || (target_sys == MZ_HOST_SYSTEM_WINDOWS_NTFS)) return mz_zip_attrib_posix_to_win32(src_attrib, target_attrib); } return MZ_SUPPORT_ERROR; } int32_t mz_zip_attrib_posix_to_win32(uint32_t posix_attrib, uint32_t *win32_attrib) { if (!win32_attrib) return MZ_PARAM_ERROR; *win32_attrib = 0; /* S_IWUSR | S_IWGRP | S_IWOTH | S_IXUSR | S_IXGRP | S_IXOTH */ if ((posix_attrib & 0000333) == 0 && (posix_attrib & 0000444) != 0) *win32_attrib |= 0x01; /* FILE_ATTRIBUTE_READONLY */ /* S_IFLNK */ if ((posix_attrib & 0170000) == 0120000) *win32_attrib |= 0x400; /* FILE_ATTRIBUTE_REPARSE_POINT */ /* S_IFDIR */ else if ((posix_attrib & 0170000) == 0040000) *win32_attrib |= 0x10; /* FILE_ATTRIBUTE_DIRECTORY */ /* S_IFREG */ else *win32_attrib |= 0x80; /* FILE_ATTRIBUTE_NORMAL */ return MZ_OK; } int32_t mz_zip_attrib_win32_to_posix(uint32_t win32_attrib, uint32_t *posix_attrib) { if (!posix_attrib) return MZ_PARAM_ERROR; *posix_attrib = 0000444; /* S_IRUSR | S_IRGRP | S_IROTH */ /* FILE_ATTRIBUTE_READONLY */ if ((win32_attrib & 0x01) == 0) *posix_attrib |= 0000222; /* S_IWUSR | S_IWGRP | S_IWOTH */ /* FILE_ATTRIBUTE_REPARSE_POINT */ if ((win32_attrib & 0x400) == 0x400) *posix_attrib |= 0120000; /* S_IFLNK */ /* FILE_ATTRIBUTE_DIRECTORY */ else if ((win32_attrib & 0x10) == 0x10) *posix_attrib |= 0040111; /* S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH */ else *posix_attrib |= 0100000; /* S_IFREG */ return MZ_OK; } /***************************************************************************/ int32_t mz_zip_extrafield_find(void *stream, uint16_t type, int32_t max_seek, uint16_t *length) { int32_t err = MZ_OK; uint16_t field_type = 0; uint16_t field_length = 0; if (max_seek < 4) return MZ_EXIST_ERROR; do { err = mz_stream_read_uint16(stream, &field_type); if (err == MZ_OK) err = mz_stream_read_uint16(stream, &field_length); if (err != MZ_OK) break; if (type == field_type) { if (length) *length = field_length; return MZ_OK; } max_seek -= field_length - 4; if (max_seek < 0) return MZ_EXIST_ERROR; err = mz_stream_seek(stream, field_length, MZ_SEEK_CUR); } while (err == MZ_OK); return MZ_EXIST_ERROR; } int32_t mz_zip_extrafield_contains(const uint8_t *extrafield, int32_t extrafield_size, uint16_t type, uint16_t *length) { void *file_extra_stream = NULL; int32_t err = MZ_OK; if (!extrafield || !extrafield_size) return MZ_PARAM_ERROR; file_extra_stream = mz_stream_mem_create(); if (!file_extra_stream) return MZ_MEM_ERROR; mz_stream_mem_set_buffer(file_extra_stream, (void *)extrafield, extrafield_size); err = mz_zip_extrafield_find(file_extra_stream, type, extrafield_size, length); mz_stream_mem_delete(&file_extra_stream); return err; } int32_t mz_zip_extrafield_read(void *stream, uint16_t *type, uint16_t *length) { int32_t err = MZ_OK; if (!type || !length) return MZ_PARAM_ERROR; err = mz_stream_read_uint16(stream, type); if (err == MZ_OK) err = mz_stream_read_uint16(stream, length); return err; } int32_t mz_zip_extrafield_write(void *stream, uint16_t type, uint16_t length) { int32_t err = MZ_OK; err = mz_stream_write_uint16(stream, type); if (err == MZ_OK) err = mz_stream_write_uint16(stream, length); return err; } /***************************************************************************/ static int32_t mz_zip_invalid_date(const struct tm *ptm) { #define datevalue_in_range(min, max, value) ((min) <= (value) && (value) <= (max)) return (!datevalue_in_range(0, 127 + 80, ptm->tm_year) || /* 1980-based year, allow 80 extra */ !datevalue_in_range(0, 11, ptm->tm_mon) || !datevalue_in_range(1, 31, ptm->tm_mday) || !datevalue_in_range(0, 23, ptm->tm_hour) || !datevalue_in_range(0, 59, ptm->tm_min) || !datevalue_in_range(0, 59, ptm->tm_sec)); #undef datevalue_in_range } static void mz_zip_dosdate_to_raw_tm(uint64_t dos_date, struct tm *ptm) { uint64_t date = (uint64_t)(dos_date >> 16); ptm->tm_mday = (uint16_t)(date & 0x1f); ptm->tm_mon = (uint16_t)(((date & 0x1E0) / 0x20) - 1); ptm->tm_year = (uint16_t)(((date & 0x0FE00) / 0x0200) + 80); ptm->tm_hour = (uint16_t)((dos_date & 0xF800) / 0x800); ptm->tm_min = (uint16_t)((dos_date & 0x7E0) / 0x20); ptm->tm_sec = (uint16_t)(2 * (dos_date & 0x1f)); ptm->tm_isdst = -1; } int32_t mz_zip_dosdate_to_tm(uint64_t dos_date, struct tm *ptm) { if (!ptm) return MZ_PARAM_ERROR; mz_zip_dosdate_to_raw_tm(dos_date, ptm); if (mz_zip_invalid_date(ptm)) { /* Invalid date stored, so don't return it */ memset(ptm, 0, sizeof(struct tm)); return MZ_FORMAT_ERROR; } return MZ_OK; } time_t mz_zip_dosdate_to_time_t(uint64_t dos_date) { struct tm ptm; mz_zip_dosdate_to_raw_tm(dos_date, &ptm); return mktime(&ptm); } int32_t mz_zip_time_t_to_tm(time_t unix_time, struct tm *ptm) { struct tm ltm; if (!ptm) return MZ_PARAM_ERROR; if (!localtime_r(&unix_time, <m)) { /* Returns a 1900-based year */ /* Invalid date stored, so don't return it */ memset(ptm, 0, sizeof(struct tm)); return MZ_INTERNAL_ERROR; } memcpy(ptm, <m, sizeof(struct tm)); return MZ_OK; } uint32_t mz_zip_time_t_to_dos_date(time_t unix_time) { struct tm ptm; mz_zip_time_t_to_tm(unix_time, &ptm); return mz_zip_tm_to_dosdate((const struct tm *)&ptm); } uint32_t mz_zip_tm_to_dosdate(const struct tm *ptm) { struct tm fixed_tm; /* Years supported: */ /* [00, 79] (assumed to be between 2000 and 2079) */ /* [80, 207] (assumed to be between 1980 and 2107, typical output of old */ /* software that does 'year-1900' to get a double digit year) */ /* [1980, 2107] (due to format limitations, only years 1980-2107 can be stored.) */ memcpy(&fixed_tm, ptm, sizeof(struct tm)); if (fixed_tm.tm_year >= 1980) /* range [1980, 2107] */ fixed_tm.tm_year -= 1980; else if (fixed_tm.tm_year >= 80) /* range [80, 207] */ fixed_tm.tm_year -= 80; else /* range [00, 79] */ fixed_tm.tm_year += 20; if (mz_zip_invalid_date(&fixed_tm)) return 0; return (((uint32_t)fixed_tm.tm_mday + (32 * ((uint32_t)fixed_tm.tm_mon + 1)) + (512 * (uint32_t)fixed_tm.tm_year)) << 16) | (((uint32_t)fixed_tm.tm_sec / 2) + (32 * (uint32_t)fixed_tm.tm_min) + (2048 * (uint32_t)fixed_tm.tm_hour)); } int32_t mz_zip_ntfs_to_unix_time(uint64_t ntfs_time, time_t *unix_time) { *unix_time = (time_t)((ntfs_time - 116444736000000000LL) / 10000000); return MZ_OK; } int32_t mz_zip_unix_to_ntfs_time(time_t unix_time, uint64_t *ntfs_time) { *ntfs_time = ((uint64_t)unix_time * 10000000) + 116444736000000000LL; return MZ_OK; } /***************************************************************************/ int32_t mz_zip_path_compare(const char *path1, const char *path2, uint8_t ignore_case) { do { if ((*path1 == '\\' && *path2 == '/') || (*path2 == '\\' && *path1 == '/')) { /* Ignore comparison of path slashes */ } else if (ignore_case) { if (tolower(*path1) != tolower(*path2)) break; } else if (*path1 != *path2) { break; } path1 += 1; path2 += 1; } while (*path1 != 0 && *path2 != 0); if (ignore_case) return (int32_t)(tolower(*path1) - tolower(*path2)); return (int32_t)(*path1 - *path2); } /***************************************************************************/ const char* mz_zip_get_compression_method_string(int32_t compression_method) { const char *method = "?"; switch (compression_method) { case MZ_COMPRESS_METHOD_STORE: method = "stored"; break; case MZ_COMPRESS_METHOD_DEFLATE: method = "deflate"; break; case MZ_COMPRESS_METHOD_BZIP2: method = "bzip2"; break; case MZ_COMPRESS_METHOD_LZMA: method = "lzma"; break; case MZ_COMPRESS_METHOD_XZ: method = "xz"; break; case MZ_COMPRESS_METHOD_ZSTD: method = "zstd"; break; } return method; } /***************************************************************************/ sight-25.1.0/3rd-party/minizip/mz_zip.h000066400000000000000000000253271503402212300177030ustar00rootroot00000000000000/* mz_zip.h -- Zip manipulation part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng Copyright (C) 2009-2010 Mathias Svensson Modifications for Zip64 support http://result42.com Copyright (C) 1998-2010 Gilles Vollant https://www.winimage.com/zLibDll/minizip.html This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_ZIP_H #define MZ_ZIP_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ typedef struct mz_zip_file_s { uint16_t version_madeby; /* version made by */ uint16_t version_needed; /* version needed to extract */ uint16_t flag; /* general purpose bit flag */ uint16_t compression_method; /* compression method */ time_t modified_date; /* last modified date in unix time */ time_t accessed_date; /* last accessed date in unix time */ time_t creation_date; /* creation date in unix time */ uint32_t crc; /* crc-32 */ int64_t compressed_size; /* compressed size */ int64_t uncompressed_size; /* uncompressed size */ uint16_t filename_size; /* filename length */ uint16_t extrafield_size; /* extra field length */ uint16_t comment_size; /* file comment length */ uint32_t disk_number; /* disk number start */ int64_t disk_offset; /* relative offset of local header */ uint16_t internal_fa; /* internal file attributes */ uint32_t external_fa; /* external file attributes */ const char *filename; /* filename utf8 null-terminated string */ const uint8_t *extrafield; /* extrafield data */ const char *comment; /* comment utf8 null-terminated string */ const char *linkname; /* sym-link filename utf8 null-terminated string */ uint16_t zip64; /* zip64 extension mode */ uint16_t aes_version; /* winzip aes extension if not 0 */ uint8_t aes_strength; /* winzip aes encryption strength */ uint16_t pk_verify; /* pkware encryption verifier */ } mz_zip_file, mz_zip_entry; /***************************************************************************/ typedef int32_t (*mz_zip_locate_entry_cb)(void *handle, void *userdata, mz_zip_file *file_info); /***************************************************************************/ void * mz_zip_create(void); /* Create zip instance for opening */ void mz_zip_delete(void **handle); /* Delete zip object */ int32_t mz_zip_open(void *handle, void *stream, int32_t mode); /* Create a zip file, no delete file in zip functionality */ int32_t mz_zip_close(void *handle); /* Close the zip file */ int32_t mz_zip_get_comment(void *handle, const char **comment); /* Get a pointer to the global comment */ int32_t mz_zip_set_comment(void *handle, const char *comment); /* Sets the global comment used for writing zip file */ int32_t mz_zip_get_version_madeby(void *handle, uint16_t *version_madeby); /* Get the version made by */ int32_t mz_zip_set_version_madeby(void *handle, uint16_t version_madeby); /* Sets the version made by used for writing zip file */ int32_t mz_zip_set_recover(void *handle, uint8_t recover); /* Sets the ability to recover the central dir by reading local file headers */ int32_t mz_zip_set_data_descriptor(void *handle, uint8_t data_descriptor); /* Sets the use of data descriptor flag when writing zip entries */ int32_t mz_zip_get_stream(void *handle, void **stream); /* Get a pointer to the stream used to open */ int32_t mz_zip_set_cd_stream(void *handle, int64_t cd_start_pos, void *cd_stream); /* Sets the stream to use for reading the central dir */ int32_t mz_zip_get_cd_mem_stream(void *handle, void **cd_mem_stream); /* Get a pointer to the stream used to store the central dir in memory */ int32_t mz_zip_set_number_entry(void *handle, uint64_t number_entry); /* Sets the total number of entries */ int32_t mz_zip_get_number_entry(void *handle, uint64_t *number_entry); /* Get the total number of entries */ int32_t mz_zip_set_disk_number_with_cd(void *handle, uint32_t disk_number_with_cd); /* Sets the disk number containing the central directory record */ int32_t mz_zip_get_disk_number_with_cd(void *handle, uint32_t *disk_number_with_cd); /* Get the disk number containing the central directory record */ /***************************************************************************/ int32_t mz_zip_entry_is_open(void *handle); /* Check to see if entry is open for read/write */ int32_t mz_zip_entry_read_open(void *handle, uint8_t raw, const char *password); /* Open for reading the current file in the zip file */ int32_t mz_zip_entry_read(void *handle, void *buf, int32_t len); /* Read bytes from the current file in the zip file */ int32_t mz_zip_entry_read_close(void *handle, uint32_t *crc32, int64_t *compressed_size, int64_t *uncompressed_size); /* Close the current file for reading and get data descriptor values */ int32_t mz_zip_entry_write_open(void *handle, const mz_zip_file *file_info, int16_t compress_level, uint8_t raw, const char *password); /* Open for writing the current file in the zip file */ int32_t mz_zip_entry_write(void *handle, const void *buf, int32_t len); /* Write bytes from the current file in the zip file */ int32_t mz_zip_entry_write_close(void *handle, uint32_t crc32, int64_t compressed_size, int64_t uncompressed_size); /* Close the current file for writing and set data descriptor values */ int32_t mz_zip_entry_seek_local_header(void *handle); /* Seeks to the local header for the entry */ int32_t mz_zip_entry_get_compress_stream(void *handle, void **compress_stream); /* Get a pointer to the compression stream used for the current entry */ int32_t mz_zip_entry_close_raw(void *handle, int64_t uncompressed_size, uint32_t crc32); /* Close the current file in the zip file where raw is compressed data */ int32_t mz_zip_entry_close(void *handle); /* Close the current file in the zip file */ /***************************************************************************/ int32_t mz_zip_entry_is_dir(void *handle); /* Checks to see if the entry is a directory */ int32_t mz_zip_entry_is_symlink(void *handle); /* Checks to see if the entry is a symbolic link */ int32_t mz_zip_entry_get_info(void *handle, mz_zip_file **file_info); /* Get info about the current file, only valid while current entry is open */ int32_t mz_zip_entry_get_local_info(void *handle, mz_zip_file **local_file_info); /* Get local info about the current file, only valid while current entry is being read */ int32_t mz_zip_entry_set_extrafield(void *handle, const uint8_t *extrafield, uint16_t extrafield_size); /* Sets or updates the extra field for the entry to be used before writing cd */ int64_t mz_zip_get_entry(void *handle); /* Return offset of the current entry in the zip file */ int32_t mz_zip_goto_entry(void *handle, int64_t cd_pos); /* Go to specified entry in the zip file */ int32_t mz_zip_goto_first_entry(void *handle); /* Go to the first entry in the zip file */ int32_t mz_zip_goto_next_entry(void *handle); /* Go to the next entry in the zip file or MZ_END_OF_LIST if reaching the end */ int32_t mz_zip_locate_entry(void *handle, const char *filename, uint8_t ignore_case); /* Locate the file with the specified name in the zip file or MZ_END_LIST if not found */ int32_t mz_zip_locate_first_entry(void *handle, void *userdata, mz_zip_locate_entry_cb cb); /* Locate the first matching entry based on a match callback */ int32_t mz_zip_locate_next_entry(void *handle, void *userdata, mz_zip_locate_entry_cb cb); /* Locate the next matching entry based on a match callback */ /***************************************************************************/ int32_t mz_zip_attrib_is_dir(uint32_t attrib, int32_t version_madeby); /* Checks to see if the attribute is a directory based on platform */ int32_t mz_zip_attrib_is_symlink(uint32_t attrib, int32_t version_madeby); /* Checks to see if the attribute is a symbolic link based on platform */ int32_t mz_zip_attrib_convert(uint8_t src_sys, uint32_t src_attrib, uint8_t target_sys, uint32_t *target_attrib); /* Converts file attributes from one host system to another */ int32_t mz_zip_attrib_posix_to_win32(uint32_t posix_attrib, uint32_t *win32_attrib); /* Converts posix file attributes to win32 file attributes */ int32_t mz_zip_attrib_win32_to_posix(uint32_t win32_attrib, uint32_t *posix_attrib); /* Converts win32 file attributes to posix file attributes */ /***************************************************************************/ int32_t mz_zip_extrafield_find(void *stream, uint16_t type, int32_t max_seek, uint16_t *length); /* Seeks to extra field by its type and returns its length */ int32_t mz_zip_extrafield_contains(const uint8_t *extrafield, int32_t extrafield_size, uint16_t type, uint16_t *length); /* Gets whether an extrafield exists and its size */ int32_t mz_zip_extrafield_read(void *stream, uint16_t *type, uint16_t *length); /* Reads an extrafield header from a stream */ int32_t mz_zip_extrafield_write(void *stream, uint16_t type, uint16_t length); /* Writes an extrafield header to a stream */ /***************************************************************************/ int32_t mz_zip_dosdate_to_tm(uint64_t dos_date, struct tm *ptm); /* Convert dos date/time format to struct tm */ time_t mz_zip_dosdate_to_time_t(uint64_t dos_date); /* Convert dos date/time format to time_t */ int32_t mz_zip_time_t_to_tm(time_t unix_time, struct tm *ptm); /* Convert time_t to time struct */ uint32_t mz_zip_time_t_to_dos_date(time_t unix_time); /* Convert time_t to dos date/time format */ uint32_t mz_zip_tm_to_dosdate(const struct tm *ptm); /* Convert struct tm to dos date/time format */ int32_t mz_zip_ntfs_to_unix_time(uint64_t ntfs_time, time_t *unix_time); /* Convert ntfs time to unix time */ int32_t mz_zip_unix_to_ntfs_time(time_t unix_time, uint64_t *ntfs_time); /* Convert unix time to ntfs time */ /***************************************************************************/ int32_t mz_zip_path_compare(const char *path1, const char *path2, uint8_t ignore_case); /* Compare two paths without regard to slashes */ /***************************************************************************/ const char* mz_zip_get_compression_method_string(int32_t compression_method); /* Gets a string representing the compression method */ /***************************************************************************/ #ifdef __cplusplus } #endif #endif /* _ZIP_H */ sight-25.1.0/3rd-party/minizip/mz_zip_rw.c000066400000000000000000001723301503402212300204030ustar00rootroot00000000000000/* mz_zip_rw.c -- Zip reader/writer part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_crypt.h" #include "mz_os.h" #include "mz_strm.h" #include "mz_strm_buf.h" #include "mz_strm_mem.h" #include "mz_strm_os.h" #include "mz_strm_split.h" #include "mz_strm_wzaes.h" #include "mz_zip.h" #include "mz_zip_rw.h" /***************************************************************************/ #define MZ_DEFAULT_PROGRESS_INTERVAL (1000u) #define MZ_ZIP_CD_FILENAME ("__cdcd__") /***************************************************************************/ typedef struct mz_zip_reader_s { void *zip_handle; void *file_stream; void *buffered_stream; void *split_stream; void *mem_stream; void *hash; uint16_t hash_algorithm; uint16_t hash_digest_size; mz_zip_file *file_info; const char *pattern; uint8_t pattern_ignore_case; const char *password; void *overwrite_userdata; mz_zip_reader_overwrite_cb overwrite_cb; void *password_userdata; mz_zip_reader_password_cb password_cb; void *progress_userdata; mz_zip_reader_progress_cb progress_cb; uint32_t progress_cb_interval_ms; void *entry_userdata; mz_zip_reader_entry_cb entry_cb; uint8_t raw; uint8_t buffer[UINT16_MAX]; int32_t encoding; uint8_t sign_required; uint8_t cd_verified; uint8_t cd_zipped; uint8_t entry_verified; uint8_t recover; } mz_zip_reader; /***************************************************************************/ int32_t mz_zip_reader_is_open(void *handle) { mz_zip_reader *reader = (mz_zip_reader *)handle; if (!reader) return MZ_PARAM_ERROR; if (!reader->zip_handle) return MZ_PARAM_ERROR; return MZ_OK; } int32_t mz_zip_reader_open(void *handle, void *stream) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; reader->cd_verified = 0; reader->cd_zipped = 0; reader->zip_handle = mz_zip_create(); if (!reader->zip_handle) return MZ_MEM_ERROR; mz_zip_set_recover(reader->zip_handle, reader->recover); err = mz_zip_open(reader->zip_handle, stream, MZ_OPEN_MODE_READ); if (err != MZ_OK) { mz_zip_reader_close(handle); return err; } mz_zip_reader_unzip_cd(reader); return MZ_OK; } int32_t mz_zip_reader_open_file(void *handle, const char *path) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; mz_zip_reader_close(handle); reader->file_stream = mz_stream_os_create(); if (!reader->file_stream) return MZ_MEM_ERROR; reader->buffered_stream = mz_stream_buffered_create(); if (!reader->buffered_stream) { mz_stream_os_delete(&reader->file_stream); return MZ_MEM_ERROR; } reader->split_stream = mz_stream_split_create(); if (!reader->split_stream) { mz_stream_os_delete(&reader->file_stream); mz_stream_buffered_delete(&reader->buffered_stream); return MZ_MEM_ERROR; } mz_stream_set_base(reader->buffered_stream, reader->file_stream); mz_stream_set_base(reader->split_stream, reader->buffered_stream); err = mz_stream_open(reader->split_stream, path, MZ_OPEN_MODE_READ); if (err == MZ_OK) err = mz_zip_reader_open(handle, reader->split_stream); return err; } int32_t mz_zip_reader_open_file_in_memory(void *handle, const char *path) { mz_zip_reader *reader = (mz_zip_reader *)handle; void *file_stream = NULL; int64_t file_size = 0; int32_t err = 0; mz_zip_reader_close(handle); file_stream = mz_stream_os_create(); if (!file_stream) return MZ_MEM_ERROR; err = mz_stream_os_open(file_stream, path, MZ_OPEN_MODE_READ); if (err != MZ_OK) { mz_stream_os_delete(&file_stream); mz_zip_reader_close(handle); return err; } mz_stream_os_seek(file_stream, 0, MZ_SEEK_END); file_size = mz_stream_os_tell(file_stream); mz_stream_os_seek(file_stream, 0, MZ_SEEK_SET); reader->mem_stream = mz_stream_mem_create(); if ((file_size <= 0) || (file_size > UINT32_MAX) || (!reader->mem_stream)) { /* Memory size is too large or too small */ mz_stream_os_close(file_stream); mz_stream_os_delete(&file_stream); mz_zip_reader_close(handle); return MZ_MEM_ERROR; } mz_stream_mem_set_grow_size(reader->mem_stream, (int32_t)file_size); mz_stream_mem_open(reader->mem_stream, NULL, MZ_OPEN_MODE_CREATE); err = mz_stream_copy(reader->mem_stream, file_stream, (int32_t)file_size); mz_stream_os_close(file_stream); mz_stream_os_delete(&file_stream); if (err == MZ_OK) err = mz_zip_reader_open(handle, reader->mem_stream); if (err != MZ_OK) mz_zip_reader_close(handle); return err; } int32_t mz_zip_reader_open_buffer(void *handle, uint8_t *buf, int32_t len, uint8_t copy) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; mz_zip_reader_close(handle); reader->mem_stream = mz_stream_mem_create(); if (!reader->mem_stream) return MZ_MEM_ERROR; if (copy) { mz_stream_mem_set_grow_size(reader->mem_stream, len); mz_stream_mem_open(reader->mem_stream, NULL, MZ_OPEN_MODE_CREATE); mz_stream_mem_write(reader->mem_stream, buf, len); mz_stream_mem_seek(reader->mem_stream, 0, MZ_SEEK_SET); } else { mz_stream_mem_open(reader->mem_stream, NULL, MZ_OPEN_MODE_READ); mz_stream_mem_set_buffer(reader->mem_stream, buf, len); } if (err == MZ_OK) err = mz_zip_reader_open(handle, reader->mem_stream); return err; } int32_t mz_zip_reader_close(void *handle) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; if (reader->zip_handle) { err = mz_zip_close(reader->zip_handle); mz_zip_delete(&reader->zip_handle); } if (reader->split_stream) { mz_stream_split_close(reader->split_stream); mz_stream_split_delete(&reader->split_stream); } if (reader->buffered_stream) mz_stream_buffered_delete(&reader->buffered_stream); if (reader->file_stream) mz_stream_os_delete(&reader->file_stream); if (reader->mem_stream) { mz_stream_close(reader->mem_stream); mz_stream_delete(&reader->mem_stream); } return err; } /***************************************************************************/ int32_t mz_zip_reader_unzip_cd(void *handle) { mz_zip_reader *reader = (mz_zip_reader *)handle; mz_zip_file *cd_info = NULL; void *cd_mem_stream = NULL; void *new_cd_stream = NULL; void *file_extra_stream = NULL; uint64_t number_entry = 0; int32_t err = MZ_OK; err = mz_zip_reader_goto_first_entry(handle); if (err != MZ_OK) return err; err = mz_zip_reader_entry_get_info(handle, &cd_info); if (err != MZ_OK) return err; if (strcmp(cd_info->filename, MZ_ZIP_CD_FILENAME) != 0) return mz_zip_reader_goto_first_entry(handle); err = mz_zip_reader_entry_open(handle); if (err != MZ_OK) return err; file_extra_stream = mz_stream_mem_create(); if (!file_extra_stream) return MZ_MEM_ERROR; mz_stream_mem_set_buffer(file_extra_stream, (void *)cd_info->extrafield, cd_info->extrafield_size); err = mz_zip_extrafield_find(file_extra_stream, MZ_ZIP_EXTENSION_CDCD, INT32_MAX, NULL); if (err == MZ_OK) err = mz_stream_read_uint64(file_extra_stream, &number_entry); mz_stream_mem_delete(&file_extra_stream); if (err != MZ_OK) return err; mz_zip_get_cd_mem_stream(reader->zip_handle, &cd_mem_stream); if (mz_stream_mem_is_open(cd_mem_stream) != MZ_OK) mz_stream_mem_open(cd_mem_stream, NULL, MZ_OPEN_MODE_CREATE); err = mz_stream_seek(cd_mem_stream, 0, MZ_SEEK_SET); if (err == MZ_OK) err = mz_stream_copy_stream(cd_mem_stream, NULL, handle, mz_zip_reader_entry_read, (int32_t)cd_info->uncompressed_size); if (err == MZ_OK) { reader->cd_zipped = 1; mz_zip_set_cd_stream(reader->zip_handle, 0, cd_mem_stream); mz_zip_set_number_entry(reader->zip_handle, number_entry); err = mz_zip_reader_goto_first_entry(handle); } reader->cd_verified = reader->entry_verified; mz_stream_mem_delete(&new_cd_stream); return err; } /***************************************************************************/ static int32_t mz_zip_reader_locate_entry_cb(void *handle, void *userdata, mz_zip_file *file_info) { mz_zip_reader *reader = (mz_zip_reader *)userdata; int32_t result = 0; MZ_UNUSED(handle); result = mz_path_compare_wc(file_info->filename, reader->pattern, reader->pattern_ignore_case); return result; } int32_t mz_zip_reader_goto_first_entry(void *handle) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; if (mz_zip_reader_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; if (mz_zip_entry_is_open(reader->zip_handle) == MZ_OK) mz_zip_reader_entry_close(handle); if (!reader->pattern) err = mz_zip_goto_first_entry(reader->zip_handle); else err = mz_zip_locate_first_entry(reader->zip_handle, reader, mz_zip_reader_locate_entry_cb); reader->file_info = NULL; if (err == MZ_OK) err = mz_zip_entry_get_info(reader->zip_handle, &reader->file_info); return err; } int32_t mz_zip_reader_goto_next_entry(void *handle) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; if (mz_zip_reader_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; if (mz_zip_entry_is_open(reader->zip_handle) == MZ_OK) mz_zip_reader_entry_close(handle); if (!reader->pattern) err = mz_zip_goto_next_entry(reader->zip_handle); else err = mz_zip_locate_next_entry(reader->zip_handle, reader, mz_zip_reader_locate_entry_cb); reader->file_info = NULL; if (err == MZ_OK) err = mz_zip_entry_get_info(reader->zip_handle, &reader->file_info); return err; } int32_t mz_zip_reader_locate_entry(void *handle, const char *filename, uint8_t ignore_case) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; if (mz_zip_entry_is_open(reader->zip_handle) == MZ_OK) mz_zip_reader_entry_close(handle); err = mz_zip_locate_entry(reader->zip_handle, filename, ignore_case); reader->file_info = NULL; if (err == MZ_OK) err = mz_zip_entry_get_info(reader->zip_handle, &reader->file_info); return err; } /***************************************************************************/ int32_t mz_zip_reader_entry_open(void *handle) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; const char *password = NULL; char password_buf[120]; reader->entry_verified = 0; if (mz_zip_reader_is_open(reader) != MZ_OK) return MZ_PARAM_ERROR; if (!reader->file_info) return MZ_PARAM_ERROR; /* If the entry isn't open for reading, open it */ if (mz_zip_entry_is_open(reader->zip_handle) == MZ_OK) return MZ_OK; password = reader->password; /* Check if we need a password and ask for it if we need to */ if (!password && reader->password_cb && (reader->file_info->flag & MZ_ZIP_FLAG_ENCRYPTED)) { reader->password_cb(handle, reader->password_userdata, reader->file_info, password_buf, sizeof(password_buf)); password = password_buf; } err = mz_zip_entry_read_open(reader->zip_handle, reader->raw, password); #ifndef MZ_ZIP_NO_CRYPTO if (err != MZ_OK) return err; if (mz_zip_reader_entry_get_first_hash(handle, &reader->hash_algorithm, &reader->hash_digest_size) == MZ_OK) { reader->hash = mz_crypt_sha_create(); if (!reader->hash) return MZ_MEM_ERROR; if (reader->hash_algorithm == MZ_HASH_SHA1) err = mz_crypt_sha_set_algorithm(reader->hash, MZ_HASH_SHA1); else if (reader->hash_algorithm == MZ_HASH_SHA256) err = mz_crypt_sha_set_algorithm(reader->hash, MZ_HASH_SHA256); else err = MZ_SUPPORT_ERROR; if (err == MZ_OK) mz_crypt_sha_begin(reader->hash); } else if (reader->sign_required && !reader->cd_verified) err = MZ_SIGN_ERROR; #endif return err; } int32_t mz_zip_reader_entry_close(void *handle) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; int32_t err_close = MZ_OK; #ifndef MZ_ZIP_NO_CRYPTO int32_t err_hash = MZ_OK; uint8_t computed_hash[MZ_HASH_MAX_SIZE]; uint8_t expected_hash[MZ_HASH_MAX_SIZE]; if (reader->hash) { mz_crypt_sha_end(reader->hash, computed_hash, sizeof(computed_hash)); mz_crypt_sha_delete(&reader->hash); err_hash = mz_zip_reader_entry_get_hash(handle, reader->hash_algorithm, expected_hash, reader->hash_digest_size); if (err_hash == MZ_OK) { /* Verify expected hash against computed hash */ if (memcmp(computed_hash, expected_hash, reader->hash_digest_size) != 0) err = MZ_CRC_ERROR; } } #endif err_close = mz_zip_entry_close(reader->zip_handle); if (err == MZ_OK) err = err_close; return err; } int32_t mz_zip_reader_entry_read(void *handle, void *buf, int32_t len) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t read = 0; read = mz_zip_entry_read(reader->zip_handle, buf, len); #ifndef MZ_ZIP_NO_CRYPTO if (read > 0 && reader->hash) mz_crypt_sha_update(reader->hash, buf, read); #endif return read; } int32_t mz_zip_reader_entry_get_hash(void *handle, uint16_t algorithm, uint8_t *digest, int32_t digest_size) { mz_zip_reader *reader = (mz_zip_reader *)handle; void *file_extra_stream = NULL; int32_t err = MZ_OK; int32_t return_err = MZ_EXIST_ERROR; uint16_t cur_algorithm = 0; uint16_t cur_digest_size = 0; file_extra_stream = mz_stream_mem_create(); if (!file_extra_stream) return MZ_MEM_ERROR; mz_stream_mem_set_buffer(file_extra_stream, (void *)reader->file_info->extrafield, reader->file_info->extrafield_size); do { err = mz_zip_extrafield_find(file_extra_stream, MZ_ZIP_EXTENSION_HASH, INT32_MAX, NULL); if (err != MZ_OK) break; err = mz_stream_read_uint16(file_extra_stream, &cur_algorithm); if (err == MZ_OK) err = mz_stream_read_uint16(file_extra_stream, &cur_digest_size); if ((err == MZ_OK) && (cur_algorithm == algorithm) && (cur_digest_size <= digest_size) && (cur_digest_size <= MZ_HASH_MAX_SIZE)) { /* Read hash digest */ if (mz_stream_read(file_extra_stream, digest, digest_size) == cur_digest_size) return_err = MZ_OK; break; } else { err = mz_stream_seek(file_extra_stream, cur_digest_size, MZ_SEEK_CUR); } } while (err == MZ_OK); mz_stream_mem_delete(&file_extra_stream); return return_err; } int32_t mz_zip_reader_entry_get_first_hash(void *handle, uint16_t *algorithm, uint16_t *digest_size) { mz_zip_reader *reader = (mz_zip_reader *)handle; void *file_extra_stream = NULL; int32_t err = MZ_OK; uint16_t cur_algorithm = 0; uint16_t cur_digest_size = 0; if (!reader || !algorithm) return MZ_PARAM_ERROR; file_extra_stream = mz_stream_mem_create(); if (!file_extra_stream) return MZ_MEM_ERROR; mz_stream_mem_set_buffer(file_extra_stream, (void *)reader->file_info->extrafield, reader->file_info->extrafield_size); err = mz_zip_extrafield_find(file_extra_stream, MZ_ZIP_EXTENSION_HASH, INT32_MAX, NULL); if (err == MZ_OK) err = mz_stream_read_uint16(file_extra_stream, &cur_algorithm); if (err == MZ_OK) err = mz_stream_read_uint16(file_extra_stream, &cur_digest_size); if (algorithm) *algorithm = cur_algorithm; if (digest_size) *digest_size = cur_digest_size; mz_stream_mem_delete(&file_extra_stream); return err; } int32_t mz_zip_reader_entry_get_info(void *handle, mz_zip_file **file_info) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; if (!file_info || mz_zip_reader_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; *file_info = reader->file_info; if (!*file_info) return MZ_EXIST_ERROR; return err; } int32_t mz_zip_reader_entry_is_dir(void *handle) { mz_zip_reader *reader = (mz_zip_reader *)handle; if (mz_zip_reader_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; return mz_zip_entry_is_dir(reader->zip_handle); } int32_t mz_zip_reader_entry_save_process(void *handle, void *stream, mz_stream_write_cb write_cb) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; int32_t read = 0; int32_t written = 0; if (mz_zip_reader_is_open(reader) != MZ_OK) return MZ_PARAM_ERROR; if (!reader->file_info || !write_cb) return MZ_PARAM_ERROR; /* If the entry isn't open for reading, open it */ if (mz_zip_entry_is_open(reader->zip_handle) != MZ_OK) err = mz_zip_reader_entry_open(handle); if (err != MZ_OK) return err; /* Unzip entry in zip file */ read = mz_zip_reader_entry_read(handle, reader->buffer, sizeof(reader->buffer)); if (read == 0) { /* If we are done close the entry */ err = mz_zip_reader_entry_close(handle); if (err != MZ_OK) return err; return MZ_END_OF_STREAM; } if (read > 0) { /* Write the data to the specified stream */ written = write_cb(stream, reader->buffer, read); if (written != read) return MZ_WRITE_ERROR; } return read; } int32_t mz_zip_reader_entry_save(void *handle, void *stream, mz_stream_write_cb write_cb) { mz_zip_reader *reader = (mz_zip_reader *)handle; uint64_t current_time = 0; uint64_t update_time = 0; int64_t current_pos = 0; int64_t update_pos = 0; int32_t err = MZ_OK; int32_t written = 0; if (mz_zip_reader_is_open(reader) != MZ_OK) return MZ_PARAM_ERROR; if (!reader->file_info) return MZ_PARAM_ERROR; /* Update the progress at the beginning */ if (reader->progress_cb) reader->progress_cb(handle, reader->progress_userdata, reader->file_info, current_pos); /* Write data to stream until done */ while (err == MZ_OK) { written = mz_zip_reader_entry_save_process(handle, stream, write_cb); if (written == MZ_END_OF_STREAM) break; if (written > 0) current_pos += written; if (written < 0) err = written; /* Update progress if enough time have passed */ current_time = mz_os_ms_time(); if ((current_time - update_time) > reader->progress_cb_interval_ms) { if (reader->progress_cb) reader->progress_cb(handle, reader->progress_userdata, reader->file_info, current_pos); update_pos = current_pos; update_time = current_time; } } /* Update the progress at the end */ if (reader->progress_cb && update_pos != current_pos) reader->progress_cb(handle, reader->progress_userdata, reader->file_info, current_pos); return err; } int32_t mz_zip_reader_entry_save_file(void *handle, const char *path) { mz_zip_reader *reader = (mz_zip_reader *)handle; void *stream = NULL; uint32_t target_attrib = 0; int32_t err_attrib = 0; int32_t err = MZ_OK; int32_t err_cb = MZ_OK; size_t path_length = 0; char *pathwfs = NULL; char *directory = NULL; if (mz_zip_reader_is_open(reader) != MZ_OK) return MZ_PARAM_ERROR; if (!reader->file_info || !path) return MZ_PARAM_ERROR; path_length = strlen(path); /* Convert to forward slashes for unix which doesn't like backslashes */ pathwfs = (char *)calloc(path_length + 1, sizeof(char)); if (!pathwfs) return MZ_MEM_ERROR; strncat(pathwfs, path, path_length); mz_path_convert_slashes(pathwfs, MZ_PATH_SLASH_UNIX); if (reader->entry_cb) reader->entry_cb(handle, reader->entry_userdata, reader->file_info, pathwfs); directory = (char *)calloc(path_length + 1, sizeof(char)); if (!directory) return MZ_MEM_ERROR; strncat(directory, pathwfs, path_length); mz_path_remove_filename(directory); /* If it is a directory entry then create a directory instead of writing file */ if ((mz_zip_entry_is_dir(reader->zip_handle) == MZ_OK) && (mz_zip_entry_is_symlink(reader->zip_handle) != MZ_OK)) { err = mz_dir_make(directory); goto save_cleanup; } /* Check if file exists and ask if we want to overwrite */ if (reader->overwrite_cb && mz_os_file_exists(pathwfs) == MZ_OK) { err_cb = reader->overwrite_cb(handle, reader->overwrite_userdata, reader->file_info, pathwfs); if (err_cb != MZ_OK) goto save_cleanup; /* We want to overwrite the file so we delete the existing one */ mz_os_unlink(pathwfs); } /* If symbolic link then properly construct destination path and link path */ if ((mz_zip_entry_is_symlink(reader->zip_handle) == MZ_OK) && (mz_path_has_slash(pathwfs) == MZ_OK)) { mz_path_remove_slash(pathwfs); mz_path_remove_filename(directory); } /* Create the output directory if it doesn't already exist */ if (mz_os_is_dir(directory) != MZ_OK) { err = mz_dir_make(directory); if (err != MZ_OK) goto save_cleanup; } /* If it is a symbolic link then create symbolic link instead of writing file */ if (mz_zip_entry_is_symlink(reader->zip_handle) == MZ_OK) { if (reader->file_info->linkname && *reader->file_info->linkname != 0) { /* Create symbolic link from UNIX1 extrafield */ err = mz_os_make_symlink(pathwfs, reader->file_info->linkname); } else if (reader->file_info->uncompressed_size < UINT16_MAX) { /* Create symbolic link from zip entry contents */ stream = mz_stream_mem_create(); if (!stream) { err = MZ_MEM_ERROR; goto save_cleanup; } err = mz_stream_mem_open(stream, NULL, MZ_OPEN_MODE_CREATE); if (err == MZ_OK) err = mz_zip_reader_entry_save(handle, stream, mz_stream_write); if (err == MZ_OK) err = mz_stream_write_uint8(stream, 0); if (err == MZ_OK) { const char *linkname = NULL; if (mz_stream_mem_get_buffer(stream, (const void **)&linkname) == MZ_OK) err = mz_os_make_symlink(pathwfs, linkname); } mz_stream_mem_close(stream); mz_stream_mem_delete(&stream); } /* Don't check return value because we aren't validating symbolic link target */ goto save_cleanup; } /* Create the file on disk so we can save to it */ stream = mz_stream_os_create(); if (!stream) { err = MZ_MEM_ERROR; goto save_cleanup; } err = mz_stream_os_open(stream, pathwfs, MZ_OPEN_MODE_CREATE); if (err == MZ_OK) err = mz_zip_reader_entry_save(handle, stream, mz_stream_write); mz_stream_close(stream); mz_stream_delete(&stream); if (err == MZ_OK) { /* Set the time of the file that has been created */ mz_os_set_file_date(pathwfs, reader->file_info->modified_date, reader->file_info->accessed_date, reader->file_info->creation_date); } if (err == MZ_OK) { /* Set file attributes for the correct system */ err_attrib = mz_zip_attrib_convert(MZ_HOST_SYSTEM(reader->file_info->version_madeby), reader->file_info->external_fa, MZ_VERSION_MADEBY_HOST_SYSTEM, &target_attrib); if (err_attrib == MZ_OK) mz_os_set_file_attribs(pathwfs, target_attrib); } save_cleanup: free(pathwfs); free(directory); return err; } int32_t mz_zip_reader_entry_save_buffer(void *handle, void *buf, int32_t len) { mz_zip_reader *reader = (mz_zip_reader *)handle; void *mem_stream = NULL; int32_t err = MZ_OK; if (mz_zip_reader_is_open(reader) != MZ_OK) return MZ_PARAM_ERROR; if (!reader->file_info || reader->file_info->uncompressed_size > INT32_MAX) return MZ_PARAM_ERROR; if (len != (int32_t)reader->file_info->uncompressed_size) return MZ_BUF_ERROR; /* Create a memory stream backed by our buffer and save to it */ mem_stream = mz_stream_mem_create(); if (!mem_stream) return MZ_MEM_ERROR; mz_stream_mem_set_buffer(mem_stream, buf, len); err = mz_stream_mem_open(mem_stream, NULL, MZ_OPEN_MODE_READ); if (err == MZ_OK) err = mz_zip_reader_entry_save(handle, mem_stream, mz_stream_mem_write); mz_stream_mem_delete(&mem_stream); return err; } int32_t mz_zip_reader_entry_save_buffer_length(void *handle) { mz_zip_reader *reader = (mz_zip_reader *)handle; if (mz_zip_reader_is_open(reader) != MZ_OK) return MZ_PARAM_ERROR; if (!reader->file_info || reader->file_info->uncompressed_size > INT32_MAX) return MZ_PARAM_ERROR; /* Get the maximum size required for the save buffer */ return (int32_t)reader->file_info->uncompressed_size; } /***************************************************************************/ int32_t mz_zip_reader_save_all(void *handle, const char *destination_dir) { mz_zip_reader *reader = (mz_zip_reader *)handle; int32_t err = MZ_OK; int32_t utf8_name_size = 0; int32_t resolved_name_size = 0; char *utf8_string = NULL; char *path = NULL; char *utf8_name = NULL; char *resolved_name = NULL; err = mz_zip_reader_goto_first_entry(handle); if (err == MZ_END_OF_LIST) return err; while (err == MZ_OK) { /* Assume 4 bytes per character needed + 1 for terminating null */ utf8_name_size = reader->file_info->filename_size * 4 + 1; resolved_name_size = utf8_name_size; if (destination_dir) { /* +1 is for the "/" separator */ resolved_name_size += (int)strlen(destination_dir) + 1; } if (!path) { path = (char *)malloc(resolved_name_size); utf8_name = (char *)malloc(utf8_name_size); resolved_name = (char *)malloc(resolved_name_size); } else { path = (char *)realloc(path, resolved_name_size); utf8_name = (char *)realloc(utf8_name, utf8_name_size); resolved_name = (char *)realloc(resolved_name, resolved_name_size); } if (!path || !utf8_name || !resolved_name) { err = MZ_MEM_ERROR; goto save_all_cleanup; } /* Construct output path */ path[0] = 0; strncpy(utf8_name, reader->file_info->filename, utf8_name_size - 1); utf8_name[utf8_name_size - 1] = 0; if ((reader->encoding > 0) && (reader->file_info->flag & MZ_ZIP_FLAG_UTF8) == 0) { utf8_string = mz_os_utf8_string_create(reader->file_info->filename, reader->encoding); if (utf8_string) { strncpy(utf8_name, (char *)utf8_string, utf8_name_size - 1); utf8_name[utf8_name_size - 1] = 0; mz_os_utf8_string_delete(&utf8_string); } } err = mz_path_resolve(utf8_name, resolved_name, resolved_name_size); if (err != MZ_OK) break; if (destination_dir) mz_path_combine(path, destination_dir, resolved_name_size); mz_path_combine(path, resolved_name, resolved_name_size); /* Save file to disk */ err = mz_zip_reader_entry_save_file(handle, path); if (err == MZ_OK) err = mz_zip_reader_goto_next_entry(handle); } if (err == MZ_END_OF_LIST) err = MZ_OK; save_all_cleanup: free(path); free(utf8_name); free(resolved_name); return err; } /***************************************************************************/ void mz_zip_reader_set_pattern(void *handle, const char *pattern, uint8_t ignore_case) { mz_zip_reader *reader = (mz_zip_reader *)handle; reader->pattern = pattern; reader->pattern_ignore_case = ignore_case; } void mz_zip_reader_set_password(void *handle, const char *password) { mz_zip_reader *reader = (mz_zip_reader *)handle; reader->password = password; } void mz_zip_reader_set_raw(void *handle, uint8_t raw) { mz_zip_reader *reader = (mz_zip_reader *)handle; reader->raw = raw; } int32_t mz_zip_reader_get_raw(void *handle, uint8_t *raw) { mz_zip_reader *reader = (mz_zip_reader *)handle; if (!raw) return MZ_PARAM_ERROR; *raw = reader->raw; return MZ_OK; } int32_t mz_zip_reader_get_zip_cd(void *handle, uint8_t *zip_cd) { mz_zip_reader *reader = (mz_zip_reader *)handle; if (!zip_cd) return MZ_PARAM_ERROR; *zip_cd = reader->cd_zipped; return MZ_OK; } int32_t mz_zip_reader_get_comment(void *handle, const char **comment) { mz_zip_reader *reader = (mz_zip_reader *)handle; if (mz_zip_reader_is_open(reader) != MZ_OK) return MZ_PARAM_ERROR; if (!comment) return MZ_PARAM_ERROR; return mz_zip_get_comment(reader->zip_handle, comment); } int32_t mz_zip_reader_set_recover(void *handle, uint8_t recover) { mz_zip_reader *reader = (mz_zip_reader *)handle; if (!reader) return MZ_PARAM_ERROR; reader->recover = recover; return MZ_OK; } void mz_zip_reader_set_encoding(void *handle, int32_t encoding) { mz_zip_reader *reader = (mz_zip_reader *)handle; reader->encoding = encoding; } void mz_zip_reader_set_overwrite_cb(void *handle, void *userdata, mz_zip_reader_overwrite_cb cb) { mz_zip_reader *reader = (mz_zip_reader *)handle; reader->overwrite_cb = cb; reader->overwrite_userdata = userdata; } void mz_zip_reader_set_password_cb(void *handle, void *userdata, mz_zip_reader_password_cb cb) { mz_zip_reader *reader = (mz_zip_reader *)handle; reader->password_cb = cb; reader->password_userdata = userdata; } void mz_zip_reader_set_progress_cb(void *handle, void *userdata, mz_zip_reader_progress_cb cb) { mz_zip_reader *reader = (mz_zip_reader *)handle; reader->progress_cb = cb; reader->progress_userdata = userdata; } void mz_zip_reader_set_progress_interval(void *handle, uint32_t milliseconds) { mz_zip_reader *reader = (mz_zip_reader *)handle; reader->progress_cb_interval_ms = milliseconds; } void mz_zip_reader_set_entry_cb(void *handle, void *userdata, mz_zip_reader_entry_cb cb) { mz_zip_reader *reader = (mz_zip_reader *)handle; reader->entry_cb = cb; reader->entry_userdata = userdata; } int32_t mz_zip_reader_get_zip_handle(void *handle, void **zip_handle) { mz_zip_reader *reader = (mz_zip_reader *)handle; if (!zip_handle) return MZ_PARAM_ERROR; *zip_handle = reader->zip_handle; if (!*zip_handle) return MZ_EXIST_ERROR; return MZ_OK; } /***************************************************************************/ void *mz_zip_reader_create(void) { mz_zip_reader *reader = (mz_zip_reader *)calloc(1, sizeof(mz_zip_reader)); if (reader) { reader->recover = 1; reader->progress_cb_interval_ms = MZ_DEFAULT_PROGRESS_INTERVAL; } return reader; } void mz_zip_reader_delete(void **handle) { mz_zip_reader *reader = NULL; if (!handle) return; reader = (mz_zip_reader *)*handle; if (reader) { mz_zip_reader_close(reader); free(reader); } *handle = NULL; } /***************************************************************************/ typedef struct mz_zip_writer_s { void *zip_handle; void *file_stream; void *buffered_stream; void *split_stream; void *hash; uint16_t hash_algorithm; void *mem_stream; void *file_extra_stream; mz_zip_file file_info; void *overwrite_userdata; mz_zip_writer_overwrite_cb overwrite_cb; void *password_userdata; mz_zip_writer_password_cb password_cb; void *progress_userdata; mz_zip_writer_progress_cb progress_cb; uint32_t progress_cb_interval_ms; void *entry_userdata; mz_zip_writer_entry_cb entry_cb; const char *password; const char *comment; uint8_t *cert_data; int32_t cert_data_size; const char *cert_pwd; uint16_t compress_method; int16_t compress_level; uint8_t follow_links; uint8_t store_links; uint8_t zip_cd; uint8_t aes; uint8_t raw; uint8_t buffer[UINT16_MAX]; } mz_zip_writer; /***************************************************************************/ int32_t mz_zip_writer_zip_cd(void *handle) { mz_zip_writer *writer = (mz_zip_writer *)handle; mz_zip_file cd_file; uint64_t number_entry = 0; int64_t cd_mem_length = 0; int32_t err = MZ_OK; int32_t extrafield_size = 0; void *file_extra_stream = NULL; void *cd_mem_stream = NULL; memset(&cd_file, 0, sizeof(cd_file)); mz_zip_get_number_entry(writer->zip_handle, &number_entry); mz_zip_get_cd_mem_stream(writer->zip_handle, &cd_mem_stream); mz_stream_seek(cd_mem_stream, 0, MZ_SEEK_END); cd_mem_length = (uint32_t)mz_stream_tell(cd_mem_stream); mz_stream_seek(cd_mem_stream, 0, MZ_SEEK_SET); cd_file.filename = MZ_ZIP_CD_FILENAME; cd_file.modified_date = time(NULL); cd_file.version_madeby = MZ_VERSION_MADEBY; cd_file.compression_method = writer->compress_method; cd_file.uncompressed_size = (int32_t)cd_mem_length; cd_file.flag = MZ_ZIP_FLAG_UTF8; if (writer->password) cd_file.flag |= MZ_ZIP_FLAG_ENCRYPTED; file_extra_stream = mz_stream_mem_create(); if (!file_extra_stream) return MZ_MEM_ERROR; mz_stream_mem_open(file_extra_stream, NULL, MZ_OPEN_MODE_CREATE); mz_zip_extrafield_write(file_extra_stream, MZ_ZIP_EXTENSION_CDCD, 8); mz_stream_write_uint64(file_extra_stream, number_entry); mz_stream_mem_get_buffer(file_extra_stream, (const void **)&cd_file.extrafield); mz_stream_mem_get_buffer_length(file_extra_stream, &extrafield_size); cd_file.extrafield_size = (uint16_t)extrafield_size; err = mz_zip_writer_entry_open(handle, &cd_file); if (err == MZ_OK) { mz_stream_copy_stream(handle, mz_zip_writer_entry_write, cd_mem_stream, NULL, (int32_t)cd_mem_length); mz_stream_seek(cd_mem_stream, 0, MZ_SEEK_SET); mz_stream_mem_set_buffer_limit(cd_mem_stream, 0); err = mz_zip_writer_entry_close(writer); } mz_stream_mem_delete(&file_extra_stream); return err; } /***************************************************************************/ int32_t mz_zip_writer_is_open(void *handle) { mz_zip_writer *writer = (mz_zip_writer *)handle; if (!writer || !writer->zip_handle) return MZ_PARAM_ERROR; return MZ_OK; } static int32_t mz_zip_writer_open_int(void *handle, void *stream, int32_t mode) { mz_zip_writer *writer = (mz_zip_writer *)handle; int32_t err = MZ_OK; writer->zip_handle = mz_zip_create(); if (writer->zip_handle == NULL) return MZ_MEM_ERROR; err = mz_zip_open(writer->zip_handle, stream, mode); if (err != MZ_OK) { mz_zip_writer_close(handle); return err; } return MZ_OK; } int32_t mz_zip_writer_open(void *handle, void *stream, uint8_t append) { int32_t mode = MZ_OPEN_MODE_WRITE; if (append) { mode |= MZ_OPEN_MODE_APPEND; } else { mode |= MZ_OPEN_MODE_CREATE; } return mz_zip_writer_open_int(handle, stream, mode); } int32_t mz_zip_writer_open_file(void *handle, const char *path, int64_t disk_size, uint8_t append) { mz_zip_writer *writer = (mz_zip_writer *)handle; int32_t mode = MZ_OPEN_MODE_READWRITE; int32_t err = MZ_OK; int32_t err_cb = 0; char directory[320]; mz_zip_writer_close(handle); if (mz_os_file_exists(path) != MZ_OK) { /* If the file doesn't exist, we don't append file */ mode |= MZ_OPEN_MODE_CREATE; /* Create destination directory if it doesn't already exist */ if (strchr(path, '/') || strrchr(path, '\\')) { strncpy(directory, path, sizeof(directory) - 1); directory[sizeof(directory) - 1] = 0; mz_path_remove_filename(directory); if (mz_os_file_exists(directory) != MZ_OK) mz_dir_make(directory); } } else if (append) { mode |= MZ_OPEN_MODE_APPEND; } else { if (writer->overwrite_cb) err_cb = writer->overwrite_cb(handle, writer->overwrite_userdata, path); if (err_cb == MZ_INTERNAL_ERROR) return err; if (err_cb == MZ_OK) mode |= MZ_OPEN_MODE_CREATE; else mode |= MZ_OPEN_MODE_APPEND; } writer->file_stream = mz_stream_os_create(); if (!writer->file_stream) return MZ_MEM_ERROR; writer->buffered_stream = mz_stream_buffered_create(); if (!writer->buffered_stream) { mz_stream_os_delete(&writer->file_stream); return MZ_MEM_ERROR; } writer->split_stream = mz_stream_split_create(); if (!writer->split_stream) { mz_stream_buffered_delete(&writer->buffered_stream); mz_stream_os_delete(&writer->file_stream); return MZ_MEM_ERROR; } mz_stream_set_base(writer->buffered_stream, writer->file_stream); mz_stream_set_base(writer->split_stream, writer->buffered_stream); mz_stream_split_set_prop_int64(writer->split_stream, MZ_STREAM_PROP_DISK_SIZE, disk_size); err = mz_stream_open(writer->split_stream, path, mode); if (err == MZ_OK) err = mz_zip_writer_open_int(handle, writer->split_stream, mode); return err; } int32_t mz_zip_writer_open_file_in_memory(void *handle, const char *path) { mz_zip_writer *writer = (mz_zip_writer *)handle; void *file_stream = NULL; int64_t file_size = 0; int32_t err = 0; mz_zip_writer_close(handle); file_stream = mz_stream_os_create(); if (!file_stream) return MZ_MEM_ERROR; err = mz_stream_os_open(file_stream, path, MZ_OPEN_MODE_READ); if (err != MZ_OK) { mz_stream_os_delete(&file_stream); mz_zip_writer_close(handle); return err; } mz_stream_os_seek(file_stream, 0, MZ_SEEK_END); file_size = mz_stream_os_tell(file_stream); mz_stream_os_seek(file_stream, 0, MZ_SEEK_SET); writer->mem_stream = mz_stream_mem_create(); if ((file_size <= 0) || (file_size > UINT32_MAX) || (!writer->mem_stream)) { /* Memory size is too large or too small */ mz_stream_os_close(file_stream); mz_stream_os_delete(&file_stream); mz_zip_writer_close(handle); return MZ_MEM_ERROR; } mz_stream_mem_set_grow_size(writer->mem_stream, (int32_t)file_size); mz_stream_mem_open(writer->mem_stream, NULL, MZ_OPEN_MODE_CREATE); err = mz_stream_copy(writer->mem_stream, file_stream, (int32_t)file_size); mz_stream_os_close(file_stream); mz_stream_os_delete(&file_stream); if (err == MZ_OK) err = mz_zip_writer_open(handle, writer->mem_stream, 1); if (err != MZ_OK) mz_zip_writer_close(handle); return err; } int32_t mz_zip_writer_close(void *handle) { mz_zip_writer *writer = (mz_zip_writer *)handle; int32_t err = MZ_OK; if (writer->zip_handle) { mz_zip_set_version_madeby(writer->zip_handle, MZ_VERSION_MADEBY); if (writer->comment) mz_zip_set_comment(writer->zip_handle, writer->comment); if (writer->zip_cd) mz_zip_writer_zip_cd(writer); err = mz_zip_close(writer->zip_handle); mz_zip_delete(&writer->zip_handle); } if (writer->split_stream) { mz_stream_split_close(writer->split_stream); mz_stream_split_delete(&writer->split_stream); } if (writer->buffered_stream) mz_stream_buffered_delete(&writer->buffered_stream); if (writer->file_stream) mz_stream_os_delete(&writer->file_stream); if (writer->mem_stream) { mz_stream_mem_close(writer->mem_stream); mz_stream_mem_delete(&writer->mem_stream); } return err; } /***************************************************************************/ int32_t mz_zip_writer_entry_open(void *handle, mz_zip_file *file_info) { mz_zip_writer *writer = (mz_zip_writer *)handle; int32_t err = MZ_OK; const char *password = NULL; char password_buf[120]; /* Copy file info to access data upon close */ memcpy(&writer->file_info, file_info, sizeof(mz_zip_file)); if (writer->entry_cb) writer->entry_cb(handle, writer->entry_userdata, &writer->file_info); password = writer->password; /* Check if we need a password and ask for it if we need to */ if (!password && writer->password_cb && (writer->file_info.flag & MZ_ZIP_FLAG_ENCRYPTED)) { writer->password_cb(handle, writer->password_userdata, &writer->file_info, password_buf, sizeof(password_buf)); password = password_buf; } #ifndef MZ_ZIP_NO_CRYPTO if (mz_zip_attrib_is_dir(writer->file_info.external_fa, writer->file_info.version_madeby) != MZ_OK) { /* Start calculating hash */ writer->hash = mz_crypt_sha_create(); writer->hash_algorithm = MZ_HASH_SHA256; if (!writer->hash) return MZ_MEM_ERROR; err = mz_crypt_sha_set_algorithm(writer->hash, writer->hash_algorithm); if (err != MZ_OK) { writer->hash_algorithm = MZ_HASH_SHA1; err = mz_crypt_sha_set_algorithm(writer->hash, writer->hash_algorithm); } mz_crypt_sha_begin(writer->hash); } #endif /* Open entry in zip */ err = mz_zip_entry_write_open(writer->zip_handle, &writer->file_info, writer->compress_level, writer->raw, password); return err; } int32_t mz_zip_writer_entry_close(void *handle) { mz_zip_writer *writer = (mz_zip_writer *)handle; int32_t err = MZ_OK; #ifndef MZ_ZIP_NO_CRYPTO const uint8_t *extrafield = NULL; int32_t extrafield_size = 0; int16_t field_length_hash = 0; uint8_t hash_digest[MZ_HASH_MAX_SIZE]; if (writer->hash) { uint16_t hash_digest_size = 0; switch (writer->hash_algorithm) { case MZ_HASH_SHA1: hash_digest_size = MZ_HASH_SHA1_SIZE; break; case MZ_HASH_SHA256: hash_digest_size = MZ_HASH_SHA256_SIZE; break; default: return MZ_PARAM_ERROR; } mz_crypt_sha_end(writer->hash, hash_digest, hash_digest_size); mz_crypt_sha_delete(&writer->hash); /* Copy extrafield so we can append our own fields before close */ writer->file_extra_stream = mz_stream_mem_create(); if (!writer->file_extra_stream) return MZ_MEM_ERROR; mz_stream_mem_open(writer->file_extra_stream, NULL, MZ_OPEN_MODE_CREATE); /* Write digest to extrafield */ field_length_hash = 4 + hash_digest_size; err = mz_zip_extrafield_write(writer->file_extra_stream, MZ_ZIP_EXTENSION_HASH, field_length_hash); if (err == MZ_OK) err = mz_stream_write_uint16(writer->file_extra_stream, writer->hash_algorithm); if (err == MZ_OK) err = mz_stream_write_uint16(writer->file_extra_stream, hash_digest_size); if (err == MZ_OK) { if (mz_stream_write(writer->file_extra_stream, hash_digest, hash_digest_size) != hash_digest_size) err = MZ_WRITE_ERROR; } if ((writer->file_info.extrafield) && (writer->file_info.extrafield_size > 0)) mz_stream_mem_write(writer->file_extra_stream, writer->file_info.extrafield, writer->file_info.extrafield_size); /* Update extra field for central directory after adding extra fields */ mz_stream_mem_get_buffer(writer->file_extra_stream, (const void **)&extrafield); mz_stream_mem_get_buffer_length(writer->file_extra_stream, &extrafield_size); mz_zip_entry_set_extrafield(writer->zip_handle, extrafield, (uint16_t)extrafield_size); } #endif if (err == MZ_OK) { if (writer->raw) err = mz_zip_entry_close_raw(writer->zip_handle, writer->file_info.uncompressed_size, writer->file_info.crc); else err = mz_zip_entry_close(writer->zip_handle); } if (writer->file_extra_stream) mz_stream_mem_delete(&writer->file_extra_stream); return err; } int32_t mz_zip_writer_entry_write(void *handle, const void *buf, int32_t len) { mz_zip_writer *writer = (mz_zip_writer *)handle; int32_t written = 0; written = mz_zip_entry_write(writer->zip_handle, buf, len); #ifndef MZ_ZIP_NO_CRYPTO if (written > 0 && writer->hash) mz_crypt_sha_update(writer->hash, buf, written); #endif return written; } /***************************************************************************/ int32_t mz_zip_writer_add_process(void *handle, void *stream, mz_stream_read_cb read_cb) { mz_zip_writer *writer = (mz_zip_writer *)handle; int32_t read = 0; int32_t written = 0; int32_t err = MZ_OK; if (mz_zip_writer_is_open(writer) != MZ_OK) return MZ_PARAM_ERROR; /* If the entry isn't open for writing, open it */ if (mz_zip_entry_is_open(writer->zip_handle) != MZ_OK) return MZ_PARAM_ERROR; if (!read_cb) return MZ_PARAM_ERROR; read = read_cb(stream, writer->buffer, sizeof(writer->buffer)); if (read == 0) return MZ_END_OF_STREAM; if (read < 0) { err = read; return err; } written = mz_zip_writer_entry_write(handle, writer->buffer, read); if (written != read) return MZ_WRITE_ERROR; return written; } int32_t mz_zip_writer_add(void *handle, void *stream, mz_stream_read_cb read_cb) { mz_zip_writer *writer = (mz_zip_writer *)handle; uint64_t current_time = 0; uint64_t update_time = 0; int64_t current_pos = 0; int64_t update_pos = 0; int32_t err = MZ_OK; int32_t written = 0; /* Update the progress at the beginning */ if (writer->progress_cb) writer->progress_cb(handle, writer->progress_userdata, &writer->file_info, current_pos); /* Write data to stream until done */ while (err == MZ_OK) { written = mz_zip_writer_add_process(handle, stream, read_cb); if (written == MZ_END_OF_STREAM) break; if (written > 0) current_pos += written; if (written < 0) err = written; /* Update progress if enough time have passed */ current_time = mz_os_ms_time(); if ((current_time - update_time) > writer->progress_cb_interval_ms) { if (writer->progress_cb) writer->progress_cb(handle, writer->progress_userdata, &writer->file_info, current_pos); update_pos = current_pos; update_time = current_time; } } /* Update the progress at the end */ if (writer->progress_cb && update_pos != current_pos) writer->progress_cb(handle, writer->progress_userdata, &writer->file_info, current_pos); return err; } int32_t mz_zip_writer_add_info(void *handle, void *stream, mz_stream_read_cb read_cb, mz_zip_file *file_info) { mz_zip_writer *writer = (mz_zip_writer *)handle; int32_t err = MZ_OK; if (mz_zip_writer_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; if (!file_info) return MZ_PARAM_ERROR; /* Add to zip */ err = mz_zip_writer_entry_open(handle, file_info); if (err != MZ_OK) return err; if (stream) { if (mz_zip_attrib_is_dir(writer->file_info.external_fa, writer->file_info.version_madeby) != MZ_OK) { err = mz_zip_writer_add(handle, stream, read_cb); if (err != MZ_OK) return err; } } err = mz_zip_writer_entry_close(handle); return err; } int32_t mz_zip_writer_add_buffer(void *handle, void *buf, int32_t len, mz_zip_file *file_info) { void *mem_stream = NULL; int32_t err = MZ_OK; if (mz_zip_writer_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; if (!buf) return MZ_PARAM_ERROR; /* Create a memory stream backed by our buffer and add from it */ mem_stream = mz_stream_mem_create(); if (!mem_stream) return MZ_STREAM_ERROR; mz_stream_mem_set_buffer(mem_stream, buf, len); err = mz_stream_mem_open(mem_stream, NULL, MZ_OPEN_MODE_READ); if (err == MZ_OK) err = mz_zip_writer_add_info(handle, mem_stream, mz_stream_mem_read, file_info); mz_stream_mem_delete(&mem_stream); return err; } int32_t mz_zip_writer_add_file(void *handle, const char *path, const char *filename_in_zip) { mz_zip_writer *writer = (mz_zip_writer *)handle; mz_zip_file file_info; uint32_t target_attrib = 0; uint32_t src_attrib = 0; int32_t err = MZ_OK; uint8_t src_sys = 0; void *stream = NULL; char link_path[1024]; const char *filename = filename_in_zip; if (mz_zip_writer_is_open(handle) != MZ_OK) return MZ_PARAM_ERROR; if (!path) return MZ_PARAM_ERROR; if (!filename) { err = mz_path_get_filename(path, &filename); if (err != MZ_OK) return err; } memset(&file_info, 0, sizeof(file_info)); /* The path name saved, should not include a leading slash. */ /* If it did, windows/xp and dynazip couldn't read the zip file. */ while (filename[0] == '\\' || filename[0] == '/') filename += 1; /* Get information about the file on disk so we can store it in zip */ file_info.version_madeby = MZ_VERSION_MADEBY; file_info.compression_method = writer->compress_method; file_info.filename = filename; file_info.uncompressed_size = mz_os_get_file_size(path); file_info.flag = MZ_ZIP_FLAG_UTF8; if (writer->zip_cd) file_info.flag |= MZ_ZIP_FLAG_MASK_LOCAL_INFO; if (writer->aes) file_info.aes_version = MZ_AES_VERSION; mz_os_get_file_date(path, &file_info.modified_date, &file_info.accessed_date, &file_info.creation_date); mz_os_get_file_attribs(path, &src_attrib); src_sys = MZ_HOST_SYSTEM(file_info.version_madeby); if ((src_sys != MZ_HOST_SYSTEM_MSDOS) && (src_sys != MZ_HOST_SYSTEM_WINDOWS_NTFS)) { /* High bytes are OS specific attributes, low byte is always DOS attributes */ if (mz_zip_attrib_convert(src_sys, src_attrib, MZ_HOST_SYSTEM_MSDOS, &target_attrib) == MZ_OK) file_info.external_fa = target_attrib; file_info.external_fa |= (src_attrib << 16); } else { file_info.external_fa = src_attrib; } if (writer->store_links && mz_os_is_symlink(path) == MZ_OK) { err = mz_os_read_symlink(path, link_path, sizeof(link_path)); if (err == MZ_OK) file_info.linkname = link_path; } else if (mz_os_is_dir(path) != MZ_OK) { stream = mz_stream_os_create(); if (!stream) return MZ_STREAM_ERROR; err = mz_stream_os_open(stream, path, MZ_OPEN_MODE_READ); } if (err == MZ_OK) err = mz_zip_writer_add_info(handle, stream, mz_stream_read, &file_info); if (stream) { mz_stream_close(stream); mz_stream_delete(&stream); } return err; } int32_t mz_zip_writer_add_path(void *handle, const char *path, const char *root_path, uint8_t include_path, uint8_t recursive) { mz_zip_writer *writer = (mz_zip_writer *)handle; DIR *dir = NULL; struct dirent *entry = NULL; int32_t err = MZ_OK; int16_t is_dir = 0; const char *filename = NULL; const char *filenameinzip = path; char *wildcard_ptr = NULL; char full_path[1024]; char path_dir[1024]; if (strrchr(path, '*')) { strncpy(path_dir, path, sizeof(path_dir) - 1); path_dir[sizeof(path_dir) - 1] = 0; mz_path_remove_filename(path_dir); wildcard_ptr = path_dir + strlen(path_dir) + 1; root_path = path = path_dir; } else { if (mz_os_is_dir(path) == MZ_OK) is_dir = 1; /* Construct the filename that our file will be stored in the zip as */ if (!root_path) root_path = path; /* Should the file be stored with any path info at all? */ if (!include_path) { if (!is_dir && root_path == path) { if (mz_path_get_filename(filenameinzip, &filename) == MZ_OK) filenameinzip = filename; } else { filenameinzip += strlen(root_path); } } if (!writer->store_links && !writer->follow_links) { if (mz_os_is_symlink(path) == MZ_OK) return err; } if (*filenameinzip != 0) err = mz_zip_writer_add_file(handle, path, filenameinzip); if (!is_dir) return err; if (writer->store_links) { if (mz_os_is_symlink(path) == MZ_OK) return err; } } dir = mz_os_open_dir(path); if (!dir) return MZ_EXIST_ERROR; while ((entry = mz_os_read_dir(dir))) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; full_path[0] = 0; mz_path_combine(full_path, path, sizeof(full_path)); mz_path_combine(full_path, entry->d_name, sizeof(full_path)); if (!recursive && mz_os_is_dir(full_path) == MZ_OK) continue; if ((wildcard_ptr) && (mz_path_compare_wc(entry->d_name, wildcard_ptr, 1) != MZ_OK)) continue; err = mz_zip_writer_add_path(handle, full_path, root_path, include_path, recursive); if (err != MZ_OK) break; } mz_os_close_dir(dir); return err; } int32_t mz_zip_writer_copy_from_reader(void *handle, void *reader) { mz_zip_writer *writer = (mz_zip_writer *)handle; mz_zip_file *file_info = NULL; int64_t compressed_size = 0; int64_t uncompressed_size = 0; uint32_t crc32 = 0; int32_t err = MZ_OK; uint8_t original_raw = 0; void *reader_zip_handle = NULL; void *writer_zip_handle = NULL; if (mz_zip_reader_is_open(reader) != MZ_OK) return MZ_PARAM_ERROR; if (mz_zip_writer_is_open(writer) != MZ_OK) return MZ_PARAM_ERROR; err = mz_zip_reader_entry_get_info(reader, &file_info); if (err != MZ_OK) return err; mz_zip_reader_get_zip_handle(reader, &reader_zip_handle); mz_zip_writer_get_zip_handle(writer, &writer_zip_handle); /* Open entry for raw reading */ err = mz_zip_entry_read_open(reader_zip_handle, 1, NULL); if (err == MZ_OK) { /* Write entry raw, save original raw value */ original_raw = writer->raw; writer->raw = 1; err = mz_zip_writer_entry_open(writer, file_info); if ((err == MZ_OK) && (mz_zip_attrib_is_dir(writer->file_info.external_fa, writer->file_info.version_madeby) != MZ_OK)) { err = mz_zip_writer_add(writer, reader_zip_handle, mz_zip_entry_read); } if (err == MZ_OK) { err = mz_zip_entry_read_close(reader_zip_handle, &crc32, &compressed_size, &uncompressed_size); if (err == MZ_OK) err = mz_zip_entry_write_close(writer_zip_handle, crc32, compressed_size, uncompressed_size); } if (mz_zip_entry_is_open(reader_zip_handle) == MZ_OK) mz_zip_entry_close(reader_zip_handle); if (mz_zip_entry_is_open(writer_zip_handle) == MZ_OK) mz_zip_entry_close(writer_zip_handle); #ifndef MZ_ZIP_NO_CRYPTO mz_crypt_sha_delete(&writer->hash); #endif writer->raw = original_raw; } return err; } /***************************************************************************/ void mz_zip_writer_set_password(void *handle, const char *password) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->password = password; } void mz_zip_writer_set_comment(void *handle, const char *comment) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->comment = comment; } void mz_zip_writer_set_raw(void *handle, uint8_t raw) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->raw = raw; } int32_t mz_zip_writer_get_raw(void *handle, uint8_t *raw) { mz_zip_writer *writer = (mz_zip_writer *)handle; if (!raw) return MZ_PARAM_ERROR; *raw = writer->raw; return MZ_OK; } void mz_zip_writer_set_aes(void *handle, uint8_t aes) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->aes = aes; } void mz_zip_writer_set_compress_method(void *handle, uint16_t compress_method) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->compress_method = compress_method; } void mz_zip_writer_set_compress_level(void *handle, int16_t compress_level) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->compress_level = compress_level; } void mz_zip_writer_set_follow_links(void *handle, uint8_t follow_links) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->follow_links = follow_links; } void mz_zip_writer_set_store_links(void *handle, uint8_t store_links) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->store_links = store_links; } void mz_zip_writer_set_zip_cd(void *handle, uint8_t zip_cd) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->zip_cd = zip_cd; } int32_t mz_zip_writer_set_certificate(void *handle, const char *cert_path, const char *cert_pwd) { mz_zip_writer *writer = (mz_zip_writer *)handle; void *cert_stream = NULL; uint8_t *cert_data = NULL; int32_t cert_data_size = 0; int32_t err = MZ_OK; if (!cert_path) return MZ_PARAM_ERROR; cert_data_size = (int32_t)mz_os_get_file_size(cert_path); if (cert_data_size == 0) return MZ_PARAM_ERROR; if (writer->cert_data) { free(writer->cert_data); writer->cert_data = NULL; } cert_data = (uint8_t *)malloc(cert_data_size); /* Read pkcs12 certificate from disk */ cert_stream = mz_stream_os_create(); if (!cert_stream) return MZ_MEM_ERROR; err = mz_stream_os_open(cert_stream, cert_path, MZ_OPEN_MODE_READ); if (err == MZ_OK) { if (mz_stream_os_read(cert_stream, cert_data, cert_data_size) != cert_data_size) err = MZ_READ_ERROR; mz_stream_os_close(cert_stream); } mz_stream_os_delete(&cert_stream); if (err == MZ_OK) { writer->cert_data = cert_data; writer->cert_data_size = cert_data_size; writer->cert_pwd = cert_pwd; } else { free(cert_data); } return err; } void mz_zip_writer_set_overwrite_cb(void *handle, void *userdata, mz_zip_writer_overwrite_cb cb) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->overwrite_cb = cb; writer->overwrite_userdata = userdata; } void mz_zip_writer_set_password_cb(void *handle, void *userdata, mz_zip_writer_password_cb cb) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->password_cb = cb; writer->password_userdata = userdata; } void mz_zip_writer_set_progress_cb(void *handle, void *userdata, mz_zip_writer_progress_cb cb) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->progress_cb = cb; writer->progress_userdata = userdata; } void mz_zip_writer_set_progress_interval(void *handle, uint32_t milliseconds) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->progress_cb_interval_ms = milliseconds; } void mz_zip_writer_set_entry_cb(void *handle, void *userdata, mz_zip_writer_entry_cb cb) { mz_zip_writer *writer = (mz_zip_writer *)handle; writer->entry_cb = cb; writer->entry_userdata = userdata; } int32_t mz_zip_writer_get_zip_handle(void *handle, void **zip_handle) { mz_zip_writer *writer = (mz_zip_writer *)handle; if (!zip_handle) return MZ_PARAM_ERROR; *zip_handle = writer->zip_handle; if (!*zip_handle) return MZ_EXIST_ERROR; return MZ_OK; } /***************************************************************************/ void *mz_zip_writer_create(void) { mz_zip_writer *writer = (mz_zip_writer *)calloc(1, sizeof(mz_zip_writer)); if (writer) { #if defined(HAVE_WZAES) writer->aes = 1; #endif #if defined(HAVE_ZLIB) || defined(HAVE_LIBCOMP) writer->compress_method = MZ_COMPRESS_METHOD_DEFLATE; #elif defined(HAVE_BZIP2) writer->compress_method = MZ_COMPRESS_METHOD_BZIP2; #elif defined(HAVE_LZMA) writer->compress_method = MZ_COMPRESS_METHOD_LZMA; #else writer->compress_method = MZ_COMPRESS_METHOD_STORE; #endif writer->compress_level = MZ_COMPRESS_LEVEL_BEST; writer->progress_cb_interval_ms = MZ_DEFAULT_PROGRESS_INTERVAL; } return writer; } void mz_zip_writer_delete(void **handle) { mz_zip_writer *writer = NULL; if (!handle) return; writer = (mz_zip_writer *)*handle; if (writer) { mz_zip_writer_close(writer); if (writer->cert_data) free(writer->cert_data); writer->cert_data = NULL; writer->cert_data_size = 0; free(writer); } *handle = NULL; } /***************************************************************************/ sight-25.1.0/3rd-party/minizip/mz_zip_rw.h000066400000000000000000000263451503402212300204140ustar00rootroot00000000000000/* mz_zip_rw.h -- Zip reader/writer part of the minizip-ng project Copyright (C) Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_ZIP_RW_H #define MZ_ZIP_RW_H #ifdef __cplusplus extern "C" { #endif /***************************************************************************/ typedef int32_t (*mz_zip_reader_overwrite_cb)(void *handle, void *userdata, mz_zip_file *file_info, const char *path); typedef int32_t (*mz_zip_reader_password_cb)(void *handle, void *userdata, mz_zip_file *file_info, char *password, int32_t max_password); typedef int32_t (*mz_zip_reader_progress_cb)(void *handle, void *userdata, mz_zip_file *file_info, int64_t position); typedef int32_t (*mz_zip_reader_entry_cb)(void *handle, void *userdata, mz_zip_file *file_info, const char *path); /***************************************************************************/ int32_t mz_zip_reader_is_open(void *handle); /* Checks to see if the zip file is open */ int32_t mz_zip_reader_open(void *handle, void *stream); /* Opens zip file from stream */ int32_t mz_zip_reader_open_file(void *handle, const char *path); /* Opens zip file from a file path */ int32_t mz_zip_reader_open_file_in_memory(void *handle, const char *path); /* Opens zip file from a file path into memory for faster access */ int32_t mz_zip_reader_open_buffer(void *handle, uint8_t *buf, int32_t len, uint8_t copy); /* Opens zip file from memory buffer */ int32_t mz_zip_reader_close(void *handle); /* Closes the zip file */ /***************************************************************************/ int32_t mz_zip_reader_unzip_cd(void *handle); /* Unzip the central directory */ /***************************************************************************/ int32_t mz_zip_reader_goto_first_entry(void *handle); /* Goto the first entry in the zip file that matches the pattern */ int32_t mz_zip_reader_goto_next_entry(void *handle); /* Goto the next entry in the zip file that matches the pattern */ int32_t mz_zip_reader_locate_entry(void *handle, const char *filename, uint8_t ignore_case); /* Locates an entry by filename */ int32_t mz_zip_reader_entry_open(void *handle); /* Opens an entry for reading */ int32_t mz_zip_reader_entry_close(void *handle); /* Closes an entry */ int32_t mz_zip_reader_entry_read(void *handle, void *buf, int32_t len); /* Reads and entry after being opened */ int32_t mz_zip_reader_entry_get_hash(void *handle, uint16_t algorithm, uint8_t *digest, int32_t digest_size); /* Gets a hash algorithm from the entry's extra field */ int32_t mz_zip_reader_entry_get_first_hash(void *handle, uint16_t *algorithm, uint16_t *digest_size); /* Gets the most secure hash algorithm from the entry's extra field */ int32_t mz_zip_reader_entry_get_info(void *handle, mz_zip_file **file_info); /* Gets the current entry file info */ int32_t mz_zip_reader_entry_is_dir(void *handle); /* Gets the current entry is a directory */ int32_t mz_zip_reader_entry_save(void *handle, void *stream, mz_stream_write_cb write_cb); /* Save the current entry to a stream */ int32_t mz_zip_reader_entry_save_process(void *handle, void *stream, mz_stream_write_cb write_cb); /* Saves a portion of the current entry to a stream callback */ int32_t mz_zip_reader_entry_save_file(void *handle, const char *path); /* Save the current entry to a file */ int32_t mz_zip_reader_entry_save_buffer(void *handle, void *buf, int32_t len); /* Save the current entry to a memory buffer */ int32_t mz_zip_reader_entry_save_buffer_length(void *handle); /* Gets the length of the buffer required to save */ /***************************************************************************/ int32_t mz_zip_reader_save_all(void *handle, const char *destination_dir); /* Save all files into a directory */ /***************************************************************************/ void mz_zip_reader_set_pattern(void *handle, const char *pattern, uint8_t ignore_case); /* Sets the match pattern for entries in the zip file, if null all entries are matched */ void mz_zip_reader_set_password(void *handle, const char *password); /* Sets the password required for extraction */ void mz_zip_reader_set_raw(void *handle, uint8_t raw); /* Sets whether or not it should save the entry raw */ int32_t mz_zip_reader_get_raw(void *handle, uint8_t *raw); /* Gets whether or not it should save the entry raw */ int32_t mz_zip_reader_get_zip_cd(void *handle, uint8_t *zip_cd); /* Gets whether or not the archive has a zipped central directory */ int32_t mz_zip_reader_get_comment(void *handle, const char **comment); /* Gets the comment for the central directory */ int32_t mz_zip_reader_set_recover(void *handle, uint8_t recover); /* Sets the ability to recover the central dir by reading local file headers */ void mz_zip_reader_set_encoding(void *handle, int32_t encoding); /* Sets whether or not it should support a special character encoding in zip file names. */ void mz_zip_reader_set_overwrite_cb(void *handle, void *userdata, mz_zip_reader_overwrite_cb cb); /* Callback for what to do when a file is being overwritten */ void mz_zip_reader_set_password_cb(void *handle, void *userdata, mz_zip_reader_password_cb cb); /* Callback for when a password is required and hasn't been set */ void mz_zip_reader_set_progress_cb(void *handle, void *userdata, mz_zip_reader_progress_cb cb); /* Callback for extraction progress */ void mz_zip_reader_set_progress_interval(void *handle, uint32_t milliseconds); /* Let at least milliseconds pass between calls to progress callback */ void mz_zip_reader_set_entry_cb(void *handle, void *userdata, mz_zip_reader_entry_cb cb); /* Callback for zip file entries */ int32_t mz_zip_reader_get_zip_handle(void *handle, void **zip_handle); /* Gets the underlying zip instance handle */ void* mz_zip_reader_create(void); /* Create new instance of zip reader */ void mz_zip_reader_delete(void **handle); /* Delete instance of zip reader */ /***************************************************************************/ typedef int32_t (*mz_zip_writer_overwrite_cb)(void *handle, void *userdata, const char *path); typedef int32_t (*mz_zip_writer_password_cb)(void *handle, void *userdata, mz_zip_file *file_info, char *password, int32_t max_password); typedef int32_t (*mz_zip_writer_progress_cb)(void *handle, void *userdata, mz_zip_file *file_info, int64_t position); typedef int32_t (*mz_zip_writer_entry_cb)(void *handle, void *userdata, mz_zip_file *file_info); /***************************************************************************/ int32_t mz_zip_writer_is_open(void *handle); /* Checks to see if the zip file is open */ int32_t mz_zip_writer_open(void *handle, void *stream, uint8_t append); /* Opens zip file from stream */ int32_t mz_zip_writer_open_file(void *handle, const char *path, int64_t disk_size, uint8_t append); /* Opens zip file from a file path */ int32_t mz_zip_writer_open_file_in_memory(void *handle, const char *path); /* Opens zip file from a file path into memory for faster access */ int32_t mz_zip_writer_close(void *handle); /* Closes the zip file */ /***************************************************************************/ int32_t mz_zip_writer_entry_open(void *handle, mz_zip_file *file_info); /* Opens an entry in the zip file for writing */ int32_t mz_zip_writer_entry_close(void *handle); /* Closes entry in zip file */ int32_t mz_zip_writer_entry_write(void *handle, const void *buf, int32_t len); /* Writes data into entry for zip */ /***************************************************************************/ int32_t mz_zip_writer_add(void *handle, void *stream, mz_stream_read_cb read_cb); /* Writes all data to the currently open entry in the zip */ int32_t mz_zip_writer_add_process(void *handle, void *stream, mz_stream_read_cb read_cb); /* Writes a portion of data to the currently open entry in the zip */ int32_t mz_zip_writer_add_info(void *handle, void *stream, mz_stream_read_cb read_cb, mz_zip_file *file_info); /* Adds an entry to the zip based on the info */ int32_t mz_zip_writer_add_buffer(void *handle, void *buf, int32_t len, mz_zip_file *file_info); /* Adds an entry to the zip with a memory buffer */ int32_t mz_zip_writer_add_file(void *handle, const char *path, const char *filename_in_zip); /* Adds an entry to the zip from a file */ int32_t mz_zip_writer_add_path(void *handle, const char *path, const char *root_path, uint8_t include_path, uint8_t recursive); /* Enumerates a directory or pattern and adds entries to the zip */ int32_t mz_zip_writer_copy_from_reader(void *handle, void *reader); /* Adds an entry from a zip reader instance */ /***************************************************************************/ void mz_zip_writer_set_password(void *handle, const char *password); /* Password to use for encrypting files in the zip */ void mz_zip_writer_set_comment(void *handle, const char *comment); /* Comment to use for the archive */ void mz_zip_writer_set_raw(void *handle, uint8_t raw); /* Sets whether or not we should write the entry raw */ int32_t mz_zip_writer_get_raw(void *handle, uint8_t *raw); /* Gets whether or not we should write the entry raw */ void mz_zip_writer_set_aes(void *handle, uint8_t aes); /* Use aes encryption when adding files in zip */ void mz_zip_writer_set_compress_method(void *handle, uint16_t compress_method); /* Sets the compression method when adding files in zip */ void mz_zip_writer_set_compress_level(void *handle, int16_t compress_level); /* Sets the compression level when adding files in zip */ void mz_zip_writer_set_follow_links(void *handle, uint8_t follow_links); /* Follow symbolic links when traversing directories and files to add */ void mz_zip_writer_set_store_links(void *handle, uint8_t store_links); /* Store symbolic links in zip file */ void mz_zip_writer_set_zip_cd(void *handle, uint8_t zip_cd); /* Sets whether or not central directory should be zipped */ int32_t mz_zip_writer_set_certificate(void *handle, const char *cert_path, const char *cert_pwd); /* Sets the certificate and timestamp url to use for signing when adding files in zip */ void mz_zip_writer_set_overwrite_cb(void *handle, void *userdata, mz_zip_writer_overwrite_cb cb); /* Callback for what to do when zip file already exists */ void mz_zip_writer_set_password_cb(void *handle, void *userdata, mz_zip_writer_password_cb cb); /* Callback for ask if a password is required for adding */ void mz_zip_writer_set_progress_cb(void *handle, void *userdata, mz_zip_writer_progress_cb cb); /* Callback for compression progress */ void mz_zip_writer_set_progress_interval(void *handle, uint32_t milliseconds); /* Let at least milliseconds pass between calls to progress callback */ void mz_zip_writer_set_entry_cb(void *handle, void *userdata, mz_zip_writer_entry_cb cb); /* Callback for zip file entries */ int32_t mz_zip_writer_get_zip_handle(void *handle, void **zip_handle); /* Gets the underlying zip handle */ void* mz_zip_writer_create(void); /* Create new instance of zip writer */ void mz_zip_writer_delete(void **handle); /* Delete instance of zip writer */ /***************************************************************************/ #ifdef __cplusplus } #endif #endif sight-25.1.0/3rd-party/minizip/unzip.h000066400000000000000000000004601503402212300175270ustar00rootroot00000000000000/* unzip.h -- Compatibility layer shim part of the minizip-ng project This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_COMPAT_UNZIP #define MZ_COMPAT_UNZIP #include "mz_compat.h" #endif sight-25.1.0/3rd-party/minizip/zip.h000066400000000000000000000004521503402212300171650ustar00rootroot00000000000000/* zip.h -- Compatibility layer shim part of the minizip-ng project This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #ifndef MZ_COMPAT_ZIP #define MZ_COMPAT_ZIP #include "mz_compat.h" #endif sight-25.1.0/CHANGELOG.md000066400000000000000000013770721503402212300145370ustar00rootroot00000000000000# sight 25.1.0 ## Bug fixes: ### build *Build error on clang because of data signals.* *Install of Ogre and Qt6 plugins.* ### ci *Remove specific linux build job for draft merge requests.* This enables the coverage deployment also for draft merge requests. ### core *Wrong optional channel name substitution.* *Change update loop order in instrinsic calibration.* * introduce dedicated service to generate the 3d model of the chessboard, to avoid update sync issues *Do not stop registered services twice.* - Some services handle their services on their own. The registering service doesn't handle start so we want to at least be smart about the stop and do it only when needed *Various sequencer and activity corrections.* First, a new object validator that verifies if an object is empty. For now it handles `sight`::data::image``and `sight::data::point_list`. It is likely to be extended to support more object types in the future. Then some cleaning was required in the configuration manager to make recent features compatible with activities. For instance, we require activity objects to have a real unique identifier that matches the requirement names. This is mandatory to get those objects work with the recent `sight`::ui::module_qt::settings``service. *Make workers unique between XML configurations.* When using the `worker="..."` XML tag in a service configuration, it was possible to reuse the same workers as other configurations. However, each configuration creates and destroys its workers. We could end up with zombie services that could not be executed because their worker was destroyed. *Be more clever when parsing an update_sequence.* * store if a start or a stop slot is called on a sequence * if a start is sequenced, other slot can be called * if a stop is sequenced and other slot called afterward, stop the sequence ### filter *Remove threading in mesher, potential source of crash.* *Reset output slice in plane_slicer when source image is empty.* ### io *Add missing serializers.* *Remove deprecated CUDA code for nvjpeg2000.* CUDA 12.9 deprecated (well throw an error when using them) some functions used to convert from/to interleaved to/from planar image format (rgbrgbrgb... ⇿ rrr...ggg...bbb...). This is required for nvjpeg2000. Without the fix, saving a DICOM with CMake variable `SIGHT_ENABLE_NVJPEG2K=ON`, will fail with a cryptic `The function "nppiCopy_8u_C3P3R" failed: -215` message. *Fix PDF writer.* *Build error with niftiio.* *Add serialisation for fiducials series.* ### ui *Corrupted preferences file resets preferences.* Instead of only throwing an exception when the preferences file is corrupted, we gracefully reset the preferences file. *Use graphic data for tiled sparse image to determine the index.* *Rework screen placement for multi window apps.* * bugs arround fullscreen on linux, hide/show a fullscreen window doesn't restore fullscreen state * initial fullscreen of the first QMainWindow wasn't working, use a QTimer to repost the event * screen index are now saved into preferences *Calibration activity.* * use loop sequence in both intrinsic and extrinsic calibration * fix display of image and chessboard detection *Maximized windows cannot be restored back from preferences.* - when a windows state is stored in preferences and is restored, the window is too big and cannot be manipulated. - fixing also some strange behavior with maximization / fullscreen *Avoid deadlock in the progress_bar when job dies on the Qt thread.* *Settings widgets alignment.* - Relayout, fix spacings / margins, cleanup, fix bugs so that the settings widgets are correctly displayed. This is not perfect (the vertical mode of checkbox is not fully centered ...because the widget is not symmetric!), but this is an overall improvement. *Remove unneeded borders/padding/margins.* *Set initial value of combobox settings when range changes.* *Signal shortcut state propagation at start.* - add an example of usage for this service in the tuto14GUI *Ensure the application and GUI test is correctly closed.* This _should_ stabilize GUI tests a bit. This roughly fixes a kind of race condition in the GUI test start and stop code. When test instructions are posted in the GUI thread (because we must do that to manipulate GUI stuff), mutexes are used to allow the test thread to wait until the current test instruction is processed on the GUI thread event loop. This mechanism works well, ...until the last window is closed, which exit the application. In that case, the GUI event loop is stopped straight, which is not problematic most of the time in a real application, but unfortunately in a GUI test, if something that should have been processed is still in the event loop, like the synchronization mutex unlock, the calling thread will simply expire, making the test fail. This MR try to work around that, we try to avoid closing the window in the GUI event loop, we try to close _ALL_ window, including modal dialogs, and we try to flush the GUI event loop before exiting the test thread. *Remove cast to QDockWidget in cardinal layout.* ### viz *Add properties to move and orient grid adaptor.* *Division by zero in histogram computation.* *Shader parameter creation with multiple adaptors.* Shader parameter objects created by the material adaptor were unintentionally deleted if several adaptors use the same reconstruction. These objects must actually be shared. *Ensure the scene node is properly updated in the text listener.* *Clipping planes computation.* *Volume render image validity verification.* *Remove widget visibility check in point_list adaptor.* The visibility of the widget should no longer be a concern for the adaptor with our new update mechanisms. In the current state, it can lead to miss object state changes. *Error in selected material pass.* We need to set the material permutation before generating any subsequent rendering pass, otherwise these passes, like the selected material, may end-up with the wrong vertex/fragment program. *Optimize updates for negatoscopes when only pixels change.* The 2D and 2D negatoscopes code was also widely refactored to share as much code as possible; this will make future maintenance easier. *Increase minimum OpenGL widget size to avoid crash.* *Positionning of the negato 2d camera was wrongly computed.* ## Enhancement: ### core *Pop_front an element from collections in data manager service.* *Add an option to ignore stopped services in update sequences.* An option was added in update sequences to ignore stopped services. This way, it is possible to include optional services in update sequence without reporting any error and above all without stopping the whole sequence. ```xml ... ``` *Add a validator to test if string_serializable contains "true" or integer values.* *Make object validator generic, add service to validate.* A new service `sight`::module::data::validate``was implemented to validate objects in a generic way. Here is an example to check that an image is filled: ```xml ``` It provides the following signals in XML, which can be connected to your application logic: * valid() * invalid() * is_valid(bool) * is_invalid(bool) Also, a new validator `sight`::data::validator::has_fiducials``was implemented to check if a series contains at least one fiducial. *Add service to extract activity objects.* A new service to extract activity objects was added. This can be useful for extracting objects in an application's main configuration, for instance. ```xml ... ``` *Add a new service to reset any data.* So far, we have had no service to reset any data object to the default. We used a `sight`::data::module::copy``service with a default "empty" object to achieve that. The new service `sight`::data::module::reset``does this in a simpler way, because empty objects in XML are thus no longer necessary. ```xml ``` ### io *Make the joystick configurable.* - Joysticks will be identified by an alias, (currently: "left" or "right"), included in the "device" structure sent in all events - Same for the axis, in motion and direction events, with the proposed aliases: - for translation: `tx`, `ty`, `tz`. - for rotation: `rx`, `ry`, `rz`. - Mapping the Joystick ID and the alias will use an arbitrary default (0=left, 1=right on Windows, the opposite on Linux), but a `set_joystick_alias(ID, alias)` will be available to change the default (by inviting the user to click on the "left" joystick...). - Mapping the axis alias, will depend on the device (for now only space mouse will be supported :smile: ), with the hope this is cross-platform (if not there will be an #ifdef). - xml configuration should look like: ```xml ... ``` In this case, the "joystick_button" button bar will be controlled by the "left" joystick. Changing "left" joystick horizontal axis (rx) will change the currently highlighted button, while pushing it (tz), will "click" on it. *Speedup image session reading/writing.* - Fix large file (>2GB) support - use low level nifti *Joystick support.* - Updated the docker image and the sight-gitlab references - Implemented `sight::io::joystick::interactor`, `sight`::io::joystick::detail::event_loop``and a module `sight::modules::io::joystick`. #### sight`::modules::io::joystick`module Controls the global joystick `event_loop` instance. Basically, it starts it when `sight::modules::io::joystick::plugin::start()` is called and stops it on `sight::modules::io::joystick::plugin::stop()`. Once started, registered `interactor` will start receiving joystick events. There is a unit test (`sight::module::io::joystick::ut`) which demonstrates basic usage, the support of module unloading, etc.. #### sight`::io::joystick::detail::event_loop`class This is where the whole implementation is. It is a singleton that uses a timer to poll for SDL events on the main thread. When one is catch, it is simply forwarded to the registered `interactor` with specific callback functions. It implies that all callback functions are executed on the main thread, so you may have to protect the access if your service runs on a specific worker. The code of `instance()` could have been simpler, but, since we are dealing with posted tasks on the main thread, special care has been needed to let the instance die without making waiting tasks crash. #### sight`::io::joystick::interactor`interface This register / unregister automatically to the `event_loop` any class that inherits from it. Then, these callbacks will be available: ``` c++ void joystick_axis_motion_event(const axis_motion_event& _event); void joystick_axis_direction_event(const axis_direction_event& _event); void joystick_hat_motion_event(const hat_motion_event& _event); void joystick_ball_motion_event(const ball_motion_event& _event); void joystick_button_pressed_event(const button_event& _event); void joystick_button_released_event(const button_event& _event); void joystick_added_event(const joystick_event& _event); void joystick_removed_event(const joystick_event& _event); ``` All callbacks use "event" structures, which should provides all needed information (device name, index, guid, axis, buttons, ...). Indeed, almost all of what is available from SDL is forwarded. Additionally, it is possible to list all attached devices with `devices()` and to temporary stop receiving event with `block_events(bool)`. > `joystick_axis_direction_event` is derived from `joystick_axis_motion_event`, but instead of sending the axis values on every change, it converts it to "up" or "down", and is only trigered once, until the axis goes back to neutral. ### ui *Combslider improvements.* - can now send the value and not only the index when attached to a data::integer - remove " " (space) as a sepparator for list of item, so we can have space in combobox items - small unrelated improvement for tracker interface - synchronizer can now accept delay defined from a property *Add animation on tickmarks widget.* *Joystick support for settings sliders.* This adds joystick support for sliders in settings. #### Joystick and event_loop Using the constructor and the destructor to register interactors to the event loop was buggy, since hidden services that implement `interactor` interface are created but should not react to joystick events since they are not really "started". So, the API changed a bit to allow explicit registering to the event loop with two functions: `start_listening_joystick()` and `stop_listening_joystick`. You must explicitly call the two when you want to receive or stop to receive joystick events (usually in the `starting()` and `stopping()` functions). An "auto-repeat" feature was also added to the joystick event loop. The `joystick_axis_direction_event` will be repeated, until the axis has returned to the dead zone, 2 time after 500ms, then 2 time after 200ms, then 4 time after 100ms, then 8 time after 50 ms, then 16 time after 25 ms, then every 10 ms. > this scheme is purely arbitrary, and can be discussed and adapted. #### Settings Sliders from settings has been updated to support the joystick (Button bar already supported it). To demonstrate that, `ex_settings` has also been updated. All 3 sliders (one "double slider", one "int slider" and one "comboslider") should be modifiable by the first joystick and 4th axis. The button bar is modifiable with a second joystick with 4th index. Button_bar has been updated to "click" the selected buttons, using the 2nd axes of the second joystick (push it !) ### viz *Add a shader that extrudes lines and applies a fog effect.* *Add a ghost shader, improve material handling in adaptors.* ## Refactor: ### build *Remove explicit std::filesystem dependency.* Compilers now all support std`::filesystem`by default ### core *Explicit channel passing in XML.* Channels now have a specific XML tag to help differentiate them from other parameters: ```xml ... ... ... ... ``` Other changes: - like objects, the channels can be specified as optional with the attribute `optional`. - the standard parameter uses the `param` keyword when passed to the `config_launcher` to be consistent with the section of the callee configuration. - the group data object is now designated as "object" instead of "data", also to make it more consistent with the section. *Snakify all tutorials.* The tutorials are the showcase of Sight and should lead by example *Autostart services in XML and simplify config launcher.* Services are now autostarted in XML. Since most services required to be started, it is simpler to start them automatically. For the rare services that do not start, you can use a new property `start`: ```xml ``` You can then start/stop the service via slots, as usual. But of course, since this is a property, you can also use a variable: ```xml ``` Other changes: * Renamed the `sight`::app::config_controller``into `sight::app::config_launcher` * Replaced all usages of `sight`::module::ui::config_launcher``with `sight::app::config_launcher` * Removed `sight`::module::ui::config_launcher``service *Sight::data::point inherit from stl container.* *Sight::app::config_controller rework.* `sight`::app::multi_config_controller``has been very useful, but having another service to launch configuration is not great. Actually `sight::app::multi_config_controller`, comparing to `sight`::app::config_controller``only allowed: 1. to dynamically change the name of the configuration 2. to associate a key to a configuration name To get rid of `sight::app::multi_config_controller`, we first modify `sight`::app::config_controller``to allow to set dynamically the configuration thanks to a property: ```xml ... ``` Second, to associate a key to a configuration name, we introduced a new generic service that associates an input property with another. It allows mapping any string_serializable data to another string_serializable data, like of course key strings to key string values, but also colours to strings, booleans to strings, etc... ```xml ``` To sum up, instead of writing: ```xml ... ``` You have to write now: ```xml ... ``` misc: apply review suggestion ### io *Remove start_target_camera slot in grabber_proxy.* This slots was clearly duplicated in the API comparing to start_camera. A new configuration setting was added to select a given grabber according to the camera identifier. ### viz *Deprecate some more attributes of viz::scene3d::render.* # sight 25.0.0 ## New features: ### build *Update vcpkg package.* Allow optimized debug ### ci *Use build image from sight-gitlab templates.* ### core *Image orientation support.* This change ensures image orientation is used in filtering and visualisation. To help with the process and to factorize some code, the `sight`::data::image``class now has two functions `data::image::image_to_world()` and `data::image::world_to_image()` that convert image coordinates from/to world coordinates. They use internally `data::image::m_orientation`, `data`::image::m_origin``and `data`::image::m_spacing``and they are templated, so you can use any container that holds 3d coordinates, without having to use `{x, y, z}` construct to convert from/to Ogre, ITK, ... Notable modified classes/helpers: - volume_renderer - ray_tracing_volume_renderer - scene - negato3d - negato2d - negato2d_camera - medical_image - plane - image_extruder - resampler - ITK (reader, writer, converter) - VTK (reader, writer, converter) - image_center - propagator - slice_index_position_editor - ruler - shape - point *Add orientation attribute in image / image_series.* - Add unit tests - Fix wrong return type in matrix4::position() and matrix4::orientation() *Improve timer precision on Windows.* This uses the native `timeBeginPeriod` function to force timer precision on Windows to be around the millisecond, otherwise, we can't go down below 1/64s. *Add camera parser for default video path.* - new camera parser that uses either sight resources path or an absolute file system path to default the camera to video file playing - update ex_video_recorder to use that parser feat(test): unitary test of camera parser *Logging rewrite.* #### No separate `sight_log` process anymore Everything is self-contained in the current `sightrun` process. The logging is, by default in debug builds, in full synchronous mode, from end-to-end. In case of a crash, it is guaranteed to have everything sent to log to be readable back (with a possible, but improbable, loss of 16 bytes when encryption is used). However, since the full synchronous mode is blocking, there may be a significant performance hit, since the compression and the encryption are done in the same thread as the caller, which in turn, also block all other threads, if logging occurs during the writing. To overcome this, in release builds, I propose to use an asynchronous mode, which do the "real" writing in a separated thread (managed by boost::log). This is a bit less safe, but since RAII model is used, there is still a very good chance that everything is well written, even in case of fatal exception. And, to be honest, it was a bit the same level of safety with the previous separated process, as the stream communication between process was also buffered. > :warning: zstd < 1.5 (for example, on ubuntu 22.04..) have a "flaw" that makes the block size to be "big", meaning we may loose a big chunk of data if interrupted (a block must be complete to be uncompressed). zstd >= 1.5 (like the one used in our VCPKG) ensure the "flush" command to complete a block, which is done on each log lines, ensuring we only "loose" the last lines. This explain why the associated unit test `crash_test` is somewhat "tolerant". > Of course we can discuss if we should be in asynchronous mode in release or always use the "safe" synchronous. #### No CMake `SIGHT_ENABLE_ENCRYPTED_LOG` anymore Log encryption is now enabled by `sightrun --encrypted-log` switch. However, the log will not be "really" encrypted, but only compressed, until a global password has been set, using, for example, the preferences dialog. See the `ex_acitivies` sample, especially the `CMakeLists.txt`, to learn how to ask a password from the user. As soon as the password is set or change and if the application has been started with `--encrypted-log` switch, a new log file "xxx.1.log.aes" will be created and the previous log "xxx.0.log.aes" will be relocated, recompressed and re-encrypted with the new password. #### No CMake `SIGHT_DEFAULT_PASSWORD` variable anymore SIGHT_DEFAULT_PASSWORD was used to pass a compile time "default" password, used to encrypt things without having to force the user to enter one. Even if the password was not stored in clear text in the binary, this was rather weak and confusing, so it has been removed. Consequently, there is no `password_keeper::has_default_password()` and `password_keeper::get_default_password()` have also been removed. #### spy_logger is no more only "global" You can now instantiate your own private logger using `spy_logger::make();`. The unit test `multiple_logger_test()` demonstrates the usage. You will not be able to use the sight macros (SIGHT_ERROR, ...) as they indeed use the global logger, but direct call to `spy_logger::error()`, `spy_logger::fatal()`, etc. The global spy_logger is now a global reference stored in `sight::core::log::g_logger`, available as soon as the `core` module is loaded. #### Archive_extractor now handles encrypted log A special extraction function `spy_logger::extract();` has been added, which is used in `sight`::module::io::zip::extract``to manage our new log format. Since the logs are no more a plain zip file, and because we want to recover truncate files, third party tools like 7zip are no more able to decompress them. If there is only compression (empty password) used, then it should be readable by any tool that reads zstd compressed file. #### Unit tests improvement The logging tests have been rewritten and improved ...and fixed, as there were some race condition, left test materials, hang process, etc.. Almost 100% test coverage on the related files. #### General fixes and improvements - Now, we test the return values and error codes from OpenSSL encryption functions → yes, some "features" that were based on "bugs" were fixed. - Proper OpenSSL initialization, and error management - Split the spy_logger implementation in `libs\__\core\log\detail\spy_logger_impl.hxx` and `libs\__\core\log\detail\stream_sink.hxx`. The stream_sink.hxx implements a `boost/iostreams/stream` which can be used elsewhere to have an ostream with transparent zstd compression, with aes256 encryption. - Clang-tidy fixes on old files - proper guard for `#include ` which generate a fatal warning on Windows - Some cleanup *Raise an error when an unknown service is used in connections.* *Add missing properties in image series fiducials.* * Enable admin requests in unit tests: useful for unit tests requiring specific devices * Add additional dicom fields to be used * Forward missing fields from image_series to the associated fiducials *Introduce properties in services.* Services rely a lot on simple data such as algorithm parameters, using simple types such as numbers and strings. Manipulating these parameters always ends up requiring the same features: - initialization of the parameter in the XML configuration of the service, - dynamic update of the parameter through a slot, - persistence of the parameter during the runtime. To simplify the coding of these three features, we introduced the concept of service *properties*. For a complete description of this new exciting development feature, pleaser refer to https://sight.pages.ircad.fr/sight-doc/SAD/src/Properties.html *Add DICOM fields to manage source image.* Add functions to DICOM API in `series` to manage DICOM `SourceImageSequence` which allows making `DERIVED` image (ex: a reconstructed volume derived from US frames sequence): - `[s|g]et_image_type()`: Sets/gets the `ImageType` of the series. The `ImageType` is a `\` separated string with the following format: `[Pixel Data Characteristics:ORIGINAL|DERIVED], [Patient Examination Characteristics:PRIMARY|SECONDARY], [modality specific:xxx|yyy|...], [zzz], ...`: See ImageType (0008,0008) DICOM tag. The exact definition is modality dependent, but the two first elements ([ORIGINAL|DERIVED] and [PRIMARY|SECONDARY]) are fixed. This allows to set a reconstructed volume as `DERIVED` and the original frame sequence as `ORIGINAL` and, optionally, other attributes. - `[s|g]et_referenced_sop_class_uid()` and `[s|g]et_referenced_sop_instance_uid()`: Sets/gets the referenced series. Both are required for a valid DICOM. - Preliminary work to simplify the fiducials DICOM API: code factorization (more can be done like introducing high level fiducials functions in `has_fiducials`) *Add new equipment and ultrasound properties to dicom series.* ### io *Add support for DicomWeb in sight_viewer.* While sight_viewer is now the new main application delivered with Sight, it still lacks some features. For instance, it can connect to a PACS using DIMSE-C, but not through DicomWeb. *Use properties instead of xml configuration for readers/writers.* Base reader and writer has been improved to support , , and windowTitle XML configuration as files, folder, resources and window_title properties. The XML configuration has been deprecated (but kept) and a unit test was added. Readers and writers that used the windowTitle configuration (mainly by defining a different "default"), were updated to use window_title property. This implies passing the class "default" to the super constructor. Breaking change(s) window_title property required a slight change in the API: using the super constructor for the class default and use *m_window_title to access the property when calling open_location_dialog(). *Add some log about nvJPEG2000 usage.* ### ui *Specify screen index for multiple frames.* - Add a `` attribute to force the displaying on a particular screen. The index is the screen index as returned by `QGuiApplication::screens()` which is the same as the Windows `display number - 1`. - Most shortcuts (menus, toolbars) are now "global", they work regardless of the current focused frame. The "contextual" ones (speed dial) are not, since there are ...contextual. - The `ex_progress_bar` example has been modified to demonstrate the use: The toolbar is on a frame on a screen and the display on another. Shortcuts "1", "2", "3" launch the according jobs. *Update noctura tab and toolbox compability.* - Tab widgets are now transparent and hand the noctura theme correctly - Toolboxes have now a configurable expand icon color - unify hove/pressed effects on buttons *Swap display of the slices indexes and positions upon clicking on the label.* ## Bug fixes: ### build *Add CMake variables to specify vendor name and url for Qt and packaging.* Added variables SIGHT_APP_VENDOR and SIGHT_APP_VENDOR_URL, use them both for the QCoreApplication (runtime) and also for CPACK_PACKAGE_VENDOR (installer). *Visual studio 2022 v17.12.0 compatibility fix.* Rename `enum sight`::data::image::pixel_format``to `sight`::data::image::pixel_format_t``to avoid ambiguities with ` sight::data::image::pixel_format()` It also makes VS Code happier. *Auto load the tested module, ensure correct naming of the target.* The naming of the unit test target is used to deduce the name of the module to be tested, which is then auto loaded. This mechanism was broken for sub-repositories. It is now fixed, and if the naming of the target is not correct, an error is reported to the user. *Gcc 14.* *Remove obsolete DICOM transfer syntax.* This allows Sight to build with DCMTK version >= 3.6.8. by removing the support of two deprecated DICOM transfer syntaxes: * `UID_RFC2557MIMEEncapsulationTransferSyntax` * `UID_XMLEncodingTransferSyntax` *Support for ubuntu 24.04.* ### ci *Do not exclude large files in code coverage.* *Deploy doxygen and coverage in a single job.* The pages deployment must be done in a single job, otherwise they can delete each other content. *Exclude large files from gcov to avoid a crash.* It appears we suffer from a known bug with files larger than 9999 lines. Until we upgrade gcov, we exclude those big files. ### core *Make default optional parameters id unique.* This fixes a crash when launching twice a XML configuration with an optional parameter, without passing the optional parameter. *Gui test launcher script always returns success.* This was indeed a problem with the windows gui test launcher, because of delayed expansion feature not enabled. In short, in a batch script, testing a "variable" must be done using enclosing ! not % and delayed expansion must be enabled with setlocal enabledelayedexpansion, otherwise, the variable value is evaluated BEFORE script execution, resulting returning 0 in all cases, even when the test fails. *Improve handling of connection exceptions in proxies.* Now, when an incompatibility is found between the signal and the slot, the exception is caught in the proxy and an error is returned. The config can still run, but it will not crash when closing. Also, an important bug was found when trying to match signal and slot signature. We can indeed omit part of the arguments of a signal when connecting a slot, but of course from the right. We can connect for instance: - (int, int) -> (int) - (int, int) -> () But of course we refuse the inverse (we throw `sight::core::com::exception::bad_slot`): - (int) -> (int, int) - () -> (int, int) This mechanism works recursively but was broken when argument types were different like: - (int, int) -> (string) This led to a stack overflow. :confused: This is now fixed and throw `sight`::core::com::exception::bad_slot``as well. *Setup the correct profile version for each app.* The version of the application is now well generated in the `profile.xml` of each app. Thus, it is now safe to use: ```cpp const auto profile = sight::core::runtime::get_current_profile(); const software_version = profile->version(); ``` It is even recommended to use this instead of passing the APP_VERSION through the XML or by a C++ compiler macro. *Dicom image position and orientation & missing accessors.* - Improve quality of life when using matrix4 *Factorize and sanitize object id name.* * Implements a `core::id::join(...)` that returns a concatenation of elements (strings, integer, anything with a `<<` operator), separated by a separator defined in `core`::id``class. We choose `-` as separator * implements a `core::id::base_id()` that returns the last element of the `id` string * removes all `get_id().substr(get_id().find_last_of('\_') + 1)` and replace them by a simple `core::id::base_id()` call * move `core::tools::id`, `core::tools::object`, `core`::tools::compare``to `core` namespace. ### geometry *Dead lock in sight::module::geometry::switch_matrices.* ### io *Crash in sight::module::ui::io::selector.* Always call configure() for sub-services otherwise properties are not initialised properly. *Dicom compatibility patch for US volume.* * Recreate image position patient in all cases, even if unneeded ### test *Sight_calibrator_uit frequent failures.* Several fixes were brought: 1. A Qt error was fixed when closing the chessboard window. This did not improve the situation but was needed anyway, 2. The board square size was unintentionally set to 0.5 instead of 20 in https://git.ircad.fr/sight/sight/-/merge_requests/1138. This was, I think, the cause of the increase in the occurrence of the test failure, yielding values of around 180 or > 400. 3. Sometimes, we still get cx=341 instead of 352. This might come from the input detection that slightly differs. We don't understand the reason, but since this is rather an issue of the test than an implementation error, we chose to increase the tolerance of the test to accept this value of 341. ### ui *Configurable toolbox icon color on windows.* *Border and caption for line layout.* The fix consists in an additional widget layer, that will hold the layout attributes (mostly, the content margins), that should not be wiped out by children. To allow that, the `widget` class has been extended to manage "compound" widgets, when the container widget exposed to children, also have an intermediate parent widget. *Delay removal of jobs in the progress bar to avoid dead locks.* *Recompute toolbar size / layout when actions are modified.* There was a bug with Toolbars in overlay layout, which prevented to "reserve" the right size. This has been "fixed" / "mitigated" by calling `QWidget::adjustSize()` at strategic places. Still, normally we should not have to do this, even for overlay. Anyway, the workaround should not have any side effect. *Forward events from overlay to scene.* When using an overlay, events that hit the non-visible/transparent parts (like the "toolbar") will be caught by the overlay, making the behaviour strange for the user who thought he was interacting with the scene. This fixes that by forwarding the events, back to the scene. This was non-trivial because of the nature of the overlay, as the scene and the overlays are "sibling" widgets, not parent/children. Qt will always send events to the top-level widget at a given position, but in our case, the top-level widget, the overlay, may be "transparent" at that position, and we needed to trick Qt into believing the overlay is indeed not there so it could hit the underlying scene. Also, make the container transparent to mouse events if no children are visible in the layout. This is the only known way to make multi-touch events (pan, pinch) work *String property was reset when calling update_enum_range.* *Slice mode display reset when moving slide.* *Use decimals in min and max labels of double sliders instead of the scientific notation.* *Various fixes in the transfer function and image code.* - move the PACS configuration in a module so that it can be used in other apps - the image is now an input and not an inout in the `module`::ui::qt::image::window_level``service - adjust the range of `module`::ui::qt::image::window_level``depending on the image range - remove the static label in the color widget of `module::ui::qt::settings` - `sight::data::helper::medical_image::get_min_max()` now returns its values as a pair to avoid a wrong initialisation in the caller code. - fix mixed rendering with binary transparent objects *Always send checked signals at start.* *Slider frame indication.* A problem was encountered where the vertical lines displayed on the slider groove became shorter than usual after numerous fiducials were applied to the image. This fixes this behaviour. ### viz *Ensure representation mode is properly set when creating a material via the mesh adaptor.* *Blending between layers.* The blending between layers was incorrect, and alpha blending was applied twice. This made transparent objects look darker proportionally to the number of layers. *Drift in trackball and predefined interactors.* Two drift issues were fixed in camera management: * the trackball interactor used to drift when combining a mouse/touch movement in width and height. This was fixed by changing the up vector to stick with the up vector of the world and by applying the first rotation on the second rotation axis, based on the right camera vector. This was also fixed in the predefined interactor by sharing the code. * the predefined interactor, on top of that, drifts when following a target. A quick workaround is proposed, that disables, by default, the following of the target orientation. Thus the interaction only follows the translation part. This behaviour looks anyway closer to the expected result. *Make material selector working again.* *Wrong initialisation value in resource manager.* This fixes a regression introduced in c7a5a36919878bc46ecaac00c383b3783e3125af and prevented some graphic resources to be updated properly. *Shape adaptor refactor / fixes.* - Only use fiducial data from source image_series, not intermediate point_list - Remove opencv "drawing" and only rely on ogre - Simplify / remove unneeded code *Don't reset the current group when removing a group.* Removing a group from `fiducial`::point``no longer resets the current group to the default value, unless we're removing the current group. This also fixes an annoying XML warning. *Restore point resizing functionality and improve point group management.* ## Refactor: ### core *Remove landmarks support of point adaptor and fix numerous bugs.* Fixed: - illegal access to deleted objects - memory leaks - visibility badly applied - desynchronization of ogre object and fiducials - race conditions on signal / slot handling - too many wrappers and clumsy code *Update and enhance ruler adaptor structure.* - The usage of point_list has been removed. We now only use a vector of ogre elements and the associated ruler id. With this id, we can find the desired ruler in fiducials and modify or remove it. - The dashed line is now working correctly. - Rulers should always be displayed according to the current slice. This includes rulers that have spheres on 2 different slices. If the current slice is in between, we will also display these rulers. The part of the line that is behind the current slice is displayed as a dashed line. This allows us to manage the ruler display the same for both 2D and 3D contexts. - Now we are registering the color of the ruler. By doing this, the indications on the slider will match the color of the ruler. We are also handling cases where rulers are created without color and outside of the adaptor. We register the assigned color by the adaptor during updating(). - When we enter in interaction mode (activate_tool), we draw rulers with larger spheres. This makes it easier to grab the sphere when using a touch screen. - All deprecated services associated to old distance signals have been deleted. *Reduce the usage of base_object where it is really needed.* ### geometry *Modernize geometry libraries.* Geometry libraries were refactored. - `sight`::geometry::glm``becomes `sight::geometry` - `sight`::geometry::data``now only contains functions relative to `sight`::data``objects Several types were renamed or moved: * `fw_matrix4x4`: removed, replaced by `glm::dmat4` * `fw_plane`: replaced by `sight::geometry::plane_t`, moved all related code to `sight::geometry` * `fw_line`: redefined either by `sight`::geometry::ray_t``or `sight`::geometry::line_t``which both refers to `std::pair`. Moved all related code to `sight::geometry` * `fw_vector_index` and `fw_vector_position`: all related code was removed Also `sight::data::image::world_to_image()` and `sight::data::image::image_to_world()` were moved from `sight`::data``to `sight::geometry::data`. They were internally rewritten to be more "SIMD" friendly and new functions were added to get the raw transforms: - `sight::data::image::world_to_image_transform()` - `sight::data::image::image_to_world_transform()` This is especially useful in tight loops to avoid recomputing these matrices for each point/voxel. ### ui *Move test dialog implementation to a test library.* ### viz *Modernize material management.* `sight::viz::scene3d`::material``is refactored and replaced by three new classes: - `sight::viz::scene3d`::material::generic`:`generic class to handle any material. It provides functions that allow to set uniforms and a function to set textures, in all techniques where they exist. This solves the initial problem of the intrusive tests for OIT and thus avoids a priori knowledge (declaration, etc...) about the generated pass. If the uniform or the texture exists, set it, otherwise, it silently does nothing. However, if nothing is set amongst all techniques, an error is logged, since it should not happen, normally you would expect to reach at least one technique. - `sight::viz::scene3d`::material::standard`:`allows using our "Default" material, and manages the permutations to handle different shading modes, normals display, etc... - derive this into `sight::viz::scene3d::material::r2vb`: specific version to handle per-primitive colours and triangles generation for quads Also, most adaptors no longer use `sight::module::viz::scene3d::material_adaptor`. It should only be required when wrapping a real `sight::data::material`. Otherwise, `sight::viz::scene3d`::material::generic``or one of its inherited classes must be used. We also used the opportunity to sort out parts of the OIT logic. We grouped the generation of OIT material techniques from `material_mgr_listener` and the dynamic compositor passes generation into a new class `sight::viz::scene3d::compositor::manager::oit`. This allowed notably to share many hard-coded strings. Last we harmonized the identifiers for Ogre objects, using the `core::id::join()` function or a new function `viz::scene3d::adaptor::gen_id()` instead of concatenating strings manually. *Replace deprecated Ogre::SharedPtr by std::shared_ptr.* ## Enhancement: ### build *Migrate most linux build jobs to Ubuntu 24.04.* ### ci *Split doxygen and coverage deploy in two jobs.* ### core *Allow optional objects with defaults in configuration parameters.* It is now possible to pass optional objects with default values in XML configurations: ```xml ... ... ``` However, the keyword `optional` was already used before to pass deferred objects. To get something harmonized with local objects declaration, the `src` attribute is now deprecated and you must now use `deferred=true` at both places. Similarly, use `preference="true"` instead of `src="preference`. ```xml ... ... ``` *Allow to construct a color directly from a string.* *Simplify signal calls and blockers usage.* ## Description The API to send a signal has been simplified for all data objects, including properties. Indeed the `sight`::core::com::has_signals``interface brings four new functions: ```cpp template void emit(const signals::signal_key_type& _key, A ... _a) const; template void emit(com::has_slots* _caller, const signals::signal_key_type& _key, A ... _a) const; template void async_emit(const signals::signal_key_type& _key, A ... _a) const; template void async_emit(com::has_slots* _caller, const signals::signal_key_type& _key, A ... _a) const; ``` They allow any signal holder to send a signal, synchronously or asynchronously, with a one-liner. The signature with a `com`::has_slots``parameter allows to block all slots of the caller connected with the signal, preventing an infinite loop. Thus, instead of writing something like: ```cpp auto sig = object->signal(data::object::MODIFIED_SIG); core`::com::connection::blocker`block(sig->get_connection(slot(service::slots::UPDATE))); sig->async_emit(); ``` You can call: ``` object->async_emit(this, data::object::MODIFIED_SIG); ``` Besides this major change, the XML configuration parser has been improved to ensure variables substitutions inside data containers. Example: ```xml ``` Before this merge-requests, the example service could not find the `sub_object` because the variable substitution was simply not performed. *Honor auto-connections outside XML applications.* Auto-connections were correctly configured and honored only by the config manager, thus required, a XML configuration. This was annoying for unit-tests and overall, that was not the proper place to do it. This is now done directly in the service code itself. *Allow to call start/stop slots in updater.* Allow to call slot="start" or slot="stop" within an update_sequence. This ensure to call start/stop in a specific order. Example: ```xml ``` *Introduce explicit update loops.* Chaining service updates in Sight live applications has always been tough. To circumvent all related issues with event-based chaining, we introduce an explicit approach allowing to specify update sequence and execute them all at once. It used to be possible to specify which services to update **once** once after start: ```xml ``` This section is now modified and allows specifying these single-shot updates, but also the ability to define an update loop: ```xml ``` In this example, `service1` and `service2` are updated once after start, while `service3` and `service4` are continuously updated. The sequence `update_loop`is identified with an uid, so it can be started and stopped like any other service. Doing this, the update sequence is not dependent on the updated() signal. This is much more robust because this means any other, maybe unwanted updates triggered from somewhere else do not affect the sequence. This new syntax also allows chaining update sequences between XML configurations. As the manner of UI registries and `wid`, it is possible to reserve a slot for another update sequence defined in another configuration: ```xml ``` Note that here the `uid` of the sequence is omitted because no interaction is required inside this XML. The sequence is played automatically by the root updater after `service1::update()` and `service2`::modify``slots. It is also possible to execute services in parallel. Mixing sequences and parallel are allowed with the syntax: ```xml ``` In this example, `service1`updates first, then `service2`, the whole update sequence in the sub-configuration, and the sequence of `service3` and `service4` are executed in parallel. When they are executed, whether or not they are on different workers, `service5` is executed. The solution proposed above could be sufficient. However, managing `sight::viz::scene3d`::render``could become tedious. Correctly using auto-connections, and maintaining the list of all adaptors in the update can be tedious and error-prone. Developers usually expect that a single call to the \`update()\` of the renderer manages the update of the adaptors properly, which is the case in auto mode when we do interactive rendering, but not in manual mode, in "live" applications. To solve this, we introduced a generic update interface `sight`::core::updater``that will be inherited by the adaptors. This interface allows to defer the update of the adaptor required by their data. In manual mode, the adaptors just flag which part of their update process they should process. When the update is actually called, they will really perform it and unflag it. The render only requires to interrogate the adaptors before rendering to know if they need to update or not. To sum up, updating a generic scene in manual mode is now much easier and not error-prone regarding synchronisation. For example, if we imagine an application reading a video, extracting a feature and then display it in overlay over the video, the update loop could look like: ```xml ``` *Optimize configuration variable substitution.* Roughly 10X speedup, measured with a big xml "configuration": - Debug build: - Before: 419.446 ms - After: 42.868 ms. - Release build: - Before: 35.768 ms - After: 4.716 ms *Simplify passing objects as parameter in XML.* We can now pass objects as parameters without declaring them locally with `src=ref`: ```xml sight::module::config ... ``` ### filter *Properly re-init normal state in mesh and remove unused needle service.* *Add a slider to navigate along the reslice plane.* This adds a slider in each view of ex_image_reslice allowing it to slide in the direction of the reslice. In this example, the reslice transform is directly manipulated, so it is not really useful. However, in real applications, the reslice transform will likely come from non-editable data, such as a sensor location. In such cases, the slider allows us to explore around the reslice direction as if we had a real axis. A property was also added to centre the reslice instead of doing it from the bottom left corner. *Add support for image orientation in reslice.* The service `sight`::module::filter::image::plane_slicer``that allows to reslice an image with an arbitrary plane now supports images having an arbitrary orientation (not aligned with orthogonal axes). To demonstrate this, a new example `ex_image_reslice` was implemented. *Add a property in propagator to know if the mask is filled.* ### io *Store PositionMeasuringDeviceUsed dicom tag as enum.* ### test *Install UI unit test headers to allow testing of apps in client apps.* ### ui *Add pixel coordinates and viewport size to picking information.* *Remove specific fiducials colour generation for unit-tests.* *Add enable/disable feature on signal_shortcut.* when the service is "disabled" the configured key-sequence is ignored. The classic behavior is kept with the "enabled" mode. ### viz *Make view up of the interactors configurable.* The behaviour of the trackball interactor has recently changed, and the camera's up vector was hardcoded to -Y. It is now configurable both for the trackball interactor and the predefined interaction with an optional matrix, from which we extract the Y axis. *Add resliced mask to ex_image_reslice.* - Add option to prevent transfer function scaling to max texture size *Add preclassification support for negatoscope.* *Add ambient color in the lighting of volume rendering.* misc: p *Flag grids as static geometry to keep them in the background.* # sight 24.1.0 ## Refactor: ### core *Do not enable all modules by default.* Sight used to provide a mechanism to select the modules for an application. This is the `` tag of the `profile.xml`. A few years ago, it was decided, by simplicity, to enable all modules by default. Indeed, when an app is installed, the selection is somehow done thanks to the CMake dependencies. However, in the build tree, you may have access to modules your app does not depend on. This is not that tragic, but sometimes this is annoying because we realize we have a missing dependency against an XML configuration for instance only at install time. Also, we may have more extensions available (for instance readers) in the build tree than in the install tree. With this merge-request, only the modules that an app depends on (recursively) are enabled. ### filter *Modernize computed signal of filter service interface.* ### viz *Factorize redundant picking code.* ## Enhancement: ### filter *Rework propagation service.* ### ui *Add two options to configure the size and weight of text service.* `sight`::module::ui::qt``has two new options: - size (optional, default="14pt"): size of the font used in the label, as supported by 'font-size' QSS attribute - weight (optional, default="bold"): normal, bold or any value supported by 'font-weight' QSS attribute The weight option defaults to 'bold' to keep the default behaviour of the service. *Toggle visibility of toolbars with modify_layout service.* *Rework view maximization.* This fixes the standard maximization shortcut (F11 on Windows, on Linux it depends on the window manager) when a 3d scene is displayed. The 3D scene used its own QShortcut that took precedence over the one installed on the main frame. This also updates sight_viewer to use a more common maximization/restore behavior, but since the 3D scene real full-screen has also been removed, the tutorial "07" has also been changed to include a demonstration of the 3D scene full-screen. And last but not the least, a crash was fixed in Speeddial code, discovered while modifying `tuto07_generic_scene`. Another kind of "deadlock" (window that was never closed) was also fixed in gui tests (seen in sight_viewer_uit) ## Bug fixes: ### build *Properly forward script arguments when using privilege escalation on windows.* ### ci *Re-enable pch for windows.* - Fix vswhere not finding Visual Studio - make pch related files snake case ### filter *Lock the image later in the propagation to avoid deadlock.* ### io *Dicom writer fixes for US volume.* * This fixes some problems / implements needed features for writing US DICOM. * It also incorporates uid / date / time generation for study / series. * Series can suggest a file name and file path for DICOM files. *Add orthogonality test in matrix helper.* * create dedicated service to test validity of transformation matrix *Disable vti related unit test.* wait for an upstream patch of vtk9 see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1064762 ### ui *Crash when notifier class is already destroyed.* *Add missing log in progress bar.* *Force a repaint when swapping view in cardinal layout.* ### viz *Orthographic height on transformed camera.* *Make the compositor name unique.* ## New features: ### core *Add api to set/get thread name and use it in worker related classes.* ### ui *Sliders should indicate frame where a fiducial is placed.* The slider will visually update with vertical lines that match the color of the fiducials. These lines will indicate the frame and the slice where the fiducial is positioned, allowing users to interact with the slider to retrieve and view specific fiducials. *Allow to display the slice position in mm in slice_index_position_editor.* - Rename service configuration tags to snake case - Move service configuration tags: `orientation`, `display_axis_selector` and `display_step_buttons` to an attribute of a `config` tag - Add a `label` attribute to specify either the slice index (`index`) or position in mm (`position`) *Add a new progress_bar service.* The progress bar is now a standalone editor, which use the same API as the old `job_bar`. The progress bar can be placed anywhere, like any regular editor. Added features: - pulse mode - svg pulse mode - title and cancel button can be set visible or not ### viz *Update negato2d_camera to be used with 2d image.* * also add option to provide the same scaling behavior than video adaptor * add option to disable interfaction (touch, mouse, keyboard) * ruler removes all distances before adding new one at update # sight 24.0.2 # sight 24.0.1 # sight 24.0.0 ## Enhancement: ### build *Update vcpkg.* ### core *Add missing subscript operator in array_iterator class.* *Do not restart multi_config_controller when the config does not change.* *Improve series error management.* *Improve series error management.* ### doc *Update main README.md.* ### geometry *Add unit test for module::geometry::concatenate_matrices.* ### io *Avoid reetrance in update loops using synchronizers.* *Add a way to set the video grabber loop mode by a generic parameter.* Also override the new configuring(const config_t&) signature in tracker interface to allow implementations to override it as well. ### ui *Properly align qt::parameters sliders.* *Update icons.* *Add a button to reset preferences settings.* *Add an option to skip action signal emit at start.* *Apply new QSS theme on sight_viewer.* Some enhancements were also brought in the QSS itself. *Add the collapsible section widget in libs and provide an example and test on it.* *Minor UI improvements.* Simplify reset of points/regions in freehand crop by using single slot for reset. Correct the accordion menu icon placement. Others minor improvements on new ircad style QSS. *Enlarge the status bar.* *Modernize status editor service.* `module`::ui::qt::status``was modernized to be able to align it with toolbar buttons. In doing so, its configuration was revised to be more concise and meaningful: ```xml horizontal|vertical SCP Server TCP Server Stopped Tracking Started ``` Here are the changes: - the `count` is removed since it is useless and error-prone. - the `form` tag is removed since now the shape is always a circle. - if one wants only a single status, one just has to declare a single ``````. No need for a shortcut. - if one wants a status without any name, one can just declare an empty ``````. - `````` is added to allow specifying the location of the text *Add optional popup to warn the user when changing activity.* *Minor improvements for line edit and toolbars.* - update color when QLineEdit is disabled - toolbar theme update - add optional spacing for toolbars - remove useless property in lineLayout *Add support for vertical sliders in SParameters.* ### viz *Skip rendering request when an adaptor is not visible.* *Render mode policy should always be respected.* *Center the volume rendering around the visible area.* *Add support for ruler and shape fiducials.* *Add an adaptor to display a background grid.* A new adaptor that displays the grid, with a customizable size, elevation and color is now available: ```xml ``` ## New features: ### core *Set parameters with config slot.* *Add a slot to reset requirements in the sequencer.* ### geometry *Add a service to damp matrices over time.* ### io *Get and store itk direction in sight::data::image.* ### ui *New icons.* *Add multiple shortcut support on toolbar.* - disable auto repeat on all QActions of toolbar. *Add sound support for notifier service.* - Add three sound files .wav to the qt resources directory. - Notifier supports three sound notifications, on for each type of notification (Success, failure and info). - New bool is passed on notify signal to add or mute sound. - Update ex_notifiers to display a toggle checkbox for adding sound to the displayed notification. *Add fullscreen support with (f11, ctrl-f11).* *Add possibility to remove fiducial distances from the current slice.* *Uncancellable pulse_progress dialog.* *Allow requesting values set through preferences_configuration.* ### viz *Add slot to update color of text.* ## Bug fixes: ### build *Fully qualify export macros to avoid conflicts.* *Install qml plugins next to binaries on Windows.* *Remove manual registration of QML plugins on Windows.* The manual registration of QML plugins on Windows has no longer been required for a while since we use the standard location. It confuses Qt if the path is different when resolving filesystem links (for instance when using SUBST). *Exclude cuda files from code coverage.* *New vcpkg build with MSVC 2022 support.* *Run generate_headers script only during install.* ### ci *Use Start-SightProcess function to launch deploy tests.* *Restore test timeout on windows.* *Point again to the dev branch for sight-data repository.* ### core *Add recursive mutex protection for GDCM access.* *Missing dependency to module_ui_icons in module_ui_qt.* *Make all modules repositories path absolute.* *Test should use canonified paths.* *Resolve path symlinks in core::runtime.* All modules and libraries' paths are now correctly located when encountering symlinks. *Single instance mode.* *Always reset output data_ptr when stopping services.* ### doc *Snake_case convention in doxygen documentation, missing parameters and several typos.* ### io *Add a new function to avoid deadlock in remove_landmarks().* Add a new function is_landmark_visible_without_lock(). Fix the remove_landmarks() function. *Add missing data for saving fiducial ruler and included unit test.* *Simplify bitmap service configuration.* *Modernize tuto05.* - add missing states - remove the openMeshAct action and fix the way to compute mesh *Fortify matrix_writer.* *Do not use cuda driver api.* *Crash when canceling selector.* fix(io): selector canceled ### ui *Module::ui::qt::image leaks.* *Ui::qt::parameters vertical sliders labels are inverted.* *Parsing of the option to hide unstarted actions.* *Accordion_button leaks.* *Remove useless spacing in ruler and shape fiducials.* *Restore display and saving of calibration images in Sight Calibrator.* *Update restrict_to_current_slice function.* - Add missing slice reference in ruler data *Do not keep progress dialog shared pointers.* *Remove unwanted std::cout from status.* *Assert the result of QMetaObject::invokeMethod.* *Synchronize with global preferences before update.* This fixes the internal stored preferences not being in sync with the actual preferences and fixes a widget/memory leak (QDialog without a parent). Unit tests were added for the preferences configuration service. *Size of the slice selector index text.* The size of the index label next to the slice selector slider now uses the current font metrics and the current maximum index to compute accurately the size of the widget. *Add possibility to find the parent widget when it isn't active.* *Allow transparency on SSequencer.* *Vertical sliders are aligned.* ### viz *Respect initial visibility state and restore it properly in negato_3d.* *Only reset the crop mask when the image changes, not its content.* *Dead lock when updating mask.* *Display of triangle meshes that use per cell colors.* *Avoid infinite loops in volume rendering box crop.* *Make Ogre aware of GLX context switches made by Qt.* *Grid no longer disappear when viewed from the top.* *Memory leak when a texture is unloaded.* *Remove unnecessary render requests.* *Add missing slicesCross argument to Plane in SNegato2d.* *Crash when zooming VR without autoresetcamera.* ## Refactor: ### core *Remove obsolete get_temp_path() and get_temporary_folder().* *Homogenise all timestamping mechanism.* - replace occurrence creation timestamps with hires_clock functions - use of system_clock instead of high_resolution_clock whenever possible - use of std`::chrono::steady_clock`when more suitable *Apply updated coding-style with clang-tidy.* *Local and parameter variables renaming in snake_case.* *Mass-rename of core namespace in snake_case.* - The core namespace is now entirely in snake_case - New() is replaced by std::make_shared<> or custom make for factory functions, - dynamicCast() is replaced by std::dynamic_pointer_cast<>(), - Most interfaces named in I* are simplified - `base` namespaces renamed to __ on the filesystem and simply removed for namespaces (`ui`::base``becomes `ui`) - Mass renaming for layout "managers" and dialogs ### ui *Remove obsolete ui::signal, ui::starter and ui::default_action services.* # sight 23.1.0 ## New features: ### io *Filters dicom series by sop class.* Use the new `module`::io::dicom::SReader``configuration: `sopFilter="1.2.840.10008.5.1.4.1.1.2"` -or- call `sight::io::dicom::Reader::setFilters({sight::data::dicom::sop::Keyword::CTImageStorage})` *Add the Spatial Fiducials IOD read/write feature.* These changes add the Spatial Fiducials IOD read/write feature to the existing DICOM reader/writer. ### ui *Predefined camera enhancement, cardinalLayout area maximization and accordion menu fix.* also: - add a modifyLayout slot for custom layouting actions - SAction can now trigger custom "parameter_t" signals - Remove various unused constants which were breaking clang build - Make the animation to take render time into account. This makes the RPM speed somewhat constant. - Bad size for non button widget in accordion menu *Add a slider to modify transfer function opacity.* ### viz *Allow disabling predefined camera animation.* *Allow moving landmarks from selected groups outside edit mode.* `sight::module::viz::scene3dQt`::adaptor::SLandmarks``has been improved to support a `MOVE` mode when not in `EDIT` mode. The `MOVE` mode is controlled by `enableMoveMode()`, `disableMoveMode()`, `toggleMoveMode()` and `changeMoveMode(bool)` slots. The move can be restricted to specific groups when `modify="groups"` adaptor configuration is used, by setting a list of group in a new parameter of the `configureLandmarks()`. `configureLandmarks()` takes now a `sight::viz::scene3d`::LandmarksConfiguration``structure, which is shared by signals, reducing the risk of typo and unwanted API modification. ## Enhancement: ### io *Update minizip to 4.0.1.* ### ui *Improve SSequencer appearance.* This improves a bit the appearance of SSequencer and above all, make it more customizable. The buttons and the fonts size can now be adjusted as well. ExActivities was also fixed. *Add the possibility to only allow moving landmarks from a given group.* We added the possibility to allow moving landmarks only for a given group. It adds the configuration key `modify` for `sight::modules::viz::scene3DQt`::adaptor::SLandmarks``with the possible values `all` (the default, which allows to modify all landmarks, ignoring the current group) and `group` (which allows to modify only the landmarks belonging to the current group). *Allow modifying the displayed columns in the series selector widget.* Add a new configuration in `module`::io::dicom::SReader``like: `displayedColumns="PatientID,Modality,Description,Date,Time,BodyPart,PatientPosition"` to configure the displayed columns *Password must be strong enough.* *Add contextual mode and menu for fiducials.* These changes introduce an editing mode for landmarks and distance. Landmarks and distances are now immutable by default, their respecting editing mode must be on in order to be created, removed, moved and renamed. Now, in order to remove a landmark or a distance, one must click on it while the editing mode is on and then click on the bin button which appears. In order to rename a landmark, one can click on the label and directly edit it. Note that, right now, the landmark groups have names, not individual landmarks; therefore, to rename a landmark will actually modify the name of its group. ### viz *Add option to disable validation through double click for SShapeExtruder.* *Add other slice lines on negato 2D views.* *Update landmark/distance adaptors to work on data::Series.* ## Bug fixes: ### build *Fix clang-15 build.* ### core *Add missing copy operators in FiducialsSeries.* ### viz *Display of 2D image in a 3D scene.* *Store the VR clipping matrix in local instead of world.* This makes this transform more portable, for instance when the image is moved/registered between the time the clipping is made and the image is rendered. *Respect the number of samples set in SVolumeRender config.* # sight 23.0.0 ## Bug fixes: ### build *Fix nvcc configuration.* *Make build compatible with CUDA.* *Protect from target coverage redefinition.* *Build with gcc 12.* *Provide a project or an application version number for xml.* We fixed and enhanced the way to specify version numbers that can be used in XML configuration: * the `PROJECT_VERSION` variable is now correctly filled and matches the global project version number specified in the CMake `project()` macro. * a new variable `_VERSION` is now defined for each application, that matches the version used in the packaging, deduced from git tags. If a matching git tag does not exist, it fallback to the latest global tag. *Clang-15 support and warning fixes.* ### ci *Try to avoid crashes when a service test fails.* *Increase timeout for tests.* *Fix Linux build jobs not outputting test reports.* *Fix GUI tests crashing because of dangling pointers.* ### core *Parsing of "channel", "uid" pair for dedicated notifications channels.* *Error in sync test.* *Add PB missing enums.* *Start deferred service if its objects are present or optional.* *Crash if getMultiFrameGroupSequence is not found.* Safe return if `getMultiFrameGroupSequence` doesn't exist in current series. *Random crash in SSynchroniser.* The `SSynchronizer`::synchronize``function should be run on a separate worker thread. Otherwise, stopping it properly while it runs will never be possible. By doing that, we introduce an implicit synchronisation when we stop the worker thread, which joins and thus waits for `SSynchronizer`::synchronize``to finish. So we even don't need a more sophisticated synchronisation mechanism with mutexes, condition variables, etc... However, we discovered three other issues: * some data objects in the unit-tests were destroyed before the synchronizer service. We modified those unit tests accordingly, * a deadlock could occur when a slot is destroyed during the asynchronous emission of a signal it is connected to. Now, we ensure the destruction occurs after the emission of the signal, * a crash could occur when a timer is destroyed before its callback is called. *Data XML parsers execution policy.* Data-specific XML parsers are executed whenever an object is created in an XML configuration. We discovered they were also executed with referenced objects, which could lead to objects overwritten with defaults. This happened for instance with transfer functions. We no longer execute the parsers in these cases. On the other side, objects created in activities do not benefit from this mechanism. This means that after this first change, an activity having a transfer function as a requirement with `create="true"` would create an empty transfer function. Many services do not handle that correctly. It turns out to be desirable to also allow specifying initial object configuration for requirements. Example from the unit test, which set the value "dummy string" to the requirement `string`: ```xml TestBuilderObjectParser ObjectParser Desc Test1 Icon Test1 dummy string ``` *Mistmatch beween malloc and delete in Demangler.* *Ensure that removed frame data are removed and not saved with GDCM writer.* ### io *Prevent SReader to lose its configuraiton on start/stop.* session`::Sreader`forgets its configuration at stop and start. Prevent this when the dialog policy is NEVER, because it means that the file-to-read path is only specified in configuration, and should not be forgotten. *Add openJpg support for dicom writer.* Some dicom tests writing fail because of an internal mismatch in dicom/nvJpeg2000/openJpeg. To summarize, for strongly randomized images, nvpeg2000 fails. This has been fixed in the 0.7 version of nvjpeg, used on linux, but not presently on windows. So, when writing the dicom in a ut on windows, nvJpeg2000 is detected, used, fails, and a backup is used to openJpeg. However, gdcm, the dicom writing lib, uses openJpeg intrinsecly, and does not support an the external use of openjpeg (bug ! ) which lead,s in the present case to a fail of the nvjpeg2000 backup system. As a result, some tests fail on windows. To fix this, a support of dicom writing in onpenjpeg has been added, to make syure that the dicom is writen in any cases *Add tests and many fixes, optimizations and cleanup for bitmap reader/writer.* - add read - write tests - ensure unsupported pixel format / type will throw an exception - support endianness correctly (libpng) - cleanup and code factorization - add some tests - ensure to throw an exception if nvjpeg2000 is asked for dicom and library is not found at runtime *Prevent ssynchronizer double lock.* Prevent SSynchronizer to synchronize in the update in legacy mode. There is a dedicated worker for that. This avoids a dead lock in legacy mode. *Ensure the parent path is created in io::session::SWriter.* *Correct SSynchronizer test random fail.* The SSynchronizer copy from the TL to the output vars did a scoped lock, while the buffer was used outside of it. This lead to concurent access and test fail. The scope has been enhanced to cover the whole copy method from the tl to the output var. ### test *Fix GUI test not working on subprojects with Ctest.* GUI tests didn't work using CTest on subprojects on Linux because of a bug in the `exec_gui_tests.sh` script. ### ui *Fix bugs with Accordion Menu.* *Enhance negato slider with window level.* We improved the negatoscope view to allow the bottom slider to tweak the transfer function window. Before that, it was only possible to do it with a right-click mouse move. *Replace INFO logging by DEBUG in SStatus.* *Preferences no longer reset when app version changes.* A regression caused the application preferences to be dependent on the commit ref. This fixes the behaviour. *Resize overlay layout according to its children.* *Transparent notifications with NVidia prime.* *Typo in SNotifier.* *Remove useless "SRecurrentSignal".* *Notification popups doesn't move down when stacked.* *Fully export class when derived from QObject.* This allows to use the class as a QObject outside its library and avoid strange missing symbols on windows. *Fix non-linear sliders value changing too much when scrolling.* Up to now, the non-linear sliders did too big step, going from maximum to minimum. This has been enhanced to do more progressive changes in non-linear sliders. *Non-linear slider wrong min/max values when changing values in SParameters.* These changes fix the wrong min/max values displayed for a non-linear slider in SParameters. This was because the rangeChanged signal was sent with the wrong values, those from the internal slider, rather than those from the non-linear slider. *First call IGuiContainer visibility slots when hide/show SSignalButton.* Call first `IGuiContainer` enable/disable and setVisibility slots in SSignalButton. This fix the bug where is SSignalButton was hiden in a SView, it was impossible to show it again. *Fix labels being cut off in SParameters with the touch-friendly style.* When the touch-friendly style is applied, the labels are cut off in SParameters if there isn't enough space. These changes fix that by applying a negative margin to the labels. *Remove the close callback of notification and force close at stopping of SNotifier.* ### viz *Compute clipping planes on each rendering frame.* *Crash when switching an activity when a notification is shown.* *Make SCamera calibrate safer.* The SCamera adaptor requires a calibration step. This calibration can be provided through an input to the service (CameraSet, cameraCalibration). However, for virtual camera in classical 3Dscene, no calibration is provided, and the calibration is computed based on the viewport size. This set is only called at the service start, and, if the viewport is hidden, or deffined with visibility=false, its height and with are null or negativ. This leads to an error which stops the application in debug. Add a calibration verification checK. If the calibration fails in SCamera, it stops the updating. If the updating is called and the calibration has not been done, it is calibrated again. *Correct several update issues with the 3D widget.* *Shape extruder does not work with touchscreen.* When using shape extruder on a volume in a 3D scene, and using a mousse, all works fine. However, on the same machine, same volume, same extrusion path, when doing it with the touchscreen the extrusion takes ages, and freezes the application. This has been fixed by the way events are handled. shape extrusion should work perfectly on both touchscreen and mousse actions. *Fix event managment for touch.* * Remove `GestureRecognizer`, `EventFilter`, `GestureFilter` as we rely on default Qt one for Pinch and Pan. * Remove Tap and long Tap, as they are no more used, but it should be easy to add them back, if needed. * Use synthesized mouse event from Qt gesture, and synthesize the one missing (in SNegato2DCamera) * Use float version of device pixel ratio to manage correctly fractional scaling (like 1.5 \* full HD) *Use resize event informations to set aspect ratio.* The aspect ratio could be broken on some initialisation paths. Also, fixed the missing label SAxis when the axis are disabled. *Touch event for landmarks.* *Update of transfer function widget when image is updated.* *Display of landmarks and distance over volume rendering.* *Correct SLine visibility.* The SLine 3D adaptor is displayed everytime that the line length is changed, even if its visibility is set to hidden. To fix this, the rendering mechanism has been upgraded to display it only when it is set to visible. ## Enhancement: ### build *Add sanitizer support.* Using the CMake option `SIGHT_SANITIZE="sanitizer(s)"` like in `cmake .... -DSIGHT_SANITIZE="thread" ....` The string is passed to the compiler with the option `-fsanitize=xxx`. Launching the result binaries will execute instrumented code that will check things like concurrent access, memory leak, address uses, etc. One an error is found, the code is stopped with an exception and a meaningful message is printed on the console. ### ci *Be more permissive.* *Cache sight-data.* *Disable gdb to speed-up coverage build.* *Fix caching with clang.* *Use a shared linux runner for build jobs.* *Use direct dependencies between jobs.* *Use merge-requests pipelines.* ### core *Tag output synchronized images with timestamps in SSynchronizer.* *Add blocker support for IContainer::scoped_emit.* The scope emiter is a shortlived object which watches a IContainer object, and, once destroyed (scope variable destroyed) sends all the appropriate signals regarding what happend to the IContainer (add/remove/changed...). However, in some cases, we might want to make use of a scope_emiter, though want to prevent it from sending a specific signal. This is added here, through a block method which can be called ``` const auto scoped_emitter = container->scoped_emit(); scoped_emitter->block(this->slot(IService::slots::UPDATE)); container->push_back(object1); container->erase(object2); container->push_back(object3); ``` In this case, the update will not be sent from the container. *Support start/stop slots connections in XML configurations.* The `AppConfigManager` has been modified to support the connection of `start` and `stop` slots. A service started by the trigger of a signal will be automatically stopped. ``` service1/computed service2/start service1/failed service2/stop ``` This deprecates the service `SStarter` that was used as an alternative so far. ### io *Add some accessors for serialization.* - keep the file path of the original DICOM file - getter for archive file path - getter for the default (de)serializer function which allow us to call them in a custom (de)serializer - fix a typo / coding style normalization - update unit tests *Forward the grabbing fps in IGrabber/SGrabberProxy.* For performance and quality evaluation, it is useful to forward a real-time frame-grabbing frame rate. A signal is added in the grabber proxy to forward the fps from the grabbers which support this functionality. Connect ```GRABBER_PROXY_SRC/fpsChanged``` to get the fps when it is updated. *Add NvJPEG2K backend when saving multiframes in DICOM.* ### test *Update MacroSaver from change to ITest::getProfilePath.* These changes reflect the changes of GUI test API to the MacroSaver. The main change is the support for the helper API that the code generated by MacroSaver will now use. ### ui *Reflect changes of presets in the TF editor.* The transfer function selector now updates the list of transfer functions properly when it changes. *Make toolbar button icons bigger in touch-friendly apps.* Some issues with touch-friendly apps were fixed: * The toolbar buttons are now bigger in touch-friendly apps by default (48\*48 instead of 32\*32) * When using SStatus with form = circle, the circles are now actually circular for any size * The slider groove is now bigger in touch-friendly apps to make it easier to use *Change Play/Pause/Stop shortcut.* Changes the controls.xml config Play/Pause/Stop shortcuts to config parameters. Space is the default value for play/pause and "s" for stop, though they can be changed in the configuration. This allows apps to use the space shortcut for their own usage. *Add swapp support from single volume view to 3/4 view with negatos.* The classical volume view in sightviewer is a single 3D scene with the volume. It is possible to go to a view with 4 scenes, with the 3D volume view on the top left, and the negato in all 3 directions in the other corner. It is presently not possible to simply make one of the negato in full screen. To make this possible, a SEvent service is added, to catch the different events, and forward a particular one. Once added to the configuration it is used to catch double click, and hide/show the different scenes. This makes it a lot easier to change the view, and go back. To keep the classical button functionality, the IAction api has been enhanced to support toogleVisibility. *Add grabber optimize channel to controls.xml configuration.* *Prevent SVideo crash on empty image update.* When providing an empty image (size (0,0,0) ) to a SVideo service, the service does not filter and forwards it to the textureManager which tests it and generates an error in particular in debug mode (ASSERT). However empty images are often used as default ones, and this leads to various difficulties. Here, an if(size\[0\] == 0 || size\[1\] == 0) test is added on the SVideo updating and return when it fails. *Use Qt layout instead of multiple 3D viewports in SightViewer.* ### viz *Improve distance measurement tool with a click and drag movement.* The distance measurement tool had some limitations. In particular, at the creation, the initial process was : - click on add distance: a distance is created at the ends (0,0) and (width, height) of the image - drag-drop the created distance ends to the location you want to measure This was too tedious. The new behaviour is : - click on add Distance: enter a dedicated mode - click on the starting point you want to measure - drag to the end - drop, the distance is ready. During your measurement, the distance is automatically updated on the screen, allowing you to visualise what you measure, and the distance between the starting point and present location. SightViewer has been updated to support the new tool and to have the negato2D and 3D interacting when the distance is created and measured. *Make Qt overlays compatible with the 3D scene.* The Ogre Qt widget has been heavily reworked. This was motivated by the need to have Qt widgets as an overlay. To make this possible, we chose to let Qt make the OpenGL compositing of the whole application. However, this required us to make a part of the composition ourselves. It was no longer possible to use a classic `Ogre`::RenderWindow``because, at some point, it will end up trying to handle its own final back buffer, while we want to render in the FBO managed by Qt. Thus, `Ogre`::RenderWindow``was banned in favour of `Ogre::RenderTarget`. This implied other significant changes to handle viewport resizes with listeners. Other changes were brought along this rework: - SightViewer XML configuration has been heavily reworked to use shared configurations. Three new configurations are now available in `sight::config::viz::scene3D` to display a single negatoscope, a 3D scene and four-split views with a 3D scene and 3 negatoscopes. - Layer transparency: if not specified, then the OIT is disabled (you still have transparency, but it is order dependent). This ensures that most render layers do not use an expensive transparency technique if it is not required. If you need OIT, `Default` is the best setting and is an alias for `HybridTransparency` - The Ogre Overlay was removed because it was hard to make it compatible with the new rendering setup. On top of that, it seems pretty irrelevant to keep two systems to do the same thing. The services that used it, like `sight::module::viz::scene3d`::adaptor::SText``have been switched to Qt. - In GUI tests, it is now possible to address the views using a `wid` in `SView` - `SImageExtruder` can now take an optional transform matrix, in the case where the image is moved in the scene (registration for instance) *Add reset layer camera slot.* The reset camera slot is connected, on the SRender service, to all layouts of the service. However, when several layouts are present, it can be expected to reset only a given layout's camera. To do this, a dedicated slot is created at the service configuration. The slot name contains the layoutId, and call the resetCameraCoordinates which is specified for the layoutId provided in parameter. *Use an additional mask for volume rendering extrusion.* This introduces a mask in the volume rendering, that is used to mark cropped regions instead of marking these regions with a special intensity value. This solves the problem of the computation of the gradient on the edges of these cropped regions and also provides smoother edges. A few extra changes were brought on the way: new toolbar icons for the cropping tool, cleaned properly the extrusions when a new image is loaded. *Add default tf loading option in TransferFunctionWidget.* The transferFunction editor widget (and windows) don't zllow to freely specify the a folder with f to pre-load, and the default tfs are always present. Add two optional parameters to the configs allowing loading pre-registered tf through the path attribute of STransferFunction. Add support to empty path (default value in the config) in the STransferFunction service. Changes the TransferFunctionWidgetCfg and TransferFunctionWindowsCfg config id to fetch new standards (sight::config::viz::scene2d::TransferFunctionWidgetCfg) *Enable binary alpha mixed rendering.* Binary-transparent surface objects are now rendered properly with volume rendering. *Add point selection feature on SLandmarks.* The SLandmark service is a graphical widget which handles the Landmarks, which can be seen as a vector of points. It has a feature to send the coordinates of the current selected point through a signal. This mechanism is enhanced, to treat a newly created point as a selected one. In addition, the selected point are outputed in a ```sight`::data::Point````variable in addition to the signal, to make it possible to treat it as a classical sight data. closes 1040 *Correct SLIne and SText.* Make SText input autoconnect to true Make SLine use getOrCreateTransformNode to get its transform Node *Add an optional transfer function presets list.* This adds the ability to specify an optional preset of transfer functions. ## New features: ### ci *Add a custom CMake command to run gcovr.* This reduces redundancy across project and make it easier to use. ### core *Introduce SMultiConfigController.* The present config launcher allows starting a given XML configuration. SMultiConfigController allows running a configuration selected between a set of configs that share the same parameters. Somehow, it creates a simple interface, where each "subconfig" is an implementation. This can have many applications, in particular in device handling, and will help to make smaller and more generic XML configurations. *Add API support for fiducials.* We introduced a new class, `FiducialsSeries`, representing a "Spatial Fiducials" DICOM IOD. `ImageSeries` and `ModelSeries` can both have a `FiducialSeries`. Some modifications were done in `SeriesImpl` in order to add support to sequences of sequences with more than one element. *Ssequencer / slandmarks fixes and enhancements.* - Application::exit() can now be synchronous and exit immediatly - SLandmarks: make "configureLandmarks()" a slot - SLandmarks: allow to limit the number of landmarks - SLandmarks: remove also landmarks no more in the data`::Landmarks`when update() is called - crash when clicking on ok without selecting a DICOM series - show number of frame in the series description - SSequencer: add a validate "next" that doesn't enable next activity - SSequencer: reset requirement that are no more valid - SSequencer: refresh validity on previous and next activities when calling checkNext() - cleanup - use const_lock whenever possible *Allow per-frame private DICOM attributes.* *Allow log relocation.* Add a `relocate_log()` that does exactly what its name suggests. Optionally, it also relocates the previous log files. Additionally: * the log singleton is now really a singleton * `sightlog` can now be launched outside "Sight", which is needed for external unit test * we have now access to all "binaries" paths from repositories (IE Sight/bin, XXXX/bin, ...the directories that contain executables like `sightrun` and `sightlog`) * changing the password while using encrypted logs, will indeed also relocate the previous logs, with the new password. This is useful when you plan to submit the logs for analysis and don't want to remember all passwords you used during the work session. * ..and some gcc-12 warnings have also been fixed. * add also a special environment variable (try with `export SIGHT_LEGACY_COMPRESSION=1`) that force legacy zip deflate algorithm for all compression operation, instead of zstd. This allows to open archives (logs, sessions, ...) with plain old third party archiver. * Force non-empty password. Empty password means "no password", which makes things ambiguous and complicated. *A better temporary directory / file.* - RAII to ensure the file / directory is deleted - Convenient conversion / utilities operator - Thread safe / process safe - path lenght is minimized - globally safer ### io *Allow to configure the baseFolder via a slot in IWriter.* *Allow to configure and override the transfer syntax used to write Enhanced US Volume.* Also: * 10% increase of nvjpeg2000 writing performance (avoiding unnecessary copy) * fix a huge memory leak while using nvjpeg2000 for DICOM writing * add profiling test for transfer syntax * allow using TempDir as a simple RAII directory cleaner * fix many memory leaks: * [valgrind_before.log](/sight/sight/uploads/7fd81924e7cdd842494917b0c09a3b11/valgrind_before.log) * [valgrind.log](/sight/sight/uploads/b84702ee5646b42910dfa3d8f679fb3d/valgrind.log) * fix a possible crash when the window is resized while not yet ready *Forward folder of loaded session as a signal.* *Add a new tool to extract data from an archive.* These changes introduce a new tool, ArchiveExtractor, to extract archives generated by Sight applications. It was designed specifically for opening archive from a real-world application, but it should be generalist enough to allow extract archives from any Sight application, as long as they're salted archives, as they aren't supported by ArchiveExtractor. This application needed a new module, module::io::zip::SExtract, which asks the user for the input file and the output path and eventually the password if needed. *Implement bitmap readers.* A reader and a service that allows fast bitmap reading were added. Some code has been refactored and factorized with the bitmap writers and some bugs have been fixed: - Image generator / randomizer that can now be called in one step, for all image formats and pixel type. - nvjpeg2000 flaw mitigation: fallback to openJPEG if the encoded file is bigger than source instead of failing - Reader / writer code factorization and cleanup ### test *Add backtrace generation mechanism for GUI tests.* These changes add helper methods to ease the creation of GUI tests. Some helpers, such as `helper`::Button``or `helper::Field`, operates at the component level, while some others, such as `helper::VideoControls`, operates at the level of a Sight XML config. Most helper methods use a `Select` helper class to get the components they operate on. For example, one can use `Select::fromDialog("fileNameEdit")` to get the component named "fileNameEdit" inside the currently displayed dialog window. Select has an implicit constructor which take a string, which will select a component within the main window, which is the most common use case. In addition, Select has two setters, `withTimeout` and `withCondition` which allows to set, respectively, a specific timeout to wait for the component to show up, and a condition for it to meet. A new GUI test was added for SightCalibrator, which is VideoControls. It checks whether SightCalibrator's video controls work correctly (and, incidentally, check that helper`::VideoControls`works). ### ui *Add accordion menus.* A new accordion menu is now available in tool bars by enclosing buttons in the `` tag. The toolbar in SightViewer was modified so that extrusion-related buttons are shown only if the extruder is activated and distance-related buttons are shown only if the distance mode is activated. The extrusion and distance modes are mutually exclusive, if buttons related to extrusion are shown, buttons related to distance are hidden, and vice versa. *Notification enhancements.* - The API has been updated, the code is no more part of `IService`, but has been moved to a new interface `INotifier`. The services that need to send notifications should now inherit from that new interface. The name of the notification method and the corresponding signal has not changed (`notify()` and `notified`), but the signature now uses a `Notification` structure, instead of only the type and the message string. For compatibility and for convenience, a `notify()` with the same parameters as before is available - Notifications can now have individual duration, that can be "infinite". The notification will then be "permanent" and will only be closed by a double click. The duration can be set within the `Notification` structure, a parameter of the new `notify()` - Notifications can now be "channeled", meaning they can be reused, shared and even closed by several services who share the same "channel" keys. The channel name is also passed in the `Notification` structure, but can also be mapped from XML configuration: ```xml ``` - Many bug fixes / small enhancement: - Changing position can now be done dynamically (useful for channels), without waiting that all previous notification to be closed to have a correct placement - Use a button to display long text instead of single click (which disallowed closing them !) - Fixed long text truncation - Race conditions / various crashes fixes. *Add a Speed Dial button.* These changes add a Speed Dial button, which displays a list of other buttons when clicked with an animation. The actual "buttons" can be any kind of QWidget, typically QPushButton, but it can also be more exotic widgets such as QOpenGLWidget. It also adds SIconSpeedDial, which is a service which allows to create a Speed Dial with a list of actions. It is the most simple and typical use case of the Speed Dial and can serve as a reference. *Create buttonbar widget for SParam enum.* The SParameter supports enums, though they are not simple to use, in terms of xml and ui. A new enum display will be added, corresponding to a bar, with buttons, image and text which allow to select a specific value. It should have the features of enumSParam, with icons and labels. The unselected options are gray, while the selected one is in full color. Gui test are added, to check this feature, and a sample is added in SParameter. *Provide a view that can be overlayed on 3D scenes.* A new `OverlayLayoutManager` is introduced, allowing the addition of overlays to a widget. Here is an example of use: ```xml ``` In OverlayLayoutManager, we define a list of views. The first view is the background widget, typically a 3D scene. The other ones are overlays, we can define properties such as ``x``, ``y``, ``width``, ``height`` to indicate where the overlay should be drawn and what its size should be. By default, ``x`` and ``y`` are the offset from the left and the top respectively, however, if they are negative, then it is the offset from the right and the bottom respectively. By default, they are absolute sizes in pixels, however, if ``%`` is appended, then it is relative to the size of the parent widget. *Add a optional forward / backward button for slider.* *Allow selecting absolute positions in negato sliders.* *Makes password dialog customizable.* Small modifications were brought to allow displaying an image and a custom message for the password dialog used in preferences. Basically, it is just a couple of parameters for module_ui_base: ```cmake module_param( module_ui_base PARAM_LIST preferences_password_dialog_title preferences_password_dialog_message preferences_password_dialog_icon PARAM_VALUES "Password required" " Please enter your password: " "sight::module::ui::qt/rename.png" ) ``` *Add Preferences::ignoreFilesystem method.* *Make toolbar hideable.* Enhance the IToolBarLayoutManager with a setVisible(bool isVisible) method Get the toggleViewAction QAction from the toolbar in the ToolBarLayoutManager and store it Set the toggleViewAction check state and trigger it when isVisible is called Add slots in the IToolBar to hide/show/setVisibility add toolbar hide/show to tuto14 *Add non-linear sliders for SParameters.* A new widget for `type=enum` parameters in SParameters is introduced, `slider`. This will use the new NonLinearSlider widget. ### viz *Create a pre-defined camera interactor.* New camera interactor SPredefinedCamera that takes multiple "point-of-view" in configuration. We can navigate through each point-of-view, the camera will go from one to another using a smooth animation. Mouse track (rotation only) can be disable, if enable you can rotate the camera arround a fixing point, but will be reset at next point of view. An optional "initial" matrix can also be set, if so camera will first use this matrix and then move to configured point-of-view according to this new position. This matrix can be updated in real time to get a "GPS-like" camera tracking an object. Others changes: * remove requestRender from Window,now only interactors should decide when trigger a render * add requestRender in ClippingBoxInteractor *Allow to specify the view distance of the landmark.* ...and many fixes on SLandmarks and one in is_less and is_greater. *Allows to use SCamera with orthographic projection.* # sight 22.1.0 ## New features: ### core *Add a function profiling utility.* - fix Warning C4244 - windows pch off support ### io *Add a DICOM Enhanced-US volume writer.* The current Dicom Series writer has been updated to call a specialized writer when the SOP class is [Enhanced US Volume](https://dicom.nema.org/dicom/2013/output/chtml/part03/sect_A.59.html). The new writer is for now minimalist and maybe not 100% DICOM compliant yet, as it requires, nor checks for mandatory tags to operate. However, all frames of a data`::ImageSeries`(on the Z axis) are saved losslessly in jpeg2000, and Series API allows writing/reading per-frame `Image Position Patient/Volume`, `Image Orientation Patient/Volume`, and `Frame Acquisition Date Time`. Even if the writer doesn't ensure other mandatory tags are present, it is still possible (and desirable) to write them using the Series API with the generated tag name/sop class from sight`::data::dicom::Tag`and sight::data::dicom::SopClass. Note however that the current API is limited to simple attributes, not the one located in a frame sequence like in `Shared Functional Groups Sequence`. Some adaptations are required (the same as \`Image Position Patient…). This part will be updated later when needed. *Use correct gdcm attributes for US volume.* - ImagePositionVolume, ImageOrientationVolume, FrameAcquisitionDateTime DICOM attribute are now stored into sequence element when using Enhenced US Volume, as before for CT/MR. - DICOM Tags, SOPClasses, Modules definitions are generated from official documentation using python code. This allows friendlier operations (no need to remember exact tag values, or SOP uids). - Unit tests have been updated to match changes and use generated DICOM tags, SOPClasses. - Some bugs with equality operator have been corrected *Allow forwarding grabber long running jobs.* *Add optimize slot to IGrabber/SGrabberProxy.* *Allow specifying a target camera in SGrabberProxy.* *Nvjpeg writer implementation.* A "fast" 2D image writer (sight::io::bitmap::Writer) and its associated service (sight::module::io::bitmap::SWriter) have been implemented. The writer uses NVidia CUDA accelerated JPEG / JPEG2000 encoding library: nvJPEG, if support has been compiled in and if a suitable GPU has been found. Alternatively, libjpeg-turbo, openJPEG, libtiff or libPNG can be used as CPU fallback. The performance should be better than VTK or even OpenCV because of direct API calls and avoided unneeded buffer copy. > :warning: VTK SImageWriter support for 2D bitmap format has been removed. The support was anyway bugged as the image were saved upsode down. > > also added: > - fix on `LocationDialog::getCurrentSelection()` that erased the first char to return an extension from a wildcard filter. Now there is a `ILocationDialog::getSelectedExtensions()` that returns a vector of extension, in case there are more than one extension from a filter. > - some small fix for high dpi displays in the GUI tester code. *Add zoom capability in video grabbers.* We added the possibility to zoom on videos. Some hardware devices offers this possibility, so it is better to use this instead of zooming in the visualization pipeline. ## Enhancement: ### build *Use /Z7 in all cases and enable ccache for windows.* ### ci *Enable debug optimized build for GUI tester.* Our GUI tester is very slow in the coverage build. We enabled optimizations for the tester code, especially for the image comparison functions, hoping that this will decrease the number of false positives. *Increase test timeout on test coverage.* ### core *Add private tag support.* *Lower the test epsilon to be a bit more tolerant.* *Add strict mode to require matrix presence in SFrameMatrixSynchronizer.* *Add a simple function to set the location of series instances.* A function was added to set both the position and orientation of a series instance at once, passing a `sight::data::Matrix4`. Besides this, `sight::data::Matrix4` has been modernized. ### filter *Make SFrameMatrix synchroniser delay specific for each timeline.* add a vector for the framesTl and one for the matrixTl to store the delay. The delay is applied at the synchronization, and the frame/matrix picked is in the past regarding the most recent data, from an amount of delay. The timelines are associated through their index. ### io *Un-pause grabber on successive startCamera calls.* *Skip camera restart when already started.* ### test *Enhance global test code coverage.* Many tests were added, especially for the most used services in our applications, that were untested so far. *Add external project gui test support.* - rename the gui test core folder to testCore -change the ITest profile path handling, by geting the full path, and not using "getWorkingDir()". It will be up to the GUI tests to provide the full profile path. - add the profile modules. The clasicall sightrun looks for the modules situated relatively to the profile path. This allows to load automatically the project modules; This will be done in the ITest too, at the test setup() -add -B bundlesPath in the cppunit_main. Similarly to classical applications, the -B argument allows specifying additional bundles path. This will be handled in the unit test too. - change test templates to add external libs path in the PATH variable and bundles path - in the eventHandling, verify that the event catched is a testEvent, and do nothing if it is not one. -install exec_gui_test and copy it in sight-projects build bin dir *Improve operator== tests for Data Objects.* ### ui *Use unique slot to configure Grabbers.* * rename some slots from SParameter from "set" to "update" for clarity * also add a vector of string as part of the parameter_t variant to be able to update all values of enum widget *Emit signal with value and key from SPreferenceConfiguration.* The signal, called `preferenceChanged`, has the same signature as the signal `SParameter::parameterChanged`. This simplifies the API when the service needs to listen to a parameter that can be changed from preference or/and from a`SParameter`. An example of usage is shown in ExSParameter. *Use a 3 columns layout for SParameters.* These changes modify SParameters layout to use a 3 columns system instead of the current 6 columns system, to improve the usability of the second and third spinboxes. In addition, the labels of the parameters now can wrap if they are too wide, and a bug where the second and third spinboxes wouldn't be disabled if the depend is disabled is also fixed. *Apply SParameters' stylesheet globally.* These changes apply the stylesheet from https://git.ircad.fr/sight/sight/-/merge_requests/795 globally. It can be enabled by setting the `"touch_friendly"` application option to `"true"` at configure time. *Add a generic signal/slot parameter type.* *Improve hitboxes for Q*SpinBox controls.* *Rename toolbar.xml to controls.xml and add more signals.* => add some out signals => disconnect fileConfigured with video play => add documentation ### viz *Add slot in SShapeExtruder to replace right click.* These changes add a slot called "cancelLastClick" to cancel the last click in the shape extruder. This allows to replace the right click in context where it isn't available, such as in a touchscreen context. In addition, this adds a button in SightViewer which triggers this slot when clicked. *Adapt default transfer function to a reference image.* The default transfer function can now be adapted to the image pixel type, especially for ultrasound images. The `data`::Image``no longer holds a default transfer function. To get a default transfer function, you have to use `sight`::module::ui::qt::image::STransferFunction``and specify a reference image as input. This is no issue if you don't want the transfer function selector in the user interface. You can register the service in an invisible view. You can have a look at `Tuto07GenericScene` for instance. Also, some bugs were fixed in SNegato2DCamera for the computation of the viewport of the axial view. *Add gestures to scene 3D.* These changes will allow to use gestures on the 3D scene. It allows in particular to pinch fingers to zoom and to long tap to place a landmark. Simple taps and pan gestures should intuitively map to mouse clicks and mouse drags. ## Refactor: ### build *Use built-in CMake PCH functions.* ### core *Introduce SSynchronizer to replace all frame/matrices sync services.* This refactors fundamentally the synchronization between frames and matrices, by bringing a single service SSynchronization that replaces all the previous ones: `SFrameMatrixSynchronizer`, `SMatrixSynchronizer`, `SMatrixTLSynchronizer` and `SFrameUpdater`. It is much more versatile to answer all the different use cases and his behaviour is well tested. *Inherit data::Activity from data::Composite.* *Separate service public and private API.* We reduced the size of the public API of the IService interface, which is undoubtedly the most used in Sight. First, the data management responsibility has been moved to the IHasData interface and now holds the getters and setters for inputs, in/outs and outputs. Secondly, a significant part of the implementation of IService methods has been moved to a pimpl in service`::detail::Service`and service::detail::ServiceConnection. The AppConfigManager was moved in the detail namespace as well, so now the public API only exposes IAppConfigManager. This refactor also finishes the migration of data::ptr. Now the objects of services are only held in data::ptr, the former input, inout and output maps of IService were removed, which simplifies a lot the code. This also implies that services can no longer specify any data in the XML configuration that is not declared as data::ptr. In this repository, only two services relied on the old behaviour SConfigController and SConfigLauncher. The migration consisted in using a single declared group of objects (data::ptr_vector) instead of multiple undeclared objects. Changes were brought in all XML configurations. Last, the ObjectService registry refactor was also finished. Its role has been reduced because we no longer rely on it to store the relationship between services and objects. This responsibility is filled by the services themselves. So now, this registry only holds a global list of registered services. It has been renamed accordingly. Doing so, the sight`::service::registry`namespace was removed, and the only other class that was there, sight`::service::registry::Proxy`was moved to sight::core::com::Proxy. Indeed, it has actually no dependency at all on services and is generic enough to be in the core`::com`namespace. *Deprecate IService::getConfigTree().* *Remove ConfigurationElement totally.* *Remove AppManager and Qml apps based on it.* *Replace ConfigurationElement in IService.* *Replace ConfigurationElement in all service configurations.* ### io *Create a widget & window configuration for pacs selector.* * the widget config is called by the windows config * widget can be used if the pacs selector should be integrated in a existing window *Create macros `SIGHT_REGISTER_SERIALIZER` to ease the serialization's function registration for each data.* ### viz *Simplify layer configuration for scene3d adaptors.* The scene3d adaptors no longer specify their layer. Instead, it is implicitly declared in the `SRender` configuration by putting the adaptors as children of the layers tags. ```xml ``` *Store transfer function as 2D Nx1x1 texture.* ## Bug fixes: ### build *Update dependency URLs.* *Use relative paths in launcher installed scripts.* *Only install header files when necessary.* This prevents rebuilding a child repository each time sight is installed. To achieve this, a custom target was added to generate the library headers without `*_API` macros in the build folder, and then copy these at install. Previously, we modified the files directly in the install folders, which led to always modify the timestamp of the headers, thus triggering the rebuild. ### ci *Use 'fetch' strategy instead of none on Windows.* On Windows, we do not have docker runners. Thus the build folders are reused between jobs. GIT_STRATEGY='none' is faster but does not provide the clean stage that is performed with 'clone' and 'fetch'. Because of this, we may end up with artifacts from previous jobs in the deploy stages. To overcome this, we use the 'fetch' strategy. It is useless to get the sources, but, this gives us the clean stage which is impossible to do in a build script. See https://gitlab.com/gitlab-org/gitlab/-/issues/17103 *Doxygen deployment.* ### io *Use new bitmap writer for screenshots.* *Ensure serializer register is unique.* *Make loop great again.* reset the timelines when reading process reached the end and is in loop mode add loop mode (slot + config + mechanism) in SMatricesReader make SMatricesReader pause system similar to SFrameGrabber one *Serialize & deserialize correctly empty objects.* - Data::Mesh * do not resize if mesh has no points / cells * Add VTK_EMPTY_CELLS in switch case - Data::Array * do not resize if deseralized array is empty - Data`::Image`/ ImageSeries * initialize Image windowCenter and windowWidth to empty vectors - Data::TransfertFunction * mistake in operator== * also test pieces vector * implementation of operator != - Misc * Remove Graph, Node, Edge, Port, ReconstructionTraits, ROITraits Data * Use operator== to compare all data in sessionTest ### test *Handle external modules properly in test bat and bin.* *Improve LoadDicom GUI test sensitiveness.* ### ui *Fix password management for preferences.* * restore BadPassword exception * changed inputDialog to return value and a boolean to know if user canceled it * allow closing sight if user cancel or if wrong password have been entered 3 times * updated ExActivities plugin configuration to force encryption and to exit on password error * "once" passwod policy configuration has been changed to "global": "global" which means the global password will be used, if set, otherwise or if wrong, it will be asked to the user * "default" configuration has been removed. User should simply not set any configuration if they want the default to apply *Add a slot in SLandmarks scene 3d adaptor to take care of landmarks' group renaming.* *Replace delete by deleteLater in QtContainer class.* To get rid of the deletion order we use deleteLater() function on the m_container QPointer. *Destroy the GUI elements in the reverse order in IGuiContainer.* ### viz *Use a read lock on the image when possible in SNegato2DCamera.* When receiving the resize event, we may end up with a deadlock if the image is already locked in writing. This may happen for instance with a writer that uses a progress bar. *Switch current drawable when swiching OpenGL context.* # sight 22.0.0 ## Enhancement: ### build *Update VCPKG.* VCPKG packages has been rebuilt, and the hash has been changed. All dependency packages have kept the same version number, so it is unlikely that a change of behavior occurs. *Remove libxml2 from the core public interface.* *Upgrade code to support C++20, and use some new features.* * Build with C++20 standard * Update the code to fix the following deprecations: * implicit capture of ‘this’ via ‘\[=\]’ is deprecated in C++20 (see [reference](https://www.nextptr.com/tutorial/ta1430524603/capture-this-in-lambda-expression-timeline-of-change) ) * Replace usage of `boost`::ublas``by glm (the current version does not build with C++20, Debian is shipped with an old version of boost...) * Use a new vcpkg build with a patched ITK version for C++20, and thus some minor adjustments because of new package versions (DCMTK and openCV for instance) * Use std`::ranges`algorithms in some places just for fun :heart_eyes: *Update VCPKG packages.* ### ci *Disable debug dialog on Windows on the CI.* When a unit test fails with an assertion failure in Windows Debug, a debug dialog will appear. This is useless in the CI, where physical access is required to view the dialog and know where is the problem. These changes remedy this, by disabling debug dialog and printing messages in the console when the environment variable DISABLE_ABORT_DIALOG is set. *Disable thread information from GDB in the CI.* *Use Sheldon's Clang-tidy hook for lint job.* Now that Clang-tidy is added as a hook for Sheldon, we can use it instead of using Clang-tidy directly, to reduce code redundancy. *Check whether packaged applications are executable.* Existing testing to prevent regressions isn't sufficient, as experience has shown. The packaged applications will now be tested automatically in the CI: SightViewer and SightCalibrator will be installed, and then executed in order to check if they start successfully or crash because of missing dependencies or various errors during installation. Also, all examples, tutorials and utilities will be launched to verify, at least, that they can start and stop. *Add Clang-Tidy to the CI.* *Add some metrics.* This shows the number of warnings and deprecated declarations in the merge request metrics reports. ### core *Create the log in user cache directory by default.* Default log file path, when nothing is specified with --log-output in the user "cache" directory, which is given by $XDG_CACHE_HOME on Linux or simply $APPDATA on Windows. As a fallback, the default temporary directory is used. In both cases, the path is suffixed with ./sight/. The final path is also displayed on the console. > VTK.log and Ogre.log has been taken into account... Some (light) adjustment and cleanup have also been made in core`::tools::Os`to manage "cache" directory and return a std`::filesystem::path`instead of a std::string. this could be a possible breaking change as you may have to call std::filesystem::path::string() when calling getUserConfigDir() (already fixed in this MR). getUserDataDir() name is misleading. Since it returns XDG_CONFIG_HOME it should be called getUserConfigDir(). It was hard to not change this in this MR for me, so it was done.... And last but not least, a small "fix" in PreferencesTest to clean correctly the user config directory after the test pass. I come through it while changing the getUserDataDir() returned type to std::filesystem::path *Remove hard-coded label value to generate mesh.* provide a from input the value to generate mesh and implements the unit tests for the codes. *Enable usage of lamba functions in slots.* It is now possible to use lambda functions in slots. For instance, this declares a slot making the sum of two integers : ```cpp auto slotSum = core::com::newSlot( [](int a, int b){ return a + b;} ); ``` *Improve camera resolution selection.* The camera service selection now provides a new setting `` allowing to automate the choice of the resolution. The value can be `min/max/WxH/preferences/prompt`. - `min`: the minimum resolution is automatically chosen. - `max`: the maximum resolution is automatically chosen. - `WxH`: the exact resolution is set (for instance 1280x720). Beware because if the device does not support the resolution, an error message will be displayed and the camera will not be available. - `preferences`: default value, the dialog is shown on the first time and then the choice is stored in the preferences of the application. This choice will then be used each time. An extra button is displayed next to the camera selector to change the resolution later. - `prompt`: former behavior which makes the resolution dialog to always prompt. *Add initializer_list constructor support for KeyConnectionsMap.* A new constructor was added to KeyConnectionsMap allowing to shorten the implementation of `IService::GetAutoConnections()`. For instance : ```cpp IService`::KeyConnectionsMap`SSample::getAutoConnections() const override { return { {"data1", data::Object::s_MODIFIED_SIG, s_UPDATE_SLOT}, {"data2", data::Object::s_MODIFIED_SIG, s_UPDATE_SLOT} }; } ``` instead of : ```cpp IService`::KeyConnectionsMap`getAutoConnections() const override { KeyConnectionsMap connections; connections.push("data1", data::Object::s_MODIFIED_SIG, s_UPDATE_SLOT); connections.push("data2", data::Object::s_MODIFIED_SIG, s_UPDATE_SLOT); return connections; } ``` The previous declaration is still possible. Few implementations were changed as example. *Secure library loading when upgrading sight version.* When executing sight in build tree, we may load the wrong version of libraries when upgrading. Now, we use the ."so" symlink first to be less sensitive to version change. ### geometry *Remove charuco related codes.* ### io *Add optional receive timeout on igtl server.* * when using the timeout receive fonction aren't blocking * also remove client from vector when disconnected ### navigation *Download openvslam vocabulary only when SOpenvslam starts.* * create a function to donwload file using curl in io_http library * minors fixes on SOpenvslam, SFrustum & SFrustumList to match latest updates ### test *Add tests for the HiResTimer class.* *Improve geometry::data::Mesh::transform unit-test.* The unit test for `geometry`::data::Mesh::transform``was pretty weak, as the test was mostly the same code as the tested code. The method is now tested with a hardcoded small mesh which is transformed, with all combinations of types possible (only points, point and point normals, point and cell normals, point and all normals). ### ui *Add an optional scroll bar for SParameters.* A `scrollable` property was implemented in `SParameters`, which adds a scroll bar to the right of the widgets. *Add a new signal to notify if the next activity is ready.* Two signals were renamed in `sight::module::ui::qt::SSequencer`: - `enabledNext()` -> hasNext() - `enabledPrevious()` -> hasPrevious() One signal was added, which is triggered when the next activity is enabled (all requirements are satisfied): - `nextEnabled()` *Simplify notification API by using single signal & slot.* * One signal "notified" in IService containing the type & the message * One slot "pop" in SNotifier reading type & message and displaying the corresponding notification popup *Move the modal transfer function editor in the right panel in SightViewer.* ### viz *Add pixel values in 2D negatoscopes.* We added the ability to pick pixel values in 2D negatoscopes, in the same way than it was already done in 3D negatoscopes. *Use image GPU resource sharing in all adaptors.* *Speedup the tf upload by 40x.* *Set SNegato2D adaptor transformable.* *Merge ExOgreRGBDStream and ExRealSense together.* The ExOgreRGBDStream sample was somehow useless since the only RGBD camera we do support is the IntelRealsense, which is demonstrated in ExRealSense. We added a missing feature from ExOgreRGBDStream into ExRealSense, the transfer function editor. The sample was also modernized a bit and debug optimizations were enabled in some modules to keep good performances in this build type. *Merge SAxis and SScaleValues together.* *Enlarge the viewport shutter to contain histogram and tf values.* ## Bug fixes: ### build *Fix various CPPCheck warnings / errors.* *Change windows export handling on lib with QT in the name.* *Small fixes to support gcc / libstdc++ 12.* *Configuration issues with GLM.* Since GLMConfig.cmake is correct on Windows and buggy on Ubuntu, we use a different approach depending on the platform: * Use the old-ish system with `target_include_directories` on Linux * Use `glmConfig.cmake` on Windows (but everywhere this time) *Clang-tidy warnings.* Here is the complete list: - readability-duplicate-include - readability-container-data-pointer - cppcoreguidelines-virtual-class-destructor - cert-err33-c - readability-identifier-naming - cppcoreguidelines-pro-type-cstyle-cast - clang-analyzer-core.NonNullParamChecker - clang-analyzer-core.NullDereference - clang-analyzer-core.CallAndMessage - cppcoreguidelines-non-private-member-variables-in-classes - google-explicit-constructor,hicpp-explicit-conversions - bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp - modernize-use-using - cppcoreguidelines-macro-usage - bugprone-macro-parentheses - hicpp-exception-baseclass - clang-analyzer-security.insecureAPI.strcpy - bugprone-too-small-loop-variable - bugprone-inaccurate-erase - cert-flp30-c - bugprone-incorrect-roundings - bugprone-suspicious-include - google-build-explicit-make-pair - google-global-names-in-headers - readability-redundant-control-flow - bugprone-throw-keyword-missing - readability-static-definition-in-anonymous-namespace - readability-string-compare - hicpp-move-const-arg,performance-move-const-arg - cppcoreguidelines-interfaces-global-init - readability-misleading-indentation - cert-err09-cpp,cert-err61-cpp,misc-throw-by-value-catch-by-reference - bugprone-parent-virtual-call - modernize-redundant-void-arg - misc-unused-using-decls - performance-inefficient-algorithm - modernize-make-unique - readability-suspicious-call-argument - bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions - bugprone-undefined-memory-manipulation - google-readability-function-size,hicpp-function-size,readability-function-size - readability-container-size-empty - bugprone-integer-division - readability-use-anyofallof - cppcoreguidelines-slicing - modernize-raw-string-literal - performance-unnecessary-copy-initialization - boost-use-to-string - readability-redundant-smartptr-get - hicpp-use-emplace,modernize-use-emplace - cert-msc32-c,cert-msc51-cpp - cppcoreguidelines-pro-type-const-cast - hicpp-multiway-paths-covered - performance-inefficient-vector-operation - cert-err34-c - performance-faster-string-find - clang-analyzer-optin.cplusplus.VirtualCall - readability-isolate-declaration - readability-qualified-auto,llvm-qualified-auto - clang-analyzer-deadcode.DeadStores - google-build-using-namespace - readability-delete-null-pointer - bugprone-implicit-widening-of-multiplication-result - misc-unused-alias-decls - readability-uppercase-literal-suffix - bugprone-branch-clone - performance-inefficient-string-concatenation - bugprone-misplaced-widening-cast - cppcoreguidelines-no-malloc,hicpp-no-malloc - performance-no-automatic-move - performance-for-range-copy - modernize-loop-convert - readability-inconsistent-declaration-parameter-name - modernize-avoid-bind - bugprone-forward-declaration-namespace - modernize-make-shared - performance-trivially-destructible - cert-dcl21-cpp - modernize-use-transparent-functors - readability-implicit-bool-conversion - cppcoreguidelines-init-variables - hicpp-noexcept-move,performance-noexcept-move-constructor - modernize-pass-by-value - hicpp-named-parameter,readability-named-parameter - google-runtime-int - hicpp-use-equals-delete,modernize-use-equals-delete - cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator - cert-oop54-cpp - readability-simplify-boolean-expr - modify .clang-tidy configuration file - bugprone-sizeof-container - llvm-else-after-return,readability-else-after-return - hicpp-use-auto,modernize-use-auto - cppcoreguidelines-pro-type-member-init,hicpp-member-init - cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays - hicpp-deprecated-headers,modernize-deprecated-headers - hicpp-use-equals-default,modernize-use-equals-default - readability-redundant-string-init - readability-redundant-member-init - modernize-use-default-member-init - hicpp-use-nullptr,modernize-use-nullptr - cppcoreguidelines-prefer-member-initializer - readability-avoid-const-params-in-decls - llvm-namespace-comment - modernize-return-braced-init-list - readability-const-return-type - modernize-use-nodiscard - readability-make-member-function-const - readability-convert-member-functions-to-static,readability-static-accessed-through-instance - modernize-concat-nested-namespaces - hicpp-use-override,modernize-use-override - cert-dcl58-cpp *Small corrections on filter_image to build with ITK5.* * disable spheroidExtraction * Add missing components *Move Ogre plugins next to the Ogre libraries.* *Remove warnings in modules.* These changes remove warnings in Sight modules. These warnings were mostly harmless, but could hide potentially harmful problems. *Remove warnings in libraries.* These changes remove all compile warnings in Sight libraries. These warnings were mostly harmless but could hide potentially harmful problems. *Correct windows application packaging.* *Forward script arguments when using privilege escalation on Windows.* ### ci *Windows SDK package made relocatable.* Sight windows SDK package is now relocatable. All mentions of the build tree are removed with careful usage of CMake. A test has been added to the CI to prevent any regression. *Reorder plugins loading in Tuto01DataServiceBasicCpp to avoid crash at exit.* *Migrate artifacts download to nextcloud.* *Remove redundant coverage report in Sight config.* ### core *Detection of runtime library folder.* *Reset the count before resetting BufferObject to avoid lock count assert at buffer destruction.* Resetting the counter in the destructor **BEFORE** resetting BufferObject shared pointer is required ! Otherwise, the lock count assert in the destruction of the buffer, in `BufferManager::::unregisterBufferImpl()` will be triggered. Also remove hack in SDistorsion service. *Parsing of transfer function data in XML.* The transfer function data can be initialized directly in XML configurations. Because of a regression, the parsing was broken and caused a crash at start. *Really disable SIGHT_DEFAULT_PASSWORD when not set by user.* *Consider multi arch folders in runtime directory detection.* *Use priorities to unload modules loaded after profile start.* Priorities are now used to unload all modules, even those loaded after the initial start of the profile. This allows notably to unload `appXml` first, and then modules like `module::viz::scene3d`. To achieve this, the start and priority properties are now set directly in the `plugin.xml`. The priority is read at runtime when parsing modules. It is used both to start and to stop modules. Some code cleanup was performed, which led for instance to remove the over-designed Starter and Stopper classes. This part of the profile management should be clearer now. The sorting of modules in the generation of the `profile.xml` is _de facto_ no longer necessary and has thus been removed to simplify the code. *Set the log output in the main of unit-tests.* The redirection of the log into `fwTest.log` is performed by a singleton in sight::core::utest. We link with this library in coreTest, but we do not use any symbol from it. Thus the linker strips it and so in the end, we do not initialize the log output. To fix this, we simply initialize it in the main file of unit tests. *Prevent race condition in serviceTest.* *Prevent race condition when adding a new id.* ### geometry *Update intrinsic parameters in SPoseFrom2d.* When intrinsic parameters, such as the resolution of the camera, change, the computation of the 2D pose did not take this into account and used the parameters set at start. Now we listen the changes of the parameters and update accordingly. ### io *Use a patched ITK header when using clang + Ubuntu 22.04 support.* - update sight-deps and use Ubuntu 22.04 image - vtk mesher has been fixed upstream *Overwrite the seriesDB in the ITK reader, make *.nii the default.* *Add patched version of buggy igtl functions.* * Add a modify version of igtlSocket.h header, included first it allows public access to socket descriptor (needed for a proper closing function) * Patched version of createServer to call patched version of bindSocket() * bindSocket: remove need to define VTK_HAVE_SO_REUSEADDR to add SO_REUSEADDR option in socket *Throw exception for common socket error (timeout, ...).* * INetwork will now throw exceptions if timeout, network error, or size mismatching of received data instead of returning a nullptr of igtl::MessageBase. * Create unit test to test server/client communication *Do not restore the UUID of objects when deserializing.* Restoring the UUID of objects implies that a serialized object is really unique. Thus if you read a file containing a object twice, you expect to have only on object in memory. This is definitely not the behavior we want, we expect the object to be duplicated each time it is read, with its own UUID. ### navigation *Remove vocabulary download from cmake.* Since the download is now performed at runtime in C++ ### test *Change float comparison equality strictness of copies.* ### ui *Deadlock when exporting transfer function.* Because SWriter`::update`locks data and so does the function before calling this, a deadlock occurred. *Allow to use subtoolbars in toolbars.* This fixes two bugs preventing from using sub toolbars in toolbars: * parsing error in `` tag attribute in the toolbar config * wrong alignment used in child toolbars *Block signal when enum parameter is updated in SParameter.* * also look in both item text and data as fallback to find value of enum *Initialize the type selection combobox with the right value.* *SNegato2D Camera crash when a single frame is provided.* *Run appXml code on the main worker thread.* This fixes a regression caused by the removal of the second initialisation stage of `IPlugin`. All `initialize()` functions, notably the one from AppXml, were called from the Qt event loop. The function loading the stylesheet is posted just before on this Qt event loop, which implies it is called before the initialisation of `AppXml`. With the removal of `initialize()`, the `start` function of `AppXml` was called directly from the main thread, before the start of the Qt event loop. Thus, the function loading the stylesheet was executed after the start of the application, causing rendering issues. It seems safer anyway to keep the previous behaviour. So, `AppXml` was slightly modified to execute the start of the application on the Qt event loop. *Various issues when loading meshes in SightViewer.* - random crash when the camera is not reset - reset the camera when a model is loaded in SightViewer - enable the hide/show model when a model is loaded - do not enable the image buttons when a model is loaded ### viz *Display of images with 32 bits integer pixel format.* This restores partially the behavior prior to 1353d895 that converts 32 bits integer images into 16 bits images when uploading them on the GPU. This fixes a regression that prevents from reading all 32 bits integer DICOMs. *Avoid deadlock in viz::scene3d::IParameter with textures.* *Invert Plane constructor param to fit SNegato usage.* *Out of bounds writing in function transfer.* An error was introduced that could fill one pixel too many, causing a crash when deleting the transfer function, potentially when exiting the application on Windows. *Add a workaround for NVidia Prime to solve a deadlock with several 3D scenes.* *Blend transfer function pieces proportionally to their opacity.* *Do not allow to move TF points outside the viewport.* *Do not reset the opacity when changing the color of a TF point.* *Change Ogre plugin location.* Ogre needs to locate its plugin at the start, in a similar way as Qt does. Over the years, we had different strategies to locate them which always fails in the end in some corner cases. Well, I finally propose to use the exact same strategy as we do for Qt, which is to locate the core Ogre library and then use a relative filesystem path from there. It has proved to be a winning strategy with Qt. ## New features: ### core *Forward SParameters signals from SGrabberProxy and IGrabber.* *Remove SeriesDB, Activity and Camera are stored in separated set.* - rename ActivitySeries to Activity - rename CameraSeries to CameraSet - rework deep/shallow copy to use full inheritance and to copy parent class - change ImageSeries inheritance to Series and Image - remove Patient, Study, Equipment data object and use GDCM to store DICOM attributes *Add generic container classes.* #### Description Implements a generic templated data container class, aka `IContainer`, that allows code factorization from all containers. STL API from the corresponding STL container classes is exposed and the inherited containers, such `Composite` and `Vector` can be seen as a real `std`::vector``and `std`::map``from outside (which they indeed are !), while keeping to be data objects, that can be used in XML, that can be serialized, etc.. Since it is a part of https://git.ircad.fr/sight/sight/-/issues/862, some containers (`ActivitySet`, `CameraSet`, `SeriesSet`) have been also implemented. Although not used yet, you can still take a look at them, as `CameraSet` uses a more complex `std::vector>` while `ActivitySet` and `SeriesSet` use a [Boost Multi-index](https://www.boost.org/doc/libs/1_79_0/libs/multi_index/doc/index.html), which allows to define a `set` which is sequenced like a `vector`. ##### Usage The best open `Composite` or `Vector` and `IContainerTest` code, but basically, all you have to do if you need a specific data object container class, is to inherits from `sight::data::IContainer`, with `XXX` being your STL container type (or boost STL compatible container, like Multi-index !). As an example: ```c++ #include "data/IContainer.hpp" class DATA_CLASS_API Vector final : public IContainer > { ... }; ``` You can then use it like a real std::vector: ```c++ auto vector = sight::data::Vector::New(); // 99.9% of STL API is available vector.reserve(2) vector.push_back(sight::data::Integer::New(1)); vector.push_back(vector.front()) vector[1] = sight::data::Integer::New(2) ... // Initializer list / assignment operators vector = {sight::data::Integer::New(1), sight::data::Integer::New(2), sight::data::Integer::New(3)}; // iterators are supported for(const auto& element ; *vector) { std`::cout`<< element->getValue() << std::endl; } ``` ##### ScoppedEmitter There is also a generic `ScopedEmitter` which replace various "helpers", that were used to send signals when adding / removing objects in the container. It uses RAII mechanism to send the right signals when it is deleted. To use them, simply call the generic `scoped_emit()` function form a container to get a `ScopedEmitter` instance, and perform operation on the container. The ScopedEmitter will take a snapshot of the content and compare it with the current container upon destruction. Signals will be fired if elements have been added or removed or changed (in case of a map like container). Short example: ```c++ auto composite = sight::data::Composite::New(); { auto scoped_emitter = composite->scoped_emit(); // Now modification to `composite` will be notified to whatever is connected to `composite` signals composite->insert({"beast", sight::data::Integer::New(666)}); ... // Signals are sent when notifier is destroyed, like outside this scope... } ``` ##### Advanced usage ###### Writing specific code for specific container Sometimes, it is useful to know if an object is a container and if yes, from which kind it is. Some template matching functions have been added to `core/tools/compare.hpp`: ```c++ #include template void my_function(const T& truc) { ... if constexpr(core::tools::is_map_like::value) { truc.insert({"maman", value}); } else if constexpr(core::tools::is_container::value) { truc.insert(value); } else { .... } } ``` ###### Generic container without inheriting from `IContainer` If you don't want the `IContainer` inheritance because you don't want to be a `sight::data::Object`, but still want to act like a `XXX`, you could inherit directly from `ContainerWrapper`: ```c++ class MySimpleContainer : public ContainerWrapper> { ... }; MySimpleContainer a; a.insert("Le petit chaton bleu est très malade."); ... ``` ### io *Add requestSettings slot to grabber interfaces.* *New DICOM reader implementaion.* A new DICOM reader has been implemented to take use of the shared DICOM context, allowing to have access to all DICOM properties from the original DICOM files. The new reader is also able to read DICOM IMAGE files that were not well-supported, most notably: * All Ultrasound Images IOD (Enhanced US Volume, Ultrasound Image, Ultrasound Multi-Frame Image) * RGB Image with YBR color space (used in jpeg image) * Encapsulated transfer syntax * Unusual planar configuration (r1r2r3...g1g2g3...b1b2b3 instead of r1g1b1r2g2b2r3g3b3...) * Monochrome 1 image Few bugs have also been corrected, and some enhancement has been done: * Allows selecting the series to read in case there are more than one series found * Huge speedup: 4x faster reading * 2x less memory used * Fixed many bugs in the Series selection dialog: * crash with date/time on anonymized data (which contained unusual dates) * wrong data displayed * wrong data selected * added utf-8 / internationalization support (yes Ren**é** is now displayed correctly, and far east patient are no more a number of squares) * Thread management of GUI stuff like cursors, DICOM logger dialog, etc.... * some tests were added (and also US data on sight-data) *Update IWriter interface to automate data output.* * Automate the output of data using Preferences and Series for the SFrameWriter, SVideoWriter and SMatrixWriter services *Refactor io itk services and add nifti support.* Global refactor of the itk io services and libs. * add of the nifti support in the lib and in the modules * refactor the lib existing read/write files to fit the global sight io naming * refactor the services, to make a single SImageReader/SImageWriter which switch depending on the extension * refactor the services, to have reader/writer which go together * add unit-test * add itk reader in sightviewer images series loader ### test *Add a macro saver to generate GUI test skeletons.* GUI testing is now possible, but creating GUI tests still isn't that simple, especially to get the graphic components to interact with. MacroSaver will generate a GUI test skeleton compatible with GuiTester to save that hassle. To use MacroSaver, one must call sightrun with the `--macro` flag, the generated tests will be available once the application is closed as the files "GuiTest.cpp" and "GuiTest.hpp". *Add a GUI test library and GUI tests.* An automated GUI test library built on top of QTest was introduced. This library can wait and get graphic components, interact with them by emulating mouse and keyboard events and check if assertions are true. In order for the graphic components to be fetched more easily, components got objectNames, so they can be easily found using `QObject::findChild()`. Additionally, the tests will test the 3D rendering, by creating snapshots and comparing them with reference images using 6 different methods (pixel by pixel, mean square error, cosine of the matrices, histogram, Spearman's correlation,...). When a test fail, it will take a screenshot of the screen, to help in troubleshooting, especially when it runs on an non-desktop environment, such as the CI. Last, first UI tests were introduced for SightViewer and SightCalibrator. ### ui *Add standard embeddable video toolbar.* *Allow to programmatically set enum values in SParameters.* ### viz *Introduce GPU resource sharing.* GPU resource sharing is introduced, with the first application to textures and transfer functions. From the user point of view, we provide a new class `sight::viz::scene3d`::Texture``and we extended the existing class `sight::viz::scene3d`::TransferFunction``in order to allow GPU resource sharing. These classes only allocate Ogre resources (and thus GPU memory) when the Id of the source object changes. In addition, they "cache" the update of these resources. Thus now, when we have, for instance, 3 negato adaptors on the same image, there is only one `Ogre`::Texture``created, and only one `Ogre`::Texture``updated when the `sight`::data::Image``is modified (and similarly for the transfer functions). *Allow applications to add Ogre plugins from CMake.* We added the possibility to enable Ogre plugins from CMake `module_param(module_viz_scene3d ...)` calls. ## Refactor: ### build *Use a single PCH with only external dependencies.* To solve the issues described in #948, we now provide a single PCH file. It only contains 3rd part libraries headers. The list was extracted from the previous pchServices recursive list, by removing all sight headers. It is also built in a second version for the targets that use optimized debug builds. During tests, we realized the build timings remain stable and even outperform sometimes the previous approach. On Clang, we also took the opportunity to use a new feature that speedups PCH builds: http://llunak.blogspot.com/2021/04/clang-precompiled-headers-and-improving.html. The gain was measured at 10%. ### core *Rename oldish macros.* *Replace EConfigurationElement with boost::property_tree::ptree in all unit tests.* *Remove visibility and transparency signals on images.* The visibility and transparency of images are now handled in a simpler way, following the MVC pattern: - the image is the model - the view is the adaptor (`SNegato2D`, etc...) - the controller is the widget (`SParameters`, `SAction`, etc...) The view does not control the state and only applies what the controller requests. This implied the following changes: - Removed s_TRANSPARENCY_MODIFIED_SIG and s_VISIBILITY_MODIFIED_SIG fields from `data::image` - Removed all associated code, including the old service `module::ui::qt::image::ImageTransparency` *Simplify core::tools::Type and massive core::tools cleaning.* `core`::tools::Type``was simplified for easier use. First, it was moved to the root of the library, so it is now `core::Type`. Then, it simply contains a single enum attribute that sets the supported type. All methods of the class use an internal map to answer to the capabilities of the type (size, signed, name, etc...) instead of storing them in each type instance. Some extra cleaning was also done. `core`::tools::Type``is a very old piece of code, previous to C++11 and was linked to some pieces of dead code. Many features it used to bring are now done easily with post C++11 code. The dispatcher, for instance, used exclusively for ITK filters, relied on `type_info` just because of the use of a deprecated ITK function to get the image type. With the new function, this is no longer necessary, so a large amount of code dealing with `type_info` was removed. *No longer use xml profile in utest, remove initialize state in IPlugin.* ### ui *Deprecated SSlotCaller in favor of SAction.* *Use new signals and slots of IAction everywhere.* *Harmonize IAction signals and slots.* *Remove deprecated services, functions and config.* `ui`::viz::SAddPoint``is removed in favor of `module::geometry::SManagePointList`, `IAction::getActiveStateValue()` is removed in favor of `IAction`::inverted``and `ISlideViewBuilder` `align` and `size` configuration attributes are removed in favor of `halign/valign` and `width/height`. ### viz *Remove transfer function merge copy in the data.* We faced multiple floating precisions issues when merging the pieces of a transfer function. To avoid that, we removed the pre-merge phase of the pieces and we actually merge directly when sampling. *Simplify transfer function management.* This is a major rework of the transfer function data. Now the transfer function is a real piecewise function. Each piece stored in the object can be individually edited and thus is serialized. Pieces can be accessed and edited independently thanks to the `pieces()` function that returns a vector of `TransferFunctionData`. When the pieces are modified, the function `mergePieces()` must be called to mix the pieces together. This implies that the duplicated `sight`::module::ui::qt::SMultipleTF``and `sight::module::viz::scene2d`::SMultipleTF``are removed in favor of `sight`::module::ui::qt::STransferFunction``and `sight::module::viz::scene2d::STransferFunction`. On top of that, the former TF "pool" is no longer exposed anywhere in the services interfaces, thus from the XML. `sight`::module::ui::qt::STransferFunction``handles this "pool", renamed as "presets", internally. Other changes were brought: - `sight::data::TransferFunction::mergePieces()` implies the removal of `sight`::module::data::SMergeTF``service. It also brings a functional change, the pieces of the transfer function is performed with an average of colors instead of a strict additive blending. - It is no longer possible to move a transfer function piece outside the range of the viewport (bug fix request in the comments of this ticket). - Adjusting the window in the negatoscope is now reflected properly in the transfer function editor (partially fixes https://git.ircad.fr/sight/sight/-/issues/847) - Moved and renamed `TransferFunctionManager` from `scene3d` to `scene2D::TransferFunctionWindow`. *Include TransferFunctionManager from TransferFunctionManagerWindow.* *Merge SHistogram, SHistogramCursor and SHistogramValue together.* Since we removed SCurveHistogram, there was no good reason to keep those three adaptors separately. Doing that, we improved the computation speed of the image histogram by a factor of 10. Computing the histogram is now so fast that it is useless to have a dedicated data for it. We removed all the ecosystem around it and the only service that uses it so far, the SHistogram adaptor, computes the histogram dynamically. On top of that, this allows to change the bins width dynamically which is very handy. We also added an axis for the image histogram that can scale with the graph. This required a refactor of the usage of the viewport in the 2D scene. Now the scene no longer holds a viewport, but can only update its viewing rect through a dedicated function available to the adaptors. The adaptors that do use the viewport now all explicitly declare it, with the correct access type (in or inout). Last, we scale the transfer function viewport range automatically from image min and max intensities. Many warnings were fixed along, notably because of the coexistence of float and double variables. The floating point precision was set to double everywhere to comply with Qt API. *Remove irrelevant SCurvedHistogram service, use SHistogram instead.* # sight 21.1.0 ## Refactor: ### build *Apply cmake-linter rules.* ### core *Remove last bits of data reflection based on camp.* Data reflection based on the camp library was definitely removed from _Sight_. It consisted in removing the files used to declare data reflection and all files that used camp. Most usages of data reflection was removed in previous merge requests, like serialisation. However, some last services still used data reflection and were modified accordingly: #### module::ui::qt::model::SModelSeriesList This service displays reconstructions in a tree widget. It used data reflection to display any model series attribute. However practically, it was only used to display the organ names, structures types and volumes. Those three possibilities were hard-coded instead. #### module::ui::qt::series::SViewer `SViewer` shows a preview of data. Normally, it takes input data, but a similar mechanism was in place to allow data extraction like in activities. This was a bit over-designed and practically only used to extract images from image series. The image preview activity was thus modified to extract the image with the new `SGetImage` service. #### module::data::SCopy The most annoying service was `SCopy`. Like `SExtractObj`, it was capable of using a path camp to copy sub-objects. It was used abusively in SightViewer, probably to avoid deferred objects. This was bad since model series and images are present multiple times in memory. Nevertheless, this could not be changed easily. Two new services needed to be written. - `module`::data::SGetVector`:`very similar to `SGetSeries`, it allows to extract elements of a `sight::data::Vector`. - `module`::ui::qt::series::SSelectDialog`:`the intent of this service is to pop-up a dialog to let the user pick a series. Indeed in SightViewer, we may load several series, but only the first one is displayed. So for instance, if we read a CT-scan with 3 different blood times, we can only display the first one. This service also resolves the problem of the type of output. It allows to output the result to different objects according to their type: image or model. With the constraint of time for the incoming release, this service is proposed in the state of a draft. It does not prompt the dialog yet, instead, it takes the first element. This allows at least to keep the same level of functionality in SightViewer. It will be finalized in Sight 22.0 *Remove oldish SPushObject and SPushField, rename SObjFromSlot.* We are very close to finish the rework we started years ago about clarifying object selection services. - `SPushObject` and `SPushField` were outdated, and can be replaced easily by the `SManage` service. So we simply removed them. - `SObjFromSlot` does not have a meaningful name. It is renamed as `SSelectObject` and no longer inherits from the also meaningless `IUpdaterSrv` interface, but rather from `IController`. *Simplify workers management.* Firstly, this was an intent to add a function to unregister workers, but in the end, this is a complete rework simplification of the `ActiveWorkers` registry. Here is a summary of the changes. No longer exposes the ActiveWorkers registry publicly. The only interest would be to have multiple registries... Mmm really? I don't think that's useful. So then if there is only one registry, as a user, I don't care that this is a singleton, I even don't care there is a registry at all. I just want to add and remove workers, right? Now, people are supposed to add and remove the workers themselves. There is no more "global" cleaning method (formerly `cleanRegistry()` ). Symmetry for the win. The initialisation phase has changed, which is somehow a breaking and important change. **So please read the following carefully**. Formerly, no default worker is created by default. It was created either by calling `initRegistry()` (instantiating a `workerAsio` or by calling `setDefaultWorker` (in concrete use cases, either a `workerAsio` or a `workerQt`). In most apps (all Qt apps of course), services, and unit tests, a worker was created. But in rare cases, no worker was set, which implies async functions such as `IService::start()`, `IService::stop()`, etc... were in fact executed synchronously. With this MR, a `workerAsio` is created at the start and set as the default worker. If someones (actually only Qt Apps) want to set a different worker (the `WorkerQt`), it can call `setDefaultWorker()` and the default worker will thus be changed. The initial worker will be removed, but the function will raise an exception to avoid early slots registration with this worker. In clear, `setDefaultWorker()` is intended to be called at the very beginning of an application and only once. Even clearer, this is going to be called only once by the main Qt and Qml plugins. So actually, no one bothers, whereas before, everyone writing a unit-test or an executable app would have to think about it and call it. Last a `resetDefaultWorker()` had to be implemented, just because QApp doesn't like to be destroyed during static variables cleaning, after the main exits. So this `resetDefaultWorker()` is actually called twice symmetrically with `setDefaultWorker()` in the main Qt and Qml plugins. *Merge fieldHelper into helper/MedicalImage.* * all functions are now static and class is replaced by namespace MedicalImage * unused functions was removed * unit test of MedicalImage was improved to test all helpers methods * Update services to use only integer values instead of pointers for slice indexes *Rename all getNumberOf*() functions to num*().* *Clean old deprecated array API.* *Clean old deprecated image API.* *Modernize mesh API.* ### io *Remove module::io::atoms and replace it by module::io::session counterpart.* All of module`::io::atoms`has been removed and replaced by module`::io::session`counterpart. `Patch` and `Filter` features of atoms have also been removed. io`::session`has been upgraded to support simple JSON on filesystem serialization. Additionally, a new preference system, using Boost property tree, has been implemented: # Preference The class is thread safe and use RAII mechanism to load and store data from and to the preference file. In a service, the basic usage would be: ``` c++ try { // Load Preferences preferences; std`::string`filename = preferences.get("debug.filename"); int level = preferences.get("debug.level"); ... // Save Preferences preferences; preferences.put("debug.filename", "log.txt"); preferences.put("debug.level", 2); } catch(const ui::base::PreferencesDisabled&) { // Nothing to do.. } ``` Which will be translated into: ``` json debug: filename: "log.txt" level: 2 ``` ## Configuration The configuration is done by static functions: - set_enabled: Enable or disable preference management as a whoole. All functions, including constructor will throw `PreferencesDisabled` exception if used while "disabled" - set_password: Set an harcoded password to be used. It enables defacto the encryption - set_password_policy: Defines how and when a password is asked. @see sight`::core::crypto::PasswordKeeper::PasswordPolicy`for possible values. > `NEVER` will never display any message box, but if a password has been set, the resulting preference file will still be encrypted. An `BadPassword` exception will be thrown instead of diplaying a message box, asking to renter the password. - set_encryption_policy: Define when the preferences file is encrypted: @see sight`::core::crypto::PasswordKeeper::EncryptionPolicy`for possible values. > `FORCE` will encrypt the file, even if no password is given. In this case a pseudo random password is used, which can be guessed if someone has access to the code. Another option is to use an harcoded password AND EncryptionPolicy::SALTED - Module Configuration: All the above can be configured through the module ui_base parameters (@see sight::module::ui::base::Plugin) The preferences are enabled by default, without encryption. An example of configuration would be: ``` cmake moduleParam( module_ui_base PARAM_LIST preferences_enabled preferences_password_policy preferences_encryption_policy preferences_password PARAM_VALUES true once salted a_bad_hardcoded_password ) ``` # Simple JSON serialization on filesystem It is now possible to use `SReader` and `SWriter` to serialize a data object into a single JSON file (until the serializer class also store additional files like for Mesh or Image object). ## SReader This is achieved withe the `archive` configuration node and its `format` attribute: - `filesystem`: Reads files from the filesystem. - `archive`: Reads files from an session archive. - `default`: uses the builtin default behavior which is `archive` ### XML sample configuration: ```xml ``` ## SWriter This is achieved withe the `archive` configuration node and its `format` attribute: - `filesystem`: Use the filesystem to store files. - `compatible`: Store files in a ZIP archive, with old deflate algorithm - `optimized`: Store files in a ZIP archive, with zstd algorithm - `default`: uses the builtin default behavior which is "optimized" ### XML sample configuration: ```xml ``` ### ui *Remove obsolete SSlider, superseded by SParameters.* ## Bug fixes: ### build *Fix application package.* * CMake configuration has been modified to ensure that sightlog binaries are always shipped with Sight, even when building a standalone application. * Ogre plugins install was not done because of a regression introduced when fixing Qml plugins install. * use the .bat instead of .exe to launch app in .lnk *Add tighter security in get_default_password access.* Add if def security around the get_default_password Export the SIGHT_DEFAULT_PASSWORD var from sight *Improve application version in package name.* *Allow to link with io_igtl from a child project.* *Prevent whole rebuild each time with CMake >= 3.20.* This was caused by the new CMake policy CMP0116 about the usage of the DEPFILE argument of add_custom_command. Since we did not succeed to use the NEW behavior yet, we switch back, temporarily, to the OLD behavior. *Qml applications on Windows.* Several fixes were brought to fix sight applications using qml on Windows: - determine qml plugin path at runtime instead of build time. We use the same strategy for Qt plugins. - install qml plugins correctly when building an installer - port deprecated QtQuick1 stuff to QtQuick2 *Enable warning as errors on sight_core.* Warnings as errors were enabled but sight_core, but this library first builds an object library. The flags were not passed to it. This has been fixed and some warnings were fixed as well. ### ci *Fix sheldon license date check.* - only build on Ubuntu 21.10 - fix code coverage html/xml report generation ### core *Replace assertion when slice index field doesn't exists.* `getSliceIndex` returns a std`::optional`that needs to be tested to know if field is present or not. *Object registration is overriden by XML configuration.* Previously, there was no data prerequisite in a service. One can declare any data in the XML configuration of a service, and use it at runtime with getters. So basically you could configure a service with any data. It was even possible to change the access type dynamically. Sight 21.0 enforced a prior declaration of data with `sight::data::ptr`. They allow specifying the key name, access type, as well as the auto-connect and the optional attributes. Normally, only declared data this way should be used in services. However, for back compatibility purposes (especially for the very specific `SConfigController` and `SConfigLauncher` services), it is still possible to register data just from the XML configuration. We discovered that this is actually buggy and the XML configuration overrides the `sight`::data::ptr``declaration. So it is for instance possible to declare input data and finally configure it as an inout. We also realized that the default auto-connect and optional attributes values were not respected with an XML configuration. The actual default values were those of the XML parser. This merge request first fixes the override of the data declaration with the XML configuration. It also fixes the XML parser to use the default auto-connect and optional attributes values declared in each service type. This required some extra work on group objects. Indeed, we used to have a minimum and a maximum number of objects. These were actually not really used, and developers misused this feature most of the time. They configured `sight`::data::ptr_vector``with the same signature as `sight`::data::ptr``which led to implicitly converting a true boolean meaning "optional data", in an integer meaning "at least one object". Thus the intent of the developer was actually the opposite of the actual result. The maximum number of objects was never used in real cases and was in fact introduced to solve an internal issue with non-XML apps. This was not a real need for the developer. In the end, `sight`::data::ptr_vector``now shares the same signature than `sight::data::ptr`. It is still possible to configure in the XML the auto-connect and optional on each member of the group, but this time the default value will be the one declared in the `sight`::data::ptr_vector``declaration. Last, fixing all of this highlighted some misconfiguration in a few services. These were corrected accordingly. *Update old "3DSimpleConfig2" by "3DSimpleConfig".* * ExDicomSegmentation & ExDump impacted *Remove dead lock and init mesh celltype properly.* *Attributes arrays not reallocatted on pushPoint and pushCell.* *Load of dynamic libraries when only versioned files are present.* On a regular Linux system, libraries are installed with the version in the extension, like libsight_core.so.21.0. The symlink without the version in the extension is only present if the development package is installed. Thus we can not rely on this symlink to load the library like we did since Sight 21.0. This commit switches back to the old way, looking for a file name that matches the library name. A cache has been added to avoid the cost of iterating over all library files each time. *Correct needle calibration.* Correct point pivot based nbeedle calibration algorithm fix header to allow include in external project misc: update coding style *Optimize data::iterator::ImageIterator.* *Check if service UID is used in signal/slot before collecting UID for parameter replace.* * parse also menuItem sid="..." attribute before collecting UID for replacements *Remove useless return after throwing exception.* *Use sight_core module path to determine the runtime dir.* *Speed-up image copy in SFrameMatrixSynchronizer.* The image copy using the image iterator is slow as hell in debug, making some applications unusable. This was replaced by a simple std::memcpy. *Add sight namespace to profiling macros.* ### doc *Update README to match Sight 21.0 changes.* ### geometry *Workarround to get rid of bufferObject assert still locked.* Force destruction of original buffer object of output image after the unlock of input & output image, doing this avoid the assert of bufferObject still locked. *Don't shift 2d points on when computing RMSE on calibration.* * Add option in SSolvePnP to shift points or not (default off) * Replace quad mesh plane.vtk by triangular mesh plane.vtp ### io *Replace deprecated OFIterator in favor of OFListIterator.* This deprecated container is removed in recent dcmtk versions. *Remove deprecated VTK types.* These types were deprecated and unused a long time ago. They are removed in latest VTK releases. *Extension() getter function in reader/writer made const.* ### test *Fix ui_qt_test not executing offscreen.* ### ui *Reduce lock scope to avoid a deadlock.* *Remove all deprecated QSignalMapper.* ### viz *Do not snap landmark onto slice plane in 2D negatoscopes.* Landmarks can now be moved properly as before with drag & drop. An extra correction was made to not snap the landmark onto the image plane in the 2D negatoscope views. This led to weird behavior when you adjust the position of the landmarks in two different orthogonal views. The landmark always snaps to the image plane, thus giving the effect of "jumping" in the other view, and preventing from adjusting the position finely. *Replace getTransformNode by a pure getter.* `getTransformNode` was ambiguous since it return the transform node if existing or creates it if it doesn't. This behaviour is kept but function was rennamed into `getOrCreateTransformNode`. A pure getter was also created `getTransformNode()` to return the transform node pointer or nullptr if it doesn't exists. Add small unit test to test ITransformable functions All adaptors was updated to call either `getOrCreateTransformNode` or `getTransformNode` regarding the case (creation of the node of getting the pointer). This fixes the crash due to Ogre exception "Node already exists" when trying to get a deleted node. *Move orientation marker when splitting views.* The orientation marker in the 3D view of SightViewer remained on the bottom right corner of the view when splitting views. Now it keeps attached to the 3D view. *Remove extra space in R2VB program preprocessor definitions.* Remove extra space in R2VB program preprocessor definitions. Sadly, this prevented the preprocessor defines of the R2VB geometry programs to be parsed properly, thus causing rendering issues. *Crash in Ogre resources loading.* The list containing the Ogre resources paths was not allocated properly. *Set texture unit state after changing a material template.* ## Enhancement: ### build *Update vcpkg pakage hashes to match last build.* *Add some flags for better performance and debugging.* *Default to warnings as errors.* *Forbid link with a module.* ### ci *Make the code coverage percentage more precise.* *Use shared scripts to deploy, add SightCalibrator deployment.* *Remove Ubuntu 20.10 Release target, replace it with Ubuntu 21.04 Debug.* ### core *Remove redundant internal lock in the Timeline object.* We removed internal locks in timeline functions. Now, we assume that all timeline objects are accessed by a lock() in the caller. *Return const pointer when using getters on const data.* Many getters in data returns shared_pointer, even if the data was const the returned pointer wasn't and underlying data was still modifiable. This has been changed to provide const getters that returns const pointer, and non const getter to returns simple shared_pointer. Doing so leads to some changes in part of the code that uses const data (usualy input data) * Use of const_cast as been restricted to 3 cases: * When moving buffer to ITK or VTK * SReconstruction when moving the pointer to data`::Material`to an internal SMesh adaptor. * SGetImage, since ImageSeries input is const, when doing a setOutput (non const) with series->getImage, a const_cast is needed to discard const qualifier. * Changing from inout to input access: * SMesh: mesh is now a const input. * Use const shared_pointer when possible *Simplify image fields initialisation, remove unused fields.* - `MedicalImageSrv` was simply removed. - Instead, `data::helper::MedicalImage::check*()` functions are called when reading or creating an image. An image is "always" a medical image, but this does not hurt to have extra fields for non-medical images. - `checkLandmarks()` was removed, the point list is now always present. - The following used fields were removed: slices count, comment, label. *Make dispalyCalibrationInfo more scalable.* *Remove obsolete atoms lib.* ### io *Move csv reader from io::dicom to io::base.* ### ui *Enable HiDPI scaling on Qt apps.* *Move notification stack up/down when oldest notification is closed.* *Add option to "read more..." when notification message exceeds maximum allowed characters.* *Upgrade default confirmation message on action.* *Add fullpath option for camera selection in Scamera.* style: apply sheldon style: apply review suggestion style: correct variable name style: apply coding style style: apply coding style style: correct comment ### viz *Make viz::scene3d::vr great again.* #### Description `viz::scene3D`::vr``has many feature that either don't work as intended or do not work at all. This is mainly due to bad service-library interactions and subtle bugs / flaws in some methods. Because of that, using this namespace as a base for subclasses requires substantial efforts to workaround those bugs / design problems. #### Changes ##### Library **Volume renderer:** - `IVolumeRenderer`::update``has been introduced to solve the problem of the initial issue (calls to virtual function from constructor). This member is `public` and `virtual`, and `override`n in `RayTracingVolumeRenderer`. - Shader source file is now specified upon construction. - As `RayTracingVolumeRenderer`::initEntryPoints``must only be called once, there is now a check it has not been called before. - New `struct`s wrapping attributes. - `createRayTracingMaterial` is now `updateRayTracingMaterial`, and does not take any parameter. - Some functions were renamed to something explaining what they actually do. - Some integer values are now `unsigned` in order to ease some asserts. **Volume rendering techniques:** - RAM leaks and follow the changes in the volume renderer (types). **Data management:** - Before this MR, the image resources was managed by **both** the service and the library. This is not the case anymore. The service now knows *almost* nothing about the data. Consequently, what was `weak_ptr` previously in the library are now `shared_ptr` or even `unique_ptr`. - The following are now set upon construction: - Raytracing fragment shader source file, - Ambient occlusion parameters, - Preintegration parameters, - Shadows parameters, - SAT parameters, - There used to be duplicated attributes in the service and the library, which represented the same flags but where not updated simultaneously. This is not the case anymore. Actually, this was the main cause of - #756, #791 and #792). ##### Module - Reported changes in the library (argument type changes, etc.) - New `struct`s wrapping attributes (config, etc.). *Improve flat shading by using fragment shader computed normals.* Flat shading may often be broken if point normals are shared between faces. To overcome this, we chose to compute normals in the fragment shader using eye position derivatives. Doing so, the outdated and never used Gouraud shading mode was removed. This removes a shader combination, which makes maintenance easier. ## New features: ### build *Forbid module linking, even for tests.* - Add a special verification step to forbid linking with a module. - Rename `fw_cppunit_test` to `fw_test`. *Allow privilege escalation on Windows.* *Allow to override the downloading of dependencies.* *Debian package build prevents networking access.* ### ci *Enable unit-tests report.* To output unit-tests results in JUnit format will allow us to see the results of the unit-tests in the GitLab web interface. *Attach gdb to running test to have a core file.* *Add code coverage to unit tests and Ubuntu 21.10 support in CI.* ### core *Remove sub-object extraction from activities.* Now, when defining a requirement in an activity, there is no need to define a corresponding `AppConfig` parameter to "replace" it with the corresponding key. In short, the same key is used for the requirement and the parameter, and there is no need to give a complicated object "path": - Before: ```xml ... ``` - After: ```xml ... ``` ##### Limitation However, this simplification implies some limitations: 1. The key must match for both requirement and `AppConfig` parameter. There is no way to "replace" them on the fly like before. We really believe this is fine as the ability to have a different name only complicate both xml (you need to specify the "mapping") and C++ code, with no real added value. 2. As we can only access to the "required" object, we cannot "walk" through the object hierarchy and extract sub-objects. For example, it is no more possible to have an `Image` as `AppConfig` parameter which is extracted from a required `ImageSeries`. This can be easily adapted with the use of the new extraction services (`SGetImage`, `SGetMesh`, ...) in `AppConfig` code. *New service sight::module::data::SGetCamera.* This new service extracts cameras and extrinsic matrices from a camera series. *New service sight::module::data::SGetSeries.* This service can be used to extract one or several series from a seriesDB. The XML configuration is fairly simple: ```xml ``` where: - seriesDB is a `sight::data::SeriesDB` - series group data are `sight::data::Series` *Remove core::data::ObjectLock.* - remove core`::data::ObjectLock`call and use straight locked_ptr in place. - rename all "dump" lock() (lock(), bufferLock(), lock_buffer(), ...) to dump_lock() to avoid confusion with "mutex" locks. - factorize dump_lock() call in IBuffered.hpp. - remove one deprecated TimeLine construct. *Implement SGetImage Service.* *Add == operators for all data object.* #### Description Now a data object of the same type can be compared using == like: ```c++ std::shared_ptr image1 = ...; std::shared_ptr image2 = ...; if(*image1 == *image2) { ... } ``` Floating point values are compared using a scaled epsilon (which gives result similar to [ULP](https://en.wikipedia.org/wiki/Unit_in_the_last_place) comparison). NaN, infinite values are also taken into account. Templated helpers `core::tools::is_equal()` have been added to ease comparison with containers, pointers and floating point values. *Encrypted log.* #### Description Our logging system is based on [boost::log](https://www.boost.org/doc/libs/1_77_0/libs/log/doc/html/index.html), which works fine, but doesn't provide a way to secure the content of the log file. We choose to use [minizip-ng](https://github.com/zlib-ng/minizip-ng) with built-in AES encryption as the backend to store the log and continue to use boost log as the frontend. We have also improved password management, to allow encryption to be used even without providing a password. The minizip backend runs in a separate and detached child process, so it will always produce a valid zip archive even if the parent process crashes. #### Additions / Changes ##### CMake - Enable log encryption support with new CMake build options: `SIGHT_ENABLE_ENCRYPTED_LOG` (OFF by default) - Allow to specify a default hardcoded (but obfuscated) password at compile time, set by `SIGHT_DEFAULT_PASSWORD` CMake definition. ##### Password management Password management has been reworked a bit to factorize and unify behaviors. ###### `SIGHT_DEFAULT_PASSWORD` It will be used when required (log encryption, preferences, session, ..) by default, until overwritten by user when an input dialog asking the user for a password is shown. It allows to use encryption, without having to ask user for a password. BTW, this is **less** secure, as the password is still hardcoded, even if obfuscated in the binary. ###### User password An input dialog asking for a password will be shown when: - the log is encrypted and no `SIGHT_DEFAULT_PASSWORD` is set **or** if `sightrun` is launched with `--ask-password` argument. - for preferences, if the appropriate `password` and `encryption` `policy` is set in module `ui_base` configuration (take a look at `libs/ui/base/Preferences.hpp`). - for session files, if the appropriate `password` and `encryption` `policy` is set in SReader and SWriter service configuration (take a look at `modules/io/session/SReader.hpp` and `modules/io/session/SWriter.hpp`). In all case, the entered password will be kept (obfuscated) in memory, allowing to only ask the password once, until, of course, it is explicitly configured to not do so. ### Sightlog logger (utils/sightlog/src/sightlog.cpp) It is a simple standalone application that read stuff from standard input and write them in a [minizip-ng](https://github.com/zlib-ng/minizip-ng) archive or a raw log file. Alternatively, it can also decrypt the real log from a log archive, which can be useful if a human need to read it back. The logger is started as a standalone detached child process by `Sight` which ensure the integrity of the log archive, even if `Sight` crashes. If `SIGHT_DEFAULT_PASSWORD` password has been used, it is also embedded inside `sightlog` binary, and it should not be necessary to use `-p` option. ###### sightlog usage ``` Sightlog logger options: -h [ --help ] Display this help message. -v [ --version ] Display the version of the program. -i [ --input ] arg Log archive to extract. -p [ --password ] arg Password to use for encryption and decryption. -a [ --ask-password ] Show a popup to enter the password. -d [ --directory ] arg Output directory when extracting a log archive. ``` For example to extract the log file from archive `/home/bozo/sight/bin/sight.log.0.zip` protected with password `w34r3th3B3st` in directory /home/bozo/logs and we can use: ```bash ./sightlog -i /home/bozo/sight/bin/sight.log.0.zip -d /home/bozo/logs -p "w34r3th3B3st" ``` ##### String "obfuscator" (libs/core/core/crypto/obfuscated_string.hpp) Allows to define a string literal that will not appears in "clear" text in the final binary. Useful for defining an "hardcoded" password, without being to simple to find and read. This is of course not as secure as a real password entered by a real user. ##### SpyLogger class - Doxygen comments.. - New method `start_logger()` and `start_encrypted_logger` which starts `sightlog` child process. - New unit tests `EncryptedLogTest` - The file paths in the log file are now trimmed again to not show the full path, but the minimal one to be able to locate a source file: (/home/bozo_the_clown/work/src/sight45/.../.../libs/core/core/spyLog.hpp -> libs/core/core/spyLog.hpp) *Add an image parser to allow basic initialisation in xml.* ### io *Allow custom serializers for any object, even when defined outside sight.* ### ui *Delete sequencer data when going backward.* Add an option to remove all the data generated when going backward with the sequencer. ### viz *Update slice indexes when a landmarks is double clicked.* Update SNegato3d & SNegato2d to move slices to the selected landmark. * SLandmarks send world position when double clicked (new signal) * SNegato3d, SNegato2d listens through a slot if slices indexes needs to be updated regarding a world position (new slot). * Conversion between world coordinates and slice index as been implemented in scene3d`::Utils`and a unit test has been added. Update adaptor`::SLandmarks`to handle double clicks on landmarks and also send the current world coordinates of the point *Use the mouse wheel to scroll through slices in the negatoscope.* Before the mouse wheel event was used to zoom in/out the image, it now allows to scroll the slices, like in other software. The Shift key speeds up scrolling. You can still zoom in/out the image with the mouse wheel, but with the Ctrl key pressed. *Add optional name to SAxis adaptor.* # sight 21.0.0 ## Bug fixes: ### build *Use project_name in variable exported by sight_generate_components_list.* Do not export SIGHT_COMPONENTS in sight_generate_components_list cmake function, use instead COMPONENTS. This avoids variable collision when using sight in subprojects, since SIGHT_COMPONENTS is exported by sightConfig.cmake. *Sight-project installation error due to sight version.* To fix the main problem, SOVERSION is no longer defined for executable targets. That simply prevents from creating these useless versioned binaries. On top of that, several other fixes were brought: * the version attribute of the `project()` CMake command is used instead of redefining a custom `SIGHT_VERSION` variable, * code cleaning was done around this, notably to rename `FWPROJECT_NAME` into `SIGHT_TARGET`, which is more correct with the usage of this variable, * dependencies computing now browses `OBJECT` libraries targets, like `io_session_obj` (fixes `Tuto01Basic` packaging for instance where `sight_io_vtk` library was missing), * dependencies computing now handles cross-repositories dependencies (fixes some child projects packaging), * components ordering was included in a higher-level function `sight_generate_component_list()` for a simpler use outside *Sight*. *Tests are relatives to last cmake project call.* * Use `${CMAKE_BINARY_DIR}` instead of `${PROJECT_BINARY_DIR}` to force executable to be produced in ./bin folder. * Also testing if safe-svfb-run isn't already copied in ./bin * early return in CppUnitConfig.cmake if CppUnit is already FOUND. *Configure child projects fails.* To fix the problem, we no longer export PCH targets and we no longer export modules .lib. On top of the initial problem, we also always build `utest`, `utestData`, and `module_utest` instead of only building them when `SIGHT_BUILD_TESTS` is `ON`. Child projects may need them even if unit tests were not built in Sight. *Generate sight component list.* CMake components in SIGHT_COMPONENTS variable are now ordered automatically according to their dependencies. It will ease the burden to manually maintain the list. *Explicit relative path in installted imported target library symlinks.* When we install packages in child repositories, we copy the necessary dependencies from Sight, for instance, the libraries. On Linux, we also need to recreate the library symlinks. This was done with absolute paths, which makes packages not relocatable. This fix just creates relative symlinks instead. *Fix package generation.* This brings back the package generation with CPack. Both Linux and Windows are functional. Sight can be packaged as a whole (similar to the former "SDK mode") in `tar.zst` (Linux) or in `.zip` (Windows). Any application or executable target can be packaged in `tar.zst` (Linux) or in `.exe` installer (Windows). The CI has been updated to always build the Sight and SightViewer packages on both platforms. However, the deployment on [Owncloud](https://owncloud.ircad.fr/index.php/apps/files/?dir=/IRCAD%20-%20Open/Binaries&fileid=894807) is only done on dev and master branch, or on any branch manually. On the dev branch, the package version number is replaced by `-latest`, so that it corresponds to a "latest" build. This prevents us from having to clean our archive folder too frequently since the packages will be erased at each upload. *Readd missing component io_session.* *Add missing component for sight-dependent project configurations.* *Makes the flag WARNING_AS_ERRORS effective.* *Add SMatricesReader export in the plugin.xml.* *Export Qt find_package in ui_qt.* Moves `find_package(Qt5 ...)` to Dependencies.cmake to be exported when using imported target `sight::ui_qt` *Geometry_eigen export.* Export also the `find_package(Eigen3 QUIET REQUIRED)` for the target geometry_eigen. *Move cppunit_main in cmake/build folder.* Fix the build of unit test on external projects. * `cppunit_main.cpp` has been moved from `cmake/cppunit` to `cmake/build` folder. * `FindOpenNI2.cmake `has been removed * `fw*.cmake` files has been removed, contents was added in `cmake/build/macros.cmake` file in order to be retrieved from outside. *Install executable shell scripts.* Install missing templates files for executable target ### ci *Small typo in SightViewer package name.* *Use sed for regex replacement of dev packages.* *Launch unit tests properly on Linux.* The code with `flock` was wrong, and the test was not executed. The initial code was restored, which should be safe. Also, there was another specific bug with `viz_scene3d` test. It crashed after destroying the first `Ogre::Root`. Indeed we chose to create and destroy it after each test. This problem is thus independent of the display number of `xvfb-run` since it does succeed to create an OpenGL context once but somehow fails to create a second. We assumed `xvfb-run` might be buggy regarding this initialization code. As a workaround, we create and destroy the `Ogre`::Root``only once thanks to a new module `sight::module::viz::scene3d::test`. Last, several tests in serviceTest were fixed. ### core *Broken library path on external projects when using '.local' in installation paths.* The deduction of the library path failed when share was already present in the main module path. The problem already occurred with module paths themselves, so the regex is now shared between these two places. *Tuto01DataServiceBasicCpp launch.* The source image loaded at start in Tuto01DataServiceBasicCpp was changed with the one used in Tuto02DataServiceBasic. This fixes the launch of this tutorial. *Add case to replace uids for slide views.* When launching a config, we substitute all `by` attributes with uids. We missed a case to handle slide views when attaching a widget. *Add an extra LD_LIBRARY_PATH for intermediate sight projects.* This fixes the inclusion of more than 2 sight projects. *Fix XML configuration parsing (#3).* Fix the parsing of objects containing sub-object (like Composite) or values (like String). Also fix the parsing of service with an external configuration ("config" tag in the XML service definition). *Make material resource file handling project independent.* Make `resources.cfg` path treatment independent of the working dir. Indeed, the present behavior uses the working dir for the absolute path generation. However, as this is done inside sight code, the prefix corresponds to the sight install path, and not the loading module-specific path. As a result, files that are not installed in the sight install dir can not be loaded. It is safer to rely on the module name, and get its specific path. *ExActivities fails to launch.* This fixes the parsing of a service configuration when a config extension was used (`config=` attribute). *Harmonize autoConnect booleans config parsing.* This merge requests changes all: - `autoConnect="yes|no"` into `autoConnect="true|false"` - `optional="yes|no"` into `optional="true|false"` *FwServicesTest randomly fails.* The last failing test was keyGroupTest. The problem was actually quite simple, the autoconnection with a swapped object is done after the swap. Thus, we have to wait for the object to finish the swap sequence before sending a modified signal from the data. Before we waited for the object to be present in the object map, but this is not sufficient since this only tells the object is registered, and the registration occurs before the swap. *Restore MSVC build.* Windows support is back! Third-part libraries are now built with vcpkg instead of Conan. We found out that vcpkg packages are much more stable and most of all, coherency is ensured between packages. Few fixes were brought to support the newest version of these libraries. Indeed they are often more recent than Ubuntu packages. Doing this, the GitLab CI/CD scripts were updated to use Powershell instead of cmd, as recommended by GitLab. *Remove TransferFunctionManagerWindow include from sightViewer plugin.xml.* *Several runtime errors after sight 21.0 upgrade.* ### doc *Rewriting doc of CardinalLayoutManagerBase.* ### io *DicomXplorer crashes when display mesh preview.* This MR fixes a crash upon selecting mesh in `dicomXplorer`. The problem was simply that old configurations used in this software were deleted. **It does not display the mesh in an activity.** It simply fixes the crash and re-enables the preview. *VTK readers doesn't handle color array properly.* When converting from vtk to sight mesh format, we check if color array named "Colors" exists in polydata in order to copy vertex or cells colors. When using PLY reader from VTK (maybe other format too) color array is named "RGB" or "RGBA". We also added a workaround to fix color rendering of first cell on Ogre 3d. Otherwise, mesh can appear black at first render. *Igtl client is not thread safe.* Make io_igtl`::Client`thread safe at connect / disconnect. OpenIgtLink socket class isn't thread safe at connection due to internal function calls (details in #736). *Dicom readers does not have the good scale along the z axis.* *Tuto02DataServiceBasic cannot load sample data.* Tuto02DataServiceBasic loads an image at start, but path to the image was hard-coded (`../../data/patient1.vtk`). A sample image was provided in the resource folder to the tutorial and loaded at startup. *Jpeg Writer (ITK) causes the application to crash upon usage.* ITK jpeg image writer has been replaced by vtk image writer service when saving snapshots in sightviewer. In addition, the image number of component in the vtk image writer service has been updated, as well as the lock systems. *Remove VTK warnings when reading meshes.* Redirect VTK messages (warnings/errors) in a VTK.log file. *Activities saving failed.* Saving activities was blocked due to extra "::" in the plugin.xml of ioActivity. *Add some Dependencies.cmake.* To build our current projects with sight 21.0, we needed to add missing `Dependencies.cmake` files, otherwise people would have to find the necessary `find_package`commands to run first. Besides this, an unrelated change has been made to reenable the load of extra bundle paths. This is needed when you depend on more than two repositories. ### test *ServiceTest randomly fails.* This fixes two tests in the `serviceTest` suite. Basically, it is just about waiting for the correct condition before testing the result. ### ui *Preferences path is broken.* The application name in the preferences file path is deduced from the profile name attribute. During the generation, this property was not generated properly. This fixes it. *Floating buttons are frozen when moving underlying main window.* ### viz *Dialog deadlock with 'always' render mode and GTK.* This removes the 'always' render mode in `viz::scene3d::SRender`, which is useless and may cause deadlocks with pop-up dialogs. *Default SRender transparency mode is broken.* Set the default transparency to `HybridTransparency`. (SightViewer's transparency has been set to default instead of `DepthPeeling`) *Cropbox reset freezes SightViewer.* We avoid a deadlock in `SVolumeRender::updateClippingBox()` by unlocking the clipping matrix data before calling the interaction callback. *SightViewer crashes at start.* In `SVideo`, the SceneManager was used to get the viewport. The correct way to retrieve is to get it through the layer. ## New features: ### build *Ubuntu 21.04 support.* ### core *Implement SessionWriter and SessionReader and many other things.* This is the foundation of the new serialization mechanism. ### New "crypto" package in `libs/core/crypto` * `secure_string`: a std`::basic_string`implementation with a custom `secure` allocator which erase used memory on de-allocation. This class is mostly used to store sensitive data like password. * `SHA256`: computes SHA256 cryptographic hashes * `Base64`: encode/decode to/from base64 strings * `AES256`: encrypt/decrypt to/from strings using AES with 256 bits key. * Unit tests in `libs/core/core/test/api/crypto/CryptoTest.xxx` ### New `ArchiveReader` and `ArchiveWriter` classes in `libs/io/zip` * Fixed some nasty bugs on ressource deallocation with WriteZipArchive / ReadZipArchive: Handle from files opened inside archive were not closed * Implements some thread safety measures: Due to minizip implementation, to avoid strange corruption problems, an archive could only be opened either in `read` or in `write` mode. In the same way, only one file in a archive should be opened at once. With old classes, no restrictions was applied, Yolo mode by default. * Allows to specify, as the zip format allows us to do, one compression method, one compression level, and one encryption key by file. This let us, for example, adapt the compression level, when the stored file is already strongly compressed, like for a mp4 file. * Use RAII mechanism to close file handle inside archive, global zip handle, etc. It means that opening the same archive, the same file inside an archive in the same scope, or even simply when the ostream/istream is alive, will dead lock (it's a feature, not a bug...) * Unit tests in `libs/io/zip/test/tu/ArchiveTest.xxx` ### Refactor UUID Not planned, but since `core::tools::Object::getUUID()` was not `const`, it requires some attention. Basically, only the generator has been kept in `core`::tools::UUID``and all code went in `core::tools::Object`. * Removed double mutex, strange double indirection, etc.. while retaining most of the feature (especially the `lazy` getter/generator, which a really doubt it is useful, but I did not Benchmarked it, so I decided to keep it) * `core::tools::Object::getUUID()` is now const at the operation doesn't modify external and even internal class state. * Unit tests in `libs/core/core/test/api/tools/UUIDTest.xxx` ### Implementation of SessionWriter, SessionReader, ... All in `libs/io/session` #### New `PasswordKeeper` password management system Designed to hold passwords, in the case you don't want the user to retype every 3 seconds its password. * Replace the less secure and more `handcrafted` solution in `libs/ui/base/preferences/helper.xxx` * Store the password in an AES256 encrypted `secure_string`. The key is computed in a deterministic way (no surprise), but should not be so easy to guess. Once the password is no more needed, or when the application quit, it will be erased from memory. * Allows to store a "global" password along several "instance" passwords. * `libs/ui/base/preferences/helper.xxx` has been a bit refactored to use the new `PasswordKeeper` * No need to say, a password should never be serialized on disk or in a database. Use only password hash for that. * Unit tests in `libs/io/session/test/tu/PasswordKeeperTest.xxx` #### New `SessionWriter` It's an implementation of a base::reader::IObjectWriter. It's purpose is to be used in a service to "write" an object to a session archive. It is the public API interface for writing a session archive. Basic usage would be something like: ```cpp auto sessionWriter = io::session::SessionWriter::New(); sessionWriter->setObject(myActivitySeries); sessionWriter->setFile("session.zip"); sessionWriter->setPassword("123") sessionWriter->write(); ``` #### New `SessionReader` It's an implementation of a base::reader::IObjectReader. It's purpose is to be used in a service to "read" an object from a session archive. It is the public API interface for reading a session archive. Basic usage would be something like: ```cpp auto sessionReader = io::session::SessionReader::New(); sessionReader->setFile("session.zip"); sessionReader->setPassword("123") sessionWriter->read(); auto myActivitySeries = sessionReader->getObject(); ``` > You may have noticed the slight change in classical `IObjectReader` API usage. It is no more needed to guess, before reading, the type of object to deserialize. Instead of "setting" and empty object, we simply "get" it back, once the "read" done #### New `SessionSerializer` This class contains the the main serialization algorithm. It also contains a specialized serializer (one for each kind of data object) registry, so it can delegate specific serialization tasks. The serialization is done on two medium: one boost ptree and, for big binary file, directly into the archive. The final ptree will also be stored in the archive as the index.json. The algorithm is really straightforward, not to say "basic": 1. Extract the UUID of the input object and the class name. 1. With the UUID, look into an object cache. If the object is already serialized, put a reference on the current ptree node, and stop. 1. With the classname, find a suitable serializer and call it. The specific serializer will update the current ptree node and maybe store binary file to the archive. 1. The serializer from step (3) will eventually return also a list of "children" object (this is the case for composite objects). Recall recursively the step (1) on them. 1. If the object contains `fields`, recall recursively the step (1) on them. > this MR text will be my base for README.md which I will write once everything are reviewed. Don't panic if you don't see it now, a new MR will be issued later. #### New `SessionDeserializer` This class is the counterpart of `SessionSerializer`. Instead of a specialized serializer registry, we have a specialized deserializer registry. The algorithm is a bit more complex, but still straightforward: 1. Extract the UUID and the class name of the current object stored in the ptree index. 1. With the UUID, look into an object cache. If the object is already deserialized return it, and stop. 1. With the UUID, look into the global UUID object registry. Use it to avoid unneeded object instantiation. It also allow us to safely deserialize children which have direct reference on parent objects. 1. With the classname, find a suitable deserializer. 1. First, deserialize the child objects by recalling recursively the step (1) on them. 1. Deserialize current object, passing the deserialized child objects from step (5) 1. If the object contains `fields`, recall recursively the step (1) on them. #### Specific object serializer/deserializer For now, only a very small subset is implemented. This subset should however cover most cases encountered while writing a serializer for a new kind of data object: * ActivitySeries\[De\]serializer: * Demonstrate how to serialize object with a child `Composite` reference and how to use another serializer to mange inheritance (with `Series`). * Composite\[De\]serializer * Equipment\[De\]serializer * Generic\[De\]serializer: * Demonstrate how to serialize "generic" object (Integer, Float, Boolean,String) * Mesh\[De\]serializer * Demonstrate how to serialize big binary data to archive * Patient\[De\]serializer * Demonstrate how to encrypt sensitive data, regardless of the encryption status of the archive * Series\[De\]serializer * String\[De\]serializer * This serialize String to and from Base64. Since boost ptree have serious flaws with strings with special characters in it, encoding to base64 is a suitable workaround, but still a bit overkill.. * Study\[De\]serializer #### Unit tests all in `libs/io/session/test/tu/SessionTest.hpp` *Promotes code from internal to open-source.* * Create `SRecurrentSignal` service in modules/ui/base/com. This service emits a signal at a defined frequency. * Create `Mesh` lib in libs/geometry/vtk and the corresponding unit test. This library computes the center of mass of a mesh using vtk. ### graphics *Add a material transparency editor.* A new service `SMaterialOpacityEditor` was added, which allows tweaking the opacity of a `sight`::data::Mesh``via `sight`::data::Material``with a slider. *Quad mesh integration in fwRenderQt3D.* ### io *Drop in replacement for SReader and SWriter to serialize an application session.* ### Main feature Two services `sight`::module::io::session::SReader``and `sight`::module::io::session::SWriter``were implemented. They read/write a root data object from/to a session file on a filesystem. The session file is indeed a standard "ZIP" archive, while the compression algorithm for files inside the session archive is ZSTD. A standard archive reader could open a session file, if it is able to handle ZIP archive with ZSTD compression. The archive can be password protected using AES256 algorithm and the compression level is set individually, depending of the type of data to serialize. The service configuration includes specifying the file extension and the password policy for encryption. Configuration example: ```xml ``` The dialog policy specifies when the open dialog is shown: * **never**: never show the open dialog * **once**: show only once, store the location as long as the service is started * **always**: always show the location dialog * **default**: default behavior, which is "always" The password policy defines if we should protect the session file using a password and when to ask for it: * **never**: a password will never be asked and the session file will never be encrypted. * **once**: a password will be asked only once and stored in the memory for subsequent uses. The session file will be encrypted. * **always**: a password will always be asked. The session file will be encrypted. * **default**: uses the builtin default behavior which is "never". The encryption policy defines if we uses the password as is or with salt. It can also be used to encrypt without password: * **password**: Use the given password for encryption. * **salted**: Use the given password with salt for encryption. * **forced**: Force encryption with a pseudo random hidden password (if no password are provided). * **default**: uses the builtin default behavior which is "password". ### General improvement: * `ExActivities` has been modified to use the new session services instead of atoms * new `TemporaryFile` class in `core::tools::System` that uses RAII to delete the associated file as soon as the `TemporaryFile` class is destroyed. * `core::tools::System::RobustRename()` now have an optional parameter to force renaming, even if the target already exists (it will be first deleted) * `ui::base::Cursor` improvement: `BusyCursor`, `WaitCursor`, `CrossCursor` classes that use RAII to restore back "default" cursor, even if an exception occurs * `ui`::xxx::dialog::InputDialog``improvement: add a "bullet" mode for password field. * `ui`::xxx::dialog::MessageDialog``improvement: add a "retry" button. *Serialize most objects.* The serialization is done through two classes: SessionSerializer, SessionsDeserializer, and many .hpp with two functions: `serialize()` and `deserialize`, depending of the type of data object to serialize. For example, the serializers for meshes and images are coded respectively in `Mesh.hpp` and `Image.hpp`. They are good samples to demonstrate how it is possible to use a well known format to serialize objects. The Sight images / meshes are converted into VTK format using Sight helpers and are then saved with the now "standard" VTK way using `vtkXMLImageDataWriter` for image and `vtkXMLPolyDataWriter` for meshes. As a side notes, since the files are stored in a zstd compressed zip file, and since VTK doesn't provide any way to use an output streams, the VTK writers are configured as such (image and mesh are equivalent): ```cpp vtkWriter->SetCompressorTypeToNone(); vtkWriter->SetDataModeToBinary(); vtkWriter->WriteToOutputStringOn(); vtkWriter->SetInputData(vtkImage); ``` This allow us to compress only one time and use fast zstd. Since the compression level can be set independently, some test need to be done to find the best efficiency. For now it is the "default" mode that is used, but better compression ratio at the expense of compression speed (not decompression!) is also possible. The drawback for using `WriteToOutputStringOn()` is that the complete data need to be written in memory before being able to serialize it. Shame on VTK for not providing an easy way to use c++ streams... Most serializers are far more simple as we only write/read values into a Boost property tree, a bit like before, but without the complexity of "atoms" tree. The version management is also quite simple, we write a integer in the tree and read it back. It is up to the user to add the `if(version < 666)...` ## Enhancement: ### build *Enable support of PCH in unit tests.* Sight CMake targets of type TEST now build with precompiled headers, which speed-ups a bit the global build time. ### core *Wrap notification signal emission in a function.* Wraps emission of "notification" signals into `IService`::notify``with an enum class NotificationType (SUCCESS, INFO, FAILURE) and the message. *Add helper function to ease error handling in boost::property_tree.* This adds a new function to ease error handling in `boost::property_tree`. ```cpp core::runtime::get_ptree_value(config, "test.true", false); // returns true core::runtime::get_ptree_value(config, "test.yes", false); // throws core::runtime::get_ptree_value(config, "test.foo", false); // returns false core::runtime::get_ptree_value(config, "test.foo", true); // returns true core::runtime::get_ptree_value(config, "test.true", 42); // throws because it implicitly request an integer ``` *Move TransferFunctionManagerWindow configuration to config folder.* - Move `TransferFunctionManagerWindow.xml` to `configs/viz/scene3d`. - Add a link in `sightViewer` application to use it. ### doc *Add a README.md for each target.* We now provide a short documentation file for each target. The goal of this file is to give an overview of the content of the library or the module to the developer. It is complementary with the doxygen and sight-doc but it should not overlap with them. ### graphics *Support keeping the original image size and bilinear filtering in SVideo.* * Add support for switching between nearest and bilinear filtering for the texture * Add support to scale or not the image to the size of the viewport ### io *Make realsense support optional.* *Add missing description for pdf writer label.* add description in plugin.xml of modules/io/document ### ui *Send value when released slider in SParameters.* Adds an option to emit value of SParameter sliders only when releasing the slider, this avoids multiple sending when moving the slider. *Add option to fix the size of QToolButton on a toolbar.* Adds an option on Toolbar layout to resize buttons to the wider one. This ensures to keep same size between each button and fix some glitches when larger button was hide / show. ### viz *Enhance tracked us display.* - change UsUltrasoundMesh default us format, and allow xml configuration of this format - add SMatrixViewer significant number for display ## Refactor: ### build *Remove obsolete activities.* The following activities were removed: - blendActivity - registrationActivity - 3DVisualizationActivity - volumeRenderingActivity Only 2DVisualization is kept, and renamed to `sight::activity::viz::negato`. Indeed some configurations of this activity are used in other activities and in **dicomxplorer**. The following activities were fixed: - DicomPacsReader - DicomPacsWriter - DicomFiltering - DicomWebReader - DicomWebWriter Also, the loading of DICOM images from the filesystem or a PACS in SightViewer should also be fixed. ### core *Use data::ptr everywhere.* This is the final merge request that generalizes the usage of data`::ptr`instead of IService::get*/getLocked*/getWeak*. The deprecated `DynamicType` class was also removed, which really helps to clear the build log from many warnings. Last, Ogre runtime deprecation warnings were also processed, which implied to refactor a bit the material and shader code. *Move remaining misplaced files.* - Move several services interfaces from `sight::service`: - `IGrabber` -> io - `IRGBDGrabber` -> io - `ICalibration` -> geometry_vision - Rename `IOperator` as `IFilter` to match the new naming scheme and avoid synonyms - `IParametersService` is renamed into `IHasParameters`, moved in `ui_base` and is no longer a service. Thus any specialized service can inherit this interface using multiple inheritances. - Rename `pchServices/pchServicesOmp` into `pchService/pchServiceOmp` (we have used the singular everywhere for `service`) - Renamed and moved `sight`::module::ui::dicom::SSeriesDBMerger``into `sight::module::ui::series::SPushSelection`, since it pushes a selection (vector) of series into a `SeriesDB`. - Removed duplicated `module`::geometry::generator::SUltrasoundMesh``and moved `module`::geometry::generator::SNeedle``into the same module than the duplicate of `SUltrasoundMesh`, i.e. in `module::filter::mesh::generator`. *New pointer types to manage data in services.* Two new pointer types are introduced, which the only aim is to be used as service class members: - `sight`::data::ptr`:`single data - `sight`::data::ptr_vector`:`group of data For instance, they can be declared this way in a class declaration: ```cpp class STest : public IService { ... private: data::ptr m_input {this, "image"}; data::ptr_vector m_inoutGroup {this, "meshes", true, 2}; data::ptr m_output {this, "genericOutput", false, true}; }; ``` `this` must be passed as the first argument, with a class instance inheriting from `IHasData`. So far, only `IService` inherits from this interface, but other cases might appear later. It is used to register the pointers in the OSR and get/set their content automatically, mainly with the `AppManager` (Xml based and C++ based). This prevents calling `registerObject()` for each data in the service constructor (it was almost never done because this only breaks C++-based apps, but normally it should have been done everywhere). Actually, this registration method was removed from the public interface of `IService` so you can no longer call it and there is no risk of conflict. All occurrences were refactored to use these new pointer types. To retrieve the data, it is simple as using a `data::mt::weak_ptr`, so you can simply call ```cpp auto input = m_input.lock(); // returns a data::mt::locked_ptr const auto data = *input; // returns a data::Image& const auto data_shared = input.get_shared(); // returns a data::Image::csptr // Access data in a group using indexes for(int i = 0; i < m_inoutGroup.size(); ++i) { auto data = m_inoutGroup[i].lock(); ... } // Access data in a group using for range loop - this gives access to the underlying map, // that's why '.second' should be specified, while '.first' gives the index for(const auto& data : m_inoutGroup) { auto data = data.second.lock(); ... } // Alternative using structured binding for(const auto&[index, data] : m_inoutGroup) { auto lockedData = data.lock(); ... } ``` *Booleans attributes configuration parsing.* Harmonize XML configuration of services code by replacing the use of "yes"/"no" with "true"/"false". *Replace getPathDifference() by std::filesystem::relative().* *Reorganize all targets and rework the build system.* Sight 21.0 brings a major update on the naming scheme. We renamed almost every target, according a new naming scheme, and we reduced drastically the scope of categories we use to sort targets. Many targets were merged together. We lost some modularity but we gained in simplicity. Also the build system was partially rewritten. Now it relies only on standard CMakeLists.txt, and no longer on our custom Properties.cmake. We no longer support injecting external repositories into a Sight build. To build other repositories, we use what we called before the SDK mode, which is actually the classic way to link some code to another in C++. Last we introduced a global `sight`::``namespace, which is also reflected on the filesystem. This makes extensibility easier by avoiding naming conflicts. # sight 20.3.0 ## New features: ### core *Add services for echography navigation and simulation.* Add various services that help to build applications based on echography. * create `cvSegmentation` module * add `SUltrasoundImage`: segments an ultrasound image from an echography video * move `SColourImageMasking` service from `colourSegmentation` module * add several services in `maths`: * `SMatrixList`: manages a list of matrices * `STransformLandMark`: applies a matrix transform to a landmark * `STransformPickedPoint`: applies a matrix transform to a picked point in a scene * `SPointToLandmarkDistance`: compute the distance between a point and a landmark * `SPointToLandmarkVector`: compute a direction vector between a point and a landmark * create `opUltrasound` module * `SUltrasoundMesh`: generates a 3D mesh to display an echographic plane in a 3D scene ### graphics *Add adaptor to show the patient orientation in Ogre scenes.* * Add a new SOrientationMarker Ogre adaptor service, that allows to display a mesh showing the orientation of the scene at the bottom right of the screen (equivalent to the VTK SOrientationMarker) * Use SOrientationMarker service in OgreViewer *Synchronize 2D negatos when picking.* * Synchronize 2D negatos when cliking on the image. * Add a parameter in `SVoxelPicker` to enable the update of image slices indexes. Send ``::fwData::Image::s_SLICE_INDEX_MODIFIED_SIG``with the picked indexes. *Allow to delete the last extruded mesh.* Add a slot to delete the last mesh generated by SShapeExtruder. ### ui *Allow to delete series from the selection view from a button.* ## Bug fixes: ### core *Fix SLandmarks by setting an optional input for the key "matrix".* *Save preferences when they are changed.* Save preferences when updated from GUI. *OgreViewer doesn't read properly json with modelSeries.* Connects seriesDB modified signal to the extractImage/Mesh update slots in OgreViewer app. *FwServicesTest randomly fails.* Fix the fwServices`::ut::LockTest::testDumpLock`by adapting the test to asynchronous unlocking. In real world application, the bug should not be present, although the randomly failing test are annoying. ### doc *Update licenses.* ### graphics *Default visibility of modelSeries adaptor is not taken into account.* Take into account default visibility in `visuOgreAdaptor::SModelSeries`. *Mesh normals aren't computed each time.* Compute normals each time a mesh is updated (if mesh has no normals). Before it was only computed if mesh needs a reallocation (number of vertices greater than the previous mesh). ### io *Mesh attributes created after getInput aren't locked.* Some attributes of ``::fwData::Mesh``may be created later (aka after the New()), like points/cells normals/colors/texture coordinates. But this can lead to some unpredicted unlock issues on internal arrays of `::fwData::Mesh`, when using new RAII methods like `getLockedInput` if the mesh hasn't point normals when getting the locked input the corresponding array will not be locked (because it will be equal to nullptr), so if you want to set normals afterwards the corresponding array will still be in an unlocked state. We now initialize all attributes (colors/normals/texture) at mesh creation. A binary mask corresponding to which attributes are currently used in mesh can be checked. We don't rely anymore on the `nullptr` of internal arrays. We also modify mesh iterator to remove the lock it performs, so we need systematically call `mesh->lock()` to lock internal array only if we don't use RAII methods (`getLocked*`) from services. Along with previous modifications, we also modify internal structure of CellsData arrays, previously we store `std::uint64_t` values, we change values to `std::uint32_t` much more adaptable on both 32bits and 64bits systems. To deal with all previous modifications we also updated data mesh version (version 4), fwData version (V15) & arData version (V17AR). We provide structural patches to load/save previous version of data. Some of our unit test has also be updated to handle all previous modifications. This could break the API. ### ui *Fix visuOgre/SNegato3D::setvisible() freeze in ExRegistration.* *Adaptor SLandmarks deadlock.* *Show distances button in OgreViewer bugs.* ### vision *Fix SNeedleGenerator colors.* ## Enhancement: ### core *Add boolean record slot to SFrameWriter.* Add boolean `record` slot to SFrameWriter and SVideoWriter *Improve SPreferencesConfiguration to prevent setting wrong parameters.* ### graphics *Allow to interact with Ogre landmarks.* Allow mouse interaction on Ogre landmarks *Add default visibility configuration for SLandmarks adaptor.* Adds a tag to configure default visibility in `SLandmarks`. Enables the possibility to hide the adaptor by default in the xml configuration. *Add default visibility configuration for SNegato2D.* Uses the tag from IAdaptor to configure default visibility in `SNegato2D`. Enables the possibility to hide the adaptor by default in the xml configuration ### io *Use igtl::serverSocket to get real port number.* Allow the use of port 0, setting port = 0 will ask socket to find the first available port number. Thus we need to modify the way the port is stored in igtlNetwork::Server, and use igtl`::ServerSocker::GetServerPort`to have the real port number. *Make igtlProtocol::MessageFactory extendable from other libraries.* Make the MessageFactory public, to be used from outside of `igtlProtocol`. This is helpful to registrar new type of IGTL Messages that are not necessary implemented in `igtlProtocol` library. ## Refactor: ### core *Add services for ITK and VTK operator modules.* ### graphics *Move all material to fwRenderOgre and deprecate the `material` module.* *Remove all usage of the deprecated API in Ogre modules and libraries.* * Remove deprecated functions in Ogre libs/modules. * Use the new RAII system in Ogre modules. ### io *Remove videoOrbbec.* * Remove videoOrbbec from sources. # sight 20.2.0 ## New features: ### graphics *Add a new picker to really pick the current slice image.* - When using an Ogre picker on a medical image, the picked position does not correspond to a voxel position of the image. In fact, the Ogre negato display the image between \[0;size\] and the picker needs to pick between \[0;size\]. *Add Qt3D SMaterial adaptor.* Implement qt3d material service: * `::fwRenderQt3D`::data::Material``object handling a qt3d material * `::fwRenderQt3D`::techniques::Lighting``object handling a qt3d technique with shader programs to compute lighting mode and rendering options such as point/line/surface rendering or normals visualization * `::fwRenderQt3D`::SMaterial``adaptor used to create a qt3d material from sight data and attach it to the render service ### io *Read tfp TF from directory with ::uiTF::SMultipleTF.* *Add setNotifyInterval method to the Grabber interface.* Adds an option in the videoQt Player to change the notifyInterval option. This is useful to require frame through "setPosition". *Add birth date request field for PACS query editor.* ### ui *Allow to manage opacity of the TF in TF editors.* Allow to manage a whole TF opacity at the same time from the TF editor. ## Refactor: ### core *SpyLog rework.* SpyLog has been unnecessarily complex for a long time. In this rework, we propose to: - deprecate the loglevel `TRACE`. Only `SLM_TRACE_FUNC()` remains, but its occurrences should never be committed. - deprecate `OSLM_*` macros in favor of `SLM_*` macros, which now take stringstreams as input (no performance penalty) - all loglevels are now always compiled, which means that the big bloat of `SPYLOG_*` CMake variables were removed. - occurrences of `OSLM_*` macros were replaced by `SLM_*` macros - occurrences of `O?SLM_TRACE*` macros were removed or replaced by higher levels logging macros - the default displayed log level is now `WARN` - the path of files displayed in the output was shortened to keep the minimum information needed: namespace(s), source fil *Deprecate configureWithIHM.* Deprecate `configureWithIHM` and use `openLocationDialog`, backward compatibility is kept until sight 22.0 *Deprecate *dataReg modules and move *dataCamp content to *data.* This deprecates the usage of dataReg and arDataReg modules, which were so far mandatory as `REQUIREMENTS` for any XML configuration using data. This was done in several steps: 1. a function was added in `fwRuntime` to allow the loading of any regular shared library (we could only load libraries from modules before). 2. `AppConfigManager` was modified to guess the library name when parsing configuration, and load the library accordingly 3. `dataReg` and `arDataReg` were emptied from the hacky symbols, and deprecated 4. the only real useful code, i.e. the XML data parsers from `dataReg` were moved to `fwServices`, so that we can remove the whole module later. 5. `dataReg` and `arDataReg` were replaced by `fwData`, `fwMedData` and `arData` in `Properties.cmake` files 6. `dataReg` and `arDataReg` were removed from all `plugin.xml` requirements. 7. all `*DataCamp` libraries were deprecated and most of their content imported in the corresponding `*Data` libraries. They were linked in an application thanks to the dataReg modules and hacky symbols. I could have loaded the modules manually like data libraries but I think it is simpler to gather everything related to a data into a single library. There is no real use case in my mind where we want only the data without any introspection. And we do not need to plan for a coexisting alternative to perform the introspection, we are anyway too tight with Camp. This is not a breaking change and it is still possible to keep the deprecated modules. ### graphics *Deprecate VTK.* * Deprecates VTk generic scene * Updates all samples to use Ogre * Cleans all sample files (xml/hpp/cpp/cmake) * Fixes some errors in our samples * Adds a new module `visuBasic` for basic generics scenes * Rename samples ### io *Deprecate VLC.* Deprecation of `videoVLC` since VLC package will no longer be available with new conan system (sight 21.0). ### vision *Deprecate the bundle.* Deprecate the bundle `uiHandEye` ## Enhancement: ### core *Complete meshFunction baricentric API.* Add a method `isInsideTetrahedron( const ::glm::dvec4 barycentricCoord);` in the MeshFunction. *Add [[nodiscard]] attribute for weak/shared_ptr::lock().* Adds `[[nodiscard]]` attributes in weak_ptr and shared_ptr lock() function. This avoids using lock() as lock function and force user to use returned lock_ptr object *Update conan.cmake file to v0.15".* Update conan.cmake file to v0.15. ### graphics *Improve scene resetting.* ### io *Increase the DICOM services log level.* Add detailed logs for our communications with the PACS. *Improve PACS research fields.* * Make research fields of ioPacs case insensitive. * Improve date research. * Avoid to read unreadable modalities with the series puller. *Execute PACS queries from a worker.* ``::ioPacs::SeriesPuller``now retrieves series in a worker to avoid the app to freeze. *Improve ioPacs queries.* Adds a PACS series selection to `OgreViewer`. * `SPacsConfigurationEditor`: send notifications. * `NotificationDialog`: use the height of the notification instead of the width for computation. * `SSelector`: remove margin. * `SNegato2D` and `SNegato3D`: avoid a division by 0. * `Utils`: sometime the default texture as the same size than the image, so there is never initialized. ### ui *Rename bad named functions.* * Deprecated `::fwGui::dialog::MessageDialog::showNotificationDialog()` and propose a new `::fwGui::dialog::MessageDialog::show()` * Deprecated `::fwGui::dialog::NotificationDialog::showNotificationDialog()` and propose a new `::fwGui::dialog::NotificationDialog::show()` * Renamed all occurrences. ## Bug fixes: ### build *Add deps between targets and their PCH for Unix Makefiles.* This fixes errors when building using the Unix Makefiles generator and multiple processes. ### core *Load library is broken when launching activity using wizard.* Adds `loadLibrary` method in `new.cpp` file in order to load libraries before starting `AppConfigManager` or `ActivityWizard` ### io *Cannot save json on mounted disk.* Create a custom `rename` function that will first try a `std`::filesystem::rename``and then fallback to a basic copy-remove scenario. Using this avoid the `Invalid cross-device link` error, when saving a json(z) file on a another disk/volume. *Tf names without extension are not saved.* ### ui *Enable the current activity of the sequencer if no data are required.* * Enable the current activity of the sequencer if it's needed. * Avoid double start of the config launcher. *The TF editor suppresses points.* ### vision *Display left and right view vertically.* Until a better fix, display left and right camera vertically in extrinsic calibration. Doing this provide a better width view of video. # sight 20.1.0 ## Bug fixes: ### build *Remove ".sh" extension from launcher scripts.* Both build and install targets are impacted. Adds `.bin` extension to executable target such as utilities/tests or fwlauncher to distinguish executable targets from shell scripts launcher *Ninja install issue on linux.* Test if the install directory ( `CMAKE_INSTALL_PREFIX`) is empty, if not print a warning at configure time. ### ci *Build on master branch on Windows.* Fix quoting of branch name while cloning ### core *Crashes when closing OgreViewer.* OgreViewer crash at closing, this crash happens because the action `openSeriesDBAct` is working on a thread. Now we wait until services are properly stopped after `service->stop()` in XXRegistrar class. *Replace misleading error logs in SlotBase.* When trying to call/run a slot, an error log was displayed if the signatures don't match, but it is allowed to call a slot with less parameters. Now, an improved information message is displayed if the slot cannot be launched with the given parameters, then we try to call the slot removing the last parameter. The error is only raised if the slot cannot be called without parameter. ### graphics *Fix screen selection for Ogre scene.* Enable the full screen of Ogre scene on the right monitor. For more informations: https://stackoverflow.com/questions/3203095/display-window-full-screen-on-secondary-monitor-using-qt. *Slice outline colors are inverted between axial and sagittal in OgreViewer.* Use blue outline color for Ogre's sagittal view, and red outline color the axial one. ### io *Change the overwrite method when saving using ioAtoms.* Change the overwrite method of `ioAtoms::SWriter`. Previously, when a file was overwritten, it was deleted then saved. But if the saving failed, the old one was still deleted. Now, the file is saved with a temporary name, then the old one is deleted and the temporary name is renamed with the real one. *Realsense doesn't work on latest 5.4 linux kernel.* Update the version of librealsense package to latest (2.35.2). This fix issue of using realsense camera on linux kernel > 5.3 *Remove useless requirement in ogreConfig.* Remove `ioVTK` module from `ogreConfig`. No reader, writer or selector was used in the configurations. *Change realsense camera timestamp to milliseconds.* Correct the Realsense grabber timestamp from microseconds to milliseconds *Allow to re-start reading a csv without re-selecting the file path.* Update 'SMatricesReader' to properly close the file stream when calling `stopReading` slot and clear the timeline. Properly stop the worker to avoid a crash when closing the application. Also remove the default delay of 1s when using one shot mode. *Remove hard-coded codec and only offer mp4 as extension available in SVideoWriter.* * Limits extension selection to only `.mp4` for now. * Limits FOURCC codec to `avc1`. This codec is also linked to the `.mp4` extension. *Fix a crash with OpenCV grabber.* - Start/stop the worker of OpenCV grabber when service start/stop ### ui *Notifications crashes when no active window was found.* Avoid crash of app when notifications were displayed without active window (focus lost). If no active window is found, print an error message and discard the current notification. *Prevent dead lock when opening organ manager.* Block Qt signals in SModelSeriesList when changing the state of the "hide all organs" checkbox. ## Enhancement: ### ci *Re-enable slow tests and remove ITKRegistrationTest.* ### core *Add empty constructor to Array iterator.* * Add empty constructor to Array BaseIterator, to make it compatible with std::minmax_element ### doc *Improve IService doxygen about output management.* Describe how to properly remove the service's outputs and why and when we should do it. Add a check when the service is unregistered and destroyed to verify if it owns an output that will also be destroyed (if the output pointer is only maintained by this service). Currently only an error log is displayed, but it may be replaced by an assert later. Other services may work on this object but didn't maintained the pointer (they used a weak pointer). *Update the screenshots in README.md.* Update README.md with more up-to-date screenshots/animations ### graphics *Use ogre to display videos in ARCalibration.* * Use Ogre for `ARCalibration`. * Properly clean ogre resources before root deletion. *Manage default visibility of `SNegato3D` from XML configuration.* Call `setVisible` method at the end of the starting method. ### io *Improve ioPacs module.* * Improves connection/disconnection management. * Improves the UI and request forms. * Use new disconnection method of dcmtk. * Fills new `medData` attributes. ### ui *Add goTo channel in ActivityLauncher.* Add `goToChannel`in `ActivityLauncher.xml` in order to call `goTo` slot from an activity. *Replace Ircad and Ihu logos by the unique Ircad/Ihu logo in the sequencer view.* Modify ActivityLauncher configuration to replace Ircad and Ihu logos by the new version of Ircad/Ihu logo. *Improve assert messages in fwGui.* Add as much information as possible in the assertion messages: service uid, why the error occurs, ... *Add check/uncheck slot in SSignalButton.* These two new slots allow to easily check/uncheck the button without using a boolean parameter. They can be connected to a SSlotCaller or any signal. Update TutoGui to show an example with these two new slots. *Allow to delete all reconstructions or one from a right click.* - Allow to delete all reconstructions from `SModelSeriesList` - Delete specific reconstruction from a right click. *Improve Qt sequencer colors management.* Allow to manage more colors in the Qt sequencer. ## New features: ### core *Implements copy for mesh iterators.* Implement `operator=()` and `operator==()` on mesh iterator's internal structs (PointInfo and CellInfo). It allows to use `std`::copy``(or other std algorithms) with Mesh iterators. Update Mesh unit tests to check using `std::copy`, `std`::equal``and `std::fill`. ### graphics *Add new experimental Qt3D renderer.* A new experimental renderer based on Qt3D has been integrated, providing basic support for mesh visualization. Three samples are provided to show how Qt3D could be integrated into a classic XML application, a pure c++ application or a QML application: * TutoSceneQt3D * TutoSceneQt3DCpp * TutoSceneQt3DQml The following classes and services have been introduced: * `::fwRenderQt3D`::GenericScene``object handling a qt3d scene * `::fwRenderQt3D`::FrameGraph``object to attach to a GenericScene, allowing to define custom qt3d framegraphs * `::fwRenderQt3D`::SRender``service used to define a GenericScene object within Sight context and attach adaptors to it * `::fwRenderQt3D`::Mesh``object creating a mesh from sight data using qt3d geometry renderer * `::fwRenderQt3D`::IAdaptor``class providing base functionalities for Qt3D adaptors. * `::visuQt3DAdaptor`::SMesh``adaptor used to create a qt3d mesh from sight data and attach it to the render service *Improve Ogre's SLandmarks for 2D scenes.* Improve Ogre resources management in '::visuOgreAdaptor::SLandmark' and display the landmarks in 2D negato scenes only on the slice of the landmark. *Add Ircad-IHU logo overlay in Ogre Scene.* - 8 positions are supported: left-top, left-center, left-bottom, center-top, center-bottom, right-top, right-center and right-bottom. ### io *Allow to save and load multiple TF with TF editor.* * Add new buttons to export and import a TF pool to `SMultipleTF` * Use the new API in `SMultipleTF`. ## Refactor: ### core *Manage pixel format in FrameTL.* Manage pixel formats: Gray Scale, RGB, BGR, RGBA and BGRA. Now, to initialize the frame timeline, the format must be specified, the previous initialization method (with number of components)is deprecated. Also improve the unit tests to check timeline initialization and copy. Update `SFrameMatrixSynchronizer`: use the new pixel format to initialize the image, but still support the old API when the format is undefined. Also use the new service's API with locked and weak pointers. ### graphics *Rename the layer "depth" to "order".* Replace `depth` attribute of Ogre layer by `order`. ### ui *Factorize visibility slots in Ogre3D renderer.* - Factorise `updateVisibility`, `toggleVisibility`, `show` and `hide`. `setVisible` is the only method to reimplement in subclasses. # sight 20.0.0 ## Bug fixes: ### .gitlab *Improve sheldon stage.* Use sheldon and build the project on the merge result instead of the working branche. *Improve sheldon stage.* ### activities *Split bundle to separate gui services from core services.* Separate service using Qt Widget from other services in order to use generic service with a Qml application, because Qt widgets cannot be instantiated in a Qml application. A new bundle `uiActivitiesQt` was created where `SCreateActivity` and `SActivityLauncher` has been moved. The old `SCreateActivity` and `SActivityLauncher` services has been kept in activities but set as deprecated. A fatal would be raised if you use them in a Qml application. `ENABLE_QML_APPLICATION` variable has been added in the main CMakeList to define if there is a Qml application that will be launched. It is temporary because we need to keep the old API until sight 21.0 and allow to remove the dependency to Qt Widgets in activities bundle. ### ActivityLauncher *Fix reader/writer configuration.* Fix the reader and writer configurations used by `ActivityLauncher` config: use the configuration given in parameters instead of the default one. ### AppConfig *Fix XML variable regex.* The regex is used to know if the xml attribute contains a variable (`${...}`). But in a few cases, for example in signal/slot connections, the variable is followed by other chars. For these cases, the regex should not be limited to `${...}`but to `${...}....`. The unit tests have been updated to check this error. ### AppConfigManager *Fix useless variable.* Remove wrong `#ifdef` around `FwCoreNotUsedMacro(ret);` ### Array *Implement missing copy constructor in Array iterator.* Implement copy constructor for const iterator and assignment operator. ### build *Fix build on linux with gcc7 in release mode.* - fixed sheldon coding style and define in fwCom - fixed unused variable (release mode) in fwServices ### CalibrationInfoReader *Wrong image channel order.* * Convert to the sight's default color format when reading calibration inputs. * Sort the calibration input by their filename. * Update the related tests. ### CI *Disable slow tests until they all pass.* *Sheldon-mr job doesn't work.* According to #402, the sheldon-mr job seems to fail when merge commit are present in-between dev and the current mr branch. Here the `git merge-base` function has been replaced by a diff of revision list between the MR branch and dev, and then keep the oldest common ancestor. **Explanations** * `git rev-list --first-parent origin/${CI_COMMIT_REF_NAME}`: revision list of the MR branch * `git rev-list --first-parent origin/dev`: revision list of dev (/!\\ we need to check is there is a limitation on number) * `diff -u <(git rev-list --first-parent origin/${CI_COMMIT_REF_NAME}) <(git rev-list --first-parent dev)` : basic diff between the two list * `| sed -ne 's/^ //p'`: removes the diff line ("+" "-", etc), to keep only the common revisions * `| head -1`: keep the first form common revisions. This is, indeed, a bit over-complicated, and can be simplified by a simple merge on the CI side, and then run sheldon, but this avoid any potential merge conflicts checking beforehand. *Use CI_JOB_TOKEN when cloning sight-data.* ### CMake *Disable RelWithDebInfo build type.* *Resolve Qt plugins path in SDK mode.* Do not install the qt.conf from our Conan package that points to a bad plugin location. This overrides the location set in `WorkerQt.cpp`, preventing Qt application built with the SDK from launching. Besides this, I also fixed file permissions of installed files (executable were not actually executable) and I corrected a warning at the end of a CMake run because of a wrong usage of `PARENT_SCOPE` in the macro `generic_install()`. *Resolve missing export when using SDK mode.* The systematic build of the object library broke the SDK build, because the export was missing for the object library. However, this means users would have to link for instance against `fwCore_SHARED_LIB` instead of `fwCore` which is the object library. This is counter-intuitive and really not desirable. We chose to invert the logic. `fwCore` is now the shared library and `fwCore_obj` is the object library. This solves the problem for external users but not for sight developers who would need to modify all `CMakeLists.txt` to link fwCore_obj with 3rd party libraries instead of fwCore. We found a middle-ground by building the object library **only** when the variable GENERATE_OBJECT_LIB is set. This variable was set for `fwRuntime` to enable the build of `fwRuntimeDetailTest`. Its `CMakeLists.txt` was last modified to link against `fwRuntime_obj` instead of `fwRuntime`. On top of that, some corrections were made because of the rework of `fwRuntime` and the usage of `std::filesystem`. *Conan cmake script not correctly downloaded.* - Add check hash of `conan.cmake` file for: * avoid downloading if the file already exists and is valid * download the file if it's corrupted *Add cmake 3.17.0 support.* Add cmake 3.17.0 support *Fix the plugin config command.* Include the appropriate headers in generated `registerService` files. *Strip cmake compilation flags on Windows.* Avoids cmake adding a space at the end of compilation flags *Fix executable installer.* This enables the packaging of executable programs under windows. By the way, the call of generic_install() is now done automatically for all APP and EXECUTABLE targets, thus enabling packaging automatically. A warning is displayed if the macro is called in the CMakeLists.txt of the application. ### conan-deps *Update hybrid_marker_tracker version.* Update hybrid_marker_tracker library version that fixes memory leak. ### ConfigLauncher *Add existing deferred objects to configuration.* * Notify xml sub-configurations that optional objects already exist. * Deprecate `IAppConfigManager`. * Improve the documentation of modified classes. ### dataReg *Add missing include to compile with latest VS2019 16.6.0.* ### Dispatcher *Fix wrong mapping for uint8 type.* `isMapping` method is used by Dispatcher to find the right type for calling template functor. ### doxygen *Doxygen uses the correct README.md to generate the main page.* Update Doxyfile to fix the path of the main README.md. Now the doxygen is generated using the main README file as the main page. ### ExIgtl *Add auto-connections INetworkSender.* * Add missing auto-connections method in INetworkSender * Fix ExIgtl example * Add JetBrains IDEs (CLion) project folder in git ignore file ### ExImageMix *Fix image voxel information.* Remove the useless hack that skipped some events in VTK picker interactor, so the information was not up-to-date. ### ExSimpleARCVOgre *Add missing module_ui_media bundle.* This solves the crash of ExSimpleARCVOgre at start. The bundle module_ui_media was missing. ### fwData/Image *Deep copy.* * Fixes an assert raised in debug when the image is locked and deep copied. * Don't let the data array expire. * Add a test to ensure this doesn't happen again. * Fix ExDump. ### fwGdcmIOTest *Test fails.* Fixes fwGdcmIOTest by using correct type when setting Tags Values. * ConsultingPhysicianName is a list * Patient Size/Weight/MassIndex are double * Attributes was missing when copying a Study ### fwGuiQt *Allow notifications to work with ogre scenes.* Fixes notifications for app that used Ogre scenes. Moves the dialog according to the parent. ### fwLauncher *Allow to exit when receiving signals.* - Use regular exit() to stop program execution when receiving SIGINT, SIGTERM, SIGQUIT signals. ### fwMath *Use glm vector when computing barycenters and fix mistake.* * Replace all `fwVec3` structure by `::glm::dvec3`, along with glm maths functions. * Fix mistake in dot product when computing barycentric coordinates. * Update unit-test ### fwRenderOgre *Fix query flags and ray mask.* * Sets all pickable adaptors query flags to a default value ('::Ogre::SceneManager::ENTITY_TYPE_MASK'). * Sets pickers query mask to a default value ('::Ogre::SceneManager::ENTITY_TYPE_MASK'). * Query flags on adaptors are now sets, (previously, they was added). * Updates documentations on updated files. It allows pickers to pick all adaptors by default. This can be adjusted in xml by modifying the query mask of the picker, and the query flags of adaptors ### fwVtkIO *Fix warnings (as error) when building with Clang.* ### gitlab *Improve sheldon stage.* Fix the build on dev, master and tags ### HybridMarkerTracking *Fix tracking issues with strong distortion.* Removing re-definition of member variable and undistort displayed image allows to correctly re-project tracking position on videos with strong distortions. ### IActivitySequencer *Retrieve more data on update.* The sequencer allows to store all data related to all activities in a member called `requirements`. When the sequencer checks an activity for the first time, it'll create it and add all it's needed data that already exist in the `requirement` list into the activity composite. If we have two activities `A1` and `A2`, `A1` needs an image called `CT` and `A2` needs the same image, and also a matrix `MA` created by `A1`. You launch A1 after adding its needed data in the wizard (`CT`), at this time, the sequencer stores `CT` in its `requirements` list. So A1 is open, the sequencer will check if needed data for A2 have been loaded (Allows to enable a button in the view). To do that, `A2` is created and the sequencer adds the `CT` into the composite of `A2`, but `MA` is missing, nothing is launched. Now `A1` has created the matrix `MA` and asks the sequencer to open the next activity, `A2`. The sequencer checks needed data, but `A2` is created, and the sequencer only updates data that are contained in the composite of `A2`. * These modifications allow to update data and also to add potential new data generated by a previous activity (`IActivitySequencer::getActivity`). * Now, before checking a next activity, the sequencer stores data of the current one (the current one has maybe created a new data). * `IActivitySequencer`::parseActivities``returns the last valid activity. Before modifications, the next one was launched. ### Image *Fix the iterator when using a different type than the image.* The image iterator is now computed based on the format of the iterator instead of using the image type. You can now use Iterator on int16 image. It can be useful to fill the image with zeros. You can also parse int32 image with int64 iterator to gain performance. `SLM_WARN` have been added when you use an iterator on a different type. ### ioDcmtk *Fix the series DB reader and the CI last modification time of file.* ### ioIGTL *Fix the module loading on windows.* - Moves the module `ioNetwork` to SrcLib, it only contains interface. Furthermore some modules like `ioIGTL` links with it, and link between modules are prohibited. ### ioVTK *Vtk Mesh readers and writers only support VTK Legacy files format.* Add more Mesh format in VTK readers/writers. * VTK Polydata (\*.vtp) * Wavefront OBJ (\*.obj) * Stereo lithography format (\*.stl) * Polygonal File Format (\*.ply) This was added to improve compatibility with other VTK software (paraview for example), it seems that the \*.vtk format is a legacy format and we should use \*.vtp when dealing with polydata (our ::fwData::Mesh). **Note:** when using OBJ/PLY/STL format you may loose some informations (point normals/point color/...), often it saves only global geometry of the data (points, edges, ...). ### IParameter *Add missing makeCurrent in IParameter.* Add missing `makeCurrent` in `fwRenderOgre/IParameter` ### ModelSeries *Add const in model series helper.* Update `::fwMedDataTools::ModelSeries`to add const on shared pointer parameters. It allows to call the helper with const sptr. ### OgreVolumeRendering *Fix the ambient occlusion.* * Fix a crash in `SummedAreaTable` due to bad storage (when `m sat Size[0]` is 256, the old `std:uint8_t` stores 0). * Fix a crash when the software is closed. ### openVSlamIOTest *Add missing dependencies for link.* This adds ffmpeg and Qt as explicit dependencies. There is clearly a bug in the OpenCV target but until this is fixed upstream, we can use this solution. ### openvslamTracker *Do not download the vocabulary file each time.* Check the file hash to prevent it to be downloaded on each CMake configuring step. ### RayTracingVolumeRenderer *Correctly blend the volume with the scene behind it.* * Fix alpha blending between the volume and the scene behind it. * Fix z-fighting between the negato picking widget and the negato plane when OIT is enabled. * Fix z-fighting between the volume and the negato planes. ### SActivitySequencer *Qml import path is not properly defined.* Check if a `qml` folder exist on the application root folder (for installed application) and use the conan path otherwise. ### SArucoTracker *Doesn't display marker if image components < 4.* Test the number of components of the input image before doing OpenCV conversion. With this modification we can now handle both 3 and 4 components images. ### SCalibrationInfoReader *Don't convert the image if loading failed.* Fix a crash when loading a folder containing files not readable as images. ### SeriesDBReader *Skip structured report and improve the activity data view.* When you load a DICOM folder that contains a `SR` modality with the reader ``::vtk`Gdcm`::Series`FBReader`, it throws an exception. Now it's skipped with a message. The activity data view is also improved in this MR. ### SFrameMatrixSynchronizer *Use new image API.* * Fix the issue when the number of components are not properly reset (#423) * Add a missing write lock on matrices. * Use the new image API ### SFrameWriter *Add missing else statement.* ### Sight *Fail to compile with Clang.* This adds the AES flag for Clang, allowing to build fwZip successfully. Initially, the MR was opened to fix this issue but actually many targets failed to compile with Clang, thus other minor fixes are included. ### SMatricesReader *Fix default path.* Fix the default path in `::ioTimeline::SMatricesReader`. The default path must be a folder, so we use the parent path of the selected file to set the default reader path. It allows to re-open the file dialog in the same folder. ### SMatrixWriter *Add fixed floating point format and set float precision.* Set floating precision fixed to 7 digits. ### SMesh *Fix recursive read lock.* Update ``::visuOgreAdaptor::SMesh``to remove a read lock in an internal method and only call it in the slots. It fixes a random freeze when starting the adaptor in a large application. *Fix auto reset in mesh adaptor.* Fix auto reset camera in `::visuOgreAdaptor::SMesh`: * call auto reset when the vertices are modified * call request render when the adaptor is started ### SNegato2DCamera *Modify camera position in function of the image.* Modify the AutoReset camera method to reset the camera position in function of the image and not the world position. If there is other adaptors than SNegato2D, the scene will be autoreset with the position of the image. It avoids some strange scaling due to other adaptors. ### SNegato3D *Properly use the ITransformable interface.* Properly use the ITransformable interface in SNegato3D. ### spylog *Reduce usage of FW_DEPRECATED Macro.* Small cleanup of usage of `FW_DEPRECATED` macros. * Macros was removed from widely used functions * keyword `[[deprecated]]` was added where it was missing ### SText *Fix text configuration.* The `text` configuration was wrong when parsing xml file. ### STransferFunction *Avoid assert when icons path are not initialized.* - Fix crash when icons path are not initialized in `StransferFunction` service. - When icons path were not defined, we have this assert: `Assertion 'path.is_relative()' failed. Path should be relative` ### STransform *Remove or add the node instead of change the visibility.* Show or hide the transform node directly impact attached entities and services like `::visuOgreAdaptor::SMesh`, that can't manage its visibility since STransform change it any time. Now, the node is just removed or added from/to its parent to avoid a visibility error. ### STransformEditor *Add missing lock.* Add missing mutex lock in `uiVisuQt::STransformEditor` ### SVector *Fix the visibility update of SVector.* * Add two slots to show and hide the vector ### SVideoWriter *Computes video framerate.* * Removes the default framerate value hardcoded to 30 fps in SVideoWriter * Computes framerate using the timestamps of the first frames in the timeline ### SVolumeRender *Fix usage of transfer functions.* * Create a new service `uiTF`::STransferFunction``instead of `uiTF`::TransferFunctionEditor``(it also fix the swapping method and lock data). * Improve the TF usage in `visuOgreAdaptor::SVolumeRender`. * Set the sampling rate at starting of `visuOgreAdaptor::SVolumeRender`. * Fix the `GridProxyGeometry` by avoid uniforms sharing between programs. * Fix the `makeCurrent` method. *Fix a crash with the volume render.* *Double lock when updating the volume sampling.* ### test *IoVTKTest randomly crashes.* Fix ioVTKTest random crash. Sometimes writer doesn't load the reconstructions in the same order than the reader saved it. We need to force writer to load file in a specific order: * prefix reconstruction filename by its index in reconstructionDB (like "0_Liver") * sort filenames by alphabetical order, using the previous prefixed index, to ensure that reconstructions are loaded in the same order than the generated ones. * add messages in CPPUNIT assert marco to help to find failing tests. ### TutoTrianConverterCtrl *Console application cannot be launched with parameters.* Forward arguments of .bat/.sh scripts to fwLauncher. ### videoVLC *Fix VLC plugins dir.* * use current working path to find VLC plugins folder in `videoVLC` for installed applications * fix cmake install target for apps using libvlc: * on Windows (with MSVC2019) VLC is now manually installed to avoid `fatal error LNK1328: missing string table` in fixup_bundle * on linux vlc plugins path is now analysed by the fixup_bundle script to find required dependencies * update VLC conan package to the version 3.0.6-r5 ### visuOgreAdaptor *Properly generate the r2vb in SMesh.* ### visuOgreQt *Intel mesa regressions.* * Fix regressions when using intel mesa GPU following the context handling MR (!249) * Handle buffer swapping manually instead of relying on ogre. *Remove unused variables.* * Fixes a warning on the CI preventing sight from building. * Removes an unused parameter in `visuOgreQt::Window`. *Always force rendering when the window is exposed.* * Fix the call to `renderNow` in `visuOgreQt/Window` * Request render when changing visibility parameters in `ExSimpleARCVOgre` *Send mouse modifiers as keyboard events.* * Take mouse modifiers into account for mouse events. * This is a quick fix and should be refactored later on. ## Documentation: ### fwData *Improve doxygen of Image, Mesh and Array.* * add examples, fix some mistakes. * improve `Image::at(x, y, z, c)` to add the component parameter, it allows to retrieves the value of an element of a image with components ### README *Remove broken install links.* Remove broken links pointing to old documentation pages. ### visuOgreQt *Clean the bundle.* Cleans the `visuOgreQt` bundle. ## Refactor: ### Boost *Replace boost use by C++17 new features.* Replace usage of Boost by **standard library** versions of: - `boost::filesystem` - `boost::make_unique` - `boost::any` - `boost`::assign``(most of them, some were left because of specific boost containers such as bimaps) Also, `fwQtTest` failed a lot during testing, so a fix has been proposed. It should no longer fail now. ### CMake *Rename THOROUGH_DEBUG option.* Rename `THOROUGH_DEBUG` into `SIGHT_ENABLE_FULL_DEBUG` ### deprecated *Remove last remaining deprecated classes and methods for sight 20.0.* * Remove deprecated class Bookmarks * Remove deprecated class AttachmentSeries and the associated reader and converter class (atom patches have been kept to support the loading of old data). * it implies the increment of MedicalData version to V14 and V16AR * Remove the stop of the worker in its destructor * it implies to properly stop the workers before to clear the worker registry (::fwThread::ActiveWorkers). In fact, the workers should be stopped by the classes that created them, but as the API to remove a worker from the registry does not exist, it will be done in #521. * it also implies to stop manually the workers used in the unit tests and not managed by the registry *Remove the use of deprecated Image, Mesh and Array API.* * Remove the last remaining usage of the deprecated API of Image, Array and Mesh outside of fwData and fwDataTools: add a `2` after get/setSize, get/setOrigin, get/SetSpacing. * Fix dump lock on Image and Array iterators: lock must be called before accessing the buffer. * Improve the documentation about the dump lock. *Use new Image, Mesh and Array API.* Refactor some services and libraries to use new Image, Mesh and Array API: * refactor `igtlProtocol` library to use new API * refactor `fwCommand` library to use new API * remove useless helpers includes * fix MeshIterator: set 'operator=()' as virtual and remove redundant cast ### filetree *Rename root folders.* All root folders were renamed, and first of all, Bundles into modules. We also chose to stick to lower-case only and so we renamed all existing folders to their lower counterpart. Besides this SrcLib was shortened into libs and Utilities in utils. Here was the old file tree: | Apps | Bundles | CMake | fwlauncher | Samples \ Examples | PoC | Tutorials | SrcLib | Utilities And here is the new one: | apps | cmake | fwlauncher | libs | modules | samples \ examples | poc | tutorials | utils ### fwData *Clean fwData and fwTest with new API of Image, Mesh and Array.* * Update `config.hpp.in` to add `_DEPRECATED_CLASS_API`, it allows to set the `deprecated` attribute on a class (display a compilation warning). * Update `fwTest` and `fwData` libraries to remove the dependency to `fwDataTools` * Clean `fwDataTools` to remove the use of deprecated helpers, but the helpers are still here. * fix SImagesBlend adaptor: fix size, spacing and origin comparison *Use new Image, Mesh and Array API in uiXXX Bundles.* * Use new Image, Mesh and Array API in uiXXX bundles * Improve `ioVTK` and `fwVTKIO` unit tests * Fix `fwTest` Image generator (number of components was not properly set) * Fix `::fwData::Image`, add a missing const on `getPixelAsString` method *Improve the API of `::fwData::Array`.* Simplifies Array API by moving the methods from the helper to the data itself: * deprecate `::fwDataTools::helper::Array` * integrate the useful methods from the helper to `::fwData::Array` * deprecate the component attribute * add `Iterator` and `ConstIterator` to iterate through the array buffer ### fwDataTools *Use the new Mesh API.* * Refactor Mesh iterator to access values on a const variable. * Use the new Mesh API in ``::fwDataTools::Mesh``to compute normals and improve unit tests. * Also fix the normals: when the cell contains more than 3 points, only the last 3 points were used to compute the normal. * Improve Image ans Array documentation. ### fwRenderOgre *Use the new API for Mesh, Image and Array.* Refactor `fwRenderOgre` and `uiVisuOgre` to use new Image, Array and Mesh API. *Clean the module_ui_media folder.* Sorts glsl, metarial and compostior. ### fwRuntime *Remove usage of deprecated methods.* * This removes the usage of all deprecated methods by !282 * This removes the usage of the term `bundle` in local variables, private functions, comments. *Rename 'Bundle' into 'Module'".* The first intent of this MR is to rename the term 'Bundle' into 'Module'. As stated in #404, 'Bundle' is not a term widely spread in software for the meaning we give to it. More often, software use either the term 'Module' or 'Plugin'. Late in summer 2019, we decided to choose 'Module'. However we did not want that change to be a breaking change. So as usual, during two major versions, 'Bundle' and 'Module' terms are likely to coexist and the old API should be deprecated but maintained. Most code mentioning 'Bundle' lies in `fwRuntime`. I realized a lot of code present in that library was never used outside. It would have been useless to double every method and class for non-public code. I've been keeping saying we do not clearly separate public and private API in our libraries. I decided so to try to achieve that in fwRuntime. After doing this, the only deprecation of the public API will be faster. To separate the private and the public API in _Sight_ **libraries**, I propose to add `detail` folders both in `include` and `src` folders. Symbols in `detail` folders will be hidden/not exported, thus reducing the size of the shared library and speeding up debugging (when doing this at a large scale of course). This has many advantages in terms of readability of code, maintenance (like this deprecation), etc... However, one drawback is that since symbols are not exported, it was no longer possible to unit-test private classes and methods. To overcome this, I propose to compile libraries in two steps. One `OBJECT_LIBRARY`, then the usual `SHARED_LIBRARY`, that uses the previous as input of course. This way, the unit-test can build directly with the `OBJECT_LIBRARY` (so the precompiled .obj) directly, removing the need of export, and allowing the test of private classes and methods. **But**, this was not that simple. In our tests that are often more functional tests than unit-tests, we face two issues: - we have circular dependencies between libraries, so the test may try to link against both the OBJECT_LIBRARY and the SHARED_LIBRARY, causing awful runtime errors, - we use lots of static singletons to register types, factories, etc... The same singleton may be instanced both in the OBJECT_LIBRARY and the SHARED_LIBRARY, ending up with a duplicate and not a singleton. The first issue can be tackled, I tried at some point, but it was quite hard, especially as soon as we load modules. The second is even harder sometimes. At the end I chose an alternative. I propose to split tests in two. One for the public API (ex: `fwRuntimeTest`) and one for the implementation (ex: `fwRuntimeImplTest`). The implementation test should be much more minimal and should not require many dependencies, thus reducing the possibility of these two problems to occur. At least that's my hope. :candle: ### fwServices *Remove the old API from fwServices.* Remove all the deprecated methods from IService and its associated helpers and registries. Remove IService default object. **Note**: the default auto-connection on the first service's object to the 'modified' signal is removed. Check your log to know if your auto-connections are connect. ### guiQt *Improve SParameters.* * Fix a bad call to `m_illum`. * Allow to avoid the automatic update of `SParameters`. * Add option in `SParameters` to hide the reset button. ### Image *Improve the API of ::fwData::Image.* Improve the API of `::fwData::Image`: * Add the pixel format information: GRAY_SCALE, RGB, RGBA, BGR, BGRA * Add an iterator for the different formats * the format involve the number of components * Prevent to use more than 3 dimensions: the size, spacing and origin are defined in a `std::array` * 4D images are no longer allowed * The ``::fwData::Array``is no longer accessible from the image * Update Dispatcher to use ``::fwTools::Type``instead of ``::fwTools::DynamicType``and deprecate DynamicType. * **Most** of the old API is still supported with deprecated warning: * the new getter/setter for size, origin and spacing are temporary post-fixed by `2` (ex: getSize2()) * Increase the version of Medical Data to serialize the image format (V12, V14AR, V16RD) * Update `WARNINGS_AS_ERRORS` macro to allow deprecated warnings. Example: ```cpp `::fwData::Image::sptr`img = ::fwData::Image::New(); img->resize({1920, 1080}, ::fwTools::Type::s_UINT8, ::fwData::Image::PixelFormat::RGBA); typedef `::fwData::Image::RGBAIteration`RGBAIteration; auto lock = img->lock(); // to prevent buffer dumping auto iter = img->begin(); const auto iterEnd = img->end(); for (; iter != iterEnd; ++iter) { iter->r = static_cast(rand()%256); iter->g = static_cast(rand()%256); iter->b = static_cast(rand()%256); iter->a = static_cast(rand()%256); } ``` ### IO *Use the new Image, Mesh and Array API.* - Use the new Image, Array and Mesh API in readers and writers. - Fix Mesh iterators to properly access the values, even if the variable is const. - Update Image to add the missing API for lazy readers : it requires access to the buffer object to read streams. *Use the new Image, Mesh and Array API.* - Refactor fwVtkIO, fwItkIO, igtlProtocol and associated bundles to use the new Image, Mesh and Array API. - Fix Image iterator: the end of the const iterator was not correct. - Improve mesh iterator to allow point and cell colors with 3 components (RGB). ### iterator *Improve iterator to access values.* - Update Image and Array iterators to allow to access values on const iterators. - Refactor Image iterator to remove complicated format and only use simple struct. - removed warnings when getting an iterator on a type different from the array type. It allows to iterate through a multiple value structure at the same time. Now, to iterate through an Array or an Image, you can use a struct like: ```cpp struct RGBA{ std::uint8t r; std::uint8t g; std::uint8t b; std::uint8t a; } ``` Then: ```cpp `::fwData::Image::sptr`image = ::fwData::Image::New(); image->resize(125, 125, 12, ::fwTools::Type::s_UINT8, ::fwData::Image::RGBA); auto itr = image->begin< RGBA >(); const auto itrEnd = image->end< RGBA >(); for (; itr != itrEnd ; ++itr) { itr->r = 12.0; itr->g = 12.0; itr->b = 12.0; itr->a = 12.0; } ``` ### module_ui_media *Fuse all module_ui_media bundles.* * Deprecate `arMedia` bundle. * Copy all `arMedia` content into `module_ui_media`. * Fixes xml files that used these bundles. ### Mesh *Improve the API of ::fwData::Mesh.* Refactor the mesh API: - deprecate the access to the points, cells and other arrays - rename allocate method to reserve - allow to allocate the color, normal and texture arrays in the same reserve method - add resize method to allocate the memory and set the number of points and cells - add iterator to iterate through the points and cells **Allocation**: The two methods `reserve()` and `resize()` allow to allocate the mesh arrays. The difference between the two methods is that resize modify the number of points and cells. - `pushPoint()` and `pushCell()` methods allow to add new points or cells, it increments the number of points and allocate the memory if needed. - `setPoint()` and `setCell()` methods allow to change the value in a given index. Example with `resize()`, `setPoint()` and `setCell()`: ```cpp `::fwData::Mesh::sptr`mesh = ::fwData::Mesh::New(); mesh->resize(NB_POINTS, NB_CELLS, CELL_TYPE, EXTRA_ARRAY); const auto lock = mesh->lock(); for (size_t i = 0; i < NB_POINTS; ++i) { const std::uint8_t val = static_cast(i); const std::array< ::fwData::Mesh::ColorValueType, 4> color = {val, val, val, val}; const float floatVal = static_cast(i); const std::array< ::fwData::Mesh::NormalValueType, 3> normal = {floatVal, floatVal, floatVal}; const std::array< ::fwData::Mesh::TexCoordValueType, 2> texCoords = {floatVal, floatVal}; const size_t value = 3*i; mesh->setPoint(i, static_cast(value), static_cast(value+1), static_cast(value+2)); mesh->setPointColor(i, color); mesh->setPointNormal(i, normal); mesh->setPointTexCoord(i, texCoords); } for (size_t i = 0; i < NB_CELLS; ++i) { mesh->setCell(i, i, i+1, i+2); const std::array< ::fwData::Mesh::ColorValueType, 4> color = {val, val, val, val}; const float floatVal = static_cast(i); const std::array< ::fwData::Mesh::NormalValueType, 3> normal = {floatVal, floatVal, floatVal}; const std::array< ::fwData::Mesh::TexCoordValueType, 2> texCoords = {floatVal, floatVal}; const size_t value = 3*i; mesh->setCellColor(i, color); mesh->setCellNormal(i, normal); mesh->setCellTexCoord(i, texCoords); } ``` Example with `reseve()`, `pushPoint()` and `pushCell()` ```cpp `::fwData::Mesh::sptr`mesh = ::fwData::Mesh::New(); mesh->reserve(NB_POINTS, NB_CELLS, CELL_TYPE, EXTRA_ARRAY); const auto lock = mesh->lock(); for (size_t i = 0; i < NB_POINTS; ++i) { const std::uint8_t val = static_cast(i); const std::array< ::fwData::Mesh::ColorValueType, 4> color = {val, val, val, val}; const float floatVal = static_cast(i); const std::array< ::fwData::Mesh::NormalValueType, 3> normal = {floatVal, floatVal, floatVal}; const std::array< ::fwData::Mesh::TexCoordValueType, 2> texCoords = {floatVal, floatVal}; const size_t value = 3*i; const size_t value = 3*i; const auto id = mesh->pushPoint(static_cast(value), static_cast(value+1), static_cast(value+2)); mesh->setPointColor(id, color); mesh->setPointNormal(id, normal); mesh->setPointTexCoord(id, texCoords); } for (size_t i = 0; i < NB_CELLS; ++i) { const auto id = mesh->pushCell(i, i+1, i+2); const `::fwData::Mesh::ColorValueType`val = static_cast< `::fwData::Mesh::ColorValueType`>(i); const std::array< ::fwData::Mesh::ColorValueType, 4> color = {val, val, val, val}; const float floatVal = static_cast(i); const std::array< ::fwData::Mesh::NormalValueType, 3> normal = {floatVal, floatVal, floatVal}; const std::array< ::fwData::Mesh::TexCoordValueType, 2> texCoords = {floatVal, floatVal}; const size_t value = 3*i; mesh->setCellColor(id, color); mesh->setCellNormal(id, normal); mesh->setCellTexCoord(id, texCoords); } ``` **Iterators** To access the mesh points and cells, you should uses the following iterators: - `::fwData::iterator::PointIterator:`to iterate through mesh points - `::fwData::iterator::ConstPointIterator:`to iterate through mesh points read-only - `::fwData::iterator::CellIterator:`to iterate through mesh cells - `::fwData::iterator::ConstCellIterator:`to iterate through mesh cells read-only Example to iterate through points: ```cpp `::fwData::Mesh::sptr`mesh = ::fwData::Mesh::New(); mesh->resize(25, 33, ::fwData::Mesh::TRIANGLE); auto iter = mesh->begin< `::fwData::iterator::PointIterator`>(); const auto iterEnd = mesh->end< `::fwData::iterator::PointIterator`>(); float p[3] = {12.f, 16.f, 18.f}; for (; iter != iterEnd; ++iter) { iter->point->x = p[0]; iter->point->y = p[1]; iter->point->z = p[2]; } ``` Example to iterate through cells: ```cpp `::fwData::Mesh::sptr`mesh = ::fwData::Mesh::New(); mesh->resize(25, 33, ::fwData::Mesh::TRIANGLE); auto iter = mesh->begin< `::fwData::iterator::ConstCellIterator`>(); const auto endItr = mesh->end< `::fwData::iterator::ConstCellIterator`>(); auto itrPt = mesh->begin< `::fwData::iterator::ConstPointIterator`>(); float p[3]; for(; iter != endItr; ++iter) { const auto nbPoints = iter->nbPoints; for(size_t i = 0 ; i < nbPoints ; ++i) { auto pIdx = static_cast< `::fwData::iterator::ConstCellIterator::difference_type`>(iter->pointIdx[i]); `::fwData::iterator::ConstPointIterator`pointItr(itrPt + pIdx); p[0] = pointItr->point->x; p[1] = pointItr->point->y; p[2] = pointItr->point->z; } } ``` `pushCell()` and `setCell()` may not be very efficient, you can use `CellIterator` to define the cell. But take care to properly define all the cell attribute. Example of defining cells using iterators ```cpp `::fwData::Mesh::sptr`mesh = ::fwData::Mesh::New(); mesh->resize(25, 33, ::fwData::Mesh::QUAD); auto it = mesh->begin< `::fwData::iterator::CellIterator`>(); const auto itEnd = mesh->end< `::fwData::iterator::CellIterator`>(); const auto cellType = ::fwData::Mesh::QUAD; const size_t nbPointPerCell = 4; size_t count = 0; for (; it != itEnd; ++it) { // define the cell type and cell offset (*it->type) = cellType; (*it->offset) = nbPointPerCell*count; // /!\ define the next offset to be able to iterate through point indices if (it != itEnd-1) { (*(it+1)->offset) = nbPointPerCell*(count+1); } // define the point indices for (size_t i = 0; i < 4; ++i) { `::fwData::Mesh::CellValueType`ptIdx = val; it->pointIdx[i] = ptIdx; } } ``` ### MeshIterator *Cast cell type into CellType enum instead of using std::uint8_t.* When iterating through mesh cells, the iterator return the type as a CellType enum instead of a std::uint8_t. It allow to avoid a static_cast to compare the type to the enum. ### OgreViewer *Recreate the whole application.* * Cleans `visuOgreAdaptor`. * Cleans the documentations of all adaptors and fix it for some of them. * Draws border of negatos and allow to enable/disable them from the xml. * Creates a new `OgreViewer`. * Adds a missing documentation in `LineLayoutManagerBase`. * Creates a xml color parser. * Fixes the fragment info adaptor by listening the viewport instead of the layer. * Improves the slide view builder ### RayTracingVolumeRenderer *Separate ray marching, sampling, compositing, and lighting code.* * Simplifies the volume ray tracing shader to make it more understandable. * Reduces macro definition combinations. ### resource *Fuse all module_ui_media bundles.* * Adds `arMedia` and `module_ui_media` into a folder `resource`. * Adds a new bundle `flatIcon` with flat theme icons. ### SNegato2DCamera *Improve 2D negato interactions.* * Add a new adaptor to replace the `Negato2D` interactor style. * Precisely zoom on a voxel using the mouse cursor. * Deprecate `::visuOgreAdaptor::SInteractorStyle`. * Update OgreViewer to use the new adaptor. * Decouple camera management from the negato adaptor. * Fix interactors to work within a viewport smaller than the whole render window. * Fix the camera's aspect ratio when the viewport width and height ratios are not the same. ### STrackballCamera *Move the trackball interaction to a new adaptor.* * Decouples the trackball interactor from the layer. * Moves trackball interactions to a new service. * Makes 'fixed' interactions the default. * Deprecate parts of the `SInteractorStyle`. ### Tuto *Use new API of Image, Mesh and Array.* Refactor some services and libraries to use new Image, Mesh and Array API: * Refactor Tuto14 and Tuto16 specific algorithms to use the new API * Refactor `scene` to use new API and remove some warnings * Refactor `visuVTKAdaptor` to use new API * Refactor `itkRegistrationOp` library to use new API ### video *Video use new data api.* Refactor some services and libraries to use new Image, Mesh and Array API: * Refactor `videoCalibration` * Refactor trackers, frame grabbers * Refactor `cvIO`: conversion between openCV and sight ### visuVTKAdaptor *Use the new Image, Mesh and Array API.* * update `fwDataTools` to remove the last uses of the deprecated API except the helpers * refactor `visuVTKAdaptor` `vtkSimpleNegato`and `vtkSimpleMesh` to remove the deprecated API * refactor `opImageFilter` and the associated library to remove the deprecated API * fix `::scene2D`::SComputeHistogram``due to a missing include and remove the deprecated API * fix ``::fwRenderOgre::Utils``due to a missing include and remove the deprecated API for the image conversion ### VRWidgetsInteractor *Move the clipping interaction to a separate class.* * Refactors vr widget interaction. Splits `VRWidgetsInteractor` into `TrackballInteractor` and `ClippingBoxInteractor` and deprecates it. * Adds right mouse button interaction to the trackball. * Renames `VRWidget` to `ClippingBox`. ## New features: ### ActivityLauncher *Update the activityLauncher configuration to change icons paths and readers configuration.* Allow to customize the activity wizard used by the sequencer. You can use different icons and/or define the readers to use. * Add a parameter in ActivityLauncher configuration `WIZARD_CONFIG` to define the custom configuration. * Update ExActivities sample to customize the wizard. ### ARCalibration *Load calibration input image folders.* * Add a service to load image folders with calibration inputs. * Move the detection method to the `calibration3d` library. *Live reprojection for intrinsic calibration.* * Compute the reprojection error for each new frame following the calibration. * Display the reprojected points and the reprojection error. * Display the detected points. * Add the ability to undistort the video images once the calibration is computed. * Modify SSolvePnP to always update the camera parameters. ### build *Embed PDB file when installing Sight in debug mode on Windows platform.* Add `install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR}/${FW_INSTALL_PATH_SUFFIX} OPTIONAL)` ### ci *Build sight-sdk-sample with the SDK.* * Refactor a bit linux release and debug jobs to build in SDK mode and install/package the SDK. The SDK is an artifact of the job and passed to other dependent jobs * Add jobs that use the SDK artifact and unpack and use it to build sight-sample-sdk * The SDK artifact can be used to manually deploy the SDK on artifactory *Use LFS repo for sight-data.* This replaces the usage of sight-data with a new repository with LFS support. This speed-ups the download time because git LFS can parallelize batch downloads. This is particularly useful for the CI: - Previously we used `curl` to retrieve the archive then `tar` to decompress the archive, **the whole process took around 170s**. - The new `git clone` commands, **this takes only 45s**. For some ci tasks like deploying issues and merge requests templates, we will use `GIT_LFS_SKIP_SMUDGE=1` which allows to skip completely the download of binaries. *Remove macos support.* * remove macos jobs in gitlab-ci script * add warning message to inform users that macos is no longer supported ### cmake *Conan update and compilation flag sharing.* * Update ogre to patched version 1.12.2 ** Match shader input/ouput attribute names in vertex/geometry/fragment shaders. ** Fix varying parsing for the volume proxy bricks shaders. ** Replace the deprecated scene node iterator. ** Remove the debug plugin configuration on windows. ** Explicitly convert quaternions to matrices. ** Fix normal rendering. * Add Visual studio 2019 support with conan package available on artifactory. All conan packages have been updated * Share "optimized" debug compiler flags across all conan package and Sight. Some previous work on array API (and in many place in our code or our dependencies) showed very bad performance in debug. To mitigate this, we want to use optimized debug build with `-Og -g` (unix) or `/Ox /Oy- /Ob1 /Z7 /MDd` (windows) which will effectively make the speed almost reach release build, while being "debuggable". The drawbacks are: ** the build will take a bit longer (max 10% longer on gcc/clang, 30% on MSVC) ** some lines may be "optimized" out ** we loose /RTC1 on windows To allow "full" debugging on Sight, we plan to add a special option to use regular flags. A new option `THOROUGH_DEBUG` has been added to allow full debugging without optimization, if needed. On windows it also add /sdl /RTC1 which performs additional security checks. Also be sure to have the latest version of conan and that you use the latest `settings.yml` on Windows or macOS (sometimes there is a `settings.yml.new` and you need to rename it `settings.yml`). On Linux, Sight automatically download an updated `settings.yml`, but you can still update conan. To update conan: `pip3 install --upgrade conan` *C++17 support.* This updates our Sight build to support C++17 standard. Some modifications were also needed from some of our dependencies: - Camp (patched upstream) - ITK (updated to 5.0.1) - Flann (imported patch from upstream) - Sofa (updated to 19.06.01 and patched locally) ### conan *Update Qt, PCL, Opencv & Eigen.* *Update conan package to support CUDA 7.5 arch.* This is mainly for supporting correctly NVIDIA RTX GPU ### conan-deps *Support Visual compiler 16 for Visual Studio 2019.* Now Sight can be compiled with the latest Visual compiler 16 provided with Visual Studio 2019. ### core *Use RAII mechanism and weak_prt / shared_ptr pattern to protect fwData against race condition and memory dumping.* Three class has been added: `weak_ptr`, `locked_ptr`, `shared_ptr`. Each of them will "store" the pointer to the real data object, but in a different manner and with a different way to access it. You will receive a `weak_ptr` when calling `IServices::getWeak[Input|Inout|Output]()` and a a `locked_ptr` when calling `IServices::getLocked[Input|Inout|Output]()` weak_ptr will use a hidden `std`::weak_ptr``to hold the real data, but it can only be accessed by "locking" it through a `locked_ptr`. `locked_ptr` will hold a `std::shared_ptr`, but also a mutex lock to guard against concurrent access and a buffer lock to prevent dumping on disk when the data object have an array object. RAII mechanism will ensure that everything is unlocked once the `locked_ptr` is destroyed. The underlying lock mutex will be a **write** mutex locker if the pointer is **NOT const**, a **read** mutex locker if the pointer **is const**. Using an `Input` will anyway force you to have const pointer, forcing you to use a **read** mutex locker. You can simply get the original data object through a std`::shared_ptr`by calling `locked_ptr::getShared()` *Simplifies macros in fwCore and enables WARNINGS_AS_ERRORS in some core libs.* * Warnings in Sight core was mainly due to the call of macro with fewer parameters than required. * We have now a macro `fwCoreClassMacro` with three versions: * fwCoreClassMacro(_class); * fwCoreClassMacro(_class, _parentClass); * fwCoreClassMacro(_class, _parentClass, _factory); * The macro `fwCoreServiceClassDefinitionsMacro` used in services has been simplified and replaced by `fwCoreServiceMacro(_class, _parentClass);` * All old deprecated macros have been placed in the `macros-legacy.hpp` file (still included by `macros.hpp` for now - until **Sight 22.0**) * List of new projects that no longer generate warnings during compilation (new cmake option `WARNINGS_AS_ERRORS` enabled): * arData * arDataTest * fwCom * fwComTest * fwCore * fwCoreTest * fwData * fwDataTest * fwMedData * fwMedDataTest * fwRuntime * fwRuntimeTest * fwServices * fwServicesTest * fwTools * fwToolsTest * fwRenderOgre * fwRenderOgreTest ### ctrlPicking *Add a new bundle for picking operation services.* * merge `::uiVisuOgre::SAddPoint`, ``::echoSimulation::STransformPickedPoint``and ``::uiMeasurement::SManageLandmarks``into `::ctrlPicking::SManagePoint` * Allow to have a maximum number of points in a pointlist via `::opPicking::SAddPoint`. * Allow to avoid points to be removed in `::ctrlPicking::SManagePoint`. * Deprecate `::uiVisuOgre::SAddPoint`. * Deprecate `::uiMeasurement::SManageLandmarks`. ### debian *Build sight on Debian-med.* Added several patches to build sight on Debian-med * fix launcher library path * fix version getter: in debian workflow, Sight version should be passed from the build parameters and not from the git repository * do not install conan deps since we use system libraries * add missing copy constructor for fwData`::ImageIterator`(new warning in gcc9) * fix support of VTK 7 * remove redundant move in return statement in fwCom \[-Werror=redundant-move\] * fix build flag for project using system lib * add a new debian gitlab-ci build job * fix the source path of dcmtk scp config file ### ExActivitiesQml *Implement activities for Qml applications.* Create a Qml sample to launch activities: `ExActivitiesQml` * Create base class for activity launcher and sequencer service to share the code between qml and qt services. * Move qml style from `guiQml` to `style` bundles, it is required because some bundles require it and we don't want to start guiQml bundle before them. * Improve `AppManager` to manage input parameters and to generate unique identifier * Create an Qml object `ActivityLauncher` to help launching activities in a Qml application ### flatdark *Update flatdark.qss to add style for persia.* Modify `flatdark.qss` to add specific style for `Persia` application. ### fwData *Allow locked_ptr to work with nullptr.* - If a data is optional, it can be null. But the AppConfig manager try to get a locked_ptr of the data, and the locked_ptr try to lock a nullptr, so an exception is thrown. Now, locked_ptr allows nullptr. - Fix a crash when clicking on `Reinitialize` or `Delete` in the Volume TF editor. *Get/set a 4x4 Matrix from TranformationMatrix3D.* - Add new functions to get/set a TransformationMatrix3D from/to a 4x4 Matrix (array of array). This ensure the row major order and avoid linear to 2d conversions before setting coefficients. ### fwGui *Manage more gui aspects.* * Manage label of SCamera. * Remove a "pading" warning in flatdark.qss. * Add tooltips on button in the WindowLevel. * Change the color of disabled QLabel. * Allow to set the size of each borders in layouts, and manage spacing between widget in the same layout. * Fix the icons aliasing in the wizard. * Fix the opacity in `fwGuiQt` *Manage inverse actions.* Handle the inverse actions behavior. When the inverse is set, it means that the behavior of toolbar button (or menu item) is inverted. It is like if it's internal state return false when it is checked and true when unchecked. *Add parameter to specify the tool button style in ToolBar.* Enable to display the text beside or under the icons in a toolBar. ### fwGuiQt *Allow to set a background color on toolbars, menus and layouts.* ### fwPreferences *Add password management capabilities.* This MR allow to specify a password, retrieve it from memory (it is stored in a scrambled form) and compare its sha256 hash to the one stored in preferences. This password is then used by SWriter and SReader to write and read encrypted data. The code rely on fwPreferences helper and should be accessible from anywhere as static variable is used to hold the password in memory. This functions should be thread safe. ### fwRenderOgre *Simplify fullscreen toggling.* * Make fullscreen toggling easier and more efficient. * Add slots to enable/disable fullscreen on the render service. * Add an action service to select a screen for fullscreen rendering. * Add a shortcut to return to windowed mode. * Fix bundle linking in `uiVisuOgre`. *Configurable viewports.* * Configure viewports for each layer. * Refactor the overlay system to enable overlays per viewport (layer). * Fix adaptors to render text using the new overlay system. *Allow pickers to pick both side of meshes.* Take into account the culling mode of entities for the picking. It allows to know which sides of the triangle to pick. See Sight/sight#373 ### fwRenderOgre/Text *Crisp font rendering.* * Improve font rendering by taking into account the dpi and rendering text at the font size. * Update the dpi on text objects when switching to a screen with a different DPI. * Handle vertical alignment for texts. * Update all adaptors to use this new font rendering method. ### fwServices *Raise an exception when IService::getLockedInput() return a NULL locked_ptr.* Throw an exception when `IService::getLockedXXX()` retrieve a NULL data object. ### fwZip *Add "encryption" support when reading and writing jsonz archives.* - add encryption and update internal minizip to version "1.2". ### gui *Add notification popups in sight applications.* * Notification can be displayed at 7 fixed positions (from top-left to bottom right including top & bottom centers). * 3 Types of notifications: INFO, SUCCESS, FAILURE (background color changes). * Notifications appears / disappears with a fade-in/out effect on opacity. * Two ways of displaying notifications: * Centralized by the new `SNotifier` service: * Using new IService signals `infoNotified`, `successNotified` and `failureNotified` connected to SNotifier slots `popInfo`, `popSuccess`, `popfailure`. * `SNotifier` can queue multiple notifications, if queue is full the oldest one is removed. * `SNotifier` handles also position of the notification per application/config (ex: always at TOP_RIGHT). * Or notification can be displayed by calling directly `::fwGui::NotificationDialog::show()`, loosing the advantages of the centralized system (position, queue, ...). * Add `ExNotifications` sample & `SDisplayTestNotification` service to test and to show how it can work. ### ImageSeries *Add more attributes to our medical data.* * Adds more attributes to our medical data folder. * Creates patch related to these data. * Add tests related to these new data versions. ### MeshFunctions *Add function to convert point coordinates to barycentric one in tetrahedron.* Add functions and test to convert a points coordinates to barycentric coordinates to its barycentric coordinate inside of a tetrahedron. Add corresponding unit test. *Add function to convert world coordinates to barycentric.* * Add functions and test to convert from world coordinates to barycentric coordinates if a point belongs to a triangle. * Add corresponding unit test. ### MeshPickerInteractor *New service for mesh picking interactions.* ### OpenVSlam *Integrate OpenVSLAM in sight.* * openvslamIO is a library that contains some conversion classes & functions in order to easily convert from/to sight to/from openvslam structures. OpenvslamIO is unit tested. * openvslamTracker contains SOpenvslam service to interface with openvslam process * start/stop/pause/resume tracking * Get camera pose for each frames given to the service * Get the map as a `::fwData::Mesh`pointcloud * Save/Load the map. * Save Trajectories as .txt files (matrix or vector/quaternions format). * ExOpenvslam is an example that show all possibilities that offer the openvlsam integration in sight. **Note**: this is a preliminary integration so for now openvslam runs only on linux and with monocular perspectives cameras (very classical models). ### SActivitySequencer *Add new editor to display an activity stepper.* The list of the activities are displayed in a toolbar, the buttons are enabled only if the activity can be launched (if all its parameters are present). The sequencer uses the data from the previously launched activity to create the current one. Create a new `ActivityLauncher` configuration to simplify the launch of an activity sequencer. Improve `ExActivities` sample to use `ActivitySequencer` configuration and add volume rendering activity ### SAxis *Add a configurable marker.* Add a configurable marker on `visuOgreAdaptor::SAxis` ### SFrameMatrixSynchronizer *Add new signals to be consistent with SMatrixTLSynchronizer.* - Add slots to be compatible with the behaviour of `SMatrixTLSynchronizer` in order to connect with a `SStatus` service. The `ExStereoARCV` example is updated to show its usage. - The configuration of `SMatrixTLSynchronizer` is updated to take into account the wish to send the status of some matrices in timelines in `SMatrixSynchronizer`. - Now, it's possible to add the tag `sendStatus` in a ``. - In this way, if you have multiple timelines and multiple matrices, you can choose which one you want to send its status like: ```xml ``` ### SImage *Add editor to display an image.* Create an editor ``::guiQt::editor::SImage``to display an image in a view. ### SImageMultiDistance *Improve interactions and resources management.* * Fixe `::visuOgreadaptor::SImageMultiDistance`. * Add auto-snap on distances. * Update distance on multiple scene. * Improve resources management. * Use our new interactor API. ### SLight *Manage point light.* * Adds point light in addition of directional light. * Allows to manage point light position with `SLightEditor`. * Fixes the specular color computation in `Lighting.glsl`. * Allows to manage ambient color of materials in a new service `SOrganMaterialEditor`. * Deprecates the service `OrganMaterialEditor`. ### SMesh *Add default visibility configuration.* * Add default visibility state in configuration of a mesh. * Add a usage example of this feature in `ExSimpleARCVOgre` ### SMultipleTF *Add new adaptor to manage TF composite.* Creates a new adaptor to display a composite of TF and interact with them. The following actions are available: * Left mouse click: selects a new current TF or move the current clicked TF point. * Left mouse double click: adds a new TF point to the current TF or open a color dialog to change the current clicked TF point. * Middle mouse click: adjusts the transfer function window/level by moving the mouse left/right and up/down respectively. * Right mouse click: remove the current clicked TF point or open a context menu to manage multiple actions which are 'delete', 'add ramp', 'clamp' or 'linear'. ### SNegato2D *Take the slide position into account.* * Take into account the slice position to have the right picking information. * Retrieve the viewport size in the event method for `SNegato2D`. ### SParameters *Manage dependencies.* Adds dependencies system on `SParameters`. ### SPointList *Add slots to update and toggle visibility.* * Add a `toggleVisibility` slot in `SPointList` * Implement this feature in `ExOpenvslam` ### SPreferencesConfiguration *Add the possibility to specify a file in the preferences.* ### SRGBDImageMasking *New service to perform depth image masking.* ### SSeriesSignal *Listen the modified signal.* ### SSquare *Deferred position update and mouse interaction toggling.* - Implement deferred position update, via the "autoRefresh" xml attribute - Allow to enable/disable mouse interaction, via the "interaction" xml attribute ### SsquareAdaptor *Add slots on scene2d SSquare.* Add a slot to the scene2D Ssquare adaptor which allows the change the configuration parameters of the square. This will allow the movement of the square by changing the values of `X` and `Y` on the widget slider. ### STextStatus *Add input string.* Add an input ``::fwData::String``and display it. ### Stransform *Check if the matrix is null.* Check if the given matrix of `visuOgreAdaptor`::STransform``is null to avoid a computation error with Ogre's nodes. ### style *Add QLabel error style.* Add a style in the qss to make a QLabel red and bold for errors. *Add service and preference to switch from one theme to another.* - Selected style is saved in preference file (if it exists), and then reloaded when you re-launch the app. ### SVector *Add parameter to configure the vector visibility on start.* Add `visible` parameter in `SVector` configuration to manage the visibility of the vector on start. ### SVideo *Add update visibility on SVideo Ogre adaptor.* Add a `updateVisibility` slot in `visuOgreAdaptor::SVideo` ### SVolumeRender *Buffer the input image in a background thread.* * Add a new option to load input images into textures in another thread. * Add a new worker type able to handle graphical resources in parallel. ### Tuto06Filter *Update the tutorial to follow the API changes (fwData and RAII).* * Improve Tuto06Filter documentation according to sight-doc. * Use new service API in SThreshold * Remove useless dump locks in SThreshold * Use SSlotCaller action instead of calling the operator service in the menu * Add some comments ### ui *Improve gui aesthetic.* * Improve the background management in 'fwGuiQt'. * Disable parameters in 'uiMeasurement::SLandmarks' instead of hide them. * Allow to configure the style of 'SActivityWizard'. * Properly use the 'hideActions' mode on toolbars. * Properly set bounding boxes on 'visuOgreAdaptor::SAxis'. * Add a flatdark theme. ### uiMeasurementQT *Allow to remove landmarks with SLandmarks.* * Allows to add or remove landmarks from picking information with the service 'uiMeasurementQt::editor::SLandmarks'. * Updates the documentation. ### visuOgreAdaptor *Improve the negato camera.* Compute the camera ratio relatively to the viewport size *Add a punch tool to the volume rendering.* Adds a punch tool, a new adaptor to creates extruded meshes from a lasso tools, and a service to remove image voxels that are in meshes. * The service `SShapeExtruder` works as follows: * The drawn shape is stored as a points list belonging to a 2D plane in the near of the camera, and a second one is stored in the far of the camera. * Once the shape is closed, a triangulation is done on the two points list with a constrained Bowyer-Watson algorithm. * Then, for each segment of the shape, two triangles are created between the segment at the near plane and the far plane. *New adaptor to output renderTarget as an image.* Add a new adaptor `SFragmentsInfo` that takes informations of the configured layer like, informations can be color, depth or primitive ID. Some minors updates are also pushed in this MR: 1. SMaterial can now be configured with a `representationMode` (SURFACE/POINT/EDGE/WIREFRAME). 1. Add new conversion function in `fwRenderOgre`::helper::Camera``to convert from screen space to NDC or viewspace. deprecates the old ones. *Directional light editing.* Adds a visual feedback to light adaptors. *Allow to manage TF windowing with SNegato2DCamera.* * Add TF windowing management with SNegato2DCamera. * Fix a configuring error from SPicker. *Add new adaptors to resize a viewport.* * Add a new compositor to draw borders in viewports. * Add an adaptor to resize viewports *Interact with the 3D negato.* * Add the same interactions on the negato as in VTK * Add a priority to the image interactors * Add the ability to cancel interactions * Deprecate the old selection interactor API *Add a new adaptor to display a vector.* Add `SVector`, a new adaptor that displays a vector. ### visuOgreQt *Properly compute cameras aspect ratio.* If an Ogre layer has a ratio different of 1:1, the camera aspect ratio of the layer is badly computed. It's due to the resize event that retrieves the wrong viewport ratio. *Use qt to create the OpenGL contexts for ogre.* * Add a new class to create OpenGL contexts for ogre. * Move the offscreen rendering manager to `visuOgreQt` and instantiate it through a factory. * Fix material management for the VR widgets. ## Performances: ### videoCalibration *Speedup chessboard detection.* * Add a scaling factor to the input to run the detection algorithm on a downscaled image. * Const-overload `::cvIO::moveToCV`. * Add a new preference parameter to configure the scaling in ARCalibration. # sight 19.0.0 ## Bug fixes: ### qml *Use of a QML Engine to recreate dialog and apply a new style.* * fwQt has been change to add a callback function that is called to create the application using QApplication or QGuiApplication. * created libraries: * guiQml: start all services needed for a Qml Application and contain element to customise Material style. * fwGuiQml: contain all visual Component for Qml, like dialogs. * guiQml * Plugin: call getQtWorker to setup services and launch the Application. Then set the style to Material. * Material.qml: Singleton to set the color of the theme globally in each Window. * qmldir: regroup all Control.qml in rc to override the theme from QtQuick.Controls and QtQuick.Controls.Material * fwGuiQml * model: regroup ListModel and TableModel that are generic model to use inside Qml * dialog: recreate all Qt dialog in Qml ### vlc *Conan package depends on testing dependencies.* Update VLC to 3.0.6-r2 to depends from Qt and FFMpeg of the testing branch. ### fwServicesTest *Increase timeout in fwTestWaitMacro.* - The timeout of `fwTestWaitMacro` was increased to give more time when the runner is heavily loaded. - An optional parameter to specify the duration of the timeout was added. This is useful when we want for instance a shorter duration when we want to verify that a condition did not occurred. ### SWriter *Automatically parse file name when saving an activity.* * Extension is added automatically if no extension is provided in filename * if an extension is provided in filename it will overwrite extension selected in dialog, a error pop if extension is unknown. ### SHybridMarkerTracker *Update hybrid_marker_track to version 1.1 to improve tracking speed.* ### fwRenderOgre *Use std::isinf() instead of isinf() from math.h.* ### fwRenderOgre *Remove Courier.ttf font file, not allowed for redistribution.* The font `Courier.ttf` was removed from `fwRenderOgre` and replaced by `DejaVuSans.ttf`, which was already present in the bundle `material`. `Courier.ttf` was only used for the unit-tests, so the test was modified to reflect this change. ### VRRender *Activities creation crash.* Add parameters `APP_NAME` and `PROGRESS_CHANNEL` in `SActivityLauncher` service in VRRender `sdb.xml`. Modify `SActivityWizard` to check if no tab was created because the selected activity did not have any input parameters. ### ExSolvePnP *Crash due to wrong data inout key name.* This MR fixes the issue Sight/sight#354. It edits the INOUT key name for the meshReader service in ExSolvePnP ### calibration *Add missing locks in SSolvePnP and SChessBoardDetector.* Without these locks, it is possible to find a configuration where the objects are used in the same time and the application crashed. ### CMake *Set policy CMP0072 to NEW to avoid warning about findOpenGL on Linux.* Please refer to `cmake --help-policy CMP0072` for details ### VideoRealSense *Only set the output when the first frame has been grabbed.* - Wait until the first frame has been grabbed to set the output. Doing so, prevent the use of an allocated but randomly filled buffer to be processed by other services, thus making them crash. See #333 for detail. - Additionally, the pause mode has been fixed to not consume all cpu power. ### VRRender *Crash at exit.* This prevents VRRender to crash when exiting in all DICOM related activities. Actually the crash occurred in the ioDicom plugin destruction. The usual linking hack, preventing the linker to strip symbols, consists in instancing one of the class of the bundle. It was placed in the destructor which is a bad idea because it creates an allocation of an UUID while the application is being destroyed. ### SSolvePnP *Add missing locks.* Without these locks, it is possible to find a configuration where the objects are used in the same time and the application crashed. ### TutoEditorQml *Fix the crash when the application is launched.* - The `startService` in `onServiceCreated()` is removed. `AppManager` base class has been updated to automatically start the services when they are added (if `startServices()` has been called once). ### core *Add missing readlock.* Some read locks are missing in central services like SCopy. Adding them may lead to slower but better, safer code ### SShaderParameter *Force parameter update when the update slot is called.* - Reset the dirty flag when calling the update. Not doing so prevented textures from being updated when modified. ### cmake *Check CMAKE_BUILD_TYPE value.* * on Windows, if the user doesn't specify a value for 'CMAKE_BUILD_TYPE', it's automatically initialized to 'Debug' after 'project()' cmake command. * it's annoying because if we wanted to build in release, we have to clean the cmake cache and define `CMAKE_BUILD_TYPE` to `Release` (just redefining `CMAKE_BUILD_TYPE` isn't enough and is "dangerous") * so now, we check `CMAKE_BUILD_TYPE` before cmake command 'project()', and if the user forgets to define `CMAKE_BUILD_TYPE` before configuring, configuration is stopped and display an error message ### RayTracingVolumeRenderer *Allow to work with derived classes.* ### visuOgreAdaptor *Missing color buffer when rendering point billboards.* Add a new material to handle the case when the rendered mesh has no color buffer ### SFrameUpdater *Change image dimension to 2D.* Modify image creation in SFrameUpdater to change dimension from 3D to 2D ### visuOgreQt *Fix crash in ogreResize().* - Add a test to check if the ogre render windows is initialized before using it in the `ogreResize()` method ### visuOgreAdaptor *Properly release resources.* Releases Ogre texture properly ### ogre *Fix infinite loop and graphic corruption in ogre shader code.* - Fix infinite loop and graphic corruption in ogre shader code by replacing a 'for' loop with its 'reverse' equivalent. It is very strange, and looks like a glsl compiler bug, but the workaround seems to work. - Some missing `makeCurrent()` were added ### visuOgreQt *Fix hidpi mode for various mouse event.* - Computes correctly the mouse and window coordinate by taking into account HiDPI (retina) display, on each events, instead of only once. This should fix problems when switching display with a different resolution. Still a manual resize may be needed to force relayouting and thus to have correct size computation. ### SGrabberProxy *Remove spaces when parsing camera Type Tags.* ### fwRenderOgre *Prevent alpha from leaking onto the rendering window.* * Disable alpha writing when blending layers with the background. * Fix the transparent widget bug. ### SAxis *Fix the SAxis node visibility.* `SAxis` visibility is defined by changing the visibility of its node, which can be used by other adapters. The visibility is now sets with another method. ### fwVTKQml *Fix QML Tutos with VTK Scene.* The vtk OpenGLRenderWindow was changed from an external to a generic one which handle his own OpenGL context. So in each render at the beginning we init the state of the OpenGL by calling OpenGLInitState. QVTKOpenGLWidget was changed by QVTKOpenGLNativeWidget because in the code source of VTK it was written: `QVTKOpenGLNativeWidget is intended to be a replacement for QVTKWidget when using Qt 5`. ### fwCore *Fix HisResClockTest::getTimeTest.* We use now the `std::chrono::high_resolution_clock::now()` instead of `std::chrono::system_clock::now()`. Although it may be different, especially on macos where system_clock tends to go too fast (!!!), it seems that on most platform it is indeed the same. This need to be validated on a long test period, and may require a revert.. ### OgreVolumeRendering *Pepper artifacts when the clipping box exceeds the image size.* Clamp clipping box coordinates to not exceed the volume size to fix some artifacts when the user sets a clipping box bigger than the rendered image. ### fwServices *Crash when swapping the inputs of stopped services.* ### Qml *Set conan Qt/Qml directory in QML_IMPORT_PATH".* Allow to use QtCreator to edit Qml files with the qt package from conan. ### ARCalibration *Reverse the model pointlist coordinates.* Reverse x and y coordinates on the calibration model according to openCV tutorial (https://docs.opencv.org/3.4/d4/d94/tutorial_camera_calibration.html). It does not seem to change the result. ### AppManager *Fix auto-start services in AppManager.* Fixes `AppManager` to start automatically services when all associated objects are available. ### OgreVolumeRendering *Fix proxy geometry generation.* A bug occurs when the volume rendering display a tiny image (like a 10\*10). ### visuOgreAdaptor *Fix camera ratio in Ogre SFrustumList.* Camera ratio was wrong in `visuOgreAdaptor`::SFrustumList``because it's not the same than `visuOgreAdaptor::SFrustum`. Now, Both adaptors have the same behavior and display the frustum at the same size and the same place. ### videoVLC *Vlc SFrameGrabber continuously pushing while paused.* ### AppConfigTest *Add a missing wait condition in startStopTest.* ### fwDataTest *Fix MTLockTest random failure.* The test was not written correctly. This could lead to race conditions. We rewrote it in the following way. The test try to lock the data to write two different string sequences in a `fwData::String`. It launches two asynchronous methods that lock the data to write, wait 2 ms between each char insertion and wait 5ms after the unlock. Then, it ensures that the letters from the two sequences are not mixed in the string. ### ObjectService *Add missing mutex.* There was a missing mutex lock in ObjectService::unregisterServiceOutput(). This should solves the random fail in AppConfigTest::startStopTest(), and possibly other random failures in the rest of the tests. ### ARCalibration *Fix extrinsic calibration synchronisation.* Now, we ensure that the chessboard is detected in the two cameras before adding the calibration information. It prevents a wrong synchronization of the calibration information. By the way, an action was also added in the extrinsic view to save the calibration images. Fixes #292 ### fwDicomIOFilter *Limit memory usage of fwDicomIOFilterTest & fwDcmtkIOTest.* This replaces the usage of deepCopy in two Dcmtk filters. We used to copy the whole DicomSeries and then remove the internal buffer. This is both inefficient and memory expensive. We propose here to use shallowCopy() instead, which does not copy the buffer. The container is still cleared out, but only the pointers, so the source buffer is not destroyed. ### Ogre *Set the origin of the SNegato2D to a corner.* The origin of the `::visuOgreAdaptor::SNegato2D` representation is moved on the lower left corner of the image. This change is necessary because the distance system (which was designed for the VTK backend) returns values from the lower left corner of the image. To get this working properly with Ogre, we need to change the origin that currently lies in the middle of the image. The method used to change the orientation of the negato was also modified accordingly. ### videoRealSense *Fix grabber to properly update the cameraSeries.* Prevent to add multiple times the same camera in the cameraSeries when the `startCamera` method is called by checking if the cameraSeries is calibrated. Fix ExRealSense configuration to set the proper parameters for the service. ### unit-tests *Fix random failures.* - **fwServicesTest**: add missing wait. Also, one second might not be enough when the system is under heavy load, so the waiting time has been increased to 2500ms instead of 1000ms. Remember this is just the worst case and usually the function returns in a shorter time. - **ioAtomsTest**: this divides the test image size by 2 and reduces the number of reconstructions from 15 to 5 in the test model series. This should help a bit to reduce the execution time, see results below. - **igtlProtocolTest**: actually fix a lot of the mesh conversion code. It crashed randomly, but only because the UTest was very poor. If done properly, it should have shown how bad the code was, since apart from positions and cells copy, all the rest of the array copies were broken. - **All tests**: - when destroying a worker, we may ask it to join twice the thread if someone else is calling `stop()`. On top of that, hen stopping a worker, we decide whether we need to join the thread by testing the state of the io_service. However this does not seem to be a reliable way, so we now test the thread state itself, - when launching two unit-tests in parallel, they may stop at the same time. Both processes will try to remove temporary folders simultaneously, so we must handle failures properly with exceptions, - use temporary folders for writing instead of folders in the build tree, - last, we realized it is a bad practice to rely on stopping a worker with the auto-destruction (i.e. using shared pointers), because this could lead the `std`::thread``to be destroyed from its own execution scope. So we deprecated the call of `stop()` in the `WorkerAsio` and now we advise people to call `stop()` from the callee thread (most often the main thread). All of this solves a lot of random errors when launching all unit-tests. We can also launch unit-tests in parallel now, making the CI jobs faster. ### SDK *Remove relative paths in 3rd part libraries.* All paths relative to the build host should now be removed from the include and library paths. The absolute paths are stripped away. Some cleaning has also been done to include only the needed modules for VTK and PCL. ### qml *VisuVTKAdaptor crashes when using QML.* Registers imageSeries and tf objects in the SImageSeries constructor. ### Qml *Fix SIOSelector when using Qml.* Change the `AccessType` of the registered object from INPUT to INOUT when we are in writer_mode. ### Ogre *Correct GLSL shaders compile errors on Intel chipsets.* ### SGrabberProxy *Fix SGrabberProxy configuration selection.* ### SScan *Convert point cloud positions to millimeter.* RealSense camera return the point cloud in meters, but we need a point cloud in millimeters. Thus we convert point positions to millimeter (multiply the values by 1000). We also add a test if the camera is already started to avoid a crash. ## New features: ### calibrationActivity *Improve widget layout.* * Display all widgets other than the scenes in a panel on the left side. * Fix the naming convention for service uids. ### cmake *Add option to build a project with warnings as errors.* * Added a cmake option `WARNINGS_AS_ERRORS` in sight to build a project in warning as error (`/Wx` on MSVC or `-Werror` on gcc/clang) * Removed warning in sight project `fwlauncher` * Enabled option `WARNINGS_AS_ERRORS` in `fwlauncher` project * Changed warning level to `w4` on Windows (to see unreferenced local variable) ### cmake *Improve package version number generation.* * Added cmake helper script `get_git_rev.cmake` (from [sight-deps](https://git.ircad.fr/Sight/sight-deps/blob/dev/cmake/utils/get_git_rev.cmake)) to find git tags, branch name, version... * Updated cmake script to generate a SDK filename using the latest tag (or git revision, see command [git describe --tags](https://git-scm.com/docs/git-describe)) * Updated cmake script to add Sight version in app packages ### cmake *Add warning for links between bundles.* Add a test in CMakeLists.txt to display a warning message if there are links between bundles ### fwRenderOgre *Add a depth technique to the VR.* Add a new technique to the volume rendering in order to display the depth of the volume. * RayTracedVolumeDepth_FP: new fragment shader that displays the volume depth. * OffScreenWindowInteractor: fix the rendering by calling the specific target instead of all targets. Remove a double connection in `::visuOgreAdaptor::SInteractorStyle`. ### CMake *Allow utilities to be launched from Visual Studio.* Generate the `vcxproj.user` for utilities. ### OgreDynamicImageTest *Speed-up copyNegatoImage() and updateImage().* - Improve copyNegatoImage using parallel omp ### videoRealSense *Align pointcloud on RGB frame for AR.* Align streams to the desired frame coordinate system (Depth, Infrared, Color). This allow us to have each stream in the same coordinate system, no need to apply transforms in xml configurations. New option is available trough ExRealSense: "Align frames to"; this allow user to choose target coordinate frame where all frames should be align to. Add "visible" option in configuration of `::visuOgreAdaptor::SPointList` ### SHybridMarkerTracker *Extract tag position and orientation in camera.* * Modify `SHybridMarkerTracker` to extract tag position and orientation and take into account an `::arData::Camera`. * Configuration file was replaced by parameters that could be set through `SParameters`. ### hybridMarkerTracker *Add service to track cylindrical hybrid marker.* - Add SHybridMarkerTracker service for tracking a cylindrical hybrid marker - Add associated ExHybridMarkerTracker example ### visuOgreAdaptor *Addition of a distance measurement editor.* - Addition of a distance measurement adaptor in the Ogre 2D negato. You can create a new distance, remove a specific distance or hide/show the distance. ### visuOgreAdaptor *Support landmarks visibility.* Added landmark visibility support in `::visuOgreAdaptor::SLandmarks` ### conan *Update all package to allow sharing of C flags.* - The goal of this code is to share C flags across all our conan packages to ensure compatible code generation as some compiler settings can lead to strange bugs, hard to debug (especially floating point mode like `-mfpmath=sse`, please see the associated issue https://git.ircad.fr/Sight/sight/issues/188). - We choose to write the flags and compiler settings inside a python file packaged as a conan package (https://git.ircad.fr/conan/conan-common/tree/stable/1.0.0). For now, you will have acces to: - `get_[c,cxx]_flags()`, `get_[c,cxx]_flags_[release,debug,relwithdebinfo]()`, `get_cuda_version()`, `get_cuda_arch()` and some utility functions for conanfile.py like `fix_conan_path(conanfile, root, wildcard)` which allows to fix path in .cmake files. ### SFrameMatrixSynchronizer *Handle time shift delay.* Handle synchronization issues in our application by applying a time shift value. ### fwRenderOgre *Update `MeshPickerInteractor` to send `PickingInfo` over signals.* Send ``::fwDataTools::PickingInfo``with `MeshPickerInteractor`to be more generic. ### SLandmarks *Configurable text size.* Use an xml configuration to set the text size of `::visuOgreAdaptor::SLandmarks`. ### visuOgreAdaptor *Addition of a dashed line SLine.* Ability to draw a dashed line and choose the distance between the dots. Configuration is done in XML (SLine service) ### videoRealSense *Implement the record and playback from realsense library.* Adding record/playback functionalities in ::videoRealSense::SScan. * Record function can records all streams synchronously in one file in rosbag format (.bag) * Playback function can replay recording and emulate a realsense device (filters can be applied, and camera parameters are the same as the real device). Also add color on Ogre's SPointList adaptor. ### visuOgreAdaptor *Adaptor displaying text.* Display text along the borders or in the center of an OGRE window. ### videoRealSense *Add filters on depth frame.* Allow to filter the depth frame with the three filters provided by the RealSense SDK: spacial, temporal and hole filling. The filters can be enabled and configured with a `SParameter`. The three filters are: * **Spatial Edge-Preserving filter**: it performs a series of 1D horizontal and vertical passes or iterations, to enhance the smoothness of the reconstructed data. * **Temporal filter**: it is intended to improve the depth data persistency by manipulating per-pixel values based on previous frames. The filter performs a single pass on the data, adjusting the depth values while also updating the tracking history. In cases where the pixel data is missing or invalid the filter uses a user-defined persistency mode to decide whether the missing value should be rectified with stored data. Note that due to its reliance on historic data the filter may introduce visible blurring/smearing artifacts, and therefore is best-suited for static scenes. * **Holes Filling filter**: the filter implements several methods to rectify missing data in the resulting image. The filter obtains the four immediate pixel "neighbors" (up, down ,left, right), and selects one of them according to a user-defined rule. Update `ExRealSense` to add widgets to change the filters parameters. Add a service configuration in ARCalibration to play the RealSense infrared frame without the emitter. Fix `SPointCloudFromDepthMap` to emit less signals. It emitted 'modified' signal on the mesh on each update which was causing the entire refresh of the VTK mesh (very slow). But only the vertex and the point colors are modified, so now, only 'vertexModified' and 'pointColorModied' signal are emitted. ### Conan *Update many conan packages.* Update conan package to more up-to-date versions and refactor sight a bit: * boost --> 1.69 * eigen --> 3.3.7 * Qt --> 5.12.2 * pcl -->1.9.1 * vtk --> 8.2.0 * itk --> 4.13.2 * opencv --> 3.4.5 * ogre --> 1.11.5 * glm --> 0.9.9.5 * gdcm --> 2.8.9 * dcmtk --> 3.6.4 * odil --> 0.10.0 * cryptopp --> 8.1 * bullet --> 2.88 Details: * **boost** related ### fwIO *Implement a fail/success notification on all readers and writers.* Adds a new member status boolean to `IReader` and `IWriter`and all of their inherited implementations. The following implementation of those interfaces have been modified : Readers : * ioAtoms/SReader * ioCalibration/SOpenCVReader * ioData/SAttachmentSeriesReader * ioData/STrianMeshReader * ioData/TransformationMatrix3DReaderService * ioDcmtk/SSeriesDBReader * ioGdcm/SDicomSeriesDBReader * ioGdcm/SSeriesDBReader * ioITK/InrImageReaderService * ioITK/SInrSeriesDBReader * ioITK/SImageReader * ioVTK/SModelSeriesReader * ioVTK/SSeriesDBReader * ioVTKGdcm/SSeriesDBLazyReader * ioVTKGdcm/SSeriesDBReader Writers : * ioAtoms/SWriter * ioCalibration/SCalibrationImagesWriter * ioCalibration/SOpenCVWriter * ioData/MeshWriterService * ioData/TransformationMatrix3DWriterService (great name btw). * ioGdcm/SDicomSeriesWriter * ioGdcm/SSeriesDBWriter * ioGdcm/SSurfaceSegmentationWriter * ioITK/InrImageWriterService * ioITK/JpgImageWriterService * ioITK/SJpgImageSeriesWriter * ioITK/SImageSeriesWriter * ioQt/SPdfWriter * ioVTK/SImageSeriesWriter * ioVTK/SImageWriter * ioVTK/SMeshWriter * ioVTK/SModelSeriesObjWriter * ioVTK/SModelSeriesWriter * ioVTKGdcm/SImageSeriesWriter The following implementation of those interfaces have **not** been modified : Note: by default we consider that a non modified reader always return success... Readers: * ioTimeline/SMatricesRead : this service is to complex for a simple muggle like me. * ioZMQ/SAtomNetworkReader : one of those that do a bit more than reading... :rolling_eyes: * ioZMQ/SImageNetworkReader : same here Writers: * ioTimeline/SMatrixWriter : see above * ioZMQ/SAtomNetworkWriter : idem * ioZMQ/SImageNetworkWriter : idem * videoOpenCV/SFrameWriter : not the actual behaviour we expect. ### videoVLC *Implement RTP video streaming with libvlc.* - Adds a new service ̀SFrameStreamer in the videoVLC bundle. Which allows to stream frames pushed into a FrameTL. - Adds a new sample ExVLCVideoStreaming that grabs and streams a given video (either passed through a file or directly a stream). - Adds and increments videoVLC conan package version dependency. ### conan *Stores all used versions of conan packages in a single file.* To simplify the process, all versions packages of conan are now merged into a simple conan-deps.cmake file ### GUI *Add tooltips on views.* A tooltip can now be set on any view of a cardinal or a line layout manager, optionally of course : ```xml ``` If the editor inside the view already has a tooltip, it will be not be overridden by this configuration, thus we do not break the existing tooltips. ### gitlab-ci *Check the compilation with SPYLOG_LEVEL=trace.* Update gitlab-ci to compile debug configuration with SPYLOG_LEVEL=trace to check wrong log message. Fix a wrong debug message. ### SGrabberProxy *Forward IGrabber's signals.* ### FrameLayoutManager *Add visibility parameter.* ### opencv *Use our own ffmpeg and rework opencv dependencies.* ### calibration *Write calibration input images to a folder.* Adds a feature to write calibration input images to a folder. This can be useful for debugging or to compare our results with those of third-party programs. Calibration info image writer. ### CI *Add a new stage to build CI on linux to build the sdk.* Besides this this also embeds some fixes for the sdk packaging on windows. ### test *Use always slow tests skipping.* Resolve "ease CI for slow test" ### fwTest *Add a template version of randomizeArray.* Add a template version of randomizeArray in fwTest generators. It is useful for floating arrays because with the existing method some 'nan' values could appear, and the comparison of 'nan' return always false. ### TutoSimpleAR *Promote ExSimpleARCV into a real tutorial.* ExSimpleARCV is promoted to a real tutorial named TutoSimpleAR. The configuration has been cleaned a bit, comments have been added everywhere and the sample data are now downloaded at configuration time to help the beginners. This sample is also a demonstration of the new design to synchronize AR rendering efficiently. As most of the tutorials, it is documented in the official documentation. ## Refactor: ### deprecated *Remove deprecated code related to sight 19.0.* * Remove deprecated methods in `fwDcmtkIO` * Remove outdated and unused bundle `uiNetwork` ### SActivitySequencer *Emit a signal when the activity requires additional data.* The service emits a signal 'dataRequired' if the activity can not be launch because some data are missing. It allows to connect it to a SActivityWizard to select new data. ### conan *Remove sight-deps support, conan is used by default.* Conan is now the official way to retrieve 3rd party libraries in Sight. Since it would be a pain to keep maintaining both sight-deps and Conan together, we agreed to skip the normal deprecation phase. * Remove option `USE_CONAN` in sight and make it the default. * Clean all parts of the build system that refer to `EXTERNAL_LIBRARIES`. * Remove support of Eclipse project ### fwPreferences *Add getValue helper.* The method `getPreferenceKey(...)` returns associated value saved in the preferences (if delimited with the character `%`) or simply returns the variable. This helper is defined (copied/pasted) 12 times in Sight. To avoid this, we have centralized all these versions in `fwPreferences` with the new method `getValue(const std::string& var, const char delimiter = '%')`. ### deprecated *Remove deprecated code related to sight 19.0.* ### trackingCalibration *Remove hand-eye reprojection service.* SChessboardReprojection was unused in sight. Remove the hand-eye reprojection service ### videoCalibration *Rewrite chessboard detection.* Modifies the SChessboardDetection service to take images as inputs instead of timelines. Makes synchronization easier. ### RayTracedVolume_FP.glsl *Remove IDVR referencies.* ## Performances: ### fwVtkIO *Improve mesh conversion.* The methods updating the mesh points, normals and texture to VTK were very slow, checking the allocated memory on each value. It is replaced by using the `SetArray()` methods with a copy of the data mesh array. This increases the speed from ~23ms to ~0.7ms for the point color update. # sight 18.1.0 ## New features: ### MarkedSphereHandleRepresentation *Add ComputeInteractionState method.* ### videoRealSense *Update realsense grabber:.* - grab also pointcloud - live loading of presets (in /rc/presets) - enable/disable IR emitter - switch between color/infrared frame - live modification of min/max range - speed-up the grabbing function - brand-new ExRealSense using Ogre backend Resolve "Output pointcloud from realsense grabber" ### ci *Add ci-jobs to build sight on windows and macos.* - Add some jobs to build sight on Linux, Macos and Windows - Launch unit-test - Use ccache to reduce build time Resolve "Add windows and macos as gitlab CI target" ### CMake *Add source packaging with CPack.* It is now possible to create a source package for a specific application. To do so, you have to: - Choose an app you'd like to package. (e.g. VRRender, OgreViewer,...) This needs to be an **installable** app make sure it calls `generic_install` in its `CMakeLists.txt` - Set it (and only it) as the `PROJECTS_TO_BUILD` in your cmake config. - Run cmake `cmake .` - Build the source archive, if building with ninja you can run `ninja package_source` else run `cpack --config CPackSourceConfig.cmake` - Retrieve the source archive (e.g. VRRender-0.9-Source.tar.gz if you chose VRRender). ## Some results A source package example : [PoCRegistration-0.1-Source.tar.gz](/uploads/a4fdd05b1681b7f01c1c553903f0f072/PoCRegistration-0.1-Source.tar.gz) ## Additional tests to run This should better be tested on all platforms. - [x] Windows - [ ] ~~macOS~~ :arrow\_right: won't work see #248 - [x] GNU/Linux ### SDecomposeMatrix *New service to decompose a matrix and associated tutorial.* Add a new service to decompose a matrix into a rotation matrix, a translation matrix and a scale matrix. Add a new sample using this service. ### TabLayoutManagerBase *Add a border option.* - add also Parameters_Gray.svg icon. Resolve "Improve GUI esthetic" ### SParameters *Add new index based enumeration slot.* Add a slot which changes enumeration state based on the index and not on the label. ### video *Add slot that plays or pauses according to current state.* - Add new slot to IGrabber that plays or pauses. - Add two state booleans on IGrabber. - Add declaration to start, stop and pause on IGrabber. - Add parent call to all derived grabbers for start, stop, pause. ### conan *Find conan packages associated with installed cuda version.* - Use conan option 'cuda' to find package associated with cuda version installed on the machine - Available cuda versions: "9.2", "10.0", "None" ### SFlip *Add service to flip images.* - Add a service to flip images in the three main axes (can handle up to 3D images) - Implement the SFlip service in Tuto06Filter ### vscode *Add vs code generator.* - add VS Code support to build and debug Sight - to use VSCode with sight: run CMake configure with option SIGHT_GENERATE_VSCODE_WS, and open the workspace file "sight.code-workspace" (in build dir) ### conan *Add linuxmint19 support.* - Support new settings 'os.distro' in cmake-conan script into sight for linux. - Available values in your packages for settings 'os.distro' are: - linuxmint18 - linuxmint19 ### idvr *Draw the IDVR's depth lines from the outside of the countersink.* ### visuOgreAdaptor::SPointList *Addition of a labeled point list.* If the option "displayLabel" is set to true on the XML configuration, it will add a label on a point when the point is created. You can also choose the character height and the color of the label on the XML configuration. ### PoCMergePointcloud *Add a new PoC to merge pointclouds from 2 RGBD Cameras (Orbbec astras).* Move opDepthMap & depthMapOp from internal repository to opensource. Resolve "Sample to merge two RGBD Cameras" ### Calibration *Export extrinsic matrix in .trf format.* Resolve "Export extrinsic matrix in .trf in calibration activity" ### OGRE *Configure stereo cameras using camera series.* - Computes each projection matrix using intrinsic and extrinsic calibrations ### Ogre *Improve stereo rendering management.* - Adds an action to enable/disable stereo in an ogre layer. - Refactors the stereo mode to be handled by the core compositor. - Fixes some adaptors that crashed when restarting. ### SImageWriter *Handles timestamps and bitmap images".* ### Player *Add getter of video duration.* Adds a getter for QMediaPlayer::duration() this is useful for knowing how much time is left on your video and especially this ticket for a video timeline editor which basically uses the `::fwVideoQt::Player`to get specific frames at specific positions. ## Bug fixes: ### template.sh *Add quotes in linux sh template (build & install).* Resolve "Wrong bundledir path in application scripts automatically generated by CMake" ### OgreViewer *Correct user interface bugs.* The compositor uniforms are now properly shown in the compositor selector. The application no longer crashes at exit when all lights are removed. ### SMaterial *Use a lookup table to correctly display 16 bits texture.* ### videoOpenCV *Duration calculation & loop mode.* * Correct the duration calculation. * Use total number of frame and current frame index to loop the video. Resolve "OpenCV grabber loop mode & duration" ### guiQt *Copy the styles plugin on install.* Installed and packaged apps looked bad because we forgot to ship the qt styles plugin. ### fwRuntime *Remove windows.h in Library.hpp.* - avoid exposing of windows.h in fwRuntime`::dl::Library`header (via dl::Win32.hpp) - use abstract class fwRuntime`::dl::Native`(as a pointer) in the hpp and instantiate the specific implementation in the cpp - update vlc conan package used in videoVLC to fix problems related to windows.h ### Flipper *Move the implementation to the correct path.* Resolve "Move Flipper.cpp from imageFlipperOp to imageFilterOp" ### videoOrbbec *Allow to use the grabber in RGB mode.* Fixes a crash in videoOrbbec`::SScan`if no depth timeline was given. Now, we can use this grabber to get only the RGB frames. ### ci *Fix job to deploy doxygen on the sight gitlab page.* - Doxygen pages can no longer be deployed on the CI due to an error on the gitlab-ci script. - There is an error because the git configuration has been defined globally for all jobs, and the deployment jobs use a docker image that doesn't contain 'git'. - To fix this, we'll use the same docker image for all linux jobs (DOCKER_ENVDEV_MINT19) ### guiQtTest *Add missing profile.xml and gui requirement.* ### fwDicomIOFilter *Fix unit tests.* ### SStereoToggler *Remove fwServicesRegisterMacro macro.* Fix compilation without PCH by removing the fwServicesRegisterMacro Resolve "Missing header in SStereoToggler.cpp" ### utests *Various fixes for fwJobsTest, fwGdcmIOTest, fwRenderOgreTest, fwThreadTest.* This fixes the following unit tests: - fwJobsTest: The problem was visible on macOS, but other platforms maybe impacted. Testing if the future is valid before waiting did the trick. Also a wrong "+" with a char* and an integer which give also a crash in release, has been replaced with a proper string concatenation - fwGdcmIOTest: The hashing function boost::hash_combine() doesn't always use the same implementation across platforms (!!!). We now use a regular sha1 hashing, a bit slower, but safer - fwRenderOgreTest: Deleting the dummy RenderWindow with destroy() leads to a double delete crash when deleting ogre root node. Using the appropriate Ogre::RenderSystem::destroyRenderWindow() seems to fix the crash - fwThreadTest: Use a workaround to mitigate the callback cost in time computation. This is a workaround because either the test itself is false (and maybe undoable, as unpredictable), either we must change the implementation of TimerAsio to take into account the time taken by the callback. We may look again for https://git.ircad.fr/Sight/sight/tree/fix/TimerAsio, but it is clear this change will have side effect since some code may actually want to wait for the callback to perform before reseting the timer. I guess that playing a video with openCV did fail with the above merge request ### cmake *Use the correct macOS flags to build pch.* Pass the correct macOS flags when compiling pch ### gitignore *Parse also versioned qtcreator cmake preferences files.* Resolve "Update gitignore to include qtcreator's versioned files" ### conan *Quiet conan output.* Update cmake script with conan OUTPUT_QUIET option to have a more quiet conan's output ### GridProxyGeometry *Fix variables declaration.* ### SGrabberProxy *Correct parsing of grabber list and ensure that the list is filled even if no config is set.* Resolve "Empty SGrabberProxy list" ### ci *Update ccache path.* - now ccache use a nfs folder mounted in docker image - previous gitlab-ci cache is not compatible with this system ### SFrameWriter *Use the correct timestamp in image filename.* Use the timeline timestamp instead of the current timestamp when saving frames using videoOpenCV::SFrameWriter ## Refactor: ### Ogre *Remove IDVR from the repository.* This MR removes all IDVR references from OgreViewer. This makes the application simpler, because over time it accumulated too many features. In the same time, we added volume rendering and negatoMPR adaptors to the VTK tab, in order to show the comparison between the two back-ends. OgreViewer has been updated to 0.3. ### ui *Remove deprecated services (ShowLandmark, SaveLandmark, LoadLandmark and DynamicView).* Resolve "Remove deprecated services" ### Bookmarks *Deprecate ::fwTools::Bookmarks.* Add FW_DEPRECATED macro in fwTools`::Bookmarks`and the associated service ctrlSelection::BookmarksSrv. The bookmarks are no longer used and the service still use a deprecated API. ### HiResClock *Un-deprecate getTime* functions.* Resolve "Un-deprecate HiResClock functions" ### videoRealSense *Use the version 2 of the realsense api.* This is a basic implementation of a Grabber for D400 cameras: * parameters such as resolution, fps, ... are set in code * the camera chosen with the videoQt selector is not taken into account by the grabber (Qt doesn't recognize correctly realsense device) * if multiple realsense cameras are plugged-in the grabber will pop a selector dialog * depth pointcloud is not outputted for now ### filterVRRender *Rename filterVRRender to filterUnknownSeries.* `FilterUnknownActivities` filter from `filterUnknownSeries` bundle allows to remove the unknown activities when reading a medical file (.json, .jsonz, .apz, ...) with `::ioAtoms::SReader`. The old filterVRRender bundle is kept for backward compatibility, but will be removed in version 19.0. ### ioGdcm *Remove plugin.xml in ioGdcm.* Remove the plugin.xml file in ioGdcm bundle because it is useless (cmake generates it automatically). Also change the documentation of services. ### uiVisuOgre *Remove obsolete getObjSrvConnections().* ### fwTools *Move shared library path search code and make it generic.* This introduces a new function in `fwTools`::Os``: ```cpp /** * @brief Return the path to a shared library name * The library should have already been loaded before. * @param _libName The name of the shared library, without any 'lib' prefix, 'd' suffix or extension, * i.e. 'jpeg' or 'boost_filesystem'. The function will try to use the appropriate combination according to * the platform and the build type. * @return path to the library on the filesystem * @throw `::fwTools::Exception`if the library could not be found (not loaded for instance) */ FWTOOLS_API `::boost::filesystem::path`getSharedLibraryPath(const std::string& _libName); ``` This function has been used to search for Qt plugins in `WorkerQt.cpp`, where this was originally needed. It can be used in some of our utilities as well. The code is generic and has been unit-tested properly. ### video *Better support of selected device in video grabbers.* * Strongly identify camera when using Qt Selector, and add better support of multiple same model cameras * Refactor CameraDeviceDlg to keep usb order in comboBox * Uniquely identify cameras using a prefix pushed in description. * Grab Webcam with OpenCV is now multiplatform. * Enhance the video support when using OpenNI grabbers. * Add support of multiple connected astra (videoOrbbec) * Use Opencv instead of Qt to grab RGB frame * Grab RGB & Depth frame in same thread (synchronized on depth) * Remove Qt in videoOrbbec * Add Astra utility program to replace over-complicated SScanIR * astraViewer: to view Color/IR and Depth stream * astraRecord3Streams: to display Color + IR + Depth and take a snapshot (! FPS will be very very slow ! ). Resolve "videoOpenni and videoOrbbec SScan don't take into account the selected device" # sight 18.0.0 ## Bug fixes: ### STransform *Check parent node existence before creating it.* ### unit-tests *Look for profile.xml in the source directory.* Instead of looking for a profile.xml in the build directory for the unit-tests, now we look into the source directory. Otherwise, this does not work when CMake is only ran once. Doing this, some missing bundle requirements in unit-tests were fixed. ### CMake *Check if profile exists to enable bundles load on unit-tests.* ### * *Add GL_SILENCE_DEPRECATION definition globally.* Before it was only defined in fwRenderOgre. Resolve "OpenGL is deprecated on MacOS 10.14" ### TutoOgreGenericScene *Update deprecated service key.* This made this application crash. ### install *Remove useless install target.* setpath.bat and .bat are only usable in build dir ### unit-tests *Ensure test data are really free and anonymized.* This anonymize some data used in our unit-tests. ### SMesh *Check if node exists before attaching it to root.* ### DCMTK *Change DCMTK files path on Windows.* ### * *Resolve errors reported by FOSSA.* - Update our license headers to mention Sight and not FW4SPL - Change the license headers to follow [GNU recommendations](https://www.gnu.org/copyleft/lesser.html) - Add IHU Strasbourg copyright - Remove LGPL license headers from minizip files - Remove DCMTK files and copy them at build instead - Add copyrights for MIT licensed files. ### fwVideoQt *Properly close the camera device and deallocate the related resources.* Resolve "ARCalibration crash when stream stops" ### macos *Fix OgreViewer for macOS in debug mode.* ### calibrationActivity *Correct intrinsic editor inputs.* Calibration activity crashes when starting the intrinsic editor because of an input that should be an inout. Merge remote-tracking branch 'origin/fix-calibration-activity-crash-on-start' into dev ### SReprojectionError *Fix indexing of marker points.* ### fwRuntime *Resolve NOMINMAX redefinition.* - NOMINMAX was defined twice (in 'fw-boost.cmake' and in 'fwRuntime/dl/Win32.hpp') - a warning C4005 was displayed each time this file is used ### conan *Update conan packages.* Conan has new packages available, it is necessary to update the versions used by Sight for macos and QML ### ImportanceCompositing_FP *Allow Nsight compiling.* ### SActivityWizard *Fix crash when loading or importing a data.* Fix load and import in SActivityWizard. SIOSelector service must be configured before to associate the output object Id. ### CMake *Use the correct variable for deps without Conan.* We just used the wrong variable... ### visuOgre *Add include file.* ### SCamera *Replace assert by warning when creating cameras.* Allow to use ``::videoQt::editor::SCamera``when we don't know if the input CameraSeries will be initialized or not: the cameras will be generated if the cameraSeries is empty. ### ogre *Fix naming conflicts and double deletes.* ### sight *Set external lib dir in scripts to launch Sight apps.* Scripts used to launch Sight applications are not correct because the variable FW_EXTERNAL_LIBRARIES_DIR is empty. ### fwRenderOgre *Silent deprecated OpenGL warnings on macos.* Resolve "[macos 10.14] OpenGL is deprecated" ### tests *Fix fwServicesTest and fwActivitiesTest.* Fix the broken tests of `fwServicesTest` and `fwActivitiesTest`: - fix the resources path - update the wait macros to test if a service exists. Replace `::fwTools::fwID::getObject(uid)` by `::fwTools::fwID::exist(uid)` because when getObject() is called, an assert is raised: the service pointer expires before it is removed from the ID map. ### CMake *Remove usage of QT_QPA_FONTDIR on Linux.* Usage of **QT_QPA_FONTDIR** is deleted in both build & install scripts since now we build Qt with fontconfig support. ### fwRenderOgre *Move declaration of static const outside the class.* ### Ogre *R2VB objects can't be picked.* This allows quad-based meshes to be picked. Since the quads are triangulated with a geometry shader, it was not that easy to perform. The chosen solution is not very elegant, I added intersection tests on the quads themselves. Testing intersections on the triangulation is harder to perform and on top of that, at the beginning we did not intend to read it from the CPU. ### IDVR *Mesh/countersink interference.* Also brings a few new buttons to the OgreViewer toolbar and fixes the clipping box update. Only works with the Default or the DepthPeeling transparency technique. [Ogre] Fix idvr with mixed rendering ### SPointListRegistration *Test if registered pointList has labels.* Fix SPointListRegistration service when having two pointLists which one has labels and the other one has no labels. Resolve "Fix SPointListRegistration" ### fwDataToolsTest *Glm initialization.* ### CMakeLists *Add a define for aligned storage compatibility with visual 2017.* ### macos *Remove installation script for executable.* Remove installation script for executable for macOS. This script did not work and broke the cmake configuration. It should be rewrite completely. See #131 ### export *Fix export macro in arData and calibrationActivity.* ### cmake *Allow use of relative path for EXTERNAL_LIBRARIES.* Resolve "Cannot use relative path for EXTERNAL_LIBRARIES" ### ogre *Fix relative paths on linux.* material paths are now absolute using predefined variables ### ogre *Add missing makeCurrent() and fix glsl errors.* ### CMakeLists.txt *Fix link errors with vtk libraries.* The PCL discovery was done in the main CMakeLists.txt, thus defining VTK compile definitions for all targets. This led to strange link errors, notably in unit-tests. ## New features: ### conan *Implement a better Conan support, especially on macOS.* - Remove libxml2 and zlib dependencies on macOS - Use DYLD_FALLBACK_LIBRARY_PATH instead of LD_LIBRARY_PATH because it is unsupported in macOS and DYLD_LIBRARY_PATH prevents using ANY system libs. - Remove unneeded activities bundle dependency to gui - Fix application script template generation to use : as path separator instead of ; - Cleanup template script - Add VLC conan package for Windows and macOS, use system for Linux - Add scripts to launch unit tests and utilities with conan packages - bonus: fix ioAtoms and ioITK unit tests (updated object access type) ### doxygen *Generate sight doxygen with Gitlab-CI.* - A new Gitlab-CI job is added to generate and publish Sight Doxygen - Doxygen is built only if sheldon and debug/release build pass - Doxygen is only deployed on the dev branch - Sight doxygen is available on https://sight.pages.ircad.fr/sight ### tutoQml *Use qml and c++ for tuto 08 and 09.* New version of the Tutorial 08 and 09 using qml and c++ (without XML configuration). - fwVTKQml:new library to allow to display a VTK scene in qml - visuVTKQml:new bundle to initialize qml FrameBuffer - uiMedDataQml, uiImageQml, uiReconstructionQml: new bundles containing services inherited from IQmlService and the associated qml files. ### tutoQml *Add basic samples using qml interfaces.* Add new samples to use Qml: - `Tuto01BasicQml`: same as Tuto01Basic but using qml instead of fwGui frame and xml - `TutoGuiQml`: same as TutoGui but using qml instead of fwGui frame and actions - `TutoEditorQml`: sample to explain how to use a IQmlEditor Add a new library `fwQml` that contains helpers to use qml: - IQmlEditor: base class for f4s service associated to qml UI - QmlEngine: to launch qml ui ### tutoCpp *Convert tuto 08 and 09 to use C++.* The new Samples `Tuto08GenericSceneCpp` and `Tuto09MesherWithGenericSceneCpp` are equivalent to the existing samples (same name without 'Cpp') but they doesn't use a XML configuration. All the services are managed in C++ in the application `Plugin` class. The services' configurations are written with `::boost::property::tree`. To achieve this, a new helper : ``::fwServices::AppManager``was added. It simplifies the management of objects/service/connections in an application. ### CMake *Use Conan to build Sight (experimental).* Add conan support in sight: - A temporary conan registry is used for the moment: [Artifactory](http://5.39.78.163:8081/artifactory) - All conan package are available on [gitlab Conan group](https://gitlab.lan.local/conan/) - A new advanced CMake option is available: `USE_CONAN` ### trackingHandEyeActivity *Load and evaluate calibration matrices.* Adds the possibility to load the hand-eye X matrix for evaluation. Once loaded the Z matrix is computed using the current tracked position and camera pose. The camera can than be moved around and the app will display the reprojection and reprojection error. Evaluate external calibrations in the hand-eye activity. ### SLabeledPointList *Implement updateVisibility slot.* - to update visibility of SLabeledPointList, we have to implement updateVisibility slot into SPoint, SPointLabel, SPointList Resolve "Implement updateVisibility slot in SLabeledPointList" ### PointList *Add pointlist management methods.* * Add a slot to clear the pointlist in SMarkerToPoint * Add a slot to manage the visibility of the pointlist in the adaptor SPointList3D ### SRenderStats *Display rendering stats such as FPS in the OGRE overlay.* - Adds a new adaptor to display FPS and triangle count in ogre windows. - Fixes a crash when setting the volume visibility when no image has been loaded. - Removes the option to print stats in a console. ### sslotcaller *Add wait option for synchronized slot calling.* Add a wait option to SSlotCaller that is used to call the slots synchronously. This allows for dependent slots to be called in the right order. ### 21-integration-of-sopticalflow-from-internship-repository *Into dev.* Add a new service that performs optical flow on a video to detect if camera is moving or not. Use the service in a new example called ExDetectCamMotion. SOpticalFlow is designed to only detect if camera is moving not if something happend on video. It can send 2 signals: cameraMoved if motion and cameraRemained if camera is stable. This can be usefull to trigger event when motion or remaining phases are detected, ex: only add calibration image if camera is not moving. ### 124-sort-example-by-topics-and-remove-numbering *Into dev.* Remove numbering of Samples, and add topics folders. Resolve "Sort example by topics and remove numbering" ### CMake *Introduce SDK mode.* After many years, we propose a real build of Sight as a SDK. This allows to: - create a binary package containing Sight binaries, libraries, includes and resources, - include/link a Sight library in a non-Sight application/library, - create a new Sight library, bundle or application outside Sight source and build tree ### .gitignore *Ignore vs2017 cmake project files.* ## Refactor: ### * *Remove "fw4spl" references.* Resolve "Remove all references to "fw4spl"" ### ITransformable *Node managements is now handle by ITransformable.* * Factorise some functions to have a common method into ITransformable. * Set the parent transformation only in STransform (else, two adaptors with the same transform can have two different parents). * Fix the stopping method of SAxis. ### ioIGTL *Modify INetwork and STDataListener to manage timestamps.* - Add receiveObject function in INetwork to return the message timestamp - Add optional timestamp parameter in manageTimeline from STDataListener ### deprecated *Remove 'FW_DEPRECATED' related to 18.0.* Remove the deprecated code associated to the macros `FW_DEPRECATED_xxx(..., "18.0)`. ### MedicalImage *Split to MedicalImage and TransferFunction.* Splits `::fwDataTools::helper::MedicalImageAdaptor`to `::fwDataTools::helper::MedicalImage`and ::fwDataTools::helper::TransferFunction Most of the adaptors used the half of the `::fwDataTools::helper::MedicalImageAdaptor`: one part to manage an fwData`::Image`and the other one to manage fwData::TransferFunction. Resolve "Split `MedicalImageAdaptor` into `MedicalImage` and `TransferFunction`" ### getObjSrvConnections *Replace getObjSrvConnections() by getAutoConnections().* Resolve getObjSrvConnections() errors by implementing getAutoConnection() method. ### fwCore::HiResClock *Use std::chrono inside and deprecate in favor of std::chrono.* ### ogre *Refactor volume ray entries compositors.* - Removes the volume ray entry compositor scripts and generates them instead. It will be a lot easier to add stereo IDVR now. - Moves all programs/materials/compositors used in fwRenderOgre to it. * **:warning: IMPORTANT** Delete share/fwRenderOgre and share/material in your build directory ### cmake *Cmake cleaning.* cleans sight CMake scripts: - removed unused platform - removed racy backward compatibility ### * *Reorganise some folders.* After the big merge with fw4spl-ar, fw4spl-ogre and fw4spl-ext, it was necessary to clean up a bit the folders hierarchy, notably to group related items together. No project is renamed, the projects were only moved or their parent directory was renamed. See sight!2 ## Documentation: ### README.md *Remove all mentions of 'fw4spl'.* # fw4spl 17.2.0 ## New features: ### calDataGenerator *Add an utility program to generate stereo pair of chessboard/charucoboard images.* This can be useful to test calibration algorithms. ### handEyeActivity *Handling a step value in SMatricesReader and SFrameGrabber.* Add a step value in readNext()/readPrevious() slots in SMatricesReader and SFrameGrabber when configured on oneShot mode. This step value can be changed calling a setStep slot, connected with an int SParameter withstep` key. We also needed to add this setStep slot in the SGrabberProxy and IGrabber in order to call it properly when using a SGrabberProxy instead of a SFrameGrabber directly. ### Ogre *Update ogre to 1.11.* This brings a bunch of fixes following API changes. Among them : * default light direction is set to the camera's view direction, was implicitly the case in ogre 1.10 but changed in 1.11 * `Codec_FreeImage` plugin loaded to support common image file formats * plugin config parsing was modified to be able to load multiple plugins * `::Ogre::Affine3` replaces `::Ogre::Matrix4` when we need to decompose a matrix * colour masks are enabled when computing volume ray entry points ## Refactor: ### ut *Replace deprecated methods to register a service.* Replace the `::OSR::registerService(obj, srv)` by `srv->registerInOut(obj, key)` in the unit tests. ### cmake *Remove racy backward compatibility.* ### registerService *Replace deprecated methods to register a service.* Replace the `::OSR::registerService(obj, srv)` by `srv->registerInOut(obj, key)` and use `::fwServices::add(srv)` helper instead of calling directly `Factory ### SVolumeRender *Store clipping matrices the same way VTK does.* Now clipping box transforms are stored in world space instead of texture space. Clipping transforms can be passed from/to VTK that way. Removes the **broken** slice based volume renderer. ### plugins *Remove the freeimage plugin.* ### textures *Convert all png and tga textures to dds.* DDS is supported natively by ogre without the freeimage plugin. The freeimage BinPkg is awful to maintain and should be considered as deprecated from now on. ## Bug fixes: ### glm *Add missing GLM_ENABLE_EXPERIMENTAL define.* Unused glm extensions have been removed ### fwRuntime *Fix memory leaks.* Fix leaks in fwRuntime ### SMaterial *Texture rendering on other formats than 8 bits.* ### IHasServices *Add wait() when stopping services.* - add wait() when stopping services in unregisterService and unregisterServices methods. ### SGrabberProxy *Include/exclude mode wasn't working as expected.* - Improve include/exclude filtering. We can include/exclude a specific service or a specific configuration of a service or both. - Grabbers are now always displayed in same order in selector dialog. - Frame by Frame mode from `::videoOpenCV::SFrameGrabber`has been excluded from calibration ### registrationActivity *Fix all errors in the registration app.* - fix all log errors - remove useless autoConnects # fw4spl 17.1.0 ## Refactor: ### fwServicesTest *Clean unit test and deprecate unused methods.* Add `FW_DEPRECATED` macro for: - swapService(obj, srv) - registerService(obj, service) - getServices(obj) - getServices(obj, type) - fwServices::add(obj, srvType, srvImpl) - fwServices::get(obj) Replace the deprecated methods in the tests by the new ones. Replace C++ configuration by XML file for the tests of AppConfigTest. Keep a few tests on the deprecated methods until the methods are officially removed. ### VRRender *Remove the deprecated logs.* Clean the configurations to remove the deprecated logs: - remove the useless objects and services - use the right key - remove useless autoConnect Update appConfig.xsd to set 'uid' attribute as required for services. Add missing 'getAutoConnections()' in some services from visuVTKAdaptor. ### tutorials *Remove the deprecated logs.* Remove the deprecated log: - clean configurations - remove useless autoConnect - use the right keys in services - remove useless services and object - add getAutoConnections() methods in uiReconstructionQt to replace the default deprecated getObjSrvConnections() from IService - use the new API to register the reader/writer in ::uiTF::TransfertFunctionEditor - remove auto-connection on 'tf' in the vtk adaptors when registering a sub-service ### ObjectService *Support optional output in services.* - if the output is not defined in the XML configuration, the object is not emitted to the configuration. - add a method in IService to check if the object is defined: hasObjectId() - the method getObjectId() throw an exception as described in the doxygen ### SWriter *Set 'data' as input instead of inout.* Set 'data' as input in '::ioAtoms::SWriter' - update RecursiveLock visitor to use const object - add constCast in SWriter before the conversion to atoms ### deprecated *Remove deprecated getObject() in services.* Replace 'getObject()' by 'getInput()' or 'getInout()' and add a deprecated log if the input key is not correct. ### getObject *Remove deprecated getObject().* Replace deprecated `getObject()` by `getInout()` in `::uiCalibration::SIntrinsicEdition` Depreciate some bundles and services: - bundles: ioZMQ, uiZMQ, uiNetwork - services: SProbeMesh and SProbePosition from echoEdSimu ### getObject *Replace the last 'getObject()' by 'getInOut()'.* Replace the last two getObject() by getInout(). They were forgotten in service that already use getInout() for these data, so the deprecated log is useless. ## New features: ### VRRender *Add activity to upload DICOM series via DicomWeb protocol.* New activity that anonymizes and uploads DICOM series onto an Orthanc PACS. ### proxyConnection *Catch exception when the connection failed.* Catch the exception raised when a connection failed between signals/slots defined in the configuration. It displays a error log with the signal/slot information. ### cvIO *Add new conversion function between cv::Mat and f4s matrices.* Convert from/to `::cv::Mat`to ::fwData::TransformationMatrix3D Convert from/to `::cv::Mat`rvec & tvec convention to ::fwData::TransformationMatrix3D Add new unit tests cases. Refactor Calibration code to use new helpers. ### trackedCameraCalibration *Merge activities.* Fuse sense specific activity and rgb activity thanks to SGrabberProxy. ### video *Import VLC, Orbbec and RealSense grabbers.* VLC, Orbbec and RealSense grabbers code is now open and imported into fw4pl-ar, as well as the video filtering. The VLC grabber is convenient especially for RTSP streams. It may also be used as a fallback when the QtMultimedia grabber fails... The Orbbec grabber works for Astra camera and the RealSense brings support for cameras based on Intel sensors. ### fwRenderOgre *Add a helper to convert pixel to view space position.* The function `convertPixelToViewSpace` translates a pixel coordinates to view space coordinates. ### SNegato2D,3D *Use the transparency of the transfer function (optionally).* A new option was added to use the transparency of the transfer function. ### SAxis *Add a label configurable option.* SAxis now has an option `label` that can be set to `true` or `false` to display or hide the axis labels (`true` by default). ### SRender *Add a 'sync' renderMode.* In the following of our recent rework of the synchronization for real-time augmented-reality, this new mode allows to make the Ogre generic scene compatible with the approach. The example ExSimpleARCVOgre was reworked to use the new sync mechanism and proves that this works. ## Documentation: ### eigenTools *Document helper namespace.* ### visuOgreAdaptor *Update some documentation.* The documentation of several adaptors were fixed. ## Bug fixes: ### cmake *Update wildcard to search all external libraries.* Before only .so.* was found. ### pchServicesOmp *Remove clang specific hack about OpenMP.* Remove a clang specific OpenMP hack in our CMake code. ### fwDataCamp *Fix compilation.* Add a missing header in fwDataCamp (Build without PCH) ### plugin_config_command *Support 0 in service or bundle names.* Fix the regex used to generate the service definition in plugin to support zero. ### docset *Fix broken docset generation.* ### boost *Add support of Boost 1.67 on Windows.* Boost >= 1.67 changes the default random provider on Windows to use BCrypt. So a link to system library bcrypt is now required to use Boost::UUID. The changes are compatible with old Boost version. ### ARCalibration *Remove warnings by using seriesDB key instead of series.* Fix series keys to seriesDB used in various configurations because it will be removed in 18.0 version of FW4SPL. ### activitySelector *Remove warnings by using seriesDB key instead of series.* Fix series keys to seriesDB used in various configurations because it will be removed in 18.0 version of FW4SPL. ### beginnerTraining *Fix the training samples.* - fix the documentation for the plugin.xml generation - remove the fwServicesRegisterMacro from the services to let cmake to generate the right one - add getAutoConnections() for tuto03 and tuto04 SStringEditor ### fwRenderOgre *Correct valgrind errors and leaks.* Memory errors were fixed and memory leaks detected by valgrind (memcheck) on the test suite: * One out of bounds in read `fwRenderOgre::helper::Mesh` * Memory leaks on Ogre root destruction ### fwRenderOgre *Missing headers.* ### fwRenderOgre *Remove clang specific hack about OpenMP.* Remove a clang specific OpenMP hack in our CMake code. ### R2VBRenderable *Clear vertex declaration before filling it.* This caused the varying to be duplicated, and thus the program link to fail. ### Mesh *Generate normals each time the mesh is modified.* For triangle based meshes, when we don't have normals, we generate them. The problem was that it was only done on the first update of the mesh. If points were added to the mesh, the corresponding normals were not computed accordingly, thus the normal layer ended to be be shorter than the position layer. This led eventually to crash at some point... # fw4spl 17.0.0 ## Bug fixes: ### VRRender *Do not crash when clicking on the distance button in VR.* The service `::uiMeasurement::editor::Distance` was also cleaned a bit, and the unused configuration option 'placeInScene' was removed. ### docset *Generation on case sensitive systems.* ### SMesh *Lock the input mesh properly in slots.* ### SPoseFrom2d *Trigger modified signal even if nothing is detected.* To keep the processing pipeline updated, we need to keep to trigger the modified signal anytime, like in SArucoTracker. ### videoQt/editor *Properly handle button actions on choose device.* - Use accept() and reject QtDialog slots instead of our own onValidate() and generic close() - In SCamera, check the result of exec dialog window to check if it's canceled and don't continue to configure the camera if so. ### Mesh *Do not compute normals with point based meshes.* We are not supposed to compute normals when displaying a point based mesh only, however the condition testing this was wrong in the code. ### fwRenderOgre *Missing headers.* ### material *Ensure Common.{program,materials} are parsed first.* Depending on your file system, the `Common.program` could be parsed after the `Video.program`, causing it to fail because it needs `TransferFunction_FP`, which lies inside Common.program to be declared first. ### IDVR *Compute the countersink geometry in world space.* We changed the way the MImP IDVR countersink geometry (CSG) is defined/computed: * CSG used to have a fixed viewport radius, it now has a fixed angle and isn't resized when zooming with the camera. * Depth lines now start at the importance zone and are in the same unit as the image's spacing. * The CSG border had to be removed because we couldn't easily adapt it to this new method :crying_cat_face: * Greyscale CSG and modulation are now separate. ### SMesh *Build error with GCC 5.4.0.* ### SAxis *Make the visibility changeable and fix adaptor stop.* ### RayTracingVolumeRenderer *Do not delay the resize of the viewport.* Delaying the resize of the entry points textures broke the auto-stereoscopic rendering. This was introduced recently in 6e2946 but was actually not necessary and did not fix anything. ## New features: ### uiPreferences *Handle floating value in preferences.* SPreferencesConfiguration only handles integer values. - number configuration element is now deprecated it has to be replaced by int - add double configuration element to handle float/double type (min: -1000000.0 max:1000000.0, decimals: 6) ### SSignalShortcut *Create new service to handle shortcuts.* A new SSignalShortcut service in fw4spl has been added. This service allows to map keys or combination of keys to the trigger of a signal. ### dicom *Add dicom_reference in Image and Model Series.* The purpose of this commit is to keep DICOM tags into fw4spl data and use them to create back valid DICOM to save image and/or models. - Added new example ExDicomSegmentation to generate a ImageSeries mask and a ModelSeries - Removed `::boost::filesystem::path`in DicomSeries - Added BufferObject in DicomSeries to store Dicom data - Updated gdcm/dcmtk reader/writer and unit tests - Updated `::opImageFilter::SThreshold`to `::fwServices::IOperator`(used in ExDicomSegmentation) - Updated `::opVTKMesh::SVTKMesher`to `::fwServices::IOperator`(used in ExDicomSegmentation) - Added dicom_reference in ModelSeries and ImageSeries - Added new MedicalData version V11 ### Calibration *Add charuco calibration.* Add ChArUco board calibration in ARCalibration: * New Bundle with services related to Charuco calibration * Brand new utility to generate charuco board. * New activity in ARCalibration * ARCalibration has been updated to version 0.5 * Both standard calibration and charuco calibration displays now reprojection error when calibration (intrinsic and extrinsic) is performed. * videoCalibration Bundles were moved from video folder to calibration folder. ### SPoseFrom2D *Add a points list data containing the corners of the marker model.* SPoseFrom2D now provides an inout data that can be used to retrieve the 3D geometry of the marker model. A mistake was also corrected in hand eye calibration, that called `SOpenCVIntrinsic` instead of `SSolvePnP`. In that case, the camera calibration was overwritten by the first service. Now, it just finds the pose of a chessboard model in the camera only, without calling the camera calibration service (what we really want to). ### MedicalData *Update fw4spl-ar data to V13AR.* - This commit adds a new data version V13AR for AR data - This new V13AR is require to manage new ModelSeries & ImageSeries with Dicom reference (fw4spl!259) ### SLandmarks *Add adaptor to display landmarks.* The new adaptor SLandmarks displays landmarks with Ogre generic scene. ### SLine *Allow length update via a slot.* A `updateLength()` slot was implemented to update of the length of the rendered line. ## Refactor: ### ioAtoms *Find the correct version without an XML parameter.* Improve `::ioAtoms::SReader`and `::ioAtoms::SReader`to find the correct data version without setting an XML parameter, only the 'patcher' tag is required to use the patch system. When no version is defined in SReader and SWriter, the current version of MedicalData defined in fwMDSemanticPatch is used. This version can be overridden by the new method 'setCurrentVersion'. You can still define your own version and context. ### CMakeLists.txt *Add discovery of additional repositories.* Setting the CMake variable ADDITIONAL_PROJECTS was tedious and error-prone. Now we explore the folders at the same level of FW4SPL to find extra repositories. Then a CMake option, set to ON by default, is proposed to enable/disable the repository. This will make CMake configuration phase easier than ever ! ### deprecated *Replace getObject by getInput or getInOut.* - Replace deprecated `getObject()` by `getInput()` or `getInOut()` - Add deprecated log if the key is not correct in the configuration. - Set the services `ExternalDataReaderService`, `SInitNewSeries` and `SSeries` as deprecated - Improve the `FW_DEPRECATED` macros to display the version where the support will be discontinued - Add a new macro `FW_DEPRECATED_KEY(key, access, version)` to define the correct 'in/inout' key. All XML configurations have not been updated, so expect to see more [deprecated] mentions in the log. Please fix your application as required. ### SSeriesDBMerger *Replace getObject.* ### ioAtoms *Find the correct version without an XML parameter.* Override the current version of fwMDSemanticPatch to use the AR version. Clean the useless IO configuration. ### Synchronization *Improve the synchronization for augmented-reality.* We reworked the way we synchronize the video frames and the extracted data in real-time. So far, we have made an extensive use of timelines. First the video grabbers store the frames in timelines. Then we process some algorithms on them and we store all the extracted data (markers, transforms, etc...) in timelines as well. At the end, we rely on ``::videoTools::SFrameMatrixSynchronizer``to pick frames, matrices, etc... at the same timestamp and give these synchronized objects to the renderer, i.e. the generic scene. However this does not work well. First this is very tedious to work with timelines, since we need to create a dedicated C++ class for each kind of data we want to manage. For some big data, like meshes, we never did it because this would consume too much memory. And some services are simply not well coded and work directly on the data instead of timelines, etc... Eventually, the renderer even screws up the synchronization since all updated objects request the rendering to be done. So we propose a different approach here. First we restrict the usage of timelines to synchronize video grabbers together, for instance when you use a camera with multiple sensors or simply several cameras. After that point, all algorithm process the data directly. A new data ``::arData::MarkerMap``is introduced to store a list of markers, since this was the only "data" that only existed in a "timeline" version. To synchronize the results of the algorithms, we propose a new service called `SSignalGate`. This service waits for several signals to be triggered before sending itself a signal, which indicates everyone before is done. This service is typically used to inform the renderer that it must send everything to the GPU right now. To achieve this, we introduced a new rendering mode in `::fwRenderVTK::SRender`. You can try Ex04SimpleARCV which uses the new design, but for now everything is backward compatible. But we strongly encourage to have a look at this very soon and try to port your application to benefit of this improvement. ### SExportWithSeriesDB *Remove getObject.* ### MesherActivity *Refactor CGogn and VTK mesher.* - This commit removed dependency to bundle opVTKMesh in opPOCMesher - CGoGNMesher is now a standard ::fwServices::IOperator - Updated MesherActivity config with new VTK/CGoGN mesher API Refactor MesherActivity ### Interactors *Allow adaptors to be an interactor implementation.* This is a first step in the refactor of interactors. We plan to implement interactors directly instead of using the only one SInteractorStyle that instantiates sub-classes. It is actually more complicated than if the interactor does the job directly. In ARPerfusion, we had to create a new interactor to select regions (ARPerfusion!10). We wanted to implement it as an adaptor, which allows us to test if the design works. So we modify the inheritance to allow adaptors to behave as an interactor directly. Consider this as a temporary step in the migration of interactors, where both solutions are possible. Besides this, there are some changes that might seem unrelated but they were necessary for our new interactor. There is a first fix to allow all kind of meshes to be displayed with a SPointList adaptor. Then there is a second commit to fix the cell color textures, which were not correctly fetched from the texture used to store them. sight-25.1.0/CHANGELOG.txt000066400000000000000000001244611503402212300147450ustar00rootroot00000000000000=== fw4spl_0.9.2.3 ( diff from fw4spl_0.9.2.2 ) 10/09/2014 === * Transplanted from fw4spl_0.10.0: * Removed all services working on deprecated data PatientDB * Removed ARLcore dependency in uiMeasurement : load/save landmarks with ioAtoms * Moved Examples, POC and Training to fw4spl-ext repository * Fixed AppConfig::getUniqueIdentifier() * Fixed wrong warning in PushObjectSrv. * Fixed gui doxygen (menuBar and toolBar) * Removed useless notify in TransferFunctionEditor::initTransferFunctions() * General: * Updated fwAtomsPatch and spyLogger according to Boost 1.54 * Removed author field in doxygen * Fixed Mac OS issue with Qt (QTBUG-32789) * Fixed ProfileRunner for Mac OS * Fixed guiQt, SliceCursor and WindowLevel memory leak * Moved gdcmIO, ioGdcm and ioGdcmQt to FW4SPL-ext * Added CMakeLists * Removed useless SelectedNodeIOUpdater service * Core: * Added getConfigDesc in fwServices * Fixed timer asio one shot * Changed SigSevBacktrace log level to ERROR * Fixed PushObjectSrv * Added 'launched' signal to ConfigActionSrv and SConfigLauncher * Added 'updated' signal on Graph and Node * Optimized Graph route * Added SSignal service * Updated template arguments to manage five parameters in fwCom * Fixed fwRuntime compilation on Linux * Added support of ! operator in seshat path (returns a string pointed by the seshat path if possible, the uid otherwise) * Allowed empty string in XML default value. * Memory dump system * Set default streamFactory when buffer's streamFactory was never explicitly set * Fixed buffer lock and buffer streams * Fixed an issue due to some implementations returning a non-NULL pointer for malloc(0) in buffer allocation * Fixed BufferManager and StructureTraits binding * Added getTemporaryFolder in System * IO: * Fixed fwData::Array initialization in vtkGdcmIO/fwVtkIO SeriesDB reader * Fixed zipped json/xml loading * Updated ioAtoms to be able to overwrite a lazy-loaded file * Added custom extensions ability in ioAtoms * Fixed atom custom extension when using JSON/XML backends * UI: * Added a way to configure button state * Fixed relative URLs in uiGenericQt::action::LaunchBrowserActionService * Updated IOSelector to display config's description if applicable * Fixed compilation of DataInfoFromMsgUpdaterSrv * Updated SExportSeries to use an existing activity description or physician name * Added ability to pass parameters in config * Updated Qt MessageDialog to manage a default button * Added several editors and services to propose a new export config * Set SShowAbout to modal * Tools: * Added fwDataTools::TransformationMatrix3D to manipulate ::fwData::TransformationMatrix3D * Removed useless dimension check for medical images * Added lib fwMedDataTools to manage fwMedData UID * Visu: * Added autoRender option in genericScene * Added ability of hidding cropping box by default in visuVTKVRAdaptor::Volume * Added locks in VTK adaptors * Updated VTK text adaptor to manage Seshat path, text alignment and font size * Added patient name in all generic scene. * Patch system: * Fixed fatal when visiting enum object in ::fwDataCamp::visitor::getObject class. * Added medical workspace filtering for MedicalData V2 context * Added ImageSeries and ActivitySeries to fwStructuralPatch * Added resection patching in MedicalData V2 context * Activities: * Added activity validator concept to fwActivities * Added image properties validator * Added new option for SActivityLauncher to define a default association * Made activity launch cancelable * Added a new validator for authorizing activity launching with several series from the same study * Added seshat path ability in activity launcher * UT: * Fixed several compilation issues (b4be6561e715 and 20ff6c2d0cda) * Fixed working directory * Software: * Updated VRRender activities: * 3DVisualisationActivity * blendActivity * volumeRenderingActivity === fw4spl_0.9.2.2 ( diff from fw4spl_0.9.2.1 ) 20/06/2013 === * Transplanted from fw4spl_0.9.1.4.: * Added a getAttribute() template method on fwAtoms/Object. * fixed on fwAtoms/Object attributes and metaInfos * Added version number on atom (see fwAtoms/Base.hpp) * Updated fwCore::SpyLogger to remove logger global severity. * Updated CompareObjects visitor for BufferObject * Created a new library fwAtomsPatch which contains the base interface to transform atoms from a version to an other into a context. * Added a version generator utility to generate .versions files (files used by the patch system). * Added MedicalData patch system to VRRender * Updated ioAtoms reader(SReader)/writer(SWriter) to use the patch system. * Added version on Atom writers/readers(Hdf5 reader/writer and BoostIO writer/reader). * Added test for getAttribute() template method. * General : * Cleaned fwZip: removed old and deprecated class/API, group minizip classes. * 'MediacalData' object version change to V2 * Updated fwData::Reconstruction to version 2 (some attributes have been removed). * Fixed duplicated extension filename in objWriter. * Added atoms_version and writer_version in TF files (json files) * Patch system : * Create patch to transform atoms object into 'MediacalData' context from version V1 to version V2. * V1 uses the fwData PatientDB, Acquisition, etc. * V2 uses the new Data fwMedData ( ModelSeries, ImageSeries, ...) * Added fwStructuralPatch library which contains structural patches. * Added fwMDContextualPatch library which contains semantic patches. * Added patchMedicalData bundle. * Updated VRRender config to use MedicalData patch system. * Added unit test for fwStructuralPatch * Memory dump system * BufferManager, BufferObject and depending classes has been moved to fwMemory library. BufferObject is not self-sufficient anymore : (Re)Allocation/destruction has been delegated to BufferManager. * Updated BufferManager and BufferObject API to allow “lazy loading” and to improve dumped data access : * A BufferObject may be initialised with a ‘stream factory’, allowing to load data in ‘dumped’ or ‘lazy loaded’ state, i.e. the data will be really loaded when needed (for example when the BufferObject is locked) * When a buffer has been lazy-loaded or dumped by the buffer manager, an internal structure stores information about the dumped state : the filesystem path of the data (if applicable), and the format of this file (if applicable). This can be useful to reuse filesystem data if needed. For example, fwAtomsBoostIO’s writer creates hardlinks to these files when possible. * If a BufferObject user need an access to the data, it is possible to get a stream to it, regardless of and without changing the buffer state. For example, fwAtomsBoostIO’s Writer use this mechanism to copy data from buffers when a BufferObject is not dumped or the dumped file do not have a compatible format for hardlinking. * fwMemory has been made thread-safe : * Buffer management runs in a dedicated thread * BufferManager has an asynchronous API, based on boost::shared_future * Naive factories has been updated to fwCore's one * IO * Added DICOM LazyReader in vtkGdcmIO. * Added VTK/VTI Lazy Image Reader in fwVtkIO. * Added lazy ability to json(z)/xml(z) data Reader in fwAtomsBoostIO. * fwAtomsBoostIO : fixed archive buffer filename and archive's buffers dir name * A new name for each buffer is generated in order to avoid to have several buffer using the same file name. * The postfix -json or -xml has been appended to buffers dir name, to avoid data overwrite when saving to archive in the same place with the same name but with a different base format. * UI * Added an example of asynchronous UI in DumpEditor using QFutureWatcher === fw4spl_0.9.2.1 ( diff from fw4spl_0.9.2.0 ) 23/05/2013 === * Merging * Merge from tag fw4spl_0.9.1.2 * Data : * Removed PatientDB/Patient/Study/Acquisition/Dictionary/DictionaryOrgan data. Removed Acquisition/Patient/PatientDB msgs. * Cleaned fwData::Reconstriction attributes * NewSptr() is depracted, use now only New() * Fixed deep copy bugs (multiple references to the same data, recursive data) and updated all data * Fixed fwMedDataCamp camp introspection for ImageSeries and Series * Moved ActivitySeries camp binding (from fwActivitiesCamp to fwMedDataCamp) * few changes in fwAtoms API : add factory, renamed Map and Sequence const iterators and added typedef, fixed all clone methods * IO : * Removed fwXML and ioXML * Added ImageSeries and ModelSeries writers/readers in vtkIO or itkIO : * read : .vtk, .vti, .mhd, .inr * write : .vtk, .vti, .mhd, .obj, .inr, .jpg * Updated ioGdcm to support fwMedData ( and fix few old problems ) * Updated fwAtomConversion to support several UUID management policies : when you convert an atom to a data, if the uuid object already exists in application, you can abort the process, you can re use the object or you can generate another uuid * Replace specific atoms reader/writer. Added ioAtoms SReader and SWriter services ( these services work on ::fwData::Object and support 'inject' mode). Removed SMedDataReader and SMedDataWriter services. * Fixed fwAtomsBoostIO issue when hiting a cache value stored in a Atoms::Map, added unit tests * General : * Updated fwAtomConversion: getSubObject() throw exceptions or null object if path is not valid ( data introspection ) * Added macro FW_FORWARD_EXCEPTION_IF(ex, cond) * Added ConfigLauncher helper to centralize management of AppConfig * Added new service SConfigLauncher, updated SConfigController * Updated SActivityLauncher to be able to launch directly a standalone activity. * Software : * Update all Tutorial, all Examples and all PoC to support last modifications * Update VRRender : * support last modifications * enable some new readers/writers * add dump functionalities and menus to monitor the app * Updated TransferFunctionEditor to save/load TF with ioAtoms. * TU : * Update io unit tests to check new readers/writers * Added fwData/fwMedData objects generators in fwTest * Updated fwDataTools : removed data generators and comparator replace by fwTest generators and fwDatacam::visitor::CompareObject * Updated fwTest::generator::SeriesDB to be DICOM compliant. === fw4spl_0.9.1.4 ( diff from fw4spl_0.9.1.3 ) 18/06/2013 === * General : * Added a getAttribute() template method on fwAtoms/Object. * fixed on fwAtoms/Object attributes and metaInfos * Added version number on atom (see fwAtoms/Base.hpp) * Updated fwCore::SpyLogger to remove logger global severity. * Updated CompareObjects visitor for BufferObject * Patch system : * Created a new library fwAtomsPatch which contains the base interface to transform atoms from a version to an other into a context. * Added a version generator utility to generate .versions files (files used by the patch system). * Added MedicalData patch system to VRRender * IO : * Updated ioAtoms reader(SReader)/writer(SWriter) to use the patch system. * Added version on Atom writers/readers(Hdf5 reader/writer and BoostIO writer/reader). * TU : * Added test for getAttribute() template method. === fw4spl_0.9.1.3 ( diff from fw4spl_0.9.1.2 ) 02/05/2013 === * Data : * Fixed deep copy bugs (multiple references to the same data, recursive data) and updated all data * IO : * Updated fwAtomConversion to support several UUID management policies: when you convert an atom to a data, if the uuid object already exists in application, you can abort the process, you can re use the object or you can generate another uuid * Replace specific atoms reader/writer. Added ioAtoms SReader and SWriter services ( these services work on ::fwData::Object and support 'inject' mode). Removed SMedDataReader and SMedDataWriter services. * Fixed fwAtomsBoostIO issue when hiting a cache value stored in a Atoms::Map, added unit tests === fw4spl_0.9.1.2 ( diff from fw4spl_0.9.1.1 ) 10/04/2013 === * General : * Fixed bugs in ConfigActionSrv * Added macro FW_FORWARD_EXCEPTION_IF to forward an exception on certain conditions * Added new bundles (monitor, monitorQt) to monitor system * Added new bundle (ctrlMemory) to provide services to dump lock data * Added new service (uiPatientDB::action::AddMedicalWorkspace) to load and merge medical workspaces * Updated VRRender 0.9.5 : add dump functionality, menus to monitor the app, import/export medical workspace * Data introspection : * Moved ::fwAtomConversion::RetrieveObjectVisitor to ::fwDataCamp::visitor::GetObject * Updated fwDataCamp::Object to bind 'isA' method * Added new visitor (RecursiveLock) to lock (mutex) data recursively * Added new visitor (CompareObjects) to compare two data objects * Added exception management and unit tests * Atoms : * Refactoring of fwAtoms : * cleaning, bug fixing, API updating (added const accessors), unit tests * added factory to build atoms * fixed all clone methods * updated atom visitor to not use camp to visit atom * Refactoring of fwAtomConversion : * removed useless visitors * proposed new conversion methods between fwData::Object and fwAtoms::Object * fixed graph conversion * added exception management and unit tests * managed null pointer in atoms * improved numeric conversion * fwData : * Removed deprecated fwData::None * Added tetra cell type for fwData::Mesh * Managed acquisition 'NetID' in deepCopy/shallowCopy * IO : * Added new library fwAtomsBoostIO to read/write atoms using Boost Property Tree. * Added new bundle ioAtoms to provide services for reading/writing medical data using fwAtomsBoostIO. * Managed zip and folder archive in fwZip * Added BufferObject comparison unit test in fwAtomsBoostIO * Managed dumped buffers during saving * Changed dump policy during medical data loading (set to 'barrier' if policy is 'never'). * Added unit tests for fwAtomsBoostIO and ioAtoms * Log : * Improved logs for 'receive' method in ::fwServices::IService * Communication : * Removed deprecated MODIFIED_KEYS event in fwComEd::CompositeMsg === fw4spl_0.9.2.0 from begin of the branch 12/02/2013 === * Data: * Few fwData class are deprecated : PatientDB, Patient, Study, Acquisition * Created a new library fwMedData to store structures for medical data. * Added structures to store Patient, Study, Series and Equipment information. * Added a type of data called Series ::fwMedData::Series. It aggregates information (Patient, Study, Series, Equipment, etc) related to a data set. * Added ImageSeries data (inherit from Series) which stores an image. * Added ModelSeries data (inherit from Series) which stores a set of reconstructions. * Added structure to store a set of ::fwMedData::SeriesDB series. It provides basic STL container API. * All new fwMedData data structures are wrapped with camp. * Added Tetra cell type management for fwData::Mesh ( update vtk adaptor to show it ) * Removed deprecated fwData::None * Core: * Added the concept of activity (fwActivities library). * Added new factory for fwActivities. * Added a default builder for the data ActivitySeries. * Bundle: * Created a bundle 'activity'. It contains * a service ::activity::SLauncherActivity to launch an activity. * Created a new bundle 'uiMedDataQt'. It contains * a service ::uiMedDataQt::SSelctor to show information about medical data. * a service ::uiMedDataQt::SExportSeries to export series to an ::fwMedData::ActivitySeries defined in the service configuration. * a service ::uiMedDataQt::SSeriesViewer to view series stored in a vector ::fwData::Vector. * Created a new bundle 'LeafActivity'. It contains * a configuration which allows to visualize medical image in 2D. * a configuration which allows to visualize a mesh and optionally a medical image in 3D. * a configuration which allows to blend two medical images (its also contains a transfert function editor). * a configuration which allows to visualize a volume rendering of image and optionally a mesh. * Added new service ::scene2D::processing::SComputeHistogram to compute histogram for an image. * Communication: * Removed deprecated MODIFIED_KEYS from fwComEd::CompositeMsg event (and support few libraries which still used it) * Added messages SeriesDBMsg, VectorMsg, ModelSeriesMsg on fwComEd library. * Added helpers for SeriesDBMsg, VectorMsg on fwComEd. * IO: * Added a reader ::vtkGdcmIO::SeriesDBReader, based on VTK and GDCM, for loading a SeriesDB from DICOM files. * Added a reader ::vtkIO::SeriesDBReader, based on VTK, for loading a SeriesDB from VTK polydata or image'.vtk' files. * Added a service ::uiIO::action::SSeriesDBMerger to read SeriesDB and merges it with the current SeriesDB. * Software: * VRRender 0.9.6. * Tests: * Added unit tests for fwActivities and fwMedData libraries. * Added unit tests for ::scene2D::processing::SComputeHistogram service. === fw4spl_0.9.1.1 ( diff from fw4spl_0.9.1.0 ) 07/02/2013 === * Merge : * Merge from tag fw4spl_0.9.0.3 to branch fw4spl_0.9.1 * General : * Modify Appconfig xml syntax. "parameters" appConfig type is now required ( "standard" and "template" type are no longer supported ) * Modify Appconfig xml syntax. Replace xml attribute : implementation to impl, autoComChannel to autoConnect. priority tag is no longer supported ( priority is now relative to connection creation order ) * Update Appconfig xml syntax. Add two new xml elements ( to support signal/slot connection ) : and (see Tuto15Multithread AppConfig to see an example) * Update SwapperSrv to support new connection system. Updated SwapperSrv xml syntax to manage and tag. * Fix VRRender application to support new communication system / new xml appConfig syntax * Fix bundles and libraries to support new communication system * Fix applications to support xml appConfig syntax * Fix JpgImageWriter to save jpg instead of png file * Fix some issues, ex : updated BufferManager counter to be thread-safe, permissions issue when deleting temporary folder, some cppcheck warnings ... * Visu : * Updated vtk adaptors : rename doUpdate(msg) by doReceive(msg) and new communication system. * Multithread and communication : * Cleaning communication system : removed ComChannelService, MessageHandler service... * Added Proxy in fwServices. Proxy is an application singleton that provides communication channels. A channel can be connected to a signal. Slots can be connected to a channel. Connection/disconnection can be dynamic during application life. A channel has a forwarder role. * Update Tuto15Multithread with another sub configurations (other examples) * Updated AppconfigManager to manage connect and proxy xml tags in config and to wait for start/stop/update. * Fix IService methods : start, stop, update and swap return a correct shared future in mono thread * Added abstraction level to fwThread's Worker and Timer, implements a Qt and WxWidget version. Updated Tuto15 with new Timer API * Fix ActiveWorkers registry : clear and reset all ActiveWorkers when app stop * TU : * Added tests for fwGuiQt WorkerQt and TimerQt * Fix some unit tests to support new communication system / new xml appConfig syntax * Change gdcm trace output stream for unit tests === fw4spl_0.9.1.0 ( diff from fw4spl_0.9.0.2 ) 29/11/2012 === * Thread: * Created new fwThread library that provides few tools to execute asynchronous tasks on different threads. * Created fwThread::Worker. This class creates and manages a thread. Thanks to the post method it is possible to execute handlers on. * Created fwThread::TaskHandler. This class encapsulates packaged task and it is used to post easily a task on a worker * Created fwThread::Timer. The Timer class provides single-shot or repetitive timers. A Timer triggers a function once after a delay, or periodically, inside the worker loop. The delay or the period is defined by the duration attribute. * Communication: * Added new fwCom library. This library provides a set of tools dedicated to communication. These communications are based on Signal and slots concept (http://en.wikipedia.org/wiki/Signals_and_slots). fwCom provides the following features : * function and method wrapping * direct slot calling * asynchronous slot calling * ability to work with multiple threads * auto-disconnection of slot and signals * arguments loss between slots and signals * fwCom::Slot is wrappers for a function or a class method that can be attached to a fwThread::Worker. The purpose of this class is to provide synchronous and asynchronous mechanisms for method and function calling. * fwCom::Slots is a structure that contains a mapping between a key and a Slot * fwCom::HasSlots manages a fwCom::Slots * fwCom::Signal allows to perform grouped calls on slots. In this purpose, Signal provides a mechanism to connect slots to itself. * fwCom::Signals is a structure that contains a mapping between a key and a Signal * fwCom::HasSignals manages a fwCom::Signals * The connection of a Slot to a Signal returns a Connection handler. Connection provides a mechanism which allows to temporarily disable a Slot in a Signal. The slot stays connected to the Signal, but it will not be triggered while the Connection is blocked : * Add new xxx.vrdc file that are parsed by a variadic_parser (fwCom/scripts/variadic_parser.py) to generate some hpp/hxx files (manipulation of template). * fwData: * Updated Object to inherit of HasSignal * all objects have a signal objectModifiedSig to emit object modification * fwServices: * IService: * Updated IService to have an associated worker and thus an associated thread * Updated IService to inherit of HasSignal and HasSlots * Updated IService to remove old communication system * Updated IService to rename update(msg) to receive(msg) * Added slots start, stop, receive, update, swap on IService. If these methods (receive excepted) are not call in thread associated to the service, the slot version is called. * Add IService::getObjSrvConnections method to propose signal/slot connections between a service and his associated object * Add new structure ActiveWorkers to register worker in f4s * Added macros for notification: fwServicesNotifyMsgMacro and fwServicesBlockAndNotifyMsgMacro * Modify IEditionService::notify to use this macros. * Log: * Add fwID::getLightId method used for log * Initialized spylog in fwTest, to enable logs within unit tests * Add communication info message, messages are enable if you define COM_LOG when you compiling f4s * Tutorials: * Create new tutorial Tuto15MultithreadCtrl that contains a few multithread examples. === fw4spl_0.9.0.3 ( diff from fw4spl_0.9.0.2 ) 11/12/2012 === * General : * Change default transfert function for Muscles and Skin * Add a new action AnonymisePatient to anonymise selected patient in PDB * Add new organ to dictionary Lymph Node * Fix OrganDictionary (re add World key). Add also few liver segment keys * Fix JpgImageWriter to save jpg instead of png file * Fix crash when using manage organ editor === fw4spl_0.9.0.2 ( diff from fw4spl_0.9.0.1 ) 02/11/2012 === * General: * renamed fwMetaData library to fwAtoms * few fixes, refactoring * Editor: * PatientDBGuiSelectorService: now it is possible to erase an acquisition OR a patient (with key del) * PatientDBGuiSelectorService: image comment edition is now possible from mouse double-clicking on item * Log: * Update application log: check if default log dir is unreachable before create log file === fw4spl_0.9.0.1 ( diff from fw4spl_0.9.0.0 ) 28/09/2012 === * Merge: * Merge from tag fw4spl_0.8.3.6 * General: * /!\ Removed WxWidgets support in f4s apps (preserved in TutoGui) * /!\ Removed old factory in fwTools * Code cleaning: fix compilation, removed unused code, added missing include, removed verbose logging, added missing export * Add new service ::fwServices::SConfigController to manage AppConfig without using an action * Add new introspection tool in fwMetaConversion to find a subOject from an object and a path. * ConfigActionSrvWithKeySendingConfigTemplate uses now data reflection to find tabPrefix and tabInfo * Fixed fwData::factory::New (register attributes on fwData::Object) * Fixed conflicts with python tolower define * Fixed AnonymiseImage (doesn't duplicate images) * Fixed PatientDBGuiSelectorService : doesn't re-set image label if it exists * Fixed opSofa compilation * Updated opsofa : Replaced EulerImplicitSolver by EulerSolver. * Log : * Updated launcher to parse options with boost program options ( added log options, added Bundle dir and runtime directory options ) * IO : * Catched exception in mesh reader service to prevent bad file format error. * Added reader inject mode in IOSelectorService to add an object in a composite * Updated SPatientDBInserter : allows user to enter a comment on image * Data introspection * Added fwCamp Library, this library is used to introspect fwData * Added Mapper for camp unsupported basic types * Added fwData binding in new library fwDataCamp * Added fwTools Buffer Object inplace binding * Added new meta data in new library fwMetaData * Added new library fwMetaConversion to convert fwData <-> fwMetaData Library. * Visu : * Added SDrop service and drag and drop support in GenericScene * Updated NegatoMPR : fixed red cross bug * New multi thread safe factories : * Added new factory for IObjectReader and IObjectWriter and update readers/writers to use it * Updated fwCommand : doesn't need to use factory * Added new factory for fwXML * Added new factory for VtkWindowInteractor and updated visuVTKQt to use it * Added new factory for fwMetaConversion * Added new factory for fwCamp * Added new factory for Gui objects and updated fwGuiQt/fwGuiWx to use new factory === fw4spl_0.8.3.6 ( diff from fw4spl_0.8.3.5 ) 26/09/2012 === * General * Fixed invalid free in fwRenderVTK/vtklogging.cpp * Code cleaning : Removed several warnings, unused file, missing export * Fixed small bug in AppConfigManager when starting and stopping ComChannels * Fixed small bug in fwservice/ObjectMsg.cpp when subject has expired * Added dynamicConfigStartStop attribute configuration in guiQt/editor/DynamicView.cpp * 2D Scene * 2D scene adaptor can now manage sub adaptors * Vector fields * Updated fwData::Image to allow multi-components images * Added VectorField Adaptor example and TutoVectorField === fw4spl_0.9.0.0 ( diff from fw4spl_0.8.3.5 ) 26/07/2012 === * General : * Fixed few compilation warning * Updated uuid generation using boost::uuid * Added Thread helper to be used for unit test * Removed old fwTools::Singleton * Removed unused XMLSubstitute * Removed unused RootManager * Disable _( macro to avoid conflict with boost and replace _( by wxGetTranslation( * Now, it is not possible to create an ::fwData::Object * Log : * Added OSLM _LOG macro * Changed log backend: log4cxx to boost.log * Updated Spylog default configuration * Fixed build errors with new spylog macros (mostly missing ';'). * Moved SpyLog in fwCore::log namespace * Mutex : * Removed old mutex in fwData::Video * replace interprocess mutex by fwCore::mt::Mutex * Added mutex typedef in fwCore * Added helpers to lock fwData::Object for multi-threading * Factories : * Added FactoryRegistry and LazyInstantiator & UT * Updated fwData to use fwCore/util factory registry * Refactoring all data you must have specific constructor * Updated ServiceFactory to use fwCore/util Instanciator and to be thread safe * Refactoring all services constructors/destructor must become public * Added new message factory * Updated ActionNotifyService : used new message factory * Updated ctrlSelection to use new data and message factories * Thread-safe : * Updated fwServices Config to become thread safe * Updated AppConfigParameters to become thread safe. * Updated fwServices AppConfig to become thread safe. * Updated fwID to become thread safe. * Updated UUID to become thread-safe. * Updated IBufferManager to become thread-safe === fw4spl_0.8.3.5 ( diff from fw4spl_0.8.3.4 ) 26/07/2012 === * General : * improved msvc2010 compatibility * Application configuration : * Add new type of app config : parameter, this type of config permits to declare template parameter and his default value. * System manages now new extension point AppConfigParameters * AppXml can use now a paremeter set to launch a config thanks to new extension point AppConfigParameters * Service configuration : * Updated fwRuntime and fwServices to accept boost property tree as configuration objects. The current implementation actually converts ptrees to ConfigurationElement and vice versa, but is fully functional. * Added examples to show how to use a ptree to configure a service from c++. * Added examples to show how to parse a service configuration with a ptree. * Scene 2D : * Fixed bug in scene 2d to manage better composite key removing * Fixed scene 2D adaptor stopping : srv configuration was lost and zvalue was not correct after call swap (stop/start) * Scene 2D adaptor can now manages sub adaptors * Add new adaptor to interact with the viewport in 2D scene (zoom, translation) * Fixed negato adaptor, it was not his job to manage zoom and translation in the view * ARLcore : * ARLcore now use fw4spl pointer * Added unit tests === fw4spl_0.8.3.4 ( diff from fw4spl_0.8.3.3 ) === * General : * Remove some warnings : type conversion, useless exports, ... * Fixed NumericRoundCast (wrong type) and add unit test * Added fwTools::os::getEnv method * Fixed zip file path creation * Updated AppConfig : 'type' attribute is not required anymore in xml files for 'service' tag, but must be consitent with 'implementation' attribute * Data : * Modified the fwData::Camera class adding the skew and distortion coefficients. * UI : * Fixed OrganListEditor when reconstructions are removed * Fixed a crash when a message dialog is shown without icon * WindowLevel now uses floating precision to compute range width * ImageTransparency : fixed focus on visibility checkbox (use QAction to set shortcut) * IO : * Fixed InrPatientDBReader problem, now it is possible to select a folder which contains inr images * Visualization : * Updated NegatoWindowingInteractor to parse TF config * Updated transfer function helper : use image window width/center to create TF * Add config option in Window Level editor to use image grey level tf instead of create new tf * Python : * python management of Image.size .spacing .origin as python list * Added handler for python outputs * Binding Image::deepCopy === fw4spl_0.8.3.3 === * General : * Refactoring of fwService, now an IService work on a fwData::Object (instead of fwTools::Object) * Disabled Qt keyword (avoid conflicts with boost signals and foreach) * Continue adding array lock ( or image/mesh lock helper ) in different lib/bundles * Fixed AppConfig (adaptField if cfg element value is not empty) * Updated temporary folder management * Refactoring of AppConfigManager to be more easily extended * Added ByteSize object and unit tests : this class manages conversion between string and size_t * Updated MenuLayoutManager to allow setting icon for actions in menu * Added new service to substract two images SImagesSubstract * Apps : * Added Ex04ImagesRegistration which subtract two images with itk * Updated Ex02ImageMix with TF selection * Visualization : * Fixed clipping plane visualization on meshes * Fixed ImagesProbeCursor, manage now image origin * Fixed ProbeCursor (problem with view reset) * Fixed shakeNormals when array is null * Updated Volume adaptor to support TF nearest mode * Fixed RemoveDistance action * Fixed ImageMultiDistances adaptor * Fixed PlaneXMLTranslator (compute plane from points) * Data : * Updated Reconstruction, Mesh, Image, and Array API to be compatible with new dump system to maniplate a buffer, you must used Mesh/Image/Array helper (in fwComEd/helper) * Updated Image and Mesh helpers * Removed fwData::Image::setDataArray (keep existing data::Array in Image) * Fixed Array deepCopy (copy array informations if buffer is empty) * Added swap on fwData::Array * Some evolution in ObjectLock : keep object reference, added copy constructor and operator implementation * Updated Array : array is buffer owner on creation * Updated Image, Mesh : not New on array when deepCopy or clear * BufferObject / IBufferManager : * Added documentation * Added swap on fwTools::BufferObject * Added fwTools::Exception on BufferAllocatePolicy allocate/reallocate * Dump management : * Added documentation * Introduced hysteresis offset in fwMemory::policy::ValveDump * Updated fwMemory Policy API : added policy factory, added setParam API on dump policies * Added service : SDumpService will help to configure the dump policy * Fwxml writer does not restore dumped image during serialization, just copy dumped file * Try to hard link raw file instead of copy to serialize patient folder * Fixed barrier limit to max(freeMemory/2, aliveMemory, 512Mo) during serialization * IO : * Updated fwXML FileFormatService system ( is not used in a separated process ) * FileFormatService is now called directly in ArrayXMLTranslator * Updated ImageXMLTranslator and MeshXMLTranslator to use Array::swap method * Fixed ResectionXMLTranslator (read "isValid" element) * Test : * Changed some namespace in different unittest libraries === fw4spl_0.8.3.2 === * General : * Fixed clang/icc compilation * Fixed import fxz (fields in few structures were not managed). * Fixed ImagesBlend Adaptor when there is twice the same image * Fixed selected acquisition index in PatientDBGuiSelectorService * New service SPatientDBInserter (io::IWriter type) that permits to push a structure (patientDB, patient, study, acquisition or image) in a patientDB. If destination pdb is not configured, a dialog box proposes to select a pdb from an active pdb list ( pdb registered in OSR ) * Added helper to compare float/double value with 'epsilon' error margin (fwMath/Comapre.hpp) and Update ImageBlend Adaptor to use it * Inactivated minimized mode (in preference) for frames * Update compression level for raw buffer ( low compression, hight speed ) * Apps : * Added new example Ex01VolumeRendering to show different services that use or manipulate a TF * Added new example Ex03Registration to show a registration between points by using ARLCore. * Transfert function : * Fixed issue with TransferFunctionEditor * Fixed issue with last table color in fwVtkWindowLevelLookupTable * Complete refactoring of TransferFunction adaptor (scene2D) to support now new TF structure ( manage NEAREST interpolation, manage clamping, manage negative window * Support new TF structure for PoC06Scene2DTF * Added TransferFunction helper to create a drawing TF * BufferObject / IBufferManager * Added fwTools::BufferObject : Base class for FW4SPL buffers. Keep a pointer to a buffer and it's allocation policy (new or malloc) without any cleverness about allocating/destroying the buffer. Users of this class needs to take care about allocation and destruction by themselves. BufferObject class has a BufferManager and Locks mechanism, Allowing to trigger special treatments on various events on BufferObjects (allocation, reallocation, destruction, swapping, locking, unlocking) (see doxygen for more information). * Added fwTools::IBufferManager : Provides interface for a buffer manager. A BufferManager is able to hook BufferObjects actions an to change it's behaviors. (see doxygen for more information) * Updated fwData::Array to use fwTools::BufferObject * Added new helper fwdata::ObjectLock : a simple helper to lock specific object, manages : Image, Mesh, Array, Reconstruction and Acquisition. * Removed few critical methods of basic structures (fwData::Array, fwData::Mesh and fwData::Image) according to buffer lock mecanism. These methods are now proposed by helpers (fwComEd::helper::Array, fwComEd::helper::Mesh, fwComEd::helper::Image) and manage buffer lock process. * Support buffer lock process in many helpers/services ( MeshGenerator, vtk conversion, itk conversion, serialization, etc ) * Dump management * Added an implementation of fwTools::IBufferManager with fwMemory::BufferManager : This implementation purpose is to manage memory load, freeing memory and restoring freed buffers as needed. A dump policy is used to trigger memory freeing process. The restore process is always triggers when a lock is requested on a dumped buffer. Available policies : * NeverDump : This policy will never take the initiative to free memory. This is the policy used when no automatic memory management is wanted. Memory will be dumped on demand. * AlwaysDump : This policy will dump buffers as often as possible. As soon as a buffer is released (ie the lock count become zero), it will be dumped. * BarrierDump : This policy defines a memory usage barrier and will try to keep the managed buffers memory usage under this barrier. * ValveDump : This policy is monitoring system memory usage and will trigger memory dump when the amount of system free memory is lower than the minFreeMem parameter. An hysteresis parameter exists to free more memory when the process is triggered. If the free system memory amount is lower than the minFreeMem, this policy will try to reach minFreeMem + hysteresisOffset bytes of free memory. * Updated darwin memory tool : take in account inactive memory as free memory * Activate BarrierDump during fwXML serialization if fwMemory::BufferManager with NeverDump policy is used. === fw4spl_0.8.3.0 === * New field API structure for data : * Remove old field API on fwTools::Object ( impact on all fwData::Object / IService / ObjectMsg / etc ) * Add new field API on fwData::Object * New transfert function structure : * Remove old transfert function structure * Add new transfert function structure : * a transfert function has its own window level * window can be negative or null * transfert function associate a value in double to a RGBA color * Added reimplementation of vtkWindowLevelLookupTable, fwVtkWindowLevelLookupTable ( in fwRenderVTK ) managing negative window and out-of-range value clamp * Method to convert a ::fwData::TransferFunction to vtk lookup table are added in vtkIO::helper::TransfertFunction * It's possible now for negato or volume rendering or window level interactor to work only on a specific transfert function * All image messages concerning window/level or transfer function has been removed, no messages are send directly on the tf * Evolution of ::fwDataTools::helper::MedicalImageAdaptor to provide some helpers to manipulate transfer function in your service * Other : * Add new macros API to generate getter/setter for fwData * fwDataGetSetCRefMacro( Param, Type ) generate : * const Type & getParam() const; * void setParam( const Type & attrParam ); * User must declare Type m_attrParam; * fwDataGetSetSptrMacro( Param, Type ) generate : * Type getParam() const; * void setParam( Type attrParam ); * User must declare Type m_attrParam; * fwData introduces new macro to register data in factory fwDataRegisterMacro ( ex : fwDataRegisterMacro( ::fwData::Image ) ) instead of REGISTER_BINDING_BYCLASSNAME * fwData provides a new factory helper (::fwData::Factory) to build ::fwData::Object, use it instead ::fwTools::Factory to build class of type ::fwData::Object * Support change in fwXML, and thus increment .yaf version (3->4) to support new structures (old yaf version are not compatible) * Move ObjectGenerator/ObjectComparator from fwXML unit test to fwDataTools to merge helper to create and compare data * Moved data visitors from fwData to fwXML * New API and events on ObjectMsg (ADDED/CHANGED/REMOVED FIELDS) * Updated CompositeMsg API ( xxx_FIELDS -> xxx_KEYS ) * New Field helper : as for composite helper, build a message with fields modifications * New Field Manager : Works the same way as the composite helper, but for fields === fw4spl_0.8.2.3 === * General : * Added new helper fwTools::Type to manage different system type * Image structure refactoring * Replaced IBufferDelegate by ::fwData::Array * fwTools::Type to define the image type * Support new image structure in the system * Improve origin image management : reader/writer, visualization 2D/3D/VR, pipeline, registration, resection * Fixed libxml memory management (source of different problems in VRMed) * Updated ImagesBlend adaptor to check if images have the same size, spacing and origin. Show a message dialog if image have not same size, spacing or origin. Added tolerance for spacing and origin comparison * Modified Pulse dialog to work when guiQt is disable * Add new function in class Array to setBuffer with all parameters instead of allocating it * Updated API to convert itk image to or from a fwData image (fwItkIO), updates unit tests * Added CDATA section parsing in xml app configuration ( used by python tuto ) * Clean code: removed depreciated USE_BOOST_REGEX define in dateAndTime helpers * Fixed libxml call to xmlCleanupParser (see http://xmlsoft.org/html/libxml-parser.html#xmlCleanupParser) * IO : * Evolution of patient folder version, now is v3 and replace fwXML archive default extension .fxz by .yaf to avoid user problem * IWriter/IReader refactoring, these classes propose now new API to regroup common source code * Added some unit tests and fixed few io problems * Added ioBasic to read/write .bio file * Reintroduced bad management of rescale data with gdcm * Testing : * Added some unit test on bundles (io) * Added some unit test on lib (io) * Added fwDataTools::Image to generate and test image and added unit test * Added new project fwTest that propose few helpers used in different UT ( for example management of data path ) * Added helper in fwTest to check patient struct after a dicom file parsing to regroup test concerning dicom format e sptr ) * Updated object comparator/generator in fwDataTools for test * Apps : * Updated Tutorials build.options : disable wx on osx64 * Updated TutoDevForum : use new image API and use generic gui * Added a basic python code usage sample with TutoPython * Added new tuto dedicated to fw4spl beginner training === fw4spl_0.8.1.2 === === fw4spl_0.8.0.0 === sight-25.1.0/CMakeLists.txt000066400000000000000000000366551503402212300154640ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.19) # Use new policy for 'install_name' and RPATH on macOS (use `cmake --help-policy CMP0068` for details) cmake_policy(SET CMP0068 NEW) # Use new policy for `FindOpenGL` to prefer GLVND by default when available on linux. # (use `cmake --help-policy CMP0072` for details). cmake_policy(SET CMP0072 NEW) # Use new policy for 'Honor visibility properties for all target types'. # (use `cmake --help-policy CMP0063` for details) cmake_policy(SET CMP0063 NEW) # Adds support for the new IN_LIST operator. cmake_policy(SET CMP0057 NEW) # Use new policy to use CMAKE_CXX_STANDARD in try_compile() macro cmake_policy(SET CMP0067 NEW) # Use new policy for CMAKE_MSVC_DEBUG_INFORMATION_FORMAT cmake_policy(SET CMP0141 NEW) if(${CMAKE_VERSION} VERSION_GREATER "3.31.0") # Use new policy for removed FindBoost module cmake_policy(SET CMP0167 NEW) # Use new policy for empty string variable after a single-value keyword cmake_policy(SET CMP0174 NEW) # Use new policy for normalized install() DESTINATION paths cmake_policy(SET CMP0177 NEW) else() # Use old policy for Documentation to add cache variables and find VTK documentation dependent packages. # (Needed for doxygen..) cmake_policy(SET CMP0106 OLD) endif() # On Windows, if the user doesn't specify a value, # 'CMAKE_BUILD_TYPE' is automatically initialized to 'Debug' after 'project()'. # So we need to check this variable at this point. set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "Choose the type of build, options are: Debug, Release, RelWithDebInfo" ) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo") if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" ) message( FATAL_ERROR "Invalid value for CMAKE_BUILD_TYPE: '${CMAKE_BUILD_TYPE}' (required Debug, Release, RelWithDebInfo)" ) endif() project( sight VERSION 25.1.0 DESCRIPTION "Surgical Image Guidance and Healthcare Toolkit" HOMEPAGE_URL "https://git.ircad.fr/sight/sight" ) if(APPLE) message(FATAL_ERROR "macOS is not supported.") endif() enable_testing() include(CheckVariableExists) include(CMakeParseArguments) include(GNUInstallDirs) include(CMakePackageConfigHelpers) option(SIGHT_ENABLE_PCH "Use pre-compiled headers to speedup the compilation" ON) option(SIGHT_VERBOSE_PCH "Display debug messages to help debugging PCH" OFF) mark_as_advanced(SIGHT_ENABLE_PCH) mark_as_advanced(SIGHT_VERBOSE_PCH) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/build/flags.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/build/macros.cmake) # We need to find Qt6 before ITK/VTK, otherwise they find Qt5 and CMake fails with a cryptic # "Targets not yet defined: Qt::XXXXPrivate", even if we don't use ITK-Qt or VTK-Qt extensions if(UNIX) find_package(Qt6 CONFIG QUIET COMPONENTS Core Gui Widgets OpenGL) endif() # We find VTK to know its version so we can enable PCL or not find_package(VTK CONFIG QUIET) ######################################################################################################################## # User options ######################################################################################################################## # Tests build / run options option(SIGHT_BUILD_TESTS "Configures projects associated tests (Test projects)" ON) option(SIGHT_TESTS_XML_OUTPUT "Tests will generate an xml output, suitable for CI integration" ON) mark_as_advanced(SIGHT_TESTS_XML_OUTPUT) set(SIGHT_TESTS_FILTER "" CACHE STRING "Allows to only build/run tests whose path contains the filter string.") mark_as_advanced(SIGHT_TESTS_FILTER) if(UNIX) option(SIGHT_ENABLE_GDB "Test scripts will attach GDB, allowing to generate core file and print backtrace in case of crash." OFF ) mark_as_advanced(SIGHT_ENABLE_GDB) if(SIGHT_ENABLE_GDB) find_program(GDB NAMES "gdb" REQUIRED) endif() endif() # Use clang-tidy linter option(SIGHT_ENABLE_CLANG_TIDY "Enable Clang Tidy checks" OFF) mark_as_advanced(SIGHT_ENABLE_CLANG_TIDY) if(SIGHT_ENABLE_CLANG_TIDY) # cmake-lint: disable=C0301 find_program(CLANG_TIDY NAMES "clang-tidy" "clang-tidy-14" REQUIRED) set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY};--use-color;--export-fixes=.clang-tidy-fixes.yaml) # Also generate compile_commands.json which is useful when launching clang-tidy by "hand" set(CMAKE_EXPORT_COMPILE_COMMANDS ON) endif() if(CMAKE_CONFIGURATION_TYPES) set(CMAKE_CONFIGURATION_TYPES ${CMAKE_BUILD_TYPE} CACHE STRING "List of supported configurations." FORCE) endif() # Use solution folders. set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER automoc) # Make Realsense optional option(SIGHT_ENABLE_REALSENSE "Enable RealSense" OFF) # Make samples/tutorials build optional option(SIGHT_BUILD_EXAMPLES "Build examples and tutorials" ON) option(SIGHT_BUILD_MANPAGES "Generate man pages for applications" OFF) ######################################################################################################################## # Warn user of install dir isn't empty ######################################################################################################################## file(GLOB_RECURSE INSTALL_DIR_CONTENT ${CMAKE_INSTALL_PREFIX}/*) list(LENGTH INSTALL_DIR_CONTENT CONTENT) if(NOT CONTENT EQUAL 0) # DIR isn't empty, warn user. message(WARNING "CMAKE_INSTALL_PREFIX (${CMAKE_INSTALL_PREFIX}) isn't empty." "Please select another folder or clean it before running install command." ) endif() ######################################################################################################################## # External libraries management ######################################################################################################################## set(FWCMAKE_RESOURCE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/) # Append our 'FindPackages.cmake' to CMAKE_MODULE_PATH list(APPEND CMAKE_MODULE_PATH ${FWCMAKE_RESOURCE_PATH}/module) if(UNIX) list(APPEND CMAKE_PREFIX_PATH ${FWCMAKE_RESOURCE_PATH}/module) list(APPEND CMAKE_FIND_ROOT_PATH ${FWCMAKE_RESOURCE_PATH}/module) if(UNIX) list(APPEND CMAKE_PREFIX_PATH /usr/share/OGRE/cmake/module) endif() set(SIGHT_EXTERNAL_LIBRARIES CACHE PATH "External libraries location") mark_as_advanced(SIGHT_EXTERNAL_LIBRARIES) # Use directly SIGHT_EXTERNAL_LIBRARIES if set, otherwise, download and install sight-deps package # Comment out this if one need to include a dependency from sight-deps one day #if(NOT SIGHT_EXTERNAL_LIBRARIES AND SIGHT_ENABLE_LIBRARY) # include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/build/download_deps.cmake) #endif() if(SIGHT_EXTERNAL_LIBRARIES) get_filename_component(ABSOLUTE_SIGHT_EXTERNAL_LIBRARIES ${SIGHT_EXTERNAL_LIBRARIES} REALPATH) set(SIGHT_EXTERNAL_LIBRARIES ${ABSOLUTE_SIGHT_EXTERNAL_LIBRARIES}) unset(ABSOLUTE_SIGHT_EXTERNAL_LIBRARIES) list(APPEND CMAKE_PREFIX_PATH ${SIGHT_EXTERNAL_LIBRARIES}) list(APPEND CMAKE_MODULE_PATH ${SIGHT_EXTERNAL_LIBRARIES}/lib/cmake/) list(APPEND CMAKE_FIND_ROOT_PATH ${SIGHT_EXTERNAL_LIBRARIES}) # To be added into LD_LIBRARY_PATH in our "launch" scripts. set(SIGHT_EXTERNAL_LIBRARIES_LIB_PATH ${SIGHT_EXTERNAL_LIBRARIES}/lib/) endif() endif() # We always need CppUnit, no need to put that in subdirectories find_package(CppUnit QUIET REQUIRED) ######################################################################################################################## # Default paths settings for libraries, module and resources ######################################################################################################################## set(FW_INSTALL_PATH_SUFFIX "${PROJECT_NAME}") set(SIGHT_MODULE_RC_PREFIX "${CMAKE_INSTALL_DATADIR}/${FW_INSTALL_PATH_SUFFIX}") if(WIN32) set(SIGHT_MODULE_LIB_PREFIX "${CMAKE_INSTALL_BINDIR}") else() set(SIGHT_MODULE_LIB_PREFIX "${CMAKE_INSTALL_LIBDIR}") endif() set(FWCONFIG_PACKAGE_LOCATION lib/cmake/sight) set_property(GLOBAL PROPERTY ${PROJECT_NAME}_COMPONENTS "") # Define the path 'FW_SIGHT_EXTERNAL_LIBRARIES_DIR' used to find external libraries required by our applications set_external_libraries_dir() if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") set(SIGHT_VCPKG_ROOT_DIR "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/debug") else() set(SIGHT_VCPKG_ROOT_DIR "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}") set(EXCLUDE_PATTERN ".*/debug/.*") endif() sight_configure_pch() add_subdirectory(lib) add_subdirectory(module) add_subdirectory(activity) add_subdirectory(config) add_subdirectory(util) add_subdirectory(app) if(SIGHT_BUILD_EXAMPLES) add_subdirectory(tutorial) add_subdirectory(example) endif() add_subdirectory(3rd-party) ######################################################################################################################## # Export and install targets ######################################################################################################################## # Will export COMPONENTS: list of all available components ordered by dependency (no dependency first). sight_generate_component_list(COMPONENTS) # Create the sightConfigVersion file configure_file(${CMAKE_SOURCE_DIR}/cmake/build/sightConfig.cmake.in ${CMAKE_BINARY_DIR}/cmake/sightConfig.cmake @ONLY) write_basic_package_version_file( "${CMAKE_BINARY_DIR}/cmake/sightConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) # Install a special xvfb-run wrapper script to workaround bugs withe the system xvfb-run launcher script configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/install/linux/safe-xvfb-run ${CMAKE_CURRENT_BINARY_DIR}/bin/safe-xvfb-run COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/install/linux/exec_gui_tests.sh ${CMAKE_CURRENT_BINARY_DIR}/bin/exec_gui_tests.sh COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/install/windows/exec_gui_tests.bat ${CMAKE_CURRENT_BINARY_DIR}/bin/exec_gui_tests.bat COPYONLY ) # Install the sightConfig.cmake and sightConfigVersion.cmake install(FILES "${CMAKE_BINARY_DIR}/cmake/sightConfig.cmake" "${CMAKE_BINARY_DIR}/cmake/sightConfigVersion.cmake" "${CMAKE_SOURCE_DIR}/cmake/build/macros.cmake" DESTINATION ${FWCONFIG_PACKAGE_LOCATION} COMPONENT dev ) # install CppUnitConfig.cmake in a module folder. install(FILES "${CMAKE_SOURCE_DIR}/cmake/module/CppUnitConfig.cmake" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/module COMPONENT dev ) # Install some files needed for the build install( FILES "${CMAKE_SOURCE_DIR}/cmake/build/configure_file.cmake" "${CMAKE_SOURCE_DIR}/cmake/build/flags.cmake" "${FWCMAKE_RESOURCE_PATH}/build/cppunit_main.cpp.in" "${FWCMAKE_RESOURCE_PATH}/build/pch.cpp" "${CMAKE_SOURCE_DIR}/cmake/build/config.hpp.in" "${CMAKE_SOURCE_DIR}/cmake/build/plugin_config.cmake" "${CMAKE_SOURCE_DIR}/cmake/build/plugin_config_command.cmake" "${CMAKE_SOURCE_DIR}/cmake/build/profile_config.cmake" "${CMAKE_SOURCE_DIR}/cmake/build/profile.xml.in" "${CMAKE_SOURCE_DIR}/cmake/build/registerServices.cpp.in" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/build COMPONENT dev ) # Install some files needed for the install install( FILES "${CMAKE_SOURCE_DIR}/cmake/install/generic_install.cmake" "${CMAKE_SOURCE_DIR}/cmake/install/get_git_rev.cmake" "${CMAKE_SOURCE_DIR}/cmake/install/helper.cmake" "${CMAKE_SOURCE_DIR}/cmake/install/install_imported.cmake.in" "${CMAKE_SOURCE_DIR}/cmake/install/pre_package.cmake" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/install COMPONENT dev ) if(WIN32) install( FILES "${CMAKE_SOURCE_DIR}/cmake/build/windows/template.bat.in" "${CMAKE_SOURCE_DIR}/cmake/build/windows/template_exe.bat.in" "${CMAKE_SOURCE_DIR}/cmake/build/windows/template_test.bat.in" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/build/windows COMPONENT dev ) install( FILES "${CMAKE_SOURCE_DIR}/cmake/install/windows/package.cmake" "${CMAKE_SOURCE_DIR}/cmake/install/windows/install_plugins.cmake" "${CMAKE_SOURCE_DIR}/cmake/install/windows/template.bat.in" "${CMAKE_SOURCE_DIR}/cmake/install/windows/template_exe.bat.in" "${CMAKE_SOURCE_DIR}/cmake/install/windows/setpath.bat.in" "${CMAKE_SOURCE_DIR}/cmake/install/windows/windows_fixup.cmake.in" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/install/windows COMPONENT dev ) install(FILES "${CMAKE_SOURCE_DIR}/cmake/install/windows/NSIS/NSIS.InstallOptions.ini.in" "${CMAKE_SOURCE_DIR}/cmake/install/windows/NSIS/NSIS.template.in" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/install/windows/NSIS/ ) install( FILES "${CMAKE_SOURCE_DIR}/cmake/install/windows/NSIS/rc/banner_nsis.bmp" "${CMAKE_SOURCE_DIR}/cmake/install/windows/NSIS/rc/dialog_nsis.bmp" "${CMAKE_SOURCE_DIR}/cmake/install/windows/NSIS/rc/app.ico" "${CMAKE_SOURCE_DIR}/cmake/install/windows/NSIS/rc/license.rtf" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/install/windows/NSIS/rc/ ) install(PROGRAMS "${CMAKE_SOURCE_DIR}/cmake/install/windows/exec_gui_tests.bat" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/install/windows COMPONENT dev ) elseif(UNIX) install( FILES "${CMAKE_SOURCE_DIR}/cmake/build/linux/template.sh.in" "${CMAKE_SOURCE_DIR}/cmake/build/linux/template_exe.sh.in" "${CMAKE_SOURCE_DIR}/cmake/build/linux/template_test.sh.in" "${CMAKE_SOURCE_DIR}/cmake/build/linux/manpage.cmake" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/build/linux COMPONENT dev ) install(FILES "${CMAKE_SOURCE_DIR}/cmake/install/linux/package.cmake" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/install/linux COMPONENT dev ) install(PROGRAMS "${CMAKE_SOURCE_DIR}/cmake/install/linux/safe-xvfb-run" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/install/linux COMPONENT dev ) install(PROGRAMS "${CMAKE_SOURCE_DIR}/cmake/install/linux/exec_gui_tests.sh" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/install/linux COMPONENT dev ) install( FILES "${CMAKE_SOURCE_DIR}/cmake/install/linux/template.sh.in" "${CMAKE_SOURCE_DIR}/cmake/install/linux/template_exe.sh.in" "${CMAKE_SOURCE_DIR}/cmake/install/linux/linux_fixup.cmake.in" DESTINATION ${FWCONFIG_PACKAGE_LOCATION}/install/linux COMPONENT dev ) endif() ######################################################################################################################## # Misc generators ######################################################################################################################## # Doxygen documentation option(SIGHT_BUILD_DOC "Build the doxygen documentation" OFF) if(SIGHT_BUILD_DOC) option(SIGHT_BUILD_DOCSET "Build a Dash/Zeal/XCode docset" OFF) include(${FWCMAKE_RESOURCE_PATH}doxygen/doxygen_generator.cmake) doxygengenerator(${PROJECT_LIST}) if(SIGHT_BUILD_DOCSET) docsetgenerator(${PROJECT_LIST}) endif() else() unset(SIGHT_BUILD_DOCSET CACHE) endif() sight_create_package_targets("${COMPONENTS}" "") sight-25.1.0/COPYING000066400000000000000000001055101503402212300137420ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Sight is: Copyright (C) 2009-2025 IRCAD France Copyright (C) 2012-2020 IHU Strasbourg Contact: sds.ircad@ircad.fr You may use, distribute and copy Sight under the terms of GNU Lesser General Public License version 3. That license references the General Public License version 3, that is displayed below. Other portions of Sight may be licensed directly under this license. ------------------------------------------------------------------------- GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . sight-25.1.0/COPYING.LESSER000066400000000000000000000177331503402212300147470ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Sight is: Copyright (C) 2009-2025 IRCAD France Copyright (C) 2012-2020 IHU Strasbourg Contact: sds.ircad@ircad.fr You may use, distribute and copy Sight under the terms of GNU Lesser General Public License version 3, which is displayed below. This license makes reference to the version 3 of the GNU General Public License, which you can find in the COPYING file. ------------------------------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. sight-25.1.0/README.md000066400000000000000000000141501503402212300141650ustar00rootroot00000000000000# Sight | Branch | Status | |--------|-----------| | Dev | [![pipeline status](https://git.ircad.fr/Sight/sight/badges/dev/pipeline.svg)](https://git.ircad.fr/Sight/sight/commits/dev) | | Master | [![pipeline status](https://git.ircad.fr/Sight/sight/badges/master/pipeline.svg)](https://git.ircad.fr/Sight/sight/commits/master) | ## Description **Sight**, the **S**urgical **I**mage **G**uidance and **H**ealthcare **T**oolkit aims to ease the creation of applications based on medical imaging. It encompasses a range of functionalities, including 2D and 3D digital image processing, visualization, augmented reality, and medical interaction simulation. Compatible with both Linux and Windows operating systems, it is coded in C++ and offers swift interface design through XML files. Moreover, it is freely accessible under the LGPL license. **Sight** is developed by the Surgical Data Science Team of [IRCAD France](https://www.ircad.fr), where it is used everyday to develop innovative applications for the operating room and medical research centers. Numerous **tutorials** and **examples**, housed within the `tutorials` and `examples` directories, are available to facilitate your smooth learning journey with Sight. Detailed steps are described [here](https://sight.pages.ircad.fr/sight-doc/Tutorials/index.html). ### Features - 2D/3D visualization of medical images, meshes, and many widgets. - Import / export medical data from various formats (DICOM, [VTK](https://www.vtk.org/), ...) and sources (files, devices, PACS, ...). - Playing, recording, processing videos (webcams, network streams, Intel RealSense devices, ...). - Easy GUI configuration and customization (XML description and stylesheets support). - Timeline, allowing to store various data (video, matrices, markers, etc...) and synchronize these data across time. - Mono and stereo camera calibration, - [ArUco](https://sourceforge.net/projects/aruco/) optical markers tracking, - [openIGTLink](http://openigtlink.org/) support through client and server services, - Advanced memory management to support large data. Unused data can be offloaded to disk, saving memory for foreground tasks. - Work session or any part of it, can be saved and restored on disk. The data itself can be encrypted using AES256 to ensure a high level of security and privacy ### Hardware / Operating System / Compiler support **Sight** is written in standard C++20 and use [CMake](https://cmake.org/) as its build system, which means that Sight should compile on any operating system that provides support for a decent C++20 compiler, CMake, **AND** Sight's dependencies (see [Install](#install) for a list of dependencies for Linux platform). However, we currently have access to a limited set of hardware/OS/compiler combinations where the code is actually tested on a regular basis. Such combination includes: - [Debian 12 stable on AMD64 with GCC 12](https://www.debian.org/ports/amd64) - [Ubuntu 22.04 on AMD64 with GCC 11/12 or CLang 17](https://releases.ubuntu.com/22.04/) - [Microsoft Windows 10/11 on AMD64 with VisualStudio 2022](https://www.microsoft.com/windows/) > If your platform is not listed, that *doesn't* imply **Sight** will not build, just we cannot guarantee it. > If you're using such a platform and manage to build and utilize **Sight**, please feel free to share your success > with us! ## Applications ### Sight Viewer **Sight Viewer** is a full featured medical image and mesh viewer with advanced rendering features such as volume rendering. It supports most medical image formats, and can also retrieve DICOM files from a PACS. It demonstrates many useful features of Sight.
MPR view of a medical 3D image with additional volume rendering
Volume rendering and transfer function tuning
Volume rendering mixed with 3D surfacic meshes
### Sight Calibrator **Sight Calibrator** is a user-friendly application to calibrate mono and stereo cameras. This software is a must-have since camera calibration is a mandatory step in any AR application.
Intrinsic & extrinsic calibration of mono/stereo cameras with live reprojection error display
## Install See [detailed install instructions](https://sight.pages.ircad.fr/sight-doc/Installation/index.html) for Windows and Linux. ## Documentation * [Documentation](https://sight.pages.ircad.fr/sight-doc) * [Tutorials](https://sight.pages.ircad.fr/sight-doc/Tutorials/index.html) * [Doxygen](https://sight.pages.ircad.fr/sight) ## Support Please note that our GitLab is currently only available in read-only access for external developers and users. This is a restriction because of the licensing model of GitLab. Since we use an EE version, we would be forced to pay for every community user, and unfortunately we cannot afford it. This licensing model might change in the future https://gitlab.com/gitlab-org/gitlab-ee/issues/4382 though. Until then, we gently ask our community users to use our GitHub mirror to [report any issues](https://github.com/IRCAD/sight/issues) or propose [contributions](https://github.com/IRCAD/sight/pulls). You can also get live community support on the [gitter chat room](https://matrix.to/#/#IRCAD-IHU_sight-support:gitter.im). sight-25.1.0/Testing/000077500000000000000000000000001503402212300143225ustar00rootroot00000000000000sight-25.1.0/Testing/Temporary/000077500000000000000000000000001503402212300163045ustar00rootroot00000000000000sight-25.1.0/Testing/Temporary/CTestCostData.txt000066400000000000000000000000041503402212300215040ustar00rootroot00000000000000--- sight-25.1.0/activity/000077500000000000000000000000001503402212300145415ustar00rootroot00000000000000sight-25.1.0/activity/CMakeLists.txt000066400000000000000000000001101503402212300172710ustar00rootroot00000000000000add_subdirectory(navigation) add_subdirectory(io) add_subdirectory(viz) sight-25.1.0/activity/io/000077500000000000000000000000001503402212300151505ustar00rootroot00000000000000sight-25.1.0/activity/io/CMakeLists.txt000066400000000000000000000001161503402212300177060ustar00rootroot00000000000000add_subdirectory(dicom) add_subdirectory(dicomweb) add_subdirectory(activity) sight-25.1.0/activity/io/activity/000077500000000000000000000000001503402212300170045ustar00rootroot00000000000000sight-25.1.0/activity/io/activity/CMakeLists.txt000066400000000000000000000003231503402212300215420ustar00rootroot00000000000000sight_add_target(module_io_activity TYPE MODULE) add_dependencies( module_io_activity module_activity module_data module_ui module_ui_qt module_ui_icons module_io_session data ) sight-25.1.0/activity/io/activity/rc/000077500000000000000000000000001503402212300174105ustar00rootroot00000000000000sight-25.1.0/activity/io/activity/rc/plugin.xml000066400000000000000000000205611503402212300214340ustar00rootroot00000000000000 ImageSeriesExport ImageSeries Export Activity to export the selected ImageSeries. sight::module::ui::icons/export.svg sight::activity::builder::Activity ModelSeriesExport ModelSeries Export Activity to export the selected ModelSeries. sight::module::ui::icons/export.svg sight::activity::builder::Activity DicomSegmentationSurfaceExport Dicom Segmentation Surface Export Activity to export a ModelSeries as a Dicom Segmentation Surface file. sight::module::ui::icons/export.svg sight::activity::builder::Activity ExportSelection ExportDicomSelection SDBReaderIOSelectorConfig sight::module::ui::io::selector IOSelectorService config for SeriesSet reader SDBSessionReaderIOSelectorConfig sight::module::ui::io::selector Session SeriesSet reader. SDBSessionWriterIOSelectorConfig sight::module::ui::io::selector Session SeriesSet writer. FullSDBReaderIOSelectorConfig sight::module::ui::io::selector IOSelectorService config for SeriesSet reader SightDataConfig Sight data ActivityReaderConfig sight::module::ui::io::selector Activity reader ActivityWriterConfig sight::module::ui::io::selector Activity writer LandmarksConfig Landmarks Reader/Writer sight::module::io::activity::default_session_reader sight::module::io::session::reader Session reader sight::module::io::activity::default_session_writer sight::module::io::session::writer Session writer sight-25.1.0/activity/io/dicom/000077500000000000000000000000001503402212300162435ustar00rootroot00000000000000sight-25.1.0/activity/io/dicom/CMakeLists.txt000066400000000000000000000005321503402212300210030ustar00rootroot00000000000000sight_add_target(activity_io_dicom TYPE MODULE) add_dependencies( activity_io_dicom data io_dimse module_activity module_memory module_data module_ui module_ui_qt module_ui_dicom module_io_dicom module_io_dimse module_ui_icons module_service module_viz_scene3d module_viz_scene3d_qt ) sight-25.1.0/activity/io/dicom/rc/000077500000000000000000000000001503402212300166475ustar00rootroot00000000000000sight-25.1.0/activity/io/dicom/rc/configurations/000077500000000000000000000000001503402212300217015ustar00rootroot00000000000000sight-25.1.0/activity/io/dicom/rc/configurations/2d_local_preview_config.xml000066400000000000000000000056301503402212300271740ustar00rootroot00000000000000 sight::activity::io::dicom::2d_local_preview false false never sight-25.1.0/activity/io/dicom/rc/configurations/2d_pacs_preview_config.xml000066400000000000000000000052631503402212300270320ustar00rootroot00000000000000 sight::activity::io::dicom::2d_pacs_preview sight-25.1.0/activity/io/dicom/rc/plugin.xml000066400000000000000000000032411503402212300206670ustar00rootroot00000000000000 sight::io::dicom::pacs_reader_config Reader configuration to convert retrieved DicomSeries to ImageSeries sight::filter::dicom::custom::default_dicom_filter sight::activity::io::dicom::series_set_selector_config sight::module::ui::io::selector io::selector config for DICOM Filtering Activity sight::activity::io::dicom::reader_config DICOM Reader config user_selection direct sight-25.1.0/activity/io/dicomweb/000077500000000000000000000000001503402212300167415ustar00rootroot00000000000000sight-25.1.0/activity/io/dicomweb/CMakeLists.txt000066400000000000000000000005261503402212300215040ustar00rootroot00000000000000sight_add_target(activity_io_dicomweb TYPE MODULE) add_dependencies( activity_io_dicomweb module_activity module_memory module_data data module_ui module_ui_qt module_io_dicom module_ui_dicom module_io_dicomweb module_ui_icons module_service module_viz_scene3d module_viz_scene3d_qt ) sight-25.1.0/activity/io/dicomweb/rc/000077500000000000000000000000001503402212300173455ustar00rootroot00000000000000sight-25.1.0/activity/io/dicomweb/rc/configurations/000077500000000000000000000000001503402212300223775ustar00rootroot00000000000000sight-25.1.0/activity/io/dicomweb/rc/configurations/2d_preview_config.xml000066400000000000000000000055521503402212300265230ustar00rootroot00000000000000 sight::activity::io::dicomweb::2d_preview_config %PACS_SERVER_HOSTNAME%:%PACS_SERVER_PORT% false false never sight-25.1.0/activity/io/dicomweb/rc/configurations/reader.xml000066400000000000000000000146511503402212300243720ustar00rootroot00000000000000 sight::config::io::dicomweb::reader %PACS_SERVER_HOSTNAME%:%PACS_SERVER_PORT% %PACS_SERVER_HOSTNAME%:%PACS_SERVER_PORT% action_pullSeries/clicked action_pullSeries/disable pullSeriesController/update sight-25.1.0/activity/io/dicomweb/rc/configurations/writer.xml000066400000000000000000000104241503402212300244360ustar00rootroot00000000000000 sight::activity::io::dicomweb::writer %PACS_SERVER_HOSTNAME%:%PACS_SERVER_PORT% anonymizeController/job_created pushSeriesController/updated action_pushSeriesToPacs/enable action_anonymize/clicked anonymizeController/update action_pushSeriesToPacs/clicked action_pushSeriesToPacs/disable pushSeriesController/update sight-25.1.0/activity/io/dicomweb/rc/plugin.xml000066400000000000000000000032371503402212300213720ustar00rootroot00000000000000 sight::config::io::dicomweb::reader DicomWeb Reader DicomWeb Reader activity sight::module::ui::icons/pull.svg sight::activity::builder::Activity sight::config::io::dicomweb::reader_config Reader configuration to convert retrieved DicomSeries to ImageSeries sight::filter::dicom::custom::default_dicom_filter sight::activity::io::dicomweb::writer DicomWeb Writer DicomWeb Writer activity sight::module::ui::icons/push.svg sight::activity::builder::Activity sight-25.1.0/activity/navigation/000077500000000000000000000000001503402212300167005ustar00rootroot00000000000000sight-25.1.0/activity/navigation/CMakeLists.txt000066400000000000000000000000361503402212300214370ustar00rootroot00000000000000add_subdirectory(calibration) sight-25.1.0/activity/navigation/calibration/000077500000000000000000000000001503402212300211675ustar00rootroot00000000000000sight-25.1.0/activity/navigation/calibration/CMakeLists.txt000066400000000000000000000007351503402212300237340ustar00rootroot00000000000000sight_add_target(activity_navigation_calibration TYPE MODULE) add_dependencies( activity_navigation_calibration module_ui module_viz_scene3d module_viz_scene3d_qt module_activity module_service module_sync data module_ui_icons module_ui_qt module_io_matrix module_io_vision module_io_video module_io_igtl module_geometry_vision module_geometry module_sync module_data module_navigation_calibration ) sight-25.1.0/activity/navigation/calibration/README.md000066400000000000000000000046731503402212300224600ustar00rootroot00000000000000# activity::navigation::calibration Contains xml activities related to calibration (camera & tools). Optical calibration is performed using a checkerboard. Tool calibration is performed using a tracked aruco tag. > **Note:** Tool calibration is deprecated and will be removed in next versions. ## Activities - **sight::navigation::calibration::cal_extrinsic_view** Defines required configuration and service for extrinsic calibration using standard checkboard. - **calibration** Defines base configuration for standard calibration activity. - **calibrationEdition** Defines configuration for calibration edition activity. - **calibrationEdition** Defines configuration for calibration edition activity. - **calIntrinsicView** Defines configuration for the standard intrinsic camera calibration. - **displayImageConfig** Used to display an image and the detected points of the checkerboard. - **displayTwoImageConfig** Used to display two images side-by-side and the detected points of checkerboard. - **toolCalibration** Defines configuration needed to perform a tool calibration using the "fixed point" method (defines an half-sphere surrounding the tool by a motion around a fixed point and computes the radius) **deprecated** - **videoEdition** Defines base configuration for video playing. ## How to use it ### CMake ```cmake add_dependencies(my_target activity_navigation_calibration ... ) ``` ### XML Example with `sight::navigation::calibration::cal_intrinsic_view` & `sight::navigation::calibration::cal_extrinsic_view` ```xml ```sight-25.1.0/activity/navigation/calibration/rc/000077500000000000000000000000001503402212300215735ustar00rootroot00000000000000sight-25.1.0/activity/navigation/calibration/rc/configurations/000077500000000000000000000000001503402212300246255ustar00rootroot00000000000000sight-25.1.0/activity/navigation/calibration/rc/configurations/cal_extrinsic_view.xml000066400000000000000000000505021503402212300312320ustar00rootroot00000000000000 sight::navigation::calibration::cal_extrinsic_view true true Point are visible Calibration in progress Points are NOT visible ${camIndex} ${camIndex} .tiff .tiff Load left images. Load right images. addAct/clicked chessboardDetectorSrv/record_points resetAct/clicked calibrationInfoEditorSrv/reset removeAct/clicked calibrationInfoEditorSrv/remove goOpenCVAct/clicked openCVExtrinsicCalSrv/update detectionStatusSrv/change_to_orange displayImageAct/clicked calibrationInfoEditorSrv/get_selection startVideoAct/clicked videoGrabber1Srv/start_camera videoGrabber2Srv/start_camera update_loop/start pauseVideoAct/clicked videoGrabber1Srv/pause_camera videoGrabber2Srv/pause_camera resumeVideoAct/show pauseVideoAct/hide update_loop/stop resumeVideoAct/clicked videoGrabber1Srv/pause_camera videoGrabber2Srv/pause_camera resumeVideoAct/hide pauseVideoAct/show update_loop/start stopVideoAct/clicked videoGrabber1Srv/stop_camera videoGrabber2Srv/stop_camera startVideoAct/show resumeVideoAct/hide pauseVideoAct/hide stopVideoAct/disable loopVideoAct/disable loopVideoAct/uncheck update_loop/stop loopVideoAct/clicked videoGrabber1Srv/loop_video videoGrabber2Srv/loop_video exportExtrinsicAct/clicked extrinsicExporterSrv/update saveInputsAct/clicked calibrationDataWriter1Srv/open_location_dialog calibrationDataWriter1Srv/update calibrationDataWriter2Srv/open_location_dialog calibrationDataWriter2Srv/update loadInputsAct/clicked calibrationDataReader1Srv/open_location_dialog calibrationDataReader1Srv/update calibrationDataReader2Srv/open_location_dialog calibrationDataReader2Srv/update videoGrabber1Srv/camera_started videoGrabber2Srv/camera_started pauseVideoAct/show startVideoAct/hide stopVideoAct/enable loopVideoAct/enable ${camera1}/id_modified ${camera2}/id_modified videoGrabber1Srv/stop_camera videoGrabber2Srv/stop_camera ${camera1}/modified ${camera2}/modified stopVideoAct/update videoSliderSrv/position_changed videoGrabber1Srv/set_position_video videoGrabber2Srv/set_position_video videoGrabber1Srv/position_modified videoGrabber2Srv/position_modified videoSliderSrv/set_position_slider videoGrabber1Srv/duration_modified videoGrabber2Srv/duration_modified videoSliderSrv/set_duration_slider chessboardDetectorSrv/chessboard_detected detectionStatusSrv/toggle_green_red ${calibrationInfo1}/get_record displayCalibrationInfoSrv/display_image ${calibrationInfo2}/added_record ${calibrationInfo2}/removed_record ${calibrationInfo2}/reset_record calibrationDataReader2Srv/updated calibrationInfoEditorSrv/update mainView/started videoSliderView/show sight-25.1.0/activity/navigation/calibration/rc/configurations/cal_intrinsic_view.xml000066400000000000000000000464561503402212300312410ustar00rootroot00000000000000 sight::navigation::calibration::cal_intrinsic_view true true Point are visible Calibration in progress Points are NOT visible .tiff true 200 undistort videoSelectorSrv/configured_cameras videoGrabberSrv/reconfigure startVideoAct/clicked videoGrabberSrv/start_camera update_loop/start pauseVideoAct/clicked videoGrabberSrv/pause_camera resumeVideoAct/show pauseVideoAct/hide update_loop/stop resumeVideoAct/clicked videoGrabberSrv/pause_camera resumeVideoAct/hide pauseVideoAct/show update_loop/start stopVideoAct/clicked videoGrabberSrv/stop_camera startVideoAct/show resumeVideoAct/hide pauseVideoAct/hide stopVideoAct/disable loopVideoAct/disable loopVideoAct/uncheck update_loop/stop loopVideoAct/clicked videoGrabberSrv/loop_video distortionAct/clicked distorterSrv/change_state chessboardReprojectionSrv/toggle_distortion addAct/clicked chessboardDetectorSrv/record_points resetAct/clicked calibrationInfoEditorSrv/reset removeAct/clicked calibrationInfoEditorSrv/remove editionAct/clicked intrinsicEditionSrv/update goOpenCVAct/clicked intrinsicCalibrationSrv/update detectionStatusSrv/change_to_orange displayImageAct/clicked calibrationInfoEditorSrv/get_selection saveInputsAct/clicked calibrationDataWriterSrv/open_location_dialog calibrationDataWriterSrv/update loadInputsAct/clicked calibrationDataReaderSrv/open_location_dialog calibrationDataReaderSrv/update videoSelectorSrv/configured_file videoSelectorSrv/configured_stream videoSelectorSrv/configured_device startVideoAct/update videoGrabberSrv/camera_started pauseVideoAct/show startVideoAct/hide stopVideoAct/enable loopVideoAct/enable ${camera}/id_modified videoGrabberSrv/stop_camera ${camera}/modified startVideoAct/enable stopVideoAct/update videoSliderSrv/position_changed videoGrabberSrv/set_position_video videoGrabberSrv/position_modified videoSliderSrv/set_position_slider videoGrabberSrv/duration_modified videoSliderSrv/set_duration_slider videoSelectorSrv/configured_file videoSliderView/show videoSelectorSrv/configured_stream videoSelectorSrv/configured_device videoSliderView/hide ${camera}/intrinsicCalibrated distortionAct/enable chessboardDetectorSrv/chessboard_detected detectionStatusSrv/toggle_green_red ${calibrationInfo}/get_record displayCalibrationInfoSrv/display_image ${calibrationInfo}/added_record ${calibrationInfo}/removed_record ${calibrationInfo}/reset_record calibrationInfoEditorSrv/update sight-25.1.0/activity/navigation/calibration/rc/configurations/calibration.xml000066400000000000000000000161671503402212300276510ustar00rootroot00000000000000 calibration write_calibration_act/clicked calibrationWriterSrv/update chessboard_size_act/checked chessboard_size_cfg/start chessboard_size_cfg/stopped chessboard_size_act/uncheck export_act/hide previous_act/show previous_act/set_enabled previous_act/clicked export_act/hide next_act/show next_act/set_enabled next_act/clicked sight-25.1.0/activity/navigation/calibration/rc/configurations/calibration_edition.xml000066400000000000000000000225201503402212300313520ustar00rootroot00000000000000 calibrationEdition writeCalibrationAct/clicked CalibrationXmlWriter/update startVideoAct/clicked stopVideoAct/clicked pauseVideoAct/clicked loopVideoAct/clicked camera0/id_modified camera1/id_modified videoSliderSrv/position_changed videoSliderSrv/set_position_slider videoSliderSrv/set_duration_slider sight-25.1.0/activity/navigation/calibration/rc/configurations/chessboard_settings.xml000066400000000000000000000042401503402212300314040ustar00rootroot00000000000000 sight::navigation::calibration::chessboard_settings Chessboard settings sight-25.1.0/module/ui/icons/rc/about.svg000066400000000000000000000015431503402212300201640ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/add_distance.svg000066400000000000000000000034131503402212300214520ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/anonymization.svg000066400000000000000000000023631503402212300217520ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/auto.svg000066400000000000000000000222321503402212300200200ustar00rootroot00000000000000AUTO sight-25.1.0/module/ui/icons/rc/axial.svg000066400000000000000000000011001503402212300201350ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/axis.svg000066400000000000000000000064711503402212300200230ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/background.svg000066400000000000000000000136131503402212300211720ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/barrel_distortion.svg000066400000000000000000000017211503402212300225750ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/bottom.svg000066400000000000000000000010261503402212300203520ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/boundary_segmentation.svg000066400000000000000000000037741503402212300234620ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/calibration.svg000066400000000000000000000040771503402212300213460ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/center_camera.svg000066400000000000000000000026551503402212300216470ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/check.svg000066400000000000000000000007651503402212300201340ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/check_safety.svg000066400000000000000000000007311503402212300215000ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/chessboard.svg000066400000000000000000000014651503402212300211720ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/composite.svg000066400000000000000000000023541503402212300210550ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/connect.svg000066400000000000000000000012611503402212300205000ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/connection.svg000066400000000000000000000057361503402212300212210ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/contact-off.svg000066400000000000000000000114671503402212300212630ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/contact-on.svg000066400000000000000000000114671503402212300211250ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/copy.svg000066400000000000000000000017621503402212300200270ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/critical.svg000066400000000000000000000022661503402212300206470ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/cropping_box.svg000066400000000000000000000047571503402212300215550ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/cross.svg000066400000000000000000000010261503402212300201770ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/crosshair.svg000066400000000000000000000033151503402212300210460ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/ct_to_em_registration.svg000066400000000000000000000114171503402212300234360ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/ct_to_us_registration.svg000066400000000000000000000100611503402212300234560ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/custom.svg000066400000000000000000000014411503402212300203610ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/debug.svg000066400000000000000000000054331503402212300201420ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/disconnect.svg000066400000000000000000000014271503402212300212040ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/distance.svg000066400000000000000000000015721503402212300206460ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/edit.svg000066400000000000000000000014301503402212300177720ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/em.svg000066400000000000000000000102461503402212300174530ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/emergency_stop.svg000066400000000000000000000033731503402212300221000ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/eraser.svg000066400000000000000000000021361503402212300203320ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/evaluation.svg000066400000000000000000000076011503402212300212220ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/export.svg000066400000000000000000000025701503402212300203740ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/extrude.svg000066400000000000000000000565471503402212300205500ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/fiducial.svg000066400000000000000000000006201503402212300206250ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/file.svg000066400000000000000000000015231503402212300177670ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/find.svg000066400000000000000000000013421503402212300177670ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/foreground.svg000066400000000000000000000043461503402212300212300ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/frame.svg000066400000000000000000000032131503402212300201400ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/frontal.svg000066400000000000000000000005241503402212300205150ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/fusion.svg000066400000000000000000000036451503402212300203620ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/gear.svg000066400000000000000000000244521503402212300177740ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/generate_points.svg000066400000000000000000000023211503402212300222330ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/hand.svg000066400000000000000000000020671503402212300177660ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/hide_2d+t.svg000066400000000000000000000043011503402212300206020ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/hide_mesh.svg000066400000000000000000000060061503402212300207760ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/hide_negato.svg000066400000000000000000000034411503402212300213170ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/hide_volume.svg000066400000000000000000000046631503402212300213600ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/human.svg000066400000000000000000000015631503402212300201640ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/idvr.svg000066400000000000000000000062261503402212300200210ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/image_series.svg000066400000000000000000000074001503402212300215040ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/import.svg000066400000000000000000000024711503402212300203650ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/information.svg000066400000000000000000000024371503402212300214020ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/ircad_logo.png000066400000000000000000000401501503402212300211360ustar00rootroot00000000000000PNG  IHDR E7iCCPICC profile(}=HPOS*;8dNDEK`Zu0y4iHR\ׂ?Ug]\AIEJ/)}Qa5 e11[ U'ҋx{~Y\JdO$2ݰ7g7->q$x .ƹ3CF&5O",w0+* qXQ5.+8k_k+iS"%$5Q)&Rt8$drȱ*TH~,LOIbc@`hmض'j$G6pq=r~%Cr$?P(gM9`[s:Y-x=9wrxY/WbKGD pHYs.#.#x?vtIME   VR IDATxktS IJS2cL KgJM2Cw/ds]NV_4Z$yt8 LVvZLHCi0`!mR[ya1YdIY+ bh\gy+6O,֢m3iʕ+`ZeK{VSBQ3D+>JaQQ Ly_M_cP=Ă=׬dd0 V55{| f1:k&Ą00000 L L L L Laaaaa@@@@@LZ$%`ra|[_s09 L L LD٣}j x@@@(F0( #cpڵNOp eO$$_KuIJ@V.I돚E~.I η57pD$#jBhiwPUc40}F^@]bIS"Il }yhJ2!ΐdd;r8p(H5oBFog1vvHz;:4 hP ۚb& n$-gu)̂YZp{W_9x#ddNih١v__ }1zqHRz*&G7}_G!RG#58}}@ %Uf;Lb<%1f#憔/*%6OеBE!Z[9 b? 4ȔÿzG7 %ihSz-P2 z;;|_#o=]<$ B2@K y +yHu0vIVwr)0OKѬ IR0 ?8[]lw ԆC#leucmb:=#GݤGކPHH(4 5 IE (4Uu3ۈ[$6lADzs͂hNO 7^%UWoZԇ8u ]7L9d/-,SHpZs]ǾppYl6.XVwf 60]X5$(RTZU1 4fj)OQ9uĭ?v B׳ j 0a7-ULaJ5 J3 Mvvv/w+BM]Rf3rJd+.1Ѧ BFh8.Өlv8]6Oqx[aBF›or%_UCCt&honpE%i{itrq Fauh-ϝut㮢 ۚRB) qg$=MiYs1{ooދw{ Yp 2痍&Bnnyr+"7Kᑳ&ȍ)|-a9er,[sħ[)m+mҙ,_oH@Oqr,v%7Iޱ"!L coz+׮@mux]5,NuzG5؃Fc+lH%Dȶe+{ LTJڝd% ]}L"q?u0 s֮HZyW[l])MH yOυaknaؙƗf=n ]X??p_S?u%v6 K,MY]fO_]lj Lk[9˷Z3{h4%K D/YݣG55{v}ޟSZ 0}>78a&u7R{{{mM44.6e@Ѹ(>.ݾi L#Vw!˷F$&%fzنP'pܶM L{J"Sny[.I55{\I~&$wYmzQW8:Q=];1*D=`H&@Y_qr"Rìn։'"8I}.o M)Q L܅kuZj4㍿j)cfsrB!vv(֢@@}$)r<%pCYNlY7p*L'OuM͞'v4'5y #s+Yl6Q LX tI-Fip ]S(1vvFZ42C7\ u/np̿(ե}h)- :HjI'%sa@T甖ѻiz#0aH%)ȻtKg{tCnn]X d'3\N\N͛wUhjj%c B(rJQoԤ H؞h2k&P1|nd}ݣȗvƺ@cJNoBUyUmݢo[Z|㍓[oE;y_7P^Z$}9elH܅z. NĂjB!}jk:[K.gb?ӷ-%uWV.0y +4K=F9Y+8*.IX+hd_㏺NIzqq͛"FZ|o[~KgaVw._?@kdLϟp^tCW~3&kAD!T,wvEv2s:w=.GC_v4uCWbI>Iԃkyy@wIƶUU;%omt[l6}JEkc|ŋuLy3өUViժUjjjһG㽙rV%L gŏJZUs-[C; VvVZ[nE{uM3>/?Xx˴ąj<:p?&ܕQD;5^>0r=^ sfv]KNԁCs֮k&/v(o4o<-zO &шpIfçR݅s"+W jT'YߙF2~N ҙӻ0ډ,S@`䗽7&\lu0&ܟBABۚd1euiN /%bT:f #xM͞s@A]8~L3inUܑ6c$zH5W >s+yzᗿ\u[MyljooR i0埚ݺ_0\͞ JŪO_<ӸӱlL @mnp0=sg|W=q d>ZG ą /tfp :ReGsЬ0GI8]6Ti)3.6Nƥ!PIi!ۚ[Qq8<20^U捻 {ΝH4ai7h}[}"3[/ǥ3`ooy$MDFC_8 ~oy"f=a"m!iJș?_9e؆1|Ύ8uQ'R=:&f.iҿ[0MO?c2Ӷji=*1+vMT)i][XE,F2nBm-.H/Z &O&24ײU_?^0̄Q UUװEHᕴ3K4릛CϿmzގ.(a7O4*HMӜ1jkQ;y#!/iS˿f唖Q(@ B12q~ KOaMq={;:4o=S{x'n+0V0 T`ݜBm-cu 6۽/` ŕ*,.IOSJ0),{?~BB}g]yӞ~=4|;]Vvz#L Ldp=B!9rrƛVOCbW/_@ =siM@|!F%л!sz8a !'6p"t󴜪jkad0o|~&{C0$<%9k1 HQx& Esn@('3H7\ mͦmcQksnUՎ j(\| )]Vw6nb& 02TQsԻ1+ 6S1J+|/A\3#HeLIc6&.Iue+|VP ai L@`'b+ŦM0Hv=A@*߫a)/k6#@f onydl#ޒ:5X $0P[$}hގkLzW 1 $sPGPHvv(ڪv].Pv]YyvxG?~) <> ?$LH2ͦ5o<y%K}>wVO7*|˚7o\ Ѳ~-ASP6?],vμ!L ]LSւzzF#Ca yJ.j c4FzCeb-غE. qOW,&L eP'vX aXs7n0ƹ/\8~L}gh-^6aHCO*6՛yJDRIsogad)Q#\mDͽk !:|簲n-~e9;U>joϤb3IR(LhpOFljϬ bT1_ԛ:VߙFVݺ; "ڠ"說05Չ~'dá̚n R]u#z{%SB nXơtvv7ЪD4K/鐤B&z;;Lα:eG.b v.PG c.ծW3i ǏI ޻uI/H_UM0115jO~<.ERJ@(3.Ї~  <9y ]gA|kcdG#$B2F配mEfm\:3a{XL_&cxւ5#lPL[C)DwZjdjg$ڑ<WC0`i2ւΚXog- L&jmn7j󔨽7 ~&P1lǢ/ĺei7F}DUՎMi/4]yJ_NBnh"4 $e;jz&55{ AƫfӒ 1NZLi#cg"Q`dHNljidC+NXEe+Y+!}ޱ) ,Ԣo<-6.Zg/F&<7\oHm!;n_{NMh9_o3Wͷ /p{Xipщ=4z >[1 wZMFsJdFNuQ3e.PH=;7FOTU?Y ?ۚzM&VFoa{Ni03=SZw}IM3PpYlJ?[oyhx|>WoG#GbG"aXVG@:qL2թ7-U{{Jj}F=HMrK4Abo yVՏ3^Z#;gݴK@:/.SCo5wS3ݶS@ќ%t I28.sNuuz{;7ۙDRXC\{-Ї~8|tNkmB!郉voH #q$e9 uޕff&}2ӮNv8GN0)Kz3`K)| ; t)"i32ؙ)N ,]ͶӷzNjCF&xjGs SBMN ӎN<8oN<R*LW3*A@fiVouN6O^}g9 Äy{B!Z4o޼1gipl 0B-}e+߇ )F<.m0L=xS| YN]]]US3b7oo^ogxABHQɾ So֑ ip%7LJ{pIv=50σT&"Dz:[N>F'2f"jk1vJݛ")?A"qη52 I_T-}zQipWTY`aHKLnBfti?<i.B&V}.ky%K}`qϗ_^3 w^ ڋa:I|:?ꗿe5ӝSx'Sg[C)~&_gdUyEHt}/g;ݛ58.~%埦 # mxY_|ݣG'YRU=-C4kd^R}5|\1Jz$|k<. .v|?(xm3vf&η55ݔ^ kԁZI~!NԎ]F}&)|6g55{ Ivǽw%3AҦhq=lL.䞞/LPoÄ4SmUՎiE-ɱJmM\pݸ $yP ,bBvdK~J")Ǔ\罒kp@O C6HPZl ;u#sϟn55{%q Na/PCv1B$U-{"Oҟkj&sf. b͠`&lE~ L^`r3,B1wX(ȦHRζ-.)IҢ}/q LHE۶j).}0@ b&&&&d0N^RKkB~v_K  a@狀 a02 m\|XY%{AYU\3{O5i>@ 0j]TbYU ހ- ;uij2;uzܲoXw*)/}D-yz8:c[bup:Xu%Ru#S~Ⱥ{Q-gbjXF|r禤OsI O>ϟz{_&{BoB?ֆXNl2԰Xpkmq:Ѓ}qh$auoX{Q^RNhdݶU _TَWo#87:`r=lTAbx8xY~O?HHz^׍ٻ 3W.uI7v-Nygq9۶1{6nAB*~v4u"r-8yLs_Sζ-S1l+3&ݐLsKVIq̍6:ii=^7uQEE:$?<\_ǵ-NPHL*)g L@F.zDbdܟyo=m#SMjp=?A\N)7KugR7't] H+4[Zս1s^YRG#{oev .Y%1=ICw:l2@m˴#nN"wVVϻ Y:sz} |Fgɝz{A]|'Sm3P  >I=뢊x |@wshve'X \\s_KWD LfNF;ldC:^dv7H@ҪfZotL{Kc6rmϏg1'>{B\<fζ-},}{sm*LDYsϪƥQTD H*)̕cj6D רmZ? >1XΘ&7K`z0nkD$Lt`4 Y~3M MFS5/g9~G,S `XӅ2rpɧ76"Xu2r:mq:S}ӯ ɝke}5S:-8$>ʚqt{iJӕb}m`*F_]Pe}_'jsG&u uK=p>[Z|C cߏiycS~u=ݘ~~VI79}zף5oigRe4'w=U+DXUhӡbeh1԰ry #Nb8ٺbJ=oY~/m^ySQ“OE}$qC{Km)2Kmڸ@`Z^JHC)^jZN6|mJ2cފ7ue2#ChHzQ{ 1Ruc+W&&`4@9 L ًw'$"J @@}NOiԩI,a"րJE L@y@Jo]TE e]T+'mTjj~ߩ r@b_Cu5ĺ%X3'&`dHbYU n?:m'$NG̍Thzݭ*~`qYP0c̉ Q=\\+>8fSY/ >0i,Q=3W.WmZFoK4a<L L@"3W.׬GG30J ן7 >0`. t9%AG|q>ʚwµb] #1ry\DFBofnza\~HLS"g\$S_K am֣ߋҾ(8ab*eܳ=ը+ZZ#;7Q& EΊE|Q7(J)b]Doq:mKLn۪+{##K巏\~b.9 Hw b{- z {I ө܇"AcxkDF 1ml𵸞 SlA 7L*HDNƵl*&\=;3Ž151O[ua};ryF}`L^nK(1t{)9۶H҄DS_å}?z1sIMղ8Ϯ VuGցH3 aRm?8orz `mf\D>rԩ+˾armRox:"51XA"1m“OEU_&kuQf=qh3W.?`zS6yJ6ӽ bu=]}.zYVrbn:{ 0w_4srq`n߰ݛ&',ۆ\|~巏1/ٶE9۶\5U)8.u(N*$B#>\w?KŁwF=zxxfH;9@ zO5i3\hF“Oh}zSfsza-Y ؁@ ^b3nJXJ Jr@{1 Sίt&>ʚi>)Շ}/;7MS Hv6Q==oЧw~]zoNv=ըaRӌo~5O.}DeMR;7sLD}c5>:r&d=@ Ͼ'wnJhoiU_YʑwV* O> At#C[uNvD` P_c6?CE+gۖ OyT>oQVIɾaݔw<:Kd!̕˕mf\>-_[ZM 5/΂+WVIf\e/ZxͶsZZw4p&uQJh'T=ըiN3$l+WL[Z:iSJ SCk&$[IhW0 1 aaaa@@@V΍QCIENDB`sight-25.1.0/module/ui/icons/rc/ircad_white.svg000066400000000000000000000033221503402212300213310ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/kidney.svg000066400000000000000000000036071503402212300203400ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/layers.svg000066400000000000000000000020331503402212300203440ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/left.svg000066400000000000000000000007201503402212300200000ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/light.svg000066400000000000000000000034221503402212300201570ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/liver.svg000066400000000000000000000024211503402212300201670ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/load.svg000066400000000000000000000011001503402212300177560ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/localization.svg000066400000000000000000000033021503402212300215350ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/lock.svg000066400000000000000000000017321503402212300200020ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/loop.svg000066400000000000000000000022641503402212300200240ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/lower_point.svg000066400000000000000000000026131503402212300214120ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/maximize.svg000066400000000000000000000032251503402212300206740ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/menu.svg000066400000000000000000000010571503402212300200160ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/merge.svg000066400000000000000000000005301503402212300201440ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/mesh.svg000066400000000000000000000014201503402212300200000ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/minus.svg000066400000000000000000000007261503402212300202070ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/model_series.svg000066400000000000000000000037221503402212300215250ustar00rootroot000000000000003D sight-25.1.0/module/ui/icons/rc/move_down.svg000066400000000000000000000010031503402212300210360ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/move_left.svg000066400000000000000000000011301503402212300210220ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/move_right.svg000066400000000000000000000010721503402212300212120ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/move_up.svg000066400000000000000000000010721503402212300205210ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/no_windowing.svg000066400000000000000000000016501503402212300215520ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/opacity.svg000066400000000000000000000046271503402212300205300ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/open.svg000066400000000000000000000010261503402212300200070ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/organs.svg000066400000000000000000000123671503402212300203510ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/orientation.svg000066400000000000000000000016711503402212300214070ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/paintbrush.svg000066400000000000000000000016331503402212300212310ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/pause.svg000066400000000000000000000012741503402212300201700ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/perspective.svg000066400000000000000000000016631503402212300214060ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/phantom.svg000066400000000000000000000105231503402212300205160ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/pincushion_distortion.svg000066400000000000000000000017201503402212300235040ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/play.svg000066400000000000000000000007001503402212300200110ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/plugin.xml000066400000000000000000000000511503402212300203420ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/plus.svg000066400000000000000000000014301503402212300200300ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/points.svg000066400000000000000000000016751503402212300203740ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/power.svg000066400000000000000000000014641503402212300202100ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/pull.svg000066400000000000000000000030361503402212300200250ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/push.svg000066400000000000000000000037031503402212300200310ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/question.svg000066400000000000000000000036011503402212300207160ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/ramp.svg000066400000000000000000000020601503402212300200040ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/record.svg000066400000000000000000000015601503402212300203270ustar00rootroot00000000000000REC sight-25.1.0/module/ui/icons/rc/record_2d+t.svg000066400000000000000000000042201503402212300211470ustar00rootroot00000000000000REC sight-25.1.0/module/ui/icons/rc/record_frame.svg000066400000000000000000000057531503402212300215110ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/register_points.svg000066400000000000000000000054701503402212300222750ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/remove_distance.svg000066400000000000000000000032731503402212300222230ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/reset.svg000066400000000000000000000012311503402212300201660ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/reset_safety.svg000066400000000000000000000011471503402212300215470ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/restore.svg000066400000000000000000000031731503402212300205360ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/revert.svg000066400000000000000000000020351503402212300203560ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/right.svg000066400000000000000000000007161503402212300201700ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/robot.svg000066400000000000000000000046201503402212300201760ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/rotate_left.svg000066400000000000000000000010611503402212300213550ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/rotate_right.svg000066400000000000000000000010631503402212300215420ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/sagittal.svg000066400000000000000000000006231503402212300206600ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/save.svg000066400000000000000000000006101503402212300200020ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/segment.svg000066400000000000000000000066111503402212300205150ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/segmentation.svg000066400000000000000000000037541503402212300215550ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/select.svg000066400000000000000000000022621503402212300203300ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/settings.svg000066400000000000000000000013401503402212300207050ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/show_2d+t.svg000066400000000000000000000044701503402212300206600ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/show_distance.svg000066400000000000000000000022411503402212300217000ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/show_mesh.svg000066400000000000000000000070341503402212300210470ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/show_negato.svg000066400000000000000000000045001503402212300213630ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/show_volume.svg000066400000000000000000000056111503402212300214210ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/sight_logo.svg000066400000000000000000000026021503402212300212050ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/snow_flake.svg000066400000000000000000000063531503402212300212060ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/sorter.svg000066400000000000000000000004721503402212300203700ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/split.svg000066400000000000000000000006161503402212300202050ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/splitter.svg000066400000000000000000000004671503402212300207240ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/square.svg000066400000000000000000000022571503402212300203550ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/status_green.svg000066400000000000000000000024611503402212300215550ustar00rootroot00000000000000 image/svg+xml sight-25.1.0/module/ui/icons/rc/status_orange.svg000066400000000000000000000024611503402212300217300ustar00rootroot00000000000000 image/svg+xml sight-25.1.0/module/ui/icons/rc/status_red.svg000066400000000000000000000024611503402212300212270ustar00rootroot00000000000000 image/svg+xml sight-25.1.0/module/ui/icons/rc/stomach.svg000066400000000000000000000051501503402212300205060ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/stop.svg000066400000000000000000000007341503402212300200400ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/swap_target.svg000066400000000000000000000033471503402212300213760ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/swap_view.svg000066400000000000000000000046361503402212300210640ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/text.svg000066400000000000000000000035441503402212300200410ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/tf.svg000066400000000000000000000016071503402212300174640ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/tf1.svg000066400000000000000000000036171503402212300175500ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/tf2.svg000066400000000000000000000034101503402212300175400ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/top.svg000066400000000000000000000010261503402212300176500ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/tracking.svg000066400000000000000000000032131503402212300206500ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/trash.svg000066400000000000000000000014241503402212300201710ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/ultrasound.svg000066400000000000000000000042211503402212300212460ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/unlock.svg000066400000000000000000000022121503402212300203370ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/upper_point.svg000066400000000000000000000026131503402212300214150ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/us.svg000066400000000000000000000027231503402212300175020ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/us_mouse.svg000066400000000000000000000035651503402212300207170ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/volume.svg000066400000000000000000000034671503402212300203700ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/wait.svg000066400000000000000000000006321503402212300200140ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/warning.svg000066400000000000000000000020171503402212300205140ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/welcome.svg000066400000000000000000000011731503402212300205040ustar00rootroot00000000000000 sight-25.1.0/module/ui/icons/rc/windowing.svg000066400000000000000000000021331503402212300210530ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/000077500000000000000000000000001503402212300152335ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/CMakeLists.txt000066400000000000000000000023111503402212300177700ustar00rootroot00000000000000sight_add_target(module_ui_qt TYPE MODULE) add_dependencies(${SIGHT_TARGET} module_ui module_ui_icons module_data module_io_session) find_package(Qt6 QUIET COMPONENTS Multimedia MultimediaWidgets Widgets SvgWidgets Help REQUIRED) target_link_libraries( ${SIGHT_TARGET} PUBLIC Qt6::Multimedia Qt6::MultimediaWidgets Qt6::Widgets Qt6::SvgWidgets Qt6::Help ) set_target_properties(${SIGHT_TARGET} PROPERTIES AUTOMOC TRUE) target_compile_definitions(${SIGHT_TARGET} PUBLIC "QT_NO_KEYWORDS") # Copy resources for styles file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/${SIGHT_MODULE_RC_PREFIX}/${SIGHT_TARGET}") qt_add_binary_resources( module_ui_qt_FLATDARK_RCC "rc/flatdark.qrc" DESTINATION "${CMAKE_BINARY_DIR}/${SIGHT_MODULE_RC_PREFIX}/module_ui_qt/flatdark.rcc" ) qt_add_binary_resources( module_ui_qt_IRCAD_RCC "rc/noctura.qrc" DESTINATION "${CMAKE_BINARY_DIR}/${SIGHT_MODULE_RC_PREFIX}/module_ui_qt/noctura.rcc" ) add_dependencies(${SIGHT_TARGET} module_ui_qt_FLATDARK_RCC) add_dependencies(${SIGHT_TARGET} module_ui_qt_IRCAD_RCC) target_link_libraries(${SIGHT_TARGET} PUBLIC activity app geometry_data io io_joystick ui_qt) if(SIGHT_BUILD_TESTS) add_subdirectory(test/ut) endif(SIGHT_BUILD_TESTS) sight-25.1.0/module/ui/qt/README.md000066400000000000000000000130061503402212300165120ustar00rootroot00000000000000# sight::module::ui::qt This module contains many user interface services implemented with Qt. The services are sorted in different folders: - **activity**: interactions with application activities - **calibration**: camera properties - **com**: communication between services - **image**: `sight::data::image` management or display - **metrics**: tools to measure distances or landmarks - **model**: `sight::data::model_series` management or display - **reconstruction**: `sight::data::reconstruction` management or display - **series**: `sight::data::series` or `sight::data::series_set` management - **video**: video playback interactions - **viz**: visualization settings or properties The main folder is reserved for other services that do not fit any of the category above. ## Services Here is the list of uncategorised services: - **launch_browser**: launches a browser with a url. - **list_view**: draws a list of string elements. - **notifier**: displays pop-up notifications in an application. - **parameters**: generic widget to modify simple values such as booleans, integers, floats and colors. - **preferences_configuration**: shows a dialog to configure preferences of an application. - **selection_menu_button**: shows a menu button. The user can select one item in the menu. - **show_about**: shows the about frame. - **show_help**: shows the help content. - **status**: shows a colored square (red, orange, green) representing a status. - **style_selector**: selects a CSS style. - **text_status**: displays and update values (int, double or string) in a `QLabel`. ### Activity - **activity::dynamic_view**: manages tab views containing activities. - **activity::launcher**: launches an activity according to the selected data. - **activity::selector**: displays a selector allowing to choose between different activities. - **activity::sequencer**: displays an activity stepper that allows to follow the workflow of an application. - **activity::view**: displays activities in a single view. - **activity::wizard**: selects the data required by an activity in order to create an `Activity`. ### Calibration - **calibration_info_editor**: handles calibration points acquisition. - **camera_config_launcher**: adds cameras to a camera series and launches configurations to calibrate them. - **camera_information_editor**: displays the intrinsic calibration of a camera. - **camera_set_editor**: displays the extrinsic calibration of a camera series. - **display_calibration_info**: displays calibration images. - **images_selector**: allows to add images to a `data::vector` from a `data::frame_tl`. - **intrinsic_edition**: sets the intrinsic parameter information. It displays a dialog to change the resolution of your calibration, and computes the new intrinsic parameters. - **optical_center_editor**: shows sliders to configure an intrinsic camera calibration - **update_intrinsic_dialog**: displays a `QDialog` in which the user can change the calibration resolution which leads to an automatic computation of the new calibration parameters. By validating the user entry, the calibration will be updated. ### Com - **signal_button**: shows a button and send a signal when it is clicked. - **signal_shortcut**: sends a signal when the associated shortcut is activated. ### image - **image_info**: displays image pixel information when it receives the mouse cursor coordinates. - **image**: displays an image. - **slice_index_position_editor**: allows to change the slice index of an image. - **transfer_function**: editor to select a transfer function. - **window_level**: allows to change the min/max value of windowing. ### Metrics - **distance**: allows to show distances in a generic scene. Represented by a button. - **landmarks**: defines a graphical editor to edit landmarks. ### Model - **model_series_list**: editor displaying the list of the organs in a ModelSeries. - **organ_transformation**: displays the organs list and allow an interactive selection to set the corresponding meshes in a map. ### Reconstruction - **representation_editor**: displays a widget to change the reconstruction representation (surface, point, edge, ...). - **organ_material_editor**: displays a widget to change the reconstruction material (colour and transparency). ### Series -**InsertSeries**: Used as a placeholder in `module::ui::qt::editor::selector` UI to insert create and insert new series. -**selector**: represents the `Series` in a hierarchical view (Study/Patient->Series) -**selector_model**: represents the `selector` model. -**selector**: shows information about the medical data. It allows to manipulate (select, erase, ...) studies and series. -**viewer**: displays a preview of the selected series in the `Vector`. For the moment, it works only on a single selection. ### Video - **camera_device_dlg**: displays a `QDialog` to choose camera device - **camera**: allows to select the device to use. It updates the data camera identifier - **slider**: allows to draw a slider. It is designed to be used with `frame_grabber` to browse a video. ### Viz - **point_editor**: displays point information. - **matrix_viewer**: defines a viewer for a `data::matrix4`. - **snapshot_editor**: allows to snap shot a generic scene. It is represented by a button. - **transform_editor**: regulates the position and rotation defined in a transformation matrix. ## How to use it ### CMake ```cmake add_dependencies(my_target module_ui_qt ... ) ``` ### XML Please consult the [doxygen](https://sight.pages.ircad.fr/sight) of each service to learn more about its use in XML configurations.sight-25.1.0/module/ui/qt/activity/000077500000000000000000000000001503402212300170675ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/activity/data_view.cpp000066400000000000000000001215041503402212300215410ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2016-2025 IRCAD France * Copyright (C) 2016-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "data_view.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::activity { const int data_view::UID_ROLE = Qt::UserRole + 1; using sight::activity::extension::activity_info; using sight::activity::extension::activity_requirement; //----------------------------------------------------------------------------- data_view::data_view(QWidget* _parent) : QTabWidget(_parent) { } //----------------------------------------------------------------------------- data_view::~data_view() = default; //----------------------------------------------------------------------------- void data_view::clear() { m_imported_object.clear(); m_tree_widgets.clear(); QTabWidget::clear(); } //----------------------------------------------------------------------------- bool data_view::eventFilter(QObject* _obj, QEvent* _event) { // get dropped data in tree widget if(_event->type() == QEvent::Drop) { auto* drop_event = static_cast(_event); auto index = static_cast(this->currentIndex()); activity_requirement requirement = m_activity_info.requirements[index]; QPointer tree = m_tree_widgets[index]; // get dropped item from event mimedata const QMimeData* q_mime_data = drop_event->mimeData(); QByteArray encoded = q_mime_data->data("application/x-qabstractitemmodeldatalist"); QDataStream stream(&encoded, QIODevice::ReadOnly); QList item_list; QTreeWidgetItem* item = nullptr; // Get the dropped item while(!stream.atEnd()) { int row = 0; int col = 0; QMap role_data_map; stream >> row >> col >> role_data_map; if(col == 0) { item = new QTreeWidgetItem(); item_list.push_back(item); } SIGHT_ASSERT("item is null", item != nullptr); QList keys = role_data_map.keys(); for(int key : keys) { item->setData(col, key, role_data_map[key]); } } // check if the limit number of data is exceeded auto nb_child = tree->topLevelItemCount() + item_list.size(); if(static_cast(nb_child) > requirement.max_occurs) { QMessageBox::warning( this, "Drop", "The maximum number of element is reached.\n" "You must remove one before adding another." ); } else { // add the dropped item in the tree for(QTreeWidgetItem* item_to_add : item_list) { item_to_add->setFlags(item_to_add->flags() & ~Qt::ItemIsDropEnabled); std::string uid = item_to_add->data(int(column_commun_t::id), UID_ROLE).toString().toStdString(); if(!uid.empty()) { // insert the object if it is in the required type data::object::sptr obj = std::dynamic_pointer_cast(core::id::get_object(uid)); if(obj && obj->is_a(requirement.type)) { // Insert the new object tree->addTopLevelItem(item_to_add); } } } } return true; } if(_event->type() == QEvent::KeyPress) { auto* key_event = static_cast(_event); if(key_event->key() == Qt::Key_Delete) { this->remove_selected_objects(); return true; } } // standard event processing return QObject::eventFilter(_obj, _event); } //----------------------------------------------------------------------------- void data_view::fill_information(const activity_info& _info) { m_activity_info = _info; this->clear(); const activity_info::requirements_t& req_vect = m_activity_info.requirements; for(const auto& req : req_vect) { auto* const layout = new QVBoxLayout(); auto* const widget = new QWidget(); widget->setLayout(layout); auto* const info_layout = new QHBoxLayout(); layout->addLayout(info_layout); auto* const type_layout = new QVBoxLayout(); auto* const txt_layout = new QVBoxLayout(); info_layout->addLayout(type_layout); info_layout->addSpacerItem(new QSpacerItem(20, 0)); info_layout->addLayout(txt_layout, 1); auto iter = m_object_icons.find(req.type); if(iter != m_object_icons.end()) { const QString filename = QString::fromStdString(iter->second); this->addTab(widget, QIcon(filename), QString::fromStdString(req.name)); auto* const icon = new QLabel(); icon->setAlignment(Qt::AlignHCenter); QPixmap pixmap(filename); icon->setPixmap(pixmap.scaled(100, 100, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); type_layout->addWidget(icon); } else { this->addTab(widget, QString::fromStdString(req.name)); } auto* const type = new QLabel(QString("%1").arg(QString::fromStdString(req.type))); type->setAlignment(Qt::AlignHCenter); type_layout->addWidget(type); auto* const name = new QLabel(QString("

%1

").arg(QString::fromStdString(req.name))); name->setStyleSheet("QLabel { font: bold; }"); txt_layout->addWidget(name); auto* const description = new QLabel(QString::fromStdString(req.description)); description->setStyleSheet("QLabel { font: italic; }"); txt_layout->addWidget(description); txt_layout->addStretch(); auto* const nb = new QLabel(); nb->setStyleSheet("QLabel { font: bold; }"); layout->addWidget(nb); QPointer tree = new QTreeWidget(); m_tree_widgets.push_back(tree); if(req.max_occurs == 0) { nb->setText("No object is required, it will be automatically created."); tree->setEnabled(false); } else if(req.min_occurs == 1 && req.max_occurs == 1) { nb->setText("One object is required: "); } else { QString nb_obj("Number of required object"); if(req.max_occurs == std::numeric_limits::max()) { nb_obj.append(QString(" >= %1").arg(req.min_occurs)); } else { nb_obj.append(QString(": [%1-%2]").arg(req.min_occurs).arg(req.max_occurs)); } nb->setText(nb_obj); } QStringList headers; headers << "" << "Description"; auto* const tree_layout = new QHBoxLayout(); auto* const button_layout = new QVBoxLayout(); if(req.type == "sight::data::string" || req.type == "sight::data::boolean" || req.type == "sight::data::integer" || req.type == "sight::data::real" || req.type == "sight::data::matrix4") { auto* const button_new = new QPushButton("New"); button_new->setToolTip("Create a new empty object"); button_layout->addWidget(button_new); QObject::connect(button_new, &QPushButton::clicked, this, &data_view::create_new_object); } auto* const button_add = new QPushButton("Load"); auto* const button_remove = new QPushButton("Remove"); auto* const button_clear = new QPushButton("Clear"); button_layout->addWidget(button_add); button_add->setToolTip(QString("Load an object of type '%1'.").arg(QString::fromStdString(req.type))); // If the type is a Series, we add a button to import the data from a series_set, // we also improve the tree header by adding more informations. data::object::sptr new_object = data::factory::make(req.type); if(new_object && std::dynamic_pointer_cast(new_object)) { auto* const button_add_from_sdb = new QPushButton("Import"); button_layout->addWidget(button_add_from_sdb); button_add_from_sdb->setToolTip( QString( "Import a series_set and extract the N first objects of type '%1', with " "N the maximum number of required objects." ). arg(QString::fromStdString(req.type)) ); QObject::connect(button_add_from_sdb, &QPushButton::clicked, this, &data_view::import_object_from_sdb); headers.clear(); headers << "" << "Name" << "Sex" << "Birthdate" << "Modality" << "Description" << "Study description" << "Date" << "Time" << "Patient age"; if(std::dynamic_pointer_cast(new_object)) { headers << "Body part examined" << "Patient position" << "Contrast agent" << "Acquisition time" << "Contrast/bolus time"; } } button_layout->addWidget(button_remove); button_remove->setToolTip(QString("Remove the selected objects")); button_layout->addWidget(button_clear); button_clear->setToolTip(QString("Remove all the objects")); button_layout->addStretch(); QObject::connect(button_add, &QPushButton::clicked, this, &data_view::import_object); QObject::connect(button_remove, &QPushButton::clicked, this, &data_view::remove_selected_objects); QObject::connect(button_clear, &QPushButton::clicked, this, &data_view::clear_tree); tree_layout->addLayout(button_layout); tree_layout->addWidget(tree, 1); tree->setHeaderLabels(headers); tree->setAlternatingRowColors(true); tree->setAcceptDrops(true); tree->setDragDropMode(QAbstractItemView::DropOnly); tree->viewport()->installEventFilter(this); tree->installEventFilter(this); QObject::connect( tree.data(), &QTreeWidget::itemDoubleClicked, this, &data_view::on_tree_item_double_clicked ); layout->addLayout(tree_layout, 1); //TODO better management of map container } for(int i = 1 ; i < this->count() ; ++i) { this->setTabEnabled(i, false); } } //----------------------------------------------------------------------------- void data_view::fill_information(const data::activity::sptr& _activity) { activity_info info = sight::activity::extension::activity::get_default()->get_info( _activity->get_activity_config_id() ); m_activity_info = info; this->fill_information(info); for(std::size_t i = 0 ; i < m_activity_info.requirements.size() ; ++i) { activity_requirement req = m_activity_info.requirements[i]; auto obj = _activity->get(req.name); if(obj) { if((req.min_occurs == 0 && req.max_occurs == 0) || (req.min_occurs == 1 && req.max_occurs == 1) || req.create) { this->add_object_item(i, obj); } else { if(req.container == "vector") { data::vector::sptr vector = std::dynamic_pointer_cast(obj); if(vector) { for(const auto& sub_obj : *vector) { this->add_object_item(i, sub_obj); } } else { SIGHT_ERROR("Object param '" + req.name + "' must be a 'data::vector'"); } } else // container == map { data::map::sptr map = std::dynamic_pointer_cast(obj); if(map) { for(const auto& sub_obj : *map) { this->add_object_item(i, sub_obj.second); } } else { SIGHT_ERROR("Object param '" + req.name + "' must be a 'data::map'"); } } } } this->setTabEnabled(int(i), true); } } //----------------------------------------------------------------------------- data::object::sptr data_view::check_data(std::size_t _index, std::string& _error_msg) { data::object::sptr object; activity_requirement req = m_activity_info.requirements[_index]; QPointer tree = m_tree_widgets[_index]; bool ok = true; if((req.min_occurs == 1 && req.max_occurs == 1) || (req.min_occurs == 0 && req.max_occurs == 0) || req.create) // One object is required { if(tree->topLevelItemCount() == 1) { QTreeWidgetItem* item = tree->topLevelItem(0); std::string uid = item->data(int(column_commun_t::id), data_view::UID_ROLE).toString().toStdString(); data::object::sptr obj = std::dynamic_pointer_cast(core::id::get_object(uid)); if(obj && obj->is_a(req.type)) { object = obj; } else { _error_msg += "\n - The parameter '" + req.name + "' must be a '" + req.type + "'."; } } else { if((req.min_occurs == 0 && req.max_occurs == 0) || req.create) { object = data::factory::make(req.type); } else { _error_msg += "\n - The parameter '" + req.name + "' is required but is not defined."; } } } else // optional object or several objects { auto nb_obj = static_cast(tree->topLevelItemCount()); if(nb_obj < req.min_occurs) { _error_msg += "\n - The parameter '" + req.name + "' must contain at least " + std::to_string(req.min_occurs) + " elements."; } else if(nb_obj > req.max_occurs) { _error_msg += "\n - The parameter '" + req.name + "' must contain at most " + std::to_string(req.max_occurs) + " elements."; } else { if(req.container == "vector") { data::vector::sptr vector = std::make_shared(); for(unsigned int i = 0 ; i < nb_obj ; ++i) { QTreeWidgetItem* item_data = tree->topLevelItem(int(i)); std::string uid = item_data->data(int(column_commun_t::id), UID_ROLE).toString().toStdString(); data::object::sptr obj = std::dynamic_pointer_cast(core::id::get_object(uid)); if(obj && obj->is_a(req.type)) { vector->push_back(obj); } else { ok = false; _error_msg += "\n - The parameter '" + req.name + "' must be a " + req.type + "."; } } if(ok) { object = vector; } } else // container == map { data::map::sptr map = std::make_shared(); for(unsigned int i = 0 ; i < nb_obj ; ++i) { QTreeWidgetItem* item_data = tree->topLevelItem(int(i)); std::string uid = item_data->data(int(column_commun_t::id), UID_ROLE).toString().toStdString(); data::object::sptr obj = std::dynamic_pointer_cast(core::id::get_object(uid)); if(obj && obj->is_a(req.type)) { std::string key = req.keys[i].key; (*map)[key] = obj; } else { ok = false; _error_msg += "\n - The parameter '" + req.name + "' must be a " + req.type + "."; } } if(ok) { object = map; } } } } if(object && !req.validator.empty()) { /// Process object validator auto data_validator = sight::data::validator::factory::make(req.validator); SIGHT_ASSERT("Validator '" + req.validator + "' instantiation failed", data_validator); sight::data::validator::return_t validation = data_validator->validate(object); if(!validation.first) { _error_msg += "\n" + validation.second; object = nullptr; } } return object; } //----------------------------------------------------------------------------- bool data_view::check_and_compute_data(const data::activity::sptr& _activity, std::string& _error_msg) { bool ok = true; _error_msg += "The required data are not correct:"; // Check if all required data are present for(std::size_t i = 0 ; i < m_activity_info.requirements.size() ; ++i) { activity_requirement req = m_activity_info.requirements[i]; std::string msg; data::object::sptr obj = this->check_data(i, msg); if(obj) { (*_activity)[req.name] = obj; } else { ok = false; _error_msg += msg; } } for(const std::string& validatot_impl : m_activity_info.validators_impl) { /// Process activity validator auto validator = sight::activity::validator::factory::make(validatot_impl); auto activity_validator = std::dynamic_pointer_cast(validator); SIGHT_ASSERT("Validator '" + validatot_impl + "' instantiation failed", activity_validator); sight::activity::validator::return_t validation = activity_validator->validate(_activity); if(!validation.first) { ok = false; _error_msg += "\n" + validation.second; } } return ok; } //----------------------------------------------------------------------------- void data_view::remove_selected_objects() { auto tab_index = static_cast(this->currentIndex()); QPointer tree = m_tree_widgets[tab_index]; QList items = tree->selectedItems(); for(QTreeWidgetItem* item : items) { if(item != nullptr) { int item_index = tree->indexOfTopLevelItem(item); QTreeWidgetItem* item_to_remove = tree->takeTopLevelItem(item_index); if(item_to_remove != nullptr) { delete item_to_remove; this->update(); } } } } //----------------------------------------------------------------------------- void data_view::clear_tree() { auto tab_index = static_cast(this->currentIndex()); QPointer tree = m_tree_widgets[tab_index]; tree->clear(); } //----------------------------------------------------------------------------- void data_view::create_new_object() { auto index = static_cast(this->currentIndex()); activity_requirement req = m_activity_info.requirements[index]; std::string type = req.type; QPointer tree = m_tree_widgets[index]; auto nb_items = static_cast(tree->topLevelItemCount()); if(nb_items >= req.max_occurs) { const QString message("Can't create more '" + QString::fromStdString(type) + "', please remove one to create another."); QMessageBox::warning(this, "New", message); return; } data::object::sptr new_object = data::factory::make(type); m_imported_object.push_back(new_object); this->add_object_item(index, new_object); } //----------------------------------------------------------------------------- void data_view::import_object() { const auto index = static_cast(this->currentIndex()); activity_requirement req = m_activity_info.requirements[index]; const std::string type = req.type; QPointer tree = m_tree_widgets[index]; const auto nb_items = static_cast(tree->topLevelItemCount()); if(nb_items >= req.max_occurs) { const QString message("Can't load more '" + QString::fromStdString(type) + "', please remove one to load another."); QMessageBox::warning(this, "Import", message); return; } auto obj = sight::module::ui::qt::activity::data_view::read_object(type, m_io_selector_srv_config); if(obj) { m_imported_object.push_back(obj); this->add_object_item(index, obj); } } //----------------------------------------------------------------------------- void data_view::import_object_from_sdb() { const auto index = static_cast(this->currentIndex()); activity_requirement req = m_activity_info.requirements[index]; const std::string type = req.type; QPointer tree = m_tree_widgets[index]; const auto nb_items = static_cast(tree->topLevelItemCount()); if(nb_items >= req.max_occurs) { const QString message("Can't load more '" + QString::fromStdString(type) + "', please remove one to load another."); QMessageBox::warning(this, "Import from series_set", message); return; } data::object::sptr new_object = data::factory::make(type); if(new_object) { SIGHT_ERROR_IF( "Imported object must inherit from 'Series'.", !std::dynamic_pointer_cast(new_object) ); // We use the series_set reader and then extract the object of this type. auto obj = sight::module::ui::qt::activity::data_view::read_object( "sight::data::series_set", m_sdb_io_selector_srv_config ); auto series_set = std::dynamic_pointer_cast(obj); if(series_set) { unsigned int nb_imported_obj = 0; for(const data::series::sptr& series : *series_set) { if(series->is_a(type)) { ++nb_imported_obj; m_imported_object.push_back(series); this->add_object_item(index, series); if(nb_imported_obj >= req.max_occurs) { break; } } } } } else { std::string msg = "Can not create object '" + type + "'"; SIGHT_ERROR(msg); QMessageBox message_box(QMessageBox::Warning, "Error", QString::fromStdString(msg), QMessageBox::Ok); } } //----------------------------------------------------------------------------- data::object::sptr data_view::read_object( const std::string& _classname, const std::string& _io_selector_srv_config ) { data::object::sptr obj; service::base::sptr io_selector_srv; io_selector_srv = service::add("sight::module::ui::io::selector"); auto io_config = service::extension::config::get_default()->get_service_config( _io_selector_srv_config, "sight::module::ui::io::selector" ); io_config.add("type..class", _classname); // add the class of the output object try { obj = data::factory::make(_classname); io_selector_srv->set_config(io_config); io_selector_srv->configure(); io_selector_srv->set_inout(obj, io::service::DATA_KEY); io_selector_srv->start(); io_selector_srv->update(); io_selector_srv->stop(); service::unregister_service(io_selector_srv); } catch(std::exception& e) { std::stringstream msg; msg << "The object can not be imported: " << e.what(); SIGHT_ERROR(msg.str()); QMessageBox message_box(QMessageBox::Warning, "Error", QString::fromStdString(msg.str()), QMessageBox::Ok); if(io_selector_srv->started()) { io_selector_srv->stop(); } service::unregister_service(io_selector_srv); } return obj; } //----------------------------------------------------------------------------- void data_view::add_object_item(std::size_t _index, const data::object::csptr& _obj) { QPointer tree = m_tree_widgets[_index]; auto* const new_item = new QTreeWidgetItem(); new_item->setFlags(new_item->flags() & ~Qt::ItemIsDropEnabled); new_item->setData(int(column_commun_t::id), UID_ROLE, QVariant(QString::fromStdString(_obj->get_id()))); const auto series = std::dynamic_pointer_cast(_obj); const auto str_obj = std::dynamic_pointer_cast(_obj); const auto int_obj = std::dynamic_pointer_cast(_obj); const auto float_obj = std::dynamic_pointer_cast(_obj); const auto bool_obj = std::dynamic_pointer_cast(_obj); const auto trf = std::dynamic_pointer_cast(_obj); if(series) { new_item->setText(int(column_series_t::name), QString::fromStdString(series->get_patient_name())); new_item->setText(int(column_series_t::sex), QString::fromStdString(series->get_patient_sex())); std::string birthdate = series->get_patient_birth_date(); if(!birthdate.empty() && birthdate != "unknown") { birthdate.insert(4, "-"); birthdate.insert(7, "-"); } new_item->setText(int(column_series_t::birthdate), QString::fromStdString(birthdate)); new_item->setText(int(column_series_t::modality), QString::fromStdString(series->get_modality_string())); new_item->setText( int(column_series_t::modality_desc), QString::fromStdString(series->get_series_description()) ); new_item->setText( int(column_series_t::study_desc), QString::fromStdString(series->get_study_description()) ); std::string date = series->get_series_date(); if(!date.empty()) { date.insert(4, "/"); date.insert(7, "/"); } new_item->setText(int(column_series_t::date), QString::fromStdString(date)); std::string time = series->get_series_time(); if(!time.empty()) { time.insert(2, ":"); time.insert(5, ":"); } new_item->setText(int(column_series_t::time), QString::fromStdString(time.substr(0, 8))); std::string patient_age = series->get_patient_age(); if(!patient_age.empty()) { patient_age.insert(3, " "); if(patient_age[0] == '0') { patient_age.erase(0, 1); } } new_item->setText(int(column_series_t::patient_age), QString::fromStdString(patient_age)); const auto image_series = std::dynamic_pointer_cast(_obj); if(image_series) { new_item->setText( int(column_image_series_t::body_part_examined), QString::fromStdString(image_series->get_body_part_examined()) ); std::string patient_position = image_series->get_patient_position(); if(!patient_position.empty()) { // Code string can contains leading or trailing spaces, we removed it first. const std::string::const_iterator forward = std::remove_if( patient_position.begin(), patient_position.end(), [&](unsigned char _c) { return _c == ' '; }); patient_position.erase(forward, patient_position.end()); if(patient_position == "HFP") { patient_position = "Head First-Prone"; } else if(patient_position == "HFS") { patient_position = "Head First-Supine"; } else if(patient_position == "HFDR") { patient_position = "Head First-Decubitus Right"; } else if(patient_position == "HFDL") { patient_position = "Head First-Decubitus Left"; } else if(patient_position == "FFDR") { patient_position = "Feet First-Decubitus Right"; } else if(patient_position == "FFDL") { patient_position = "Feet First-Decubitus Left"; } else if(patient_position == "FFP") { patient_position = "Feet First-Prone"; } else if(patient_position == "FFS") { patient_position = "Feet First-Supine"; } else if(patient_position == "LFP") { patient_position = "Left First-Prone"; } else if(patient_position == "LFS") { patient_position = "Left First-Supine"; } else if(patient_position == "RFP") { patient_position = "Right First-Prone"; } else if(patient_position == "RFS") { patient_position = "Right First-Supine"; } else if(patient_position == "AFDR") { patient_position = "Anterior First-Decubitus Right"; } else if(patient_position == "AFDL") { patient_position = "Anterior First-Decubitus Left"; } else if(patient_position == "PFDR") { patient_position = "Posterior First-Decubitus Right"; } else if(patient_position == "PFDL") { patient_position = "Posterior First-Decubitus Left"; } } new_item->setText( int(column_image_series_t::patient_position), QString::fromStdString(patient_position) ); new_item->setText( int(column_image_series_t::contrast_agent), QString::fromStdString(image_series->get_contrast_bolus_agent()) ); std::string acquisition_time = image_series->get_acquisition_time(); if(!acquisition_time.empty()) { acquisition_time.insert(2, ":"); acquisition_time.insert(5, ":"); } new_item->setText( int(column_image_series_t::acquisition_time), QString::fromStdString(acquisition_time.substr(0, 8)) ); std::string contrast_time = image_series->get_contrast_bolus_start_time(); if(!contrast_time.empty()) { contrast_time.insert(2, ":"); contrast_time.insert(5, ":"); } new_item->setText( int(column_image_series_t::contrast_bolus_start_time), QString::fromStdString(contrast_time.substr(0, 8)) ); } } else if(str_obj) { std::string description = str_obj->value(); if(description.empty()) { description = _obj->get_classname(); } new_item->setText(int(column_object_t::desc), QString::fromStdString(description)); } else if(int_obj) { new_item->setText(int(column_object_t::desc), QString("%1").arg(int_obj->value())); } else if(float_obj) { new_item->setText(int(column_object_t::desc), QString("%1").arg(float_obj->value())); } else if(bool_obj) { new_item->setText(int(column_object_t::desc), bool_obj->value() ? "true" : "false"); } else if(trf) { std::stringstream str; str << *trf; new_item->setText(int(column_object_t::desc), QString::fromStdString(str.str())); } else { new_item->setText(int(column_object_t::desc), QString::fromStdString(_obj->get_classname())); } // set icon auto iter = m_object_icons.find(_obj->get_classname()); if(iter != m_object_icons.end()) { new_item->setIcon(int(column_commun_t::id), QIcon(QString::fromStdString(iter->second))); } tree->addTopLevelItem(new_item); for(int i = 0 ; i < tree->columnCount() ; ++i) { tree->resizeColumnToContents(i); } } //----------------------------------------------------------------------------- void data_view::on_tree_item_double_clicked(QTreeWidgetItem* _item, int /*unused*/) { if(_item != nullptr) { std::string uid = _item->data(int(column_commun_t::id), UID_ROLE).toString().toStdString(); if(!uid.empty()) { data::object::sptr obj = std::dynamic_pointer_cast(core::id::get_object(uid)); if(obj) { if(obj->is_a("sight::data::string")) { data::string::sptr str = std::dynamic_pointer_cast(obj); bool is_ok_clicked = false; QString value = QInputDialog::getText( this, "Edition", "Enter the String value:", QLineEdit::Normal, QString::fromStdString(str->value()), &is_ok_clicked ); if(is_ok_clicked) { str->value() = value.toStdString(); _item->setText(int(column_object_t::desc), value); } } else if(obj->is_a("sight::data::integer")) { data::integer::sptr int_obj = std::dynamic_pointer_cast(obj); bool is_ok_clicked = false; int value = QInputDialog::getInt( this, "Edition", "Enter the Integer value:", int(int_obj->value()), std::numeric_limits::min(), std::numeric_limits::max(), 1, &is_ok_clicked ); if(is_ok_clicked) { int_obj->value() = value; _item->setText(int(column_object_t::desc), QString("%1").arg(value)); } } else if(obj->is_a("sight::data::real")) { data::real::sptr float_obj = std::dynamic_pointer_cast(obj); bool is_ok_clicked = false; double value = QInputDialog::getDouble( this, "Edition", "Enter the Integer value:", float_obj->value(), std::numeric_limits::min(), std::numeric_limits::max(), 3, &is_ok_clicked ); if(is_ok_clicked) { float_obj->value() = static_cast(value); _item->setText(int(column_object_t::desc), QString("%1").arg(value)); } } else if(obj->is_a("sight::data::boolean")) { data::boolean::sptr bool_obj = std::dynamic_pointer_cast(obj); QMessageBox::StandardButton button = QMessageBox::question( this, "Edition", "Defines the Boolean value" ); bool_obj->value() = (button == QMessageBox::Yes); _item->setText(int(column_object_t::desc), bool_obj->value() ? "true" : "false"); } else if(obj->is_a("sight::data::matrix4")) { data::matrix4::sptr trf = std::dynamic_pointer_cast(obj); std::stringstream str; str << *trf; bool is_ok_clicked = false; QString value = QInputDialog::getMultiLineText( this, "Edition", "Enter the Matrix coefficient (separated by a space):", QString::fromStdString(str.str()), &is_ok_clicked ); QStringList coeff_list = value.trimmed().split(QRegularExpression("\\s+")); if(is_ok_clicked && coeff_list.size() == 16) { data::matrix4::container_t coeffs; bool conversion_ok = false; for(int i = 0 ; i < 16 ; ++i) { coeffs[std::size_t(i)] = coeff_list[i].toDouble(&conversion_ok); if(!conversion_ok) { QMessageBox::warning( this, "ERROR", "This values cannot be converted to matrix coefficients" ); return; } } (*trf) = coeffs; _item->setText(int(column_object_t::desc), value.trimmed()); } else if(is_ok_clicked) { QMessageBox::warning( this, "ERROR", "This values cannot be converted to matrix coefficients. It must contain " "16 values" ); return; } } else { SIGHT_DEBUG("Object of type '" + obj->classname() + "' can not yet be editted"); } } } } } //----------------------------------------------------------------------------- } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/data_view.hpp000066400000000000000000000171001503402212300215420ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2016-2024 IRCAD France * Copyright (C) 2016-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include namespace sight::module::ui::qt::activity { /** * @brief This class displays a tab widget allowing to select the required data to create an activity. */ class data_view : public QTabWidget { Q_OBJECT; public: /// Identifier of the role UID in the series tree item. static const int UID_ROLE; using object_icon_map_t = std::map; /// Defines common header columns used in the tree widget of all created tabs. enum class column_commun_t : int { id = 0 }; /// Defines header columns used in the tree widget of all data. enum class column_object_t : int { desc = 1 }; /// Defines header columns used in the tree widget of all series. enum class column_series_t : int { name = 1, sex, birthdate, modality, modality_desc, study_desc, date, time, patient_age }; /// Defines header columns used in the tree widget of image series. enum column_image_series_t : int { body_part_examined = 10, patient_position, contrast_agent, acquisition_time, contrast_bolus_start_time }; /** * @brief Creates the tabbed widget. * @param _parent the parent of this widget. */ data_view(QWidget* _parent = nullptr); /// Destroys the tabbed widget. ~data_view() override; /** * @brief Creates all tabs from an activity information. * * One tab will be added for each activity parameter, if the type of the data is a @ref data::series, * more informations will be displayed in the tree widget. * * @param _info the struct containing the activity configuration. */ void fill_information(const sight::activity::extension::activity_info& _info); /** * @brief Creates all tabs from an activity. * @param _activity the activity. */ void fill_information(const data::activity::sptr& _activity); /** * @brief Checks if all the required data are selected and fill activity data map. * * It checks if all required data are present in the widget, and create the activity data map. * Then it checks if the activity parameters are accessible from the map of data (it checks if the object are * accessible from the object paths. * * @return True if data are correct, else false and errorMsg contains detailled error. * @param _activity activity to fill with the selected data. * @param[out] _error_msg it will contain the error information if the data are not correct. */ bool check_and_compute_data( const data::activity::sptr& _activity, std::string& _error_msg ); /** * @brief Checks if the current required data are selected And return this data. * @param _index index of the requirement to check (this index also correspond to a tab). * @param[out] _error_msg it will contain the error information if the data are not correct. * @return return the data if it is correct, else return a nullptr and errorMsg contains detailled error. */ data::object::sptr check_data(std::size_t _index, std::string& _error_msg); /** * @brief Sets the IO selector configuration used to import data. * @param _io_selector_srv_config the service configuration used for the IO selector. */ inline void set_io_selector_config(const std::string& _io_selector_srv_config) { m_io_selector_srv_config = _io_selector_srv_config; } /** * @brief Sets the IO selector configuration used to import data from a series set. * @param _io_selector_srv_config the service configuration used for the IO selector. */ inline void set_sdbio_selector_config(const std::string& _io_selector_srv_config) { m_sdb_io_selector_srv_config = _io_selector_srv_config; } /// Removes all the widget tabs. virtual void clear(); /** * @brief Sets the map that defines the icon associated to an object. * @param _object_icons the map associating an object type to an icon. */ void set_object_icon_association(const object_icon_map_t& _object_icons) { m_object_icons = _object_icons; } protected: /// Catch KeyEvent and DropEvent from tree widgets bool eventFilter(QObject* _obj, QEvent* _event) override; private: using imported_object_t = std::vector; using tree_container_t = std::vector >; /// Imports an object required for the selected tab. void import_object(); /// Imports a series_set and then extract the object required for the selected tab. void import_object_from_sdb(); /// Creates a new object for the selected tab (just use data::factory::make(type)). void create_new_object(); /** * @brief Calls @ref module::ui::editor::selector service to read the object. Return true if the object is * properly * read. * @param _classname the class name of the object to read. * @param _io_selector_srv_config the IO selector configuration. * @return the object read by the selector. */ static data::object::sptr read_object(const std::string& _classname, const std::string& _io_selector_srv_config); /// Removes the selected object in the current tree. void remove_selected_objects(); /// Removes all objects in the current tree. void clear_tree(); /** * @brief Allows to edit the current data. * @note Currently it is only available for simple types (@see data::string). */ void on_tree_item_double_clicked(QTreeWidgetItem* _item, int _column); /** * @brief Adds the current item in the tree. * @param _index index used to find the associated tree widget. * @param _obj object to add in the tree. */ void add_object_item(std::size_t _index, const data::object::csptr& _obj); /// Sets the activity information sight::activity::extension::activity_info m_activity_info; /// Defines the IO selector config. std::string m_io_selector_srv_config; /// Dewfines the IO selector config to import data from a series_set. std::string m_sdb_io_selector_srv_config; /// Stores references on the imported object before to add them in the activity. imported_object_t m_imported_object; /// Stores the tree widget of each tab. tree_container_t m_tree_widgets; /// Associates an icon to an object type. object_icon_map_t m_object_icons; }; } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/dynamic_view.cpp000066400000000000000000000257501503402212300222620ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "dynamic_view.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::activity { static const core::com::slots::key_t CREATE_TAB_SLOT = "create_tab"; static const core::com::signals::key_t ACTIVITY_SELECTED_SLOT = "activitySelected"; static const core::com::signals::key_t NOTHING_SELECTED_SLOT = "nothingSelected"; //------------------------------------------------------------------------------ dynamic_view::dynamic_view() noexcept { new_slot(CREATE_TAB_SLOT, &dynamic_view::create_tab, this); m_sig_activity_selected = new_signal(ACTIVITY_SELECTED_SLOT); m_sig_nothing_selected = new_signal(NOTHING_SELECTED_SLOT); } //------------------------------------------------------------------------------ dynamic_view::~dynamic_view() noexcept = default; //------------------------------------------------------------------------------ void dynamic_view::configuring() { this->sight::ui::activity_view::configuring(); const auto& config = this->get_config(); m_main_activity_closable = config.get("mainActivity..closable", m_main_activity_closable); m_document_mode = config.get("config..document", m_document_mode); } //------------------------------------------------------------------------------ void dynamic_view::starting() { this->sight::ui::service::create(); auto parent_container = std::dynamic_pointer_cast(this->get_container()); m_tab_widget = new QTabWidget(); m_tab_widget->setTabsClosable(true); m_tab_widget->setDocumentMode(m_document_mode); m_tab_widget->setMovable(true); QObject::connect(m_tab_widget, &QTabWidget::tabCloseRequested, this, &dynamic_view::close_tab_signal); QObject::connect(m_tab_widget, &QTabWidget::currentChanged, this, &dynamic_view::changed_tab); auto* layout = new QBoxLayout(QBoxLayout::TopToBottom); layout->addWidget(m_tab_widget); parent_container->set_layout(layout); m_current_widget = nullptr; if(!m_main_activity_id.empty()) { this->build_main_activity(); } } //------------------------------------------------------------------------------ void dynamic_view::stopping() { while(m_tab_widget->count() != 0) { this->close_tab(0, true); } m_tab_widget->clear(); this->destroy(); m_tab_widget = nullptr; } //------------------------------------------------------------------------------ void dynamic_view::updating() { } //------------------------------------------------------------------------------ void dynamic_view::launch_activity(data::activity::sptr _activity) { if(this->validate_activity(_activity)) { dynamic_view_info view_info = this->create_view_info(_activity); view_info.closable = true; this->launch_tab(view_info); } } //------------------------------------------------------------------------------ void dynamic_view::create_tab(sight::activity::message _info) { dynamic_view_info view_info; view_info.title = _info.get_title(); view_info.tab_id = _info.get_tab_id(); view_info.closable = _info.is_closable(); view_info.icon = _info.get_icon_path(); view_info.tooltip = _info.get_tool_tip(); view_info.view_config_id = _info.get_app_config_id(); view_info.replacement_map = _info.get_replacement_map(); view_info.activity = _info.get_activity(); this->launch_tab(view_info); } //------------------------------------------------------------------------------ void dynamic_view::launch_tab(dynamic_view_info& _info) { static int count = 0; auto iter = std::find(m_activity_ids.begin(), m_activity_ids.end(), _info.activity->get_id()); if(iter != m_activity_ids.end()) { sight::ui::dialog::message::show( "Launch Activity", "The current activity is already launched. \n" "It cannot be launched twice.", sight::ui::dialog::message::warning ); return; } if(m_title_to_count.find(_info.title) != m_title_to_count.end()) { m_title_to_count[_info.title]++; } else { m_title_to_count[_info.title] = 1; } QString final_title = QString("%1 %2").arg(_info.title.c_str(), "(%1)").arg(m_title_to_count[_info.title]); _info.wid = QString("dynamic_view-%1").arg(count++).toStdString(); auto sub_container = sight::ui::qt::container::widget::make(); auto* widget = new QWidget(m_tab_widget); sub_container->set_qt_container(widget); sight::ui::registry::register_wid_container(_info.wid, sub_container); _info.replacement_map["WID_PARENT"] = _info.wid; _info.replacement_map["GENERIC_UID"] = sight::app::extension::config::get_unique_identifier(_info.view_config_id); auto helper = sight::app::config_manager::make(); try { helper->set_config(_info.view_config_id, _info.replacement_map); if(!m_dynamic_config_start_stop) { helper->launch(); } else { helper->create(); } } catch(std::exception& e) { sight::ui::dialog::message::show( "Activity launch failed", e.what(), sight::ui::dialog::message::critical ); SIGHT_ERROR(e.what()); return; } _info.container = sub_container; _info.helper = helper; m_activity_ids.insert(_info.activity->get_id()); m_dynamic_info_map[widget] = _info; m_tab_id_list.insert(_info.tab_id); int index = m_tab_widget->addTab(widget, final_title); if(!_info.tooltip.empty()) { m_tab_widget->setTabToolTip(index, QString::fromStdString(_info.tooltip)); } if(!_info.icon.empty()) { m_tab_widget->setTabIcon(index, QIcon(QString::fromStdString(_info.icon))); } m_tab_widget->setCurrentWidget(widget); } //------------------------------------------------------------------------------ void dynamic_view::info(std::ostream& /*_sstream*/) { } //------------------------------------------------------------------------------ void dynamic_view::close_tab_signal(int _index) { close_tab(_index, false); } //------------------------------------------------------------------------------ void dynamic_view::close_tab(int _index, bool _force_close) { QWidget* widget = m_tab_widget->widget(_index); SIGHT_ASSERT("Widget is not in dynamicInfoMap", m_dynamic_info_map.find(widget) != m_dynamic_info_map.end()); dynamic_view_info info = m_dynamic_info_map[widget]; if(info.closable || _force_close) { m_tab_id_list.erase(info.tab_id); if(!m_dynamic_config_start_stop) { info.helper->stop_and_destroy(); } else { if(info.helper->started()) { info.helper->stop(); } info.helper->destroy(); } info.helper.reset(); //Remove tab first, to avoid tab beeing removed by container->destroy m_current_widget = nullptr; m_tab_widget->removeTab(_index); sight::ui::registry::unregister_wid_container(info.wid); info.container->destroy_container(); info.container.reset(); m_dynamic_info_map.erase(widget); m_activity_ids.erase(info.activity->get_id()); } else { sight::ui::dialog::message::show( "Close tab", "The tab " + info.title + " can not be closed.", sight::ui::dialog::message::info ); } } //------------------------------------------------------------------------------ void dynamic_view::changed_tab(int _index) { QWidget* widget = m_tab_widget->widget(_index); if(m_dynamic_config_start_stop && widget != m_current_widget) { if(m_current_widget != nullptr) { dynamic_view_info oldinfo = m_dynamic_info_map[m_current_widget]; oldinfo.helper->stop(); } if(widget != nullptr) { dynamic_view_info newinfo = m_dynamic_info_map[widget]; if(!newinfo.helper->started()) { newinfo.helper->start(); newinfo.helper->update(); } } } m_current_widget = widget; if(_index >= 0) { dynamic_view_info info = m_dynamic_info_map[widget]; m_sig_activity_selected->async_emit(info.activity); } else { m_sig_nothing_selected->async_emit(); } } //------------------------------------------------------------------------------ void dynamic_view::build_main_activity() { auto activity = this->create_main_activity(); if(activity) { dynamic_view_info view_info; view_info = this->create_view_info(activity); view_info.closable = m_main_activity_closable; this->launch_tab(view_info); } } //------------------------------------------------------------------------------ dynamic_view::dynamic_view_info dynamic_view::create_view_info(data::activity::sptr _activity) { auto [info, replacementMap] = sight::activity::extension::activity::get_default()->get_info_and_replacement_map( *_activity, m_parameters ); dynamic_view_info view_info; view_info.title = info.title; view_info.icon = info.icon; view_info.tooltip = info.tab_info.empty() ? info.title : info.tab_info; view_info.view_config_id = info.app_config.id; view_info.activity = _activity; view_info.replacement_map = replacementMap; return view_info; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/dynamic_view.hpp000066400000000000000000000150771503402212300222700ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include class QTabWidget; namespace sight::data { class map; } // namespace sight::data namespace sight::module::ui::qt::activity { /** * @brief This editor manages tabs containing activities. * * This service should received signals containing Activity connected to the slot \b launch_activity. It will * launch the activity in a new tab. * * @note The same activity cannot be launch in two different tabs. * * @section Signal Signal * - \b activitySelected( data::object::sptr ): this signal is emitted when the current tab selection * changed, it contains the associated Activity. The activity is send as a data::object in order to * connect this signal to slots receiving a data::object. * - \b nothingSelected(): this signal is emitted when no tab are selected. * * @section Slots Slots * - \b launch_activity( data::activity::sptr ): this slot allows to create a tab with the given activity. * - \b create_tab( activity::message ): this slot allows to create a tab with the given activity information. * * @section XML XML Configuration * @code{.xml} @endcode * - \b mainActivity (optional): information about the main activity (first tab). The activity will be generated. * This activity must not have requirement. * - \b id : identifier of the activity * - \b closable (optional, default 'no') : defines if the user can close this tab. * - \b parameters (optional) : additional parameters used to launch the activities * - \b parameter: defines a parameter * - \b replace: name of the parameter as defined in the config * - \b by: defines the string that will replace the parameter name. * * @subsection Configuration Configuration: * - \b document (optional, default="true") : sets the document mode of the tab bar. */ class dynamic_view : public QObject, public sight::ui::activity_view { Q_OBJECT public: SIGHT_DECLARE_SERVICE(dynamic_view, sight::ui::activity_view); /// Constructor. Do nothing. dynamic_view() noexcept; /// Destructor. Do nothing. ~dynamic_view() noexcept override; using activity_selected_signal_t = core::com::signal; static const core::com::signals::key_t ACTIVITY_SELECTED_SIG; using nothing_selected_signal_t = core::com::signal; static const core::com::signals::key_t NOTHING_SELECTED_SIG; protected: /** * @brief Install the container. */ void starting() override; /** * @brief Destroy the container. */ void stopping() override; /** * @brief Update * */ void updating() override; /** * @brief Configure the view * @see sight::ui::service::initialize() */ void configuring() override; void info(std::ostream& _sstream) override; private: using activity_id_t = std::set; struct dynamic_view_info { sight::ui::qt::container::widget::sptr container; sight::app::config_manager::sptr helper; std::string wid; std::string title; bool closable {}; std::string icon; std::string tooltip; std::string tab_id; std::string view_config_id; std::map replacement_map; data::activity::sptr activity; }; using dynamic_view_info_map_type = std::map; /** * @brief Launch tab */ void launch_tab(dynamic_view_info& _info); /** * @brief Slot: Launch the given activity in a new tab. * @note The same activity cannot be launched in two different tabs. */ void launch_activity(data::activity::sptr _activity) override; /// launch a new tab according to the receiving msg void create_tab(sight::activity::message _info); /// Create the main activity and launch the activity virtual void build_main_activity(); /// Create view info from activity dynamic_view_info create_view_info(data::activity::sptr _activity); /** * @brief Close the tab at the given index. * @param _index : index of the tab to close * @param _force_close : if true, close the tab even if the tab is not "closable" */ void close_tab(int _index, bool _force_close); protected Q_SLOTS: /// Called when the tab close button is clicked: close the tab if it is "closable" void close_tab_signal(int _index); /// Called when the current tab selection changed void changed_tab(int _index); private: std::map m_title_to_count; std::set m_tab_id_list; activity_id_t m_activity_ids; dynamic_view_info_map_type m_dynamic_info_map; bool m_dynamic_config_start_stop {false}; QPointer m_tab_widget; QPointer m_current_widget; activity_selected_signal_t::sptr m_sig_activity_selected; nothing_selected_signal_t::sptr m_sig_nothing_selected; bool m_main_activity_closable {true}; /// Allows to set the document mode. bool m_document_mode {true}; }; } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/launcher.cpp000066400000000000000000000461771503402212300214130ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ // cspell:ignore NOLINTNEXTLINE #include "launcher.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(sight::activity::extension::activity_info) namespace sight::module::ui::qt::activity { //------------------------------------------------------------------------------ const core::com::slots::key_t launcher::LAUNCH_SERIES_SLOT = "launch_series"; const core::com::slots::key_t launcher::LAUNCH_ACTIVITY_SLOT = "launch_activity"; const core::com::slots::key_t launcher::UPDATE_STATE_SLOT = "update_state"; const core::com::signals::key_t launcher::ACTIVITY_LAUNCHED_SIG = "activity_launched"; using sight::activity::extension::activity; using sight::activity::extension::activity_info; using sight::activity::message; using sight::activity::validator::base; using sight::activity::validator::base; //------------------------------------------------------------------------------ launcher::launcher() noexcept : m_sig_activity_launched(new_signal(ACTIVITY_LAUNCHED_SIG)), m_mode("message") { new_slot(LAUNCH_SERIES_SLOT, &launcher::launch_series, this); new_slot(LAUNCH_ACTIVITY_SLOT, &launcher::launch_activity, this); new_slot(UPDATE_STATE_SLOT, &launcher::update_state, this); } //------------------------------------------------------------------------------ launcher::~launcher() noexcept = default; //------------------------------------------------------------------------------ void launcher::starting() { this->action_service_starting(); this->update_state(); } //------------------------------------------------------------------------------ void launcher::stopping() { this->action_service_stopping(); } //------------------------------------------------------------------------------ void launcher::configuring() { this->sight::ui::action::initialize(); using config_t = service::config_t; m_parameters.clear(); if(this->get_config().count("config") > 0) { SIGHT_ASSERT( "There must be one (and only one) element.", this->get_config().count("config") == 1 ); const service::config_t srvconfig = this->get_config(); const service::config_t& config = srvconfig.get_child("config"); m_mode = config.get_optional("mode").get_value_or("message"); SIGHT_ASSERT( "launcher mode must be either 'immediate' or 'message'", "message" == m_mode || "immediate" == m_mode ); if(config.count("parameters") == 1) { const service::config_t& config_parameters = config.get_child("parameters"); // NOLINTNEXTLINE(bugprone-branch-clone) BOOST_FOREACH(const config_t::value_type& v, config_parameters.equal_range("parameter")) { parameters_t::value_type parameter(v.second); m_parameters.push_back(parameter); } } SIGHT_ASSERT("A maximum of 1 tag is allowed", config.count("parameters") < 2); if(config.count("filter") == 1) { const service::config_t& config_filter = config.get_child("filter"); SIGHT_ASSERT("A maximum of 1 tag is allowed", config_filter.count("mode") < 2); const auto mode = config_filter.get("mode"); SIGHT_ASSERT( "'" << mode << "' value for tag isn't valid. Allowed values are : 'include', 'exclude'.", mode == "include" || mode == "exclude" ); m_filter_mode = mode; // NOLINTNEXTLINE(bugprone-branch-clone) BOOST_FOREACH(const config_t::value_type& v, config_filter.equal_range("id")) { m_keys.push_back(v.second.get("")); } } SIGHT_ASSERT("A maximum of 1 tag is allowed", config.count("filter") < 2); if(config.count("quickLaunch") == 1) { m_quick_launch.clear(); const service::config_t& config_quick_launch = config.get_child("quickLaunch"); // NOLINTNEXTLINE(bugprone-branch-clone) BOOST_FOREACH(const config_t::value_type& v, config_quick_launch.equal_range("association")) { const service::config_t& association = v.second; const service::config_t xmlattr = association.get_child(""); SIGHT_FATAL_IF("The attribute \"type\" is missing", xmlattr.count("type") != 1); SIGHT_FATAL_IF("The attribute \"id\" is missing", xmlattr.count("id") != 1); auto type = xmlattr.get("type"); auto id = xmlattr.get("id"); m_quick_launch[type] = id; } } SIGHT_ASSERT("A maximum of 1 tag is allowed", config.count("quickLaunch") < 2); } } //------------------------------------------------------------------------------ activity_info launcher::show(const activity_infos_t& _infos) { QWidget* parent = qApp->activeWindow(); auto* dialog = new QDialog(parent); dialog->setWindowTitle(QString::fromStdString("Choose an activity")); auto* model = new QStandardItemModel(dialog); for(const activity_info& info : _infos) { std::string text; if(info.title.empty()) { text = info.id; } else { text = info.title + (info.description.empty() ? "" : "\n" + info.description); } auto* item = new QStandardItem(QIcon(info.icon.c_str()), QString::fromStdString(text)); item->setData(QVariant::fromValue(info)); item->setEditable(false); model->appendRow(item); } auto* selection_list = new QListView(); selection_list->setIconSize(QSize(100, 100)); selection_list->setUniformItemSizes(true); selection_list->setModel(model); QModelIndex index = model->index(0, 0); if(index.isValid()) { selection_list->selectionModel()->select(index, QItemSelectionModel::Select); } auto* ok_button = new QPushButton("Ok"); auto* cancel_button = new QPushButton("Cancel"); auto* h_layout = new QHBoxLayout(); h_layout->addWidget(ok_button); h_layout->addWidget(cancel_button); auto* v_layout = new QVBoxLayout(); v_layout->addWidget(selection_list); v_layout->addLayout(h_layout); dialog->setLayout(v_layout); QObject::connect(ok_button, SIGNAL(clicked()), dialog, SLOT(accept())); QObject::connect(cancel_button, SIGNAL(clicked()), dialog, SLOT(reject())); QObject::connect(selection_list, SIGNAL(doubleClicked(const QModelIndex&)), dialog, SLOT(accept())); activity_info info; if(dialog->exec() != 0) { QModelIndex current_index = selection_list->selectionModel()->currentIndex(); QStandardItem* item = model->itemFromIndex(current_index); QVariant var = item->data(); info = var.value(); } return info; } //------------------------------------------------------------------------------ launcher::activity_infos_t launcher::get_enabled_activities(const activity_infos_t& _infos) { activity_infos_t configs; if(m_filter_mode == "include" || m_filter_mode == "exclude") { const bool is_include_mode = m_filter_mode == "include"; for(const auto& info : _infos) { auto key_it = std::find(m_keys.begin(), m_keys.end(), info.id); if((key_it != m_keys.end() && is_include_mode) || (key_it == m_keys.end() && !is_include_mode)) { configs.push_back(info); } } } else { configs = _infos; } return configs; } //------------------------------------------------------------------------------ void launcher::updating() { const auto selection = m_series.lock(); SIGHT_ASSERT("The input key '" << SERIES << "' is not correctly set.", selection); const bool launch_as = this->launch_as(selection.get_shared()); if(!launch_as) { activity_infos_t infos = activity::get_default()->get_infos(selection.get_shared()); infos = this->get_enabled_activities(infos); if(!infos.empty()) { activity_info info; if((m_keys.size() == 1 && m_filter_mode == "include") || (infos.size() == 1)) { info = infos[0]; } else { info = this->show(infos); } if(!info.id.empty()) { this->send_config(info); } } else { sight::ui::dialog::message::show( "Activity launcher", "Not available activity for the current selection.", sight::ui::dialog::message::warning ); } } } //------------------------------------------------------------------------------ void launcher::update_state() { const auto selection = m_series.lock(); SIGHT_ASSERT("The input key '" << SERIES << "' is not correctly set.", selection); bool is_enabled = false; if(selection->size() == 1 && std::dynamic_pointer_cast((*selection)[0])) { data::activity::sptr as = std::dynamic_pointer_cast((*selection)[0]); if(m_filter_mode == "include" || m_filter_mode == "exclude") { const bool is_include_mode = m_filter_mode == "include"; auto key_it = std::find(m_keys.begin(), m_keys.end(), as->get_activity_config_id()); is_enabled = ((key_it != m_keys.end()) != is_include_mode); is_enabled &= activity::get_default()->has_info( as->get_activity_config_id() ); } else { is_enabled = activity::get_default()->has_info( as->get_activity_config_id() ); } } else { activity_info::data_count_t data_count; data_count = activity::get_default()->get_data_count(selection.get_shared()); if(m_filter_mode.empty() && data_count.size() == 1) { data::object::sptr obj = selection->front(); if(std::dynamic_pointer_cast(obj)) { is_enabled = true; } } activity_infos_t infos = activity::get_default()->get_infos(selection.get_shared()); infos = this->get_enabled_activities(infos); is_enabled |= !infos.empty(); } this->set_enabled(is_enabled); } //------------------------------------------------------------------------------ void launcher::build_activity( const activity_info& _info, const data::vector::csptr& _selection ) { auto builder = sight::activity::builder::factory::make(_info.builder_impl); SIGHT_ASSERT(_info.builder_impl << " instantiation failed", builder); auto activity = builder->build_data(_info, _selection); if(!activity) { const std::string msg = "The activity <" + _info.title + "> can't be launched. Builder <" + _info.builder_impl + "> failed."; sight::ui::dialog::message::show( "Activity can not be launched", msg, sight::ui::dialog::message::warning ); SIGHT_ERROR(msg); return; } // Applies activity validator on activity to check the data if(!_info.validators_impl.empty()) { for(const std::string& validator_impl : _info.validators_impl) { /// Process activity validator auto validator = sight::activity::validator::factory::make(validator_impl); auto activity_validator = std::dynamic_pointer_cast(validator); if(activity_validator) { auto ret = activity_validator->validate(activity); if(!ret.first) { const std::string message = "The activity '" + _info.title + "' can not be launched:\n" + ret.second; sight::ui::dialog::message::show( "Activity launch", message, sight::ui::dialog::message::critical ); return; } } } } const message msg(activity, _info, m_parameters); if(m_mode == "message") { m_sig_activity_launched->async_emit(msg); } else { sight::ui::lock_action lock(this->get_sptr()); const std::string& view_config_id = msg.get_app_config_id(); auto replacement_map = msg.get_replacement_map(); replacement_map["GENERIC_UID"] = app::extension::config::get_unique_identifier(); auto helper = app::config_manager::make(); helper->set_config(view_config_id, replacement_map); helper->launch(); helper->stop_and_destroy(); } } //------------------------------------------------------------------------------ void launcher::send_config(const activity_info& _info) { // Start module containing the activity if it is not started core::runtime::start_module(_info.bundle_id); const auto selection = m_series.lock(); SIGHT_ASSERT("The input key '" << SERIES << "' is not correctly set.", selection); sight::activity::validator::return_t validation; validation.first = true; for(auto const& validator_impl : _info.validators_impl) { auto validator = sight::activity::validator::factory::make(validator_impl); SIGHT_ASSERT(validator_impl << " instantiation failed", validator); auto valid = validator->validate(_info, selection.get_shared()); validation.first &= valid.first; if(!valid.first) { validation.second += "\n" + valid.second; } } if(!validation.first) { sight::ui::dialog::message::show( "Activity can not be launched", "The activity " + _info.title + " can't be launched. Reason : " + validation.second, sight::ui::dialog::message::warning ); } else { this->build_activity(_info, selection.get_shared()); } } //------------------------------------------------------------------------------ bool launcher::launch_as(const data::vector::csptr& _selection) { bool launch_as = false; activity_info::data_count_t data_count; data_count = activity::get_default()->get_data_count(_selection); if(data_count.size() == 1) { for(const data::object::sptr& obj : *_selection) { data::activity::sptr as = std::dynamic_pointer_cast(obj); if(!as) { launch_as = false; break; } this->launch_activity(as); launch_as = true; } } return launch_as; } //------------------------------------------------------------------------------ void launcher::launch_series(data::series::sptr _series) { auto selection = std::make_shared(); selection->push_back(_series); activity_infos_t infos = activity::get_default()->get_infos(selection); if(m_quick_launch.find(_series->get_classname()) != m_quick_launch.end()) { std::string activity_id = m_quick_launch[_series->get_classname()]; SIGHT_ASSERT( "Activity information not found for" + activity_id, activity::get_default()->has_info(activity_id) ); this->send_config(activity::get_default()->get_info(activity_id)); } else if(!infos.empty()) { this->send_config(infos.front()); } else { sight::ui::dialog::message::show( "Activity launcher", "Not available activity for the current selection.", sight::ui::dialog::message::warning ); } } //------------------------------------------------------------------------------ void launcher::launch_activity(data::activity::sptr _activity) { activity_info info; info = activity::get_default()->get_info(_activity->get_activity_config_id()); // Applies activity validator on activity to check the data if(!info.validators_impl.empty()) { for(const std::string& validator_impl : info.validators_impl) { /// Process activity validator auto validator = sight::activity::validator::factory::make(validator_impl); auto activity_validator = std::dynamic_pointer_cast(validator); if(activity_validator) { auto ret = activity_validator->validate(_activity); if(!ret.first) { const std::string message = "The activity '" + info.title + "' can not be launched:\n" + ret.second; sight::ui::dialog::message::show( "Activity launch", message, sight::ui::dialog::message::critical ); return; } } } } m_sig_activity_launched->async_emit(message(_activity, info, m_parameters)); } //------------------------------------------------------------------------------ service::connections_t launcher::auto_connections() const { connections_t connections; connections.push(SERIES, data::vector::ADDED_OBJECTS_SIG, UPDATE_STATE_SLOT); connections.push(SERIES, data::vector::REMOVED_OBJECTS_SIG, UPDATE_STATE_SLOT); return connections; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/launcher.hpp000066400000000000000000000237011503402212300214040ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::ui::qt::activity { /** * @brief This action launches an activity according to the selected data * * This action works on a data::vector. It proposes all the available activity according to the selected data and * the given configuration. And then, send a signal with all the activity information. * * This action should be followed by the service '::module::ui::qt::editor::dynamic_view' : this service listens the * action * signals and launches the activity in a new tab. * * @section Slots Slots * - \b launch_series(data::series::sptr) : This slot launches the first available activity for this series or uses * m_quickLaunch information if a default association is defined for this series type. It sends the signal * 'activityLaunched' with all the activity information. * * - \b launch_activity(data::activity::sptr) : This slot allows to launch the given activity. * It sends the signal 'activityLaunched' with all the activity information. * * - \b update_state() : Updates action state (enable if activities are available for current selection). * * @section Signal Signal * - \b activity_launched(activity::message) : This signal is emitted when the activity is created, * it contains the activity information. It should be connected to the slot 'createTab' of the service * '::module::ui::qt::editor::dynamic_view'. * * @section XML XML Configuration * @code{.xml} immediate include activity_viz_negato 3DVisualizationActivity VolumeRenderingActivity @endcode * @subsection Input Input * - \b series [sight::data::vector]: vector containing series inherited from data::series * @subsection Configuration Configuration * - \b mode (optional): there are two mode: "message" and "immediate" * - \b message (used by default): the action send a signal containing the information needed to launch the * chosen activity. The service '::module::ui::qt::editor::dynamic_view' allows to launch the activity in a new * tab. For that, it must listen the action signal. * - \b immediate: the activity is automatically started et stopped by this action. It is used to run a process * without creating a new tab, for example, to save the selected data. * - \b parameters (optional): list of the parameters used to launch the activity, it is the parameters for the * config associated to the activity. * - \b parameter: defines a parameter * - \b replace: name of the parameter as defined in the config * - \b by: defines the string that will replace the parameter name. It shall be a simple string (ex. frontal) * - \b filter (optional): it allows to filter the activity that can be proposed. * - \b mode: 'include' or 'exclude'. Defines if the activity in the following list are proposed (include) or not * (exclude). * - \b id: id of the activity * - \b quickLaunch (optional): defines the activity that will be launched on a double-click on a series. The * launched activity depends of the series type (ImageSeries, ModelSeries, ...). * - \b association: allows to associate an activity to launch with a type of series * - \b type: type of series (data::image_series, data::model_series, ....) * - \b id: identifier of the activity. */ class launcher : public sight::ui::action { public: SIGHT_DECLARE_SERVICE(launcher, sight::ui::action); /// Constructor. Do nothing. launcher() noexcept; /// Destructor. Do nothing. ~launcher() noexcept override; /** * @name Slot API * @{ */ static const core::com::slots::key_t LAUNCH_SERIES_SLOT; static const core::com::slots::key_t LAUNCH_ACTIVITY_SLOT; static const core::com::slots::key_t UPDATE_STATE_SLOT; /// @} /** * @name Signal API * @{ */ using activity_launched_signal_t = core::com::signal; /// Key in m_signals map of signal m_sigActivityLaunched static const core::com::signals::key_t ACTIVITY_LAUNCHED_SIG; /// @} protected: /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection * * Connect Vector::ADDED_OBJECTS_SIG to this::UPDATE_STATE_SLOT * Connect Vector::REMOVED_OBJECTS_SIG to this::UPDATE_STATE_SLOT */ connections_t auto_connections() const override; ///This method launches the action::starting method. void starting() override; ///This method launches the action::stopping method. void stopping() override; /** * @brief Show activity selector. */ void updating() override; /** * @brief Initialize the action. * @see sight::ui::action::initialize() */ void configuring() override; using parameters_t = sight::activity::extension::activity_config_params_type; using keys_t = std::vector; using quick_launch_t = std::map; //// SLOT: Updates action state (enable if activities are available for current selection). virtual void update_state(); private: /** * @brief Launches activity if only Activity are selected. * @return Returns true if only Activity are selected. */ bool launch_as(const data::vector::csptr& _selection); /** * @brief Slots to launch the given series. * @param _series the activity is launched on this series. * * If series is an Activity, it is launched, otherwise it launches the first available activity for * this series or used m_quickLaunch information if a default association is defined for this series type. */ void launch_series(data::series::sptr _series); /** * @brief Slots to launch the given activity. * @param _activity the activity to launch. */ void launch_activity(data::activity::sptr _activity); /** * @brief Send message to launch new tab view * If given activity info contains an activity::validator::base, first checks if activity is valid according to * validator, then build activity with activity builder. * * @param _info activity information */ void send_config(const sight::activity::extension::activity_info& _info); /** * @brief Builds and launch activity with the input data given in selection. * * @param _info activity information * @param _selection input data to launch the activity */ void build_activity(const sight::activity::extension::activity_info& _info, const data::vector::csptr& _selection); using activity_infos_t = sight::activity::extension::activity::infos_t; /// Show custom dialog box static sight::activity::extension::activity_info show(const activity_infos_t& _infos); /// Returns enabled activity infos according to activity filter. activity_infos_t get_enabled_activities(const activity_infos_t& _infos); parameters_t m_parameters; /** * @brief filter mode : include or exclude activity configurations. * @note Allowed values : 'include' or 'exclude' */ std::string m_filter_mode; /// Id-s of activity configurations to be enabled or disabled, according to filter mode. keys_t m_keys; /// Signal emitted when activity is launched. Send a message containing the activity information. activity_launched_signal_t::sptr m_sig_activity_launched; /// launcher's mode (message or immediate) std::string m_mode; /// Defines quick association between series type (a classname) and an activity id used by launchSeries method quick_launch_t m_quick_launch; static constexpr std::string_view SERIES = "series"; data::ptr m_series {this, SERIES}; }; } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/selector.cpp000066400000000000000000000207531503402212300214220ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2016-2025 IRCAD France * Copyright (C) 2016-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ // cspell:ignore NOLINTNEXTLINE #include "selector.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(sight::activity::extension::activity_info) namespace sight::module::ui::qt::activity { //------------------------------------------------------------------------------ const core::com::signals::key_t selector::ACTIVITY_ID_SELECTED_SIG = "activity_id_selected"; const core::com::signals::key_t selector::LOAD_REQUESTED_SIG = "load_requested"; //------------------------------------------------------------------------------ selector::selector() noexcept { new_signal(ACTIVITY_ID_SELECTED_SIG); new_signal(LOAD_REQUESTED_SIG); } //------------------------------------------------------------------------------ selector::~selector() noexcept = default; //------------------------------------------------------------------------------ void selector::configuring() { sight::ui::service::initialize(); const auto cfg = this->get_config(); if(cfg.count("filter") == 1) { const service::config_t& config_filter = cfg.get_child("filter"); SIGHT_ASSERT("A maximum of 1 tag is allowed", config_filter.count("mode") < 2); const auto mode = config_filter.get("mode"); SIGHT_ASSERT( "'" + mode + "' value for tag isn't valid. Allowed values are : 'include', 'exclude'.", mode == "include" || mode == "exclude" ); m_filter_mode = mode; // NOLINTNEXTLINE(bugprone-branch-clone) BOOST_FOREACH(const config_t::value_type& v, config_filter.equal_range("id")) { m_keys.push_back(v.second.get("")); } } SIGHT_ASSERT("A maximum of 1 tag is allowed", cfg.count("filter") < 2); } //------------------------------------------------------------------------------ void selector::starting() { sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast(get_container()); auto* group_box = new QGroupBox(tr("Activity")); auto* scroll_area = new QScrollArea(); scroll_area->setWidget(group_box); scroll_area->setWidgetResizable(true); auto* main_layout = new QVBoxLayout(); main_layout->addWidget(scroll_area); m_button_group = new QButtonGroup(group_box); auto infos = sight::activity::extension::activity::get_default()->get_infos(); m_activities_info = this->get_enabled_activities(infos); // Add the load button sight::activity::extension::activity_info info_load; info_load.title = "Load activity"; info_load.icon = core::runtime::get_module_resource_file_path("sight::module::ui::icons", "load.svg").string(); info_load.description = "Load a previously saved activity."; m_activities_info.insert(m_activities_info.begin(), info_load); std::size_t index_button = 0; const float rows = std::sqrt(static_cast(m_activities_info.size())); int num_cols = static_cast(std::ceil(rows)); const int num_rows = static_cast(std::floor(rows)); num_cols = 2 * num_cols + 1; const QString service_id = QString::fromStdString(base_id()); QWidget* const container = qt_container->get_qt_container(); container->setObjectName(service_id); const std::string style_grid("QGridLayout#activities {" "border-width: 4px;" "}"); container->setStyleSheet(QString::fromUtf8(style_grid.c_str())); auto* activities_layout = new QGridLayout(); activities_layout->setRowMinimumHeight(0, 5); activities_layout->setRowStretch(0, 2); group_box->setLayout(activities_layout); QFont font; font.setPointSize(12); font.setBold(true); std::string style("* {" "padding: 16px;" "text-align:bottom" "}"); int i = 1; int j = 0; for(const auto& info : m_activities_info) { auto* button = new QPushButton(QIcon(info.icon.c_str()), QString::fromStdString(" " + info.title)); button->setToolTip(QString::fromStdString(info.description)); button->setIconSize(QSize(80, 80)); button->setObjectName(service_id + '/' + info.title.c_str()); button->setFont(font); button->setStyleSheet(QString::fromUtf8(style.c_str())); m_button_group->addButton(button, static_cast(index_button)); auto* label = new QLabel(QString::fromStdString(info.description)); label->setWordWrap(true); activities_layout->setColumnMinimumWidth(j, 10); activities_layout->setColumnStretch(j, 5); activities_layout->addWidget(button, i, j + 1); activities_layout->addWidget(label, i + 1, j + 1); j += 2; if(j == num_cols - 1) { activities_layout->setColumnMinimumWidth(j, 10); activities_layout->setColumnStretch(j, 5); i += 3; j = 0; } activities_layout->setRowMinimumHeight(i + 2, 5); activities_layout->setRowStretch(i + 2, 1); ++index_button; } activities_layout->setRowMinimumHeight(num_rows * 3, 5); activities_layout->setRowStretch(num_rows * 3, 2); qt_container->set_layout(main_layout); QObject::connect(m_button_group, &QButtonGroup::idClicked, this, &selector::on_clicked); } //------------------------------------------------------------------------------ void selector::stopping() { this->disconnect(); this->destroy(); } //------------------------------------------------------------------------------ void selector::updating() { } //------------------------------------------------------------------------------ void selector::on_clicked(int _id) { if(_id == 0) { auto sig = this->signal(LOAD_REQUESTED_SIG); sig->async_emit(); } else { auto sig = this->signal(ACTIVITY_ID_SELECTED_SIG); sig->async_emit(m_activities_info[static_cast(_id)].id); } } //------------------------------------------------------------------------------ selector::activity_infos_t selector::get_enabled_activities(const activity_infos_t& _infos) { activity_infos_t configs; if(m_filter_mode == "include" || m_filter_mode == "exclude") { const bool is_include_mode = m_filter_mode == "include"; for(const auto& info : _infos) { auto key_it = std::find(m_keys.begin(), m_keys.end(), info.id); if((key_it != m_keys.end() && is_include_mode) || (key_it == m_keys.end() && !is_include_mode)) { configs.push_back(info); } } } else { configs = _infos; } return configs; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/selector.hpp000066400000000000000000000105101503402212300214150ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2016-2024 IRCAD France * Copyright (C) 2016-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include namespace sight::module::ui::qt::activity { /** * @brief This editor launches an activity according to the given configuration * * This editor proposes all the available activities according to the given configuration. * It sends a signal with the activity identifier when a button is pushed. * * It should work with the module::ui::qt::editor::wizard that creates or updates the activity. * * @section Signals Signals * - \b activity_id_selected(std::string) : This signal is emitted when the activity is selected, it * contains all activity identifier. It should be connected to the slot 'createActivity' of the service * 'wizard'. * - \b load_requested() : This signal is emitted when the "load activity" button is pushed. * * @section XML XML Configuration * * @code{.xml} include activity_viz_negato 3DVisualizationActivity VolumeRenderingActivity @endcode * * - \b filter (optional): it allows to filter the activity that can be proposed. * - \b mode: 'include' or 'exclude'. Defines if the activity in the following list are proposed (include) or not * (exclude). * - \b id: id of the activity */ class selector : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(selector, sight::ui::editor); /// Constructor. Do nothing. selector() noexcept; /// Destructor. Do nothing. ~selector() noexcept override; /** * @name Signals API * @{ */ static const core::com::signals::key_t ACTIVITY_ID_SELECTED_SIG; using activity_id_selected_signal_t = core::com::signal; static const core::com::signals::key_t LOAD_REQUESTED_SIG; using load_requested_signal_t = core::com::signal; /// @} protected: /// Initialize the editor. void configuring() override; /// This method launches the editor::starting method. void starting() override; /// This method launches the editor::stopping method. void stopping() override; /// Show activity selector. void updating() override; using keys_t = std::vector; private Q_SLOTS: void on_clicked(int _id); private: /** * @brief Slots to launch the given activity. * @param _activity the activity to be launched. */ void launch_activity(data::activity::sptr _activity); using activity_infos_t = sight::activity::extension::activity::infos_t; /// Returns enabled activity infos according to activity filter. activity_infos_t get_enabled_activities(const activity_infos_t& _infos); /** * @brief filter mode : include or exclude activity configurations. * @note Allowed values : 'include' or 'exclude' */ std::string m_filter_mode; /// Id-s of activity configurations to be enabled or disabled, according to filter mode. keys_t m_keys; /// Informations used to launch activities activity_infos_t m_activities_info; /// Pointer on the buttons group QPointer m_button_group; }; } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/sequencer.cpp000066400000000000000000000433771503402212300216030ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2016-2025 IRCAD France * Copyright (C) 2016-2021 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "sequencer.hpp" #include #include #include #include #include #include #include #include #include #include #include // cspell:ignore Roboto namespace sight::module::ui::qt::activity { //------------------------------------------------------------------------------ static const std::string CLEAR_ACTIVITIES_CONFIG = "clearActivities"; static const std::string BUTTON_WIDTH = "buttonWidth"; static const std::string FONT_SIZE = "fontSize"; static const std::string WARNING_MESSAGE = "warning_message"; class number_icon final : public QIcon { public: explicit number_icon(int _number, const QColor& _background = Qt::white) { QPixmap pixmap(256, 256); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing); // Draw the white circle painter.setBrush(_background); painter.drawEllipse(0, 0, 255, 255); // Draw the number painter.setPen(Qt::transparent); painter.setBrush(Qt::transparent); painter.setCompositionMode(QPainter::CompositionMode_Clear); painter.setBackgroundMode(Qt::TransparentMode); painter.setFont(QFont("Roboto", 100, QFont::Bold)); painter.drawText(pixmap.rect(), Qt::AlignCenter, QString::number(_number)); painter.end(); this->addPixmap(pixmap, QIcon::Normal); } }; class sequencer_button final : public QPushButton { public: explicit sequencer_button(const QString& _text, QWidget* _parent = nullptr) : QPushButton(" " + _text, _parent) { } //------------------------------------------------------------------------------ [[nodiscard]] QSize minimumSizeHint() const final { // This allows well painted rounded button return QPushButton::minimumSizeHint().grownBy(QMargins(3, 0, 3, 0)); } //------------------------------------------------------------------------------ [[nodiscard]] QSize sizeHint() const final { // This allows well painted rounded button return QPushButton::sizeHint().grownBy(QMargins(3, 0, 3, 0)); } }; //------------------------------------------------------------------------------ inline static void set_button_enabled( QButtonGroup* _button_group, int _index, bool _enabled, std::optional _checked = std::nullopt ) { if(auto* const button = _button_group->button(_index); button != nullptr) { const auto& sibling = button->parentWidget()->findChildren(); if(const auto spacer_index = sibling.indexOf(button) - 1; spacer_index >= 0) { sibling[spacer_index]->setEnabled(_enabled); } button->setEnabled(_enabled); if(_checked.has_value()) { button->setChecked(*_checked); } } } sequencer::sequencer() noexcept { new_slot(slots::GO_TO, &sequencer::go_to, this); new_slot(slots::CHECK_NEXT, &sequencer::check_next, this); new_slot(slots::VALIDATE_NEXT, &sequencer::validate_next, this); new_slot(slots::NEXT, &sequencer::next, this); new_slot(slots::PREVIOUS, &sequencer::previous, this); new_slot(slots::SEND_INFO, &sequencer::send_info, this); new_slot(slots::RESET_REQUIREMENTS, &sequencer::reset_requirements, this); new_slot(slots::ENABLE_USER_WARNING, &sequencer::enable_user_warning, this); new_slot(slots::DISABLE_USER_WARNING, &sequencer::disable_user_warning, this); new_slot(slots::SET_USER_WARNING, &sequencer::set_user_warning, this); } //------------------------------------------------------------------------------ void sequencer::configuring() { this->sight::ui::service::initialize(); const service::config_t config = this->get_config(); auto pair = config.equal_range("activity"); auto it = pair.first; for( ; it != pair.second ; ++it) { m_activity_ids.push_back(it->second.get(".id")); m_activity_names.push_back(it->second.get(".name", "")); } m_clear_activities = config.get(CLEAR_ACTIVITIES_CONFIG, m_clear_activities); m_button_width = config.get(BUTTON_WIDTH, m_button_width); m_font_size = config.get(FONT_SIZE, m_font_size); m_warning_message = config.get(WARNING_MESSAGE, m_warning_message); m_warn_user = !m_warning_message.empty(); } //------------------------------------------------------------------------------ void sequencer::starting() { this->sight::ui::service::create(); const auto qt_container = std::dynamic_pointer_cast(get_container()); m_widget = new QWidget(); m_widget->setObjectName(base_id() + "/sequencer"); m_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_widget->setProperty("class", "sequencer"); auto* const widget_layout = new QHBoxLayout(); widget_layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); // Build buttons m_button_group = new QButtonGroup(m_widget); m_button_group->setExclusive(true); auto activity_reg = sight::activity::extension::activity::get_default(); for(int i = 0, last = int(m_activity_ids.size()) - 1 ; i <= last ; ++i) { const auto button_label = QString::fromStdString( m_activity_names[std::size_t(i)].empty() ? activity_reg->get_info(m_activity_ids[std::size_t(i)]).title : m_activity_names[std::size_t(i)] ); auto* const button = new sequencer_button(button_label, m_widget); button->setObjectName(m_widget->objectName() + "/" + button_label + "_button"); button->setProperty("class", "sequencer_button"); button->setCheckable(true); button->setEnabled(false); button->setIcon(number_icon(i + 1)); button->setIconSize(QSize(32, 32)); widget_layout->addWidget(button); m_button_group->addButton(button, i); if(i != last) { auto* const sequencer_spacer = new QFrame(m_widget); sequencer_spacer->setObjectName(m_widget->objectName() + "/" + button_label + "_spacer"); sequencer_spacer->setProperty("class", "sequencer_spacer"); sequencer_spacer->setFrameShape(QFrame::Shape::HLine); sequencer_spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); sequencer_spacer->setEnabled(false); widget_layout->addWidget(sequencer_spacer); } } widget_layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); m_widget->setLayout(widget_layout); QObject::connect(m_button_group, &QButtonGroup::idClicked, this, &sequencer::go_to); // Add the sequencer to the container auto* main_layout = qt_container->get_qt_container()->layout(); if(main_layout == nullptr) { main_layout = new QVBoxLayout(); main_layout->setContentsMargins(0, 0, 0, 0); qt_container->set_layout(main_layout); } main_layout->addWidget(m_widget); } //------------------------------------------------------------------------------ void sequencer::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void sequencer::updating() { { auto activity_set = m_activity_set.lock(); SIGHT_ASSERT("Missing '" << ACTIVITY_SET_INOUT << "' activity_set", activity_set); m_current_activity = this->parse_activities(*activity_set); } if(m_current_activity >= 0) { for(int i = 0 ; i <= m_current_activity ; ++i) { this->enable_activity(i); } // launch the last activity this->go_to(m_current_activity); } else { // launch the first activity this->enable_activity(0); this->go_to(0); } } //------------------------------------------------------------------------------ void sequencer::set_user_warning(bool _state) { // If warning message wasn't configured, force value to false. if(m_warning_message.empty()) { SIGHT_ERROR("No warning message is configured in sequencer"); m_warn_user = false; } else { m_warn_user = _state; } } //------------------------------------------------------------------------------ void sequencer::enable_user_warning() { this->set_user_warning(true); } //------------------------------------------------------------------------------ void sequencer::disable_user_warning() { this->set_user_warning(false); } //------------------------------------------------------------------------------ void sequencer::go_to(int _index) { if(_index < 0 || _index >= static_cast(m_activity_ids.size())) { SIGHT_ERROR("no activity to launch at index " << _index) return; } if(m_warn_user && m_current_activity != _index && m_current_activity >= 0) { auto dialog = sight::ui::dialog::message( "Warning", m_warning_message + "\nDo you want to continue?", sight::ui::dialog::message::warning ); dialog.add_button(sight::ui::dialog::message::yes_no); const auto button = dialog.show(); if((button == sight::ui::dialog::message::no)) { return; } } auto activity_set = m_activity_set.lock(); SIGHT_ASSERT("Missing '" << ACTIVITY_SET_INOUT << "' activity_set", activity_set); // Clear activities if go backward. if(m_clear_activities && m_current_activity > _index) { auto dialog = sight::ui::dialog::message( "Sequencer", "The data will be deleted! \nDo you want to continue?", sight::ui::dialog::message::warning ); dialog.add_button(sight::ui::dialog::message::yes_no); const auto button = dialog.show(); if((button == sight::ui::dialog::message::no)) { return; } // Disable all next activities (including current) for(int i = _index + 1, end = int(activity_set->size()) ; i < end ; ++i) { this->disable_activity(i); } // Remove all last activities. this->remove_last_activities(*activity_set, std::size_t(_index)); } // Store data otherwise. else if(m_current_activity >= 0) { this->store_activity_data(*activity_set, std::size_t(m_current_activity)); } const auto new_idx = static_cast(_index); data::activity::sptr activity = this->get_activity(*activity_set, new_idx, slot(service::slots::UPDATE)); bool ok = true; std::string error_msg; std::tie(ok, error_msg) = sight::module::ui::qt::activity::sequencer::validate_activity(activity); if(ok) { m_activity_created->async_emit(activity); m_current_activity = _index; set_button_enabled(m_button_group, _index, true, true); } else { sight::ui::dialog::message::show("Activity not valid", error_msg); m_data_required->async_emit(activity); } } //------------------------------------------------------------------------------ void sequencer::check_next() { auto activity_set = m_activity_set.lock(); SIGHT_ASSERT("Missing '" << ACTIVITY_SET_INOUT << "' activity_set", activity_set); // Store current activity data before checking the next one, // new data can be added in the current activity during the process. if(m_current_activity >= 0) { store_activity_data(*activity_set, std::size_t(m_current_activity)); } // Check if the next activity is valid (creates the activity if needed) // NOLINTNEXTLINE(bugprone-misplaced-widening-cast) const auto next_index = std::size_t(m_current_activity + 1); if(next_index < m_activity_ids.size()) { const auto& next_activity = this->get_activity(*activity_set, next_index, slot(service::slots::UPDATE)); const auto& [ok, error] = sequencer::validate_activity(next_activity); if(ok) { enable_activity(int(next_index)); } else { disable_activity(int(next_index)); SIGHT_DEBUG(error); } m_next_enabled->async_emit(ok); // Refresh next activities validity std::size_t last_valid = next_index; if(ok) { // Find the last valid activity while(++last_valid < activity_set->size()) { const auto& activity = activity_set->at(last_valid); const auto& [next_ok, next_error] = sequencer::validate_activity(activity); if(next_ok) { enable_activity(int(last_valid)); } else { disable_activity(int(last_valid)); SIGHT_DEBUG(next_error); break; } } } // Disable all next activities while(++last_valid < activity_set->size()) { disable_activity(int(last_valid)); } } } //------------------------------------------------------------------------------ void sequencer::validate_next() { auto activity_set = m_activity_set.lock(); SIGHT_ASSERT("Missing '" << ACTIVITY_SET_INOUT << "' activity_set", activity_set); // Store current activity data before checking the next one, // new data can be added in the current activity during the process. if(m_current_activity >= 0) { store_activity_data(*activity_set, std::size_t(m_current_activity)); } // Check if the next activity is valid (creates the activity if needed) // NOLINTNEXTLINE(bugprone-misplaced-widening-cast) const auto next_index = std::size_t(m_current_activity + 1); if(next_index < m_activity_ids.size()) { const auto& next_activity = this->get_activity(*activity_set, next_index, slot(service::slots::UPDATE)); const auto& [ok, error] = sight::module::ui::qt::activity::sequencer::validate_activity(next_activity); if(ok) { m_next_valid->async_emit(); } else { disable_activity(int(next_index)); SIGHT_DEBUG(error); m_next_invalid->async_emit(); } m_next_validated->async_emit(ok); // Refresh next activities validity std::size_t last_valid = next_index; if(ok) { // Find the last valid activity while(++last_valid < activity_set->size()) { const auto& activity = activity_set->at(last_valid); const auto& [next_ok, next_error] = sequencer::validate_activity(activity); if(!next_ok) { disable_activity(int(last_valid)); SIGHT_DEBUG(next_error); break; } } } // Disable all next activities while(++last_valid < activity_set->size()) { disable_activity(int(last_valid)); } } } //------------------------------------------------------------------------------ void sequencer::next() { const auto next_index = m_current_activity + 1; go_to(next_index); } //------------------------------------------------------------------------------ void sequencer::previous() { this->go_to(m_current_activity - 1); } //------------------------------------------------------------------------------ void sequencer::send_info() const { const bool previous_enabled = (m_current_activity > 0); m_has_previous->async_emit(previous_enabled); const bool next_enabled = (m_current_activity < static_cast(m_activity_ids.size()) - 1); m_has_next->async_emit(next_enabled); } //------------------------------------------------------------------------------ void sequencer::enable_activity(int _index) { set_button_enabled(m_button_group, _index, true); } //------------------------------------------------------------------------------ void sequencer::disable_activity(int _index) { set_button_enabled(m_button_group, _index, false); } //------------------------------------------------------------------------------ service::connections_t sequencer::auto_connections() const { connections_t connections; connections.push(ACTIVITY_SET_INOUT, data::activity_set::ADDED_OBJECTS_SIG, service::slots::UPDATE); connections.push(ACTIVITY_SET_INOUT, data::activity_set::MODIFIED_SIG, service::slots::UPDATE); return connections; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/sequencer.hpp000066400000000000000000000250611503402212300215760ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2016-2025 IRCAD France * Copyright (C) 2016-2021 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::ui::qt::activity { /** * @brief This editor displays an activity stepper that allows to select the activity to launch, and display the * current selection * * The order of the activities is given in the configuration. * * Activity are created for each activity using the data produced by the previous activities. This activities are * stored in the current ActivitySet. By default all the data are stored, you can to backward and forward as you want in * the existing activities. Using the tag 'clearActivities', you can remove the last activities when going backward to * force the user to re-generate the data. * * @warning If an activity can not be launched with the existing parameters, the signal 'dataRequired' is emitted. It * can be connected to an activity wizard to add the missing data, or you can supplied 'requirementOverrides' map. * * @note If the inout ActivitySet already contains activities, their are parsed and the sequencer open on the last * activities. Be careful to store them in the right order. * * @section Signal Signal * - \b activity_created(data::activity::sptr) : This signal is emitted when an activity is created (using * next() or previous(). * - \b data_required() : This signal is emitted when the activity can not be launch because it requires data. * - \b hasNext(bool): This signal is emitted on sendInfo() slot, with the information if an activity is present after * the current one. * - \b hasPrevious(bool): This signal is emitted on sendInfo() slot, with the information if an activity is present * before the current one. * - \b nextEnabled(bool): This signal is emitted when the next button is enabled and can be launched. * * @section Slots Slots * - \b next() : Create the next activity * - \b previous() : Create the next activity * - \b go_to(int) : Create the activity at the given index * - \b send_info() : Send the 'hasNext' and 'hasPrevious' signals for the current activity * - \b reset_requirements() : Reset requirements that have been created by the activity * * @section XML XML Configuration * @code{.xml} false 200 12 @endcode * @subsection In In * - \b requirementOverrides [sight::data::map] (optional): provide some data that will be passed as activity * requirements. Can be used to supply required data that cannot be created by the activity, or to override * data that would normally be passed from an activity to the next. * * @subsection In-Out In-Out * - \b activity_set [sight::data::activity_set]: used to store the Activity of the managed activities * * @subsection Configuration Configuration * - \b activity : * - \b id: id of the activities to launch. The first activity in the list is the first that will be launched. * - \b name(optional): name of the activity to display in the editor. If it is empty, the the activity's will be * used * - \b clearActivities (optional, default: false): defines if the activities and their requirements should be removed * when going backward. * - \b buttonWidth (optional): the width of the buttons of the sequencer. * - \b fontSize (optional): the size of the font used in the buttons of the sequencer. * * @todo listen the current activity data to notify when the next activity can be created */ class sequencer : public QObject, public sight::ui::editor, public sight::activity::sequencer { Q_OBJECT public: SIGHT_DECLARE_SERVICE(sequencer, sight::ui::editor); /// Initialize signals and slots sequencer() noexcept; /// Destructor. Do nothing. ~sequencer() noexcept override = default; struct slots final { using key_t = sight::core::com::slots::key_t; inline static const key_t GO_TO = "go_to"; inline static const key_t CHECK_NEXT = "check_next"; inline static const key_t VALIDATE_NEXT = "validate_next"; inline static const key_t NEXT = "next"; inline static const key_t PREVIOUS = "previous"; inline static const key_t SEND_INFO = "send_info"; inline static const key_t RESET_REQUIREMENTS = "reset_requirements"; inline static const key_t ENABLE_USER_WARNING = "enable_user_warning"; inline static const key_t DISABLE_USER_WARNING = "disable_user_warning"; inline static const key_t SET_USER_WARNING = "set_user_warning"; }; struct signals final { using key_t = sight::core::com::signals::key_t; inline static const key_t ACTIVITY_CREATED = "activity_created"; inline static const key_t DATA_REQUIRED = "data_required"; inline static const key_t HAS_PREVIOUS = "hasPrevious"; inline static const key_t HAS_NEXT = "hasNext"; inline static const key_t NEXT_ENABLED = "nextEnabled"; inline static const key_t NEXT_VALIDATED = "next_validated"; inline static const key_t NEXT_VALID = "next_valid"; inline static const key_t NEXT_INVALID = "next_invalid"; using void_signal_t = core::com::signal; using bool_signal_t = core::com::signal; using activity_signal_t = core::com::signal; }; /// Slot: Check if the next activities can be enabled void check_next(); /// Slot: Validate the next activities without enabling it. Emits nextValidated(true/false), next(In)Valid signals void validate_next(); /// Slot: Create the next activity, emit 'dataRequired' signal if the activity require additional data void next(); /// Slot: Create the previous activity, emit 'dataRequired' signal if the activity require additional data void previous(); /// Slot: Send the 'hasNext' and 'enablePrevious' signals for the current activity void send_info() const; // Slot: Reset requirements of activities using sight::activity::sequencer::reset_requirements; /// Slot: Enables the user warning dialog about possible loss of un-validated data /// Does nothing if "warning_message" wasn't configured. void enable_user_warning(); /// Slot: Disables the user warning dialog about possible loss of un-validated data /// Does nothing if "warning_message" wasn't configured. void disable_user_warning(); /// Slot: Configures the user warning according to _state value. /// Does nothing if "warning_message" wasn't configured. void set_user_warning(bool _state); public Q_SLOTS: /// Slot: create the activity at the given index, emit 'dataRequired' signal if the activity require additional data void go_to(int _index); protected: /// Parses the configuration void configuring() override; /// Create the sequencer widgets: launch Qml file void starting() override; /// Destroy the container void stopping() override; /** * @brief Analyse the contained in the current activity_set. * * - if the is an unknown activity, it is removed * - else, the activity data is stored in m_requirements * - the last activity is launched */ void updating() override; /// Connect the service to the ActivitySet signals connections_t auto_connections() const override; private: /// Invoke 'enableActivity' method in Qml file void enable_activity(int _index); /// Invokes 'disableActivity' method in Qml file void disable_activity(int _index); /// List of the activities std::vector m_activity_names; QPointer m_widget; QPointer m_button_group; /// Defines if the activities should be cleared when going backward bool m_clear_activities {false}; /// Display a warning message if changing activities without validating it. bool m_warn_user {false}; /// Configured warning message, if empty no warnings are displayed. std::string m_warning_message; std::string m_button_width {"200"}; double m_font_size {12.0}; const signals::activity_signal_t::sptr m_activity_created { new_signal(signals::ACTIVITY_CREATED) }; const signals::activity_signal_t::sptr m_data_required { new_signal(signals::DATA_REQUIRED) }; const signals::bool_signal_t::sptr m_has_previous { new_signal(signals::HAS_PREVIOUS) }; const signals::bool_signal_t::sptr m_has_next { new_signal(signals::HAS_NEXT) }; const signals::bool_signal_t::sptr m_next_enabled { new_signal(signals::NEXT_ENABLED) }; const signals::bool_signal_t::sptr m_next_validated { new_signal(signals::NEXT_VALIDATED) }; const signals::void_signal_t::sptr m_next_valid { new_signal(signals::NEXT_VALID) }; const signals::void_signal_t::sptr m_next_invalid { new_signal(signals::NEXT_INVALID) }; static constexpr std::string_view ACTIVITY_SET_INOUT = "activitySet"; data::ptr m_activity_set {this, ACTIVITY_SET_INOUT}; }; } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/view.cpp000066400000000000000000000117641503402212300205560ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2024 IRCAD France * Copyright (C) 2017-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "view.hpp" #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::activity { const core::com::signals::key_t ACTIVITY_LAUNCHED_SIG = "activity_launched"; static const std::string BORDER_CONFIG = "border"; //------------------------------------------------------------------------------ view::view() : m_sig_activity_launched(new_signal(ACTIVITY_LAUNCHED_SIG)) { } //------------------------------------------------------------------------------ view::~view() = default; //----------------------------------------------------------------------------- void view::configuring() { this->sight::ui::activity_view::configuring(); const config_t config_t = this->get_config(); const auto config = config_t.get_child_optional("config."); if(config) { m_border = config->get(BORDER_CONFIG, m_border); } } //------------------------------------------------------------------------------ void view::starting() { this->sight::ui::service::create(); const QString service_id = QString::fromStdString(base_id()); auto parent_container = std::dynamic_pointer_cast(this->get_container()); parent_container->get_qt_container()->setObjectName(service_id); auto* layout = new QVBoxLayout(); if(m_border >= 0) { layout->setContentsMargins(m_border, m_border, m_border, m_border); } auto* widget = new QWidget(); layout->addWidget(widget); widget->setObjectName(service_id + "/container"); auto sub_container = sight::ui::qt::container::widget::make(); sub_container->set_qt_container(widget); m_wid = this->get_id() + "_container"; sight::ui::registry::register_wid_container(m_wid, sub_container); parent_container->set_layout(layout); m_config_manager = sight::app::config_manager::make(); if(!m_main_activity_id.empty()) { data::activity::sptr activity = this->create_main_activity(); if(activity) { this->launch_activity(activity); } } } //------------------------------------------------------------------------------ void view::stopping() { if(m_config_manager && m_config_manager->started()) { m_config_manager->stop_and_destroy(); } auto sub_container = sight::ui::registry::get_wid_container(m_wid); sight::ui::registry::unregister_wid_container(m_wid); sub_container->destroy_container(); this->destroy(); } //------------------------------------------------------------------------------ void view::updating() { } //------------------------------------------------------------------------------ void view::launch_activity(data::activity::sptr _activity) { if(this->validate_activity(_activity)) { if(m_config_manager->started()) { m_config_manager->stop_and_destroy(); } auto [info, replacementMap] = sight::activity::extension::activity::get_default()->get_info_and_replacement_map( *_activity, m_parameters ); replacementMap["WID_PARENT"] = m_wid; replacementMap["GENERIC_UID"] = sight::app::extension::config::get_unique_identifier(info.app_config.id); try { m_config_manager->set_config(info.app_config.id, replacementMap); m_config_manager->launch(); m_sig_activity_launched->async_emit(_activity); } catch(std::exception& e) { sight::ui::dialog::message::show( "Activity launch failed", e.what(), sight::ui::dialog::message::critical ); SIGHT_ERROR(e.what()); } } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/view.hpp000066400000000000000000000073211503402212300205550ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2024 IRCAD France * Copyright (C) 2017-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::ui::qt::activity { /** * @brief This editor displays activities in a single view (when a new activity is launched, it replaces the previous * one). * * This service should receive signals containing Activity connected to the slot \b launch_activity. * * @section Signals Signals * - \b activity_launched( data::activity::sptr ): signal emitted when the activity is launched * * @section Slots Slots * - \b launch_activity( data::activity::sptr ): this slot allows to create a view for the given activity * * @section XML XML Configuration * @code{.xml} @endcode * - \b mainActivity (optional): information about the main activity. The activity will be generated. * This activity must not have requirement. * - \b id : identifier of the activity * - \b parameters (optional) : additional parameters used to launch the activities * - \b parameter: defines a parameter * - \b replace: name of the parameter as defined in the config * - \b by: defines the string that will replace the parameter name. * * @subsection Configuration Configuration * - \b border (optional, default="-1"): contents margins of the layout. */ class view : public sight::ui::activity_view { public: SIGHT_DECLARE_SERVICE(view, sight::ui::activity_view); /// Constructor. Do nothing. view(); /// Destructor. Do nothing. ~view() override; /** * @name Signal API * @{ */ using activity_launched_signal_t = core::com::signal; /** * @} */ protected: /// Configures the service. void configuring() final; /// Install the container. void starting() override; /// Destroy the container. void stopping() override; /// Do nothing void updating() override; private: /** * @brief Slot: Launch the given activity in the current view (stop and destroy the previous one). */ void launch_activity(data::activity::sptr _activity) override; /// Helper to launch activity configuration sight::app::config_manager::sptr m_config_manager; /// WID used to register the activity container std::string m_wid; activity_launched_signal_t::sptr m_sig_activity_launched; /// Contents margins of the layout. int m_border {-1}; }; } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/wizard.cpp000066400000000000000000000362701503402212300211030ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2016-2024 IRCAD France * Copyright (C) 2016-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "wizard.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::activity { //------------------------------------------------------------------------------ const core::com::slots::key_t wizard::CREATE_ACTIVITY_SLOT = "create_activity"; const core::com::slots::key_t wizard::UPDATE_ACTIVITY_SLOT = "update_activity"; const core::com::signals::key_t wizard::ACTIVITY_CREATED_SIG = "activity_created"; const core::com::signals::key_t wizard::ACTIVITY_UPDATED_SIG = "activity_updated"; const core::com::signals::key_t wizard::CANCELED_SIG = "canceled"; using sight::activity::extension::activity_info; using sight::activity::extension::activity; //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ wizard::wizard() noexcept { new_slot(CREATE_ACTIVITY_SLOT, &wizard::create_activity, this); new_slot(UPDATE_ACTIVITY_SLOT, &wizard::update_activity, this); m_sig_activity_created = new_signal(ACTIVITY_CREATED_SIG); m_sig_activity_updated = new_signal(ACTIVITY_UPDATED_SIG); m_sig_canceled = new_signal(CANCELED_SIG); } //------------------------------------------------------------------------------ wizard::~wizard() noexcept = default; //------------------------------------------------------------------------------ void wizard::configuring() { sight::ui::service::initialize(); const auto config = this->get_config(); m_io_selector_config = config.get("ioSelectorConfig", ""); SIGHT_ASSERT("ioSelector Configuration must not be empty", !m_io_selector_config.empty()); m_sdb_io_selector_config = config.get("sdbIoSelectorConfig", ""); if(m_sdb_io_selector_config.empty()) { m_sdb_io_selector_config = m_io_selector_config; } m_confirm_update = config.get("confirm", m_confirm_update); m_is_cancelable = config.get("cancel", m_is_cancelable); const auto icons_cfg = config.get_child("icons"); const auto icon_cfg = icons_cfg.equal_range("icon"); for(auto it_icon = icon_cfg.first ; it_icon != icon_cfg.second ; ++it_icon) { const auto another_icon_cfg = it_icon->second.get_child(""); const auto type = another_icon_cfg.get("type"); SIGHT_ASSERT("'type' attribute must not be empty", !type.empty()); const auto icon = another_icon_cfg.get("icon"); SIGHT_ASSERT("'icon' attribute must not be empty", !icon.empty()); const auto file = core::runtime::get_resource_file_path(icon); m_object_icons[type] = file.string(); } SIGHT_ASSERT("icons are empty", !m_object_icons.empty()); } //------------------------------------------------------------------------------ void wizard::starting() { sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast(get_container()); QWidget* const container = qt_container->get_qt_container(); auto* layout = new QVBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); m_title = new QLabel(""); m_title->setObjectName("SActivityWizard_title"); m_title->setAlignment(Qt::AlignHCenter); layout->addWidget(m_title); m_description = new QLabel(""); m_description->setObjectName("SActivityWizard_description"); m_description->setAlignment(Qt::AlignHCenter); layout->addWidget(m_description); // If the style sheet is empty, we are using the default theme. // If a style sheet is set, the style must be set in the style sheet. if(qApp->styleSheet().isEmpty()) { m_title->setStyleSheet("QLabel { font: bold; color: blue; }"); m_description->setStyleSheet("QLabel { font: italic; border: solid 1px;}"); } m_data_view = new data_view(); m_data_view->set_io_selector_config(m_io_selector_config); m_data_view->set_sdbio_selector_config(m_sdb_io_selector_config); m_data_view->set_object_icon_association(m_object_icons); layout->addWidget(m_data_view, 1); auto* button_layout = new QHBoxLayout(); layout->addLayout(button_layout); if(m_is_cancelable) { m_cancel_button = new QPushButton("Cancel"); m_cancel_button->setToolTip("Cancel the activity creation"); button_layout->addWidget(m_cancel_button); } m_reset_button = new QPushButton("Clear"); m_reset_button->setToolTip("Clear the current selected data"); button_layout->addWidget(m_reset_button); m_ok_button = new QPushButton("Apply"); m_ok_button->setToolTip("Create or update the activity with the selected data"); button_layout->addWidget(m_ok_button); container->setLayout(layout); QObject::connect( m_data_view.data(), &data_view::currentChanged, this, &wizard::on_tab_changed ); QObject::connect(m_ok_button.data(), &QPushButton::clicked, this, &wizard::on_build_activity); QObject::connect(m_reset_button.data(), &QPushButton::clicked, this, &wizard::on_reset); if(m_is_cancelable) { QObject::connect(m_cancel_button.data(), &QPushButton::clicked, this, &wizard::on_cancel); } } //------------------------------------------------------------------------------ void wizard::stopping() { m_data_view->clear(); QObject::disconnect( m_data_view.data(), &data_view::currentChanged, this, &wizard::on_tab_changed ); QObject::disconnect(m_ok_button.data(), &QPushButton::clicked, this, &wizard::on_build_activity); QObject::disconnect(m_reset_button.data(), &QPushButton::clicked, this, &wizard::on_reset); if(m_is_cancelable) { QObject::disconnect(m_cancel_button.data(), &QPushButton::clicked, this, &wizard::on_cancel); } this->destroy(); } //------------------------------------------------------------------------------ void wizard::updating() { auto as = m_activity.lock(); if(as) { this->update_activity(as.get_shared()); } else { SIGHT_DEBUG("activity is not defined, it cannot be updated"); } } //------------------------------------------------------------------------------ void wizard::create_activity(std::string _activity_id) { m_mode = mode::create; activity_info info; info = activity::get_default()->get_info(_activity_id); // load activity module core::runtime::start_module(info.bundle_id); m_new_activity = std::make_shared(); m_new_activity->set_activity_config_id(info.id); m_title->setText(QString("

%1

").arg(QString::fromStdString(info.title))); m_description->setText(QString::fromStdString(info.description)); bool need_config = false; // If we have requirements but they are not needed to start (maxOccurs = 0), we can skip the config as well for(const auto& req : info.requirements) { if(req.max_occurs > 0) { need_config = true; break; } } if(need_config) { m_data_view->fill_information(info); if(m_data_view->count() > 1) { m_ok_button->setText("Next"); } this->slot(slots::SHOW)->async_run(); } else { // Create data automatically if they are not provided by the user for(const auto& req : info.requirements) { SIGHT_ASSERT("minOccurs and maxOccurs should be 0", req.min_occurs == 0 && req.max_occurs == 0); (*m_new_activity)[req.name] = sight::activity::detail::data::create(req.type, req.object_config); } const auto activity_set = m_activity_set.lock(); SIGHT_ASSERT("The inout key '" << ACTIVITY_SET << "' is not defined.", activity_set); const auto scoped_emitter = activity_set->scoped_emit(); activity_set->push_back(m_new_activity); m_sig_activity_created->async_emit(m_new_activity); } } //------------------------------------------------------------------------------ void wizard::update_activity(data::activity::sptr _activity) { activity_info info; info = activity::get_default()->get_info(_activity->get_activity_config_id()); // load activity module core::runtime::start_module(info.bundle_id); m_title->setText(QString("

%1

").arg(QString::fromStdString(info.title))); m_description->setText(QString::fromStdString(info.description)); m_mode = mode::update; m_new_activity = _activity; bool need_config = false; // If we have requirements but they are not needed to start (maxOccurs = 0), we can skip the config as well for(const auto& req : info.requirements) { if(req.max_occurs != 0) { need_config = true; break; } } if(need_config) { m_data_view->fill_information(m_new_activity); if(m_data_view->count() > 1) { m_ok_button->setText("Next"); } } else { // Start immediately without popping any configuration UI data::object::modified_signal_t::sptr sig; sig = m_new_activity->signal(data::object::MODIFIED_SIG); sig->async_emit(); m_sig_activity_updated->async_emit(m_new_activity); } } //------------------------------------------------------------------------------ void wizard::on_tab_changed(int _index) { if(_index == m_data_view->count() - 1) { m_ok_button->setText("Apply"); } else { m_ok_button->setText("Next"); } } //------------------------------------------------------------------------------ void wizard::on_reset() { if(m_new_activity) { activity_info info; info = activity::get_default()->get_info(m_new_activity->get_activity_config_id()); m_data_view->fill_information(info); if(m_data_view->count() > 1) { m_ok_button->setText("Next"); } } } //------------------------------------------------------------------------------ void wizard::on_cancel() { m_data_view->clear(); m_sig_canceled->async_emit(); } //------------------------------------------------------------------------------ void wizard::on_build_activity() { int index = m_data_view->currentIndex(); int last_tab = m_data_view->count() - 1; if(index < 0) { return; } std::string error_msg; // Check current data if(m_data_view->check_data(std::size_t(index), error_msg)) { if(index != last_tab) { // enable and select the next tab m_data_view->setTabEnabled(index + 1, true); m_data_view->setCurrentIndex(index + 1); } else // index == lastTab { // Create/update activity if(m_mode == mode::update && m_confirm_update) { QMessageBox::StandardButton button = QMessageBox::question( qApp->activeWindow(), "Update activity", "You will override your activity. You could loose some data.\n" "Would you duplicate your activity ?", QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No ); if(button == QMessageBox::Cancel) { return; } if(button == QMessageBox::Yes) { m_new_activity = data::object::copy(m_new_activity); m_mode = mode::create; // The new activity should be added in the activity_set } } // check all data and create/update the activity bool ok = m_data_view->check_and_compute_data(m_new_activity, error_msg); if(ok) { if(m_mode == mode::create) { // Add the new activity in activity_set activity_info info = activity::get_default()->get_info(m_new_activity->get_activity_config_id()); const auto& [description, input_ok] = sight::ui::dialog::input::show_input_dialog( "Activity creation", "Please, give a description of the activity.", info.title ); if(!input_ok && description.empty()) { return; } m_new_activity->set_description(description); const auto activity_set = m_activity_set.lock(); SIGHT_ASSERT("The inout key '" << ACTIVITY_SET << "' is not defined.", activity_set); const auto scoped_emitter = activity_set->scoped_emit(); activity_set->push_back(m_new_activity); m_sig_activity_created->async_emit(m_new_activity); } else // m_mode == Mode::UPDATE { data::object::modified_signal_t::sptr sig; sig = m_new_activity->signal(data::object::MODIFIED_SIG); sig->async_emit(); m_sig_activity_updated->async_emit(m_new_activity); } } else { QString message = "This activity can not be created : \n"; message.append(QString::fromStdString(error_msg)); QMessageBox::warning(qApp->activeWindow(), "Activity Creation", message); SIGHT_ERROR(error_msg); } } } else { QMessageBox::warning(qApp->activeWindow(), "Error", QString::fromStdString(error_msg)); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/activity/wizard.hpp000066400000000000000000000171241503402212300211050ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2016-2024 IRCAD France * Copyright (C) 2016-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "module/ui/qt/activity/data_view.hpp" #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::activity { /** * @brief This editor allows to select the data required by an activity in order to create the Activity. * * This editor displays a tab widget (one tab by data). * It works on a data::activity_set and adds the created activity into the activity_set. * * @section Slots Slots * - \b create_activity(std::string) : This slot displays the gui allowing to create a data::activity with * the required data for the given activity. * - \b update_activity(data::activity::sptr) : This slot displays the gui allowing to update the required * data for the given activity. * * @section Signals Signals * - \b activity_created(data::activity::sptr) : This signal is emitted when the activity is built. * - \b activity_updated(data::activity::sptr) : This signal is emitted when the activity is updated. * - \b canceled() : This signal is emitted when the cancel button is clicked. * * @section XML XML Configuration * * @code{.xml} config config activityUid true true @endcode * @subsection In-Out In-Out * - \b activity_set [sight::data::activity_set]: activity_set to store the create activity * - \b activity [sight::data::activity] (optional): uid of the activity to update. It is only used when * the update() method is called. * @subsection Configuration Configuration * - \b ioSelectorConfig : configuration for the selector service used to import data in this editor. * - \b sdbIoSelectorConfig(optional, default: ioSelectorConfig): configuration for the selector service used to * import data in this editor from a ActivitySet. * - \b icons : defines the icons displayed for a type of data * - \b type : type of data * - \b icon : path of the icon to display * - \b confirm (optional, true by default): if true, the editor proposes a confirmation dialog when the activity is * updated. * - \b cancel (optional, true by default): if true, the editor proposes a cancel button. On cancel click, the editor is * emptied and the signal 'canceled' is emitted. */ class wizard : public QObject, public sight::ui::editor { public: SIGHT_DECLARE_SERVICE(wizard, sight::ui::editor); /// Constructor. Do nothing. wizard() noexcept; /// Destructor. Do nothing. ~wizard() noexcept override; /** * @name Slot API * @{ */ static const core::com::slots::key_t CREATE_ACTIVITY_SLOT; static const core::com::slots::key_t UPDATE_ACTIVITY_SLOT; /// @} /** * @name Signal API * @{ */ static const core::com::signals::key_t ACTIVITY_CREATED_SIG; using activity_created_signal_t = core::com::signal; static const core::com::signals::key_t ACTIVITY_UPDATED_SIG; using activity_updated_signal_t = core::com::signal; static const core::com::signals::key_t CANCELED_SIG; using canceled_signal_t = core::com::signal; /// @} protected: ///This method creates the editor gui. void starting() override; ///This method destroys the editor gui. void stopping() override; /// Update the activity if it is defined in the configuration, else does nothing. void updating() override; /// Initialize the editor. void configuring() override; private: enum class mode : std::uint8_t { create, update }; /// SLOT: Displays the gui allowing to select the required data for the activity. void create_activity(std::string _activity_id); /// SLOT: Displays the gui allowing to update the required data for the activity. void update_activity(data::activity::sptr _activity); /** * @brief Called when the user click on the 'apply' Button. * * if mode == CREATE : It creates the activity and add it to the activity_set. * else, it update the current activity. */ void on_build_activity(); /// Called when the tab selection changed. It check if the current data are properly selected. void on_tab_changed(int _index); /// Called when the user click on the 'reset' Button. void on_reset(); /// Called when the user click on the 'cancel' Button. void on_cancel(); data::activity::sptr m_new_activity; ///< Activity builded QPointer m_data_view; ///< view used to select required data for activity QPointer m_ok_button; ///< Button 'Apply' or 'Next' to validate the activity creation QPointer m_reset_button; ///< Button to clear the activity parameters QPointer m_cancel_button; ///< Button to cancel the activity creation QPointer m_title; ///< Label to show activity title QPointer m_description; ///< Label to show activity description std::string m_io_selector_config; ///< configuration used to import data std::string m_sdb_io_selector_config; ///< configuration used to import data from e ActivitySet data_view::object_icon_map_t m_object_icons; ///< Map defining the icon associated to an object. mode m_mode {mode::create}; ///< editor mode (CREATE or UPDATE) bool m_confirm_update {true}; ///< if true, the editor proposes a confirmation dialog when the activity is updated. bool m_is_cancelable {true}; /// true if the cancel button is proposed activity_created_signal_t::sptr m_sig_activity_created; ///< Signal emitted when the activity is created activity_created_signal_t::sptr m_sig_activity_updated; ///< Signal emitted when the activity is updated canceled_signal_t::sptr m_sig_canceled; /// Signal emitted when the creation is canceled. static constexpr std::string_view ACTIVITY_SET = "activitySet"; data::ptr m_activity_set {this, ACTIVITY_SET}; data::ptr m_activity {this, "activity"}; }; } // namespace sight::module::ui::qt::activity sight-25.1.0/module/ui/qt/calibration/000077500000000000000000000000001503402212300175225ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/calibration/calibration_info_editor.cpp000066400000000000000000000227671503402212300251140ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/calibration/calibration_info_editor.hpp" #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { const core::com::slots::key_t calibration_info_editor::REMOVE_SLOT = "remove"; const core::com::slots::key_t calibration_info_editor::RESET_SLOT = "reset"; const core::com::slots::key_t calibration_info_editor::GET_SELECTION_SLOT = "get_selection"; // ---------------------------------------------------------------------------- calibration_info_editor::calibration_info_editor() noexcept { new_slot(REMOVE_SLOT, &calibration_info_editor::remove, this); new_slot(RESET_SLOT, &calibration_info_editor::reset, this); new_slot(GET_SELECTION_SLOT, &calibration_info_editor::get_selection, this); } // ---------------------------------------------------------------------------- void calibration_info_editor::updating() { const auto cal_info1 = m_calibration_info1.lock(); SIGHT_ASSERT("Object " << CALIBRATION_INFO_1 << " is not a CalibrationInfo !", cal_info1); const auto pl_list1 = cal_info1->get_point_list_container(); m_captures_list_widget->clear(); const auto cal_info2 = m_calibration_info2.lock(); if(cal_info2) { const auto pl_list2 = cal_info2->get_point_list_container(); std::size_t capture_idx = 0; auto it1 = pl_list1.begin(); auto it2 = pl_list2.begin(); for( ; it1 != pl_list1.end() && it2 != pl_list2.end() ; ++it1, ++it2) { QString count_string; std::size_t count1 = (*it1)->get_points().size(); std::size_t count2 = (*it2)->get_points().size(); count_string = QString("%1. %2 and %3 elements").arg(capture_idx).arg(count1).arg(count2); m_captures_list_widget->addItem(count_string); ++capture_idx; } m_nb_captures_label->setText(QString().setNum(capture_idx)); if(pl_list1.size() != pl_list2.size()) { const auto* const err_msg = "Left and right calibration input datasets do not have the same size.\n\n" "Your images may be out of sync."; sight::ui::dialog::message::show( "Inputs do not match", err_msg, sight::ui::dialog::message::warning ); } } else { std::size_t capture_idx = 0; for(const auto& it1 : pl_list1) { QString count_string; std::size_t count = it1->get_points().size(); count_string = QString("%1. %2 element%3").arg(capture_idx).arg(count).arg(count > 1 ? "s" : ""); m_captures_list_widget->addItem(count_string); ++capture_idx; } m_nb_captures_label->setText(QString().setNum(capture_idx)); } } // ---------------------------------------------------------------------------- void calibration_info_editor::configuring() { sight::ui::service::initialize(); } // ---------------------------------------------------------------------------- void calibration_info_editor::starting() { sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast(get_container()); // Creation of the Qt elements // Main container, VBox auto* v_layout = new QVBoxLayout(); // First HBox, displays number of items and the remove button auto* nb_items_h_box = new QHBoxLayout(); // Fill the nbItemsHBox auto* label = new QLabel("nb captures:"); nb_items_h_box->addWidget(label); const QString service_id = QString::fromStdString(base_id()); m_nb_captures_label = new QLabel("-"); m_nb_captures_label->setObjectName(service_id + "/nbCapturesLabel"); nb_items_h_box->addWidget(m_nb_captures_label); nb_items_h_box->addStretch(); // The ListWidget m_captures_list_widget = new QListWidget(); m_captures_list_widget->setObjectName(service_id + "/capturesListWidget"); QObject::connect( m_captures_list_widget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(on_item_double_clicked(QListWidgetItem*)) ); // Fill the main VBox v_layout->addLayout(nb_items_h_box); v_layout->addWidget(m_captures_list_widget); qt_container->set_layout(v_layout); this->updating(); } // ---------------------------------------------------------------------------- void calibration_info_editor::stopping() { sight::ui::service::destroy(); } // ---------------------------------------------------------------------------- void calibration_info_editor::remove() { int row = m_captures_list_widget->currentRow(); if(row >= 0) { const auto idx = static_cast(row); { const auto cal_info1 = m_calibration_info1.lock(); SIGHT_ASSERT("Object " << CALIBRATION_INFO_1 << " is not a CalibrationInfo !", cal_info1); const auto cal_info2 = m_calibration_info2.lock(); cal_info1->remove_record(idx); //Notify { auto sig = cal_info1->signal( data::calibration_info::REMOVED_RECORD_SIG ); core::com::connection::blocker block(sig->get_connection(slot(service::slots::UPDATE))); sig->async_emit(); } if(cal_info2) { cal_info2->remove_record(idx); //Notify { auto sig = cal_info2->signal( data::calibration_info::REMOVED_RECORD_SIG ); core::com::connection::blocker block(sig->get_connection(slot(service::slots::UPDATE))); sig->async_emit(); } } } this->updating(); } } // ---------------------------------------------------------------------------- void calibration_info_editor::reset() { const auto cal_info1 = m_calibration_info1.lock(); SIGHT_ASSERT("Object " << CALIBRATION_INFO_1 << " is not a CalibrationInfo !", cal_info1); const auto cal_info2 = m_calibration_info2.lock(); cal_info1->reset_records(); //Notify { auto sig = cal_info1->signal( data::calibration_info::RESET_RECORD_SIG ); core::com::connection::blocker block(sig->get_connection(slot(service::slots::UPDATE))); sig->async_emit(); } if(cal_info2) { cal_info2->reset_records(); //Notify { auto sig = cal_info2->signal( data::calibration_info::RESET_RECORD_SIG ); core::com::connection::blocker block(sig->get_connection(slot(service::slots::UPDATE))); sig->async_emit(); } } m_captures_list_widget->clear(); m_nb_captures_label->setText("0"); } // ---------------------------------------------------------------------------- void calibration_info_editor::get_selection() { int row = m_captures_list_widget->currentRow(); if(row >= 0) { const auto idx = static_cast(row); const auto cal_info1 = m_calibration_info1.lock(); SIGHT_ASSERT("Object " << CALIBRATION_INFO_1 << " is not a CalibrationInfo !", cal_info1); //Notify { auto sig = cal_info1->signal( data::calibration_info::GET_RECORD_SIG ); sig->async_emit(idx); } } } // ---------------------------------------------------------------------------- service::connections_t calibration_info_editor::auto_connections() const { connections_t connections; connections.push(CALIBRATION_INFO_1, data::object::MODIFIED_SIG, service::slots::UPDATE); connections.push(CALIBRATION_INFO_2, data::object::MODIFIED_SIG, service::slots::UPDATE); return connections; } // ---------------------------------------------------------------------------- void calibration_info_editor::on_item_double_clicked(QListWidgetItem* /*unused*/) { this->get_selection(); } // ---------------------------------------------------------------------------- } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/calibration_info_editor.hpp000066400000000000000000000105211503402212300251020ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { /** * @brief calibration_info_editor service is used to handle the calibration points acquisition. * * * @section Slots Slots * - \b remove() : removes the current selected image. * - \b reset() : clears all the calibration information. * - \b get_selection() : emits the CalibrationInfo signal 'getRecord(index)' with the current selection index. * * @section XML XML Configuration * @code{.xml} @endcode * @subsection Input Input * - \b calInfo1 [sight::data::calibration_info]: calibration information for first camera. * - \b calInfo2 [sight::data::calibration_info] (optional): calibration information for optional second camera. */ class calibration_info_editor final : public QObject, public sight::ui::editor { Q_OBJECT; public: SIGHT_DECLARE_SERVICE(calibration_info_editor, sight::ui::editor); /** * @name Slots API * @{ */ static const core::com::slots::key_t REMOVE_SLOT; static const core::com::slots::key_t RESET_SLOT; static const core::com::slots::key_t GET_SELECTION_SLOT; ///@} calibration_info_editor() noexcept; ~calibration_info_editor() noexcept final = default; protected: /// Initializes the editor void configuring() final; /** * @brief Starting method : This method is used to initialize the service. */ void starting() final; /** * @brief Stopping method : This method is used to stop the service. */ void stopping() final; /** * @brief Updating method : This method is used to update the service. */ void updating() final; /** * @brief Slot: removes the current selected image. */ void remove(); /** * @brief Slot:clears all the calibration information. */ void reset(); /** * @brief Slot: emits the CalibrationInfo signal 'getRecord(index)' with the current selection index. */ void get_selection(); /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection */ connections_t auto_connections() const final; private Q_SLOTS: /** * @brief Slot called when an element is double-clicked in the list widget. */ void on_item_double_clicked(QListWidgetItem* /*unused*/); private: /** * @brief Index of the last acquisition. */ int m_capture_idx {}; /** * @brief Label displaying the number of point acquisitions. */ QPointer m_nb_captures_label; /** * @brief Calibration point list. */ QPointer m_captures_list_widget; static constexpr std::string_view CALIBRATION_INFO_1 = "calInfo1"; static constexpr std::string_view CALIBRATION_INFO_2 = "calInfo2"; data::ptr m_calibration_info1 {this, CALIBRATION_INFO_1}; data::ptr m_calibration_info2 {this, CALIBRATION_INFO_2, true}; }; } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/camera_config_launcher.cpp000066400000000000000000000424651503402212300246770ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/calibration/camera_config_launcher.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { camera_config_launcher::camera_config_launcher() noexcept = default; //------------------------------------------------------------------------------ camera_config_launcher::~camera_config_launcher() noexcept = default; //------------------------------------------------------------------------------ void camera_config_launcher::configuring() { this->initialize(); service::config_t configuration = this->get_config(); SIGHT_ASSERT( "There must be one (and only one) element.", configuration.count("config") == 1 ); const service::config_t& srvconfig = configuration; const service::config_t& config = srvconfig.get_child("config"); const service::config_t& intrinsic = config.get_child("intrinsic"); const service::config_t& extrinsic = config.get_child("extrinsic"); m_intrinsic_launcher.parse_config(intrinsic, this->get_sptr()); m_extrinsic_launcher.parse_config(extrinsic, this->get_sptr()); } //------------------------------------------------------------------------------ void camera_config_launcher::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); auto* layout = new QHBoxLayout(); m_camera_combo_box = new QComboBox(); layout->addWidget(m_camera_combo_box); QIcon add_icon(QString::fromStdString( core::runtime::get_module_resource_file_path( "sight::module::ui::icons", "plus.svg" ) .string() )); m_add_button = new QPushButton(add_icon, ""); m_add_button->setToolTip("Add a new camera."); layout->addWidget(m_add_button); QIcon import_icon(QString::fromStdString( core::runtime::get_module_resource_file_path( "sight::module::ui::icons", "import.svg" ) .string() )); m_import_button = new QPushButton(import_icon, ""); m_import_button->setToolTip("Import an intrinsic calibration."); layout->addWidget(m_import_button); QIcon remove_icon(QString::fromStdString( core::runtime::get_module_resource_file_path( "sight::module::ui::icons", "minus.svg" ) .string() )); m_remove_button = new QPushButton(remove_icon, ""); m_remove_button->setToolTip("Remove the camera."); layout->addWidget(m_remove_button); m_extrinsic_button = new QPushButton("Extrinsic"); layout->addWidget(m_extrinsic_button); m_extrinsic_button->setCheckable(true); qt_container->set_layout(layout); std::size_t nb_cam = 0; { const auto camera_set = m_camera_set.lock(); SIGHT_ASSERT("Missing cameraSet.", camera_set); nb_cam = camera_set->size(); } if(nb_cam == 0) { this->add_camera(); m_extrinsic_button->setEnabled(false); m_remove_button->setEnabled(false); } else { for(std::size_t i = 0 ; i < nb_cam ; ++i) { m_camera_combo_box->addItem(QString("Camera %1").arg(i + 1)); } const bool more_than_one_camera = (nb_cam > 1); m_extrinsic_button->setEnabled(more_than_one_camera); m_remove_button->setEnabled(more_than_one_camera); this->start_intrinsic_config(0); } QObject::connect(m_camera_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(on_camera_changed(int))); QObject::connect(m_add_button, &QPushButton::clicked, this, &self_t::on_add_clicked); QObject::connect(m_import_button, &QPushButton::clicked, this, &self_t::on_import_clicked); QObject::connect(m_remove_button, &QPushButton::clicked, this, &self_t::on_remove_clicked); QObject::connect(m_extrinsic_button, &QPushButton::toggled, this, &self_t::on_extrinsic_toggled); } //------------------------------------------------------------------------------ void camera_config_launcher::stopping() { m_intrinsic_launcher.stop_config(); m_extrinsic_launcher.stop_config(); this->destroy(); } //------------------------------------------------------------------------------ void camera_config_launcher::updating() { } //------------------------------------------------------------------------------ void camera_config_launcher::on_camera_changed(int _index) { { const auto camera_set = m_camera_set.lock(); SIGHT_ASSERT( "Bad index: " << _index, _index >= 0 && static_cast(_index) < camera_set->size() ); } if(_index == 0) { m_extrinsic_button->setChecked(false); m_extrinsic_button->setEnabled(false); } else { m_extrinsic_button->setEnabled(true); } if(m_extrinsic_button->isChecked()) { this->start_extrinsic_config(static_cast(_index)); } else { this->start_intrinsic_config(static_cast(_index)); } } //------------------------------------------------------------------------------ void camera_config_launcher::on_add_clicked() { m_extrinsic_button->setEnabled(true); m_remove_button->setEnabled(true); this->add_camera(); } //------------------------------------------------------------------------------ void camera_config_launcher::on_import_clicked() { auto vector = std::make_shared(); auto reader = sight::service::add("sight::module::io::session::reader"); reader->set_inout(vector, io::service::DATA_KEY); try { service::config_t config; config.add("dialog..extension", ".cam"); config.add("dialog..description", "Cameras"); reader->configure(config); reader->start(); reader->open_location_dialog(); reader->update(); reader->stop(); } catch(std::exception const& e) { sight::ui::dialog::message dlg; const auto msg = "Cannot read file: " + std::string(e.what()); dlg.set_title("Read error"); dlg.set_message(msg); dlg.set_icon(sight::ui::dialog::message::icons::critical); SIGHT_ERROR(msg); throw; } sight::service::remove(reader); QStringList cameras; std::map camera_map; std::size_t n_set = 0; for(const auto& object : *vector) { const auto& camera_set = std::dynamic_pointer_cast(object); if(camera_set) { for(std::size_t n_camera = 0, end = camera_set->size() ; n_camera != end ; ++n_camera) { const auto& camera = camera_set->get_camera(n_camera); const auto& camera_id = camera->get_camera_id() + " [" + std::to_string(n_set) + ", " + std::to_string(n_camera) + "]"; camera_map.insert(std::make_pair(camera_id, camera)); cameras << QString::fromStdString(camera_id); } ++n_set; } } if(n_set == 0) { sight::ui::dialog::message::show( "No CameraSet in file", "There are no CameraSet present in the loaded file.", sight::ui::dialog::message::critical ); } else if(cameras.empty()) { sight::ui::dialog::message::show( "No Cameras in file", "There are CameraSet present in the loaded CameraSet, but no Cameras were found", sight::ui::dialog::message::critical ); } else { auto qt_container = std::dynamic_pointer_cast(this->get_container()); bool ok = false; auto selected = QInputDialog::getItem( qt_container->get_qt_container(), "Please select a camera", "Camera", cameras, 0, false, &ok ); if(ok) { const auto selected_std = selected.toStdString(); const auto selected_camera = camera_map[selected_std]; const auto cam_idx = m_camera_combo_box->currentIndex(); const auto camera_set = m_camera_set.lock(); auto camera = camera_set->get_camera(std::size_t(cam_idx)); camera->deep_copy(selected_camera); camera->signal( data::camera::INTRINSIC_CALIBRATED_SIG ) ->async_emit(); } } } //------------------------------------------------------------------------------ void camera_config_launcher::on_remove_clicked() { const auto index = static_cast(m_camera_combo_box->currentIndex()); if(index > 0) { m_camera_combo_box->blockSignals(true); { const auto camera_set = m_camera_set.lock(); // Remove camera data::camera::sptr camera = camera_set->get_camera(index); camera_set->remove_camera(camera); auto sig = camera_set->signal( data::camera_set::REMOVED_CAMERA_SIG ); sig->async_emit(camera); // Remove calibrationInfo std::string calibration_info_key = "calibrationInfo_" + std::to_string(index); const auto activity = m_activity.lock(); activity->erase(calibration_info_key); const std::size_t nb_cam = camera_set->size(); if(nb_cam == 1) { m_extrinsic_button->setEnabled(false); m_remove_button->setEnabled(false); } // Renamed all items from 1 to nbCam m_camera_combo_box->clear(); for(std::size_t i = 0 ; i < nb_cam ; ++i) { m_camera_combo_box->addItem(QString("Camera %1").arg(i + 1)); } } // select first camera m_camera_combo_box->setCurrentIndex(0); this->start_intrinsic_config(0); m_camera_combo_box->blockSignals(false); } else { sight::ui::dialog::message::show("Warning", "The first camera can not be deleted"); } } //------------------------------------------------------------------------------ void camera_config_launcher::on_extrinsic_toggled(bool _checked) { std::size_t index = 0; { const auto camera_set = m_camera_set.lock(); index = static_cast(m_camera_combo_box->currentIndex()); SIGHT_ASSERT("Bad index: " << index, index < camera_set->size()); } if(_checked) { this->start_extrinsic_config(index); } else { this->start_intrinsic_config(index); } } //------------------------------------------------------------------------------ void camera_config_launcher::start_intrinsic_config(std::size_t _index) { sight::app::field_adaptor_t replace_map; { const auto camera_set = m_camera_set.lock(); data::camera::sptr camera = camera_set->get_camera(_index); std::string calibration_info_key = "calibrationInfo_" + std::to_string(_index); const auto activity = m_activity.lock(); auto calib_info = std::dynamic_pointer_cast((*activity)[calibration_info_key]); const auto board_properties = m_board_properties.lock(); replace_map["camera"] = camera->get_id(); replace_map["calibrationInfo"] = calib_info->get_id(); replace_map["board_properties"] = board_properties->get_id(); } m_extrinsic_launcher.stop_config(); m_intrinsic_launcher.stop_config(); m_intrinsic_launcher.start_config(this->get_sptr(), replace_map); } //------------------------------------------------------------------------------ void camera_config_launcher::start_extrinsic_config(std::size_t _index) { sight::app::field_adaptor_t replace_map; { const std::size_t camera_idx = std::max(_index, std::size_t(1)); const auto camera_set = m_camera_set.lock(); data::camera::sptr camera1 = camera_set->get_camera(0); data::camera::sptr camera2 = camera_set->get_camera(camera_idx); // Check if the two cameras are calibrated if(!camera1->get_is_calibrated() || !camera2->get_is_calibrated()) { sight::ui::dialog::message::show("Calibration", "Cameras must be intrinsically calibrated."); m_extrinsic_button->setChecked(false); return; } // cspell: ignore Extr // Add 2 calibration info in Activity if not exist std::string calibration_info1_key = "calibrationInfoExtr0_" + std::to_string(camera_idx); std::string calibration_info2_key = "calibrationInfoExtr1_" + std::to_string(camera_idx); const auto activity = m_activity.lock(); data::calibration_info::sptr calib_info1; data::calibration_info::sptr calib_info2; // Get the calibrationInfo from the activity if it exists or create it. if(activity->find(calibration_info1_key) == activity->end() || activity->find(calibration_info2_key) == activity->end()) { calib_info1 = std::make_shared(); calib_info2 = std::make_shared(); (*activity)[calibration_info1_key] = calib_info1; (*activity)[calibration_info2_key] = calib_info2; } else { calib_info1 = std::dynamic_pointer_cast((*activity)[calibration_info1_key]); calib_info2 = std::dynamic_pointer_cast((*activity)[calibration_info2_key]); } const auto board_properties = m_board_properties.lock(); replace_map["camera1"] = camera1->get_id(); replace_map["camera2"] = camera2->get_id(); replace_map["calibrationInfo1"] = calib_info1->get_id(); replace_map["calibrationInfo2"] = calib_info2->get_id(); replace_map["camIndex"] = std::to_string(_index); replace_map["board_properties"] = board_properties->get_id(); replace_map["camera_set"] = camera_set->get_id(); } m_extrinsic_launcher.stop_config(); m_intrinsic_launcher.stop_config(); m_extrinsic_launcher.start_config(this->get_sptr(), replace_map); } //------------------------------------------------------------------------------ void camera_config_launcher::add_camera() { std::size_t nb_cam = 0; { const auto camera_set = m_camera_set.lock(); nb_cam = camera_set->size(); data::camera::sptr camera = std::make_shared(); // Add the CalibrationInfo in activity to be saved in activity std::string calibration_info_key = "calibrationInfo_" + std::to_string(nb_cam); data::calibration_info::sptr calib_info = std::make_shared(); const auto activity = m_activity.lock(); (*activity)[calibration_info_key] = calib_info; // Add the camera camera_set->add_camera(camera); auto sig = camera_set->signal( data::camera_set::ADDED_CAMERA_SIG ); sig->async_emit(camera); } m_camera_combo_box->blockSignals(true); m_camera_combo_box->addItem(QString("Camera %1").arg(nb_cam + 1)); m_camera_combo_box->setCurrentIndex(static_cast(nb_cam)); m_extrinsic_button->setChecked(false); this->start_intrinsic_config(nb_cam); m_camera_combo_box->blockSignals(false); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/camera_config_launcher.hpp000066400000000000000000000114421503402212300246730ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { /** * @brief This editor adds cameras to a camera series and launches configurations to calibrate them. * * @note Currently, only one or two cameras are allowed. * * This service launches an config for intrinsic camera calibration and another config for extrinsic calibration. * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection In-Out In-Out: * - \b camera_set [sight::data::camera_set] : stores camera calibrations. * - \b activity [sight::data::activity]: stores the information used to generate the calibration. * It allows to re-open the activity with this information. * * @subsection Configuration Configuration: * - \b config: contains the appConfigs to launch for intrinsic and extrinsic calibration. * - \b intrinsic: configuration to launch the intrinsic calibration config. @see * module::ui::action::config_launcher * - \b extrinsic: configuration to launch the extrinsic calibration config. @see * module::ui::action::config_launcher * */ class camera_config_launcher : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(camera_config_launcher, sight::ui::editor); /// Constructor. Do nothing. camera_config_launcher() noexcept; /// Destructor. Do nothing. ~camera_config_launcher() noexcept override; protected: void configuring() override; ///This method launches the editor::starting method. void starting() override; ///This method launches the editor::stopping method. void stopping() override; void updating() override; private Q_SLOTS: void on_add_clicked(); void on_import_clicked(); void on_remove_clicked(); void on_extrinsic_toggled(bool _checked); void on_camera_changed(int _index); private: /// Start the configuration for the intrinsic calibration void start_intrinsic_config(std::size_t _index); /// Start the configuration for the extrinsic calibration void start_extrinsic_config(std::size_t _index); /// Adds a Camera in camera_set and the CalibrationInfo in the activity void add_camera(); QPointer m_camera_combo_box; QPointer m_add_button; QPointer m_import_button; QPointer m_remove_button; QPointer m_extrinsic_button; sight::app::helper::config_launcher m_intrinsic_launcher; sight::app::helper::config_launcher m_extrinsic_launcher; data::ptr m_camera_set {this, "camera_set"}; data::ptr m_activity {this, "activity"}; data::ptr m_board_properties {this, "board_properties"}; }; } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/camera_information_editor.cpp000066400000000000000000000160311503402212300254320ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/calibration/camera_information_editor.hpp" #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { // ------------------------------------------------------------------------- const core::com::slots::key_t camera_information_editor::UPDATE_INFOS_SLOT = "updateInfos"; // ------------------------------------------------------------------------- camera_information_editor::camera_information_editor() noexcept { new_slot(UPDATE_INFOS_SLOT, &camera_information_editor::update_informations, this); } // ------------------------------------------------------------------------- void camera_information_editor::configuring() { sight::ui::service::initialize(); } // ------------------------------------------------------------------------- void camera_information_editor::starting() { const QString service_id = QString::fromStdString(base_id()); sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast(get_container()); qt_container->get_qt_container()->setObjectName(service_id); auto* main_layout = new QBoxLayout(QBoxLayout::TopToBottom); main_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); auto* grid_layout = new QGridLayout(); auto* desc = new QLabel("description: "); m_description = new QLabel(); m_description->setObjectName(service_id + "/description"); grid_layout->addWidget(desc, 0, 0); grid_layout->addWidget(m_description, 0, 1); auto* title_layout = new QBoxLayout(QBoxLayout::LeftToRight); m_is_calibrated = new QLabel(); m_is_calibrated->setObjectName(service_id + "/isCalibrated"); title_layout->addWidget(m_is_calibrated); auto* info_layout = new QGridLayout; info_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); main_layout->addLayout(grid_layout); main_layout->addLayout(title_layout); main_layout->addLayout(info_layout); m_width = new QLabel(); m_width->setObjectName(service_id + "/width"); m_height = new QLabel(); m_height->setObjectName(service_id + "/height"); m_cx = new QLabel(); m_cx->setObjectName(service_id + "/cx"); m_cy = new QLabel(); m_cy->setObjectName(service_id + "/cy"); m_fx = new QLabel(); m_fx->setObjectName(service_id + "/fx"); m_fy = new QLabel(); m_fy->setObjectName(service_id + "/fy"); m_k1 = new QLabel(); m_k1->setObjectName(service_id + "/k1"); m_k2 = new QLabel(); m_k2->setObjectName(service_id + "/k2"); m_p1 = new QLabel(); m_p1->setObjectName(service_id + "/p1"); m_p2 = new QLabel(); m_p2->setObjectName(service_id + "/p2"); m_k3 = new QLabel(); m_k3->setObjectName(service_id + "/k3"); m_skew = new QLabel(); m_skew->setObjectName(service_id + "/skew"); m_error = new QLabel(); m_error->setObjectName(service_id + "/error"); info_layout->addWidget(m_width, 0, 0); info_layout->addWidget(m_height, 0, 1); info_layout->addWidget(m_skew, 0, 2); info_layout->addWidget(m_error, 0, 3); info_layout->addWidget(m_cx, 1, 0); info_layout->addWidget(m_cy, 1, 1); info_layout->addWidget(m_fx, 1, 2); info_layout->addWidget(m_fy, 1, 3); info_layout->addWidget(m_k1, 2, 0); info_layout->addWidget(m_k2, 2, 1); info_layout->addWidget(m_p1, 2, 2); info_layout->addWidget(m_p2, 2, 3); info_layout->addWidget(m_k3, 2, 4); qt_container->set_layout(main_layout); update_informations(); } // ------------------------------------------------------------------------- void camera_information_editor::stopping() { this->destroy(); } // ------------------------------------------------------------------------- void camera_information_editor::update_informations() { const auto camera = m_camera.lock(); m_description->setText(QString::fromStdString(camera->get_description())); //IS CALIBRATED if(camera->get_is_calibrated()) { m_is_calibrated->setText("The camera is calibrated."); } else { m_is_calibrated->setText("The camera is not calibrated."); this->clear_labels(); return; } const auto fill_label = [](QLabel* _label, const auto& _title, const auto& _value) { std::stringstream out; out << _title << ": " << _value << ""; _label->setText(out.str().c_str()); }; fill_label(m_height, "Height", camera->get_height()); fill_label(m_width, "Width", camera->get_width()); fill_label(m_cx, "Cx", camera->get_cx()); fill_label(m_cy, "Cy", camera->get_cy()); fill_label(m_fx, "Fx", camera->get_fx()); fill_label(m_fy, "Fy", camera->get_fy()); const data::camera::dist_array_t& dist = camera->get_distortion_coefficient(); fill_label(m_k1, "K1", dist[0]); fill_label(m_k2, "K2", dist[1]); fill_label(m_p1, "P1", dist[2]); fill_label(m_p2, "P2", dist[3]); fill_label(m_k3, "K3", dist[4]); fill_label(m_skew, "Skew", camera->get_skew()); fill_label(m_error, "Error", camera->calibration_error()); } // ------------------------------------------------------------------------- void camera_information_editor::clear_labels() { m_width->setText(""); m_height->setText(""); m_cx->setText(""); m_cy->setText(""); m_fx->setText(""); m_fy->setText(""); m_k1->setText(""); m_k2->setText(""); m_p1->setText(""); m_p2->setText(""); m_k3->setText(""); m_skew->setText(""); m_error->setText(""); } // ---------------------------------------------------------------------------- service::connections_t camera_information_editor::auto_connections() const { connections_t connections; connections.push(CAMERA, data::camera::ID_MODIFIED_SIG, UPDATE_INFOS_SLOT); connections.push(CAMERA, data::camera::INTRINSIC_CALIBRATED_SIG, UPDATE_INFOS_SLOT); return connections; } } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/camera_information_editor.hpp000066400000000000000000000104751503402212300254450ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { /** * @brief camera_information_editor service is used to display the intrinsic calibration of a camera. * * @subsection Configuration XML * @code @endcode * * @subsection Inputs Inputs * - \b camera [sight::data::camera]: camera's information that will be displayed. * * @subsection Slots Slots * - \b updateInformations(): Updates the informations of the intrinsic calibration. */ class camera_information_editor : public QObject, public sight::ui::editor { Q_OBJECT; public: SIGHT_DECLARE_SERVICE(camera_information_editor, sight::ui::editor); static const core::com::slots::key_t UPDATE_INFOS_SLOT; using update_infos_slot_t = core::com::slot; /** * @brief Constructor. */ camera_information_editor() noexcept; /** * @brief Destructor. */ ~camera_information_editor() noexcept override = default; /** * @brief Configuring method : This method is used to configure the service. */ void configuring() override; /** * @brief Starting method : This method is used to initialize the service. */ void starting() override; /** * @brief Stopping method : This method is used to stop the service. */ void stopping() override; /** * @brief Updating method : This method is used to update the service. */ void updating() override { } protected: service::connections_t auto_connections() const override; /** * @brief Slot: Updates the informations of the intrinsic calibration. */ void update_informations(); /** * @brief Clear all the labels. */ void clear_labels(); /** * @brief Label that displays the camera description */ QLabel* m_description {}; /** * @brief Label that displays if the camera is calibrated. */ QLabel* m_is_calibrated {}; /** * @brief Width of the video. */ QLabel* m_width {}; /** * @brief Height of the video. */ QLabel* m_height {}; /** * @brief Optical center x. */ QLabel* m_cx {}; /** * @brief Optical center y. */ QLabel* m_cy {}; /** * @brief field of view x. */ QLabel* m_fx {}; /** * @brief field of view y. */ QLabel* m_fy {}; /** * @brief Distortion coefficient k1. */ QLabel* m_k1 {}; /** * @brief Distortion coefficient k2. */ QLabel* m_k2 {}; /** * @brief Distortion coefficient p1. */ QLabel* m_p1 {}; /** * @brief Distortion coefficient p2. */ QLabel* m_p2 {}; /** * @brief Distortion coefficient k3. */ QLabel* m_k3 {}; /** * @brief Camera skew. */ QLabel* m_skew {}; /** * @brief Calibration error. */ QLabel* m_error {}; static constexpr std::string_view CAMERA = "camera"; data::ptr m_camera {this, CAMERA}; }; } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/camera_set_editor.cpp000066400000000000000000000104301503402212300236750ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/calibration/camera_set_editor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { const core::com::slots::key_t camera_set_editor::UPDATE_INFOS_SLOT = "updateInfos"; // ------------------------------------------------------------------------- camera_set_editor::camera_set_editor() noexcept : m_cam_index(1) { new_slot(UPDATE_INFOS_SLOT, &camera_set_editor::update_informations, this); } // ------------------------------------------------------------------------- void camera_set_editor::configuring() { sight::ui::service::initialize(); service::config_t config = this->get_config(); m_cam_index = config.get("index", 1); } // ------------------------------------------------------------------------- void camera_set_editor::starting() { sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast(get_container()); auto* main_layout = new QBoxLayout(QBoxLayout::TopToBottom); main_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); m_description = new QLabel(""); main_layout->addWidget(m_description); auto* grid_layout = new QGridLayout(); for(std::uint8_t i = 0 ; i < 4 ; ++i) { for(std::uint8_t j = 0 ; j < 4 ; ++j) { auto* label = new QLabel(""); m_matrix_labels.push_back(label); grid_layout->addWidget(label, i, j); } } main_layout->addLayout(grid_layout); qt_container->set_layout(main_layout); this->update_informations(); } // ------------------------------------------------------------------------- void camera_set_editor::stopping() { this->destroy(); } // ------------------------------------------------------------------------- void camera_set_editor::update_informations() { const auto camera_set = m_camera_set.lock(); //IS CALIBRATED data::matrix4::csptr matrix = camera_set->get_extrinsic_matrix(m_cam_index); if(matrix) { m_description->setText("The cameras are calibrated."); } else { m_description->setText("The cameras are not calibrated."); this->clear_labels(); return; } for(std::uint8_t i = 0 ; i < 4 ; ++i) { for(std::uint8_t j = 0 ; j < 4 ; ++j) { m_matrix_labels[i * 4 + j]->setText(QString("%1").arg((*matrix)(i, j))); } } } // ------------------------------------------------------------------------- void camera_set_editor::clear_labels() { for(const auto& label : m_matrix_labels) { label->setText(QString("")); } } // ---------------------------------------------------------------------------- service::connections_t camera_set_editor::auto_connections() const { return { {m_camera_set, data::camera_set::ADDED_CAMERA_SIG, UPDATE_INFOS_SLOT}, {m_camera_set, data::camera_set::EXTRINSIC_CALIBRATED_SIG, UPDATE_INFOS_SLOT}, {m_camera_set, data::camera_set::REMOVED_CAMERA_SIG, UPDATE_INFOS_SLOT}, }; } } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/camera_set_editor.hpp000066400000000000000000000070651503402212300237140ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "data/camera_set.hpp" #include #include #include #include #include namespace sight::module::ui::qt::calibration { /** * @brief camera_set_editor service is used to display the extrinsic calibration of a camera series. * * @subsection Configuration XML * @code{.xml} ... @endcode * @subsection Configuration Configuration * - \b index (optional, default: 1): index of the camera in camera_set used to display extrinsic matrix * * @subsection Inputs Inputs * - \b camera_set [sight::data::camera_set]: input camera_set. * * @subsection Slots Slots * -\b updateInformations(): Updates the informations of the intrinsic calibration. */ class camera_set_editor : public QObject, public sight::ui::editor { Q_OBJECT; public: SIGHT_DECLARE_SERVICE(camera_set_editor, sight::ui::editor); static const core::com::slots::key_t UPDATE_INFOS_SLOT; using update_infos_slot_t = core::com::slot; /** * @brief Constructor. */ camera_set_editor() noexcept; /** * @brief Destructor. */ ~camera_set_editor() noexcept { } /** * @brief Configuring method : This method is used to configure the service. */ void configuring() override; /** * @brief Starting method : This method is used to initialize the service. */ void starting() override; /** * @brief Stopping method : This method is used to stop the service. */ void stopping() override; /** * @brief Updating method : This method is used to update the service. */ void updating() override { } protected: /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection */ service::connections_t auto_connections() const override; /** * @brief Slot: Updates the informations of the intrinsic calibration. */ void update_informations(); /** * @brief Clear all the labels. */ void clear_labels(); QPointer m_description; ///< description of camera series QVector > m_matrix_labels; ///< Labels for matrix's elements /// Index of the camera in camera_set used to display extrinsic matrix. std::size_t m_cam_index; data::ptr m_camera_set {this, "camera_set"}; }; } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/display_calibration_info.cpp000066400000000000000000000121751503402212300252630ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/calibration/display_calibration_info.hpp" #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { //------------------------------------------------------------------------------ static const core::com::slots::key_t DISPLAY_IMAGE_SLOT = "display_image"; static const core::com::slots::key_t STOP_CONFIG_SLOT = "stopConfig"; static const std::string SINGLE_IMAGE_CONFIG = "singleImageConfig"; static const std::string TWO_IMAGES_CONFIG = "twoImagesConfig"; static const std::string CLOSE_CONFIG_ID = "CLOSE_CONFIG"; //------------------------------------------------------------------------------ display_calibration_info::display_calibration_info() noexcept { new_slot(DISPLAY_IMAGE_SLOT, &display_calibration_info::display_image, this); new_slot(STOP_CONFIG_SLOT, &display_calibration_info::stop_config, this); } //------------------------------------------------------------------------------ void display_calibration_info::configuring(const config_t& _config) { m_single_image_config = _config.get("config.." + SINGLE_IMAGE_CONFIG, m_single_image_config); m_two_images_config = _config.get("config.." + TWO_IMAGES_CONFIG, m_two_images_config); } //------------------------------------------------------------------------------ void display_calibration_info::starting() { m_proxychannel = this->get_id() + "_stopConfig"; } //------------------------------------------------------------------------------ void display_calibration_info::stopping() { if(m_config_mgr) { core::com::proxy::sptr proxies = core::com::proxy::get(); proxies->disconnect(m_proxychannel, this->slot(STOP_CONFIG_SLOT)); m_config_mgr->stop_and_destroy(); m_config_mgr.reset(); } } //------------------------------------------------------------------------------ void display_calibration_info::updating() { } //------------------------------------------------------------------------------ void display_calibration_info::stop_config() { if(m_config_mgr) { this->stopping(); } } //------------------------------------------------------------------------------ void display_calibration_info::display_image(std::size_t _idx) { if(!m_config_mgr) { // Grab images from our map data const auto cal_info1 = m_calibration_info1.lock(); SIGHT_ASSERT("Object " << CALIBRATION_INFO_1 << " is not a CalibrationInfo !", cal_info1); const auto cal_info2 = m_calibration_info2.lock(); std::string str_config = m_single_image_config; // Prepare configuration sight::app::field_adaptor_t replace_map; data::image::csptr img1 = cal_info1->get_image(_idx); replace_map["imageId1"] = img1->get_id(); replace_map["calibrationData"] = cal_info1->get_id(); data::point_list::csptr point_list1 = cal_info1->get_point_list(img1); replace_map["pointListId1"] = point_list1->get_id(); if(cal_info2) { str_config = m_two_images_config; data::image::csptr img2 = cal_info2->get_image(_idx); replace_map["imageId2"] = img2->get_id(); data::point_list::csptr point_list2 = cal_info2->get_point_list(img2); replace_map["pointListId2"] = point_list2->get_id(); } replace_map[CLOSE_CONFIG_ID] = m_proxychannel; const auto config = app::extension::config::get()->get_adapted_template_config( str_config, replace_map, sight::app::extension::config::get_unique_identifier(str_config) ); // Launch configuration m_config_mgr = app::config_manager::make(); m_config_mgr->set_config(config); m_config_mgr->launch(); // Proxy to be notified of the window closure core::com::proxy::sptr proxies = core::com::proxy::get(); proxies->connect(m_proxychannel, this->slot(STOP_CONFIG_SLOT)); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/display_calibration_info.hpp000066400000000000000000000077751503402212300253020ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::ui::qt::calibration { /** * @brief Launches an config to display calibration images. * * This service works on a data::map. * It starts/stops a template configuration. This service can display one or two images. * * If there is one calibration info in the service parameters, the template configuration used is * "DisplayImageConfig.xml". The parameters are : * - "imageId1" is the image to be displayed. * * If there are two calibration infos, the template configuration used is "DisplayTwoImagesConfig.xml". * The parameters are : * - "imageId1" is the first image. * - "imageId2" is the second image. * * @section XML XML Configuration * @code{.xml} @endcode * @subsection Input Input * - \b calInfo1 [sight::data::calibration_info]: calibration information for first camera. * - \b calInfo2 [sight::data::calibration_info] (optional): calibration information for optional second camera. * * @section Slots Slots * - \b display_image(std::size_t index): launches the configuration to display the calibration image at the given index * on * an external window. * - \b stopConfig() : Stop the displayed configuration. */ class display_calibration_info final : public service::controller { public: SIGHT_DECLARE_SERVICE(display_calibration_info, service::controller); /// Constructor. Does nothing. display_calibration_info() noexcept; /// Destructor. Does nothing. ~display_calibration_info() noexcept final = default; protected: /// Set the configurations to start for intrinsic and extrinsic void configuring(const config_t& _config) final; /// Starts the config void starting() final; /// Stops the config void stopping() final; /// Does nothing void updating() final; private: /** * @name Slots * @} */ /// Slot: stop the config. void stop_config(); /// Slot: Launch an appConfig to display an image on an external window. void display_image(std::size_t _idx); /** * @} */ /// config manager, used to launch the config app::config_manager::sptr m_config_mgr; std::string m_single_image_config {"displayImageConfig"}; std::string m_two_images_config {"displayTwoImagesConfig"}; std::string m_proxychannel; ///< Name of the channel used to connect stopConfig slot to the config frame closing. static constexpr std::string_view CALIBRATION_INFO_1 = "calInfo1"; static constexpr std::string_view CALIBRATION_INFO_2 = "calInfo2"; data::ptr m_calibration_info1 {this, CALIBRATION_INFO_1}; data::ptr m_calibration_info2 {this, CALIBRATION_INFO_2, true}; }; } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/images_selector.cpp000066400000000000000000000152341503402212300234000ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/calibration/images_selector.hpp" #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { const core::com::slots::key_t images_selector::ADD_SLOT = "add"; const core::com::slots::key_t images_selector::REMOVE_SLOT = "remove"; const core::com::slots::key_t images_selector::RESET_SLOT = "reset"; //------------------------------------------------------------------------------ images_selector::images_selector() noexcept { new_slot(ADD_SLOT, &images_selector::add, this); new_slot(REMOVE_SLOT, &images_selector::remove, this); new_slot(RESET_SLOT, &images_selector::reset, this); } //------------------------------------------------------------------------------ images_selector::~images_selector() noexcept = default; //------------------------------------------------------------------------------ void images_selector::configuring() { sight::ui::service::initialize(); } //------------------------------------------------------------------------------ void images_selector::starting() { const auto frame_tl = m_frame_tl.lock(); SIGHT_ASSERT("Frame timeline is not found.", frame_tl); sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast(get_container()); // Main container, VBox auto* v_layout = new QVBoxLayout(); // First HBox, displays number of items and the remove button auto* nb_items_h_box = new QHBoxLayout(); // Fill the nbItemsHBox auto* label = new QLabel("nb captures:"); nb_items_h_box->addWidget(label); m_nb_captures_label = new QLabel("0"); nb_items_h_box->addWidget(m_nb_captures_label); nb_items_h_box->addStretch(); // The ListWidget m_captures_list_widget = new QListWidget(); // Fill the main VBox v_layout->addLayout(nb_items_h_box); v_layout->addWidget(m_captures_list_widget); qt_container->set_layout(v_layout); this->updating(); } //------------------------------------------------------------------------------ void images_selector::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void images_selector::updating() { const auto vector = m_selected_image.lock(); m_captures_list_widget->clear(); unsigned int capture_idx = 0; for(const data::object::sptr& obj : *vector) { data::image::sptr image = std::dynamic_pointer_cast(obj); if(image) { QString count_string; count_string = QString("%1. %2").arg(capture_idx).arg(QString::fromStdString(image->get_id())); m_captures_list_widget->addItem(count_string); ++capture_idx; } } m_nb_captures_label->setText(QString("%1").arg(capture_idx)); } // ---------------------------------------------------------------------------- void images_selector::remove() { int idx = m_captures_list_widget->currentRow(); if(idx >= 0) { const auto vector = m_selected_image.lock(); data::object::sptr obj = (*vector)[std::size_t(idx)]; const auto scoped_emitter = vector->scoped_emit(); vector->remove(obj); this->updating(); } } // ---------------------------------------------------------------------------- void images_selector::reset() { const auto vector = m_selected_image.lock(); const auto scoped_emitter = vector->scoped_emit(); vector->clear(); m_captures_list_widget->clear(); m_nb_captures_label->setText(QString("0")); } //------------------------------------------------------------------------------ void images_selector::add(core::clock::type _timestamp) { const auto frame_tl = m_frame_tl.lock(); CSPTR(data::frame_tl::buffer_t) buffer = frame_tl->get_closest_buffer(_timestamp); if(!buffer) { SIGHT_INFO("Buffer not found with timestamp " << _timestamp); return; } data::image::sptr image = std::make_shared(); data::image::size_t size; size[0] = frame_tl->get_width(); size[1] = frame_tl->get_height(); size[2] = 1; enum data::image::pixel_format_t format { data::image::pixel_format_t::undefined }; // FIXME since frameTL does not have format information, we assume that image are Grayscale, RGB or RGBA according // to the number of components. switch(frame_tl->num_components()) { case 1: format = data::image::gray_scale; break; case 3: format = data::image::rgb; break; case 4: format = data::image::rgba; break; default: format = data::image::undefined; } image->resize(size, frame_tl->type(), format); const data::image::origin_t origin = {0., 0., 0.}; image->set_origin(origin); const data::image::spacing_t spacing = {1., 1., 1.}; image->set_spacing(spacing); image->set_window_width({100}); image->set_window_center({0}); const auto dump_lock = image->dump_lock(); const std::uint8_t* frame_buff = &buffer->get_element(0); auto* img_buffer = static_cast(image->buffer()); std::copy(frame_buff, frame_buff + buffer->size(), img_buffer); const auto vector = m_selected_image.lock(); const auto scoped_emitter = vector->scoped_emit(); vector->push_back(image); this->updating(); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/images_selector.hpp000066400000000000000000000070501503402212300234020ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { /** * @brief This editor allows to add images to a data::vector from a data::frame_tl. * * @section Slots Slots * - \b add(core::clock::type): . * - \b remove(): . * - \b reset(): . * @section XML XML Configuration * * @code{.xml} @endcode * @subsection Input Input: * - \b frame_tl [sight::data::frame_tl]: frame timeline used to extract images. * @subsection In-Out In-Out: * - \b key2 [sight::data::vector]: vector containing extracted images. */ class images_selector : public QObject, public sight::ui::editor { Q_OBJECT; public: SIGHT_DECLARE_SERVICE(images_selector, sight::ui::editor); /// Constructor. images_selector() noexcept; /// Destructor. Does nothing ~images_selector() noexcept override; /** * @name Slots API * @{ */ static const core::com::slots::key_t ADD_SLOT; using add_slot_t = core::com::slot; static const core::com::slots::key_t REMOVE_SLOT; using remove_slot_t = core::com::slot; static const core::com::slots::key_t RESET_SLOT; using reset_slot_type = core::com::slot; ///@} protected: /// Configure the editor. void configuring() override; /// Initialize the widgets. void starting() override; /// destroy the widgets. void stopping() override; /// FILL ME. void updating() override; protected: /// Slot: called when the user presses the remove acquisition button. void remove(); /// Slot: called when the user presses the reset button. void reset(); /// Slot: to add an image in the vector. void add(core::clock::type _timestamp); private: /// Index of the last acquisition. int m_capture_idx {0}; /// Label displaying the number of point acquisitions. QPointer m_nb_captures_label; /// Calibration point list. QPointer m_captures_list_widget; data::ptr m_frame_tl {this, "frame_tl"}; data::ptr m_selected_image {this, "selection"}; }; } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/intrinsic_edition.cpp000066400000000000000000000110711503402212300237430ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/calibration/intrinsic_edition.hpp" #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { // ------------------------------------------------------------------------- intrinsic_edition::intrinsic_edition() : m_dialog(new update_intrinsic_dialog()) { core::com::has_slots::m_slots.set_worker(this->worker()); QObject::connect(m_dialog, &update_intrinsic_dialog::new_calibration, this, &intrinsic_edition::on_new_calibration); } // ------------------------------------------------------------------------- intrinsic_edition::~intrinsic_edition() { QObject::disconnect( m_dialog, &update_intrinsic_dialog::new_calibration, this, &intrinsic_edition::on_new_calibration ); } // ------------------------------------------------------------------------- void intrinsic_edition::on_new_calibration(std::array& _cal) { m_calibration = _cal; this->update_calibration(); } // ------------------------------------------------------------------------- void intrinsic_edition::update_calibration() { const auto camera = m_camera.lock(); SIGHT_ASSERT("The inout key '" << CAMERA << "' is not correctly set.", camera); camera->set_width(std::size_t(m_calibration[0])); camera->set_height(std::size_t(m_calibration[1])); camera->set_fx(m_calibration[2]); camera->set_fy(m_calibration[3]); camera->set_cx(m_calibration[4]); camera->set_cy(m_calibration[5]); camera->set_distortion_coefficient( m_calibration[6], m_calibration[7], m_calibration[8], m_calibration[9], m_calibration[10] ); camera->set_skew(m_calibration[11]); data::camera::intrinsic_calibrated_signal_t::sptr sig; sig = camera->signal( data::camera::INTRINSIC_CALIBRATED_SIG ); sig->async_emit(); } // ------------------------------------------------------------------------- void intrinsic_edition::configuring() { this->read_calibration(); m_dialog->set_parameters(m_calibration); } // ------------------------------------------------------------------------- void intrinsic_edition::read_calibration() { const auto camera = m_camera.lock(); SIGHT_ASSERT("The inout key '" << CAMERA << "' is not correctly set.", camera); m_calibration[0] = double(camera->get_width()); m_calibration[1] = double(camera->get_height()); m_calibration[2] = camera->get_fx(); m_calibration[3] = camera->get_fy(); m_calibration[4] = camera->get_cx(); m_calibration[5] = camera->get_cy(); m_dist_parameters = camera->get_distortion_coefficient(); m_calibration[6] = m_dist_parameters[0]; m_calibration[7] = m_dist_parameters[1]; m_calibration[8] = m_dist_parameters[2]; m_calibration[9] = m_dist_parameters[3]; m_calibration[10] = m_dist_parameters[4]; m_calibration[11] = camera->get_skew(); } // ------------------------------------------------------------------------- void intrinsic_edition::starting() { } // ------------------------------------------------------------------------- void intrinsic_edition::stopping() { } // ------------------------------------------------------------------------- void intrinsic_edition::updating() { this->read_calibration(); m_dialog->set_parameters(m_calibration); m_dialog->show(); } // ------------------------------------------------------------------------- } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/intrinsic_edition.hpp000066400000000000000000000062771503402212300237640ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "module/ui/qt/calibration/update_intrinsic_dialog.hpp" #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { /** * @brief intrinsic_edition service is used to set the intrinsic parameter information. * * This services displays a dialog to change the resolution of your calibration, and computes the new intrinsic * parameters. * * @section XML XML Configuration * * @code{.xml} @endcode * @subsection In-Out In-Out * - \b camera [sight::data::camera]: camera on which the intrinsic parameters will be modified. */ class intrinsic_edition : public QObject, public service::base { Q_OBJECT; public: SIGHT_DECLARE_SERVICE(intrinsic_edition, sight::service::base); /** * @brief Constructor. */ intrinsic_edition(); /** * @brief Destructor. */ ~intrinsic_edition() override; /** * @brief Configuring method : This method is used to configure the service. */ void configuring() override; /** * @brief Starting method : This method is used to initialize the service. */ void starting() override; /** * @brief Stopping method : This method is used to stop the service. */ void stopping() override; /** * @brief Updating method : This method is used to update the service. */ void updating() override; private Q_SLOTS: /** * @brief onNewCalibration * @param _cal the new calibration data */ void on_new_calibration(std::array& _cal); private: void update_calibration(); void read_calibration(); std::array m_intrinsic {}; std::array m_dist_parameters {}; double m_skew {}; std::array m_resolution {}; std::array m_calibration {}; update_intrinsic_dialog* m_dialog; static constexpr std::string_view CAMERA = "camera"; data::ptr m_camera {this, CAMERA}; }; } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/optical_center_editor.cpp000066400000000000000000000167051503402212300246000ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2023 IRCAD France * Copyright (C) 2014-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/calibration/optical_center_editor.hpp" #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { //------------------------------------------------------------------------------ optical_center_editor::optical_center_editor() noexcept = default; //------------------------------------------------------------------------------ optical_center_editor::~optical_center_editor() noexcept = default; //------------------------------------------------------------------------------ void optical_center_editor::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void optical_center_editor::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(get_container()); auto* v_layout = new QVBoxLayout(); auto* cx_layout = new QHBoxLayout(); auto* cy_layout = new QHBoxLayout(); auto* fy_layout = new QHBoxLayout(); auto* cx_label = new QLabel(tr("CCD X:")); m_cx_slider = new QSlider(Qt::Horizontal); m_cx_label = new QLabel(); cx_layout->addWidget(cx_label); cx_layout->addWidget(m_cx_slider); cx_layout->addWidget(m_cx_label); auto* cy_label = new QLabel(tr("CCD Y:")); m_cy_slider = new QSlider(Qt::Horizontal); m_cy_label = new QLabel(); cy_layout->addWidget(cy_label); cy_layout->addWidget(m_cy_slider); cy_layout->addWidget(m_cy_label); auto* fy_label = new QLabel(tr("Fy:")); m_fy_slider = new QSlider(Qt::Horizontal); m_fy_label = new QLabel(); fy_layout->addWidget(fy_label); fy_layout->addWidget(m_fy_slider); fy_layout->addWidget(m_fy_label); v_layout->addLayout(cx_layout); v_layout->addLayout(cy_layout); v_layout->addLayout(fy_layout); qt_container->set_layout(v_layout); QObject::connect(m_cx_slider, SIGNAL(valueChanged(int)), this, SLOT(on_cx_slider_changed(int))); QObject::connect(m_cy_slider, SIGNAL(valueChanged(int)), this, SLOT(on_cy_slider_changed(int))); QObject::connect(m_fy_slider, SIGNAL(valueChanged(int)), this, SLOT(on_fy_slider_changed(int))); this->updating(); } //------------------------------------------------------------------------------ void optical_center_editor::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void optical_center_editor::updating() { const auto camera = m_camera.lock(); SIGHT_ASSERT("object '" << CAMERA << "' is not defined.", camera); SIGHT_ASSERT("Camera " + camera->get_id() + " must be calibrated.", camera->get_is_calibrated()); const auto matrix = m_matrix.lock(); SIGHT_ASSERT("object '" << MATRIX << "' is not defined.", matrix); // Reset matrix if it isn't correctly formatted. if((*matrix)(3, 3) == 1.) { matrix->fill(0.); } const double d_cx = (*matrix)(0, 2); const double d_cy = (*matrix)(1, 2); const double d_fy = (*matrix)(1, 1); const int cx = static_cast(camera->get_cx() + d_cx); const int cy = static_cast(camera->get_cy() + d_cy); const int fy = static_cast(camera->get_fy() + d_fy); const int delta_x = static_cast(camera->get_width() / 5); const int delta_y = static_cast(camera->get_height() / 5); const int delta_fy = static_cast(camera->get_fy() * .5); m_cx_slider->setRange(cx - delta_x, cx + delta_x); m_cy_slider->setRange(cy - delta_y, cy + delta_y); m_fy_slider->setRange(fy - delta_fy, fy + delta_fy); m_cx_slider->setValue(cx); m_cy_slider->setValue(cy); m_fy_slider->setValue(fy); m_cx_label->setText(QString("%1").arg(cx)); m_cy_label->setText(QString("%1").arg(cy)); m_fy_label->setText(QString("%1").arg(fy)); } //------------------------------------------------------------------------------ service::connections_t optical_center_editor::auto_connections() const { connections_t connections; connections.push(CAMERA, data::camera::INTRINSIC_CALIBRATED_SIG, service::slots::UPDATE); connections.push(CAMERA, data::camera::MODIFIED_SIG, service::slots::UPDATE); connections.push(MATRIX, data::matrix4::MODIFIED_SIG, service::slots::UPDATE); return connections; } //------------------------------------------------------------------------------ void optical_center_editor::on_cx_slider_changed(int _value) { const auto camera = m_camera.lock(); SIGHT_ASSERT("object '" << CAMERA << "' is not defined.", camera); const auto matrix = m_matrix.lock(); SIGHT_ASSERT("object '" << MATRIX << "' is not defined.", matrix); (*matrix)(0, 2) = _value - camera->get_cx(); m_cx_label->setText(QString("%1").arg(_value)); auto sig = matrix->signal(data::object::MODIFIED_SIG); { core::com::connection::blocker block(sig->get_connection(slot(service::slots::UPDATE))); sig->async_emit(); } } //------------------------------------------------------------------------------ void optical_center_editor::on_cy_slider_changed(int _value) { const auto camera = m_camera.lock(); SIGHT_ASSERT("object '" << CAMERA << "' is not defined.", camera); const auto matrix = m_matrix.lock(); SIGHT_ASSERT("object '" << MATRIX << "' is not defined.", matrix); (*matrix)(1, 2) = _value - camera->get_cy(); m_cy_label->setText(QString("%1").arg(_value)); auto sig = matrix->signal(data::object::MODIFIED_SIG); { core::com::connection::blocker block(sig->get_connection(slot(service::slots::UPDATE))); sig->async_emit(); } } //------------------------------------------------------------------------------ void optical_center_editor::on_fy_slider_changed(int _value) { const auto camera = m_camera.lock(); SIGHT_ASSERT("object '" << CAMERA << "' is not defined.", camera); const auto matrix = m_matrix.lock(); SIGHT_ASSERT("object '" << MATRIX << "' is not defined.", matrix); (*matrix)(1, 1) = _value - camera->get_fy(); m_fy_label->setText(QString("%1").arg(_value)); auto sig = matrix->signal(data::object::MODIFIED_SIG); { core::com::connection::blocker block(sig->get_connection(slot(service::slots::UPDATE))); sig->async_emit(); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/optical_center_editor.hpp000066400000000000000000000066141503402212300246030ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { /** * @brief This editor shows sliders to configure an intrinsic camera calibration. * * @section XML XML Configuration * * @code{.xml} @endcode * @subsection Input Input * - \b camera [sight::data::camera]: camera to edit. * @subsection In-Out In-Out * - \b matrix [sight::data::matrix4]: output matrix holding the delta values. * * This service takes a camera calibration and outputs a matrix holding the difference between * the input parameters and the camera parameters set by the user. Those differences are stored like this: * @verbatim dFx 0 dCx 0 0 dFy dCy 0 0 0 0 0 0 0 0 0 */ class optical_center_editor : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(optical_center_editor, sight::ui::editor); /// Constructor. optical_center_editor() noexcept; /// Destructor. Does nothing ~optical_center_editor() noexcept override; protected: /// Does nothing void configuring() override; /// Create UI. void starting() override; /// Destroy UI. void stopping() override; /// Does nothing. void updating() override; /// Defines the connection between camera and matrix and this editor connections_t auto_connections() const override; private Q_SLOTS: void on_cx_slider_changed(int _value); void on_cy_slider_changed(int _value); void on_fy_slider_changed(int _value); private: QPointer m_cx_slider; ///< Slider to modify cx QPointer m_cy_slider; ///< Slider to modify cy QPointer m_fy_slider; ///< Slider to modify fy QPointer m_cx_label; ///< Label for cx value QPointer m_cy_label; ///< Label for cy value QPointer m_fy_label; ///< Label for fy value static constexpr std::string_view CAMERA = "camera"; static constexpr std::string_view MATRIX = "matrix"; data::ptr m_camera {this, CAMERA}; data::ptr m_matrix {this, MATRIX}; }; } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/plugin.cpp000066400000000000000000000026321503402212300215270ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2023 IRCAD France * Copyright (C) 2014-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/calibration/plugin.hpp" #include namespace sight::module::ui::qt::calibration { SIGHT_REGISTER_PLUGIN("sight::module::ui::qt::calibration::plugin"); plugin::~plugin() noexcept = default; //------------------------------------------------------------------------------ void plugin::start() { } //------------------------------------------------------------------------------ void plugin::stop() noexcept { } } // namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/plugin.hpp000066400000000000000000000032441503402212300215340ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #ifndef _UICALIBRATION_PLUGIN_HPP_ #define UICALIBRATION_PLUGIN_HPP #include #include namespace sight::module::ui::qt::calibration { struct plugin : public core::runtime::plugin { /** * @brief Destructor. */ ~plugin() noexcept override; /** * @brief Start method. * * @exception core::runtime::RuntimeException. * This method is used by runtime in order to initialize the module. */ void start() override; /** * @brief Stop method. * * This method is used by runtime in order to close the module. */ void stop() noexcept override; }; } // namespace sight::module::ui::qt::calibration #endif // _UICALIBRATION_PLUGIN_HPP_ sight-25.1.0/module/ui/qt/calibration/update_intrinsic_dialog.cpp000066400000000000000000000216201503402212300251120ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2023 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/calibration/update_intrinsic_dialog.hpp" #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::calibration { //----------------------------------------------------------------------------- update_intrinsic_dialog::update_intrinsic_dialog() : m_width(new QLineEdit()), m_height(new QLineEdit()), m_cx(new QLabel()), m_cy(new QLabel()), m_fx(new QLabel()), m_fy(new QLabel()), m_k1(new QLabel()), m_k2(new QLabel()), m_p1(new QLabel()), m_p2(new QLabel()), m_k3(new QLabel()), m_skew(new QLabel()) { //Design of the QDialog QHBoxLayout* validate_button_layout = nullptr; QHBoxLayout* compute_button_layout = nullptr; QHBoxLayout* resolution_layout = nullptr; QVBoxLayout* main_layout = nullptr; QGridLayout* parameters_layout = nullptr; QPushButton* validate_button = nullptr; QPushButton* cancel_button = nullptr; QPushButton* compute_button = nullptr; QPushButton* reset_button = nullptr; parameters_layout = new QGridLayout; resolution_layout = new QHBoxLayout; auto* w_label = new QLabel("Width :"); auto* h_label = new QLabel("Height :"); resolution_layout->addWidget(w_label); resolution_layout->addWidget(m_width); resolution_layout->addWidget(h_label); resolution_layout->addWidget(m_height); parameters_layout->addWidget(m_skew, 0, 2); parameters_layout->addWidget(m_cx, 1, 0); parameters_layout->addWidget(m_cy, 1, 1); parameters_layout->addWidget(m_fx, 1, 2); parameters_layout->addWidget(m_fy, 1, 3); parameters_layout->addWidget(m_k1, 2, 0); parameters_layout->addWidget(m_k2, 2, 1); parameters_layout->addWidget(m_p1, 2, 2); parameters_layout->addWidget(m_p2, 2, 3); parameters_layout->addWidget(m_k3, 2, 4); validate_button_layout = new QHBoxLayout(); compute_button_layout = new QHBoxLayout(); main_layout = new QVBoxLayout(); compute_button = new QPushButton("Compute"); reset_button = new QPushButton("Reset"); validate_button = new QPushButton("Validate"); cancel_button = new QPushButton("Cancel"); compute_button_layout->addWidget(compute_button); compute_button_layout->addWidget(reset_button); validate_button_layout->addWidget(validate_button); validate_button_layout->addWidget(cancel_button); main_layout->addLayout(resolution_layout); main_layout->addLayout(compute_button_layout); main_layout->addLayout(parameters_layout); main_layout->addLayout(validate_button_layout); this->setModal(true); this->setLayout(main_layout); this->setWindowTitle("Calibration Edition"); QObject::connect(compute_button, &QPushButton::clicked, this, &update_intrinsic_dialog::on_push_compute); QObject::connect(reset_button, &QPushButton::clicked, this, &update_intrinsic_dialog::on_push_reset); QObject::connect(validate_button, &QPushButton::clicked, this, &update_intrinsic_dialog::on_validate); QObject::connect(cancel_button, &QPushButton::clicked, this, &update_intrinsic_dialog::close); } //----------------------------------------------------------------------------- update_intrinsic_dialog::~update_intrinsic_dialog() = default; //----------------------------------------------------------------------------- void update_intrinsic_dialog::set_parameters(std::array& _parameters) { m_calibration = _parameters; m_origin_calibration = _parameters; m_ratio = m_calibration[0] / m_calibration[1]; this->update_infos(); } //----------------------------------------------------------------------------- void update_intrinsic_dialog::on_validate() { Q_EMIT new_calibration(m_calibration); this->close(); } //----------------------------------------------------------------------------- void update_intrinsic_dialog::on_push_compute() { double height = m_height->text().toDouble(); double width = m_width->text().toDouble(); double ratio = width / height; //new resolution don't respect the original ratio if(std::abs(m_ratio - ratio) > 0.0001) { sight::ui::dialog::message::sptr warning_mess = std::make_shared(); warning_mess->show( "Warning", "The new resolution don't respect the original resolution ratio !" , sight::ui::dialog::message::warning ); return; } //alpha : original resolution / new resolution (!! Ratio should be kept (16/9, 4/3 ...) !!!) double alpha = m_origin_calibration[1] / height; double fx_new = NAN; double fy_new = NAN; double cx_new = NAN; double cy_new = NAN; double k1_new = NAN; double k2_new = NAN; double p1_new = NAN; double p2_new = NAN; // fx_new = fx_old / alpha // fy_new = fy_old / alpha // cx_new = cx_old / alpha // cy_new = cy_old / alpha fx_new = m_calibration[2] / alpha; fy_new = m_calibration[3] / alpha; cx_new = m_calibration[4] / alpha; cy_new = m_calibration[5] / alpha; // k1_new = k1_old*alpha^2 // k2_new = k2_old*alpha^4 // t1_new = t1_old*alpha^2 // t2_new = t2_old*alpha^2 k1_new = m_calibration[6] * (alpha * alpha); k2_new = m_calibration[7] * (alpha * alpha * alpha * alpha); p1_new = m_calibration[8] * (alpha * alpha); p2_new = m_calibration[9] * (alpha * alpha); m_calibration[0] = width; m_calibration[1] = height; m_calibration[2] = fx_new; m_calibration[3] = fy_new; m_calibration[4] = cx_new; m_calibration[5] = cy_new; m_calibration[6] = k1_new; m_calibration[7] = k2_new; m_calibration[8] = p1_new; m_calibration[9] = p2_new; this->update_infos(); } //----------------------------------------------------------------------------- void update_intrinsic_dialog::on_push_reset() { for(unsigned int i = 0 ; i < m_origin_calibration.size() ; ++i) { m_calibration[i] = m_origin_calibration[i]; } this->update_infos(); } //----------------------------------------------------------------------------- void update_intrinsic_dialog::update_infos() { std::stringstream out; out << m_calibration[0]; m_width->setText(out.str().c_str()); out.str(""); out << m_calibration[1]; m_height->setText(out.str().c_str()); out.str(""); //CX out << "Cx: " << m_calibration[4] << ""; m_cx->setText(out.str().c_str()); out.str(""); //CY out << "Cy: " << m_calibration[5] << ""; m_cy->setText(out.str().c_str()); out.str(""); //FX out << "Fx: " << m_calibration[2] << ""; m_fx->setText(out.str().c_str()); out.str(""); //FY out << "Fy: " << m_calibration[3] << ""; m_fy->setText(out.str().c_str()); out.str(""); //K1 out << "K1: " << m_calibration[6] << ""; m_k1->setText(out.str().c_str()); out.str(""); //K2 out << "K2: " << m_calibration[7] << ""; m_k2->setText(out.str().c_str()); out.str(""); //P1 out << "P1: " << m_calibration[8] << ""; m_p1->setText(out.str().c_str()); out.str(""); //P2 out << "P2: " << m_calibration[9] << ""; m_p2->setText(out.str().c_str()); out.str(""); //K3 out << "K3: " << m_calibration[10] << ""; m_k3->setText(out.str().c_str()); out.str(""); //SKEW out << "Skew: " << m_calibration[11] << ""; m_skew->setText(out.str().c_str()); } //----------------------------------------------------------------------------- } //namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/calibration/update_intrinsic_dialog.hpp000066400000000000000000000066371503402212300251320ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2015 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::ui::qt::calibration { /** * @brief The update_intrinsic_dialog class displays a QDialog. The user can change the calibration resolution which * leads to an automatic computation of the new calibration parameters. By validating the user entry, the calibration * will be updated. */ class update_intrinsic_dialog : public QDialog { Q_OBJECT; public: /// constructor update_intrinsic_dialog(); /// destructor ~update_intrinsic_dialog() override; /** * @brief setParameters * @param _parameters parameters in a array< double , 12> */ void set_parameters(std::array& _parameters); Q_SIGNALS: void new_calibration(std::array& _new_parameters); private Q_SLOTS: /** * @brief onPushCompute compute the calibration with new resolution */ void on_push_compute(); /** * @brief onPushReset reset the original calibration */ void on_push_reset(); /** * @brief onValidate send the signal newCalibration */ void on_validate(); private: /** * @brief updateInfos display new calibration */ void update_infos(); /** * @brief concatenation of resolution (0-1) , intrinsic parameters (2-5), distortions (6-10), and skew(11) */ std::array m_calibration {}; /** * @brief store the original calibration (to reset) */ std::array m_origin_calibration {}; /** * @brief Width of the video. */ QLineEdit* m_width; /** * @brief Height of the video. */ QLineEdit* m_height; /** * @brief Optical center x. */ QLabel* m_cx; /** * @brief Optical center y. */ QLabel* m_cy; /** * @brief field of view x. */ QLabel* m_fx; /** * @brief field of view y. */ QLabel* m_fy; /** * @brief Distortion coefficient k1. */ QLabel* m_k1; /** * @brief Distortion coefficient k2. */ QLabel* m_k2; /** * @brief Distortion coefficient p1. */ QLabel* m_p1; /** * @brief Distortion coefficient p2. */ QLabel* m_p2; /** * @brief Distortion coefficient k3. */ QLabel* m_k3; /** * @brief Camera skew. */ QLabel* m_skew; /// ratio of the calibration resolution double m_ratio {0.}; }; } //namespace sight::module::ui::qt::calibration sight-25.1.0/module/ui/qt/com/000077500000000000000000000000001503402212300160115ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/com/signal_button.cpp000066400000000000000000000241421503402212300213700ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2015-2025 IRCAD France * Copyright (C) 2015-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "signal_button.hpp" #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::com { static const core::com::slots::key_t SET_CHECKED_SLOT = "set_checked"; static const core::com::slots::key_t CHECK_SLOT = "check"; static const core::com::slots::key_t UNCHECK_SLOT = "uncheck"; static const core::com::slots::key_t SET_ENABLED_SLOT = "set_enabled"; static const core::com::slots::key_t ENABLE_SLOT = "enable"; static const core::com::slots::key_t DISABLE_SLOT = "disable"; static const core::com::slots::key_t SET_VISIBLE_SLOT = "set_visible"; static const core::com::slots::key_t SHOW_SLOT = "show"; static const core::com::slots::key_t HIDE_SLOT = "hide"; //----------------------------------------------------------------------------- signal_button::signal_button() noexcept { new_signal(signals::CLICKED); new_signal(signals::IS_CHECKED); new_signal(signals::TOGGLED); new_signal(signals::CHECKED); new_signal(signals::UNCHECKED); new_slot(SET_CHECKED_SLOT, &signal_button::set_checked, this); new_slot(CHECK_SLOT, &signal_button::check, this); new_slot(UNCHECK_SLOT, &signal_button::uncheck, this); new_slot(SET_ENABLED_SLOT, &signal_button::set_enabled, this); new_slot(ENABLE_SLOT, &signal_button::enable, this); new_slot(DISABLE_SLOT, &signal_button::disable, this); new_slot(SET_VISIBLE_SLOT, &signal_button::set_visible, this); new_slot(SHOW_SLOT, &signal_button::show, this); new_slot(HIDE_SLOT, &signal_button::hide, this); } //----------------------------------------------------------------------------- signal_button::~signal_button() noexcept = default; //----------------------------------------------------------------------------- void signal_button::configuring() { this->initialize(); const auto configuration = this->get_config(); const auto cfg = configuration.get_child_optional("config"); if(cfg.has_value()) { m_checkable = cfg->get("checkable", m_checkable); m_check_at_start = cfg->get("checked", m_check_at_start); m_enable = cfg->get("enable", m_enable); m_text = cfg->get("text", m_text); m_text2 = cfg->get("text2", m_text2); m_tool_tip = core::ptree::get_and_deprecate(*cfg, "tool_tip", "toolTip", "26.0", m_tool_tip); if(const auto icon = cfg->get_optional("icon"); icon.has_value()) { m_icon = core::runtime::get_module_resource_file_path(icon.value()); } if(const auto icon = cfg->get_optional("icon2"); icon.has_value()) { m_icon2 = core::runtime::get_module_resource_file_path(icon.value()); } m_joystick_alias = sight::io::joystick::interactor::to_joystick(cfg->get("joystick", "")); m_icon_width = core::ptree::get_and_deprecate(*cfg, "icon_width", "iconWidth", "26.0", m_icon_width); m_icon_height = core::ptree::get_and_deprecate(*cfg, "icon_height", "iconHeight", "26.0", m_icon_height); } } //----------------------------------------------------------------------------- void signal_button::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); auto* layout = new QVBoxLayout(); m_button = new QPushButton(QString::fromStdString(m_text)); m_button->setEnabled(m_enable); const QString service_id = QString::fromStdString(base_id()); m_button->setObjectName(service_id + "/signal_button"); m_button->setProperty("class", "signal-button"); layout->addWidget(m_button); qt_container->set_layout(layout); if(!m_tool_tip.empty()) { m_button->setToolTip(QString::fromStdString(m_tool_tip)); } if(!m_icon.empty()) { m_button->setIcon(QIcon(QString::fromStdString(m_icon.string()))); } if(m_icon_width > 0 && m_icon_height > 0) { m_button->setIconSize(QSize(int(m_icon_width), int(m_icon_height))); } if(m_checkable) { m_button->setCheckable(true); if(m_check_at_start) { m_button->setChecked(true); if(!m_text2.empty()) { m_button->setText(QString::fromStdString(m_text2)); } if(!m_icon2.empty()) { m_button->setIcon(QIcon(QString::fromStdString(m_icon2.string()))); } } } QObject::connect(m_button.data(), &QPushButton::clicked, this, &signal_button::on_clicked); QObject::connect(m_button.data(), &QPushButton::toggled, this, &signal_button::on_toggled); if(m_joystick_alias != sight::io::joystick::joystick_t::unknown) { this->start_listening_joystick(); } } //----------------------------------------------------------------------------- void signal_button::updating() { } //----------------------------------------------------------------------------- void signal_button::stopping() { this->destroy(); if(m_joystick_alias != sight::io::joystick::joystick_t::unknown) { this->stop_listening_joystick(); } } //----------------------------------------------------------------------------- void signal_button::joystick_axis_direction_event(const sight::io::joystick::axis_direction_event& _event) { if(_event.device->alias == m_joystick_alias) { if(_event.axis_alias == sight::io::joystick::axis_t::tz and _event.value == sight::io::joystick::axis_direction_event::direction_t::backward) { m_button->animateClick(); } } } //----------------------------------------------------------------------------- void signal_button::on_clicked() { const auto sig = this->signal(signals::CLICKED); sig->async_emit(); } //----------------------------------------------------------------------------- void signal_button::on_toggled(bool _toggled) { this->set_checked(_toggled); // legacy { const auto sig = this->signal(signals::TOGGLED); sig->async_emit(_toggled); } // current signal { const auto sig = this->signal(signals::IS_CHECKED); sig->async_emit(_toggled); } // checked/unchecked signals if(_toggled) { const auto sig = this->signal(signals::CHECKED); sig->async_emit(); } else { const auto sig = this->signal(signals::UNCHECKED); sig->async_emit(); } } //----------------------------------------------------------------------------- void signal_button::set_checked(bool _checked) { if(_checked) { if(!m_text2.empty()) { m_button->setText(QString::fromStdString(m_text2)); } if(!m_icon2.empty()) { m_button->setIcon(QIcon(QString::fromStdString(m_icon2.string()))); } } else { if(!m_text.empty()) { m_button->setText(QString::fromStdString(m_text)); } if(!m_icon.empty()) { m_button->setIcon(QIcon(QString::fromStdString(m_icon.string()))); } } // properly check/uncheck the button when this method is called from the sight slot. this->blockSignals(true); m_button->setChecked(_checked); this->blockSignals(false); } //----------------------------------------------------------------------------- void signal_button::check() { this->set_checked(true); } //----------------------------------------------------------------------------- void signal_button::uncheck() { this->set_checked(false); } //----------------------------------------------------------------------------- void signal_button::set_enabled(bool _is_enabled) { editor::set_enabled(_is_enabled); // Keep this in case of signal_button is used outside a view container m_button->setEnabled(_is_enabled); } //----------------------------------------------------------------------------- void signal_button::enable() { this->set_enabled(true); } //----------------------------------------------------------------------------- void signal_button::disable() { this->set_enabled(false); } //----------------------------------------------------------------------------- void signal_button::set_visible(bool _is_visible) { editor::set_visible(_is_visible); // Keep this in case of signal_button is used outside a view container m_button->setVisible(_is_visible); } //----------------------------------------------------------------------------- void signal_button::show() { this->set_visible(true); } //----------------------------------------------------------------------------- void signal_button::hide() { this->set_visible(false); } //----------------------------------------------------------------------------- } // namespace sight::module::ui::qt::com sight-25.1.0/module/ui/qt/com/signal_button.hpp000066400000000000000000000146451503402212300214040ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2015-2025 IRCAD France * Copyright (C) 2015-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include namespace sight::module::ui::qt::com { /** * @brief This editor shows a button and sends a signal when it is clicked. * * If the mode is "checkable", it sends a signal with a boolean in parameter representing the "check" state. * * @section Signals Signals * - \b clicked(): This signal is emitted when the button is clicked. * - \b toggled(bool): This signal is emitted when the button is checked. * * @section Slot Slot * - \b set_checked(bool): allows to check/uncheck the button. * - \b check(): allows to check the button. * - \b uncheck(): allows to uncheck the button. * - \b set_enabled(bool): sets the button executability. * - \b enable(): sets the button executable. * - \b disable(): sets the button inexecutable. * - \b set_visible(bool):s ets the button visibility. * - \b show(): shows the button. * - \b hide(): hides the button. * * @section XML XML configuration * @code{.xml} true|false ... ... ... ... true|false ... ... left/right @endcode * * @subsection Configuration Configuration: * - \b text (optional, string, default=""): text displayed on the button. * - \b icon (optional, string, default=""): icon displayed on the button. * - \b checkable (optional, bool, default=false): if true, the button is checkable. * - \b enable (optional, bool, default=true): if true, the button is executable. * - \b text2 (optional, string, default=""): text displayed if the button is checked. * - \b icon2 (optional, string, default=""): icon displayed if the button is checked. * - \b checked (optional, bool, default=false): if true, the button is checked at start. * - \b iconWidth (optional, unsigned, default=0): icon width. * - \b iconHeight (optional, unsigned, default=0): icon height. * - \b joystick (optional, string, default=""): assign left or right joystick push (tz) on button click. */ class signal_button final : public QObject, public sight::ui::editor, public sight::io::joystick::interactor { Q_OBJECT public: struct signals { using bool_t = core::com::signal; using void_t = core::com::signal; static inline const core::com::signals::key_t IS_CHECKED = "is_checked"; /// Same as "is_checked" but kept for legacy reasons. static inline const core::com::signals::key_t TOGGLED = "toggled"; static inline const core::com::signals::key_t CLICKED = "clicked"; static inline const core::com::signals::key_t CHECKED = "checked"; static inline const core::com::signals::key_t UNCHECKED = "unchecked"; }; /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(signal_button, sight::ui::editor); /// Creates signals and slots. signal_button() noexcept; /// Destroys the service. ~signal_button() noexcept final; protected: /// Configures the class parameters. void configuring() final; /// Launches the editor::starting method. void starting() final; ///Does nothing. void updating() final; /// Launches the editor::stopping method. void stopping() final; /** * @brief Manage joystick events * * @param _event */ void joystick_axis_direction_event(const sight::io::joystick::axis_direction_event& _event) final; private Q_SLOTS: void on_clicked(); void on_toggled(bool /*toggled*/); private: /// SLOT: checks or unchecks the button. void set_checked(bool _checked); /// SLOT: checks the button. void check(); /// SLOT: unchecks the button. void uncheck(); /// SLOT: sets the button executability. void set_enabled(bool _is_enabled) final; /// SLOT: sets the button executable. void enable() final; /// SLOT: sets the button inexecutable. void disable() final; /// SLOT: sets the button visibility. void set_visible(bool _is_visible) final; /// SLOT: shows the button. void show() final; /// SLOT: hides he button. void hide() final; /// Contains the button QPointer m_button {nullptr}; /// Defines the button's text. std::string m_text; /// Defines the button's text when it is checked. std::string m_text2; /// Defines the path of the button's icon. std::filesystem::path m_icon; /// Defines the path of the button's icon when it is checked. std::filesystem::path m_icon2; /// Defines if the button is checkable. bool m_checkable {false}; /// Defines if the button is executable. bool m_enable {true}; /// Defines if the button is checked at start. bool m_check_at_start {false}; /// Defines the icon width. unsigned m_icon_width {0}; /// Defines the icon height. unsigned m_icon_height {0}; /// Defines the button tooltip. std::string m_tool_tip; /// Left/Right or none for joystick usage. sight::io::joystick::joystick_t m_joystick_alias {sight::io::joystick::joystick_t::unknown}; }; } // namespace sight::module::ui::qt::com sight-25.1.0/module/ui/qt/com/signal_shortcut.cpp000066400000000000000000000214531503402212300217320ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2018-2025 IRCAD France * Copyright (C) 2018-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "signal_shortcut.hpp" #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::com { //----------------------------------------------------------------------------- signal_shortcut::signal_shortcut() noexcept { new_signal(signals::IS_ENABLED); new_signal(signals::ENABLED); new_signal(signals::DISABLED); new_signal(signals::ACTIVATED); new_signal(signals::IS_CHECKED); new_signal(signals::CHECKED); new_signal(signals::UNCHECKED); new_slot(slots::SET_ENABLED, &signal_shortcut::set_enabled, this); new_slot(slots::SET_DISABLED, [this](bool _disabled){this->set_enabled(!_disabled);}); new_slot(slots::ENABLE, [this](){this->set_enabled(true);}); new_slot(slots::DISABLE, [this](){this->set_enabled(false);}); new_slot(slots::APPLY_ENABLED, [this](){this->set_enabled(*m_enabled);}); new_slot(slots::SET_CHECKED, &signal_shortcut::set_checked, this); new_slot(slots::CHECK, [this](){this->set_checked(true);}); new_slot(slots::UNCHECK, [this](){this->set_checked(false);}); new_slot(slots::APPLY_CHECKED, [this](){this->set_checked(*m_checked);}); } //----------------------------------------------------------------------------- void signal_shortcut::configuring() { const auto config_tree = this->get_config(); if(auto properties = config_tree.get_child_optional("properties"); not properties.has_value()) { const auto enabled = m_enabled.lock(); *enabled = core::ptree::get_value(config_tree, "state..enabled", true); } const auto config_shortcut = config_tree.get_child("config."); m_shortcut = config_shortcut.get("shortcut", m_shortcut); SIGHT_ASSERT("Shortcut must not be empty", !m_shortcut.empty()); m_wid = config_shortcut.get("wid", m_wid); m_sid = config_shortcut.get("sid", m_sid); SIGHT_ASSERT( "Either The wid or sid attribute must be specified for signal_shortcut", !m_wid.empty() || !m_sid.empty() ); } //----------------------------------------------------------------------------- void signal_shortcut::set_enabled(bool _enabled) { { const auto enabled = m_enabled.lock(); if(_enabled != enabled->value()) { *enabled = _enabled; enabled->async_emit(this, data::object::MODIFIED_SIG); } } if(_enabled) { this->enable(); auto sig = this->signal(signals::ENABLED); sig->async_emit(); } else { this->disable(); auto sig = this->signal(signals::DISABLED); sig->async_emit(); } auto sig = this->signal(signals::IS_ENABLED); sig->async_emit(_enabled); } //----------------------------------------------------------------------------- void signal_shortcut::set_checked(bool _checked) { { const auto checked = m_checked.lock(); if(_checked != checked->value()) { *checked = _checked; checked->async_emit(this, data::object::MODIFIED_SIG); } } if(_checked) { auto sig = this->signal(signals::CHECKED); sig->async_emit(); } else { auto sig = this->signal(signals::UNCHECKED); sig->async_emit(); } auto sig = this->signal(signals::IS_CHECKED); sig->async_emit(_checked); } //----------------------------------------------------------------------------- void signal_shortcut::starting() { if(*m_enabled) { this->enable(); // Make sure that we propagate this status to imitate action behaviour. if(*m_checked) { auto sig = this->signal(signals::CHECKED); sig->async_emit(); } else { auto sig = this->signal(signals::UNCHECKED); sig->async_emit(); } } } //----------------------------------------------------------------------------- void signal_shortcut::stopping() { if(*m_enabled) { this->disable(); } } //----------------------------------------------------------------------------- void signal_shortcut::updating() { } //------------------------------------------------------------------------------ service::connections_t signal_shortcut::auto_connections() const { return { {m_checked, sight::data::object::MODIFIED_SIG, slots::APPLY_CHECKED}, {m_enabled, sight::data::object::MODIFIED_SIG, slots::APPLY_ENABLED} }; } //------------------------------------------------------------------------------ void signal_shortcut::on_activation() { if(*m_enabled) { this->signal(signals::ACTIVATED)->async_emit(); const auto checked = [&](){return m_checked.lock()->value();}(); this->set_checked(not checked); } } //------------------------------------------------------------------------------ void signal_shortcut::enable() { sight::ui::container::widget::sptr fwc = nullptr; // Either get the container via a service id if(!m_sid.empty()) { if(const bool sid_exists = core::id::exist(m_sid); sid_exists) { service::base::sptr service = service::get(m_sid); auto container_srv = std::dynamic_pointer_cast(service); fwc = container_srv->get_container(); } else { SIGHT_ERROR("Invalid service id " << m_sid); } } // or a window id else if(!m_wid.empty()) { fwc = sight::ui::registry::get_wid_container(m_wid); if(!fwc) { SIGHT_ERROR("Invalid window id " << m_wid); } } if(fwc != nullptr) { if(auto qtc = std::dynamic_pointer_cast(fwc); qtc != nullptr) { // Get the associated widget to use as parent for the shortcut QWidget* widget = qtc->get_qt_container(); std::vector shortcuts; boost::split(shortcuts, m_shortcut, boost::is_any_of(";,")); for(const auto& shortcut : shortcuts) { // Create a key sequence from the string and its associated QShortcut QKeySequence shortcut_sequence = QKeySequence(QString::fromStdString(shortcut)); auto* shortcut_object = new QShortcut(shortcut_sequence, widget); shortcut_object->setContext(Qt::ApplicationShortcut); // Connect the activated signal to the onActivation method of this class QObject::connect(shortcut_object, &QShortcut::activated, this, &self_t::on_activation); m_shortcut_objects.emplace_back(shortcut_object); } } } else { SIGHT_ERROR( "Cannot setup shortcut " << m_shortcut << " on invalid " << (!m_wid.empty() ? "wid " + m_wid : "sid " + m_sid) ); } } //------------------------------------------------------------------------------ void signal_shortcut::disable() { for(const auto& shortcut_object : m_shortcut_objects) { QObject::disconnect(shortcut_object, &QShortcut::activated, this, &self_t::on_activation); // We need to delete manually otherwise it stays referenced by the widget and can never enable again delete shortcut_object; } m_shortcut_objects.clear(); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::com sight-25.1.0/module/ui/qt/com/signal_shortcut.hpp000066400000000000000000000143131503402212300217340ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2018-2025 IRCAD France * Copyright (C) 2018-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::ui::qt::com { /** * @brief This service sends a signal when the associated shortcut is activated. * @section XML XML configuration * @code{.xml} @endcode * * @subsection Configuration Configuration * - \b shortcut: comma or semi-colon separated list of shortcuts * - \b sid/wid (exclusive): id of the service/window associated to the gui container * to which the shortcut will be associated * * @subsection Properties Properties * - \b enabled: enables or disables the shortcut. * - \b checked: initial value if you want to use this dual state. * * @section Signals Signals * - \b activated(): This signal is emitted when the shortcut is received. * - \b is_enabled(bool): This signal is emitted when the service change the enables state (current state is sent as a * boolean parameter) * - \b enabled(): This signal is emitted when the service is enabled. * - \b disabled(): This signal is emitted when the service is disabled * - \b is_checked(bool): Emitted when the state is checked or unchecked. * - \b checked(): Emitted when the state is checked. * - \b unchecked(): Emitted when the state is unchecked.. * * @section Slots Slots * - \b set_enabled(bool): Sets whether the service emits "activated" when the key-sequence is triggered * - \b set_disabled(bool): Opposite of set_enabled(bool). * - \b enable(): Make the service active. * - \b disable(): Make the service inactive. * - \b set_checked(bool): sets whether the state is checked. * - \b check(): check the state. * - \b uncheck(): uncheck the state. */ class signal_shortcut final : public QObject, public service::base { Q_OBJECT public: struct signals { using bool_t = core::com::signal; using void_t = core::com::signal; static inline const core::com::signals::key_t IS_ENABLED = "is_enabled"; static inline const core::com::signals::key_t ENABLED = "enabled"; static inline const core::com::signals::key_t DISABLED = "disabled"; static inline const core::com::signals::key_t IS_CHECKED = "is_checked"; static inline const core::com::signals::key_t CHECKED = "checked"; static inline const core::com::signals::key_t UNCHECKED = "unchecked"; static inline const core::com::signals::key_t ACTIVATED = "activated"; }; struct slots { static inline const core::com::slots::key_t SET_ENABLED = "set_enabled"; static inline const core::com::slots::key_t SET_DISABLED = "set_disabled"; static inline const core::com::slots::key_t ENABLE = "enable"; static inline const core::com::slots::key_t DISABLE = "disable"; static inline const core::com::slots::key_t APPLY_ENABLED = "apply_enabled"; static inline const core::com::slots::key_t SET_CHECKED = "set_checked"; static inline const core::com::slots::key_t CHECK = "check"; static inline const core::com::slots::key_t UNCHECK = "uncheck"; static inline const core::com::slots::key_t APPLY_CHECKED = "apply_checked"; }; SIGHT_DECLARE_SERVICE(signal_shortcut, service::base); /// Constructor. Do nothing. signal_shortcut() noexcept; /// Destructor. Do nothing. ~signal_shortcut() noexcept final = default; /// Enables or disables the shortcut service. void set_enabled(bool _enabled); /// Checks or unchecks the shortcut service. void set_checked(bool _checked); protected: /** @name Service methods ( final from service::base ) * @{ */ /// This method configures the service void configuring() final; /** * @brief This method enables the eventFilter */ void starting() final; /** * @brief This method deletes the eventFilter */ void stopping() final; /** * @brief This method does nothing. */ void updating() final; /// Connects the properties service::connections_t auto_connections() const final; private Q_SLOTS: void on_activation(); private: /// Creates the shortcuts void enable(); /// Destroys the shortcuts void disable(); /// string containing the shortcut(s) to trigger, separated by , or ; std::string m_shortcut; /// Service id used to get the widget of the activity to set up a shortcut in /// Either this member or m_wid has to be specified std::string m_sid; /// Window id used to get the widget of the activity to set up a shortcut in /// Either this member or m_sid has to be specified std::string m_wid; /// Qt shortcut objects std::vector > m_shortcut_objects; /// Enabled property /// True: the "activated" signal is triggered when key sequence is detected. /// False: the key sequence is ignored. sight::data::property m_enabled {this, "enabled", true}; /// Dual state, on/off sight::data::property m_checked {this, "checked", false}; }; } // namespace sight::module::ui::qt::com sight-25.1.0/module/ui/qt/icon_speed_dial.cpp000066400000000000000000000216761503402212300210540ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "icon_speed_dial.hpp" #include "core/thread/worker.hpp" #include #include #include #include #include #include #include #include class resize_actions_filter : public QObject { private: std::vector m_actions; public: explicit resize_actions_filter(std::vector _actions) : m_actions(std::move(_actions)) { } //------------------------------------------------------------------------------ bool eventFilter(QObject* /*target*/, QEvent* _e) override { if(_e->type() == QEvent::Resize) { auto* resize_event = static_cast(_e); std::ranges::for_each(m_actions, [resize_event](QWidget* _w){_w->setFixedSize(resize_event->size());}); } return false; } }; class resize_icon_filter : public QObject { public: //------------------------------------------------------------------------------ bool eventFilter(QObject* _target, QEvent* _e) override { if(_e->type() == QEvent::Resize) { auto* push_button = qobject_cast(_target); push_button->setIconSize(push_button->size()); } return false; } }; namespace sight::module::ui::qt { icon_speed_dial::icon_speed_dial() { new_slot(icon_speed_dial::slots::FOLD, [this]{m_speed_dial->fold();}); new_slot(icon_speed_dial::slots::UNFOLD, [this]{m_speed_dial->unfold();}); new_slot(icon_speed_dial::slots::UPDATE_ACTIONS, &icon_speed_dial::update_actions, this); } //------------------------------------------------------------------------------ void icon_speed_dial::configuring() { initialize(); const auto& config = this->get_config(); auto direction = config.get("config..direction"); if(direction == "up") { m_direction = sight::ui::qt::widget::speed_dial::direction::up; } else if(direction == "right") { m_direction = sight::ui::qt::widget::speed_dial::direction::right; } else if(direction == "down") { m_direction = sight::ui::qt::widget::speed_dial::direction::down; } else if(direction == "left") { m_direction = sight::ui::qt::widget::speed_dial::direction::left; } else { SIGHT_ASSERT("Invalid direction " << direction << " for speed_dial " << get_id(), false); } m_spacing = config.get("config..spacing", -1); m_icon = config.get("config..icon"); m_unfolded_icon = config.get("config..unfolded_icon", ""); m_animation_duration = config.get("config..animationDuration", -1); std::ranges::transform( boost::make_iterator_range(config.get_child("actions").equal_range("action")), std::back_inserter(m_actions), [](const auto& _action) -> action { return { .sid = _action.second.template get(".sid"), .name = _action.second.get(".name", ""), .icon = _action.second.template get(".icon"), .shortcut = _action.second.get(".shortcut", "") }; }); } //------------------------------------------------------------------------------ void icon_speed_dial::starting() { create(); const std::string service_id = base_id(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); auto* layout = new QBoxLayout(QBoxLayout::TopToBottom); m_speed_dial = new sight::ui::qt::widget::speed_dial(m_direction); m_speed_dial->setObjectName(QString::fromStdString(service_id)); if(m_spacing >= 0) { m_speed_dial->set_spacing(m_spacing); } if(!m_icon.empty()) { QIcon icon = m_speed_dial->icon(); icon.addFile(QString::fromStdString(core::runtime::get_module_resource_file_path(m_icon).string())); m_speed_dial->setIcon(icon); } if(!m_unfolded_icon.empty()) { QIcon icon = m_speed_dial->icon(); icon.addFile( QString::fromStdString(core::runtime::get_module_resource_file_path(m_unfolded_icon).string()), {}, QIcon::Normal, QIcon::On ); m_speed_dial->setIcon(icon); } if(m_animation_duration >= 0) { m_speed_dial->set_animation_duration(m_animation_duration); } for(const action& action : m_actions) { auto* q_action = new QPushButton; q_action->setFixedSize(m_speed_dial->size()); q_action->setObjectName(QString::fromStdString(action.name.empty() ? action.sid : action.name)); std::string sid = action.sid; auto service = std::dynamic_pointer_cast(sight::service::get(sid)); SIGHT_ASSERT("icon_speed_dial only supports action", service != nullptr); auto update_if_enabled = [sid] { if(auto action = std::dynamic_pointer_cast(sight::service::get(sid)); action != nullptr && action->enabled()) { sight::service::get(sid)->update(); } }; QObject::connect(q_action, &QPushButton::clicked, update_if_enabled); q_action->setEnabled(service->enabled()); auto is_enabled_slot = new_slot("setEnabledQt_" + action.sid, &QPushButton::setEnabled, q_action); is_enabled_slot->set_worker(worker()); service->signal("is_enabled")->connect(is_enabled_slot); service->signal("is_visible")->connect(slot(slots::UPDATE_ACTIONS)); q_action->setToolTip(QString::fromStdString(action.name)); q_action->setIcon( QIcon( QString::fromStdString( core::runtime::get_module_resource_file_path( action.icon ).string() ) ) ); if(!action.shortcut.empty()) { auto* shortcut = new QShortcut(QString::fromStdString(action.shortcut), m_speed_dial->window()); QObject::connect(shortcut, &QShortcut::activated, update_if_enabled); m_shortcuts.push_back(shortcut); } q_action->installEventFilter(new resize_icon_filter); m_widgets.push_back(q_action); } m_speed_dial->installEventFilter(new resize_actions_filter(m_widgets)); update_actions(); layout->addWidget(m_speed_dial); qt_container->set_layout(layout); m_speed_dial->show(); } //------------------------------------------------------------------------------ void icon_speed_dial::updating() { } //------------------------------------------------------------------------------ void icon_speed_dial::stopping() { get_container()->destroy_container(); m_speed_dial = nullptr; std::ranges::for_each(m_shortcuts, [](QShortcut* _shortcut){_shortcut->setEnabled(false);}); std::ranges::for_each( m_actions, [](const action& _a) { if(auto service = sight::service::get(_a.sid); service != nullptr && !service->stopped()) { service->stop(); } }); } //------------------------------------------------------------------------------ void icon_speed_dial::update_actions() { std::vector actions; for(std::size_t i = 0 ; i < m_actions.size() ; i++) { if(auto action = std::dynamic_pointer_cast(sight::service::get(m_actions[i].sid)); action != nullptr && action->visible()) { actions.push_back(m_widgets[i]); } } m_speed_dial->update_actions(std::move(actions)); } } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/icon_speed_dial.hpp000066400000000000000000000100231503402212300210410ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "core/macros.hpp" #include "ui/__/service.hpp" #include #include #include namespace sight::module::ui::qt { /** * A service which allows to display a menu as a speed dial. * Example of configuration: * @code{.xml} @endcode * - \ * - direction (enum, mandatory): The direction where the buttons are to be shown. Either "up", "right", "down" or * "left". * - spacing (int, default=10): The space, in pixels, between the buttons. * - icon (string, mandatory): The icon of the button. * - unfolded_icon (string, default=""): The icon of the button when it is unfolded. Defaults to the icon when folded * if unspecified. * - animationDuration (int, default=250): The duration, in milliseconds, of the animation when folding/unfolding. * - \ * - \ * - sid (string, mandatory): The sid of the related action. * - name (string, default=""): The name of the button (shown in the tooltip). * - icon (string, mandatory): The icon of the button. * - shortcut (string, default=""): The shortcut associated to the action. If unspecified, no shortcuts are * created. */ class icon_speed_dial : public sight::ui::service { public: icon_speed_dial(); struct action { std::string sid; std::string name; std::string icon; std::string shortcut; }; SIGHT_DECLARE_SERVICE(icon_speed_dial, sight::ui::service); struct slots { static inline const core::com::slots::key_t FOLD = "fold"; static inline const core::com::slots::key_t UNFOLD = "unfold"; static inline const core::com::slots::key_t UPDATE_ACTIONS = "updateActions"; }; void configuring() override; void starting() override; void updating() override; void stopping() override; private: void update_actions(); enum sight::ui::qt::widget::speed_dial::direction m_direction = sight::ui::qt::widget::speed_dial::direction::up; int m_spacing = -1; std::string m_icon; std::string m_unfolded_icon; int m_animation_duration = -1; std::vector m_actions; std::vector m_widgets; sight::ui::qt::widget::speed_dial* m_speed_dial = nullptr; std::vector m_shortcuts; }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/image/000077500000000000000000000000001503402212300163155ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/image/image.cpp000066400000000000000000000061611503402212300201070ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2019-2024 IRCAD France * Copyright (C) 2019-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "image.hpp" #include #include #include #include #include #include namespace sight::module::ui::qt::image { //----------------------------------------------------------------------------- image::image() noexcept = default; //----------------------------------------------------------------------------- image::~image() noexcept = default; //----------------------------------------------------------------------------- void image::starting() { this->create(); const auto qt_container = std::dynamic_pointer_cast(this->get_container()); auto* const layout = new QVBoxLayout(); auto* const label = new QLabel(""); QPixmap pixmap; pixmap.load(m_path.string().c_str()); if(pixmap.isNull()) { SIGHT_ERROR("image could not be found: " << m_path); } if(m_width != -1 && m_height != -1) { label->setPixmap(pixmap.scaled(m_width, m_height, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else if(m_width != -1) { label->setPixmap((pixmap.scaledToWidth(m_width, Qt::SmoothTransformation))); } else if(m_height != -1) { label->setPixmap((pixmap.scaledToHeight(m_height, Qt::SmoothTransformation))); } else { label->setPixmap(pixmap); } layout->addWidget(label); qt_container->set_layout(layout); } //----------------------------------------------------------------------------- void image::stopping() { this->destroy(); } //----------------------------------------------------------------------------- void image::configuring() { this->initialize(); const config_t cfg = this->get_config(); auto path_cfg = cfg.get("path"); m_path = core::runtime::get_module_resource_file_path(path_cfg); m_width = cfg.get("width", m_width); m_height = cfg.get("height", m_height); } //----------------------------------------------------------------------------- void image::updating() { } //----------------------------------------------------------------------------- } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/image/image.hpp000066400000000000000000000047751503402212300201250ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2019-2024 IRCAD France * Copyright (C) 2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::ui::qt::image { /** * @brief This editor displays an image. * * @section XML XML configuration * @code{.xml} ... ... ... @endcode * @subsection Configuration Configuration * - \b path: path of the image. * - \b width (optional): width of the image in pixels. * - \b height (optional): height of the image in pixels. */ class image : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(image, sight::ui::editor); /// Constructor. Do nothing. image() noexcept; /// Destructor. Do nothing. ~image() noexcept override; protected: /** @name Service methods ( override from service::base ) * @{ */ /** * @brief This method launches the editor::starting method. */ void starting() override; /** * @brief This method launches the editor::stopping method. */ void stopping() override; /** * @brief This method is used to update services. Do nothing. */ void updating() override; /// This method is used to configure the class parameters. void configuring() override; /** @} */ private: std::filesystem::path m_path; /// Path of the image int m_width {-1}; /// Width of the image (pixels) int m_height {-1}; /// Height of the image (pixels) }; } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/image/image_info.cpp000066400000000000000000000131611503402212300211200ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/image/image_info.hpp" #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::image { static const core::com::slots::key_t GET_INTERACTION_SLOT = "get_interaction"; image_info::image_info() noexcept { new_slot(GET_INTERACTION_SLOT, &image_info::get_interaction, this); } //------------------------------------------------------------------------------ image_info::~image_info() noexcept = default; //------------------------------------------------------------------------------ void image_info::starting() { this->sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); auto* h_layout = new QHBoxLayout(); auto* static_text = new QLabel(QObject::tr("intensity:")); h_layout->addWidget(static_text, 0, Qt::AlignVCenter); m_value_text = new QLineEdit(); m_value_text->setReadOnly(true); h_layout->addWidget(m_value_text, 1, Qt::AlignVCenter); qt_container->set_layout(h_layout); } //------------------------------------------------------------------------------ void image_info::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void image_info::configuring() { this->sight::ui::service::initialize(); } //------------------------------------------------------------------------------ void image_info::updating() { const auto image = m_image.lock(); SIGHT_ASSERT("The input '" << IMAGE << "' is not defined", image); const bool image_is_valid = data::helper::medical_image::check_image_validity(image.get_shared()); m_value_text->setEnabled(image_is_valid); } //------------------------------------------------------------------------------ void image_info::get_interaction(data::tools::picking_info _info) { if(_info.m_event_id == data::tools::picking_info::event::mouse_move) { const auto image = m_image.lock(); SIGHT_ASSERT("The input '" << IMAGE << "' is not defined", image); const bool image_is_valid = data::helper::medical_image::check_image_validity(image.get_shared()); m_value_text->setEnabled(image_is_valid); if(image_is_valid) { const std::array& point = _info.m_world_pos; const data::image::size_t size = image->size(); if(point[0] < 0 || point[1] < 0 || point[2] < 0) { SIGHT_ERROR( "The received coordinates are not in image space, maybe you used the wrong picker " "interactor (see ::visuVTKAdaptor::imagePickerInteractor)" ); return; } const data::image::size_t coords = {{static_cast(point[0]), static_cast(point[1]), static_cast(point[2]) } }; bool is_inside = (coords[0] < size[0] && coords[1] < size[1]); if(image->num_dimensions() < 3) { is_inside = (is_inside && coords[2] == 0); } else { is_inside = (is_inside && coords[2] < size[2]); } if(!is_inside) { SIGHT_ERROR( "The received coordinates are not in image space, maybe you used the wrong picker " "interactor (see ::visuVTKAdaptor::imagePickerInteractor)" ); return; } const auto dump_lock = image->dump_lock(); const std::string intensity = image->get_pixel_as_string(coords[0], coords[1], coords[2]); m_value_text->setText(QString::fromStdString(intensity)); } } } //------------------------------------------------------------------------------ void image_info::info(std::ostream& _sstream) { _sstream << "image Info Editor"; } //------------------------------------------------------------------------------ service::connections_t image_info::auto_connections() const { connections_t connections; connections.push(IMAGE, data::image::MODIFIED_SIG, service::slots::UPDATE); connections.push(IMAGE, data::image::BUFFER_MODIFIED_SIG, service::slots::UPDATE); return connections; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/image/image_info.hpp000066400000000000000000000062161503402212300211300ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::image { /** * @brief image_info service allows to display image pixel information when it receives the mouse cursor coordinates. * * @warning The interaction must be received in pixel coordinates (for example from the the adaptor * ::visuVTKAdaptor::imagePickerInteractor) * * @section Slots Slots * - \b get_interaction(data::tools::picking_info): display image pixel information * * @section XML XML Configuration * * @code{.xml} @endcode * @subsection Input Input * - \b image [sight::data::image]: image used to retrieve the pixel value. */ class image_info : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(image_info, sight::ui::editor); image_info() noexcept; ~image_info() noexcept override; protected: /// Starts editor. void starting() override; /// Stops editor. void stopping() override; /// Check if the image is valid, if not the editor is disabled void updating() override; /// Initializes the editor void configuring() override; void info(std::ostream& _sstream) override; /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection * * Connect image::MODIFIED_SIG to this::service::slots::UPDATE * Connect image::BUFFER_MODIFIED_SIG to this::service::slots::UPDATE */ connections_t auto_connections() const override; private: /// Slot: get the picking information to display the pixel value void get_interaction(data::tools::picking_info _info); /// Label to display the pixel value QPointer m_value_text; static constexpr std::string_view IMAGE = "image"; data::ptr m_image {this, IMAGE}; }; } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/image/slice_index_position_editor.cpp000066400000000000000000000540701503402212300246070ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/image/slice_index_position_editor.hpp" #include "data/image_series.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::image { namespace medical_image = data::helper::medical_image; static const core::com::slots::key_t UPDATE_SLICE_INDEX_SLOT = "updateSliceIndex"; static const core::com::slots::key_t UPDATE_SLICE_TYPE_SLOT = "updateSliceType"; const service::base::key_t slice_index_position_editor::IMAGE_INOUT = "image"; std::map slice_index_position_editor::orientation_prefix_map = { {slice_index_position_editor::axis_t::axial, "S"}, {slice_index_position_editor::axis_t::frontal, "P"}, {slice_index_position_editor::axis_t::sagittal, "L"} }; //------------------------------------------------------------------------------ slice_index_position_editor::slice_index_position_editor() noexcept { new_slot(UPDATE_SLICE_INDEX_SLOT, &slice_index_position_editor::update_slice_index, this); new_slot(UPDATE_SLICE_TYPE_SLOT, &slice_index_position_editor::update_slice_type, this); } //---------------------------------------------------------------------------------------- void slice_index_position_editor::configuring() { this->initialize(); const config_t config = this->get_config(); static const std::string s_CONFIG = "config.."; static const std::string s_SLICE_INDEX_CONFIG = s_CONFIG + "orientation"; static const std::string s_SLICE_LABEL_CONFIG = s_CONFIG + "label"; static const std::string s_AXIS_CONFIG = s_CONFIG + "display_axis_selector"; static const std::string s_STEP_CONFIG = s_CONFIG + "display_step_buttons"; auto orientation = config.get(s_SLICE_INDEX_CONFIG, "axial"); auto label_option = config.get(s_SLICE_LABEL_CONFIG, "index"); m_display_axis_selector = config.get(s_AXIS_CONFIG, m_display_axis_selector); m_display_step_buttons = config.get(s_STEP_CONFIG, m_display_step_buttons); if(label_option == "position") { m_label_option = label_option_t::position; } else if(label_option == "index") { m_label_option = label_option_t::index; } else { SIGHT_FATAL("The value for the xml element \"label\" can only be position or index."); } if(orientation == "axial") { m_axis = axis_t::axial; // Z } else if(orientation == "frontal") { m_axis = axis_t::frontal; // Y } else if(orientation == "sagittal") { m_axis = axis_t::sagittal; // X } else { SIGHT_FATAL("The value for the xml element \"orientation\" can only be axial, frontal or sagittal."); } } //------------------------------------------------------------------------------------------- void slice_index_position_editor::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); const QString service_id = QString::fromStdString(base_id()); auto* layout = new QVBoxLayout(); sight::ui::qt::slice_selector::ChangeIndexCallback fct_index = [this](int _index) { slice_index_notification(_index); }; sight::ui::qt::slice_selector::ChangeTypeCallback fct_type = [this](int _type){slice_type_notification(_type);}; sight::ui::qt::slice_selector::ChangeLabelCallback fct_label = [this](){slice_label_notification();}; if(m_label_option == label_option_t::index) { m_slice_selector_with_index = new sight::ui::qt::slice_selector( m_display_axis_selector, m_display_step_buttons, static_cast(1) ); } else if(m_label_option == label_option_t::position) { m_slice_selector_with_index = new sight::ui::qt::slice_selector( m_display_axis_selector, m_display_step_buttons, 0.0 ); } m_slice_selector_with_index->setProperty("class", "slice_selector"); m_slice_selector_with_index->set_enabled(false); m_slice_selector_with_index->setObjectName(service_id); m_slice_selector_with_index->set_change_index_callback(fct_index); m_slice_selector_with_index->set_change_type_callback(fct_type); m_slice_selector_with_index->set_change_label_callback(fct_label); m_slice_selector_with_index->set_type_selection(m_axis); layout->addWidget(m_slice_selector_with_index); m_slice_selector_with_index->set_prefix(orientation_prefix_map.at(m_axis)); layout->setContentsMargins(0, 0, 0, 0); qt_container->set_layout(layout); this->updating(); } //------------------------------------------------------------------------------ void slice_index_position_editor::updating() { { const auto image = m_image.const_lock(); if(m_label_option == label_option_t::index) { const bool image_is_valid = medical_image::check_image_validity(image.get_shared()); m_slice_selector_with_index->set_enable(image_is_valid); m_axial_index = std::max( std::int64_t(0), medical_image::get_slice_index(*image, axis_t::axial).value_or(0) ); m_frontal_index = std::max( std::int64_t(0), medical_image::get_slice_index(*image, axis_t::frontal).value_or(0) ); m_sagittal_index = std::max( std::int64_t(0), medical_image::get_slice_index(*image, axis_t::sagittal).value_or(0) ); this->update_slice_index_from_img(*image); } else if(m_label_option == label_option_t::position) { const bool image_is_valid = medical_image::check_image_validity(image.get_shared()); m_slice_selector_with_index->set_enabled(image_is_valid); const auto& origin = image->origin(); m_axial_position = medical_image::get_slice_position( *image, axis_t::axial ).value_or(double(origin[axis_t::axial])); m_frontal_position = medical_image::get_slice_position( *image, axis_t::frontal ).value_or(double(origin[axis_t::frontal])); m_sagittal_position = medical_image::get_slice_position( *image, axis_t::sagittal ).value_or(double(origin[axis_t::sagittal])); this->update_slice_index_from_img(*image); } } this->update_slider_fiducial(); } //---------------------------------------------------------------------------- void slice_index_position_editor::stopping() { this->destroy(); m_slice_selector_with_index = nullptr; } //------------------------------------------------------------------------------------ void slice_index_position_editor::destroyEditorContainer() { auto qt_container = std::dynamic_pointer_cast( this->get_container() ); qt_container->destroy_container(); } //------------------------------------------------------ void slice_index_position_editor::update_slice_index(int _axial, int _frontal, int _sagittal) { if(_sagittal != m_sagittal_index || _frontal != m_frontal_index || _axial != m_axial_index) { m_sagittal_index = _sagittal; m_frontal_index = _frontal; m_axial_index = _axial; const auto image = m_image.lock(); medical_image::set_slice_index(*image, axis_t::axial, m_axial_index); medical_image::set_slice_index(*image, axis_t::frontal, m_frontal_index); medical_image::set_slice_index(*image, axis_t::sagittal, m_sagittal_index); const auto& origin = image->origin(); m_sagittal_position = medical_image::get_slice_position(*image, axis_t::sagittal).value_or(origin[sagittal]); m_frontal_position = medical_image::get_slice_position(*image, axis_t::frontal).value_or(origin[frontal]); m_axial_position = medical_image::get_slice_position(*image, axis_t::axial).value_or(origin[axial]); this->update_slice_index_from_img(*image); } } //----------------------------------------------------------------------------- void slice_index_position_editor::info(std::ostream& /*_sstream*/) { } //------------------------------------------------------------------------------ void slice_index_position_editor::update_slice_index_from_img(const sight::data::image& _image) { if(medical_image::check_image_validity(_image)) { if(m_label_option == label_option_t::index) { const auto image_size = _image.size(); const auto index_ind = medical_image::get_slice_index(_image, m_axis).value_or(image_size[m_axis] / 2); // Update QSlider int max = 0; if(_image.num_dimensions() > m_axis) { max = static_cast(image_size[m_axis] - 1); } m_slice_selector_with_index->set_slice_range(0, max); m_slice_selector_with_index->set_slice_value(static_cast(index_ind)); // Find the max value for each dimension const int absolute_max = int(*std::ranges::max_element(image_size) - 1); m_slice_selector_with_index->set_index_digits(std::uint8_t(std::to_string(absolute_max).length())); } if(m_label_option == label_option_t::position) { const auto image_size = _image.size(); const auto& spacing = _image.spacing(); const auto& origin = _image.origin(); double max_position = 0.0; if(_image.num_dimensions() > m_axis) { max_position = static_cast(image_size[m_axis] - 1); } double min_position = 0.00; m_slice_selector_with_index->set_position_range(min_position, max_position); const auto index_position = medical_image::get_slice_index(_image, m_axis).value_or(image_size[m_axis] / 2); m_slice_selector_with_index->set_image_info(origin[m_axis], spacing[m_axis]); m_slice_selector_with_index->set_position_text(static_cast(index_position)); m_slice_selector_with_index->set_position_value(static_cast(index_position)); } } } //------------------------------------------------------------------------------ void slice_index_position_editor::slice_index_notification(int _index) { const auto image = m_image.lock(); medical_image::set_slice_index(*image, m_axis, _index); std::array idx { static_cast(medical_image::get_slice_index(*image, axis_t::sagittal).value_or(0)), static_cast(medical_image::get_slice_index(*image, axis_t::frontal).value_or(0)), static_cast(medical_image::get_slice_index(*image, axis_t::axial).value_or(0)) }; image->async_emit(this, data::image::SLICE_INDEX_MODIFIED_SIG, idx[2], idx[1], idx[0]); } //------------------------------------------------------------------------ void slice_index_position_editor::slice_label_notification() { m_label_option = (m_label_option == label_option_t::index) ? label_option_t::position : label_option_t::index; this->destroyEditorContainer(); this->create(); auto* layout = new QVBoxLayout(); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); qt_container->set_layout(layout); const QString service_id = QString::fromStdString(base_id()); if(m_label_option == label_option_t::index) { m_slice_selector_with_index = new sight::ui::qt::slice_selector( m_display_axis_selector, m_display_step_buttons, static_cast(1) ); } else if(m_label_option == label_option_t::position) { m_slice_selector_with_index = new sight::ui::qt::slice_selector( m_display_axis_selector, m_display_step_buttons, 0.0 ); } sight::ui::qt::slice_selector::ChangeIndexCallback fct_index = [this](int _t){slice_index_notification(_t);}; sight::ui::qt::slice_selector::ChangeTypeCallback fct_type = [this](int _t){slice_type_notification(_t);}; sight::ui::qt::slice_selector::ChangeLabelCallback fct_label = [this](){slice_label_notification();}; m_slice_selector_with_index->setProperty("class", "slice_selector"); m_slice_selector_with_index->set_enable(true); m_slice_selector_with_index->set_change_index_callback(fct_index); m_slice_selector_with_index->set_change_type_callback(fct_type); m_slice_selector_with_index->set_change_label_callback(fct_label); m_slice_selector_with_index->set_type_selection(m_axis); m_slice_selector_with_index->set_prefix(orientation_prefix_map.at(m_axis)); m_slice_selector_with_index->setObjectName(service_id); layout->addWidget(m_slice_selector_with_index); layout->setContentsMargins(0, 0, 0, 0); this->updating(); } //------------------------------------------------------------------------ void slice_index_position_editor::update_slice_type(int _from, int _to) { if(_to == static_cast(m_axis)) { m_axis = static_cast(_from); } else if(_from == static_cast(m_axis)) { m_axis = static_cast(_to); } if(m_label_option == label_option_t::index) { m_slice_selector_with_index->clear_slider_index(); } else if(m_label_option == label_option_t::position) { m_slice_selector_with_index->clear_slider_position(); } this->update_slice_type_from_img(m_axis); } //-------------------------------------------------------------------------- void slice_index_position_editor::update_slice_type_from_img(const axis_t& _type) { if(m_label_option == label_option_t::position) { const std::string& new_orientation_prefix = orientation_prefix_map.at(_type); m_slice_selector_with_index->set_prefix(new_orientation_prefix); m_slice_selector_with_index->set_type_selection(static_cast(_type)); } else if(m_label_option == label_option_t::index) { m_slice_selector_with_index->set_type_selection(static_cast(_type)); } const auto image = m_image.const_lock(); SIGHT_ASSERT("The inout key '" + IMAGE_INOUT + "' is not defined.", image); this->update_slice_index_from_img(*image); } //------------------------------------------------------------------------------ void slice_index_position_editor::update_slider_fiducial() { const auto image = m_image.lock(); const auto image_series = std::dynamic_pointer_cast(image.get_shared()); if(image_series) { if(m_label_option == label_option_t::index) { m_slice_selector_with_index->clear_slider_index(); } else { m_slice_selector_with_index->clear_slider_position(); } const auto fiducials_series = image_series->get_fiducials(); const auto sets = fiducials_series->get_fiducial_sets(); std::vector color_list; for(const auto& fiducials : sets) { std::array colors = {0.F, 0.F, 0.F, 1.F}; if(fiducials.color.has_value()) { colors = fiducials.color.value(); } const QColor color = QColor::fromRgbF( colors[0], colors[1], colors[2], colors[3] ); color_list.push_back(color); if(image_series->get_dimension_organization_type() == data::dicom::dimension_organization_t::tiled_sparse) { for(const auto& set : fiducials.fiducial_sequence) { if(set.graphic_coordinates_data_sequence.has_value()) { for(const auto& point : *set.graphic_coordinates_data_sequence) { if(m_label_option == label_option_t::index) { if(not point.referenced_image_sequence.referenced_frame_number.empty()) { const auto slice_index = point.referenced_image_sequence.referenced_frame_number.at(0) - 1; m_slice_selector_with_index->add_slider_position(slice_index, color); } else { SIGHT_ERROR( "Cannot find referenced_frame_number for fiducial, unable to add a slider mark" ); } } else { SIGHT_ERROR("Unsupported 'position' label option with tiled sparse images"); } } } } } else { for(const auto& set : fiducials.fiducial_sequence) { for(const auto& point : set.contour_data) { std::optional fiducial_position; const std::array array_point = {point.x, point.y, point.z}; fiducial_position = geometry::data::get_fiducial_slice_index(*image, array_point, m_axis); if(fiducial_position.has_value()) { if(m_label_option == label_option_t::index) { m_slice_selector_with_index->add_slider_position(fiducial_position.value(), color); } else { m_slice_selector_with_index->add_position_slider( static_cast(fiducial_position.value()), color ); } } } } } } } } //------------------------------------------------------------------------------ void slice_index_position_editor::slice_type_notification(int _type) { auto type = static_cast(_type); SIGHT_ASSERT( "Bad slice type " << type, type == axis_t::x_axis || type == axis_t::y_axis || type == axis_t::z_axis ); const auto old_type = m_axis; // Change slice type m_axis = type; // Fire the signal { const auto image = m_image.const_lock(); image->async_emit(this, data::image::SLICE_TYPE_MODIFIED_SIG, static_cast(old_type), _type); this->update_slice_index_from_img(*image); } } //------------------------------------------------------------------------------ service::connections_t slice_index_position_editor::auto_connections() const { connections_t connections; connections.push(IMAGE_INOUT, data::image::MODIFIED_SIG, service::slots::UPDATE); connections.push(IMAGE_INOUT, data::image::SLICE_INDEX_MODIFIED_SIG, UPDATE_SLICE_INDEX_SLOT); connections.push(IMAGE_INOUT, data::image::SLICE_TYPE_MODIFIED_SIG, UPDATE_SLICE_TYPE_SLOT); connections.push(IMAGE_INOUT, data::image::BUFFER_MODIFIED_SIG, service::slots::UPDATE); connections.push(IMAGE_INOUT, data::image::RULER_MODIFIED_SIG, service::slots::UPDATE); connections.push(IMAGE_INOUT, data::image::FIDUCIAL_REMOVED_SIG, service::slots::UPDATE); const auto image = m_image.lock(); const auto image_series = std::dynamic_pointer_cast(image.get_shared()); if(image_series) { // connect fiducials added/removed to update slot. connections.push(IMAGE_INOUT, data::has_fiducials::signals::GROUP_REMOVED, service::slots::UPDATE); connections.push(IMAGE_INOUT, data::has_fiducials::signals::POINT_REMOVED, service::slots::UPDATE); connections.push(IMAGE_INOUT, data::has_fiducials::signals::POINT_ADDED, service::slots::UPDATE); connections.push(IMAGE_INOUT, data::has_fiducials::signals::POINT_MODIFIED, service::slots::UPDATE); } return connections; } } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/image/slice_index_position_editor.hpp000066400000000000000000000134741503402212300246170ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "data/image_series.hpp" #include #include #include #include #include namespace sight::module::ui::qt::image { enum label_option_t { index = 0, position }; enum orientation_t { /// Directions. x_axis = 0, y_axis, z_axis, /// Planar definitions. sagittal = x_axis, frontal = y_axis, axial = z_axis }; /** * @brief slice_index_position_editor service allows to change the slice index/position of an image. * * This is represented by * - a slider to select the slice index/position * - a choice list to select the slice orientation (axial, frontal, sagittal) * * @section XML XML Configuration * * @code{.xml} @endcode * @subsection In-Out In-Out * - \b image [sight::data::image]: image on which the slice index/position will be changed. * * @subsection Configuration Configuration * - \b orientation : Axis on which the index/position of slice will be changed, must be "axial", "frontal" or *"sagittal". ( default = "axial") * - \b label : allows to choose between showing the position of the slice or the index of the slice. (default = *"index") * - \b display_axis_selector : Allows to change the axis.(default = "true") * - \b display_step_buttons : Allows to change the slice index/position with step buttons.(default = "false") */ class slice_index_position_editor : public sight::ui::editor { public: SIGHT_DECLARE_SERVICE(slice_index_position_editor, sight::ui::editor); /// Constructor. Do nothing. slice_index_position_editor() noexcept; /// Destructor. Do nothing. ~slice_index_position_editor() noexcept override = default; protected: static const service::base::key_t IMAGE_INOUT; /// @brief The slice type: axial, frontal, sagittal. using axis_t = data::helper::medical_image::axis_t; void configuring() override; /** * @brief Install the layout. */ void starting() override; /** * @brief Destroy the layout. */ void stopping() override; /// Update editor information from the image void updating() override; /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection * * Connect image::MODIFIED_SIG to this::service::slots::UPDATE * Connect image::SLICE_INDEX_MODIFIED_SIG to this::UPDATE_SLICE_INDEX_SLOT * Connect image::SLICE_TYPE_MODIFIED_SIG to this::UPDATE_SLICE_TYPE_SLOT * Connect image::BUFFER_MODIFIED_SIG to this::UPDATE_BUFFER_SLOT */ connections_t auto_connections() const override; /// Overrides void info(std::ostream& _sstream) override; /// Update the editor slider from the image slice index. void update_slice_index_from_img(const sight::data::image& _image); /// Update the editor slice type choice from the image slice type. void update_slice_type_from_img(const axis_t& _type); /// This method is called when the slider is move. Notify the slice index is modified. void slice_index_notification(int _index); /// This method is called when the slice type selected change. Notify the slice type is modified. void slice_type_notification(int _type); ///update the slider verticals lines. void update_slider_fiducial(); /// This method is called when the label type changes, to destroy the previous type of `qt_container`. void destroyEditorContainer(); /// This method is called when the slice label option changes. It notifies that the slice label option has been /// toggled. void slice_label_notification(); private: /** * @name Slots * @{ */ /// Slot: update image slice index void update_slice_index(int _axial, int _frontal, int _sagittal); /// Slot: update image slice type void update_slice_type(int _from, int _to); /// Slot: update image buffer void update_buffer(); /** * @} */ sight::ui::qt::slice_selector* m_slice_selector_with_index {}; data::ptr m_image {this, "image"}; std::int64_t m_axial_index {-1}; std::int64_t m_frontal_index {-1}; std::int64_t m_sagittal_index {-1}; double m_axial_position {-1}; double m_sagittal_position {-1}; double m_frontal_position {-1}; label_option_t m_label_option {label_option_t::index}; axis_t m_axis {axis_t::z_axis}; static std::map orientation_prefix_map; bool m_display_axis_selector {true}; bool m_display_step_buttons {false}; }; } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/image/transfer_function.cpp000066400000000000000000001004341503402212300225540ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2024 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/image/transfer_function.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::image { static const std::string USE_DEFAULT_PATH_CONFIG = "useDefaultPath"; static const std::string PATH_CONFIG = "path"; static const std::string DELETE_ICON_CONFIG = "deleteIcon"; static const std::string NEW_ICON_CONFIG = "newIcon"; static const std::string COPY_ICON_CONFIG = "copyIcon"; static const std::string REINITIALIZE_ICON_CONFIG = "reinitializeIcon"; static const std::string RENAME_ICON_CONFIG = "renameIcon"; static const std::string IMPORT_ICON_CONFIG = "importIcon"; static const std::string EXPORT_ICON_CONFIG = "exportIcon"; static const std::string ICON_WIDTH_CONFIG = "iconWidth"; static const std::string ICON_HEIGHT_CONFIG = "iconHeight"; const core::com::slots::key_t UPDATE_DEFAULT_PRESET_SLOT = "updateDefaultPreset"; const core::com::slots::key_t UPDATE_PRESETS_SLOT = "updatePresets"; //------------------------------------------------------------------------------ transfer_function::transfer_function() { new_slot(UPDATE_DEFAULT_PRESET_SLOT, &transfer_function::update_default_preset, this); new_slot(UPDATE_PRESETS_SLOT, [this](){transfer_function::initialize_presets();}); const std::filesystem::path module_path = core::runtime::get_module_resource_path( std::string( "sight::module::ui::qt" ) ); m_delete_icon = module_path / "delete.svg"; m_new_icon = module_path / "new.svg"; m_copy_icon = module_path / "copy.svg"; m_rename_icon = module_path / "text.svg"; m_reinitialize_icon = module_path / "reset.svg"; m_import_icon = module_path / "import.svg"; m_export_icon = module_path / "export.svg"; } //------------------------------------------------------------------------------ void transfer_function::configuring() { this->initialize(); const config_t tree = this->get_config(); const auto config = tree.get_child_optional("config"); bool use_default_path = true; if(config) { const auto path_cfg = config->equal_range(PATH_CONFIG); for(auto it_cfg = path_cfg.first ; it_cfg != path_cfg.second ; ++it_cfg) { const auto additional_tf_path = it_cfg->second.get_value(); if(!additional_tf_path.empty()) { const auto path = core::runtime::get_module_resource_file_path(additional_tf_path); m_paths.push_back(path); } } const auto config_attr = config->get_child_optional(""); if(config_attr) { use_default_path = config_attr->get(USE_DEFAULT_PATH_CONFIG, use_default_path); const auto delete_icon_cfg = config_attr->get_optional(DELETE_ICON_CONFIG); if(delete_icon_cfg) { m_delete_icon = core::runtime::get_module_resource_file_path(delete_icon_cfg.value()); } const auto new_icon_cfg = config_attr->get_optional(NEW_ICON_CONFIG); if(new_icon_cfg) { m_new_icon = core::runtime::get_module_resource_file_path(new_icon_cfg.value()); } const auto copy_icon_cfg = config_attr->get_optional(COPY_ICON_CONFIG); if(copy_icon_cfg) { m_copy_icon = core::runtime::get_module_resource_file_path(copy_icon_cfg.value()); } const auto reinitialize_icon_cfg = config_attr->get_optional(REINITIALIZE_ICON_CONFIG); if(reinitialize_icon_cfg) { m_reinitialize_icon = core::runtime::get_module_resource_file_path(reinitialize_icon_cfg.value()); } const auto rename_icon_cfg = config_attr->get_optional(RENAME_ICON_CONFIG); if(rename_icon_cfg) { m_rename_icon = core::runtime::get_module_resource_file_path(rename_icon_cfg.value()); } const auto import_icon_cfg = config_attr->get_optional(IMPORT_ICON_CONFIG); if(import_icon_cfg) { m_import_icon = core::runtime::get_module_resource_file_path(import_icon_cfg.value()); } const auto export_icon_cfg = config_attr->get_optional(EXPORT_ICON_CONFIG); if(export_icon_cfg) { m_export_icon = core::runtime::get_module_resource_file_path(export_icon_cfg.value()); } m_icon_width = config_attr->get(ICON_WIDTH_CONFIG, m_icon_width); m_icon_height = config_attr->get(ICON_HEIGHT_CONFIG, m_icon_height); } } if(use_default_path) { const auto path_root = core::runtime::get_module_resource_file_path("sight::module::ui::qt", "tf"); m_paths.push_back(path_root); } } //------------------------------------------------------------------------------ void transfer_function::starting() { this->create(); // Get the Qt container const auto qt_container = std::dynamic_pointer_cast(this->get_container()); // Layout management auto* const layout = new QBoxLayout(QBoxLayout::LeftToRight); // Buttons creation m_preset_combo_box = new QComboBox(); layout->addWidget(m_preset_combo_box); // Manage connection with the editor. QObject::connect( m_preset_combo_box, qOverload(&QComboBox::activated), this, &transfer_function::preset_choice ); if(*m_editable) { m_delete_button = new QPushButton(QIcon(m_delete_icon.string().c_str()), ""); m_delete_button->setToolTip(QString("Delete")); m_new_button = new QPushButton(QIcon(m_new_icon.string().c_str()), ""); m_new_button->setToolTip(QString("New")); m_copy_button = new QPushButton(QIcon(m_copy_icon.string().c_str()), ""); m_copy_button->setToolTip(QString("Copy")); m_reinitialize_button = new QPushButton(QIcon(m_reinitialize_icon.string().c_str()), ""); m_reinitialize_button->setToolTip(QString("Reinitialize")); m_rename_button = new QPushButton(QIcon(m_rename_icon.string().c_str()), ""); m_rename_button->setToolTip(QString("Rename")); m_import_button = new QPushButton(QIcon(m_import_icon.string().c_str()), ""); m_import_button->setToolTip(QString("Import")); m_export_button = new QPushButton(QIcon(m_export_icon.string().c_str()), ""); m_export_button->setToolTip(QString("Export")); if(m_icon_width > 0 && m_icon_height > 0) { int icon_width = int(m_icon_width); int icon_height = int(m_icon_height); m_delete_button->setIconSize(QSize(icon_width, icon_height)); m_new_button->setIconSize(QSize(icon_width, icon_height)); m_copy_button->setIconSize(QSize(icon_width, icon_height)); m_reinitialize_button->setIconSize(QSize(icon_width, icon_height)); m_rename_button->setIconSize(QSize(icon_width, icon_height)); m_import_button->setIconSize(QSize(icon_width, icon_height)); m_export_button->setIconSize(QSize(icon_width, icon_height)); } layout->addWidget(m_delete_button); layout->addWidget(m_new_button); layout->addWidget(m_copy_button); layout->addWidget(m_reinitialize_button); layout->addWidget(m_rename_button); layout->addWidget(m_import_button); layout->addWidget(m_export_button); QObject::connect(m_delete_button, &QPushButton::clicked, this, &transfer_function::delete_preset); QObject::connect(m_new_button, &QPushButton::clicked, this, &transfer_function::create_preset); QObject::connect(m_copy_button, &QPushButton::clicked, this, &transfer_function::copy_preset); QObject::connect(m_reinitialize_button, &QPushButton::clicked, this, &transfer_function::reinitialize_presets); QObject::connect(m_rename_button, &QPushButton::clicked, this, &transfer_function::rename_preset); QObject::connect(m_import_button, &QPushButton::clicked, this, &transfer_function::import_preset); QObject::connect(m_export_button, &QPushButton::clicked, this, &transfer_function::export_preset); } qt_container->set_layout(layout); // Initializes the TF preset from paths. this->initialize_presets(); this->update_default_preset(); } //------------------------------------------------------------------------------ void transfer_function::updating() { // Select first the TF in the presets if has changed std::string selected_tf_key = m_preset_combo_box->currentText().toStdString(); const std::string tf_name = [this]{const auto current_tf = m_current_tf.lock(); return current_tf->name();}(); if(selected_tf_key != tf_name) { const int index = m_preset_combo_box->findText(QString::fromStdString(tf_name)); if(index < 0) { SIGHT_ERROR("Could not find transfer function '" + tf_name + "'"); return; } this->preset_choice(index); // Update the selected key now that we have found the one we were looking for selected_tf_key = m_preset_combo_box->currentText().toStdString(); } // Now updates the TF const auto opt_presets = m_opt_presets.lock(); sight::data::map& presets = (opt_presets != nullptr) ? *opt_presets : *m_tf_presets; const auto new_selected_tf = std::dynamic_pointer_cast(presets[selected_tf_key]); { auto tf_lock = data::mt::locked_ptr(new_selected_tf); const auto current_tf = m_current_tf.lock(); new_selected_tf->deep_copy(current_tf.get_shared()); new_selected_tf->async_emit(data::object::MODIFIED_SIG); } } //------------------------------------------------------------------------------ void transfer_function::stopping() { this->destroy(); } //------------------------------------------------------------------------------ service::connections_t transfer_function::auto_connections() const { return { {IMAGE_INPUT, data::image::MODIFIED_SIG, UPDATE_DEFAULT_PRESET_SLOT}, {IMAGE_INPUT, data::image::MODIFIED_SIG, service::slots::UPDATE}, {IMAGE_INPUT, data::image::BUFFER_MODIFIED_SIG, service::slots::UPDATE}, {CURRENT_INPUT, data::transfer_function::MODIFIED_SIG, service::slots::UPDATE}, {CURRENT_INPUT, data::transfer_function::POINTS_MODIFIED_SIG, service::slots::UPDATE}, {CURRENT_INPUT, data::transfer_function::WINDOWING_MODIFIED_SIG, service::slots::UPDATE}, {PRESETS_INOUT, data::map::MODIFIED_SIG, UPDATE_PRESETS_SLOT}, {PRESETS_INOUT, data::map::ADDED_OBJECTS_SIG, UPDATE_PRESETS_SLOT}, {PRESETS_INOUT, data::map::CHANGED_OBJECTS_SIG, UPDATE_PRESETS_SLOT}, {PRESETS_INOUT, data::map::REMOVED_OBJECTS_SIG, UPDATE_PRESETS_SLOT} }; } //------------------------------------------------------------------------------ bool transfer_function::has_preset_name(const sight::data::map& _presets, const std::string& _name) { return _presets.find(_name) != _presets.end(); } //------------------------------------------------------------------------------ std::string transfer_function::create_preset_name( const sight::data::map& _presets, const std::string& _basename ) { bool has_transfer_function_name = true; std::string new_name = _basename; int cpt = 1; while(has_transfer_function_name) { std::stringstream tmp_str; tmp_str << _basename << "_" << cpt; new_name = tmp_str.str(); has_transfer_function_name = sight::module::ui::qt::image::transfer_function::has_preset_name( _presets, new_name ); cpt++; } return new_name; } //------------------------------------------------------------------------------ void transfer_function::initialize_presets(const std::string& _current_preset_name) { m_tf_presets = std::make_shared(); std::string current_preset_name = _current_preset_name; { const auto opt_presets = m_opt_presets.lock(); sight::data::map& presets = (opt_presets != nullptr) ? *opt_presets : *m_tf_presets; if(opt_presets != nullptr) { // If we specify the presets, use the internal map to save initial state m_tf_presets->deep_copy(opt_presets.get_shared()); if(presets.find(current_preset_name) == presets.end()) { current_preset_name = presets.begin()->first; } } else { // Add the default TF if it not exists. const std::string& default_tf_name = data::transfer_function::DEFAULT_TF_NAME; if(!sight::module::ui::qt::image::transfer_function::has_preset_name(presets, default_tf_name)) { const auto image = m_image.lock(); if(image) { const data::transfer_function::sptr default_tf = data::transfer_function::create_default_tf(image->type()); const auto scoped_emitter = presets.scoped_emit(); scoped_emitter->block(slot(UPDATE_PRESETS_SLOT)); presets[default_tf_name] = default_tf; } } // Test if transfer function map has few TF if(presets.size() <= 1) { // Creates the TF reader. const auto tf = std::make_shared(); const auto tf_reader = sight::service::add( "sight::module::io::session::reader" ); tf_reader->set_inout(tf, io::service::DATA_KEY); // Parse all paths contained in m_path and read basic TF. for(const std::filesystem::path& dir_path : m_paths) { SIGHT_ASSERT( "Invalid directory path '" + dir_path.string() + "'", std::filesystem::exists(dir_path) ); for(std::filesystem::directory_iterator it(dir_path) ; it != std::filesystem::directory_iterator() ; ++it) { if(!std::filesystem::is_directory(*it) && it->path().extension().string() == ".tf") { const std::filesystem::path file = *it; // Add a new map for each TF path. service::config_t config; config.put("file", file.string()); config.put("archive..format", "filesystem"); tf_reader->set_config(config); tf_reader->configure(); tf_reader->start(); tf_reader->update(); tf_reader->stop(); if(!tf->name().empty()) { const data::transfer_function::sptr new_tf = data::object::copy(tf); if(sight::module::ui::qt::image::transfer_function::has_preset_name( presets, new_tf->name() )) { new_tf->set_name( sight::module::ui::qt::image::transfer_function::create_preset_name( presets, new_tf->name() ) ); } presets[new_tf->name()] = new_tf; } } } } // Delete the reader. sight::service::remove(tf_reader); } } // Update all presets in the editor. m_preset_combo_box->clear(); { // Gets TF presets. // Iterate over each map to add them to the presets selector. for(const auto& elt : presets) { m_preset_combo_box->addItem(elt.first.c_str()); } // If the current TF preset exists, find it. const auto current_tf_preset = m_current_tf.lock(); if(current_tf_preset) { for(const auto& elt : presets) { if(std::dynamic_pointer_cast(elt.second)->name() == current_tf_preset->name()) { current_preset_name = elt.first; break; } } } } } int index = m_preset_combo_box->findText(QString::fromStdString(current_preset_name)); if(index == 1) { // Fallback if the previously selected TF no longer exists index = m_preset_combo_box->findText(QString::fromStdString(data::transfer_function::DEFAULT_TF_NAME)); } // Set the current map this->preset_choice(index); } //------------------------------------------------------------------------------ void transfer_function::preset_choice(int _index) { m_preset_combo_box->setCurrentIndex(_index); // Set the output to the current preset. this->set_current_preset(); const std::string tf_name = m_preset_combo_box->currentText().toStdString(); if(*m_editable) { const bool is_enabled = (tf_name != data::transfer_function::DEFAULT_TF_NAME); m_rename_button->setEnabled(is_enabled); m_delete_button->setEnabled(is_enabled); } } //------------------------------------------------------------------------------ void transfer_function::set_current_preset() { const std::string new_selected_tf_key = m_preset_combo_box->currentText().toStdString(); const auto opt_presets = m_opt_presets.lock(); sight::data::map& presets = (opt_presets != nullptr) ? *opt_presets : *m_tf_presets; const auto new_selected_tf = std::dynamic_pointer_cast(presets[new_selected_tf_key]); const auto current_tf = m_current_tf.lock(); if(new_selected_tf && new_selected_tf->name() != current_tf->name()) { current_tf->deep_copy(new_selected_tf); current_tf->async_emit(this, data::object::MODIFIED_SIG); } } //------------------------------------------------------------------------------ void transfer_function::update_default_preset() { const auto opt_presets = m_opt_presets.lock(); const auto image = m_image.lock(); if(image != nullptr && opt_presets == nullptr) { const data::transfer_function::sptr default_tf = data::transfer_function::create_default_tf(image->type()); const std::string default_tf_name = data::transfer_function::DEFAULT_TF_NAME; (*m_tf_presets)[default_tf_name] = default_tf; const auto current_tf = m_current_tf.lock(); current_tf->deep_copy(default_tf); current_tf->async_emit(data::object::MODIFIED_SIG); } } //------------------------------------------------------------------------------ void transfer_function::delete_preset() { if(m_preset_combo_box->count() < 2) { sight::ui::dialog::message message_box; message_box.set_title("Delete error"); message_box.set_message("Transfer function cannot be deleted, at least one transfer function is required."); message_box.set_icon(sight::ui::dialog::message::warning); message_box.add_button(sight::ui::dialog::message::ok); message_box.show(); return; } sight::ui::dialog::message message_box; message_box.set_title("Delete confirmation"); message_box.set_message("Are you sure you want to delete this TF preset ?"); message_box.set_icon(sight::ui::dialog::message::question); message_box.add_button(sight::ui::dialog::message::ok); message_box.add_button(sight::ui::dialog::message::cancel); sight::ui::dialog::message::buttons answer_copy = message_box.show(); if(answer_copy != sight::ui::dialog::message::cancel) { const int index = std::max(m_preset_combo_box->currentIndex() - 1, 0); { // Remove the current TF preset from the Map. const std::string selected_tf_preset_key = m_preset_combo_box->currentText().toStdString(); const auto opt_presets = m_opt_presets.lock(); sight::data::map& presets = (opt_presets != nullptr) ? *opt_presets : *m_tf_presets; const auto scoped_emitter = presets.scoped_emit(); scoped_emitter->block(slot(UPDATE_PRESETS_SLOT)); presets.erase(selected_tf_preset_key); m_preset_combo_box->removeItem( m_preset_combo_box->findText( QString::fromStdString( selected_tf_preset_key ) ) ); } // Set the current map this->preset_choice(index); } } //------------------------------------------------------------------------------ void transfer_function::create_preset() { const auto& old_name = m_preset_combo_box->currentText().toStdString(); sight::ui::dialog::input input; input.set_title("Create new transfer function"); input.set_message("Enter transfer function name :"); input.set_input(old_name); const auto& [newName, ok] = input.get_input(); if(!ok) { return; } if(!newName.empty()) { { const auto opt_presets = m_opt_presets.lock(); sight::data::map& presets = (opt_presets != nullptr) ? *opt_presets : *m_tf_presets; // Gets TF presets. if(!sight::module::ui::qt::image::transfer_function::has_preset_name(presets, newName)) { // Create the new map. const auto image = m_image.lock(); auto default_tf = image ? data::transfer_function::create_default_tf(image->type()) : data::transfer_function::create_default_tf(); default_tf->set_name(newName); const auto scoped_emitter = presets.scoped_emit(); scoped_emitter->block(slot(UPDATE_PRESETS_SLOT)); presets[newName] = default_tf; // Recreates presets. m_preset_combo_box->clear(); for(const auto& elt : presets) { m_preset_combo_box->addItem(elt.first.c_str()); } } else { sight::ui::dialog::message message_box; message_box.set_title("Warning"); message_box.set_message("This TF preset name already exists so you can not overwrite it."); message_box.set_icon(sight::ui::dialog::message::warning); message_box.add_button(sight::ui::dialog::message::ok); message_box.show(); return; } } // Set the current map. this->preset_choice(m_preset_combo_box->findText(QString::fromStdString(newName))); } } //------------------------------------------------------------------------------ void transfer_function::copy_preset() { const auto& old_name = m_preset_combo_box->currentText().toStdString(); sight::ui::dialog::input input; input.set_title("Copy transfer function"); input.set_message("Enter new transfer function name:"); input.set_input(old_name); const auto& [newName, ok] = input.get_input(); if(!ok) { return; } if(!newName.empty()) { { const auto opt_presets = m_opt_presets.lock(); sight::data::map& presets = (opt_presets != nullptr) ? *opt_presets : *m_tf_presets; // Gets TF presets. if(sight::module::ui::qt::image::transfer_function::has_preset_name(presets, newName)) { sight::ui::dialog::message message_box; message_box.set_title("Error"); message_box.set_message("This preset name already exists, please choose another one."); message_box.set_icon(sight::ui::dialog::message::warning); message_box.add_button(sight::ui::dialog::message::ok); message_box.show(); return; } const auto current_tf = std::dynamic_pointer_cast(presets[old_name]); SIGHT_ASSERT("Can not find current TF.", current_tf); data::transfer_function::sptr tf = data::object::copy(current_tf); tf->set_name(newName); const auto scoped_emitter = presets.scoped_emit(); scoped_emitter->block(slot(UPDATE_PRESETS_SLOT)); presets[newName] = tf; // Recreates presets. m_preset_combo_box->clear(); for(const auto& elt : presets) { m_preset_combo_box->addItem(elt.first.c_str()); } } // Set the current map. this->preset_choice(m_preset_combo_box->findText(QString::fromStdString(newName))); } else { sight::ui::dialog::message message_box; message_box.set_title("Error"); message_box.set_message("No preset name specified."); message_box.set_icon(sight::ui::dialog::message::warning); message_box.add_button(sight::ui::dialog::message::ok); message_box.show(); } } //------------------------------------------------------------------------------ void transfer_function::reinitialize_presets() { // Clears previous settings. m_preset_combo_box->clear(); { const auto opt_presets = m_opt_presets.lock(); if(opt_presets != nullptr) { // If we specify the presets, restore the initial state from the internal map const auto scoped_emitter = opt_presets->scoped_emit(); scoped_emitter->block(slot(UPDATE_PRESETS_SLOT)); opt_presets->deep_copy(m_tf_presets); } } m_tf_presets->clear(); // Initialize presets. this->initialize_presets(); } //------------------------------------------------------------------------------ void transfer_function::rename_preset() { const auto& old_name = m_preset_combo_box->currentText().toStdString(); sight::ui::dialog::input input; input.set_title("Rename transfer function preset"); input.set_message("Enter new name:"); input.set_input(old_name); const auto& [newName, ok] = input.get_input(); if(!ok) { return; } if(!newName.empty() && newName != old_name) { { const auto opt_presets = m_opt_presets.lock(); sight::data::map& presets = (opt_presets != nullptr) ? *opt_presets : *m_tf_presets; // Gets TF presets. if(!sight::module::ui::qt::image::transfer_function::has_preset_name(presets, newName)) { auto tf = std::dynamic_pointer_cast(presets[old_name]); // Rename the map. presets.erase(old_name); presets[newName] = tf; // Creates presets. m_preset_combo_box->clear(); for(const auto& elt : presets) { m_preset_combo_box->addItem(elt.first.c_str()); } tf->set_name(newName); } else { sight::ui::dialog::message message_box; message_box.set_title("Warning"); message_box.set_message("This TF preset name already exists so you can not overwrite it."); message_box.set_icon(sight::ui::dialog::message::warning); message_box.add_button(sight::ui::dialog::message::ok); message_box.show(); return; } } // Set the current map. this->preset_choice(m_preset_combo_box->findText(QString::fromStdString(newName))); } } //------------------------------------------------------------------------------ void transfer_function::import_preset() { const auto new_tf = std::make_shared(); const auto reader = sight::service::add("sight::module::io::session::reader"); reader->set_inout(new_tf, io::service::DATA_KEY); service::config_t config; config.put("dialog..extension", ".tf"); config.put("dialog..description", "Transfer Function Preset"); config.put("archive..format", "filesystem"); reader->configure(config); reader->start(); reader->open_location_dialog(); reader->update().wait(); // Check the loaded map. if(!new_tf->empty()) { int index = 0; { std::string preset_name(new_tf->name()); const auto opt_presets = m_opt_presets.lock(); sight::data::map& presets = (opt_presets != nullptr) ? *opt_presets : *m_tf_presets; if(sight::module::ui::qt::image::transfer_function::has_preset_name(presets, preset_name)) { preset_name = sight::module::ui::qt::image::transfer_function::create_preset_name(presets, preset_name); } const auto scoped_emitter = presets.scoped_emit(); scoped_emitter->block(slot(UPDATE_PRESETS_SLOT)); presets[preset_name] = new_tf; new_tf->set_name(preset_name); m_preset_combo_box->addItem(QString(preset_name.c_str())); index = static_cast(presets.size() - 1); } this->preset_choice(index); } reader->stop().wait(); sight::service::remove(reader); } //------------------------------------------------------------------------------ void transfer_function::export_preset() { const auto writer = sight::service::add("sight::module::io::session::writer"); { const auto current_tf = m_current_tf.const_lock(); writer->set_input(current_tf.get_shared(), io::service::DATA_KEY); } service::config_t config; config.add("dialog..extension", ".tf"); config.add("dialog..description", "Transfer Function Preset"); config.put("archive..format", "filesystem"); writer->configure(config); writer->start(); writer->open_location_dialog(); writer->update().wait(); writer->stop().wait(); sight::service::remove(writer); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/image/transfer_function.hpp000066400000000000000000000206111503402212300225570ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2024 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::image { /** * @brief Editor to select a transfer function from a list of presets. * * The default presets are dedicated to CT and MRI images. Other locations can be specified to add different presets. * The editor also generates a default transfer function called "GreyLevel". If a reference image is specified, * this function will be generated accordingly to the image type. * * @section XML XML Configuration * @code{.xml} .... .... .... @endcode * * * @subsection Input Input * - \b image [sight::data::image](optional): reference image that can be used to generate the default transfer * function. * * @subsection In-Out In-Out * - \b current [sight::data::transfer_function]: current transfer function used to change editor * selection. It should be the same as the output. * - \b presets [sight::data::map](optional): map of sight::data::transfer_function that should be used as * presets, instead of loading it from the specified path(s). * * @subsection Configuration Configuration * - \b useDefaultPath (optional, default="true"): if true, load tf files from uiTF module. * - \b path (optional): path to a directory containing tf files. * - \b deleteIcon (optional): path of the delete button icon. * - \b newIcon (optional): path of the new button icon. * - \b copyIcon (optional): path of the copy button icon. * - \b reinitializeIcon (optional): path of the reinitialize button icon. * - \b renameIcon (optional): path of the rename button icon. * - \b importIcon (optional): path of the import button icon. * - \b exportIcon (optional): path of the export button icon. * - \b iconWidth (optional, default="16"): icon width. * - \b iconHeight (optional, default="16"): icon height. * * @subsection Properties Properties * - \b editable (optional, default="true"): if false, only display the combo box selector. */ class transfer_function final : public QObject, public sight::ui::editor { Q_OBJECT public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(module::ui::qt::image::transfer_function, sight::ui::editor); /// Creates the editor. transfer_function(); /// Destroyes the editor. ~transfer_function() noexcept override = default; private: /// Configures the editor. void configuring() override; /// Creates container and the UI. void starting() override; /// Does nothing. void updating() override; /// Destroys the UI. void stopping() override; /** * @brief * Connect image::MODIFIED_SIG to this::service::slots::UPDATE * Connect image::BUFFER_MODIFIED_SIG to this::UPDATE_BUFFER_SLOT */ connections_t auto_connections() const override; /** * @brief Checks if the map contains the specified key. * @param _presets list of transfer functions. * @param _name the name used to search. * @return True if the preset named _name is found. */ static bool has_preset_name(const sight::data::map& _presets, const std::string& _name); /** * @brief Create a string that represents a TF preset name not already present in a preset list. * * For example, if CT-GreyLevel is already used, it will return CT-GreyLevel_1. * * @param _presets list of transfer functions. * @param _basename the name of the TF preset to create. * @return The new name of the TF preset. */ static std::string create_preset_name(const sight::data::map& _presets, const std::string& _basename); /** * @brief Initializes the map. * * Add their names to m_presetComboBox. If the map does not contain any TF (or only the default grey level TF, * the service creates a few from the resources of the module. */ void initialize_presets( const std::string& _current_preset_name = sight::data::transfer_function::DEFAULT_TF_NAME ); /// Sets the current TF preset to the output of this service. void set_current_preset(); /// Update the default transfer function according to the image void update_default_preset(); private Q_SLOTS: /// Changes the current selected TF preset. void preset_choice(int _index); /// Deletes the current selected TF preset. void delete_preset(); /// Creates a new TF preset with a GreyLevel TF. void create_preset(); /// Copies the current TF preset. void copy_preset(); /// Resets the map. void reinitialize_presets(); /// Renames the current selected TF preset. void rename_preset(); /// Imports a TF preset. void import_preset(); /// Exports the current selected TF preset. void export_preset(); private: /// Stores path were looking for TF presets. std::vector m_paths; /// Contains the list of all TF preset. QComboBox* m_preset_combo_box {nullptr}; /// Contains the delete TF preset button. QPushButton* m_delete_button {nullptr}; /// Contains the new TF preset button. QPushButton* m_new_button {nullptr}; /// Contains the copy TF preset button. QPushButton* m_copy_button {nullptr}; /// Contains the reset TF preset button. QPushButton* m_reinitialize_button {nullptr}; /// Contains the rename TF preset button. QPushButton* m_rename_button {nullptr}; /// Contains the import TF button. QPushButton* m_import_button {nullptr}; /// Contains the export TF button. QPushButton* m_export_button {nullptr}; /// Defines the path of the delete button icon. std::filesystem::path m_delete_icon; /// Defines the path of the new button icon. std::filesystem::path m_new_icon; /// Defines the path of the copy button icon. std::filesystem::path m_copy_icon; /// Defines the path of the reinitialize button icon. std::filesystem::path m_reinitialize_icon; /// Defines the path of the rename button icon. std::filesystem::path m_rename_icon; /// Defines the path of the import button icon. std::filesystem::path m_import_icon; /// Defines the path of the export button icon. std::filesystem::path m_export_icon; /// Defines icons width. unsigned int m_icon_width {16}; /// Defines icons height. unsigned int m_icon_height {16}; /// Working copy of the TF presets, can be internal or use the optional "presets" input data::map::sptr m_tf_presets; static constexpr std::string_view CURRENT_INPUT = "tf"; static constexpr std::string_view IMAGE_INPUT = "image"; static constexpr std::string_view PRESETS_INOUT = "presets"; data::ptr m_current_tf {this, CURRENT_INPUT}; data::ptr m_opt_presets {this, PRESETS_INOUT, true}; data::ptr m_image {this, IMAGE_INPUT, true}; data::property m_editable {this, "editable", true}; }; } // namespace sight::module::ui::qt::image. sight-25.1.0/module/ui/qt/image/transfer_function_opacity.cpp000066400000000000000000000114161503402212300243050ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/image/transfer_function_opacity.hpp" #include #include #include namespace sight::module::ui::qt::image { //------------------------------------------------------------------------------ void transfer_function_opacity::configuring() { this->initialize(); const config_t config = this->get_config(); if(auto piece = config.get_optional("config..piece"); piece.has_value()) { m_piece = piece.value(); } } //------------------------------------------------------------------------------ void transfer_function_opacity::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); auto* layout = new QHBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); m_slider = new QSlider(Qt::Horizontal); m_slider->setRange(1, 100); layout->addWidget(m_slider, 0); QObject::connect(m_slider, &QSlider::valueChanged, this, &transfer_function_opacity::change_opacity); qt_container->set_layout(layout); this->updating(); } //------------------------------------------------------------------------------ void transfer_function_opacity::updating() { const auto tf = m_tf.const_lock(); const auto& pieces = tf->pieces(); double min = 1.0; double max = 0.0; const auto get_min_max_fn = [&min, &max](auto& _p) { std::ranges::for_each( *_p, [&min, &max](auto& _v) { min = _v.second.a > 0. ? std::min(_v.second.a, min) : min; max = std::max(_v.second.a, max); }); }; if(!m_piece.has_value()) { std::ranges::for_each(pieces, get_min_max_fn); } else { get_min_max_fn(pieces[m_piece.value()]); } const double window = (1 - min) * 2 + max; m_previous_value = static_cast(max / window * 100); m_slider->blockSignals(true); m_slider->setValue(m_previous_value); m_slider->blockSignals(false); } //------------------------------------------------------------------------------ void transfer_function_opacity::stopping() { QObject::disconnect(m_slider, &QSlider::valueChanged, this, &transfer_function_opacity::change_opacity); this->destroy(); } //------------------------------------------------------------------------------ void transfer_function_opacity::change_opacity(int _value) { const auto tf = m_tf.lock(); const auto& pieces = tf->pieces(); const auto old_value = static_cast(m_previous_value); const auto update_opacity_fn = [factor = _value / old_value](auto& _p) { std::ranges::for_each( *_p, [factor](auto& _v) { _v.second.a *= factor; }); }; if(!m_piece.has_value()) { std::ranges::for_each(pieces, update_opacity_fn); } else { update_opacity_fn(pieces[m_piece.value()]); } m_previous_value = _value; auto sig = tf->signal(data::transfer_function::POINTS_MODIFIED_SIG); { const core::com::connection::blocker block(sig->get_connection(slot(service::slots::UPDATE))); sig->async_emit(); } } //------------------------------------------------------------------------------ service::connections_t transfer_function_opacity::auto_connections() const { return { {TF, data::transfer_function::MODIFIED_SIG, service::slots::UPDATE}, {TF, data::transfer_function::POINTS_MODIFIED_SIG, service::slots::UPDATE}, {TF, data::transfer_function::WINDOWING_MODIFIED_SIG, service::slots::UPDATE} }; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/image/transfer_function_opacity.hpp000066400000000000000000000061601503402212300243120ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::ui::qt::image { /** * @brief Provides a slider allowing to scale the opacity of a transfer function or one of its piece. * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection In-Out In-Out * - \b tf [sight::data::transfer_function]: the current transfer function. * * @subsection Configuration Configuration * - \b piece(optional, default="-1"): if specified not negative, apply the opacity on the given piece instead of the * whole function. */ class transfer_function_opacity final : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(transfer_function_opacity, sight::ui::editor); /// Initialize signals and slots. transfer_function_opacity() noexcept = default; /// Destroys the service. ~transfer_function_opacity() noexcept override = default; protected: /// Configures the service. void configuring() final; /// Installs the layout. void starting() final; /// Updates editor information from the image. void updating() final; /// Destroys the layout. void stopping() final; /** * @brief Proposals to connect service slots to associated object signals. * * Connect all transfers functions signals to the update() slot. */ [[nodiscard]] connections_t auto_connections() const final; protected Q_SLOTS: /// Called when the slider moves void change_opacity(int _value); private: /// If configured, contains the index of a transfer function piece, otherwise -1 std::optional m_piece = {}; /// Slider widget QPointer m_slider; /// Previous value, used to scale the opacity when it changes int m_previous_value {50}; static constexpr std::string_view TF = "tf"; data::ptr m_tf {this, TF}; }; } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/image/window_level.cpp000066400000000000000000000437741503402212300215360ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/image/window_level.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::image { //------------------------------------------------------------------------------ window_level::window_level() noexcept { new_slot(slots::UPDATE_IMAGE, &window_level::update_image, this); } //------------------------------------------------------------------------------ void window_level::configuring() { const config_t config = this->get_config(); m_minimal = config.get("config..minimal", m_minimal); m_auto_windowing = config.get("config..autoWindowing", m_auto_windowing); m_enable_square_tf = config.get("config..enableSquareTF", m_enable_square_tf); } //------------------------------------------------------------------------------ void window_level::starting() { this->initialize(); { const auto image = m_image.lock(); SIGHT_ASSERT("inout '" << IMAGE << "' does not exist.", image); this->create(); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); auto* const layout = new QGridLayout(); m_value_text_min = new QLineEdit(); auto* const min_validator = new QDoubleValidator(m_value_text_min); m_value_text_min->setValidator(min_validator); m_value_text_max = new QLineEdit(); auto* const max_validator = new QDoubleValidator(m_value_text_max); m_value_text_max->setValidator(max_validator); m_range_slider = new sight::ui::qt::widget::range_slider(); layout->addWidget(m_range_slider, 0, 0, 1, -1); QObject::connect( m_range_slider, SIGNAL(slider_range_edited(double,double)), this, SLOT(on_window_level_widget_changed(double,double)) ); if(not m_minimal) { m_toggle_tf_button = new QToolButton(); QIcon ico; std::string square_icon(core::runtime::get_module_resource_file_path( "sight::module::ui::qt", "square.svg" ).string()); std::string ramp_icon(core::runtime::get_module_resource_file_path( "sight::module::ui::qt", "ramp.svg" ).string()); ico.addPixmap(QPixmap(QString::fromStdString(square_icon)), QIcon::Normal, QIcon::On); ico.addPixmap(QPixmap(QString::fromStdString(ramp_icon)), QIcon::Normal, QIcon::Off); m_toggle_tf_button->setIcon(ico); m_toggle_tf_button->setCheckable(true); m_toggle_tf_button->setToolTip("Function style"); m_toggle_auto_button = new QToolButton(); QIcon icon; std::string win(core::runtime::get_module_resource_file_path( "sight::module::ui::qt", "windowing.svg" ).string()); icon.addFile(QString::fromStdString(win), QSize(), QIcon::Normal, QIcon::On); std::string nowindo(core::runtime::get_module_resource_file_path( "sight::module::ui::qt", "no_windowing.svg" ).string()); icon.addFile(QString::fromStdString(nowindo), QSize(), QIcon::Normal, QIcon::Off); m_toggle_auto_button->setIcon(icon); m_toggle_auto_button->setToolTip("Automatic Windowing"); m_toggle_auto_button->setCheckable(true); m_toggle_auto_button->setChecked(m_auto_windowing); m_dynamic_range_selection = new QToolButton(); m_dynamic_range_selection->setPopupMode(QToolButton::InstantPopup); m_dynamic_range_menu = new QMenu(m_dynamic_range_selection); QAction* const action1 = m_dynamic_range_menu->addAction("-1024; 1023"); QAction* const action2 = m_dynamic_range_menu->addAction("-100; 300"); QAction* const action3 = m_dynamic_range_menu->addAction("Fit W/L"); QAction* const action4 = m_dynamic_range_menu->addAction("Fit Data"); // TODO //QAction *action5 = m_dynamicRangeMenu->addAction( "Custom ..." ); // TODO m_dynamic_range_selection->setMenu(m_dynamic_range_menu); action1->setData(QVariant(1)); action2->setData(QVariant(2)); action3->setData(QVariant(3)); action4->setData(QVariant(4)); //action5->setData(QVariant(5)); layout->addWidget(m_value_text_min, 1, 0); layout->addWidget(m_toggle_tf_button, 1, 1); layout->addWidget(m_toggle_auto_button, 1, 2); layout->addWidget(m_dynamic_range_selection, 1, 3); layout->addWidget(m_value_text_max, 1, 4); // Set the visibility after the layout is created so it doesn't open its own window. m_toggle_tf_button->setVisible(m_enable_square_tf); QObject::connect( m_value_text_min, &::QLineEdit::editingFinished, this, &window_level::on_text_editing_finished ); QObject::connect( m_value_text_max, &::QLineEdit::editingFinished, this, &window_level::on_text_editing_finished ); QObject::connect(m_toggle_tf_button, &::QToolButton::toggled, this, &window_level::on_toggle_tf); QObject::connect(m_toggle_auto_button, &::QToolButton::toggled, this, &window_level::on_toggle_auto_wl); QObject::connect( m_dynamic_range_selection, &::QToolButton::triggered, this, &window_level::on_dynamic_range_selection_changed ); } qt_container->set_layout(layout); } this->updating(); } //------------------------------------------------------------------------------ void window_level::updating() { const auto image = m_image.lock(); SIGHT_ASSERT("inout '" << IMAGE << "' does not exist.", image); const bool image_is_valid = data::helper::medical_image::check_image_validity(image.get_shared()); this->set_enabled(image_is_valid); if(image_is_valid) { const auto tf = m_tf.const_lock(); const auto piece = static_cast(*m_piece); data::transfer_function::min_max_t min_max = *m_piece != -1 && piece < tf->pieces().size() ? tf->pieces()[piece]->window_min_max() : tf->window_min_max(); this->on_image_window_level_changed(min_max.first, min_max.second); } } //------------------------------------------------------------------------------ void window_level::stopping() { if(not m_minimal) { QObject::disconnect( m_dynamic_range_selection, &::QToolButton::triggered, this, &window_level::on_dynamic_range_selection_changed ); QObject::disconnect(m_toggle_auto_button, &::QToolButton::toggled, this, &window_level::on_toggle_auto_wl); QObject::disconnect(m_toggle_tf_button, &::QToolButton::toggled, this, &window_level::on_toggle_tf); QObject::disconnect( m_range_slider, SIGNAL(slider_range_edited(double,double)), this, SLOT(on_window_level_widget_changed(double,double)) ); QObject::disconnect( m_value_text_max, &::QLineEdit::editingFinished, this, &window_level::on_text_editing_finished ); QObject::disconnect( m_value_text_min, &::QLineEdit::editingFinished, this, &window_level::on_text_editing_finished ); } this->destroy(); } //------------------------------------------------------------------------------ void window_level::update_image() { { const auto image = m_image.lock(); SIGHT_ASSERT("inout '" << IMAGE << "' does not exist.", image); const bool image_is_valid = data::helper::medical_image::check_image_validity(image.get_shared()); this->set_enabled(image_is_valid); if(image_is_valid) { const auto& [min, max] = data::helper::medical_image::get_min_max(image.get_shared()); if(m_auto_windowing) { this->update_image_window_level(min, max); } const auto range = max - min; this->set_widget_dynamic_range(min - range, max + range); } } this->updating(); } //------------------------------------------------------------------------------ void window_level::info(std::ostream& _sstream) { _sstream << "Window level editor"; } //------------------------------------------------------------------------------ void window_level::update_widget_min_max(double _image_min, double _image_max) { const double range_min = this->from_window_level(_image_min); const double range_max = this->from_window_level(_image_max); m_range_slider->set_pos(range_min, range_max); } //------------------------------------------------------------------------------ double window_level::from_window_level(double _val) { double val_min = m_widget_dynamic_range_min; double val_max = val_min + m_widget_dynamic_range_width; val_min = std::min(_val, val_min); val_max = std::max(_val, val_max); this->set_widget_dynamic_range(val_min, val_max); const double res = (_val - m_widget_dynamic_range_min) / m_widget_dynamic_range_width; return res; } //------------------------------------------------------------------------------ double window_level::to_window_level(double _val) const { return m_widget_dynamic_range_min + m_widget_dynamic_range_width * _val; } //------------------------------------------------------------------------------ void window_level::update_image_window_level(double _image_min, double _image_max) { const auto tf = m_tf.lock(); const auto piece = static_cast(*m_piece); if(*m_piece != -1 && piece < tf->pieces().size()) { tf->pieces()[piece]->set_window_min_max({_image_min, _image_max}); } else { tf->set_window_min_max({_image_min, _image_max}); } tf->async_emit(this, data::transfer_function::WINDOWING_MODIFIED_SIG, tf->window(), tf->level()); } //------------------------------------------------------------------------------ void window_level::on_window_level_widget_changed(double _min, double _max) { const double image_min = this->to_window_level(_min); const double image_max = this->to_window_level(_max); this->update_image_window_level(image_min, image_max); this->update_text_window_level(image_min, image_max); } //------------------------------------------------------------------------------ void window_level::on_dynamic_range_selection_changed(QAction* _action) { const auto tf = m_tf.const_lock(); SIGHT_ASSERT("TransferFunction null pointer", tf); const auto piece = static_cast(*m_piece); const auto wl = *m_piece != -1 && piece < tf->pieces().size() ? tf->pieces()[piece]->window_min_max() : tf->window_min_max(); double min = m_widget_dynamic_range_min; double max = m_widget_dynamic_range_width + min; const auto image = m_image.lock(); SIGHT_ASSERT("inout '" << IMAGE << "' does not exist.", image); const int index = _action->data().toInt(); switch(index) { case 0: break; case 1: // -1024; 1023 min = -1024; max = 1023; break; case 2: // -100; 300 min = -100; max = 300; break; case 3: // Fit Window/Level min = std::min(wl.first, wl.second); max = std::max(wl.first, wl.second); break; case 4: // Fit image Range std::tie(min, max) = data::helper::medical_image::get_min_max(image.get_shared()); break; case 5: // Custom : TODO break; default: SIGHT_ASSERT("Unknown range selector index", 0); } this->set_widget_dynamic_range(min, max); this->update_widget_min_max(wl.first, wl.second); } //------------------------------------------------------------------------------ void window_level::on_image_window_level_changed(double _image_min, double _image_max) { this->update_widget_min_max(_image_min, _image_max); this->update_text_window_level(_image_min, _image_max); } //------------------------------------------------------------------------------ void window_level::update_text_window_level(double _image_min, double _image_max) { m_value_text_min->setText(QString("%1").arg(_image_min)); m_value_text_max->setText(QString("%1").arg(_image_max)); } //------------------------------------------------------------------------------ void window_level::on_toggle_tf(bool _square_tf) { const auto current_tf = m_tf.lock(); data::transfer_function::sptr new_tf; if(_square_tf) { new_tf = std::make_shared(); data::transfer_function::color_t color(1., 1., 1., 1.); new_tf->set_name("SquareTF"); auto tf_data = new_tf->pieces().emplace_back(std::make_shared()); tf_data->insert({0.0, color}); tf_data->insert({1.0, color}); tf_data->set_clamped(true); } else { if(m_previous_tf) { new_tf = m_previous_tf; } else { new_tf = data::transfer_function::create_default_tf(); } } new_tf->set_window(current_tf->window()); new_tf->set_level(current_tf->level()); m_previous_tf = data::object::copy(current_tf.get_shared()); current_tf->deep_copy(new_tf); // Send signal current_tf->async_emit(this, data::transfer_function::POINTS_MODIFIED_SIG); } //------------------------------------------------------------------------------ void window_level::on_toggle_auto_wl(bool _auto_wl) { m_auto_windowing = _auto_wl; if(m_auto_windowing) { const auto image = m_image.lock(); SIGHT_ASSERT("inout '" << IMAGE << "' does not exist.", image); const auto& [min, max] = data::helper::medical_image::get_min_max(image.get_shared()); this->update_image_window_level(min, max); this->on_image_window_level_changed(min, max); } } //------------------------------------------------------------------------------ void window_level::on_text_editing_finished() { double min = NAN; double max = NAN; if(get_widget_double_value(m_value_text_min, min) && get_widget_double_value(m_value_text_max, max)) { this->update_widget_min_max(min, max); this->update_image_window_level(min, max); } } //------------------------------------------------------------------------------ bool window_level::get_widget_double_value(QLineEdit* _widget, double& _val) { bool ok = false; _val = _widget->text().toDouble(&ok); QPalette palette; if(!ok) { palette.setBrush(QPalette::Base, QBrush(Qt::red)); } else { palette.setBrush(QPalette::Base, QApplication::palette().brush(QPalette::Base)); } _widget->setPalette(palette); return ok; } //------------------------------------------------------------------------------ void window_level::set_widget_dynamic_range(double _min, double _max) { SIGHT_ASSERT("Maximum is not greater than minimum", _max >= _min); m_widget_dynamic_range_min = _min; m_widget_dynamic_range_width = std::max(1., _max - _min); if(not m_minimal) { m_dynamic_range_selection->setText(QString("%1, %2 ").arg(_min).arg(_max)); } } //------------------------------------------------------------------------------ service::connections_t window_level::auto_connections() const { return { {IMAGE, data::image::MODIFIED_SIG, slots::UPDATE_IMAGE}, {IMAGE, data::image::BUFFER_MODIFIED_SIG, slots::UPDATE_IMAGE}, {TF, data::transfer_function::MODIFIED_SIG, service::slots::UPDATE}, {TF, data::transfer_function::POINTS_MODIFIED_SIG, service::slots::UPDATE}, {TF, data::transfer_function::WINDOWING_MODIFIED_SIG, service::slots::UPDATE} }; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/image/window_level.hpp000066400000000000000000000136571503402212300215400ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include class QAction; class QComboBox; class QLabel; class QLineEdit; class QMenu; class QSlider; class QToolButton; namespace sight::ui::qt::widget { class range_slider; } // namespace sight::ui::qt::widget namespace sight::module::ui::qt::image { /** * @brief window_level service allows to change the min/max value of windowing. * * This is represented by two sliders to modify the min and max values of windowing. * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection Input Input * - \b image [sight::data::image]: image on which the windowing will be changed. * * @subsection In-Out In-Out * - \b tf [sight::data::transfer_function] (optional): the current TransferFunction. If it is not defined, we use the * image's default transferFunction (CT-GreyLevel). * * @subsection Configuration Configuration * - \b minimal(optional, default="false"): if 'true', only the windowing range slider is shown * - \b autoWindowing(optional, default="false"): if 'true', image windowing will be automatically compute from image * pixel * min/max intensity when this service receive BUFFER event. * - \b enableSquareTF(optional, default="true"): if 'true', enables the button to switch between current TF and square * TF. * * @subsection Properties Properties * - \b piece(optional, default="-1"): if >=0, restrict all settings made to the configured piece in the TF */ class window_level final : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(window_level, sight::ui::editor); struct slots { static inline const core::com::slots::key_t UPDATE_IMAGE = "update_image"; }; /// Initialize signals and slots. window_level() noexcept; /// Destroys the service. ~window_level() noexcept final = default; protected: /// Configures the service. void configuring() final; /// Installs the layout. void starting() final; /// Updates slider position void updating() final; /// Destroys the layout. void stopping() final; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::image::MODIFIED_SIG to module::ui::qt::image::window_level::service::slots::UPDATE * Connect image::BUFFER_MODIFIED_SIG to module::ui::qt::image::window_level::service::slots::UPDATE */ connections_t auto_connections() const final; /** * @brief Adds informations about this service into the stream. * @param _sstream Stream where stores information. */ void info(std::ostream& _sstream) final; /// Slot: Updates the slider range void update_image(); protected Q_SLOTS: void on_text_editing_finished(); void on_toggle_tf(bool _square_tf); void on_toggle_auto_wl(bool _auto_wl); void on_window_level_widget_changed(double _min, double _max); void on_dynamic_range_selection_changed(QAction* _action); protected: double to_window_level(double _val) const; double from_window_level(double _val); void on_image_window_level_changed(double _image_min, double _image_max); void update_widget_min_max(double _image_min, double _image_max); void update_image_window_level(double _image_min, double _image_max); void update_text_window_level(double _image_min, double _image_max); static bool get_widget_double_value(QLineEdit* _widget, double& _val); void set_widget_dynamic_range(double _min, double _max); private: QPointer m_value_text_min; QPointer m_value_text_max; QPointer m_toggle_tf_button; QPointer m_toggle_auto_button; QPointer m_dynamic_range_selection; QPointer m_dynamic_range_menu; QPointer m_range_slider; double m_widget_dynamic_range_min {-1024}; double m_widget_dynamic_range_width {4000}; bool m_minimal {false}; bool m_auto_windowing {false}; bool m_enable_square_tf {true}; /// Store previous TF, used in onToggleTF() to restore this TF when switching to the square TF data::transfer_function::sptr m_previous_tf; static constexpr std::string_view IMAGE = "image"; static constexpr std::string_view TF = "tf"; data::ptr m_image {this, IMAGE}; data::ptr m_tf {this, TF}; sight::data::property m_piece {this, "piece", -1}; }; } // namespace sight::module::ui::qt::image sight-25.1.0/module/ui/qt/launch_browser.cpp000066400000000000000000000061241503402212300207570ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "launch_browser.hpp" #include #include #include #include #include #include namespace sight::module::ui::qt { //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ launch_browser::launch_browser() noexcept = default; //------------------------------------------------------------------------------ launch_browser::~launch_browser() noexcept = default; //------------------------------------------------------------------------------ void launch_browser::info(std::ostream& _sstream) { _sstream << "Action for manage url" << std::endl; } //----------------------------------------------------------------------------- void launch_browser::configuring() { this->sight::ui::action::initialize(); const auto& config = this->get_config(); m_url = config.get("url", m_url); } //------------------------------------------------------------------------------ void launch_browser::updating() { SIGHT_WARN_IF("URL is empty.", m_url.empty()); QUrl url(QString::fromStdString(m_url), QUrl::TolerantMode); if(url.isRelative()) // no scheme { std::filesystem::path path(QCoreApplication::applicationDirPath().toStdString()); path = path.parent_path(); // install folder path path /= url.path().toStdString(); QString url_str = QString::fromStdString("file:///" + path.string()); url = QUrl(url_str, QUrl::TolerantMode); } bool is_success = QDesktopServices::openUrl(url); SIGHT_WARN_IF("Browser wasn't successfully launched.", !is_success); } //------------------------------------------------------------------------------ void launch_browser::starting() { this->sight::ui::action::action_service_starting(); } //------------------------------------------------------------------------------ void launch_browser::stopping() { this->sight::ui::action::action_service_stopping(); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/launch_browser.hpp000066400000000000000000000037151503402212300207670ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include namespace sight::module::ui::qt { /** * @brief This action launch a browser on the url given in configuration. */ class launch_browser : public sight::ui::action { public: SIGHT_DECLARE_SERVICE(launch_browser, sight::ui::action); /// Constructor. Do nothing. launch_browser() noexcept; /// Destructor. Do nothing. ~launch_browser() noexcept override; protected: /** * @brief configure the action. * @code{.xml} http://www.ircad.fr @endcode */ void configuring() override; /// Starts action void starting() override; /// Launch a browser on the url given in the configuration void updating() override; /// Stops action void stopping() override; void info(std::ostream& _sstream) override; private: std::string m_url; }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/list_view.cpp000066400000000000000000000117061503402212300177510ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2023 IRCAD France * Copyright (C) 2017-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "list_view.hpp" #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { const core::com::signals::key_t list_view::ITEM_ADDED_SIG = "itemAdded"; const core::com::signals::key_t list_view::ITEM_REMOVED_SIG = "itemRemoved"; const core::com::signals::key_t list_view::ITEM_DOUBLE_CLICKED_SIG = "itemDoubleClicked"; const core::com::slots::key_t list_view::INSERT_ITEM_SLOT = "insertItem"; const core::com::slots::key_t list_view::REMOVE_ITEM_SLOT = "removeItem"; //------------------------------------------------------------------------------ list_view::list_view() noexcept { new_signal(ITEM_ADDED_SIG); new_signal(ITEM_REMOVED_SIG); new_signal(ITEM_DOUBLE_CLICKED_SIG); new_slot(INSERT_ITEM_SLOT, &list_view::insert_item, this); new_slot(REMOVE_ITEM_SLOT, &list_view::remove_item, this); } //------------------------------------------------------------------------------ list_view::~list_view() noexcept = default; //------------------------------------------------------------------------------ bool list_view::eventFilter(QObject* _watched, QEvent* _event) { // filter "del" key release event in order to delete the current selected item of the list. if(_watched == m_list_widget && _event->type() == QEvent::KeyRelease) { auto* event_key = static_cast(_event); if(event_key->key() == Qt::Key_Delete) { const QList& list_item = m_list_widget->selectedItems(); if(!list_item.empty()) { this->remove_item(m_list_widget->row(list_item.first())); return true; } } } return false; } //------------------------------------------------------------------------------ void list_view::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void list_view::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); QPointer layout = new QHBoxLayout(); layout->setObjectName("list_viewLayout"); m_list_widget = new(QListWidget); m_list_widget->setObjectName("list_viewWidget"); m_list_widget->installEventFilter(this); layout->addWidget(m_list_widget); connect(m_list_widget, &QListWidget::itemDoubleClicked, this, &self_t::on_item_double_clicked); qt_container->set_layout(layout); } //------------------------------------------------------------------------------ void list_view::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void list_view::updating() { } //------------------------------------------------------------------------------ void list_view::insert_item(int _index, std::string _value) { // insert item at the index position of the list auto* new_item = new QListWidgetItem(QString::fromStdString(_value)); m_list_widget->insertItem(_index, new_item); // notify this->signal(ITEM_ADDED_SIG)->async_emit(_index); } //------------------------------------------------------------------------------ void list_view::remove_item(int _index) { // remove item at index position delete m_list_widget->takeItem(_index); // notify this->signal(ITEM_REMOVED_SIG)->async_emit(_index); } //------------------------------------------------------------------------------ void list_view::on_item_double_clicked(QListWidgetItem* _item) { const int index = m_list_widget->row(_item); this->signal(ITEM_DOUBLE_CLICKED_SIG)->async_emit(index); } //------------------------------------------------------------------------------ } //namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/list_view.hpp000066400000000000000000000067711503402212300177640ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2024 IRCAD France * Copyright (C) 2017-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::ui::qt { /** * @brief This editor allows to draw a list. * The currently selected item can be deleted by pressing on "del" key * @section XML XML Configuration * @code{.xml} @endcode * * @section Signals Signals * - \b itemAdded(int): This editor emits the signal "itemAdded" with the index value of added item. * - \b itemRemoved(int): This editor emits the signal "itemRemoved" with the index value of removed item. * - \b itemDoubleClicked(int): This editor emits the signal "itemDoubleClicked" with the index value of selected item. * * @section Slots Slots * - \b insertItem(int, std::string): This slot allows to add an item with its index and text. * - \b removeItem(int): This slot allows to remove the element at the index. */ class list_view : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(list_view, sight::ui::editor); /// Constructor. Do nothing. list_view() noexcept; /// Destructor. Do nothing. ~list_view() noexcept override; /**@name Signals API * @{ */ static const core::com::signals::key_t ITEM_ADDED_SIG; using item_added_signal_t = core::com::signal; static const core::com::signals::key_t ITEM_REMOVED_SIG; using item_removed_signal_t = core::com::signal; static const core::com::signals::key_t ITEM_DOUBLE_CLICKED_SIG; using item_double_clicked_signal_t = core::com::signal; /** @} */ /** * @name Slots API * @{ */ static const core::com::slots::key_t INSERT_ITEM_SLOT; static const core::com::slots::key_t REMOVE_ITEM_SLOT; /// SLOT : Called to insert an item at index. void insert_item(int _index, std::string _value); /// SLOT : Called to remove the item at the index position. void remove_item(int _index); ///@} protected: /// used to catch the del key released event bool eventFilter(QObject* _watched, QEvent* _event) override; /// Installs the layout void starting() override; /// Destroys the layout void stopping() override; /// Does nothing void updating() override; /// Configure the service void configuring() override; private: QPointer m_list_widget; /// listWidget private Q_SLOTS: /// called when an item is double clicked void on_item_double_clicked(QListWidgetItem* _item); }; } //namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/material_opacity_editor.cpp000066400000000000000000000070641503402212300226420ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2021-2023 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/material_opacity_editor.hpp" #include #include #include #include #include namespace sight::module::ui::qt { //------------------------------------------------------------------------------ void material_opacity_editor::configuring() { const config_t configuration = this->get_config(); m_label = configuration.get("label", m_label); this->initialize(); } //------------------------------------------------------------------------------ void material_opacity_editor::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); auto* const opacity_label = new QLabel(tr(m_label.c_str())); m_opacity_slider = new QSlider(Qt::Horizontal); m_opacity_slider->setToolTip(tr("Adjust opacity level")); m_opacity_slider->setRange(0, 100); m_opacity_slider->setTickInterval(20); m_opacity_slider->setTickPosition(QSlider::TicksBelow); m_opacity_slider->setMinimumSize(m_opacity_slider->sizeHint()); m_opacity_value = new QLabel(""); m_opacity_value->setMinimumSize(m_opacity_value->sizeHint()); auto* const main_layout = new QVBoxLayout(); auto* const opacity_layout = new QHBoxLayout(); opacity_layout->addWidget(opacity_label, 0); opacity_layout->addWidget(m_opacity_slider, 1); opacity_layout->addWidget(m_opacity_value, 0); main_layout->addLayout(opacity_layout, 0); qt_container->set_layout(main_layout); this->updating(); QObject::connect(m_opacity_slider, &QSlider::valueChanged, this, &material_opacity_editor::on_opacity_slider); } //------------------------------------------------------------------------------ void material_opacity_editor::updating() { auto material = m_material.lock(); const int a = static_cast(material->diffuse()->alpha() * 100.F); m_opacity_slider->setValue(a); } //------------------------------------------------------------------------------ void material_opacity_editor::on_opacity_slider(int _value) { auto material = m_material.lock(); material->diffuse()->alpha() = static_cast(_value) / 100.F; std::stringstream ss; ss << _value << "%"; m_opacity_value->setText(QString::fromStdString(ss.str())); auto sig = material->signal(data::object::MODIFIED_SIG); sig->async_emit(); } //------------------------------------------------------------------------------ void material_opacity_editor::stopping() { this->destroy(); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/material_opacity_editor.hpp000066400000000000000000000052371503402212300226470ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2021-2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include class QSlider; class QLabel; namespace sight::module::ui::qt { /** * @brief Adds an opacity editor widget (Slider) applied on a data::material. * * @section XML XML Configuration * * @code{.xml} @endcode * *@subsection In-Out In-Out * - \b material [sight::data::material]: material object to update. * *@subsection Configuration Configuration * - \b label (optional, default="Material opacity : "): label of the slider. */ class material_opacity_editor : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(material_opacity_editor, sight::ui::editor); /// Destroys the service. ~material_opacity_editor() noexcept override = default; private: /// Configures the slider's label. void configuring() override; /// Sets the connections and the UI elements. void starting() override; /// Sets the opacity slider's value to the material's alpha value. void updating() override; /// Destroys the connections and cleans the container. void stopping() override; QPointer m_opacity_slider; QPointer m_opacity_value; /// Name that appears next to the slider. std::string m_label {"Material opacity : "}; data::ptr m_material {this, "material"}; private Q_SLOTS: /** * @brief Slot: called when the opacity slider changed. * @param _value The new opacity value. */ void on_opacity_slider(int _value); }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/metrics/000077500000000000000000000000001503402212300167015ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/metrics/distance.cpp000066400000000000000000000071271503402212300212060ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/metrics/distance.hpp" #include #include #include #include #include #include namespace sight::module::ui::qt::metrics { //------------------------------------------------------------------------------ const core::com::signals::key_t distance::DISTANCE_REQUESTED_SIG = "distance_requested"; //------------------------------------------------------------------------------ distance::distance() noexcept { m_sig_distance_requested = new_signal(DISTANCE_REQUESTED_SIG); } //------------------------------------------------------------------------------ distance::~distance() noexcept = default; //------------------------------------------------------------------------------ void distance::starting() { this->sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); namespace fs = std::filesystem; fs::path path_image_dist = core::runtime::get_module_resource_file_path("sight::module::ui::qt", "distance.svg"); SIGHT_ASSERT("image " << path_image_dist << "is missing", fs::exists(path_image_dist)); QIcon image_dist(QString::fromStdString(path_image_dist.string())); m_dist_button = new QPushButton(image_dist, tr("")); m_dist_button->setToolTip(tr("distance")); auto* layout = new QVBoxLayout(); layout->addWidget(m_dist_button, 1); layout->setContentsMargins(0, 0, 0, 0); QObject::connect(m_dist_button, &QPushButton::clicked, this, &self_t::on_distance_button); qt_container->set_layout(layout); } //------------------------------------------------------------------------------ void distance::stopping() { QObject::disconnect(m_dist_button, &QPushButton::clicked, this, &self_t::on_distance_button); this->destroy(); } //------------------------------------------------------------------------------ void distance::configuring() { this->sight::ui::service::initialize(); } //------------------------------------------------------------------------------ void distance::updating() { } //------------------------------------------------------------------------------ void distance::on_distance_button() { const auto image = m_image.lock(); SIGHT_ASSERT("'image' key is not found.", image); // force distance to be shown data::helper::medical_image::set_distance_visibility(*image, true); m_sig_distance_requested->async_emit(); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::metrics sight-25.1.0/module/ui/qt/metrics/distance.hpp000066400000000000000000000061141503402212300212060ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include namespace sight::module::ui::qt::metrics { /** * @brief distance service is represented by a button. It allows to show distances in a generic scene. * * @note Sends a signal to request a distance. It should be connected to an ImageMultidistance adaptor to create the * distance. * * @section Signals Signals * \b distance_requested() : signal to request a distance. It should be connected to an ImageMultidistance * adaptor to create the distance. * * @section XML XML Configuration * * @code{.xml} @endcode * @subsection In-Out In-Out * -\b image [data::image]: image in which we calculate the distance. */ class distance : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(distance, sight::ui::editor); /// Constructor. Do nothing. distance() noexcept; /// Destructor. Do nothing. ~distance() noexcept override; /** * @name Signal * @{ */ static const core::com::signals::key_t DISTANCE_REQUESTED_SIG; using distance_requested_signal_t = core::com::signal; /** * @} */ protected: /** * @brief Install the layout. * * This method launches the editor::starting method. */ void starting() override; /** * @brief Destroy the layout. * * This method launches the editor::stopping method. */ void stopping() override; /// Do nothing void updating() override; /// Initialize the widget void configuring() override; protected Q_SLOTS: /** * @brief Notify the scene that a distance is added. */ void on_distance_button(); private: QPointer m_dist_button; distance_requested_signal_t::sptr m_sig_distance_requested; /// signal emitted when a distance is requested data::ptr m_image {this, "image"}; }; } // namespace sight::module::ui::qt::metrics sight-25.1.0/module/ui/qt/metrics/landmarks.cpp000066400000000000000000001634111503402212300213670ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2025 IRCAD France * Copyright (C) 2017-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "data/landmarks.hpp" #include "module/ui/qt/metrics/landmarks.hpp" #include "data/image_series.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::metrics { static_assert( std::is_same_v, "'groupModified' signal from data::landmarks and data::has_fiducials must have the same signature" ); namespace { //------------------------------------------------------------------------------ [[nodiscard]] std::vector get_group_names(landmarks_or_image_series_const_ptr _li) { if(_li.landmarks != nullptr) { return _li.landmarks->get_group_names(); } if(_li.image_series != nullptr) { return _li.image_series->get_fiducials()->get_point_fiducials_group_names(); } SIGHT_ASSERT("Either 'landmarks' or 'fiducialsSeries' must be configured as inout", false); return {}; } //------------------------------------------------------------------------------ [[nodiscard]] std::optional num_points( landmarks_or_image_series_const_ptr _li, const std::string& _group_name ) { if(_li.landmarks != nullptr) { if(!_li.landmarks->has_group(_group_name)) { return std::nullopt; } return _li.landmarks->num_points(_group_name); } if(_li.image_series != nullptr) { return _li.image_series->get_fiducials()->get_number_of_points_in_group(_group_name); } SIGHT_ASSERT("Either 'landmarks' or 'fiducialsSeries' must be configured as inout", false); return {}; } /** * Get a fiducial set as a structure compatible with data::landmarks * @param _group_name The name of the group to fetch * @return The fiducial set as a structure compatible with data::landmarks */ [[nodiscard]] std::optional get_group( const data::fiducials_series& _fiducials, const std::string& _group_name ) { const auto fiducial_set = _fiducials.get_fiducial_set_and_index(_group_name); if(!fiducial_set.has_value()) { return std::nullopt; } data::landmarks::color_t color = fiducial_set->first.color.value_or(std::array {1.F, 1.F, 1.F, 1.F}); data::landmarks::size_t size = fiducial_set->first.size.value_or(10); data::landmarks::shape shape = data::landmarks::shape::sphere; switch(fiducial_set->first.shape.value_or(data::fiducials_series::private_shape::sphere)) { case data::fiducials_series::private_shape::sphere: shape = data::landmarks::shape::sphere; break; case data::fiducials_series::private_shape::cube: shape = data::landmarks::shape::cube; default: break; } bool visibility = fiducial_set->first.visibility.value_or(true); data::landmarks::landmarks_group group(color, size, shape, visibility); auto query_results = _fiducials.query_fiducials( std::nullopt, sight::data::fiducials_series::shape::point, _group_name ); for(auto q : query_results) { if(!q.m_contour_data.has_value()) { group.m_points.push_back( {{q.m_contour_data.value()[0], q.m_contour_data.value()[1], q.m_contour_data.value()[2] } }); } } return group; } //------------------------------------------------------------------------------ [[nodiscard]] std::optional get_group( landmarks_or_image_series_const_ptr _li, const std::string& _group_name ) { if(_li.landmarks != nullptr) { if(!_li.landmarks->has_group(_group_name)) { return std::nullopt; } return _li.landmarks->get_group(_group_name); } if(_li.image_series != nullptr) { return get_group(*_li.image_series->get_fiducials(), _group_name); } SIGHT_ASSERT("Either 'landmarks' or 'fiducialsSeries' must be configured as inout", false); return {}; } //------------------------------------------------------------------------------ [[nodiscard]] std::optional get_point( landmarks_or_image_series_const_ptr _li, const std::string& _group_name, std::size_t _index ) { if(_li.landmarks != nullptr) { if(!_li.landmarks->has_group(_group_name)) { return std::nullopt; } return _li.landmarks->get_point(_group_name, _index); } if(_li.image_series != nullptr) { return _li.image_series->get_fiducials()->get_point(_group_name, _index); } SIGHT_ASSERT("Either 'landmarks' or 'fiducialsSeries' must be configured as inout", false); return {}; } } // namespace //------------------------------------------------------------------------------ static const char* s_group_property_name = "group"; static const int GROUP_NAME_ROLE = Qt::UserRole + 1; static const core::com::slots::key_t ADD_POINT_SLOT = "add_point"; static const core::com::slots::key_t MODIFY_POINT_SLOT = "modifyPoint"; static const core::com::slots::key_t SELECT_POINT_SLOT = "selectPoint"; static const core::com::slots::key_t DESELECT_POINT_SLOT = "deselectPoint"; static const core::com::slots::key_t REMOVE_POINT_SLOT = "removePoint"; static const core::com::slots::key_t ADD_GROUP_SLOT = "add_group"; static const core::com::slots::key_t REMOVE_GROUP_SLOT = "remove_group"; static const core::com::slots::key_t MODIFY_GROUP_SLOT = "modifyGroup"; static const core::com::slots::key_t RENAME_GROUP_SLOT = "rename_group"; static const std::string SIZE_CONFIG = "size"; static const std::string OPACITY_CONFIG = "opacity"; static const std::string ADVANCED_CONFIG = "advanced"; static const std::string TEXT_CONFIG = "text"; const core::com::signals::key_t landmarks::SEND_WORLD_COORD = "send_world_coord"; const core::com::signals::key_t landmarks::GROUP_SELECTED = "group_selected"; //------------------------------------------------------------------------------ landmarks::landmarks() noexcept { new_slot(ADD_POINT_SLOT, &landmarks::add_point, this); new_slot(MODIFY_POINT_SLOT, &landmarks::modify_point, this); new_slot(SELECT_POINT_SLOT, &landmarks::select_point, this); new_slot(DESELECT_POINT_SLOT, &landmarks::deselect_point, this); new_slot(ADD_GROUP_SLOT, &landmarks::add_group, this); new_slot(REMOVE_POINT_SLOT, &landmarks::remove_point, this); new_slot(REMOVE_GROUP_SLOT, &landmarks::remove_group, this); new_slot(MODIFY_GROUP_SLOT, &landmarks::modify_group, this); new_slot(RENAME_GROUP_SLOT, &landmarks::rename_group, this); new_signal(SEND_WORLD_COORD); new_signal(GROUP_SELECTED); } //------------------------------------------------------------------------------ landmarks::~landmarks() noexcept = default; //------------------------------------------------------------------------------ void landmarks::configuring() { this->sight::ui::service::initialize(); const service::config_t config = this->get_config(); m_default_landmark_size = config.get(SIZE_CONFIG, m_default_landmark_size); SIGHT_FATAL_IF( "'size' value must be a positive number greater than 0 (current value: " << m_default_landmark_size << ")", m_default_landmark_size <= 0.F ); m_default_landmark_opacity = config.get(OPACITY_CONFIG, m_default_landmark_opacity); SIGHT_FATAL_IF( "'opacity' value must be a number between 0.0 and 1.0 (current value: " << m_default_landmark_opacity << ")", m_default_landmark_opacity<0.F || m_default_landmark_opacity>1.F ); m_advanced_mode = config.get(ADVANCED_CONFIG, false); m_text = config.get(TEXT_CONFIG, m_text); } //------------------------------------------------------------------------------ void landmarks::starting() { this->sight::ui::service::create(); const QString service_id = QString::fromStdString(base_id()); const auto qt_container = std::dynamic_pointer_cast( this->get_container() ); qt_container->get_qt_container()->setObjectName(service_id); auto* const layout = new QVBoxLayout(); auto* const grid_layout = new QGridLayout(); m_visibility_checkbox = new QCheckBox(); auto* const visibility_label = new QLabel(QString("Visibility")); m_visibility_checkbox->setObjectName(service_id + "/" + visibility_label->text()); m_size_slider = new QSlider(Qt::Horizontal); m_size_slider->setMinimum(1); m_size_slider->setMaximum(100); auto* const size_label = new QLabel(QString("Size")); m_size_slider->setObjectName(service_id + "/" + size_label->text()); m_opacity_slider = new QSlider(Qt::Horizontal); auto* const opacity_label = new QLabel("Opacity"); m_opacity_slider->setObjectName(service_id + "/" + opacity_label->text()); m_shape_selector = new QComboBox(); m_shape_selector->addItem(QString("Cube")); m_shape_selector->addItem(QString("Sphere")); auto* const shape_label = new QLabel("Shape"); m_shape_selector->setObjectName(service_id + "/" + shape_label->text()); if(m_advanced_mode) { m_new_group_button = new QPushButton("New Group"); m_new_group_button->setObjectName(service_id + "/" + m_new_group_button->text()); } m_remove_button = new QPushButton("Delete"); m_remove_button->setObjectName(service_id + "/" + m_remove_button->text()); m_remove_button->setShortcut(QKeySequence::Delete); grid_layout->addWidget(visibility_label, 0, 0); grid_layout->addWidget(m_visibility_checkbox, 0, 1); grid_layout->addWidget(size_label, 1, 0); grid_layout->addWidget(m_size_slider, 1, 1); grid_layout->addWidget(opacity_label, 2, 0); grid_layout->addWidget(m_opacity_slider, 2, 1); grid_layout->addWidget(shape_label, 3, 0); grid_layout->addWidget(m_shape_selector, 3, 1); grid_layout->addWidget(m_remove_button, 4, 1); m_group_editor_widget = new QWidget(); m_group_editor_widget->setLayout(grid_layout); m_tree_widget = new QTreeWidget(); m_tree_widget->setObjectName(service_id + "/treeWidget"); if(!m_text.empty()) { auto* helper_text_label = new QLabel(QString::fromStdString(m_text)); layout->addWidget(helper_text_label); } if(m_advanced_mode) { layout->addWidget(m_new_group_button); } layout->addWidget(m_tree_widget); layout->addWidget(m_group_editor_widget); m_group_editor_widget->setDisabled(true); QStringList headers; headers << "Group" << "Color"; if(m_advanced_mode) { // Add empty third column to display and edit point coordinates. headers << ""; } m_tree_widget->setHeaderLabels(headers); qt_container->set_layout(layout); this->updating(); SIGHT_ASSERT( "Either 'landmarks' or 'imageSeries' parameter must be set.", (m_landmarks.const_lock() != nullptr) + (m_image_series.const_lock() != nullptr) == 1 ); if(auto image_series = m_image_series.lock()) { image_series->get_fiducials()->set_group_names_for_point_fiducials(); } } //------------------------------------------------------------------------------ service::connections_t landmarks::auto_connections() const { connections_t connections; connections.push(LANDMARKS_INOUT, data::landmarks::MODIFIED_SIG, service::slots::UPDATE); connections.push(LANDMARKS_INOUT, data::landmarks::POINT_ADDED_SIG, ADD_POINT_SLOT); connections.push(LANDMARKS_INOUT, data::landmarks::POINT_MODIFIED_SIG, MODIFY_POINT_SLOT); connections.push(LANDMARKS_INOUT, data::landmarks::POINT_SELECTED_SIG, SELECT_POINT_SLOT); connections.push(LANDMARKS_INOUT, data::landmarks::POINT_DESELECTED_SIG, DESELECT_POINT_SLOT); connections.push(LANDMARKS_INOUT, data::landmarks::GROUP_ADDED_SIG, ADD_GROUP_SLOT); connections.push(LANDMARKS_INOUT, data::landmarks::GROUP_REMOVED_SIG, REMOVE_GROUP_SLOT); connections.push(LANDMARKS_INOUT, data::landmarks::POINT_REMOVED_SIG, REMOVE_POINT_SLOT); connections.push(LANDMARKS_INOUT, data::landmarks::GROUP_MODIFIED_SIG, MODIFY_GROUP_SLOT); connections.push(LANDMARKS_INOUT, data::landmarks::GROUP_RENAMED_SIG, RENAME_GROUP_SLOT); connections.push(IMAGE_SERIES_INOUT, data::image_series::MODIFIED_SIG, service::slots::UPDATE); connections.push(IMAGE_SERIES_INOUT, data::has_fiducials::signals::POINT_ADDED, ADD_POINT_SLOT); connections.push(IMAGE_SERIES_INOUT, data::has_fiducials::signals::POINT_MODIFIED, MODIFY_POINT_SLOT); connections.push(IMAGE_SERIES_INOUT, data::has_fiducials::signals::POINT_SELECTED, SELECT_POINT_SLOT); connections.push(IMAGE_SERIES_INOUT, data::has_fiducials::signals::POINT_DESELECTED, DESELECT_POINT_SLOT); connections.push(IMAGE_SERIES_INOUT, data::has_fiducials::signals::GROUP_ADDED, ADD_GROUP_SLOT); connections.push(IMAGE_SERIES_INOUT, data::has_fiducials::signals::GROUP_REMOVED, REMOVE_GROUP_SLOT); connections.push(IMAGE_SERIES_INOUT, data::has_fiducials::signals::POINT_REMOVED, REMOVE_POINT_SLOT); connections.push(IMAGE_SERIES_INOUT, data::has_fiducials::signals::GROUP_MODIFIED, MODIFY_GROUP_SLOT); connections.push(IMAGE_SERIES_INOUT, data::has_fiducials::signals::GROUP_RENAMED, RENAME_GROUP_SLOT); return connections; } //------------------------------------------------------------------------------ void landmarks::updating() { m_tree_widget->blockSignals(true); { landmarks_or_image_series_const_lock li_lock = const_lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); m_tree_widget->clear(); for(const auto& name : get_group_names(li_lock)) { this->add_group(name); this->add_point(name); } } QObject::connect(m_tree_widget.data(), &QTreeWidget::itemChanged, this, &landmarks::on_group_name_edited); QObject::connect(m_tree_widget.data(), &QTreeWidget::currentItemChanged, this, &landmarks::on_selection_changed); QObject::connect( m_tree_widget.data(), &QTreeWidget::itemDoubleClicked, this, &landmarks::on_landmark_double_clicked ); QObject::connect(m_size_slider.data(), &QSlider::valueChanged, this, &landmarks::on_size_changed); QObject::connect(m_opacity_slider.data(), &QSlider::valueChanged, this, &landmarks::on_opacity_changed); QObject::connect(m_visibility_checkbox.data(), &QCheckBox::stateChanged, this, &landmarks::on_visibility_changed); QObject::connect(m_shape_selector.data(), &QComboBox::currentTextChanged, this, &landmarks::on_shape_changed); QObject::connect(m_remove_button.data(), &QPushButton::clicked, this, &landmarks::on_remove_selection); QObject::connect(m_new_group_button.data(), &QPushButton::clicked, this, &landmarks::on_add_new_group); m_tree_widget->blockSignals(false); } //------------------------------------------------------------------------------ void landmarks::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void landmarks::on_color_button() { QObject* const sender = this->sender(); // Create Color choice dialog. auto qt_container = std::dynamic_pointer_cast(this->get_container()); QWidget* const container = qt_container->get_qt_container(); SIGHT_ASSERT("container not instanced", container); const auto old_color = sender->property("color").value(); const QColor color_qt = QColorDialog::getColor(old_color, container, "Select Color", QColorDialog::ShowAlphaChannel); if(color_qt.isValid()) { auto* const color_button = dynamic_cast(sender); color_button->setProperty("color", color_qt); set_color_button_icon(color_button, color_qt); const std::string group_name = color_button->property(s_group_property_name).value().toStdString(); data::landmarks::color_t color = {{ float(color_qt.red()) / 255.F, float(color_qt.green()) / 255.F, float(color_qt.blue()) / 255.F, float(color_qt.alpha()) / 255.F } }; data::landmarks::group_modified_signal_t::sptr sig; { landmarks_or_image_series_lock li_lock = lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); if(li_lock.landmarks != nullptr) { auto& group = li_lock.landmarks->get_group(group_name); group.m_color = color; sig = li_lock.landmarks->signal( data::landmarks::GROUP_MODIFIED_SIG ); } else if(li_lock.image_series != nullptr) { std::optional > fiducial_set = li_lock.image_series->get_fiducials()->get_fiducial_set_and_index(group_name); if(!fiducial_set.has_value()) { return; } li_lock.image_series->get_fiducials()->set_color(fiducial_set->second, color); sig = li_lock.image_series->signal( data::has_fiducials::signals::GROUP_MODIFIED ); } } m_opacity_slider->setValue(static_cast(color[3] * float(m_opacity_slider->maximum()))); { core::com::connection::blocker block(sig->get_connection(this->slot(MODIFY_GROUP_SLOT))); sig->async_emit(group_name); } } } //------------------------------------------------------------------------------ void landmarks::on_group_name_edited(QTreeWidgetItem* _item, int _column) { SIGHT_ERROR_IF("A column different from the group's name is being edited", _column != 0); m_tree_widget->blockSignals(true); if(_column == 0) { const QString old_group_name = _item->data(0, GROUP_NAME_ROLE).toString(); const QString new_group_name = _item->text(0); if(new_group_name.isEmpty()) { const QString msg = "The new group name for '" + old_group_name + "' is empty. Please enter a valid name and try again"; QMessageBox msg_box(QMessageBox::Warning, "No group name", msg, QMessageBox::Ok); msg_box.exec(); _item->setText(0, old_group_name); } else if(old_group_name != new_group_name) { try { { landmarks_or_image_series_lock li_lock = lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); const data::landmarks::GroupNameContainer group_names = get_group_names(li_lock); if(li_lock.landmarks != nullptr) { li_lock.landmarks->rename_group(old_group_name.toStdString(), new_group_name.toStdString()); const auto sig = li_lock.landmarks->signal( data::landmarks::GROUP_RENAMED_SIG ); { core::com::connection::blocker block(sig->get_connection(this->slot(RENAME_GROUP_SLOT))); sig->async_emit(old_group_name.toStdString(), new_group_name.toStdString()); } } else if(li_lock.image_series != nullptr) { std::optional > fiducial_set = li_lock.image_series->get_fiducials()->get_fiducial_set_and_index( old_group_name.toStdString() ); if(!fiducial_set.has_value()) { throw data::exception("'" + old_group_name.toStdString() + "' group doesn't exist"); } if(std::ranges::find(group_names, new_group_name.toStdString()) != group_names.end()) { throw data::exception("'" + new_group_name.toStdString() + "' group already exists"); } li_lock.image_series->get_fiducials()->set_group_name( fiducial_set->second, new_group_name.toStdString() ); const auto sig = li_lock.image_series->signal( data::has_fiducials::signals::GROUP_RENAMED ); core::com::connection::blocker block(sig->get_connection(this->slot(RENAME_GROUP_SLOT))); sig->async_emit(old_group_name.toStdString(), new_group_name.toStdString()); } } _item->setData(0, GROUP_NAME_ROLE, new_group_name); QWidget* const widget = m_tree_widget->itemWidget(_item, 1); widget->setProperty(s_group_property_name, new_group_name); } catch(data::exception& e) { const QString msg = "Can't rename '" + old_group_name + "' as '" + new_group_name + ".\n" + QString(e.what()); QMessageBox msg_box(QMessageBox::Warning, "Can't rename" + old_group_name, msg, QMessageBox::Ok); msg_box.exec(); _item->setText(0, old_group_name); } } } m_tree_widget->blockSignals(false); } //------------------------------------------------------------------------------ void landmarks::on_selection_changed(QTreeWidgetItem* _current, QTreeWidgetItem* _previous) { if(_previous != nullptr) { landmarks_or_image_series_const_lock li_lock = const_lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); data::landmarks::point_deselected_signal_t::sptr deselect_sig; static_assert( std::is_same_v, "'pointDeselected' signal from data::landmarks and data::has_fiducials must have the same signature" ); if(li_lock.landmarks != nullptr) { deselect_sig = li_lock.landmarks->signal( data::landmarks::POINT_DESELECTED_SIG ); } else if(li_lock.image_series != nullptr) { deselect_sig = li_lock.image_series->signal( data::has_fiducials::signals::POINT_DESELECTED ); } const core::com::connection::blocker block(deselect_sig->get_connection(this->slot(DESELECT_POINT_SLOT))); if(m_advanced_mode) { const QTreeWidgetItem* const previous_parent = _previous->parent(); if(previous_parent != nullptr) { const std::string& group_name = previous_parent->text(0).toStdString(); const auto index = static_cast(previous_parent->indexOfChild(_previous)); SIGHT_ASSERT( "index must be inferior to the number of points in '" + group_name + "'.", index < num_points(li_lock, group_name) ); deselect_sig->async_emit(group_name, index); } } else { const std::string& group_name = _previous->text(0).toStdString(); deselect_sig->async_emit(group_name, 0); } } if(_current != nullptr) { int size = 0; bool visible = false; QString shape_text; float opacity = NAN; { landmarks_or_image_series_const_lock li_lock = const_lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); data::landmarks::point_selected_signal_t::sptr select_sig; static_assert( std::is_same_v, "'pointSelected' signal from data::landmarks and data::has_fiducials must have the same signature" ); if(li_lock.landmarks != nullptr) { select_sig = li_lock.landmarks->signal( data::landmarks::POINT_SELECTED_SIG ); } else if(li_lock.image_series != nullptr) { select_sig = li_lock.image_series->signal( data::has_fiducials::signals::POINT_SELECTED ); } std::string group_name; if(m_advanced_mode) { const QTreeWidgetItem* const current_parent = _current->parent(); if(current_parent != nullptr) { group_name = current_parent->text(0).toStdString(); const auto index = static_cast(current_parent->indexOfChild(_current)); SIGHT_ASSERT( "index must be inferior to the number of points in '" + group_name + "'.", index < num_points(li_lock, group_name) ); const core::com::connection::blocker block(select_sig->get_connection( this->slot( SELECT_POINT_SLOT ) )); select_sig->async_emit(group_name, index); } else { group_name = _current->text(0).toStdString(); } } else { group_name = _current->text(0).toStdString(); core::com::connection::blocker block(select_sig->get_connection(this->slot(SELECT_POINT_SLOT))); select_sig->async_emit(group_name, 0); } std::optional group = get_group(li_lock, group_name); if(!group.has_value()) { return; } size = static_cast(group->m_size); visible = group->m_visibility; shape_text = group->m_shape == data::landmarks::shape::cube ? "Cube" : "Sphere"; opacity = group->m_color[3]; signal(GROUP_SELECTED)->async_emit(group_name); } // Set widget values m_size_slider->setValue(size); m_visibility_checkbox->setChecked(visible); m_shape_selector->setCurrentText(shape_text); m_opacity_slider->setValue(static_cast(opacity * float(m_opacity_slider->maximum()))); } else { signal(GROUP_SELECTED)->async_emit(""); } m_group_editor_widget->setDisabled(_current == nullptr); } //------------------------------------------------------------------------------ void landmarks::on_landmark_double_clicked(QTreeWidgetItem* _item, int /*unused*/) const { if(_item != nullptr) { // Exclude top level item if(_item->childCount() != 0) { return; } std::array index { _item->text(0), _item->text(1), _item->text(2) }; if(index[0].isEmpty() || index[1].isEmpty() || index[2].isEmpty()) { // Do nothing if a value is missing. return; } // Convert to double std::array check = {false, false, false}; data::landmarks::point_t world_coord { index[0].toDouble(check.data()), index[1].toDouble(&check[1]), index[2].toDouble(&check[2]) }; // Check that conversion to double performed well. if(!check[0] || !check[1] || !check[2]) { SIGHT_ERROR( "Cannot convert landmark position to double (" + index[0].toStdString() + ", " + index[1].toStdString() + ", " + index[2].toStdString() + ")" ); return; } update_current_landmark(world_coord); } } //------------------------------------------------------------------------------ void landmarks::update_current_landmark(data::landmarks::point_t& _world_coord) const { // Send signal with world coordinates of the landmarks SIGHT_DEBUG( " Send world coordinates [" << _world_coord[0] << ", " << _world_coord[1] << ", " << _world_coord[2] << " ]" ); this->signal(SEND_WORLD_COORD)->async_emit( _world_coord[0], _world_coord[1], _world_coord[2] ); //update the data with the current point auto current_landmark = m_current_landmark.lock(); if(current_landmark) { *current_landmark = _world_coord; current_landmark->async_emit(sight::data::object::MODIFIED_SIG); } } //------------------------------------------------------------------------------ void landmarks::on_size_changed(int _new_size) { const auto real_size = static_cast(_new_size); std::string group_name; if(current_selection(group_name)) { landmarks_or_image_series_lock li_lock = lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); data::landmarks::group_modified_signal_t::sptr sig; if(li_lock.landmarks != nullptr) { sig = li_lock.landmarks->signal( data::landmarks::GROUP_MODIFIED_SIG ); li_lock.landmarks->set_group_size(group_name, real_size); } else if(li_lock.image_series != nullptr) { sig = li_lock.image_series->signal( data::has_fiducials::signals::GROUP_MODIFIED ); std::optional > fiducial_set = li_lock.image_series->get_fiducials()->get_fiducial_set_and_index(group_name); if(!fiducial_set.has_value()) { SIGHT_WARN("Couldn't change size of size of group '" << group_name << "', it doesn't exist"); return; } li_lock.image_series->get_fiducials()->set_size(fiducial_set->second, real_size); } const core::com::connection::blocker block(sig->get_connection(this->slot(MODIFY_GROUP_SLOT))); sig->async_emit(group_name); } } //------------------------------------------------------------------------------ void landmarks::on_opacity_changed(int _new_opacity) { const auto slider_size = static_cast(m_opacity_slider->maximum() - m_opacity_slider->minimum()); const float real_opacity = static_cast(_new_opacity) / slider_size; std::string group_name; if(current_selection(group_name)) { landmarks_or_image_series_lock li_lock = lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); data::landmarks::group_modified_signal_t::sptr sig; if(li_lock.landmarks != nullptr) { sig = li_lock.landmarks->signal( data::landmarks::GROUP_MODIFIED_SIG ); const auto group_color = li_lock.landmarks->get_group(group_name).m_color; const data::landmarks::color_t new_group_color = {{group_color[0], group_color[1], group_color[2], real_opacity}}; li_lock.landmarks->set_group_color(group_name, new_group_color); } else if(li_lock.image_series != nullptr) { sig = li_lock.image_series->signal( data::has_fiducials::signals::GROUP_MODIFIED ); std::optional > fs = li_lock.image_series->get_fiducials()->get_fiducial_set_and_index(group_name); if(!fs.has_value()) { SIGHT_WARN("Couldn't modify the color of group '" << group_name << "', it doesn't exist"); return; } std::optional color = li_lock.image_series->get_fiducials()->get_color(fs->second); SIGHT_ASSERT("The group must have a color", color.has_value()); (*color)[3] = real_opacity; li_lock.image_series->get_fiducials()->set_color(fs->second, *color); } QTreeWidgetItem* const item = get_group_item(group_name); auto* const color_button = dynamic_cast(m_tree_widget->itemWidget(item, 1)); auto current_color = color_button->property("color").value(); current_color.setAlphaF(real_opacity); color_button->setProperty("color", current_color); set_color_button_icon(color_button, current_color); core::com::connection::blocker block(sig->get_connection(this->slot(MODIFY_GROUP_SLOT))); sig->async_emit(group_name); } } //------------------------------------------------------------------------------ void landmarks::on_visibility_changed(int _visibility) { std::string group_name; if(current_selection(group_name)) { landmarks_or_image_series_lock li_lock = lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); if(li_lock.landmarks != nullptr) { li_lock.landmarks->set_group_visibility(group_name, static_cast(_visibility)); const auto sig = li_lock.landmarks->signal( data::landmarks::GROUP_MODIFIED_SIG ); const core::com::connection::blocker block(sig->get_connection(this->slot(MODIFY_GROUP_SLOT))); sig->async_emit(group_name); } else if(li_lock.image_series != nullptr) { std::optional > fiducial_set = li_lock.image_series->get_fiducials()->get_fiducial_set_and_index(group_name); if(!fiducial_set.has_value()) { SIGHT_WARN("Couldn't change visibility of group '" << group_name << "', it doesn't exist"); return; } li_lock.image_series->get_fiducials()->set_visibility(fiducial_set->second, static_cast(_visibility)); const auto sig = li_lock.image_series->signal( data::image_series::signals::GROUP_MODIFIED ); const core::com::connection::blocker block(sig->get_connection(this->slot(MODIFY_GROUP_SLOT))); sig->async_emit(group_name); } } } //------------------------------------------------------------------------------ void landmarks::on_shape_changed(const QString& _shape) { std::string group_name; if(current_selection(group_name)) { SIGHT_ASSERT("Shape must be 'Cube' or 'Sphere'.", _shape == "Cube" || _shape == "Sphere"); const data::landmarks::shape s = (_shape == "Cube") ? data::landmarks::shape::cube : data::landmarks::shape::sphere; landmarks_or_image_series_lock li_lock = lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); data::landmarks::group_modified_signal_t::sptr sig; if(li_lock.landmarks != nullptr) { sig = li_lock.landmarks->signal( data::landmarks::GROUP_MODIFIED_SIG ); li_lock.landmarks->set_group_shape(group_name, s); } else if(li_lock.image_series != nullptr) { sig = li_lock.image_series->signal( data::has_fiducials::signals::GROUP_MODIFIED ); std::optional > fiducial_set = li_lock.image_series->get_fiducials()->get_fiducial_set_and_index(group_name); if(!fiducial_set.has_value()) { SIGHT_WARN("Couldn't change shape of group '" << group_name << "', it doesn't exist"); return; } li_lock.image_series->get_fiducials()->set_shape( fiducial_set->second, s == data::landmarks::shape::cube ? data::fiducials_series::private_shape::cube : data::fiducials_series::private_shape::sphere ); } const core::com::connection::blocker block(sig->get_connection(this->slot(MODIFY_GROUP_SLOT))); sig->async_emit(group_name); } } //------------------------------------------------------------------------------ void landmarks::on_add_new_group() { const std::string group_name = this->generate_new_group_name(); landmarks_or_image_series_lock li_lock = lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); if(li_lock.landmarks != nullptr) { li_lock.landmarks->add_group(group_name, this->generate_new_color(), m_default_landmark_size); li_lock.landmarks->signal(data::landmarks::GROUP_ADDED_SIG)->async_emit( group_name ); } else if(li_lock.image_series != nullptr) { li_lock.image_series->get_fiducials()->add_group( group_name, this->generate_new_color(), m_default_landmark_size ); li_lock.image_series->signal( data::has_fiducials::signals::GROUP_ADDED )->async_emit(group_name); } } //------------------------------------------------------------------------------ void landmarks::on_remove_selection() { m_tree_widget->blockSignals(true); QTreeWidgetItem* const item = m_tree_widget->currentItem(); if(item != nullptr) { const int top_level_index = m_tree_widget->indexOfTopLevelItem(item); landmarks_or_image_series_lock li_lock = lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); if(m_advanced_mode && top_level_index == -1) // Delete point { QTreeWidgetItem* const item_parent = item->parent(); const auto index = static_cast(item_parent->indexOfChild(item)); const std::string& group_name = item_parent->text(0).toStdString(); data::landmarks::point_removed_signal_t::sptr sig; static_assert( std::is_same_v, "'pointRemoved' signal from data::landmarks and data::has_fiducials must have the same signature" ); if(li_lock.landmarks != nullptr) { sig = li_lock.landmarks->signal( data::landmarks::POINT_REMOVED_SIG ); li_lock.landmarks->remove_point(group_name, index); } else if(li_lock.image_series != nullptr) { sig = li_lock.image_series->signal( data::has_fiducials::signals::POINT_REMOVED ); li_lock.image_series->get_fiducials()->remove_point(group_name, index); } item_parent->removeChild(item); { const core::com::connection::blocker block(sig->get_connection(this->slot(REMOVE_POINT_SLOT))); sig->async_emit(group_name, index); } } else { const std::string& group_name = item->text(0).toStdString(); data::landmarks::group_removed_signal_t::sptr sig; static_assert( std::is_same_v, "'groupRemoved' signal from data::landmarks and data::has_fiducials must have the same signature" ); if(li_lock.landmarks != nullptr) { sig = li_lock.landmarks->signal( data::landmarks::GROUP_REMOVED_SIG ); li_lock.landmarks->remove_group(group_name); } else if(li_lock.image_series != nullptr) { sig = li_lock.image_series->signal( data::has_fiducials::signals::GROUP_REMOVED ); li_lock.image_series->get_fiducials()->remove_group(group_name); } delete m_tree_widget->takeTopLevelItem(top_level_index); { const core::com::connection::blocker block(sig->get_connection(this->slot(REMOVE_GROUP_SLOT))); sig->async_emit(group_name); } } m_tree_widget->setCurrentItem(nullptr); m_group_editor_widget->setDisabled(true); } m_tree_widget->blockSignals(false); } //------------------------------------------------------------------------------ void landmarks::add_point(std::string _group_name) const { if(m_advanced_mode) { m_tree_widget->blockSignals(true); landmarks_or_image_series_const_lock li_lock = const_lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); QTreeWidgetItem* const item = get_group_item(_group_name); const auto nb_childs = static_cast(item->childCount()); std::optional maybe_nb_points = num_points(li_lock, _group_name); if(!maybe_nb_points.has_value()) { return; } std::size_t nb_points = *maybe_nb_points; for(std::size_t idx = nb_childs ; idx < nb_points ; ++idx) { std::optional maybe_new_point = get_point(li_lock, _group_name, idx); if(!maybe_new_point.has_value()) { continue; } data::landmarks::point_t new_point = *maybe_new_point; auto* const pt = new QTreeWidgetItem(); for(int i = 0 ; i < 3 ; ++i) { pt->setText(i, QString::fromStdString(std::to_string(new_point[static_cast(i)]))); } item->addChild(pt); } m_tree_widget->blockSignals(false); } } //------------------------------------------------------------------------------ void landmarks::add_group(std::string _name) const { QColor color; { landmarks_or_image_series_const_lock li_lock = const_lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); std::optional group = get_group(li_lock, _name); if(!group.has_value()) { return; } color = convert_to_q_color(group->m_color); } auto* const item = new QTreeWidgetItem(); item->setFlags(item->flags() | Qt::ItemIsEditable); item->setData(0, GROUP_NAME_ROLE, QString::fromStdString(_name)); m_tree_widget->addTopLevelItem(item); item->setText(0, QString::fromStdString(_name)); auto* const button = new QPushButton(); set_color_button_icon(button, color); button->setProperty("color", color); button->setProperty(s_group_property_name, QString::fromStdString(_name)); QObject::connect(button, &QPushButton::clicked, this, &landmarks::on_color_button); m_tree_widget->setItemWidget(item, 1, button); } //------------------------------------------------------------------------------ void landmarks::remove_group(std::string _name) const { try { QTreeWidgetItem* const item = get_group_item(_name); while(item->childCount() != 0) { QTreeWidgetItem* const child = item->child(0); item->removeChild(child); } } catch(const std::exception& e) { std::cout << e.what() << std::endl; } } //------------------------------------------------------------------------------ void landmarks::remove_point(std::string _group_name, std::size_t _index) const { m_tree_widget->blockSignals(true); QTreeWidgetItem* const item = get_group_item(_group_name); SIGHT_ASSERT("Index must be less than " << item->childCount(), static_cast(_index) < item->childCount()); QTreeWidgetItem* const point_item = item->child(static_cast(_index)); item->removeChild(point_item); m_tree_widget->blockSignals(false); } //------------------------------------------------------------------------------ void landmarks::rename_group(std::string _old_name, std::string _new_name) const { m_tree_widget->blockSignals(true); const QString qt_new_name = QString::fromStdString(_new_name); QTreeWidgetItem* const item = get_group_item(_old_name); item->setData(0, GROUP_NAME_ROLE, qt_new_name); QWidget* const widget = m_tree_widget->itemWidget(item, 1); widget->setProperty(s_group_property_name, qt_new_name); item->setText(0, qt_new_name); m_tree_widget->blockSignals(false); } //------------------------------------------------------------------------------ void landmarks::modify_group(std::string _name) const { QTreeWidgetItem* const item = get_group_item(_name); item->setText(0, _name.c_str()); landmarks_or_image_series_const_lock li_lock = const_lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); std::optional maybe_group = get_group(li_lock, _name); if(!maybe_group.has_value()) { return; } data::landmarks::landmarks_group group = *maybe_group; auto* const color_button = dynamic_cast(m_tree_widget->itemWidget(item, 1)); const QColor color = convert_to_q_color(group.m_color); set_color_button_icon(color_button, color); bool group_selected = item->isSelected(); if(m_advanced_mode) // Check if a child is selected. { for(int i = 0 ; i < item->childCount() && !group_selected ; ++i) { group_selected = item->child(i)->isSelected(); } } if(group_selected) { // Set widget values m_size_slider->setValue(static_cast(group.m_size)); m_visibility_checkbox->setChecked(group.m_visibility); const QString shape_text = group.m_shape == data::landmarks::shape::cube ? "Cube" : "Sphere"; m_shape_selector->setCurrentText(shape_text); const float opacity = group.m_color[3]; m_opacity_slider->setValue(static_cast(opacity * float(m_opacity_slider->maximum()))); } } //------------------------------------------------------------------------------ void landmarks::modify_point(std::string _group_name, std::size_t _index) const { if(m_advanced_mode) { auto const item_list = m_tree_widget->findItems(QString::fromStdString(_group_name), Qt::MatchExactly); SIGHT_ASSERT("Only a single item can be named '" + _group_name + "'", item_list.size() == 1); QTreeWidgetItem* const item = item_list.at(0); SIGHT_ASSERT("Index must be less than " << item->childCount(), static_cast(_index) < item->childCount()); QTreeWidgetItem* const point_item = item->child(static_cast(_index)); landmarks_or_image_series_const_lock li_lock = const_lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); std::optional maybe_point = get_point(li_lock, _group_name, _index); if(!maybe_point.has_value()) { return; } data::landmarks::point_t point = *maybe_point; m_tree_widget->blockSignals(true); for(int i = 0 ; i < 3 ; ++i) { point_item->setText(i, QString("%1").arg(point[static_cast(i)])); } m_tree_widget->blockSignals(false); } } //------------------------------------------------------------------------------ void landmarks::select_point(std::string _group_name, std::size_t _index) const { m_tree_widget->blockSignals(true); QTreeWidgetItem* current_item = get_group_item(_group_name); if(m_advanced_mode) { SIGHT_ASSERT( "Index must be less than " << current_item->childCount(), static_cast(_index) < current_item->childCount() ); current_item = current_item->child(static_cast(_index)); } m_tree_widget->setCurrentItem(current_item); int size = 0; bool visible = false; QString shape_text; float opacity = NAN; { landmarks_or_image_series_const_lock li_lock = const_lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); std::optional maybe_group = get_group(li_lock, _group_name); if(!maybe_group.has_value()) { return; } data::landmarks::landmarks_group group = *maybe_group; size = static_cast(group.m_size); visible = group.m_visibility; shape_text = group.m_shape == data::landmarks::shape::cube ? "Cube" : "Sphere"; opacity = group.m_color[3]; } // Set widget values m_size_slider->setValue(size); m_visibility_checkbox->setChecked(visible); m_shape_selector->setCurrentText(shape_text); m_opacity_slider->setValue(static_cast(opacity * float(m_opacity_slider->maximum()))); m_group_editor_widget->setEnabled(true); m_tree_widget->blockSignals(false); } //------------------------------------------------------------------------------ void landmarks::deselect_point(std::string /*unused*/, std::size_t /*unused*/) const { m_tree_widget->blockSignals(true); m_tree_widget->setCurrentItem(nullptr); m_group_editor_widget->setDisabled(true); m_tree_widget->blockSignals(false); } //------------------------------------------------------------------------------ std::string landmarks::generate_new_group_name() const { std::size_t group_count = 0; // const auto landmarks = m_landmarks.lock(); landmarks_or_image_series_const_lock li_lock = const_lock(); SIGHT_ASSERT( "Neither inout '" << LANDMARKS_INOUT << "' nor inout '" << IMAGE_SERIES_INOUT << "' exists.", li_lock.landmarks != nullptr || li_lock.image_series != nullptr ); const data::landmarks::GroupNameContainer group_names = get_group_names(li_lock); const std::string new_group_name_prefix = m_advanced_mode ? "Group_" : "Point_"; while(std::find( group_names.begin(), group_names.end(), new_group_name_prefix + std::to_string(group_count) ) != group_names.end()) { ++group_count; } return new_group_name_prefix + std::to_string(group_count); } //------------------------------------------------------------------------------ std::array landmarks::generate_new_color() { const std::array color = { m_distributor(m_generator), m_distributor(m_generator), m_distributor(m_generator), m_default_landmark_opacity }; return color; } //------------------------------------------------------------------------------ bool landmarks::current_selection(std::string& _selection) const { QTreeWidgetItem* item = m_tree_widget->currentItem(); const bool selected_group = (item != nullptr); if(selected_group) { const int top_level_index = m_tree_widget->indexOfTopLevelItem(item); if(m_advanced_mode && top_level_index == -1) { item = item->parent(); } _selection = item->text(0).toStdString(); } return selected_group; } //------------------------------------------------------------------------------ QColor landmarks::convert_to_q_color(const data::landmarks::color_t& _color) { return { static_cast(_color[0] * 255), static_cast(_color[1] * 255), static_cast(_color[2] * 255), static_cast(_color[3] * 255) }; } //------------------------------------------------------------------------------ void landmarks::set_color_button_icon(QPushButton* _button, const QColor& _color) { const int icon_size = _button->style()->pixelMetric(QStyle::PM_LargeIconSize); QPixmap pix(icon_size, icon_size); pix.fill(_color); _button->setIcon(QIcon(pix)); } //------------------------------------------------------------------------------ QTreeWidgetItem* landmarks::get_group_item(const std::string& _group_name) const { const auto item_list = m_tree_widget->findItems(QString::fromStdString(_group_name), Qt::MatchExactly); SIGHT_ASSERT("Only a single item can be named '" + _group_name + "'", item_list.size() == 1); return item_list.at(0); } //------------------------------------------------------------------------------ landmarks_or_image_series_lock landmarks::lock() { return {.landmarks = m_landmarks.lock(), .image_series = m_image_series.lock()}; } //------------------------------------------------------------------------------ landmarks_or_image_series_const_lock landmarks::const_lock() const { return {.landmarks = m_landmarks.const_lock(), .image_series = m_image_series.const_lock()}; } } // namespace sight::module::ui::qt::metrics sight-25.1.0/module/ui/qt/metrics/landmarks.hpp000066400000000000000000000402651503402212300213750ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2024 IRCAD France * Copyright (C) 2017-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::metrics { struct landmarks_or_image_series_const_ptr { data::landmarks::csptr landmarks; data::image_series::csptr image_series; }; struct landmarks_or_image_series_const_lock { data::mt::locked_ptr landmarks; data::mt::locked_ptr image_series; operator landmarks_or_image_series_const_ptr() const { return {.landmarks = landmarks.get_shared(), .image_series = image_series.get_shared()}; } }; struct landmarks_or_image_series_ptr { data::landmarks::sptr landmarks; data::image_series::sptr image_series; operator landmarks_or_image_series_const_ptr() const { return {.landmarks = landmarks, .image_series = image_series}; } }; struct landmarks_or_image_series_lock { data::mt::locked_ptr landmarks; data::mt::locked_ptr image_series; operator landmarks_or_image_series_ptr() const { return {.landmarks = landmarks.get_shared(), .image_series = image_series.get_shared()}; } operator landmarks_or_image_series_const_ptr() const { return {.landmarks = landmarks.get_shared(), .image_series = image_series.get_shared()}; } }; struct image_or_image_series_const_ptr { data::image::csptr image; data::image_series::csptr image_series; }; struct image_or_image_series_const_lock { data::mt::locked_ptr image; data::mt::locked_ptr image_series; operator image_or_image_series_const_ptr() const { return {.image = image.get_shared(), .image_series = image_series.get_shared()}; } }; struct image_or_image_series_ptr { data::image::csptr image; data::image_series::sptr image_series; operator image_or_image_series_const_ptr() const { return {.image = image, .image_series = image_series}; } }; struct image_or_image_series_lock { data::mt::locked_ptr image; data::mt::locked_ptr image_series; operator image_or_image_series_ptr() const { return {.image = image.get_shared(), .image_series = image_series.get_shared()}; } operator image_or_image_series_const_ptr() const { return {.image = image.get_shared(), .image_series = image_series.get_shared()}; } }; /** * @brief This service defines a graphical editor to edit landmarks. * * @section Slots Slots * - \b add_point(std::string): adds a point to editor. * - \b modifyPoint(std::string, std::size_t): updates the editor when a point has moved. * - \b selectPoint(std::string, std::size_t): selects a point in the editor. * - \b deselectPoint(std::string, std::size_t): deselect a point in the editor. * - \b removePoint(std::string, std::size_t): removes a point from editor. * - \b add_group(std::string): adds a group to the editor. * - \b remove_group(std::string): removes a group from the editor. * - \b modifyGroup(std::string): updates a group attributes. * - \b rename_group(std::string, std::string): renames a group in the editor. * * @section Signals Signals * - \b group_selected(std::string groupName): triggered when a new group is selected from the editor * * @section XML XML Configuration * * @code{.xml} Use 'Ctrl+Left Click' to add new landmarks 10.0 0.5 true @endcode * * @subsection In In * - \b matrix [sight::data::matrix4] (optional): Matrix used to compute transformation from the picked * point to the landmarks * * @subsection In-Out In-Out * - \b landmarks [sight::data::landmarks]: the landmarks structure on which this editor is working. * - \b imageSeries [sight::data::image_series]: the imageSeries structure on which this editor is working. * Either landmarks or imageSeries parameter must be set. * - \b currentLandmark [sight::data::point]: (optional) the coordinates of the currently selected landmark. * It is updated when a new landmark is created, or when a double click is made on an existing landmark. * * @subsection Configuration Configuration * - \b text (optional): text displayed at the top of this editor. * - \b size (optional, default="10.0"): default size of created landmarks. * - \b opacity (optional, default="1.0"): default opacity of created landmarks. * - \b advanced (optional, default="false"): if "true", use the advanced mode displaying point information * and groups with multiple points. */ class landmarks final : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(landmarks, sight::ui::editor); /// Initializes slots. landmarks() noexcept; /// Destroys the service. ~landmarks() noexcept override; /// Configures the service. void configuring() override; /// Installs the layout. void starting() override; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::landmarks::MODIFIED_SIG of s_LANDMARKS_INOUT to * module::ui::qt::metrics::landmarks::service::slots::UPDATE * Connect data::landmarks::POINT_ADDED_SIG of s_LANDMARKS_INOUT to * module::ui::qt::metrics::landmarks::ADD_POINT_SLOT * Connect data::landmarks::POINT_MODIFIED_SIG of s_LANDMARKS_INOUT to * module::ui::qt::metrics::landmarks::MODIFY_POINT_SLOT * Connect data::landmarks::POINT_SELECTED_SIG of s_LANDMARKS_INOUT to * module::ui::qt::metrics::landmarks::SELECT_POINT_SLOT * Connect data::landmarks::POINT_DESELECTED_SIG of s_LANDMARKS_INOUT to * module::ui::qt::metrics::landmarks::DESELECT_POINT_SLOT * Connect data::landmarks::GROUP_ADDED_SIG of s_LANDMARKS_INOUT to * module::ui::qt::metrics::landmarks::ADD_GROUP_SLOT * Connect data::landmarks::GROUP_REMOVED_SIG of s_LANDMARKS_INOUT to * module::ui::qt::metrics::landmarks::REMOVE_GROUP_SLOT * Connect data::landmarks::POINT_REMOVED_SIG of s_LANDMARKS_INOUT to * module::ui::qt::metrics::landmarks::REMOVE_POINT_SLOT * Connect data::landmarks::GROUP_MODIFIED_SIG of s_LANDMARKS_INOUT to * module::ui::qt::metrics::landmarks::MODIFY_GROUP_SLOT * Connect data::landmarks::GROUP_RENAMED_SIG of s_LANDMARKS_INOUT to * module::ui::qt::metrics::landmarks::RENAME_GROUP_SLOT */ connections_t auto_connections() const override; /// Signal send when double clicked on a landmark, send its world coordinates; static const core::com::signals::key_t SEND_WORLD_COORD; using world_coordinates_signal_t = core::com::signal; static const core::com::signals::key_t GROUP_SELECTED; using group_selected_signal_t = core::com::signal; /// Resets the interface content and create connections between widgets and this service. void updating() override; /// Destroys the layout. void stopping() override; /// Called when a color button is clicked. void on_color_button(); /** * @brief Called when a group name is changed. * @param _item the changed item. * @param _column the changed column * * @pre _column must be 0. */ void on_group_name_edited(QTreeWidgetItem* _item, int _column); /** * @brief Called when a new group is selected in the editor. * @param _current the new selected item. * @param _previous the old selected item. */ void on_selection_changed(QTreeWidgetItem* _current, QTreeWidgetItem* _previous); /** * @brief Called when double clicked on a landmark * * @param _item clicked item (landmark) * @param _column column num (not used) */ void on_landmark_double_clicked(QTreeWidgetItem* _item, int _column) const; /** * @brief Called when a new landmark is set as current one (double click or pick) * * @param _world_coord coordinate of the current landmark */ void update_current_landmark(data::landmarks::point_t& _world_coord) const; /** * @brief Called when a group's point size is modified. * @param _new_size the new size of the group. */ void on_size_changed(int _new_size); /** * @brief Called when a group's opacity is modified. * @param _new_opacity the new opacity of the group. */ void on_opacity_changed(int _new_opacity); /** * @brief Called when a group's visibility is turned on or off. * @param _visibility the visibility status */ void on_visibility_changed(int _visibility); /** * @brief Called when the landmarks' shape is changed for a group. * @param _shape the new shape of the group. * * @pre _shape must be 'Cube' or 'Sphere'. */ void on_shape_changed(const QString& _shape); /// Called when the new group button is pressed, adds an empty landmark group in our data. void on_add_new_group(); /// Called when the remove button is pressed, deletes selected group or point. void on_remove_selection(); /** * @brief SLOT: adds a point to the editor. * @param _group_name the group name where the point is added. */ void add_point(std::string _group_name) const; /** * @brief Slot: updates a point coordinates in the editor. * @param _group_name the group name of the updated point. * @param _index the index of the point to update. */ void modify_point(std::string _group_name, std::size_t _index) const; /** * @brief SLOT: selects the point's corresponding item in the editor. * @param _group_name the group name of the selected point. * @param _index the index of the point to select. */ void select_point(std::string _group_name, std::size_t _index) const; /// Slot: deselects the currently selected item. void deselect_point(std::string /*unused*/, std::size_t /*unused*/) const; /** * @brief Slot: adds a landmark group to the editor. * @param _name Name of the new group. */ void add_group(std::string _name) const; /** * @brief SLOT: removes a group from the editor. * @param _name The group name to remove. */ void remove_group(std::string _name) const; /** * @brief SLOT: removes point from editor * @param _group_name the group name of the point the remove. * @param _index the index of the point to remove. */ void remove_point(std::string _group_name, std::size_t _index) const; /** * @brief SLOT: renames a group in the editor. * @param _old_name the old name of the group. * @param _new_name the new name of the group. */ void rename_group(std::string _old_name, std::string _new_name) const; /** * @brief SLOT: updates a group properties in the editor. * @param _name The group name to updates. */ void modify_group(std::string _name) const; /** * @brief Gets the name of the currently selected group. * @param [out] _selection the name of the currently selected group. * @return false if no group is selected. */ bool current_selection(std::string& _selection) const; /** * @brief Gets tree item representing the group. * @param _group_name the name of the item to find. * @return The item representing _groupName. */ QTreeWidgetItem* get_group_item(const std::string& _group_name) const; /** * @brief Generates a group name that doesn't exist already. * @return A group name that doesn't exist already. */ std::string generate_new_group_name() const; /** * @brief Generates a new random color. * @return An array of 4 float (between 0.f and 1.f) representing an rgba color. */ std::array generate_new_color(); /** * @brief Converts a landmark color to a QColor. * @param _color The landmarks color type. * @return A QColor with same colour value than _color. */ static QColor convert_to_q_color(const data::landmarks::color_t& _color); /** * @brief Draws a colored square on the button. * @param _button the button where the square will be drawn. * @param _color the color of the square. */ static void set_color_button_icon(QPushButton* _button, const QColor& _color); landmarks_or_image_series_lock lock(); landmarks_or_image_series_const_lock const_lock() const; /// Contains a tree representing landmarks sorted by their groups. QPointer m_tree_widget; /// Contains all widgets. QPointer m_group_editor_widget; /// Contains the slider used to change the size of the current selected group. /// @see onSizeChanged(int) QPointer m_size_slider; /// Contains the slider used to change the opacity of the current selected group. /// @see onOpacityChanged(int) QPointer m_opacity_slider; /// Contains the button used to change the visibility of the current selected group. /// @see onVisibilityChanged(int) QPointer m_visibility_checkbox; /// Contains the combo box used to change the shape of the current selected group. /// @see onShapeChanged(const QString&) QPointer m_shape_selector; /// Contains the button used to adds a new empty group. /// @see onAddNewGroup() QPointer m_new_group_button; /// Contains the button used to remove a group or a landmark. /// @see onRemoveSelection() QPointer m_remove_button; /// Enables/disables the advanced mode. bool m_advanced_mode {false}; /// Sets the default landmark size. float m_default_landmark_size {10.F}; /// Sets the default landmark opacity. float m_default_landmark_opacity {1.F}; /// Sets the text displayed at the top of this editor. std::string m_text; static constexpr std::string_view LANDMARKS_INOUT = "landmarks"; static constexpr std::string_view IMAGE_SERIES_INOUT = "imageSeries"; static constexpr std::string_view MATRIX_IN = "matrix"; static constexpr std::string_view CURRENT_LANDMARK_INOUT = "currentLandmark"; data::ptr m_matrix {this, MATRIX_IN, true}; data::ptr m_landmarks {this, LANDMARKS_INOUT}; data::ptr m_image_series {this, IMAGE_SERIES_INOUT}; data::ptr m_current_landmark {this, CURRENT_LANDMARK_INOUT, true}; /// Used to generate random color std::uniform_real_distribution m_distributor {0.0F, 1.0F}; std::mt19937 m_generator {std::random_device {}()}; }; } // namespace sight::module::ui::qt::metrics sight-25.1.0/module/ui/qt/model/000077500000000000000000000000001503402212300163335ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/model/model_series_list.cpp000066400000000000000000000404071503402212300225510ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "model_series_list.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::model { //------------------------------------------------------------------------------ static const core::com::signals::key_t RECONSTRUCTION_SELECTED_SIG = "reconstruction_selected"; static const core::com::signals::key_t EMPTIED_SELECTION_SIG = "emptied_selection"; static const core::com::slots::key_t SHOW_RECONSTRUCTIONS_SLOT = "showReconstructions"; //------------------------------------------------------------------------------ model_series_list::model_series_list() noexcept { m_sig_reconstruction_selected = new_signal(RECONSTRUCTION_SELECTED_SIG); m_sig_emptied_selection = new_signal(EMPTIED_SELECTION_SIG); new_slot(SHOW_RECONSTRUCTIONS_SLOT, &model_series_list::show_reconstructions, this); } //------------------------------------------------------------------------------ void model_series_list::configuring() { this->initialize(); const config_t config_t = this->get_config(); const auto config = config_t.get_child_optional("config."); if(config) { m_enable_hide_all = config->get("enable_hide_all", m_enable_hide_all); m_enable_delete = config->get("enable_delete", m_enable_delete); } auto columns = config_t.get_child("columns"); for(auto it_col = columns.begin() ; it_col != columns.end() ; ++it_col) { m_headers << QString::fromStdString(it_col->first); } } //------------------------------------------------------------------------------ void model_series_list::starting() { this->create(); const QString service_id = QString::fromStdString(base_id()); auto qt_container = std::dynamic_pointer_cast(this->get_container()); qt_container->get_qt_container()->setObjectName(service_id); auto* layout = new QVBoxLayout; auto* layout_button = new QHBoxLayout; layout->addLayout(layout_button); m_tree = new QTreeWidget(); m_tree->setColumnCount(static_cast(m_headers.size())); m_tree->setHeaderLabels(m_headers); if(m_enable_hide_all) { // check box "show" m_show_check_box = new QCheckBox(tr("Hide all organs")); m_show_check_box->setObjectName(service_id + "/" + m_show_check_box->text()); m_show_check_box->setToolTip(tr("Show or hide all organs")); layout_button->addWidget(m_show_check_box, 0); QObject::connect(m_show_check_box, &QCheckBox::stateChanged, this, &self_t::on_show_reconstructions); m_check_all_button = new QPushButton(tr("Check all")); m_check_all_button->setObjectName(service_id + "/" + m_check_all_button->text()); layout_button->addWidget(m_check_all_button, 0); QObject::connect(m_check_all_button, &QPushButton::clicked, this, &self_t::on_check_all_check_box); m_un_check_all_button = new QPushButton(tr("UnCheck all")); m_un_check_all_button->setObjectName(service_id + "/" + m_un_check_all_button->text()); layout_button->addWidget(m_un_check_all_button, 0); QObject::connect(m_un_check_all_button, &QPushButton::clicked, this, &self_t::on_un_check_all_check_box); } if(m_enable_delete) { m_delete_all_button = new QPushButton(tr("Delete all")); m_delete_all_button->setObjectName(service_id + "/" + m_delete_all_button->text()); layout_button->addWidget(m_delete_all_button, 0); QObject::connect(m_delete_all_button, &QPushButton::clicked, this, &self_t::on_delete_all_check_box); } layout->addWidget(m_tree, 1); qt_container->set_layout(layout); QObject::connect( m_tree, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(on_current_item_changed(QTreeWidgetItem*,QTreeWidgetItem*)) ); this->updating(); QObject::connect( m_tree, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(on_current_item_changed(QTreeWidgetItem*,int)) ); if(m_enable_delete) { m_tree->setContextMenuPolicy(Qt::CustomContextMenu); QObject::connect( m_tree, &QTreeWidget::customContextMenuRequested, this, &model_series_list::on_custom_context_menu_requested ); } } //------------------------------------------------------------------------------ service::connections_t model_series_list::auto_connections() const { return { {MODEL_SERIES, data::model_series::MODIFIED_SIG, service::slots::UPDATE}, {MODEL_SERIES, data::model_series::RECONSTRUCTIONS_ADDED_SIG, service::slots::UPDATE}, {MODEL_SERIES, data::model_series::RECONSTRUCTIONS_REMOVED_SIG, service::slots::UPDATE} }; } //------------------------------------------------------------------------------ void model_series_list::updating() { m_tree->blockSignals(true); this->update_reconstructions(); this->refresh_visibility(); m_tree->blockSignals(false); } //------------------------------------------------------------------------------ void model_series_list::stopping() { if(m_enable_hide_all) { QObject::disconnect(m_show_check_box, &QCheckBox::stateChanged, this, &self_t::on_show_reconstructions); QObject::disconnect(m_check_all_button, &QPushButton::clicked, this, &self_t::on_check_all_check_box); QObject::disconnect(m_un_check_all_button, &QPushButton::clicked, this, &self_t::on_un_check_all_check_box); } if(m_enable_delete) { QObject::disconnect(m_delete_all_button, &QPushButton::clicked, this, &self_t::on_delete_all_check_box); QObject::disconnect( m_tree, &QTreeWidget::customContextMenuRequested, this, &self_t::on_custom_context_menu_requested ); } QObject::disconnect( m_tree, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(on_current_item_changed(QTreeWidgetItem*,int)) ); QObject::disconnect( m_tree, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(on_current_item_changed(QTreeWidgetItem*,QTreeWidgetItem*)) ); this->destroy(); } //------------------------------------------------------------------------------ void model_series_list::update_reconstructions() { auto qt_container = std::dynamic_pointer_cast(this->get_container()); QWidget* const container = qt_container->get_qt_container(); SIGHT_ASSERT("container not instanced", container); auto model_series = m_model_series.lock(); bool has_reconstructions = !model_series->get_reconstruction_db().empty(); container->setEnabled(has_reconstructions); this->fill_tree(model_series); if(has_reconstructions) { if(m_show_check_box != nullptr) { m_show_check_box->blockSignals(true); const bool show_all_rec = model_series->get_field("ShowReconstructions", std::make_shared(true))->value(); m_show_check_box->setCheckState(show_all_rec ? Qt::Unchecked : Qt::Checked); m_show_check_box->blockSignals(false); } } } //------------------------------------------------------------------------------ void model_series_list::fill_tree(const data::mt::locked_ptr& _model_series) { const auto& reconstructions = _model_series->get_reconstruction_db(); if(!m_tree->selectedItems().empty()) { m_sig_emptied_selection->async_emit(); } m_tree->clear(); // Create items for(auto const& reconstruction : reconstructions) { QStringList info; for(auto const& column : m_headers) { if(column == "organ_name") { info << QString::fromStdString(reconstruction->get_organ_name()); } else if(column == "structure_type") { info << QString::fromStdString(reconstruction->get_structure_type()); } else if(column == "volume") { auto volume = reconstruction->get_computed_mask_volume(); info << (volume < 0 ? "unknown" : QString::fromStdString(std::to_string(volume))); } } auto* item = new QTreeWidgetItem(info); item->setCheckState(0, Qt::Unchecked); m_tree->addTopLevelItem(item); item->setData(0, Qt::UserRole, QString::fromStdString(reconstruction->get_id())); } for(int i = 0 ; i < m_tree->topLevelItemCount() ; i++) { m_tree->resizeColumnToContents(i); } } //------------------------------------------------------------------------------ void model_series_list::on_current_item_changed(QTreeWidgetItem* _current, QTreeWidgetItem* /*unused*/) { SIGHT_ASSERT("Current selected item is null", _current); std::string id = _current->data(0, Qt::UserRole).toString().toStdString(); data::reconstruction::sptr rec = std::dynamic_pointer_cast(core::id::get_object(id)); m_sig_reconstruction_selected->async_emit(rec); } //------------------------------------------------------------------------------ void model_series_list::on_current_item_changed(QTreeWidgetItem* _current, int _column) { sight::module::ui::qt::model::model_series_list::on_organ_choice_visibility(_current, _column); } //------------------------------------------------------------------------------ void model_series_list::on_organ_choice_visibility(QTreeWidgetItem* _item, int /*unused*/) { std::string id = _item->data(0, Qt::UserRole).toString().toStdString(); data::reconstruction::sptr rec = std::dynamic_pointer_cast(core::id::get_object(id)); SIGHT_ASSERT("rec not instanced", rec); const bool item_is_checked = (_item->checkState(0) == Qt::Checked); if(rec->get_is_visible() != item_is_checked) { rec->set_is_visible(item_is_checked); data::reconstruction::visibility_modified_signal_t::sptr sig; sig = rec->signal( data::reconstruction::VISIBILITY_MODIFIED_SIG ); sig->async_emit(item_is_checked); } } //------------------------------------------------------------------------------ void model_series_list::on_show_reconstructions(int _state) { const bool visible = static_cast(_state); m_check_all_button->setEnabled(!visible); m_un_check_all_button->setEnabled(!visible); m_tree->setEnabled(!visible); { auto model_series = m_model_series.lock(); data::helper::field helper(model_series.get_shared()); helper.add_or_swap("ShowReconstructions", std::make_shared(_state == Qt::Unchecked)); } } //------------------------------------------------------------------------------ void model_series_list::refresh_visibility() { for(int i = 0 ; i < m_tree->topLevelItemCount() ; ++i) { QTreeWidgetItem* item = m_tree->topLevelItem(i); std::string id = item->data(0, Qt::UserRole).toString().toStdString(); data::reconstruction::sptr rec = std::dynamic_pointer_cast(core::id::get_object(id)); item->setCheckState(0, rec->get_is_visible() ? Qt::Checked : Qt::Unchecked); } } //------------------------------------------------------------------------------ void model_series_list::show_reconstructions(bool _show) { if(m_show_check_box != nullptr) { m_show_check_box->setCheckState(_show ? Qt::Unchecked : Qt::Checked); } } //------------------------------------------------------------------------------ void model_series_list::on_check_all_check_box() { this->on_check_all_boxes(true); } //------------------------------------------------------------------------------ void model_series_list::on_un_check_all_check_box() { this->on_check_all_boxes(false); } //------------------------------------------------------------------------------ void model_series_list::on_check_all_boxes(bool _visible) { for(int i = 0 ; i < m_tree->topLevelItemCount() ; ++i) { QTreeWidgetItem* item = m_tree->topLevelItem(i); item->setCheckState(0, _visible ? Qt::Checked : Qt::Unchecked); } } //------------------------------------------------------------------------------ void model_series_list::on_delete_all_check_box() { auto model_series = m_model_series.lock(); // Remove all reconstructions. data::model_series::reconstruction_vector_t reconstructions = model_series->get_reconstruction_db(); model_series->set_reconstruction_db(data::model_series::reconstruction_vector_t()); // Send the signals. auto sig = model_series->signal( data::model_series::RECONSTRUCTIONS_REMOVED_SIG ); sig->async_emit(reconstructions); } //------------------------------------------------------------------------------ void model_series_list::on_custom_context_menu_requested(const QPoint& _pos) { QModelIndex index = m_tree->indexAt(_pos); if(index.isValid()) { auto* const delete_action = new QAction("Delete"); QObject::connect( delete_action, &QAction::triggered, this, [index, this]() { auto model_series = m_model_series.lock(); data::model_series::reconstruction_vector_t deleted_reconstructions; // Remove reconstruction. data::model_series::reconstruction_vector_t reconstructions = model_series->get_reconstruction_db(); const auto rec_it = reconstructions.begin() + index.row(); const data::reconstruction::sptr reconstruction = *rec_it; reconstructions.erase(rec_it); model_series->set_reconstruction_db(reconstructions); // Send the signals. deleted_reconstructions.push_back(reconstruction); auto sig = model_series->signal( data::model_series::RECONSTRUCTIONS_REMOVED_SIG ); sig->async_emit(deleted_reconstructions); }); QMenu context_menu; context_menu.addAction(delete_action); context_menu.exec(QCursor::pos()); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::model sight-25.1.0/module/ui/qt/model/model_series_list.hpp000066400000000000000000000176461503402212300225670ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include class QTreeWidget; class QCheckBox; class QListWidgetItem; class QTreeWidgetItem; namespace sight::module::ui::qt::model { /** * @brief Editor displaying the list of the organs in a ModelSeries. * * It allows to show/hide a Reconstruction when it is associated to a render scene with a Model series adaptor * (for example fwRenderVTK::render with ::visuVTKAdaptor::model_series). * It also allows to select a Reconstruction: associated to the module::data::select_object, the * reconstruction is available to be used by other services. * It is mostly associated to module::ui::qt::reconstruction::organ_material_editor and * module::ui::qt::reconstruction::RepresentationEditor to update the reconstrution color, transparency, .... * * @section Signals Signals * - \b reconstruction_selected(data::object::sptr): this signal emits the selected reconstruction * - \b emptied_selection(): this signal is emitted when no reconstruction is selected * * @section Slots Slots * - \b showReconstructions(bool): slot called to show or hide all the reconstructions * * @section XML XML Configuration * @code{.xml} true @endcode * * @subsection In-Out In-Out * - \b model_series [sight::data::model_series]: model series containing the organs to list * * @subsection Configuration Configuration * - \b enable_hide_all (optional, bool, default=true): if 'true', allows to hide all models through a single checkbox * displayed in UI. * - \b enable_delete (optional, bool, default=false): if 'true', allows to delete models through a single button * displayed in UI. * - \b column (optional, string, default=""): defines columns to be shown in reconstruction list. XML child element * names follow data::reconstruction serialization attribute names. * The name of the tag will be used as the column name. * The attribute 'view' is optional and has the following values: * - positive: a numeric value is displayed only if it is positive. Otherwise, 'Unknown' is displayed. */ class model_series_list final : public QObject, public sight::ui::editor { Q_OBJECT public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(model_series_list, sight::ui::editor); /// Initializes the slot and signals. model_series_list() noexcept; /// Cleans ressources. ~model_series_list() noexcept final = default; protected: /// Configures the editor. void configuring() final; /// Creates layouts. void starting() final; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::model_series::MODIFIED_SIG of s_MODEL_SERIES_INOUT to service::slots::UPDATE. * Connect data::model_series::RECONSTRUCTIONS_ADDED_SIG of s_MODEL_SERIES_INOUT to service::slots::UPDATE. * Connect data::model_series::RECONSTRUCTIONS_REMOVED_SIG of s_MODEL_SERIES_INOUT to service::slots::UPDATE. */ connections_t auto_connections() const final; /** * @brief Refreshes the editor. * @see updateReconstructions(). * @see refreshVisibility(). */ void updating() final; /// Disconnects connections. void stopping() final; private: /// Updates reconstructions. void update_reconstructions(); /// Fills the editor tree. void fill_tree(const data::mt::locked_ptr& _model_series); /// SLOT: Shows (or hide) reconstructions. void show_reconstructions(bool _show); /// Refreshes reconstructions visibility on the editor. void refresh_visibility(); /** * @brief Checks or unchecks reconstructions. * @param _visible the checked status. * @see onCheckAllCheckBox(). * @see onUnCheckAllCheckBox(). */ void on_check_all_boxes(bool _visible); private Q_SLOTS: /// Changes the current item, called when new current item is setted in m_organChoice. void on_current_item_changed(QTreeWidgetItem* _current, QTreeWidgetItem* /*unused*/); /// Changes the current item, called when new current item is setted in m_organChoice. static void on_current_item_changed(QTreeWidgetItem* _current, int _column); /// Shows reconstructions, called when m_showCheckBox is clicked. void on_show_reconstructions(int _state); /// Shows a reconstruction, called when new current item is setted in m_organChoice. static void on_organ_choice_visibility(QTreeWidgetItem* _item, int /*unused*/); /// Shows reconstructions, called when m_checkAllButton is clicked. void on_check_all_check_box(); /// Shows reconstructions, called when m_unCheckAllButton is clicked. void on_un_check_all_check_box(); /// Deletes all reconstructions, called when m_deleteAllButton is clicked. void on_delete_all_check_box(); /// Opens a context menu to deletes a specific reconstruction. void on_custom_context_menu_requested(const QPoint& _pos); private: /// Contains the button to check all reconstructions. QPointer m_check_all_button; /// Contains the button to uncheck all reconstructions. QPointer m_un_check_all_button; /// Contains the button to delete all reconstructions. QPointer m_delete_all_button; /// Contains the button to hide or show all reconstructions. QPointer m_show_check_box; /// Contains the reconstructions tree: QPointer m_tree; /// Enables m_showCheckBox. bool m_enable_hide_all {true}; /// Enables m_deleteAllButton. bool m_enable_delete {false}; /// Defines the header of the tree. QStringList m_headers; /// Contains the signal emitted when a reconstruction is selected. using reconstruction_selected_signal_t = core::com::signal; reconstruction_selected_signal_t::sptr m_sig_reconstruction_selected; /// Contains the signal emitted when we clean the list. using emptied_selection_signal_t = core::com::signal; emptied_selection_signal_t::sptr m_sig_emptied_selection; /// Contains the slot to show (or hide) reconstructions. using show_reconstructions_slot_t = core::com::slot; show_reconstructions_slot_t::sptr m_slot_show_reconstructions; static constexpr std::string_view MODEL_SERIES = "model_series"; data::ptr m_model_series {this, "model_series"}; }; } // namespace sight::module::ui::qt::model sight-25.1.0/module/ui/qt/model/organ_transformation.cpp000066400000000000000000000303501503402212300232740ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "organ_transformation.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::model { static const std::string MATRIX_FIELD_NAME = "TransformMatrix"; organ_transformation::organ_transformation() noexcept : m_save_button(nullptr), m_load_button(nullptr), m_reset_button(nullptr), m_reconstruction_list_box(nullptr) { } //------------------------------------------------------------------------------ organ_transformation::~organ_transformation() noexcept = default; //------------------------------------------------------------------------------ void organ_transformation::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void organ_transformation::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); auto* layout = new QVBoxLayout(); auto* group_box = new QGroupBox(tr("Organs")); layout->addWidget(group_box); auto* layout_group_box = new QVBoxLayout(); group_box->setLayout(layout_group_box); m_select_all_check_box = new QCheckBox(tr("Select All")); m_reconstruction_list_box = new QListWidget(group_box); m_reset_button = new QPushButton(tr("Reset")); m_save_button = new QPushButton(tr("Save")); m_load_button = new QPushButton(tr("Load")); m_save_selection_combo_box = new QComboBox(); QObject::connect(m_reconstruction_list_box, &QListWidget::itemClicked, this, &self_t::on_reconstruction_check); QObject::connect(m_reset_button, &QPushButton::clicked, this, &self_t::on_reset_click); QObject::connect(m_save_button, &QPushButton::clicked, this, &self_t::on_save_click); QObject::connect(m_load_button, &QPushButton::clicked, this, &self_t::on_load_click); QObject::connect(m_select_all_check_box, &QCheckBox::stateChanged, this, &self_t::on_select_all_changed); layout_group_box->addWidget(m_select_all_check_box, 0); layout_group_box->addWidget(m_reconstruction_list_box, 1); layout_group_box->addWidget(m_reset_button, 0); layout_group_box->addWidget(m_save_button, 0); layout_group_box->addWidget(m_save_selection_combo_box, 0); layout_group_box->addWidget(m_load_button, 0); qt_container->set_layout(layout); this->updating(); } //------------------------------------------------------------------------------ void organ_transformation::stopping() { QObject::disconnect(m_reconstruction_list_box, &QListWidget::itemClicked, this, &self_t::on_reconstruction_check); QObject::disconnect(m_reset_button, &QPushButton::clicked, this, &self_t::on_reset_click); QObject::disconnect(m_save_button, &QPushButton::clicked, this, &self_t::on_save_click); QObject::disconnect(m_load_button, &QPushButton::clicked, this, &self_t::on_load_click); QObject::disconnect(m_select_all_check_box, &QCheckBox::stateChanged, this, &self_t::on_select_all_changed); this->destroy(); } //------------------------------------------------------------------------------ void organ_transformation::updating() { this->add_mesh_transform(); this->refresh(); } //------------------------------------------------------------------------------ void organ_transformation::info(std::ostream& /*sstream*/) { } //------------------------------------------------------------------------------ void organ_transformation::refresh() { m_reconstruction_map.clear(); m_reconstruction_list_box->clear(); const auto series = m_model_series.lock(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); QWidget* const container = qt_container->get_qt_container(); SIGHT_ASSERT("container not instanced", container); bool has_reconstructions = !series->get_reconstruction_db().empty(); container->setEnabled(has_reconstructions); if(has_reconstructions) { const auto p_map = m_map.lock(); for(const data::reconstruction::sptr& rec : series->get_reconstruction_db()) { m_reconstruction_map[rec->get_organ_name()] = rec; } for(auto& it : m_reconstruction_map) { std::string organ_name = it.first; auto* item = new QListWidgetItem(QString::fromStdString(organ_name), m_reconstruction_list_box); if(p_map && p_map->find(organ_name) != p_map->end()) { item->setCheckState(Qt::Checked); } else { item->setCheckState(Qt::Unchecked); } m_reconstruction_list_box->addItem(item); } } } //------------------------------------------------------------------------------ void organ_transformation::notify_transformation_matrix(data::matrix4::sptr _a_trans_mat) { auto sig = _a_trans_mat->signal(data::object::MODIFIED_SIG); sig->async_emit(); } //------------------------------------------------------------------------------ void organ_transformation::on_reconstruction_check(QListWidgetItem* _current_item) { const auto p_map = m_map.lock(); if(p_map) { std::string item_name = _current_item->text().toStdString(); data::reconstruction::sptr p_reconstruction = m_reconstruction_map[item_name]; data::mesh::sptr p_mesh = p_reconstruction->get_mesh(); const auto scoped_emitter = p_map->scoped_emit(); if((_current_item->checkState()) == Qt::Checked) { p_map->insert_or_assign(item_name, p_mesh); } else { p_map->erase(item_name); } } } //------------------------------------------------------------------------------ void organ_transformation::on_reset_click() { const auto series = m_model_series.lock(); //search the corresponding triangular mesh for(const data::reconstruction::sptr& rec : series->get_reconstruction_db()) { data::mesh::sptr p_tmp_tr_mesh = rec->get_mesh(); data::matrix4::sptr p_tmp_mat = p_tmp_tr_mesh->get_field(MATRIX_FIELD_NAME); if(p_tmp_mat) { geometry::data::identity(*p_tmp_mat); notify_transformation_matrix(p_tmp_mat); } } } //------------------------------------------------------------------------------ void organ_transformation::on_save_click() { inner_mat_mapping_t mat_map; const auto series = m_model_series.lock(); if(!series->get_reconstruction_db().empty()) { for(const data::reconstruction::sptr& rec : series->get_reconstruction_db()) { data::mesh::sptr p_tmp_tr_mesh = rec->get_mesh(); data::matrix4::sptr p_tmp_mat = p_tmp_tr_mesh->get_field(MATRIX_FIELD_NAME); if(p_tmp_mat) { data::matrix4::sptr p_cpy_tmp_mat; p_cpy_tmp_mat = data::object::copy(p_tmp_mat); mat_map[p_tmp_tr_mesh->get_id()] = p_cpy_tmp_mat; } } std::stringstream tmp_save_name; tmp_save_name << "save_" << m_save_count; m_save_listing[tmp_save_name.str()] = mat_map; m_save_selection_combo_box->addItem(QString::fromStdString(tmp_save_name.str())); m_save_count++; } } //------------------------------------------------------------------------------ void organ_transformation::on_load_click() { if(m_save_selection_combo_box->count() != 0) { inner_mat_mapping_t mat_map = m_save_listing[m_save_selection_combo_box->currentText().toStdString()]; const auto series = m_model_series.lock(); //search the corresponding triangular mesh for(const data::reconstruction::sptr& rec : series->get_reconstruction_db()) { data::mesh::sptr p_tmp_tr_mesh = rec->get_mesh(); if(mat_map.find(p_tmp_tr_mesh->get_id()) != mat_map.end()) { data::matrix4::sptr p_tmp_mat = p_tmp_tr_mesh->get_field(MATRIX_FIELD_NAME); if(p_tmp_mat) { p_tmp_mat->shallow_copy(mat_map[p_tmp_tr_mesh->get_id()]); notify_transformation_matrix(p_tmp_mat); } } } } } //------------------------------------------------------------------------------ void organ_transformation::on_select_all_changed(int _state) { const auto p_map = m_map.lock(); const auto scoped_emitter = p_map->scoped_emit(); if(_state == Qt::Checked) { m_reconstruction_list_box->setEnabled(false); const auto series = m_model_series.lock(); for(const data::reconstruction::sptr& rec : series->get_reconstruction_db()) { p_map->insert({rec->get_organ_name(), rec->get_mesh()}); } } else if(_state == Qt::Unchecked) { m_reconstruction_list_box->setEnabled(true); QList item_list = m_reconstruction_list_box->findItems("", Qt::MatchContains); for(QListWidgetItem* item : item_list) { if(item->checkState() == Qt::Unchecked) { p_map->erase(item->text().toStdString()); } } this->refresh(); } } //------------------------------------------------------------------------------ void organ_transformation::add_mesh_transform() { const auto series = m_model_series.lock(); for(const data::reconstruction::sptr& rec : series->get_reconstruction_db()) { data::mesh::sptr mesh = rec->get_mesh(); if(!mesh->get_field(MATRIX_FIELD_NAME)) { data::helper::field field_helper(mesh); field_helper.set_field(MATRIX_FIELD_NAME, std::make_shared()); field_helper.notify(); } } } //------------------------------------------------------------------------------ service::connections_t organ_transformation::auto_connections() const { connections_t connections; connections.push(MODEL_SERIES, data::model_series::MODIFIED_SIG, service::slots::UPDATE); connections.push(MODEL_SERIES, data::model_series::RECONSTRUCTIONS_ADDED_SIG, service::slots::UPDATE); connections.push(MODEL_SERIES, data::model_series::RECONSTRUCTIONS_REMOVED_SIG, service::slots::UPDATE); connections.push(MAP, data::map::MODIFIED_SIG, service::slots::UPDATE); connections.push(MAP, data::map::ADDED_OBJECTS_SIG, service::slots::UPDATE); connections.push(MAP, data::map::CHANGED_OBJECTS_SIG, service::slots::UPDATE); connections.push(MAP, data::map::REMOVED_OBJECTS_SIG, service::slots::UPDATE); return connections; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::model sight-25.1.0/module/ui/qt/model/organ_transformation.hpp000066400000000000000000000107511503402212300233040ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include class QListWidget; class QPushButton; class QListWidgetItem; class QComboBox; class QCheckBox; namespace sight::module::ui::qt::model { /** * @brief Display the organs list and allow an interactive selection to set the corresponding meshes in a map * @section XML XML Configuration * @code{.xml} @endcode * @subsection InOut InOut * - \b modelSeries [sight::data::model_series]: modelSeries to modify. * - \b map [sight::data::map]: map. */ class organ_transformation : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(organ_transformation, sight::ui::editor); /// constructor organ_transformation() noexcept; /// destructor ~organ_transformation() noexcept override; protected: void configuring() override; void starting() override; void stopping() override; void updating() override; void info(std::ostream& _stream) override; /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection * * Connect ModelSeries::MODIFIED_SIG to this::service::slots::UPDATE * Connect ModelSeries::RECONSTRUCTIONS_ADDED_SIG to this::service::slots::UPDATE * Connect ModelSeries::RECONSTRUCTIONS_REMOVED_SIG to this::service::slots::UPDATE * Connect Map::MODIFIED_SIG to this::service::slots::UPDATE * Connect Map::ADDED_OBJECTS_SIG to this::service::slots::UPDATE * Connect Map::REMOVED_OBJECTS_SIG to this::service::slots::UPDATE * Connect Map::CHANGED_OBJECTS_SIG to this::service::slots::UPDATE */ connections_t auto_connections() const override; private Q_SLOTS: void on_reconstruction_check(QListWidgetItem* _current_item); void on_reset_click(); void on_save_click(); void on_load_click(); /// Slot to check all the organs of the list void on_select_all_changed(int _state); private: void refresh(); static void notify_transformation_matrix(data::matrix4::sptr _a_trans_mat); /// Create the transformation in mesh field. This field is used in the adaptors to transform the mesh void add_mesh_transform(); // reconstruction_map_t using reconstruction_map_t = std::map; using inner_mat_mapping_t = std::map; using save_mapping_t = std::map; reconstruction_map_t m_reconstruction_map; QPointer m_save_button; QPointer m_load_button; QPointer m_reset_button; QPointer m_reconstruction_list_box; QPointer m_save_selection_combo_box; QPointer m_select_all_check_box; //variables for the functionalities of saving & loading save_mapping_t m_save_listing; unsigned int m_save_count {0}; static constexpr std::string_view MODEL_SERIES = "modelSeries"; static constexpr std::string_view MAP = "map"; data::ptr m_model_series {this, "modelSeries"}; data::ptr m_map {this, "map"}; }; } // namespace sight::module::ui::qt::model sight-25.1.0/module/ui/qt/notifier.cpp000066400000000000000000000443121503402212300175620ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2025 IRCAD France * Copyright (C) 2021 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/notifier.hpp" #include #include #include #include #include #include #include namespace sight::module::ui::qt { static const core::com::slots::key_t POP_NOTIFICATION_SLOT = "pop"; static const core::com::slots::key_t CLOSE_NOTIFICATION_SLOT = "close_notification"; static const core::com::slots::key_t SET_ENUM_PARAMETER_SLOT = "set_enum_parameter"; static const std::string POSITION_KEY("position"); static const std::string DURATION_KEY("duration"); static const std::string SIZE_KEY("size"); static const std::string MAX_KEY("max"); static const std::string CLOSABLE_KEY("closable"); static const std::string INFINITE("infinite"); static const std::vector SOUND_BOARD = { std::filesystem::canonical( sight::core::runtime::get_resource_file_path("sight::module::ui::qt/sounds/info_beep.wav") ), std::filesystem::canonical( sight::core::runtime::get_resource_file_path("sight::module::ui::qt/sounds/success_beep.wav") ), std::filesystem::canonical( sight::core::runtime::get_resource_file_path("sight::module::ui::qt/sounds/failure_beep.wav") ) }; static const std::map POSITION_MAP = { {"TOP_RIGHT", service::notification::position::top_right}, {"TOP_LEFT", service::notification::position::top_left}, {"CENTERED_TOP", service::notification::position::centered_top}, {"CENTERED", service::notification::position::centered}, {"BOTTOM_RIGHT", service::notification::position::bottom_right}, {"BOTTOM_LEFT", service::notification::position::bottom_left}, {"CENTERED_BOTTOM", service::notification::position::centered_bottom} }; //----------------------------------------------------------------------------- notifier::notifier() noexcept { new_slot(POP_NOTIFICATION_SLOT, ¬ifier::pop, this); new_slot(CLOSE_NOTIFICATION_SLOT, ¬ifier::close_notification, this); new_slot(SET_ENUM_PARAMETER_SLOT, ¬ifier::set_enum_parameter, this); } //----------------------------------------------------------------------------- void notifier::configuring() { const auto& config = this->get_config(); if(const auto& channels = config.get_child_optional("channels"); channels) { for(const auto& channel : boost::make_iterator_range(channels->equal_range("channel"))) { configuration channel_config {}; // UID const auto& uid = channel.second.get_optional(".uid").value_or(""); // Position if(const auto& position = channel.second.get_optional("." + POSITION_KEY); position) { if(POSITION_MAP.contains(*position)) { channel_config.position = POSITION_MAP.at(*position); } else { SIGHT_ERROR( "Position '" + *position + "' isn't a valid position value, accepted values are:" "TOP_RIGHT, TOP_LEFT, CENTERED_TOP, CENTERED, BOTTOM_RIGHT, BOTTOM_LEFT, CENTERED_BOTTOM." ) } } // Duration if(const auto& duration = channel.second.get_optional("." + DURATION_KEY); duration) { if(*duration == INFINITE) { channel_config.duration = std::chrono::milliseconds(0); } else { try { channel_config.duration = std::chrono::milliseconds(std::stoul(*duration)); } catch(...) { SIGHT_ERROR( "Duration '" + *duration + "' is not valid. Accepted values are: '" + INFINITE + "' or a positive number of milliseconds." ) } } } // Size if(const auto& size = channel.second.get_optional("." + SIZE_KEY); size) { try { if(const auto pos = size->find_first_of('x'); pos != std::string::npos) { const auto width = std::stoul(size->substr(0, pos)); const auto height = std::stoul(size->substr(pos + 1)); channel_config.size = {int(width), int(height)}; } else { throw std::runtime_error("No 'x' found."); } } catch(...) { SIGHT_ERROR( "Size '" + *size + "' is not valid. Accepted values are: `n x n` where 'n' is a positive number." ) } } // Max if(const auto& max = channel.second.get_optional("." + MAX_KEY); max) { try { channel_config.max = std::stoul(*max); } catch(...) { SIGHT_ERROR( "Maximum '" + *max + "' is not valid. Accepted values are positive numbers." ) } } // Closable if(const auto& closable = channel.second.get_optional("." + CLOSABLE_KEY); closable) { channel_config.closable = *closable == "true"; } m_channels.insert_or_assign(uid, channel_config); } } // Lastly, initialize sound strutures. m_sound = std::make_unique(qApp); m_sound->setVolume(1.0); m_default_message = config.get("message", m_default_message); m_parent_container_id = config.get("parent..uid", m_parent_container_id); } //----------------------------------------------------------------------------- void notifier::starting() { if(!m_parent_container_id.empty()) { auto container = sight::ui::registry::get_sid_container(m_parent_container_id); if(!container) { container = sight::ui::registry::get_wid_container(m_parent_container_id); } // If we have an SID/WID set the container. if(container) { m_container_where_to_display_notifs = container; } } } //----------------------------------------------------------------------------- void notifier::stopping() { for(const auto& [position, stack] : m_stacks) { for(const auto& popup : stack.popups) { popup->close(); } } m_stacks.clear(); } //----------------------------------------------------------------------------- void notifier::updating() { } //----------------------------------------------------------------------------- void notifier::set_enum_parameter(std::string _val, std::string _key) { try { if(_key == POSITION_KEY) { m_channels[""].position = POSITION_MAP.at(_val); } else if(_key == DURATION_KEY) { if(_val == INFINITE) { m_channels[""].duration = std::chrono::milliseconds(0); } else { m_channels[""].duration = std::chrono::milliseconds(std::stoul(_val)); } } else if(_key == SIZE_KEY) { if(const auto pos = _val.find_first_of('x'); pos != std::string::npos) { const auto width = std::stoul(_val.substr(0, pos)); const auto height = std::stoul(_val.substr(pos + 1)); m_channels[""].size = {int(width), int(height)}; } } else if(_key == MAX_KEY) { m_channels[""].max = std::stoul(_val); } else if(_key == CLOSABLE_KEY) { m_channels[""].closable = _val == "true"; } } catch(...) { SIGHT_ERROR(std::string("Value '") + _val + "' is not handled for key " + _key); } } //----------------------------------------------------------------------------- void notifier::pop(service::notification _notification) { const bool channel_configured = m_channels.contains(_notification.m_channel); // Get channel configuration (or global configuration if there is no channel) const auto& channel_configuration = channel_configured ? m_channels[_notification.m_channel] : m_channels[""]; const auto& default_configuration = m_channels[""]; // Get the stack configuration. First try the channel, then the default, then the notification itself // If you want that services totally control the notification, associate them to an unconfigured notifier const auto& position = channel_configured && channel_configuration.position ? *channel_configuration.position : (channel_configured && !channel_configuration.position) || !default_configuration.position ? _notification.m_position : *default_configuration.position; const auto& duration = channel_configured && channel_configuration.duration ? *channel_configuration.duration : (channel_configured && !channel_configuration.duration) || !default_configuration.duration ? _notification.m_duration : *default_configuration.duration; const auto& size = channel_configured && channel_configuration.size ? *channel_configuration.size : (channel_configured && !channel_configuration.size) || !default_configuration.size ? _notification.m_size : *default_configuration.size; const auto& max = channel_configuration.max ? *channel_configuration.max : default_configuration.max ? *default_configuration.max : 0; const auto& closable = channel_configured && channel_configuration.closable ? *channel_configuration.closable : (channel_configured && !channel_configuration.closable) || !default_configuration.closable ? _notification.m_closable : *default_configuration.closable; // Get the wanted stack auto& target_stack = m_stacks[position]; // Compute harmonized max and size target_stack.max = target_stack.max ? std::max(*target_stack.max, max) : max; target_stack.size = target_stack.size ? std::array { std::max((*target_stack.size)[0], size[0]), std::max((*target_stack.size)[1], size[1]) } : size; // If the maximum number of notification is reached, remove the oldest one. clean_notifications(position, *target_stack.max, *target_stack.size); // Get or create the notification const auto& popup = [&] { // If a channel is present, try to retrieve the associated dialog if(!_notification.m_channel.empty()) { for(auto& [old_position, stack] : m_stacks) { for(const auto& popup : stack.popups) { if(popup->get_channel() == _notification.m_channel) { // If the position doesn't match, fix it if(old_position != position) { // Explicit copy auto copy = popup; copy->set_index(static_cast(target_stack.popups.size())); target_stack.popups.emplace_back(copy); // Remove the original stack.popups.remove(popup); return copy; } return popup; } } } } // No channel or the dialog was not found, create a new one auto popup = std::make_shared(); popup->set_index(static_cast(target_stack.popups.size())); target_stack.popups.emplace_back(popup); return popup; }(); popup->set_container(m_container_where_to_display_notifs); const std::string& message_to_show = _notification.m_message.empty() ? m_default_message : _notification.m_message; popup->set_message(message_to_show); popup->set_type(_notification.m_type); popup->set_position(position); popup->set_duration(duration); popup->set_size(*target_stack.size); std::weak_ptr weak_notifier = this->shared_from_this(); popup->set_closed_callback( [weak_notifier, popup](auto&& ...) { if(auto notifier = std::dynamic_pointer_cast(weak_notifier.lock()); notifier) { notifier->on_notification_closed(popup); } }); popup->set_channel(_notification.m_channel); popup->set_closable(closable); popup->show(); if(_notification.m_sound.has_value() && _notification.m_sound.value()) { m_sound->setSource( QUrl::fromLocalFile( QString::fromStdString(SOUND_BOARD[std::size_t(_notification.m_type)].string()) ) ); m_sound->play(); } } //------------------------------------------------------------------------------ void notifier::close_notification(std::string _channel) { bool found = false; for(const auto& [position, stack] : m_stacks) { for(const auto& popup : stack.popups) { if(popup->get_channel() == _channel) { found = true; popup->close(); } } } SIGHT_WARN_IF("Notification on channel '" << _channel << "' is already closed.", !found); } //------------------------------------------------------------------------------ void notifier::on_notification_closed(const sight::ui::dialog::notification::sptr& _notif) { // If the notification still exist for(auto& [position, stack] : m_stacks) { if(auto it = std::find(stack.popups.begin(), stack.popups.end(), _notif); it != stack.popups.end()) { erase_notification(position, it); } } } //------------------------------------------------------------------------------ std::list::iterator notifier::erase_notification( const enum service::notification::position& _position, const std::list::iterator& _it ) { // Remove the notification from the container auto& stack = m_stacks[_position]; const auto next_it = stack.popups.erase(_it); auto remaining_it = next_it; // Move all the remaining notifications one index lower while(remaining_it != stack.popups.end()) { (*remaining_it)->move_down(); ++remaining_it; } // Return the it pointing after the erased one return next_it; } //------------------------------------------------------------------------------ void notifier::clean_notifications( const enum service::notification::position& _position, std::size_t _max, std::array _size, bool _skip_permanent ) { // Get the correct "stack" auto& stack = m_stacks[_position]; std::size_t removable_popups = 0; // Count how many popups that can be removed there are for(const auto& popup : stack.popups) { if(!_skip_permanent || popup->get_duration()) { ++removable_popups; } } for(auto it = stack.popups.begin() ; removable_popups >= _max && it != stack.popups.end() ; ) { // If the popup is removable if(const auto& duration = (*it)->get_duration(); !_skip_permanent || (duration && duration->count() > 0)) { // Remove it (*it)->close(); it = erase_notification(_position, it); --removable_popups; } else { ++it; } } // Adjust sizes for(const auto& popup : stack.popups) { popup->set_size(_size); } } //----------------------------------------------------------------------------- } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/notifier.hpp000066400000000000000000000204151503402212300175650ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2024 IRCAD France * Copyright (C) 2021 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include namespace sight::module::ui::qt { /** * @brief notifier is a general service used to display notification in a centralized way. * notifier needs to be connected to [Success/Failure/Info]Notified signals implemented in base. * * @section Slots Slots * - \b pop(): Adds a popup in the queue & display it. * - \b close_notification(std::string channel): Close the popup associated with the given channel. * - \b set_enum_parameter(std::string value, std::string key): Changes the position of notifications (key "position"), * accepted values are the same than the "position" tag in the XML configuration. * * @section XML XML Configuration * * @code{.xml} Default Message @endcode * * @subsection Configuration Configuration * - \b message (optional): Default message of the notification if the emitted signal contains empty string. * (default: "Notification"). * - \b parent (optional): UID of the gui Container where the notifications will be displayed (default the whole app), * NOTE: we use the xml attribute "uid" to resolve "${GENERIC_UID}_" prefixes. * - \b channels (optional): Channels sub-configuration. * - \b channel (optional): Channel specific configuration. * * - \b uid (optional): The uid that identify the channel. If no uid is set, then the settings apply to the global * default, where notification widgets are not shared, but will share the configuration * (position, duration, ...). * All notification request that use the same channel, will share the same notification dialog widget and * the same configuration. * * - \b position (optional): Position of the notification queue (default: TOP_RIGHT). * Accepted values are: * - TOP_RIGHT: default value. * - TOP_LEFT * - CENTERED_TOP * - CENTERED * - BOTTOM_RIGHT * - BOTTOM_LEFT * - CENTERED_BOTTOM * * Different channels can share the same postion. However, some configuration (most notably "max" * and size) will be shared since they configure the behavior of the stack. If so, the value may be * "harmonized" across all dialogs by taking the maximum value. * @note This may change if the positioning algorithm got updated * * - \b duration (optional): Override duration in ms (+ 1 sec for fadein & fadeout effects). * * - \b max (optional): maximum number of notifications in the same position. * Permanent notification are not counted. * * - \b size (optional): size of notifications in the same position. * * - \b closable (optional): override default which is closable for timed notification. This is mostly useful to * allow closing of permanent notification * */ class notifier final : public service::controller { public: SIGHT_DECLARE_SERVICE(notifier, service::controller); /// Constructor, initializes position map & slots. notifier() noexcept; /// Destructor, clears the position map. ~notifier() noexcept override = default; /// Slot: This method is used to set an enum parameter. void set_enum_parameter(std::string _val, std::string _key); /// Slot: pops a notification. /// @param _notification notification. void pop(service::notification _notification); /// Slot: close a notification identified by the channel name. /// @param _channel notification channel. void close_notification(std::string _channel); protected: /** @name Service methods ( override from service::base ) * @{ */ /// This method configures the service void configuring() override; /** * @brief Starts and setups the service optionally gets the parent container SID/WID if set. */ void starting() override; /** * @brief Stops & clears the service */ void stopping() override; /** * @brief This method does nothing. */ void updating() override; private: /// Called when a notification is closed void on_notification_closed(const sight::ui::dialog::notification::sptr& _notif); /// Erase a notification from m_popups and move down the remaining /// @param _position The stack where we need to erase a notification /// @param _it the iterator pointing on the element to erase std::list::iterator erase_notification( const enum service::notification::position& _position, const std::list::iterator& _it ); /// Count the number of notifications and remove the oldest if > m_maxStackedNotifs /// @param _position The stack to clean /// @param _max The maximum number of element /// @param _skip_permanent if true, only non permanent notifications are counted void clean_notifications( const enum service::notification::position& _position, std::size_t _max, std::array _size, bool _skip_permanent = true ); /// Default message (if message in slot are empty), the default message can be configured in xml. std::string m_default_message {"Notification"}; std::unique_ptr m_sound; struct configuration final { std::optional position {std::nullopt}; std::optional duration {std::nullopt}; std::optional > size {std::nullopt}; std::optional max {std::nullopt}; std::optional closable {std::nullopt}; }; std::map m_channels {{"", {.max = {3}}}}; /// A stack of notification struct stack final { std::optional > size {std::nullopt}; std::optional max {std::nullopt}; std::list popups {}; }; /// Map of displayed Stack std::map m_stacks { {service::notification::position::top_right, {}}, {service::notification::position::top_left, {}}, {service::notification::position::bottom_right, {}}, {service::notification::position::bottom_left, {}}, {service::notification::position::centered, {}}, {service::notification::position::centered_top, {}}, {service::notification::position::centered_bottom, {}}, }; /// widget where notifications will be displayed in, nullptr by default. sight::ui::container::widget::csptr m_container_where_to_display_notifs {nullptr}; /// Parent container ID (SID or WID), empty by default. std::string m_parent_container_id; }; } //namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/parameters.cpp000066400000000000000000003070621503402212300201120ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "parameters.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { //------------------------------------------------------------------------------ namespace { // Internal static function to split a string using separator (usually =), mainly used to split enum into value, data std::pair split_string(const std::string& _str, const std::string& _sep = "=") { std::string left; std::string right; const boost::char_separator subsep(_sep.c_str()); const boost::tokenizer > subtokens {_str, subsep}; auto it = subtokens.begin(); if(it != subtokens.end()) { left = *it; ++it; // check if we have another part if(it != subtokens.end()) { right = *it; } } return {left, right}; } } // anonymous namespace //----------------------------------------------------------------------------- parameters::parameters() noexcept { new_signal(signals::PARAMETER_CHANGED_SIG); new_signal(signals::BOOLEAN_CHANGED_SIG); new_signal(signals::COLOR_CHANGED_SIG); new_signal(signals::DOUBLE_CHANGED_SIG); new_signal(signals::DOUBLE2_CHANGED_SIG); new_signal(signals::DOUBLE3_CHANGED_SIG); new_signal(signals::INTEGER_CHANGED_SIG); new_signal(signals::INTEGER2_CHANGED_SIG); new_signal(signals::INTEGER3_CHANGED_SIG); new_signal(signals::ENUM_CHANGED_SIG); new_signal(signals::ENUM_INDEX_CHANGED_SIG); new_slot(slots::SET_PARAMETER_SLOT, ¶meters::set_parameter, this); new_slot(slots::SET_BOOL_PARAMETER_SLOT, ¶meters::set_bool_parameter, this); new_slot(slots::SET_COLOR_PARAMETER_SLOT, ¶meters::set_color_parameter, this); new_slot(slots::SET_DOUBLE_PARAMETER_SLOT, ¶meters::set_double_parameter, this); new_slot(slots::SET_DOUBLE2_PARAMETER_SLOT, ¶meters::set_double2_parameter, this); new_slot(slots::SET_DOUBLE3_PARAMETER_SLOT, ¶meters::set_double3_parameter, this); new_slot(slots::SET_INT_PARAMETER_SLOT, ¶meters::set_int_parameter, this); new_slot(slots::SET_INT2_PARAMETER_SLOT, ¶meters::set_int2_parameter, this); new_slot(slots::SET_INT3_PARAMETER_SLOT, ¶meters::set_int3_parameter, this); new_slot(slots::SET_ENUM_PARAMETER_SLOT, ¶meters::set_enum_parameter, this); new_slot(slots::SET_ENUM_INDEX_PARAMETER_SLOT, ¶meters::set_enum_index_parameter, this); new_slot(slots::UPDATE_ENUM_RANGE_SLOT, ¶meters::update_enum_range, this); new_slot(slots::UPDATE_INT_MIN_PARAMETER_SLOT, ¶meters::update_int_min_parameter, this); new_slot(slots::UPDATE_INT_MAX_PARAMETER_SLOT, ¶meters::update_int_max_parameter, this); new_slot(slots::UPDATE_DOUBLE_MIN_PARAMETER_SLOT, ¶meters::update_double_min_parameter, this); new_slot(slots::UPDATE_DOUBLE_MAX_PARAMETER_SLOT, ¶meters::update_double_max_parameter, this); } //----------------------------------------------------------------------------- void parameters::configuring() { this->initialize(); } //----------------------------------------------------------------------------- void parameters::starting() { this->create(); const std::string service_id = base_id(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); qt_container->get_qt_container()->setObjectName(QString::fromStdString(service_id)); QScrollArea* scroll_area = nullptr; QWidget* viewport = nullptr; service::config_t config = this->get_config(); const service::config_t& parameters_cfg = config.get_child("parameters"); const bool scrollable = parameters_cfg.get(".scrollable", false); if(scrollable) { scroll_area = new QScrollArea(qt_container->get_qt_container()->parentWidget()); scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); viewport = new QWidget(qt_container->get_qt_container()); } auto* layout = new QFormLayout {}; layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); layout->setAlignment(Qt::AlignCenter); this->blockSignals(true); // We don't support having multiple parameters with the same key (this isn't a good idea any way). // This set keeps tracks of the ones we add, and triggers and assert if it already existed. [[maybe_unused]] std::set keys; // Create widgets for(const auto& param : boost::make_iterator_range(parameters_cfg.equal_range("param"))) { const service::config_t& cfg = param.second; const auto orientation = cfg.get(".orientation", "horizontal") == "horizontal" ? Qt::Horizontal : Qt::Vertical; const auto param_box_direction = orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; const auto type = cfg.get(".type"); const auto min_width = cfg.get(".min_width", 0); const auto min_height = cfg.get(".min_height", 0); const param_widget widget { .name = cfg.get(".name"), .key = cfg.get(".key"), .default_value = cfg.get(".defaultValue"), .reset_button = cfg.get(".reset", true), .hide_min_max = cfg.get(".hideMinMax", false), .min_size = {min_width, min_height}, }; SIGHT_ASSERT( get_id() << ": Key " << std::quoted(widget.key) << " already exists.", keys.insert(widget.key).second ); auto* const param_box = new QGroupBox {scroll_area == nullptr ? qt_container->get_qt_container() : scroll_area}; { const auto qt_key = QString::fromStdString(widget.key) + "_box"; // This intermediate widget shouldn't be visible // Note that because we're setting this stylesheet on the fly on this "top-level" widget, we must // apply it again on its children: children inherits the stylesheet of their parents // Note that these widgets aren't required for display purposes, but having them greatly simplifies the // search done in get_param_widget. param_box->setStyleSheet("background-color: rgba(0,0,0,0); border: 0px;"); param_box->setContentsMargins(0, 0, 0, 0); param_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); param_box->setProperty("key", qt_key); param_box->setObjectName(qt_key); m_param_boxes.emplace_back(param_box); } auto* const param_box_layout = new QBoxLayout {param_box_direction, param_box}; auto* const row_layout = new QHBoxLayout {}; row_layout->addWidget(param_box); // Label { QLabel* parameter_label = nullptr; if(!widget.name.empty()) { parameter_label = new QLabel(QString::fromStdString(widget.name)); parameter_label->setWordWrap(true); parameter_label->setAlignment(Qt::AlignCenter); parameter_label->setStyleSheet("margin: -0.1em;"); } if(parameter_label != nullptr) { // When horizontal, the label is added to the "main" widget layout, otherwise, at the top of the box if(orientation == Qt::Vertical) { param_box_layout->addWidget(parameter_label); layout->addRow(row_layout); } else { layout->addRow(parameter_label, row_layout); } } else { layout->addRow(row_layout); } } if(type == "bool") { if(auto* reset = this->create_bool_widget(*param_box_layout, widget)) { row_layout->addWidget(reset); } } else if(type == "color") { if(auto* reset = this->create_color_widget(*param_box_layout, widget)) { row_layout->addWidget(reset); } } else if(type == "double" || type == "double2" || type == "double3") { const std::string widget_type = cfg.get(".widget", "spin"); const double min = cfg.get(".min", 0.); const double max = cfg.get(".max", 1.); const double_widget widget_double = {widget, min, max}; const int count = (type == "double3") ? 3 : (type == "double2" ? 2 : 1); if(widget_type == "spin") { if(auto* reset = this->create_double_widget(*param_box_layout, widget_double, count, orientation)) { row_layout->addWidget(reset); } } else if(widget_type == "slider") { // TODO: this could be supported now SIGHT_ASSERT(get_id() << ": Count > 1 is not supported with sliders", count == 1); const std::uint8_t decimals = cfg.get(".decimals", 2); const bool on_release = cfg.get(".emitOnRelease", false); if(auto* reset = this->create_double_slider_widget( *param_box_layout, widget_double, decimals, orientation, on_release ) ) { // Looks better under the rest if(orientation == Qt::Vertical) { param_box_layout->addWidget(reset, /*stretch = */ 0, Qt::AlignCenter); } else { row_layout->addWidget(reset); } } } else { SIGHT_ERROR( get_id() << ": Unknown widget type : " << std::quoted(widget_type) << " for " << std::quoted(widget.name) << ". Must be 'spin' or 'slider'." ); } } else if(type == "int" || type == "int2" || type == "int3") { const std::string widget_type = cfg.get(".widget", "slider"); const int min = cfg.get(".min", 0); const int max = cfg.get(".max", 100); const int_widget widget_int = {widget, min, max}; const int count = (type == "int3") ? 3 : (type == "int2" ? 2 : 1); if(widget_type == "spin") { if(auto* reset = this->create_integer_spin_widget(*param_box_layout, widget_int, count, orientation)) { row_layout->addWidget(reset); } } else if(widget_type == "slider") { // TODO: this could be supported now SIGHT_ASSERT(get_id() << ": Count > 1 is not supported with sliders", count == 1); const bool on_release = cfg.get(".emitOnRelease", false); if(auto* reset = this->create_integer_slider_widget(*param_box_layout, widget_int, orientation, on_release)) { // Looks better under the rest if(orientation == Qt::Vertical) { param_box_layout->addWidget(reset, /*stretch = */ 0, Qt::AlignCenter); } else { row_layout->addWidget(reset); } } } else { SIGHT_ERROR( get_id() << ": Unknown widget type : " << std::quoted(widget_type) << " for " << widget.name << ". Must be 'spin' or 'slider'." ); } } else if(type == "enum") { const auto widget_type = cfg.get(".widget", "combobox"); if(widget_type == "combobox") { const auto options = cfg.get(".values"); // split values separated by ',', ' ', ';' std::vector values; std::vector data; sight::module::ui::qt::parameters::parse_enum_string(options, values, data); this->create_enum_widget(*param_box_layout, widget, values, data); } else if(widget_type == "slider") { const auto options = cfg.get(".values"); // split values separated by ',', ' ', ';' std::vector values; std::vector data; sight::module::ui::qt::parameters::parse_enum_string(options, values, data); const bool on_release = cfg.get(".emitOnRelease", false); this->create_slider_enum_widget(*param_box_layout, widget, values, orientation, on_release); } else if(widget_type == "buttonBar") { const int h_offset = cfg.get(".hOffset", 0); const int width = cfg.get(".width", 0); const int height = cfg.get(".height", 0); const std::string style = cfg.get(".style", "iconOnly"); const auto value_config = cfg.equal_range("item"); std::vector button_list; for(auto value_config_it = value_config.first ; value_config_it != value_config.second ; ++value_config_it) { const auto value = value_config_it->second.get(".value"); const auto label = value_config_it->second.get(".label", ""); const auto icon_path_relative = value_config_it->second.get(".icon", ""); const std::string icon_path = core::runtime::get_module_resource_file_path(icon_path_relative).generic_string(); button_list.push_back(enum_button_param({value, label, icon_path})); } this->create_button_bar_enum_widget( *param_box_layout, widget, button_list, width, height, h_offset, style, orientation ); } } } for(const auto& param : boost::make_iterator_range(parameters_cfg.equal_range("param"))) { const service::config_t& cfg = param.second; const auto key = cfg.get(".key"); const std::string depends = cfg.get(".depends", ""); const std::string depends_value = cfg.get(".dependsValue", ""); const bool dependsreverse = cfg.get(".dependsReverse", false); if(!depends.empty()) { auto* const depends_widget = get_param_widget(depends); // Note: we disable the QGroupBox containing the widget**s** instead of individual ones, because // some parameter create several widgets (3 spinboxes, etc.) const auto widget_container_it = std::ranges::find_if( m_param_boxes, [qt_key = QString::fromStdString(key) + "_box"](const QWidget* const _w) { return _w->objectName() == qt_key; }); SIGHT_ASSERT( get_id() << ": unknown parameter " << std::quoted(key) << ".", widget_container_it != m_param_boxes.cend() ); if(auto* const widget = qobject_cast((*widget_container_it))) { widget->installEventFilter(this); auto* check_box = qobject_cast(depends_widget); if(check_box != nullptr) { QObject::connect( check_box, &QCheckBox::stateChanged, this, [ = ]{on_depends_changed(check_box, widget, dependsreverse);}); on_depends_changed(check_box, widget, dependsreverse); } else { auto* combo_box = qobject_cast(depends_widget); if(combo_box != nullptr) { QObject::connect( combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, [ = ]{on_depends_changed(combo_box, widget, depends_value, dependsreverse);}); on_depends_changed(combo_box, widget, depends_value, dependsreverse); } } } } } if(scroll_area != nullptr) { viewport->setLayout(layout); scroll_area->setWidgetResizable(true); scroll_area->setWidget(viewport); auto* main_layout = new QGridLayout(); qt_container->set_layout(main_layout); main_layout->addWidget(scroll_area); // The size of the vertical scroll bar isn't taken into account when the QGridLayout fill the space, as such // some buttons (particularly reset buttons) get hidden. The workaround is to biggen the right margin a little. int scroll_bar_width = QScrollBar().sizeHint().width(); QMargins margins = layout->contentsMargins(); margins.setRight(scroll_bar_width); layout->setContentsMargins(margins); } else { qt_container->set_layout(layout); } this->blockSignals(false); } //----------------------------------------------------------------------------- void parameters::updating() { auto qt_container = std::dynamic_pointer_cast(this->get_container()); service::config_t config = this->get_config(); const service::config_t& parameters_cfg = config.get_child("parameters"); // emit signal for each widget for(const auto& param : boost::make_iterator_range(parameters_cfg.equal_range("param"))) { const service::config_t& cfg = param.second; const auto key = cfg.get(".key"); const auto type = cfg.get(".type"); if(auto* const child = get_param_widget(key); child != nullptr) { if(type == "bool") { const QCheckBox* box = dynamic_cast(child); SIGHT_ASSERT(get_id() << ": Widget must be a QCheckBox", box); const bool state = (box->checkState() == Qt::Checked); if(!m_block_signals) { this->signal(signals::BOOLEAN_CHANGED_SIG) ->async_emit(state, key); this->signal(signals::PARAMETER_CHANGED_SIG)->async_emit(state, key); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::BOOLEAN_CHANGED_SIG << "(" << std::boolalpha << state << std::noboolalpha << ", " << key << ")" ); } } else if(type == "color") { const auto color_qt = child->property("color").value(); this->emit_color_signal(color_qt, key); } else if(type == "double" || type == "double2" || type == "double3") { this->emit_double_signal(child); } else if(type == "int" || type == "int2" || type == "int3") { this->emit_integer_signal(child); } else if(type == "enum") { if(auto* box = qobject_cast(child); box != nullptr) { const QString data = box->itemData(box->currentIndex()).toString(); if(!m_block_signals) { this->signal(signals::ENUM_CHANGED_SIG) ->async_emit(data.toStdString(), key); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(data.toStdString(), key); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::ENUM_CHANGED_SIG << "(" << data.toStdString() << ", " << key << ")" ); this->signal(signals::ENUM_INDEX_CHANGED_SIG) ->async_emit(box->currentIndex(), key); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(box->currentIndex(), key); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::ENUM_INDEX_CHANGED_SIG << "(" << box->currentIndex() << ", " << key << ")" ); } } else if(auto* slider = qobject_cast(child); slider != nullptr) { if(!m_block_signals) { int value = slider->value(); this->signal(signals::INTEGER_CHANGED_SIG) ->async_emit(value, key); this->signal(signals::PARAMETER_CHANGED_SIG)->async_emit(value, key); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::INTEGER_CHANGED_SIG << "(" << value << ", " << key << ")" ); this->signal(signals::ENUM_CHANGED_SIG) ->async_emit(std::to_string(value), key); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(std::to_string(value), key); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::ENUM_CHANGED_SIG << "(" << value << ", " << key << ")" ); this->signal(signals::ENUM_INDEX_CHANGED_SIG) ->async_emit(int(slider->index()), key); } } else if(auto* button_group = qobject_cast(child); button_group != nullptr) { if(auto* checked_button = button_group->checkedButton(); checked_button != nullptr) { // Find the currently checked button const int button_index = static_cast(button_group->buttons().indexOf(checked_button)); const std::string value = checked_button->property("value").toString().toStdString(); this->signal(signals::ENUM_CHANGED_SIG)->async_emit(value, key); this->signal(signals::PARAMETER_CHANGED_SIG)->async_emit(value, key); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::ENUM_CHANGED_SIG << "(" << value << ", " << key << ")" ); this->signal(signals::ENUM_INDEX_CHANGED_SIG) ->async_emit(button_index, key); } } else { SIGHT_ASSERT(get_id() << ": Widget must either be a ComboBox or non_linear_slider", false); } } } } } //----------------------------------------------------------------------------- void parameters::stopping() { m_param_boxes.clear(); // Avoid keeping dangling pointers this->destroy(); } //----------------------------------------------------------------------------- bool parameters::eventFilter(QObject* _watched, QEvent* _event) { if(_event->type() == ::QEvent::EnabledChange) { auto* check_box = qobject_cast(_watched); if(check_box != nullptr) { check_box->stateChanged(check_box->isChecked() ? Qt::Checked : Qt::Unchecked); } else { auto* combo_box = qobject_cast(_watched); if(combo_box != nullptr) { combo_box->currentIndexChanged(combo_box->currentIndex()); } } } return false; } //----------------------------------------------------------------------------- void parameters::on_depends_changed(QCheckBox* _check_box, QWidget* _widget, bool _reverse) { if(!_check_box->isEnabled()) { _widget->setDisabled(true); } else if(_reverse) { _widget->setDisabled(_check_box->checkState() != 0U); } else { _widget->setEnabled(_check_box->checkState() != 0U); } } //------------------------------------------------------------------------------ void parameters::on_depends_changed(QComboBox* _combo_box, QWidget* _widget, const std::string& _value, bool _reverse) { if(!_combo_box->isEnabled()) { _widget->setDisabled(true); } else if(_reverse) { _widget->setDisabled(_combo_box->currentText().toStdString() == _value); } else { _widget->setEnabled(_combo_box->currentText().toStdString() == _value); } } //----------------------------------------------------------------------------- void parameters::on_change_enum(int _value) const { const QObject* sender = this->sender(); const QString key = sender->property("key").toString(); const auto* box = dynamic_cast(sender); SIGHT_ASSERT(get_id() << ": Wrong widget type", box); const QString data = box->itemData(_value).toString(); if(!m_block_signals) { this->signal(signals::ENUM_CHANGED_SIG) ->async_emit(data.toStdString(), key.toStdString()); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(data.toStdString(), key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::ENUM_CHANGED_SIG << "(" << data.toStdString() << ", " << key.toStdString() << ")" ); this->signal(signals::ENUM_INDEX_CHANGED_SIG) ->async_emit(_value, key.toStdString()); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(box->currentIndex(), key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::ENUM_INDEX_CHANGED_SIG << "(" << _value << ", " << key.toStdString() << ")" ); } } //----------------------------------------------------------------------------- void parameters::on_change_boolean(int _value) const { const QObject* sender = this->sender(); const QString key = sender->property("key").toString(); const bool checked = _value == Qt::Checked; if(!m_block_signals) { this->signal(signals::BOOLEAN_CHANGED_SIG) ->async_emit(checked, key.toStdString()); this->signal(signals::PARAMETER_CHANGED_SIG)->async_emit(checked, key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::BOOLEAN_CHANGED_SIG << "(" << (checked ? "true" : "false") << ", " << key.toStdString() << ")" ); } } //------------------------------------------------------------------------------ void parameters::on_color_button() { QObject* sender = this->sender(); // Create Color choice dialog. auto qt_container = std::dynamic_pointer_cast(this->get_container()); QWidget* const container = qt_container->get_qt_container(); SIGHT_ASSERT(get_id() << ": Container not instantiated yet.", container); const auto old_color = sender->property("color").value(); const QColor color_qt = QColorDialog::getColor(old_color, container); if(color_qt.isValid()) { const QString key = sender->property("key").toString(); auto* colour_button = dynamic_cast(sender); colour_button->setProperty("color", color_qt); int icon_size = colour_button->style()->pixelMetric(QStyle::PM_LargeIconSize); QPixmap pix(icon_size, icon_size); pix.fill(color_qt); colour_button->setIcon(QIcon(pix)); this->emit_color_signal(color_qt, key.toStdString()); } } //----------------------------------------------------------------------------- void parameters::on_change_integer(int /*unused*/) const { QObject* sender = this->sender(); this->emit_integer_signal(sender); } //------------------------------------------------------------------------------ void parameters::emit_integer_signal(QObject* _widget) const { if(!m_block_signals) { SIGHT_ASSERT(get_id() << ": Widget is null", _widget != nullptr); const QString key = _widget->property("key").toString(); const int count = _widget->property("count").toInt(); SIGHT_ASSERT(get_id() << ": Invalid widgets count, must be <= 3", count <= 3); const auto* spinbox = dynamic_cast(_widget); const auto* slider = dynamic_cast(_widget); SIGHT_ASSERT(get_id() << ": Wrong widget type", spinbox || slider); if(count == 1) { int value = 0; if(spinbox != nullptr) { value = spinbox->value(); } else { value = slider->value(); } this->signal(signals::INTEGER_CHANGED_SIG) ->async_emit(value, key.toStdString()); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(value, key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::INTEGER_CHANGED_SIG << "(" << value << ", " << key.toStdString() << ")" ); } else { int value1 = 0; int value2 = 0; if(spinbox != nullptr) { const QSpinBox* spin1 = _widget->property("widget#0").value(); const QSpinBox* spin2 = _widget->property("widget#1").value(); value1 = spin1->value(); value2 = spin2->value(); } else { const QSlider* spin1 = _widget->property("widget#0").value(); const QSlider* spin2 = _widget->property("widget#1").value(); value1 = spin1->value(); value2 = spin2->value(); } if(count == 2) { this->signal(signals::INTEGER2_CHANGED_SIG) ->async_emit(value1, value2, key.toStdString()); const sight::ui::int2_t values = {value1, value2}; this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(values, key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::INTEGER2_CHANGED_SIG << "(" << value1 << ", " << value2 << ", " << key.toStdString() << ")" ); } else { int value3 = 0; if(spinbox != nullptr) { const QSpinBox* spin3 = _widget->property("widget#2").value(); value3 = spin3->value(); } else { const QSlider* spin3 = _widget->property("widget#2").value(); value3 = spin3->value(); } this->signal(signals::INTEGER3_CHANGED_SIG) ->async_emit(value1, value2, value3, key.toStdString()); const sight::ui::int3_t values = {value1, value2, value3}; this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(values, key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::INTEGER3_CHANGED_SIG << "(" << value1 << ", " << value2 << ", " << value3 << ", " << key.toStdString() << ")" ); } } } } //----------------------------------------------------------------------------- void parameters::on_change_double(double /*unused*/) const { QObject* sender = this->sender(); this->emit_double_signal(sender); } //------------------------------------------------------------------------------ void parameters::emit_double_signal(QObject* _widget) const { if(!m_block_signals) { const QString key = _widget->property("key").toString(); const int count = _widget->property("count").toInt(); auto* spinbox = qobject_cast(_widget); auto* slider = qobject_cast(_widget); if(slider != nullptr) { const double value = sight::module::ui::qt::parameters::get_double_slider_value(slider); this->signal(signals::DOUBLE_CHANGED_SIG) ->async_emit(value, key.toStdString()); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(value, key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::DOUBLE_CHANGED_SIG << "(" << value << ", " << key.toStdString() << ")" ); } else if(spinbox != nullptr) { SIGHT_ASSERT(get_id() << ": Invalid widgets count, must be <= 3", count <= 3); if(count == 1) { this->signal(signals::DOUBLE_CHANGED_SIG) ->async_emit(spinbox->value(), key.toStdString()); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(spinbox->value(), key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::DOUBLE_CHANGED_SIG << "(" << spinbox->value() << ", " << key.toStdString() << ")" ); } else { const QDoubleSpinBox* spin1 = spinbox->property("widget#0").value(); const QDoubleSpinBox* spin2 = spinbox->property("widget#1").value(); const double value1 = spin1->value(); const double value2 = spin2->value(); if(count == 2) { this->signal(signals::DOUBLE2_CHANGED_SIG) ->async_emit(value1, value2, key.toStdString()); const sight::ui::double2_t values = {value1, value2}; this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(values, key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::DOUBLE2_CHANGED_SIG << "(" << value1 << ", " << value2 << ", " << key.toStdString() << ")" ); } else { const QDoubleSpinBox* spin3 = spinbox->property("widget#2").value(); const double value3 = spin3->value(); this->signal(signals::DOUBLE3_CHANGED_SIG) ->async_emit(value1, value2, value3, key.toStdString()); const sight::ui::double3_t values = {value1, value2, value3}; this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(values, key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::DOUBLE3_CHANGED_SIG << "(" << value1 << ", " << value2 << ", " << value3 << ", " << key.toStdString() << ")" ); } } } } } //----------------------------------------------------------------------------- void parameters::on_change_double_slider(int /*unused*/) const { auto* sender = qobject_cast(this->sender()); this->emit_double_signal(sender); } //----------------------------------------------------------------------------- void parameters::on_slider_mapped(QLabel* _label, QSlider* _slider) { _label->setText(QString::number(_slider->value())); } //----------------------------------------------------------------------------- void parameters::on_slider_range_mapped(QLabel* _min_label, QLabel* _max_label, QSlider* _slider) { const int min = _slider->minimum(); const int max = _slider->maximum(); _min_label->setText(QString::number(min)); _max_label->setText(QString::number(max)); } //----------------------------------------------------------------------------- void parameters::on_double_slider_mapped(QLabel* _label, QSlider* _slider) { const double new_value = get_double_slider_value(_slider); const int decimals = _slider->property("decimals").toInt(); _label->setText(QString::number(new_value, 'f', decimals)); } //----------------------------------------------------------------------------- void parameters::on_double_slider_range_mapped(QLabel* _min_label, QLabel* _max_label, QSlider* _slider) { const double min = _slider->property("min").toDouble(); const double max = _slider->property("max").toDouble(); const int decimals = _slider->property("decimals").toInt(); _min_label->setText(QString::number(min, 'g', decimals)); _max_label->setText(QString::number(max, 'g', decimals)); } //----------------------------------------------------------------------------- void parameters::on_reset_boolean_mapped(QWidget* _widget) const { auto* checkbox = qobject_cast(_widget); if(checkbox != nullptr) { int value = checkbox->property("defaultValue").toInt(); checkbox->setCheckState(Qt::CheckState(value)); const QString key = checkbox->property("key").toString(); if(!m_block_signals) { this->signal(signals::BOOLEAN_CHANGED_SIG) ->async_emit(value != 0, key.toStdString()); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(value != 0, key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::BOOLEAN_CHANGED_SIG << "(" << (value ? "true" : "false") << ", " << key.toStdString() << ")" ); } } } //----------------------------------------------------------------------------- void parameters::on_reset_color_mapped(QWidget* _widget) const { auto* colour_button = qobject_cast(_widget); if(colour_button != nullptr) { const auto color = colour_button->property("defaultValue").value(); const QString key = colour_button->property("key").toString(); int icon_size = colour_button->style()->pixelMetric(QStyle::PM_LargeIconSize); QPixmap pix(icon_size, icon_size); pix.fill(color); colour_button->setIcon(QIcon(pix)); colour_button->setProperty("color", color); const std::array new_color = { static_cast(color.red()), static_cast(color.green()), static_cast(color.blue()), static_cast(color.alpha()) }; if(!m_block_signals) { this->signal(signals::COLOR_CHANGED_SIG) ->async_emit(new_color, key.toStdString()); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(new_color, key.toStdString()); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::COLOR_CHANGED_SIG << "(" << int(new_color[0]) << ", " << int(new_color[1]) << ", " << int(new_color[2]) << ", " << int(new_color[3]) << ", " << key.toStdString() << ")" ); } } } //----------------------------------------------------------------------------- void parameters::on_reset_integer_mapped(QWidget* _widget) { auto* slider = dynamic_cast(_widget); auto* spinbox = dynamic_cast(_widget); this->blockSignals(true); if(slider != nullptr) { const int value = slider->property("defaultValue").toInt(); slider->setValue(value); } else if(spinbox != nullptr) { const QString key = spinbox->property("key").toString(); const int value = spinbox->property("defaultValue").toInt(); const int count = spinbox->property("count").toInt(); SIGHT_ASSERT(get_id() << ": Invalid widgets count, must be <= 3", count <= 3); auto* spin1 = spinbox->property("widget#0").value(); spin1->setValue(value); if(count > 1) { auto* spin2 = spinbox->property("widget#1").value(); spin2->setValue(value); if(count == 3) { auto* spin3 = spinbox->property("widget#2").value(); spin3->setValue(value); } } } this->blockSignals(false); this->emit_integer_signal(_widget); } //----------------------------------------------------------------------------- void parameters::on_reset_double_mapped(QWidget* _widget) { auto* spinbox = qobject_cast(_widget); auto* slider = qobject_cast(_widget); this->blockSignals(true); if(slider != nullptr) { const double value = slider->property("defaultValue").toDouble(); const double min = slider->property("min").toDouble(); const double max = slider->property("max").toDouble(); const double value_range = max - min; const int slider_val = int(std::round(((value - min) / value_range) * double(slider->maximum()))); slider->setValue(slider_val); } else if(spinbox != nullptr) { const QString key = spinbox->property("key").toString(); const double value = spinbox->property("defaultValue").toDouble(); const int count = spinbox->property("count").toInt(); SIGHT_ASSERT(get_id() << ": Invalid widgets count, must be <= 3", count <= 3); auto* spin1 = spinbox->property("widget#0").value(); spin1->setValue(value); if(count > 1) { auto* spin2 = spinbox->property("widget#1").value(); spin2->setValue(value); if(count == 3) { auto* spin3 = spinbox->property("widget#2").value(); spin3->setValue(value); } } } this->blockSignals(false); this->emit_double_signal(_widget); } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* parameters::create_reset_button(const std::string& _key, std::function _on_click) const { std::string service_id = base_id(); auto* reset_button = new QPushButton("R"); reset_button->setObjectName(QString::fromStdString(service_id + "/Reset " + _key)); reset_button->setFocusPolicy(Qt::NoFocus); reset_button->setToolTip("Reset to the default value."); reset_button->setMaximumWidth(20); reset_button->setStyleSheet(qApp->styleSheet()); QObject::connect(reset_button, &QPushButton::clicked, this, _on_click); return reset_button; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* parameters::create_bool_widget(QBoxLayout& _layout, const param_widget& _setup) const { auto* checkbox = new QCheckBox(); { const auto key = QString::fromStdString(_setup.key); checkbox->setTristate(false); if(_setup.default_value == "true") { checkbox->setCheckState(Qt::Checked); } checkbox->setObjectName(key); checkbox->setProperty("key", key); checkbox->setProperty("defaultValue", checkbox->checkState()); checkbox->setMinimumSize(_setup.min_size); checkbox->setStyleSheet(qApp->styleSheet()); checkbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } _layout.addWidget(checkbox); // Forward to the Sight signal QObject::connect(checkbox, &QCheckBox::stateChanged, this, ¶meters::on_change_boolean); // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, checkbox](){on_reset_boolean_mapped(checkbox);}); } return nullptr; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* parameters::create_color_widget(QBoxLayout& _layout, const param_widget& _setup) const { auto* colour_button = new QPushButton("Color"); { colour_button->setObjectName(QString::fromStdString(_setup.key)); colour_button->setToolTip(tr("Selected color")); std::string color_str = "#ffffffff"; if(!_setup.default_value.empty()) { std::array color {}; data::tools::color::hexa_string_to_rgba(_setup.default_value, color); color_str = _setup.default_value; } std::array color {}; data::tools::color::hexa_string_to_rgba(color_str, color); const int icon_size = colour_button->style()->pixelMetric(QStyle::PM_LargeIconSize); const QColor color_qt(color[0], color[1], color[2], color[3]); QPixmap pix(icon_size, icon_size); pix.fill(color_qt); colour_button->setIcon(QIcon(pix)); colour_button->setProperty("key", QString::fromStdString(_setup.key)); colour_button->setProperty("defaultValue", color_qt); colour_button->setProperty("color", color_qt); colour_button->setMinimumSize(_setup.min_size); colour_button->setStyleSheet(qApp->styleSheet()); colour_button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } _layout.addWidget(colour_button); QObject::connect(colour_button, &QPushButton::clicked, this, ¶meters::on_color_button); // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, colour_button]{on_reset_color_mapped(colour_button);}); } return nullptr; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* parameters::create_double_widget( QBoxLayout& _layout, const double_widget& _setup, int _count, Qt::Orientation _orientation ) { const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* sub_layout = new QBoxLayout {layout_direction}; { _layout.addLayout(sub_layout); } std::array spinboxes {}; // Spinboxes for(std::size_t i = 0 ; i < std::size_t(_count) ; ++i) { auto* spinbox = new QDoubleSpinBox(); spinbox->setObjectName(QString::fromStdString(_setup.key + "/" + std::to_string(i))); spinboxes[i] = spinbox; auto count_decimals = [](double _num) -> int { std::stringstream out; out << _num; const std::string s = out.str(); const std::string t = s.substr(s.find('.') + 1); return static_cast(t.length()); }; spinbox->setDecimals(std::max(std::max(count_decimals(_setup.min), count_decimals(_setup.max)), 2)); spinbox->setRange(_setup.min, _setup.max); // Beware, set setSingleStep after setRange() and setDecimals() otherwise it may fail spinbox->setSingleStep(std::abs(spinbox->maximum() - spinbox->minimum()) / 100.); spinbox->setMinimumSize(_setup.min_size); // Set value last only after setting range and decimals, otherwise the value may be truncated double initial_value = std::midpoint(_setup.min, _setup.max); if(not _setup.default_value.empty()) { try { initial_value = boost::lexical_cast(_setup.default_value); } catch([[maybe_unused]] const boost::bad_lexical_cast& e) { SIGHT_ASSERT( "Invalid value " << std::quoted(_setup.default_value) << ": not convertible to 'int/double': " << e.what(), false ); } } spinbox->setValue(initial_value); spinbox->setProperty("key", QString::fromStdString(_setup.key)); spinbox->setProperty("count", _count); spinbox->setProperty("defaultValue", spinbox->value()); spinbox->setStyleSheet(qApp->styleSheet()); spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); sub_layout->addWidget(spinbox); QObject::connect( spinbox, QOverload::of(&QDoubleSpinBox::valueChanged), this, ¶meters::on_change_double ); } QDoubleSpinBox* spinbox = spinboxes[0]; spinbox->setObjectName(QString::fromStdString(_setup.key)); // Set a property with a pointer on each member of the group for(std::size_t i = 0 ; i < std::size_t(_count) ; ++i) { for(std::size_t j = 0 ; j < std::size_t(_count) ; ++j) { const std::string prop_name = std::string("widget#") + std::to_string(j); spinboxes[i]->setProperty(prop_name.c_str(), QVariant::fromValue(spinboxes[j])); } } // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, spinbox]{on_reset_double_mapped(spinbox);}); } return nullptr; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* parameters::create_double_slider_widget( QBoxLayout& _layout, const double_widget& _setup, std::uint8_t _decimals, Qt::Orientation _orientation, bool _on_release ) { const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; double initial_value = std::midpoint(_setup.min, _setup.max); if(not _setup.default_value.empty()) { try { initial_value = boost::lexical_cast(_setup.default_value); } catch([[maybe_unused]] const boost::bad_lexical_cast& e) { SIGHT_ASSERT( "Invalid value " << std::quoted(_setup.default_value) << ": not convertible to 'int/double': " << e.what(), false ); } } auto* const sub_layout = new QBoxLayout {layout_direction}; sub_layout->setSpacing(0); sub_layout->setContentsMargins(0, 0, 0, 0); _layout.addLayout(sub_layout); auto* const slider = new QSlider(); { const double value_range = _setup.max - _setup.min; slider->setOrientation(_orientation); slider->setObjectName(QString::fromStdString(_setup.key)); slider->setProperty("key", slider->objectName()); slider->setProperty("count", 1); slider->setProperty("defaultValue", initial_value); slider->setProperty("decimals", _decimals); slider->setProperty("min", _setup.min); slider->setProperty("max", _setup.max); // tracking true: emit signal when value change, false: emit signal when slider released. slider->setTracking(!_on_release); set_double_slider_range(slider, initial_value); const int default_slider_value = int(std::round(((initial_value - _setup.min) / value_range) * double(slider->maximum()))); slider->setValue(default_slider_value); slider->setMinimumSize(_setup.min_size); slider->setProperty("widget#0", QVariant::fromValue(slider)); slider->setStyleSheet(qApp->styleSheet()); slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } QFont min_max_labels_font; min_max_labels_font.setPointSize(7); min_max_labels_font.setItalic(true); auto* const min_value_label = new QLabel(); min_value_label->setFont(min_max_labels_font); min_value_label->setText(QString::number(_setup.min, 'g', _decimals)); min_value_label->setToolTip("Minimum value."); min_value_label->setObjectName(QString::fromStdString(_setup.key + "/minValueLabel")); min_value_label->setAlignment(Qt::AlignCenter); min_value_label->setStyleSheet(qApp->styleSheet()); auto* const max_value_label = new QLabel(); max_value_label->setFont(min_max_labels_font); max_value_label->setText(QString::number(_setup.max, 'g', _decimals)); max_value_label->setToolTip("Maximum value."); max_value_label->setObjectName(QString::fromStdString(_setup.key + "/maxValueLabel")); max_value_label->setAlignment(Qt::AlignCenter); max_value_label->setStyleSheet(qApp->styleSheet()); auto* const value_label = new QLabel(); value_label->setStyleSheet("QLabel { font: bold; }"); value_label->setText(QString::number(initial_value, 'f', _decimals)); value_label->setToolTip("Current value."); sight::module::ui::qt::parameters::set_label_minimum_size(value_label, _setup.min, _setup.max, _decimals); value_label->setObjectName(QString::fromStdString(_setup.key + "/valueLabel")); value_label->setAlignment(Qt::AlignCenter); value_label->setStyleSheet(qApp->styleSheet()); // Slots { // Forward to the corresponding Sight signal QObject::connect(slider, &QSlider::valueChanged, this, ¶meters::on_change_double_slider); // Update the labels when the value or the range changes QObject::connect( slider, &QSlider::valueChanged, this, [value_label, slider] { sight::module::ui::qt::parameters::on_double_slider_mapped(value_label, slider); }); QObject::connect( slider, &QSlider::rangeChanged, this, [ = ]{on_double_slider_range_mapped(min_value_label, max_value_label, slider);}); } // Sub layout setup { const auto alignment = _orientation == Qt::Vertical ? Qt::AlignCenter : Qt::Alignment {}; auto* const first_label = _orientation == Qt::Vertical ? max_value_label : min_value_label; auto* const second_label = _orientation == Qt::Vertical ? min_value_label : max_value_label; sub_layout->addWidget(first_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(slider, /*scretch=*/ 0, alignment); sub_layout->addWidget(second_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(value_label, /*scretch=*/ 0, alignment); sub_layout->setAlignment(alignment); if(_setup.hide_min_max) { min_value_label->hide(); max_value_label->hide(); } } // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, slider]{on_reset_double_mapped(slider);}); } return nullptr; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* parameters::create_integer_slider_widget( QBoxLayout& _layout, const int_widget& _setup, Qt::Orientation _orientation, bool _on_release ) { int initial_value = std::midpoint(_setup.min, _setup.max); if(not _setup.default_value.empty()) { try { initial_value = boost::lexical_cast(_setup.default_value); } catch([[maybe_unused]] const boost::bad_lexical_cast& e) { SIGHT_ASSERT( "Invalid value " << std::quoted(_setup.default_value) << ": not convertible to 'int/double': " << e.what(), false ); } } const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* sub_layout = new QBoxLayout {layout_direction}; _layout.addLayout(sub_layout); sub_layout->setSpacing(0); sub_layout->setContentsMargins(0, 0, 0, 0); auto* slider = new QSlider(); slider->setOrientation(_orientation); slider->setObjectName(QString::fromStdString(_setup.key)); slider->setMinimum(_setup.min); slider->setMaximum(_setup.max); slider->setValue(initial_value); slider->setMinimumSize(_setup.min_size); // tracking true: emit signal when value change, false: emit signal when slider released. slider->setTracking(!_on_release); slider->setProperty("key", QString::fromStdString(_setup.key)); slider->setProperty("count", 1); slider->setProperty("defaultValue", slider->value()); slider->setProperty("widget#0", QVariant::fromValue(slider)); slider->setStyleSheet(qApp->styleSheet()); slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QFont min_max_labels_font; min_max_labels_font.setPointSize(7); min_max_labels_font.setItalic(true); auto* min_value_label = new QLabel(); min_value_label->setFont(min_max_labels_font); min_value_label->setText(QString::number(slider->minimum())); min_value_label->setToolTip("Minimum value."); min_value_label->setObjectName(QString::fromStdString(_setup.key + "/minValueLabel")); min_value_label->setAlignment(Qt::AlignCenter); min_value_label->setStyleSheet(qApp->styleSheet()); auto* max_value_label = new QLabel(); max_value_label->setFont(min_max_labels_font); max_value_label->setText(QString::number(slider->maximum())); max_value_label->setToolTip("Maximum value."); max_value_label->setObjectName(QString::fromStdString(_setup.key + "/maxValueLabel")); max_value_label->setAlignment(Qt::AlignCenter); max_value_label->setStyleSheet(qApp->styleSheet()); auto* value_label = new QLabel(); value_label->setStyleSheet("QLabel { font: bold; }"); value_label->setText(QString("%1").arg(slider->value())); value_label->setToolTip("Current value."); sight::module::ui::qt::parameters::set_label_minimum_size(value_label, _setup.min, _setup.max); value_label->setObjectName(QString::fromStdString(_setup.key + "/valueLabel")); value_label->setAlignment(Qt::AlignCenter); value_label->setStyleSheet(qApp->styleSheet()); // Sub layout setup { const auto alignment = _orientation == Qt::Vertical ? Qt::AlignCenter : Qt::Alignment {}; auto* const first_label = _orientation == Qt::Vertical ? max_value_label : min_value_label; auto* const second_label = _orientation == Qt::Vertical ? min_value_label : max_value_label; sub_layout->addWidget(first_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(slider, /*scretch=*/ 0, alignment); sub_layout->addWidget(second_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(value_label, /*scretch=*/ 0, alignment); sub_layout->setAlignment(alignment); if(_setup.hide_min_max) { min_value_label->hide(); max_value_label->hide(); } } // Connections { // Forward to the corresponding Sight signal QObject::connect(slider, &QSlider::valueChanged, this, ¶meters::on_change_integer); // Update the labels when the value or the range changes QObject::connect( slider, &QSlider::valueChanged, this, [value_label, slider]{sight::module::ui::qt::parameters::on_slider_mapped(value_label, slider);}); QObject::connect( slider, &QSlider::rangeChanged, this, [ = ]{on_slider_range_mapped(min_value_label, max_value_label, slider);}); } // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, slider]{on_reset_integer_mapped(slider);}); } return nullptr; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* parameters::create_integer_spin_widget( QBoxLayout& _layout, const int_widget& _setup, int _count, Qt::Orientation _orientation ) { int initial_value = std::midpoint(_setup.min, _setup.max); if(not _setup.default_value.empty()) { try { initial_value = boost::lexical_cast(_setup.default_value); } catch([[maybe_unused]] const boost::bad_lexical_cast& e) { SIGHT_ASSERT( "Invalid value " << std::quoted(_setup.default_value) << ": not convertible to 'int/double': " << e.what(), false ); } } const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* sub_layout = new QBoxLayout {layout_direction}; _layout.addLayout(sub_layout); std::array spinboxes {}; // Spinboxes for(std::size_t i = 0 ; i < std::size_t(_count) ; ++i) { auto* spinbox = new QSpinBox(); spinboxes[i] = spinbox; spinbox->setObjectName(QString::fromStdString(_setup.key + "/" + std::to_string(i))); spinbox->setMinimum(_setup.min); spinbox->setMaximum(_setup.max); spinbox->setValue(initial_value); spinbox->setMinimumSize(_setup.min_size); spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); spinbox->setProperty("key", QString::fromStdString(_setup.key)); spinbox->setProperty("count", _count); spinbox->setProperty("defaultValue", spinbox->value()); spinbox->setStyleSheet(qApp->styleSheet()); spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); sub_layout->addWidget(spinbox); // Connect spinbox value with our editor QObject::connect(spinbox, QOverload::of(&QSpinBox::valueChanged), this, ¶meters::on_change_integer); } QSpinBox* first_spinbox = spinboxes[0]; first_spinbox->setObjectName(QString::fromStdString(_setup.key)); // Set a property with a pointer on each member of the group for(std::size_t i = 0 ; i < std::size_t(_count) ; ++i) { for(std::size_t j = 0 ; j < std::size_t(_count) ; ++j) { const std::string prop_name = std::string("widget#") + std::to_string(j); spinboxes[i]->setProperty(prop_name.c_str(), QVariant::fromValue(spinboxes[j])); } } // Reset button if(_setup.reset_button) { return this->create_reset_button( _setup.key, [this, first_spinbox](){on_reset_integer_mapped(first_spinbox);}); } return nullptr; } //----------------------------------------------------------------------------- void parameters::update_enum_list(const std::vector& _list, const std::string _key) { std::vector labels; std::vector keys; for(const auto& s : _list) { const auto [left_part, right_part] = split_string(s); labels.push_back(left_part); if(!right_part.empty()) { keys.push_back(right_part); } else { keys.push_back(left_part); } } this->blockSignals(true); QObject* widget = this->get_param_widget(_key); auto* combobox = qobject_cast(widget); if(combobox != nullptr) { combobox->clear(); int idx = 0; for(const auto& l : labels) { combobox->insertItem(idx, QString::fromStdString(l)); ++idx; } // Add optional data idx = 0; for(const auto& k : keys) { combobox->setItemData(idx, QString::fromStdString(k)); ++idx; } } else if(auto* non_linear_slider = qobject_cast(widget); non_linear_slider != nullptr) { std::vector values; values.reserve(labels.size()); std::ranges::transform(labels, std::back_inserter(values), [](const std::string& _s){return std::stoi(_s);}); non_linear_slider->set_values(values); non_linear_slider->set_value(values[0]); } this->blockSignals(false); } //----------------------------------------------------------------------------- void parameters::parse_enum_string( const std::string& _options, std::vector& _labels, std::vector& _keys, std::string _separators ) { const boost::char_separator sep(_separators.c_str()); const boost::tokenizer > tokens {_options, sep}; for(const auto& token : tokens) { const auto [left_part, right_part] = split_string(token); _labels.push_back(left_part); if(!right_part.empty()) { _keys.push_back(right_part); } else { _keys.push_back(left_part); } } } //------------------------------------------------------------------------------ void parameters::create_enum_widget( QBoxLayout& _layout, const param_widget& _setup, const std::vector& _values, const std::vector& _data ) const { auto* menu = new QComboBox(); menu->setObjectName(QString::fromStdString(_setup.key)); menu->setStyleSheet(qApp->styleSheet()); menu->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); menu->setProperty("key", QString::fromStdString(_setup.key)); int idx = 0; for(const auto& value : _values) { menu->insertItem(idx, QString::fromStdString(value)); ++idx; } // Add optional data idx = 0; for(const auto& choice : _data) { menu->setItemData(idx, QString::fromStdString(choice)); ++idx; } _layout.addWidget(menu); QObject::connect(menu, QOverload::of(&QComboBox::currentIndexChanged), this, ¶meters::on_change_enum); // Set the comboBox to the default value menu->setCurrentText(QString::fromStdString(_setup.default_value)); menu->setMinimumSize(_setup.min_size); } //------------------------------------------------------------------------------ void parameters::create_slider_enum_widget( QBoxLayout& _layout, const param_widget& _setup, const std::vector& _values, Qt::Orientation _orientation, bool _on_release ) const { const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* sub_layout = new QBoxLayout {layout_direction}; { _layout.addLayout(sub_layout); } std::vector int_values; std::ranges::transform( _values, std::back_inserter(int_values), [](const std::string& _s){return std::stoi(_s);}); auto* const slider = new sight::ui::qt::widget::non_linear_slider(); slider->set_orientation(_orientation); slider->setMinimumSize(_setup.min_size); slider->setObjectName(QString::fromStdString(_setup.key)); slider->setProperty("key", QString::fromStdString(_setup.key)); slider->set_values(int_values); slider->set_value(std::stoi(_setup.default_value)); slider->set_tracking(!_on_release); slider->setStyleSheet(qApp->styleSheet()); slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QFont min_max_labels_font; min_max_labels_font.setPointSize(7); min_max_labels_font.setItalic(true); auto* const min_value_label = new QLabel(); min_value_label->setFont(min_max_labels_font); min_value_label->setText(QString::fromStdString(_values.front())); min_value_label->setToolTip("Minimum value."); min_value_label->setObjectName(QString::fromStdString(_setup.key + "/minValueLabel")); min_value_label->setAlignment(Qt::AlignCenter); min_value_label->setStyleSheet(qApp->styleSheet()); auto* const max_value_label = new QLabel(); max_value_label->setFont(min_max_labels_font); max_value_label->setText(QString::fromStdString(_values.back())); max_value_label->setToolTip("Maximum value."); max_value_label->setObjectName(QString::fromStdString(_setup.key + "/maxValueLabel")); max_value_label->setAlignment(Qt::AlignCenter); max_value_label->setStyleSheet(qApp->styleSheet()); auto* value_label = new QLabel(); value_label->setStyleSheet("QLabel { font: bold; }"); value_label->setText(QString::number(slider->value())); value_label->setToolTip("Current value."); set_label_minimum_size(value_label, int_values.front(), int_values.back()); value_label->setObjectName(QString::fromStdString(_setup.key + "/valueLabel")); value_label->setAlignment(Qt::AlignCenter); // Sub layout setup { const auto alignment = _orientation == Qt::Vertical ? Qt::AlignCenter : Qt::Alignment {}; auto* const first_label = _orientation == Qt::Vertical ? max_value_label : min_value_label; auto* const second_label = _orientation == Qt::Vertical ? min_value_label : max_value_label; sub_layout->addWidget(first_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(slider, /*stretch=*/ 0, alignment); sub_layout->addWidget(second_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(value_label, /*scretch=*/ 0, alignment); if(_setup.hide_min_max) { min_value_label->hide(); max_value_label->hide(); } } // Connections { // Forward to the Sight signal QObject::connect( slider, &sight::ui::qt::widget::non_linear_slider::value_changed, [this, key = _setup.key, slider, value_label](int _value) { if(!m_block_signals) { this->signal(signals::INTEGER_CHANGED_SIG) ->async_emit(_value, key); this->signal(signals::PARAMETER_CHANGED_SIG)->async_emit(_value, key); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::INTEGER_CHANGED_SIG << "(" << _value << ", " << key << ")" ); this->signal(signals::ENUM_CHANGED_SIG) ->async_emit(std::to_string(_value), key); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(std::to_string(_value), key); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::ENUM_CHANGED_SIG << "(" << _value << ", " << key << ")" ); this->signal(signals::ENUM_INDEX_CHANGED_SIG) ->async_emit(int(slider->index()), key); } value_label->setText(QString::number(_value)); }); // Update the labels QObject::connect( slider, &sight::ui::qt::widget::non_linear_slider::range_changed, [ = ](int _min, int _max) { min_value_label->setText(QString::number(_min)); max_value_label->setText(QString::number(_max)); }); } } //----------------------------------------------------------------------------- void parameters::create_button_bar_enum_widget( QBoxLayout& _layout, const param_widget& _setup, const std::vector& _button_list, const int _width, const int _height, const int _spacing, const std::string& _style, Qt::Orientation _orientation ) const { const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* sub_layout = new QBoxLayout {layout_direction}; _layout.addLayout(sub_layout); if(_spacing != 0) { sub_layout->setSpacing(_spacing); } // create a button group to deactivate the buttons on selection auto* button_bar_group = new QButtonGroup(sub_layout); button_bar_group->setObjectName(QString::fromStdString(_setup.key)); // create the buttons from the provided list int button_index = 0; for(const auto& button_param : _button_list) { auto* enum_button = new QToolButton(); button_bar_group->addButton(enum_button); // The name needs to be the ky_value, to find it when the service is updated through a slot enum_button->setObjectName((QString::fromStdString(_setup.key + "_" + button_param.value))); enum_button->setIcon(QIcon(QString::fromStdString(button_param.icon_path))); enum_button->setToolTip(QString::fromStdString(button_param.label)); enum_button->setCheckable(true); enum_button->setProperty("class", "buttonBar"); enum_button->setProperty("value", QString::fromStdString(button_param.value)); enum_button->setText(QString::fromStdString(button_param.label)); enum_button->setStyleSheet(qApp->styleSheet()); enum_button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); if(_style == "ToolButtonTextOnly") { enum_button->setToolButtonStyle(Qt::ToolButtonTextOnly); } else if(_style == "ToolButtonTextBesideIcon") { enum_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else if(_style == "ToolButtonTextUnderIcon") { enum_button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } else if(_style == "ToolButtonFollowStyle") { enum_button->setToolButtonStyle(Qt::ToolButtonFollowStyle); } else { enum_button->setToolButtonStyle(Qt::ToolButtonIconOnly); } // create an effect to make it gray when not selected, and full color when selected auto* effect = new QGraphicsColorizeEffect; effect->setColor(QColor(10, 10, 10)); effect->setStrength(0.7); enum_button->setGraphicsEffect(effect); // the size depends on the configuration. xml > qss if(_width != 0 || _height != 0) { // the size is provided through the xml config. Don't use the qss and ignore _setup.min_size const int width = _width == 0 ? _height : _width; const int height = _height == 0 ? _width : _height; enum_button->setIconSize({width, height}); } else { // the size is not provided through the xml. Use the qss style. enum_button->setProperty("class", "buttonBarTouchFriendly"); } // add the button in the grid at its place sub_layout->addWidget(enum_button); // create the connection to fire signals when the button is clicked QObject::connect( enum_button, &QToolButton::clicked, [this, button_param, key = _setup.key, button_index] { if(!m_block_signals) { this->signal(signals::ENUM_CHANGED_SIG) ->async_emit(button_param.value, key); this->signal(signals::PARAMETER_CHANGED_SIG) ->async_emit(button_param.value, key); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::ENUM_CHANGED_SIG << "(" << button_param.value << ", " << key << ")" ); this->signal(signals::ENUM_INDEX_CHANGED_SIG) ->async_emit(button_index, key); } }); // create connection to change the display (gray/full color) when the selection state changes QObject::connect( enum_button, &QAbstractButton::toggled, [ = ](bool _checked) { QGraphicsEffect* effect = enum_button->graphicsEffect(); effect->setEnabled(!_checked); }); // set the default value if(button_param.value == _setup.default_value) { enum_button->toggle(); } ++button_index; } } //----------------------------------------------------------------------------- double parameters::get_double_slider_value(const QSlider* _slider) { const double min = _slider->property("min").toDouble(); const double max = _slider->property("max").toDouble(); const double value_range = max - min; double double_value = min; if(_slider->maximum() != 0) { double_value = (double(_slider->value()) / _slider->maximum()) * value_range + min; } return double_value; } //------------------------------------------------------------------------------ void parameters::set_parameter(sight::ui::parameter_t _val, std::string _key) { this->blockSignals(true); if(std::holds_alternative(_val)) { this->set_bool_parameter(std::get(_val), _key); } else if(std::holds_alternative(_val)) { this->set_color_parameter(std::get(_val), _key); } else if(std::holds_alternative(_val)) { this->set_double_parameter(std::get(_val), _key); } else if(std::holds_alternative(_val)) { const auto values = std::get(_val); this->set_double2_parameter(values[0], values[1], _key); } else if(std::holds_alternative(_val)) { const auto values = std::get(_val); this->set_double3_parameter(values[0], values[1], values[2], _key); } else if(std::holds_alternative(_val)) { const auto values = std::get(_val); this->set_int2_parameter(values[0], values[1], _key); } else if(std::holds_alternative(_val)) { const auto values = std::get(_val); this->set_int3_parameter(values[0], values[1], values[2], _key); } else if(std::holds_alternative(_val)) { this->set_enum_parameter(std::get(_val), _key); } else if(std::holds_alternative(_val)) { // This can be either an int or a enum index // Solve the ambiguity by testing the widget type QObject* child = this->get_param_widget(_key); auto* spinbox = qobject_cast(child); auto* slider = qobject_cast(child); auto* non_linear_slider = qobject_cast(child); if(spinbox != nullptr || slider != nullptr || non_linear_slider != nullptr) { this->set_int_parameter(std::get(_val), _key); } else { this->set_enum_index_parameter(std::get(_val), _key); } } else if(std::holds_alternative(_val)) { this->update_enum_list(std::get(_val), _key); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::set_bool_parameter(bool _val, std::string _key) { this->blockSignals(true); QObject* child = this->get_param_widget(_key); auto* checkbox = qobject_cast(child); if(checkbox != nullptr) { checkbox->setCheckState(_val ? Qt::Checked : Qt::Unchecked); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::set_color_parameter(std::array _color, std::string _key) { this->blockSignals(true); QObject* child = this->get_param_widget(_key); auto* color_button = qobject_cast(child); if(color_button != nullptr) { const int icon_size = color_button->style()->pixelMetric(QStyle::PM_LargeIconSize); QPixmap pix(icon_size, icon_size); QColor color_qt(_color[0], _color[1], _color[2], _color[3]); pix.fill(color_qt); color_button->setIcon(QIcon(pix)); color_button->setProperty("color", color_qt); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::set_double_parameter(double _val, std::string _key) { this->blockSignals(true); QObject* child = this->get_param_widget(_key); if(auto* spinbox = qobject_cast(child); spinbox != nullptr) { spinbox->setValue(_val); } else if(auto* slider = qobject_cast(child); slider != nullptr) { const double min = slider->property("min").toDouble(); const double max = slider->property("max").toDouble(); const double value_range = max - min; const int slider_val = int(std::round(((_val - min) / value_range) * double(slider->maximum()))); slider->setValue(slider_val); } else { SIGHT_ERROR(get_id() << ": Widget " << std::quoted(_key) << " must be a QSlider or a QDoubleSpinBox"); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::set_double2_parameter(double _val0, double _val1, std::string _key) { this->blockSignals(true); QObject* child = this->get_param_widget(_key); if(child != nullptr) { auto* spin0 = child->property("widget#0").value(); auto* spin1 = child->property("widget#1").value(); spin0->setValue(_val0); spin1->setValue(_val1); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::set_double3_parameter(double _val0, double _val1, double _val2, std::string _key) { this->blockSignals(true); QObject* child = this->get_param_widget(_key); if(child != nullptr) { auto* spin0 = child->property("widget#0").value(); auto* spin1 = child->property("widget#1").value(); auto* spin2 = child->property("widget#2").value(); spin0->setValue(_val0); spin1->setValue(_val1); spin2->setValue(_val2); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::set_int_parameter(int _val, std::string _key) { this->blockSignals(true); QObject* child = this->get_param_widget(_key); auto* spinbox = qobject_cast(child); auto* slider = qobject_cast(child); if(spinbox != nullptr) { spinbox->setValue(_val); } else if(slider != nullptr) { slider->setValue(_val); } else if(auto* non_linear_slider = qobject_cast(child); non_linear_slider != nullptr) { non_linear_slider->set_value(_val); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::set_int2_parameter(int _val0, int _val1, std::string _key) { this->blockSignals(true); QObject* child = this->get_param_widget(_key); if(child != nullptr) { auto* spin0 = child->property("widget#0").value(); auto* spin1 = child->property("widget#1").value(); spin0->setValue(_val0); spin1->setValue(_val1); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::set_int3_parameter(int _val0, int _val1, int _val2, std::string _key) { this->blockSignals(true); QObject* widget = this->get_param_widget(_key); if(widget != nullptr) { auto* spin0 = widget->property("widget#0").value(); auto* spin1 = widget->property("widget#1").value(); auto* spin2 = widget->property("widget#2").value(); spin0->setValue(_val0); spin1->setValue(_val1); spin2->setValue(_val2); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::set_enum_parameter(std::string _val, std::string _key) { this->blockSignals(true); QObject* widget = this->get_param_widget(_key); auto* combobox = qobject_cast(widget); if(combobox != nullptr) { // Find first in text auto res = combobox->findText(QString::fromStdString(_val)); if(res == -1) { // fallback, try to find in optional data. res = combobox->findData(QString::fromStdString(_val)); } if(res >= 0) { combobox->setCurrentIndex(res); } else { SIGHT_WARN( get_id() << "value " << std::quoted(_val) << "doesn't exist for parameter " << std::quoted( _key ) << "." ); } } else if(auto* non_linear_slider = qobject_cast(widget); non_linear_slider != nullptr) { non_linear_slider->set_value(std::stoi(_val)); } else if(auto* button_group = qobject_cast(widget); button_group != nullptr) { const auto enum_key = QString::fromStdString(_key + "_" + _val); const auto& buttons = button_group->buttons(); auto button_it = std::ranges::find_if( buttons, [&enum_key](const auto* const _b) { return _b->objectName() == enum_key; }); SIGHT_ASSERT( get_id() << ": Value " << std::quoted(_val) << " does not appear in parameter widget " << std::quoted(_key), button_it != buttons.cend() ); (*button_it)->toggle(); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::set_enum_index_parameter(int _val, std::string _key) { this->blockSignals(true); QObject* widget = this->get_param_widget(_key); auto* combobox = qobject_cast(widget); if(combobox != nullptr) { combobox->setCurrentIndex(_val); } this->blockSignals(false); } //------------------------------------------------------------------------------ void parameters::update_enum_range(std::string _options, std::string _key) { this->blockSignals(true); QObject* widget = this->get_param_widget(_key); auto* combobox = qobject_cast(widget); if(combobox != nullptr) { combobox->clear(); std::vector values; std::vector data; sight::module::ui::qt::parameters::parse_enum_string(_options, values, data); int idx = 0; for(const auto& value : values) { combobox->insertItem(idx, QString::fromStdString(value)); ++idx; } // Add optional data idx = 0; for(const auto& choice : data) { combobox->setItemData(idx, QString::fromStdString(choice)); ++idx; } } this->blockSignals(false); } //----------------------------------------------------------------------------- void parameters::emit_color_signal(const QColor _color, const std::string& _key) const { const std::array new_color = {{static_cast(_color.red()), static_cast(_color.green()), static_cast(_color.blue()), static_cast(_color.alpha()) } }; if(!m_block_signals) { this->signal(signals::COLOR_CHANGED_SIG)->async_emit(new_color, _key); this->signal(signals::PARAMETER_CHANGED_SIG)->async_emit(new_color, _key); SIGHT_DEBUG( get_id() << ": [EMIT] " << signals::COLOR_CHANGED_SIG << "(" << int(new_color[0]) << ", " << int(new_color[1]) << ", " << int(new_color[2]) << ", " << int(new_color[3]) << ", " << _key << ")" ); } } //----------------------------------------------------------------------------- void parameters::blockSignals(bool _block) { m_block_signals = _block; } //------------------------------------------------------------------------------ void parameters::update_int_min_parameter(int _min, std::string _key) { QObject* child = this->get_param_widget(_key); auto* spinbox = qobject_cast(child); auto* slider = qobject_cast(child); if(spinbox != nullptr) { const int count = child->property("count").toInt(); auto* spin0 = child->property("widget#0").value(); spin0->setMinimum(_min); if(count >= 2) { auto* spin1 = child->property("widget#1").value(); spin1->setMinimum(_min); } if(count >= 3) { auto* spin2 = child->property("widget#2").value(); spin2->setMinimum(_min); } } else if(slider != nullptr) { slider->setMinimum(_min); } else { SIGHT_ERROR(get_id() << ": Widget " << std::quoted(_key) << " must be a QSlider or a QDoubleSpinBox"); } } //------------------------------------------------------------------------------ void parameters::update_int_max_parameter(int _max, std::string _key) { QObject* child = this->get_param_widget(_key); auto* spinbox = qobject_cast(child); auto* slider = qobject_cast(child); if(spinbox != nullptr) { const int count = child->property("count").toInt(); auto* spin0 = child->property("widget#0").value(); spin0->setMaximum(_max); if(count >= 2) { auto* spin1 = child->property("widget#1").value(); spin1->setMaximum(_max); } if(count >= 3) { auto* spin2 = child->property("widget#2").value(); spin2->setMaximum(_max); } } else if(slider != nullptr) { slider->setMaximum(_max); } else { SIGHT_ERROR(get_id() << ": Widget " << std::quoted(_key) << " must be a QSlider or a QDoubleSpinBox"); } } //------------------------------------------------------------------------------ void parameters::update_double_min_parameter(double _min, std::string _key) { QObject* child = this->get_param_widget(_key); auto* spinbox = qobject_cast(child); auto* slider = qobject_cast(child); if(spinbox != nullptr) { const int count = child->property("count").toInt(); auto* spin0 = child->property("widget#0").value(); spin0->setMinimum(_min); if(count >= 2) { auto* spin1 = child->property("widget#1").value(); spin1->setMinimum(_min); } if(count >= 3) { auto* spin2 = child->property("widget#2").value(); spin2->setMinimum(_min); } } else if(slider != nullptr) { const double value = get_double_slider_value(slider); slider->setProperty("min", _min); set_double_slider_range(slider, value); } else { SIGHT_ERROR(get_id() << ": Widget " << std::quoted(_key) << " must be a QSlider or a QDoubleSpinBox"); } } //------------------------------------------------------------------------------ void parameters::update_double_max_parameter(double _max, std::string _key) { QObject* child = this->get_param_widget(_key); auto* spinbox = qobject_cast(child); auto* slider = qobject_cast(child); if(spinbox != nullptr) { const int count = child->property("count").toInt(); auto* spin0 = child->property("widget#0").value(); spin0->setMaximum(_max); if(count >= 2) { auto* spin1 = child->property("widget#1").value(); spin1->setMaximum(_max); } if(count >= 3) { auto* spin2 = child->property("widget#2").value(); spin2->setMaximum(_max); } } else if(slider != nullptr) { const double value = get_double_slider_value(slider); slider->setProperty("max", _max); set_double_slider_range(slider, value); } else { SIGHT_ERROR(get_id() << ": Widget " << std::quoted(_key) << " must be a QSlider or a QDoubleSpinBox"); } } //----------------------------------------------------------------------------- void parameters::set_double_slider_range(QSlider* _slider, double _current_value) { const std::string key = _slider->property("key").toString().toStdString(); const double min = _slider->property("min").toDouble(); const double max = _slider->property("max").toDouble(); const std::uint8_t decimals = static_cast(_slider->property("decimals").toUInt()); int max_slider_value = 1; for(std::uint8_t i = 0 ; i < decimals ; ++i) { max_slider_value *= 10; } const double value_range = max - min; max_slider_value = int(max_slider_value * value_range); // The slider's maximum internal range is [0; 2 147 483 647] // We could technically extend this range by setting the minimum to std::numeric_limits::min() // but it would be ridiculous to use a slider handling so many values. _slider->setMinimum(0); SIGHT_ERROR_IF( ": The requested value range for " << std::quoted( key ) << " is too large to be handled by a double slider. " "Please reduce your range, the number of decimals or use a 'spin' widget.", max_slider_value < std::numeric_limits::epsilon() ); if(max_slider_value < std::numeric_limits::epsilon()) { max_slider_value = 1.; } _slider->setMaximum(max_slider_value); // Update the slider integer value according to the new mix/max if(_current_value <= min) { _slider->setValue(0); // qt does not emit the signal if the value does not change, we have to force qt signal to update the displayed // value and emit 'doubleChanged' signal Q_EMIT _slider->valueChanged(0); } else if(_current_value > max) { _slider->setValue(max_slider_value); } else { const int slider_val = int(std::round(((_current_value - min) / value_range) * double(_slider->maximum()))); _slider->setValue(slider_val); } } //----------------------------------------------------------------------------- QObject* parameters::get_param_widget(const std::string& _key) { const auto key = QString::fromStdString(_key); const auto it = std::ranges::find_if( m_param_boxes, [box_key = key + "_box"](const QWidget* const _w){return _w->objectName() == box_key;}); if(it == m_param_boxes.cend()) { SIGHT_ERROR(get_id() << ": No param with '" + _key + "' not found"); return nullptr; } auto* widget = (*it)->findChild(key); // The child widget *MUST* exist if a parameter exists with this name, if it does not exist, this is a regression // caused by modifications in create_xxx_widgets. SIGHT_ASSERT(get_id() << ": Widget " << std::quoted(_key) << " not found in its parent box.", widget != nullptr); return widget; } //----------------------------------------------------------------------------- } //namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/parameters.hpp000066400000000000000000000614271503402212300201210ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include class QGroupBox; namespace sight::module::ui::qt { /** * @brief Generic editor to interact with parameters. * * It allows to setup an editor with several parameters. * Once the user validates the parameters, a signal is sent containing the key and the value. * It supports booleans, doubles or integer at the moment. * * @note This service doesn't need any data. * * @section Signals Signals * - \b parameter_changed(ui::parameter_t, std::string): Emitted when any parameter changes. * - \b bool_changed(bool, std::string): Emitted when a boolean parameter changes. * - \b color_changed(std::array, std::string): Emitted when a color parameter changes. * - \b double_changed(double, std::string): Emitted when a real parameter changes. * - \b double2_changed(double, double, std::string): Emitted when two real parameters change. * - \b double3_changed(double, double, double, std::string): Emitted when three real parameters change. * - \b int_changed(int, std::string): Emitted when an integer parameter changes. * - \b int2_changed(int, int, std::string): Emitted when two integer parameters change. * - \b int3_changed(int, int, int, std::string): Emitted when three integer parameters change. * - \b enum_changed(std::string, std::string): Emitted when enum parameter changes, returns the the label of the * currently selected item. * - \b enum_index_changed(int, std::string): Emitted when enum parameter changes, returns the index of the currently * selected item. * * @section Slots Slots * - \b set_parameter(ui::parameter_t, std::string): set a parameter. * - \b set_bool_parameter(bool, std::string): set a boolean parameter. * - \b set_color_parameter(std::array, std::string): set a color parameter. * - \b set_double_parameter(double, std::string): set a double parameter. * - \b set_double2_parameter(double, double, std::string): set two double parameters. * - \b set_double3_parameter(double, double, double, std::string): set three double parameters. * - \b set_int_parameter(int, std::string): set an integer parameter. * - \b set_int2_parameter(int, int, std::string): set two int parameters. * - \b set_int3_parameter(int, int, int, std::string): set three int parameters. * - \b set_enum_parameter(std::string, std::string): set an enum parameter. * - \b set_enum_index_parameter(int, std::string): set an enum parameter using the index of the enum. * - \b updateEnumRange(std::string, std::string): update range of an existing enum (value can contains a tokenized list * such as value1;value2;value3=test;...) * - \b update_int_min_parameter(int, std::string): set the minimum value of an integer parameter (int, int2, int3) * - \b update_int_max_parameter(int, std::string): set the maximum value of an integer parameter (int, int2, int3) * - \b update_double_min_parameter(double, std::string): set the minimum value of a double parameter (double, double2, * double3) * - \b update_double_max_parameter(double, std::string): set the maximum value of a double parameter (double, double2, * double3) * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection Configuration Configuration: * tag: * - \b scrollable: If true, add a scroll bar if the content doesn't fit on the screen. If false, flatten the content * tag: * - \b type: bool, color, enum, double, double2, double3, int, int2, int3. * - \b name: label to display. * - \b key: name used in the signal to identify the parameter. * - \b defaultValue: value used to initialize the parameter. * - \b min: minimum value, if relevant for the data type. * - \b max: maximum value, if relevant for the data type. * - \b widget (optional) : widget type, available for types 'int', 'double' and 'enum'. * For 'int' and 'double', you can choose between a 'spin' or a 'slider' widget. Defaults to 'spin' for 'double' and * 'slider' for 'int'. * For 'enum', you can choose between 'combobox' (the default), 'slider' (only available with integer values), or *'buttonBar'. * buttonBar widget requires additional configuration. * - \b value: the enum value sent when clicking on the button. * - \b label (optional, default=""): test displayed under the button. * - \b icon: path to the icon to display. * - \b decimals (optional, default=2): number of decimals settable using a double slider. * - \b reset (optional, default=true): display the reset button. * - \b values: list of possible values separated by a comma ',' a space ' ' or a semicolon ';' (only for enum type). * The actual displayed value and the returned one in the signal can be different using '=' to separate the two. For * example 'values="BLEND=imageBlend,CHECKERBOARD=imageCheckerboard"' means the combo will display BLEND, CHECKERBOARD * and will send 'imageBlend' or 'imageCheckerboard'. * - \b depends (optional, string): key of the dependency. * - \b dependsValue (optional, string): value of the dependency in case of enum. * - \b dependsReverse (optional, bool, default=false): reverse the dependency status checking. * - \b emitOnRelease (optional, default = false): sliders only, if true send value when slider is released, * send value when value changed otherwise. * - \b min_width (optional, int) Minimum width, in device coordinates. @todo Support relative widget size. * - \b min_height (optional, int) Minimum height, in device coordinates. @todo Support relative widget size. */ class parameters : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(parameters, sight::ui::editor); /// @brief Struct to handle all signals. struct signals { using signal_t = core::com::signals::key_t; /// Generic changed signal type using changed_signal_t = core::com::signal; /// Boolean changed signal type using boolean_changed_signal_t = core::com::signal; /// Color changed signal type using color_changed_signal_t = core::com::signal, std::string)>; /// Double changed signal type using double_changed_signal_t = core::com::signal; using double2_changed_signal_t = core::com::signal; using double3_changed_signal_t = core::com::signal; /// Integer changed signal type using integer_changed_signal_t = core::com::signal; using integer2_changed_signal_t = core::com::signal; using integer3_changed_signal_t = core::com::signal; /// Enum changed signal type using enum_changed_signal_t = core::com::signal; using enum_changed_index_signal_t = core::com::signal; inline static const signal_t PARAMETER_CHANGED_SIG = "parameter_changed"; inline static const signal_t BOOLEAN_CHANGED_SIG = "bool_changed"; inline static const signal_t COLOR_CHANGED_SIG = "color_changed"; inline static const signal_t DOUBLE_CHANGED_SIG = "double_changed"; inline static const signal_t DOUBLE2_CHANGED_SIG = "double2_changed"; inline static const signal_t DOUBLE3_CHANGED_SIG = "double3_changed"; inline static const signal_t INTEGER_CHANGED_SIG = "int_changed"; inline static const signal_t INTEGER2_CHANGED_SIG = "int2_changed"; inline static const signal_t INTEGER3_CHANGED_SIG = "int3_changed"; inline static const signal_t ENUM_CHANGED_SIG = "enum_changed"; inline static const signal_t ENUM_INDEX_CHANGED_SIG = "enum_index_changed"; }; /// @brief Struct to handle all slots struct slots { using slots_t = core::com::slots::key_t; inline static const slots_t SET_PARAMETER_SLOT = "set_parameter"; inline static const slots_t SET_BOOL_PARAMETER_SLOT = "set_bool_parameter"; inline static const slots_t SET_COLOR_PARAMETER_SLOT = "set_color_parameter"; inline static const slots_t SET_DOUBLE_PARAMETER_SLOT = "set_double_parameter"; inline static const slots_t SET_DOUBLE2_PARAMETER_SLOT = "set_double2_parameter"; inline static const slots_t SET_DOUBLE3_PARAMETER_SLOT = "set_double3_parameter"; inline static const slots_t SET_INT_PARAMETER_SLOT = "set_int_parameter"; inline static const slots_t SET_INT2_PARAMETER_SLOT = "set_int2_parameter"; inline static const slots_t SET_INT3_PARAMETER_SLOT = "set_int3_parameter"; inline static const slots_t SET_ENUM_PARAMETER_SLOT = "set_enum_parameter"; inline static const slots_t SET_ENUM_INDEX_PARAMETER_SLOT = "set_enum_index_parameter"; inline static const slots_t UPDATE_ENUM_RANGE_SLOT = "updateEnumRange"; inline static const slots_t UPDATE_INT_MIN_PARAMETER_SLOT = "update_int_min_parameter"; inline static const slots_t UPDATE_INT_MAX_PARAMETER_SLOT = "update_int_max_parameter"; inline static const slots_t UPDATE_DOUBLE_MIN_PARAMETER_SLOT = "update_double_min_parameter"; inline static const slots_t UPDATE_DOUBLE_MAX_PARAMETER_SLOT = "update_double_max_parameter"; }; struct enum_button_param { std::string value {""}; std::string label {""}; std::string icon_path {""}; }; struct param_widget { std::string name = {}; std::string key = {}; std::string default_value = {}; bool reset_button = true; bool hide_min_max = false; QSize min_size = {0, 0}; }; template struct scalar_widget : param_widget { T min = T {0}; T max = T {1}; }; using int_widget = scalar_widget; using double_widget = scalar_widget; parameters() noexcept; /// Destructor. Does nothing ~parameters() noexcept override = default; /// Configure the editor. void configuring() override; /// Initializes Qt input widgets for parameters according to xml configuration void starting() override; /// This method launches the editor::stopping method void stopping() override; /// This method is used to update services. Does nothing void updating() override; private Q_SLOTS: /** * @brief Called when a dependency widget state (enable or disable) has changed to modify the state of the child * widget. * @param _check_box Dependency widget. * @param _widget Child widget. * @param _reverse Reverse the state check. */ static void on_depends_changed(QCheckBox* _check_box, QWidget* _widget, bool _reverse); /** * @brief Called when a dependency widget state (enable or disable) has changed to modify the state of the child * widget. * @param _combo_box Dependency widget. * @param _widget Child widget. * @param _value Value of the combo box. * @param _reverse Reverse the state check. */ static void on_depends_changed(QComboBox* _combo_box, QWidget* _widget, const std::string& _value, bool _reverse); /// This method is called when a boolean value changes void on_change_boolean(int _value) const; /// This method is called when a color button is clicked void on_color_button(); /// This method is called when an integer value changes void on_change_integer(int _value) const; /// This method is called when a double value changes void on_change_double(double _value) const; /// This method is called when a double slider value changes void on_change_double_slider(int _value) const; /// This method is called when selection changes (QComboBox) void on_change_enum(int _value) const; /// This method is called to connect sliders to their labels static void on_slider_mapped(QLabel* _label, QSlider* _slider); /// This method is called to connect double sliders to their labels static void on_double_slider_mapped(QLabel* _label, QSlider* _slider); /// This method is called to connect reset buttons and checkboxes void on_reset_boolean_mapped(QWidget* _widget) const; /// This method is called to connect reset buttons and color widgets void on_reset_color_mapped(QWidget* _widget) const; /// This method is called to connect reset buttons and sliders void on_reset_integer_mapped(QWidget* _widget); /// This method is called to connect reset buttons and sliders void on_reset_double_mapped(QWidget* _widget); /// This method is called when the integer slider range is modified, it updates the min and max labels static void on_slider_range_mapped(QLabel* _min_label, QLabel* _max_label, QSlider* _slider); /// This method is called when the double slider range is modified, it updates the min and max labels static void on_double_slider_range_mapped(QLabel* _min_label, QLabel* _max_label, QSlider* _slider); private: /** * @brief Called on all dependent widget to update it. * @param _watched Widget to update. * @param _event Event type, only care about ::QEvent::EnabledChange * @return False. */ bool eventFilter(QObject* _watched, QEvent* _event) override; /// Creates a reset button for one widget. /// @param _key Name of the parameter it resets. /// @param _on_click Slot to call when the button is clicked (when QPushButton::clicked is sent) /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_reset_button(const std::string& _key, std::function _on_click) const; /// Create a widget associated with a boolean type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_bool_widget(QBoxLayout& _layout, const param_widget& _setup) const; /// Create a widget associated with a color type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_color_widget(QBoxLayout& _layout, const param_widget& _setup) const; /// Create a widget associated with a double type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_double_widget( QBoxLayout& _layout, const double_widget& _setup, int _count, Qt::Orientation _orientation ); /// Create a slider widget associated with a double type. /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_double_slider_widget( QBoxLayout& _layout, const double_widget& _setup, std::uint8_t _decimals, Qt::Orientation _orientation, bool _on_release ); /// Create a slider widget associated with an integer type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_integer_slider_widget( QBoxLayout& _layout, const int_widget& _setup, Qt::Orientation _orientation, bool _on_release ); /// Create a spin widget associated with an integer type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_integer_spin_widget( QBoxLayout& _layout, const int_widget& _setup, int _count, Qt::Orientation _orientation ); /// Parses the string for an enum static void parse_enum_string( const std::string& _options, std::vector& _labels, std::vector& _keys, std::string _separators = ", ;" ); /// Create a multi choice widget void create_enum_widget( QBoxLayout& _layout, const param_widget& _setup, const std::vector& _values, const std::vector& _data ) const; /// Create a multi choice widget with integer values void create_slider_enum_widget( QBoxLayout& _layout, const param_widget& _setup, const std::vector& _values, Qt::Orientation _orientation, bool _on_release ) const; void create_button_bar_enum_widget( QBoxLayout& _layout, const param_widget& _setup, const std::vector& _button_list, const int _width, const int _height, const int _spacing, const std::string& _style, Qt::Orientation _orientation ) const; /// Emit the signal(s) for the integer widget void emit_integer_signal(QObject* _widget) const; /// Emit the signal(s) for the double widget void emit_double_signal(QObject* _widget) const; /// Emit the signal for the color widget void emit_color_signal(const QColor _color, const std::string& _key) const; /** * @name Slots * @{ */ /// Slot: This method is used to set any parameter. void set_parameter(sight::ui::parameter_t _val, std::string _key); /// Slot: This method is used to set a boolean parameter. void set_bool_parameter(bool _val, std::string _key); /// Slot: This method is used to set a color parameter. void set_color_parameter(std::array _color, std::string _key); /// Slot: This method is used to set a double parameter. void set_double_parameter(double _val, std::string _key); /// Slot: This method is used to set two double parameters. void set_double2_parameter(double _val0, double _val1, std::string _key); /// Slot: This method is used to set three double parameters. void set_double3_parameter(double _val0, double _val1, double _val2, std::string _key); /// Slot: This method is used to set an integer parameter. void set_int_parameter(int _val, std::string _key); /// Slot: This method is used to set two int parameters. void set_int2_parameter(int _val0, int _val1, std::string _key); /// Slot: This method is used to set three int parameters. void set_int3_parameter(int _val0, int _val1, int _val2, std::string _key); /// Slot: This method is used to set an enum parameter. void set_enum_parameter(std::string _val, std::string _key); /// SLOT: This method sets an enum parameter using the index of the enum void set_enum_index_parameter(int /*val*/, std::string _key); /// SLOT: This method updates the all enum values using a tokenized string ("value1;value2") void update_enum_range(std::string _options, std::string _key); /// Slot: Updates the minimum value of an integer parameter (int, int2, int3) void update_int_min_parameter(int _min, std::string _key); /// Slot: Updates the maximum value of an integer parameter (int, int2, int3) void update_int_max_parameter(int _max, std::string _key); /// Slot: Updates the minimum value of a double parameter (double, double2, double3) void update_double_min_parameter(double _min, std::string _key); /// Slot: Updates the maximum value of a double parameter (double, double2, double3) void update_double_max_parameter(double _max, std::string _key); /// @} /// Internal function that updates enum widget value using a list of string (each element can contains value & data /// ex:"Value=data") void update_enum_list(const std::vector& _list, const std::string _key); /// Return the widget of the parameter with the given key, or nullptr if it does not exist QObject* get_param_widget(const std::string& _key); /// Compute the double slider value from a slider position. static double get_double_slider_value(const QSlider* _slider); /// Compute the double slider range according to the min and max property, update the internal slider value /// according to the new range static void set_double_slider_range(QSlider* _slider, double _current_value); /// Adjust the minimum size of a label according to the range values template static void set_label_minimum_size(QLabel* _label, T _min, T _max, std::uint8_t _decimals = 0); template static QString value_to_string_label(T _value, std::uint8_t _decimals); /// Block (or not) signal emission for this service void blockSignals(bool _block); /// if true, the signals are not emitted bool m_block_signals {false}; /// The list of intermediate boxes containing each widgets. This array is processed each time we need to find /// a parameter with a given key (when enabling/disabling, etc.). /// This vector is cleared upon stopping(). std::vector > m_param_boxes; }; //------------------------------------------------------------------------------ template<> inline QString parameters::value_to_string_label(int _value, std::uint8_t /*unused*/) { return QString("%1").arg(_value); } //------------------------------------------------------------------------------ template<> inline QString parameters::value_to_string_label(double _value, std::uint8_t _decimals) { return QString::number(_value, 'f', _decimals); } //------------------------------------------------------------------------------ template void parameters::set_label_minimum_size(QLabel* _label, T _min, T _max, std::uint8_t _decimals) { const auto min_string = value_to_string_label(_min, _decimals); const auto max_string = value_to_string_label(_max, _decimals); // Create a dummy label with same properties QLabel dummy_label; dummy_label.setFont(_label->font()); dummy_label.setStyleSheet(_label->styleSheet()); // Fill it with the string of the max value and request the size from Qt dummy_label.setText(max_string); const QSize size_with_max_value = dummy_label.sizeHint(); // Fill it with the string of the min value and request the size from Qt dummy_label.setText(min_string); const QSize size_with_min_value = dummy_label.sizeHint(); // Compute the maximum size and set it to our label const QSize max_size = size_with_max_value.expandedTo(size_with_min_value); _label->setMinimumSize(max_size); } //------------------------------------------------------------------------------ } //namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/plugin.cpp000066400000000000000000000165741503402212300172520ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/plugin.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Register all dialog implementation when we do use qt namespace base_dialog = sight::ui::dialog; SIGHT_REGISTER_GUI(sight::ui::qt::dialog::input, base_dialog::input_base::REGISTRY_KEY); SIGHT_REGISTER_GUI(sight::ui::qt::dialog::location, base_dialog::location_base::REGISTRY_KEY); SIGHT_REGISTER_GUI(sight::ui::qt::dialog::logger, base_dialog::logger_base::REGISTRY_KEY); SIGHT_REGISTER_GUI(sight::ui::qt::dialog::message, base_dialog::message_base::REGISTRY_KEY); SIGHT_REGISTER_GUI(sight::ui::qt::dialog::notification, base_dialog::notification_base::REGISTRY_KEY); SIGHT_REGISTER_GUI(sight::ui::qt::dialog::progress, base_dialog::progress_base::REGISTRY_KEY); SIGHT_REGISTER_GUI(sight::ui::qt::dialog::pulse_progress, base_dialog::pulse_progress_base::REGISTRY_KEY); SIGHT_REGISTER_GUI(sight::ui::qt::dialog::selector, base_dialog::selector_base::REGISTRY_KEY); namespace sight::module::ui::qt { //----------------------------------------------------------------------------- SIGHT_REGISTER_PLUGIN("sight::module::ui::qt::plugin"); /// This class is used to provide a proper disabled icon, especially when the icon is full white. class proxy_style final : public QProxyStyle { public: using QProxyStyle::QProxyStyle; //------------------------------------------------------------------------------ QPixmap generatedIconPixmap(QIcon::Mode _icon_mode, const QPixmap& _pixmap, const QStyleOption* _option) const final { if(_icon_mode == QIcon::Disabled) { // Copy the original pixmap QPixmap disabled_pixmap = _pixmap; // Create a QPainter and fill the pixmap with a semi-transparent white QPainter painter(&disabled_pixmap); painter.setCompositionMode(QPainter::CompositionMode_SourceIn); painter.fillRect(disabled_pixmap.rect(), QColor(255, 255, 255, 25)); painter.end(); return disabled_pixmap; } return QProxyStyle::generatedIconPixmap(_icon_mode, _pixmap, _option); } }; //----------------------------------------------------------------------------- void plugin::start() { core::runtime::profile::sptr profile = core::runtime::get_current_profile(); SIGHT_ASSERT("Profile is not initialized", profile); int& argc = profile->get_raw_arg_count(); char** argv = profile->get_raw_params(); std::function(int&, char**)> callback = [](int& _argc, char** _argv) { return QSharedPointer(new sight::ui::qt::app(_argc, _argv, true)); }; auto worker_qt = sight::ui::qt::get_qt_worker(argc, argv, callback, profile->name(), profile->version()); core::thread::set_default_worker(worker_qt); worker_qt->post([this](auto&& ...){load_style_sheet();}); core::runtime::get_current_profile()->set_run_callback(run); } //----------------------------------------------------------------------------- void plugin::stop() noexcept { core::thread::reset_default_worker(); } //----------------------------------------------------------------------------- int plugin::run() noexcept { auto worker_qt = core::thread::get_default_worker(); worker_qt->get_future().wait(); // This is required to start worker_qt loop int result = std::any_cast(worker_qt->get_future().get()); return result; } //----------------------------------------------------------------------------- void plugin::load_style_sheet() { if(qApp != nullptr) { if(this->get_module()->has_parameter("resource")) { const std::string resource_file = this->get_module()->get_parameter_value("resource"); const auto path = core::runtime::get_module_resource_file_path(resource_file); [[maybe_unused]] const bool resource_loaded = QResource::registerResource(path.string().c_str()); SIGHT_ASSERT("Cannot load resources '" + resource_file + "'.", resource_loaded); } // Also apply our proxy style to override default disabled icon look if(this->get_module()->has_parameter("style")) { const std::string style = this->get_module()->get_parameter_value("style"); qApp->setStyle(new proxy_style(QStyleFactory::create(QString::fromStdString(style)))); } else { qApp->setStyle(new proxy_style); } QString touch_friendly_style; if(this->get_module()->get_parameter_value("touch_friendly") == "true") { const std::filesystem::path touch_friendly_style_path = core::runtime::get_module_resource_file_path( "sight::module::ui::qt/touch-friendly.qss" ); { QFile data(QString::fromStdString(touch_friendly_style_path.string())); if(data.open(QFile::ReadOnly)) { touch_friendly_style = QTextStream(&data).readAll(); } } } QString app_style; if(this->get_module()->has_parameter("stylesheet")) { const std::string stylesheet_value = this->get_module()->get_parameter_value("stylesheet"); const std::filesystem::path path = core::runtime::get_module_resource_file_path(stylesheet_value); { QFile data(QString::fromStdString(path.string())); if(data.open(QFile::ReadOnly)) { app_style = QTextStream(&data).readAll(); } } } QString style_result = app_style + touch_friendly_style; if(!style_result.isEmpty()) { qApp->setStyleSheet(style_result); } } } //----------------------------------------------------------------------------- } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/plugin.hpp000066400000000000000000000032131503402212300172410ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "sight/module/ui/qt/config.hpp" #include namespace sight::core::thread { class Worker; } // namespace sight::core::thread namespace sight::module::ui::qt { /** * @brief This class is called when the guiQt module is loaded. */ class SIGHT_MODULE_UI_QT_CLASS_API plugin final : public core::runtime::plugin { public: /** * @brief destructor */ SIGHT_MODULE_UI_QT_API ~plugin() noexcept final = default; // Overrides SIGHT_MODULE_UI_QT_API void start() final; // Overrides SIGHT_MODULE_UI_QT_API void stop() noexcept final; static SIGHT_MODULE_UI_QT_API int run() noexcept; private: void load_style_sheet(); }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/preferences_configuration.cpp000066400000000000000000000536041503402212300231770ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "preferences_configuration.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { const core::com::signals::key_t preferences_configuration::PARAMETERS_MODIFIED_SIG = "parameters_modified"; const core::com::signals::key_t preferences_configuration::PREFERENCE_CHANGED_SIG = "preference_changed"; //------------------------------------------------------------------------------ sight::ui::parameter_t preferences_configuration::convert_value(const preference_elt& _elt) { switch(_elt.m_type) { case preference_t::text: case preference_t::path: case preference_t::combobox: case preference_t::file: { return _elt.m_preference_value; } case preference_t::INT: { return std::stoi(_elt.m_preference_value); } case preference_t::DOUBLE: { return std::stod(_elt.m_preference_value); } case preference_t::checkbox: { return _elt.m_preference_value == "true"; } case preference_t::list: { std::vector list; boost::split(list, _elt.m_preference_value, boost::is_any_of(_elt.m_separator)); return list; } default: { SIGHT_ASSERT("Preference type not handled", false); return {}; } } } //----------------------------------------------------------------------------- preferences_configuration::preferences_configuration() noexcept { m_sig_parameters_modified = new_signal(PARAMETERS_MODIFIED_SIG); m_sig_preference_changed = new_signal(PREFERENCE_CHANGED_SIG); new_slot(slots::REQUEST_VALUES, &preferences_configuration::request_values, this); } //------------------------------------------------------------------------------ preferences_configuration::~preferences_configuration() noexcept = default; //------------------------------------------------------------------------------ void preferences_configuration::configuring() { this->initialize(); const auto configuration = this->get_config(); for(const auto& cfg : boost::make_iterator_range(configuration.equal_range("preference"))) { preference_elt pref; auto type = cfg.second.get("type"); // balise type of .xml preference if(type == "checkbox") { pref.m_type = preference_t::checkbox; } else if(type == "text") { pref.m_type = preference_t::text; } else if(type == "path") { pref.m_type = preference_t::path; } else if(type == "file") { pref.m_type = preference_t::file; } else if(type == "combobox") { pref.m_type = preference_t::combobox; const auto values_cfg = cfg.second.get("values"); const boost::char_separator sep(", ;"); const boost::tokenizer > tokens {values_cfg, sep}; for(const std::string& value : tokens) { pref.m_values.push_back(value); } } else if(type == "double") { pref.m_type = preference_t::DOUBLE; pref.m_d_min_max.first = cfg.second.get("min", pref.m_d_min_max.first); pref.m_d_min_max.second = cfg.second.get("max", pref.m_d_min_max.second); } else if(type == "int") { pref.m_type = preference_t::INT; pref.m_i_min_max.first = cfg.second.get("min", pref.m_i_min_max.first); pref.m_i_min_max.second = cfg.second.get("max", pref.m_i_min_max.second); } else if(type == "list") { pref.m_type = preference_t::list; pref.m_separator = cfg.second.get("separator", ","); } else { SIGHT_ERROR("Preference type " << type << " is not implemented"); } pref.m_name = cfg.second.get("name"); pref.m_preference_key = cfg.second.get("key"); pref.m_default_value = cfg.second.get("default_value"); m_preferences.emplace_back(pref); } } //------------------------------------------------------------------------------ void preferences_configuration::starting() { this->action_service_starting(); } //------------------------------------------------------------------------------ void preferences_configuration::updating() { // Preferences may have been updated after start or last update. this->update_from_preferences(); const QString service_id = QString::fromStdString(base_id()); QDialog dialog; dialog.setObjectName("preferences_configuration"); QPointer layout = new QGridLayout(); int index = 0; for(preference_elt& pref : m_preferences) { QPointer label = new QLabel(QString::fromStdString(pref.m_name)); layout->addWidget(label, index, 0); QWidget* widget_to_reset = nullptr; if(pref.m_type == preference_t::text || pref.m_type == preference_t::list) { pref.m_line_edit = new QLineEdit(); pref.m_line_edit->setObjectName(pref.m_preference_key.c_str()); pref.m_line_edit->setText(QString::fromStdString(pref.m_preference_value)); layout->addWidget(pref.m_line_edit, index, 1); widget_to_reset = pref.m_line_edit; } else if(pref.m_type == preference_t::checkbox) { pref.m_check_box = new QCheckBox(); pref.m_check_box->setObjectName(pref.m_preference_key.c_str()); pref.m_check_box->setChecked(pref.m_preference_value == "true"); layout->addWidget(pref.m_check_box, index, 1); widget_to_reset = pref.m_check_box; } else if(pref.m_type == preference_t::INT || pref.m_type == preference_t::DOUBLE) { pref.m_line_edit = new QLineEdit(); if(pref.m_type == preference_t::INT) { pref.m_line_edit->setValidator(new QIntValidator(pref.m_i_min_max.first, pref.m_i_min_max.second)); } else { pref.m_line_edit->setValidator( new QDoubleValidator( pref.m_d_min_max.first, pref.m_d_min_max.second, 6 ) ); } pref.m_line_edit->setObjectName(pref.m_preference_key.c_str()); pref.m_line_edit->setText(QString::fromStdString(pref.m_preference_value)); layout->addWidget(pref.m_line_edit, index, 1); widget_to_reset = pref.m_line_edit; QObject::connect( pref.m_line_edit, &QLineEdit::textEdited, [&]() { int pos = 0; QLineEdit* const edit = pref.m_line_edit; QString text = edit->text(); const bool is_valid = edit->validator()->validate(text, pos) == QValidator::State::Acceptable; if(qApp->styleSheet().isEmpty()) { static const QColor s_DEFAULT_TEXT_COLOR = QLineEdit().palette().color(QPalette::Text); QPalette palette = edit->palette(); const QColor color = is_valid ? s_DEFAULT_TEXT_COLOR : QColorConstants::Red; palette.setColor(QPalette::Text, color); edit->setPalette(palette); } else { edit->setProperty("type", is_valid ? "" : "error"); edit->style()->unpolish(edit); edit->style()->polish(edit); } }); } else if(pref.m_type == preference_t::path) { pref.m_line_edit = new QLineEdit(); pref.m_line_edit->setObjectName(pref.m_preference_key.c_str()); pref.m_line_edit->setText(QString::fromStdString(pref.m_preference_value)); layout->addWidget(pref.m_line_edit, index, 1); widget_to_reset = pref.m_line_edit; QPointer directory_selector = new QPushButton("..."); layout->addWidget(directory_selector, index, 2); QObject::connect( directory_selector.data(), &QPushButton::clicked, [pref]() { sight::module::ui::qt::preferences_configuration::on_select_dir(pref.m_line_edit); }); } else if(pref.m_type == preference_t::file) { pref.m_line_edit = new QLineEdit(); pref.m_line_edit->setObjectName(pref.m_preference_key.c_str()); pref.m_line_edit->setText(QString::fromStdString(pref.m_preference_value)); layout->addWidget(pref.m_line_edit, index, 1); widget_to_reset = pref.m_line_edit; QPointer directory_selector = new QPushButton("..."); layout->addWidget(directory_selector, index, 2); QObject::connect( directory_selector.data(), &QPushButton::clicked, [pref]() { sight::module::ui::qt::preferences_configuration::on_select_file(pref.m_line_edit); }); } else if(pref.m_type == preference_t::combobox) { pref.m_combo_box = new QComboBox(); pref.m_combo_box->setObjectName(pref.m_preference_key.c_str()); for(const std::string& value : pref.m_values) { pref.m_combo_box->addItem(QString::fromStdString(value)); } const int current_index = pref.m_combo_box->findText(QString::fromStdString(pref.m_preference_value)); if(current_index < 0) { SIGHT_WARN( "Preference '" + pref.m_preference_value + "' can't be find in combobox. The first one is selected." ); pref.m_combo_box->setCurrentIndex(0); } else { pref.m_combo_box->setCurrentIndex(current_index); } layout->addWidget(pref.m_combo_box, index, 1); widget_to_reset = pref.m_combo_box; } QPointer default_selector = new QPushButton("R"); default_selector->setFocusPolicy(Qt::NoFocus); default_selector->setToolTip("Reset this preference value to default"); default_selector->setMaximumWidth(30); layout->addWidget(default_selector, index, 3); if(widget_to_reset != nullptr) { QObject::connect( default_selector, &QPushButton::clicked, this, [this, widget_to_reset]() { this->on_reset_to_default_value(widget_to_reset); }); } ++index; } QPointer cancel_button = new QPushButton("Cancel"); cancel_button->setObjectName(cancel_button->text()); QPointer ok_button = new QPushButton("OK"); ok_button->setObjectName(ok_button->text()); ok_button->setDefault(true); QPointer button_layout = new QHBoxLayout(); button_layout->addWidget(cancel_button); button_layout->addWidget(ok_button); layout->addLayout(button_layout, index, 1, 4, 2); QObject::connect(cancel_button.data(), &QPushButton::clicked, &dialog, &QDialog::reject); QObject::connect(ok_button.data(), &QPushButton::clicked, &dialog, &QDialog::accept); dialog.setLayout(layout); if(dialog.exec() == QDialog::Accepted) { sight::ui::preferences preferences; for(preference_elt& pref : m_preferences) { bool preference_update = false; // only emit signal for preference that has changed. if((pref.m_type == preference_t::text || pref.m_type == preference_t::path || pref.m_type == preference_t::file || pref.m_type == preference_t::list) && !pref.m_line_edit->text().isEmpty()) { preference_update = pref.m_preference_value != pref.m_line_edit->text().toStdString(); pref.m_preference_value = pref.m_line_edit->text().toStdString(); } else if(pref.m_type == preference_t::checkbox) { const std::string checked = pref.m_check_box->isChecked() ? "true" : "false"; preference_update = pref.m_preference_value != checked; pref.m_preference_value = checked; } else if(pref.m_type == preference_t::INT || pref.m_type == preference_t::DOUBLE) { int pos = 0; QLineEdit* const edit = pref.m_line_edit; QString text = edit->text(); const bool is_valid = edit->validator()->validate(text, pos) == QValidator::State::Acceptable; if(is_valid) { preference_update = pref.m_preference_value != pref.m_line_edit->text().toStdString(); pref.m_preference_value = pref.m_line_edit->text().toStdString(); } if(qApp->styleSheet().isEmpty()) { static const QColor s_DEFAULT_TEXT_COLOR = QLineEdit().palette().color(QPalette::Text); QPalette palette = edit->palette(); palette.setColor(QPalette::Text, s_DEFAULT_TEXT_COLOR); } else { edit->setProperty("type", ""); edit->style()->unpolish(edit); edit->style()->polish(edit); } } else if(pref.m_type == preference_t::combobox) { preference_update = pref.m_preference_value != pref.m_combo_box->currentText().toStdString(); pref.m_preference_value = pref.m_combo_box->currentText().toStdString(); } preferences.put(pref.m_preference_key, pref.m_preference_value); // Emit preferenceChanged signal with new value and preference key. if(preference_update) { const auto value = this->convert_value(pref); m_sig_preference_changed->async_emit(value, pref.m_preference_key); } } m_sig_parameters_modified->async_emit(); } } //------------------------------------------------------------------------------ void preferences_configuration::stopping() { this->action_service_stopping(); } //------------------------------------------------------------------------------ void preferences_configuration::request_values() { this->update_from_preferences(); for(auto& pref : m_preferences) { const auto value = this->convert_value(pref); m_sig_preference_changed->async_emit(value, pref.m_preference_key); } } //------------------------------------------------------------------------------ void preferences_configuration::on_select_dir(QPointer _line_edit) { static auto default_directory = std::make_shared(); sight::ui::dialog::location dialog_file; dialog_file.set_title("Select Storage directory"); dialog_file.set_default_location(default_directory); dialog_file.set_option(sight::ui::dialog::location::write); dialog_file.set_type(sight::ui::dialog::location::folder); const auto result = std::dynamic_pointer_cast(dialog_file.show()); if(result) { default_directory->set_folder(result->get_folder()); _line_edit->setText(QString::fromStdString(result->get_folder().string())); dialog_file.save_default_location(default_directory); } } //------------------------------------------------------------------------------ void preferences_configuration::on_select_file(QPointer _line_edit) { static auto default_directory = std::make_shared(); sight::ui::dialog::location dialog_file; dialog_file.set_title("Select File"); dialog_file.set_default_location(default_directory); dialog_file.set_option(sight::ui::dialog::location::read); dialog_file.set_type(sight::ui::dialog::location::single_file); auto result = std::dynamic_pointer_cast(dialog_file.show()); if(result) { default_directory->set_folder(result->get_file().parent_path()); _line_edit->setText(QString::fromStdString(result->get_file().string())); dialog_file.save_default_location(default_directory); } } //------------------------------------------------------------------------------ void preferences_configuration::update_from_preferences() noexcept { try { sight::ui::preferences preferences; for(auto& preference : m_preferences) { if(const auto& found = preferences.get_optional(preference.m_preference_key); found) { preference.m_preference_value = *found; } else { preference.m_preference_value = preference.m_default_value; preferences.put(preference.m_preference_key, preference.m_default_value); } } } catch(const sight::ui::preferences_disabled& /*e*/) { // Nothing to do... } } //------------------------------------------------------------------------------ void preferences_configuration::on_reset_to_default_value(QObject* _widget) { if(auto* const line_edit = qobject_cast(_widget); line_edit != nullptr) { for(const auto& pref : m_preferences) { if(pref.m_line_edit == line_edit) { QString default_value; switch(pref.m_type) { case preference_t::text: case preference_t::path: case preference_t::file: case preference_t::list: case preference_t::INT: case preference_t::DOUBLE: default_value = QString::fromStdString(pref.m_default_value); break; default: break; } line_edit->setText(default_value); const sight::ui::parameter_t variant_value = default_value.toStdString(); auto sig = this->signal(PREFERENCE_CHANGED_SIG); sig->async_emit(variant_value, pref.m_preference_key); break; } } } else if(auto* const combo_box = qobject_cast(_widget); combo_box != nullptr) { for(const auto& pref : m_preferences) { if(pref.m_combo_box == combo_box) { const QString default_value = QString::fromStdString(pref.m_default_value); int index = combo_box->findText(default_value); if(index != -1) { combo_box->setCurrentIndex(index); } const sight::ui::parameter_t variant_value = default_value.toStdString(); auto sig = this->signal(PREFERENCE_CHANGED_SIG); sig->async_emit(variant_value, pref.m_preference_key); break; } } } else if(auto* const check_box = qobject_cast(_widget); check_box != nullptr) { for(const auto& pref : m_preferences) { if(pref.m_check_box == check_box) { const bool check_state = pref.m_default_value == "true"; check_box->setChecked(check_state); const sight::ui::parameter_t variant_value = check_state; auto sig = this->signal(PREFERENCE_CHANGED_SIG); sig->async_emit(variant_value, pref.m_preference_key); break; } } } } } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/preferences_configuration.hpp000066400000000000000000000156121503402212300232010ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::data { class String; } // namespace sight::data namespace sight::module::ui::qt { /** * @brief This action shows a dialog to configure preferences of an application. * * @note This service works with preferences and does nothing if the preferences service is not found. * * @section XML XML Configuration * @code{.xml} path Video path VIDEO_REF_DIR ... file Video file VIDEO_REF_FILE ... text DICOM/MPPS port DICOM_PORT_KEY 11112 text AE Title DICOM_AETITLE_KEY dicom checkbox Keep PixelData (C-STORE) KEEP_PIXELDATA_KEY true combobox Device name DEVICE_NAME trakStar,Aurora trakStar 50 500 int Device port DEVICE_PORT 104 list Values for Nonlinear nonlin , 25,50,90,120,150 @endcode * * @subsection Configuration Configuration: * - \b type (mandatory, path/text/checkbox/int/double/combobox): the type of the parameter field. * - \b name (mandatory, string): the name of the parameter. * - \b key (mandatory, string): the key of the parameter. * - \b default_value (mandatory): the default value of the parameter. * - \b values (optional): list of possible values separated by a comma ',' a space ' ' or a semicolon ';' (only for * 'combobox' type). * - \b min (optional, int/double, default=0/-1000000.0): minimum value allowed in the field. * - \b max (optional, int/double, default=999999/1000000.0): maximum value allowed in the field. * - \b separator (optional, list, default=','): the separator for the list type */ class preferences_configuration final : public QObject, public sight::ui::action { Q_OBJECT public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(preferences_configuration, sight::ui::action); /// Initializes the signal. preferences_configuration() noexcept; /// Destroys the service. ~preferences_configuration() noexcept override; protected: /// Configures the service. void configuring() override; /// Gets the preference map. void starting() override; /// Shows a dialog to configure preferences declared in xml. void updating() override; /// Does nothing. void stopping() override; /// Does nothing. void request_values(); private Q_SLOTS: void on_reset_to_default_value(QObject* _widget); private: /// Type of signal when parameters are updated. using parameters_modified_signal_t = core::com::signal; static const core::com::signals::key_t PARAMETERS_MODIFIED_SIG; /// Generic changed signal type using changed_signal_t = core::com::signal; static const core::com::signals::key_t PREFERENCE_CHANGED_SIG; /// Internal wrapper holding slots keys. struct slots { using key_t = sight::core::com::slots::key_t; static inline const key_t REQUEST_VALUES = "request_values"; }; enum class preference_t : std::int8_t { text, checkbox, INT, path, file, combobox, DOUBLE, list }; struct preference_elt { preference_t m_type {preference_t::text}; QPointer m_line_edit; QPointer m_check_box; QPointer m_combo_box; std::string m_preference_value; std::string m_preference_key; std::string m_name; std::string m_default_value; std::pair m_i_min_max {0, 999999}; std::pair m_d_min_max {-1000000.0, 1000000.0}; std::string m_separator; std::vector m_values; }; /// @brief Converts string value from PreferenceElt.m_preferenceValue to real type regarding PreferenceElt.m_type. /// @param _elt The preference element /// @return std::variant as defined by parameter_t. static sight::ui::parameter_t convert_value(const preference_elt& _elt); static void on_select_dir(QPointer _line_edit); static void on_select_file(QPointer _line_edit); void update_from_preferences() noexcept; parameters_modified_signal_t::sptr m_sig_parameters_modified; changed_signal_t::sptr m_sig_preference_changed; std::vector m_preferences; }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/progress_bar.cpp000066400000000000000000000300121503402212300204230ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2024-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "progress_bar.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { //----------------------------------------------------------------------------- progress_bar::progress_bar() noexcept { new_slot(slots::SHOW_JOB, &progress_bar::show_job, this); } //----------------------------------------------------------------------------- void progress_bar::starting() { this->create(); const std::string service_id = base_id(); auto widget_container = std::dynamic_pointer_cast(this->get_container()); auto* qt_container = widget_container->get_qt_container(); qt_container->setObjectName(QString::fromStdString(service_id)); // Create the layout. m_layout = new QHBoxLayout(); m_layout->setObjectName(QString::fromLatin1("/QHBoxLayout")); m_layout->setContentsMargins(0, 0, 0, 0); // Create the label which holds the descriptive text shown with the progress bar. if(m_show_title) { m_title = new QLabel(qt_container); m_title->setObjectName(QString::fromLatin1("/QLabel")); m_title->setVisible(false); m_layout->addWidget(m_title); } // Create the progress bar. if(!m_svg_path.empty() && m_pulse) { // ...or an svg widget. const auto& resource_path = core::runtime::get_module_resource_file_path(m_svg_path); m_svg_widget = new QSvgWidget(QString::fromStdString(resource_path.string()), qt_container); m_svg_widget->setObjectName(QString::fromLatin1("/QSvgWidget")); m_svg_widget->renderer()->setAspectRatioMode(Qt::KeepAspectRatio); if(m_svg_size) { m_svg_widget->setFixedSize(*m_svg_size, *m_svg_size); } m_svg_widget->setVisible(false); m_layout->addWidget(m_svg_widget); } else { m_progress_bar = new QProgressBar(qt_container); m_progress_bar->setObjectName(QString::fromLatin1("/QProgressBar")); if(m_pulse) { m_progress_bar->setRange(0, 0); } else { m_progress_bar->setRange(0, 100); } m_progress_bar->setVisible(false); m_layout->addWidget(m_progress_bar); } // Create button to cancel job. if(m_show_cancel) { m_cancel_button = new QToolButton(qt_container); m_cancel_button->setObjectName(QString::fromLatin1("/QToolButton")); m_cancel_button->setText("Cancel"); const auto& resource_path = core::runtime::get_module_resource_path("sight::module::ui::icons"); const auto& icon_path = resource_path / "cross.svg"; m_cancel_button->setIcon(QIcon(QString::fromStdString(icon_path.string()))); m_cancel_button->setIconSize(QSize(24, 24)); m_cancel_button->setVisible(false); m_layout->addWidget(m_cancel_button); } const auto& margins = qt_container->contentsMargins(); int minimum_height = 0; if(!m_title.isNull()) { minimum_height = std::max(minimum_height, m_title->sizeHint().height()); } if(!m_svg_widget.isNull()) { minimum_height = std::max(minimum_height, m_svg_widget->sizeHint().height()); } if(!m_progress_bar.isNull()) { minimum_height = std::max(minimum_height, m_progress_bar->sizeHint().height()); } if(!m_cancel_button.isNull()) { minimum_height = std::max(minimum_height, m_cancel_button->sizeHint().height()); } qt_container->setMinimumHeight( std::max( minimum_height + margins.bottom() + margins.top() + 2, qt_container->minimumSizeHint().height() ) ); // Add layout to the qt_container. widget_container->set_layout(m_layout); } //----------------------------------------------------------------------------- void progress_bar::stopping() { { std::lock_guard m_lock(m_mutex); m_jobs.clear(); } if(!m_title.isNull()) { m_title->deleteLater(); m_title.clear(); } if(!m_progress_bar.isNull()) { m_progress_bar->deleteLater(); m_progress_bar.clear(); } if(!m_svg_widget.isNull()) { m_svg_widget->deleteLater(); m_svg_widget.clear(); } if(!m_cancel_button.isNull()) { m_cancel_button->deleteLater(); m_cancel_button.clear(); } if(!m_layout.isNull()) { m_layout->deleteLater(); m_layout.clear(); } this->destroy(); } // namespace sight::module::ui::qt //----------------------------------------------------------------------------- void progress_bar::updating() { } //----------------------------------------------------------------------------- void progress_bar::configuring() { this->initialize(); const auto& config = this->get_config(); m_show_title = config.get("config..show_title", m_show_title); m_show_cancel = config.get("config..show_cancel", m_show_cancel); m_svg_path = config.get("config..svg", m_svg_path); m_pulse = config.get("config..pulse", !m_svg_path.empty()); SIGHT_ASSERT("The svg attribute is only valid in pulse mode.", m_svg_path.empty() || m_pulse); if(const auto& svg_size = config.get_optional("config..svg_size"); svg_size) { SIGHT_ASSERT("The svg size attribute is only valid if an svg file is used.", !m_svg_path.empty()); m_svg_size = *svg_size; } } //------------------------------------------------------------------------------ void progress_bar::update_widgets() { std::lock_guard m_lock(m_mutex); // Update visibility of the widgets. const bool visible = !m_jobs.empty(); if(!m_title.isNull() && m_title->isVisible() != visible) { m_title->setVisible(visible); } if(!m_progress_bar.isNull() && m_progress_bar->isVisible() != visible) { m_progress_bar->setVisible(visible); } if(!m_svg_widget.isNull() && m_svg_widget->isVisible() != visible) { m_svg_widget->setVisible(visible); } if(!m_cancel_button.isNull() && !visible && m_cancel_button->isVisible()) { m_cancel_button->setVisible(false); } // Iterate over a copy of jobs, because they can be removed in show_job state hook while iterating over them, // in the rare case where we release the last reference in "if(const auto& job = weak_job.lock();" // We have the weak/shared mechanism to protect each pointer, but we also require the iterator to be safe const auto safe_jobs_list = m_jobs; // Update the widgets value. for(const auto& weak_job : safe_jobs_list) { // Get the current job as shared pointer. if(const auto& job = weak_job.lock(); job && job->get_state() == core::jobs::base::state::running) { const auto name = job->name(); const std::string msg = (job->get_logs().empty()) ? "" : job->get_logs().back(); const auto title = name.empty() ? QString() : QString::fromStdString( name + (msg.empty() ? "" : " - ") + msg ); const int value = int((float(job->get_done_work_units()) / float(job->get_total_work_units()) * 100)); if(!m_title.isNull() && m_title->text() != title) { m_title->setText(title); } if(!m_pulse && !m_progress_bar.isNull() && m_progress_bar->value() != value) { m_progress_bar->setValue(value); } if(!m_cancel_button.isNull()) { const bool cancelable = job->is_cancelable(); if(m_cancel_button->isVisible() != cancelable) { m_cancel_button->setVisible(cancelable); } } // Look only for the first job alive. break; } } } //----------------------------------------------------------------------------- void progress_bar::show_job(core::jobs::base::sptr _job) { { std::lock_guard m_lock(m_mutex); // Add the job to the list. // We use weak pointer to avoid taking ownership of the job. if(std::find_if( m_jobs.cbegin(), m_jobs.cend(), [&_job](const auto& _other_job) { return _other_job.lock() == _job; }) == m_jobs.cend()) { m_jobs.push_back(_job); } } // Use a "weak" this to avoid ownership to be passed to the lambdas which can be executed in different threads. auto weak_this = this->weak_from_this(); _job->add_done_work_hook( [weak_this](core::jobs::base&, std::uint64_t) { core::thread::get_default_worker()->post_task( [weak_this] { if(auto shared_this = dynamic_pointer_cast(weak_this.lock()); shared_this) { shared_this->update_widgets(); } }); }); core::jobs::base::wptr weak_job = _job; _job->add_state_hook( [weak_this, weak_job](core::jobs::base::state _state) { if(auto shared_this = dynamic_pointer_cast(weak_this.lock()); shared_this) { if(_state == core::jobs::base::canceled || _state == core::jobs::base::finished) { core::thread::get_default_worker()->post_task( [weak_this, weak_job] { if(auto shared_this = dynamic_pointer_cast(weak_this.lock()); shared_this) { { // Some cleanup to remove expired jobs. std::lock_guard m_lock(shared_this->m_mutex); std::erase_if( shared_this->m_jobs, [weak_job](const auto& _weak_job) { return _weak_job.expired() || (_weak_job.lock() == weak_job.lock()); }); } shared_this->update_widgets(); } }); } } }); if(!m_cancel_button.isNull()) { QObject::connect( m_cancel_button, &QPushButton::clicked, [weak_job] { if(auto canceled_job = weak_job.lock(); canceled_job) { // Call the cancel hook canceled_job->cancel(); } }); } } //----------------------------------------------------------------------------- } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/progress_bar.hpp000066400000000000000000000074141503402212300204420ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2024-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { /** * @brief Service displaying a progress bar. * * @section Slots Slots * - \b show_job(core::jobs::base::sptr _job): visualize the progression of jobs. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Configuration Configuration: * - \b show_title : Show the title of the current job if true (default: true) * - \b show_cancel : Show the cancel button of the current job if true (default: true) * - \b svg : If path is valid, will display an svg for pulse mode * - \b svg_size : The default size of the svg. If not set, the svg will be displayed at its original size */ class progress_bar : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(progress_bar, sight::ui::editor); /** * @brief Constructor. Do nothing. */ progress_bar() noexcept; /** * @brief Destructor. Do nothing. */ ~progress_bar() noexcept override = default; struct slots final { using key_t = sight::core::com::slots::key_t; static inline const key_t SHOW_JOB = "show_job"; }; /** * @brief Update widgets visibility. This method is called by the job hooks. */ void update_widgets(); protected: /** * @brief Do nothing. */ void updating() override; /** * @brief initialize the service. */ void configuring() override; /** * @brief Create the related widgets for the progress bar. */ void starting() override; /** * @brief Destroy the layout. */ void stopping() override; /** * @brief show_job slot's method. */ void show_job(core::jobs::base::sptr _job); private: /// Show the title of the current job if true bool m_show_title {true}; /// Show the cancel button of the current job if true bool m_show_cancel {true}; /// True for pulse mode bool m_pulse {false}; /// If path is valid, will display an svg for pulse mode std::filesystem::path m_svg_path; /// The default size of the svg std::optional m_svg_size; QPointer m_layout; QPointer m_title; QPointer m_progress_bar; QPointer m_svg_widget; QPointer m_cancel_button; std::vector m_jobs; /// Protect the jobs list std::recursive_mutex m_mutex; }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/rc/000077500000000000000000000000001503402212300156375ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/rc/copy.svg000066400000000000000000000017621503402212300173400ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/delete.svg000066400000000000000000000014241503402212300176230ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/distance.svg000066400000000000000000000015721503402212300201570ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/export.svg000066400000000000000000000025701503402212300177050ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/flatdark.qrc000066400000000000000000000014621503402212300201410ustar00rootroot00000000000000 resources/branch_closed-on.png resources/branch_closed.png resources/branch_open-on.png resources/branch_open.png resources/close.png resources/close-hover.png resources/close-pressed.png resources/down_arrow.png resources/down_arrow_disabled.png resources/left_arrow.png resources/left_arrow_disabled.png resources/right_arrow.png resources/right_arrow_disabled.png resources/up_arrow.png resources/up_arrow_disabled.png resources/undock.png flatdark.qss sight-25.1.0/module/ui/qt/rc/flatdark.qss000066400000000000000000000733171503402212300201720ustar00rootroot00000000000000/************************************************************************/ /******************************** Default *******************************/ /************************************************************************/ QWidget { color: rgb(255, 255, 255); border: none; } QMainWindow { border: none; background: rgb(46, 48, 52); padding: 3px; } QFrame { padding: 3px; border: none; background: transparent; } QDialog { border: none; background: rgb(46, 48, 52); padding: 3px; color: rgb(255, 255, 255); } QColorDialog { border: none; background: rgb(54, 57, 62); padding: 3px; } /************************************************************************/ /************************* QAbstractItemView ****************************/ /************************************************************************/ QAbstractItemView { alternate-background-color: transparent; padding: 3px; color: rgb(255, 255, 255); } /************************************************************************/ /******************************* QToolTip *******************************/ /************************************************************************/ QToolTip { border: none; padding: 5px; background: rgb(39, 42, 49); color: rgb(255, 255, 255); } /************************************************************************/ /******************************* QMenuBar *******************************/ /************************************************************************/ QMenuBar { padding: 3px; border: none; border-radius: 0px; background: transparent; } QMenuBar::item { padding: 3px 10px 3px 10px; margin: 2px; border-radius: 4px; color: rgb(255, 255, 255); } QMenuBar::item:selected { background: rgb(62, 68, 83); } /************************************************************************/ /******************************** QMenu *********************************/ /************************************************************************/ QMenu, QMenu:disabled { border: none; padding: 3px; background: rgb(46, 48, 52); } QMenu::icon { /* TODO */ } QMenu::item { padding: 3px 10px 3px 10px; margin: 2px; border-radius: 4px; margin-left: 25px; color: rgb(255, 255, 255); } QMenu::item:selected { background: rgb(62, 68, 83); } QMenu::separator { height: 2px; background: rgb(62, 68, 83); margin: 2px 0px 2px 0px; } QMenu::right-arrow { image: url(:/resources/right_arrow.png); } /******************************* Checkbox *******************************/ QMenu::indicator:non-exclusive { border: 2px solid rgb(86, 90, 94); border-radius: 4px; width: 14px; height: 14px; margin-left: -22px; margin-right: 22px; } QMenu::indicator:non-exclusive:unchecked:pressed, QMenu::indicator:non-exclusive:checked:pressed { border-color: rgb(106, 110, 114); } QMenu::indicator:non-exclusive:checked:pressed { background: rgb(124, 171, 207); } QMenu::indicator:non-exclusive:checked { background: rgb(104, 151, 187); } QMenu::indicator:non-exclusive:checked:disabled, QMenu::indicator:non-exclusive:unchecked:disabled { border-color: rgb(46, 48, 52); } QMenu::indicator:non-exclusive:checked:disabled { background: rgb(141, 141, 141); } /******************************** Radio *********************************/ QMenu::indicator:exclusive { border: 2px solid rgb(86, 90, 94); border-radius: 4px; width: 14px; height: 14px; margin-left: -22px; margin-right: 22px; } QMenu::indicator:exclusive:unchecked:pressed, QMenu::indicator:exclusive:checked:pressed { border-color: rgb(106, 110, 114); } QMenu::indicator:exclusive:checked:pressed { background: rgb(124, 171, 207); } QMenu::indicator:exclusive:checked { background: rgb(104, 151, 187); } QMenu::indicator:exclusive:checked:disabled, QMenu::indicator:exclusive:unchecked:disabled { border-color: rgb(46, 48, 52); } QMenu::indicator:exclusive:checked:disabled { background: rgb(141, 141, 141); } /************************************************************************/ /******************************* QToolBar *******************************/ /************************************************************************/ QToolBar { border: none; background: rgb(62, 68, 83); padding: 3px; icon-size: 32px 32px; } QToolBar > QWidget { border: none; background: transparent; } QToolBar QWidget { background: transparent; } QToolBar::separator { border-radius: 1px; background: rgb(46, 48, 52) } QToolBar::separator:vertical { height: 2px; margin-left: 5px; margin-left: 5px; } QToolBar::separator:horizontal { width: 2px; margin-top: 5px; margin-bottom: 5px; } QToolBar::handle { border-radius: 1px; border: 2px solid rgb(46, 48, 52); background: transparent; } QToolBar::handle:vertical { border-left: transparent; border-right: transparent; height: 2px; margin-left: 5px; margin-left: 5px; } QToolBar::handle:horizontal { border-top: transparent; border-bottom: transparent; width: 2px; margin-top: 5px; margin-bottom: 5px; } /************************************************************************/ /****************************** QToolButton *****************************/ /************************************************************************/ QToolButton { border-radius: 4px; border: none; background: transparent; margin: 3px; padding: 1px; } QToolButton:disabled { background: transparent; color: rgb(194, 198, 199); } QToolButton:hover, QToolButton::menu-button:hover { background: rgb(82, 88, 103); } QToolButton:checked, QToolButton:pressed, QToolButton::menu-button:pressed { background: rgb(102, 108, 123); } /************************************************************************/ /************* QToolButton used in module::ui::qt::status ***************/ /************************************************************************/ QToolButton.status { icon-size: 32px 32px; color: rgb(255, 255, 255); } /************************************************************************/ /******************************** QLabel ********************************/ /************************************************************************/ QLabel { border: none; background: transparent; color: rgb(255, 255, 255); padding: 3px; } QLabel[type="error"] { font-weight: bold; color: rgb(210, 82, 82); } /************************************************************************/ /******************************* QLineEdit ******************************/ /************************************************************************/ QLineEdit { border-radius: 4px; border: none; background: rgb(86, 90, 94); color: rgb(255, 255, 255); padding: 3px; } QLineEdit:disabled { background: rgb(46, 48, 52); color: rgb(69, 69, 69); } QLineEdit[type="error"] { color: rgb(210, 82, 82); } /************************************************************************/ /******************************* QTextEdit ******************************/ /************************************************************************/ QTextEdit { border-radius: 4px; border: none; color: rgb(255, 255, 255); background: rgb(86, 90, 94); } /************************************************************************/ /**************************** QPlainTextEdit ****************************/ /************************************************************************/ QPlainTextEdit { border-radius: 4px; background: rgb(54, 57, 62); color: rgb(255, 255, 255); border-radius: 4px; border: 2px solid rgb(46, 48, 52); } /************************************************************************/ /****************************** QPushButton *****************************/ /************************************************************************/ QPushButton { border-radius: 4px; border: none; background: rgb(86, 90, 94); padding: 5px; color: rgb(255, 255, 255); } QPushButton:disabled { background: rgb(46, 48, 52); color: rgb(118, 121, 124); } QPushButton:hover { background: rgb(82, 88, 103); } QPushButton:pressed, QPushButton:checked { background: rgb(102, 108, 123); } /************************************************************************/ /******************************* QCheckBox ******************************/ /************************************************************************/ QCheckBox { border: none; color: rgb(255, 255, 255); } QCheckBox::indicator { border: 2px solid rgb(86, 90, 94); border-radius: 4px; width: 14px; height: 14px; } QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:pressed, QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:pressed { border-color: rgb(106, 110, 114); } QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:pressed { background: rgb(124, 171, 207); } QCheckBox::indicator:checked { background: rgb(104, 151, 187); } QCheckBox::indicator:checked:disabled, QCheckBox::indicator:unchecked:disabled { border-color: rgb(46, 48, 52); } QCheckBox::indicator:checked:disabled { background: rgb(141, 141, 141); } /************************************************************************/ /******************************* QGroupBox ******************************/ /************************************************************************/ QGroupBox { border: 2px solid rgb(46, 48, 52); border-radius: 4px; margin-top: 9px; } QGroupBox::indicator { border: 2px solid rgb(86, 90, 94); border-radius: 4px; width: 14px; height: 14px; } QGroupBox::indicator:unchecked:hover, QGroupBox::indicator:unchecked:pressed, QGroupBox::indicator:checked:hover, QGroupBox::indicator:checked:pressed { border-color: rgb(106, 110, 114); } QGroupBox::indicator:checked:hover, QGroupBox::indicator:checked:pressed { background: rgb(124, 171, 207); } QGroupBox::indicator:checked { background: rgb(104, 151, 187); } QGroupBox::indicator:checked:disabled, QGroupBox::indicator:unchecked:disabled { border-color: rgb(46, 48, 52); } QGroupBox::indicator:checked:disabled { background: rgb(141, 141, 141); } QGroupBox::title { color: rgb(255, 255, 255); subcontrol-origin: margin; subcontrol-position: top center; padding-left: 10px; padding-right: 10px; } /************************************************************************/ /***************************** slide_bar *********************************/ /************************************************************************/ QGroupBox#slide_bar { margin: 0px; background: transparent; padding: 3px; border: none; } /************************************************************************/ /*************************** QAbstractSpinBox ***************************/ /************************************************************************/ QAbstractSpinBox { border-radius: 4px; border: none; padding: 5px; background: rgb(86, 90, 94); color: rgb(255, 255, 255); min-width: 40px; } QAbstractSpinBox:disabled { background: rgb(46, 48, 52); color: rgb(118, 121, 124); } QAbstractSpinBox:up-button { background: transparent; subcontrol-origin: border; subcontrol-position: center right; } QAbstractSpinBox:down-button { background: transparent; subcontrol-origin: border; subcontrol-position: center left; } QAbstractSpinBox:hover { background: rgb(82, 88, 103); } /******************************** Arrow *********************************/ QAbstractSpinBox::up-arrow, QAbstractSpinBox::up-arrow:disabled, QAbstractSpinBox::up-arrow:off { image: url(:/resources/up_arrow_disabled.png); width: 10px; height: 10px; } QAbstractSpinBox::up-arrow:hover { image: url(:/resources/up_arrow.png); } QAbstractSpinBox::down-arrow, QAbstractSpinBox::down-arrow:disabled, QAbstractSpinBox::down-arrow:off { image: url(:/resources/down_arrow_disabled.png); width: 10px; height: 10px; } QAbstractSpinBox::down-arrow:hover { image: url(:/resources/down_arrow.png); } /************************************************************************/ /******************************** QSlider *******************************/ /************************************************************************/ QSlider { border: none; } QSlider::groove { background: rgb(86, 90, 94); border-radius: 3px; } QSlider::groove:disabled { background: rgb(46, 48, 52); } QSlider::groove:horizontal { height: 6px; } QSlider::groove:vertical { width: 6px; } QSlider::handle { background: rgb(104, 151, 187); width: 14px; height: 14px; border-radius: 3px; } QSlider::handle:disabled { background: rgb(141, 141, 141); } QSlider::handle:horizontal { margin: -2px 0px; } QSlider::handle:vertical { margin: 0px -2px; } QSlider::handle:hover { background: rgb(124, 171, 207); } QSlider::handle:pressed { background: rgb(144, 191, 227); } /************************************************************************/ /****************************** QComboBox *******************************/ /************************************************************************/ QComboBox { border-radius: 4px; border: none; background: rgb(86, 90, 94); padding: 5px; color: rgb(255, 255, 255); min-width: 40px; } QComboBox:disabled { background: rgb(46, 48, 52); color: rgb(118, 121, 124); } QComboBox:hover { background: rgb(82, 88, 103); } QComboBox:on { background: rgb(102, 108, 123); } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 15px; border: none; } QComboBox::down-arrow { image: url(:/resources/down_arrow_disabled.png); } QComboBox::down-arrow:on, QComboBox::down-arrow:hover { image: url(:/resources/down_arrow.png); } QComboBox QAbstractItemView { border: none; background: rgb(86, 90, 94); border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; color: rgb(255, 255, 255); selection-background-color: rgb(102, 108, 123); outline: 0; } /************************************************************************/ /************************* QAbstractScrollArea **************************/ /************************************************************************/ QAbstractScrollArea { background: transparent; padding: 0px; } QScrollBar:horizontal { height: 18px; margin: 3px 15px 3px 15px; border: 2px transparent; border-radius: 4px; background: rgb(86, 90, 94); } QScrollBar::handle:horizontal { background: rgb(104, 151, 187); min-width: 5px; border-radius: 3px; min-width: 5px; } QScrollBar::add-line:horizontal { margin: 0px 3px 0px 3px; border-image: url(:/resources/right_arrow_disabled.png); width: 10px; height: 10px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { margin: 0px 3px 0px 3px; border-image: url(:/resources/left_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar::add-line:horizontal:hover, QScrollBar::add-line:horizontal:on { border-image: url(:/resources/right_arrow.png); height: 10px; width: 10px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on { border-image: url(:/resources/left_arrow.png); height: 10px; width: 10px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal { background: transparent; } QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: transparent; } QScrollBar:vertical { background: rgb(86, 90, 94); width: 18px; margin: 15px 3px 15px 3px; border: 2px transparent; border-radius: 4px; } QScrollBar::handle:vertical { background: rgb(104, 151, 187); min-height: 5px; border-radius: 3px; min-height: 5px; } QScrollBar::sub-line:vertical { margin: 3px 0px 3px 0px; border-image: url(:/resources/up_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar::add-line:vertical { margin: 3px 0px 3px 0px; border-image: url(:/resources/down_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line:vertical:hover, QScrollBar::sub-line:vertical:on { border-image: url(:/resources/up_arrow.png); height: 10px; width: 10px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on { border-image: url(:/resources/down_arrow.png); height: 10px; width: 10px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { background: transparent; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; } QScrollBar::handle:hover { background: rgb(124, 171, 207); } /************************************************************************/ /**************************** QRadioButton ******************************/ /************************************************************************/ QRadioButton { border: none; color: rgb(255, 255, 255); } QRadioButton::indicator { border: 2px solid rgb(86, 90, 94); border-radius: 7px; width: 10px; height: 10px; } QRadioButton::indicator:unchecked:hover, QRadioButton::indicator:unchecked:pressed, QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:pressed { border-color: rgb(106, 110, 114); } QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:pressed { background: rgb(124, 171, 207); } QRadioButton::indicator:checked { background: rgb(104, 151, 187); } QRadioButton::indicator:checked:disabled, QRadioButton::indicator:unchecked:disabled { border-color: rgb(46, 48, 52); } QRadioButton::indicator:checked:disabled { background: rgb(141, 141, 141); } /************************************************************************/ /****************************** QListWidget *****************************/ /************************************************************************/ QListWidget { color: rgb(255, 255, 255); border: none; outline: 0; } QListView { border: 2px solid rgb(46, 48, 52); background: rgb(54, 57, 62); padding: 3px; } QListView::item { border-radius: 4px; padding: 3px; color: rgb(255, 255, 255); } QListView::item:selected:hover, QListView::item:!selected:hover { border-radius: 4px; background: rgb(82, 88, 103); } QListView::item:selected { border-radius: 4px; background: rgb(102, 108, 123); } /******************************* Checkbox *******************************/ QListView { border: none; color: rgb(255, 255, 255); } QListView::indicator { border: 2px solid rgb(86, 90, 94); border-radius: 4px; width: 14px; height: 14px; } QListView::indicator:unchecked:hover, QListView::indicator:unchecked:pressed, QListView::indicator:checked:hover, QListView::indicator:checked:pressed { border-color: rgb(106, 110, 114); } QListView::indicator:checked:hover, QListView::indicator:checked:pressed { background: rgb(124, 171, 207); } QListView::indicator:checked { background: rgb(104, 151, 187); } QListView::indicator:checked:disabled, QListView::indicator:unchecked:disabled { border-color: rgb(46, 48, 52); } QListView::indicator:checked:disabled { background: rgb(141, 141, 141); } /************************************************************************/ /****************************** QTreeWidget *****************************/ /************************************************************************/ QTreeWidget { color: rgb(255, 255, 255); border: none; outline: 0; } QTreeView { border: 2px solid rgb(46, 48, 52); background: rgb(54, 57, 62); padding: 0px; } QTreeView::item { padding: 3px; color: rgb(255, 255, 255); } QTreeView::item:selected:hover, QTreeView::item:!selected:hover { padding: 3px; background: rgb(82, 88, 103); } QTreeView::item:selected { padding: 3px; background: rgb(102, 108, 123); } /******************************* Checkbox *******************************/ QTreeView::indicator { border: 2px solid rgb(86, 90, 94); border-radius: 4px; width: 14px; height: 14px; } QTreeView::indicator:unchecked:hover, QTreeView::indicator:unchecked:pressed, QTreeView::indicator:checked:hover, QTreeView::indicator:checked:pressed { border-color: rgb(106, 110, 114); } QTreeView::indicator:checked:hover, QTreeView::indicator:checked:pressed { background: rgb(124, 171, 207); } QTreeView::indicator:checked { background: rgb(104, 151, 187); } QTreeView::indicator:checked:disabled, QTreeView::indicator:unchecked:disabled { border-color: rgb(46, 48, 52); } QTreeView::indicator:checked:disabled { background: rgb(141, 141, 141); } /********************************* Arrow ********************************/ QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { image: url(:/resources/branch_closed.png); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { image: url(:/resources/branch_open.png); } QTreeView::branch:has-children:!has-siblings:closed:hover, QTreeView::branch:closed:has-children:has-siblings:hover { image: url(:/resources/branch_closed-on.png); } QTreeView::branch:open:has-children:!has-siblings:hover, QTreeView::branch:open:has-children:has-siblings:hover { image: url(:/resources/branch_open-on.png); } /************************************************************************/ /****************************** QTabWidget ******************************/ /************************************************************************/ QTabBar { border: none; background: transparent; padding: 0px; icon-size: 20px; } QTabBar::close-button { image: url(:/resources/close.png); background: transparent; } QTabBar::close-button:hover { image: url(:/resources/close-hover.png); background: transparent; } QTabBar::close-button:pressed { image: url(:/resources/close-pressed.png); background: transparent; } QTabWidget::pane { border: 3px solid rgb(54, 57, 62); background: rgb(54, 57, 62); border-radius: 6px; padding: 3px; } QTabWidget::pane:left, QTabWidget::pane:top { border-top-left-radius: 0px; } QTabWidget::pane:right { border-top-right-radius: 0px; } QTabWidget::pane:bottom { border-bottom-left-radius: 0px; } QTabBar::tab { color: rgb(255, 255, 255); background: rgb(54, 57, 62); border: 3px solid rgb(54, 57, 62); } /********************************** Top *********************************/ QTabBar::tab:top { padding: 3px 15px 3px 15px; border-top-left-radius: 4px; border-top-right-radius: 4px; min-width: 50px; } /******************************** Right *********************************/ QTabBar::tab:right { padding: 15px 3px 15px 3px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; min-height: 50px; } /******************************** Bottom ********************************/ QTabBar::tab:bottom { padding: 3px 15px 3px 15px; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; min-width: 50px; } /********************************* Left *********************************/ QTabBar::tab:left { padding: 15px 3px 15px 3px; border-top-left-radius: 4px; border-bottom-left-radius: 4px; min-height: 50px; } QTabBar::tab:!selected { border-color: rgb(86, 90, 94); background: rgb(86, 90, 94); } QTabBar::tab:!selected:hover { background: rgb(82, 88, 103); border-color: rgb(82, 88, 103); } /************************************************************************/ /***************************** QHeaderView ******************************/ /************************************************************************/ QHeaderView { background: rgb(62, 68, 83); margin: 0px; border-radius: 0px; } QHeaderView::section { background: transparent; color: rgb(255, 255, 255); padding: 5px 10px 5px 10px; border-radius: 0px; text-align: center; padding-left: 10px; } QHeaderView::section::vertical { border-top: 1px solid rgb(46, 48, 52); border-bottom: 1px solid rgb(46, 48, 52); } QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one { border-top: none; } QHeaderView::section::vertical::last, QHeaderView::section::vertical::only-one { border-bottom: none; } QHeaderView::section::horizontal { border-left: 1px solid rgb(46, 48, 52); border-right: 1px solid rgb(46, 48, 52); } QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one { border-left: none; } QHeaderView::section::horizontal::last, QHeaderView::section::horizontal::only-one { border-right: none; } QHeaderView::down-arrow { image: url(:/resources/down_arrow.png); } QHeaderView::up-arrow { image: url(:/resources/up_arrow.png); } /************************************************************************/ /***************************** QDockWidget ******************************/ /************************************************************************/ QDockWidget { background: rgb(62, 68, 83); titlebar-close-icon: url(:/resources/close.png); titlebar-normal-icon: url(:/resources/undock.png); color: rgb(255, 255, 255); } QDockWidget::close-button, QDockWidget::float-button { border: 2px solid transparent; border-radius: 4px; background: transparent; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { background: rgba(255, 255, 255, 10); } QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { padding: 1px -1px -1px 1px; background: rgba(255, 255, 255, 10); } /************************************************************************/ /**************************** QProgressBar ******************************/ /************************************************************************/ QProgressBar { border: 2px solid rgb(54, 57, 62); border-radius: 4px; text-align: center; background: rgb(86, 90, 94); color: rgb(255, 255, 255); } QProgressBar::chunk { background-color: rgb(119, 183, 103); } /************************************************************************/ /************************** SActivityWizard *****************************/ /************************************************************************/ #SActivityWizard_title { font: bold; border: none; background: rgb(62, 68, 83); padding: 3px; } #SActivityWizard_description { font: italic; } /************************************************************************/ /**************************** QfwToolBox ********************************/ /************************************************************************/ .toolbox_button { text-align: left; background-color: lightgray; border-style: solid; border-width: 1px; border-color: darkgray; height: 20px; } /************************************************************************/ /********************** Notification Popups *****************************/ /************************************************************************/ #NotificationDialog_Success { background-color: rgb(119, 183, 103); color: rgb(255, 255, 255); font-weight: bold; font-size: 16px; border-radius: 4px; } #NotificationDialog_Failure { background-color: rgb(255, 107, 104); color: rgb(255, 255, 255); font-weight: bold; font-size: 16px; border-radius: 4px; } #NotificationDialog_Info { background-color: rgb(104, 151, 187); color: rgb(255, 255, 255); font-weight: bold; font-size: 16px; border-radius: 4px; } /************************************************************************/ /*************************** SQuestions *********************************/ /************************************************************************/ #SQuestions_Header { background-color: rgb(54, 57, 62); } #SQuestions_Button { font-size: 25pt; color: rgb(119, 183, 103); background: rgba(119, 183, 103, 30%); border-color: rgb(119, 183, 103); padding: 10px; } #SQuestions_Button:hover { background: rgba(119, 183, 103, 50%); } #SQuestions_Button:pressed, #SQuestions_Button:checked { background: rgba(119, 183, 103, 70%); } #SQuestions_GroupBox { border: none; margin-top: 0px; margin-bottom: 0px; } #SQuestions_Spacer { margin: 0px 150px 0px 150px; border: none; border-bottom: 4px solid rgb(46, 48, 52); border-radius: 0px; } /************************** Custom class *****************************/ .accordion_menu[folded="false"] { background-color: rgba(0, 0, 0, 15%); border: 1px solid rgba(0, 0, 0, 40%); border-radius: 4px; margin: 1px; } .Bracket { padding: 0; background: none; } .view_background { background: #2E2F30; } .scene2d_render { background: #36393E } QPushButton.Bracket:disabled { background: none; } .sequencer_spacer[frameShape="4"] { background: rgb(194, 198, 199); min-height: 2px; max-height: 2px; min-width: 6px; max-width: 100px; border-radius: 4px; } .sequencer_spacer[frameShape="4"]:disabled { background: rgb(69, 69, 69); } .sequencer_button { border-radius: 12px; padding: 12px; font-size: 18px } sight-25.1.0/module/ui/qt/rc/import.svg000066400000000000000000000024711503402212300176760ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/new.svg000066400000000000000000000015231503402212300171520ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/no_windowing.svg000066400000000000000000000016501503402212300210630ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/noctura.qrc000066400000000000000000000015331503402212300200230ustar00rootroot00000000000000 resources/background.jpg resources/branch_closed-on.png resources/branch_closed.png resources/branch_open-on.png resources/branch_open.png resources/close.png resources/close-hover.png resources/close-pressed.png resources/down_arrow.png resources/down_arrow_disabled.png resources/left_arrow.png resources/left_arrow_disabled.png resources/right_arrow.png resources/right_arrow_disabled.png resources/up_arrow.png resources/up_arrow_disabled.png resources/undock.png noctura.qss sight-25.1.0/module/ui/qt/rc/noctura.qss000066400000000000000000000740761503402212300200600ustar00rootroot00000000000000/************************************************************************/ /******************************** Default *******************************/ /************************************************************************/ * { font-family: "Roboto"; font-size: 14pt; font-weight: normal; } QWidget { color: rgb(255, 255, 255); border: none; } QMainWindow { border: none; background: #303A65; padding: 3px; } QFrame { padding: 3px; border: none; background: transparent; } QDialog { border: none; background: #303A65; padding: 3px; color: rgb(255, 255, 255); border-image: url(:/resources/background.jpg) 0 0 0 0 stretch stretch; border-width: 0px; } QColorDialog { border: none; background: #303A65; padding: 3px; } /************************************************************************/ /************************* QAbstractItemView ****************************/ /************************************************************************/ QAbstractItemView { alternate-background-color: transparent; padding: 3px; color: rgb(255, 255, 255); } /************************************************************************/ /******************************* QToolTip *******************************/ /************************************************************************/ QToolTip { border: none; padding: 5px; background: #303A65; color: rgb(255, 255, 255); } /************************************************************************/ /******************************* QMenuBar *******************************/ /************************************************************************/ QMenuBar { padding: 3px; border: none; border-radius: 0px; background: transparent; } QMenuBar::item { padding: 3px 10px 3px 10px; margin: 2px; border-radius: 4px; color: rgb(255, 255, 255); } QMenuBar::item:selected { background: #C0B3D8; } /************************************************************************/ /******************************** QMenu *********************************/ /************************************************************************/ QMenu, QMenu:disabled { border: none; padding: 3px; background: rgba(255,255,255,50%); } QMenu::icon { /* TODO */ } QMenu::item { padding: 3px 10px 3px 10px; margin: 2px; border-radius: 4px; margin-left: 25px; color: rgb(255, 255, 255); } QMenu::item:selected { background: #C0B3D8; } QMenu::separator { height: 2px; background: #C0B3D8; margin: 2px 0px 2px 0px; } QMenu::right-arrow { image: url(:/resources/right_arrow.png); } /******************************* Checkbox *******************************/ QMenu::indicator:non-exclusive { border: 2px solid #C0B3D8; border-radius: 4px; width: 14px; height: 14px; margin-left: -22px; margin-right: 22px; } QMenu::indicator:non-exclusive:unchecked:pressed, QMenu::indicator:non-exclusive:checked:pressed { border-color: #C0B3D8; } QMenu::indicator:non-exclusive:checked:pressed { background: #C0B3D8; } QMenu::indicator:non-exclusive:checked { background: #C0B3D8; } QMenu::indicator:non-exclusive:checked:disabled, QMenu::indicator:non-exclusive:unchecked:disabled { border-color: rgba(255,255,255,50%); } QMenu::indicator:non-exclusive:checked:disabled { background: rgba(255,255,255,50%); } /******************************** Radio *********************************/ QMenu::indicator:exclusive { border: 2px solid #AAA4BD; border-radius: 4px; width: 14px; height: 14px; margin-left: -22px; margin-right: 22px; } QMenu::indicator:exclusive:unchecked:pressed, QMenu::indicator:exclusive:checked:pressed { border-color: #C0B3D8; } QMenu::indicator:exclusive:checked:pressed { background: #C0B3D8; } QMenu::indicator:exclusive:checked { background: #C0B3D8; } QMenu::indicator:exclusive:checked:disabled, QMenu::indicator:exclusive:unchecked:disabled { border-color: rgba(255,255,255,50%); } QMenu::indicator:exclusive:checked:disabled { background: rgba(255,255,255,50%); } /************************************************************************/ /******************************* QToolBar *******************************/ /************************************************************************/ QToolBar { background: rgba(255, 255, 255, 10%); padding: 3px; icon-size: 32px 32px; margin:5px; border-radius: 30%; } QToolBar > QWidget { border: none; background: transparent; } QToolBar QWidget { background: transparent; } QToolBar::separator { border-radius: 1px; background: rgba(255,255,255,50%); margin: 10%; } QToolBar::separator:vertical { height: 2px; } QToolBar::separator:horizontal { width: 2px; } QToolBar::handle { border-radius: 1px; border: 2px solid #FFFFFF; background: transparent; } QToolBar::handle:vertical { border-left: transparent; border-right: transparent; height: 2px; margin-left: 5px; margin-left: 5px; } QToolBar::handle:horizontal { border-top: transparent; border-bottom: transparent; width: 2px; margin-top: 5px; margin-bottom: 5px; } /************************************************************************/ /****************************** QToolButton *****************************/ /************************************************************************/ QToolButton { border-radius: 4px; border: none; background: transparent; padding: 5px; } QToolButton:disabled { color: rgba(255,255,255,25%); } QToolButton:hover, QToolButton::menu-button:hover { background: rgba(255,255,255,50%); } QToolButton:checked, QToolButton:pressed, QToolButton::menu-button:pressed { background: rgba(192, 179, 216, 50%); } QToolButton::checked { background: rgba(192, 179, 216, 50%); } /************************************************************************/ /*********************** QToolButton in button bar **********************/ /************************************************************************/ QToolButton.buttonBar:pressed, QToolButton.buttonBar:checked { background: rgba(192, 179, 216, 50%); } QToolButton.buttonBar { margin-left:32px; margin-right:32px; width:128px; } /************************************************************************/ /*********************** selected QAbstractButton in button bar **********************/ /************************************************************************/ QAbstractButton.selectedButtonBar { margin-left:32px; margin-right:32px; width:128px; border-radius: 4px; border: none; background: transparent; padding: 5px; } QAbstractButton.selectedButtonBar:checked { background: rgba(192, 179, 216, 50%); border: 1px solid white ; } /************************************************************************/ /************* QToolButton used in module::ui::qt::status ***************/ /************************************************************************/ QToolButton.status { color: rgb(255, 255, 255); } /************************************************************************/ /******************************** QLabel ********************************/ /************************************************************************/ QLabel { border: none; background: transparent; color: rgb(255, 255, 255); padding: 3px; } QLabel[type="error"] { font-weight: bold; color: #BA292A; } /************************************************************************/ /******************************* QLineEdit ******************************/ /************************************************************************/ QLineEdit { border-radius: 15%; border: none; background: rgba(255, 255, 255, 10%); color: rgb(255, 255, 255); padding: 3px; } QLineEdit:disabled { background: rgba(255, 255, 255, 4%); color: rgb(145, 145, 145); } QLineEdit[type="error"] { color: #BA292A; } QLineEdit.lineEditDicomEditor { color: white; background-color:rgb(62, 68, 83) ; } /************************************************************************/ /******************************* QTextEdit ******************************/ /************************************************************************/ QTextEdit { border-radius: 10px; border: none; color: rgb(255, 255, 255); background: rgba(255, 255, 255, 10%); } /************************************************************************/ /**************************** QPlainTextEdit ****************************/ /************************************************************************/ QPlainTextEdit { border-radius: 10px; background: rgba(255, 255, 255, 10%); color: rgb(255, 255, 255); border: 2px solid rgb(255, 255, 255); } /************************************************************************/ /****************************** QPushButton *****************************/ /************************************************************************/ QPushButton { border-radius: 4px; background: rgba(255, 255, 255, 10%); padding: 5px; color: rgb(255, 255, 255); } QPushButton:disabled { background: transparent; color: rgba(255,255,255,50%); border: 1px solid rgba(255, 255, 255, 10%); } QPushButton:hover { background: rgba(48, 58, 101, 75%); } QPushButton:pressed, QPushButton:checked { background: rgba(192, 179, 216, 50%); } /************************************************************************/ /******************************* QCheckBox ******************************/ /************************************************************************/ QCheckBox { border: none; color: rgb(255, 255, 255); } QCheckBox::indicator { border: 2px solid #0E164D; border-radius: 4px; width: 14px; height: 14px; } QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:pressed, QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:pressed { border-color: #FFFFFF; } QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:pressed { background: rgb(124, 171, 207); } QCheckBox::indicator:checked { background: rgb(104, 151, 187); } QCheckBox::indicator:checked:disabled, QCheckBox::indicator:unchecked:disabled { border-color: rgb(46, 48, 52); } QCheckBox::indicator:checked:disabled { background: #AAA4BD; } /************************************************************************/ /******************************* QGroupBox ******************************/ /************************************************************************/ QGroupBox { border: 2px solid #FFFFFF; border-radius: 4px; margin-top: 15px; } QGroupBox::indicator { border: 2px solid #FFFFFF; border-radius: 4px; width: 14px; height: 14px; } QGroupBox::indicator:unchecked:hover, QGroupBox::indicator:unchecked:pressed, QGroupBox::indicator:checked:hover, QGroupBox::indicator:checked:pressed { border-color: #FFFFFF; } QGroupBox::indicator:checked:hover, QGroupBox::indicator:checked:pressed { background: rgb(124, 171, 207); } QGroupBox::indicator:checked { background: rgb(104, 151, 187); } QGroupBox::indicator:checked:disabled, QGroupBox::indicator:unchecked:disabled { border-color: #AAA4BD; } QGroupBox::indicator:checked:disabled { background: #AAA4BD; } QGroupBox::title { color: rgb(255, 255, 255); font: bolder; subcontrol-origin: margin; subcontrol-position: top center; padding-left: 15px; padding-right: 15px; } /************************************************************************/ /***************************** slide_bar *********************************/ /************************************************************************/ QGroupBox#slide_bar { margin: 0px; background: transparent; padding: 3px; border: none; } /************************************************************************/ /*************************** QAbstractSpinBox ***************************/ /************************************************************************/ QAbstractSpinBox { border-radius: 4px; border: none; padding: 5px; background: #303A65; color: rgb(255, 255, 255); min-width: 40px; } QAbstractSpinBox:disabled { background: #303A65; color:#AAA4BD; } QAbstractSpinBox:up-button { background: transparent; subcontrol-origin: border; subcontrol-position: center right; } QAbstractSpinBox:down-button { background: transparent; subcontrol-origin: border; subcontrol-position: center left; } QAbstractSpinBox:hover { background: #C0B3D8; } /******************************** Arrow *********************************/ QAbstractSpinBox::up-arrow, QAbstractSpinBox::up-arrow:disabled, QAbstractSpinBox::up-arrow:off { image: url(:/resources/up_arrow_disabled.png); width: 10px; height: 10px; } QAbstractSpinBox::up-arrow:hover { image: url(:/resources/up_arrow.png); } QAbstractSpinBox::down-arrow, QAbstractSpinBox::down-arrow:disabled, QAbstractSpinBox::down-arrow:off { image: url(:/resources/down_arrow_disabled.png); width: 10px; height: 10px; } QAbstractSpinBox::down-arrow:hover { image: url(:/resources/down_arrow.png); } /************************************************************************/ /******************************** QSlider *******************************/ /************************************************************************/ QSlider { border: none; margin: 0px; padding: 0px; } QSlider::groove { background: #8288A2; border-radius: 3px; } QSlider::groove:disabled { background: #AAA4BD; } QSlider::groove:horizontal { height: 6px; } QSlider::groove:vertical { width: 6px; } QSlider::handle { background: rgb(255, 255, 255); width: 14px; height: 14px; border-radius: 3px; } QSlider::handle:disabled { background: #AAA4BD; } QSlider::handle:horizontal { margin: -2px 0px; } QSlider::handle:vertical { margin: 0px -2px; } QSlider::handle:hover { background: rgb(124, 171, 207); } QSlider::handle:pressed { background: rgb(144, 191, 227); } /************************************************************************/ /****************************** QComboBox *******************************/ /************************************************************************/ QComboBox { border-radius: 4px; border: none; background: #56608B; padding: 5px; color: rgb(255, 255, 255); min-width: 40px; } QComboBox:disabled { background: #AAA4BD; color: rgb(118, 121, 124); } QComboBox:hover { background: rgb(82, 88, 103); } QComboBox:on { background: #AAA4BD; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 15px; border: none; } QComboBox::down-arrow { image: url(:/resources/down_arrow_disabled.png); } QComboBox::down-arrow:on, QComboBox::down-arrow:hover { image: url(:/resources/down_arrow.png); } QComboBox QAbstractItemView { border: none; background: #303A65; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; color: rgb(255, 255, 255); selection-background-color: #AAA4BD; outline: 0; } /************************************************************************/ /************************* QAbstractScrollArea **************************/ /************************************************************************/ QAbstractScrollArea { background: transparent; padding: 0px; } QScrollBar:horizontal { height: 18px; margin: 3px 15px 3px 15px; border: 2px transparent; border-radius: 4px; background: #FFFFFF; } QScrollBar::handle:horizontal { background: #8288A2; min-width: 5px; border-radius: 3px; min-width: 5px; } QScrollBar::add-line:horizontal { margin: 0px 3px 0px 3px; border-image: url(:/resources/right_arrow_disabled.png); width: 10px; height: 10px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { margin: 0px 3px 0px 3px; border-image: url(:/resources/left_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar::add-line:horizontal:hover, QScrollBar::add-line:horizontal:on { border-image: url(:/resources/right_arrow.png); height: 10px; width: 10px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on { border-image: url(:/resources/left_arrow.png); height: 10px; width: 10px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal { background: transparent; } QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: transparent; } QScrollBar:vertical { background: #FFFFFF;; width: 18px; margin: 15px 3px 15px 3px; border: 2px transparent; border-radius: 4px; } QScrollBar::handle:vertical { background: #8288A2; min-height: 5px; border-radius: 3px; min-height: 5px; } QScrollBar::sub-line:vertical { margin: 3px 0px 3px 0px; border-image: url(:/resources/up_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar::add-line:vertical { margin: 3px 0px 3px 0px; border-image: url(:/resources/down_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line:vertical:hover, QScrollBar::sub-line:vertical:on { border-image: url(:/resources/up_arrow.png); height: 10px; width: 10px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on { border-image: url(:/resources/down_arrow.png); height: 10px; width: 10px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { background: transparent; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; } QScrollBar::handle:hover { background: rgb(124, 171, 207); } /************************************************************************/ /**************************** QRadioButton ******************************/ /************************************************************************/ QRadioButton { border: none; color: rgb(255, 255, 255); } QRadioButton::indicator { border: 2px solid #0E164D; border-radius: 7px; width: 10px; height: 10px; } QRadioButton::indicator:unchecked:hover, QRadioButton::indicator:unchecked:pressed, QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:pressed { border-color: #0E164D; } QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:pressed { background: #C0B3D8; } QRadioButton::indicator:checked { background: #C0B3D8; } QRadioButton::indicator:checked:disabled, QRadioButton::indicator:unchecked:disabled { border-color: #AAA4BD } QRadioButton::indicator:checked:disabled { background: #AAA4BD; } /************************************************************************/ /****************************** QListWidget *****************************/ /************************************************************************/ QListWidget { color: rgb(255, 255, 255); border: none; outline: 0; } QListView { border: 2px solid #FFFFFF; background: #303A65; padding: 3px; } QListView::item { border-radius: 4px; padding: 3px; color: rgb(255, 255, 255); } QListView::item:selected:hover, QListView::item:!selected:hover { border-radius: 4px; background: #C0B3D8; } QListView::item:selected { border-radius: 4px; background: #C0B3D8; } /******************************* Checkbox *******************************/ QListView { border: none; color: rgb(255, 255, 255); } QListView::indicator { border: 2px solid #AAA4BD; border-radius: 4px; width: 14px; height: 14px; } QListView::indicator:unchecked:hover, QListView::indicator:unchecked:pressed, QListView::indicator:checked:hover, QListView::indicator:checked:pressed { border-color: #AAA4BD; } QListView::indicator:checked:hover, QListView::indicator:checked:pressed { background: rgb(124, 171, 207); } QListView::indicator:checked { background: rgb(104, 151, 187); } QListView::indicator:checked:disabled, QListView::indicator:unchecked:disabled { border-color: rgb(46, 48, 52); } QListView::indicator:checked:disabled { background: rgb(141, 141, 141); } /************************************************************************/ /****************************** QTreeWidget *****************************/ /************************************************************************/ QTreeWidget { color: rgb(255, 255, 255); border: none; outline: 0; } QTreeWidget QLineEdit { background: #5d5c72; } QTreeView { background: rgba(255, 255, 255, 10%); padding: 0px; } QTreeView::item { padding: 3px; color: rgb(255, 255, 255); } QTreeView::item:selected:hover, QTreeView::item:!selected:hover { padding: 3px; background: rgba(255, 255, 255, 15%); } QTreeView::item:selected { padding: 3px; background: rgba(255, 255, 255, 15%); } QTreeView::branch { background: transparent; } /******************************* Checkbox *******************************/ QTreeView::indicator { border: 2px solid rgb(86, 90, 94); border-radius: 4px; width: 14px; height: 14px; } QTreeView::indicator:unchecked:hover, QTreeView::indicator:unchecked:pressed, QTreeView::indicator:checked:hover, QTreeView::indicator:checked:pressed { border-color: rgb(106, 110, 114); } QTreeView::indicator:checked:hover, QTreeView::indicator:checked:pressed { background: rgb(124, 171, 207); } QTreeView::indicator:checked { background: rgb(104, 151, 187); } QTreeView::indicator:checked:disabled, QTreeView::indicator:unchecked:disabled { border-color: rgb(46, 48, 52); } QTreeView::indicator:checked:disabled { background: rgb(141, 141, 141); } /********************************* Arrow ********************************/ QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { image: url(:/resources/branch_closed.png); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { image: url(:/resources/branch_open.png); } QTreeView::branch:has-children:!has-siblings:closed:hover, QTreeView::branch:closed:has-children:has-siblings:hover { image: url(:/resources/branch_closed-on.png); } QTreeView::branch:open:has-children:!has-siblings:hover, QTreeView::branch:open:has-children:has-siblings:hover { image: url(:/resources/branch_open-on.png); } /************************************************************************/ /****************************** QTabWidget ******************************/ /************************************************************************/ QTabBar::tab { background: rgba(0, 0, 0, 10%); color: white; padding: 9px; font-size: 13pt; border: 0; } QTabBar::tab:selected { background: rgba(255, 255, 255, 10%); border: 1px solid rgba(55, 60, 91, 100%); border-bottom : 0px; } QTabWidget::pane { background: rgba(255, 255, 255, 0%); border: 1px solid rgba(55, 60, 91, 100%); } QTabBar::tab:!selected { background: rgba(86, 90, 94, 10%); } QTabBar::tab:!selected:hover { background: rgb(82, 88, 103); border-color: rgb(82, 88, 103); } /************************************************************************/ /***************************** QHeaderView ******************************/ /************************************************************************/ QHeaderView { background: rgba(255,255,255,15%); margin: 0px; } QHeaderView::section { background: transparent; color: rgb(255, 255, 255); padding: 5px 10px 5px 10px; border-radius: 0px; text-align: center; padding-left: 10px; } QHeaderView::section::vertical { border-top: 1px solid white; border-bottom: 1px solid white; } QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one { border-top: none; } QHeaderView::section::vertical::last, QHeaderView::section::vertical::only-one { border-bottom: none; } QHeaderView::section::horizontal { border-left: 1px solid white; border-right: 1px solid white; } QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one { border-left: none; } QHeaderView::section::horizontal::last, QHeaderView::section::horizontal::only-one { border-right: none; } QHeaderView::down-arrow { image: url(:/resources/down_arrow.png); } QHeaderView::up-arrow { image: url(:/resources/up_arrow.png); } /************************************************************************/ /***************************** QDockWidget ******************************/ /************************************************************************/ QDockWidget { background: rgb(62, 68, 83); titlebar-close-icon: url(:/resources/close.png); titlebar-normal-icon: url(:/resources/undock.png); color: rgb(255, 255, 255); } QDockWidget::close-button, QDockWidget::float-button { border: 2px solid transparent; border-radius: 4px; background: transparent; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { background: rgba(255, 255, 255, 10%); } QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { padding: 1px -1px -1px 1px; background: rgba(255, 255, 255, 10%); } /************************************************************************/ /**************************** QProgressBar ******************************/ /************************************************************************/ QProgressBar { border: 2px solid rgb(54, 57, 62); border-radius: 4px; text-align: center; background: rgb(86, 90, 94); color: rgb(255, 255, 255); } QProgressBar::chunk { background-color: #303A65; } /************************** Custom class *****************************/ .accordion_menu[folded="false"] { background: rgba(255, 255, 255, 20%); padding: 10%; border-radius: 10%; } .ImageSlider { background: rgba(255, 255, 255, 10%); padding: 20%; border-radius: 10%; } .Bracket { padding: 0px; margin: 0px; border: 0px; border-radius: 0px; } .Background { border-image: url(:/resources/background.jpg) 0 0 0 0 stretch stretch; border-width: 0px; } .rounded_background { border-image: url(:/resources/background.jpg) 0 0 0 0 stretch stretch; border-width: 0px; border-radius: 30%; background: transparent; } .Welcome_panel { border-radius:40px; background: rgba(255,255,255,30%); } .SelectorView { border-top: 2px solid #FFFFFF; border-left: none; border-right: none; border-bottom: none; border-radius: 0px; } .SelectorView::title { font-weight: bold; font-size: 160pt; } .slice_selector { background: rgb(255,255,255); } .view_background { background: rgba(255, 255, 255, 0%); } .view_as_toolbar { background: rgba(255, 255, 255, 10%); padding: 3px; icon-size: 32px 32px; margin: 5px; border-radius: 30%; } .scene2d_render { background: #303A65; } .sequencer_spacer[frameShape="4"] { background: rgb(255,255,255); min-height: 2px; max-height: 2px; min-width: 6px; max-width: 100px; border-radius: 4px; } .sequencer_spacer[frameShape="4"]:disabled { background: rgb(86, 90, 94); } .sequencer_button { border-radius: 24px; font-size: 18pt; } .sequencer_button:checked { border: 2px solid #FFFFFF; } .sequencer_button:hover { background-color: #C0B3D8; } /************************************************************************/ /************************** SActivityWizard *****************************/ /************************************************************************/ #SActivityWizard_title { font: bold; border: none; background: rgb(62, 68, 83); padding: 3px; } #SActivityWizard_description { font-weight: italic; } /************************************************************************/ /**************************** QfwToolBox ********************************/ /************************************************************************/ .toolbox_button { text-align: left; background-color: #303A65; border-style: solid; border-width: 1px; border-color:rgba(255, 255, 255, 10%); height: 20px; } .toolbox_button:checked { background-color: #303A65; } .toolbox_button:unchecked { background-color: #303A65; } .toolbox_button:hover { background-color: #303A65; border-color: rgba(255, 255, 255, 30%); } .toolbox_frame { background-color: rgba(255, 255, 255, 10%); padding: 3px; margin: 5px; border-radius: 10%; } /************************************************************************/ /********************** Notification Popups *****************************/ /************************************************************************/ #NotificationDialog_Success { background-color: #51AF3B; color: rgb(255, 255, 255); font-weight: bold; font-size: 20px; border-radius: 4px; } #NotificationDialog_Failure { background-color: #BA292A; color: rgb(255, 255, 255); font-weight: bold; font-size: 20px; border-radius: 4px; } #NotificationDialog_Info { background-color: #51566b; color: rgb(255, 255, 255); font-weight: bold; font-size: 20px; border-radius: 4px; } /************************************************************************/ /****************************** tickmarks_slider ************************/ /************************************************************************/ .tickmarks_slider::groove:horizontal { height: 2px; background: transparent } .tickmarks_slider::handle:horizontal { width: 6px; margin: -4px 0; background: transparent; border-radius: 3px; } sight-25.1.0/module/ui/qt/rc/opacity.svg000066400000000000000000000046271503402212300200410ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/plugin.xml000066400000000000000000000411031503402212300176560ustar00rootroot00000000000000 sight::ui::container sight::module::ui::qt::icon_speed_dial This widget shows a speed dial with predefined icons. sight::ui::action sight::module::ui::qt::launch_browser sight::data::object This action launch a browser on the url given in configuration. sight::ui::editor sight::module::ui::qt::list_view This editor allows to draw a list. sight::service::controller sight::module::ui::qt::notifier notifier is a general service used to display notification in a centralized way. sight::ui::editor sight::module::ui::qt::parameters Generic editor to interact with parameters. sight::ui::editor sight::module::ui::qt::settings Generic editor to interact with properties. sight::ui::action sight::module::ui::qt::preferences_configuration This action shows a dialog to configure preferences of an application. sight::ui::editor sight::module::ui::qt::selection_menu_button This service show a menu button. The user can select one item in the menu. sight::ui::editor sight::module::ui::qt::progress_bar This service shows a progress bar. sight::ui::action sight::module::ui::qt::show_about sight::data::object This action show the about frame. sight::ui::action sight::module::ui::qt::show_help sight::data::object This action show the help contents. sight::ui::editor sight::module::ui::qt::status status service shows a colored square (red, orange, green) representing a status. sight::service::controller sight::module::ui::qt::style_selector Selector of theme/style. sight::ui::editor sight::module::ui::qt::text_status This service is used to displays and update values (int, double or string) in a QLabel. sight::ui::editor sight::module::ui::qt::text This service is used to display a label. sight::ui::view::activity sight::module::ui::qt::activity::dynamic_view This editor manages tabs containing activities. sight::ui::action sight::module::ui::qt::activity::launcher sight::data::vector This action launches an activity according to the selected data sight::ui::editor sight::module::ui::qt::activity::selector This editor launches an activity according to the given configuration sight::ui::editor sight::module::ui::qt::activity::sequencer sight::data::map sight::data::activity_set This editor displays an activity stepper that allows to select the activity to launch, and display the sight::ui::activity_view sight::module::ui::qt::activity::view This editor displays activities in a single view (when a new activity is launched, it replaces the previous sight::ui::editor sight::module::ui::qt::activity::wizard sight::data::activity_set This editor allows to select the data required in order to create the Activity. sight::ui::editor sight::module::ui::qt::calibration::calibration_info_editor calibration_info_editor service is used to handle the calibration points acquisition. sight::ui::editor sight::module::ui::qt::calibration::camera_config_launcher sight::data::map This editor adds cameras to a camera series and launches configurations to calibrate them. sight::ui::editor sight::module::ui::qt::calibration::camera_information_editor sight::data::camera camera_information_editor service is used to display the intrinsic calibration of a camera. sight::ui::editor sight::module::ui::qt::calibration::camera_set_editor sight::data::camera_set camera_set_editor service is used to display the extrinsic calibration of a camera series. sight::service::controller sight::module::ui::qt::calibration::display_calibration_info sight::data::object Launch an config to display calibration images. sight::ui::editor sight::module::ui::qt::calibration::images_selector sight::data::vector This editor allows to add images into a data::vector from an data::frame_tl. sight::service::base sight::module::ui::qt::calibration::intrinsic_edition sight::data::camera intrinsic_edition service is used to set the intrinsic parameter infos. sight::ui::editor sight::module::ui::qt::calibration::optical_center_editor sight::data::camera This editor shows sliders to configure an intrinsic camera calibration. sight::ui::editor sight::module::ui::qt::com::signal_button This editor shows a button and send a signal when it is clicked. sight::service::base sight::module::ui::qt::com::signal_shortcut This service sends a signal when the associated shortcut is checked. sight::ui::editor sight::module::ui::qt::image::image_info sight::data::image image_info service allows to display image pixel information when it receives the mouse cursor coordinates. sight::ui::editor sight::module::ui::qt::image::image This editor displays an image. sight::ui::editor sight::module::ui::qt::image::transfer_function sight::data::transfer_function Editor to select a transfer function preset. sight::ui::editor sight::module::ui::qt::image::slice_index_position_editor sight::data::image slice_index_position_editor service allows to change the slice index of an image. sight::ui::editor sight::module::ui::qt::image::window_level sight::data::image window_level service allows to change the min/max value of windowing. sight::ui::editor sight::module::ui::qt::image::transfer_function_opacity sight::data::transfer_function window_level service allows to change the opacity value of transparency. sight::ui::editor sight::module::ui::qt::metrics::distance sight::data::image Distance service is represented by a button. It allows to show distances in a generic scene. sight::ui::editor sight::module::ui::qt::metrics::landmarks This service defines a graphical editor to edit landmarks. sight::ui::editor sight::module::ui::qt::model::model_series_list sight::data::model_series Editor displaying the list of the organs in a ModelSeries. sight::ui::editor sight::module::ui::qt::model::organ_transformation Display the organs list and allow an interactive selection to set the corresponding meshes in a map sight::ui::editor sight::module::ui::qt::reconstruction::representation_editor sight::data::reconstruction Display a widget to change the reconstruction representation (surface, point, edge, ...). sight::ui::editor sight::module::ui::qt::reconstruction::organ_material_editor sight::data::reconstruction Display a widget to change the reconstruction material (color and transparency). sight::ui::editor sight::module::ui::qt::series::selector sight::data::series_set This editor shows information about the medical data. It allows to manipulate (select, erase, ...) sight::service::controller sight::module::ui::qt::series::select_dialog sight::data::series_set sight::data::model_series sight::data::image_series This editor shows information about the medical data. It allows to manipulate (select, erase, ...) sight::service::controller sight::module::ui::qt::series::viewer sight::data::vector This Service allows to preview the selected series in the Vector. For the moment, it works only on a sight::ui::editor sight::module::ui::qt::video::camera This editor allows to select the device to use. It updates the data camera identifier. sight::ui::editor sight::module::ui::qt::video::slider This editor allows to draw a slider. It is designed to be used with frame_grabber to browse a video. sight::ui::editor sight::module::ui::qt::viz::point_editor sight::data::map point_editor service allows to display point information. sight::ui::editor sight::module::ui::qt::viz::matrix_viewer This class defines a viewer for a data::matrix4. sight::ui::editor sight::module::ui::qt::viz::transform_editor sight::data::matrix4 This editor regulates the position and rotation defined in a transformation matrix. sight::ui::editor sight::module::ui::qt::viz::snapshot_editor snapshot_editor service is represented by a button. It allows to snap shot a generic scene. sight::ui::editor sight::module::ui::qt::material_opacity_editor sight::data::material Allows to edit a Mesh's opacity sight-25.1.0/module/ui/qt/rc/ramp.svg000066400000000000000000000020601503402212300173150ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/reset.svg000066400000000000000000000012311503402212300174770ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/resources/000077500000000000000000000000001503402212300176515ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/rc/resources/background.jpg000066400000000000000000002743471503402212300225130ustar00rootroot00000000000000JFIF(ICC_PROFILEmntrRGB XYZ acsp- desctrXYZdgXYZxbXYZrTRC(gTRC(bTRC(wtptcprt<.ƥC >-*+DN Ŷ.^%K8Xْsn.iIa0*UUM-[œ:8\ӥ:77eU)sNSuimֽ.q4rn0巨5T?@ I^ȑb@iCza@ $8[;gGBN_ zfs 862#&] q`:l j#0:o % -::[` e\щb= !F=>Q* 0nPWI^%7eV^ˑǜQK#zS - = PQ dJZZh*- ]Y% -@Jm: *w[u)paA๧KVIh*T.6YZt)M9{]x*ycK}aɳS.r^JRSoTe0ԼU|pɦj<7MQgTwPq[eǀU]'U`R՝8XٓsLᘫx$Midt 7ދ^ R\^4o# ޟu^JQ,"ݕI,\}bp^I* TYc%=W?F˂ 5JI&몿1{|[r)Mo5U 402h\4*I8ix2t,ygF\4~ :0u:U=f&ŀ8Z0c7 ^(vW5[1 O0{+ 8.H\p\pT0{#dKҡ.7> 74r ҥ F*ZUڥɖj/:nW^.Z0oz}y(*X:oSqφ)nkXƗx.&ԯJ74} ,<*bv1UZO7M3w\Y%P)eHվ団52*H O_@No+I핹fKҡ.n[zAmKY&nAݷ8NBX߲tO@qR@9k%"#_}< @8>ÌT5"J< '  BUrlrhMqZ=86O4x1Mtw{sN5{hTjoI./roJoh tFŝI$ۢ)S>W+I%uV^ib/π0lm%Vq+b[ro4[,fT_bI`TچwTQ$]:pdU TVu:JvsN8x5Tހ 6 lC$In-ץJu«:'&ӢrJNwK)vW4bx-^5=Tb|ZW5 ex^X.tjy/N V=l"z)r^Bp;6-h+ByDx7 ^(+a  Ps@M nb5M36#\ %@Sd4kӠ+M1wětEcu$]: MODZZhrUSe`cVc b)fR3}P[ozwL ˍ>@ـ֡#mW:jK~jp>L T6D+v@򾽮 e~ Uq;& Oكշc1[ZЃumå}L4`0 ɦ핹f@v<|QZCF@-MR~ HɷGL;b`ꩯOj)Tu,x>:mP6 $ ` JTNJ\ӒIUV̋mXIDj05\>VK3^{3S>W+`(Jdé,{f 9rmNa/њ%iʝtb|3KY/J- {{ecRW5+."1n%N+= yX׳ɗ_-KGte҄p\ZPU{_ Pς-:)J~@ iw9iRfv1IuQdM@. SS1oXI7V˟uやm&#)7U^: % TmQfy@wkW귷`̝es9]}-2t%݀7 IU.hvwk:[~68n .W_O(  ˗#'V[RN܈8p17ls#'V[Tbn+۵KȜ[nNo. ol[nX@ ^D<|M(I`Y9REpN΀ *@ɫe[dMe$ttmO];)My>{TRO6U-eJ\t}l:oWQz/@ҥ Rj-ZҊ)uSXt2uڌJX^@u:]7&No3շc'[˿fd[-Țs 3S2uoe$[-Ȃu0ޠsKCU5p4%[hy,JWNTA`5'?X:ަcLʤ:X3t7xytkOP5\VU/>@}T=T]<{okyD:U_oLh4f|g4Gcy%eSȡ&w|p LF7>꼔X*qE,?;u%luo6k+T77`0mP:y8Y6ҭg+{݌8XٚCXK2nzi<3oS.nIj mQZɀMgOYI<iIhp=ršx{6 4(nk# 5s&̺n6ᦙ1FT:x>:1{}1 ;#djOy2V?q4Ccf,짜p\LP0{#dE`uE;{(=cKE*Z~Ơ(+a.pK@ԼU$}2 (jڊTtU*Kæz? :wkW*T[ESa$1U-q[aK=bld/w b~ыPn08~7V׽vޠn[z8UB7'9d&$c?˯H }{\oB շcTSV^rCqv7`tM9`wm*ܸm07Dߍ'9V>o ˗nѯ$7M aֲ0uRF-Z8[ ;oi$trtMMsÄcz? ] yguT 4&k 0P g}nL':cWQ4Q:ItL8>s:qt->?ٙ=ORr-oO"ְx&Ph^yɽwMj9Y%wa]' -Z0|VIhട`"l8[#б}<#Vpz(XV*UTKSMY*ޠP}<VIhgpU~ikԩ|S@Y)b$ۢ-EO;\s $ QL5ffq9n+RMětEc\쎤 gN/i) 7+MwJ-CQ O%ۄl‚Xz6d^^ԛdމwmVxwdPKW~7.^`Sy[^)'E_wm V  ˓tUvަoE V|`8~71I͟0j(k:]ɽ7VߍMqMsx.;& [~6+p_H 9od$58\rD8>v sHJS@\j⷇ &IDpcK_rҥcEzW^4,(m%Vt[,h82`TSNMMUToP:K[ֆ]k%< ɵ tިڊKW^.*a}ԒwRГg [ŷ(J{Óyj iEקCQ O%:Ƕf7rq$ V@ķbh=|@m$4+f4~Pu:wvI%Db 5@ E6t@V?z^-BKч_pWWRpXފB]")Aխ+B\oܙh*-UijzZJiBK3PUyXU{P݀ B9{S s~ EQuxjib6NK%F-ٕI%DIQ)jiyoHWYR=Lӫ F).ZclQQm_}<vMQ$"No3շc1[$8{0smn\NNr=7'E_wm '7_k,_> Jo+kozKM.7pH7.^`+o`VVk: yox=bQN:ejr, f_;"*jrUqyN0c[pu6:nG6k4k咅`mVu4kVk3'DۆDmwJ8Xv5M36=tSd w0]MJʮT3xcjsJ)Odܛ 9j 46v~,$[ET/ԒTGL:{SJ\^ U͗}x̀VCZweֲT:X>i=bIٵvMAݷf-88X8ˌ o@ M$o`um_B6q{B-2k 8yTk.7p,DWijŷ72$TIy-M0~JR{gLJ/Wi7 77e 8KـzMiRƗxÓ`*J F x/&u[x)M]o%alI`]Bt<5)2N-=V:MqZ=~ NkG^\4<8FiMx;D-cܸ%dׁRm5OP8IphP7 ;J~E@YMEs@l\Xk5W̔M*Z~B{y֞IVIt^*m*$Ur б}<3v]U ;7W\9D:uY. BK7 EUwI7ꫝT$)EF7S1>*#[Җ.U{](RmtEc\,JR]s44o Ly7WPx*Lt7xyQdަf跷 6aA+yk *_ʜ.9tU2mM2y;&jrdfRIu~ȋoSuudH)neמ@fJo+kozKTO&貽\5Bjp UJ|"@o33t[ۆD^v{-B|t.r^C5UT L% FM>ٙtUGHU]^!W^R^QoID_;&EݷjR-t" Ьs2K&Uc-KF).5M2,~:,.i[z̧[WMY.꬝\}cU%7#DL]0//mp L]ib/π)d܀t[nXvJ6i*)YO=O( ׽DΦ@zi{[kz~suNj)Tu=D(~rEsU^W jYO# QU2&Y[ 5fƓw̛g:X>t|Ȧ]pY]'Ɠw$իUm;t52R3}'U gPv*y{+E\ߢ9mB̀1f..zT%Abz(q*$t`lb]*$T'W]=PzWKE= 6,CBj*t lI.X*ES=TӂEQuxNe%\ B9U_EcKK 6RKƪxRȵ—ÖFn I%DtP̢RP:Y(Phˌj}O` `/ :ԃsv`i*-շcRl^\,eRo']Y.[ɽM/ٚO~l&*pFjKdބzVn{pȚ,_ WVK` iR婚TzdF/0ԥBZ*4F9pT/,V?*%]kw8nvͭ@TqXj.*aJM/ٙ%UseԴbG6k ZZ\7fU7ldSd4hԛUJ_{iA|xT_Xx@ڡ {܍5>ҡg/{UE<{mUKNO|#U)7榓skWm,0kO3??(])8[;gGCÂxZ.+x iŮUhdq@ W`X=g *̘ttt(T8j1R&5׷z]zw)MF%[,L"K=2;z"z[{EUX9EO;;M36^e^7 U%1.^w;azhR%BϡH-bhUjv*/;Q[/=oСBp] ad15=T)sØļU/#sǷ0pvM)7vޭޠy4:x@P @U E$uJ-wMjfևMu7Cu@Ic2J4f}~hKC`̤n.N괲">2 @5cUb$Ki)e@m @-M0~j[$::n t˜X)7ʊ]zTI`w;GV=7NbKܝTf}[w0XwQ42x.-uC'˂}6:MjW ۆg5ilUZ,Ϧ^ug(^oXƗxÐVbi_{jZ,~:ZcB_ B9e&zi1%=4StW0n1UZ^v.a/S<2[ˆߢ-n8 Z`:ա)'Jևo+c]Iê<좜xfK@9pVGe^WO˩dTF PRbK Fnާ }t%ߙ+4dF(\伀ĨBT6 M ib/ς@sI͟ѳ“2mM̀77`7 ^@sI͟"nY:`V28UN/p@omndT 2dvM Me{vƓ~ l  zwmY:'2 o&To@fn{pN[8F37EfjqKܘ<| @T7ˮǐ0o o|ـ(7 SoM5RXꥼ*s@-{JhJ{FTeVKd* :nҗ=~I.vbR>i)`줒&ug*.U:-#[N.6Q_斱6%4>? 5M2/~ %@cWzLAԛpbUI%Dt]iawM*[{qx&5 k[M qpN~ 6v3o)-7kZ/*pY<U)E+E_=TqGGU5k[˧!<DWrbmzi~weB*v+R V1{|$ymCirIM3DŽbbzQIT{;G?@ǡqU95 6v5. z)rH҃x+DIv: %[(΀ *5_I F)-[`^JoUFno s{ԒTGAF'=asPTUjѰ T./E6bYҕZ )vYI`r1゠ҘX{)b°TY^HСNoƆ7zB,/RmJ/;i}Wٺ)9R@lP@)BϢN&\伀*:nHTR-˗i*I w"vN68T @IU5U8`I w"ulpN]@9$ԚIr udLJ0%ZJxe]zwm'uL &jnkr̂59pJxʐ/wkO@,A~ݒp p>| QKW}>]&5M>0M"TT,k2K _٥K| MS9TM'1Uqe~9~A'vfGUo8{^ƕmZ7ϱ8*n\ SRbmzYytsn.i]Is5)&u&d*Uk3UIqz}VaNom,} 5M2,~J,Ʒx{Àu&!xpTY$eO _Җ7~9}7WKC%4aAZ%@j;=^ %k֬wڸQ;J-K$9|٠zor%}-=4,/*_?|+TˏO:p`9|2=kE5W_,f*{GiPOrKj*/$ۢ,^wS׋кKChTKJ,Rmz,J`LSme~Iu՗@f%,udLJ0JR 59Rڮ/#{g7 Lվ[Tsv+RO2%&]F ;2}m$ճ V@Gz&AݷYc7`-;'W_jrd\3l @B تyhWu7cT|?FZ 1[jK 7Ww8nxfPc*-KDT+bPK·@K4 `iRfQdUbv1m̲KC$` :nxyI%o")-^zJ/;@TjKU$]ssalҥC-귷bKRuwʘwƗxÓgeKm+r_њ=BɵygF\4~ L)+-mRp̴* F-ٕQK9U6XsTQ,rESOc.c3.)MsZ>ѼMk~GLVivӁjzTRʻѝx@d[l Wѯh#E?[۴tC@xA]?qڿE6ZO8+(W . 7| O%iBn5ԪXrhфi<.oST7q6G+_C7l|w#M3ls O%8l^yzhy-A*ݫ|J-K$$zh\休x߳UyqMj߲Q%sT/ҡ.7tQU{]SU]VX鴾СNoFਲXKK ÀBd ҡqRmz,J:˿ t8~7:FMXi3U^qY;J8~7:FMXid#S @tUvަ[#Sa ;un8u6lp @9enX3[̀JtǬ0JxYf_;" NTl[|fA4}(Ic0 O@M# #[N%D΂Iwn\ RGVJxYsm@Utn.&ڽ -zT(R % 4u)qB@6).+4 L]ۢPv1m̪I* ^w:7M3wf/xMviRQNT tިTtUH3_P/5RXPmW wz~jl`>{`bbGώ'ԛtE# ]7fg}98]Xm\Jxg@qXmb9츐I|jPɜI,|Ί7j&on[3Y}yFѼ}%z?TKCJ:Jy_~;:VVKx~ܧ>]x/L(~rEZћT%O!FMQo*obK@m94/n_&2Mzܱ&ղmP'RoJ eDT@4ƭ 9|٠6_Ei}~(ѶtZ~O[J'Y%{;W=.OM4Ϸk\ %fThz~gJkEճE-)]^:N ;_B} B߀)E9{3]Y, Sx_{Rmz,JH ɤ w39dn\|nZ}+djrdbr=&zM˗8N;oKsddO ܰf F@+TkQnΕ.qnRS:j<76FnV1>3W+% @ *?;7IDY*$qԖ&qvonoonUI/{ 'ԛtETRWIٚX$,M W0x$ W`˥E0qYWtZ# t&Xv1Sw ΃1.^4<XFôz? Q} bnKi=z? Q1G_r\ߞG=z(~`I+/3nkG=^b[Q^%[ͭA)SVF,#rm{GѵC|%y}mRm/Vgv0 ߉릟Jh1-EN#/B99v)sØ^v{xZ=zP7U{ZTv_%*~lr@˗ÞY$#—Jl.@(m%VFN659V>oO.ĪljtSu՗A6a,ߛ?e 9S u(Ts 2i']Ț}-2T @uSXfxQV언*HQ^v{n--JEQow77eՒZ > LGۢ8Z)%4 ΥD΀ `gߎfns}U]<]Y%gRr E҅4˾ pKPV1>.irY(Rmx*iwnNT6!b77`u&4o#Rmmi$])jMc3KoF1.g3b·v\I*#To(YejJ|a~p 5sQ&]),آiE uޏ_jm,L*w&r(7w^,,[a .޽TK@6f}iգΌk'ى~$zZvmh5M)?o8bQ2K\~Y1|Q:I2(O$L ߠzk8Jo/G}T뽽;Gߝu~ƕ;[|9T%xk `Wij]*$bͭ@VaNo`GK?/<=2x߲Q%r-:8UE>7 $j-p`I;Λ)7Z.oTJԲ䠪ر,2*\u` RdF E6,tƦڽJ:*pv/`Ԇ0 uE)%뽤e"x_^ȁHWtZ/Z}羄 &xxBEx*Pv>}bo?Oj=gy5J}n\ W pk()Ֆ3N~2rE-E~Ԧ$'4YR2uo`עNLNQ&ٛRnc%&Mcv'`R`IM wti =[|団QR Bx[z~i@p>|i}WKK#j-Ud۶ uR\@[,=ۖ 5pTU{8UE>7 $BIWI;J~F6۫8 AUkfS:)EʥK캲KCqE`Ӣv -JthjT% ]x*yc&Z)9 B)*ZҀ(7|UՒ?<Ԩ\^?GRKŒIQIux뱠m)qBj1mM"TT7M2TG@3UQ%X+weϬc[=R I*#M jf{ooeTR΃v뎜تy$ۢ+dTV2~v|}PKo#_s 7xpuC]{w( ԛJ6-ٕQK9M4ᙌ)+-i& _ .M&v8] ßaM;~2ѬS]UԳ{3ոj0|_O(*JzË]z &"8y8wb]^:hBhm%VIU}2_3UQ%7`%K@JZZzZdބduZ}rE(VoSRkThH>%(X o$w2n0Pj*k*,~6qԔNfR%j/ EUⶱětEK4oo.(7 LvI%DJ*t%Ьsҥ~@vqxKFn V$NZ)9poKQ.~ɰ6,J7xpr)cň77g@Rm7xdy 4'T/RmWij B9{_cKx$ۢ8Z*45IO1ت*yڃjo_bjߟPxoĿꆯK8dI*#j :XcV&*7|I`t 70=rbxp\I4+G MUm~u(Q9od$VnN<=HZlJx+o`q\דT⹯ n."1gi.i PNoƀ{?GCkSfs^Ig};XVIhҥ̸J,:n 66gL4<8E;&LͭwmP&_XiF\̢@vmh ְz`8>5WmJ/;T)s, I./ I%%"MUTYc{ UiyغKBPTUE>7 $W2tO`9:jmP1҄V +ߵEUwKb%P0)Cu-JE#K긷&҅`j/ID,_oE%DΖPO Ԩ\^?@Irm)q5W 4)E|_ZƀR+:Z^v,J#K긷&Z)Rm+-4^w:T.-+ܜXJ6;W|Ŏ%D%V֡G6kRK-EQ_斱ʜ.9u$ŷ736%D?ƪRKz'RʻѝxF[ʛj5Kww|`MmCWFIQM< aLXaʂXz5DIv:fQMuM:3OUkT;6=d[%< y*jrh@ fvT0Vx-RvIO0Rk0h 72KoGRoTkȚ mcyUR*w+M)]8i{ PM֙*X4X K$UF4pfe*a L]}PrRi[Sd4>72Xyn#UQe*X7'9d“uu$gMLXSuyd$/05J8-EBX߲%8ԨyLj,ɀ3_}<A,ЃoV8}m4No9S&rX='7 "wx.ɀടp>|RKbR gŸ6Z](IhJXǓ2Kϝ ^Kc&n.ar-J߸'7:jF@ \UZn@)E VZn.rXsE@ )W,v⨩smm^JS s~]@r*w/ܘ mbR QNooǎj)+.^w15Qߘ5c%K41]Y,̱V,f { -@Wij A,z PlTIyԛtEc\F4&{6KO`Xc[=TKcF 5u{*q*$R|ۂs-qd Q_斱X7ێ UՒZm2ɵ9^k{z? /GRwnܢg (7|4cq1r6oݩ[۹GvEU:>_X䛜L8+ҿ&NE5'#IF3TyYٵ*pPSl 97eJ%6n?TTs߱% ZޟTD*4&dKȞ3Ty frEi;O/"$]Mx{uáj[\Z*EK|-th[Ji }IDZ*w*K~toq5ׯRN-c"VPiw<ΨEIRU=%kJU<M@ Ls͘P~n.;#S /䝝+nzQh F/)(*6N&!58\rN/` nɽ"nt]tʕHf@nN~t`Lo&fN YdL&3u{[X{`nkH8bx-Sm _O(5HNRW:zj—-˗8>wӵ-ٔrQVun.Я-[.g[IUrmmdpUMJ/enѨ4$iRw Me{v#Su{[dPwV +ߵUVT7.^e҅`pB\oܵ*~h5W2Z)%*_wudo} ])KQS@je^7V`XڲK@)BL9 -ACpYN*q*$t7ePj+E/3JWt,/UZY$8,{Fn ۢ,^w3SKn.r^NmXětEK?R{n7u4[v'JB9{_0Z^v.U+;qK$ۛ6%Dو,kwS\qI.v)ffe]x*yܛsvM7ƧF_GUZҥ̘QBׯj+jR|wq~h#. Ǭz\MHiFpsio3ԃTmxT{ߖdVVkA9CDId鄟,-4X|3'P?ٙ=OSZ/Rc{u]gnꜣX)2BK}ZJMQM6:H*yi{r-oO" U|w2Ǟbj1m̫x%DPKĒw)nˏR~@Z[ r[Уend=pO_\4~PT`vn ;K@{ Ӯ"jӑSmZaVvӽI%DtB8\r5Jc KP E6,tرȪ*y-J BEUⶱcTTi)ijP@n ;iZJ/;t)stNRKՒZPOoZpW.m,ТTIy:Z^vxf,spYN(Zje|JaDufPw:ڻÕ}+w:p7ciUc qJNvo2`D]*$N ;iRKWC5Z)KB9{_(ZKH7.AԛtG EQ_斱KPfjp#\N S]r"we'5r5Py4ޏ 6.^63%UO@iFpZj.putU'(RX M:z'I2-oO"tk@u*rxjJP|W`uDcxsDL]iھ00>r.(*B2)C8(ᦹ0NM)pU6`WlN8ixNɸS`VۊjWSDROOfJKѭKx^3}_{~@QdLn.7 ܶ'7rg[o ҡ%ߙ:b 5_X]/\=2q|I ]v2z J5'LJtÓoENA0//mv<|СNoŀv8װ ,ԨK2uofeTrE(Y EUⶱZ-r9oRi~f 7bZ/P&mSJ]B@QdZ qpXFqnLEUwKt,Þ  BRͭ阪`Rs@(7 vӽV$ZzPWij wSQ%s/J~`+G͸ ܹyf^rrQ%s5Bh-'V_Nwӵ%V$1[,ҥYYtX*,~1W,qB7vm& |IDY*$b C]rP=7e/ZȔvӽDL=Ӊ .らQ%rYN0ԺTIyܑ(VrԑFn Vw:aSDFn饖J ~5{NϢ7UFJڦlNIU;VΛ]2bPy=2hv3Ԅ‹E57;vV&ᦙa&g}uC];+FT<,|u8Y,);+ [v 5'Oxp\Szv񖸖MuL/\SZ=z)JM×{m\w֏^N(%F(X=+J55o*[TRT[ꈠ*X.bRW]rS-FLnvM虇VJΙdU]CoPiq>u7NqԖ=I-7 f j./Om@5M2, A(7`u)qn)%ƀ5JPw%<˥ 4-Tj*k n\[`qߐpYN0ԺTIyKxܜ^$iN]7vBKBV>-+]HE }8^w4Z^v&NzibV>-vY%+6'|R&DQ%s-A,{9vYp̢TIyܼU~ UwK S.g.12j ;_#GK#S)` E$u#[W҇k2h*;J{_qR*Jol˛j-p0, `{&Vx- iS)̢]=$V5l85j>RJ<]o>L8zQh"Ln=2 Y< Q0y*'O 5_3yRJi*e.PQ9T{)^.o. a{2שQ*f=:NiŮUh[`],qTqFMQ)uuϬLke?ٖ'7Nma70*% ^X=e0rX=j3L Py׋8Q]'0#R"ԹK@ V|^u̩GZ`]i){ApuYY%Dޠt9dIվ1Z . ʐ/w5HVZ/bn+۵͑}-kOJ #'V[nRifHw:Zc@(]5Td R\^z$x8h^@j eԨKx=tOdJуhU^{T8,_oE BTu]@ KPn ;iRȢTIyˍ СNo6u& uިcK_rl4f5J_pY 5J_5WH-4;J~@ PUydU~W43[/j-KDȚœ/VK]/cTY^lZv*ZUڥbz(MޤW~R 5V_೾Q]kN%[ͻ.pXwӵVIhv):=b\\we`|[U{_aY%E|[Kx7?lU~ +hKC-4_soVvsV>-<k05B5KIߧ2InޠXNiS7v^w0 誈+-_G4~jS UkNx(UڬtK{m`JQ8i.oOS-}<iUcDU%(X]$:y]a(RUߒdVק^Ҍ|(yz3L•|>[/D ZޟTDA֏^KPW3#2)2N a{ػXD;ˈU5hyguT U+Yn\K֚w%c.aUX=ORsi[{vLqҟ<}-)&ҶvD|2uک1^Ӌ"V;K@kwKLzqP:9ģ$A*7j2N0nx#Snf_ ;/wWk" Ww6+v@ q7gLon]z5*`w b~;&LoPO2t 2 O VƟ vM b~wmrG K@yUXHtSyx,8@R 2T[߄f_;@ u]@ɽ[hoSUjպ./?,++D߃@nn˥ -:+Bl)Wij]*$J ;_+ONo-Z ]yKiKKVn ;iN%DΙ4J0Qg߸5W2R ;@ EU7gR Q+RʻWij AUk:lNWdtQ>UZ$R e͹0Z {cz1qé,*|7|2ܘ-EGRR=$9һKR-%WŎU NpW.mUv!R8ױ[Dh-jQoed.$:T9~rjsk{ (65KJ6Y~7KWx*s~?bgS;d m,X4s>Y2שEM:2)s8eifAŮUhds] 7}u2F8szox j<<~8iux0z J4<c~18GD ik3$ڶWQ&͝)'(*ż-*aŷWJw+ 5UP7j_,}2 HVB-ޠrX=%;N+ 7 8p>|UX~ jgllKV$% {|w>`7F=7 >%7sTk0R 2T[߄R_;KKKP҄RvUZ~ADT[hzZaS.c{hP7UiyغKBpYN(i$#`t _+58\^%@W5W@H-4:_u*p/v-.+$l7c -KDN'p:(V7UiyغKBpYN( V>-%vՒZ$fht+sUiyغK@a4SRRKՒZP˽Id7WFUVT8,_oEZž\o#J-N+$Hq[f)u>^VIhHc7 ^(CT>7_վ _+2rbq.QI4 kG40ޟf7r %@KV$%bU*z4u&WPZ^v-EJt]Z6pM/C 5n,JO349-9"\ӭ2M7ME>G%O2.l&設T=4a:Lz(N@Nߖdjc޼E3 To29FXǒGˊkG^i+>J:NSmrURBN-u@qT2A9JƓwcۆg:xsx.qO9]L̃I94%(XƥCn^ɰF+-8?a)E恨*9No#peݤH-+|2ܘ-EGj1L7 ^F 9fk/l1{| \1>0P)&d''lX8R󂲅pp&X_XiW|'4k=g 8ixp4ӣNqMhSk-c#MQjٖTdLi5F STrL̜jT<'7Dd[vTddc+ߎg_FI'l,fQMu"k@JhI;ΛJژ_zz',ߛ?aݷOn0ɽ%<{|]zpT4fy@@Tv%8u~<5f֍!,^prX=%;N+ _O(oP4TިkcG}-׏TZ/ϭhiPxIc"ܶpt+SPc{8nGހdމKӂwLmPX`6r*ZUˬ$ Э<}UZp"V[\JZ\J+$%5rMQெ\ےbmDY@ P}<TK'W, 5J@r @*Z]KP\wӵVIhZ= BKEPU{_--\s@n$೶a]t+Nj ;_s@5V_%`X㜱ܾR 6j ;_!YGezhϋpM*]Y%ʯɽU:oV>-4uda{`a`ESKV$%|2ܕ3XZ<⨕e]\њVWoo;FKS=^z,%Ikk6/d;.5F8|3$z51]cf\gsK~nɝoK}8'*@ʪl_94#%FmPrrnM$xkJo+k$=@ VR jr@ fN *jp-qAmhH ҡ.7`~˯7 2'5`f_;"Fx@ q5g@wfրܣ (s5 N8T (7RXmKyw%FU46v~,]u *o{2],]-{I,:<^  M]ud5tHW5ovW4\^=Qf7q\גu䁥ދ^ . t]zT PZpW.m5KroɗglppO_jQZ&AjbuR CTJԲA(Vo<<:L 燀n e͹0Z {C@n.bWkVvpR@'[˿CPUyXU{&1h-҄~f騪cTY^lg㕾QUioj*kXWkJ/;SpYN(ZIDfMCxrʭV<[}8^w4գGi6&(8,_oEȻ}o/+E1wuMf4,^:eV/2idnΦӪ,"z5U1'u:5M[SIDuSyw~7@v ;_Uⶱ59|27EAE&4韓."n:$\ԛtEsTWu&nVbbUU.^m{wmE rB-#7W$譋K܉jy9l5[h*Yb_ER\Bo;Au]3vUBUeqgz&EY$虳qM 7s Ǭ QWxu7 iŮUhdQuu|Ljeyjlkuy$U6x2'uA$}3%T]n& ԦFJڥN9a\N&pܒ8oM=Vh9sǷ0 S#'V[d{g9t -CkR2OOe6tӻ|f+]o؍UKL+ Dfy*x܃k@rO%Z8.K'%=bMj<>z1(ɰp]`߃sz_nɽ"nu[c {8ip;~HKp[}oBuu񱹷ZL ox1RnfRIu~1ɂɗ_ &]~xBEG(^xG@9 E7mQoJX 3f>o` + FQo~59x߲)BlKV$#V_·~ Ьs)s/Zw7F/v #GZ$t @4<^g]Y%@r:j kPZ+j-p`vp0S p ZQTvqUk'U>ʒTJԲ@2n-w| }Wij]*$ ХYy:1[jT^v{3E A*~2Z*{t%ߙVpmm^JdRK*;WG6kKCQU{]P]9K{i}W5WRo%"KX7>mW3xI+4& EQowFn Ko56[{n UTYc{\0oydP)nt @Z7UŕR CQ%s-B!Z5{#pYN*q*$t\YSV~>Wij B9{_(Y6^w: EUⶱĒ͜m,MQe{q7gaL"ܹym7F5ƮZy)M3wK_ǞXΪޟn%7rRmĥK,_RBKVxK3)d63uyZ-pFug/J~AJXx >[/o&!B~ :zj}m"hou* wȧocI$oXNp?ɿ݌UP*.eWRy|8wLQRVv:S]r4 V; 4k}"SߙƓTdZkMUek;4ޏ 6,fJ O$ZJ܈xbo)+<'̕SuFj;߽Sj9b+m%VFN657|` O0ZI4Xk*]!|T@g2d bq\_a5:WGIid9R`@-4ud=1fh+'WŎcvhԛtEK PzY`:Z^v,J#K긷&Υ.51S)`KPn ;i&#%"sJm:NʷDVQ]ٺT%0+M[]Mn.4E>?;J۲`c&"[X#[W1ooeZՒ;WO;VIhf*4V~RKŒIQiwnMmv5_}t7{P~Tvަ`B߃ȷVߍMm{ziYycSvަ`N o$t^^TNsK uz-:u2RS=}wmni5ow.oɀ oPV +ߵEUwK]`" ˗jl=Y!R5dzJQ*qna5K>Dvmh x}nUYZHfFj;߽̖2 #5GeMUek9Jp7H̓j_٠e+UOC MQ~Yo_v3ԃTmxC)Ɨ~ jO @fրi5:a i|Xզr;K~@ x^ e8.K)fջTu䎖#G#z:Er/N 1:PJjT%Xa/Ѫ)= 7F/—Pj*t&M vW51]Y%@ ZƔtj.w)^@ Ps ܶ6X6 EUwLnX)N\m5W,b +ߵ+7c -KD̗J];WO;WijZ1IuqnHsJ- -AOrXފJ/;jBR*u"ڦJ[ 7jy6u*pwӵ$ȢK@g:[#Ub$Ag};Xi|^bWqxqnƷx{Â)nk7dd[vTf4~z(oz֚$0u]8UR-Nx E)sԓmpc[=lDT:n$ۢ+d Ts}΃ju&"ݙI|%|_c$iSl[LJ]2gߎ`#[bb$: Px!Sz&tݱϋX%-.$zn'7 _+znSbnCo@No+窅Czpdއȶ۫;)6dfdT]B\oo.tUCּ>#S\nU`D#K긷&²K@vW5N`VoPwMjAٵeK 1'DۆD[2CҪj >TnԻ8Rk3 հϋH; ^6isG g};\`~uzp.P Ma %5s'%<73CY(ڛ@}/4<ܢ^U$`JkP1< ىk/H:'2%gwYOCJS1+{ 2h#%FmS%&}7Fc{8+Me@o'wS˾C׸JI%tiEP`ɥuulíe~/@‚Xz66ŀ8TkwQ(=À[9o{|?{ڸ nϾQm:RgQ3-5{Nnl9UZ >1< 8k| X׳(pub% J}pKVK -K$Hq[eRfQ@V="&ǧѠ*8kPKPub^z WijjqK% -T)|ZVIh8+6, EUwLi}WNeP6 V.bɨ*~,f*{ر%)R)>5WKEQow;On +l3J/;VIh gRKՒZx )u<%ZVIh8,{7E3wIrRK 4rR%M~ήZoڲK@MT@c[=kw8*Rf }bQ$]:a*)uS@J-^@TP@|8 E5J=S_`ML8ŭ:pL'ێ tbʒkca^C iŮUhdl9.kҥisx.ܳt+S๓p`0{#u5B_`,㔬vW5*w\1{|}:BNz"[eɔ]p_,UuM@8hL*Z~O2%UZL 1 -)J~Gbիu]\/wKLwKPU{_dWk-J߹QI~QTY^iJ& vJv5WEAQe{EvJ'S/-N$)`ej9+<7QY%GUZpYN:3S Q*$tB UT./QU{]R#tl/@c[=1~R7xdymhP]QN2KoFP<^_'IsgTW`T跽MCWFRҦK|ŌŀiE]hQA+KTS#ߏ*5Tiw98W-$nXiw96u&Ej⪯7RX&x.PQJ%4Ko+{tJ֤]z3f+< Tĥ.)o&jN,ĝ,KstKdeK2tOdJ$T.e@RCQ$] 5p7.^f⨯KX5`q5Ī/80Vۯ'@y}oSnPAe\;kCU5rǷ0ٵ75|2jjHQ~YX>ɃT}m"nn0j\]+})5RCsGo[m՛vYc^&K rQnNj]21<;3&<mnd,A~V언pխuPVVkru6 ԹKqY9NUpxm+b[roZo@` f)Zx=t[ۆD*'5>02*R@Mc%MQ gy5]/8swN:0`=W?CN:0`=W?Gߞ +, WHQK*FvEFGN~  6v4,NJkPZ 7җ>ho{\zjF5 U =\>q:tpO [eƓ-c#MMsÄa`Mbp/ӑl áf@{e<^NZ}8Z*uUZVIh/l֦Wo(@R"t+SR e ^x_q[eU~Qc57KK7F/T1|PWij]Y%Dc{. )Bya( _ & AQo~ -A*~hX D΀Ar-JGRKՒZP4>?%UiyغKBpYN(Mm0ڦJAg};Xm5<y7c -KDم/U+oWij]*$N ;iA {d]tG6k RqxV+uᨦf/ ][}38oвI*#t7xymf*_d 5+M1wKO#Lʤ;DTnUF~J-ZK$77fN2)e]L>%j&libuQM(ZUE,< iw9(x.QmЪI*! C5UX9dhEt֦k$wMp3Gf j>Y*rX=5f֍Ƹx%+3t֩?.O;k@nje0LĤӢf`{Ska}88 уwL^ 8{3&|_ 7 ^GUZNo3RuofLϦx4u[<^gdމwmM8O5ۻJmw"< Egdx4bx-"o'+< 51Ljky5Q2BX߳:\M+Vvd[Nc4?|׽nSÿ$]5dZKJo%KK*3A5 yf֍* >]x߳EN7c@s`+ROsX>ɖ2 X='0 'ߘ^uzV=4@.;߿@}キ=wҖKO2ÚUWMɺ*.sK uWɐ y O8֯{{W97;vOc碮G(^u3z=7 N~8zQh џ\ZI7|rܘ:ѣsOF\M5TtMb&`p3)$dF54xA);Tiw1)0nBU c[ͼ}ToJXӊySj#}͑kc>0{8^w:d8.HZ0|;VpǷʀB\oܬ0{#f! ܶ=K@ уV?V=1TK4 er^J#H,_oE Q.һKPYbYN(qY%GDgd`LU,)B{/DK*J ;_nzRK Abz&'S\_KVIhn ;iZJ/;ƫi |exfKՒZvEvU*R-IQUwxXU7Zn[D5~?iA.JKsj(,4S%EYA.X$8Y&(74íQ,] (7zdҋ}:ڂXz6aֲS & W)z}bl,鹉n] 2 ˗ {eK Q[x W%-k-vR]%6Jx``FxD@QgF282ͭA'Vξ'KNɵh~`=kvGN~5*a&O W{[۴h GᗢX*?8z M_ +ӜY=1 ɗ_2Kw7:Jo+k{ yU,Z]̵&מW'@x*Qo*oT X (w]iq`ttt6㜜t?=,NMW=#@f]̺I/pJo+kLJ&W|-Pu(4TY d%.BX߳tc_(`~2⹯%)s//b$#7Wg)ptĒ-)<;2EK7O]Nt,Au]3l2v]:U#7W  }0|2vX߳i']qvl^v{&貽\po% ĝ[eLq6mݧ<72mMTHtSd[;f `~ӊ漝y ,Ԧ.!,^rSm0` b~;&LoPO2X=gm5g3PT1< *xӋ]zM<*R@L&%0ЃoVM+|L@|8OP9gZ>M~qŬY`eиyz-g]x2y4 mޔ=e@dU$VyTppdP*]4߰e>U*iY%fM${fjT%áenfP/ӑl8]` 8Ԩ&1 cz=|q7MV o\FSub^*hb@ ӂ e^sN O ۲oB2oK``FC6 ӂ: EUɸb@8.KP7Bqi_E[ ,J/;VIhN BUƆ?`n.pZ*ZUܬ/e[ +B -A,{K a]tU^{T.U}=ǧ]8ਲX cK}?d~.ilU_ڜJ%%;u,جcK_rr(U(DŽwE߅:FIQ=,RgN%|_cD7 uޏ7L' 8>9XU%Ou&]5V˟XI%DQ$"MvaCWFt%KoSbRŃ\9Uߝ0 ?F(*4`bx-/\m=$BX߲ Rwvo\Af=Ӄ|Brd%.BX߲b|D)7覉7Tȝn, m[a+ Q&i &:qvIU e{v#uuÓh ;& ղv>/#,sy[^0~}l4FMXiJ\ FNڦ*[U$N-R'w},,n$+xR7sf%-d ɤ w{gL5%FߖdrE A9oe Um~@T|c+<;2@n?˯&pYRYpMpmpO@e<A6/fk#(dfdI2ROOe)lT7904`7`T-bJR8`>M}&v3\M<8i[ױ^zW,񥭭4iVāWfb=4x02y_iT8 'j/ Uƾ* iyG f-~ :W-K %z?GՆ14yfh .MU'=T0 Uqڿ@zo2fI4Xku8s,s855GfK)=TNRz'1< ҥ WFX= U(P :zjrGQ,қFdބzZX=uE)f MnYN i87Hݽ[N-uv35iLo@)~0u&_O%'%J' ^ a־&z Ԧ=uI K6MeɼQ&Mۦ|vȣ]z*Te²\0(Α os umtM){]#>L;|n)|bo+䛢o[ⷹT)>KQ]6V^o;&,ߛ?4Tyߋن 3'DnDԝ]^+{8Jum `Nk2UOdJǂ;rq ZtyݛZRTof@z\"Sm`հϋH>jӧ@;Qf wz@(οM ySUNճ@Vit/6}8/3v UT}269)$dIɼ|dj[;)6dfdu$@:wL+,sTP|UgAmE zpDIQM3DŽfb '7Lv,[\O8.lL8hU-VB|9߰ '*+x`wMjٵ7<{|O]]'GUz4]]'-T[s*T𦔡aC8]]'@b[vwգprԨKÿ$]`@ Ps3U){ts⹯ q[gds2tOO5J_Jwkvܲz:r8-=o^%SJéq\א^8-z"\MXމ´)|h*-%WO;VIh e-7}뾵Ee]ͨZq&edjTeIۥTGUZpYN &/"Q%rYN1WO;K; L0tvӽ%WO˩TE N"h^ud X%;JKls~B92_ٔOЍuK7uxJA*il{c]Se|DcMec9rKE.dk,πجcK}aȌiw96Q($] 9`i,f-#UMoENRK3⣆Yq tEtUXMAm|QKWJ[M_G(~ E&3FT7 ^(MRx:t[,=TM@= pbx-}/"m(;."m1<fݓzӂN+6ľH[g4o },Ox7Fu<'H RJZZ3d^/wՒZT0d[-rS*՞ק JUQ| &\q:_:*WbۆgP5z-59nsI-/Ѹb["RjV9eNj3V5V,ع*we]< /#K^_e%.SN څUzWԒTEaon(E[+Xru%LӂՑ.&D-ӆ 6FƏbd,oS7X%7 `a{ɺ-ti_P 8ixp4ӣ.q]8z Vs6kBUX34Ԣi@ItGf#zQTs\̓'rՓ4+G $kө;T<+ML {~*?6 ӪMvz2NϬNFckcZ#tæd.Rp[/GNGt=kzp\9 ⎀B|7 -@á/P@: тTM- _+{?Db;].咖ջTՒZd`.ecTZ/Gtr#v8\v P+u칈ŧWXIDuA๧KI,|jZ%>/^=QJq9~ ӊ漕I,BNRm4&z}2* EQ.U]^+k e0{#^+NyiVJ/;/oTB,0YNh%aR8׳++$x08P#r1~k|Ա}߲~UTB\_AQUEo1Uw~s7̕UbƗx}oƷrRf ;4dI*#U%l^ 7$x( 5ӿz%ZX_zPo_TmPMޝ~Q]IDN~QUAE5'#&xe"fQ+Kvmh RgSͿݎJ)+^Jz(rX=E<{?D @ ;93<2uoezvVы'BKFx<^ ,,S)2rX='캲KCzwmY:'2%(Vo<<br\_M7r-N]+y84Ƨ=h2e$\설&ufi`o?Nj$lOWRk:Vdu7n_["z6.UH Xg*.TpY.Q$DAƧ6s[FaoUܽV:y-%im\U,]UVm8pJ>|$>28e.*q]A)JX{ǃoKfLk4r˖9X2uvxjr=&MP?3i*JÎ}2_2:YSy[^)'E_wmnkKt[0NW5/w b~ыы K@{?DZ0|`Qv;N+QJy|(ӫ QSҢ\=15KIoUvEI5 Jb/Z$tx. 7XŧWbENǾEs^AXƗxÑXrl^߷ݓNc'aޟu1V+uסRwvK¸.R$ KjcߩLN>;k6E ] ugU oIqx/ jZ)7GzX-hEOمDۇ׺&n8_D_KƋZk<ޯt1UQ-ݽﱸGЬU&T˾ o6YUTKu&߷tD阪*weo͞zaj15WsSw.>VKƷx{ÂbD`&]ŷ72$I*#RN/djyd7ێ9E bn.ɜQo9R]s:Jn-SYN0^Jڶt܁jVʖq~NI./mVmb%QJI~L@{?D ZTD,ߛ?R/wĝnJ/D{ef@P}<N[y`%<{|ȻlSx.ɝ/wĝndT{羀ms ibfh*_2KKF\^aX7UQeͶIVUTgS{vbeI6.bRg(VZ([@!Wo?dO&.Z sK uVLβ98ӓn*ӽoEكZ(79%m=+; ދyBU1TaM=aۿW9U9%v2$g71)RXq֯LUUw x81)dɍ}3)V,nĝ]^+{Ar]!,^5B%>P b~ː/w_(_(P{?D @6:15l3@ >IܰI%%!)$dIYƥCY*,<k/y fRIu~%6A9)RxmVp4 * d5e<xpOz=CthMbpkթkF] )LڭoWlIŬ|{ςn b^C* r {<\g 5Oa-D뱓e>V iTpો]WO"U'yڥ J‹)?iFq/*_˙<4i9c]DcK}aɳF@%6A*n݀ F-ك)cŨ IQ3U> Z2I^ ^@ STK\˶(Nf-F$'YdLp~!wٗ2ONӉ ɶދ 77<;2F阺xh%i̝no4å}Z?Z: ostNIͬ:&͓#7W]z&A[pzX//r5oSRuo0J-QR}*w<5 9̤Ug}IDkb#*TۓyjnIb}rnmkë-QEg?2꧞ b[ÿOs?'YR<5?k[7|}_nG-c /nSe$A, sL9UYW}껃|}I~=I~]>PKz.~OW>uPgĸXbMyt%φvo+)*:>d@0`8Jumԥ[,`:]ɽ;l(Vo<<(!,^r*gy`~` 9R@jߖ5ӿz)RT̩GZ`]iщdր5IY:+b"jN,ɚi9P4dO1UQe ̤]r_$nn VK`J.4`nmVvMXi ῂ5U8`Nf@ќzS'^Ã]v>C&KL55'̸'j/xcfﻰy\4Mbpk wZy59;>?>8'brX 9&X?!Lox3^>ѬUl\,%(UΛU_s klش\3ꭋNӛZO3MMsÄuwV>|M*xJ auɍCMbe>"-MtR>4iG5΋TkCˊxãNSOԢfm5ŤE5'#@N.8i5FNP>2O oOZk<|wIN2ZWQDU&k5[mYǩmEhVu䓃)ZmtXQo4[ŽL)ƭ^i*RoKbRm~ں:~9o 4nI}8ޯiuڏKsbgԵWoE:W?:֏qdދmԓΛyG|=~y6rUW9j?/# PxW+Tt9U/G q6Éx:kqWzPr8\N~\]~rOq}$O{mZ(U<}zomw:bRw瓲]:\oPހ77`Sy[^0uݷgzT$tBX߲%7F/7F/80T9,h@ Cd ORun%Zޠ/wᩪ=2kO@uW|L%:Y])RXHUE>r9$ԚIr&7+%4<8G>Q|pkƽO8~r^myŪ::qNWΊÂxZ.4녞9`}a<'M3DŽaWz\ZF%\(x.+,Gv?Y5٣>r0&4IC5RRu=-_n)SkX=21U^QBa/6x6w̜\Z}>L|w&NM9j5$ZX.OBQpXiF\x~74\ӭtg|dyi>++v(׿*%+ Ì9Eڧ() MpлI?D4>mT^&Go"WΗThb,LmTL&cUY]Rn}")j}Eڞ [8d&E4—x+g=/e+%瞹zsp6G_D!xƶJ36K/w%$sEUg#%HC-BX$-dr1wjV;xilbտdJ3SI%DV19-?[7ێ U6XyKXcK}aDt@㩵7XƗxÓKoF:x-~˩.y 䬖|I6 Qoogŷ72$I*# 9|2ߏ5XrlU}8( `t֠j)5z\ ʝJM/ى6&sGLO VnkrO/{17d}#&VPԵ0ͪn]OD?哢ȲvZ>'1n.VrRw2tOfwi8i*Z:+b!'E_Rn*{!7tu6 mL9l_s,$Mky}NbܷLmuY)$ԚIr#+,%*YcDK,}c#}톀rKey%Vu93 'ynnϼLռ[}9Y>icY';;}0o4w%~YkqC-MN_vU,Z]9%v#Vue=TaԿҒtUU OWSxX$u|t*5UXoP"a&KkMWg[ߓV ¸1C,ߛ?zi<34/oB[-,+7 Pxxe/,V?Ԯ+57WLֿp@u&+og'=`#UX ĥK,}cO0y/@3S\ndހ{gJ.;8)1'W_8)y ݀:uzr?bqk*Va:qs]8[+ESk# 5gGDuY>U*M/}{ 5Jb]3Z$]a[}6&UUa7^`a+'÷ i5FI⹧[BQpXjhs|p93G+-oO"f*wO;Y5䜡zO2RW +\I4?c(_B2OAjk9Ɠw̤L.n`NnD\Zba'(f}P)uuϬ J‹˥bӶN.4[ꈝP4'O2mdxgYҚ} 45ȌUtww8_는k.-uC'ÂxZ.2]9IH,Uu4] Ipۯ8[;gGCpy_^(gNrh)Mhå&ꭖ}ceZ<~RJySj#]L:W[T6eFtLb&5oX-p{JqOUJiR2qjێ@QAM~Y(x\9.}v$ Wѯh#ޜ7jM<AmnDT] ;_{ɛ,Uu+GVE궷z5EMw{#TK@ VѥB QJF׭ f[փЧҩ ]dp[/FTEVǢEZ|-Ko@m'[Z1Jm_ _zXSmh;KQOUKxͨ%[-ҿFaҩ)ݬqrKRэ]zX0{"UwUk^_^s#]q-Vj"RxaoX*z.;%-3~>k+X[p&~uj1K `]$]:Œӛ8wgJ4OoiwKOck?},n3iwKO^1F5{Lz\~#ԒMc]L•\xNx9Q3|H5zL0s+ 7b궳׶o&i%o۟s{$݀ 9`+weϬ $:|`cK}aG6k VK LT owL 90fBX߲r,~G#<{|eSœ*}`[}#9&/4zp\D%,~x''DK:nvO >2;ne+u'7d}ieﲧ7v=^wsv/BU,]M;}2_%7juqżމ-]cI[>zIX}mnOko̓?s\?{zAɥ̓ٳjsPJmc{w: 'ΐMTV2NUSXx4:+bKKDmՀpU/,`"` $l9,M*‹ySzu&FAUjE^[3_P *rܚ(w4JYIY%}7&Vx- t5 83&貽\N@p.^` [旸8)ym756|X3_}< /)JX{ǁ)Vx`4f}[/01[k̝eRuvxd* 4d VF j̀"~֫ɇ4+d+uJ| R]V}iW|Q]:X}k2n3^>O%IoxZkz%_EūblꩮZ3^>jk=\SU*x aue ׏Qk5М\,i<@\lZjOFSbIŬ|\I0R⷇<|IŧקS(5&TR9Ɠw̫Mbga^6kГgPxNKQgТUB+gF5Vw%)ȑuTg 5f *̏Ӭ_)5:~q6Ox z~ST4`wf\:oEhk\& >Ui{Zk# z? XʶxVˡ7;oZQVOhr-49e TZ떘T*~b`( Q|%K/Z1J:g%|_c@M߾oTcK}Mr<ten3>0 STzߐ]4QQ,*ԠSi]s_;'E_MT@I'no|M 2%g+F+FMEZ0|`bx-"x.ɀ:x7^FmYxZր2uo)'DۆD V:A:]RȘCЄ]|lno+̓4ǎk+ߐo@ M8,ߛ?,~"Sz(J@ 8Wk#'VYmS-MKߴ24Dɗ_9mﲅpp&?œ~}Tj>_O+}6&oLEIPx3=]Č\U_6QN$:a[.}`j1`î+8JUMAŷ71ffU$,|o|pIQ] u&X%7!dh:'p`[hKj$,IҒũ'6_|5'V[aY4)[6`RfnoR1aV4fN68*.^uARl鱆ť[Ϥo~AʭWt5[܀4OiƓ TH4ձ\ӥ̂xxtUGIFTJ4mJk%{W`SbWލ'UM6}@ O$nxMSN)xӞPsm, R.7+o`^Ro]I,J]Z%:>  C'u0˳#( [AmԌkxs`i"U)A;zTSMJV]u>(K+gESoSQIޅh . 7>ƿ贿kPo.JF4_FFk^ . B_#xsT_lc[Twϥ 3l.?#TݳeT[E#D[ UdU;պ뱸*fzPׁEk;{{}ke)uY%%F`k׏|C ܼbݖK3QjRdbX;fӉ[f1ok>%Jal2l{ߗ&RuTYU5v1 *WOּ8[/WDVфtEc_:_Bn |i;R-hڭkнPS0hQJ-:|*pگ UuG*z6bKph7`M")-^zVµ. pۅө . -K*lIDmR]s3UQ%`Ut7xyܳ'^/w* ӂ:%8vX߳)$@ b*tH)bhj 0 U>=$ݩwM'[i7rMm{ƥC̕n\h q5dZ3T{ߖY=1j"17Eվ58\^&@pq[`+o`[|8.KU @|8Nqʒ.dFP.-uZ= ׏]s|֙冺qMhԛ]zа(O\pk+k 3Xrl'ZG>Q|pkx>x?gҩ+G 8'j/n uڵ8X=< pO_I'zS&h)ǥEɖ^W-KM+.ygmEaޔ4Xh~iV֫-V-;4yӂ}6SD?ɯ݌TvӶmCw^f6k\v_ɂMp-Qs]Vt+E%þ5.ܷiV[gZ>%z?GRuUO [}57Dnj=6Z|Īe@!)}E4ޞ $6x*SU+Q%TUe(odnc誋tUA.JA^}upb1o[EhS٘vX`[^N)Kˡx4_5S0lvaVk.ߡ d0w-"e]F5{Al+䕵iTyvqDT)\uA*V0jx:V'ʭlKX*K3j-C LBoSn1}Z$RU)o_Dta\j|1it~N$:( # ޟu^I֒Vr(ԛtET/R\E}JV@mNO5FےǝYut4t2V1㓍-NƗxV[/0 5J/;VIhpuE/ /w총,Nmx]vMAlsSజ!'Vm&貽\e{v;M۩bNڤ*[U6Ѓum칝#fd˹o%rݺ9wϮܵ ;Jt}E6mx2JZdoEK̺wijԩqߑKt<ӕ)7l 4ӆV=/oT'τ BY7{[(y՚zWIZ\5` EgL3 BUrlj<Y89,-y\vMKhoV Ck,B~rOakqRLtM){8M0|pbx-͜v7T4,x.ɂUq5WFM(S 0O0Rk; Z&巨SuETZ=:>ϔ+oj,M$SIYURQr=)%(UU'NybS<Se% gLz5.\Z%(VL~Du^}p5~OȋMb{O221UZi-K+V҃xrV0wf>YeVӭV3ǥT 7uF[`[S^9r1nVI%DIQkwk8Vż]mWij( bU(KM'7Y?EMadp0{"mUv#j<}P^Q,uurB.u%%|_c@>)Kwn l_镥f (7|4c%z2QTK3> ;5>h:"R ez(j1m7`c[=PmhWij 9rpi/ SKoFzFsx.ɝnvMhW׵ɝnl߫ R7|"',ĝ[eO<[nYI:'r-ࢲKBsy_^#'DeXT|M'ǥuLW㛫]߻3'o MS7NUIZIx͒gr=59cyJ2:}Xqxa%PJX!'Eu]􄥋xeI[_覘\_ !'EBRSy}1'D܋x:Y'6Dm𷆽jT..2oKfAUfjaa_}//r'[o : LU)Dq};:+b3㻥0陆ufMJ%USe8q` /N BY7~P4.R~w+B^|/oT1{|is^NhӪ3ѧcT⹯ =ApWvsR+jӡ@$:ՠ҅o͘  ⹯ 9,ӎSUɜnzyڋ0q&nAݷ{r@Jje*fJ ˍ>H1Z:|$:[äfYjZ8:'zYp Iվv^+u.2ͭ9dc:ge[<}Ĵ k{hΘxM5T@m`d ;Vو55#J==OT+7HQ筦]2xUwrn4<.-mU2kZj=4ՙE>_N-Yx?8~(f}+\OkXiIdjZ~"\N0<5% gj&ᦙ)My>{<$.=jnrl A_'^ 8v=ҎipyaϠ /('k+MdSÆ4)ek{"U()>ǤuyfNi/e澏K5k\_RmWRy|58zy(㮝4U|q A&̰4M>ttnrGFjzWO =?]]8iV<M;[)e] ^0޻B%bukmM/J ┼VxˈO%Kҫ5jվQAS^f[cpw푌]SbY&U(j/%GX~N+$Hq[jYC]qvk֭}hH/I~ a[-3 :]YZ[nx~C)%X&mRԿc ^-߲{NR}QQE]hdlU4_ѕࢋxwȴU<Y9%}P^Q,ۢ66X槹[ҭԙX&(K+gES*lA&w˦E C6`r_Mpp[/Fk+{6jNE44ۢ&r_)r°(jI*#hG@*?@ tKJ^:Ќ[}3*J$:dZ&lu&*v1m̪I*!EO;3U-ZuMKDRKJM mbIQjrd Y'6FrAkO~lI>[ |jZzOvMAlno,&r1s!LJo+kf|_mݾo%~WHQT<gw̜EE9UUY_'ټ:/rXiw=NیRvB%7Et;'V_'5yoӕUXVg}w':_t,W_ NX9%};~=Kг_I|#-57WY'6DQ XxËfdoE|>IWO箼LrBbrw94N ㏷Sm//r&@ }{\i*-շcqߑ<}8.Y/Naӊ漒 ӂV="Сs- ^((bEMӊ漀.v>-1tk,]].HFBOPN+ яM* ORp8k/wṫ7/N t4`d[-rUs.9"P['ހ; ѳ毆\ߓjlpR 3S\nn4-1tk, ɴױ#GEg_ KMMsÄP'pq VkpO >]TikM5k^b+k'OsY5٢R4=U~7sM.WC2}e ,z&X;XpNP^Y7 9(x>(,:I^Q7 9;[Y5٣ M OuX1pr'/uE;DU(S}|};뫇mut~ 1YW{~ J*v*w%K 彩70y|%iEQ^Zkࡆ;)(O4^#]߃f齍ձoE=K%/4iBt\L5Xwh~CJ-Ue7(wXƪRntoK8U7-Kͳbx_F˓qI.v)޴0ҙW-ۃ6aEdLVQC];+DT4N&nvJ QKWJJ/;PQwNTIQ;FM4Z|ibQ [kH! ^(UJ(N[k8U<(7zJ(8J;V)N+?+$H0K(^B)N+U*_ Ʒx{ÂP /-coRZ)+E"wkOi1Xo2dgqZK|)6a@9UP)7䛲oDKbn{p:VlwmbnFs)RסʜKu&fr\Y*c O[4I%};~xdMiS0Mk'6Vx|:+ۯ0XS}kU}U'\y򸧕6_zSʛX˃ۯ5?IZWÂ}6=3E7MhVkVhU _ uVҪ1/j[}9:Wrͮ~:CSTZ/Qh!Ez߇cN:2 9HҋnX*.=)S72mAScK}aɳuTp.8‚νt6_),_UB$Ҝ7Ke[OPoEڂo?5F .]q/Ғ_r KVᙪa]0\u}FU-EtmAٺd*O gaJ ZAHERy–ud+qetV${oPz? &bGᔂi:_ۥ$۽ p^3(Gpq[`8kp.pbJ^6%hï`7/lt56eAH5W\{5c]Ӧ=O8p򊐎+ud̝nSmJ}UKp`3'Ei7 :rXz}܄Ziٹ%NUmd *e]cJxzO;x)7NY.'6ӆ㕩mwbržUu+[-䜩ꘪhmKSN߅bőʮ',n6y}8= 1opfR<ӕ,yŷ~nl˩rׇolYS -oSk}rN7 *5'W`Br|߂Mcac2uufRIu~qev_/JlJY.H;'үvjp JU{g,ߛ?eVIhB~@ _+.px^Vr--ھVDV׽*\R9R@]`GI}{X&iÆowҩ>7Vi M5Tt+J@T)|bxwd$RlpvJڗtڧ)3??(SX>iќ74,NxsddK- qx|jT<ȀBK0N7 P@Jj S<*FN, jWW .ޠtCv-C f,ȭW@ 6% VxwdS>V|DO_&(GVq~So_iG2 R<V9sY_Te[<}h'ێ&c*d]cG(ʪZ:V@\p갇pg:5=wWx}ɸ5cN&`}'?9UqOGϹiTたJ 媾x7qO*mb=,웋XȪ3 >,V?AaU}DU6O7t#+BnګpWzha\ദ2v[xtedzéz? ˊ+=n uگˎn=5yϰ{opO\4~z^u流;sh_sz㆏7Fq<4ދz?ꆯM:>C=W?Cz~W|}j2njۏuu (%ZUBY.Z/ \䊃}7+UAaLn9++$+we*%V1J:g::Y.}"iYBzx'*UڊV*)/eK2u4mX`]q(ߎ%oUmA,n6u&( oc_xFk7KKVݺok=iAڴPe =URfڦKEQ_新t–]|>nm);E`,_Oc{.B8ײ-(^ ӂ:۵)CrѸc% ^(2  QWijUFcuU>BK@q]'GH\q[eO+pPվ[TWk}l$v0߱$茝W٢SmeJ<d۳} -ZN J\jbrżdx=k~o%Jͻt{To{KK,{eVrolê\&Ƙ7picO$,_e䜩ޭ'%K'Icֽo\ӻMD߷,KBsjX~v ,|#IeJP.gI%jc57WLR\]YWiܭ4RÞmp?&NY.ԥDuwMCtVDumRJ_coޯЄ]|ljRi_#]zi5Yr;me$\쎶#'W_Y;y\nfoҥߑ . 燀is^A^ː+upO@ ǀV2F:~v.󕃶9nէC`HJ*㗵ѫT+lsN7F/T1< Ydlx'5i"ˊySj#@M hsT() M&bn+۵/w#S @wj|nAݷIdm ՓRvdމwm^X=e0u'7왩2Qҽ g$Njq3 N}8$R?%&fJH*gѣ*-d}~2Fx=1uU^U0aƏ쌩gx5U=w.jҜPقXOcGǢ.u'<˧d&sT9pI|^8-T[*Z=S<e[Dp[/F QAhVn4ӟ>_D)>% ZޟTE(*m`dg4[!FMv8z(*4<8Eio_,ȸbn a{ػMbpٗK\Xft8<N0J|<:7 Ƶ^W7 5I/g_L5{ZUyF"lmWeӆx_T< JDT_EVv˴3|;\r4Oc?Ws%o589,I85\?e]/8>O+EE8'j/R\:rGGG? WMiiKyU-_@ҩXSYu^1Qm? Wf]T瑵8󄲗cR+M4;P:cyKOD_PKͨ<ݺÞr&_+f贈&nytJ-M(j,DwjcV߃ -eI%DiEY>~yŽ:lFra~syy*$Vs}>a,[ye<{gfgCum$[Wk3P+.8_ug}w%,~y5'E_wma9nN|6Hz77-ടK%ߞBRhRh̥[,`$2_1-q\ѐ.yۛ-J|^@JZ sc{.AYEzVf&ulviMZ2Mu~(;Mz[9\lܣ'c{4qP.u:4pMtVZ @ '+< n/oTA5rsYNɀ&貽\nI핹f@is2?I}2tOkw;S# D/03_}< {gp( 5*gS~Fd[pț+oXuI2r)q7d1e8zY'p"6pHH_"5@t?O4=ҿ>%KdKR1K{<ج%]9O\.h|5_'7\ut>v=tt냪ޏ ^fzwy\V^wGD˛rqN14.[fITiz ^8-ehGJ)Qx^QX'M<kBMMsÄ]M3DŽfbi6}299tZVOA]mWǣ+ pFa`YU(xtXqw~- 861(W . d;ҟ>&^U[]v0.#zҿ^Ã]v[\LVRvǼ'zWQh#?ZVadͩV|gxpUї[ƽi_J[߱k:RGld~_nT,U{_O8=x zOh/GPytӟ2gO[d[k1PKhKou8Y&^5<ب3* uޏ5FMg.hs6ML5~?iA'\wЊV.x.El 7$ѼJF7>꼜nt\^mEp-'UƩ%M0Ү;W2v*v8R ҞiRȢK@.J:=ze>1{|&lPn.iK :+A$]:WK'JA(JQPVo49T1{}1 ;#d/0[ހrɽ;l燹ِ-7EdARswK,}]䬝[WV=nvƥ5-]?t"j-'| R6Rݔhp(z:n65@+ER50{#dT>\\3%T_Dx߰ѫT/)Cut IU[.+< %YK se>U*F\(Vt1GˊuypN~0/s5}XW:U*;Gp:MVWyR5~?iE,g Q, iAm| QK9I%XZ4aBj(+F`mxh )LJ)M~z? 6I×HM5m:("xgJmrٗxt1{|ScV;F/RKQGdމVIh")r^򎙓}m"`"sx.I=yݺ۞I: Yb$'iPs-lIG۽U7}Yvޭo6%RSSB8Ѓum&Smn~J|_JՊ漓Lr`V?Fx@lY<68kᩳ:Nlo,=jl^%!R-T[@^%56j.u%:r~j qpZ.k=V  q\אFJ, x^G@d)BM]g'7r`5 3%T_1V4sD@ɗ_FN, n\9`LF bx-j _O('Z? O2*P@sight-25.1.0/module/ui/qt/rc/resources/branch_closed-on.png000066400000000000000000000002231503402212300235540ustar00rootroot00000000000000PNG  IHDR bKGDӵW\ pHYs  tIME  +J<0t$IDATc`@XL\&dY&dp##Ȉa  +uIENDB`sight-25.1.0/module/ui/qt/rc/resources/branch_closed.png000066400000000000000000000002401503402212300231410ustar00rootroot00000000000000PNG  IHDR sRGBbKGDS4] pHYs  tIME )G$IDATc`@s>XL\&dY&dpN"a )4IENDB`sight-25.1.0/module/ui/qt/rc/resources/branch_open-on.png000066400000000000000000000002261503402212300232470ustar00rootroot00000000000000PNG  IHDR |NbKGDӵW\ pHYs  tIME  u1'IDATe upԐPxU!TpH 4+IENDB`sight-25.1.0/module/ui/qt/rc/resources/branch_open.png000066400000000000000000000002461503402212300226370ustar00rootroot00000000000000PNG  IHDR |NsRGBbKGDS4] pHYs  tIME M[o*IDATc` s> 020 000BPƹ]fIENDB`sight-25.1.0/module/ui/qt/rc/resources/close-hover.png000066400000000000000000000011261503402212300226050ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIMEܾiTXtCommentCreated with GIMPd.eIDATx[! EM7.H e9<{h=C+V9*S"YY2dpS)BFJ"d& 2|][ {ery帟Q,}$A8451Qz5%Xm󒰕ǺWۂ"F!<%0NMHi [F@6<jo @=%86:@){FqE;haUZ(<]@ >De0Z8ϖP5 `o36Ej&b$h9!A3GHp;Ea|CQ %gi#iș`|EWb=o =J#y]MnMdk]_,vY:Y$XM'3 )ya>\xG[:D>IENDB`sight-25.1.0/module/ui/qt/rc/resources/close-pressed.png000066400000000000000000000011261503402212300231270ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME-ziTXtCommentCreated with GIMPd.eIDATx[! EMՎЍe9<{h=C+V9Qw]P )DBd((2"So [EL=d6V ~(+v{帞NQ,}$A8451Qz5%Xm󒰕ǺWۂ"F!<%0NMHi [F@6<jo @=%86:@){FqE;haUZ(<]@ >De0Z8ϖP5 `o36Ej&b$h9!A3GHp;Ea|CQ %gi#iș`|EWb=o =J#y]MnMdk]_,vY:Y$XM'3 )ya>\xQZw#IENDB`sight-25.1.0/module/ui/qt/rc/resources/close.png000066400000000000000000000011121503402212300214570ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME87iTXtCommentCreated with GIMPd.eIDATxI E#]ӻT& ]2!h xر!̏l;YB@+p ͒!DȌdFpO2;Un!+H{%^ /=U V8s\EwkԻaC[C0Ơq-b%:Wۄ6 "*_Ǣx$0,-y^`D16=586<@ <BZ!,1%h6xg' h 020 000BPƹ]fIENDB`sight-25.1.0/module/ui/qt/rc/resources/left_arrow.png000066400000000000000000000002461503402212300225250ustar00rootroot00000000000000PNG  IHDR sRGBbKGD̿ pHYs  tIME5*IDATc`g``B0 d``b``4D s@d@ uIENDB`sight-25.1.0/module/ui/qt/rc/resources/left_arrow_disabled.png000066400000000000000000000002461503402212300243540ustar00rootroot00000000000000PNG  IHDR sRGBbKGD̿ pHYs  tIME w*IDATc`|```B0 d``b`H@ s@#dIENDB`sight-25.1.0/module/ui/qt/rc/resources/right_arrow.png000066400000000000000000000002401503402212300227020ustar00rootroot00000000000000PNG  IHDR sRGBbKGD̿ pHYs  tIME$ $IDATc`@XL\&dY&dp!ha m u7#IENDB`sight-25.1.0/module/ui/qt/rc/resources/right_arrow_disabled.png000066400000000000000000000002401503402212300245310ustar00rootroot00000000000000PNG  IHDR sRGBbKGD̿ pHYs  tIME R+$IDATc`@s>XL\&dY&dpN"a )4IENDB`sight-25.1.0/module/ui/qt/rc/resources/undock.png000066400000000000000000000011021503402212300216340ustar00rootroot00000000000000PNG  IHDR@@iqbKGDyyS pHYs  tIME;_tMiTXtCommentCreated with GIMPd.eIDATx Ch˷ivIKB^7Q|p(,3f9m%DW1U,֋pQ|&Qx&]|+]x+3jBUvr]g'}/lU*!S_+mwj dqNSτն%x2UNp%W@0[L4ptgCpELWXv+< . {N*O](j9N;WIȜ̂pS$&k0ЯL'u@sj^aB7EAoqBŷ@B)-˜ êF<"Ǯ))e%2*ć !"Z%1 %O4>n2Z"ry&@Ƽ4յ ̋3FS!r6+cIENDB`sight-25.1.0/module/ui/qt/rc/resources/up_arrow.png000066400000000000000000000002361503402212300222160ustar00rootroot00000000000000PNG  IHDR |NsRGBbKGD̿ pHYs  tIME."IDATc`  BH* *ʵvIENDB`sight-25.1.0/module/ui/qt/rc/resources/up_arrow_disabled.png000066400000000000000000000002371503402212300240460ustar00rootroot00000000000000PNG  IHDR |NsRGBbKGD̿ pHYs  tIME# #IDATc` |RB) hNe4IENDB`sight-25.1.0/module/ui/qt/rc/sounds/000077500000000000000000000000001503402212300171525ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/rc/sounds/failure_beep.wav000066400000000000000000004574461503402212300223370ustar00rootroot00000000000000RIFF_WAVEfmt Ddata`^lpOP!%R[ ]i#Pc$ey/C|ieybrJ^ cwiu ei-/&V=^ 3 g / qu*D'5R "!$#$&X&;)(+*--&0g/x21437C6i98;:=<@+?IBOAgDgCtFpEwHmG^JPI>L+KNLONIQ*PRQ&TSjUETVuUWyVXlW^Y?XZXZY1[Z[iZ[Z[Z[Z[Z[kZ0[ZZYZX3YXDXW#WVUTeTFSRQPONMLKIHXEZD|A@u=) MHݒӓܔڔ PfיDbԠš&_d{Whs8!ɬ͔ѣ ֽ`ڽ9$'uOuL  SQ#u"!& &**..2165N:j9=+ȂVͺ~8#nٖ!hJ912rf=cbcbNd3cdcdc6edMe2dRe3d:edecdcWd5ccbAcbbfaa``__^^]x]W\"\ZZYYWuWRVUTSRQPONZMMLJIHGED=CEBi@}?=<{:9g76-4k30%0-, *l)&%"h"E'q/P ( sTHBtr[SBur%&ʯǶ"kcּսW[­0x!Y5T#J4YY~Ģr#̠lNoEiZyП+Oɡ8Yƣ}vƧ A\ԫEY~J=I-Ĥud6Q?Lh֐+Yߔj05*h5QEAL,H  x+0x3s ##&#&)!),,/.21B5p477{:9<A@CBEDGFIHKJcMQLNMPiOQP;S$RuTVSUyTV~UWjV[XCWYWYXMZ-YZY[Y^[@Z[gZ[~Z[lZn[QZ*[ ZZY_Z?YYX,YXmXOWWxVVUUzTlTSS.SRQPaPLONM0M%LsKfJIHGFEDCBIAU@>><;:9976442[1K/.l,+s)(w&%`#"B rDR v L ' 6:}*UR!~jOY[}֪59͟JȥnJǿϻ˼8: 6CɯܰȯӮ٬+aѪOr Īwa~dܪ+yDa+ 1Wg,2ٹֺsiaMaOwVĠŁDz,ʔXϊBgv 1߂P% {X-m1q""H%$'>'d*),(,>/}.10335)5897:;9<$;=gAq@BBDCED\GSFHGIHJIKJLKMLuNbMO NON)POPzOPOQO/QP2Q P!Q PPOPOHP4OON;O'NNMMLLKLJJIIHHGXGWFED|DCBAKA^@?>=<;;99775532a10/`.,,I*)'/'A%$"" P.x 7c( T &ʑhŦw`viͼýyrpn~zٶ۷+0R\"*4inȵ05MOøϼbMB&îkDȨɦtˉNy9w1ш;ӦNtٟIۛ!t[1=< Rw +=Gl_o l $U}#x7 D "o"%$;'&G)(D+*3-,/`.0'021Q435$5{768$8_:9;:<<*>I=H?\>T@i?IAZ@.B:AB BCB]DfCDClEqDED'F)EiFjEFEFEFEuF{EGFEEEDEDEDDCCB%C+BRB^AnAz@s@?f?y>F>\==,<;:s:99-8765-5]43210*0/h.B-,U+*[)(V'&?%$$#" p J=>;p7 t &?8^n G\ Mq,`>%މܝ5ڵVՍh}'Ϧj+ʖwK2 2yd:(OB~quʾgZ׿SC|O1O!ʕ]˯Fηw3vZիշUr@ܝZJ:89DQ"f<j+DKsx:  V $ s7?FF7-u `5 !I!#"2%$&8&h(')H)j+*,$,5.-/.0 01613Y2-4d3-5g4#6Q57667787A9m899g:9::E;l:;:;:<(;#߽ߧ1&ܱܥ9يS۽I1޾ޱ/39[_DPk#j%{@d3wGB kJ+ q Y" pZ 3;$$b > "!R#"$#%"%&@&'P'(W()O)*:*++,+a-,.f-..`/./6/i0/0091010111612[1$2a1 2`1 2G11"110\100@00/0X//....>.--,,,+4++\**g))s( (i'&\&%;%$$n#"3"! _ 1[\n d Bd n 7 $ |S)vi# }.;EpJ9x>|bAMNP4 kv{ۚ.ڼW׌.{y;ճ]6yjҍӰ\ӉDӁ+~:Ӕ?ӴmҔ%|%ӅEԺfD~y!'ׁأDxP8(ܸ"ݱ'ޯ3߹Gh1b.r f_ dz"I$U 1jYbB7&we = + S /{=xBsEd}e B !!]"!$#"#I#$#%$%%9&%&&%'&'&'@'#('c('('((( (($(((('x('?(''Y'' 'B'&&>&Z&%%>%?%$$$#`#=#""!!,! [  ~*//"}W6^(gi(! U <  sB":0??J[j'LvG]8XIu ' + -   Y&hOm" rg/hL2q)Lo&=]n xvil\QOD?46& 7I%wKr0r[^Q x&Rt"l1 dL@FPj>w/~Y2  xCG>J u9 X+xcM=q2e&V#NGA"A#=-D/C>LBNPW\[`dnctmpw|z}y w n f U ~ L n0W Dd}+F l"*wt;*E&,_DI} 2]pwgDa[{ CCYCN! 7~$m>pKz(4 ; D  D  E  B  92' zpjeabgn}@"qNZHQM"3Gs&QkG+*NrZ%u@2  }/-ou'<SpG%j J. kTv0R/Gb y+0vq%' xk7YAf/*{V*y:Y16WBZ09VN6t%\5Mp2Ma x xyoqegScMW9O5J&C#>@<@GL%Y1jB|Tn Q:mODQV$55-\cVO{fopE |MRA{jyLMNYS h8s_M|=l4\(Q'GA$88#6$4'6050735:7;8F7A:L5E5I,D*@  6  0    dBT&hy*2;4|n % ZNZ,d)CVWS=$TB~',WIc@Kwr!j["R8] " 3  2 6  65-+ ';Z:xY0gX?6Q_2D0NK}#CNeJ908DcBv0s_&% tu;4%1S_.\7yS9~r d y X r C [ 0 I  0 ct-?z4872map^a,n _$RloK"\`wMFeI[)4N>&cN|#:`s9 Q g t ~wuuu{'H6u`NF3:Qb8V=jfJo'W1 |(FkO$p;, s|jV[ [p*a3}P'hUDw5d+W"I>5. $ $    q Z i6J&Wa)HJ;7lZ|clJ@a=~-[ #6;7%a,y9_7;[K\:? k`VB >j!Eq ! % 3  4:<:"9$6#3&,"*%%'&%(()0.>$@%P8^?lUf' R?p@73/bvPqa%>z3l4yf__nI JW@yft;72;X!o4bAZ< {vuykpljdcbZ]QTDQ=C*<"*    gz?J#t|0:SSA.gNqMY' w<z O =Vbd]F*UE'3ZQkNY-,=.}V7k.Lh ' z C Y j | *C+]Cj.^Q,#yu*-mxf~Hd`+sO7'$,T26hHN@h vS#]e}LGhNb;A`^E>}.TDk   % /;A-G2M@I>TKJLWRP[Z^VfYj^qa|gsw&V:ra K=}lq)3nr04vGk9$wlo wCgH"o0)`zT hB57?l+t9kL(X =wm~alZcQVDE<:4,'"!  ` n B N  + go)1gdj] 7%M.H~=&iLw )?QOM9c6BdCDlWvL]$'2z)t?y.a ?  Y * q ? [ p  0=/P8:4642705,7(3';#6'A&B0P0UDjJq_s*\FD<6C eyVti2?=i; nbhpDJP?v~c n63|,5Wh0W8zP5qtizixel[cZ[TROKK?D7<)0$   u P_'8 Ua$7;%I.N,7 t] c-d%@FQC2s=}2q%HE[BI!!9)xQ9j1Qm 1 L b z *6S6hIm# TBy ZOXZ$2&>EvFV'x\OHP`{-XH"q;5yw3-oov0{lR5_D *~lo_`MJ;8,  v r V S ) $   gd0'_UiQ8P$K)H8-f.UvuX,k*|-Axz* %aj!w!pYC8c L s 8 G[7hHw[jz 68OIjiRL<>#w8O1 6&Z_Lo0cA-5MuKf zo@I&B*XE)}'|(~0^ j/]5mB |hNj=S&8# {se]HC*'   t x J K   uu30WT UAl~!tKGU6qAd-$rI"}CfPWz#hs1544{?x8h  % P  i 7 ` z (7(?=\O]eys;:YX7:ss5;)5Qm8Y8eW8CKM) .\RS5_b:L2TAxlXZg(q2o9} Lk@v-S2talFS15j_H?  m n < 3F:XQF0qT[vHPy _Z&Q'330e9u3t"5gj| pxFIUQ;{+l! S  | ? j &BZ4nRk",7BPWko --ZR#d`567Cob|k)5pC `9jbWaj7aBd&V]=ZAuc>;DEq3u7`7e=`{K`%;!qgWP8( |r O M $  Z QKFWE2S4\3Kv6cAt&CU\^N>`7FaELugg~N\pwib.f' V $ H  q B b+H4VNtf&0DHhl4/bc UP/;BO&},*Zl^T!jSJHSd~J |IV8npMaEjW)&mm4:~FJ`8\8xWd;@pfRG& | J L   l k..RMPEy 35P=FW%C\joh[>"K~8}%1b[cPf'8AE!j)a  C  y D t 6T8p_z ,0AK\jt 0.YT~ IC|zFF2j}"wYWp ZH~Bb i@KVmKKsc^v=SiiY\$ c ! a + T & |Uz&E2^Qq *3DZiz!JP|}#\`-3~,6Ui8U2[L|)jCmD#sqy4O#_0;+ekE^Cn\+q m9z5wzbS>-}nGA  n j - & _X nfQ<{aoek=5H%kCr"9JMN@*| O ~/R>BsbkTn'??J$k/ n  S $ [  /X?xf9<Z^{9AbhKK9>"ks0|Nh=_Coe8FK sAgUNNYoD~RN@ai:U,Y:t\L8E8i*`vFk ;Yw4FvtGK& |dH4 } Q @   ^ T   s6&u+cFb eN[m&k>v7Tgxus^D#S:09nfwkI]kybj - k 3 t  9  lEu *QCoj(BLZsFLwyRO]dV_"yTjJiYZp(u@ yQ1 0Q zBwmWit8S$O)`C&nlQt/ag+OWn#9 w~JL'$nYF+~\N& w B ? u 6 - H:~5bG{ZUl6AXI8q@o'!b,Zr{#'n^y&{.e:SE a. w B 1  a ? v )P=|i 59[X)>N`}9Gp}-cnTiKvIya6.,H v>#hSLJUf~>j>kux;N@E!cG88r%VE^&J[y/<~VT%&zeN?l]2  { n 8 ' ^ N dPB)iJ|UxH[!$;!f Cq -LgkulbE*e2Qj^V~%5|*Wsv6n5 ?  x Q #  W:q 8/^Z&5GWt;Ejv CEwCF&4~2FoY~\{NU ^{@ aI965IU|,bD-,eD~ \oL[5^R#/Gp&~u]@ v I `%XGMRb{  7!U!#w#4&%U('*),,.).1M0+3h2K54f76x98;:=nA@UC`B%E.DFEHGRJKIKJrMeLNMIP6OQPRQTRUSVTVUWVfXFWXWY]XYX8ZYnZQYZpYZuYZfY[Z:YZXYXCY$XXWXV=;:8754H2{1. .+]*5'&3#" BB D I]|[hFNxه3P,+G%Ńrܽgd ڲ˯> 5ĢࣕӝZsٗ󘃖DT'0#*<>rB|AEDHGwKkJ>N1MPOeSLRUTXV)Z Y0\ [ ^\_^taT`cabdGcedfegfhgihjihkcj lklk mlqmmlmlmmn"m:n=m3n=m'n)mmmmlpmll m llkkjSkKjjiihhggffe^eKd dbbna`_E_#^o]N\[^ZjYKX;WVTSxR\QON2M#LfJ\IrGoFdDnC?AE@==:97A632//,a+9('M$#U I5 _M<cN=DS|۰MӰa˥tD$־ǿӻ̼6;$ƯmD`A`TtܢQkٟEj(B;0M]sgzCQ FWi Qi<# 8Bce!ϦtY[|zΰu _b5/*/O0ɂUΗ(ҜJٽ٪AHni.~>yIK t4(zt  ##&l&]*)-,0-04B37I6 :49< ^BpADCnGpFIHLK?N1MIP:ODR.QTSUTWkVYWZgY[Z][F^$]V_6^Q`1_-a`b`baKc.bcbEd+cdcdcec5ed:ed%e ddcdcedDccbocLbbab`Qa)`n`L_q_I^b^:]0] \[ZZfYYWWZVUT TR0RQ1PONMKJIHCG9FDC4B1A?><;98663300-,b*)'m&##B PDW{> 3s=I$bH0[v1_,ӽϼ!X.ȞąwmsyS]EUXk}ܯ5 ?Ĩ橄`~JrWxuѣaݠt@ߟ۠Р٠֟ /Yà7`ˡp,J&)Feƪ/IծYh!۲䳵ĵ¹!odQ6ƶ~O1δ~Ҭ]դAإ=۲>Mi"\*]pZ[9  q>d *0! &$# 'x&)C),,a/.2E1437D698;:>3=E@^?^BkA^DkCKFREH$GIHKJMLNMPNRQ>PR~QSRTSUTVUkWPV!XWXWRY6XYX,ZYqZSYZYZYZYZYZ|YZZ=YZXYxXYWuXZWWVVUVU'U TTSRQQPqP^O OMMLKJTJKIHGFEDCBA@?>=_K%9 1'.5If L~R׮|їs(͵yi7?!?0wj$ %)Ŵе{JZ*>$85LUlIjȫWy#ª㫝ͫ0N"-Oϯѳ(jڷUdﻍFD @̓PД2ӚJؙ7)ݿPe7I_}Oh#0vV 9  PI,*7;! ##&{%m('**,?, /k.B1}0N32L5{477a6978:9<;9>L=?>MAZ@BADCdE`DFEGFHGIHJIKwJGL1KLKMvLNLNnMNM*ONZOCNxO]N~OlNpOTNKO9NOMNMTNBMMLFM0LLKKJ KJ+JI+I!H"HGFEEDD}C!C(BA@3@A?>=<ªn^C5/&)"85^[߸6<,9ζѷx=H#2U^`h۷sxѹӺwudhc`xrdWIJ>'ǣT/ˤu_.*ӡՄ|wہݑ I-b@.0uBg MVXE BM&Wl!!##y%$T'&))(*F*,+X.-/9/103@2y435 537_6{8798:9;;<<=<>=?>_@j?A@A@AB@ABA(C!BwCyBCBCB DCDCDCCBCByCrBCBBA-B*AA@@?6@:?m?q>>==<<;;:t:9F9\88!765Z5~433u2100U/.-,,E+C*)~('&&$0$"T" c ~vnkM9`5  nT5*p?c@% -&.o-W.-m.-.-y.-n.-L.-.l--.--,?-,,',e,++8+Y+****x)f)((('C''}&:&%K%$a$#e#"a"!X! A 'f4g/>EQ9q8 q 0  M5k\4B\r%NhFl%i,k5hN0$ 0FlޗB݃ QۨsnGaLׁ؁"3֌ׯOׂ#_QFSe׋'׺d֓>ד1׏]lSvsۦۺKf#ݱuް9߆ _C/%7Hi%G-wZ9>n}7U6@vw+dN ,  O $ y E bzgH c.{<g8P  a n! !h!m"!"J"D#"###T#*$#m$#$#$!$$;$$J$$J$$B$$*$$ $t$#>$##^###J#""S"v"!!m!y! ] X -e-u&a?d   z e v O W3;unPX5=# yrhifow-U#\5c%vWUJU`zCw/wK wht~>l!e)z]30  )"OS=Lav9Q*nEmVBh.Z =5xcy S a 9 P & 9   Sk(7{AH[`G>p^anHAhG=q!M^qvp]I p16^',L?R17bZ `I"W;k"CW  p 7  J ^ l }+I,_J~d U=uSAHFziy0E|)a(wR4 7P}/`%tD cbM]Ss y GSS f!S!s9{Z>y%YB%|yqjm_bT\BL8E%3&   s Se)Ff{%;ZbSW.mV(uH7e+4?;-tIX@o8/A$.c^e U-nUCa%| H \ {    )/='I3ZCkQi~D,p\[K=;__=L8UQ{1n(c3mVLIRc~ O H [;{sajb{|GRL\yB \)\6|Y:pl[sZiNZCL:@21+(  s T d 0 C"Xf1Zbho B@`U_PE.g8Et,RmiL#h.m%Ho$#dRiI9|W 8\}5 V t  $#.* 4(10;9;A?GCSFZMdSr^{fuCdJm+jL;)I? !3Tx4h&uN)|}}3R~.n5V4 o~kPZLWu<MqN#d>zj[sOdAR;H-1#.  j { L c 6 F )  sE\$Zm!u%(gc zmI(};@m0JSSK6vA G+Jnww yKFaO4v\<cF d   , 2A$@/L9MBTLWS`_dhiql}vy0V;uZ/fS2#zh'[^HRQezBo(i2gM@9?Me,bd-YB)"73eh\jDUrL_5ykTrH[7J*6'wdtI` 2 E  -  `}5LVj'*6wE<UBM.)Y1}<x-NwbCXUv"LUrf|\f68JC*n] =e(S v  4=&M8TDcUd[smqp} 6Q4oWu <n^,qeif/6  CW5c&yD'pnn|6cI \zk?E$=+TFz!pv$M`{J$]4r\zHf6L&;%jrT\@H /  [ m0@[j-EK;=no{{VCf<z.f1FTWRA*~WhI{"@9O0@ry')vVF-VG k  "  9@-Y@_Pmbxm~~! 8O6iOmH+}g+qb]Q|\kUnl3I:q9 oPC5=C_xG {HV= s~VnSzjH=?>l)r,\1c 9m\p=Z/=+rpV`CF+7  u M ] $ 7 _g-PVVV+C3H,3 jVj7pD_za>h4[ FS|$qGNWX1s+k> l2 c   0I-RDhQqjs-E3[Iwa N<{h&f[DAZ_*;4&IP~'DKY0:\UG+PL2 3L7~oo!mPUyJy#R%xauE^/?* ~xqjMQA>%  j w A O  & Ta#TY da E9iXv[lDBkS MJv -4/# zPP Qe =5cDh8Nt%x,{ K H#S"z G w  1B/ZDjZ|p*"A5TLqb}5%[H}*"h_>,sh$gaOW uTkOrg2H=o5 sRC;8IXyA zGT< ppUdTv i<=9=`i0O&`4 ^Jk1P9 ukeZLE51 d g 9 B   zGI:4=5}o ) .v{E9BPBXjlk\G%RF5Erq}n |HHZT<x+l%O}7 l 8 S(`?yUi| ",.@BUTmj)UM}w!\T.05=m`wg$-d:KmR;419Pd?t,mJq_;?&9.OK|~in#AN_/p?rQ{7[:!fePS=4'ok F P & %  n v@E IL`b SKszVKjC9nH\svwlV<W#i#>w<.F%6 i o ~%tX N @q1 _ + F  a<{Wu #%88BP[cg}%G@mc$ _Tfb`d(lh~J_ OI{[B82=Ge#[_oY($ s n(XQHIb%l*mB| EiGn"K$}dnMR.1u\U75  k j 8 >  VX881'lXw{c_0m,u:s5VdtpkW=u>v.o&SPt`xU_-/KI1y-o-_" S  /  S ' sPj12KB[cvzB<^\~1&h]XL61BJ s~7G|(P^-y\B524BXy+ZGo5'clKiP~ lD;<7\]h5h7U5N/ryRY97~k`B8  u q E G   j m-)YZ f` G:n\g}X`1*~CdDs(>V_`^H3Sv2O49i]hXh.6>K+p-r& \ & \  7  `@b 3'IKid "+FJfm 31jf DGglhv1D+ )?gz'|4l&o- e 1 n 0 Y 5 e7&PLvn -q 5Sn{{fL$a-C[ OJqz#mLhl%~f!q8 p A z  J " W %K/oc~8&@I+%|3WF;)9~-dVj5^!jBU)pp=IriG2 p^=)   O ?   v7&G1w-Z;wP}Om7DaW S1h.Qn}"s]7w>_foL?nQxKm0L\o^o9 D / g @ n &Q8z_;6_X(EUi~=Ox"/bp3C(AHa=1 9#YUBUh1fB 4VM0{d"y|DV+J(T4s UF3<-VGRA xA]1Yg.7 }yME#fK2 j U .  b M   k)yh S4sU ]Rh.6P:+i4` !.10 [.`!r'9vz E(R+H-Je\hB O  E  | Q J.y^"MDto:Ihy7Gdt GQ RZ?OTd6vv0&]cZ v3b4|hROJWc:`0 pRRO wUpDsL mS;C/W BEl-\x#<[k+3mn@5 lP:|W=   S > v b%{e `;c{'|"f s:7@&k Bp -NbsxvlV=|Y{?XIMy1 /j9VUeC  S  S & b  3jJ}"RDyp%*S\{.K^y-\i(_iAUIZ "uVwMsXyUecP\F.(&+=Rr0f;|,u98m\_-z `;@(J7p1\u:[fu0? ql97 yfH0vL5 ~ N > v h ,  p,qX 8T.]0O(JCG!]+Wu #39:3uJ&Tx +x'&eWnpZ z2MM gI \ ! Z 1 t  I-aD3ph%'RX6Nd~7^j'Yh7H3J Vo+L=F3iaG]k+]7 ukbnu8c1m[IW^6%y)wBkLJ-J0n#P b+GzK] {zFM }lD8oR5 U >  h 5 ! ~@$l)\<~W _Vo4>WI=wM{ +IW`eYN4W GL\?1nPUEm$@EdJ ] # n > | $ ^Av5aU!MO 8Kj|%;Vn'Wf*>|"3w6R$u b c zMN@Vr=sR4  +N k0fB(r$%FXFCT,}VT:N2g'O\};h9H ^b)+UG{`A$eI | C . W = H+^8D= {!NUXI,g1\zpHG fz'slD+^6`*Po,3R9 T  e 9 * cB C2tc ;<kk 2Bcu4Qn Nb"4l%g}7Qs5Z.[;r]  o S  aF<b:wM|Jk0De[ZA|Lu8IhivkbN/o Dr-EACv7>}.X z!w@( ~ G  ^ 3 { *  fIN;w%OQ ,We 'GeDU(^rPh b|-J,&{1SL5>Pw<eM=77?RmCyS?3@Fw `\"kK"ZmJx+Q Tw/Qh$in*.zyF: _EdB e J "  d & w ZY6`y/}-mET l \ Q0c+Gi{$('kR0l7U_fSE}_fY<^ d( p 7  Y / w *  mL" _J<3nmHMz 6[t 5RtPh;Z Ed .tQEtK i>91I^-]84V!W1jv{k8u[VY qFh3^1m<XP)9jFD7wG5i?hE%  T /  O , l/ ^4|P[ Rv5Kd ]\EP}AXr~nV7pB l.?=4e4 @7_-O E  k @  d % fJ7l^*d`Q\?T}:X 8Qy>t!CBg=%wx-QK:HUwCcJ3.#07NhJz\J?EL !z ` Wa;z jBG~HpYy,8Uny59rm.)q].fL$pK$  f 2  c + n N }7Y0j8f.O~%CB@)e6c&E\kusq^J)d5k383{f ~.<w2Y%Q = d 6 `  _B&eUXT AL.Gj)Nk&Bm*`~.q3U+xdd!x E<07Ln6}[D0%#)5KgAy`@=HV-w^X%_D|r BQ%Lx_z1^9 q E  m E  M * Wv1;6kAWa]K&e Bdz*421c L"c-EVWD1uOYN6jw: 7 ] 5  ` "  mM?'mA8z99v2Eo 4Ov(Js5e9q2T(i R}H|VwSKGXs? m@y8].g>+}$,z=[B7w<Q|MT$GnSnj"8KV =9mf.[B`5vJ  p H  U - d#EX*\$Jx(DNSC%`5b3;=;,wS}AfomfF%f5t3m Rb* ' S 3 a .  tXK="]Y"!bi&1iy-Iv Gr 0c4b"GZ9n*a.kEmUY_x4lF}bK=87CQ j*O} C&r`f)uFl)^`$q<0`c0PrHl`q$z3;[Y~B3eV!mA{N) { M  b :  n 3  W+p=v?p4Xx);@5{ j.[(JcwxhL.].ew$-8+~a=RRy;R  | L 6 e 1  b!fQ<0z  PL!_m+>rLg4X.]G} Jy#V D F\5ndh~?a'cM<54:L_"U;o RRN_.zY LJ \D{xBYJiRkn{)AE][u4$Q7[=W 1  w A  x H  t > X_V?_p$&{)vP8g<Rjwy"}"ob I)If(<:5!jI^]N/ `  ` 4 ! ~ O -  _"nVS=(!pjCJ)as C[/Q*S :p:oH1q'r3R=.}'2Ds>lA' -SuORF2?Mb=-p+k8uV JWAbhR=kq*u=dLGOi' LY|8W/F3;;DMK]Q mWvZoJ^ 0 f 3  a - G  VVKv1J] deM<v3a-I_tutpY@rL[h!4%~eIb&oh R [ - ' a A E3E,-)zr`dFR'C 8p>o(UL~ U9s*j.p@eH:ADf)K|^L;98BRg*V:s MM?Y!b9g]_p0"PTz.Ms-hx!jrnt%{r( u.q1b+ oM  L  | F h - } BE{9e=Mb^U;m"U 1Jgx!//1{"jN4 Xr/TOU8h%F SR v E T ' $ _ I & Z=[FMDA<,/x)fx "_{ -c=u;j;uXE? N'p VG:JXPsI#  ?f?w@2}!r~%?Z(j,b 8  g / P  i - q1k'S0RW eWKY7b.5@<2' }Nr 6np q&hGg*y=wA i 5 L  # f N 0 eMm`ia  ea Y]J] B[ Ii#`#MQ.iZWa4z^H@;_g+oFwWB/'%*7G b/O5%viIMPf3tSB=G*[M~~7Qp>O.8&-zz!tjm\bEG'^"  P  s D  W  ` & b Q s+E\`cM=z :p<Zwvb8i;w9@YIK(}M_(c0 [ , D   a V 5 sZ,}x,)-1#1$5w 7u%K8c*[*_?u(`]#i9_J5:?es? fL1 rjdir5Ij4iZ',q}0r1Y6j$RH$L:^\z5Td /Bt"gj]VOFD0t/sTI'y R  w 5  K  V ! XI z&PZvekD4mFf8E[`a]R<q(W,~KUzt/ f'_0I Q J  7 {  Y U - |c6"D8RFIMQY Qd^w6v;^.o J}2i&a(j=~dC5'k[9Lj"3doPW>?~*$sg]H> vTA  e 5  N \  [  Ns3Eml(~lk5q$T:R`nhn\Q1xMZ OF[:q;d*}7 3  & r } T S , dC(TG _X ki!qv"/5CLe)vHk D,{!s%v7WG'/qBGR&tJ.1R,yRG>&qx4zGmTGEyN} c$CDly3Pm7F|)_eGE/&m_ =#kN  q ' A  M  N  A y*Vf!#~0!i[%^:^+0+&g<a .^ju+sWBa& p ) o  a  y H Q & #dK1 eQzo0+=AJV^s7~Dh<~Z>p2k2q@^7'pt lEXkD%u[D;,42GQs=b?,uJQF\%d:n]W]n(CEew,E[(3ikBF'$i^<*vZ4Y 8 t L Z ]  R n9Iv~:0<aT Bf;\peN(U*i{-8QPJ3b9  N  R K p ; O ! #aW4y[5I>`]x~3@Pc/yIr&R >2y/w9S: qb qn!Rq1fQ6'#yy'{.AYxK)yd7,{(s(D[;s(i"k%x1!HDgr 9OrSQ*'h\B2yaI)tKh$  <  J  Q  H  ~ . \t*:L::W ;y Dd{(>IOPI=y(a>l5Dsn#h# c@a# u < | E t B _ 4 B  hT? lB4`\~:BUj4Hh>`Jx=o:sB]5"kd Vml+e.R,rhfjt .T0`%k^IS Sk8}V?62;'NGim0Fft:Bvr D@|q O9|b>a7y J S Q I u 2 W }'CBO>1x C{%O}3GT^\ZK9"\*J}:=L>;p> Y  g 0 d 3 T ) 9  iUD%yQEuu4=^j3}Fd6x N})] C9;G^6i YYUk,|>|N$xY8$0Jk>m"ZP'0t4p2`8pTB=D1QKdm)6Xc&#ZV&ZKz!Q2wYs$:  H  G  < ~ ) b  v8?XNU8z0m.[5_ztJw(BLR tm#uVK" t A  P  O C  / f[D)cT) ON|DS!sB^:lQ~Cv@yF^2eWGPYw:WqN/paPSK\ ax9V* ]G\YM^^*Y|ktr5 Z?n,$dfBR<_&`1jQ~DnBgHjTtk'7E]g>;h_"F1dKbns#  x " s  ^  s > K  y  x:.Aj!i"\Ao0BNQUJ?+\r&>;:ZFN( \ !  G  c  p  r  l}[`F@,w^T.-nrAK#~Wm5O6&ur%z9XK"#f u&jK WmCjK3 (6V%rNt*]U(.ny)hFO~&_ QIwHsRyc {&6HNjj?7`Wt0D!S.]8_ 6 V , B  ) ^ g , + F4?_T BeFg  lHk: NN u.d+  j 8 c 3 N  ` g  e}]iNK6+sfIE!jtBO- lSo?^4Z0\8hN m77p yU w%i'\$e9nJ2{kVSKR Wk/xJg-`TY^>S Hk8qE~"h ZV|W|b t+1CS]su8/TFj]lw~}  q  Z l : H  {  w > 2 J,6} B8n=a~.H\gpqlbT;r$J+JZFCU> F  { A  b 9 x Q  ^  d{akUTA8,ee@G + l|Jd+M8+|"u"x, B(bM "_kZ ~/x5v@Z)nYSJOTcu;#cMBC(d| -`R$V-|i]{\vbwk} z !%7k^~XrXn^plx~ 2.D=ZMq]i t}!{o  [ m > K     I =  [ FT)g$lO {6a%054)  rM[/{xA5fN m D  S  A  ] : q Q adzbo[]OI=/+kxR_8H!6&}yy!5(QJy}Pa?`U JR|R1tY?0!#8He?b,"c\!*cpIl"WL!R-wbVvTpVo_sizv !#3.C;RD`KhNiKdB[6H + l  p @ 8  ^ P  k DSZW,Uy6SnlNs2M{DC|t3 t @  ] / f ( L ! e = uQ}^zaq^hW\MI>3, ~hxWjHa?Y:XitNkK{1n,z<L!}^NA6}6r9>S^N:ys$*dm9R7ZD| Iv L,zh`v]oZo_ojpvx   ty]_ > :  v l ? * Z 6  R  g-m VIw(>Zd#w/u1}8w-i)`D1 ~Ma'mk)  E 0 H # ] ) Z ! >  U-cAjLlPhO`MQGD?41() | %6,SHsn4Am'e 3m+a!e*W+s R2+CfKkH> DR&E}+SDPv,H,th|^p\m_hcfkhkekcj_m\kYcOV=E', s s K B   n @ $ u B  h 1 w 4 x=sBk0Ro  cH]1YV L ; Z 9  W & X + ^  5 I*Y>_LcQbS\RUMNLKMDF@C;E9N@[Nma x 11[`->r9s%RK Q#e0 x[A,we] XZ`&l1{Hb;+q[RW&2p5lKC}Oy.U3{m}`kXbV[RUOJODJ:D.;#-lsQR)%  [ T " }  h /  i / | H N  ?z1j"AXu2>QQ^RU>n5_;fm14 y D / j K  n @  X ! U 1 ~\ + ?(M;VH_Wc`edefjlhpjtp~x '">>Y_(Qd.Q~+] EBy;e?vP,$ K0lX/'bh#4n Ps(V@n 8sIn(G 'lZmM_AT8G-5&'zrj`QG1% pk?7  q R + ] . ~ N  g # b ' d  : h,Mu"230.]s5K  m o 3 , h T   \ . | P & h ' V2|] .C*N@XRballvx"0/GD`a}9CkxC\1X =p-co6n@ c@}(dOB4/.1< G%[3qUr RS8BqKmBj#B{@i 1xMd)> }fuQ\>G,1zneYO?3 lZA.tZ4  o L $ } H  i ( n D  , ^ - b  >[%{@XhwufUy9X 9  N Q   c Q   s <  v F  [ 0 m5 c;b0I4^Kpbu '%44CJR`f{ ):Qa4Vq;`8k T RLo=wS' ~k_[T]`p+YA{oLJOc,K}#R 4n3V g?Z3nzP\6@!& |vc^JB0%k[C2~dH+ iL ! w F o :  R L  Y + Y ( L w  ! 4 ANOSME 7$  v R W " ( O B  o C % ^ -  | U " bA|9cKr :+UIqf& >AU]mv#6D[i1Nf,Iu;t$GFs)JwB^AuR >"#8O0kVs3*ffQb#;k +[5bQ{>Pp&AiIZ(: }[]=:lbH:oR:}\:xM % t @  c ! f 8 v & ] ) ^ E  k * Q g t [ w = Y  /  j q 9 6 u A , f N  v L ' u9vZH+t_/"NHnj85WVoz +@Re(@Xv4UwBoFy3^_#PyFL)r[M=s7m2g6j8pHvNiz 3*ZZ!3]n;cDo5r%@D[k:N wLX&0 kpAH"h]<2uhA1 p]*rC%x:  q +  o N =  } U  V * Q   0 I  ] ' l < w C ~ K L } J y C l : _ ' G  5  o } M L   z k 8 - s X + i @  jDZ4u.gFy,VF}o 1(SMxs 2/SWx8Jh} E\"@p ?y'SKu1MAX3jF,/ F0nR| A>xy%4iw 7R!FoPq#\ M   a T (  o W .  { X 1  g5g+ rTK5n K<zl;2e^&$RQ|FMx#M^2I{ 7v)H@Zj4TKz!D aHt1bNC7436 ?I(Z:nQn /-`Z0Ek#|Hn 9uL}#P0 xvdc`bkt8+`R EIv,Bn=c IuNkUs(k-CMe&u p  /  [ - X w   3 >  Q  S ! \ % [ % X ! O  C 2 "  s M Y ! - ^ X #  l X &  zO/b;h4^`GA&{cK=uRM('_d6>y(`tTjYw )s"GY@h6d3_>rT='#AO9vYy-(]W3>n{4Gx*QuBn5m!B}6NPh&t5@T]vu92TGh[ v^v\dEDi R , e  : q A o  H  d 4 K l | q V t : N  -  t z F E  d \ &  bD[8kD_C S6;#{hRI1%ni GI'3hwXdGb Ji\|4~6] y7i/g9jGiV@y3o+c]!Y\'^*k=uG_t )VCxr U ; v I z  F  q ? e & @  Y  k 4 | ? P W _ \ a R P } > d . S  9  S [ - 0 b S $  qS' xX1 q?$~A,@,{'oeVU6;{!&cmJV;M.I1I:e^}e~&Jc?r"UoMgQ_ \fgkx{))98G=QDQ=P35h]<*bB} M $ U 6 \ /  O s    - 2  3 4 / $   t T a 0 9  R H   u]3lI-kM}`'o+{l%rh `ZLM;A'3s+m~&g,l"=5[cI]'P|VP)gH}2bP<-# (7H+\FsZ8:op-@f{8UyJj0 2   sY9_@rA(ZG iZ ncoh fb  ][VXLVETJ\Qfe}5@Yv.VGr7 }Z.] 4~e^LFrAu=lDuEwR]m:1_Z 8Cn{)Nf,MqF].Dy-nwbh ch \[]WZOWET<=#t$nSEkP(b:pEs 3  W ( T u  0  L  W ' h 6 p @ w F { K | K w G p @ g 8 W ' G  2   q u M M & jX4iK-]Ds6(RD`T ea gedgiofu!j&v!:9RXu +=_!|Ce0h5^/dDl%S 5 1$K=5-#p" hYQ7,rW@`:tOL#wR ! J  i > \ x        h p I V ( )   ]M*qY:wf;fV z</TKd\ inrz (|+90A=YOi!m"ANm,@e/U}IZ/\2|bHy3a"NB 1.$"%%0 8E+XCgVr>Aaq0Oh|(Sd Rf#(fvEH(*xw]ZNE:/z$ufbK@)vkM8yY<zV$`9o&M+}T| &  K  \ 8 x P ` s  } s ] n N Y 1 >    rdD8 rV7vJ1 vC0n`%G:]] qo"!39HMXch{$2=Q d|6Pk .Xw8u@e7e9g?#~nbWTMzOxSYcr}=:][*6Uh#DXv 0Es ;G{SY,6x|Z\C:!qk SE5$p\B.uZ<!zY3kOBvQ%P&sLq ! /  D  Q & Y 7 g : d A n E g B d @ ` 8 O - G $ 3  !  vtSJ/$kU:XE!aQ$RJz7/VU st(,BDbf v*4JYo$:\m,Oi1d-Jb}7X %rRv5X<$2(HEc`4Fcp9Rq1Vi"Rd'cn4=zRX14}{ ZQ1)}tWL)pa8#|f;t^#]Ap&U0a!@b?Wv        koQQ1/ tdK3jR,~q@,~s<2nh)!OP xu*/IQsx"1JUm~(8Ui&Kc $Rn3u>\)Yv/O (rUtCd/M!@2 *!&'9&>2SJbZxw(0Lat "?Rm"FX2Dx4BwAGV_+-swGC_a0)zpB5zD7s4&r^H2wf0ZAi % C*W=tWd{yfnU]=?&) cO8+kW4}WFbT'[PJIuo5.XW<@fn$'R[ BOEV _s%3Xl+@[v7N1ve~^{WoOmQkOmXpYwim %.JNcw-@Tq9Nq*SdGX MU%X\&*lr59}|GJRSSR NA7&e]2%cT2!XG{f -?+M5XB`KiOgXpPhWgH\JS5D27"j]F:|aJ7 dL/jC0xF5 y>/th5*gVTK D=yv7:wy5:FL mq4CyIW 1 jKd4F0  %>:VUrp $W+B4 )!-:/H>\Vph3=Xd~ "8Pg/EdyDUy+?iz3bo&[i$X[RTKH71npMHzM@ym(N@t`1M/dLv]nl|_kCO3<yrVS1'xVD(y^A' ]I$ p]-vd4*wk3-yt85{y==EJ Yb$1KY'Zi(EdCZ#@yk]|RtKiEg@cCbDgHiRsX}gv6/PVuu(8Vf} 7Rg!6[n?Ks&3gr"Y]KI=9t{($fb IBzsPBw</kX 3P<v_|+;N3X<gImOxZwZ~_z[|\rRmMa@U5E&4  |_[==qPE(hU7}eD) `J"q`1"}i<6IDXY gp78V])Sc#1pD^1lRm5V:' /!D8aUxq=Bdj 6Ecy $>Yu)L_1Z&D5'  *8'G9\Omcz)/TUsy 7B^s+Gaw *Bbs=Jm~ L]/5fhGF}z#VO'XFD5r[C!^Ik <T3nI^qr_mHT4< ^[B4udL4!xc@3td73 ][+$~~EFhm03QW*yCVyGY2e~8Q,xZr5W3r\~Vq@b?Z0Q-J*H&D*F+H4P9QBbOd[|l "'@L]d} 1GZo 2O[}AKu FOy{!ON!$ZZ/.b_5.c^'$ZL~ 9)bR| C,cUl  7 I0Y?kQsZiox{|}qn`tVdFV7A$1rqVQ0, zmS=%xbF)fR1#`W(RJxDEnr|sA4na,UEq*Q9xc'B+`D|dx$.>D&M-R3Q7Y6U9R2N2E&@#0( |}^ZE>%|nXB- r`B3zVI(^S*![R&%ZW%)[`&*el5?T`)8bxBR3hUj8T(@,   ,>4TFhav '*KMio"0LYq3K_w.N^~ >My}::go,,][MCxo+SFi :*iKm#;W7oOey kvT]=E#%  ngMA*hO?,rhE8 |XN"^\**gm6: qwCOZg+;Um3CwTo8M2vlOoE`4Q#?8,#! %2:+H8UKf]vk;FilKHwu($YP| 6,cV;)cSy*M5v`~; V?w[u*< L0X<bKqSv^cjkrlqkes^pVaIWAH.7$%pbVJ5' qYO4)mgB<jj>9[\12|~PW!'pwHS"sIY#1g|IZ"7oZp+)&<9RVll /=J[p 8B[i#-SQw#$MNzx"LH{rQ?tj >/iVzE)aQj;N6mOh} ( 0:;$D#C*C&B)=":!3, lfUN=1"xnZM4,ywTO**``27 ^f4;bh/D at9F"tMa);{Yq:Q 5yj}Pm?S,I/ &(&97GK]]mu )0EWgk)5VUu~29`Y!%OIvt?8kc 3([Rt ?4bV{. PAl_z,@/XHiWq~zue^RK;1( vhTI5) g[<;{WU2-ed=>ryOM&]h59|Wb4AwUd=M .}uZnSaBV5D+= 2*&    &)"4*:4GESM^apn~ '3@R]kv %-DLkr>Cdf &NFmp2-WR~vA5a[>0^Ov;*YMub /!D3SDhUsbtwrhe\QHC8-#~liME10 omKL*)sxNL&-hn=A#Z`1:uRV+:\j=H( o}V^75""~od]I>0(uwcT65 hiNG$"lnIF"$pqGH#(puNS,. gpIN*5 oxYeBK09#}&,5DNYbnu$*BD[f|:>Z^}(,MKko;>ab 11OMst /0QIil)"?;WTnf~'#0&:4<0B<E:C?H?C;D9;5<//*,   wf^UG:2! ~u`YB:%|`YB;moNE((}uY_93plHO.. q{Z[9C!$|cpRZ?J-7'  "*34AKVXdp| (2DHZf} */KMgm 1,HQpl54QPwv85YUvu  *%ICa]}w%#>2MKe\uo    urj\LL@3$"}s[ZG<#  olTM.. ljMJ*( feGH&#eiLI+/ eiOQ28 x{fpW\IP:C19$*%   %(10;@HLWXajtw %08KSbi{ !(;AY`y|.3OSlo&-KEek"<?^Xw{"<5RSqi 1*A:TQbYtnw~{uobZWNB;2%! vs^SE@(  zb^CB(!npII0. vxQT9=chIL+0s|^cFP/5% zrxcn[fOZGSBJ;E4>5>+62:,8062@4;:QXa_uw12JH`bzv )'C@]\vt,+JB_av-)DDaYrs $ 66JF\\ok} !'(-%)/6.,/50..1.)**%" vveeTO?B+)~xcfKL41idKN60~}edKK1.jnZU??*)|xjlYZOM9=4/&  !$+2::GPWXbmww #54BMa`px43DMccw ''9>XVkr.+AHWWst $"44FBTXfavurof`RPF=2/   zfgVO:9)   wbhSB-5 po\XA>*'sl^bKE55  xzih^^NRFE9>2/&*!!  #,+.7@=@KSUY`hosz#&4AJO[frx+8=LVfnz!5>JP_j{*3=@T]fk~!-0=:KOXUfgrp}zxnn`_RRGB66(&slbYKC70!ynf[J@50 ufbUI76' xvhfQPC@++ xnrekZaRXKU@F?I6=4@07-:-3'8.3(719.;4@9CLUccvx(1/A8LEUNdYibvm{r{zt~judiWbNTEI5?.0$qlZ[NG21'   xj`VL=5,  tp`XIE5/! pr_aMO=?,. sq~iwer^n\lYhWjTeWhUj]hWqap`vh{kuw  '#1*GAQLf_ur,0@@SXflz~"+7AKSaiuy#'73IG[Wmhx*0$>)D8Q9VG]KgPjYrXucw`~g|hg~igf}d}bs_wXjTiLaJ\>O9J-;(8&!w|kkTYIG26$!wqhXPI@/( |}ieQZC;)4ufoUaGR6A)9# }|&"3,C?OH`^nh{|&*2=LNXdnr%#/9HGX^nk #2*F<QIfYphw (,6 8!C.A)P6F4X9M;X:T=T;V:R:O3M5H.C+?#81)! ludcOSCB22!}whaUQB;..tw]iLT:B$3}r^vUgC\8N+B8+" (++;4ECOPa\fl{x),>;MQ]\tq 8-?9^Ld]n~ /?,L:\GhTxel !  %#%&! %   {sme_\PHBA5- $ls\_KR8<). |htZeHU6A'7!ro|WmWcAZ@M0C)90)     '#2084CELIV[c^kswx )#8L>D,;07#,#*%     !!!!+.0-37?;BFPHTW^Yjdop}v- 40H=QLeYpgz 2'<8PBZTobsq %2*;5G@QKYWh]ll{p|}rwns`bY^NOBG6;-0"|rx_fV\BE6B&*" x|jv]aMWAG2=()# {w{lrgj^cX]PTMREHCI=>8@863;1108/.04/12115348888=?C?EFLIRPWV_Zbdniqrz *&6-F>PJ_Wldwr(#8-C?SH^[oeys "'+7/==FBPOUSbbgbnrwpyzxzlyjl^hX^OVEO>C1;+2% t|loYcNU@H3:'* z|lqb`RYGE:=//#" }|{ysolqmd_hd]W`\XS\WSN\TPQZQSS[RTU`VZZd]b`ifngqnwt|y &&57?;MMWUbdqo|{)074CFMP_^fivv  '*1248AADGNQSSY^c\akmgkswssz}zx~~zzv|tsnrlnff_e\[TWMQGG=E7801$*!!|ssbgYYMP>?56%({|onfcWWOLAA85+)## }y|yzy|uur|uwsys}xwry}{|%!..;6BCOLXVddpmzz %&*1=@@HRWX_imov{ &"*,3.6:>6AEFALOOHTUWQYWZY]Z^]a[``a\_`bX]^`UYZZSUSSJPMIDG@@;<6301*("&  zxtlc^^XMGG@620(! zvung`^YRLLB=:6/,&#  "'*.3:=BGMOX[`eilw{{(,01>AFHST[]hhlq~{   "&('+-/.4436;;78A@9:EE<=DE=>BB??@@:<=;477413,,*,$!#~}pqme]_YRGGDA310+  ~yspjdb`UNQOF>==5.-*"  !'(-.7ELNMVXZ_gditws| "('+588>DGLQRX]`fhiovwy{zxrjhhd[XVRKKE>:;61*'##}yxntig`eZ[UVIQIF=D=;09/1(,"&"!    $#)*+,8537CAACNMPPX[]]ghkksr|~~ !)-/07>?@DLNOP[^]`hjlpwvx|{}yxnqiibc[ZSULMEF<@64-3'#% zzurrmddgbYYZWSNMIIGC<@@:1970)/.*"*&"&!     !%%#'&.)//2/:886D@ACKFLPTNYZYZfefeopsqy|  "()-/659=DBFIOOSU][]ajghorryzy}~x{tqmngddbZZWSNPJCCD;597,)-)  ~zzsvonfmfd^e[ZV[QQNQJLFFBF@AD<93744,,)+$%  }~z~zzwyuwttpurpkpqmjnmljjlnhgmlhikjkilklkokloqkqrspvswtxt}x|y{  &'&,(0/306686>;>>FCCDKLJJNPRQRUYVW[]][``badeddjfiikiolllumnounsqtouovqrnyqqnxqroxmpoukqlpjqglhmchcj`c_g]`[aV]VYR[NRNTHLGPEC?I=@<@5;75+4//&+&+ % }|}}z|wz~yx~z~wz~{y}z| !!'(%'/0*-55359;9:>A>?CHECDNKHHRROLTTUQVVZSYY]W_Z][b[`]e\a^g``]jac]h^e`e^g_e]e[c^c[a[bX_X_T\VZT[OVSXJPOSHNGNDLCG?F>B:@8>581:02,4)+'-#(#"#      &!$#+)*&110(4353;38;@6;;D?C=FAHBHEHBNKKBOONGRMPLUMQNUNSOVQUNTQWNSQVOUOTQUNQPUKOMSMNGONLDLIIDHGG?CDB<B?;9A;64:940332*,/,'()&"##       !"$ &'$*#,).).-2-3/51707584;8;8<;=9><><A@?=BC@>EB>BGB=CFB@CCABDCA@CBB?BAD??=B=<<A;<9=8;7:6:26270306..+3*+(.#)$(&!%"        # '"$) %$-#)'-(,(/*-+3.,,7/..6/343/841194247355854576366348402751/335.003-/-.-0(-,+%-(("*$("%&! "!"          !#$ !& %!#!(#$#'%)$&$,&#%,'&%)'(%(''$*(&$*(%$''%%'%$#$%#""!!$ "                                                     LIST@INFOISFT4Mixcraft 7.7 64-bit Version 311 (libsndfile-1.0.31)id3 JID3@?   |jTXXX)SoftwareMixcraft 7.7 64-bit Version 311sight-25.1.0/module/ui/qt/rc/sounds/info_beep.wav000066400000000000000000002447561503402212300216410ustar00rootroot00000000000000RIFFIWAVEfmt Ddata(IBnY|Qk.?FU3r1 { $ X % U &D+eJm$#>F\dp 3;]a"%\b",lz(s9T@J5n je!EdJ xf~}Hl+xU5()9X?oS`bCqK1,9V6#Nb 3h[jY]dfxD  j u3w# 2; X }#",&%(/(y+*.e-0/Q3255r87::n=5B7AxDCFEHGJILKNMPOpRTQTRU|T WU[X;WYpXZY[Z\j[Q]*\]\u^P]^] _]F_#^P_'^9_^_]^]?^]]\\[&\Z'[ ZZXXWbW6VUTTRRQPNMLVKCJHGEDBA?><+;m8743z0/,,{+''"" P } d;Nf)yQEJvջq'˵ƉkTM>XW UrJgnģEg֜5{zlr}j]؏uB&ՎΎގ"[D 80ɔ6QǘCa" $L8ŨʬίDVȸ׹wyKFD/aAəlкl١ڤ/\*\( e h5@AW"!&I&N+*/.3 377;:?>KCUBFE&JIRMDL\PKO8S"RUT}X^WZY][-_ ^a_badaced_gCfhwgihjikyjFl7klkqmjlml:n:m~n}mnmnmnmnmEnCmml~mrllkIl7k|kqjjiihyh]g5gfedJd$cbsa`_^]\y[bZ8YWVQU+TRgQONLmKKI=:96 62 2.-_*)%X%~! qMH, `D8\ cyr%? ӹαʻ^DǿUT *]ުvAe-XAi{Ԟ[z%Ú⛥ʚəϗ(r5J&Kb+E]ٙ%O!?uCG:]{m3ڷ&%|lń`.Ͼӑwz0pLf)29  O/Q O$#?(',f+//|32716l:9=<@?CBFEIHNL=KNM6QPrSYRUoTWlVfYCX![Y\[1^ ]_i^`_a`bacb}d_c+eded*fexf_efefefefefie6feed7eddnccbbaa``_{_X^^\\t[ZY4YXTW-VIU)T!SQPOlNMMKJ,IHWFQEdCgBR@\?#=6<98k6522C/.+*''#D#]lbZ<P  yOo YGFMh ޒ@"ґU ˴̕aUQ#PRQ[T:SUTWU+XW=YX.Z Y[Y[Z]\5[\[M]!\]j\]\]\]\]}\_]7\\[|\S[[Z![YFZYWY(X=XWWUUT]T4SRQ2QPoOQNMvLKJzIfHEGX=f<:9764310.-z+*6('$B$u! {zK\ Q#@C D{l#5gڳ֋}|͚>ɕv Ö?1 0C.HիԪ%N|z$IӦӦ 3X(Bb0 -O{۰bx|dmei~}^Hƻe? ΋ϏKk$^\n݇/Z.mOp%4{. | R  =p"+!%!$$'&Y*)-d,/ /Z2144L7u698;:==@!?BACBEDWGRFHG_JQIKJMK/NM@O*N7P"OQOQPRgQSQSiRSRTRAT$SJT*S0TSTRSR@S&RRQRPOQ8PyP[O}OiNmNTMDM-LKJJIIHGFEDDC5B:A?@M?/>;=< ;98v7655421/5/S-,*)'&'$Z$"x! rSk 5 9 al,D OۏE؟@ ӹӈ>ˋ̂UZ,D&H'aL–ѾFBź\]׶۷öŵѳ۴P]ϲ6K߰m~Zsmݰ?PϲO_óϴշݸloڻ׼^Zr^P7AC#`.ˈYΊv+Ֆi ړے!:i!wB n=baM:+ t583; M N#"%0%*('|*),,.<.1S03[25I46(687:9)@?A@C*B@DECJEND7F=EGFGFHGIHIHIH:J4IjJ\I~JxIxJjIWJNIJ IIHOIDHHGHGZGVFF}EEDDChCmB1B:A@??>==t<;:9918@7i6^54h32_10E/.-`,*/*('6&%#5#W! QD@bd<  } S 4+rw:^/sTvcߡP{HԱѷ )b*͡olEƍr8ѿѾɿ)$~y޼dcy}`^bdyz_Z޻߼qgо& tZƸT.ɳzL.έϻvљPӊ7Մ/ב/٩@_ނ8?%;W>s/V'9~ P KdR-e"=?>?>?>1@;?O>>=>*=\=o<<;;::99887S7668543O321 1F0/.-,A,7+*p)('&%%#!#!,!lBAH } ^ 2 TMTgj.b*gG805Icݒ%bسk ԁLъn%͔Πo͂KxM~Qʛqǥ hFŲW7ŝĀdJ@#5>#eIŚĀN2ũU6ǪLJrLN#79Dh-Δ_ϖ&ц=Өm֠א41zܘ)d+ YD')5/GAR^eolx |fO/^:R~% !>!Y#"$K$[&%'&'&)(w*)++,8, .R-!/h.#0_/1T01,12132-4a343O5454&6Q5x6565656 6666565N6x555545/4p4332 39282l1^10g0/k/.T.-8-z,,L+**u)((r'&&9%$#'#)"!  i?U/]})Wr  $&4@BTQok1hX=+`XV[|ލ 6َڹ^ً4sahx1ԚXэf*ϔDΚn:ͯε͇ΥrΤtζͯͅh4Γ8оφOзѦhk'= ָ,Tـ!ۿTݗW޲9x q-!nZo#TLl 64eJl C  ~ [1 /|tDg,a ! ""#'#$2$%#%&&'&g('0)()7)*)&+t*+*),w+,+,>,@-,v-,-,-,-,-,-,_-,-\,, ,X,++,+X+****l)c)((''*'&F&%d%%g$#h#"Z"!=!  l6r&@xx ?OI d 4 7h\34k}Fi,^'g=h`_M^LCCPlݐ)[ۡRگOٻِ6ؿخURײzןM׋;)׈:סMzײOצP ش؃&٨ٖ5/vۍ+Sݳ݆jOG>KRnAw M1z_u.g[{)?bb-!Q< b 2 N L5_">JRPF4rDC %! !5!S"!"E"Q#"#&#$#u$#$$$K$%s$/%$9%$3%$$%$$`$$0$$#C$##G##""i""!!Z!S!  j<nCbw~oX=lB c c ( ! tU0tc,!QU <\&^4kF)=f%[e;wQr/މeߵGߧ;ߧ:߷Kaޏ-qP|kx6bH_4 vM5 0:-SFdd{}8-\Lzbz    $ "!`4l5IjF[oqtdP1FDk oKO3QelndK5P(f0Ws6Q\imm i ^ U n @ U / :igGE )pOs6]I6'!0@^zBn@b-zd`gu7dp@(-I{,rCijd}#Yv)}KuGqmgiqy&3>F^bwu- <  N # Z ) c - h 1 j(d&_N>'X'N UtFNe]ZI+WO|#;@>- \&i<WhjiUBqP<k 4a hmVO5- `nAR!7" +QzJ/ vW$up1eQ!lecs"Z"h6koq}:[ SD]?*jVNA?B ?L(S3`Km]w  (?2UAlQ~`n s  } | qgTAv"X6 x>e d}=EPK<(U,d4R^cWCy?l>Vdg_P.h(d7k!ATkq'|.{5x 5 p 5 c * W # B  /wd_IE', q]REBz?DMXn+[2p"lV+ >ZMeE5,3GeR W7~xu} &Dv0QoG}O6)'6CNRUko{   vS-V-Uw'iz 57</\(h,V{  iF C&AX``U=%M"b ?c)Jc} uauNV;=##zdvH`9Q"C83,+26IU$p:YA|\iR2#)=` <W1 ~1U4wS%uw2[~/w?o@nXF6/x&n*g&f2g7iBsPv^p  +6C$M & Q ) V ' U  O  K :,~^#Lm 7o}/.*}\28c7FMA0 Q]+LZa^N:jE;y ?l$8BGN  H F ;  .  " vgiRN9B& ;U-SeH]O-/$0<[9r2 n`U[o)a(l@ uu{ @UMr8hGyY<%'(/1?GCTXfZvln{|   y p b T}7b(L(Pm,5B|%b= M2VluuhJ(x;o=N]cZR/p/pI>_~'@Xf t x  ~ z sm`rRdCJ37" }qh]ZZ[hq&W2iL:UN16/CR{'[!p@0$;^JX,{rqs#8k;X'U*yaYGF>~BvAwKuMzY~ep~ x[|9Z *p/F@J~} }iDbS}'.'xJO)MYkadD9R9{$W'Uv! ( . . ) & omXeIP6L)@67/ 45BI%_1pIe2eN9*RS6G>`pNR#ojj~8q-sBrtgzL` c$}E`@!j K7#* 0.;+C5I2Q7P2T 2 L ( L =  4   j<X MSRPy]Pk;s:Q_^R:n6dDLnbtQVzmS5p>Xp)A O ] g k n midw^pNYMQ682/ 0V2r\D5|$#J\;XP{=B YD-35Nn"V Y9 rr]qi1:2I xCe<sfZXP|V{RwXu]{cvjuz y i \ u @ `,E )a{.;_hWPpnnE/:} 9f~ i;A+?ebw]e62D+n7d@a      vrkcfejv~"L0}c+wl|UdMll Cd!xD) |z0R(wLslO^KsiEQb#z<a?bG,    fG`8 KXkqKAgM^:/L"b 1SahcO2ZAv 9*3w n+t[/a8\     |yn[]NL3=#-" +;\1tW|1q^TV"1Cv&\$~bLGJ^zQ_+vYcVsw EY e#HgL*vaG5*$+)4/9>CIHVR^Sj[n^u_z^y X w T p F j = X + O1h~/G q'*vp 41~ n9S.OgoobC$t8g>QgbhNGoZF&X(@Yg$u 3 } A  G P { K |PmDgDV8M.9%+ 0W8~h)j` db6=,=Dg*q5}T?.19Qq(YjC oHTN g'b6 [:w~s~} } f Le,G !~IUx&#k`}syG6>{;[~uT)j*j (BFS=Au e\B~ 9\ 3Pl {rqdkYcP^H[EZ>Z?aAdKsP}br4cOgatzOdOpsMr0]8)&0Pv^'|H9 !FN?T s7^)o[E1p([LB 6 3* * %(#)!!$"%) *$-+-" $   {Wl/GXcuwXJqZjG?e='Xw""uP#o*R#7*2oo#qZ3gFi % 1 @ D  L  L%J"K,A#@(2/"  3B&[=w\zQ<|ff)p x 8KF J'?c'h"{e:;4'PN1J;E>E=ACAD?D8G?I7Q@VC^IkWv`s>-p_c^[e';2OP<\'_G63;Lk.pG]X+76%VPDG`)s8g=~"W9~vq}lxcf`bYUUOJCE59).  Z e3? bb#9,j0*m h'yT'Yo!y]7\4ou )#fj!kgAx,`% E  \ ) z A X m }-A1\Gvf L@ SS co7P.UFz0z9qH# .M |@<#|LS/I3^R36EP U,m<}gRz>a1O>* {ipR^ 8 E  ) ]i)6wx-182pavku?79Cs'+{QI=L}u|an(,}bX >d) M t  !.6<(E3D=OCOLXX]a_edvnzp >1hU.$kjQQfyFgIx oPg$~M( zifj+S'oOjoBZ8cO$v#x*^ j2lF~)[ :vi{aeKZFB56+%  d g ; B   UUC><,kTZwDOgO=h)BS^VO-|Kb(^^ses:<?4{B~4`  7 R ! l 4 }Tc{/)::QLj`{4+d] TUGSr^ i5C5h. v`UW^y 7e5~=(^gHdOu[ ]r/z<}R#a4{aLj4U#=%|lfUT<< "  v x G Q   rr.3NC6#[>g>S#y-~P!@XjecO, j*K??q]Zv7IUZ4r.e : d . R v $ 5 >.PBUMf`po| D:[XA:ttEKAW,~v0g i&z>wN9!$!8Ps7rbRuVqRjNBPN IPe?o H+ubqOW;A"(uhML . (   x L J OGbK7R.S#5ZQBEq [4h"o'XPvZtIUpscZ!R D t  < W)pH_x$'4EFa[ * W  v J i 1D0VFi\up+-NPgh 6;qq$.rz!hx,D /4[[\x>iTPPcx.h@9,]qBgD~eNDTSOTa;fBiVb6G & vhVB0$  ] J "  n`)>'g#DN:` dgA <WhwniN0j$E6:jVXs?N `hO C  A  u 8 g  5Y)lQh'%<8SSio.1S` OY+gz.p@a,T4hZEX n;qU<31:Kn'\; v44rk$~ VCJGk*f.I}&U\x;Y0hhED,&}gQ6+  k [ 3 ( jQwaY9~WZs>H[H,^,Jeqwp`F W|0?} G!K2I[NW _ " ^ + R ( ~Xz  @)VIwi4EVev5BeqPa,C \8k*@ =t"@@5u P%5CB8%{Ms+.5j_i[{6JQZ4 : 1 c 1 e DfFl 1"PIqj *DVp.Of#Pl GceCm;lI ulw2k7yhZXaq&MvL7~94 e{PzUrTCQGo-f(zBo?Wr)E_g4: aX1"d I , f A # P0x<h=MCi14*k2d$;LQJ@$c9e&2}N6c6X7H[E O  X # _  5 b<n8"\HsCBfk&EXv?Z 0^|)^%N0{/{I2({,OpC (T&~X?7FP2,}@kIM>UBFy 7a/Rm@RWW+&eO( ^ 9  e 9  l=yQy5>3`q$x#rR+Z} ]3^v&+rc +0k=SN  a 7 ~ F ; rH:"rT"D>zr%.R_9Tq6V;kAY>r;z W=4AXeH.#'0Nj%aC8=Yn7\+_4yVR4F/^FX AuAW w~;B  og;0yV> q H &  c 4  \#W*yHM|6W kl]J7Ut|$+%|]GSm&{y#]D|RHp&EO i I  b ' d 9 ~U3q'VF}KK{'IX4Vx,Lz"G{?dxEx&d&Ke%Az@L^]##wg9*y^6 { V 6 c ,  o E v6GE {0O] `Y/d 4\t~jFtGly!*yo?!T#P{8J m  Y " v F  f Z9vH3n&VP6  \ 3 j 1 Wi)e$H ix,~iW@a {'5E=:}$^ @N]kbO1o?z7mJ O  q  Y $ x D  eU2vI4j*$aY EL~8K{%Mj;dQW9v1wB'l f^|> X3uzwAvW)%~-9[GvFZ>yRiH+X [y2Re#QYXK {E6jG  p M  a 1 ` * }FODp#=@E!c Cl -/- k=^51w( R(c.aJ V "  x > ! j F O-s[E TI HMIS *[t=cFr6f@z-m,tF+x"mz1A ^9&|gf cr.GlWD(.:U 4+r7xSM^ 7_ ,o"?K^z5;geKAkS" ~_%  t I  I  u D  YXGd!*?$qa>` |+6C@;x)h?Yn $0"vU q-|7n- K  }  v F / \ ,zU[?,jb9/wtBGXo AY=\L{L-r$g.vI:{0{1c5W3 ?c_'Iu!5x.y%,(&}t* ziaI7  b 8 T a  ]  }H\"?*s'r1b;dwnGz'@~<>M;p%K ]  \  K _ * - gP,]?bETFNH>C@NId$g3\9&m!l.}O>-r)Td3rXJBCHZr1`Ijmc)P4u(e0eFtg!#M^ A^2H$.pwhh\K7%qZ8  X + j 7 o : d , H vu7,;Q ?w 3G[ bg]U;"Rb-PGK.\ t 7 x A p = [ - 7  rnE/ oD*N9NL\W `k's5I`:c!KBH*_Q%1jD?\2zTA'!+: X.sQ1*wxTe#d0_ H@vKw_}1<\r&M_0Aw[W>8}nQ?~c,  D  K # D  + l  n7+G!j+jKs3Mo~qh<e3@AgM N+ Q  d - f 1 W & ?  bP4zb7$L>cbsy1<Zf%6 bEj6]3a>r].7k.q$c'i@oeZZ`n 7^OX[C^ c3lYT] o$%DOm~)N^(0^b84nh >*hMj)  4 5  ~ # b j 2 / P8|A H;k2EPN Q;1Xf+gUnP N  { > J  M # @  $urOB& q^5&TS{}?H jyhi-!VBv\n}  ~  t X \ / ' Y = Z&i2hAf#H^smW=]3OP  {n, z U  P  t 5  D  A  5 ijJ@%zdG8yoDAyMX(9 |kd j}99ivPs'e`/s>ljcmx0aIDF#6y/P[4yjip -'AG_j (2KSus  .*QDlZk st  i x R Z 0 / ] N  l D SN @k-?FNF?)aq/?zp7, E $ ` ( X ! z 8  @  =2 smWI5\Y15uyLX-;&w ps=2ej=\ @w4n4a@[G2(%%0;V2kKzPJ%[nYEwK*nb _f t//EM^my#;;VPoe~sz z p t Y X 8 1 s Z 0 e / ~ >JLp4Qi}wkKq/Ftu10 a O a ?  r <  e / A  KE9&~gdNC2qiOO31} y~92\c,Gv#Ow T{ 7e,pHs.R;(     }ovZ`CB$gQ /  g ;  g 9 } ;  L  V  z B i     x U e - 9   \ R !  z I ,  Q " ` 6 m+N,iK|apy     '$;=W`{'Oc,W1m"Yl4j:u^LBz9t+&5O.fVs.5kl7^t?j>l(MYx,ElOe9G!0 jhSO93t]N5jL.`> f : D  c  G  ~ P 1 T # q < W i z  z f S m 9 L  * t n < 4  r F & d ;  f3uXE(rX%A<_Wyp/5FL`f 1F^u'Ah.Z)j@Sy?uGv!O* ;+`]#Q` )Qo 1\,g iPiK P , q  Y 0 g ) R & y J o    e n E N   U D   z T 7 pD%yDh! ]G*VC}q /&RPwz+/P[wIS/\vNn[0?h(V+U(rbI|Cr4e6g1c>mCuWj8:hg :Kq<^JkMm!cs!2JW!NT#SY$(aW. TFtZ+ i,{_d@0 o F  I v J x <  K  Z 0 c 0 h < g 7 d 6 Y * K  : !  l p E =  q [ 8  kJ%X; [?B1t&\M&URNO}ER|&\i ?U8UDgo(O qjIa*g.ycaMA4~sNN0+lg @N&2o+o0w*CMl,Qu; }[-W 4~xy@9b\BRz+Hl8bOh G` N^Zm#rx,2DCZ]of!~l+~n! vb^@/oU(`?q& H & u J m  $ " (   l p J K "  jV4eErD+]DaQ \KRC62~eg UV@J:K?LLdq'Cj(KWu(Nw\Fn6[(PE@@?!L+O;eJpc{*-KV'F^8]z2J}+czZjVc Uedchimlqina^QB.z]CT9t+S:_ .  C ^ 6 h E | U ] d f d ` ~ U o I _ 7 G # 4  aV1)gQ1yT4 f1J7UE[MSMOIJKDDBPLX as"9Ww/NRr<oRu,R7 =5VLwu>Km| $D^~9b~8O~'cyQ^DQ ns QT:8"geF9 [L! YG @'s]-W7~b+= L'[<_<lHfGnH`B`;O,C#0 a]>0tX@#y[BW?u=,bW s1/KIeb4:Z`>Lv6H E`({Jg :hLj5U9 *  ,<5UKkf 5A^n3Ih(Qf!K]']h,7w~LV%&kkAAZP&bZ$]L C/va 8 cJr8P5nO}`w|gqR[>="xhM>$}dB-^L"cT$RN}>=pk$"RR 9?tx'5l{/8xDV t?X'kRi*E* ~zzz~ '-BAck/G[t"9Wi:It0=jw,7pw3C}|DAF?y85se PEz K6ve5%^?tcz "# ' ) !geIE*!gU@% |]G$}j?1{LA QD KEIGAH JQdp,9[l-=|Rj0D |a~Rk=Y3N#@94.30;&@-K>ZKh`{s5D^e#9Tm}4Fiz GX0M`y 7No{ -Ago;@lyJS+._`<<unF=yt ;1la =0kY|1M:hRiuq^`E?*$zeR>)}oL; YL#PJvuF@ il54`e*1bk0<{N[&8 g~HY)< yr[xUjH`B[=S9S:QXI_Ng[sgw*4HRgs'8Qe{ '7X`ERw{ >Brv ==ll6-ff$TJw-!THr0"S?n`y*<(I2S>`JcLmXlUqZmWjTdN\ES<E/6"(}lfP>/#hX?0 h]8/ {rNIUQ%%^^26su>F`k6@ p{L]*9u^oEW0G- #+(99NNegw} />Paq/ib88 _`-1 X_08 ksET$* ~\pFP%8ug|brSiSeK^I^K\I_QaRh[ldym~ #);HXbn},?Gbo#?Ikq34Za(*RQzzD?jg&IBoe-"K<dYs*0'H.H@ZD_OdUnYl]p]n^nZgXbNZIP@F18+( sjXG>2k\KC$jc@:xNP.%ac87t|RV*0}Yg>D( weqK[:E!6 #%44CI[_kq+7JWhs ,6S[s{ +-PVru /1RSzx  .*QLvp;1VNvm/ A8ZIj_rsraVIJ;+ sjZP:.wrSL2,  [W99ae==!mwTT-;apMT-<#|q~dtYhN\HW=O=K4D7G1?6H6E ) +.:=JR^cmu.4DNahx%8>YZqy ;=ZXru5/MKhb|$3+I?\Sk`y   vp`[VL<5.$ {gdTJ61y}e[BH/% inTS49#ovYaCH,7 |k}bgSeMS@O8E2=(8)2-!-*(,!*!0&3-80@;B@SKRUdakjw{ "(5@KR`fu/3IL^cz| %&@>VUpo )&@:TOjc~z ,#6+G@PB\WgXoh{n~t}xwnvid]aTPHH<90,#}{nbTTD7)+wu_]JH/-wx`bIM44${hoU\GN39&/  "((4ADIW^any */CFTXin))9:RRdb{{$%;0FH\Qhg{s   %)#, /)2%2,3'1)2'+#,"%  zwmb[XLA:8( oj[YDC63 nt^\IP:8$+wlo\hTVEP=B2:'0!'  #!&'/1;OEJIXPVR`\_Yhcf`lfjgofmknemkmejdhdd]b^]VYUSLNJHB@<<51.-&!xvih`\PQFA89+)psiiY_TRBJA<,7-*"   !$)/38:BHIKYZYaomnu )*51=AIDSTZYiemp|v         ~|xvomda\]SOHIB?44.-#"  ywovki_d\^STMRFHAC:>57/4*,&+""#         !"#$)*,-15968?ECBJPNPWZ[\ciihpuwy~ *'(,949<B@LJMOZVZ\gbgjrmtw}vzvvwpkjjeb^\YYRONKEE?>;813/)%(  ~|wyuvotpohmklbeeiaa^d_^[aZ[\`XZZ]X[Z\X\\^Z][_`a]acebeehhkjmoomtvuuyz}~ !#$)(,+004487=;??CBGFIIPLMPUPVWXU]\\\a^aafacfidiiiflllhlnohmonjnnollknkkkmiijjefggdbbfa[]c^WY]XTUURPPOMKJIFDFB>>@<8785300.,()(% #!    """$ #$ !"$$"#(##%###"'#!%#!#  "     LIST@INFOISFT4Mixcraft 7.7 64-bit Version 311 (libsndfile-1.0.31)id3 JID3@?   |jTXXX)SoftwareMixcraft 7.7 64-bit Version 311sight-25.1.0/module/ui/qt/rc/sounds/success_beep.wav000066400000000000000000004026221503402212300223420ustar00rootroot00000000000000RIFFWAVEfmt DdataqvNS(*]a)jt+6m9FnI['>" $%;SgnQ`tu"tl\S]OkXw\ 3 A o-Y,ups  "="$Y$!'&^)(+*--0R/N2143658 8;":=7<)?:>(A3@CBDCFEdH]GIHwKjJLKNMRO9NTP=OGQ*P RPRQKS,RSRTR.TS7TSTRSR~S]RRQCR'QhQOPhPPO6ONMLLL>KJIHGuFoEDC|A@>=;:A8k74300,_+ '&"!~b}02 ~v'.8i[i؇g#ʧkG( !`kʲܳh/O.KUyԤ7WϜܚ/zwuxgW9! =#eҎB.Əe^(9 Qs>aOs|7Y8&Aatµ̶JOī%~:՝ֆ *߯YjPsAQ*$ Sk"'"5'&+*/,/4M3(8N7<+;?>xC~BFEFJ;IxMfLwP]ORS8RVTXhWZY$][/_^a_ba|dYcedQg1fhqgihjirkbj*l"klkSmSlmlnm[n\mnmnmnmTn]mnmmlGmDllk lkCk1jZjMiWi=h-hgfeyeddcb:bag`>_d^C]?\[YX|WVVTSRPON LJHGcE`DA@'>9=P:s9`65H21.b-)')j%$ j aca* x ^r&B!8֔׹oӪlϰʀƹ*Ė.+ƵԶͲ޳Ql˪q:_.QAhc )Ӛ󛶙֚ǘۙ2CZe+=#,>RԕJYӖA\$? ?=]vҝIm ƣ~ Slݯ`lXameoTɯ}|4Y T]}K4}6NJ+ p ,^   $#('U,+ 0S/32/7Y6:9=<A@ D CFEIHULKKNM6Q PySWRUsTWdVaY?X[Y\[^\u_Q^`_a`bacbcd;ceceodedPf7efjefefefreSf4eedejddcMd,cc`bbwaan`l`K_'_^]\;\[Z{YXWVUTSRQkPROMLhKXJHGEDBA?><;U9x855c21. . +X*8'&Y#"e`P0  vjCL,MُA֠Vϰ|f>/ 'ROAR # +@[!Gg7)19_ m,ʝꞡǞ՞0XŸ&Kǟb[kTvڨ?_&>!8?SwѶܷCCջԼvH81(Bm5εn Լyڍۊ*@T\kjD1 `^FG!?!<%$('+3+/_.22n1>5k4%8O7::=<; :B9E7o6V43O10..y-+U*''c$# v  q4 b 7&1P&U KJy#ݯفڵ\לQԚWѭuʫȀ[Æt3. {=Pn-r(؍6ұӆC!˟̚lpNlLnXĖͿ  `i7=("/ԫR*ٳGNo]|=tNw9G"kh 8 9 q~(W_ d c#"%:%/('*),,.).0802424"46587H:m9;;s=<>=J@U?A@BACBDCEDFEcG[F HGHGI HkI[HIHIHIHIHIHvIgHIHHGH GdGaFFEEDDCCBBAhAy@@*?>=<=O<;:9"9B8i7t6543210/y.-O,+*h)'0'o%$ #"  "_f Z | 0rd AV*i7O&=>`ߋߴFr C +KϊSʢ7 ʪǂ5ĶʼnsW?5&.<3aUa[⼄}6: OI !ʽľhY[KYDw_߯2Ǟvɣo:ͮΦh|-S F=I]~ެ2Z&q#w,R6\$0zo]7 , H0vNv-:! D#"0%$"'&(P(**z,+'.s-//J1021%4Z3y5465778&8 :*9::;:<;_=q<==>=> >\?m>?>?>?>???>?>h?z>?>>=>=b=w<<;;:::99887665Q543-3211M0/.---7,+|*)( (&=&$[$"t"!x |i[5l;wq ; ( m7'FRl!Y"YD)#",Ilݟ1|.،7Ԥv1 Ҽҥf[̸ˣʞɲ4 yVƶI)űpR!ĵĥļ9ƌmnNǞ|]/ʶ˸̿MЊIѐ8ӨR֩J>tۋEJnXQIMNZcu e>o =Jq !%!=#"$&$0&%'&(K(8*)t+*,+--../ /0/10\213G232N4344C5t45455$6P5@6o5R6}5G6u5)6T55'554Q5444Y43323M2V21100//..--,z,+P+* *j)((b'&&b%$# #|"|! fB(w-dFv. K  _ 5 vVk .-QGvn N;/+ljlwޛ޵Fj'ڿؖpعa׳\ֺgӄҮ0z2А<}G ίϞck-B35Nu=Ϲtf'ϙ^ЬѝSJӗqղ_ֹW׻^rُ(ۼaܨ4{S.#{%O<=v@m_s {2)D# f 7  l(; /g[%wGx* Q! l"!|#"~$#u%$[&%='&(d'()({)(*z)* *;+*++,n+s,+,,,:,-a,'-u,+-v,-g,,C,,,},+,s++ +=+** **v)t)((('W'"'&E&%S%$_$#T#"F"!&!  SNBO}"CO  } K ] : 7"tsM]-G: 9L0whfs3h> !ߵ?sܨ_ۮEڬ~ؙن&k؂%L% ״ ת׺0]ח<ׂ<بFٵٟ:1b{ (ܽzݹEޓu_ZRcl2]3s@?!d,kx +#'xW)p2V!'{R"]AXto|_`(jT3^.>EIF @ 4  '  |ogTD), iSj0XA2" #;Nm7e#nJ[L)+19`5yBjnt 1^^2ieQa[4v3QW/`A-  32AAWbfu+ BI ! \ 3 _ - f 6 g - e(ZS <+m5[u+:tw $}[5@v!BRZT?"u6z'IiojmB6E*f%Rm7T`qr } s t g \ qLZ=C#$ oyXa7C1> h?mW;TP072EY}+a(yW?87Lf C|NzjQYOpsNi|Cg?vb]STT[`pz4+I@_Mo\is} | p^N{/\?Y{ 65Fvzte8YBc~ gCS8amx]W iU:g=i !    pj^IB2-k[MvGt;n>s>zDW_<qQ<&PT2F=`rSZ0 @i'k/tVVKba/CI f.wR5bL7*(-">5DGZ[cou~    iHo:|7LFI}sxYGf'b':D:/ f3j Mc|ml90{1jI(L_x.};O P Y W Q y P f@[:C$2mxVi@U,C= ,&"&&6CX-sCkT7{-{MP=NSvBDlTMKZxPM'`UESLtzbw4\0V/ zrlrmy &2 @'J-Q3Z 2 X 2 ] - T & Q  D 3xGf0NaPRxr}meA%z8s)Rjw|p[6 I7Rxwu SN \?IwKk qgaUN??'/ !;cg*xO0"0Tz.jB i]CN@b`?K]$~BqP8}$dR?4-&")#/1&72DBKOT[_iaurv ~ p ` Ot3Y>n8N dpVSugsXN%wQ ?l'0.}T j?huki/oS3f "@R]l 2 n 5 o @ n ; g B ^5X8D$9$&~qcYySzMxM{OV`s 6bE~fZzw[c]u+cY*phbs'YZ!{dHG8QL|(w(G|F$rD)   pOp)JYl$y|'RSlXa>1X#e 4N[eUF#|Cx,[n|xK>F,p,Vy.Ll }smedSZCI2B$5. '#!!&,8HZ6uHm ?$z`TF vdup0LI`C.+/@^ >~>uML0H8gdKZu9V'kR=|,dSF : 1 -+**-"0*32997@?K?QH[G^HbLdCeCa : [ / Q ( G  7  " [t(E s.8)+WKfLS,h(r.]z tJV D] d[q c6oOz3 ? L  P ! V ( T - Q-M0C'>&-$  9Y7`ZED? th Qs,{I%rw|v$U$dMGLYwA {Le\1=$C4jbT _I_-}`J|7g'RC3 .! !! &#&  |Zu2S $g}'8AF~,`G TKukD =-9f\xZb2--vdLs*Mk |nk\]GK7A+ (GcR)-E=W 3o+>F  Q  T ' T ( S - K)F'51yxnrm{|? nN[P\\/<$?;h |.v8 {_>:5DZ}9o't\,+ 4'i gq-A\5{Q5 u [x<Y4Qe '*ja}ssG0{3p/Rm|wbCORi-"=%OD6t%` 3Uo1L b s }|wlmdgW_MW>O5I-D$B#@FF$R*Z4jE~Ts$U:x _R gsF^HnnLp4cD0,1Ba(iAXS(;C1kc \h$[+mEfYKu8>;G6F:K4J3H,H*>=  -  #  d<T$`s ~#ZKqYhB>_=w"Ij{ _8 ^6ks %zMQI<=g#Ou    !  - G#_Eb<+|h ZZ}f q5HJ}Q+'Ht:5 zLS2P:naNSt3~F vT4gN0}o ^ x N i 3 M  :  ]s-?oq%.|zE4W5Dw dc0] !wQG?K|llLT ^ Z-hX. F  ] , l B z S _ ms{}~zyrnnhmkq|4W1;6n7{@bIs)T:$ zpxbkUaDS/B 0    g ~<G `j-+qc{uRL W-k+DU[SB&`#F02`LmAW|%&m#m-^"P  /  @  V 2 bBnTv_ku}$F/^Ju D5{sVUeuC]=cY3HV%bJ3-0;Zv@x#j[ gh3%woC FfBqL#ui~VnKU=N17'/ ly N \ 2 ?   gs6;xx142"jReVb$kY$MnmK TbpC9]@W"39B]S! G  d 8 V x   $,04/A=I>WNdTrgt (UC|q&aaKPk}TnZ !e}?lF/ $0 ^CrId16N6tEm-61n Gt1H*$Q4U$8QaCA4e* Y  +  :T4^Go[zh{7&IDjX|!TOLMFQ q bu DMSzG}~ +V'hd_/y,WBC:j*m([8d8}h}Th?K/=qpWZ;;"  [ ^ & + ||:4OH5Y7^1G qg_9l 3@D4$t=cgrC$R!Adqgk6l3i % H  h @ a  )#.3DELU[ijxv&C=n_/%cd;;?O ) y73q1S$eN?9DTn2_?)=D #%| G5%t:4JIqWt1R3~zbbQF:4' fg = B   V VUO `P5O(L)J=+f,Xmv\:p 7DP3K%Hz);C&j.k J  u D q  7N4_Pxc{..JGa[~y0&_[D@+y4H +,LK@_\>*!,De)aEKK$y2aJULy9u6i=f=rXl?M(0tcVR81 U Y . * HC zo#qa@L'?ss/{]9\5<E5(kHv,?,P/U$?bo^e%f' e C  h @ f(#92KNbcr|1(QPun*$\["bibq'A. 8(ci c Nzldm|%Ib[X(~hnA'#l:x3|IyCkC_$B}_dFA+(jdB;  f e - ' zq,# 1pm,,;{/F K !9!""P#"#b#j$#$1$$l$%}$%p$$=$y$##k#Z#"""!#!  [| fs_ >  y\]t!9c6nNaXWvmӾӾ}ЉPlDtPǖÀTIƹźƷϸfu"+s& ^wƱֲ̲!-⼊iSž ˘ώ"԰Oًޣ[TKhmj |0r%H%H,+2187>=ZD\CIH OMSRXaW\[`{_#dcPg5fjilk nmBoMn?pKoqpqpq qr2qr,qqpbqppoonnmfmdlkjih~gdfdca`p^P]ZY}V_UQPLKGFA@;:e54.-''} K E>*hnUݽeορWVEÅ On!?j.K-DUƓє͐ÑGAې]U㑭h{4G=`Ox^wϵٶfW.= ͓Gf;*69Kp] r lYF&%, ,2+288>=CBHGMLQPUTYsX\[_^vbUadcfehsgih6k#j+l#klkjmflmlmlmlQ$ANuRq-P 3>^ެĴ ,߽D̡ͤZӪIk@UO4 z/8 ]&%L,+2?1z76<;A@BFEEJINMYRHQUTXW[Z,^ ]i`D_Sb3adbgeKdfte{g_f#h gh|ghghgqhcggf%g ffddc8cbfa@`A_!^\[ ZYWUSRPO%LKGFKCNBp>=C9i8336.-\('K"![=%>6rwթU ʱ˲ō$ʷԸdzҴ"ק5Y"1i$ (m0 :&E"%J$H*,SIlIb$XJȩͬ7ӴT_.r#b6c[atokgG}B %;%A+*v0/v548:Y9>=BBGEJI,NMVQ>P:TSVU2YXJ['Z][^]_^a_a`sbRabababaEb aar``z_m_L^]\:\[=ZYWVkUMTRQ}OeNL KmHdGDCG@X?;:7K6,2g1-Y,''%"!zX ;sۂܑ=:96522..*)$&%! !n6HB  ;%21+@*R:xZ[V،ٴ^գYѽ̄ɍjG/4"^W]`8AHW); =[ˬޭ֬_y 53UfҲmyGUbe0+޿Cˋi.E6Xۑa^6z$Iwy#d * i\1#";'&+s*..b215488;;>=WAg@CBEDGFIH?K7JLKMLNM@O.NONONONOwNOM7N$M2MLKJrJjIHGFEDCLBXA?><<:%96632/B/O,+x('}$#e ){q)u S \ZGfTOtfs۩QӹӊJ7*iVhf͸عUZ% ;S´XmFZmӳqLY]l#)ͺѻȾ‹r,*r,Ք|7eS [.kb B&v I XbP1s,"!%'%<)(,+/.2154K8g7:9=*<9?E>*A,@BAwDqCEDFEGFHG!IHjIVHyIkHJI8HHG8H.G_GPF?F=EDCkCnBA@?>=<[;n:87/6Q5M32N0/"-i,)")[&%"3"Dahb K  0  Bu{܇%@'/h3ɛY3 oe꾮|ǹκJNLGɿshV>XEƓlxD$ѳԣ دBە$%&K$EcH) F*K q ##&.&)),+I/.1184r3}6587q:9(<<;=<?>7@M?E>Z=<;;5:99X817Y64&4210O/d-,*)'&$#]! ?4X l 5 KD9ZDn&vTGeٙ@Ұ|7,'ȃ`żäģˆõp9("D4&»8ƬƇL$Wѯf0s؉"gUbFB"k<8^ / s">H<! #A#s&%(O(W+*-,/.10t32%5P465779C8:;9::;:<;F<\;P.-,R+))*'&$#!P!  } M ;  [^"W/zjvG kW؉ٟHr(p&ӎRΘC˩̚o˅_ʟ|_=% șr.R$͊R ҙԋ֞/فxv7_F6s-`5W4MGPFDRCW:Q , N  /5C_T!+!$h#+&%=('-*)+E+-,5/s.0/112/23343^54546G576_5)6P55 5s54434/3 3=2110/,/k.-,+(+*Z) (b'%T%###j! xr<%k  ".;6IR[rr34Uiv7uTTsص`ԛK9 _)̛h9/'E͜gͼ̈́΀Lw9ЋQу$ӢS<ןr^`}.~OR.dCV&s&&v4  m4x*@J] [ "D"$$U&%'L'k)(**,T+-c,.Z-.'./.0[/v0/0/0/0/S0///?/.z.--,,+L+*)K)('&P&A%$w#"!!Qei 8 j = >' xStOyr7} 2zKZZ د։Մԣ/ԘH҅Ө^ӬdҌ)ӤT<ժՌփס9ٔ,ܚܖ 6߿t< 1_Q5U;?3\ )N^e Y: Nhsf! "4"$p#&%$,&%'u&'='(')l(y)()))+))%))(O)((,(7('t'&&%%$x$#A#"!`!| uX2t#  h Z<$ r|;HzvxT9 @ރe[q ۡSaO٢؃vؖ2٘6ڹڽVzWR\ޑQ* OatYIBr?hEcI`Ta[ac^g[jXkI` = Q  7  [_7{:i  v! D"!#f"##${#s$#$$$5$$<$$$p$#$s##""R"#"!M! Q E^6zsKx7 _   $8@RRwxS\ r")q2 ,gK1޿Po9(ݺ/_ݤ<ޙތ",߷k:!AtI y i9&I+Oh(i_(~H $ Y h$I@M`eS/f44h 2 '  ?djV+l o7N=X J J ! X>ONP\ &v [hFa'|U@;RqBds~6wtQ+0m:lfu6h>K!`I,OQl!#TLj y m  x<&m"Ru`-Sb1^pb@s.oEquPfyW l 8 [ 6 0SMq8T: | ( I7,\o<l-! Uw?fwiL ?YcW9{0+}T O  'oM8\`#qsGV%aG(EDVks:%>  % V??k \1^LD A`eIRdlH+l [k] u C jGI3}M\7(2 [op9#S.o!W. .S#t`o0kU\d<3*S9b;[Rfrx  23  {  D-n#[|{Q-VMGVzwB'$o#Qij`=:2[  ` , F ~oDArKd9dM#9E0gH&->q>#8s:( :VwaT`z,ki(lF?-LOcvx ,:  /  u a)m.UowuT/+/=h'BG+m$?7))UbfS3lr%qA  G  , ykW4/gvJbCe[2Tb8;fAs$M# 6qQ.@DEhWbr@xL](Y<6*;@BWLcN_ = B  u QT(&Oi ' _6_saDo5yZ)%nz-'m 6 ` fnXK?** wp$Rkd,0kV*=DL.0H{=+:LfF:9Mn bo/g; )'"&4+@'8   y C "j"Vd*MY+_srSE_TC@iw}i IB: k 0 w G jFH5hoPbHm[-L T$)Z< ~ 8oE:Eo2 s!Er[Wa. mm &f7&)%36;I;O0 =  w  a(s*T m{%v`;HOhK-vw7PkTD,Q\[C!cb g 4 ?  /u_Z:H'B*NK{E R( !?!k?KLT*-=m$i3 mUNUlExF^#T- ~d _ 1  m3n0JE9:CmRK>Z^F\aRE4@7 zE9 ~ F  u #  lwZtZs DXU#sr G(k.^2)/[ yVioqA&w v6T BVD~c}YhY\VKR>C$  D *l%Y wFn=2)x0T^R#i  r+Y-(rX!SD z H  gn{id^IU9U;d[3O Y(MrQ^t"s[Wt!lTG\v&cIPVy%efV jchulnpcfOJ (  k5x2\ y'0*rSlv  u;fo=z {cE^k aM#gh n > U+T4C-+' !6?i@C)_F ?d>&2PmDTPGwU<"@UWJ0_o'T*QcX2_ :zi1JLF*V> _ % =#C7>?>G;PEfd5P Lzkfv,rj?^bs_\waS4VZ pD,#t*};_' f I  Z Jt(<FB.j ??AY9!eUw&a9w r7B q G  z[+?<Z]t";eG GX9l6bYe<j3O h]bn&[a,Z2zHTzs?,G # e  ?a%;EC/j8TW\]Tw w UTSk</{ .;>3| B 9 c 5 gLy=>^j-K}3dfZN]n."}X> 5zpEe^_,^E=;O"dR}1NyXq(}GC p\&q P  B  w<c{hEe iaWM&A@)HRvPP?e-><t0W, m ?  aH/!PRv >\(] _0wR>:@TRR\CsR*,Mp?SA0U# (SS I}1EX[q< t9 J !C[ hb Z3 i<#f6>6 [xP+eg~:H   a / }; eV DSy;s(_"j[W`~H3 u7\20]1o1J'q ch$s?kWg 8k*BT^xh*| e D  zCicD]eZzK@!48~@GmHH9f %66x*\4 r ? , vNA(j['-\kPq&T1nU SY!sKxp_BgA4>`5LbKAY# D,}- q N  4MV\K373kMWS%?<([YFb%u!e s r ? -  yB&y([Z3Et>4j/~sgu @5]y,aYa onEnhzH%r[ QP ^?wc 0QtAQPM]N \6 , c , S zwTu:~w1j *!e,qpa`?h {/4<q)T' v K )  a]Y3;m+b :O!`F2-1H j?68?OfMLbB,N h.|u#VR 3S~3D>@F:4  x N  8LZXK2w%0[CG U}-.YLG[,^-.* R U  oDX@0+np^u#xAo ;G+t&;R[!D)]wzFrWDGH c V!h*j)aK(^_-`S\i  4` Z8DC: l _ 2  wc!k_  Q[CYRw<z\ICJ^ OP/#`#S9=N~a"21O&sM>18C&`R}|;U(x~riiNH  Q  *4B7){<J|dN'>;#=LpTX M&Oaod[y9O   o M(J4-.p{ \x,o0c1 {}. iT"?`%#Z4 z1Fh=*t#q-zJkkR9J VDn w V T #  n=$:40=(EIw : dXR^t.xq?VOIkl~CD?%MN[3 dG==D^:vjK.+xw`J# N $  K j }zd>` !wt F"PhgU%N$unQ{$=RRN}4Y  2 }Q5 w?*;4:CXs6gMGI\x41^!,mYc_X+G;~8N28di -IxR[3,{n0  g E f g@HZCt8E"?SC*I<u=TCl(Kksxi W e 9 > sF*^P io#x4ItGtR5&%9X9um[?\{PF B`#T8l|w5 a8' /Q=nn:Zx1<]?A>vG_osk W } > N  $ }`Bk3$DEjv:V>l'a!X!d3tJ}MVB-w/^J@Y|[ o hO]lmh} 03_t+DtA?}y6$kQ }   p;943C1Zst_3L9/1t(W j l @ =  n?.b]@MHnE# ' UC+E J<^;28[.3"yUPnYFI|Lbz*@_r&_Y&`L 6  b 1 S b t sp]?`6MA|Qgf8XfV:bU?\!oi0??6 #  [A&yl.'NVx5R gE-ica#n7ZssGgb]7.VOQ(MDKc8A~h  2 > F=1uw0*|ZHM!GOH)RHVqiZ w nL>!zB8~3<{E`5 UPQpK;L(F)7V-pd_;y%^PFK+ZCed$MY5,p`B!pD k  rEb%(A qc)>?&\f (YYD^l+j3e , P " 4   kG2 ~>EyI{{"[Hv " ,  =  < 5%_b\<f)+m#1# u*.a:PS?s r xMK#[K Z_oL^;"/]O/E =*~7 bKC[%}cbU66sM)n"[NMW9bM{p!7Ue-)bZ6  P - f B q D u I q;]+H h`S!GL9]xvd:SK LX Z!36:  (  |fE2^\ (l?P$~cd]p ce/<5`<39X(*{ \0|(\)kD+-)EBap*-b_*X?e  }JV eW\ka1ZYY0Z^Hr3COs1J] b _ U j > I $ %|XKll-?\{1X9 -M3iZn}g qAN lO] ~)r!Pu~xY}'7o \p L~>c r  ] S5,yJH ^g 6V6[E-+#4A!iJl~ExdtK0-@fCJ=rf"|_JAsBrIu^q 7Cjq6-fS2  J  Z ( f . d - aK 1 HBz%qy'g2KH<n .y$~+?8z', . "  bM=kk39Vq?L>DU5` gbP[uH<w=6[&sJ,  3(MLks38]_A+iJd | tVw4Erp:wvSn|vU)(Apn?b=P ^ ] V v E a 0 9mG=oq;HdNv:d1e1c:vPoiv+P/my- #YLm'vl%t4rss -:ef 0M.gC{ Q ^ ^ [ N p3W,pm"\4ZrW aw^6[da.HP  S ' K  >  " fY3,mx?P&od]dr%kf(}*u6fMKZ&YYr>ya r9H" %,BNazB5dYr Zk+2M1vBb` BpvJj!hLt7D'_E` { y coQQ,'piJEpNh3OA5 4 :J-bKz9L l3(Y8'}4G} ^p2gCGGu^MGwEuQy[q 6?UX~"2B " J ) O ' F  <  ! a`lCB]Ft'GPN+M iIHk QP    }oTE,"Q_*<=5xy'k W1-|``nbp'T'y__2eI.42EM]qy ;1]Ezct } l ?W%SDjWa>` |%w#j@gW f6I 6To : x H  Q w N m I Z8B'# b_;B|aJr;a.a0^2nH~\59IhE);{( hc7 G3IMxww#  &  u ^ 0  u H  C![:[<H,&dW-#XX #Ud2C0/(>Lh 6] d1Y0 ! KNy 1\8tKm8Y;SL]iv %/WXDAkaypjI.  Z  _ 9 p  # /  0  '  v a 7  } [ )  `<{_iu`^J9(rk;9 lq;G!j}d{o*Ls&If3^: /,TQz.Qu]~.M1(/0BQ_|A?nd%B5TFZCN/* oO & z 0  l C v     t m E * n B  ~Y-1$wfKA~KP!Wh7L%>%@:Vd#Qw/V~'W 6!0 KDrs3Y{.XiUlPgZrr*1SX~94]TwhrnnUH' c = 5  v I v    } y P B  d ; E&fJr\oX\E9'vnFB|QZ->( )'F[zPw 2h?n'LB 6=H1aX|})RuA|Gi(H9"85HSbu -*TMuo!,,jX8 e ? ,  a = e i g ? 4 b >  P4{b x{ qgZC8WW*0x~^iPbRmj>\Ci(nOx9a.T-V6^Ipb*Rq5m5O*} %0DLgi=3XInWs]jTR7' d5  d >  q J k p o D @   z W .  vR:J/E41#_X1/oyFV(<-)47Tl/q%QbKv9a1];`?mau8Pv CadG_lNg n|igVG<"ah>E%rmw&Ab/o-PyeXV^odM]KF<)( {xY[68 ws~ (3Vt DEn5 yu&1[n"Ho#f=W'; -!.*8AM_g{} "4(?2B/5}`Bv4  { V  C  [ 8 u B y U O q K e 6 C  $ | T 8  o7oR y$  q`V=8pxYgL^Kc^| ">j:Hr9,4Xe:[Ge2 mczfws -1GF\Vk`salVV>/xR28  y U 0  D  V - \ 0 V 1 I  3  U 7  ~J)l!?'O:L;<.){}b`DD)2)%/6Ma}Sr"yFk9 /0[k0R|0Od}J_;O:LEQU\lm}  "1#4#+vaDuW O + l  . A  L ' J # D  /  c L .  fGH+lV|lswmd\LE.-ou]iSgXrn5V~0N j3\.#'LTx /\|"c/K%yy $32J@\MgUjR^GF-c9k \ 8 s  (  6 >  5  /   v b =  e2s]3C-D3;/'!  gnM\7J+=-=8LRl#>t'Kg=_? 0+SVz&Nj Ef %tTjDU@KHLTWcfsq~   odO4yF)q" ] 7 l      o d 8   rH#n( S9kSu_p_`WLF42  so} y *E`}#a}>gAeD)%=0Y] >a3Q Vo/J/"!)%55:CDNNTORFE3)bBgJ3  h I w   z m E .  \< P0~d # )$pu\^IM;B5=6@CTcz:W?_"\~7^;."$;.NQuz&Dn+d~)=o^hT]UZ]^jfxp| wv`R: d0|[ B  q Q y w T C !  zZ+ z\A/]LfXb[ZSNE:8$, "9Da!B8^dAn(NB:?"J9_S|~=Z|Ihi@V'7'#&-,82@8B8=,+ t[;s=_ ;  o B p n n K C   `8n1gFh vvdkU_LXHYOdd{ :^~-p,OiLt7b0U(X9\@qd|8Rx4X Li6sgyeuftmxvz~~{ui]M9#[FW8{ E ! s P o m s R G  uM-N1m =&K8L>F<<4.'! &2E\r=] Yx<iQs>e?`;cMna{%>`5i">^n@M.9(/),.12578;58..! sZE` iJ L ) s N n r t R P - ! eFs[M5q^v  xzmqah\c[jazy %:X~-KPu8q^S}SzXj#3VsSp^.H o|fqenhnkmpltflZ[I@+}c:n.rQ E  j B X m w u q \ i F L  uL( m1nS&:)D5E7<600(*%% &"03DPf}6f9Ei5qom$2Zm@d Jf*yZlFU:F3>3=48452.,n\=&Q99~\ =  Y 5 o L | X \ ~ Y o J \ 7 :   vS-vB$i F4aOo_tjsjmfffcgemkx|*Ba{Xv (Kk9 .=_v<[:Taq6I*}{zytuooadOL5'~\BgID#}Z - I ! Z 5 c > c ? [ 3 G * 1  zV8V6C,q_ ,1(,+'*(-(2)75FOcqE_AeyBh> 25Yj%Fp6t7M tYmHX=G5>/3-*' xcU8 z_/ ~` S+b %  C  J ! R ) M  @  +  bM"sMfI1Q:gPp[pbmgmkkpnwy .I[$Az/N p;c; 32X^8Y~Oo *uG`'=$}xswnld]PE3& }iA-G)y X:n  4 :  ?  0  '  eU8f8l& ZCj &+%+(++,16=IQaq2\t #g9e;[8 ,0UVz!Ef3NNd-i|RcCS:A13)' xeW;'^H\E/ cGt  %  *  $    reD$~Q8N6s=,UGeUk`njpvs|z)2J]yC]Pm-c6^=! 4/ST|0T}2k$?_k:F* y~mmd_VLA3!|gN& b* sR8lGv     ukE6jGkPE/jXt"),&2.6;BNVfr;M|!A<[\9a=.&>1UOz| "FcNnd.I{_nKX=F04#$ugRA%uG/@%l A"uT{ vL@! a7q3rW+H5\KeZkdrq{|%1GVv(^t"o,H [;\!C3'+ , @+WXrs4So/K0[Kzk!+*8:CKQ[gs 1Rh C]Y}">ZDa'S@=A'J8^T{w'?c6j3w@V(itT[@E/- saV>-oW*fnIO,{TyxyWP+a@rTXBr 8-KDZUfdtt'2FZn-Ex'BKn.}aDk6Y)S,N0Z@cWu4Vp QlSr1~Uh5D'lkXSB<&lT6J16~\'T4{VwxyV[2#zT.r> gJ4nXu*#;7LL`ex 5Aj}!`q .|AX'dNpCe9_BaHo^}w(F`3O4ISl'7q|Y^CF./unTK)[GbJI-o/ T8xUtuXV70kC(^@A,tf0#H@[Ujj{-9KZt "L` Eas3R!l]QxSpVg}:Tv8h-n+> ep?G' daLD0'rSAwc'~aY6v, S.mOet|zevQU00 sU3 pW kO4"]Lxm+(=BPZit(HX*@+Ma~*Mtkcgr :Km(TsHk$p;L"ciJL1.jeD>oA.K3/hL6 P1pHahqh_rMV/1 }dG%p>p#^Jz <-UHh^{v!9B\i$3gy"b~5Yy%D~{ 4Bi}>`/KC^ "xM[*6 }bbHD&"lb9+XDUB/gLCT7jFlPrMfH[8A!&iH(R2I1t I?ng/-HLci5Fg~Vn]y4e/P +!ALp8Y9u)>Ta%1jqHL('vsOJ&YFlWaC+ eCx );G%M%F$@)waA* V8\F>'q_&HCfb.2PYv#O`>UUs.i;^8 21PW}7Vx1fz'n0B jv@H fdE<ySEzk/o& oX/ dJs"/) 'paA+ eIqW`F/ YK~s(#KIjl&/Ub,?z*>Ie &lDa!D '  "<=WZ.NoNi Mh #oz8C wPS'&piD:vg9(E12v_,^Aj la@* sU&t6k TG{C9b`:Geq5Hv,l 7Lb/zXr9^"=6 ',,@1OLlm2JhAW7JO] xHQ!][2+jb5%Q@T?3z]%N4|Z~~ZO5$sW,D+9'xg C>rk%(MSt=Fu~'Vm\t+Ig,iLl?`*K.Q*J9ZGib~z2Ni6O';y0BR^(Y]+-cZ2&[Ln]hP=%gM,lOpopPI+w\9X?TA8'm`3-[YGKxQa>PMdBb*wbOqKjGfNp^yo.Lf(?o #crl}+8X^&)ZX'%ZQzk0w+xbD.h?bF{_rt~bcDA&|`C,gU rf\O0,c`,+[] 'U`7B,CCYKf3 zqhot 3Ng2dy I_MZer*4W\!%TM}l;&C78!i E-c0J.cDvX`dduVhJM20 }d@'wb)v0 {mXS)%VZ(*Vb1XQk!@ />Zo1]u:O6FKT fm-3[U#yD7]N _MJ0zH/c 9P/Z>dFcC\AP0;uaE)i4$K79/{uSO"#Y]*3gsQbQgq,GQk'D3Abo,Ne*=s'q|#,AD^b"$NHf^}k#ydU=M2{a& :A&H)A(:(z`G0 {I4aT _REAXW,3fmJX6K>Rk)? Zt2R- %*BKhu$Jb&btQ` `cuw52UPti."?/:$p^HC(vX#$pUD) {R@ t`$zn!h_ MH$"bd=E$1s2|2Fg|$A eA`&B * $!=5UZv(Ga "Re>O;JNU fl#%C?SK[NP@)dQ@*oTxynS<$ZIo8*>30$ukPS42tw[j Ygj|!1\r#9mQf3Q#60 $ '* 4-JH^e$DW{BR%/v(u}*-:qbzRsLgLhOo^wm3HZv*>pKX;J7>CKOQ_X^[XH8*m='ta.I4kOhzwkiNL5)nW1jX&;-<35,{w" ki[fV_ bp ?QYn)>ulh|ks.FZw'4]o5C"nwqnss# {s+"{.~x nYJ:wB*tb ?&YCoU{ensm|irWVDE+ q]8)|q8+YM]] ^XNSCI7@9ML\x5JXq-F1I\p&Sd*fpOTCJFHOCKIO?7&tYD J,vc7M0]@nQpRsXmO`EQ58xaD. {L; re)z/&,({z&$vv!kt!p|*9Hk~-CXs3K $!:Gaq $F[P]/@")s%uuoqspc]OG$aTG5u`( 2 K.J8X9L;L-8#&vbF.[Gz?0NCRJNOMLHQW\m|+3]p+9`p:R*"+@Qdw @Rz?K"gm RYMQHG>>5,ycT/!kZE3q\" +53/$u_A2gU TImeus"wt#*x%)~'3;G`k*]n!8h}E^'< % 3pNQ38%){ypkdXQF-!vd9&u_@,nQ| |jTF,n_/fU{80GBMMORXar|-O^Yj5oRh5P7& &..ID^b}8Nn 2bu6Az!`lUYAD33!hb@9wA8{g:)eQss_PC(ub>(vi4$RK n^tn" -+R4N8H6QHYOfi~~4Ikw EU OX(-uxWWB>*$rk QD!bRN?k /P:pWpuvWO=4oJ2UH|92ZTpm! 56NVnx+7bp&7Na"9 ~k]tQmTkNh]ub{w"7Od{6O|7J T`7=fcHK!ie1&pgTDs'C3cLsavt{bcOH0(oP=hY%WH}q-&IBba|{26[]"[_)S]'7}xmor~&;Mhw 3>k~(3is?cb=6|w?5}t#TEw 6(Q=cTt^}nnt|ixfdQTB:' tUBte5-pb#"OHhk"=FccEPM^!Sh-< *BUh{*;bo"Wb"%en@Daa;7{ TGN@y(UDo,=-R6[IfKhSeKaJQ8C,,nS<$~nC4v=0nc EAhd EFw~2?~BSXj3G% "=H[j#7[lEROX'go;>WX# ga[R)XEs ,=)H1K8N5H5>&0jS>'uOBSH{?5faE@nn+-_e&,p{>F[l;H-'-=M`p.Q_1@s|4>uDN[`..ro62yr*$eX*SE{l#0./(  |jRC)[O$fc+\W??lp+)TWRZ!iv;JhvEY)<& "49LWkx-L[)/^n(_b'+op98}{JEJ@7)l]*N;vb xdL7'dV+{p=.vp1)aaNP~@EwGNcr4DjSc4L$5) $1,FF[`w*H]x+Q]ISQV\] df _Z A5rh%M>m\|nZL;%j];&O@ME {?=sr..in''ju9;am1;rboBS6E#5-'&)"2/?=QUck~+DQuCJ18ru15su76zw=9}t2,pg H;xl!H2eZotfVE6!tf=7 ^X%gb$$\^OZSY ^m%1Zj1Bh|ViBY9M1F-D1B6L?SPeatv*GSk}8Aoz#]_S[TSTJD8rN:p  @+\Gycw}ya`K@+ vfL8uc4-tA;|{@8v};7x|GMVn|8H~[j/F%~~y#6ETk~(IX'Z^AG~1-qo#\V;.rd0[Jp #<'O7^HmVtZxd{_taoTaKQ9@'$ ~m[E*cY5!M? YVee))ry@C ek3AZn;H+#>MZi&@Op@P}(3`gOP47olD@yt/%ZOs+<,I<VAYL_KYJZFH;A++yl[K+rh;0 _V&ul87JHgd'/[d-0 `lAK"-&1:Pasy>Djs78ksHN1+iiJG~$QJz 5'YNo /6(>0F5B2A39',   zrWJ.$zsGAnq<6OSjm,8LY"L`'0}hr=W+;"38FThq4Bbk&,X`56hlLI*#`Z0 ]V4$]I|k$/,0%! {jZE4! tUK'~RGri42QM suBF y~MY'huO\1A. ))9?Q\oy,=\gLIST@INFOISFT4Mixcraft 7.7 64-bit Version 311 (libsndfile-1.0.31)id3 JID3@?   |jTXXX)SoftwareMixcraft 7.7 64-bit Version 311sight-25.1.0/module/ui/qt/rc/square.svg000066400000000000000000000022571503402212300176660ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/text.svg000066400000000000000000000035441503402212300173520ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/rc/tf/000077500000000000000000000000001503402212300162505ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/rc/tf/CT-Bones-U16.tf000066400000000000000000000024171503402212300205320ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "72f6f5b4-3ece-48f7-9ec4-1bc2da626a58", "Level": "1262", "Window": "157", "Name": "Q1QtQm9uZXMtVTE2", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "1184.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "1191.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0", "Alpha": "0.30991700291633606" }, "Value2": { "Value": "1247.000000", "Red": "1", "Green": "1", "Blue": "0.72548997402191162", "Alpha": "0.59504097700119019" }, "Value3": { "Value": "1341.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "0.79178899526596069" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Bones.tf000066400000000000000000000024061503402212300201570ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "8e4b8a28-7464-4afa-bc83-0d419cd577b1", "Level": "262", "Window": "157", "Name": "Q1QtQm9uZXM=", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "184.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "191.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0", "Alpha": "0.30991700291633606" }, "Value2": { "Value": "247.000000", "Red": "1", "Green": "1", "Blue": "0.72548997402191162", "Alpha": "0.59504097700119019" }, "Value3": { "Value": "341.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "0.79178899526596069" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Liver-2.tf000066400000000000000000000042601503402212300203310ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "72f6f5b4-3ece-48f7-9ec4-1bc2da626a58", "Level": "222", "Window": "344", "Name": "Q1QtTGl2ZXItMg==", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "50.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "82.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value2": { "Value": "102.000000", "Red": "1", "Green": "0.35294100642204285", "Blue": "0.27450999617576599", "Alpha": "0.12999999523162842" }, "Value3": { "Value": "171.000000", "Red": "0.77647101879119873", "Green": "1", "Blue": "1", "Alpha": "0.18208999931812286" }, "Value4": { "Value": "221.000000", "Red": "0.88627499341964722", "Green": "0.74901998043060303", "Blue": "0.49803900718688965", "Alpha": "0.54925400018692017" }, "Value5": { "Value": "283.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0", "Alpha": "0.6328359842300415" }, "Value6": { "Value": "327.000000", "Red": "1", "Green": "1", "Blue": "0.76862698793411255", "Alpha": "0.9134330153465271" }, "Value7": { "Value": "394.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "1" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Liver-U16.tf000066400000000000000000000037041503402212300205450ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "72f6f5b4-3ece-48f7-9ec4-1bc2da626a58", "Level": "1138", "Window": "323", "Name": "Q1QtTGl2ZXItVTE2", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "977.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "1050.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0.086274497210979462", "Alpha": "0" }, "Value2": { "Value": "1100.000000", "Red": "1", "Green": "0.28235301375389099", "Blue": "0.28235301375389099", "Alpha": "0.20000000298023224" }, "Value3": { "Value": "1150.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0.50196099281311035", "Alpha": "0.40000000596046448" }, "Value4": { "Value": "1200.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0", "Alpha": "0.60000002384185791" }, "Value5": { "Value": "1250.000000", "Red": "1", "Green": "1", "Blue": "0", "Alpha": "0.80000001192092896" }, "Value6": { "Value": "1300.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "1" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Liver.tf000066400000000000000000000036701503402212300201760ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "72f6f5b4-3ece-48f7-9ec4-1bc2da626a58", "Level": "138", "Window": "323", "Name": "Q1QtTGl2ZXI=", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "-23.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "50.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0.086274497210979462", "Alpha": "0" }, "Value2": { "Value": "100.000000", "Red": "1", "Green": "0.28235301375389099", "Blue": "0.28235301375389099", "Alpha": "0.20000000298023224" }, "Value3": { "Value": "150.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0.50196099281311035", "Alpha": "0.40000000596046448" }, "Value4": { "Value": "200.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0", "Alpha": "0.60000002384185791" }, "Value5": { "Value": "250.000000", "Red": "1", "Green": "1", "Blue": "0", "Alpha": "0.80000001192092896" }, "Value6": { "Value": "300.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "1" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Lungs-U16.tf000066400000000000000000000030231503402212300205460ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "8e4b8a28-7464-4afa-bc83-0d419cd577b1", "Level": "375", "Window": "650", "Name": "Q1QtTHVuZ3MtVTE2", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "50.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "0" }, "Value1": { "Value": "100.000000", "Red": "0.9882349967956543", "Green": "0.83921599388122559", "Blue": "0.63529402017593384", "Alpha": "0.60000002384185791" }, "Value2": { "Value": "200.000000", "Red": "0.9803919792175293", "Green": "0.67843097448348999", "Blue": "0.27058801054954529", "Alpha": "0.5" }, "Value3": { "Value": "400.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0.28623199462890625" }, "Value4": { "Value": "700.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Lungs.tf000066400000000000000000000030261503402212300202000ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "8e4b8a28-7464-4afa-bc83-0d419cd577b1", "Level": "-625", "Window": "650", "Name": "Q1QtTHVuZ3M=", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "-950.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "0" }, "Value1": { "Value": "-900.000000", "Red": "0.9882349967956543", "Green": "0.83921599388122559", "Blue": "0.63529402017593384", "Alpha": "0.60000002384185791" }, "Value2": { "Value": "-800.000000", "Red": "0.9803919792175293", "Green": "0.67843097448348999", "Blue": "0.27058801054954529", "Alpha": "0.5" }, "Value3": { "Value": "-600.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0.28623199462890625" }, "Value4": { "Value": "-300.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Muscles-U16.tf000066400000000000000000000060451503402212300211000ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "8e4b8a28-7464-4afa-bc83-0d419cd577b1", "Level": "1073", "Window": "346", "Name": "Q1QtTXVzY2xlcy1VMTY=", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "900.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "947.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value2": { "Value": "994.000000", "Red": "0.25097998976707458", "Green": "0", "Blue": "0", "Alpha": "0.17595300078392029" }, "Value3": { "Value": "995.000000", "Red": "0.37647101283073425", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value4": { "Value": "1005.000000", "Red": "0.43921598792076111", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value5": { "Value": "1006.000000", "Red": "0.47058799862861633", "Green": "0", "Blue": "0", "Alpha": "0.20527899265289307" }, "Value6": { "Value": "1041.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0.24666699767112732" }, "Value7": { "Value": "1074.000000", "Red": "0.62352901697158813", "Green": "0.21960799396038055", "Blue": "0.16078400611877441", "Alpha": "0.41333299875259399" }, "Value8": { "Value": "1110.000000", "Red": "1", "Green": "0.35294100642204285", "Blue": "0.27450999617576599", "Alpha": "0.53666698932647705" }, "Value9": { "Value": "1144.000000", "Red": "1", "Green": "0", "Blue": "0", "Alpha": "0.65333300828933716" }, "Value10": { "Value": "1184.000000", "Red": "1", "Green": "1", "Blue": "0", "Alpha": "0.72333300113677979" }, "Value11": { "Value": "1246.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "0.89333301782608032" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Muscles.tf000066400000000000000000000033701503402212300205250ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "72f6f5b4-3ece-48f7-9ec4-1bc2da626a58", "Level": "96.5", "Window": "299", "Name": "Q1QtTXVzY2xlcw==", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "-53.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "6.000000", "Red": "0.47058799862861633", "Green": "0", "Blue": "0", "Alpha": "0.20527899265289307" }, "Value2": { "Value": "74.000000", "Red": "0.62352901697158813", "Green": "0.21960799396038055", "Blue": "0.16078400611877441", "Alpha": "0.41333299875259399" }, "Value3": { "Value": "110.000000", "Red": "1", "Green": "0.35294100642204285", "Blue": "0.27450999617576599", "Alpha": "0.53666698932647705" }, "Value4": { "Value": "184.000000", "Red": "1", "Green": "1", "Blue": "0", "Alpha": "0.72333300113677979" }, "Value5": { "Value": "246.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "0.89333301782608032" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Skin-U16.tf000066400000000000000000000070631503402212300203720ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "8e4b8a28-7464-4afa-bc83-0d419cd577b1", "Level": "741", "Window": "1157", "Name": "Q1QtU2tpbi1VMTY=", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "163.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "302.000000", "Red": "1", "Green": "0.81176501512527466", "Blue": "0.72548997402191162", "Alpha": "0.20362000167369843" }, "Value2": { "Value": "442.000000", "Red": "0.9803919792175293", "Green": "0.67843097448348999", "Blue": "0.27058801054954529", "Alpha": "0.24886900186538696" }, "Value3": { "Value": "612.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0.23755699396133423" }, "Value4": { "Value": "777.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value5": { "Value": "872.000000", "Red": "1", "Green": "0.7098039984703064", "Blue": "0.41568601131439209", "Alpha": "0.27149298787117004" }, "Value6": { "Value": "908.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0.25097998976707458", "Alpha": "0.30316698551177979" }, "Value7": { "Value": "1001.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value8": { "Value": "1036.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0.18778300285339355" }, "Value9": { "Value": "1076.000000", "Red": "0.61960798501968384", "Green": "0.22745099663734436", "Blue": "0.1568630039691925", "Alpha": "0.39592799544334412" }, "Value10": { "Value": "1120.000000", "Red": "1", "Green": "0.34902000427246094", "Blue": "0.27450999617576599", "Alpha": "0.55656099319458008" }, "Value11": { "Value": "1177.000000", "Red": "1", "Green": "0", "Blue": "0", "Alpha": "0.71040701866149902" }, "Value12": { "Value": "1221.000000", "Red": "1", "Green": "1", "Blue": "0", "Alpha": "0.79637998342514038" }, "Value13": { "Value": "1320.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "0.87556600570678711" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Skin.tf000066400000000000000000000063721503402212300200230ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "72f6f5b4-3ece-48f7-9ec4-1bc2da626a58", "Level": "-327.80200000000002", "Window": "1296.5999999999999", "Name": "Q1QtU2tpbg==", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "-976.604000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "-0" }, "Value1": { "Value": "-837.604000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "-1.994930012139858e-17" }, "Value2": { "Value": "-745.129000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value3": { "Value": "-401.218000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "-0" }, "Value4": { "Value": "-297.307000", "Red": "1", "Green": "0.7098039984703064", "Blue": "0.41176500916481018", "Alpha": "0.50020599365234375" }, "Value5": { "Value": "-62.297000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0.24705900251865387", "Alpha": "0.50069200992584229" }, "Value6": { "Value": "1.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value7": { "Value": "36.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0.18778300285339355" }, "Value8": { "Value": "76.000000", "Red": "0.61960798501968384", "Green": "0.22745099663734436", "Blue": "0.1568630039691925", "Alpha": "0.39592799544334412" }, "Value9": { "Value": "120.000000", "Red": "1", "Green": "0.34902000427246094", "Blue": "0.27450999617576599", "Alpha": "0.55656099319458008" }, "Value10": { "Value": "177.000000", "Red": "1", "Green": "0", "Blue": "0", "Alpha": "0.71040701866149902" }, "Value11": { "Value": "221.000000", "Red": "1", "Green": "1", "Blue": "0", "Alpha": "0.79637998342514038" }, "Value12": { "Value": "320.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "0.87556600570678711" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Thorax.tf000066400000000000000000000055021503402212300203560ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "72f6f5b4-3ece-48f7-9ec4-1bc2da626a58", "Level": "-418", "Window": "1187", "Name": "Q1QtVGhvcmF4", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "-1011.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "1" }, "Value1": { "Value": "-690.000000", "Red": "0.9882349967956543", "Green": "0.83921599388122559", "Blue": "0.63529402017593384", "Alpha": "0.72727298736572266" }, "Value2": { "Value": "-597.000000", "Red": "0.9803919792175293", "Green": "0.67843097448348999", "Blue": "0.27058801054954529", "Alpha": "0.51906198263168335" }, "Value3": { "Value": "-480.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0.28623199462890625" }, "Value4": { "Value": "-195.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value5": { "Value": "5.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value6": { "Value": "95.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value7": { "Value": "121.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0.39882698655128479" }, "Value8": { "Value": "135.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0.50196099281311035", "Alpha": "0.52199399471282959" }, "Value9": { "Value": "155.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0.50196099281311035", "Alpha": "0.52199399471282959" }, "Value10": { "Value": "176.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "1" } } } } sight-25.1.0/module/ui/qt/rc/tf/CT-Vessels.tf000066400000000000000000000033041503402212300205330ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "72f6f5b4-3ece-48f7-9ec4-1bc2da626a58", "Level": "216", "Window": "176", "Name": "Q1QtVmVzc2Vscw==", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "128.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "140.000000", "Red": "0.62352901697158813", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value2": { "Value": "159.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0.50196099281311035", "Alpha": "0.30000001192092896" }, "Value3": { "Value": "187.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0", "Alpha": "0.60000002384185791" }, "Value4": { "Value": "219.000000", "Red": "1", "Green": "1", "Blue": "0.64313697814941406", "Alpha": "0.90149301290512085" }, "Value5": { "Value": "304.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "1" } } } } sight-25.1.0/module/ui/qt/rc/tf/MRI-T2-Kidneys.tf000066400000000000000000000032661503402212300211300ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "8e4b8a28-7464-4afa-bc83-0d419cd577b1", "Level": "573", "Window": "283", "Name": "TVJJLVQyLWtpZG5leXM=", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "432.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "445.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value2": { "Value": "482.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0.50196099281311035", "Alpha": "0.13966499269008636" }, "Value3": { "Value": "554.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0", "Alpha": "0.43575400114059448" }, "Value4": { "Value": "657.000000", "Red": "1", "Green": "1", "Blue": "0", "Alpha": "0.79329597949981689" }, "Value5": { "Value": "715.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "1" } } } } sight-25.1.0/module/ui/qt/rc/tf/MRI-T2-Liver.tf000066400000000000000000000046051503402212300206010ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "8e4b8a28-7464-4afa-bc83-0d419cd577b1", "Level": "324", "Window": "606", "Name": "TVJJLVQyLUxpdmVy", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "21.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "62.000000", "Red": "1", "Green": "0.69411802291870117", "Blue": "0.39215698838233948", "Alpha": "0" }, "Value2": { "Value": "109.000000", "Red": "0.74901998043060303", "Green": "0.34509798884391785", "Blue": "0.19607800245285034", "Alpha": "0" }, "Value3": { "Value": "147.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value4": { "Value": "295.000000", "Red": "0.25097998976707458", "Green": "0", "Blue": "0", "Alpha": "0.047485999763011932" }, "Value5": { "Value": "402.000000", "Red": "0.74901998043060303", "Green": "0.17254899442195892", "Blue": "0.137254998087883", "Alpha": "0.34357500076293945" }, "Value6": { "Value": "466.000000", "Red": "1", "Green": "0.35294100642204285", "Blue": "0.27450999617576599", "Alpha": "0.67318397760391235" }, "Value7": { "Value": "539.000000", "Red": "1", "Green": "1", "Blue": "0", "Alpha": "0.83240199089050293" }, "Value8": { "Value": "627.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "1" } } } } sight-25.1.0/module/ui/qt/rc/tf/MRI-T2-vessels.tf000066400000000000000000000032661503402212300212060ustar00rootroot00000000000000{ "sight::data::transfer_function": { "uuid": "8e4b8a28-7464-4afa-bc83-0d419cd577b1", "Level": "526", "Window": "251", "Name": "TVJJLVQyLXZlc3NlbHM=", "InterpolationMode": "0", "IsClamped": "false", "BackgroundColor": { "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "TFData": { "Value0": { "Value": "401.000000", "Red": "0", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value1": { "Value": "416.000000", "Red": "0.50196099281311035", "Green": "0", "Blue": "0", "Alpha": "0" }, "Value2": { "Value": "480.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0.50196099281311035", "Alpha": "0.30726298689842224" }, "Value3": { "Value": "549.000000", "Red": "1", "Green": "0.50196099281311035", "Blue": "0", "Alpha": "0.60614502429962158" }, "Value4": { "Value": "604.000000", "Red": "1", "Green": "1", "Blue": "0", "Alpha": "0.79329597949981689" }, "Value5": { "Value": "652.000000", "Red": "1", "Green": "1", "Blue": "1", "Alpha": "1" } } } } sight-25.1.0/module/ui/qt/rc/touch-friendly.qss000066400000000000000000000025461503402212300213320ustar00rootroot00000000000000QAbstractSpinBox::up-button, QAbstractSpinBox::down-button { width: 1.5em; height: 1.5em; subcontrol-origin: border; } QAbstractSpinBox::up-button { subcontrol-position: center right; } QAbstractSpinBox::down-button { subcontrol-position: center left; } QCheckBox::indicator { width: 1.5em; height: 1.5em; } QPushButton { min-width: 1.5em; min-height: 1.5em; border: 0px; border-radius: 8px; } QPushButton.Bracket { min-width: 0.4em; min-height: 0.4em; } QPushButton.signal-button { icon-size: 48px 48px; } QPushButton.buttonBarTouchFriendly { icon-size: 48px 48px; } QSlider:horizontal { height: 2em; } QSlider:vertical { width: 2em; } QSlider::handle { width: 2em; height: 2em; border-radius: 0.74em; } QSlider::groove:horizontal { height: 2em; border-radius: 0.74em; } QSlider::groove:vertical { width: 2em; border-radius: 0.74em; } QComboBox { height: 1.5em; } QToolBar { icon-size: 48px 48px; } QToolButton { min-width: 1.5em; min-height: 1.5em; icon-size: 48px 48px; } /************************************************************************/ /************* QToolButton used in module::ui::qt::status ***************/ /************************************************************************/ QToolButton.status { icon-size: 48px 48px; } sight-25.1.0/module/ui/qt/rc/windowing.svg000066400000000000000000000021331503402212300203640ustar00rootroot00000000000000 sight-25.1.0/module/ui/qt/reconstruction/000077500000000000000000000000001503402212300203145ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/reconstruction/organ_material_editor.cpp000066400000000000000000000276221503402212300253630ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2019-2024 IRCAD France * Copyright (C) 2019-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/reconstruction/organ_material_editor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::reconstruction { organ_material_editor::organ_material_editor() noexcept = default; //------------------------------------------------------------------------------ organ_material_editor::~organ_material_editor() noexcept = default; //------------------------------------------------------------------------------ service::connections_t organ_material_editor::auto_connections() const { connections_t connections; connections.push(RECONSTRUCTION, data::object::MODIFIED_SIG, service::slots::UPDATE); return connections; } //------------------------------------------------------------------------------ void organ_material_editor::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void organ_material_editor::starting() { this->create(); const QString service_id = QString::fromStdString(base_id()); auto qt_container = std::dynamic_pointer_cast(this->get_container()); qt_container->get_qt_container()->setObjectName(service_id); m_diffuse_colour_button = new QPushButton(tr("Diffuse")); m_diffuse_colour_button->setObjectName(service_id + "/" + m_diffuse_colour_button->text()); m_diffuse_colour_button->setToolTip(tr("Selected organ's diffuse color")); m_diffuse_colour_button->setMinimumSize(m_diffuse_colour_button->sizeHint()); m_ambient_colour_button = new QPushButton(tr("Ambient")); m_ambient_colour_button->setObjectName(service_id + "/" + m_ambient_colour_button->text()); m_ambient_colour_button->setToolTip(tr("Selected organ's ambient color")); m_ambient_colour_button->setMinimumSize(m_ambient_colour_button->sizeHint()); const char* transparency = "Transparency"; auto* const transparency_label = new QLabel(tr((std::string(transparency) + " : ").c_str())); m_opacity_slider = new QSlider(Qt::Horizontal); m_opacity_slider->setObjectName(service_id + "/" + transparency); m_opacity_slider->setToolTip(tr("Selected organ's opacity")); m_opacity_slider->setRange(0, 100); m_opacity_slider->setTickInterval(20); m_opacity_slider->setTickPosition(QSlider::TicksBelow); m_opacity_slider->setMinimumSize(m_opacity_slider->sizeHint()); m_transparency_value = new QLabel(""); m_transparency_value->setObjectName(service_id + "/transparencyValue"); m_transparency_value->setMinimumSize(m_transparency_value->sizeHint()); auto* const main_layout = new QVBoxLayout(); auto* const button_layout = new QHBoxLayout(); button_layout->addWidget(m_diffuse_colour_button, 0); button_layout->addWidget(m_ambient_colour_button, 0); main_layout->addLayout(button_layout, 0); auto* const transparency_layout = new QHBoxLayout(); transparency_layout->addWidget(transparency_label, 0); transparency_layout->addWidget(m_opacity_slider, 1); transparency_layout->addWidget(m_transparency_value, 0); main_layout->addLayout(transparency_layout, 0); qt_container->set_layout(main_layout); qt_container->set_enabled(false); QObject::connect(m_opacity_slider, &QSlider::valueChanged, this, &self_t::on_opacity_slider); QObject::connect(m_diffuse_colour_button, &QPushButton::clicked, this, &self_t::on_diffuse_color_button); QObject::connect(m_ambient_colour_button, &QPushButton::clicked, this, &self_t::on_ambient_color_button); this->updating(); } //------------------------------------------------------------------------------ void organ_material_editor::updating() { this->refresh_material(); } //------------------------------------------------------------------------------ void organ_material_editor::stopping() { QObject::disconnect(m_opacity_slider, &QSlider::valueChanged, this, &self_t::on_opacity_slider); QObject::disconnect(m_diffuse_colour_button, &QPushButton::clicked, this, &self_t::on_diffuse_color_button); QObject::disconnect(m_ambient_colour_button, &QPushButton::clicked, this, &self_t::on_ambient_color_button); this->destroy(); } //------------------------------------------------------------------------------ void organ_material_editor::on_diffuse_color_button() { data::material::sptr material; { SIGHT_ASSERT("The inout key '" << RECONSTRUCTION << "' is not defined.", !m_rec.expired()); auto reconstruction = m_rec.lock(); material = reconstruction->get_material(); } bool needrefresh = false; { data::mt::locked_ptr lock(material); int red = static_cast(material->diffuse()->red() * 255); int green = static_cast(material->diffuse()->green() * 255); int blue = static_cast(material->diffuse()->blue() * 255); // Create Color choice dialog. auto qt_container = std::dynamic_pointer_cast(this->get_container()); QWidget* const container = qt_container->get_qt_container(); SIGHT_ASSERT("container not instanced", container); const QColor old_color(red, green, blue); const QColor color = QColorDialog::getColor(old_color, container); if(color.isValid()) { material->diffuse()->red() = static_cast(color.redF()); material->diffuse()->green() = static_cast(color.greenF()); material->diffuse()->blue() = static_cast(color.blueF()); this->material_notification(); needrefresh = true; } } if(needrefresh) { refresh_material(); } } //------------------------------------------------------------------------------ void organ_material_editor::on_ambient_color_button() { data::material::sptr material; { SIGHT_ASSERT("The inout key '" << RECONSTRUCTION << "' is not defined.", !m_rec.expired()); auto reconstruction = m_rec.lock(); material = reconstruction->get_material(); } bool needrefresh = false; { data::mt::locked_ptr lock(material); const int red = static_cast(material->ambient()->red() * 255.F); const int green = static_cast(material->ambient()->green() * 255.F); const int blue = static_cast(material->ambient()->blue() * 255.F); // Create Color choice dialog. auto qt_container = std::dynamic_pointer_cast(this->get_container()); QWidget* const container = qt_container->get_qt_container(); SIGHT_ASSERT("container not instanced", container); const QColor old_color(red, green, blue); const QColor color = QColorDialog::getColor(old_color, container); if(color.isValid()) { material->ambient()->red() = static_cast(color.redF()); material->ambient()->green() = static_cast(color.greenF()); material->ambient()->blue() = static_cast(color.blueF()); this->material_notification(); needrefresh = true; } } if(needrefresh) { refresh_material(); } } //------------------------------------------------------------------------------ void organ_material_editor::on_opacity_slider(int _value) { data::material::sptr material; { SIGHT_ASSERT("The inout key '" << RECONSTRUCTION << "' is not defined.", !m_rec.expired()); auto reconstruction = m_rec.lock(); material = reconstruction->get_material(); } data::mt::locked_ptr lock(material); material->diffuse()->alpha() = static_cast(_value) / 100.F; std::stringstream ss; ss << _value << "%"; m_transparency_value->setText(QString::fromStdString(ss.str())); this->material_notification(); } //------------------------------------------------------------------------------ void organ_material_editor::refresh_material() { auto qt_container = std::dynamic_pointer_cast( this->get_container() ); QWidget* const container = qt_container->get_qt_container(); SIGHT_ASSERT("container not instanced", container); data::material::csptr material; { SIGHT_ASSERT("The inout key '" << RECONSTRUCTION << "' is not defined.", !m_rec.expired()); auto reconstruction = m_rec.lock(); container->setEnabled(!reconstruction->get_organ_name().empty()); material = reconstruction->get_material(); } int alpha = 0; { data::mt::locked_ptr lock(material); { const QColor material_diffuse_color = QColor( static_cast(material->diffuse()->red() * 255.F), static_cast(material->diffuse()->green() * 255.F), static_cast(material->diffuse()->blue() * 255.F), static_cast(material->diffuse()->alpha() * 255.F) ); const int icon_size = m_diffuse_colour_button->style()->pixelMetric(QStyle::PM_LargeIconSize); QPixmap pix(icon_size, icon_size); pix.fill(material_diffuse_color); m_diffuse_colour_button->setIcon(QIcon(pix)); } { const QColor material_ambient_color = QColor( static_cast(material->ambient()->red() * 255.F), static_cast(material->ambient()->green() * 255.F), static_cast(material->ambient()->blue() * 255.F), static_cast(material->ambient()->alpha() * 255.F) ); const int icon_size = m_ambient_colour_button->style()->pixelMetric(QStyle::PM_LargeIconSize); QPixmap pix(icon_size, icon_size); pix.fill(material_ambient_color); m_ambient_colour_button->setIcon(QIcon(pix)); } alpha = static_cast(material->diffuse()->alpha() * 100.F); } m_opacity_slider->setValue(alpha); std::stringstream ss; ss << alpha << "%"; m_transparency_value->setText(QString::fromStdString(ss.str())); } //------------------------------------------------------------------------------ void organ_material_editor::material_notification() { SIGHT_ASSERT("The inout key '" << RECONSTRUCTION << "' is not defined.", !m_rec.expired()); auto reconstruction = m_rec.lock(); data::object::modified_signal_t::sptr sig = reconstruction->get_material()->signal( data::object::MODIFIED_SIG ); sig->async_emit(); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::reconstruction sight-25.1.0/module/ui/qt/reconstruction/organ_material_editor.hpp000066400000000000000000000067661503402212300253760ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2019-2024 IRCAD France * Copyright (C) 2019-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include class QSlider; class QPushButton; class QLabel; namespace sight::module::ui::qt::reconstruction { /** * @brief Display a widget to change the reconstruction material (color and transparency). * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection In-Out In-Out * - \b reconstruction [sight::data::reconstruction]: reconstruction containing the material to update. */ class organ_material_editor : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(organ_material_editor, sight::ui::editor); /// Creates the service. organ_material_editor() noexcept; /// Destroys the service. ~organ_material_editor() noexcept override; private: /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::object::MODIFIED_SIG of s_RECONSTRUCTION_INOUT to service::slots::UPDATE */ connections_t auto_connections() const override; /// Configures the service. void configuring() final; /// Sets the connections and the UI elements. void starting() final; /// Updates the UI according to the material (color and transparency widgets). void updating() final; /// Destroys the connections and cleans the container. void stopping() final; /// Updates the UI according to the material (color and transparency widgets) void refresh_material(); /// Notifies the material changes. void material_notification(); QPointer m_diffuse_colour_button; QPointer m_ambient_colour_button; QPointer m_opacity_slider; QPointer m_transparency_value; static constexpr std::string_view RECONSTRUCTION = "reconstruction"; data::ptr m_rec {this, RECONSTRUCTION}; private Q_SLOTS: /** * @brief Slot: called when the opacity slider changed. * @param _value The new opacity value. */ void on_opacity_slider(int _value); /// Slot: called when the diffuse color button is clicked. void on_diffuse_color_button(); /// Slot: called when the ambient color button is clicked. void on_ambient_color_button(); }; } // namespace sight::module::ui::qt::reconstruction sight-25.1.0/module/ui/qt/reconstruction/representation_editor.cpp000066400000000000000000000337141503402212300254400ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/reconstruction/representation_editor.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::reconstruction { //------------------------------------------------------------------------------ representation_editor::representation_editor() noexcept = default; //------------------------------------------------------------------------------ representation_editor::~representation_editor() noexcept = default; //------------------------------------------------------------------------------ void representation_editor::starting() { this->create(); const QString service_id = QString::fromStdString(base_id()); auto qt_container = std::dynamic_pointer_cast(this->get_container()); qt_container->get_qt_container()->setObjectName(service_id); auto* layout = new QVBoxLayout(); auto* group_box = new QGroupBox(tr("Representation")); group_box->setObjectName(service_id + "/" + group_box->title()); auto* layout_group_box = new QVBoxLayout(); group_box->setLayout(layout_group_box); m_button_group = new QButtonGroup(group_box); m_button_group->setObjectName(service_id + "/buttonGroup"); auto* button_surface = new QRadioButton(tr("Surface"), group_box); button_surface->setObjectName(service_id + "/" + button_surface->text()); button_surface->setMinimumSize(button_surface->sizeHint()); m_button_group->addButton(button_surface, 0); layout_group_box->addWidget(button_surface); button_surface->setChecked(true); auto* button_point = new QRadioButton(tr("Point"), group_box); button_point->setObjectName(service_id + "/" + button_point->text()); button_point->setMinimumSize(button_point->sizeHint()); m_button_group->addButton(button_point, 1); layout_group_box->addWidget(button_point); auto* button_wireframe = new QRadioButton(tr("Wireframe"), group_box); button_wireframe->setObjectName(service_id + "/" + button_wireframe->text()); button_wireframe->setMinimumSize(button_wireframe->sizeHint()); m_button_group->addButton(button_wireframe, 2); layout_group_box->addWidget(button_wireframe); auto* button_edge = new QRadioButton(tr("Edge"), group_box); button_edge->setObjectName(service_id + "/" + button_edge->text()); button_edge->setMinimumSize(button_edge->sizeHint()); m_button_group->addButton(button_edge, 3); layout_group_box->addWidget(button_edge); // shading group box auto* group_box_shading = new QGroupBox(tr("shading")); group_box_shading->setObjectName(service_id + "/" + group_box_shading->title()); auto* layout_group_box_shading = new QVBoxLayout(); group_box_shading->setLayout(layout_group_box_shading); m_button_group_shading = new QButtonGroup(group_box_shading); m_button_group_shading->setObjectName(service_id + "/buttonGroupShading"); auto* button_ambient = new QRadioButton(tr("Ambient"), group_box_shading); button_ambient->setObjectName(service_id + "/" + button_ambient->text()); button_ambient->setMinimumSize(button_ambient->sizeHint()); m_button_group_shading->addButton(button_ambient, 0); layout_group_box_shading->addWidget(button_ambient); button_ambient->setChecked(true); auto* button_flat = new QRadioButton(tr("Flat"), group_box_shading); button_flat->setObjectName(service_id + "/" + button_flat->text()); button_flat->setMinimumSize(button_flat->sizeHint()); m_button_group_shading->addButton(button_flat, 1); layout_group_box_shading->addWidget(button_flat); button_flat->setChecked(true); auto* button_phong = new QRadioButton(tr("Phong"), group_box_shading); button_phong->setObjectName(service_id + "/" + button_phong->text()); button_phong->setMinimumSize(button_phong->sizeHint()); m_button_group_shading->addButton(button_phong, 2); layout_group_box_shading->addWidget(button_phong); layout->addWidget(group_box); layout->addWidget(group_box_shading); auto* group_box_normals = new QGroupBox(tr("Normals")); group_box_normals->setObjectName(service_id + "/" + group_box_normals->title()); auto* layout_group_box_normals = new QVBoxLayout(group_box_normals); m_options_radio_box = new QButtonGroup(); m_options_radio_box->setObjectName(service_id + "/normalsRadioBox"); auto* point_normals_button = new QRadioButton(tr("Show point normals")); point_normals_button->setObjectName(service_id + "/" + point_normals_button->text()); auto* cell_normals_button = new QRadioButton(tr("Show cell normals")); cell_normals_button->setObjectName(service_id + "/" + cell_normals_button->text()); auto* selected_button = new QRadioButton(tr("Selected")); selected_button->setObjectName(service_id + "/" + selected_button->text()); auto* hide_normals_button = new QRadioButton(tr("Hide normals")); hide_normals_button->setObjectName(service_id + "/" + hide_normals_button->text()); m_options_radio_box->addButton(point_normals_button, 1); m_options_radio_box->addButton(cell_normals_button, 2); m_options_radio_box->addButton(selected_button, 3); m_options_radio_box->addButton(hide_normals_button, 0); layout_group_box_normals->addWidget(point_normals_button); layout_group_box_normals->addWidget(cell_normals_button); layout_group_box_normals->addWidget(selected_button); layout_group_box_normals->addWidget(hide_normals_button); layout->addWidget(group_box_normals); QObject::connect(m_options_radio_box, &QButtonGroup::idClicked, this, &self_t::on_show_options); qt_container->set_layout(layout); qt_container->set_enabled(false); QObject::connect(m_button_group, &QButtonGroup::idClicked, this, &self_t::on_change_representation); QObject::connect(m_button_group_shading, &QButtonGroup::idClicked, this, &self_t::on_change_shading); this->updating(); } //------------------------------------------------------------------------------ void representation_editor::stopping() { QObject::disconnect(m_button_group, &QButtonGroup::idClicked, this, &self_t::on_change_representation); QObject::disconnect(m_button_group_shading, &QButtonGroup::idClicked, this, &self_t::on_change_shading); QObject::disconnect(m_options_radio_box, &QButtonGroup::idClicked, this, &self_t::on_show_options); this->destroy(); } //------------------------------------------------------------------------------ void representation_editor::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void representation_editor::updating() { auto qt_container = std::dynamic_pointer_cast( this->get_container() ); QWidget* const container = qt_container->get_qt_container(); SIGHT_ASSERT("container not instanced", container); SIGHT_ASSERT("The inout key '" << RECONSTRUCTION << "' is not defined.", !m_rec.expired()); auto reconstruction = m_rec.lock(); m_material = reconstruction->get_material(); container->setEnabled(!reconstruction->get_organ_name().empty()); this->refresh_representation(); this->refresh_options(); this->refresh_shading(); } //------------------------------------------------------------------------------ void representation_editor::on_change_representation(int _id) { data::material::representation_t selected_mode = data::material::surface; switch(_id) { case 1: selected_mode = data::material::point; break; case 2: selected_mode = data::material::wireframe; break; case 3: selected_mode = data::material::edge; break; default: // 0 or other selected_mode = data::material::surface; } m_material->set_representation_mode(selected_mode); this->notify_material(); } //------------------------------------------------------------------------------ void representation_editor::on_change_shading(int _id) { data::material::shading_t selected_mode = data::material::shading_t::phong; switch(_id) { case 0: selected_mode = data::material::shading_t::ambient; break; case 1: selected_mode = data::material::shading_t::flat; break; default: // 2 or other selected_mode = data::material::shading_t::phong; } m_material->set_shading_mode(selected_mode); this->notify_material(); } //------------------------------------------------------------------------------ void representation_editor::refresh_representation() { int representation_mode = m_material->get_representation_mode(); QAbstractButton* button = nullptr; switch(representation_mode) { case data::material::surface: button = m_button_group->button(0); button->setChecked(true); break; case data::material::point: button = m_button_group->button(1); button->setChecked(true); break; case data::material::wireframe: button = m_button_group->button(2); button->setChecked(true); break; case data::material::edge: button = m_button_group->button(3); button->setChecked(true); break; default: button = m_button_group->button(0); button->setChecked(true); } } //------------------------------------------------------------------------------ void representation_editor::refresh_shading() { auto shading_mode = static_cast(m_material->get_shading_mode()); QAbstractButton* button = nullptr; switch(shading_mode) { case data::material::shading_t::ambient: button = m_button_group_shading->button(0); button->setChecked(true); break; case data::material::shading_t::flat: button = m_button_group_shading->button(1); button->setChecked(true); break; case data::material::shading_t::phong: button = m_button_group_shading->button(2); button->setChecked(true); break; default: button = m_button_group_shading->button(2); button->setChecked(true); } } //------------------------------------------------------------------------------ void representation_editor::refresh_options() { QAbstractButton* button_hide = m_options_radio_box->button(0); button_hide->setChecked(m_material->get_options_mode() == data::material::standard); QAbstractButton* button_normals = m_options_radio_box->button(1); button_normals->setChecked(m_material->get_options_mode() == data::material::normals); QAbstractButton* selected = m_options_radio_box->button(2); selected->setChecked(m_material->get_options_mode() == data::material::selected); } //------------------------------------------------------------------------------ void representation_editor::on_show_options(int _state) { switch(_state) { case 0: m_material->set_options_mode(data::material::standard); break; case 1: m_material->set_options_mode(data::material::normals); break; case 2: m_material->set_options_mode(data::material::cells_normals); break; case 3: m_material->set_options_mode(data::material::selected); break; default: SIGHT_ASSERT("Invalid state: " << _state, false); } this->notify_material(); SIGHT_ASSERT("The inout key '" << RECONSTRUCTION << "' is not defined.", !m_rec.expired()); auto reconstruction = m_rec.lock(); // In VTK backend the normals is handled by the mesh and not by the material auto sig = reconstruction->signal( data::reconstruction::MESH_CHANGED_SIG ); sig->async_emit(reconstruction->get_mesh()); } //------------------------------------------------------------------------------ void representation_editor::notify_material() { SIGHT_ASSERT("The inout key '" << RECONSTRUCTION << "' is not defined.", !m_rec.expired()); auto reconstruction = m_rec.lock(); data::object::modified_signal_t::sptr sig; sig = reconstruction->get_material()->signal( data::object::MODIFIED_SIG ); sig->async_emit(); } //------------------------------------------------------------------------------ service::connections_t representation_editor::auto_connections() const { connections_t connections; connections.push(RECONSTRUCTION, data::object::MODIFIED_SIG, service::slots::UPDATE); return connections; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::reconstruction sight-25.1.0/module/ui/qt/reconstruction/representation_editor.hpp000066400000000000000000000064021503402212300254370ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include class QRadioButton; class QCheckBox; class QButtonGroup; class QAbstractButton; namespace sight::module::ui::qt::reconstruction { /** * @brief Display a widget to change the reconstruction representation (surface, point, edge, ...). * * @section XML XML Configuration * * @code{.xml} @endcode * @subsection In-Out In-Out * - \b reconstruction [sight::data::reconstruction]: reconstruction that will be updated */ class representation_editor : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(representation_editor, sight::ui::editor); /// Constructor. Do nothing. representation_editor() noexcept; /// Destructor. Do nothing. ~representation_editor() noexcept override; protected: /// Initialize the UI void starting() override; /// Clean the UI void stopping() override; /// Update the UI according to the reconstruction void updating() override; /// Do nothing. void configuring() override; /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection * * Connect Reconstruction::MODIFIED_SIG to this::service::slots::UPDATE */ connections_t auto_connections() const override; /// Notify the changes void notify_material(); protected Q_SLOTS: void on_change_representation(int _id); void on_change_shading(int _id); void on_show_options(int _state); private: void refresh_options(); void refresh_representation(); void refresh_shading(); QPointer m_button_group; QPointer m_button_group_shading; QPointer m_options_radio_box; data::material::sptr m_material; static constexpr std::string_view RECONSTRUCTION = "reconstruction"; data::ptr m_rec {this, RECONSTRUCTION}; }; } // namespace sight::module::ui::qt::reconstruction sight-25.1.0/module/ui/qt/selection_menu_button.cpp000066400000000000000000000125361503402212300223520ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "selection_menu_button.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { static const core::com::signals::key_t SELECTED_SIG = "selected"; static const core::com::slots::key_t SET_ENABLED_SIG = "set_enabled"; static const core::com::slots::key_t SENABLE_SIG = "enable"; static const core::com::slots::key_t DISABLE_SIG = "disable"; selection_menu_button::selection_menu_button() noexcept : m_sig_selected(new_signal(SELECTED_SIG)) { new_slot(SET_ENABLED_SIG, &selection_menu_button::set_enabled, this); new_slot(SENABLE_SIG, &selection_menu_button::enable, this); new_slot(DISABLE_SIG, &selection_menu_button::disable, this); } //------------------------------------------------------------------------------ void selection_menu_button::configuring() { this->initialize(); const auto& config = this->get_config(); m_text = config.get("text", m_text); m_tool_tip = config.get("toolTip", m_tool_tip); m_selection = config.get("selected", m_selection); const auto& items = config.get_child("items"); for(const auto& elem : boost::make_iterator_range(items.equal_range("item"))) { const auto txt = elem.second.get(".text"); const int value = elem.second.get(".value"); m_items.emplace_back(value, txt); } } //------------------------------------------------------------------------------ void selection_menu_button::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); m_drop_down_button = new QPushButton(QString::fromStdString(m_text)); m_drop_down_button->setToolTip(QString::fromStdString(m_tool_tip)); // m_dropDownButton->setMaximumWidth(40); m_p_drop_down_menu = new QMenu(); m_action_group = new QActionGroup(m_p_drop_down_menu); for(const auto& item : m_items) { auto* action = new QAction(QString::fromStdString(item.second), m_p_drop_down_menu); action->setCheckable(true); action->setData(QVariant(item.first)); m_action_group->addAction(action); m_p_drop_down_menu->addAction(action); if(item.first == m_selection) { action->setChecked(true); } } QObject::connect(m_action_group, &QActionGroup::triggered, this, &self_t::on_selection); m_drop_down_button->setMenu(m_p_drop_down_menu); auto* v_layout = new QVBoxLayout(); v_layout->addWidget(m_drop_down_button); v_layout->setContentsMargins(0, 0, 0, 0); qt_container->set_layout(v_layout); } //------------------------------------------------------------------------------ void selection_menu_button::stopping() { QObject::disconnect(m_action_group, &QActionGroup::triggered, this, &self_t::on_selection); for(QAction* action : m_action_group->actions()) { m_action_group->removeAction(action); } this->destroy(); } //------------------------------------------------------------------------------ void selection_menu_button::updating() { } //------------------------------------------------------------------------------ void selection_menu_button::info(std::ostream& /*_sstream*/) { } //------------------------------------------------------------------------------ void selection_menu_button::on_selection(QAction* _action) { if(_action->isChecked()) { int value = _action->data().toInt(); m_sig_selected->async_emit(value); return; } } //------------------------------------------------------------------------------ void selection_menu_button::set_enabled(bool _enabled) { m_drop_down_button->setEnabled(_enabled); } //------------------------------------------------------------------------------ void selection_menu_button::enable() { this->set_enabled(true); } //------------------------------------------------------------------------------ void selection_menu_button::disable() { this->set_enabled(false); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/selection_menu_button.hpp000066400000000000000000000104311503402212300223470ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include class QMenu; class QActionGroup; class QPushButton; namespace sight::module::ui::qt { /** * @brief This service show a menu button. The user can select one item in the menu. * * When the user select an item, the signal \c selected(int selection) is emitted: it sends the value of the selected * item. * * @note This service doesn't modify its associated object, so it can work on any type of object. * * @section XML Example of configuration * @code{.xml} ... ... 2 @endcode * * - \b text (optional, default ">"): text displayed on the button * - \b toolTip (optional): button tool tip * - \b items: list of the menu items * - \b item: one item * - \b text: the text displayed in the menu * - \b value: the value emitted when the item is selected * - \b selected: the value of the item selected by default * * @section Signal Signal * - \b selected(int): This signal is emitted when the user select an item. Sends the item value. * * @section Slots Slots * - \b set_enabled(bool): This slots allows to enable/disable the button * - \b enable(): This slot allows to enable the button * - \b disable(): This slots allows to disable the button */ class selection_menu_button : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(selection_menu_button, sight::ui::editor); selection_menu_button() noexcept; ~selection_menu_button() noexcept override = default; protected: /** * @brief Install the layout. */ void starting() override; /** * @brief Destroy the layout. */ void stopping() override; /// Do nothing void updating() override; /// Configure the editor. void configuring() override; /// Overrides void info(std::ostream& _sstream) override; protected Q_SLOTS: /// This method is called when the popup menu is clicked. Notify the selection changed. void on_selection(QAction* _action); private: /** * @name Signals * @{ */ /// Signal emitted when an item is selected using selected_signal_t = core::com::signal; selected_signal_t::sptr m_sig_selected; /** * @} */ /** * @name Slots * @{ */ /// Slot: enable/disable the button void set_enabled(bool _enabled) override; /// Slot: enable the button void enable() override; /// Slot: disable the button void disable() override; /** * @} */ std::string m_text {">"}; ///< Text displayed on the button std::string m_tool_tip; ///< Tool tip displayed on the button using item_t = std::pair; using item_container_t = std::vector; item_container_t m_items; QPointer m_p_drop_down_menu; QPointer m_drop_down_button; QPointer m_action_group; int m_selection {0}; }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/series/000077500000000000000000000000001503402212300165255ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/series/select_dialog.cpp000066400000000000000000000057441503402212300220410ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2022-2023 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "select_dialog.hpp" #include namespace sight::module::ui::qt::series { const core::com::signals::key_t select_dialog::IMAGE_SELECTED_SIG = "image_selected"; const core::com::signals::key_t select_dialog::MODEL_SELECTED_SIG = "model_selected"; //------------------------------------------------------------------------------ select_dialog::select_dialog() { new_signal(IMAGE_SELECTED_SIG); new_signal(MODEL_SELECTED_SIG); } //------------------------------------------------------------------------------ void select_dialog::configuring() { } //------------------------------------------------------------------------------ void select_dialog::starting() { } //----------------------------------------------------------------------------- void select_dialog::updating() { const auto series_set = m_series_set.lock(); SIGHT_THROW_IF("Missing input database series", !series_set); SIGHT_THROW_IF("The series set is empty, nothing can be extracted.", series_set->empty()); // TODO: Prompt later to select the element to extract, now just take the first element SIGHT_INFO( "[select_dialog] Extracting the first element of the seriesBD, future development will prompt" " the user to pick a series" ); auto first_element = series_set->front(); if(auto model_series = std::dynamic_pointer_cast(first_element); model_series) { m_model_series = model_series; auto sig = this->signal(MODEL_SELECTED_SIG); sig->async_emit(); } else if(auto image_series = std::dynamic_pointer_cast(first_element); image_series) { m_image = image_series; auto sig = this->signal(IMAGE_SELECTED_SIG); sig->async_emit(); } } //----------------------------------------------------------------------------- void select_dialog::stopping() { } //----------------------------------------------------------------------------- } // namespace sight::module::ui::qt::series sight-25.1.0/module/ui/qt/series/select_dialog.hpp000066400000000000000000000065531503402212300220450ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2022-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include namespace sight::module::ui::qt::series { /** * @brief This service is used to select a series from a series set and then extract it to the appropriate typed data. * * This service is a work in progress and will be updated in Sight 22.0 to pop-up a dialog to let the user select the * series. * * The output objects must be marked as "deferred" in the XML configuration. * * @section Signals Signals * - \b image_selected(): Emitted when an image is selected. * - \b model_selected(): Emitted when a model is selected. * * @section XML XML Configuration * * @code{.xml} @endcode * @subsection Input Input * - \b series_set [sight::data::series_set]: database series from which the series extracted. * * @subsection Output Output * - \b modelSeries [sight::data::model_series]: output model series extracted from the database series. * - \b imageSeries [sight::data::image_series]: output image series extracted from the database series. * - \b image [sight::data::image]: output image extracted from the database series. */ class select_dialog : public service::controller { public: /// Type of signal sent when a screen is selected. using selected_signal_t = core::com::signal; static const core::com::signals::key_t IMAGE_SELECTED_SIG; static const core::com::signals::key_t MODEL_SELECTED_SIG; SIGHT_DECLARE_SERVICE(select_dialog, service::controller); /// Constructor select_dialog(); /// Destructor ~select_dialog() override = default; protected: /// Does nothing void starting() override; /// Configure the service void configuring() override; /// Does nothing void stopping() override; /// Extract the object(s) void updating() override; private: sight::data::ptr m_series_set {this, "series_set"}; sight::data::ptr m_model_series {this, "model_series"}; sight::data::ptr m_image {this, "image"}; }; } // namespace sight::module::ui::qt::series sight-25.1.0/module/ui/qt/series/selector.cpp000066400000000000000000000235671503402212300210660ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "selector.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::series { //------------------------------------------------------------------------------ static const core::com::signals::key_t SERIES_DOUBLE_CLICKED_SIG = "series_double_clicked"; static const core::com::slots::key_t ADD_SERIES_SLOT = "addSeries"; static const core::com::slots::key_t REMOVE_SERIES_SLOT = "removeSeries"; static const std::string SELECTION_MODE_CONFIG = "selectionMode"; static const std::string ALLOWED_REMOVE_CONFIG = "allowedRemove"; static const std::string INSERT_MODE_CONFIG = "insertMode"; static const std::string REMOVE_STUDY_ICON_CONFIG = "removeStudyIcon"; static const std::string REMOVE_SERIE_ICON_CONFIG = "removeSerieIcon"; static const std::string DISPLAYED_COLUMN_CONFIG = "displayedColumns"; //------------------------------------------------------------------------------ selector::selector() { m_sig_series_double_clicked = new_signal(SERIES_DOUBLE_CLICKED_SIG); new_slot(ADD_SERIES_SLOT, &selector::add_series, this); m_slot_remove_series = new_slot(REMOVE_SERIES_SLOT, &selector::remove_series, this); } //------------------------------------------------------------------------------ selector::~selector() noexcept = default; //------------------------------------------------------------------------------ void selector::configuring() { this->sight::ui::service::initialize(); const config_t tree = this->get_config(); if(const auto& icons = tree.get_child_optional("icons"); icons.has_value()) { for(const auto& elt : boost::make_iterator_range(icons->equal_range("icon"))) { const auto series = elt.second.get(".series"); SIGHT_ASSERT("'series' attribute is missing", !series.empty()); const auto icon = elt.second.get(".icon"); SIGHT_ASSERT("'icon' attribute is missing", !icon.empty()); const auto file = core::runtime::get_resource_file_path(icon); m_series_icons[series] = file.string(); } } const auto config = tree.get_child_optional("config"); if(config) { const auto config_attr = config->get_child_optional(""); const auto remove_study_icon_cfg = config_attr->get_optional(REMOVE_STUDY_ICON_CONFIG); if(remove_study_icon_cfg) { m_remove_study_icon = core::runtime::get_module_resource_file_path(remove_study_icon_cfg.value()); } const auto remove_serie_icon_cfg = config_attr->get_optional(REMOVE_SERIE_ICON_CONFIG); if(remove_serie_icon_cfg) { m_remove_series_icon = core::runtime::get_module_resource_file_path(remove_serie_icon_cfg.value()); } const auto selection_mode = config_attr->get(SELECTION_MODE_CONFIG, "extended"); if(selection_mode == "single") { m_selection_mode = QAbstractItemView::SingleSelection; } else if(selection_mode == "extended") { m_selection_mode = QAbstractItemView::ExtendedSelection; } else { SIGHT_WARN( std::string("value ") + selection_mode + " is not managed for '" + SELECTION_MODE_CONFIG + "'" ); } m_remove_allowed = config_attr->get(ALLOWED_REMOVE_CONFIG, m_remove_allowed); m_insert_mode = config_attr->get(INSERT_MODE_CONFIG, m_insert_mode); m_displayed_columns = config_attr->get(DISPLAYED_COLUMN_CONFIG, m_displayed_columns); } } //------------------------------------------------------------------------------ void selector::starting() { this->sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); m_selector_widget = new sight::ui::qt::series::selector(m_displayed_columns); m_selector_widget->set_series_icons(m_series_icons); m_selector_widget->setSelectionMode(m_selection_mode); m_selector_widget->allow_remove(m_remove_allowed); m_selector_widget->set_insert_mode(m_insert_mode); m_selector_widget->set_remove_study_icon(m_remove_study_icon); m_selector_widget->set_remove_series_icon(m_remove_series_icon); auto* layout = new QVBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_selector_widget); qt_container->set_layout(layout); QObject::connect( m_selector_widget, SIGNAL( series_selected( QVector, QVector ) ), this, SLOT( on_selected_series( QVector, QVector ) ) ); if(!m_insert_mode) { QObject::connect( m_selector_widget, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(on_double_click(const QModelIndex&)) ); } if(m_remove_allowed) { QObject::connect( m_selector_widget, SIGNAL(remove_series(QVector)), this, SLOT(on_remove_series(QVector)) ); } this->updating(); } //------------------------------------------------------------------------------ service::connections_t selector::auto_connections() const { connections_t connections; connections.push(SERIES_SET,data::series_set::ADDED_OBJECTS_SIG,ADD_SERIES_SLOT); connections.push(SERIES_SET,data::series_set::REMOVED_OBJECTS_SIG,REMOVE_SERIES_SLOT); return connections; } //------------------------------------------------------------------------------ void selector::updating() { const auto series_set = m_series_set.lock(); m_selector_widget->clear(); for(const auto& series : *series_set) { m_selector_widget->add_series(series); } } //------------------------------------------------------------------------------ void selector::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void selector::on_selected_series( QVector _selection, QVector _deselection ) { const auto selection_vector = m_selection.lock(); const auto scoped_emitter = selection_vector->scoped_emit(); for(const auto& series : _deselection) { selection_vector->remove(series); } for(const auto& series : _selection) { selection_vector->push_back(series); } } //------------------------------------------------------------------------------ void selector::on_double_click(const QModelIndex& _index) { m_selector_widget->clearSelection(); m_selector_widget->setCurrentIndex(_index); const auto selection_vector = m_selection.lock(); if(m_selector_widget->get_item_type(_index) == sight::ui::qt::series::selector_model::series) { SIGHT_ASSERT("There must be only one object selected",selection_vector->size() == 1); data::object::sptr obj = selection_vector->front(); data::series::sptr series = std::dynamic_pointer_cast(obj); SIGHT_ASSERT("Object must be a 'data::series'",series); m_sig_series_double_clicked->async_emit(series); } } //------------------------------------------------------------------------------ void selector::on_remove_series(QVector _selection) { const auto series_set = m_series_set.lock(); auto sig = series_set->signal(data::series_set::REMOVED_OBJECTS_SIG); core::com::connection::blocker block(sig->get_connection(m_slot_remove_series)); { const auto scoped_emitter = series_set->scoped_emit(); for(const auto& series : _selection) { series_set->remove(series); } } } //------------------------------------------------------------------------------ void selector::add_series(data::series_set::container_t _added_series) { for(const auto& series : _added_series) { m_selector_widget->add_series(series); } } //------------------------------------------------------------------------------ void selector::remove_series(data::series_set::container_t _removed_series) { for(const auto& series : _removed_series) { m_selector_widget->remove_series(series); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::series sight-25.1.0/module/ui/qt/series/selector.hpp000066400000000000000000000156501503402212300210650ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::series { /** * @brief This editor shows information about the medical data. It allows to manipulate (select, erase, ...) * studies and series. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection In-Out In-Out * - \b seriesSet [sight::data::series_set]: series_set on which the editor operates. * - \b selection [sight::data::vector]: defines the id of the data::vector where the selection will be put or get. * * @subsection Configuration Configuration * - \b selectionMode (optional, single/extended, default=extended): defines the selection mode for the series, where * extended means "multiple". * - \b allowedRemove (optional, bool, default=true): allows user to remove series. * - \b insertMode (optional, bool, default=false): only allows selection of module::ui::qt::InsertSeries. * - \b removeStudyIcon (optional, string, default=""): remove study button icon. * - \b removeSerieIcon (optional, string, default=""): remove series button icon. * - \b icons (optional): defines the icon to associate for a series. * - \b series (mandatory, string): series name, e.g. {data::image_series, data::model_series, ...}. * - \b icon (mandatory, string): icon path. */ class selector final : public QObject, public sight::ui::editor { Q_OBJECT public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(selector, sight::ui::editor); /// Creates the signal and slots. selector(); /// Destroys the service. ~selector() noexcept override; protected: /// Configures the service according to the xml tags found. void configuring() override; /// Creates container and add selector. void starting() override; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::series_set::ADDED_OBJECTS_SIG of s_SERIES_SET_INOUT to ADD_SERIES_SLOT * Connect data::series_set::REMOVED_OBJECTS_SIG of s_SERIES_SET_INOUT to REMOVE_SERIES_SLOT */ connections_t auto_connections() const override; /// Fills selector with the series contained in series_set. void updating() override; /// Destroys GUI. void stopping() override; protected Q_SLOTS: /** * @brief Manages the selection vector according to selection/deselection. * @param _selection series to add in selection vector. * @param _deselection series to remove from selection vector. */ void on_selected_series( QVector _selection, QVector _deselection ); /** * @brief Sends a 'seriesDoubleClicked' signal when the user double click on a series. * @param _index index of the clicked item in the selector. * @todo Manages double click on a study. */ void on_double_click(const QModelIndex& _index); /** * @brief Removes series from series_set and notify. * @param _selection series to remove from series_set. */ void on_remove_series(QVector _selection); private: using remove_series_slot_t = core::com::slot; using series_double_clicked_signal_t = core::com::signal)>; /// SLOT: adds series into the selector. void add_series(data::series_set::container_t _added_series); /// SLOT: removes series from the selector. void remove_series(data::series_set::container_t _removed_series); /// Contains the slot used to remove series from the selector. remove_series_slot_t::sptr m_slot_remove_series; /// Contains the selector widget. QPointer m_selector_widget {nullptr}; /// Contains the signal emitted when there is a double click on a series. series_double_clicked_signal_t::sptr m_sig_series_double_clicked {nullptr}; /// Stores a map containing the specified icons for a series (map\). sight::ui::qt::series::selector::series_icon_t m_series_icons; /// Defines if series can be removed. bool m_remove_allowed {true}; /// Defines the behaviour of the treeview selection mode. QAbstractItemView::SelectionMode m_selection_mode {QAbstractItemView::ExtendedSelection}; /// Allows selection of module::ui::qt::InsertSeries only. bool m_insert_mode {false}; /// Defines the path of the remove study button icon. std::filesystem::path m_remove_study_icon; /// Defines the path of the remove series button icon. std::filesystem::path m_remove_series_icon; /// Defines the columns to be displayed in the widget std::string m_displayed_columns = "PatientName/SeriesInstanceUID,PatientSex,PatientBirthDate/Icon,Modality,StudyDescription/SeriesDescription,StudyDate/SeriesDate,StudyTime/SeriesTime,PatientAge,BodyPartExamined,PatientPositionString,ContrastBolusAgent,AcquisitionTime,ContrastBolusStartTime"; static constexpr std::string_view SERIES_SET = "seriesSet"; static constexpr std::string_view SELECTION = "selection"; data::ptr m_series_set {this, SERIES_SET}; data::ptr m_selection {this, SELECTION}; }; } // namespace sight::module::ui::qt::series sight-25.1.0/module/ui/qt/series/viewer.cpp000066400000000000000000000126241503402212300205370ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "viewer.hpp" #include #include #include #include namespace sight::module::ui::qt::series { //------------------------------------------------------------------------------ void viewer::info(std::ostream& _sstream) { // Update message _sstream << std::string("viewer"); } //------------------------------------------------------------------------------ void viewer::starting() { this->updating(); } //------------------------------------------------------------------------------ void viewer::stopping() { if(m_config_template_manager) { m_config_template_manager->stop_and_destroy(); m_config_template_manager.reset(); } } //------------------------------------------------------------------------------ void viewer::updating() { if(m_config_template_manager) { m_config_template_manager->stop_and_destroy(); m_config_template_manager.reset(); } const auto vector = m_series.lock(); SIGHT_ASSERT("The input key '" << SERIES << "' is not defined.", vector); if(vector->size() == 1) { data::object::sptr obj = vector->front(); std::string classname = obj->get_classname(); auto itr = m_series_configs.find(classname); if(itr != m_series_configs.end()) { series_config_info info = itr->second; std::string config_id = info.config_id; std::map replace_map; // Generate generic UID std::string generic_uid_adaptor = app::extension::config::get_unique_identifier(this->get_id()); replace_map["GENERIC_UID"] = generic_uid_adaptor; replace_map["WID_PARENT"] = m_parent_view; replace_map["objectID"] = obj->get_id(); for(const auto& elt : info.parameters) { SIGHT_ASSERT( "Value '" << elt.first << "' already used in extracted values.", replace_map.find(elt.first) == replace_map.end() ); replace_map[elt.first] = elt.second; } // Init manager m_config_template_manager = app::config_manager::make(); m_config_template_manager->set_config(config_id, replace_map); // Launch config m_config_template_manager->launch(); } } } //------------------------------------------------------------------------------ void viewer::configuring() { const auto& config = this->get_config(); m_parent_view = config.get("parentView..wid"); const auto& configs = config.get_child("configs"); for(const auto& elt : boost::make_iterator_range(configs.equal_range("config"))) { series_config_info info; info.config_id = elt.second.get(".id", ""); SIGHT_ASSERT("'id' attribute must not be empty", !info.config_id.empty()); const std::string series_type = elt.second.get(".type", ""); SIGHT_ASSERT("'type' attribute must not be empty", !series_type.empty()); SIGHT_ASSERT( "Type " << series_type << " is already defined.", m_series_configs.find(series_type) == m_series_configs.end() ); for(const auto& param : boost::make_iterator_range(elt.second.equal_range("parameter"))) { const std::string replace = param.second.get(".replace", ""); SIGHT_ASSERT("'replace' attribute must not be empty", !replace.empty()); std::string by = param.second.get(".by", ""); if(by.empty()) { by = param.second.get(".uid", ""); } SIGHT_ASSERT("'by' attribute must not be empty", !by.empty()); info.parameters[replace] = by; } m_series_configs[series_type] = info; } } //------------------------------------------------------------------------------ service::connections_t viewer::auto_connections() const { connections_t connections; connections.push(SERIES, data::vector::ADDED_OBJECTS_SIG, service::slots::UPDATE); connections.push(SERIES, data::vector::REMOVED_OBJECTS_SIG, service::slots::UPDATE); return connections; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::series sight-25.1.0/module/ui/qt/series/viewer.hpp000066400000000000000000000112131503402212300205350ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include namespace sight::module::ui::qt::series { /** * @brief This Service allows to preview the selected series in the Vector. For the moment, it works only on a * single selection. * * @section XML XML Configuration * * @code{.xml} @endcode * @subsection Input Input * - \b series [sight::data::vector]: vector containing the series to preview. * @subsection Configuration Configuration * - \b parentView: wid of the view where the config will install its windows. * - \b config: gives the available association between data type and associated config. * - \b id: identifier of the config to launch * - \b type: classname of the object stored in Vector associated to this config. * - \b parameter: allow to pass specific value to the associated config * - \b replace: name of the parameter to be replaced * - \b by: specific value to replace for the parameter */ class viewer : public service::controller { public: SIGHT_DECLARE_SERVICE(viewer, service::controller); /// Constructor viewer() = default; /// Destructor ~viewer() noexcept override = default; protected: /// Calls updating on starting. void starting() override; /// Stops the config if it is running. void stopping() override; /// Configures the service. void configuring() override; /** * @brief Launch the config on the object if possible. * * If there is a single selection : it launches an config on the object defined in this service configuration * (stored in m_seriesConfigs). The selected object fwID replaces the 'objectID' parameter in the config. * no configuration are launched if there is no selection, a multiple selection or if there is no configuration * associated with the selected object. */ void updating() override; /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection * * Connect Vector::ADDED_OBJECTS_SIG to this::service::slots::UPDATE * Connect Vector::REMOVED_OBJECTS_SIG to this::service::slots::UPDATE */ connections_t auto_connections() const override; void info(std::ostream& _sstream) override; private: using replace_values_map_t = std::map; /// Stucture to register configuration informations. struct series_config_info { /// Id of the configuration to launch. std::string config_id; /// Stores the parameters to pass to config. replace_values_map_t parameters; }; using series_config_map_t = std::map; /// config manager app::config_manager::sptr m_config_template_manager; /// Stores the wid of the view where the config will install its windows. std::string m_parent_view; /// Stores the association between data type and associated configuration. series_config_map_t m_series_configs; static constexpr std::string_view SERIES = "series"; data::ptr m_series {this, SERIES}; }; } // namespace sight::module::ui::qt::series sight-25.1.0/module/ui/qt/settings.cpp000066400000000000000000003412131503402212300176030ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2024-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "settings.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { //------------------------------------------------------------------------------ inline static void set_minimum_size(QWidget* _widget, const settings::param_widget& _params) { if(_params.min_width.has_value()) { _widget->setMinimumWidth(std::max(_widget->minimumWidth(), *_params.min_width)); } if(_params.min_height.has_value()) { _widget->setMinimumHeight(std::max(_widget->minimumHeight(), *_params.min_height)); } } //----------------------------------------------------------------------------- settings::settings() noexcept { new_slot(slots::UPDATE_ENUM_RANGE, &settings::update_enum_range, this); new_slot(slots::UPDATE_INT_MIN_PARAMETER, &settings::update_int_min_parameter, this); new_slot(slots::UPDATE_INT_MAX_PARAMETER, &settings::update_int_max_parameter, this); new_slot(slots::UPDATE_DOUBLE_MIN_PARAMETER, &settings::update_double_min_parameter, this); new_slot(slots::UPDATE_DOUBLE_MAX_PARAMETER, &settings::update_double_max_parameter, this); } //----------------------------------------------------------------------------- void settings::configuring() { this->initialize(); } //----------------------------------------------------------------------------- void settings::starting() { this->block_signals(true); this->create(); const std::set is_text_widget { "text", "file", "dir", "file_read", "file_write", "dir_write", "dir_read" }; auto qt_container = std::dynamic_pointer_cast(this->get_container()); qt_container->get_qt_container()->setObjectName(QString::fromStdString(base_id())); QScrollArea* scroll_area = nullptr; service::config_t config = this->get_config(); const auto& ui_cfg = config.get_child("ui"); const bool scrollable = ui_cfg.get(".scrollable", false); const auto spacing = ui_cfg.get_optional(".spacing"); auto* layout = new QFormLayout; layout->setAlignment(Qt::AlignCenter); layout->setFormAlignment(Qt::AlignCenter); layout->setLabelAlignment(Qt::AlignVCenter | Qt::AlignLeft); layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); if(spacing) { layout->setVerticalSpacing(*spacing); } if(scrollable) { scroll_area = new QScrollArea(qt_container->get_qt_container()->parentWidget()); scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); auto* const viewport = new QWidget(qt_container->get_qt_container()); viewport->setLayout(layout); scroll_area->setWidgetResizable(true); scroll_area->setWidget(viewport); } // We don't support having multiple properties with the same key (this isn't a good idea any way). // This set keeps tracks of the ones we add, and triggers and assert if it already exists. [[maybe_unused]] std::set keys; const bool use_map = m_settings_map.const_lock() != nullptr; // Create widgets for(std::size_t data_index = 0 ; const auto& param : boost::make_iterator_range(ui_cfg.equal_range("item"))) { const service::config_t& cfg = param.second; const auto orientation = cfg.get(".orientation", "horizontal") == "horizontal" ? Qt::Horizontal : Qt::Vertical; const auto param_box_direction = orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; param_widget widget { .name = cfg.get(".name"), .reset_button = cfg.get(".reset", true), .hide_min_max = cfg.get(".hide_min_max", false), .min_width = cfg.get_optional(".min_width"), .min_height = cfg.get_optional(".min_height"), .use_index = cfg.get(".use_index", true) }; widget.data_index = data_index++; const std::string type = [&, this]() { sight::data::mt::locked_ptr map_lock; sight::data::mt::locked_ptr lock; sight::data::object::csptr obj; if(use_map) { widget.key = cfg.get(".key"); map_lock = m_settings_map.const_lock(); obj = map_lock->at(widget.key); } else { lock = m_settings[widget.data_index].const_lock(); obj = lock.get_shared(); widget.key = obj->base_id(); } auto serializable_data = std::dynamic_pointer_cast(obj); widget.default_value = serializable_data->to_string(); SIGHT_ERROR_IF( "No type should be defined for " << std::quoted(widget.key) << " when passing a data object. It will be ignored.", cfg.get_optional(".type").has_value() ); return obj->get_classname(); }(); SIGHT_ASSERT( get_id() << ": Key " << std::quoted(widget.key) << " already exists.", keys.insert(widget.key).second ); auto* const param_box = new QWidget; const auto qt_key = QString::fromStdString(widget.key) + "_box"; param_box->setProperty(qt_property::key, qt_key); param_box->setObjectName(qt_key); param_box->setContentsMargins(0, 0, 0, 0); if(orientation == Qt::Vertical) { param_box->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding); } else { param_box->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); } m_param_boxes.emplace_back(param_box); auto* const param_box_layout = new QBoxLayout {param_box_direction, param_box}; param_box_layout->setContentsMargins(0, 0, 0, 0); auto* const row_layout = new QHBoxLayout; row_layout->setContentsMargins(0, 0, 0, 0); if(orientation == Qt::Vertical) { row_layout->addWidget(param_box, 0, Qt::AlignCenter); } else { row_layout->addWidget(param_box, 0, Qt::AlignVCenter); } // Label if(!widget.name.empty()) { auto* const parameter_label = new QLabel(QString::fromStdString(widget.name)); parameter_label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); parameter_label->setWordWrap(true); parameter_label->setAlignment(Qt::AlignCenter); // When horizontal, the label is added to the "main" widget layout, otherwise, at the top of the box if(orientation == Qt::Vertical) { param_box_layout->addWidget(parameter_label, 0, Qt::AlignCenter); layout->addRow(row_layout); } else { layout->addRow(parameter_label, row_layout); } } else { layout->addRow(row_layout); } // If we have a reset button, we need to add it to the row layout QPushButton* reset = nullptr; bool created = false; // Just needed for integer type // Widget who is able to be controlled by a joystick if(const auto& joystick = to_joystick(cfg.get(".joystick", "")); joystick != sight::io::joystick::joystick_t::unknown) { std::vector axes; boost::split( axes, cfg.get(".joystick_axis", ""), boost::is_any_of(",; \t"), boost::token_compress_on ); m_widget_joysticks.insert_or_assign( widget.key, widget_joystick { .alias = joystick, .axis_1 = !axes.empty() ? to_axis(axes[0]) : sight::io::joystick::axis_t::unknown, .axis_2 = axes.size() > 1 ? to_axis(axes[1]) : sight::io::joystick::axis_t::unknown, .axis_3 = axes.size() > 2 ? to_axis(axes[2]) : sight::io::joystick::axis_t::unknown }); } if(type == "sight::data::boolean") { reset = this->create_bool_widget(param_box_layout, widget, orientation); } else if(type == "sight::data::color") { reset = this->create_color_widget(param_box_layout, widget); } else if(type == "sight::data::real" || type == "sight::data::dvec2" || type == "sight::data::dvec3") { const std::string widget_type = cfg.get(".widget", "spin"); const double min = cfg.get(".min", 0.); const double max = cfg.get(".max", 1.); const double_widget widget_double = {widget, min, max}; const int count = (type == "sight::data::dvec3") ? 3 : (type == "sight::data::dvec2" ? 2 : 1); if(widget_type == "spin") { reset = this->create_double_spin_widget(param_box_layout, widget_double, count, orientation); } else if(widget_type == "slider") { // TODO: this could be supported now SIGHT_ASSERT(get_id() << ": Count > 1 is not supported with sliders", count == 1); const std::uint8_t decimals = cfg.get(".decimals", 2); const bool on_release = cfg.get(".emit_on_release", false); reset = this->create_double_slider_widget( param_box_layout, widget_double, decimals, orientation, on_release ); if(orientation == Qt::Vertical) { param_box->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); } } else { SIGHT_ERROR( get_id() << ": Unknown widget type : " << std::quoted(widget_type) << " for " << std::quoted(widget.name) << ". Must be 'spin' or 'slider'." ); } } else if(type == "sight::data::integer" || type == "sight::data::ivec2" || type == "sight::data::ivec3") { const auto widget_type = cfg.get(".widget"); const int min = cfg.get(".min", 0); const int max = cfg.get(".max", 100); const int_widget widget_int = {widget, min, max}; const int count = (type == "sight::data::ivec3") ? 3 : (type == "sight::data::ivec2" ? 2 : 1); if(widget_type == "spin") { reset = this->create_integer_spin_widget(param_box_layout, widget_int, count, orientation); created = true; } else if(widget_type == "slider") { // TODO: this could be supported now SIGHT_ASSERT(get_id() << ": Count > 1 is not supported with sliders", count == 1); const bool on_release = cfg.get(".emit_on_release", false); reset = this->create_integer_slider_widget(param_box_layout, widget_int, orientation, on_release); if(orientation == Qt::Vertical) { param_box->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); } created = true; } else { SIGHT_ERROR_IF( get_id() << ": Unknown widget type : " << std::quoted(widget_type) << " for " << widget.name << ". Must be 'spin' or 'slider'.", type != "sight::data::integer" ); } } if(!created and (type == "sight::data::integer" or type == "sight::data::string")) { const auto widget_type = cfg.get(".widget", ""); if(widget_type == "combobox") { const auto options = cfg.get(".values"); // split values separated by ',', ' ', ';' std::vector values; std::vector data; sight::module::ui::qt::settings::parse_enum_string(options, values, data); this->create_enum_combobox_widget(param_box_layout, widget, values, data); } else if(widget_type == "comboslider") { const auto options = cfg.get(".values"); // split values separated by ',', ' ', ';' std::vector values; std::vector data; sight::module::ui::qt::settings::parse_enum_string(options, values, data); const bool on_release = cfg.get(".emit_on_release", false); this->create_enum_slider_widget(param_box_layout, widget, values, orientation, on_release); if(orientation == Qt::Vertical) { param_box->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); } } else if(widget_type == "buttonBar") { const int h_offset = cfg.get(".hOffset", 0); const int width = cfg.get(".width", 0); const int height = cfg.get(".height", 0); const std::string style = cfg.get(".style", "iconOnly"); const auto value_config = cfg.equal_range("item"); std::vector button_list; for(auto value_config_it = value_config.first ; value_config_it != value_config.second ; ++value_config_it) { const auto value = value_config_it->second.get(".value"); const auto label = value_config_it->second.get(".label", ""); const auto icon_path_relative = value_config_it->second.get(".icon", ""); const std::string icon_path = core::runtime::get_module_resource_file_path(icon_path_relative).generic_string(); button_list.push_back(enum_button_param({value, label, icon_path})); } this->create_enum_button_bar_widget( param_box_layout, widget, button_list, width, height, h_offset, style, orientation ); } else if(widget_type == "tickmarks") { const auto options = cfg.get(".values", ""); std::vector labels; std::vector data; sight::module::ui::qt::settings::parse_enum_string(options, labels, data); this->create_tickmarks_widget(param_box_layout, widget, labels); } else if(is_text_widget.contains(widget_type)) { reset = this->create_text_widget(param_box_layout, widget, widget_type); } else { SIGHT_ERROR("Unknown widget type for key: " + widget.key); } } if(reset != nullptr) { // Looks better under the rest if(orientation == Qt::Vertical) { param_box_layout->addWidget(reset, /*stretch = */ 0, Qt::AlignCenter); } else { row_layout->addWidget(reset); } } } for(std::size_t data_index = 0 ; const auto& param : boost::make_iterator_range(ui_cfg.equal_range("item"))) { const service::config_t& cfg = param.second; std::string key; sight::data::mt::locked_ptr map_lock; sight::data::mt::locked_ptr lock; sight::data::object::sptr obj; if(use_map) { key = cfg.get(".key"); map_lock = m_settings_map.const_lock(); obj = map_lock->at(key); } else { lock = m_settings[data_index].lock(); obj = lock.get_shared(); key = obj->base_id(); } const std::string depends = cfg.get(".depends", ""); const std::string depends_value = cfg.get(".depends_value", ""); const bool depends_reverse = cfg.get(".depends_reverse", false); if(!depends.empty()) { auto* const depends_widget = get_param_widget(depends); // Note: we disable the QGroupBox containing the widget**s** instead of individual ones, because // some parameter create several widgets (3 spinboxes, etc.) const auto widget_container_it = std::ranges::find_if( m_param_boxes, [qt_key = QString::fromStdString(key) + "_box"](const QWidget* const _w) { return _w->objectName() == qt_key; }); SIGHT_ASSERT( get_id() << ": unknown parameter " << std::quoted(key) << ".", widget_container_it != m_param_boxes.cend() ); if(auto* const widget = qobject_cast((*widget_container_it))) { widget->installEventFilter(this); auto* check_box = qobject_cast(depends_widget); if(check_box != nullptr) { QObject::connect( check_box, &QCheckBox::stateChanged, this, [ = ]{on_depends_changed(check_box, widget, depends_reverse);}); on_depends_changed(check_box, widget, depends_reverse); } else { auto* combo_box = qobject_cast(depends_widget); if(combo_box != nullptr) { QObject::connect( combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, [ = ]{on_depends_changed(combo_box, widget, depends_value, depends_reverse);}); on_depends_changed(combo_box, widget, depends_value, depends_reverse); } } } } ++data_index; } if(scroll_area != nullptr) { auto* main_layout = new QHBoxLayout(); main_layout->addWidget(scroll_area); main_layout->setContentsMargins(0, 0, 0, 0); qt_container->set_layout(main_layout); } else { qt_container->set_layout(layout); } this->start_listening_joystick(); this->block_signals(false); } //----------------------------------------------------------------------------- void settings::updating() { } //----------------------------------------------------------------------------- void settings::stopping() { this->stop_listening_joystick(); m_settings_slots.clear(); m_param_boxes.clear(); // Avoid keeping dangling pointers this->destroy(); } //----------------------------------------------------------------------------- bool settings::eventFilter(QObject* _watched, QEvent* _event) { if(_event->type() == ::QEvent::EnabledChange) { auto* check_box = qobject_cast(_watched); if(check_box != nullptr) { check_box->stateChanged(check_box->isChecked() ? Qt::Checked : Qt::Unchecked); } else { auto* combo_box = qobject_cast(_watched); if(combo_box != nullptr) { combo_box->currentIndexChanged(combo_box->currentIndex()); } } } return false; } //----------------------------------------------------------------------------- void settings::on_depends_changed(QCheckBox* _check_box, QWidget* _widget, bool _reverse) { if(!_check_box->isEnabled()) { _widget->setDisabled(true); } else if(_reverse) { _widget->setDisabled(_check_box->checkState() != 0U); } else { _widget->setEnabled(_check_box->checkState() != 0U); } } //------------------------------------------------------------------------------ void settings::on_depends_changed(QComboBox* _combo_box, QWidget* _widget, const std::string& _value, bool _reverse) { if(!_combo_box->isEnabled()) { _widget->setDisabled(true); } else if(_reverse) { _widget->setDisabled(_combo_box->currentText().toStdString() == _value); } else { _widget->setEnabled(_combo_box->currentText().toStdString() == _value); } } //------------------------------------------------------------------------------ void settings::on_color_button() { QObject* sender = this->sender(); // Create Color choice dialog. auto qt_container = std::dynamic_pointer_cast(this->get_container()); QWidget* const container = qt_container->get_qt_container(); SIGHT_ASSERT(get_id() << ": Container not instantiated yet.", container); const auto old_color = sender->property("color").value(); const QColor color_qt = QColorDialog::getColor( old_color, container, "pick a color", QColorDialog::ShowAlphaChannel ); if(color_qt.isValid()) { auto* colour_button = dynamic_cast(sender); colour_button->setProperty("color", color_qt); int icon_size = colour_button->style()->pixelMetric(QStyle::PM_LargeIconSize); QPixmap pix(icon_size, icon_size); pix.fill(color_qt); colour_button->setIcon(QIcon(pix)); sight::data::color::array_t color_array( {color_qt.redF(), color_qt.greenF(), color_qt.blueF(), color_qt.alphaF()}); update_data(sender, color_array); } } //----------------------------------------------------------------------------- void settings::on_change_integer(int _val) { QObject* sender = this->sender(); auto* slider = dynamic_cast(sender); auto* spinbox = dynamic_cast(sender); if(slider != nullptr) { update_data(sender, _val); } else if(spinbox != nullptr) { const int count = spinbox->property(qt_property::count).toInt(); SIGHT_ASSERT(get_id() << ": Invalid widgets count, must be <= 3", count <= 3); if(count == 1) { update_data(sender, _val); } else { const auto index = spinbox->property(qt_property::index).toUInt(); SIGHT_ASSERT(get_id() << ": Invalid widgets index, must be <= 3", index <= 3); if(count <= 2) { const auto obj = data(sender); auto value = obj->value(); value[index] = _val; update_data(sender, value); } else { const auto obj = data(sender); auto value = obj->value(); value[index] = _val; update_data(sender, value); } } } } //----------------------------------------------------------------------------- void settings::on_change_double(double _val) { QObject* sender = this->sender(); auto* slider = dynamic_cast(sender); auto* spinbox = dynamic_cast(sender); if(slider != nullptr) { update_data(sender, _val); } else if(spinbox != nullptr) { const int count = spinbox->property(qt_property::count).toInt(); SIGHT_ASSERT(get_id() << ": Invalid widgets count, must be <= 3", count <= 3); if(count == 1) { update_data(sender, _val); } else { const auto index = spinbox->property(qt_property::index).toUInt(); SIGHT_ASSERT(get_id() << ": Invalid widgets index, must be <= 3", index <= 3); if(count <= 2) { const auto obj = data(sender); auto value = obj->value(); value[index] = _val; update_data(sender, value); } else { const auto obj = data(sender); auto value = obj->value(); value[index] = _val; update_data(sender, value); } } } } //----------------------------------------------------------------------------- void settings::on_change_double_slider(int /*unused*/) { auto* slider = qobject_cast(this->sender()); const double value = sight::module::ui::qt::settings::get_double_slider_value(slider); update_data(slider, static_cast(value)); } //----------------------------------------------------------------------------- void settings::on_slider_mapped(QLabel* _label, QSlider* _slider) { _label->setText(QString::number(_slider->value())); } //----------------------------------------------------------------------------- void settings::on_slider_range_mapped(QLabel* _min_label, QLabel* _max_label, QSlider* _slider) { const int min = _slider->minimum(); const int max = _slider->maximum(); _min_label->setText(QString::number(min)); _max_label->setText(QString::number(max)); } //----------------------------------------------------------------------------- void settings::on_double_slider_mapped(QLabel* _label, QSlider* _slider) { const double new_value = get_double_slider_value(_slider); const int decimals = _slider->property("decimals").toInt(); _label->setText(QString::number(new_value, 'f', decimals)); } //----------------------------------------------------------------------------- void settings::on_double_slider_range_mapped(QLabel* _min_label, QLabel* _max_label, QSlider* _slider) { const double min = _slider->property("min").toDouble(); const double max = _slider->property("max").toDouble(); const int decimals = _slider->property("decimals").toInt(); _min_label->setText(QString::number(min, 'g', decimals)); _max_label->setText(QString::number(max, 'g', decimals)); } //----------------------------------------------------------------------------- void settings::on_reset_boolean(QWidget* _widget) { auto* checkbox = qobject_cast(_widget); if(checkbox != nullptr) { const auto value = data(checkbox)->default_value(); checkbox->setCheckState(value ? Qt::Checked : Qt::Unchecked); update_data(_widget, value); } } //----------------------------------------------------------------------------- void settings::on_reset_color(QWidget* _widget) { auto* colour_button = qobject_cast(_widget); if(colour_button != nullptr) { const auto color = data(colour_button)->default_value(); QColor color_qt; color_qt.setRgbF(color[0], color[1], color[2], color[3]); int icon_size = colour_button->style()->pixelMetric(QStyle::PM_LargeIconSize); QPixmap pix(icon_size, icon_size); pix.fill(color_qt); colour_button->setIcon(QIcon(pix)); colour_button->setProperty("color", color_qt); update_data(_widget, color); } } //----------------------------------------------------------------------------- void settings::on_reset_integer(QWidget* _widget) { auto* slider = qobject_cast(_widget); auto* spinbox = qobject_cast(_widget); if(slider != nullptr) { const auto value = data(slider)->default_value(); slider->setValue(static_cast(value)); update_data(_widget, value); } else if(spinbox != nullptr) { const int count = spinbox->property(qt_property::count).toInt(); SIGHT_ASSERT(get_id() << ": Invalid widgets count, must be <= 3", count <= 3); auto* spin1 = spinbox->property("widget#0").value(); if(count == 1) { const auto value1 = data(spin1)->default_value(); spin1->setValue(static_cast(value1)); update_data(_widget, spin1->value()); } else { auto* spin2 = spinbox->property("widget#1").value(); if(count <= 2) { const auto value = data(spin2)->default_value(); spin1->setValue(static_cast(value[0])); spin2->setValue(static_cast(value[1])); update_data(_widget, value); } else { auto* spin3 = spinbox->property("widget#2").value(); const auto value = data(spin3)->default_value(); spin1->setValue(static_cast(value[0])); spin2->setValue(static_cast(value[1])); spin3->setValue(static_cast(value[2])); update_data(_widget, value); } } } } //----------------------------------------------------------------------------- void settings::on_reset_double(QWidget* _widget) { auto* spinbox = qobject_cast(_widget); auto* slider = qobject_cast(_widget); if(slider != nullptr) { const double value = data(slider)->default_value(); const double min = slider->property("min").toDouble(); const double max = slider->property("max").toDouble(); const double value_range = max - min; const int slider_val = int(std::round(((value - min) / value_range) * double(slider->maximum()))); slider->setValue(slider_val); update_data(_widget, value); } else if(spinbox != nullptr) { const unsigned int count = spinbox->property(qt_property::count).toUInt(); SIGHT_ASSERT(get_id() << ": Invalid widgets count, must be <= 3", count <= 3); auto* spin1 = spinbox->property("widget#0").value(); if(count == 1) { const double value = data(spin1)->default_value(); spin1->setValue(value); update_data(_widget, value); } else { auto* spin2 = spinbox->property("widget#1").value(); if(count <= 2) { const auto value = data(spin2)->default_value(); spin1->setValue(value[0]); spin2->setValue(value[1]); update_data(_widget, value); } else { auto* spin3 = spinbox->property("widget#2").value(); const auto value = data(spin3)->default_value(); spin1->setValue(value[0]); spin2->setValue(value[1]); spin3->setValue(value[2]); update_data(_widget, value); } } } } //----------------------------------------------------------------------------- void settings::on_reset_string(QWidget* _widget) { auto* edit = qobject_cast(_widget); if(edit != nullptr) { const auto value = data(edit)->default_value(); edit->setText(QString::fromStdString(value)); update_data(_widget, value); } } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* settings::create_reset_button(const std::string& _key, std::function _on_click) const { std::string service_id = base_id(); auto* reset_button = new QPushButton("R"); reset_button->setObjectName(QString::fromStdString(service_id + "/Reset " + _key)); reset_button->setFocusPolicy(Qt::NoFocus); reset_button->setToolTip("Reset to the default value."); reset_button->setMaximumWidth(20); reset_button->setStyleSheet(qApp->styleSheet()); QObject::connect(reset_button, &QPushButton::clicked, this, _on_click); return reset_button; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* settings::create_bool_widget(QBoxLayout* _layout, const param_widget& _setup, Qt::Orientation _orientation) { auto* checkbox = new QCheckBox(); // Base properties const auto key = QString::fromStdString(_setup.key); checkbox->setObjectName(key); checkbox->setProperty(qt_property::key, key); checkbox->setProperty(qt_property::data_index, static_cast(_setup.data_index)); // Data const auto obj = data(checkbox); const auto init_value = obj->value(); checkbox->setCheckState(init_value ? Qt::Checked : Qt::Unchecked); connect_data(obj, _setup.key); // Style checkbox->setTristate(false); checkbox->setStyleSheet(qApp->styleSheet()); if(_orientation == Qt::Vertical) { _layout->addWidget(checkbox, 0, Qt::AlignCenter); } else { _layout->addWidget(checkbox, 0, Qt::AlignLeft | Qt::AlignVCenter); } // Forward to the Sight signal QObject::connect( checkbox, &QCheckBox::stateChanged, [this, key = _setup.key, checkbox](int _value) { const bool checked = _value == Qt::Checked; update_data(checkbox, checked); }); // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, checkbox](){on_reset_boolean(checkbox);}); } return nullptr; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* settings::create_color_widget(QBoxLayout* _layout, const param_widget& _setup) { auto* colour_button = new QPushButton(); { // Base properties colour_button->setObjectName(QString::fromStdString(_setup.key)); colour_button->setProperty(qt_property::key, QString::fromStdString(_setup.key)); colour_button->setToolTip(tr("Selected color")); colour_button->setProperty(qt_property::data_index, static_cast(_setup.data_index)); // Data const auto obj = data(colour_button); const auto init_value = obj->value(); QColor color_qt; color_qt.setRgbF(init_value[0], init_value[1], init_value[2], init_value[3]); connect_data(obj, _setup.key); // Style const int icon_size = colour_button->style()->pixelMetric(QStyle::PM_LargeIconSize); QPixmap pix(icon_size, icon_size); pix.fill(color_qt); colour_button->setIcon(QIcon(pix)); colour_button->setProperty("color", color_qt); colour_button->setStyleSheet(qApp->styleSheet()); set_minimum_size(colour_button, _setup); } _layout->addWidget(colour_button); QObject::connect(colour_button, &QPushButton::clicked, this, &settings::on_color_button); // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, colour_button]{on_reset_color(colour_button);}); } return nullptr; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* settings::create_double_spin_widget( QBoxLayout* _layout, const double_widget& _setup, int _count, Qt::Orientation _orientation ) { const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* const sub_layout = new QBoxLayout {layout_direction}; sub_layout->setContentsMargins(0, 0, 0, 0); _layout->addLayout(sub_layout); _layout->setProperty(qt_property::key, QString::fromStdString(_setup.key)); _layout->setProperty(qt_property::data_index, static_cast(_setup.data_index)); std::array spinboxes {}; std::array init_values {0., 0., 0.}; if(_count == 1) { const auto obj = data(_layout); init_values[0] = obj->value(); connect_data(obj, _setup.key); } else if(_count == 2) { const auto obj = data(_layout); const auto value = obj->value(); std::ranges::copy(value, init_values.begin()); connect_data(obj, _setup.key); } else if(_count == 3) { const auto obj = data(_layout); const auto value = obj->value(); std::ranges::copy(value, init_values.begin()); connect_data(obj, _setup.key); } // Spinboxes for(std::size_t i = 0 ; i < std::size_t(_count) ; ++i) { auto* spinbox = new QDoubleSpinBox(); // Base properties spinbox->setObjectName(QString::fromStdString(_setup.key + "/" + std::to_string(i))); spinbox->setProperty(qt_property::key, QString::fromStdString(_setup.key)); spinbox->setProperty(qt_property::count, _count); spinbox->setProperty(qt_property::index, static_cast(i)); spinbox->setProperty(qt_property::data_index, static_cast(_setup.data_index)); spinboxes[i] = spinbox; // Data auto count_decimals = [](double _num) -> int { std::stringstream out; out << _num; const std::string s = out.str(); const std::string t = s.substr(s.find('.') + 1); return static_cast(t.length()); }; spinbox->setDecimals(std::max(std::max(count_decimals(_setup.min), count_decimals(_setup.max)), 2)); spinbox->setRange(_setup.min, _setup.max); // Beware, set setSingleStep after setRange() and setDecimals() otherwise it may fail spinbox->setSingleStep(std::abs(spinbox->maximum() - spinbox->minimum()) / 100.); // Set value last only after setting range and decimals, otherwise the value may be truncated spinbox->setValue(init_values[i]); // Style spinbox->setStyleSheet(qApp->styleSheet()); set_minimum_size(spinbox, _setup); sub_layout->addWidget(spinbox); QObject::connect( spinbox, QOverload::of(&QDoubleSpinBox::valueChanged), this, &settings::on_change_double ); } QDoubleSpinBox* spinbox = spinboxes[0]; spinbox->setObjectName(QString::fromStdString(_setup.key)); // Set a property with a pointer on each member of the group for(std::size_t i = 0 ; i < std::size_t(_count) ; ++i) { for(std::size_t j = 0 ; j < std::size_t(_count) ; ++j) { const std::string prop_name = std::string("widget#") + std::to_string(j); spinboxes[i]->setProperty(prop_name.c_str(), QVariant::fromValue(spinboxes[j])); } } // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, spinbox]{on_reset_double(spinbox);}); } return nullptr; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* settings::create_double_slider_widget( QBoxLayout* _layout, const double_widget& _setup, std::uint8_t _decimals, Qt::Orientation _orientation, bool _on_release ) { const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* const sub_layout = new QBoxLayout {layout_direction}; sub_layout->setContentsMargins(0, 0, 0, 0); _layout->addLayout(sub_layout); auto* const slider = new QSlider(); // Base properties const double value_range = _setup.max - _setup.min; slider->setOrientation(_orientation); slider->setObjectName(QString::fromStdString(_setup.key)); slider->setProperty(qt_property::key, slider->objectName()); slider->setProperty(qt_property::count, 1); slider->setProperty(qt_property::data_index, static_cast(_setup.data_index)); // Data const auto obj = data(slider); const auto init_value = obj->value(); connect_data(obj, _setup.key); slider->setProperty("decimals", _decimals); slider->setProperty("min", _setup.min); slider->setProperty("max", _setup.max); // tracking true: emit signal when value change, false: emit signal when slider released. slider->setTracking(!_on_release); set_double_slider_range(slider, init_value); const int default_slider_value = int(std::round(((init_value - _setup.min) / value_range) * double(slider->maximum()))); slider->setValue(default_slider_value); // Compute a "usable" page step slider->setPageStep(int(std::round(value_range * 10))); // Style slider->setProperty("widget#0", QVariant::fromValue(slider)); slider->setStyleSheet(qApp->styleSheet()); set_minimum_size(slider, _setup); QFont min_max_labels_font; min_max_labels_font.setPointSize(7); min_max_labels_font.setItalic(true); auto* const min_value_label = new QLabel(); min_value_label->setFont(min_max_labels_font); min_value_label->setText(QString::number(_setup.min, 'f', _decimals)); min_value_label->setToolTip("Minimum value."); min_value_label->setObjectName(QString::fromStdString(_setup.key + "/minValueLabel")); min_value_label->setAlignment(Qt::AlignCenter); min_value_label->setStyleSheet(qApp->styleSheet()); auto* const max_value_label = new QLabel(); max_value_label->setFont(min_max_labels_font); max_value_label->setText(QString::number(_setup.max, 'f', _decimals)); max_value_label->setToolTip("Maximum value."); max_value_label->setObjectName(QString::fromStdString(_setup.key + "/maxValueLabel")); max_value_label->setAlignment(Qt::AlignCenter); max_value_label->setStyleSheet(qApp->styleSheet()); auto* const value_label = new QLabel(); value_label->setStyleSheet("QLabel { font: bold; }"); value_label->setText(QString::number(init_value, 'f', _decimals)); value_label->setToolTip("Current value."); sight::module::ui::qt::settings::set_label_minimum_size(value_label, _setup.min, _setup.max, _decimals); value_label->setObjectName(QString::fromStdString(_setup.key + "/valueLabel")); value_label->setAlignment(Qt::AlignCenter); value_label->setStyleSheet(qApp->styleSheet()); // Slots { // Forward to the corresponding Sight signal QObject::connect(slider, &QSlider::valueChanged, this, &settings::on_change_double_slider); // Update the labels when the value or the range changes QObject::connect( slider, &QSlider::valueChanged, this, [value_label, slider] { sight::module::ui::qt::settings::on_double_slider_mapped(value_label, slider); }); QObject::connect( slider, &QSlider::rangeChanged, this, [ = ]{on_double_slider_range_mapped(min_value_label, max_value_label, slider);}); } // Sub layout setup { const auto alignment = _orientation == Qt::Vertical ? Qt::AlignCenter : Qt::Alignment {}; auto* const first_label = _orientation == Qt::Vertical ? max_value_label : min_value_label; auto* const second_label = _orientation == Qt::Vertical ? min_value_label : max_value_label; sub_layout->addWidget(first_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(slider, /*scretch=*/ 0, alignment); sub_layout->addWidget(second_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(value_label, /*scretch=*/ 0, alignment); sub_layout->setAlignment(alignment); if(_setup.hide_min_max) { min_value_label->hide(); max_value_label->hide(); } } // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, slider]{on_reset_double(slider);}); } return nullptr; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* settings::create_integer_slider_widget( QBoxLayout* _layout, const int_widget& _setup, Qt::Orientation _orientation, bool _on_release ) { const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* sub_layout = new QBoxLayout {layout_direction}; _layout->addLayout(sub_layout); sub_layout->setContentsMargins(0, 0, 0, 0); auto* slider = new QSlider(); // Base properties slider->setObjectName(QString::fromStdString(_setup.key)); slider->setProperty(qt_property::key, QString::fromStdString(_setup.key)); slider->setProperty(qt_property::count, 1); slider->setProperty(qt_property::data_index, static_cast(_setup.data_index)); slider->setProperty("widget#0", QVariant::fromValue(slider)); // Data const auto obj = data(slider); const auto init_value = obj->value(); slider->setMinimum(_setup.min); slider->setMaximum(_setup.max); slider->setValue(static_cast(init_value)); // tracking true: emit signal when value change, false: emit signal when slider released. slider->setTracking(!_on_release); connect_data(obj, _setup.key); // Style slider->setOrientation(_orientation); slider->setStyleSheet(qApp->styleSheet()); set_minimum_size(slider, _setup); QFont min_max_labels_font; min_max_labels_font.setPointSize(7); min_max_labels_font.setItalic(true); auto* min_value_label = new QLabel(); min_value_label->setFont(min_max_labels_font); min_value_label->setText(QString::number(slider->minimum())); min_value_label->setToolTip("Minimum value."); min_value_label->setObjectName(QString::fromStdString(_setup.key + "/minValueLabel")); min_value_label->setAlignment(Qt::AlignCenter); min_value_label->setStyleSheet(qApp->styleSheet()); auto* max_value_label = new QLabel(); max_value_label->setFont(min_max_labels_font); max_value_label->setText(QString::number(slider->maximum())); max_value_label->setToolTip("Maximum value."); max_value_label->setObjectName(QString::fromStdString(_setup.key + "/maxValueLabel")); max_value_label->setAlignment(Qt::AlignCenter); max_value_label->setStyleSheet(qApp->styleSheet()); auto* value_label = new QLabel(); value_label->setStyleSheet("QLabel { font: bold; }"); value_label->setText(QString("%1").arg(slider->value())); value_label->setToolTip("Current value."); sight::module::ui::qt::settings::set_label_minimum_size(value_label, _setup.min, _setup.max); value_label->setObjectName(QString::fromStdString(_setup.key + "/valueLabel")); value_label->setAlignment(Qt::AlignCenter); value_label->setStyleSheet(qApp->styleSheet()); // Sub layout setup { const auto alignment = _orientation == Qt::Vertical ? Qt::AlignCenter : Qt::Alignment {}; auto* const first_label = _orientation == Qt::Vertical ? max_value_label : min_value_label; auto* const second_label = _orientation == Qt::Vertical ? min_value_label : max_value_label; sub_layout->addWidget(first_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(slider, /*scretch=*/ 0, alignment); sub_layout->addWidget(second_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(value_label, /*scretch=*/ 0, alignment); sub_layout->setAlignment(alignment); if(_setup.hide_min_max) { min_value_label->hide(); max_value_label->hide(); } } // Connections { // Forward to the corresponding Sight signal QObject::connect(slider, &QSlider::valueChanged, this, &settings::on_change_integer); // Update the labels when the value or the range changes QObject::connect( slider, &QSlider::valueChanged, this, [value_label, slider]{sight::module::ui::qt::settings::on_slider_mapped(value_label, slider);}); QObject::connect( slider, &QSlider::rangeChanged, this, [ = ]{on_slider_range_mapped(min_value_label, max_value_label, slider);}); } // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, slider]{on_reset_integer(slider);}); } return nullptr; } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* settings::create_integer_spin_widget( QBoxLayout* _layout, const int_widget& _setup, int _count, Qt::Orientation _orientation ) { const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* sub_layout = new QBoxLayout {layout_direction}; sub_layout->setContentsMargins(0, 0, 0, 0); _layout->addLayout(sub_layout); _layout->setProperty(qt_property::key, QString::fromStdString(_setup.key)); _layout->setProperty(qt_property::data_index, static_cast(_setup.data_index)); std::array spinboxes {}; std::array init_values {0, 0, 0}; if(_count == 1) { const auto obj = data(_layout); init_values[0] = obj->value(); connect_data(obj, _setup.key); } else if(_count == 2) { const auto obj = data(_layout); const auto value = obj->value(); std::ranges::copy(value, init_values.begin()); connect_data(obj, _setup.key); } else if(_count == 3) { const auto obj = data(_layout); const auto value = obj->value(); std::ranges::copy(value, init_values.begin()); connect_data(obj, _setup.key); } // Spinboxes for(std::size_t i = 0 ; i < std::size_t(_count) ; ++i) { auto* spinbox = new QSpinBox(); spinboxes[i] = spinbox; // Base properties spinbox->setObjectName(QString::fromStdString(_setup.key + "/" + std::to_string(i))); spinbox->setProperty(qt_property::key, QString::fromStdString(_setup.key)); spinbox->setProperty(qt_property::count, _count); spinbox->setProperty(qt_property::index, static_cast(i)); spinbox->setProperty(qt_property::data_index, static_cast(_setup.data_index)); spinbox->setMinimum(_setup.min); spinbox->setMaximum(_setup.max); spinbox->setValue(static_cast(init_values[i])); // Style spinbox->setStyleSheet(qApp->styleSheet()); set_minimum_size(spinbox, _setup); sub_layout->addWidget(spinbox); // Connect spinbox value with our editor QObject::connect(spinbox, QOverload::of(&QSpinBox::valueChanged), this, &settings::on_change_integer); } QSpinBox* first_spinbox = spinboxes[0]; first_spinbox->setObjectName(QString::fromStdString(_setup.key)); // Set a property with a pointer on each member of the group for(std::size_t i = 0 ; i < std::size_t(_count) ; ++i) { for(std::size_t j = 0 ; j < std::size_t(_count) ; ++j) { const std::string prop_name = std::string("widget#") + std::to_string(j); spinboxes[i]->setProperty(prop_name.c_str(), QVariant::fromValue(spinboxes[j])); } } // Reset button if(_setup.reset_button) { return this->create_reset_button( _setup.key, [this, first_spinbox](){on_reset_integer(first_spinbox);}); } return nullptr; } //----------------------------------------------------------------------------- void settings::parse_enum_string( const std::string& _options, std::vector& _labels, std::vector& _keys, std::string _separators ) { const boost::char_separator sep(_separators.c_str()); const boost::tokenizer > tokens {_options, sep}; for(const auto& token : tokens) { std::vector parts; boost::split(parts, token, boost::is_any_of("=")); _labels.push_back(parts[0]); if(parts.size() > 1) { _keys.push_back(parts[1]); } else { _keys.push_back(parts[0]); } } } //------------------------------------------------------------------------------ void settings::create_enum_combobox_widget( QBoxLayout* _layout, const param_widget& _setup, const std::vector& _values, const std::vector& _data ) { auto* combo_box = new QComboBox(); combo_box->setObjectName(QString::fromStdString(_setup.key)); combo_box->setStyleSheet(qApp->styleSheet()); combo_box->setProperty(qt_property::key, QString::fromStdString(_setup.key)); combo_box->setProperty(qt_property::data_index, static_cast(_setup.data_index)); combo_box->setProperty(qt_property::use_index, _setup.use_index); for(int idx = 0 ; const auto& value : _values) { combo_box->insertItem(idx, QString::fromStdString(value)); ++idx; } // Add optional data for(int idx = 0 ; const auto& choice : _data) { combo_box->setItemData(idx, QString::fromStdString(choice)); ++idx; } _layout->addWidget(combo_box); const bool is_string = data(combo_box) != nullptr; // Set the comboBox to the default value if(is_string) { const auto obj = data(combo_box); const auto init_value = obj->value(); for(std::size_t idx = 0 ; const auto& choice : _data) { if(choice == init_value) { combo_box->setCurrentText(QString::fromStdString(_values[idx])); } ++idx; } connect_data(obj, _setup.key); QObject::connect( combo_box, QOverload::of(&QComboBox::currentIndexChanged), [this, combo_box](int _value) { const std::string data = combo_box->itemData(_value).toString().toStdString(); update_data(combo_box, data); }); } else if(const auto integer_obj = data(combo_box); integer_obj) { const auto value = integer_obj->value(); if(_setup.use_index) { combo_box->setCurrentIndex(int(value)); QObject::connect( combo_box, QOverload::of(&QComboBox::currentIndexChanged), [this, combo_box](int _value) { update_data(combo_box, _value); }); } else { combo_box->setCurrentText(QString::number(value)); QObject::connect( combo_box, &QComboBox::currentTextChanged, [this, combo_box](const QString& _value) { update_data(combo_box, _value.toLongLong()); }); } connect_data(integer_obj, _setup.key); } // Style set_minimum_size(combo_box, _setup); } //------------------------------------------------------------------------------ void settings::create_enum_slider_widget( QBoxLayout* _layout, const param_widget& _setup, const std::vector& _values, Qt::Orientation _orientation, bool _on_release ) { const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* sub_layout = new QBoxLayout {layout_direction}; sub_layout->setContentsMargins(0, 0, 0, 0); _layout->addLayout(sub_layout); std::vector int_values; std::ranges::transform( _values, std::back_inserter(int_values), [](const std::string& _s){return std::stoi(_s);}); auto* const slider = new sight::ui::qt::widget::non_linear_slider(); slider->set_orientation(_orientation); set_minimum_size(slider, _setup); slider->setObjectName(QString::fromStdString(_setup.key)); slider->setProperty(qt_property::key, QString::fromStdString(_setup.key)); slider->setProperty(qt_property::data_index, static_cast(_setup.data_index)); slider->setProperty(qt_property::use_index, _setup.use_index); slider->set_values(int_values); slider->set_tracking(!_on_release); // Set the slider to the default value if(const auto string_obj = data(slider); string_obj) { slider->set_value(std::stoi(string_obj->value())); connect_data(string_obj, _setup.key); } else if(const auto integer_obj = data(slider); integer_obj) { const auto value = integer_obj->value(); if(_setup.use_index) { slider->set_value(int_values[std::size_t(value)]); } else { slider->set_value(int(value)); } connect_data(integer_obj, _setup.key); } slider->setStyleSheet(qApp->styleSheet()); QFont min_max_labels_font; min_max_labels_font.setPointSize(7); min_max_labels_font.setItalic(true); auto* const min_value_label = new QLabel(); min_value_label->setFont(min_max_labels_font); min_value_label->setText(QString::fromStdString(_values.front())); min_value_label->setToolTip("Minimum value."); min_value_label->setObjectName(QString::fromStdString(_setup.key + "/minValueLabel")); min_value_label->setAlignment(Qt::AlignCenter); min_value_label->setStyleSheet(qApp->styleSheet()); auto* const max_value_label = new QLabel(); max_value_label->setFont(min_max_labels_font); max_value_label->setText(QString::fromStdString(_values.back())); max_value_label->setToolTip("Maximum value."); max_value_label->setObjectName(QString::fromStdString(_setup.key + "/maxValueLabel")); max_value_label->setAlignment(Qt::AlignCenter); max_value_label->setStyleSheet(qApp->styleSheet()); auto* value_label = new QLabel(); value_label->setStyleSheet("QLabel { font: bold; }"); value_label->setText(QString::number(slider->value())); value_label->setToolTip("Current value."); set_label_minimum_size(value_label, int_values.front(), int_values.back()); value_label->setObjectName(QString::fromStdString(_setup.key + "/valueLabel")); value_label->setAlignment(Qt::AlignCenter); // Sub layout setup { const auto alignment = _orientation == Qt::Vertical ? Qt::AlignCenter : Qt::Alignment {}; auto* const first_label = _orientation == Qt::Vertical ? max_value_label : min_value_label; auto* const second_label = _orientation == Qt::Vertical ? min_value_label : max_value_label; sub_layout->addWidget(first_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(slider, /*stretch=*/ 0, alignment); sub_layout->addWidget(second_label, /*scretch=*/ 0, alignment); sub_layout->addWidget(value_label, /*scretch=*/ 0, alignment); if(_setup.hide_min_max) { min_value_label->hide(); max_value_label->hide(); } } // Connections { // Forward to the Sight signal QObject::connect( slider, &sight::ui::qt::widget::non_linear_slider::value_changed, [this, key = _setup.key, slider, value_label, use_index = _setup.use_index](int _value) { if(const auto string_obj = data(slider); string_obj) { update_data(slider, std::to_string(_value)); } else { if(use_index) { update_data(slider, static_cast(slider->index())); } else { update_data(slider, static_cast(_value)); } } value_label->setText(QString::number(_value)); }); // Update the labels QObject::connect( slider, &sight::ui::qt::widget::non_linear_slider::range_changed, [ = ](int _min, int _max) { min_value_label->setText(QString::number(_min)); max_value_label->setText(QString::number(_max)); }); } } //----------------------------------------------------------------------------- void settings::create_tickmarks_widget( QBoxLayout* _layout, const param_widget& _setup, const std::vector& _values ) { auto* tick_widget = new sight::ui::qt::widget::tickmarks_slider(); tick_widget->setProperty( qt_property::key, QString::fromStdString(_setup.key) ); tick_widget->setProperty( qt_property::data_index, static_cast(_setup.data_index) ); tick_widget->setObjectName(QString::fromStdString(_setup.key)); tick_widget->set_range(0, static_cast(_values.size()) - 1); tick_widget->set_tick_labels(_values); QStringList q_labels; for(const auto& s : _values) { q_labels << QString::fromStdString(s); } tick_widget->setProperty("tickLabels", q_labels); tick_widget->set_tick_interval(1); tick_widget->set_current_tick(0); tick_widget->setMinimumSize(100, 50); auto* value_label = new QLabel(); value_label->setObjectName(QString::fromStdString(_setup.key + "/valueLabel")); value_label->setVisible(false); const bool is_string = data(tick_widget) != nullptr; if(is_string) { const auto obj = data(tick_widget); const auto init_value = obj->value(); tick_widget->set_tick_labels({init_value}); connect_data(obj, _setup.key); } else { auto obj = data(tick_widget); int init_index = static_cast(obj->value()); init_index = std::clamp(init_index, 0, int(_values.size()) - 1); tick_widget->set_current_tick(init_index); value_label->setText( QString::fromStdString( _values[static_cast(init_index)] ) ); connect_data(obj, _setup.key); } connect( tick_widget, &sight::ui::qt::widget::tickmarks_slider::value_changed, this, [this, tick_widget](int _index) { const auto& labels = tick_widget->tick_labels(); if(_index < 0 || _index >= static_cast(labels.size())) { return; } const std::string& tick_value = labels[static_cast(_index)]; if(auto str = data(tick_widget)) { update_data(tick_widget, tick_value); } else { update_data( tick_widget, static_cast(_index) ); } }); _layout->addWidget(tick_widget); std::string options = boost::algorithm::join(_values, ","); _layout->addWidget(value_label); this->update_tickmarks(tick_widget, options); } //----------------------------------------------------------------------------- void settings::create_enum_button_bar_widget( QBoxLayout* _layout, const param_widget& _setup, const std::vector& _button_list, const int _width, const int _height, const int _spacing, const std::string& _style, Qt::Orientation _orientation ) { const auto layout_direction = _orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight; auto* sub_layout = new QBoxLayout {layout_direction}; sub_layout->setContentsMargins(0, 0, 0, 0); if(_spacing != 0) { sub_layout->setSpacing(_spacing); } _layout->addLayout(sub_layout); // create a button group to deactivate the buttons on selection auto* button_bar_group = new QButtonGroup(sub_layout); button_bar_group->setObjectName(QString::fromStdString(_setup.key)); // create the buttons from the provided list int button_index = 0; const auto is_button_bar = _width != 0 || _height != 0; for(const auto& button_param : _button_list) { auto* enum_button = new QToolButton(); button_bar_group->addButton(enum_button); // The name needs to be the key_value, to find it when the service is updated through a slot enum_button->setObjectName((QString::fromStdString(_setup.key + "_" + button_param.value))); enum_button->setProperty(qt_property::key, QString::fromStdString(_setup.key)); enum_button->setProperty(qt_property::data_index, static_cast(_setup.data_index)); enum_button->setIcon(QIcon(QString::fromStdString(button_param.icon_path))); enum_button->setToolTip(QString::fromStdString(button_param.label)); enum_button->setCheckable(true); enum_button->setProperty("class", "buttonBar"); enum_button->setProperty("value", QString::fromStdString(button_param.value)); enum_button->setProperty("button_index", button_index); enum_button->setText(QString::fromStdString(button_param.label)); enum_button->setStyleSheet("border : 1px solid transparent;"); if(_orientation == Qt::Vertical) { enum_button->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); } else { enum_button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); } if(_style == "ToolButtonTextOnly") { enum_button->setToolButtonStyle(Qt::ToolButtonTextOnly); } else if(_style == "ToolButtonTextBesideIcon") { enum_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else if(_style == "ToolButtonTextUnderIcon") { enum_button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } else if(_style == "ToolButtonFollowStyle") { enum_button->setToolButtonStyle(Qt::ToolButtonFollowStyle); } else { enum_button->setToolButtonStyle(Qt::ToolButtonIconOnly); } // create an effect to make it gray when not selected, and full color when selected auto* effect = new QGraphicsColorizeEffect; effect->setColor(QColor(10, 10, 10)); effect->setStrength(0.7); enum_button->setGraphicsEffect(effect); // the size depends on the configuration. xml > qss if(is_button_bar) { // the size is provided through the xml config. Don't use the qss and ignore _setup.min_size const int width = _width == 0 ? _height : _width; const int height = _height == 0 ? _width : _height; enum_button->setIconSize({width, height}); } else { // the size is not provided through the xml. Use the qss style. enum_button->setProperty("class", "buttonBarTouchFriendly"); } // add the button in the grid at its place sub_layout->addWidget(enum_button, 0, Qt::AlignCenter); // create the connection to fire signals when the button is clicked QObject::connect( enum_button, &QToolButton::clicked, [this, button_param, enum_button, button_index] { if(const auto string_obj = data(enum_button); string_obj == nullptr) { update_data(enum_button, static_cast(button_index)); } else { update_data(enum_button, button_param.value); } }); // create connection to change the display (gray/full color) when the selection state changes QObject::connect( enum_button, &QAbstractButton::toggled, [ = ](bool _checked) { QGraphicsEffect* effect = enum_button->graphicsEffect(); effect->setEnabled(!_checked); }); const bool is_string = data(enum_button) != nullptr; // Set the slider to the default value if(is_string) { // set the default value const auto obj = data(enum_button); const auto init_value = obj->value(); connect_data(obj, _setup.key); if(button_param.value == init_value) { enum_button->toggle(); } } else { const auto obj = data(enum_button); const auto init_value = obj->value(); connect_data(obj, _setup.key); if(button_index == init_value) { enum_button->toggle(); } } ++button_index; } // create the connection to fire signals when the button is clicked connect( button_bar_group, &QButtonGroup::buttonClicked, [button_bar_group, is_button_bar](QAbstractButton* _selected_button) { if(is_button_bar) { for(const auto& button : button_bar_group->buttons()) { button->setStyleSheet("border: 1px solid transparent;"); } _selected_button->setStyleSheet("border: 1px solid white;"); } }); } //----------------------------------------------------------------------------- [[nodiscard]] QPushButton* settings::create_text_widget(QBoxLayout* _layout, const param_widget& _setup, const std::string& _type) { auto* edit = new QLineEdit(); const auto key = QString::fromStdString(_setup.key); edit->setObjectName(key); // Base properties edit->setProperty(qt_property::key, key); edit->setProperty(qt_property::data_index, static_cast(_setup.data_index)); // Data const auto obj = data(edit); const auto init_value = obj->value(); edit->setText(QString::fromStdString(init_value)); connect_data(obj, _setup.key); edit->setStyleSheet(qApp->styleSheet()); edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); set_minimum_size(edit, _setup); _layout->addWidget(edit); // Forward to the Sight signal QObject::connect( edit, &QLineEdit::textChanged, [this, key = _setup.key, edit](QString _value) { update_data(edit, _value.toStdString()); }); QPushButton* dir_selector = nullptr; if(_type == "file" or _type == "file_read" or _type == "file_write") { dir_selector = new QPushButton("..."); QObject::connect( dir_selector, &QPushButton::clicked, [edit, _type]() { static auto default_directory = std::make_shared(); sight::ui::dialog::location dialog_file; dialog_file.set_title("Select file"); dialog_file.set_default_location(default_directory); using sight::ui::dialog::location; dialog_file.set_option(_type == "file_write" ? location::write : location::read); dialog_file.set_type(sight::ui::dialog::location::single_file); auto result = std::dynamic_pointer_cast(dialog_file.show()); if(result) { default_directory->set_folder(result->get_file().parent_path()); edit->setText(QString::fromStdString(result->get_file().string())); dialog_file.save_default_location(default_directory); } }); } else if(_type == "dir" or _type == "dir_read" or _type == "dir_write") { dir_selector = new QPushButton("..."); QObject::connect( dir_selector, &QPushButton::clicked, [edit, _type]() { static auto default_directory = std::make_shared(); sight::ui::dialog::location dialog_file; dialog_file.set_title("Select directory"); dialog_file.set_default_location(default_directory); using sight::ui::dialog::location; dialog_file.set_option(_type == "dir_read" ? location::read : location::write); dialog_file.set_type(sight::ui::dialog::location::folder); const auto result = std::dynamic_pointer_cast(dialog_file.show()); if(result) { default_directory->set_folder(result->get_folder()); edit->setText(QString::fromStdString(result->get_folder().string())); dialog_file.save_default_location(default_directory); } }); } if(dir_selector != nullptr) { dir_selector->setMaximumWidth(20); dir_selector->setStyleSheet(qApp->styleSheet()); _layout->addWidget(dir_selector, 0, Qt::AlignCenter); } // Reset button if(_setup.reset_button) { return this->create_reset_button(_setup.key, [this, edit](){on_reset_string(edit);}); } return nullptr; } //----------------------------------------------------------------------------- double settings::get_double_slider_value(const QSlider* _slider) { const double min = _slider->property("min").toDouble(); const double max = _slider->property("max").toDouble(); const double value_range = max - min; double double_value = min; if(_slider->maximum() != 0) { double_value = (double(_slider->value()) / _slider->maximum()) * value_range + min; } return double_value; } //------------------------------------------------------------------------------ void settings::update_enum_range(std::string _options, std::string _key) { QObject* widget = this->get_param_widget(_key); // Early return if the widget is not the widget we search for if(widget == nullptr) { return; } this->block_signals(true); std::vector values; std::vector data; sight::module::ui::qt::settings::parse_enum_string(_options, values, data); if(auto* combobox = qobject_cast(widget); combobox != nullptr) { QSignalBlocker blocker(combobox); combobox->clear(); int idx = 0; for(const auto& value : values) { combobox->insertItem(idx, QString::fromStdString(value)); ++idx; } // Add optional data idx = 0; for(const auto& choice : data) { combobox->setItemData(idx, QString::fromStdString(choice)); ++idx; } if(const auto string_obj = settings::data(combobox); string_obj) { const auto init_value = string_obj->value(); combobox->setCurrentText(QString::fromStdString(init_value)); } else if(const auto integer_obj = settings::data(combobox); integer_obj) { const auto current_value = integer_obj->value(); if(combobox->property(qt_property::use_index).toBool()) { combobox->setCurrentIndex(static_cast(current_value)); } else { combobox->setCurrentText(QString::number(current_value)); } } } else if(auto* const non_linear_slider = qobject_cast(widget); non_linear_slider != nullptr) { // Convert string values to integers std::vector int_values; int_values.reserve(values.size()); std::ranges::transform( values, std::back_inserter(int_values), [](const std::string& _s) { return std::stoi(_s); }); // Retrieve the old value before updating the slider const auto& old_value = [&non_linear_slider, &_key, this]() -> std::optional { if(const auto string_obj = settings::data(non_linear_slider); string_obj) { try { return std::stoi(string_obj->value()); } catch(...) { SIGHT_ERROR( "Failed to convert " << string_obj->value() << " string to integer for key: " << _key ); } } else if(const auto integer_obj = settings::data(non_linear_slider); integer_obj) { const auto& old_values = non_linear_slider->values(); const auto current_value = integer_obj->value(); if(non_linear_slider->property(qt_property::use_index).toBool()) { if(current_value >= 0 && current_value < std::int64_t(old_values.size())) { return old_values[std::size_t(current_value)]; } } else { // If the slider is not using indices, return the value directly return current_value; } } return std::nullopt; }(); // Apply new values non_linear_slider->set_values(int_values); // Reapply old value, if possible if(old_value && std::ranges::find(int_values, *old_value) != int_values.end()) { // If the old value is still in the new values, set it non_linear_slider->set_value(*old_value); } else if(!int_values.empty()) { // Otherwise, set the first value as default non_linear_slider->set_index(0); } } else if(auto* const tickmarks = qobject_cast(widget); tickmarks != nullptr) { this->update_tickmarks(tickmarks, _options); } this->block_signals(false); } //------------------------------------------------------------------------------ void settings::update_tickmarks(sight::ui::qt::widget::tickmarks_slider* const _tickmarks, const std::string& _options) { QSignalBlocker guard(_tickmarks); std::vector tick_labels; std::vector tick_data; sight::module::ui::qt::settings::parse_enum_string(_options, tick_labels, tick_data); if(!tick_labels.empty()) { const int max_index = static_cast(tick_labels.size()) - 1; _tickmarks->set_range(0, max_index); _tickmarks->set_tick_interval(1); _tickmarks->set_tick_labels(tick_labels); int current_index = 0; if(auto string_data = settings::data(_tickmarks)) { const std::string& current_value = string_data->value(); auto it = std::find(tick_data.begin(), tick_data.end(), current_value); if(it != tick_data.end()) { current_index = static_cast(std::distance(tick_data.begin(), it)); } } else if(auto integer_data = settings::data(_tickmarks)) { current_index = static_cast(std::clamp(integer_data->value(), 0, max_index)); } _tickmarks->set_current_tick(current_index); } } //----------------------------------------------------------------------------- void settings::block_signals(bool _block) { m_block_signals = _block; } //------------------------------------------------------------------------------ void settings::update_int_min_parameter(int _min, std::string _key) { QObject* child = this->get_param_widget(_key); auto* spinbox = qobject_cast(child); auto* slider = qobject_cast(child); if(spinbox != nullptr) { const int count = child->property(qt_property::count).toInt(); auto* spin0 = child->property("widget#0").value(); spin0->setMinimum(_min); if(count >= 2) { auto* spin1 = child->property("widget#1").value(); spin1->setMinimum(_min); } if(count >= 3) { auto* spin2 = child->property("widget#2").value(); spin2->setMinimum(_min); } } else if(slider != nullptr) { slider->setMinimum(_min); } else { SIGHT_ERROR(get_id() << ": Widget " << std::quoted(_key) << " must be a QSlider or a QDoubleSpinBox"); } } //------------------------------------------------------------------------------ void settings::update_int_max_parameter(int _max, std::string _key) { QObject* child = this->get_param_widget(_key); auto* spinbox = qobject_cast(child); auto* slider = qobject_cast(child); if(spinbox != nullptr) { const int count = child->property(qt_property::count).toInt(); auto* spin0 = child->property("widget#0").value(); spin0->setMaximum(_max); if(count >= 2) { auto* spin1 = child->property("widget#1").value(); spin1->setMaximum(_max); } if(count >= 3) { auto* spin2 = child->property("widget#2").value(); spin2->setMaximum(_max); } } else if(slider != nullptr) { slider->setMaximum(_max); } else { SIGHT_ERROR(get_id() << ": Widget " << std::quoted(_key) << " must be a QSlider or a QDoubleSpinBox"); } } //------------------------------------------------------------------------------ void settings::update_double_min_parameter(double _min, std::string _key) { QObject* child = this->get_param_widget(_key); auto* spinbox = qobject_cast(child); auto* slider = qobject_cast(child); if(spinbox != nullptr) { const int count = child->property(qt_property::count).toInt(); auto* spin0 = child->property("widget#0").value(); spin0->setMinimum(_min); if(count >= 2) { auto* spin1 = child->property("widget#1").value(); spin1->setMinimum(_min); } if(count >= 3) { auto* spin2 = child->property("widget#2").value(); spin2->setMinimum(_min); } } else if(slider != nullptr) { const auto value = data(slider)->value(); slider->setProperty("min", _min); set_double_slider_range(slider, value); } else { SIGHT_ERROR(get_id() << ": Widget " << std::quoted(_key) << " must be a QSlider or a QDoubleSpinBox"); } } //------------------------------------------------------------------------------ void settings::update_double_max_parameter(double _max, std::string _key) { QObject* child = this->get_param_widget(_key); auto* spinbox = qobject_cast(child); auto* slider = qobject_cast(child); if(spinbox != nullptr) { const int count = child->property(qt_property::count).toInt(); auto* spin0 = child->property("widget#0").value(); spin0->setMaximum(_max); if(count >= 2) { auto* spin1 = child->property("widget#1").value(); spin1->setMaximum(_max); } if(count >= 3) { auto* spin2 = child->property("widget#2").value(); spin2->setMaximum(_max); } } else if(slider != nullptr) { const auto value = data(slider)->value(); slider->setProperty("max", _max); set_double_slider_range(slider, value); } else { SIGHT_ERROR(get_id() << ": Widget " << std::quoted(_key) << " must be a QSlider or a QDoubleSpinBox"); } } //----------------------------------------------------------------------------- void settings::set_double_slider_range(QSlider* _slider, double _current_value) { const double min = _slider->property("min").toDouble(); const double max = _slider->property("max").toDouble(); const std::uint8_t decimals = static_cast(_slider->property("decimals").toUInt()); int max_slider_value = 1; for(std::uint8_t i = 0 ; i < decimals ; ++i) { max_slider_value *= 10; } const double value_range = max - min; max_slider_value = int(max_slider_value * value_range); // The slider's maximum internal range is [0; 2 147 483 647] // We could technically extend this range by setting the minimum to std::numeric_limits::min() // but it would be ridiculous to use a slider handling so many values. _slider->setMinimum(0); const std::string key = _slider->property(qt_property::key).toString().toStdString(); SIGHT_ERROR_IF( ": The requested value range for " << std::quoted( key ) << " is too large to be handled by a double slider. " "Please reduce your range, the number of decimals or use a 'spin' widget.", max_slider_value < std::numeric_limits::epsilon() ); if(max_slider_value < std::numeric_limits::epsilon()) { max_slider_value = 1.; } _slider->setMaximum(max_slider_value); // Update the slider integer value according to the new mix/max if(_current_value <= min) { _slider->setValue(0); // qt does not emit the signal if the value does not change, we have to force qt signal to update the displayed // value and emit 'doubleChanged' signal Q_EMIT _slider->valueChanged(0); } else if(_current_value > max) { _slider->setValue(max_slider_value); } else { const int slider_val = int(std::round(((_current_value - min) / value_range) * double(_slider->maximum()))); _slider->setValue(slider_val); } } //----------------------------------------------------------------------------- QObject* settings::get_param_widget(const std::string& _key) { const auto key = QString::fromStdString(_key); const auto it = std::ranges::find_if( m_param_boxes, [box_key = key + "_box"](const QWidget* const _w){return _w->objectName() == box_key;}); if(it == m_param_boxes.cend()) { SIGHT_DEBUG(get_id() << ": No param with '" + _key + "' found"); return nullptr; } auto* widget = (*it)->findChild(key); // The child widget *MUST* exist if a parameter exists with this name, if it does not exist, this is a regression // caused by modifications in create_xxx_widgets. SIGHT_ASSERT(get_id() << ": Widget " << std::quoted(_key) << " not found in its parent box.", widget != nullptr); return widget; } //------------------------------------------------------------------------------ template<> inline QString settings::value_to_string_label(int _value, std::uint8_t /*unused*/) { return QString("%1").arg(_value); } //------------------------------------------------------------------------------ template<> inline QString settings::value_to_string_label(double _value, std::uint8_t _decimals) { return QString::number(_value, 'f', _decimals); } //------------------------------------------------------------------------------ template void settings::set_label_minimum_size(QLabel* _label, T _min, T _max, std::uint8_t _decimals) { const auto min_string = value_to_string_label(_min, _decimals); const auto max_string = value_to_string_label(_max, _decimals); // Create a dummy label with same properties QLabel dummy_label; dummy_label.setFont(_label->font()); dummy_label.setStyleSheet(_label->styleSheet()); // Fill it with the string of the max value and request the size from Qt dummy_label.setText(max_string); const QSize size_with_max_value = dummy_label.sizeHint(); // Fill it with the string of the min value and request the size from Qt dummy_label.setText(min_string); const QSize size_with_min_value = dummy_label.sizeHint(); // Compute the maximum size and set it to our label const QSize max_size = size_with_max_value.expandedTo(size_with_min_value); _label->setMinimumSize(max_size); } //------------------------------------------------------------------------------ template<> void settings::set_parameter(const bool& _val, std::string _key) { this->block_signals(true); QObject* child = this->get_param_widget(_key); auto* checkbox = qobject_cast(child); if(checkbox != nullptr) { checkbox->setCheckState(_val ? Qt::Checked : Qt::Unchecked); } this->block_signals(false); } //------------------------------------------------------------------------------ template<> void settings::set_parameter(const sight::data::color::array_t& _val, std::string _key) { this->block_signals(true); QObject* child = this->get_param_widget(_key); auto* color_button = qobject_cast(child); if(color_button != nullptr) { const int icon_size = color_button->style()->pixelMetric(QStyle::PM_LargeIconSize); QPixmap pix(icon_size, icon_size); QColor color_qt; color_qt.setRgbF(_val[0], _val[1], _val[2], _val[3]); pix.fill(color_qt); color_button->setIcon(QIcon(pix)); color_button->setProperty("color", color_qt); } this->block_signals(false); } //------------------------------------------------------------------------------ template<> void settings::set_parameter(const std::string& _val, std::string _key) { this->block_signals(true); QObject* widget = this->get_param_widget(_key); auto* combobox = qobject_cast(widget); if(combobox != nullptr) { // Find first in text auto res = combobox->findText(QString::fromStdString(_val)); if(res == -1) { // fallback, try to find in optional data. res = combobox->findData(QString::fromStdString(_val)); } if(res >= 0) { combobox->setCurrentIndex(res); } else { SIGHT_WARN( get_id() << "value " << std::quoted(_val) << "doesn't exist for parameter " << std::quoted( _key ) << "." ); } } else if(auto* non_linear_slider = qobject_cast(widget); non_linear_slider != nullptr) { non_linear_slider->set_value(std::stoi(_val)); } else if(auto* button_group = qobject_cast(widget); button_group != nullptr) { const auto enum_key = QString::fromStdString(_key + "_" + _val); const auto& buttons = button_group->buttons(); auto button_it = std::ranges::find_if( buttons, [&enum_key](const auto* const _b) { return _b->objectName() == enum_key; }); SIGHT_ASSERT( get_id() << ": Value " << std::quoted(_val) << " does not appear in parameter widget " << std::quoted(_key), button_it != buttons.cend() ); (*button_it)->toggle(); } else if(auto* line_edit = qobject_cast(widget); line_edit != nullptr) { line_edit->setText(QString::fromStdString(_val)); } else if(auto* tickmarks_widget = qobject_cast(widget); tickmarks_widget != nullptr) { auto q_labels = tickmarks_widget->property("tickLabels").toStringList(); qsizetype idx_q = q_labels.indexOf(QString::fromStdString(_val)); int idx = static_cast(idx_q >= 0 ? idx_q : 0); tickmarks_widget->set_current_tick(idx >= 0 ? idx : 0); } else { SIGHT_ERROR("Unknown widget type in set_parameter callback"); } this->block_signals(false); } //------------------------------------------------------------------------------ template<> void settings::set_parameter(const std::int64_t& _val, std::string _key) { this->block_signals(true); QObject* widget = this->get_param_widget(_key); auto* spinbox = qobject_cast(widget); auto* slider = qobject_cast(widget); auto* combobox = qobject_cast(widget); const auto val = static_cast(_val); if(spinbox != nullptr) { spinbox->setValue(val); } else if(slider != nullptr) { slider->setValue(val); } else if(auto* non_linear_slider = qobject_cast(widget); non_linear_slider != nullptr) { if(non_linear_slider->property(qt_property::use_index).toBool()) { non_linear_slider->set_index(std::size_t(val)); } else { // If the slider is not using indices, set the value directly non_linear_slider->set_value(val); } } else if(auto* tickmarks_widget = qobject_cast(widget); tickmarks_widget != nullptr) { tickmarks_widget->set_current_tick(val); } else if(combobox != nullptr) { if(combobox->property(qt_property::use_index).toBool()) { combobox->setCurrentIndex(int(val)); } else { // If the slider is not using indices, set the value directly combobox->setCurrentText(QString::number(val)); } } else if(auto* button_group = qobject_cast(widget); button_group != nullptr) { const auto button_index = static_cast(_val); const auto& buttons = button_group->buttons(); const auto button_it = std::ranges::find_if( buttons, [&button_index](const auto* const _b) { return _b->property("button_index").toInt() == button_index; }); SIGHT_ASSERT( get_id() << ": Index " << std::quoted(std::to_string(_val)) << " does not appear in parameter widget " << std::quoted(_key), button_it != buttons.cend() ); (*button_it)->toggle(); } this->block_signals(false); } //------------------------------------------------------------------------------ template<> void settings::set_parameter(const sight::data::ivec2::array_t& _val, std::string _key) { this->block_signals(true); QObject* widget = this->get_param_widget(_key); if(widget != nullptr) { auto* spin0 = widget->property("widget#0").value(); auto* spin1 = widget->property("widget#1").value(); spin0->setValue(static_cast(_val[0])); spin1->setValue(static_cast(_val[1])); } this->block_signals(false); } //------------------------------------------------------------------------------ template<> void settings::set_parameter(const sight::data::ivec3::array_t& _val, std::string _key) { this->block_signals(true); QObject* widget = this->get_param_widget(_key); if(widget != nullptr) { auto* spin0 = widget->property("widget#0").value(); auto* spin1 = widget->property("widget#1").value(); auto* spin2 = widget->property("widget#2").value(); spin0->setValue(static_cast(_val[0])); spin1->setValue(static_cast(_val[1])); spin2->setValue(static_cast(_val[2])); } this->block_signals(false); } //------------------------------------------------------------------------------ template<> void settings::set_parameter(const double& _val, std::string _key) { this->block_signals(true); QObject* child = this->get_param_widget(_key); if(auto* spinbox = qobject_cast(child); spinbox != nullptr) { spinbox->setValue(_val); update_data(spinbox, _val); } else if(auto* slider = qobject_cast(child); slider != nullptr) { const double min = slider->property("min").toDouble(); const double max = slider->property("max").toDouble(); const double value_range = max - min; const int slider_val = int(std::round( ((std::max( _val, min ) - min) / value_range) * double(slider->maximum()) )); slider->setValue(slider_val); } else { SIGHT_ERROR(get_id() << ": Widget " << std::quoted(_key) << " must be a QSlider or a QDoubleSpinBox"); } this->block_signals(false); } //------------------------------------------------------------------------------ template<> void settings::set_parameter(const sight::data::dvec2::array_t& _val, std::string _key) { this->block_signals(true); QObject* widget = this->get_param_widget(_key); if(widget != nullptr) { auto* spin0 = widget->property("widget#0").value(); auto* spin1 = widget->property("widget#1").value(); spin0->setValue(_val[0]); spin1->setValue(_val[1]); } this->block_signals(false); } //------------------------------------------------------------------------------ template<> void settings::set_parameter(const sight::data::dvec3::array_t& _val, std::string _key) { this->block_signals(true); QObject* widget = this->get_param_widget(_key); if(widget != nullptr) { auto* spin0 = widget->property("widget#0").value(); auto* spin1 = widget->property("widget#1").value(); auto* spin2 = widget->property("widget#2").value(); spin0->setValue(_val[0]); spin1->setValue(_val[1]); spin2->setValue(_val[2]); } this->block_signals(false); } //------------------------------------------------------------------------------ template requires std::derived_from > std::shared_ptr settings::data(const QObject* _widget) { if(const auto data_index = _widget->property(qt_property::data_index); data_index.isValid()) { sight::data::mt::locked_ptr lock; sight::data::object::csptr obj; const std::string key = _widget->property(qt_property::key).toString().toStdString(); const auto map = m_settings_map.lock(); if(map) { obj = map->at(key); } else { lock = m_settings[static_cast(data_index.toUInt())].const_lock(); obj = lock.get_shared(); } const auto typed_obj = std::dynamic_pointer_cast(obj); return typed_obj; } return nullptr; } //------------------------------------------------------------------------------ template requires std::derived_from > void settings::connect_data(const CSPTR(DATATYPE)& _obj, const std::string& _key) { const auto sig = _obj->template signal(data::object::MODIFIED_SIG); const auto slot = core::com::new_slot( [_obj, _key, this]() { this->set_parameter(_obj->value(), _key); }); slot->set_worker(this->worker()); m_settings_slots[_key] = slot; sig->connect(slot); } //------------------------------------------------------------------------------ template requires std::derived_from > void settings::update_data(const QObject* _widget, const SUBTYPE& _val) { if(const auto data_index = _widget->property(qt_property::data_index); data_index.isValid()) { sight::data::mt::locked_ptr lock; sight::data::object::sptr obj; const std::string key = _widget->property(qt_property::key).toString().toStdString(); const auto map = m_settings_map.lock(); if(map) { obj = map->at(key); } else { lock = m_settings[static_cast(data_index.toUInt())].lock(); obj = lock.get_shared(); } const auto typed_obj = std::dynamic_pointer_cast(obj); typed_obj->set_value(_val); if(not m_block_signals) { const auto sig = obj->signal(data::object::MODIFIED_SIG); core::com::connection::blocker block(sig->get_connection(m_settings_slots[key])); sig->async_emit(); } } } //----------------------------------------------------------------------------- void settings::joystick_axis_direction_event(const sight::io::joystick::axis_direction_event& _event) { using direction_t = sight::io::joystick::axis_direction_event::direction_t; SIGHT_DEBUG("Joystick alias: " << this->to_string(_event.device->alias)); SIGHT_DEBUG("Joystick axis alias: " << this->to_string(_event.axis_alias)); SIGHT_DEBUG("Joystick axis: " << int(_event.axis)); SIGHT_DEBUG("Joystick direction: " << this->to_string(_event.value)); for(const auto& [widget_key, widget_joystick] : m_widget_joysticks) { auto* const widget = get_param_widget(widget_key); if(auto* const button_group = dynamic_cast(widget); button_group != nullptr) { // Filter the joystick event to only handle the right joystick if((_event.axis_alias != widget_joystick.axis_1 && _event.axis_alias != widget_joystick.axis_2) || _event.device->alias != widget_joystick.alias) { continue; } // Get the list of buttons const auto& buttons = button_group->buttons(); // Get the checked button auto* const checked_button = button_group->checkedButton(); // Find the current index of the checked button const auto checked_index = buttons.indexOf(checked_button); // Increment / decrement the index according to the joystick event if(_event.axis_alias == widget_joystick.axis_1 && _event.value == direction_t::left) { const auto go_left_or_down = [&buttons](auto _next_index) -> auto { while(--_next_index >= 0) { if(auto* const next_button = buttons[_next_index]; next_button->isEnabled() && next_button->isVisible()) { next_button->toggle(); return _next_index; } } return _next_index; }; auto next_index = go_left_or_down(checked_index); if(next_index < 0) { go_left_or_down(buttons.size()); } } else if(_event.axis_alias == widget_joystick.axis_1 && _event.value == direction_t::right) { const auto go_right_or_up = [&buttons](auto _next_index) -> auto { while(++_next_index < buttons.size()) { if(auto* const next_button = buttons[_next_index]; next_button->isEnabled() && next_button->isVisible()) { next_button->toggle(); return _next_index; } } return _next_index; }; auto next_index = go_right_or_up(checked_index); if(next_index >= buttons.size()) { go_right_or_up(-1); } } else if(_event.axis_alias == widget_joystick.axis_2 && _event.value == direction_t::backward && checked_button != nullptr) { checked_button->animateClick(); button_group->idClicked(static_cast(checked_index)); } } else if(auto* const non_linear_slider = dynamic_cast(widget); non_linear_slider != nullptr) { // Filter the joystick event to only handle the right joystick if(_event.axis_alias != widget_joystick.axis_1 || _event.device->alias != widget_joystick.alias) { continue; } if(_event.value == direction_t::left) { const int new_index = int(non_linear_slider->index()) - 1; non_linear_slider->set_index(new_index < 0 ? std::size_t(0) : std::size_t(new_index)); } else if(_event.value == direction_t::right) { const int new_index = int(non_linear_slider->index()) + 1; const int last_index = std::max(0, int(non_linear_slider->num_values()) - 1); non_linear_slider->set_index(new_index > last_index ? std::size_t(last_index) : std::size_t(new_index)); } } else if(auto* const slider = dynamic_cast(widget); slider != nullptr) { // Filter the joystick event to only handle the right joystick if((_event.axis_alias != widget_joystick.axis_1 && _event.axis_alias != widget_joystick.axis_2) || _event.device->alias != widget_joystick.alias) { continue; } const auto get_step = [&_event, &slider] { if(_event.value == direction_t::centered) { return 0; } if(_event.axis == 0) { return slider->singleStep(); } if(_event.axis == 4) { if(slider->property("decimals").isNull() || _event.count < 10) { return slider->singleStep(); } if(_event.count < 20) { return std::max(slider->singleStep(), slider->pageStep() / 4); } if(_event.count < 40) { return std::max(slider->singleStep(), slider->pageStep() / 3); } if(_event.count < 80) { return std::max(slider->singleStep(), slider->pageStep() / 2); } if(_event.count < 160) { return std::max(slider->singleStep(), slider->pageStep()); } } return slider->singleStep(); }; if(_event.axis_alias == widget_joystick.axis_2) { if(_event.value == direction_t::left) { slider->setValue(slider->value() - get_step()); } else if(_event.value == direction_t::right) { slider->setValue(slider->value() + get_step()); } } else if(_event.axis_alias == widget_joystick.axis_1) { if(_event.value == direction_t::right) { slider->setValue(slider->value() + get_step()); } else if(_event.value == direction_t::left) { slider->setValue(slider->value() - get_step()); } } } } } //----------------------------------------------------------------------------- } //namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/settings.hpp000066400000000000000000000517121503402212300176120ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2024-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { /** * @brief Generic editor to interact with properties. * * It allows to setup an editor with several properties. * Once the user validates the properties, a signal is sent containing the key and the value. * It supports booleans, doubles or integer at the moment. * * @section Slots Slots * - \b update_enum_range(std::string, std::string): update range of an existing enum (value can contains a tokenized * list * such as value1;value2;value3=test;...) * - \b update_int_min_parameter(int, std::string): set the minimum value of an integer parameter (int, int2, int3) * - \b update_int_max_parameter(int, std::string): set the maximum value of an integer parameter (int, int2, int3) * - \b update_double_min_parameter(double, std::string): set the minimum value of a double parameter (double, double2, * double3) * - \b update_double_max_parameter(double, std::string): set the maximum value of a double parameter (double, double2, * double3) * * @section XML XML Configuration * * The configuration with several objects is as follows: * * @code{.xml} @endcode * * The configuration with a map of objects is as follows: * * @code{.xml} @endcode * No default value can be given because in this configuration, it is the responsibility if the data to do it. Doing * otherwise could be confusing and would lead to timing issues with start order. * Also, the type is defined by the type of the object key in the properties map. * * @subsection Configuration Configuration: * tag: * - \b scrollable: If true, add a scroll bar if the content doesn't fit on the screen. If false, flatten the content * tag: * - \b name: label to display. * - \b key: name used in the signal to identify the parameter. * - \b min: minimum value, if relevant for the data type. * - \b max: maximum value, if relevant for the data type. * - \b hide_min_max (optional, boolean): allows to hide the min and max labels. * - \b widget (optional) : widget type, available for types 'sight::data::integer', 'sight::data::real', * 'sight::data::string'. * For 'sight::data::real', you can choose between a 'spin' or a 'slider' widget. Defaults to 'spin'. * For 'sight::data::integer', you can choose between a 'spin', a 'slider', a 'combobox', a 'comboslider', a 'tickmarks' * , or a 'buttonBar'. * For 'string', you can choose between 'text', 'file_[read/write]', 'dir_[read/write]', * buttonBar widget requires additional configuration. * - \b value: the enum value sent when clicking on the button. * - \b label (optional, default=""): test displayed under the button. * - \b icon: path to the icon to display. * - \b decimals (optional, default=2): number of decimals settable using a double slider. * - \b reset (optional, default=true): display the reset button. * - \b values: for 'combobox', 'comboslider', or 'buttonBar' widgets, the list of possible values separated by a * comma ',' a space ' ' or a semicolon ';'. * The actual displayed value and the returned one in the signal can be different using '=' to separate the two. For * example 'values="BLEND=imageBlend,CHECKERBOARD=imageCheckerboard"' means the combo will display BLEND, CHECKERBOARD * and will send 'imageBlend' or 'imageCheckerboard'. * - \b depends (optional, string): key of the dependency. * - \b depends_value (optional, string): value of the dependency in case of enum. * - \b depends_reverse (optional, bool, default=false): reverse the dependency status checking. * - \b emit_on_release (optional, default = false): sliders only, if true send value when slider is released, * send value when value changed otherwise. * - \b min_width (optional, int) Minimum width, in device coordinates. @todo Support relative widget size. * - \b min_height (optional, int) Minimum height, in device coordinates. @todo Support relative widget size. * - \b use_index (optional, bool, default=true): for 'comboslider', whether to use the index or the value (if "false") * of the element. * - \b joystick (optional, string): joystick alias to use for the widget. It can be 'left' or 'right'. * - \b joystick_axis (optional, string): joystick axes to use for the widget. It can be a combination up to three axes. * Allowed values: 'rx', 'ry', 'rz', 'tx', 'ty', 'tz'. */ class settings : public QObject, public sight::ui::editor, public sight::io::joystick::interactor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(settings, sight::ui::editor); /// @brief Struct to handle all slots struct slots { using key_t = core::com::slots::key_t; inline static const key_t UPDATE_ENUM_RANGE = "update_enum_range"; inline static const key_t UPDATE_INT_MIN_PARAMETER = "update_int_min_parameter"; inline static const key_t UPDATE_INT_MAX_PARAMETER = "update_int_max_parameter"; inline static const key_t UPDATE_DOUBLE_MIN_PARAMETER = "update_double_min_parameter"; inline static const key_t UPDATE_DOUBLE_MAX_PARAMETER = "update_double_max_parameter"; }; struct enum_button_param { std::string value {}; std::string label {}; std::string icon_path {}; }; struct param_widget { std::string name {}; std::string key {}; std::size_t data_index {0}; std::string default_value {}; bool reset_button {true}; bool hide_min_max {false}; bool preference {false}; boost::optional min_width {}; boost::optional min_height {}; bool use_index {true}; }; struct qt_property { inline static const char* key = "key"; inline static const char* data_index = "data_index"; inline static const char* count = "count"; inline static const char* index = "index"; inline static const char* use_index = "use_index"; }; template struct scalar_widget : param_widget { T min = T {0}; T max = T {1}; }; using int_widget = scalar_widget; using double_widget = scalar_widget; settings() noexcept; /// Destructor. Does nothing ~settings() noexcept override = default; protected: /// Configure the editor. void configuring() override; /// Initializes Qt input widgets for settings according to xml configuration void starting() override; /// This method launches the editor::stopping method void stopping() override; /// This method is used to update services. Does nothing void updating() override; /** * @brief Manage joystick events * * @param _event */ void joystick_axis_direction_event(const sight::io::joystick::axis_direction_event& _event) final; private Q_SLOTS: /** * @brief Called when a dependency widget state (enable or disable) has changed to modify the state of the child * widget. * @param _check_box Dependency widget. * @param _widget Child widget. * @param _reverse Reverse the state check. */ static void on_depends_changed(QCheckBox* _check_box, QWidget* _widget, bool _reverse); /** * @brief Called when a dependency widget state (enable or disable) has changed to modify the state of the child * widget. * @param _combo_box Dependency widget. * @param _widget Child widget. * @param _value Value of the combo box. * @param _reverse Reverse the state check. */ static void on_depends_changed(QComboBox* _combo_box, QWidget* _widget, const std::string& _value, bool _reverse); /// This method is called when a color button is clicked void on_color_button(); /// This method is called when an integer value changes void on_change_integer(int _value); /// This method is called when a double value changes void on_change_double(double _value); /// This method is called when a double slider value changes void on_change_double_slider(int _value); /// This method is called to connect sliders to their labels static void on_slider_mapped(QLabel* _label, QSlider* _slider); /// This method is called to connect double sliders to their labels static void on_double_slider_mapped(QLabel* _label, QSlider* _slider); /// This method is called to connect reset buttons and checkboxes void on_reset_boolean(QWidget* _widget); /// This method is called to connect reset buttons and color widgets void on_reset_color(QWidget* _widget); /// This method is called to connect reset buttons and sliders void on_reset_integer(QWidget* _widget); /// This method is called to connect reset buttons and sliders void on_reset_double(QWidget* _widget); /// This method is called to connect reset buttons and text widgets void on_reset_string(QWidget* _widget); /// This method is called when the integer slider range is modified, it updates the min and max labels static void on_slider_range_mapped(QLabel* _min_label, QLabel* _max_label, QSlider* _slider); /// This method is called when the double slider range is modified, it updates the min and max labels static void on_double_slider_range_mapped(QLabel* _min_label, QLabel* _max_label, QSlider* _slider); private: /** * @brief Called on all dependent widget to update it. * @param _watched Widget to update. * @param _event Event type, only care about ::QEvent::EnabledChange * @return False. */ bool eventFilter(QObject* _watched, QEvent* _event) override; /// Creates a reset button for one widget. /// @param _key Name of the parameter it resets. /// @param _on_click Slot to call when the button is clicked (when QPushButton::clicked is sent) /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_reset_button(const std::string& _key, std::function _on_click) const; /// Create a widget associated with a boolean type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_bool_widget(QBoxLayout* _layout, const param_widget& _setup, Qt::Orientation _orientation); /// Create a widget associated with a color type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_color_widget(QBoxLayout* _layout, const param_widget& _setup); /// Create a widget associated with a double type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_double_spin_widget( QBoxLayout* _layout, const double_widget& _setup, int _count, Qt::Orientation _orientation ); /// Create a slider widget associated with a double type. /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_double_slider_widget( QBoxLayout* _layout, const double_widget& _setup, std::uint8_t _decimals, Qt::Orientation _orientation, bool _on_release ); /// Create a slider widget associated with an integer type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_integer_slider_widget( QBoxLayout* _layout, const int_widget& _setup, Qt::Orientation _orientation, bool _on_release ); /// Create a spin widget associated with an integer type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_integer_spin_widget( QBoxLayout* _layout, const int_widget& _setup, int _count, Qt::Orientation _orientation ); /// Parses the string for an enum static void parse_enum_string( const std::string& _options, std::vector& _labels, std::vector& _keys, std::string _separators = ",;\n\t" ); /// Create a multi choice widget void create_enum_combobox_widget( QBoxLayout* _layout, const param_widget& _setup, const std::vector& _values, const std::vector& _data ); /// Create a multi choice widget with integer values void create_enum_slider_widget( QBoxLayout* _layout, const param_widget& _setup, const std::vector& _values, Qt::Orientation _orientation, bool _on_release ); /// Create a tickmarks widget void create_tickmarks_widget( QBoxLayout* _layout, const param_widget& _setup, const std::vector& _values ); void create_enum_button_bar_widget( QBoxLayout* _layout, const param_widget& _setup, const std::vector& _button_list, const int _width, const int _height, const int _spacing, const std::string& _style, Qt::Orientation _orientation ); /// Create a text widget associated with a string type /// @returns The reset button, to put in a layout of your choice, or nullptr if not required. [[nodiscard]] QPushButton* create_text_widget(QBoxLayout* _layout, const param_widget& _setup, const std::string& _type); /** * @name Slots * @{ */ template requires std::derived_from > void set_parameter(const SUBTYPE& _val, std::string _key); /// SLOT: This method is used to set an enum parameter. void set_enum_parameter(std::string _val, std::string _key); /// SLOT: This method sets an enum parameter using the index of the enum void set_enum_index_parameter(int /*val*/, std::string _key); /// SLOT: This method updates the all enum values using a tokenized string ("value1;value2") void update_enum_range(std::string _options, std::string _key); /// SLOT: Updates the minimum value of an integer parameter (int, int2, int3) void update_int_min_parameter(int _min, std::string _key); /// SLOT: Updates the maximum value of an integer parameter (int, int2, int3) void update_int_max_parameter(int _max, std::string _key); /// SLOT: Updates the minimum value of a double parameter (double, double2, double3) void update_double_min_parameter(double _min, std::string _key); /// SLOT: Updates the maximum value of a double parameter (double, double2, double3) void update_double_max_parameter(double _max, std::string _key); /// @} /// Updates the values of tickmarks widgets void update_tickmarks(sight::ui::qt::widget::tickmarks_slider* const _tickmarks, const std::string& _options); /// Return the widget of the parameter with the given key, or nullptr if it does not exist QObject* get_param_widget(const std::string& _key); /// Compute the double slider value from a slider position. static double get_double_slider_value(const QSlider* _slider); /// Compute the double slider range according to the min and max property, update the internal slider value /// according to the new range static void set_double_slider_range(QSlider* _slider, double _current_value); /// Adjust the minimum size of a label according to the range values template static void set_label_minimum_size(QLabel* _label, T _min, T _max, std::uint8_t _decimals = 0); template static QString value_to_string_label(T _value, std::uint8_t _decimals); /// Block (or not) signal emission for this service void block_signals(bool _block); /// Returns the data pointer at the given index and key template requires std::derived_from > CSPTR(DATATYPE) data(const QObject * _widget); template requires std::derived_from > void connect_data(const CSPTR(DATATYPE)& _obj, const std::string& _key); template requires std::derived_from > void update_data(const QObject* _widget, const SUBTYPE& _val); /// if true, the signals are not emitted bool m_block_signals {false}; /// The list of intermediate boxes containing each widgets. This array is processed each time we need to find /// a parameter with a given key (when enabling/disabling, etc.). /// This vector is cleared upon stopping(). std::vector > m_param_boxes; /// Used when we bind widgets to data data::ptr_vector m_settings {this, "keys"}; /// Used when we bind widgets to a map of data data::ptr m_settings_map {this, "map"}; using object_modified_t = core::com::slot; using settings_slot_container_t = std::map; settings_slot_container_t m_settings_slots; struct widget_joystick { sight::io::joystick::joystick_t alias {sight::io::joystick::joystick_t::unknown}; sight::io::joystick::axis_t axis_1 {sight::io::joystick::axis_t::unknown}; sight::io::joystick::axis_t axis_2 {sight::io::joystick::axis_t::unknown}; sight::io::joystick::axis_t axis_3 {sight::io::joystick::axis_t::unknown}; }; std::map m_widget_joysticks; }; //------------------------------------------------------------------------------ } //namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/show_about.cpp000066400000000000000000000117641503402212300201220ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include #if defined(QT_WEBKIT) #include #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include "show_about.hpp" namespace sight::module::ui::qt { //------------------------------------------------------------------------------ show_about::show_about() noexcept : m_fs_about_path(""), m_title("About"), m_size(500, 300) { } //------------------------------------------------------------------------------ show_about::~show_about() noexcept = default; //------------------------------------------------------------------------------ void show_about::info(std::ostream& _sstream) { _sstream << "show_about" << std::endl; } //------------------------------------------------------------------------------ void show_about::configuring() { this->sight::ui::action::initialize(); const auto& config = this->get_config(); const auto filename = config.get("filename..id"); // Convert the path from a module location m_fs_about_path = core::runtime::get_module_resource_file_path(filename); m_b_service_is_configured = std::filesystem::exists(m_fs_about_path); SIGHT_WARN_IF("About file " + filename + " doesn't exist", !m_b_service_is_configured); m_title = config.get("title", m_title); m_size.setWidth(config.get("size..width", m_size.width())); m_size.setHeight(config.get("size..height", m_size.height())); } //------------------------------------------------------------------------------ void show_about::updating() { SIGHT_ASSERT("The service 'show_about' isn't configured properly.", m_b_service_is_configured); auto* dialog = new QDialog(qApp->activeWindow()); dialog->setWindowTitle(QString::fromStdString(m_title)); QUrl url = QUrl::fromLocalFile(QString::fromStdString(m_fs_about_path.string())); #if defined(QT_WEBKIT) QWebView* htmlView = new QWebView(dialog); htmlView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); htmlView->load(url); QObject::connect(htmlView, SIGNAL(linkClicked(const QUrl&)), this, SLOT(on_url_clicked(const QUrl&))); #else auto* html_view = new QTextBrowser(dialog); html_view->setSource(url); html_view->setOpenExternalLinks(true); html_view->setMinimumSize(m_size); QStringList search_paths; search_paths.append(QString::fromStdString(m_fs_about_path.parent_path().string())); html_view->setSearchPaths(search_paths); #endif auto* close_button = new QPushButton(QObject::tr("Close")); auto* h_layout = new QHBoxLayout(); h_layout->addStretch(); h_layout->addWidget(close_button); h_layout->setContentsMargins(5, 5, 5, 5); auto* line = new QFrame(dialog); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); auto* layout = new QVBoxLayout(); layout->addWidget(html_view, 0); layout->addWidget(line, 0); layout->addLayout(h_layout, 0); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); dialog->setLayout(layout); QObject::connect(close_button, SIGNAL(clicked()), dialog, SLOT(accept())); QObject::connect(dialog, SIGNAL(accepted()), dialog, SLOT(deleteLater())); dialog->setModal(true); dialog->show(); } //------------------------------------------------------------------------------ void show_about::starting() { this->sight::ui::action::action_service_starting(); } //------------------------------------------------------------------------------ void show_about::stopping() { this->sight::ui::action::action_service_stopping(); } //------------------------------------------------------------------------------ void show_about::on_url_clicked(const QUrl& _url) { QDesktopServices::openUrl(_url); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/show_about.hpp000066400000000000000000000051011503402212300201130ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::ui::qt { /** * @brief This action show the about frame. */ class show_about : public QObject, public sight::ui::action { Q_OBJECT public: SIGHT_DECLARE_SERVICE(show_about, sight::ui::action); /** * @name Constructor/Destructor * @{ */ show_about() noexcept; ~show_about() noexcept override; /** @} */ protected: /** * @brief Configuring method. * * XML configuration sample: @code{.xml} title @endcode * This method is used to configure the service. */ void configuring() override; /// Starts action void starting() override; /// Shows the frame void updating() override; /// Stops action void stopping() override; /// Prints service info void info(std::ostream& _sstream) override; protected Q_SLOTS: /** * @brief Triggered when an URL is clicked in the about frame. * * @param _url clicked URL */ static void on_url_clicked(const QUrl& _url); private: /// Set to 'true' if the about file path is known. bool m_b_service_is_configured {false}; /** * @brief about file path. */ std::filesystem::path m_fs_about_path; /// Frame title (default value set to "About"). std::string m_title; /// Frame size. QSize m_size; }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/show_help.cpp000066400000000000000000000120441503402212300177300ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "show_help.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { //------------------------------------------------------------------------------ /** @brief Help browser */ class help_browser : public QTextBrowser { public: explicit help_browser(QHelpEngine* _help_engine, QWidget* _parent = nullptr) : QTextBrowser(_parent), m_help_engine(_help_engine) { } //------------------------------------------------------------------------------ QVariant loadResource(int _type, const QUrl& _url) override { if(_url.scheme() == "qthelp") { return {m_help_engine->fileData(_url)}; } return QTextBrowser::loadResource(_type, _url); } private: QHelpEngine* m_help_engine; }; //------------------------------------------------------------------------------ void show_help::info(std::ostream& _sstream) { _sstream << "Action for show help contents" << std::endl; } //------------------------------------------------------------------------------ void show_help::configuring() { /* * .qhp/.qch (source/binary) : Contains a table of contents, * an index of items in the documentation, and a file manifest. * .qhcp/.qhc (source/binary): Contains information that is used to customize * the appearance and available features of Qt Assistant. */ this->sight::ui::action::initialize(); const auto configuration = this->get_config(); const auto filename = configuration.get("filename..id"); m_fs_help_path = std::filesystem::path(filename); m_b_service_is_configured = std::filesystem::exists(m_fs_help_path); SIGHT_WARN_IF("Help file " << filename << " doesn't exist", !m_b_service_is_configured); } //------------------------------------------------------------------------------ void show_help::updating() { SIGHT_ASSERT("The Help service isn't configured properly.", m_b_service_is_configured); auto* dialog = new QDialog(qApp->activeWindow()); dialog->setWindowTitle(QString("Help")); auto* help_engine = new QHelpEngine(QString::fromStdString(m_fs_help_path.string()), dialog); if(!help_engine->setupData()) { SIGHT_ERROR("HelpEngine error: " << help_engine->error().toStdString()); sight::ui::dialog::message message_box; message_box.set_title("Warning"); message_box.set_message("Help file is missing or not correct."); message_box.set_icon(sight::ui::dialog::message::warning); message_box.add_button(sight::ui::dialog::message::ok); message_box.show(); // Setup help engine information failed. // qhc (Qt Help Collection) or qch (Qt Compressed Help) file is not correct. } else { auto* help_panel = new QSplitter(Qt::Horizontal); auto* help_browser = new class help_browser (help_engine, dialog); help_panel->insertWidget(0, help_engine->contentWidget()); help_panel->insertWidget(1, help_browser); help_panel->setStretchFactor(1, 1); auto* h_layout = new QHBoxLayout(); h_layout->addWidget(help_panel); dialog->setLayout(h_layout); QObject::connect( help_engine->contentWidget(), SIGNAL(linkActivated(const QUrl&)), help_browser, SLOT(setSource(const QUrl&)) ); dialog->exec(); } } //------------------------------------------------------------------------------ void show_help::starting() { this->sight::ui::action::action_service_starting(); } //------------------------------------------------------------------------------ void show_help::stopping() { this->sight::ui::action::action_service_stopping(); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/show_help.hpp000066400000000000000000000041251503402212300177360ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include namespace sight::module::ui::qt { /** * @brief This action show the help contents. */ class show_help : public sight::ui::action { public: SIGHT_DECLARE_SERVICE(show_help, sight::ui::action); show_help() noexcept = default; ~show_help() noexcept override = default; protected: /** * @brief Configuring method. * * XML configuration sample: * @code{.xml} @endcode * This method is used to configure the service. */ void configuring() override; /// Starts action void starting() override; /// Show the frame void updating() override; /// Stops action void stopping() override; void info(std::ostream& _sstream) override; private: /** * @brief the m_bServiceIsConfigured value is \b true * if the help files path is known. */ bool m_b_service_is_configured {false}; /** * @brief help files path. */ std::filesystem::path m_fs_help_path; }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/status.cpp000066400000000000000000000221551503402212300172670ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "status.hpp" #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { enum class status_color : std::uint8_t { green, orange, red }; //------------------------------------------------------------------------------ QIcon& icon(status_color _color) { std::array icons = {"status_green.svg", "status_orange.svg", "status_red.svg"}; const auto path = core::runtime::get_module_resource_file_path( "sight::module::ui::icons", icons[static_cast(_color)] ); static QIcon icon; icon.addFile(QString::fromStdString(path.string()), QSize(), QIcon::Disabled); return icon; } //----------------------------------------------------------------------------- status::status() noexcept { new_slot(slots::CHANGE_TO_GREEN_SLOT, &status::change_to_green, this); new_slot(slots::CHANGE_TO_RED_SLOT, &status::change_to_red, this); new_slot(slots::CHANGE_TO_ORANGE_SLOT, &status::change_to_orange, this); new_slot(slots::TOGGLE_GREEN_RED_SLOT, &status::toggle_green_red, this); new_slot(slots::CHANGE_NTH_TO_GREEN_SLOT, &status::change_nth_to_green, this); new_slot(slots::CHANGE_NTH_TO_RED_SLOT, &status::change_nth_to_red, this); new_slot(slots::CHANGE_NTH_TO_ORANGE_SLOT, &status::change_nth_to_orange, this); new_slot(slots::TOGGLE_NTH_GREEN_RED_SLOT, &status::toggle_nth_green_red, this); } //------------------------------------------------------------------------------ void status::configuring(const config_t& _config) { this->initialize(); m_green_tooltip = _config.get("green", ""); m_red_tooltip = _config.get("red", ""); m_orange_tooltip = _config.get("orange", ""); m_orientation = _config.get("layout", "horizontal"); SIGHT_ASSERT( "Value for element 'layout' should be 'horizontal' or 'vertical'.", m_orientation == "horizontal" || m_orientation == "vertical" ); const auto label_display = _config.get("labels..display", "under"); SIGHT_ASSERT( "Value for element 'display' should be 'beside' or 'under'.", label_display == "beside" || label_display == "under" ); m_label_display = label_display == "beside" ? label_display::BESIDE : label_display::UNDER; const auto config_labels = _config.get_child_optional("labels"); // Check if the labels tag exists if(config_labels) { const auto label_status_config = config_labels.get().equal_range("name"); // Fill the label_status vector for(int i = 0 ; const service::config_t::value_type & v : boost::make_iterator_range(label_status_config)) { const auto label = v.second.get(""); auto* status = new QToolButton(); status->setText(QString::fromStdString(label)); const QString service_id = QString::fromStdString(base_id()); status->setObjectName(service_id + "/" + QString::number(i++)); m_label_status.push_back(status); } } } //------------------------------------------------------------------------------ void status::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); QBoxLayout* layout = nullptr; if(m_orientation == "horizontal") { layout = new QHBoxLayout(); } else { layout = new QVBoxLayout(); } for(auto& status : m_label_status) { if(!status->text().isEmpty()) { if(m_label_display == label_display::BESIDE) { status->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else { status->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } } else { status->setToolButtonStyle(Qt::ToolButtonIconOnly); } layout->addWidget(status); status->setDisabled(true); status->setProperty("class", "status"); } layout->setContentsMargins(0, 0, 0, 0); qt_container->set_layout(layout); this->change_to_red(); } //------------------------------------------------------------------------------ void status::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void status::updating() { } //------------------------------------------------------------------------------ void status::change_to_green() { for(auto& status : m_label_status) { status->setIcon(icon(status_color::green)); status->setToolTip(QString::fromStdString(m_green_tooltip)); } } //------------------------------------------------------------------------------ void status::change_to_red() { for(auto& status : m_label_status) { status->setIcon(icon(status_color::red)); status->setToolTip(QString::fromStdString(m_red_tooltip)); } } //------------------------------------------------------------------------------ void status::change_to_orange() { for(auto& status : m_label_status) { status->setIcon(icon(status_color::orange)); status->setToolTip(QString::fromStdString(m_orange_tooltip)); } } //------------------------------------------------------------------------------ void status::toggle_green_red(const bool _green) { for(auto& status : m_label_status) { status->setIcon(_green ? icon(status_color::green) : icon(status_color::red)); status->setToolTip(_green ? QString::fromStdString(m_green_tooltip) : QString::fromStdString(m_red_tooltip)); } } //------------------------------------------------------------------------------ void status::change_nth_to_green(const int _index) { SIGHT_DEBUG_IF( "Index(" << _index << ") must be in vector range [0:" << m_label_status.size() - 1 << "]", _index < 0 || _index >= int(m_label_status.size()) ); if(_index >= 0 && _index < m_label_status.size()) { const auto status = m_label_status.at(_index); status->setIcon(icon(status_color::green)); status->setToolTip(QString::fromStdString(m_green_tooltip)); } } //------------------------------------------------------------------------------ void status::change_nth_to_red(const int _index) { SIGHT_DEBUG_IF( "Index(" << _index << ") must be in vector range [0:" << m_label_status.size() - 1 << "]", _index < 0 || _index >= int(m_label_status.size()) ); if(_index >= 0 && _index < m_label_status.size()) { const auto status = m_label_status.at(_index); status->setIcon(icon(status_color::red)); status->setToolTip(QString::fromStdString(m_red_tooltip)); } } //------------------------------------------------------------------------------ void status::change_nth_to_orange(const int _index) { SIGHT_DEBUG_IF( "Index(" << _index << ") must be in vector range [0:" << m_label_status.size() - 1 << "]", _index < 0 || _index >= int(m_label_status.size()) ); if(_index >= 0 && _index < m_label_status.size()) { const auto status = m_label_status.at(_index); status->setIcon(icon(status_color::orange)); status->setToolTip(QString::fromStdString(m_orange_tooltip)); } } //------------------------------------------------------------------------------ void status::toggle_nth_green_red(const int _index, const bool _green) { SIGHT_DEBUG_IF( "Index(" << _index << ") must be in vector range [0:" << m_label_status.size() - 1 << "]", _index < 0 || _index >= int(m_label_status.size()) ); if(_index >= 0 && _index < m_label_status.size()) { const auto status = m_label_status.at(_index); status->setIcon(_green ? icon(status_color::green) : icon(status_color::red)); status->setToolTip(_green ? QString::fromStdString(m_green_tooltip) : QString::fromStdString(m_red_tooltip)); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/status.hpp000066400000000000000000000133701503402212300172730ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::ui::qt { /** * @brief Shows a colored circle (red, orange, green) representing a status. * * @note To change the status color, you should call the slots 'changeToGreen', * 'changeToOrange', 'changeToRed' * or 'toggleGreenRed'. * * @brief Configures the status tooltip * * @section XML XML Configuration * @code{.xml} horizontal|vertical SCP Server TCP Server Stopped Tracking Started @endcode * - \b count (optional, '1' by default): the number of status * - \b layout(optional, 'horizontal' by default): orientation of the layout * - \b labels (optional): the description associated to the indicators when count > 1 * - \b display (optional, 'under' by default): location of the label display * - \b name (optional): the description associated to each indicator * - \b name (optional): the description associated to the indicator when count = 1 or is missing * - \b red (optional): the description associated to the red status * - \b green (optional): the description associated to the green status * - \b orange (optional): the description associated to the orange status * * @section Slots Slots * - \b change_to_green(): This slot allows to change the indicator color to green. If there is more than one status, it * changes them all. * - \b change_to_red(): This slot allows to change the indicator color to red. If there is more than one status, it * changes them all. * - \b change_to_orange(): This slot allows to change the indicator color to orange. If there is more than one status, * it * changes them all. * - \b toggle_green_red(bool): This slot allows to change the indicator color to green or red. If there is more than * one, * it toggle them all. * - \b change_nth_to_green(int): This slot allows to change the indicator color to green for the ith status. * - \b change_nth_to_red(int): This slot allows to change the indicator color to red for the ith status. * - \b change_nth_to_orange(int): This slot allows to change the indicator color to orange for the ith status. * - \b toggle_nth_green_red(int,bool): This slot allows to change the indicator color to green or red for the ith * status. */ class status : public QObject, public sight::ui::editor { public: SIGHT_DECLARE_SERVICE(status, sight::ui::editor); /// Constructor. Do nothing. status() noexcept; /// Destructor. Do nothing. ~status() noexcept override = default; struct slots { static inline const core::com::slots::key_t CHANGE_TO_GREEN_SLOT = "change_to_green"; static inline const core::com::slots::key_t CHANGE_TO_RED_SLOT = "change_to_red"; static inline const core::com::slots::key_t CHANGE_TO_ORANGE_SLOT = "change_to_orange"; static inline const core::com::slots::key_t TOGGLE_GREEN_RED_SLOT = "toggle_green_red"; static inline const core::com::slots::key_t CHANGE_NTH_TO_GREEN_SLOT = "change_nth_to_green"; static inline const core::com::slots::key_t CHANGE_NTH_TO_RED_SLOT = "change_nth_to_red"; static inline const core::com::slots::key_t CHANGE_NTH_TO_ORANGE_SLOT = "change_nth_to_orange"; static inline const core::com::slots::key_t TOGGLE_NTH_GREEN_RED_SLOT = "toggle_nth_green_red"; }; protected: void configuring(const config_t& _config) override; void starting() override; void stopping() override; /// Does nothing void updating() override; /// SLOT : change label color to green void change_to_green(); /// SLOT : change label color to red void change_to_red(); /// SLOT : change label color to orange void change_to_orange(); /// SLOT : change label color (true = green, false = red) void toggle_green_red(bool _green); /// SLOT : change nth label color to green void change_nth_to_green(int _index); /// SLOT : change nth label color to red void change_nth_to_red(int _index); /// SLOT : change nth label color to orange void change_nth_to_orange(int _index); /// SLOT : change nth label color (true = green, false = red) void toggle_nth_green_red(int _index, bool _green); private: QVector > m_label_status; std::string m_green_tooltip; ///< Tooltip for green status std::string m_red_tooltip; ///< Tooltip for red status std::string m_orange_tooltip; ///< Tooltip for orange status std::string m_orientation; ///< Layout orientation enum class label_display { UNDER, BESIDE } m_label_display {label_display::UNDER}; }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/style_selector.cpp000066400000000000000000000111561503402212300210030ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2023 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/style_selector.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt { static const core::com::slots::key_t UPDATE_FROM_PREFS_SLOT = "update_from_preferences"; //----------------------------------------------------------------------------- style_selector::style_selector() noexcept { new_slot(UPDATE_FROM_PREFS_SLOT, &style_selector::update_from_prefs, this); } //----------------------------------------------------------------------------- style_selector::~style_selector() noexcept = default; //----------------------------------------------------------------------------- void style_selector::configuring() { } //----------------------------------------------------------------------------- void style_selector::starting() { m_style_map["DEFAULT"] = std::filesystem::path(""); const auto style_rc = core::runtime::get_module_resource_path("sight::module::ui::qt"); // Stores each rcc & qss for(const auto& p : std::filesystem::directory_iterator(style_rc)) { std::filesystem::path f = p; if(f.extension() == ".rcc") { const std::string filename = f.filename().replace_extension("").string(); std::string name = filename; std::transform( filename.begin(), filename.end(), name.begin(), [](unsigned char _c) -> unsigned char {return static_cast(std::toupper(_c));}); m_style_map[name] = f.replace_extension(""); } } // Apply theme from preferences if any. this->update_from_prefs(); } //----------------------------------------------------------------------------- void style_selector::stopping() { m_style_map.clear(); } //----------------------------------------------------------------------------- void style_selector::updating() { } //----------------------------------------------------------------------------- void style_selector::change_style(const std::string& _style_name) { auto path = m_style_map[_style_name]; sight::ui::preferences preferences; // DEFAULT (no theme) case. if(path.empty()) { qApp->setStyleSheet(""); preferences.put("THEME", "DEFAULT"); return; } // Load ressources [[maybe_unused]] const bool resource_loaded = QResource::registerResource( path.replace_extension( ".rcc" ).string().c_str() ); SIGHT_ASSERT("Cannot load resources '" + path.replace_extension(".rcc").string() + "'.", resource_loaded); // Load stylesheet. QFile data(QString::fromStdString(path.replace_extension(".qss").string())); if(data.open(QFile::ReadOnly)) { QTextStream style_in(&data); const QString style = style_in.readAll(); data.close(); qApp->setStyleSheet(style); preferences.put("THEME", _style_name); } } //----------------------------------------------------------------------------- void style_selector::update_from_prefs() { // Apply previously saved style in preferences file. try { sight::ui::preferences preferences; if(const auto& theme = preferences.get_optional("THEME"); theme) { this->change_style(*theme); } } catch(const sight::ui::preferences_disabled& /*e*/) { // Nothing to do.. } } //----------------------------------------------------------------------------- } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/style_selector.hpp000066400000000000000000000065401503402212300210110ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2024 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::ui::qt { /** * @brief selector of theme/style. * Theme should be stored in the rc/ folder of this module. * * When a new theme is applied on an application, it will be saved in preference file with "THEME" key. * style_selector will first load & apply the theme saved in preference file. * * Theme names are generated by parsing the *.rcc (from *.qrc files) and then name are uppercased (ex: flatdark.rcc * becames FLATDARK). * * To change a theme while application is running please use a preferences_configuration like : * @code{.xml} combobox Color Theme THEME DEFAULT,FLATDARK DEFAULT * @endcode * * Next connect the "parameters_modified" signal to the "update_from_preferences" slot of this service. * * @section Slots Slots * - \b updateFromPrefs(): called when changing the preference key "THEME", apply the selected theme * by reading preference file. * * @section XML XML Configuration * * @code{.xml} @endcode */ class style_selector : public service::controller { public: SIGHT_DECLARE_SERVICE(style_selector, sight::service::controller); /// Constructor, initializes slots. style_selector() noexcept; /// Destructor, clears the position map. ~style_selector() noexcept override; protected: /** @name Service methods ( override from service::base ) * @{ */ /// Does nothing. void configuring() override; /** * @brief Starts and setups the service by parsing rc folder to find availble themes. */ void starting() override; /** * @brief Does nothing */ void stopping() override; /** * @brief Does nothing. */ void updating() override; private: /// Applies the style _stylename. void change_style(const std::string& _style_name); ///Slot: check preference key THEME, and update theme accordingly. void update_from_prefs(); /// Map to stores theme names and their path. std::map m_style_map; }; } //namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/test/000077500000000000000000000000001503402212300162125ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/test/ut/000077500000000000000000000000001503402212300166425ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/test/ut/CMakeLists.txt000066400000000000000000000007661503402212300214130ustar00rootroot00000000000000sight_add_target(module_ui_qt_ut TYPE TEST) find_package(Qt6 QUIET COMPONENTS Widgets SvgWidgets Test REQUIRED) target_link_libraries(${SIGHT_TARGET} PRIVATE Qt6::Widgets Qt6::SvgWidgets Qt6::Test) set_target_properties(${SIGHT_TARGET} PROPERTIES AUTOMOC TRUE) target_compile_definitions(${SIGHT_TARGET} PUBLIC "QT_NO_KEYWORDS") add_dependencies(${SIGHT_TARGET} module_service module_ui module_ui_qt module_app) target_link_libraries(${SIGHT_TARGET} PUBLIC utest utest_data core data service ui_qt) sight-25.1.0/module/ui/qt/test/ut/gui_qt_test.cpp000066400000000000000000000073621503402212300217050ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "gui_qt_test.hpp" #include #include #include #include // Registers the fixture into the 'registry' CPPUNIT_TEST_SUITE_REGISTRATION(sight::module::ui::qt::ut::gui_qt_test); namespace sight::module::ui::qt::ut { //------------------------------------------------------------------------------ void gui_qt_test::setUp() { // Set up context before running a test. } //------------------------------------------------------------------------------ void gui_qt_test::tearDown() { // Clean up after the test run. } //------------------------------------------------------------------------------ #define ASSERT_NOT_NULL(expr) if((expr) == nullptr){throw std::runtime_error(#expr " is null.");} //------------------------------------------------------------------------------ void gui_qt_test::test_default_frame() { data::string::sptr object = std::make_shared(); service::config_t frame_config; frame_config.put("gui.frame.name", "guiQtUnitTest"); frame_config.put("gui.frame.min_size..width", "800"); frame_config.put("gui.frame.min_size..height", "600"); service::base::sptr srv = service::add("sight::module::ui::frame"); ASSERT_NOT_NULL(srv); srv->set_config(frame_config); srv->configure(); srv->start(); auto* window = qobject_cast(qApp->activeWindow()); ASSERT_NOT_NULL(qApp); ASSERT_NOT_NULL(qApp->activeWindow()); ASSERT_NOT_NULL(window); CPPUNIT_ASSERT_EQUAL(std::string("guiQtUnitTest"), window->windowTitle().toStdString()); srv->stop(); service::unregister_service(srv); } //------------------------------------------------------------------------------ void gui_qt_test::test_fullscreen_frame() { data::string::sptr object = std::make_shared(); service::config_t frame_config; frame_config.put("gui.frame.name", "gui_qt_test_test_fullscreen_frame"); frame_config.put("gui.frame.min_size..width", "800"); frame_config.put("gui.frame.min_size..height", "600"); #ifndef _WIN32 frame_config.put("gui.frame.style..mode", "FULLSCREEN"); #else frame_config.put("gui.frame.style..mode", "FRAMELESS"); #endif frame_config.put("gui.frame.screen..index", "0"); service::base::sptr srv = service::add("sight::module::ui::frame"); ASSERT_NOT_NULL(srv); srv->set_config(frame_config); srv->configure(); srv->start(); auto* window = qobject_cast(qApp->activeWindow()); ASSERT_NOT_NULL(qApp); ASSERT_NOT_NULL(qApp->activeWindow()); ASSERT_NOT_NULL(window); CPPUNIT_ASSERT_EQUAL(std::string("guiQtUnitTest"), window->windowTitle().toStdString()); srv->stop(); service::unregister_service(srv); } } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/gui_qt_test.hpp000066400000000000000000000026671503402212300217150ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include namespace sight::module::ui::qt::ut { /** * @brief Test many methods to create mesh. */ class gui_qt_test : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE(gui_qt_test); CPPUNIT_TEST(test_default_frame); CPPUNIT_TEST(test_fullscreen_frame); CPPUNIT_TEST_SUITE_END(); public: // interface void setUp() override; void tearDown() override; static void test_default_frame(); static void test_fullscreen_frame(); }; } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/loader.cpp000066400000000000000000000127571503402212300206300ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "loader.hpp" #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::ut { inline static const struct loader final { loader() { CPPUNIT_ASSERT_NO_THROW( core::runtime::add_modules( core::runtime::get_resource_file_path( "module_ui_qt_ut" ) ) ); sight::core::runtime::init(); } } LOADER; //------------------------------------------------------------------------------ std::tuple make_container(const std::string& _child_uuid) { auto uuid = _child_uuid; if(uuid.empty()) { uuid = core::tools::uuid::generate(); uuid.erase(std::remove(uuid.begin(), uuid.end(), '-'), uuid.end()); } const service::base::sptr container(service::add("sight::module::ui::frame")); service::config_t container_config; container_config.put("gui.frame.name", "guiQtUnitTest"); container_config.put("gui.frame.min_size..width", "640"); container_config.put("gui.frame.min_size..height", "480"); container_config.put("registry.view..sid", uuid); // Configure and start the container service container->configure(container_config); container->start().get(); return std::make_tuple(container, uuid); } //------------------------------------------------------------------------------ void destroy_container(service::base::sptr& _container) { if(_container) { if(_container->started()) { _container->stop().get(); } service::remove(_container); // Do not forget to reset the container to close remaining windows, otherwise the next test will fail _container.reset(); } } //------------------------------------------------------------------------------ std::shared_future click_on_button( const std::string& _button_name, const std::string& _widget_name ) { const auto& widget_name = QString::fromStdString(_widget_name); const auto& button_name = QString::fromStdString(_button_name); return wait_for_widget( [widget_name, button_name](QWidget* _widget) { if(widget_name.isEmpty() || _widget->objectName().startsWith(widget_name)) { if(auto* button = _widget->findChild(button_name); button != nullptr) { QPointer button_ptr(button); core::thread::get_default_worker()->post_task( [button_ptr] { if(!button_ptr.isNull()) { button_ptr->click(); } }).get(); return true; } } return false; }); } //------------------------------------------------------------------------------ std::shared_future wait_for_widget(std::function _predicate) { return std::async( std::launch::async, [_predicate] { for(const auto start = std::chrono::steady_clock::now() ; std::chrono::steady_clock::now() - start < std::chrono::seconds(3) ; ) { for(auto* widget : qApp->allWidgets()) { if(_predicate(widget)) { return true; } } // Wait a bit std::this_thread::sleep_for(std::chrono::milliseconds(100)); } // Close all modal windows to exit the local event loop if(QWindow* window = qApp->modalWindow(); window != nullptr) { QPointer window_ptr(window); core::thread::get_default_worker()->post_task( [window_ptr] { if(!window_ptr.isNull()) { window_ptr->close(); } }); } else { core::thread::get_default_worker()->post_task( [] { qApp->closeAllWindows(); }); } return false; }); } } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/loader.hpp000066400000000000000000000037571503402212300206350ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::ui::qt::ut { // Utility class to clean a service when destroyed struct service_cleaner final { service_cleaner(service::base::sptr service) : m_service(std::move(service)) { } ~service_cleaner() { if(m_service->started()) { m_service->stop().wait(); } service::remove(m_service); } service::base::sptr m_service; }; /// Build a container service and start it std::tuple make_container(const std::string& _child_uuid = std::string()); /// Stop and unregister the container service. void destroy_container(service::base::sptr& _container); /// Click on a button on a widget std::shared_future click_on_button( const std::string& _button_name, const std::string& _widget_name = std::string() ); /// Wait for a widget with the predicate std::shared_future wait_for_widget( std::function _predicate ); } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/preferences_configuration.cpp000066400000000000000000000461431503402212300246060ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "preferences_configuration.hpp" #include "loader.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Registers the fixture into the 'registry' CPPUNIT_TEST_SUITE_REGISTRATION(sight::module::ui::qt::ut::preferences_configuration); namespace sight::module::ui::qt::ut { preferences_configuration::~preferences_configuration() { // Remove the preferences file(s) if(const auto& root = m_preferences_path.parent_path(); std::filesystem::exists(root)) { std::filesystem::remove_all(m_preferences_path.parent_path()); } } //------------------------------------------------------------------------------ void preferences_configuration::setUp() { // Set up context before running a test. CPPUNIT_ASSERT(qApp != nullptr); const std::string& profile_name = sight::core::tools::uuid::generate(); sight::core::runtime::get_current_profile()->set_name(profile_name); sight::ui::preferences::set_enabled(true); sight::ui::preferences::set_password_policy(core::crypto::password_keeper::password_policy::never); m_preferences_path = core::tools::os::get_user_config_dir(profile_name) / "preferences.json"; // Clear the preferences file sight::ui::preferences preferences; sight::ui::preferences::clear(); // Build container std::tie(m_container, m_child_uuid) = make_container(); } //------------------------------------------------------------------------------ void preferences_configuration::tearDown() { // Destroy container destroy_container(m_container); // Reset preferences context after running a test. sight::ui::preferences::set_enabled(false); // Remove the preferences file(s) if(const auto& root = m_preferences_path.parent_path(); std::filesystem::exists(root)) { std::filesystem::remove_all(m_preferences_path.parent_path()); } m_preferences_path.clear(); } //------------------------------------------------------------------------------ void preferences_configuration::widgets_test() { // Register the service sight::service::base::sptr preferences_configuration( service::add("sight::module::ui::qt::preferences_configuration", m_child_uuid) ); // Will stop the service and unregister it when destroyed service_cleaner cleaner(preferences_configuration); // Build preferences_configuration configuration service::config_t config; service::config_t text_preference; text_preference.put("type", "text"); text_preference.put("name", "text"); text_preference.put("key", "text"); text_preference.put("default_value", "text"); config.add_child("preference", text_preference); service::config_t path_preference; path_preference.put("type", "path"); path_preference.put("name", "path"); path_preference.put("key", "path"); path_preference.put("default_value", "/highway/to/hell"); config.add_child("preference", path_preference); service::config_t file_preference; file_preference.put("type", "file"); file_preference.put("name", "file"); file_preference.put("key", "file"); file_preference.put("default_value", "/stairway/to/heaven"); config.add_child("preference", file_preference); service::config_t checkbox_preference; checkbox_preference.put("type", "checkbox"); checkbox_preference.put("name", "checkbox"); checkbox_preference.put("key", "checkbox"); checkbox_preference.put("default_value", "true"); config.add_child("preference", checkbox_preference); service::config_t int_preference; int_preference.put("type", "int"); int_preference.put("name", "int"); int_preference.put("key", "int"); int_preference.put("min", "0"); int_preference.put("max", "1000"); int_preference.put("default_value", "666"); config.add_child("preference", int_preference); service::config_t double_preference; double_preference.put("type", "double"); double_preference.put("name", "double"); double_preference.put("key", "double"); double_preference.put("min", "0.0"); double_preference.put("max", "1000.0"); double_preference.put("default_value", "666.666"); config.add_child("preference", double_preference); service::config_t list_preference; list_preference.put("type", "list"); list_preference.put("name", "list"); list_preference.put("key", "list"); list_preference.put("separator", ";"); list_preference.put("default_value", "1;2;3"); config.add_child("preference", list_preference); service::config_t combobox_preference; combobox_preference.put("type", "combobox"); combobox_preference.put("name", "combobox"); combobox_preference.put("key", "combobox"); combobox_preference.put("separator", ";"); combobox_preference.put("values", "0.3;2000;banana"); combobox_preference.put("default_value", "banana"); config.add_child("preference", combobox_preference); // Configure and start the service CPPUNIT_ASSERT_NO_THROW(preferences_configuration->configure(config)); CPPUNIT_ASSERT_NO_THROW(preferences_configuration->start().get()); bool text_done = false; bool path_done = false; bool file_done = false; bool checkbox_done = false; bool int_done = false; bool double_done = false; bool list_done = false; bool combobox_done = false; auto script = wait_for_widget( [&](QWidget* _widget) { if(_widget != nullptr && _widget->objectName().startsWith("preferences_configuration")) { if(!text_done) { if(auto* text = _widget->findChild("text"); text != nullptr) { QPointer text_ptr(text); // Send back to the main thread core::thread::get_default_worker()->post_task( [text_ptr] { if(!text_ptr.isNull()) { text_ptr->setText("modified"); } }).get(); text_done = true; } } if(!path_done) { if(auto* path = _widget->findChild("path"); path != nullptr) { QPointer path_ptr(path); // Send back to the main thread core::thread::get_default_worker()->post_task( [path_ptr] { if(!path_ptr.isNull()) { path_ptr->setText("modified"); } }).get(); path_done = true; } } if(!file_done) { if(auto* file = _widget->findChild("file"); file != nullptr) { QPointer file_ptr(file); // Send back to the main thread core::thread::get_default_worker()->post_task( [file_ptr] { if(!file_ptr.isNull()) { file_ptr->setText("modified"); } }).get(); file_done = true; } } if(!checkbox_done) { if(auto* checkbox = _widget->findChild("checkbox"); checkbox != nullptr) { QPointer checkbox_ptr(checkbox); // Send back to the main thread core::thread::get_default_worker()->post_task( [checkbox_ptr] { if(!checkbox_ptr.isNull()) { checkbox_ptr->setChecked(false); } }).get(); checkbox_done = true; } } if(!int_done) { if(auto* int_widget = _widget->findChild("int"); int_widget != nullptr) { QPointer int_ptr(int_widget); // Send back to the main thread core::thread::get_default_worker()->post_task( [int_ptr] { if(!int_ptr.isNull()) { int_ptr->setText("42"); } }).get(); int_done = true; } } if(!double_done) { if(auto* double_widget = _widget->findChild("double"); double_widget != nullptr) { QPointer double_ptr(double_widget); // Send back to the main thread core::thread::get_default_worker()->post_task( [double_ptr] { if(!double_ptr.isNull()) { double_ptr->setText("42.0"); } }).get(); double_done = true; } } if(!list_done) { if(auto* list = _widget->findChild("list"); list != nullptr) { QPointer list_ptr(list); // Send back to the main thread core::thread::get_default_worker()->post_task( [list_ptr] { if(!list_ptr.isNull()) { list_ptr->setText("3;2;1"); } }).get(); list_done = true; } } if(!combobox_done) { if(auto* combobox = _widget->findChild("combobox"); combobox != nullptr) { QPointer combobox_ptr(combobox); // Send back to the main thread core::thread::get_default_worker()->post_task( [combobox_ptr] { if(!combobox_ptr.isNull()) { combobox_ptr->setCurrentIndex(2); } }).get(); combobox_done = true; } } // Schedule the click on the OK button if(text_done && path_done && file_done && checkbox_done && int_done && double_done && list_done && combobox_done) { const bool clicked = click_on_button("OK", "preferences_configuration").get(); CPPUNIT_ASSERT_MESSAGE("The OK button should have been clicked", clicked); return clicked; } } return false; }); // Update the service: will show the dialog with an OK button CPPUNIT_ASSERT_EQUAL( std::future_status::ready, preferences_configuration->update().wait_for(std::chrono::seconds(10)) ); // Wait for the script thread to finish (should be done since otherwise we would be blocked on the update() call) CPPUNIT_ASSERT_EQUAL( std::future_status::ready, script.wait_for(std::chrono::seconds(5)) ); CPPUNIT_ASSERT_MESSAGE("'text' widget was not found", text_done); CPPUNIT_ASSERT_MESSAGE("'path' widget was not found", path_done); CPPUNIT_ASSERT_MESSAGE("'file' widget was not found", file_done); CPPUNIT_ASSERT_MESSAGE("'checkbox' widget was not found", checkbox_done); CPPUNIT_ASSERT_MESSAGE("'int' widget was not found", int_done); CPPUNIT_ASSERT_MESSAGE("'double' widget was not found", double_done); CPPUNIT_ASSERT_MESSAGE("'list' widget was not found", list_done); CPPUNIT_ASSERT_MESSAGE("'combobox' widget was not found", combobox_done); // Should be true... CPPUNIT_ASSERT(script.get()); // Check preferences { sight::ui::preferences preferences; CPPUNIT_ASSERT_EQUAL(std::string("modified"), preferences.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("modified"), preferences.get("path")); CPPUNIT_ASSERT_EQUAL(std::string("modified"), preferences.get("file")); CPPUNIT_ASSERT_EQUAL(std::string("false"), preferences.get("checkbox")); CPPUNIT_ASSERT_EQUAL(std::string("42"), preferences.get("int")); CPPUNIT_ASSERT_EQUAL(std::string("42.0"), preferences.get("double")); CPPUNIT_ASSERT_EQUAL(std::string("3;2;1"), preferences.get("list")); CPPUNIT_ASSERT_EQUAL(std::string("banana"), preferences.get("combobox")); } // Cleanup CPPUNIT_ASSERT_NO_THROW(preferences_configuration->stop().get()); } //------------------------------------------------------------------------------ void preferences_configuration::preferences_changed_after_start_test() { static const std::string s_KEY = "patient_folders_root"; // Register the service sight::service::base::sptr preferences_configuration( service::add("sight::module::ui::qt::preferences_configuration", m_child_uuid) ); // Will stop the service and unregister it when destroyed service_cleaner cleaner(preferences_configuration); // Build preferences_configuration configuration service::config_t preference; preference.put("type", "path"); preference.put("name", "Patient folders root"); preference.put("key", s_KEY); preference.put("default_value", "default_value"); service::config_t config; config.put_child("preference", preference); // Configure and start the service CPPUNIT_ASSERT_NO_THROW(preferences_configuration->configure(config)); CPPUNIT_ASSERT_NO_THROW(preferences_configuration->start().get()); // Modify the preference outside the service { sight::ui::preferences preferences; preferences.put(s_KEY, "/home/green"); } // Start a thread to wait for the dialog to appear and click on the OK button auto clicker = click_on_button("OK", "preferences_configuration"); // Update the service: will show the dialog with an the OK button CPPUNIT_ASSERT_EQUAL( std::future_status::ready, preferences_configuration->update().wait_for(std::chrono::seconds(10)) ); // Wait for the clicker thread to finish (should be done since otherwise we would be blocked on the update() call) CPPUNIT_ASSERT_EQUAL( std::future_status::ready, clicker.wait_for(std::chrono::seconds(5)) ); // Should be true and should not block because we asserted there is no timeout just before CPPUNIT_ASSERT_MESSAGE( "The button should have been clicked", clicker.get() ); // Modify the preference outside the service { sight::ui::preferences preferences; preferences.put(s_KEY, "/home/house"); } // Create a slot to receive the preference_changed signal auto slot = sight::core::com::new_slot( [](sight::ui::parameter_t _value, std::string _key) { if(_key == s_KEY) { CPPUNIT_ASSERT(std::holds_alternative(_value)); CPPUNIT_ASSERT_EQUAL(std::string("/home/house"), std::get(_value)); } }); auto worker = core::thread::worker::make(); slot->set_worker(worker); auto signal = preferences_configuration->signal("preference_changed"); auto connection = signal->connect(slot); // Request the service internal state (should execute the "slot") CPPUNIT_ASSERT_NO_THROW(preferences_configuration->slot("request_values")->run()); // Start again a thread to wait for the dialog to appear and click on the OK button clicker = click_on_button("OK", "preferences_configuration"); // Update the service a second time: will show the dialog with an the OK button -> and should not crash CPPUNIT_ASSERT_EQUAL( std::future_status::ready, preferences_configuration->update().wait_for(std::chrono::seconds(10)) ); /// Wait for the clicker thread to finish (should be done since otherwise we would be blocked on the update() call) CPPUNIT_ASSERT_EQUAL( std::future_status::ready, clicker.wait_for(std::chrono::seconds(5)) ); // Should be true and should not block because we asserted there is no timeout just before CPPUNIT_ASSERT_MESSAGE( "The button should have been clicked", clicker.get() ); // Cleanup CPPUNIT_ASSERT_NO_THROW(preferences_configuration->stop().get()); CPPUNIT_ASSERT_NO_THROW(worker->stop()); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/preferences_configuration.hpp000066400000000000000000000034341503402212300246070ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::ui::qt::ut { /** * @brief Test many methods to create mesh. */ class preferences_configuration : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE(preferences_configuration); CPPUNIT_TEST(widgets_test); CPPUNIT_TEST(preferences_changed_after_start_test); CPPUNIT_TEST_SUITE_END(); public: ~preferences_configuration() override; // interface void setUp() override; void tearDown() override; void widgets_test(); void preferences_changed_after_start_test(); private: /// The path where the preference file is stored. std::filesystem::path m_preferences_path; /// The container service sight::service::base::sptr m_container; /// The child uuid to use to be added to the container std::string m_child_uuid; }; } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/progress_bar_test.cpp000066400000000000000000000244441503402212300231050ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "progress_bar_test.hpp" #include "loader.hpp" #include #include #include #include #include #include #include #include #include CPPUNIT_TEST_SUITE_REGISTRATION(sight::module::ui::qt::ut::progress_bar_test); namespace sight::module::ui::qt::ut { class dummy_job : public core::jobs::base { public: explicit dummy_job(const std::string& _name, std::uint64_t _total_work_unit = 100) : base(_name) { m_total_work_units = _total_work_unit; m_state = sight::core::jobs::base::state::running; } ~dummy_job() override { m_state = sight::core::jobs::base::state::finished; } //------------------------------------------------------------------------------ std::shared_future run_impl() override { return {}; } using core::jobs::base::done_work; }; //------------------------------------------------------------------------------ void progress_bar_test::setUp() { // Build container. std::tie(m_container, m_child_uuid) = make_container(); } //------------------------------------------------------------------------------ void progress_bar_test::tearDown() { // Destroy container. destroy_container(m_container); } //------------------------------------------------------------------------------ void progress_bar_test::basic_test() { // Title and cancel button are shown. launch_test(true, true, false); // Destroy the container and recreate it. tearDown(); setUp(); // Cancel button is shown. launch_test(false, true, false); tearDown(); setUp(); //Title is shown. launch_test(true, false, false); } //------------------------------------------------------------------------------ void progress_bar_test::pulse_test() { // Display a pulse progress bar. launch_test(true, true, true); } //------------------------------------------------------------------------------ void progress_bar_test::svg_test() { // Display a pulse waiting icon. launch_test(true, true, true, "sight::module::ui::icons/wait.svg"); } //------------------------------------------------------------------------------ void progress_bar_test::launch_test( bool _show_title, bool _show_cancel, bool _pulse, const std::string& _svg, bool _show_log ) { // Build configuration service::config_t config; config.put(".show_title", _show_title); config.put(".show_cancel", _show_cancel); config.put(".pulse", _pulse); if(!_svg.empty()) { config.put(".svg", _svg); config.put(".svg_size", "48"); } config.add_child("config", config); // Register the service. sight::service::base::sptr progress_bar( service::add("sight::module::ui::qt::progress_bar", m_child_uuid) ); // Will stop the service and unregister it when destroyed. service_cleaner cleaner(progress_bar); CPPUNIT_ASSERT_NO_THROW(progress_bar->configure(config)); CPPUNIT_ASSERT_NO_THROW(progress_bar->start().get()); // Check that progress_bar is not visible before show_job(). auto check_visibility = wait_for_widget( [_show_title, _show_cancel, _svg, this](QWidget* _widget) { if(_widget != nullptr && _widget->objectName().startsWith(QString::fromStdString(m_child_uuid))) { auto check_progress_widget = false; if(_svg.empty()) { if(auto* progress_bar_widget = _widget->findChild("/QProgressBar"); progress_bar_widget != nullptr) { CPPUNIT_ASSERT_EQUAL_MESSAGE( "The progress_bar widget should not be visible before show_job().", false, progress_bar_widget->isVisible() ); check_progress_widget = true; } } else { if(auto* svg_widget = _widget->findChild("/QSvgWidget"); svg_widget != nullptr) { CPPUNIT_ASSERT_EQUAL_MESSAGE( "The progress_bar widget should not be visible before show_job().", false, svg_widget->isVisible() ); check_progress_widget = true; } } auto check_label = !_show_title; if(auto* label = _widget->findChild("/QLabel"); label != nullptr) { CPPUNIT_ASSERT_EQUAL_MESSAGE( "The label should not be visible before show_job().", false, label->isVisible() ); check_label = true; } auto check_button = !_show_cancel; if(auto* button = _widget->findChild("/QToolButton"); button != nullptr) { CPPUNIT_ASSERT_EQUAL_MESSAGE( "The cancel button should not be visible before show_job().", false, button->isVisible() ); check_button = true; } return check_progress_widget && check_label && check_button; } return false; }); // Wait for the script thread to finish. CPPUNIT_ASSERT_EQUAL( std::future_status::ready, check_visibility.wait_for(std::chrono::seconds(5)) ); // Should be true. CPPUNIT_ASSERT(check_visibility.get()); // Create job and slot. static const std::string s_JOB_NAME = "Your Dream Job"; static int job_count = 0; const std::string job_name = s_JOB_NAME + std::to_string(job_count++); auto job = std::make_shared(job_name); progress_bar->slot("show_job")->run(std::static_pointer_cast(job)); // Check that progress_bar is set with correct information. for(int i = 1 ; i <= 100 ; i++) { job->done_work(std::uint64_t(i)); if(_show_log) { job->log(std::to_string(i)); } auto check_progress_info = wait_for_widget( [_show_title, _pulse, _svg, _show_log, i, job, job_name, this](QWidget* _widget) { if(_widget != nullptr && _widget->objectName().startsWith(QString::fromStdString(m_child_uuid))) { auto correct_title = !_show_title; if(auto* label = _widget->findChild("/QLabel"); label != nullptr) { CPPUNIT_ASSERT_EQUAL_MESSAGE( "The title of progress_bar should be equal to job name.", job_name, label->text().toStdString() + (_show_log ? " - " + std::to_string(i) : "") ); correct_title = true; } auto correct_done_work_units = false; if(_svg.empty()) { if(auto* progress_bar_widget = _widget->findChild("/QProgressBar"); progress_bar_widget != nullptr) { // In pulse mode, the value is irrelevant if(!_pulse) { // Do the same operation that the progress_bar does. int value = (int) (float(i) / float(job->get_total_work_units()) * 100); CPPUNIT_ASSERT_EQUAL_MESSAGE( "The value of progress_bar should be equal to done work units.", value, progress_bar_widget->value() ); } correct_done_work_units = true; } } else { if(auto* progress_bar_widget = _widget->findChild("/QSvgWidget"); progress_bar_widget != nullptr) { // In pulse mode, the value is irrelevant correct_done_work_units = true; } } return correct_title && correct_done_work_units; } return false; }); // Wait for the script thread to finish. CPPUNIT_ASSERT_EQUAL( std::future_status::ready, check_progress_info.wait_for(std::chrono::seconds(10)) ); // Should be true. CPPUNIT_ASSERT(check_progress_info.get()); } // Cleanup CPPUNIT_ASSERT_NO_THROW(progress_bar->stop().get()); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/progress_bar_test.hpp000066400000000000000000000033271503402212300231070ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include namespace sight::module::ui::qt::ut { class progress_bar_test : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE(progress_bar_test); CPPUNIT_TEST(basic_test); CPPUNIT_TEST(pulse_test); CPPUNIT_TEST(svg_test); CPPUNIT_TEST_SUITE_END(); public: void setUp() override; void tearDown() override; void basic_test(); void pulse_test(); void svg_test(); private: void launch_test( bool _show_title, bool _show_cancel, bool _pulse, const std::string& _svg = std::string(), bool _show_log = false ); /// The container service. sight::service::base::sptr m_container; /// The child uuid to use to be added to the container. std::string m_child_uuid; }; } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/rc/000077500000000000000000000000001503402212300172465ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/test/ut/rc/ut_sequencer/000077500000000000000000000000001503402212300217505ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/test/ut/rc/ut_sequencer/plugin.xml000066400000000000000000000032541503402212300237740ustar00rootroot00000000000000 id_0 title_0 desc_0 icon_0 sight::activity::builder::Activity id_1 title_1 desc_1 icon_1 sight::activity::builder::Activity id_2 title_2 desc_2 icon_2 sight::activity::builder::Activity sight-25.1.0/module/ui/qt/test/ut/sequencer.cpp000066400000000000000000000140571503402212300213470ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "sequencer.hpp" #include "loader.hpp" #include #include #include #include #include #include #include #include #include #include // Registers the fixture into the 'registry' CPPUNIT_TEST_SUITE_REGISTRATION(sight::module::ui::qt::ut::sequencer); namespace sight::module::ui::qt::ut { //------------------------------------------------------------------------------ void sequencer::setUp() { // Set up context before running a test. CPPUNIT_ASSERT(qApp != nullptr); static bool done = false; if(!done) { CPPUNIT_ASSERT_NO_THROW(core::runtime::load_module("ut_sequencer")); CPPUNIT_ASSERT_NO_THROW(core::runtime::load_module("sight::module::app")); CPPUNIT_ASSERT_NO_THROW(core::runtime::load_module("sight::module::activity")); done = true; } // Build container std::tie(m_container, m_child_uuid) = make_container(); } //------------------------------------------------------------------------------ void sequencer::tearDown() { // Destroy container destroy_container(m_container); } //------------------------------------------------------------------------------ void sequencer::reset_requirements_test() { // Register the service sight::service::base::sptr sequencer(service::add("sight::module::ui::qt::activity::sequencer", m_child_uuid)); // Will stop the service and unregister it when destroyed service_cleaner cleaner(sequencer); // Set inout auto activity_set = std::make_shared(); sequencer->set_inout(activity_set, "activitySet", true); // Build sequencer configuration service::config_t sequencer_config; for(int i = 0 ; i < 3 ; ++i) { auto& activity = sequencer_config.add("activity", ""); activity.put(".id", "id_" + std::to_string(i)); activity.put(".name", "name_" + std::to_string(i)); } // Configure the service CPPUNIT_ASSERT_NO_THROW(sequencer->configure(sequencer_config)); CPPUNIT_ASSERT_NO_THROW(sequencer->start().get()); // This should go to the first activity CPPUNIT_ASSERT_NO_THROW(sequencer->update().get()); // Add some requirements from "outside" activity_set->at(0)->insert_or_assign("outside_1", std::make_shared(1)); activity_set->at(0)->insert_or_assign("outside_2", std::make_shared(2)); // Go to the last activity, so all requirements are created CPPUNIT_ASSERT_NO_THROW(sequencer->slot("next")->run()); CPPUNIT_ASSERT_NO_THROW(sequencer->slot("next")->run()); // 3 activities should be in the set CPPUNIT_ASSERT_EQUAL(std::size_t(3), activity_set->size()); // Lambda helper to check the activity set const auto& check_activity = [&activity_set](bool _should_be_empty, bool _modify = false) { for(const auto& activity : *activity_set) { // At least one requirement should be present in the current activity CPPUNIT_ASSERT_GREATEREQUAL(std::size_t(1), activity->size()); for(const auto& [key, value] : *activity) { if(key.starts_with("inside_")) { if(auto string = std::dynamic_pointer_cast(value); string) { // The initial value should be empty string CPPUNIT_ASSERT_EQUAL(_should_be_empty ? std::string() : key, string->value()); if(_modify) { // Set a new value string->set_value(key); } } } else if(key.starts_with("outside_")) { if(auto integer = std::dynamic_pointer_cast(value); integer) { if(key.ends_with("1")) { CPPUNIT_ASSERT_EQUAL(std::int64_t(1), integer->value()); } else if(key.ends_with("2")) { CPPUNIT_ASSERT_EQUAL(std::int64_t(2), integer->value()); } } } } } }; // Modify them to simulate user interaction check_activity(true, true); // Just to be sure, check that nothing change CPPUNIT_ASSERT_NO_THROW(sequencer->update().get()); check_activity(false); // Reset the requirements CPPUNIT_ASSERT_NO_THROW(sequencer->slot("reset_requirements")->run()); // Check that the requirements are reset check_activity(true); // cleanup CPPUNIT_ASSERT_NO_THROW(sequencer->stop().get()); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/sequencer.hpp000066400000000000000000000030051503402212300213430ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include namespace sight::module::ui::qt::ut { /** * @brief Test many methods to create mesh. */ class sequencer : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE(sequencer); CPPUNIT_TEST(reset_requirements_test); CPPUNIT_TEST_SUITE_END(); public: // interface void setUp() override; void tearDown() override; void reset_requirements_test(); private: /// The container service sight::service::base::sptr m_container; /// The child uuid to use to be added to the container std::string m_child_uuid; }; } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/signal_button_test.cpp000066400000000000000000000077571503402212300232750ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "signal_button_test.hpp" #include "loader.hpp" #include #include #include #include #include #include #include #include // Registers the fixture into the 'registry' CPPUNIT_TEST_SUITE_REGISTRATION(sight::module::ui::qt::ut::signal_button_test); namespace sight::module::ui::qt::ut { //------------------------------------------------------------------------------ void signal_button_test::setUp() { // Necessary to start with a fresh qApp, if other tests were launched sight::core::runtime::unload_module("sight::module::ui::qt"); m_module = sight::core::runtime::load_module("sight::module::ui::qt"); m_module->start(); m_worker = sight::core::thread::worker::make(); std::tie(m_container, m_child_uid) = make_container(); } //------------------------------------------------------------------------------ void signal_button_test::tearDown() { // Destroy container. destroy_container(m_container); m_container.reset(); m_worker->stop(); m_worker.reset(); if(m_module->is_started()) { m_module->stop(); } } class signal_button_test_context { public: explicit signal_button_test_context(const std::string& _uid) { // Register the service signal_button_srv = service::add("sight::module::ui::qt::com::signal_button", _uid); { // Build configuration service::config_t config; config.put("text", "test"); config.put("checked", "false"); config.put("joystick", "left"); config.put("checkable", "false"); config.add_child("config", config); CPPUNIT_ASSERT_NO_THROW(signal_button_srv->configure(config)); CPPUNIT_ASSERT_NO_THROW(signal_button_srv->start().get()); } } ~signal_button_test_context() { if(signal_button_srv->started()) { signal_button_srv->stop().wait(); } service::remove(signal_button_srv); } sight::service::base::sptr signal_button_srv; }; //------------------------------------------------------------------------------ void signal_button_test::click_test() { signal_button_test_context context(m_child_uid); bool called = false; auto slot = sight::core::com::new_slot( [&]() { called = true; }); slot->set_worker(m_worker); context.signal_button_srv->signal("clicked")->connect(slot); QString button_name = QString::fromStdString(m_child_uid) + "/signal_button"; for(auto* widget : qApp->allWidgets()) { if(auto* button = widget->findChild(button_name); button != nullptr) { QPointer button_ptr(button); button_ptr->clicked(); } } SIGHT_TEST_WAIT(called); CPPUNIT_ASSERT_EQUAL(true, called); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/signal_button_test.hpp000066400000000000000000000032701503402212300232640ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "core/runtime/module.hpp" #include #include namespace sight::module::ui::qt::ut { /** * @brief Test many methods to create mesh. */ class signal_button_test : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE(signal_button_test); CPPUNIT_TEST(click_test); CPPUNIT_TEST_SUITE_END(); public: // interface void setUp() override; void tearDown() override; void click_test(); private: /// The container service. sight::service::base::sptr m_container; // The worker for the test slots sight::core::thread::worker::sptr m_worker; /// The child uid to use to be added to the container. std::string m_child_uid; std::shared_ptr m_module; }; } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/signal_shortcut_test.cpp000066400000000000000000000276261503402212300236320ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "signal_shortcut_test.hpp" #include "loader.hpp" #include #include #include #include #include #include #include #include // Registers the fixture into the 'registry' CPPUNIT_TEST_SUITE_REGISTRATION(sight::module::ui::qt::ut::signal_shortcut_test); namespace sight::module::ui::qt::ut { //------------------------------------------------------------------------------ void signal_shortcut_test::setUp() { // Necessary to start with a fresh qApp, if other tests were launched sight::core::runtime::unload_module("sight::module::ui::qt"); m_module = sight::core::runtime::load_module("sight::module::ui::qt"); m_module->start(); m_worker = sight::core::thread::worker::make(); std::tie(m_container, m_child_uid) = make_container(); } //------------------------------------------------------------------------------ void signal_shortcut_test::tearDown() { // Destroy container. destroy_container(m_container); m_container.reset(); m_worker->stop(); m_worker.reset(); m_module->stop(); } class test_context { public: test_context(const std::string& _uid, const std::string& _shortcuts) { view = service::add("sight::module::ui::view"); { std::stringstream config_string; config_string << "" R"()" R"( )" R"()" R"()" R"()" R"()"; sight::service::base::config_t config; boost::property_tree::read_xml(config_string, config); view->set_id(_uid); // Configure and start the container service view->configure(config); view->start().get(); } // Register the service signal_shortcut_srv = service::add("sight::module::ui::qt::com::signal_shortcut"); { // Build configuration service::config_t config; config.put(".shortcut", _shortcuts); config.put(".sid", _uid); config.add_child("config", config); CPPUNIT_ASSERT_NO_THROW(signal_shortcut_srv->configure(config)); CPPUNIT_ASSERT_NO_THROW(signal_shortcut_srv->start().get()); } } ~test_context() { if(signal_shortcut_srv->started()) { signal_shortcut_srv->stop().wait(); } service::remove(signal_shortcut_srv); view->stop().get(); service::remove(view); } sight::service::base::sptr view; sight::service::base::sptr signal_shortcut_srv; }; //------------------------------------------------------------------------------ void signal_shortcut_test::single_shortcut_test() { test_context context(m_child_uid, "S"); bool called = false; auto slot = sight::core::com::new_slot( [&]() { called = true; }); slot->set_worker(m_worker); context.signal_shortcut_srv->signal("activated")->connect(slot); // This is essential to get the key shortcut processed bool exposed = QTest::qWaitForWindowExposed(qApp->activeWindow()); CPPUNIT_ASSERT_EQUAL(true, exposed); QTest::keyClick(qApp->activeWindow(), Qt::Key_S, Qt::NoModifier); SIGHT_TEST_WAIT(called); CPPUNIT_ASSERT_EQUAL(true, called); } //------------------------------------------------------------------------------ void signal_shortcut_test::multiple_shortcuts_test() { test_context context(m_child_uid, "S;CTRL+F2"); bool called = false; auto slot = sight::core::com::new_slot( [&]() { called = true; }); slot->set_worker(m_worker); context.signal_shortcut_srv->signal("activated")->connect(slot); // This is essential to get the key shortcut processed bool exposed = QTest::qWaitForWindowExposed(qApp->activeWindow()); CPPUNIT_ASSERT_EQUAL(true, exposed); QTest::keyClick(qApp->activeWindow(), Qt::Key_S, Qt::NoModifier); SIGHT_TEST_WAIT(called); CPPUNIT_ASSERT_EQUAL(true, called); called = false; QTest::keyClick(qApp->activeWindow(), Qt::Key_F2, Qt::ControlModifier); SIGHT_TEST_WAIT(called); CPPUNIT_ASSERT_EQUAL(true, called); } //------------------------------------------------------------------------------ void signal_shortcut_test::enable_disable_test() { test_context context(m_child_uid, "SHIFT+S;CTRL+F2;L"); bool called = false; auto slot = sight::core::com::new_slot( [&]() { called = true; }); slot->set_worker(m_worker); context.signal_shortcut_srv->signal("activated")->connect(slot); // This is essential to get the key shortcut processed bool exposed = QTest::qWaitForWindowExposed(qApp->activeWindow()); CPPUNIT_ASSERT_EQUAL(true, exposed); QTest::keyClick(qApp->activeWindow(), Qt::Key_S, Qt::ShiftModifier); SIGHT_TEST_WAIT(called); CPPUNIT_ASSERT_EQUAL(true, called); context.signal_shortcut_srv->slot("disable")->run(); called = false; QTest::keyClick(qApp->activeWindow(), Qt::Key_S, Qt::ShiftModifier); SIGHT_TEST_FAIL_WAIT(not called); CPPUNIT_ASSERT_EQUAL(false, called); context.signal_shortcut_srv->slot("enable")->run(); called = false; QTest::keyClick(qApp->activeWindow(), Qt::Key_L, Qt::NoModifier); SIGHT_TEST_WAIT(called); CPPUNIT_ASSERT_EQUAL(true, called); } //------------------------------------------------------------------------------ void signal_shortcut_test::check_uncheck_test() { test_context context(m_child_uid, "SHIFT+S;CTRL+F2;L"); std::atomic_bool called = false; auto slot = sight::core::com::new_slot( [&called]() { called = true; }); slot->set_worker(m_worker); context.signal_shortcut_srv->signal("activated")->connect(slot); std::atomic_bool checked = false; auto checked_slot = sight::core::com::new_slot( [&checked]() { checked = true; }); checked_slot->set_worker(m_worker); context.signal_shortcut_srv->signal("checked")->connect(checked_slot); auto unchecked_slot = sight::core::com::new_slot( [&checked]() { checked = false; }); unchecked_slot->set_worker(m_worker); context.signal_shortcut_srv->signal("unchecked")->connect(unchecked_slot); // This is essential to get the key shortcut processed bool exposed = QTest::qWaitForWindowExposed(qApp->activeWindow()); CPPUNIT_ASSERT_EQUAL(true, exposed); QTest::keyClick(qApp->activeWindow(), Qt::Key_F2, Qt::ControlModifier); SIGHT_TEST_WAIT(called); CPPUNIT_ASSERT_EQUAL(true, called.load()); SIGHT_TEST_WAIT(checked); CPPUNIT_ASSERT_EQUAL(true, checked.load()); called = false; QTest::keyClick(qApp->activeWindow(), Qt::Key_S, Qt::ShiftModifier); SIGHT_TEST_WAIT(called); CPPUNIT_ASSERT_EQUAL(true, called.load()); SIGHT_TEST_WAIT(not checked); CPPUNIT_ASSERT_EQUAL(false, checked.load()); called = false; context.signal_shortcut_srv->slot("check")->run(); CPPUNIT_ASSERT_EQUAL(false, called.load()); SIGHT_TEST_WAIT(checked); CPPUNIT_ASSERT_EQUAL(true, checked.load()); called = false; QTest::keyClick(qApp->activeWindow(), Qt::Key_L, Qt::NoModifier); SIGHT_TEST_WAIT(called); CPPUNIT_ASSERT_EQUAL(true, called.load()); SIGHT_TEST_WAIT(not checked); CPPUNIT_ASSERT_EQUAL(false, checked.load()); QTest::keyClick(qApp->activeWindow(), Qt::Key_F2, Qt::ControlModifier); SIGHT_TEST_WAIT(called); CPPUNIT_ASSERT_EQUAL(true, called.load()); SIGHT_TEST_WAIT(checked); CPPUNIT_ASSERT_EQUAL(true, checked.load()); called = false; context.signal_shortcut_srv->slot("uncheck")->run(); CPPUNIT_ASSERT_EQUAL(false, called.load()); SIGHT_TEST_WAIT(not checked); CPPUNIT_ASSERT_EQUAL(false, checked.load()); called = false; context.signal_shortcut_srv->slot("set_checked")->run(true); CPPUNIT_ASSERT_EQUAL(false, called.load()); SIGHT_TEST_WAIT(checked); CPPUNIT_ASSERT_EQUAL(true, checked.load()); called = false; context.signal_shortcut_srv->slot("set_checked")->run(false); CPPUNIT_ASSERT_EQUAL(false, called.load()); SIGHT_TEST_WAIT(not checked); CPPUNIT_ASSERT_EQUAL(false, checked.load()); } //------------------------------------------------------------------------------ void signal_shortcut_test::two_instances_test() { const auto* shortcut_key = "S"; test_context context(m_child_uid, shortcut_key); // Register another service auto second_signal_shortcut_srv = service::add("sight::module::ui::qt::com::signal_shortcut"); { // Build configuration service::config_t config; config.put(".shortcut", shortcut_key); config.put(".sid", m_child_uid); config.add_child("config", config); config.put("properties..enabled", false); CPPUNIT_ASSERT_NO_THROW(second_signal_shortcut_srv->configure(config)); CPPUNIT_ASSERT_NO_THROW(second_signal_shortcut_srv->start().get()); } service_cleaner cleaner(second_signal_shortcut_srv); bool called1 = false; auto slot1 = sight::core::com::new_slot( [&]() { called1 = true; }); slot1->set_worker(m_worker); context.signal_shortcut_srv->signal("activated")->connect(slot1); bool called2 = false; auto slot2 = sight::core::com::new_slot( [&]() { called2 = true; }); slot2->set_worker(m_worker); second_signal_shortcut_srv->signal("activated")->connect(slot2); // This is essential to get the key shortcut processed bool exposed = QTest::qWaitForWindowExposed(qApp->activeWindow()); CPPUNIT_ASSERT_EQUAL(true, exposed); QTest::keyClick(qApp->activeWindow(), Qt::Key_S, Qt::NoModifier); SIGHT_TEST_WAIT(called1); CPPUNIT_ASSERT_EQUAL(true, called1); SIGHT_TEST_FAIL_WAIT(not called2); CPPUNIT_ASSERT_EQUAL(false, called2); context.signal_shortcut_srv->slot("disable")->run(); second_signal_shortcut_srv->slot("enable")->run(); called1 = false; called2 = false; QTest::keyClick(qApp->activeWindow(), Qt::Key_S, Qt::NoModifier); SIGHT_TEST_WAIT(called2); CPPUNIT_ASSERT_EQUAL(true, called2); SIGHT_TEST_FAIL_WAIT(not called1); CPPUNIT_ASSERT_EQUAL(false, called1); second_signal_shortcut_srv->slot("disable")->run(); context.signal_shortcut_srv->slot("enable")->run(); called1 = false; called2 = false; QTest::keyClick(qApp->activeWindow(), Qt::Key_S, Qt::NoModifier); SIGHT_TEST_WAIT(called1); CPPUNIT_ASSERT_EQUAL(true, called1); SIGHT_TEST_FAIL_WAIT(not called2); CPPUNIT_ASSERT_EQUAL(false, called2); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/test/ut/signal_shortcut_test.hpp000066400000000000000000000037401503402212300236260ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "core/runtime/module.hpp" #include #include namespace sight::module::ui::qt::ut { /** * @brief Test many methods to create mesh. */ class signal_shortcut_test : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE(signal_shortcut_test); CPPUNIT_TEST(single_shortcut_test); CPPUNIT_TEST(multiple_shortcuts_test); CPPUNIT_TEST(enable_disable_test); CPPUNIT_TEST(check_uncheck_test); CPPUNIT_TEST(two_instances_test); CPPUNIT_TEST_SUITE_END(); public: // interface void setUp() override; void tearDown() override; void single_shortcut_test(); void multiple_shortcuts_test(); void enable_disable_test(); void check_uncheck_test(); void two_instances_test(); private: /// The container service. sight::service::base::sptr m_container; // The worker for the test slots sight::core::thread::worker::sptr m_worker; /// The child uid to use to be added to the container. std::string m_child_uid; std::shared_ptr m_module; }; } // namespace sight::module::ui::qt::ut sight-25.1.0/module/ui/qt/text.cpp000066400000000000000000000057131503402212300167310ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "text.hpp" #include #include #include namespace sight::module::ui::qt { //----------------------------------------------------------------------------- text::text() : m_label(new QLabel()) { m_label->setStyleSheet("font-weight: bold;"); } //------------------------------------------------------------------------------ void text::configuring() { this->initialize(); const auto config = this->get_config(); QString style_sheet; if(const auto label = config.get_optional("label"); label.has_value()) { const QString txt = QString::fromStdString(label.value()); m_label->setText(QString(txt)); } QString color = "white"; if(const auto color_cfg = config.get_optional("color"); color_cfg.has_value()) { const QString txt_color = QString::fromStdString(color_cfg.value()); if(!txt_color.isEmpty()) { color = txt_color; } } const auto size = config.get("size", "14pt"); const auto weight = config.get("weight", "bold"); m_label->setStyleSheet( m_label->styleSheet() + " color: " + color + ";" + "font-size: " + QString::fromStdString(size) + ";" + "font-weight: " + QString::fromStdString(weight) + ";" ); } //------------------------------------------------------------------------------ void text::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); auto* const layout = new QHBoxLayout(); layout->addWidget(m_label); layout->setAlignment(Qt::AlignCenter); qt_container->set_layout(layout); } //------------------------------------------------------------------------------ void text::updating() { } //------------------------------------------------------------------------------ void text::stopping() { this->destroy(); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/text.hpp000066400000000000000000000050111503402212300167250ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::ui::qt { /** * @brief This service is used to display a simple label. * * @section XML XML Configuration * @code{.xml} #FF0000 @endcode * * @subsection Configuration Configuration * - \b label (optional, default="") : text to show before size of the vector * - \b color (optional, default="white") : needed color of the displayed label in a CSS style as names (ex: red), * rgb/rgba (ex: rgb(0,255,137,0.3)) or hexadecimal (ex: #355C66). * - \b size (optional, default="14pt") : size of the font used in the label, as supported by 'font-size' QSS attribute * - \b weight (optional, default="bold") : normal, bold any value supported by 'font-weight' QSS attribute */ class text final : public QObject, public sight::ui::editor { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(text, sight::ui::editor); /// Initializes slots and member. text(); /// Destroys the service. ~text() override = default; private: /// Configures the service. void configuring() override; /// Installs the layout and gets the input data if it exists and displays it. void starting() override; /// Gets the input data if it exists and displays it. void updating() override; /// Destroys the layout. void stopping() override; /// Stores the static text to be displayed. QPointer m_label; }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/text_status.cpp000066400000000000000000000145021503402212300203300ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "text_status.hpp" #include #include #include #include namespace sight::module::ui::qt { static const core::com::slots::key_t SET_DOUBLE_PARAMETER_SLOT = "set_double_parameter"; static const core::com::slots::key_t SET_INT_PARAMETER_SLOT = "set_int_parameter"; static const core::com::slots::key_t SET_BOOL_PARAMETER_SLOT = "set_bool_parameter"; static const core::com::slots::key_t SET_STRING_PARAMETER_SLOT = "set_string_parameter"; //----------------------------------------------------------------------------- text_status::text_status() : m_label_static_text(new QLabel()), m_suffix_value(new QLabel()) { new_slot(SET_DOUBLE_PARAMETER_SLOT, &text_status::set_double_parameter, this); new_slot(SET_INT_PARAMETER_SLOT, &text_status::set_int_parameter, this); new_slot(SET_BOOL_PARAMETER_SLOT, &text_status::set_bool_parameter, this); new_slot(SET_STRING_PARAMETER_SLOT, &text_status::set_string_parameter, this); } //------------------------------------------------------------------------------ void text_status::configuring() { this->initialize(); const auto config = this->get_config(); const QString service_id = QString::fromStdString(base_id()); const auto size = config.get("size", "14pt"); const auto weight = config.get("weight", "normal"); m_decimals = config.get("decimals", m_decimals); QString color = "red"; if(const auto color_cfg = config.get_optional("color"); color_cfg.has_value()) { const QString txt_color = QString::fromStdString(color_cfg.value()); if(!txt_color.isEmpty()) { color = txt_color; } } const QString stylesheet = " color: " + color + ";" + "font-size: " + QString::fromStdString(size) + ";" + "font-weight: " + QString::fromStdString(weight) + ";"; m_label_value = new QLabel(); m_label_value->setObjectName(service_id); m_label_value->setStyleSheet(m_label_value->styleSheet() + stylesheet); if(const auto label = config.get_optional("label"); label.has_value()) { const QString txt = QString::fromStdString(label.value()); m_label_static_text->setText(QString(txt)); m_label_static_text->setStyleSheet(m_label_static_text->styleSheet() + stylesheet); } if(const auto suffix = config.get_optional("suffix"); suffix.has_value()) { m_suffix_value->setText(QString::fromStdString(*suffix)); m_suffix_value->setStyleSheet(m_suffix_value->styleSheet() + stylesheet); } } //------------------------------------------------------------------------------ void text_status::starting() { this->create(); auto* const layout = new QHBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(m_label_static_text); layout->addWidget(m_label_value); layout->addWidget(m_suffix_value); auto qt_container = std::dynamic_pointer_cast(this->get_container()); qt_container->set_layout(layout); this->updating(); } //------------------------------------------------------------------------------ service::connections_t text_status::auto_connections() const { service::connections_t connections; connections.push(STRING_INPUT, data::object::MODIFIED_SIG, service::slots::UPDATE); return connections; } //------------------------------------------------------------------------------ void text_status::updating() { if(const auto string_input = m_string.lock(); string_input) { if(const auto real = std::dynamic_pointer_cast(string_input.get_shared()); real) { m_label_value->setText(QString::number(real->value(), 'f', m_decimals)); } else if(const auto string = std::dynamic_pointer_cast(string_input.get_shared()); string) { m_label_value->setText(QString::fromStdString(string->to_string())); } else { SIGHT_ERROR("Provided data does inherit from data::string_serializable, it cannot be displayed as string."); } m_label_value->parentWidget()->adjustSize(); } } //------------------------------------------------------------------------------ void text_status::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void text_status::set_int_parameter(int _val) { QString str; str.setNum(_val); m_label_value->setText(str); } //------------------------------------------------------------------------------ void text_status::set_double_parameter(double _val) { QString str; str.setNum(_val, 'f', m_decimals); m_label_value->setText(str); } //------------------------------------------------------------------------------ void text_status::set_bool_parameter(bool _val) { QString str(_val ? "ON" : "OFF"); m_label_value->setText(str); } //------------------------------------------------------------------------------ void text_status::set_string_parameter(std::string _val) { m_label_value->setText(QString::fromStdString(_val)); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/text_status.hpp000066400000000000000000000106511503402212300203360ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2025 IRCAD France * Copyright (C) 2017-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include namespace sight::module::ui::qt { /** * @brief This service is used to displays and update values (int, double or string) in a QLabel. * Values are set using slots or using a data::string input. * * @section Slots Slots * - \b set_int_parameter(int): display the value in the QLabel. * - \b set_double_parameter(double): display the value in the QLabel. * - \b set_bool_parameter(int): display the value in the QLabel. * - \b set_string_parameter(int): display the value in the QLabel. * * @section XML XML Configuration * @code{.xml} units #FF0000 2 @endcode * * @subsection Input Input * - \b string(data::object, optional): data to display, should be displayable as a string, thus inheriting from * data::string_serializable. * * @subsection Configuration Configuration * - \b label (optional, default="") : text to show before the data * - \b suffix (optional, default="") : text to add after the data * - \b color (optional, default="red") : needed color of the displayed label in a CSS style as names (ex: red), * rgb/rgba (ex: rgb(0,255,137,0.3)) or hexadecimal (ex: #355C66). * - \b size (optional, default="14pt") : size of the font used in the label, as supported by 'font-size' QSS attribute * - \b weight (optional, default="bold") : normal, bold any value supported by 'font-weight' QSS attribute * - \b decimals (optional, default="2") : if a sight::data::real data is provided, number of decimals to display */ class text_status final : public QObject, public sight::ui::editor { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(text_status, sight::ui::editor); /// Initializes slots and member. text_status(); /// Destroys the service. ~text_status() override = default; private: /// Configures the service. void configuring() override; /// Installs the layout and gets the input data if it exists and displays it. void starting() override; /// Gets the input data if it exists and displays it. void updating() override; /// Destroys the layout. void stopping() override; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::object::MODIFIED_SIG of s_STRING_INPUT to service::slots::UPDATE */ connections_t auto_connections() const override; /// Sets the interger to display. void set_int_parameter(int _val); /// Sets the double to display. void set_double_parameter(double _val); /// Sets the boolean to display. void set_bool_parameter(bool _val); /// Sets the string to display. void set_string_parameter(std::string _val); /// Stores the label. QPointer m_label_value; /// Stores the static text to be displayed. QPointer m_label_static_text; /// Stores the suffix. QPointer m_suffix_value; /// Number of decimals if we display a float value int m_decimals {2}; static constexpr std::string_view STRING_INPUT = "string"; data::ptr m_string {this, STRING_INPUT, true}; }; } // namespace sight::module::ui::qt sight-25.1.0/module/ui/qt/video/000077500000000000000000000000001503402212300163415ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/video/camera.cpp000066400000000000000000000441411503402212300203010ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "camera.hpp" #include "module/ui/qt/video/camera_device_dlg.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::video { static const core::com::signals::key_t CONFIGURED_CAMERAS_SIG = "configured_cameras"; static const core::com::signals::key_t CONFIGURED_DEVICE_SIG = "configured_device"; static const core::com::signals::key_t CONFIGURED_FILE_SIG = "configured_file"; static const core::com::signals::key_t CONFIGURED_STREAM_SIG = "configured_stream"; static const core::com::slots::key_t CONFIGURE_DEVICE_SLOT = "configureDevice"; static const core::com::slots::key_t CONFIGURE_FILE_SLOT = "configureFile"; static const core::com::slots::key_t CONFIGURE_STREAM_SLOT = "configureStream"; static const std::string USE_ABSOLUTE_PATH = "useAbsolutePath"; static const std::string CREATE_CAMERA_NUMBER_CONFIG = "createCameraNumber"; static const std::string LABEL_CONFIG = "label"; static const std::string RESOLUTION_CONFIG = "resolution"; const std::string camera::RESOLUTION_PREF_KEY = "camera_resolution"; //------------------------------------------------------------------------------ camera::camera() : m_sig_configured_cameras(new_signal(CONFIGURED_CAMERAS_SIG)) { new_signal(CONFIGURED_DEVICE_SIG); new_signal(CONFIGURED_FILE_SIG); new_signal(CONFIGURED_STREAM_SIG); new_slot(CONFIGURE_DEVICE_SLOT, &camera::on_choose_device, this); new_slot(CONFIGURE_FILE_SLOT, &camera::on_choose_file, this); new_slot(CONFIGURE_STREAM_SLOT, &camera::on_choose_stream, this); } //------------------------------------------------------------------------------ void camera::configuring() { const service::config_t config = this->get_config(); m_b_video_support = core::ptree::get_and_deprecate( config, "video_support", "videoSupport", "26.0", false ); m_use_absolute_path = config.get(USE_ABSOLUTE_PATH, false); m_num_create_cameras = config.get(CREATE_CAMERA_NUMBER_CONFIG, m_num_create_cameras); m_label = config.get(LABEL_CONFIG, m_label); m_resolution = config.get(RESOLUTION_CONFIG, "preferences"); this->initialize(); } //------------------------------------------------------------------------------ void camera::starting() { this->create(); const auto qt_container = std::dynamic_pointer_cast( this->get_container() ); QPointer layout = new QHBoxLayout(); if(!m_label.empty()) { const QPointer source_label = new QLabel(QString::fromStdString(m_label)); layout->addWidget(source_label); } const QString service_id = QString::fromStdString(base_id()); m_devices_combo_box = new QComboBox(); m_devices_combo_box->setObjectName(service_id); layout->addWidget(m_devices_combo_box); m_devices_combo_box->addItem("Device...", "device"); // Add video file if(m_b_video_support) { m_devices_combo_box->addItem("File...", "file"); m_devices_combo_box->addItem("Stream...", "stream"); } // Add button to edit the preferences when and set `m_preferenceMode` to true if(m_resolution == "preferences") { auto path = core::runtime::get_module_resource_path("sight::module::ui::icons"); // Add preference setting button auto* set_pref_button = new QPushButton(); set_pref_button->setProperty("class", "signal-button"); set_pref_button->setIcon(QIcon(QString::fromStdString((path / "gear.svg").string()))); set_pref_button->setToolTip("Set camera resolution preference"); layout->addWidget(set_pref_button); m_preference_mode = true; QObject::connect(set_pref_button, &QPushButton::clicked, this, &self_t::set_preference); } qt_container->set_layout(layout); QObject::connect(m_devices_combo_box, qOverload(&QComboBox::activated), this, &camera::on_activated); QObject::connect(m_devices_combo_box, qOverload(&QComboBox::currentIndexChanged), this, &camera::on_apply); // Create camera data if necessary auto camera_set = m_camera_set.lock(); if(camera_set) { const std::size_t num_cameras = camera_set->size(); if(num_cameras == 0) { SIGHT_ASSERT("No camera data in the CameraSet.", m_num_create_cameras != 0); for(std::size_t i = 0 ; i < m_num_create_cameras ; ++i) { auto camera = std::make_shared(); camera_set->add_camera(camera); const auto sig = camera_set->signal( data::camera_set::ADDED_CAMERA_SIG ); sig->async_emit(camera); } SIGHT_INFO("No camera data in the CameraSet, " << m_num_create_cameras << " will be created."); } else { SIGHT_WARN_IF( "CameraSet contains camera data but the service is configured to create " << m_num_create_cameras << " cameras.", m_num_create_cameras != 0 ); } } } //------------------------------------------------------------------------------ void camera::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void camera::updating() { } //------------------------------------------------------------------------------ void camera::on_apply(int _index) { switch(_index) { case 0: this->on_choose_device(); break; case 1: this->on_choose_file(); break; case 2: this->on_choose_stream(); break; default: SIGHT_ASSERT("Invalid index: " << _index, false); } } //------------------------------------------------------------------------------ void camera::on_activated(int _index) { // If the current index did change, onCurrentIndexChanged will be called, we wouldn't want onApply to be called // twice if(m_old_index == _index) { on_apply(_index); } m_old_index = _index; } //------------------------------------------------------------------------------ void camera::on_choose_file() { std::vector cameras = this->get_cameras(); // Check preferences const std::filesystem::path video_dir_preference_path(sight::ui::preferences().get( "VIDEO_DIR_PREF", std::string() )); static auto default_directory = std::make_shared(); sight::ui::dialog::location dialog_file; dialog_file.set_default_location(default_directory); dialog_file.add_filter("All files", "*.*"); dialog_file.add_filter("videos", "*.avi *.m4v *.mkv *.mp4 *.ogv"); dialog_file.add_filter("images", "*.bmp *.jpeg *.jpg *.png *.tiff"); dialog_file.add_filter("realsense files (*.bag)", "*.bag *.rosbag"); dialog_file.set_option(sight::ui::dialog::location::read); dialog_file.set_option(sight::ui::dialog::location::file_must_exist); std::size_t count = 0; for(auto& camera : cameras) { std::filesystem::path video_path; if(count == 1 && cameras.size() == 2) { // Try to guess the second stream path for RGBD cameras auto file = cameras[0]->get_video_file(); if(std::filesystem::is_directory(video_dir_preference_path)) { file = video_dir_preference_path / file; file = file.lexically_normal(); } const auto dir = file.parent_path(); if(!dir.empty()) { const auto parent_dir = dir.parent_path(); const auto cur_dir = *(--dir.end()); const auto find_valid_image_path = [&](std::set _folders) { for(const auto& leaf_dir : _folders) { const auto dir = parent_dir / leaf_dir; if(std::filesystem::exists(dir)) { std::filesystem::directory_iterator current_entry(dir); std::filesystem::directory_iterator end_entry; while(current_entry != end_entry) { std::filesystem::path entry_path = *current_entry; if(entry_path.has_stem()) { return entry_path; } ++current_entry; } } } return std::filesystem::path(); }; static const std::set s_DEPTH_FOLDERS = {{"d", "D", "depth", "Depth", "DEPTH"}}; static const std::set s_COLOR_FOLDERS = {{"c", "C", "color", "Color", "COLOR", "RGB"}}; if(s_DEPTH_FOLDERS.find(cur_dir.string()) != s_DEPTH_FOLDERS.end()) { video_path = find_valid_image_path(s_COLOR_FOLDERS); } else if(s_COLOR_FOLDERS.find(cur_dir.string()) != s_COLOR_FOLDERS.end()) { video_path = find_valid_image_path(s_DEPTH_FOLDERS); } } } if(video_path.empty()) { dialog_file.set_title("Choose a file to load for video source #" + std::to_string(count++)); auto result = std::dynamic_pointer_cast(dialog_file.show()); if(result) { default_directory->set_folder(result->get_file().parent_path()); dialog_file.save_default_location(default_directory); video_path = result->get_file(); } } if(!video_path.empty()) { if(std::filesystem::is_directory(video_dir_preference_path)) { if(!m_use_absolute_path) { const auto video_relative_path = std::filesystem::relative( video_path, video_dir_preference_path ); const std::filesystem::path concatenated_path = video_dir_preference_path / video_relative_path; if(std::filesystem::exists(concatenated_path)) { video_path = video_relative_path; } else { SIGHT_WARN( "Relative path '" + video_relative_path.string() + "' generated from preferences is not valid." ); } } } else { SIGHT_WARN( "Video directory '" + video_dir_preference_path.string() + "' stored in preference is not valid." ); } { data::mt::locked_ptr lock(camera); camera->set_camera_source(data::camera::file); camera->set_video_file(video_path.string()); } const data::camera::modified_signal_t::sptr sig = camera->signal(data::camera::MODIFIED_SIG); sig->async_emit(); this->signal(CONFIGURED_FILE_SIG)->async_emit(); } } m_sig_configured_cameras->async_emit(); } //------------------------------------------------------------------------------ void camera::on_choose_stream() { std::vector cameras = this->get_cameras(); std::size_t count = 0; for(auto& camera : cameras) { sight::ui::dialog::input input; input.set_title("Enter stream url for video source #" + std::to_string(count++)); const auto& [streamSource, ok] = input.get_input(); if(ok && !streamSource.empty()) { { data::mt::locked_ptr lock(camera); camera->set_camera_source(data::camera::stream); camera->set_stream_url(streamSource); } const data::camera::modified_signal_t::sptr sig = camera->signal(data::camera::MODIFIED_SIG); sig->async_emit(); this->signal(CONFIGURED_STREAM_SIG)->async_emit(); } } m_sig_configured_cameras->async_emit(); } //------------------------------------------------------------------------------ void camera::on_choose_device() { if(m_preference_mode) { try { sight::ui::preferences resolution_preference; m_camera_resolution_preference = resolution_preference.get(RESOLUTION_PREF_KEY); } catch(boost::property_tree::ptree_error&) { SIGHT_ERROR("Couldn't get preference. The required key doesn't exist."); } if(!m_camera_resolution_preference.empty()) { m_resolution = m_camera_resolution_preference; } } std::vector cameras = this->get_cameras(); const auto& devices = QMediaDevices::videoInputs(); if(devices.isEmpty()) { auto* error_message_box = new QMessageBox( QMessageBox::Critical, "Error", "No device available. Please connect a camera device and relaunch the application." ); error_message_box->exec(); } else { bool configured = true; std::size_t count = 0; for(auto& camera : cameras) { module::ui::qt::video::camera_device_dlg cam_dialog(m_resolution); cam_dialog.setWindowTitle(QString("Camera device selector for video source #%1").arg(count++)); if(((devices.size() > 1 && m_resolution != "preferences") || m_resolution == "prompt") && cam_dialog.exec() != QDialog::Accepted) { return; } bool is_selected = false; { data::mt::locked_ptr lock(camera); is_selected = cam_dialog.get_selected_camera(camera, m_resolution); configured &= is_selected; } if(is_selected) { const data::camera::modified_signal_t::sptr sig = camera->signal(data::camera::MODIFIED_SIG); sig->async_emit(); this->signal(CONFIGURED_DEVICE_SIG)->async_emit(); } else if(m_preference_mode) { [[maybe_unused]] const bool ok = QMetaObject::invokeMethod( this, &camera::set_preference, Qt::QueuedConnection ); SIGHT_ASSERT("The slot `set_preference` was not found.", ok); } } if(configured) { m_sig_configured_cameras->async_emit(); } } } //------------------------------------------------------------------------------ std::vector camera::get_cameras() const { std::vector cameras; auto camera_set = m_camera_set.lock(); if(camera_set) { for(std::size_t i = 0, num_cameras = camera_set->size() ; i < num_cameras ; ++i) { cameras.push_back(camera_set->get_camera(i)); } } else { const auto camera = m_camera.lock(); if(camera) { cameras.push_back(camera.get_shared()); } } return cameras; } //------------------------------------------------------------------------------ void camera::set_preference() { // set m_resolution to "prompt" mode in order to display the camera selection dialog anytime the button is clicked m_resolution = "prompt"; m_preference_mode = false; this->on_choose_device(); m_preference_mode = true; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::video sight-25.1.0/module/ui/qt/video/camera.hpp000066400000000000000000000154541503402212300203130ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::video { /** * @brief This editor allows to select the device to use. It updates the data camera identifier. * * @section Signals Signals * - \b configured_cameras(): emitted when the cameras have been successfully configured. * - \b configured_device(): emitted when the user selects a device as the video source. * - \b configured_file(): emitted when the user selects a file as the video source. * - \b configured_stream(): emitted when the user selects a stream as the video source. * * @section Slots Slots * - \b configureDevice(): configure the cameras as device sources. * - \b configureFile(): configure the cameras as file sources. * - \b configureStream(): configure the cameras as stream sources. * * @section XML XML Configuration * * Configure this service either with a single camera data: * * @code{.xml} true @endcode * Or with a camera series. The user will be prompted to select the source streams for each camera inside the series. * In this case, it is possible to configure the service to create empty cameras with the parameter * \b createCameraNumber. This may be useful to load/save camera data without an existing calibration. * * @code{.xml} 2 true false min/max/preferences/prompt/640x480 @endcode * * @subsection In-Out In-Out * - \b camera [sight::data::camera]: camera data. * - \b camera_set [sight::data::camera_set]: camera series thus containing several camera. * * @subsection Configuration Configuration * - \b video_support (optional, default="false"): if we can open a video file in addition with cameras. * - \b useAbsolutePath (optional, default="false"): when using a file input, tells if the path should be stored as * absolute or relative to the video preferences directory. * - \b createCameraNumber (optional, default="0"): number of cameras to create. If the parameter is set and the * camera series already contains camera data, an assertion will be raised. * - \b label (optional, default="Video source: "): label of the selector. * - \b resolution (optional, default="preferences"): Camera resolution. If 'preferences' is set, the camera resolution * will * be extracted from the preferences, otherwise, `min`, `max` or a specific value (eg: 640x480) are computed from the * camera's supported resolutions. When `prompt` option is set, the camera selection dialog will be always displayed. * * @section remarks remarks * In order to launch directly the video after selecting and configuring the device, remember to connect the *`configuredDevice()` signal with your grabber's `startCamera` slot. */ class camera final : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(camera, sight::ui::editor); /// Initialize signals and slots. camera(); /// Destroys the service. ~camera() noexcept override = default; // Key saved in the preference file static const std::string RESOLUTION_PREF_KEY; protected Q_SLOTS: /** * @brief Calls when user select another device. * @param _index the index of the selected device. */ void on_apply(int _index); /** * @brief Calls when user select another device. * @param _index the index of the selected device. */ void on_activated(int _index); /// Allows setting the camera resolution preference. void set_preference(); private: /// Type of the 'configured' signal. using configured_signal_t = core::com::signal; /// Configures the service. void configuring() final; /// Installs the layout. void starting() final; /// Destroys the layout. void stopping() final; /// Does nothing. void updating() final; /// Calls when user select a file. void on_choose_file(); /// Calls when user select a stream. void on_choose_stream(); /// Calls when user select a device. void on_choose_device(); /// Retrieves camera objects according to the XML configuration. std::vector get_cameras() const; /// Combobox for camera selection. QPointer m_devices_combo_box; /// Offer the possibility to select a video file. bool m_b_video_support {false}; /// Number of cameras to create when using a camera series as input. std::size_t m_num_create_cameras {0}; // Sets the file path as absolute ones bool m_use_absolute_path {false}; /// Signal emitted when the camera_set has been configured. configured_signal_t::sptr m_sig_configured_cameras; /// Label of the selector. std::string m_label {"Video source: "}; /// Requested resolution in xml configuration of the service std::string m_resolution; /// Value extracted from the preferences std::string m_camera_resolution_preference; bool m_preference_mode {false}; static constexpr std::string_view CAMERA = "camera"; static constexpr std::string_view CAMERA_SET = "camera_set"; data::ptr m_camera {this, CAMERA, true}; data::ptr m_camera_set {this, CAMERA_SET, true}; int m_old_index {}; }; } // namespace sight::module::ui::qt::video sight-25.1.0/module/ui/qt/video/camera_device_dlg.cpp000066400000000000000000000352251503402212300224510ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "camera_device_dlg.hpp" #include "module/ui/qt/video/formats.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QCameraDevice); Q_DECLARE_METATYPE(QCameraFormat); namespace sight::module::ui::qt::video { //----------------------------------------------------------------------------- camera_device_dlg::camera_device_dlg(std::string _xml_resolution_config) : m_devices_combo_box(new QComboBox()), m_cam_settings(new QListWidget()) { auto* main_layout = new QVBoxLayout(); auto* button_layout = new QHBoxLayout(); auto* selector_layout = new QHBoxLayout(); auto* validate_button = new QPushButton("Validate"); auto* cancel_button = new QPushButton("Cancel"); auto* device_label = new QLabel("Camera device: "); selector_layout->addWidget(device_label); selector_layout->addWidget(m_devices_combo_box); // Detect available devices. const auto& devices = QMediaDevices::videoInputs(); std::map name_to_uid; std::vector name_list; //We should keep the same order as given by Qt, and uniquely identify cameras with the same name. // First run: collect all detected device names and UIDs std::size_t index = 0; for(const auto& cam_info : devices) { //MacOs appends random number when cameras has same names, remove it to do it ourself. const std::string qt_cam_name = cam_info.description().toStdString(); const std::string cam_name = qt_cam_name.substr(0, qt_cam_name.rfind('#') - 1); // check if the name already exists const auto multiple_name = std::count(name_list.begin(), name_list.end(), cam_name); std::string unique_name = cam_name; if(multiple_name > 0) { unique_name = cam_name + " #" + std::to_string(multiple_name + 1); } name_list.push_back(cam_name); // prefix by index to keep the order in the map name_to_uid[std::to_string(index) + ". " + unique_name] = cam_info; ++index; } //Second Run: Add generated unique name into the comboBox. for(auto& p : name_to_uid) { const auto& device_name = p.first; auto& device_info = p.second; m_devices_combo_box->addItem(QString(device_name.c_str()), QVariant::fromValue(device_info)); } // Select camera on which the resolution should be applied onto if(index > 1 && _xml_resolution_config != "preferences" && _xml_resolution_config != "prompt") { //No need of m_camSettings widget button_layout->addWidget(validate_button); button_layout->addWidget(cancel_button); main_layout->addLayout(selector_layout); main_layout->addLayout(button_layout); this->setModal(true); this->setLayout(main_layout); this->setWindowTitle("Camera device selector"); } else { button_layout->addWidget(validate_button); button_layout->addWidget(cancel_button); main_layout->addLayout(selector_layout); main_layout->addWidget(m_cam_settings); main_layout->addLayout(button_layout); this->setModal(true); this->setLayout(main_layout); this->setWindowTitle("Camera device selector"); } this->on_select_device(m_devices_combo_box->currentIndex()); QObject::connect(m_devices_combo_box, SIGNAL(activated(int)), this, SLOT(on_select_device(int))); QObject::connect(validate_button, &QPushButton::clicked, this, &camera_device_dlg::accept); QObject::connect(cancel_button, &QPushButton::clicked, this, &camera_device_dlg::reject); } //----------------------------------------------------------------------------- bool camera_device_dlg::get_selected_camera(data::camera::sptr& _camera, std::string& _resolution_xml_option) { int index = m_devices_combo_box->currentIndex(); if(index >= 0) { const auto cam_info = qvariant_cast(m_devices_combo_box->itemData(index)); const auto supported_resolutions = cam_info.photoResolutions(); [[maybe_unused]] enum data::camera::pixel_format_t format = data::camera::pixel_format_t::invalid; QListWidgetItem* item = m_cam_settings->currentItem(); const auto resolution_warning = [this](size_t _source_res_x, size_t _source_res_y, int _target_res_x, int _target_res_y) { QMessageBox::warning( this, "Warning", QString( "The selected resolution (%3x%4) does not match the calibration (%1x%2).\n" "Please select a video source with a resolution of %1x%2." ).arg(_source_res_x).arg(_source_res_y).arg(_target_res_x).arg(_target_res_y) ); }; if(((item != nullptr) && _resolution_xml_option == "preferences") || ((item != nullptr) && _resolution_xml_option == "prompt")) { const auto settings = qvariant_cast(item->data(Qt::UserRole)); _camera->set_maximum_frame_rate(settings.maxFrameRate()); if((_camera->get_width() != 0 || _camera->get_height() != 0) && !(_camera->get_width() == static_cast(settings.resolution().width()) && _camera->get_height() == static_cast(settings.resolution().height())) && _camera->get_is_calibrated()) { resolution_warning( _camera->get_width(), _camera->get_height(), settings.resolution().width(), settings.resolution().height() ); return false; } const auto resolution = settings.resolution(); _camera->set_width(static_cast(resolution.width())); _camera->set_height(static_cast(resolution.height())); pixel_format_translator_t::left_const_iterator iter; iter = pixel_format_translator.left.find(settings.pixelFormat()); try { sight::ui::preferences resolution_preference; const auto pref_value = std::to_string(settings.resolution().width()) + "x" + std::to_string( settings.resolution().height() ); resolution_preference.put(camera::RESOLUTION_PREF_KEY, pref_value); } catch(boost::property_tree::ptree_error&) { SIGHT_ERROR(" Couldn't save preference"); } if(iter != pixel_format_translator.left.end()) { format = iter->second; } else { SIGHT_ERROR("No compatible pixel format found"); } } else if(!_resolution_xml_option.empty()) { const QSize xml_resolution_value = this->get_resolution(_resolution_xml_option, supported_resolutions); if(xml_resolution_value.isNull()) { return false; } _camera->set_maximum_frame_rate(30.F); if((_camera->get_width() != 0 || _camera->get_height() != 0) && !(_camera->get_width() == static_cast(xml_resolution_value.width()) && _camera->get_height() == static_cast(xml_resolution_value.height())) && _camera->get_is_calibrated()) { resolution_warning( _camera->get_width(), _camera->get_height(), xml_resolution_value.width(), xml_resolution_value.height() ); return false; } _camera->set_height(static_cast(xml_resolution_value.height())); _camera->set_width(static_cast(xml_resolution_value.width())); } else { SIGHT_ERROR("No camera setting selected, using default..."); _camera->set_maximum_frame_rate(30.F); _camera->set_height(0); _camera->set_width(0); return false; } //FIXME : Setting the pixel format generate an error (gstreamer) #ifndef __linux__ _camera->set_pixel_format(format); #endif _camera->set_camera_source(data::camera::device); _camera->set_camera_id(cam_info.id().toStdString()); //Use our description. _camera->set_description(m_devices_combo_box->currentText().toStdString()); return true; } return false; } //----------------------------------------------------------------------------- void camera_device_dlg::on_select_device(int _index) { m_cam_settings->clear(); if(_index >= 0) { const auto& cam_info = qvariant_cast(m_devices_combo_box->itemData(_index)); for(const auto& settings : cam_info.videoFormats()) { auto format = data::camera::pixel_format_t::invalid; pixel_format_translator_t::left_const_iterator iter; iter = pixel_format_translator.left.find(settings.pixelFormat()); if(iter != pixel_format_translator.left.end()) { format = iter->second; } else { SIGHT_ERROR("No compatible pixel format found"); } std::stringstream stream; stream << "[" << settings.resolution().width() << "X" << settings.resolution().height() << "]"; stream << "\t" << settings.maxFrameRate() << " fps"; stream << "\tFormat:" << data::camera::get_pixel_format_name(format); auto* item = new QListWidgetItem(QString::fromStdString(stream.str())); item->setData(Qt::UserRole, QVariant::fromValue(settings)); m_cam_settings->addItem(item); } } } //----------------------------------------------------------------------------- QSize camera_device_dlg::get_resolution( const std::string& _resolution_xml_option, const QList& _supported_resolutions ) { if(_resolution_xml_option == "min") { const auto criteria = [&](const QSize& _a, const QSize& _b) { return (_a.width() * _a.height()) <= (_b.width() * _b.height()); }; const QSize min = *std::min_element(_supported_resolutions.begin(), _supported_resolutions.end(), criteria); return min; } if(_resolution_xml_option == "max") { const auto criteria = [&](const QSize& _a, const QSize& _b) { return (_a.width() * _a.height()) <= (_b.width() * _b.height()); }; const QSize max = *std::max_element(_supported_resolutions.begin(), _supported_resolutions.end(), criteria); return max; } if(_resolution_xml_option == "preferences") { try { sight::ui::preferences resolution_preference; auto resolution_preference_str = resolution_preference.get(camera::RESOLUTION_PREF_KEY); std::regex res_pattern("(\\d*)[Xx](\\d*)"); std::smatch match; std::regex_match(resolution_preference_str, match, res_pattern); if(!match.empty()) { int xml_width = std::stoi(std::string(match[1].first, match[1].second)); int xml_height = std::stoi(std::string(match[2].first, match[2].second)); const QSize res {xml_width, xml_height}; // find the resolution among the supportedResolutions list if(_supported_resolutions.indexOf(res) >= 0) { return res; } QMessageBox::critical( this, "Error", "The requested resolution is not supported." ); } else { QMessageBox::critical( this, "Error", "The requested resolution is not supported." ); } } catch(boost::property_tree::ptree_error&) { SIGHT_ERROR("Couldn't get preference. The required key doesn't exist."); } } else { std::regex res_pattern("(\\d*)[Xx](\\d*)"); std::smatch match; std::regex_match(_resolution_xml_option, match, res_pattern); if(!match.empty()) { int xml_width = std::stoi(std::string(match[1].first, match[1].second)); int xml_height = std::stoi(std::string(match[2].first, match[2].second)); const QSize res {xml_width, xml_height}; // find the resolution among the supportedResolutions list if(_supported_resolutions.indexOf(res) >= 0) { return res; } QMessageBox::critical( this, "Error", "The requested resolution is not supported." ); } else { QMessageBox::critical( this, "Error", "The requested resolution is not supported." ); } } return {0, 0}; } } // namespace sight::module::ui::qt::video sight-25.1.0/module/ui/qt/video/camera_device_dlg.hpp000066400000000000000000000037341503402212300224560ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "camera.hpp" #include #include #include #include namespace sight::data { class Camera; } // namespace sight::data namespace sight::module::ui::qt::video { /** * @brief The camera_device_dlg class displays a QDialog to choose camera device. */ class camera_device_dlg : public QDialog { Q_OBJECT; public: /// constructor camera_device_dlg(std::string _xml_resolution = ""); /// destructor ~camera_device_dlg() override = default; bool get_selected_camera(SPTR(data::camera) & _camera, std::string& _resolution_xml_option); // filter the list of supported resolution to extract the lowest, highest and medium resolution in relation to // `resolutionType` QSize get_resolution( const std::string& _resolution_xml_option, const QList& _supported_resolutions ); private Q_SLOTS: void on_select_device(int _index); private: QComboBox* m_devices_combo_box; QListWidget* m_cam_settings; }; } // namespace sight::module::ui::qt::video sight-25.1.0/module/ui/qt/video/formats.cpp000066400000000000000000000075371503402212300205340ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "formats.hpp" #include // cspell:ignore XRGB BGRX namespace sight::module::ui::qt::video { // The format of the code is shitty, that's because of uncrustify who doesn't allow us to write a pair per line. pixel_format_translator_t pixel_format_translator = boost::assign::list_of ( QVideoFrameFormat::PixelFormat::Format_Invalid, data::camera::pixel_format_t::invalid ) ( QVideoFrameFormat::PixelFormat::Format_ARGB8888, data::camera::pixel_format_t::argb32 ) ( QVideoFrameFormat::PixelFormat::Format_ARGB8888_Premultiplied, data::camera::pixel_format_t::argb32_premultiplied ) ( QVideoFrameFormat::PixelFormat::Format_XRGB8888, data::camera::pixel_format_t::rgb32 ) ( QVideoFrameFormat::PixelFormat::Format_BGRA8888, data::camera::pixel_format_t::bgra32 ) ( QVideoFrameFormat::PixelFormat::Format_BGRA8888_Premultiplied, data::camera::pixel_format_t::bgra32_premultiplied ) ( QVideoFrameFormat::PixelFormat::Format_BGRX8888, data::camera::pixel_format_t::bgr32 ) ( QVideoFrameFormat::PixelFormat::Format_AYUV, data::camera::pixel_format_t::ayuv444 ) ( QVideoFrameFormat::PixelFormat::Format_AYUV_Premultiplied, data::camera::pixel_format_t::ayuv444_premultiplied ) ( QVideoFrameFormat::PixelFormat::Format_YUYV, data::camera::pixel_format_t::yuv444 ) ( QVideoFrameFormat::PixelFormat::Format_YUV420P, data::camera::pixel_format_t::yuv420_p ) ( QVideoFrameFormat::PixelFormat::Format_YV12, data::camera::pixel_format_t::yv12 ) ( QVideoFrameFormat::PixelFormat::Format_UYVY, data::camera::pixel_format_t::uyvy ) ( QVideoFrameFormat::PixelFormat::Format_YUYV, data::camera::pixel_format_t::yuyv ) ( QVideoFrameFormat::PixelFormat::Format_NV12, data::camera::pixel_format_t::nv12 ) ( QVideoFrameFormat::PixelFormat::Format_NV21, data::camera::pixel_format_t::nv21 ) ( QVideoFrameFormat::PixelFormat::Format_IMC1, data::camera::pixel_format_t::imc1 ) ( QVideoFrameFormat::PixelFormat::Format_IMC2, data::camera::pixel_format_t::imc2 ) ( QVideoFrameFormat::PixelFormat::Format_IMC3, data::camera::pixel_format_t::imc3 ) ( QVideoFrameFormat::PixelFormat::Format_IMC4, data::camera::pixel_format_t::imc4 ) ( QVideoFrameFormat::PixelFormat::Format_Y8, data::camera::pixel_format_t::y8 ) ( QVideoFrameFormat::PixelFormat::Format_Y16, data::camera::pixel_format_t::y16 ) ( QVideoFrameFormat::PixelFormat::Format_Jpeg, data::camera::pixel_format_t::jpeg ); } // namespace sight::module::ui::qt::video sight-25.1.0/module/ui/qt/video/formats.hpp000066400000000000000000000026261503402212300205330ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::ui::qt::video { //----------------------------------------------------------------------------- using pixel_format_translator_t = boost::bimaps::bimap; extern pixel_format_translator_t pixel_format_translator; } // namespace sight::module::ui::qt::video sight-25.1.0/module/ui/qt/video/slider.cpp000066400000000000000000000134221503402212300203310ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "slider.hpp" #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::video { const core::com::signals::key_t slider::POSITION_CHANGED_SIG = "position_changed"; const core::com::slots::key_t slider::SET_POSITION_SLIDER_SLOT = "set_position_slider"; const core::com::slots::key_t slider::SET_DURATION_SLIDER_SLOT = "set_duration_slider"; static const char* s_unknown_time = "--:--:--"; //------------------------------------------------------------------------------ QString convert_m_sec_to_hhmmss(int64_t _milliseconds) { std::chrono::milliseconds ms(_milliseconds); std::chrono::hours hours = std::chrono::duration_cast(ms); ms -= hours; std::chrono::minutes minutes = std::chrono::duration_cast(ms); ms -= minutes; std::chrono::seconds seconds = std::chrono::duration_cast(ms); QTime time(int(hours.count()), int(minutes.count()), static_cast(seconds.count())); return time.toString("hh:mm:ss"); } //------------------------------------------------------------------------------ slider::slider() noexcept { /// Slot to change the position of the slider new_slot(SET_POSITION_SLIDER_SLOT, &slider::set_position, this); /// Slot to change the duration of the slider new_slot(SET_DURATION_SLIDER_SLOT, &slider::set_duration, this); m_sig_position_changed = new_signal(POSITION_CHANGED_SIG); } //------------------------------------------------------------------------------ void slider::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); const QString service_id = QString::fromStdString(base_id()); QPointer layout = new QHBoxLayout(); layout->setObjectName(service_id); m_position_slider = new QSlider(Qt::Horizontal); m_position_slider->setObjectName(service_id + "/positionSlider"); m_position_slider->setRange(0, 0); QObject::connect(m_position_slider, &QSlider::sliderPressed, this, &self_t::slider_pressed); QObject::connect(m_position_slider, &QSlider::sliderReleased, this, &self_t::change_position); m_current_position = new QLabel(); m_current_position->setObjectName(service_id + "/currentPosition"); m_current_position->setText(s_unknown_time); m_total_duration = new QLabel(); m_total_duration->setObjectName(service_id + "/totalDuration"); m_total_duration->setText(s_unknown_time); layout->addWidget(m_current_position); layout->addWidget(m_position_slider); layout->addWidget(m_total_duration); qt_container->set_layout(layout); this->updating(); } //------------------------------------------------------------------------------ void slider::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void slider::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void slider::updating() { } //------------------------------------------------------------------------------ void slider::change_position() { int64_t new_pos = m_position_slider->sliderPosition(); m_position_slider->setSliderPosition(static_cast(new_pos)); if(new_pos == -1) { m_current_position->setText(s_unknown_time); } else { m_current_position->setText(convert_m_sec_to_hhmmss(new_pos)); } // Notify the new position m_sig_position_changed->async_emit(new_pos); m_slider_pressed = false; } //------------------------------------------------------------------------------ void slider::slider_pressed() { m_slider_pressed = true; } //------------------------------------------------------------------------------ void slider::set_position(int64_t _new_pos) { if(!m_slider_pressed) { m_position_slider->setValue(static_cast(_new_pos)); if(_new_pos == -1) { m_current_position->setText(s_unknown_time); } else { m_current_position->setText(convert_m_sec_to_hhmmss(_new_pos)); } } } //------------------------------------------------------------------------------ void slider::set_duration(int64_t _duration) { m_position_slider->setRange(0, static_cast(_duration)); if(_duration == -1) { m_total_duration->setText(s_unknown_time); } else { m_total_duration->setText(convert_m_sec_to_hhmmss(_duration)); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::video sight-25.1.0/module/ui/qt/video/slider.hpp000066400000000000000000000063231503402212300203400ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::video { /** * @brief This editor allows to draw a slider. It is designed to be used with frame_grabber to browse a video. */ class slider : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(slider, sight::ui::editor); /// Constructor. Do nothing. slider() noexcept; /// Destructor. Do nothing. ~slider() noexcept override = default; /**@name Signals API * @{ */ static const core::com::signals::key_t POSITION_CHANGED_SIG; using position_changed_signal_t = core::com::signal; /** @} */ /** * @name Slots API * @{ */ static const core::com::slots::key_t SET_POSITION_SLIDER_SLOT; using change_position_slot_t = core::com::slot; static const core::com::slots::key_t SET_DURATION_SLIDER_SLOT; using change_duration_slot_t = core::com::slot; ///@} protected: /// Installs the layout void starting() override; /// Destroys the layout void stopping() override; /// Does nothing void updating() override; /** * @brief Configure the service * * @code{.xml} @endcode */ void configuring() override; /// Signal when the position os the slider changed position_changed_signal_t::sptr m_sig_position_changed; /// SLOT : Call to set the video position. void set_position(int64_t _new_pos); /// SLOT : Call to set the video position. void set_duration(int64_t _duration); protected Q_SLOTS: /// Calls when the cursor is moved. void change_position(); /// Calls when the cursor starts to move . void slider_pressed(); private: /// Slider to show progress. QPointer m_position_slider; QPointer m_current_position; QPointer m_total_duration; /// Is the slider pressed ? bool m_slider_pressed {false}; }; } // namespace sight::module::ui::qt::video sight-25.1.0/module/ui/qt/viz/000077500000000000000000000000001503402212300160435ustar00rootroot00000000000000sight-25.1.0/module/ui/qt/viz/matrix_viewer.cpp000066400000000000000000000076361503402212300214500ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2023 IRCAD France * Copyright (C) 2017-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "matrix_viewer.hpp" #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::viz { // ------------------------------------------------------------------------------ matrix_viewer::matrix_viewer() noexcept : m_title("matrix") { } // ------------------------------------------------------------------------------ matrix_viewer::~matrix_viewer() noexcept = default; // ------------------------------------------------------------------------------ void matrix_viewer::configuring() { sight::ui::service::initialize(); m_title = this->get_config().get("title", "matrix"); } // ------------------------------------------------------------------------------ void matrix_viewer::starting() { sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast(get_container()); auto* main_layout = new QBoxLayout(QBoxLayout::TopToBottom); main_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); QPointer description = new QLabel(QString::fromStdString(m_title)); main_layout->addWidget(description); auto* grid_layout = new QGridLayout(); for(int i = 0 ; i < 4 ; ++i) { for(int j = 0 ; j < 4 ; ++j) { auto* label = new QLabel(""); m_matrix_labels.push_back(label); grid_layout->addWidget(label, i, j); } } main_layout->addLayout(grid_layout); qt_container->set_layout(main_layout); this->update_from_matrix(); } // ------------------------------------------------------------------------------ void matrix_viewer::stopping() { this->destroy(); } // ------------------------------------------------------------------------------ void matrix_viewer::updating() { this->update_from_matrix(); } // ------------------------------------------------------------------------------ void matrix_viewer::update_from_matrix() { const auto matrix = m_matrix.lock(); for(unsigned int i = 0 ; i < 4 ; ++i) { for(unsigned int j = 0 ; j < 4 ; ++j) { m_matrix_labels[int(i * 4 + j)]->setText(QString("%1").arg((*matrix)(i, j), 0, 'f', 2)); } } } // ------------------------------------------------------------------------------ void matrix_viewer::clear_labels() { for(int i = 0 ; i < 4 ; ++i) { for(int j = 0 ; j < 4 ; ++j) { m_matrix_labels[i * 4 + j]->setText(QString("")); } } } // ------------------------------------------------------------------------------ service::connections_t matrix_viewer::auto_connections() const { connections_t connections; connections.push(MATRIX, data::matrix4::MODIFIED_SIG, service::slots::UPDATE); return connections; } // ------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::viz sight-25.1.0/module/ui/qt/viz/matrix_viewer.hpp000066400000000000000000000056711503402212300214520ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2024 IRCAD France * Copyright (C) 2017-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include class QLabel; namespace sight::module::ui::qt::viz { /** * @brief This class defines a viewer for a data::matrix4. * * @section XML XML Configuration * * @code{.xml} matrix name @endcode * @subsection Input Input * - \b matrix [sight::data::matrix4]: matrix to display * * @subsection Configuration Configuration * - \b title (optional): defines the displayed title on top of the matrix viewer (default: matrix). */ class matrix_viewer : public QObject, public sight::ui::editor { public: SIGHT_DECLARE_SERVICE(matrix_viewer, sight::ui::editor); /// Constructor. Do nothing. matrix_viewer() noexcept; /// Destructor. Do nothing. ~matrix_viewer() noexcept override; /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection * * Connect Matrix4::MODIFIED_SIG to this::service::slots::UPDATE */ connections_t auto_connections() const override; protected: /// Configures the title of the matrix viewer void configuring() override; /// Initializes the layout of the matrix viewer void starting() override; /// Destroys the layout void stopping() override; /// Updates the matrix values to display void updating() override; private: /// Updates the view when the matrix changes void update_from_matrix(); /// Clears matrix values void clear_labels(); std::string m_title; ///< Title of the matrix that will be displayed QVector > m_matrix_labels; ///< Labels for matrix's elements static constexpr std::string_view MATRIX = "matrix"; data::ptr m_matrix {this, MATRIX}; }; } // namespace sight::module::ui::qt::viz sight-25.1.0/module/ui/qt/viz/point_editor.cpp000066400000000000000000000101251503402212300212450ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/viz/point_editor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::viz { static const core::com::slots::key_t GET_INTERACTION_SLOT = "get_interaction"; point_editor::point_editor() noexcept { new_slot(GET_INTERACTION_SLOT, &point_editor::get_interaction, this); } //------------------------------------------------------------------------------ point_editor::~point_editor() noexcept = default; //------------------------------------------------------------------------------ void point_editor::starting() { this->sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); auto* h_layout = new QHBoxLayout(); auto* static_text_x = new QLabel(tr("x:")); h_layout->addWidget(static_text_x, 0, Qt::AlignVCenter); m_text_ctrl_x = new QLineEdit(); m_text_ctrl_x->setValidator(new QDoubleValidator(m_text_ctrl_x)); h_layout->addWidget(m_text_ctrl_x, 1, Qt::AlignVCenter); auto* static_text_y = new QLabel(tr("y:")); h_layout->addWidget(static_text_y, 0, Qt::AlignVCenter); m_text_ctrl_y = new QLineEdit(); m_text_ctrl_y->setValidator(new QDoubleValidator(m_text_ctrl_y)); h_layout->addWidget(m_text_ctrl_y, 1, Qt::AlignVCenter); auto* static_text_z = new QLabel(tr("z:")); h_layout->addWidget(static_text_z, 0, Qt::AlignVCenter); m_text_ctrl_z = new QLineEdit(); m_text_ctrl_z->setValidator(new QDoubleValidator(m_text_ctrl_z)); h_layout->addWidget(m_text_ctrl_z, 1, Qt::AlignVCenter); qt_container->set_layout(h_layout); this->updating(); } //------------------------------------------------------------------------------ void point_editor::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void point_editor::configuring() { this->sight::ui::service::initialize(); } //------------------------------------------------------------------------------ void point_editor::updating() { } //------------------------------------------------------------------------------ void point_editor::get_interaction(data::tools::picking_info _info) { if(_info.m_event_id == data::tools::picking_info::event::mouse_left_down) { m_text_ctrl_x->setText(QString("%1").arg(_info.m_world_pos[0], 0, 'f', 3)); m_text_ctrl_y->setText(QString("%1").arg(_info.m_world_pos[1], 0, 'f', 3)); m_text_ctrl_z->setText(QString("%1").arg(_info.m_world_pos[2], 0, 'f', 3)); } } //------------------------------------------------------------------------------ void point_editor::info(std::ostream& _sstream) { _sstream << "Point Editor"; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::viz sight-25.1.0/module/ui/qt/viz/point_editor.hpp000066400000000000000000000041351503402212300212560ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include namespace sight::module::ui::qt::viz { /** * @brief point_editor service allows to display point information. */ class point_editor : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(point_editor, sight::ui::editor); /// Constructor. Do nothing. point_editor() noexcept; /// Destructor. Do nothing. ~point_editor() noexcept override; protected: ///This method launches the editor::starting method. void starting() override; ///This method launches the editor::stopping method. void stopping() override; void updating() override; void configuring() override; /// Overrides void info(std::ostream& _sstream) override; private: /// Slot: get the interaction information void get_interaction(data::tools::picking_info _info); QPointer m_text_ctrl_x; QPointer m_text_ctrl_y; QPointer m_text_ctrl_z; }; } // namespace sight::module::ui::qt::viz sight-25.1.0/module/ui/qt/viz/snapshot_editor.cpp000066400000000000000000000126521503402212300217620ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/viz/snapshot_editor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::viz { //------------------------------------------------------------------------------ const core::com::signals::key_t snapshot_editor::SNAPPED_SIG = "snapped"; //------------------------------------------------------------------------------ snapshot_editor::snapshot_editor() noexcept { m_sig_snapped = new_signal(SNAPPED_SIG); } //------------------------------------------------------------------------------ snapshot_editor::~snapshot_editor() noexcept = default; //------------------------------------------------------------------------------ void snapshot_editor::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); std::filesystem::path path = core::runtime::get_module_resource_file_path( "sight::module::ui::qt", "frame.svg" ); QIcon icon(QString::fromStdString(path.string())); m_snap_button = new QPushButton(icon, ""); m_snap_button->setToolTip(QObject::tr("Snapshot")); auto* h_layout = new QHBoxLayout(); h_layout->addWidget(m_snap_button); h_layout->setContentsMargins(0, 0, 0, 0); qt_container->set_layout(h_layout); QObject::connect(m_snap_button, &QPushButton::clicked, this, &self_t::on_snap_button); } //------------------------------------------------------------------------------ void snapshot_editor::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void snapshot_editor::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void snapshot_editor::updating() { } //------------------------------------------------------------------------------ void snapshot_editor::info(std::ostream& /*_sstream*/) { } //------------------------------------------------------------------------------ void snapshot_editor::on_snap_button() { auto qt_container = std::dynamic_pointer_cast( this->get_container() ); QWidget* container = qt_container->get_qt_container(); SIGHT_ASSERT("container not instanced", container); if(container->isVisible()) { std::string filename = sight::module::ui::qt::viz::snapshot_editor::request_file_name(); if(!filename.empty()) { m_sig_snapped->async_emit(filename); } } else { std::string msg_info("It is not possible to snapshot the negato view. This view is not shown on screen."); sight::ui::dialog::message message_box; message_box.set_title("Negato view snapshot"); message_box.set_message(msg_info); message_box.set_icon(sight::ui::dialog::message::warning); message_box.add_button(sight::ui::dialog::message::ok); message_box.show(); } } //------------------------------------------------------------------------------ std::string snapshot_editor::request_file_name() { static auto default_directory = std::make_shared(); std::string file_name; sight::ui::dialog::location dialog_file; dialog_file.set_title("Save snapshot as"); dialog_file.set_default_location(default_directory); dialog_file.add_filter("Image file", "*.jpg *.jpeg *.bmp *.png *.tiff"); dialog_file.add_filter("jpeg", "*.jpg *.jpeg"); dialog_file.add_filter("bmp", "*.bmp"); dialog_file.add_filter("png", "*.png"); dialog_file.add_filter("tiff", "*.tiff"); dialog_file.add_filter("all", "*.*"); dialog_file.set_option(sight::ui::dialog::location::write); auto result = std::dynamic_pointer_cast(dialog_file.show()); if(result) { file_name = result->get_file().string(); default_directory->set_folder(result->get_file().parent_path()); dialog_file.save_default_location(default_directory); } return file_name; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::viz sight-25.1.0/module/ui/qt/viz/snapshot_editor.hpp000066400000000000000000000055351503402212300217710ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include class QPushButton; namespace sight::module::ui::qt::viz { /** * @brief snapshot_editor service is represented by a button. It allows to snap shot a generic scene. * * Send a 'snapped' signal containing the filename used to save the snapshot. * @note You need to connect the 'snapped' signal to one visuVTKAdaptor::Snapshot to save the file. */ class snapshot_editor : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(snapshot_editor, sight::ui::editor); /// Constructor. Do nothing. snapshot_editor() noexcept; /// Destructor. Do nothing. ~snapshot_editor() noexcept override; protected: /** * @brief Install the layout. */ void starting() override; /** * @brief Destroy the layout. */ void stopping() override; /// Do nothing void updating() override; /** * @brief Configure the editor. * * Example of configuration * @code{.xml} @endcode */ void configuring() override; /// Overrides void info(std::ostream& _sstream) override; protected Q_SLOTS: /** * @brief Show a file dialog and notify the scene must be printed. */ void on_snap_button(); private: static std::string request_file_name(); /** * @name Signals * @{ */ /// Type of signal to snap shot using snapped_signal_t = core::com::signal; static const core::com::signals::key_t SNAPPED_SIG; snapped_signal_t::sptr m_sig_snapped; ///< snap shot signal /** * @} */ QPointer m_snap_button; }; } // namespace sight::module::ui::qt::viz sight-25.1.0/module/ui/qt/viz/transform_editor.cpp000066400000000000000000000311161503402212300221320ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2025 IRCAD France * Copyright (C) 2017-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/qt/viz/transform_editor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::qt::viz { //------------------------------------------------------------------------------ transform_editor::transform_editor() noexcept : m_rotation("xyz"), m_translation("xyz") { m_translation_range[0] = -300; m_translation_range[1] = +300; m_rotation_range[0] = -180; m_rotation_range[1] = 180; new_slot(slots::SET_TRANSLATION_RANGE, &transform_editor::set_translation_range, this); } //------------------------------------------------------------------------------ void transform_editor::configuring() { static const std::regex s_REGEX("[xyz][xyz]?[xyz]?"); this->initialize(); service::config_t config = this->get_config(); const std::string rotation = config.get("rotation..enabled", "true"); if(rotation == "false") { m_rotation = ""; } else if(rotation == "true") { m_rotation = "xyz"; } else if(std::regex_match(rotation, s_REGEX)) { m_rotation = rotation; } else { SIGHT_ERROR("Attribute 'rotation' should be 'true', 'false' or a combination of [xyz]"); } m_rotation_range[0] = config.get("rotation..min", m_rotation_range[0]); m_rotation_range[1] = config.get("rotation..max", m_rotation_range[1]); const std::string translation = config.get("translation..enabled", "true"); if(translation == "false") { m_translation = ""; } else if(translation == "true") { m_translation = "xyz"; } else if(std::regex_match(translation, s_REGEX)) { m_translation = translation; } else { SIGHT_ERROR("Attribute 'translation' should be 'true', 'false' or a combination of [xyz]"); } m_translation_range[0] = config.get("translation..min", m_translation_range[0]); m_translation_range[1] = config.get("translation..max", m_translation_range[1]); } //------------------------------------------------------------------------------ void transform_editor::starting() { const std::array description {"Translation X", "Translation Y", "Translation Z", "Rotation X", "Rotation Y", "Rotation Z" }; this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); auto* layout = new QVBoxLayout(); qt_container->set_layout(layout); const auto minimal = *m_minimal; for(unsigned int i = 0 ; i < max_slider_index ; i++) { auto* slider_layout = new QHBoxLayout(); m_sliders[i].m_slider = new QSlider(Qt::Horizontal); m_sliders[i].m_slider->setTickInterval(1); if(not minimal) { m_sliders[i].m_slider_value = new QLineEdit(); m_sliders[i].m_label_min = new QLabel(); m_sliders[i].m_label_max = new QLabel(); m_sliders[i].m_label_definition = new QLabel(); m_sliders[i].m_label_definition->setText(description[i]); slider_layout->addWidget(m_sliders[i].m_label_definition, 0); slider_layout->addWidget(m_sliders[i].m_label_min, 0); } slider_layout->addWidget(m_sliders[i].m_slider, 3); if(not minimal) { slider_layout->addWidget(m_sliders[i].m_label_max, 0); slider_layout->addWidget(m_sliders[i].m_slider_value, 1); QObject::connect(m_sliders[i].m_slider_value, &QLineEdit::editingFinished, this, &self_t::on_text_changed); } layout->addLayout(slider_layout, 0); QObject::connect(m_sliders[i].m_slider, &QSlider::valueChanged, this, &self_t::on_slider_changed); } for(unsigned int i = position_x ; i <= position_z ; i++) { if(not minimal) { m_sliders[i].m_label_min->setText(QString("%1").arg(m_translation_range[0])); m_sliders[i].m_label_max->setText(QString("%1").arg(m_translation_range[1])); } m_sliders[i].m_slider->setRange(m_translation_range[0], m_translation_range[1]); } const std::string axes = "xyzxyz"; for(unsigned int i = position_x ; i <= position_z ; i++) { const bool visible = m_translation.find(axes[i]) != std::string::npos; if(not minimal) { m_sliders[i].m_slider_value->setVisible(visible); m_sliders[i].m_label_min->setVisible(visible); m_sliders[i].m_label_max->setVisible(visible); m_sliders[i].m_label_definition->setVisible(visible); } m_sliders[i].m_slider->setVisible(visible); } for(unsigned int i = rotation_x ; i <= rotation_z ; i++) { if(not minimal) { m_sliders[i].m_label_min->setText(QString("%1").arg(m_rotation_range[0])); m_sliders[i].m_label_max->setText(QString("%1").arg(m_rotation_range[1])); } m_sliders[i].m_slider->setRange(m_rotation_range[0], m_rotation_range[1]); } for(unsigned int i = rotation_x ; i <= rotation_z ; i++) { const bool visible = m_rotation.find(axes[i]) != std::string::npos; if(not minimal) { m_sliders[i].m_slider_value->setVisible(visible); m_sliders[i].m_label_min->setVisible(visible); m_sliders[i].m_label_max->setVisible(visible); m_sliders[i].m_label_definition->setVisible(visible); } m_sliders[i].m_slider->setVisible(visible); } this->update_from_matrix(); } //------------------------------------------------------------------------------ void transform_editor::stopping() { const auto minimal = *m_minimal; for(unsigned int i = 0 ; i < max_slider_index ; i++) { QObject::disconnect( m_sliders[i].m_slider, &QSlider::valueChanged, this, &self_t::on_slider_changed ); if(not minimal) { QObject::disconnect( m_sliders[i].m_slider_value, &QLineEdit::editingFinished, this, &self_t::on_text_changed ); } } this->destroy(); } //------------------------------------------------------------------------------ void transform_editor::updating() { this->update_from_matrix(); } //----------------------------------------------------------------------------- service::connections_t transform_editor::auto_connections() const { connections_t connections; connections.push(MATRIX_INOUT, data::matrix4::MODIFIED_SIG, service::slots::UPDATE); return connections; } //------------------------------------------------------------------------------ void transform_editor::set_translation_range(double _min, double _max) { if(_min != std::numeric_limits::max()) { m_translation_range[0] = static_cast(_min); } if(_max != std::numeric_limits::min()) { m_translation_range[1] = static_cast(_max); } const auto minimal = *m_minimal; for(unsigned int i = position_x ; i <= position_z ; i++) { if(m_sliders[i].m_slider->isVisible()) { const auto matrix = m_matrix.lock(); SIGHT_ASSERT("Unable to get matrix", matrix); const auto value = std::clamp((*matrix)[3 + static_cast(i) * 4], _min, _max); m_sliders[i].m_slider->blockSignals(true); if(not minimal) { m_sliders[i].m_label_min->setText(QString("%1").arg(m_translation_range[0])); m_sliders[i].m_label_max->setText(QString("%1").arg(m_translation_range[1])); m_sliders[i].m_slider_value->setText(QString("%1").arg(value)); } (*matrix)[3 + static_cast(i) * 4] = value; m_sliders[i].m_slider->setValue(static_cast(value)); m_sliders[i].m_slider->setRange(m_translation_range[0], m_translation_range[1]); m_sliders[i].m_slider->blockSignals(false); if(_min == _max) { m_sliders[i].m_slider->setEnabled(false); } else { m_sliders[i].m_slider->setEnabled(true); } // matrix->async_emit(this, sight::data::object::MODIFIED_SIG); } } } //------------------------------------------------------------------------------ void transform_editor::on_slider_changed(int /*unused*/) { const auto rx = glm::radians(m_sliders[rotation_x].m_slider->value()); const auto ry = glm::radians(m_sliders[rotation_y].m_slider->value()); const auto rz = glm::radians(m_sliders[rotation_z].m_slider->value()); const double tx = m_sliders[position_x].m_slider->value(); const double ty = m_sliders[position_y].m_slider->value(); const double tz = m_sliders[position_z].m_slider->value(); const glm::dquat quat = glm::dquat(glm::dvec3(rx, ry, rz)); glm::dmat4x4 mat = glm::mat4_cast(quat); mat = glm::translate(mat, glm::dvec3(tx, ty, tz)); const auto matrix = m_matrix.lock(); geometry::data::from_glm_mat(*matrix, mat); const auto minimal = *m_minimal; if(not minimal) { for(unsigned int i = 0 ; i < max_slider_index ; i++) { m_sliders[i].m_slider_value->setText(QString("%1").arg(m_sliders[i].m_slider->value())); } } matrix->async_emit(this, data::object::MODIFIED_SIG); } //------------------------------------------------------------------------------ void transform_editor::on_text_changed() { for(unsigned int i = 0 ; i < max_slider_index ; i++) { QString string = m_sliders[i].m_slider_value->text(); m_sliders[i].m_slider->setValue(string.toInt()); } } //------------------------------------------------------------------------------ void transform_editor::update_from_matrix() { const auto matrix = m_matrix.lock(); SIGHT_ASSERT("Unable to get matrix", matrix); const glm::dmat4x4 mat = geometry::data::to_glm_mat(*matrix); const glm::dquat quat(mat); const glm::dvec3 angles = glm::eulerAngles(quat); const glm::dvec4 translation = mat[3]; const auto minimal = *m_minimal; // Block for(unsigned int i = 0 ; i < max_slider_index ; i++) { m_sliders[i].m_slider->blockSignals(true); if(not minimal) { m_sliders[i].m_slider_value->blockSignals(true); } } for(glm::length_t i = position_x, j = 0 ; i <= position_z ; i++, ++j) { m_sliders[unsigned(i)].m_slider->setValue(static_cast(translation[j])); } for(glm::length_t i = rotation_x, j = 0 ; i <= rotation_z ; i++, ++j) { m_sliders[unsigned(i)].m_slider->setValue(static_cast(glm::degrees(angles[j]))); } if(not minimal) { for(unsigned int i = 0 ; i < max_slider_index ; i++) { m_sliders[i].m_slider_value->setText(QString("%1").arg(m_sliders[i].m_slider->value())); m_sliders[i].m_slider_value->blockSignals(false); } } for(unsigned int i = 0 ; i < max_slider_index ; i++) { m_sliders[i].m_slider->blockSignals(false); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::qt::viz sight-25.1.0/module/ui/qt/viz/transform_editor.hpp000066400000000000000000000125361503402212300221440ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2025 IRCAD France * Copyright (C) 2017-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include class QSlider; class QLabel; class QLineEdit; namespace sight::module::ui::qt::viz { /** * @brief This editor regulates the position and rotation defined in a transformation matrix. To respect the T*R*S * matrices multiplication order, the translation is applied after the rotation. * * @section Signals Signals * - \b set_translation_range(double, double): Allows to dynamically set the min and max of the translation. * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection In-Out In-Out * - \b matrix [sight::data::matrix4]: matrix modified by the editor * * @subsection Configuration Configuration * - \b enabled (optional): enable/disable rotation/translation edition. * Can be 'true', 'false' or a combination of [xyz] (default: true). * - \b min (optional): set the minimum value for translation/rotation (default: translation=-300, rotation=-180 ). * - \b max (optional): set the maximum value for translation/rotation (default: translation=+300, rotation=180). * * @subsection Configuration Configuration * - \b minimal (optional, default=false): only shows the slider, no label displayed */ class transform_editor final : public QObject, public sight::ui::editor { Q_OBJECT; public: struct slots { static inline const std::string SET_TRANSLATION_RANGE = "set_translation_range"; }; SIGHT_DECLARE_SERVICE(transform_editor, sight::ui::editor); /// Constructor. Do nothing. transform_editor() noexcept; /// Destructor. Do nothing. ~transform_editor() noexcept final = default; protected: /// This method is used to configure the service parameters: void configuring() final; ///This method launches the sight::ui::service::create method. void starting() final; ///This method launches the sight::ui::service::destroy method. void stopping() final; /// Updates Slider value void updating() final; // Connect data::matrix4::MODIFIED_SIG to update slot connections_t auto_connections() const final; void set_translation_range(double _min, double _max); private Q_SLOTS: /// Slot called when slider value changed. void on_slider_changed(int _value); /// Slot called when line edit value changed. void on_text_changed(); private: /// Update the editor when the matrix changes void update_from_matrix(); /* * @brief This enum defines the transformation matrix entries indexes */ enum slider_index { position_x = 0, position_y, position_z, rotation_x, rotation_y, rotation_z, max_slider_index }; /* * @brief This struct regulates a transformation matrix entry in the editor */ struct slider_widget { QPointer m_slider; ///< Slider to change coefficient value. QPointer m_label_min; ///< Label to show the min value. QPointer m_label_max; ///< Label to show the max value. QPointer m_label_definition; ///< Label to show the coefficient description. QPointer m_slider_value; ///< Editor to show the current value of the slider. }; /// Array containing the different structs to regulate the transformation matrix entries. std::array m_sliders; /// Contains a string identifying which axes [xyz] are displayed for rotation std::string m_rotation; /// Contains a string identifying which axes [xyz] are displayed for translation std::string m_translation; /// Range of translation sliders std::array m_translation_range {}; /// Range of rotation sliders std::array m_rotation_range {}; static constexpr std::string_view MATRIX_INOUT = "matrix"; data::ptr m_matrix {this, MATRIX_INOUT}; /// If true, only shows the slider, no label displayed data::property m_minimal {this, "minimal", false}; }; } // namespace sight::module::ui::qt::viz sight-25.1.0/module/ui/test/000077500000000000000000000000001503402212300155665ustar00rootroot00000000000000sight-25.1.0/module/ui/test/CMakeLists.txt000066400000000000000000000005741503402212300203340ustar00rootroot00000000000000sight_add_target(module_ui_test TYPE MODULE) set_target_properties(${SIGHT_TARGET} PROPERTIES AUTOMOC TRUE) find_package(Qt6 QUIET COMPONENTS Widgets Test REQUIRED) target_link_libraries(${SIGHT_TARGET} PUBLIC core ui_test Qt6::Widgets Qt6::Test) set_target_properties(${SIGHT_TARGET} PROPERTIES AUTOMOC TRUE) target_compile_definitions(${SIGHT_TARGET} PUBLIC "QT_NO_KEYWORDS") sight-25.1.0/module/ui/test/macro_saver.cpp000066400000000000000000002274461503402212300206120ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2021-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ // cspell:ignore ilwc strat htfr modif imvs #include "macro_saver.hpp" #include #include #include #include #include #include #include #include #include #include #include //------------------------------------------------------------------------------ const QEvent::Type MODEL_VIEW_SELECT_TYPE = static_cast(QEvent::registerEventType()); const QEvent::Type NUMBER_INPUT_MODIFICATION_TYPE = static_cast(QEvent::registerEventType()); //------------------------------------------------------------------------------ QString modifiers_to_string(Qt::KeyboardModifiers _modifiers) { QString res; static const std::map s_MODIFIERS_LIST { {"Shift", Qt::KeyboardModifier::ShiftModifier}, {"Control", Qt::KeyboardModifier::ControlModifier}, {"Alt", Qt::KeyboardModifier::AltModifier}, {"Meta", Qt::KeyboardModifier::MetaModifier}, {"Keypad", Qt::KeyboardModifier::KeypadModifier}, {"GroupSwitch", Qt::KeyboardModifier::GroupSwitchModifier} }; if(_modifiers == Qt::KeyboardModifier::NoModifier) { res = "Qt::KeyboardModifier::NoModifier"; } else { for(const auto& [name, value] : s_MODIFIERS_LIST) { if((_modifiers & value) != 0U) { if(!res.isEmpty()) { res += " | "; } res += "Qt::KeyboardModifier::" + name + "Modifier"; } } } return res; } //------------------------------------------------------------------------------ macro_interaction::macro_interaction( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers ) : receiver_id(_receiver_id), how_to_find_receiver(_how_to_find_receiver), modifiers(_modifiers) { } //------------------------------------------------------------------------------ pre_interaction::pre_interaction( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type ) : macro_interaction(_receiver_id, _how_to_find_receiver, _modifiers), type(_type) { } //------------------------------------------------------------------------------ post_interaction::post_interaction( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type ) : macro_interaction(_receiver_id, _how_to_find_receiver, _modifiers), type(_type) { } //------------------------------------------------------------------------------ interaction_mouse::interaction_mouse(QPoint _from, QPoint _to, Qt::MouseButton _button) : from(_from), to(_to), button(_button) { } interaction_mouse_wheel::interaction_mouse_wheel(QPoint _angle_delta, QPoint _pos) : angle_delta(_angle_delta), pos(_pos) { } //------------------------------------------------------------------------------ interaction_keyboard::interaction_keyboard(Qt::Key _key, QString _sequence) : key(_key), sequence(std::move(_sequence)) { } //------------------------------------------------------------------------------ bool interaction_keyboard::is_printable() const { return !sequence.isEmpty() && sequence[0].isPrint(); } //------------------------------------------------------------------------------ interaction_model_view_select::interaction_model_view_select(QString _name) : name(std::move(_name)) { } number_input_modification::number_input_modification(modification_t _type, double _number) : modif_type(_type), modif_number(_number) { } //------------------------------------------------------------------------------ pre_interaction_mouse::pre_interaction_mouse( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type, QPoint _from, QPoint _to, Qt::MouseButton _button ) : pre_interaction(_receiver_id, _how_to_find_receiver, _modifiers, _type), interaction_mouse(_from, _to, _button) { } pre_interaction_mouse_wheel::pre_interaction_mouse_wheel( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type, QPoint _angle_delta, QPoint _pos ) : pre_interaction(_receiver_id, _how_to_find_receiver, _modifiers, _type), interaction_mouse_wheel(_angle_delta, _pos) { } //------------------------------------------------------------------------------ pre_interaction_keyboard::pre_interaction_keyboard( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type, Qt::Key _key, const QString& _sequence ) : pre_interaction(_receiver_id, _how_to_find_receiver, _modifiers, _type), interaction_keyboard(_key, _sequence) { } //------------------------------------------------------------------------------ pre_interaction_model_view_select::pre_interaction_model_view_select( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type, const QString& _name ) : pre_interaction(_receiver_id, _how_to_find_receiver, _modifiers, _type), interaction_model_view_select(_name) { } pre_interaction_number_input_modification::pre_interaction_number_input_modification( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type, modification_t _modif_type, double _modif_number ) : pre_interaction(_receiver_id, _how_to_find_receiver, _modifiers, _type), number_input_modification(_modif_type, _modif_number) { } //------------------------------------------------------------------------------ post_interaction_mouse::post_interaction_mouse( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type, QPoint _from, QPoint _to, Qt::MouseButton _button ) : post_interaction(_receiver_id, _how_to_find_receiver, _modifiers, _type), interaction_mouse(_from, _to, _button) { } post_interaction_mouse_wheel::post_interaction_mouse_wheel( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type, QPoint _angle_delta, QPoint _pos ) : post_interaction(_receiver_id, _how_to_find_receiver, _modifiers, _type), interaction_mouse_wheel(_angle_delta, _pos) { } //------------------------------------------------------------------------------ post_interaction_keyboard::post_interaction_keyboard( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type, Qt::Key _key, const QString& _sequence ) : post_interaction(_receiver_id, _how_to_find_receiver, _modifiers, _type), interaction_keyboard(_key, _sequence) { } //------------------------------------------------------------------------------ post_interaction_model_view_select::post_interaction_model_view_select( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type, const QString& _name ) : post_interaction(_receiver_id, _how_to_find_receiver, _modifiers, _type), interaction_model_view_select(_name) { } post_interaction_number_input_modification::post_interaction_number_input_modification( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type, modification_t _modif_type, double _modif_number ) : post_interaction(_receiver_id, _how_to_find_receiver, _modifiers, _type), number_input_modification(_modif_type, _modif_number) { } interaction_helper_api::interaction_helper_api( intptr_t _receiver_id, const QVector& _how_to_find_receiver, QString _method_name, std::optional _select, QStringList _args ) : post_interaction(_receiver_id, _how_to_find_receiver, {}, helper_api), method_name(std::move(_method_name)), select(std::move(_select)), args(std::move(_args)) { } //------------------------------------------------------------------------------ bool macro_saver::eventFilter(QObject* _target, QEvent* _e) { if(_e->type() == QEvent::ChildAdded) { auto* ce = dynamic_cast(_e); infect(ce->child()); if(m_main_window == nullptr) { m_main_window = qApp->activeWindow(); } } else if(_e->type() == QEvent::MouseButtonPress || _e->type() == QEvent::MouseButtonRelease || _e->type() == QEvent::MouseMove || _e->type() == QEvent::MouseButtonDblClick || _e->type() == QEvent::Wheel || _e->type() == QEvent::KeyPress || _e->type() == QEvent::KeyRelease) { // Ignore the MouseMove event if no buttons are pressed, else we will get one thousand of events QWidget* w = qobject_cast(_target); if((_e->type() != QEvent::MouseMove || dynamic_cast(_e)->buttons() != Qt::MouseButton::NoButton) && !(w != nullptr && w->isWindow()) && _target != m_main_window) { std::unique_ptr interaction = create_interaction(_target, _e); if(interaction != nullptr) { m_interactions.emplace_back(std::move(interaction)); } } } return QObject::eventFilter(_target, _e); } //------------------------------------------------------------------------------ void macro_saver::infect(QObject* _o) { _o->installEventFilter(this); for(QObject* child : _o->children()) { infect(child); } } //------------------------------------------------------------------------------ QObject* macro_saver::findChild( QObject* _o, const QString& _type, const QString& _object_name, Qt::FindChildOptions _options ) { for(QObject* child : _o->children()) { if(child->inherits(_type.toLatin1()) && (_object_name == "" || child->objectName() == _object_name)) { return child; } if(_options == Qt::FindChildrenRecursively) { QObject* rec = findChild(child, _type, _object_name, _options); if(rec != nullptr) { return rec; } } } return nullptr; } //------------------------------------------------------------------------------ QObjectList macro_saver::findChildren( QObject* _o, const QString& _type, const QString& _object_name, Qt::FindChildOptions _options ) { QObjectList res; for(QObject* child : _o->children()) { if(child->inherits(_type.toLatin1()) && (_object_name == "" || child->objectName() == _object_name)) { res.append(child); } if(_options == Qt::FindChildrenRecursively) { res.append(findChildren(child, _type, _object_name, _options)); } } return res; } //------------------------------------------------------------------------------ static QObject* parent_of(const QObject* _obj) { QObject* parent = _obj->parent(); if(parent != nullptr && parent->objectName() == "qt_scrollarea_viewport") { parent = parent->parent(); } return parent; } //------------------------------------------------------------------------------ QVector macro_saver::find(QObject* _o) { assert(_o != nullptr); QWidget* w = qobject_cast(_o); QByteArray class_name = _o->metaObject()->className(); if(class_name == "QWidgetWindow" || class_name == "QColorPicker") // Those are private classes of Qt, we can't use // them as is. { class_name = "QWidget"; } if(_o == m_main_window) { return {{find_strategy_t::root, class_name, "", 0}}; } if(_o == qApp->activeModalWidget()) { return {{find_strategy_t::active_modal_widget, class_name, w->objectName().isEmpty() ? w->windowTitle() : w->objectName(), 0 } }; } if(_o->objectName() != "") { QVector res {{find_strategy_t::object_name, class_name, _o->objectName().toLatin1(), 0}}; if(QObjectList children; w != nullptr && (children = findChildren(w->window(), class_name, _o->objectName())).size() == 1 && children[0] == _o) { res.append(find(w->window())); } else if(QObjectList children2 = findChildren(m_main_window, class_name, _o->objectName()); children2.size() == 1 && children2[0] == _o) { res.append(find(m_main_window)); } else { QVector best_ancestor_strat; for(QObject* current_ancestor = parent_of(_o) ; current_ancestor != nullptr ; current_ancestor = parent_of(current_ancestor)) { if(QObjectList children3 = findChildren(current_ancestor, class_name, _o->objectName()); children3.size() == 1 && children3[0] == _o) { QVector current_ancestor_strat = find(current_ancestor); if(current_ancestor_strat.back().type != find_strategy_t::cant_be_found && (best_ancestor_strat.empty() || current_ancestor_strat.size() < best_ancestor_strat.size())) { best_ancestor_strat = current_ancestor_strat; } } } if(best_ancestor_strat.empty()) { res.append({find_strategy_t::cant_be_found, class_name, "", 0}); } else { res.append(best_ancestor_strat); } } return res; } if(findChildren(m_main_window, class_name).size() == 1) { return {{find_strategy_t::global_type, class_name, "", 0}}; } if(w != nullptr && w->actions().size() == 1) { QVector res {{find_strategy_t::action, class_name, "", 0}}; res.append(find(w->actions()[0])); return res; } if(parent_of(_o) == nullptr) { return {{find_strategy_t::cant_be_found, class_name, "", 0}}; } if(findChildren(parent_of(_o), class_name, "", Qt::FindDirectChildrenOnly).size() == 1) { QVector res {{find_strategy_t::local_type, class_name, "", 0}}; res.append(find(parent_of(_o))); return res; } // else int index = 0; for( ; index < parent_of(_o)->children().size() ; index++) { if(parent_of(_o)->children()[index] == _o) { break; } } QVector res {{find_strategy_t::nth_child, class_name, "", index}}; res.append(find(parent_of(_o))); return res; } //------------------------------------------------------------------------------ static QString select_to_code(const sight::ui::test::helper::selector& _select) { switch(_select.type()) { case sight::ui::test::helper::selector::type::from_main: return QString("\"%1\"").arg(std::get(_select.data()).c_str()); case sight::ui::test::helper::selector::type::from_dialog: return QString("selector::fromDialog(\"%1\")").arg(std::get(_select.data()).c_str()); case sight::ui::test::helper::selector::type::from_parent: { auto [parentName, childName] = std::get >(_select.data()); return QString(R"(selector::fromParent("%1", "%2"))").arg(parentName.c_str()).arg(childName.c_str()); } case sight::ui::test::helper::selector::type::from_current: return QString("selector::fromCurrent(\"%1\")").arg(std::get(_select.data()).c_str()); case sight::ui::test::helper::selector::type::current: return "Select::current()"; case sight::ui::test::helper::selector::type::dialog: return "Select::dialog()"; } SIGHT_ASSERT( "Invalid Select type", 0 ); return ""; } //------------------------------------------------------------------------------ [[nodiscard]] static std::pair, sight::ui::test::helper::selector> compute_select( QVector _how_to_find_receiver ) { sight::ui::test::helper::selector select = sight::ui::test::helper::selector::current(); if(_how_to_find_receiver.size() >= 2 && _how_to_find_receiver[0].type == find_strategy_t::object_name && _how_to_find_receiver[1].type == find_strategy_t::root) { select = sight::ui::test::helper::selector(_how_to_find_receiver[0].string.toStdString()); _how_to_find_receiver.pop_front(); _how_to_find_receiver.pop_front(); } else if(_how_to_find_receiver.size() >= 2 && _how_to_find_receiver[0].type == find_strategy_t::object_name && _how_to_find_receiver[1].type == find_strategy_t::active_modal_widget) { select = sight::ui::test::helper::selector::from_dialog(_how_to_find_receiver[0].string.toStdString()); _how_to_find_receiver.pop_front(); _how_to_find_receiver.pop_front(); } else if(_how_to_find_receiver.size() >= 2 && _how_to_find_receiver[0].type == find_strategy_t::object_name && _how_to_find_receiver[1].type == find_strategy_t::object_name && _how_to_find_receiver[2].type == find_strategy_t::root) { select = sight::ui::test::helper::selector::from_parent( _how_to_find_receiver[1].string.toStdString(), _how_to_find_receiver[0].string.toStdString() ); _how_to_find_receiver.pop_front(); _how_to_find_receiver.pop_front(); _how_to_find_receiver.pop_front(); } else if(!_how_to_find_receiver.empty() && _how_to_find_receiver[0].type == find_strategy_t::active_modal_widget) { select = sight::ui::test::helper::selector::dialog(); _how_to_find_receiver.pop_front(); } return {_how_to_find_receiver, select}; } //------------------------------------------------------------------------------ void macro_saver::save() { // Interactions preprocessing std::vector > pre_post_interactions; for(std::size_t i = 0 ; i < m_interactions.size() ; i++) { if(m_interactions[i]->type == QEvent::Type::MouseButtonPress) { const auto& im = static_cast(*m_interactions[i]); if(i + 1 == m_interactions.size()) { // This mouse button press event is the last event, it won't be ended with a release. Ignore it. continue; } if(m_interactions[i + 1]->type == QEvent::Type::MouseButtonRelease) { // A mouse button press immediately followed with a mouse button release is a simple mouse click. pre_post_interactions.emplace_back( std::make_unique( im.receiver_id, im.how_to_find_receiver, im.modifiers, mouse_click, im.from, im.to, im.button ) ); i++; } else if(m_interactions[i + 1]->type == QEvent::Type::MouseMove) { // A mouse button press followed with a mouse move and ended with a release is a mouse drag. i++; // Canonize the mouse drag for( ; i < m_interactions.size() && m_interactions[i]->type == QEvent::Type::MouseMove ; i++) { } if(i < m_interactions.size() && m_interactions[i]->type == QEvent::Type::MouseButtonRelease) { const auto& release = static_cast(*m_interactions[i]); pre_post_interactions.emplace_back( std::make_unique( im.receiver_id, im.how_to_find_receiver, im.modifiers, mouse_drag, im.from, release.to, im.button ) ); } } } else if(m_interactions[i]->type == QEvent::Type::MouseButtonDblClick) { if(pre_post_interactions.back()->type == mouse_double_click) { // This is a duplicated event, ignore it continue; } if(pre_post_interactions.back()->type == mouse_click) { // Ignore the click that generated this double click pre_post_interactions.pop_back(); } const auto& im = static_cast(*m_interactions[i]); pre_post_interactions.emplace_back( std::make_unique( im.receiver_id, im.how_to_find_receiver, im.modifiers, mouse_double_click, im.from, im.to, im.button ) ); } else if(m_interactions[i]->type == QEvent::Type::Wheel) { const auto& imw = static_cast(*m_interactions[i]); QPoint angle_delta = imw.angle_delta / 2; // Divide by two because events are duplicated // Compress all mouse wheel events into one while(i + 1 < m_interactions.size() && m_interactions[i + 1]->type == QEvent::Type::Wheel) { i++; angle_delta += static_cast(*m_interactions[i]).angle_delta / 2; } pre_post_interactions.emplace_back( std::make_unique( imw.receiver_id, imw.how_to_find_receiver, imw.modifiers, mouse_wheel, angle_delta, imw.pos ) ); } else if(m_interactions[i]->type == QEvent::Type::KeyPress) { const auto& ik = static_cast(*m_interactions[i].get()); if(ik.is_printable()) { // If the key is printable, we will attempt to "compress" it with the following key events if they are // printable as well, to make the resulting code more readable. QString sequence; const pre_interaction_keyboard* press = nullptr; while(i < m_interactions.size() && m_interactions[i]->type == QEvent::Type::KeyPress && (press = static_cast(m_interactions[i].get())) != nullptr && press->is_printable()) { sequence += press->sequence; i++; for( ; i < m_interactions.size() && m_interactions[i]->type == QEvent::Type::KeyRelease ; i++) { } } i--; if(sequence.size() > 0) { pre_post_interactions.emplace_back( std::make_unique( ik.receiver_id, ik.how_to_find_receiver, ik.modifiers, (sequence.size() > 1 ? keyboard_sequence : keyboard_click), ik.key, sequence ) ); } } else { // If the key isn't printable, then it's a control key such as BackSpace. We can't compress it with // other key events. // Avoid duplicated events for( ; i < m_interactions.size() && m_interactions[i]->type == QEvent::Type::KeyPress ; i++) { } if(i < m_interactions.size() && m_interactions[i]->type == QEvent::Type::KeyRelease) { pre_post_interactions.emplace_back( std::make_unique( ik.receiver_id, ik.how_to_find_receiver, ik.modifiers, keyboard_click, ik.key, ik.sequence ) ); } } } else if(m_interactions[i]->type == MODEL_VIEW_SELECT_TYPE) { const auto& ilwc = static_cast(*m_interactions[i]); pre_post_interactions.emplace_back( std::make_unique( ilwc.receiver_id, ilwc.how_to_find_receiver, ilwc.modifiers, model_view_select, ilwc.name ) ); } else if(m_interactions[i]->type == NUMBER_INPUT_MODIFICATION_TYPE) { const auto& nim = static_cast(*m_interactions[i]); pre_post_interactions.emplace_back( std::make_unique( nim.receiver_id, nim.how_to_find_receiver, nim.modifiers, number_input_modification, nim.modif_type, nim.modif_number ) ); } } std::vector > post_interactions; for(std::size_t i = 0 ; i < pre_post_interactions.size() ; i++) { if(pre_post_interactions[i]->type == mouse_click && (pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QPushButton" || pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QToolButton")) { if(pre_post_interactions[i]->how_to_find_receiver.back().string == "preferences_configuration") { // Special case: preferences Configuration pop up close button if(interaction_helper_api* iha = nullptr; post_interactions.back()->type == helper_api && (iha = static_cast(post_interactions.back().get()))->method_name == "PreferencesConfiguration::fill") { iha->args[0] += "}"; } else { post_interactions.emplace_back( std::make_unique( pre_post_interactions[i]-> receiver_id, QVector {}, "PreferencesConfiguration", std::nullopt, QStringList {"{}" }) ); } } else { QVector how_to_find_receiver = pre_post_interactions[i]->how_to_find_receiver; sight::ui::test::helper::selector select = sight::ui::test::helper::selector::current(); if(how_to_find_receiver.front().type == find_strategy_t::action) { // helper::Button::push already handles the "action" case. how_to_find_receiver.pop_front(); } std::tie(how_to_find_receiver, select) = compute_select(how_to_find_receiver); post_interactions.emplace_back( std::make_unique( pre_post_interactions[i]->receiver_id, how_to_find_receiver, "Button::push", select ) ); } } else if(pre_post_interactions[i]->type == mouse_click && pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QCheckBox") { auto [howToFindReceiver, select] = compute_select(pre_post_interactions[i]->how_to_find_receiver); post_interactions.emplace_back( std::make_unique( pre_post_interactions[i]->receiver_id, howToFindReceiver, "CheckBox::toggle", select ) ); } else if(pre_post_interactions[i]->type == model_view_select && (pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QCheckBox" || pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QComboBox") && pre_post_interactions[i]->how_to_find_receiver.back().string == "preferences_configuration") { const auto& imvs = static_cast(*pre_post_interactions[i]); QString arg = QString(R"({"%1", "%2"})").arg(imvs.how_to_find_receiver[0].string).arg(imvs.name); if(interaction_helper_api* iha = nullptr; post_interactions.back()->type == helper_api && (iha = static_cast(post_interactions.back().get()))->method_name == "PreferencesConfiguration::fill") { if(!iha->args[0].contains( QString("\"%1\"").arg( pre_post_interactions[i]->how_to_find_receiver[0].string ) )) { iha->args[0] += QString(", " + arg); } } else { post_interactions.emplace_back( std::make_unique( pre_post_interactions[i]->receiver_id, QVector {}, "PreferencesConfiguration::fill", std::nullopt, QStringList {"{" + arg }) ); } } else if(pre_post_interactions[i]->type == model_view_select && pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QComboBox") { if(!post_interactions.empty() && post_interactions.back()->type == mouse_drag) { // It seems mouse drags are always duplicated with comboboxes post_interactions.pop_back(); } else if(post_interactions.size() >= 2 && post_interactions.back()->type == mouse_click && post_interactions[post_interactions.size() - 2]->type == mouse_click) { // It seems mouse clicks are always duplicated with comboboxes post_interactions.pop_back(); post_interactions.pop_back(); } else if(!post_interactions.empty() && post_interactions.back()->type == helper_api) { // It seems mouse clicks are always duplicated with comboboxes continue; } const auto& imvs = static_cast(*pre_post_interactions[i]); auto [howToFindReceiver, select] = compute_select(imvs.how_to_find_receiver); post_interactions.emplace_back( std::make_unique( imvs.receiver_id, howToFindReceiver, "ComboBox::select", select, QStringList {QString("\"%1\"").arg(imvs.name) }) ); } else if((pre_post_interactions[i]->type == keyboard_click || pre_post_interactions[i]->type == keyboard_sequence) && pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QLineEdit") { const auto& ik = static_cast(*pre_post_interactions[i]); if(pre_post_interactions[i]->how_to_find_receiver.back().type == find_strategy_t::active_modal_widget) { // Compress all keyboard sequence/click in one QString sequence = (ik.is_printable() ? ik.sequence : ""); for( ; i + 1 < pre_post_interactions.size() && pre_post_interactions[i + 1]->receiver_id == ik.receiver_id && (pre_post_interactions[i + 1]->type == keyboard_click || pre_post_interactions[i + 1]->type == keyboard_sequence) ; i++) { const auto& ik2 = static_cast(*pre_post_interactions[i + 1]); if(ik2.key == Qt::Key::Key_Backspace) { sequence.resize(sequence.size() - 1); } else if(ik2.key == Qt::Key::Key_Return) { break; } else if(ik2.is_printable()) { sequence += ik2.sequence; } // Ignore other keys } if(pre_post_interactions[i]->how_to_find_receiver.back().class_name == "QFileDialog") { post_interactions.emplace_back( std::make_unique( ik.receiver_id, QVector {}, "FileDialog::fill", std::nullopt, QStringList {QString("\"%1\"").arg(sequence) }) ); if(i + 1 < pre_post_interactions.size() && pre_post_interactions[i + 1]->receiver_id == ik.receiver_id && pre_post_interactions[i + 1]->type == keyboard_click && static_cast(*pre_post_interactions[i + 1]).key == Qt::Key::Key_Return) { // Ignore the Enter key press to confirm the file dialog, as it is already handled by // helper::FieldDialog::fill i++; } } else if(pre_post_interactions[i]->how_to_find_receiver.back().string == "preferences_configuration") { QString arg = QString(R"({"%1", "%2"})").arg(pre_post_interactions[i]->how_to_find_receiver[0].string).arg( sequence ); if(interaction_helper_api* iha = nullptr; post_interactions.back()->type == helper_api && (iha = static_cast(post_interactions.back().get()))->method_name == "PreferencesConfiguration::fill") { if(!iha->args[0].contains( QString("\"%1\"").arg( pre_post_interactions[i]->how_to_find_receiver[0]. string ) )) { iha->args[0] += QString(", " + arg); } } else { post_interactions.emplace_back( std::make_unique( pre_post_interactions[i]-> receiver_id, QVector {}, "PreferencesConfiguration::fill", std::nullopt, QStringList {"{" + arg }) ); } } } else { auto [howToFindReceiver, select] = compute_select(ik.how_to_find_receiver); post_interactions.emplace_back( std::make_unique( ik.receiver_id, howToFindReceiver, "field::fill", select, QStringList {QString("\"%1\"").arg(ik.sequence) }) ); } } else if(pre_post_interactions[i]->type == model_view_select && pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QListWidget") { const auto& ilwc = static_cast(*pre_post_interactions[i]); if(pre_post_interactions[i]->how_to_find_receiver.back().type == find_strategy_t::active_modal_widget && pre_post_interactions[i]->how_to_find_receiver.back().string == "selector") { if(post_interactions.back()->type == helper_api && static_cast(*post_interactions.back()).method_name == "selector::select" && post_interactions.back()->receiver_id == pre_post_interactions[i]->receiver_id) { // The user selected an item then selected another one; take only the last choice post_interactions.pop_back(); } post_interactions.emplace_back( std::make_unique( ilwc.receiver_id, QVector {}, "selector::select", std::nullopt, QStringList {QString("\"%1\"").arg(ilwc.name) }) ); if(i + 1 < pre_post_interactions.size() && pre_post_interactions[i + 1]->type == mouse_click && pre_post_interactions[i + 1]->how_to_find_receiver[0].class_name == "QPushButton" && pre_post_interactions[i + 1]->how_to_find_receiver[0].string == "Ok" && pre_post_interactions[i + 1]->how_to_find_receiver[1].type == find_strategy_t::active_modal_widget) { // Ignore the eventual "Ok" button click as it is handled by helper::selector::select i++; } if(i + 1 < pre_post_interactions.size() && pre_post_interactions[i + 1]->type == mouse_double_click && pre_post_interactions[i + 1]->how_to_find_receiver[0].string == "SelectorDialogWindow") { // It seems there might be a duplicated double click event i++; } } else { auto [howToFindReceiver, select] = compute_select(ilwc.how_to_find_receiver); post_interactions.emplace_back( std::make_unique( ilwc.receiver_id, howToFindReceiver, "ListWidget::setCurrentText", select, QStringList {QString("\"%1\"").arg(ilwc.name) }) ); } } else if(pre_post_interactions[i]->type == number_input_modification && pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QSlider") { if(!post_interactions.empty() && (post_interactions.back()->type == mouse_drag || post_interactions.back()->type == mouse_click || post_interactions.back()->type == mouse_double_click)) { // It seems mouse drags are always duplicated with sliders post_interactions.pop_back(); } else if(!post_interactions.empty() && post_interactions[post_interactions.size() - 1]->type == helper_api) { // It seems mouse drags are always duplicated with sliders continue; } const auto& nim = static_cast(*pre_post_interactions[i]); QVector how_to_find_receiver = nim.how_to_find_receiver; sight::ui::test::helper::selector select = sight::ui::test::helper::selector::current(); if(how_to_find_receiver.front().type == find_strategy_t::local_type && how_to_find_receiver.front().class_name == "QSlider") { // Finding the actual QSlider is already handled in helper::Slider::set how_to_find_receiver.pop_front(); } std::tie(how_to_find_receiver, select) = compute_select(how_to_find_receiver); if(nim.modif_type == modification_t::set) { post_interactions.emplace_back( std::make_unique( nim.receiver_id, how_to_find_receiver, "Slider::set", select, QStringList {QString::number(nim.modif_number) }) ); } else { QString position; if(nim.modif_type == modification_t::decrement) { position = "helper::Slider::Position::LEFT"; } else if(nim.modif_type == modification_t::increment) { position = "helper::Slider::Position::RIGHT"; } if(interaction_helper_api* previous_interaction = nullptr; !post_interactions.empty() && post_interactions.back()->type == helper_api && post_interactions.back()->receiver_id == nim.receiver_id && (previous_interaction = static_cast(post_interactions.back().get()))->method_name == "Slider::move" && previous_interaction->args[0] == position) { // If the previous interaction is the same, we compress it with the current one if(previous_interaction->args.size() == 1) { previous_interaction->args.push_back("2"); } else { previous_interaction->args[1] = QString::number(previous_interaction->args[1].toFloat() + 1); } } else { post_interactions.emplace_back( std::make_unique( nim.receiver_id, how_to_find_receiver, "Slider::move", select, QStringList {position }) ); } } } else if(pre_post_interactions[i]->type == number_input_modification && (pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QSpinBox" || pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QDoubleSpinBox")) { if(!post_interactions.empty() && post_interactions.back()->type == mouse_click) { // It seems mouse clicks are always duplicated with spinboxes post_interactions.pop_back(); } else if(!post_interactions.empty() && post_interactions.back()->type == helper_api) { // It seems mouse clicks are always duplicated with spinboxes continue; } const auto& nim = static_cast(*pre_post_interactions[i]); auto [howToFindReceiver, select] = compute_select(nim.how_to_find_receiver); if(nim.modif_type == modification_t::set) { assert(0); } else { QString method_name; if(nim.modif_type == modification_t::decrement) { method_name = "SpinBox::decrement"; } else if(nim.modif_type == modification_t::increment) { method_name = "SpinBox::increment"; } if(interaction_helper_api* previous_interaction = nullptr; !post_interactions.empty() && post_interactions.back()->type == helper_api && post_interactions.back()->receiver_id == nim.receiver_id && (previous_interaction = static_cast(post_interactions.back().get()))->method_name == method_name) { if(previous_interaction->args.empty()) { previous_interaction->args.push_back("2"); } else { previous_interaction->args[0] = QString::number(previous_interaction->args[0].toFloat() + 1); } } else { post_interactions.emplace_back( std::make_unique( nim.receiver_id, howToFindReceiver, method_name, select ) ); } } } else if(pre_post_interactions[i]->type == model_view_select && pre_post_interactions[i]->how_to_find_receiver[0].class_name == "QColorDialog") { // Ignore all interactions inside the color dialog while(std::ranges::any_of( post_interactions.back()->how_to_find_receiver, [](const find_strategy& _s) { return _s.class_name == "QColorDialog"; })) { post_interactions.pop_back(); } if((post_interactions.back()->type == mouse_click || post_interactions.back()->type == helper_api) && !post_interactions.back()->how_to_find_receiver.empty() && post_interactions.back()->how_to_find_receiver[0].class_name == "QPushButton") { // Get rid of duplicate button click post_interactions.pop_back(); } QVector how_to_find_receiver; std::optional select; if(interaction_helper_api* iha = nullptr; post_interactions.back()->type == helper_api && (iha = static_cast(post_interactions.back().get()))->method_name == "Button::push") { // helper::ColorParameter::select already clicks on the Color button how_to_find_receiver = iha->how_to_find_receiver; select = iha->select; post_interactions.pop_back(); } else { // It's a duplicate, ignore it continue; } post_interactions.emplace_back( std::make_unique( pre_post_interactions[i]->receiver_id, how_to_find_receiver, "ColorParameter::select", select, QStringList {QString("\"%1\"").arg( static_cast(*pre_post_interactions [i]).name ) }) ); } else if(pre_post_interactions[i]->how_to_find_receiver.back().string == "preferences_configuration" || std::ranges::any_of( pre_post_interactions[i]->how_to_find_receiver, [](const find_strategy& _f) { return _f.string == "preferences_configurationWindow"; })) { // Ignore other interactions in PreferencesConfiguration } else { post_interactions.emplace_back(std::move(pre_post_interactions[i])); } } QStringList dependencies; bool use_helpers = false; bool use_select_constructor = false; for(const std::unique_ptr& interaction : post_interactions) { for(const find_strategy& strategy : interaction->how_to_find_receiver) { if(strategy.type == find_strategy_t::global_type || strategy.type == find_strategy_t::local_type) { dependencies.append(strategy.class_name); } else if(strategy.type == find_strategy_t::action) { dependencies.append("QAction"); } else if(strategy.type == find_strategy_t::active_modal_widget) { dependencies.append("ui/test/helper/dialog.hpp"); dependencies.append(strategy.class_name); } } if(interaction->type == mouse_click || interaction->type == mouse_drag) { dependencies.append("QPoint"); } else if(interaction->type == model_view_select) { dependencies.append("QListWidget"); } if(interaction->type == helper_api) { use_helpers = true; const auto& iha = static_cast(*interaction); if(iha.select && iha.select.value().type() != sight::ui::test::helper::selector::type::from_main) { use_select_constructor = true; } dependencies.append(QString("ui/test/helper/%1.hpp").arg(iha.method_name.split("::")[0])); } } dependencies.sort(); dependencies.removeDuplicates(); static constexpr auto s_WRITE = [](QFile& _f, int _indentation, const QString& _line) { if(_f.write( QByteArray( _indentation, ' ' ) + _line.toLatin1() + '\n' ) < _line.size()) { throw std::runtime_error("Couldn't write data in the file."); } }; QFile cpp("gui_test.cpp"); cpp.open(QIODevice::WriteOnly); s_WRITE(cpp, 0, "#include \"gui_test.hpp\""); s_WRITE(cpp, 0, ""); s_WRITE(cpp, 0, "#include "); s_WRITE(cpp, 0, "#include "); s_WRITE(cpp, 0, ""); s_WRITE(cpp, 0, "#include "); s_WRITE(cpp, 0, ""); for(const QString& dependency : dependencies) { s_WRITE(cpp, 0, QString("#include <%1>").arg(dependency)); } s_WRITE(cpp, 0, ""); s_WRITE(cpp, 0, "CPPUNIT_TEST_SUITE_REGISTRATION(gui_test);"); s_WRITE(cpp, 0, ""); s_WRITE(cpp, 0, "//------------------------------------------------------------------------------"); s_WRITE(cpp, 0, ""); s_WRITE(cpp, 0, "std::filesystem::path gui_test::getProfilePath()"); s_WRITE(cpp, 0, "{"); s_WRITE(cpp, 4, "const std::filesystem::path cwd = std::filesystem::path("); s_WRITE(cpp, 8, "boost::dll::this_line_location().parent_path().parent_path().string()"); s_WRITE(cpp, 4, ");"); std::filesystem::path absolute_profile_path(sight::core::runtime::get_current_profile()->file_path()); QString profile_path(QString::fromStdString(std::filesystem::relative(absolute_profile_path).string())); s_WRITE(cpp, 4, QString("return cwd / \"%1\";").arg(profile_path).toLatin1()); s_WRITE(cpp, 0, "}"); s_WRITE(cpp, 0, ""); s_WRITE(cpp, 0, "void gui_test::test()"); s_WRITE(cpp, 0, "{"); s_WRITE(cpp, 4, "start(\"gui_test\","); s_WRITE(cpp, 8, "[](sight::ui::testCore::Tester& tester)"); s_WRITE(cpp, 4, "{"); if(use_helpers) { s_WRITE(cpp, 8, "namespace helper = sight::ui::testCore::helper;"); } if(use_select_constructor) { s_WRITE(cpp, 8, "using Select = helper::Select;"); } s_WRITE(cpp, 0, ""); intptr_t current_item_id = 0; for(const std::unique_ptr& interaction : post_interactions) { if(interaction->receiver_id != current_item_id) { // The receiver of this interaction isn't the current item: We must take it. if(interaction->how_to_find_receiver.size() >= 2 && interaction->how_to_find_receiver.back().type == find_strategy_t::root && interaction->how_to_find_receiver[interaction->how_to_find_receiver.size() - 2].type == find_strategy_t::object_name) { s_WRITE( cpp, 8, QString(R"(tester.take("%1", "%1");)").arg( interaction->how_to_find_receiver[interaction-> how_to_find_receiver .size() - 2].string ) ); interaction->how_to_find_receiver.pop_back(); interaction->how_to_find_receiver.pop_back(); } for(auto i = interaction->how_to_find_receiver.size() - 1 ; i >= 0 ; i--) { find_strategy strat = interaction->how_to_find_receiver[i]; if(strat.type == find_strategy_t::root) { s_WRITE(cpp, 8, "tester.take(\"main window\", tester.getMainWindow());"); } else if(strat.type == find_strategy_t::active_modal_widget) { s_WRITE( cpp, 8, QString("helper::Dialog::take<%1*>(tester, \"%2\");").arg(strat.class_name).arg(strat.string) ); } else if(strat.type == find_strategy_t::object_name) { s_WRITE(cpp, 8, "tester.yields("); s_WRITE(cpp, 12, QString("\"%1\",").arg(strat.string)); s_WRITE( cpp, 12, QString("[](QObject* old) -> QObject* {return old->findChild(\"%1\");}") .arg(strat.string) ); s_WRITE(cpp, 8, ");"); } else if(strat.type == find_strategy_t::global_type) { s_WRITE(cpp, 8, QString("tester.take%1(").arg(strat.string.isEmpty() ? "" : "")); s_WRITE(cpp, 12, QString("\"%1\",").arg(strat.class_name)); s_WRITE(cpp, 12, "[&tester]() -> QObject*"); s_WRITE(cpp, 8, "{"); s_WRITE( cpp, 12, QString("return tester.getMainWindow()->findChild<%1*>();") .arg(strat.class_name) ); s_WRITE(cpp, 8, "});"); } else if(strat.type == find_strategy_t::action) { s_WRITE(cpp, 8, "tester.yields("); s_WRITE(cpp, 12, QString("\"%1\",").arg(strat.class_name)); s_WRITE(cpp, 12, "[](QObject* old) -> QObject*"); s_WRITE(cpp, 8, "{"); s_WRITE( cpp, 12, "return sight::ui::testCore::Tester::getWidgetFromAction(qobject_cast(old));" ); s_WRITE(cpp, 8, "});"); } else if(strat.type == find_strategy_t::local_type) { s_WRITE(cpp, 8, "tester.yields("); s_WRITE(cpp, 12, QString("\"%1\",").arg(strat.class_name)); s_WRITE(cpp, 12, "[](QObject* old) -> QObject*"); s_WRITE(cpp, 8, "{"); s_WRITE(cpp, 12, QString("return old->findChild<%1*>();").arg(strat.class_name)); s_WRITE(cpp, 8, "});"); } else if(strat.type == find_strategy_t::nth_child) { s_WRITE(cpp, 8, "tester.yields("); s_WRITE(cpp, 12, QString("\"%1\",").arg(strat.class_name)); s_WRITE(cpp, 12, "[](QObject* old) -> QObject*"); s_WRITE(cpp, 8, "{"); s_WRITE(cpp, 12, QString("return old->children()[%1];").arg(strat.integer)); s_WRITE(cpp, 8, "});"); } else if(strat.type == find_strategy_t::cant_be_found) { s_WRITE( cpp, 8, QString("// TODO: Find the %1. MacroSaver wasn't able to find it automatically.") .arg(strat.class_name) ); } } current_item_id = interaction->receiver_id; } const static QMetaEnum s_MOUSE_BUTTON = QMetaEnum::fromType(); const static QMetaEnum s_KEY = QMetaEnum::fromType(); if(interaction->type == mouse_click) { const auto& im = static_cast(*interaction); s_WRITE( cpp, 8, QString( "tester.interact(std::make_unique(Qt::MouseButton::%1, %2, %3));" ) .arg(s_MOUSE_BUTTON.valueToKey(static_cast(im.button))).arg(modifiers_to_string(im.modifiers)) .arg(QTest::toString(im.from)) ); } else if(interaction->type == mouse_double_click) { const auto& im = static_cast(*interaction); s_WRITE( cpp, 8, QString( "tester.interact(std::make_unique(Qt::MouseButton::%1, %2, %3));" ) .arg(s_MOUSE_BUTTON.valueToKey(static_cast(im.button))).arg(modifiers_to_string(im.modifiers)) .arg(QTest::toString(im.from)) ); } else if(interaction->type == mouse_drag) { const auto& im = static_cast(*interaction); s_WRITE( cpp, 8, QString( "tester.interact(std::make_unique(%1, %2, Qt::MouseButton::%3, %4));" ) .arg(QTest::toString(im.from)).arg(QTest::toString(im.to)) .arg(s_MOUSE_BUTTON.valueToKey(static_cast(im.button))).arg(modifiers_to_string(im.modifiers)) ); } else if(interaction->type == mouse_wheel) { const auto& im = static_cast(*interaction); s_WRITE( cpp, 8, QString( "tester.interact(std::make_unique(%1, %2, %3));" ) .arg(QTest::toString(im.angle_delta)).arg(modifiers_to_string(im.modifiers)).arg( QTest::toString( im.pos ) ) ); } else if(interaction->type == keyboard_click) { const auto& ik = static_cast(*interaction); s_WRITE( cpp, 8, QString("tester.interact(std::make_unique(Qt::Key::%1, %2));") .arg(s_KEY.valueToKey(static_cast(ik.key))).arg(modifiers_to_string(ik.modifiers)) ); } else if(interaction->type == keyboard_sequence) { const auto& ik = static_cast(*interaction); s_WRITE( cpp, 8, QString("tester.interact(std::make_unique(\"%1\", %2));") .arg(ik.sequence).arg(modifiers_to_string(ik.modifiers)) ); } else if(interaction->type == model_view_select) { const auto& ilwc = static_cast(*interaction); s_WRITE(cpp, 8, "tester.do_something_asynchronously("); s_WRITE(cpp, 12, "[](QListWidget* obj)"); s_WRITE(cpp, 8, "{"); s_WRITE( cpp, 12, QString("obj->setCurrentItem(obj->findItems(\"%1\", Qt::MatchFlag::MatchExactly)[0]);") .arg(ilwc.name) ); s_WRITE(cpp, 8, "});"); } else if(interaction->type == number_input_modification) { assert(0); } else if(interaction->type == helper_api) { const auto& ha = static_cast(*interaction); s_WRITE( cpp, 8, QString("helper::%1(tester%2%3);").arg(ha.method_name).arg( ha.select ? ", " + select_to_code(*ha.select) : "" ).arg( ha.args.empty() ? "" : ", " + ha.args.join(", ") ) ); } } s_WRITE(cpp, 4, "});"); s_WRITE(cpp, 0, "}"); QFile hpp("gui_test.hpp"); hpp.open(QIODevice::WriteOnly); s_WRITE(hpp, 0, "#pragma once"); s_WRITE(hpp, 0, ""); s_WRITE(hpp, 0, "#include "); s_WRITE(hpp, 0, ""); s_WRITE(hpp, 0, "#include "); s_WRITE(hpp, 0, ""); s_WRITE(hpp, 0, "#include "); s_WRITE(hpp, 0, ""); s_WRITE(hpp, 0, "class gui_test : public sight::ui::testCore::test"); s_WRITE(hpp, 0, "{"); s_WRITE(hpp, 0, "CPPUNIT_TEST_SUITE(gui_test);"); s_WRITE(hpp, 0, "CPPUNIT_TEST(test);"); s_WRITE(hpp, 0, "CPPUNIT_TEST_SUITE_END();"); s_WRITE(hpp, 0, ""); s_WRITE(hpp, 0, "public:"); s_WRITE(hpp, 0, ""); s_WRITE(hpp, 4, "std::filesystem::path getProfilePath() override;"); s_WRITE(hpp, 0, ""); s_WRITE(hpp, 4, "void test();"); s_WRITE(hpp, 0, "};"); } //------------------------------------------------------------------------------ template static T* find_ancestor(QObject* _child) { for(QObject* current_ancestor = _child ; current_ancestor != nullptr ; current_ancestor = current_ancestor->parent()) { if(auto* ancestor = qobject_cast(current_ancestor)) { return ancestor; } } return nullptr; } //------------------------------------------------------------------------------ std::unique_ptr macro_saver::create_interaction(QObject* _target, QEvent* _e) { switch(_e->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseMove: case QEvent::MouseButtonDblClick: { auto* me = static_cast(_e); if(auto* lw = qobject_cast(_target->parent())) { if(_e->type() == QEvent::MouseButtonPress) { return std::make_unique( reinterpret_cast(_target), find(lw), Qt::KeyboardModifier::NoModifier, MODEL_VIEW_SELECT_TYPE, lw->itemAt(me->pos())->text() ); } return nullptr; } auto* lv = qobject_cast(_target->parent()); if(auto* cb = find_ancestor(_target); cb != nullptr && lv != nullptr) { if(_e->type() == QEvent::MouseButtonRelease) { return std::make_unique( reinterpret_cast(cb), find(cb), Qt::KeyboardModifier::NoModifier, MODEL_VIEW_SELECT_TYPE, cb->model()->data(lv->indexAt(me->pos())).toString() ); } return nullptr; } if(auto* slider = qobject_cast(_target)) { if(_e->type() == QEvent::MouseButtonRelease) { bool drag_in_progress = m_drag_in_progress; m_drag_in_progress = false; QRect decrement_zone = slider->rect(); QRect increment_zone = slider->rect(); if(slider->orientation() == Qt::Horizontal) { decrement_zone.setWidth(static_cast(decrement_zone.width() * 0.25)); increment_zone.setX(static_cast(increment_zone.width() * 0.75)); } else if(slider->orientation() == Qt::Vertical) { decrement_zone.setHeight(static_cast(decrement_zone.height() * 0.25)); increment_zone.setY(static_cast(decrement_zone.height() * 0.75)); } if(!drag_in_progress && (decrement_zone.contains(me->pos()) || increment_zone.contains(me->pos()))) { if(decrement_zone.contains(me->pos())) { return std::make_unique( reinterpret_cast( _target), find(slider), me->modifiers(), NUMBER_INPUT_MODIFICATION_TYPE, modification_t::decrement, 1 ); } if(increment_zone.contains(me->pos())) { return std::make_unique( reinterpret_cast( _target), find(slider), me->modifiers(), NUMBER_INPUT_MODIFICATION_TYPE, modification_t::increment, 1 ); } } else { return std::make_unique( reinterpret_cast(_target), find(slider), me->modifiers(), NUMBER_INPUT_MODIFICATION_TYPE, modification_t::set, slider->value() ); } } else if(_e->type() == QEvent::MouseMove) { m_drag_in_progress = true; } else if(_e->type() == QEvent::MouseButtonPress) { m_drag_in_progress = false; } return nullptr; } if(auto* spinbox = qobject_cast(_target)) { if(_e->type() == QEvent::MouseButtonRelease) { QRect decrement_zone = spinbox->rect(); QRect increment_zone = spinbox->rect(); decrement_zone.setWidth(static_cast(decrement_zone.width() * 0.25)); increment_zone.setX(static_cast(increment_zone.width() * 0.75)); if(decrement_zone.contains(me->pos())) { return std::make_unique( reinterpret_cast(_target), find(spinbox), me->modifiers(), NUMBER_INPUT_MODIFICATION_TYPE, modification_t::decrement, 1 ); } if(increment_zone.contains(me->pos())) { return std::make_unique( reinterpret_cast(_target), find(spinbox), me->modifiers(), NUMBER_INPUT_MODIFICATION_TYPE, modification_t::increment, 1 ); } } return nullptr; } if(auto* button = qobject_cast(_target); _e->type() == QEvent::MouseButtonRelease && button != nullptr && qobject_cast( button->window() ) != nullptr) { return std::make_unique( reinterpret_cast(button->window()), find(button->window()), me->modifiers(), MODEL_VIEW_SELECT_TYPE, button->window()->findChild()->text() ); } if(auto* checkbox = qobject_cast( _target ); checkbox != nullptr && checkbox->window()->objectName() == "preferences_configuration") { // Exception for preferences Configuration, in order to add it in helper::PreferencesConfiguration::fill // map if(_e->type() == QEvent::MouseButtonRelease) { return std::make_unique( reinterpret_cast(checkbox), find(checkbox), me->modifiers(), MODEL_VIEW_SELECT_TYPE, checkbox->isChecked() ? "true" : "false" ); } return nullptr; } if(_e->type() == QEvent::MouseButtonDblClick && _target->objectName() == m_main_window->windowTitle() + "Window") { return nullptr; } return std::make_unique( reinterpret_cast(_target), find(_target), me->modifiers(), _e->type(), me->pos(), me->pos(), me->button() ); } case QEvent::KeyPress: case QEvent::KeyRelease: { auto* ke = static_cast(_e); QObject* receiver = nullptr; if(qApp->focusWidget() == nullptr) { receiver = _target; } else { receiver = qApp->focusWidget(); } if(ke->key() == Qt::Key_Shift || ke->key() == Qt::Key_Control || ke->key() == Qt::Key_Meta || ke->key() == Qt::Key_Alt || ke->key() == Qt::Key_AltGr) { // Ignore modifier keys return nullptr; } if(_e->type() == QEvent::KeyPress && !m_interactions.empty() && m_interactions.back()->type == QEvent::KeyPress && static_cast(*m_interactions.back()).key == ke->key() && !ke->isAutoRepeat()) { // Ignore duplicates (if the same key is pressed multiple times in a row without any releases, it must // be an auto repeat, else it means the event was duplicated). return nullptr; } return std::make_unique( reinterpret_cast(receiver), find(receiver), ke->modifiers(), _e->type(), static_cast(ke->key()), ke->text() ); } case QEvent::Wheel: { auto* we = static_cast(_e); return std::make_unique( reinterpret_cast(_target), find(_target), we->modifiers(), _e->type(), we->angleDelta(), we->position().toPoint() ); } default: return nullptr; } } sight-25.1.0/module/ui/test/macro_saver.hpp000066400000000000000000000255201503402212300206040ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2021-2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ // cspell:ignore modif #pragma once #include #include #include #include #include #include #include #include #include #include #include /// An enumeration which represents the type of a user interaction enum interaction_t { mouse_click, mouse_double_click, mouse_drag, mouse_wheel, keyboard_click, keyboard_sequence, model_view_select, number_input_modification, helper_api }; /// An enumeration which represents how to find a widget, @see FindStrategy enum class find_strategy_t { root, // The object to be found is root active_modal_widget, // The object to be found is the active modal widget object_name, // Find an object using its object name global_type, // Find an object using its type from the root action, // Find an object via of its action, requires to find the action local_type, // Find an object using its type from the parent, requires to find the parent nth_child, // Find an object using its index in its parent children list, requires to find the parent cant_be_found // Error: the object can't be found }; enum class modification_t { increment, decrement, set }; /// A structure which represents a strategy to find a specific widget struct find_strategy { find_strategy_t type; QString class_name; QString string; int integer; }; /// The base class for all interaction struct macro_interaction { macro_interaction( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers ); intptr_t receiver_id; QVector how_to_find_receiver; Qt::KeyboardModifiers modifiers; }; /// An interaction before the preprocessing (that is, directly caught from Qt) struct pre_interaction : public macro_interaction { pre_interaction( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type ); QEvent::Type type; }; /// An interaction after the preprocessing (ready to be translated into C++) struct post_interaction : public macro_interaction { post_interaction( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type ); interaction_t type; }; /// A mouse interaction (mouse click, mouse drag...) struct interaction_mouse { interaction_mouse(QPoint _from, QPoint _to, Qt::MouseButton _button); QPoint from; QPoint to; Qt::MouseButton button; }; /// A mouse wheel interaction struct interaction_mouse_wheel { interaction_mouse_wheel(QPoint _angle_delta, QPoint _pos); QPoint angle_delta; QPoint pos; }; /// A keyboard interaction (key press, key sequence...) struct interaction_keyboard { interaction_keyboard(Qt::Key _key, QString _sequence); [[nodiscard]] bool is_printable() const; Qt::Key key; QString sequence; }; /// An interaction when selecting an item on a model-based widget struct interaction_model_view_select { explicit interaction_model_view_select(QString _name); QString name; }; struct number_input_modification { explicit number_input_modification(modification_t _type, double _number); modification_t modif_type; double modif_number; }; /// A mouse interaction before the preprocessing struct pre_interaction_mouse : public pre_interaction, public interaction_mouse { pre_interaction_mouse( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type, QPoint _from, QPoint _to, Qt::MouseButton _button ); }; /// A mouse wheel interaction before the preprocessing struct pre_interaction_mouse_wheel : public pre_interaction, public interaction_mouse_wheel { pre_interaction_mouse_wheel( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type, QPoint _angle_delta, QPoint _pos ); }; /// A keyboard interaction before the preprocessing struct pre_interaction_keyboard : public pre_interaction, public interaction_keyboard { pre_interaction_keyboard( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type, Qt::Key _key, const QString& _sequence ); }; /// A list widget click interaction before the preprocessing struct pre_interaction_model_view_select : public pre_interaction, public interaction_model_view_select { pre_interaction_model_view_select( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type, const QString& _name ); }; struct pre_interaction_number_input_modification : public pre_interaction, public number_input_modification { pre_interaction_number_input_modification( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, QEvent::Type _type, modification_t _modif_type, double _modif_number ); }; /// A mouse interaction after the preprocessing struct post_interaction_mouse : public post_interaction, public interaction_mouse { post_interaction_mouse( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type, QPoint _from, QPoint _to, Qt::MouseButton _button ); }; /// A mouse wheel interaction after the preprocessing struct post_interaction_mouse_wheel : public post_interaction, public interaction_mouse_wheel { post_interaction_mouse_wheel( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type, QPoint _angle_delta, QPoint _pos ); }; /// A keyboard interaction after the preprocessing struct post_interaction_keyboard : public post_interaction, public interaction_keyboard { post_interaction_keyboard( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type, Qt::Key _key, const QString& _sequence ); }; /// A list widget click interaction after the preprocessing struct post_interaction_model_view_select : public post_interaction, public interaction_model_view_select { post_interaction_model_view_select( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type, const QString& _name ); }; struct post_interaction_number_input_modification : public post_interaction, public number_input_modification { post_interaction_number_input_modification( intptr_t _receiver_id, const QVector& _how_to_find_receiver, Qt::KeyboardModifiers _modifiers, interaction_t _type, modification_t _modif_type, double _modif_number ); }; struct interaction_helper_api : public post_interaction { interaction_helper_api( intptr_t _receiver_id, const QVector& _how_to_find_receiver, QString _method_name, std::optional _select, QStringList _args = {}); QString method_name; std::optional select; QStringList args; }; /// Class which captures interactions to save them as a GuiTester-compatible GUI test C++ source file class macro_saver : public QObject { Q_OBJECT public: // Constructor. Does nothing. macro_saver() = default; /** * @brief Method used by Qt when an infected widget receives an event. * @details If it's the creation of a child widget, it infects it as well, if it's a user interaction, it saves it * in its interactions vector. * * @param _target The widget being targeted by the event * @param _e The event which was sent to the target * @returns Should the event handling stop? */ bool eventFilter(QObject* _target, QEvent* _e) override; /** * @brief Infects a widget and its children recursively, so that the events they will receive will be filtered with * @ref MacroSaver * * @param _o The widget to infect */ void infect(QObject* _o); /// Saves the user interactions as GuiTester-compatible GUI test C++ source files named GuiTest.cpp and GuiTest.hpp void save(); private: std::unique_ptr create_interaction(QObject* _target, QEvent* _e); QVector find(QObject* _o); static QObject* findChild( QObject* _o, const QString& _type, const QString& _object_name = "", Qt::FindChildOptions _options = Qt::FindChildrenRecursively ); static QObjectList findChildren( QObject* _o, const QString& _type, const QString& _object_name = "", Qt::FindChildOptions _options = Qt::FindChildrenRecursively ); std::vector > m_interactions; QWidget* m_main_window = nullptr; bool m_drag_in_progress = false; }; sight-25.1.0/module/ui/test/plugin.cpp000066400000000000000000000027241503402212300175750ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2022-2023 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ // cspell:ignore qputenv #include "plugin.hpp" #include namespace sight::module::ui::test { SIGHT_REGISTER_PLUGIN("sight::module::ui::test::plugin"); //------------------------------------------------------------------------------ void plugin::start() noexcept { SIGHT_ASSERT("QApplication must be started for MacroSaver to infect it", qApp); qputenv("GUI_TESTS_ARE_RUNNING", "1"); m_macro.infect(qApp); } //------------------------------------------------------------------------------ void plugin::stop() noexcept { m_macro.save(); } } // namespace sight::module::ui::test sight-25.1.0/module/ui/test/plugin.hpp000066400000000000000000000024531503402212300176010ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2022-2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "macro_saver.hpp" #include "sight/module/ui/test/config.hpp" #include namespace sight::module::ui::test { class SIGHT_MODULE_UI_TEST_CLASS_API plugin : public core::runtime::plugin { public: SIGHT_MODULE_UI_TEST_API void start() noexcept override; SIGHT_MODULE_UI_TEST_API void stop() noexcept override; private: macro_saver m_macro; }; } // namespace sight::module::ui::test sight-25.1.0/module/ui/test/rc/000077500000000000000000000000001503402212300161725ustar00rootroot00000000000000sight-25.1.0/module/ui/test/rc/plugin.xml000066400000000000000000000000671503402212300202150ustar00rootroot00000000000000 sight-25.1.0/module/ui/viz/000077500000000000000000000000001503402212300154175ustar00rootroot00000000000000sight-25.1.0/module/ui/viz/CMakeLists.txt000066400000000000000000000011451503402212300201600ustar00rootroot00000000000000sight_add_target(module_ui_viz TYPE MODULE) find_package(Qt6 QUIET COMPONENTS Widgets REQUIRED) target_link_libraries(${SIGHT_TARGET} PUBLIC Qt6::Widgets) set_target_properties(${SIGHT_TARGET} PROPERTIES AUTOMOC TRUE) target_compile_definitions(${SIGHT_TARGET} PUBLIC "QT_NO_KEYWORDS") add_dependencies(${SIGHT_TARGET} module_ui module_ui_qt) target_link_libraries( ${SIGHT_TARGET} PUBLIC core data io geometry_data ui_qt service viz_scene3d ui ) if(SIGHT_BUILD_TESTS) add_subdirectory(test/ut) endif(SIGHT_BUILD_TESTS) sight-25.1.0/module/ui/viz/README.md000066400000000000000000000030551503402212300167010ustar00rootroot00000000000000# sight::module::ui::viz Contains all user interface widgets related to scene3d (Ogre). ## Services - **compositor_parameter_editor**: allows to edit shader uniform from a compositor material. - **compositor_selector**: allows to select an Ogre Compositor and apply it to a layer. - **core_compositor_editor**: allows to select an Ogre Compositor and apply it to a layer. - **light_editor**: creates a user interface to manage a light adaptor. - **light_selector**: selects light adaptor from a selected layer or create a new one. - **material_selector**: allows to select an Ogre material template and apply it to the current `data::reconstruction`. - **screen_selector**: selects a screen and sends its index. - **shader_parameter_editor**: allows to edit each parameters from each shader of a `data::reconstruction`. - **stereo_selector**: allows to select the stereo mode of an Ogre Compositor. - **stereo_toggler**: enables/disables stereo in an ogre scene layer. - **texture_selector**: allows to select a `data::image` and apply it to the current reconstruction as an Ogre texture. ## Other classes - **helper/utils**: contains functions to convert from Ogre to Qt color format. - **helper/parameter_editor**: contains static function to create a config that will be used to expose a shader parameter adaptor in a parameters. ## How to use it ### CMake ```cmake add_dependencies(my_target module_ui_viz ... ) ``` ### XML Please consult the [doxygen](https://sight.pages.ircad.fr/sight) of each service to learn more about its use in xml configurations.sight-25.1.0/module/ui/viz/compositor_parameter_editor.cpp000066400000000000000000000153321503402212300237330ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "compositor_parameter_editor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { const core::com::slots::key_t compositor_parameter_editor::UPDATE_COMPOSITOR_SLOT = "updateCompositor"; //------------------------------------------------------------------------------ compositor_parameter_editor::compositor_parameter_editor() noexcept { new_slot(UPDATE_COMPOSITOR_SLOT, &compositor_parameter_editor::update_compositor, this); } //------------------------------------------------------------------------------ compositor_parameter_editor::~compositor_parameter_editor() noexcept = default; //------------------------------------------------------------------------------ void compositor_parameter_editor::configuring() { this->initialize(); auto config = this->get_config(); m_layer_id = config.get("layer..id", ""); } //------------------------------------------------------------------------------ void compositor_parameter_editor::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); m_sizer = new QVBoxLayout(); m_sizer->setContentsMargins(0, 0, 0, 0); qt_container->set_layout(m_sizer); this->updating(); } //------------------------------------------------------------------------------ void compositor_parameter_editor::stopping() { this->clear(); this->destroy(); } //------------------------------------------------------------------------------ void compositor_parameter_editor::updating() { } //------------------------------------------------------------------------------ void compositor_parameter_editor::update_compositor( std::string /*_compositorName*/, bool /*_enabled*/, sight::viz::scene3d::layer::sptr _layer ) { if(_layer->layer_id() == m_layer_id) { // We will create a new layout so clear everything before this->clear(); bool found = false; const auto adaptors = _layer->get_registered_adaptors(); // Is there at least one parameter that we can handle ? for(const auto& w_adaptor : adaptors) { const auto adaptor = w_adaptor.lock(); if(adaptor->get_classname() == "sight::module::viz::scene3d::adaptor::compositor_parameter") { /// filter object types const auto shader_obj = adaptor->inout(sight::viz::scene3d::parameter_adaptor::PARAMETER_INOUT).lock(); const auto& obj_type = shader_obj->get_classname(); if(obj_type == "sight::data::boolean" || obj_type == "sight::data::real" || obj_type == "sight::data::integer") { found = true; break; } } } if(!found) { return; } /// Getting this widget's container auto qt_container = std::dynamic_pointer_cast(this->get_container()); QWidget* container = qt_container->get_qt_container(); auto* p2 = new QWidget(container); m_editor_info.editor_panel = sight::ui::qt::container::widget::make(); m_editor_info.editor_panel->set_qt_container(p2); const std::string uuid = this->get_id(); m_editor_info.uuid = uuid + "-editor"; sight::ui::registry::register_sid_container(m_editor_info.uuid, m_editor_info.editor_panel); auto editor_service = sight::service::add("sight::module::ui::qt::settings", m_editor_info.uuid); m_editor_info.srv = editor_service; service::config_t editor_config; // Get all ShaderParameter subservices from the corresponding Material adaptor for(const auto& w_adaptor : adaptors) { const auto adaptor = w_adaptor.lock(); if(adaptor->get_classname() == "sight::module::viz::scene3d::adaptor::compositor_parameter") { auto param_adaptor = std::dynamic_pointer_cast(adaptor); auto param_config = module::ui::viz::helper::parameter_editor::create_config(param_adaptor); if(!param_config.empty()) { editor_config.add_child("ui.item", param_config); } } } editor_service->set_config(editor_config); editor_service->configure(); editor_service->start(); this->fill_gui(); } } //------------------------------------------------------------------------------ void compositor_parameter_editor::clear() { service::base::sptr obj_service = m_editor_info.srv.lock(); if(obj_service) { obj_service->stop(); sight::ui::registry::unregister_sid_container(m_editor_info.uuid); sight::service::unregister_service(obj_service); m_sizer->removeWidget(m_editor_info.editor_panel->get_qt_container()); m_editor_info.editor_panel->destroy_container(); m_editor_info.editor_panel.reset(); } } //------------------------------------------------------------------------------ void compositor_parameter_editor::fill_gui() { auto editor_service = m_editor_info.srv.lock(); if(editor_service) { m_sizer->addWidget(m_editor_info.editor_panel->get_qt_container(), 0); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/compositor_parameter_editor.hpp000066400000000000000000000056271503402212300237460ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::ui::viz { /** * @brief Editor allowing to edit shader uniform from a compositor material. * @section XML XML Configuration * * @code{.xml} @endcode */ class compositor_parameter_editor : public sight::ui::editor { public: SIGHT_DECLARE_SERVICE(compositor_parameter_editor, sight::ui::editor); /// Constructor. compositor_parameter_editor() noexcept; /// Destructor. Destroy UI ~compositor_parameter_editor() noexcept override; /** * @name Slots API * @{ */ /// Slot: update the interface. static const core::com::slots::key_t UPDATE_COMPOSITOR_SLOT; /** @} */ protected: /// Configure the editor to associate with each object type void configuring() override; /// Start the service, slot connections, widget initialization . void starting() override; /// Destroy the service and the container. void stopping() override; /// Update the interface. void updating() override; private: /// Slot: update the interface. void update_compositor(std::string _compositor_name, bool _enabled, sight::viz::scene3d::layer::sptr _layer); /// Clear the current container void clear(); /// Instanciates the needed ui editors according to the stored informations void fill_gui(); /// Internal class that contain the informations concerning the editor that is created. struct shader_editor_info { std::string uuid; sight::ui::qt::container::widget::sptr editor_panel; service::base::wptr srv; }; shader_editor_info m_editor_info; QVBoxLayout* m_sizer {}; /// Name of the layer when we configure it in XML std::string m_layer_id; }; } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/compositor_selector.cpp000066400000000000000000000223361503402212300222270ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "compositor_selector.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { using sight::viz::scene3d::layer; const core::com::slots::key_t compositor_selector::INIT_COMPOSITOR_LIST_SLOT = "initCompositorList"; static const std::string COMPOSITOR_RESOURCEGROUP_NAME = "compositorsPostFX"; //------------------------------------------------------------------------------ compositor_selector::compositor_selector() noexcept { new_slot(INIT_COMPOSITOR_LIST_SLOT, &compositor_selector::init_compositor_list, this); } //------------------------------------------------------------------------------ compositor_selector::~compositor_selector() noexcept = default; //------------------------------------------------------------------------------ void compositor_selector::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); m_layers_box = new QComboBox(); m_compositor_chain = new QListWidget(); auto* layout = new QVBoxLayout(); layout->addWidget(m_layers_box); layout->addWidget(m_compositor_chain); qt_container->set_layout(layout); this->refresh_renderers(); QObject::connect(m_layers_box, SIGNAL(activated(int)), this, SLOT(on_selected_layer_item(int))); QObject::connect( m_compositor_chain, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(on_selected_compositor_item(QListWidgetItem*)) ); } //------------------------------------------------------------------------------ void compositor_selector::stopping() { m_connections.disconnect(); this->destroy(); } //------------------------------------------------------------------------------ void compositor_selector::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void compositor_selector::updating() { } //------------------------------------------------------------------------------ void compositor_selector::on_selected_layer_item(int _index) { m_current_layer = m_layers[static_cast(_index)]; layer::sptr layer = m_current_layer.lock(); if(layer) { // Set current context layer->render_service()->make_current(); // Empty the list widget m_compositor_chain->clear(); // Update the current layer m_layer_compositor_chain = layer->get_compositor_chain(); // We need the ogre's viewport in order to add the compositors, // this is why we have to check the viewport's existence if(layer->get_viewport() != nullptr) { // Fill the list widget this->update_compositor_list(); // Iterates through the compositors and checks the enabled ones this->check_enabled_compositors(); } } } //------------------------------------------------------------------------------ void compositor_selector::on_selected_compositor_item(QListWidgetItem* _compositor_item) { const std::string compositor_name = _compositor_item->text().toStdString(); const bool is_checked = (_compositor_item->checkState() == Qt::Checked); layer::sptr layer = m_current_layer.lock(); if(layer) { layer->render_service()->make_current(); layer->update_compositor_state(compositor_name, is_checked); } } //------------------------------------------------------------------------------ void compositor_selector::init_compositor_list(layer::sptr _layer) { m_current_layer = m_layers[0]; if(_layer == m_current_layer.lock()) { on_selected_layer_item(0); } } //------------------------------------------------------------------------------ void compositor_selector::refresh_renderers() { m_layers_box->clear(); // Fill layer box with all enabled layers const auto renderers = sight::service::get_services("sight::viz::scene3d::render"); for(const auto& srv : renderers) { auto render = std::dynamic_pointer_cast(srv); for(auto& layer_map : render->get_layers()) { const std::string id = layer_map.first; std::string render_id = render->get_id(); m_layers_box->addItem(QString::fromStdString(render_id + " : " + id)); m_layers.push_back(layer_map.second); m_connections.connect( layer_map.second, layer::INIT_LAYER_SIG, this->get_sptr(), INIT_COMPOSITOR_LIST_SLOT ); } } if(!m_layers.empty()) { this->on_selected_layer_item(0); } } //------------------------------------------------------------------------------ void compositor_selector::update_compositor_list() { layer::sptr layer = m_current_layer.lock(); if(layer) { layer->render_service()->make_current(); // Retrieving all compositors Ogre::ResourceManager::ResourceMapIterator iter = Ogre::CompositorManager::getSingleton().getResourceIterator(); while(iter.hasMoreElements()) { Ogre::ResourcePtr compositor = iter.getNext(); if(compositor->getGroup() == COMPOSITOR_RESOURCEGROUP_NAME) { QString compositor_name = compositor.get()->getName().c_str(); layer->add_available_compositor(compositor_name.toStdString()); auto* new_compositor = new QListWidgetItem(compositor_name, m_compositor_chain); new_compositor->setFlags(new_compositor->flags() | Qt::ItemIsUserCheckable); new_compositor->setCheckState(Qt::Unchecked); } } } } //------------------------------------------------------------------------------ void compositor_selector::check_enabled_compositors() { layer::sptr layer = m_current_layer.lock(); if(layer) { layer->render_service()->make_current(); if(!m_layer_compositor_chain.empty()) { for(int i(0) ; i < m_compositor_chain->count() ; ++i) { QListWidgetItem* current_compositor = m_compositor_chain->item(i); std::string current_compositor_name = current_compositor->text().toStdString(); auto layer_compositor = std::find_if( m_layer_compositor_chain.begin(), m_layer_compositor_chain.end(), [¤t_compositor_name](const auto& _compositor) { return _compositor.first == current_compositor_name; }); if(layer_compositor != m_layer_compositor_chain.end()) { if(layer_compositor->second) { current_compositor->setCheckState(Qt::Checked); layer->update_compositor_state(current_compositor->text().toStdString(), true); } } } } } } //------------------------------------------------------------------------------ void compositor_selector::uncheck_compositors() { for(int i(0) ; i < m_compositor_chain->count() ; ++i) { QListWidgetItem* current_compositor = m_compositor_chain->item(i); current_compositor->setCheckState(Qt::Unchecked); } } //------------------------------------------------------------------------------ bool compositor_selector::is_enabled_compositor(const std::string& _compositor_name) { auto layer_compositor = std::find_if( m_layer_compositor_chain.begin(), m_layer_compositor_chain.end(), [&_compositor_name](const auto& _compositor) { return _compositor.first == _compositor_name; }); if(layer_compositor != m_layer_compositor_chain.end()) { return layer_compositor->second; } return false; } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/compositor_selector.hpp000066400000000000000000000102451503402212300222300ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { /** * @brief Allows to select an Ogre Compositor and apply it to a layer */ class compositor_selector : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(compositor_selector, sight::ui::editor); /** @} */ /** * @name Slots API * @{ */ using init_layer_slot_t = core::com::slot; /// Slot: Populate the list of available compositors for the selected layer static const core::com::slots::key_t INIT_COMPOSITOR_LIST_SLOT; /** @} */ /// Constructor. compositor_selector() noexcept; /// Destructor. Does nothing ~compositor_selector() noexcept override; protected: /** * @brief method description: * @code{.xml} param @endcode * - \b Parameter : parameter description. */ void configuring() override; /// Sets the connections and the UI elements void starting() override; /// Destroys the connections and cleans the container void stopping() override; /// Does nothing void updating() override; private Q_SLOTS: /// Slot: called when a layer is selected /// Sets the current layer and initializes the compositor list void on_selected_layer_item(int _index); /// Slot: called when an item of the list widget is checked void on_selected_compositor_item(QListWidgetItem* _compositor_item); private: /// Slot: Populate the list of available compositors for the selected layer void init_compositor_list(sight::viz::scene3d::layer::sptr _layer); /// Retrieves all the layers from the application thanks to the render services void refresh_renderers(); /// Checks if there is an XML configured compositor chain. /// Sets the local compositor chain according to this. void synchronise_with_layer_compositor_chain(); /// Retrieves the available compositors from the compositor resource group and stores them in the list widget void update_compositor_list(); /// Iterates through the compositor chain and checks the enabled compositors void check_enabled_compositors(); /// Iterates through the compositor chain and unchecks them void uncheck_compositors(); /// Indicates if a compositor is enabled on the layer bool is_enabled_compositor(const std::string& _compositor_name); QPointer m_layers_box; QPointer m_compositor_chain; std::vector m_layers; sight::viz::scene3d::layer::wptr m_current_layer; sight::viz::scene3d::compositor::chain_manager::compositor_chain_t m_layer_compositor_chain; ///Connection service, needed for slot/signal association core::com::helper::sig_slot_connection m_connections; }; } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/core_compositor_editor.cpp000066400000000000000000000303371503402212300227050ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "core_compositor_editor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { //------------------------------------------------------------------------------ core_compositor_editor::core_compositor_editor() noexcept = default; //------------------------------------------------------------------------------ core_compositor_editor::~core_compositor_editor() noexcept = default; //------------------------------------------------------------------------------ void core_compositor_editor::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); auto* layout = new QVBoxLayout(); auto* label_layer_selector = new QLabel(tr("3D layer selected")); layout->addWidget(label_layer_selector); m_layers_box = new QComboBox(); layout->addWidget(m_layers_box); // Transparency depth management { auto* label_transparency = new QLabel(tr("Transparency depth")); layout->addWidget(label_transparency); m_transparency_depth_slider = new QSlider(Qt::Horizontal); layout->addWidget(m_transparency_depth_slider); m_transparency_depth_slider->setEnabled(false); m_transparency_depth_slider->setTickInterval(1); m_transparency_depth_slider->setTickPosition(QSlider::TicksBelow); m_transparency_depth_slider->setRange(0, 12); m_transparency_depth_slider->setValue(8); } // Transparency selector { auto* group_box = new QGroupBox(tr("Transparency technique")); auto* layout_group_box = new QVBoxLayout(); group_box->setLayout(layout_group_box); layout->addWidget(group_box); m_transparency_button_group = new QButtonGroup(group_box); m_button_default = new QRadioButton(tr("Default"), group_box); m_button_default->setMinimumSize(m_button_default->sizeHint()); m_button_default->setEnabled(false); m_transparency_button_group->addButton(m_button_default, 0); layout_group_box->addWidget(m_button_default); m_label_default = new QLabel(tr("No Order Independent Transparency")); m_label_default->setEnabled(false); layout_group_box->addWidget(m_label_default); m_button_depth_peeling = new QRadioButton(tr("Depth Peeling"), group_box); m_button_depth_peeling->setMinimumSize(m_button_depth_peeling->sizeHint()); m_button_depth_peeling->setEnabled(false); m_transparency_button_group->addButton(m_button_depth_peeling, 1); layout_group_box->addWidget(m_button_depth_peeling); m_label_depth_peeling = new QLabel(tr("Exact color blending but slowest technique")); m_label_depth_peeling->setEnabled(false); layout_group_box->addWidget(m_label_depth_peeling); m_button_dual_depth_peeling = new QRadioButton(tr("Dual Depth Peeling"), group_box); m_button_dual_depth_peeling->setMinimumSize(m_button_dual_depth_peeling->sizeHint()); m_button_dual_depth_peeling->setEnabled(false); m_transparency_button_group->addButton(m_button_dual_depth_peeling, 2); layout_group_box->addWidget(m_button_dual_depth_peeling); m_label_dual_depth_peeling = new QLabel(tr("Exact color blending but slow technique")); m_label_dual_depth_peeling->setEnabled(false); layout_group_box->addWidget(m_label_dual_depth_peeling); m_button_weighted_blended_oit = new QRadioButton(tr("Weighted Blended OIT"), group_box); m_button_weighted_blended_oit->setMinimumSize(m_button_weighted_blended_oit->sizeHint()); m_button_weighted_blended_oit->setEnabled(false); m_transparency_button_group->addButton(m_button_weighted_blended_oit, 3); layout_group_box->addWidget(m_button_weighted_blended_oit); m_label_weighted_blended_oit = new QLabel(tr("Approximative color blending but fastest")); m_label_weighted_blended_oit->setEnabled(false); layout_group_box->addWidget(m_label_weighted_blended_oit); m_button_hybrid_transparency = new QRadioButton(tr("Hybrid transparency"), group_box); m_button_hybrid_transparency->setMinimumSize(m_button_hybrid_transparency->sizeHint()); m_button_hybrid_transparency->setEnabled(false); m_transparency_button_group->addButton(m_button_hybrid_transparency, 4); layout_group_box->addWidget(m_button_hybrid_transparency); m_label_hybrid_transparency = new QLabel(tr("Depth Peeling + Weighted Blended OIT = half exact half fast")); m_label_hybrid_transparency->setEnabled(false); layout_group_box->addWidget(m_label_hybrid_transparency); m_button_cel_shading_depth_peeling = new QRadioButton(tr("CelShading + Depth Peeling"), group_box); m_button_cel_shading_depth_peeling->setMinimumSize(m_button_depth_peeling->sizeHint()); m_button_cel_shading_depth_peeling->setEnabled(false); m_transparency_button_group->addButton(m_button_cel_shading_depth_peeling, 5); layout_group_box->addWidget(m_button_cel_shading_depth_peeling); m_label_cel_shading_depth_peeling = new QLabel( tr( "Depth peeling with an edge detection per layer." ) ); m_label_cel_shading_depth_peeling->setEnabled(false); layout_group_box->addWidget(m_label_cel_shading_depth_peeling); } qt_container->set_layout(layout); this->refresh_renderers(); QObject::connect(m_layers_box, SIGNAL(activated(int)), this, SLOT(on_selected_layer_item(int))); QObject::connect( m_transparency_depth_slider, SIGNAL(valueChanged(int)), this, SLOT(on_edit_transparency_depth(int)) ); QObject::connect(m_transparency_button_group, SIGNAL(buttonClicked(int)), this, SLOT(on_edit_transparency(int))); } //------------------------------------------------------------------------------ void core_compositor_editor::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void core_compositor_editor::refresh_renderers() { m_layers_box->clear(); // Fill layer box with all enabled layers const auto renderers = sight::service::get_services("sight::viz::scene3d::render"); for(const auto& srv : renderers) { sight::viz::scene3d::render::sptr render = std::dynamic_pointer_cast(srv); for(auto& layer_map : render->get_layers()) { // Adds default layers (3D scene) const std::string id = layer_map.first; std::string render_id = render->get_id(); m_layers_box->addItem(QString::fromStdString(render_id + " : " + id)); m_layers.push_back(layer_map.second); } } m_layers_box->setCurrentIndex(-1); } //------------------------------------------------------------------------------ void core_compositor_editor::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void core_compositor_editor::updating() { } //------------------------------------------------------------------------------ void core_compositor_editor::on_selected_layer_item(int _index) { namespace compositor = sight::viz::scene3d::compositor; if(!m_is_layer_selected) { m_transparency_depth_slider->setEnabled(true); m_button_default->setEnabled(true); m_button_depth_peeling->setEnabled(true); m_button_dual_depth_peeling->setEnabled(true); m_button_weighted_blended_oit->setEnabled(true); m_button_hybrid_transparency->setEnabled(true); m_button_cel_shading_depth_peeling->setEnabled(true); m_label_default->setEnabled(true); m_label_depth_peeling->setEnabled(true); m_label_dual_depth_peeling->setEnabled(true); m_label_weighted_blended_oit->setEnabled(true); m_label_hybrid_transparency->setEnabled(true); m_label_cel_shading_depth_peeling->setEnabled(true); } // Reloads buttons to match layer's parameters m_current_layer = m_layers[static_cast(_index)]; // If the layer is not yet started, we can't use its default compositor auto layer = m_current_layer.lock(); if(layer) { switch(layer->get_transparency_technique()) { case compositor::DEFAULT: m_transparency_button_group->button(0)->setChecked(true); break; case compositor::depthpeeling: m_transparency_button_group->button(1)->setChecked(true); break; case compositor::dualdepthpeeling: m_transparency_button_group->button(2)->setChecked(true); break; case compositor::weightedblendedoit: m_transparency_button_group->button(3)->setChecked(true); break; case compositor::hybridtransparency: m_transparency_button_group->button(4)->setChecked(true); break; case compositor::cellshading_depthpeeling: m_transparency_button_group->button(5)->setChecked(true); break; } m_transparency_depth_slider->setValue(layer->get_transparency_depth()); } } //------------------------------------------------------------------------------ void core_compositor_editor::on_edit_transparency_depth(int _depth) { auto layer = m_current_layer.lock(); if(layer) { layer->set_transparency_depth(_depth); } } //------------------------------------------------------------------------------ void core_compositor_editor::on_edit_transparency(int _index) { namespace compositor = sight::viz::scene3d::compositor; auto layer = m_current_layer.lock(); if(layer) { switch(_index) { case 0: layer->set_transparency_technique(compositor::DEFAULT); break; case 1: layer->set_transparency_technique(compositor::depthpeeling); break; case 2: layer->set_transparency_technique(compositor::dualdepthpeeling); break; case 3: layer->set_transparency_technique(compositor::weightedblendedoit); break; case 4: layer->set_transparency_technique(compositor::hybridtransparency); break; case 5: layer->set_transparency_technique(compositor::cellshading_depthpeeling); break; default: break; } m_transparency_button_group->button(0)->setChecked(true); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/core_compositor_editor.hpp000066400000000000000000000066431503402212300227150ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include class QAbstractButton; class QButtonGroup; class QComboBox; class QRadioButton; class QLineEdit; class QSlider; class QCheckBox; class QComboBox; // needed for the radius class QDoubleSpinBox; // needed for the samples number class QSpinBox; namespace sight::module::ui::viz { /** * @brief Allows to select an Ogre Compositor and apply it to a layer */ class core_compositor_editor : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(core_compositor_editor, sight::ui::editor); /// Constructor. core_compositor_editor() noexcept; /// Destructor. Does nothing ~core_compositor_editor() noexcept override; protected: /** * @brief method description: * @code{.xml} param @endcode * - \b Parameter : parameter description. */ void configuring() override; /// FILL ME. void starting() override; /// FILL ME. void stopping() override; /// FILL ME. void updating() override; /// FILL ME. void refresh_renderers(); std::vector m_layers; sight::viz::scene3d::layer::wptr m_current_layer; protected Q_SLOTS: void on_selected_layer_item(int _index); void on_edit_transparency_depth(int _depth); void on_edit_transparency(int _index); private: QPointer m_transparency_button_group; QPointer m_transparency_button_group_shading; QPointer m_normals_radio_box; QPointer m_layers_box; QPointer m_transparency_depth_slider; QPointer m_combo_box; QRadioButton* m_button_default {}; QRadioButton* m_button_depth_peeling {}; QRadioButton* m_button_dual_depth_peeling {}; QRadioButton* m_button_weighted_blended_oit {}; QRadioButton* m_button_hybrid_transparency {}; QRadioButton* m_button_cel_shading_depth_peeling {}; QLabel* m_label_default {}; QLabel* m_label_depth_peeling {}; QLabel* m_label_dual_depth_peeling {}; QLabel* m_label_weighted_blended_oit {}; QLabel* m_label_hybrid_transparency {}; QLabel* m_label_cel_shading_depth_peeling {}; bool m_is_layer_selected {}; }; } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/helper/000077500000000000000000000000001503402212300166765ustar00rootroot00000000000000sight-25.1.0/module/ui/viz/helper/parameter_editor.cpp000066400000000000000000000117661503402212300227430ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2025 IRCAD France * Copyright (C) 2017-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/ui/viz/helper/parameter_editor.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz::helper { //----------------------------------------------------------------------------- template std::pair get_range(T _value) { static const T s_RANGE = 10; T max; T min; // For _value > 1, we use [-_value * range;+_value *range] if(_value > 1) { max = +_value * s_RANGE; min = -_value * s_RANGE; } else if(_value < -1) // For _value < 0, we use [_value * range;-_value *range] { max = -_value * s_RANGE; min = +_value * s_RANGE; } else if(_value > 0 || _value < 0) // For -1 < value < 0 < _value < 1, we use [_value / range;+_value * range] { max = _value * s_RANGE; min = _value / s_RANGE; } else // For _value == 0, we use [0; 1] { max = 0; min = 1; } return std::make_pair(min, max); } //----------------------------------------------------------------------------- service::config_t parameter_editor::create_config( const sight::viz::scene3d::parameter_adaptor::csptr& _adaptor ) { service::config_t param_config; /// Getting associated object infos const auto shader_obj = _adaptor->inout(sight::viz::scene3d::parameter_adaptor::PARAMETER_INOUT).lock(); const auto& obj_type = shader_obj->get_classname(); const auto set_config = [&](auto& _data) { const auto minmax = get_range(_data); param_config.add(".name", _adaptor->get_param_name()); param_config.add(".key", _adaptor->get_param_name()); param_config.add(".min", minmax.first); param_config.add(".max", minmax.second); }; if(obj_type == "sight::data::boolean" || obj_type == "sight::data::color") { param_config.add(".name", _adaptor->get_param_name()); param_config.add(".key", _adaptor->get_param_name()); } else if(obj_type == "sight::data::real") { auto float_value = std::dynamic_pointer_cast(shader_obj.get_shared()); set_config(float_value->value()); param_config.add(".widget", "slider"); } else if(obj_type == "sight::data::integer") { auto int_value = std::dynamic_pointer_cast(shader_obj.get_shared()); set_config(int_value->value()); param_config.add(".widget", "slider"); } else if(obj_type == "sight::data::ivec2") { auto data = std::dynamic_pointer_cast(shader_obj.get_shared()); set_config(data->value()[0]); } else if(obj_type == "sight::data::ivec3") { auto data = std::dynamic_pointer_cast(shader_obj.get_shared()); set_config(data->value()[0]); } else if(obj_type == "sight::data::ivec4") { auto data = std::dynamic_pointer_cast(shader_obj.get_shared()); set_config(data->value()[0]); } else if(obj_type == "sight::data::dvec2") { auto data = std::dynamic_pointer_cast(shader_obj.get_shared()); set_config(data->value()[0]); } else if(obj_type == "sight::data::dvec3") { auto data = std::dynamic_pointer_cast(shader_obj.get_shared()); set_config(data->value()[0]); } else if(obj_type == "sight::data::dvec4") { auto data = std::dynamic_pointer_cast(shader_obj.get_shared()); set_config(data->value()[0]); } else { SIGHT_ERROR("No editor found for the object of type " << obj_type); } param_config.add(".reset", "false"); return param_config; } //----------------------------------------------------------------------------- } // namespace sight::module::ui::viz::helper sight-25.1.0/module/ui/viz/helper/parameter_editor.hpp000066400000000000000000000027541503402212300227450ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2025 IRCAD France * Copyright (C) 2017-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include namespace sight::module::ui::viz::helper { class parameter_editor { public: /** * @brief Create a config that will be used to expose an shader parameter adaptor in a parameters. * * @param[in] _adaptor adaptor to bind. * @param[inout] _connections helper that stores connections between the editor and the adaptors. */ static service::config_t create_config(const sight::viz::scene3d::parameter_adaptor::csptr& _adaptor); }; } // namespace sight::module::ui::viz::helper sight-25.1.0/module/ui/viz/helper/utils.hpp000066400000000000000000000042331503402212300205510ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2017 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include namespace sight::module::ui::viz::helper { class utils { public: /** * @brief convertOgreColorToQColor * @param _ogre_color the Ogre color to convert. * @return the converted QColor. */ static constexpr QColor convert_ogre_color_to_q_color(const Ogre::ColourValue& _ogre_color) { const int r = static_cast(_ogre_color.r * 255); const int g = static_cast(_ogre_color.g * 255); const int b = static_cast(_ogre_color.b * 255); const int a = static_cast(_ogre_color.a * 255); return {r, g, b, a}; } /** * @brief convertQColorToOgreColor * @param _q_color the Qt color to convert. * @return the converted Ogre color. */ static inline Ogre::ColourValue convert_q_color_to_ogre_color(const QColor& _q_color) { const float r = static_cast(_q_color.red()) / 255.F; const float g = static_cast(_q_color.green()) / 255.F; const float b = static_cast(_q_color.blue()) / 255.F; const float a = static_cast(_q_color.alpha()) / 255.F; return Ogre::ColourValue(r, g, b, a); } }; } // namespace sight::module::ui::viz::helper sight-25.1.0/module/ui/viz/light_editor.cpp000066400000000000000000000475111503402212300206100ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "light_editor.hpp" #include "module/ui/viz/helper/utils.hpp" #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { using sight::viz::scene3d::light_adaptor; using sight::viz::scene3d::layer; //------------------------------------------------------------------------------ static const core::com::slots::key_t EDIT_LIGHT_SLOT = "edit_light"; //------------------------------------------------------------------------------ light_editor::light_editor() noexcept { new_slot(EDIT_LIGHT_SLOT, &light_editor::edit_light, this); } //------------------------------------------------------------------------------ light_editor::~light_editor() noexcept = default; //------------------------------------------------------------------------------ void light_editor::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void light_editor::starting() { this->create(); const QString service_id = QString::fromStdString(base_id()); const auto qt_container = std::dynamic_pointer_cast( this->get_container() ); qt_container->get_qt_container()->setObjectName(service_id); m_light_name_label = new QLabel("No light selected"); m_light_name_label->setAlignment(Qt::AlignHCenter); m_light_type_box = new QComboBox(); m_light_type_box->addItems( QStringList() << light_adaptor::POINT_LIGHT.c_str() << light_adaptor::DIRECTIONAL_LIGHT.c_str() ); m_light_type_box->setEnabled(false); m_visual_feedback = new QPushButton("Feedback"); m_visual_feedback->setObjectName(service_id + "/" + m_visual_feedback->text()); m_visual_feedback->setCheckable(true); m_visual_feedback->setEnabled(false); m_diffuse_color_btn = new QPushButton("Diffuse color"); m_diffuse_color_btn->setObjectName(service_id + "/" + m_diffuse_color_btn->text()); m_diffuse_color_btn->setEnabled(false); m_specular_color_btn = new QPushButton("Specular color"); m_specular_color_btn->setObjectName(service_id + "/" + m_specular_color_btn->text()); m_specular_color_btn->setEnabled(false); m_theta_slider = new QSlider(Qt::Horizontal); m_theta_slider = new QSlider(Qt::Horizontal); m_theta_slider->setObjectName(service_id + "/thetaSlider"); m_theta_slider->setMinimum(0); m_theta_slider->setMaximum(light_adaptor::OFFSET_RANGE); m_theta_slider->setEnabled(false); m_phi_slider = new QSlider(Qt::Horizontal); m_phi_slider->setObjectName(service_id + "/phiSlider"); m_phi_slider->setMinimum(0); m_phi_slider->setMaximum(light_adaptor::OFFSET_RANGE); m_phi_slider->setEnabled(false); m_x_translation = new QSlider(Qt::Horizontal); m_x_translation->setObjectName(service_id + "/xTranslation"); m_x_translation->setMinimum(-2000); m_x_translation->setMaximum(2000); m_x_translation->setEnabled(false); m_y_translation = new QSlider(Qt::Horizontal); m_y_translation->setObjectName(service_id + "/yTranslation"); m_y_translation->setMinimum(-2000); m_y_translation->setMaximum(2000); m_y_translation->setEnabled(false); m_z_translation = new QSlider(Qt::Horizontal); m_z_translation->setObjectName(service_id + "/zTranslation"); m_z_translation->setMinimum(-2000); m_z_translation->setMaximum(2000); m_z_translation->setEnabled(false); m_x_label = new QLineEdit("X: 0"); m_x_label->setObjectName(service_id + "/xLabel"); m_x_label->setReadOnly(true); m_x_label->setMaximumWidth(70); m_y_label = new QLineEdit("Y: 0"); m_y_label->setObjectName(service_id + "/yLabel"); m_y_label->setReadOnly(true); m_y_label->setMaximumWidth(70); m_z_label = new QLineEdit("Z: 0"); m_z_label->setObjectName(service_id + "/zLabel"); m_z_label->setReadOnly(true); m_z_label->setMaximumWidth(70); m_x_reset = new QPushButton("Reset"); m_x_reset->setObjectName(service_id + "/xReset"); m_x_reset->setEnabled(false); m_y_reset = new QPushButton("Reset"); m_y_reset->setObjectName(service_id + "/yReset"); m_y_reset->setEnabled(false); m_z_reset = new QPushButton("Reset"); m_z_reset->setObjectName(service_id + "/zReset"); m_z_reset->setEnabled(false); // Name of the selected light and its type auto* layout = new QVBoxLayout(); layout->addWidget(m_light_name_label); auto* const type_layout = new QHBoxLayout(); type_layout->addWidget(m_light_type_box); type_layout->addWidget(m_visual_feedback); layout->addLayout(type_layout); // Diffuse and specular colors auto* const color_layout = new QHBoxLayout(); color_layout->addWidget(m_diffuse_color_btn); color_layout->addWidget(m_specular_color_btn); layout->addLayout(color_layout); // Theta offset auto* const theta_layout = new QHBoxLayout(); theta_layout->addWidget(new QLabel("Theta offset :")); theta_layout->addWidget(m_theta_slider); layout->addLayout(theta_layout); // Phi offset auto* const phi_layout = new QHBoxLayout(); phi_layout->addWidget(new QLabel("Phi offset :")); phi_layout->addWidget(m_phi_slider); layout->addLayout(phi_layout); // Translations; auto* const x_transformation_layout = new QHBoxLayout(); x_transformation_layout->addWidget(m_x_label, 0); x_transformation_layout->addWidget(m_x_translation, 1); x_transformation_layout->addWidget(m_x_reset, 0); layout->addLayout(x_transformation_layout); auto* const y_transformation_layout = new QHBoxLayout(); y_transformation_layout->addWidget(m_y_label, 0); y_transformation_layout->addWidget(m_y_translation, 1); y_transformation_layout->addWidget(m_y_reset, 0); layout->addLayout(y_transformation_layout); auto* const z_transformation_layout = new QHBoxLayout(); z_transformation_layout->addWidget(m_z_label, 0); z_transformation_layout->addWidget(m_z_translation, 1); z_transformation_layout->addWidget(m_z_reset, 0); layout->addLayout(z_transformation_layout); qt_container->set_layout(layout); QObject::connect(m_diffuse_color_btn, &QPushButton::clicked, this, &light_editor::on_edit_diffuse_color); QObject::connect(m_specular_color_btn, &QPushButton::clicked, this, &light_editor::on_edit_specular_color); QObject::connect(m_theta_slider, &QSlider::valueChanged, this, &light_editor::on_edit_theta_offset); QObject::connect(m_phi_slider, &QSlider::valueChanged, this, &light_editor::on_edit_phi_offset); QObject::connect(m_light_type_box, &QComboBox::currentTextChanged, this, &light_editor::on_edit_type); QObject::connect(m_visual_feedback, &QPushButton::clicked, this, &light_editor::on_toggle_feedback); QObject::connect(m_x_translation, &QSlider::valueChanged, this, &light_editor::on_edit_x_translation); QObject::connect(m_y_translation, &QSlider::valueChanged, this, &light_editor::on_edit_y_translation); QObject::connect(m_z_translation, &QSlider::valueChanged, this, &light_editor::on_edit_z_translation); QObject::connect(m_x_reset, &QPushButton::clicked, this, &light_editor::on_reset_x_translation); QObject::connect(m_y_reset, &QPushButton::clicked, this, &light_editor::on_reset_y_translation); QObject::connect(m_z_reset, &QPushButton::clicked, this, &light_editor::on_reset_z_translation); } //------------------------------------------------------------------------------ void light_editor::updating() { } //------------------------------------------------------------------------------ void light_editor::stopping() { QObject::disconnect(m_diffuse_color_btn, &QPushButton::clicked, this, &light_editor::on_edit_diffuse_color); QObject::disconnect(m_specular_color_btn, &QPushButton::clicked, this, &light_editor::on_edit_specular_color); QObject::disconnect(m_theta_slider, &QSlider::valueChanged, this, &light_editor::on_edit_theta_offset); QObject::disconnect(m_phi_slider, &QSlider::valueChanged, this, &light_editor::on_edit_phi_offset); QObject::disconnect(m_light_type_box, &QComboBox::currentTextChanged, this, &light_editor::on_edit_type); QObject::disconnect(m_visual_feedback, &QPushButton::clicked, this, &light_editor::on_toggle_feedback); QObject::disconnect(m_x_translation, &QSlider::valueChanged, this, &light_editor::on_edit_x_translation); QObject::disconnect(m_y_translation, &QSlider::valueChanged, this, &light_editor::on_edit_y_translation); QObject::disconnect(m_z_translation, &QSlider::valueChanged, this, &light_editor::on_edit_z_translation); QObject::disconnect(m_x_reset, &QPushButton::clicked, this, &light_editor::on_reset_x_translation); QObject::disconnect(m_y_reset, &QPushButton::clicked, this, &light_editor::on_reset_y_translation); QObject::disconnect(m_z_reset, &QPushButton::clicked, this, &light_editor::on_reset_z_translation); this->destroy(); } //------------------------------------------------------------------------------ void light_editor::on_edit_diffuse_color(bool /*unused*/) { Ogre::ColourValue new_diffuse_color = this->edit_color( m_current_light->get_diffuse_color(), "Light diffuse color" ); m_current_light->set_diffuse_color(new_diffuse_color); } //------------------------------------------------------------------------------ void light_editor::on_edit_specular_color(bool /*unused*/) { Ogre::ColourValue new_specular_color = this->edit_color( m_current_light->get_specular_color(), "Light specular color" ); m_current_light->set_specular_color(new_specular_color); } //------------------------------------------------------------------------------ void light_editor::on_edit_theta_offset(int _value) { m_current_light->set_theta_offset(static_cast(_value - light_adaptor::OFFSET_RANGE / 2.)); } //------------------------------------------------------------------------------ void light_editor::on_edit_phi_offset(int _value) { m_current_light->set_phi_offset(static_cast(_value - light_adaptor::OFFSET_RANGE / 2.)); } //------------------------------------------------------------------------------ void light_editor::on_edit_type(const QString& _type) { if(_type == light_adaptor::POINT_LIGHT.c_str()) { m_current_light->set_type(Ogre::Light::LT_POINT); m_theta_slider->setEnabled(false); m_phi_slider->setEnabled(false); if(m_current_light->get_name().find(layer::DEFAULT_LIGHT_NAME) == std::string::npos) { m_x_translation->setEnabled(true); m_y_translation->setEnabled(true); m_z_translation->setEnabled(true); m_x_reset->setEnabled(true); m_y_reset->setEnabled(true); m_z_reset->setEnabled(true); } } else if(_type == light_adaptor::DIRECTIONAL_LIGHT.c_str()) { m_current_light->set_type(Ogre::Light::LT_DIRECTIONAL); if(m_current_light->get_name().find(layer::DEFAULT_LIGHT_NAME) == std::string::npos) { m_theta_slider->setEnabled(true); m_phi_slider->setEnabled(true); } m_x_translation->setEnabled(false); m_y_translation->setEnabled(false); m_z_translation->setEnabled(false); m_x_reset->setEnabled(false); m_y_reset->setEnabled(false); m_z_reset->setEnabled(false); } else { SIGHT_ASSERT("Unknow type for light", false); } m_current_light->update(); } //------------------------------------------------------------------------------ void light_editor::on_toggle_feedback(bool _enable) { m_current_light->enable_visual_feedback(_enable); m_current_light->render_service()->request_render(); } //------------------------------------------------------------------------------ void light_editor::on_edit_x_translation(int _value) { Ogre::Node* const light_node = this->get_light_node(); const Ogre::Vector3 current_pos = light_node->getPosition(); light_node->setPosition(Ogre::Vector3(static_cast(_value), current_pos[1], current_pos[2])); m_current_light->render_service()->request_render(); m_x_label->setText(QString("X: %1").arg(_value)); } //------------------------------------------------------------------------------ void light_editor::on_edit_y_translation(int _value) { Ogre::Node* const light_node = this->get_light_node(); const Ogre::Vector3 current_pos = light_node->getPosition(); light_node->setPosition(Ogre::Vector3(current_pos[0], static_cast(_value), current_pos[2])); m_current_light->render_service()->request_render(); m_y_label->setText(QString("Y: %1").arg(_value)); } //------------------------------------------------------------------------------ void light_editor::on_edit_z_translation(int _value) { Ogre::Node* const light_node = this->get_light_node(); const Ogre::Vector3 current_pos = light_node->getPosition(); light_node->setPosition(Ogre::Vector3(current_pos[0], current_pos[1], static_cast(_value))); m_current_light->render_service()->request_render(); m_z_label->setText(QString("Z: %1").arg(_value)); } //------------------------------------------------------------------------------ void light_editor::on_reset_x_translation(bool /*unused*/) { Ogre::Node* const light_node = this->get_light_node(); const Ogre::Vector3 current_pos = light_node->getPosition(); light_node->setPosition(Ogre::Vector3(0.F, current_pos[1], current_pos[2])); m_current_light->render_service()->request_render(); m_x_label->setText("X: 0"); m_x_translation->setValue(0); } //------------------------------------------------------------------------------ void light_editor::on_reset_y_translation(bool /*unused*/) { Ogre::Node* const light_node = this->get_light_node(); const Ogre::Vector3 current_pos = light_node->getPosition(); light_node->setPosition(Ogre::Vector3(current_pos[0], 0.F, current_pos[2])); m_current_light->render_service()->request_render(); m_y_label->setText("Y: 0"); m_y_translation->setValue(0); } //------------------------------------------------------------------------------ void light_editor::on_reset_z_translation(bool /*unused*/) { Ogre::Node* const light_node = this->get_light_node(); const Ogre::Vector3 current_pos = light_node->getPosition(); light_node->setPosition(Ogre::Vector3(current_pos[0], current_pos[1], 0.F)); m_current_light->render_service()->request_render(); m_z_label->setText("Z: 0"); m_z_translation->setValue(0); } //------------------------------------------------------------------------------ Ogre::Node* light_editor::get_light_node() const { Ogre::SceneNode* const root = m_current_light->layer()->get_scene_manager()->getRootSceneNode(); Ogre::Node* const light_node = sight::viz::scene3d::helper::scene::get_node_by_id(m_current_light->get_transform_id(), root); return light_node; } //------------------------------------------------------------------------------ void light_editor::edit_light(light_adaptor::sptr _light_adaptor) { m_current_light = _light_adaptor; if(_light_adaptor) { SIGHT_ASSERT("The selected light adaptor doesn't exist.", _light_adaptor); m_light_name_label->setText(m_current_light->get_name().c_str()); m_light_type_box->setCurrentIndex(static_cast(m_current_light->type())); m_diffuse_color_btn->setEnabled(true); m_specular_color_btn->setEnabled(true); m_light_type_box->setEnabled(true); if(m_current_light->get_name().find(layer::DEFAULT_LIGHT_NAME) == std::string::npos) { m_visual_feedback->setEnabled(true); if(m_current_light->type() == Ogre::Light::LT_DIRECTIONAL) { m_theta_slider->setEnabled(true); m_phi_slider->setEnabled(true); } else if(m_current_light->type() == Ogre::Light::LT_POINT) { m_x_translation->setEnabled(true); m_y_translation->setEnabled(true); m_z_translation->setEnabled(true); m_x_reset->setEnabled(true); m_y_reset->setEnabled(true); m_z_reset->setEnabled(true); } else { SIGHT_ASSERT("Unknow type for light", false); } } else { m_visual_feedback->setEnabled(false); m_theta_slider->setEnabled(false); m_phi_slider->setEnabled(false); m_x_translation->setEnabled(false); m_y_translation->setEnabled(false); m_z_translation->setEnabled(false); m_x_reset->setEnabled(false); m_y_reset->setEnabled(false); m_z_reset->setEnabled(false); } m_visual_feedback->setChecked(m_current_light->is_visual_feedback_on()); m_theta_slider->setValue( static_cast(m_current_light->get_theta_offset() + float(light_adaptor::OFFSET_RANGE / 2.)) ); m_phi_slider->setValue( static_cast(m_current_light->get_phi_offset() + float(light_adaptor::OFFSET_RANGE / 2.)) ); Ogre::SceneNode* const root = m_current_light->layer()->get_scene_manager()->getRootSceneNode(); const Ogre::Node* const light_node = sight::viz::scene3d::helper::scene::get_node_by_id( m_current_light->get_transform_id(), root ); const Ogre::Vector3 current_pos = light_node->getPosition(); m_x_translation->setValue(static_cast(current_pos[0])); m_y_translation->setValue(static_cast(current_pos[1])); m_z_translation->setValue(static_cast(current_pos[2])); } else { m_diffuse_color_btn->setEnabled(false); m_specular_color_btn->setEnabled(false); m_theta_slider->setEnabled(false); m_phi_slider->setEnabled(false); m_light_type_box->setEnabled(false); m_visual_feedback->setEnabled(false); } } //------------------------------------------------------------------------------ Ogre::ColourValue light_editor::edit_color(const Ogre::ColourValue& _current_color, const std::string& _title) { auto qt_container = std::dynamic_pointer_cast( this->get_container() ); QWidget* const container = qt_container->get_qt_container(); QColor q_color = QColorDialog::getColor( module::ui::viz::helper::utils::convert_ogre_color_to_q_color(_current_color), container, _title.c_str() ); return module::ui::viz::helper::utils::convert_q_color_to_ogre_color(q_color); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/light_editor.hpp000066400000000000000000000146631503402212300206170ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { /** * @brief This service creates a user interface to manage a light adaptor. * * @section Slots Slots * - \b edit_light(viz::scene3d::light_adaptor::sptr): loads the editor with the parameters from the selected light. * * @section XML XML Configuration * * @code{.xml} * */ class light_editor : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(light_editor, sight::ui::editor); /// Creates the service. light_editor() noexcept; /// Destroys the service. ~light_editor() noexcept override; private: /// Configures the service. void configuring() final; /// Sets the connections and the UI elements. void starting() final; /// Does nothing. void updating() final; /// Destroys the connections and cleans the container. void stopping() final; /** * @brief Gets the current light node. * @return The node where the current light is attached. */ Ogre::Node* get_light_node() const; /** * @brief SLOT: sets the current light adaptor to edit. * @param _light_adaptor The light adaptor to edit. */ void edit_light(sight::viz::scene3d::light_adaptor::sptr _light_adaptor); /** * @brief Opens a QColorDialog to pick a new color that is returned. * @param _current_color the current light color. * @param _title the title of the dialog. */ Ogre::ColourValue edit_color(const Ogre::ColourValue& _current_color, const std::string& _title); /// Contains the name of the light. QPointer m_light_name_label; /// Contains a list of each possible light type. QPointer m_light_type_box; /// Contains a button to show or hide the visual feedback of the light. QPointer m_visual_feedback; /// Contains a button that manage the light diffuse color. QPointer m_diffuse_color_btn; /// Contains a button that manage the light specular color. QPointer m_specular_color_btn; /// Contains a slider used to edit the theta value of directional lights. QPointer m_theta_slider; /// Contains a slider used to edit the phi value of directional lights. QPointer m_phi_slider; /// Contains a slider used to edit the X translation value of directional lights. QPointer m_x_translation; QPointer m_x_label; QPointer m_x_reset; /// Contains a slider used to edit the Y translation value of directional lights. QPointer m_y_translation; QPointer m_y_label; QPointer m_y_reset; /// Contains a slider used to edit the Z translation value of directional lights. QPointer m_z_translation; QPointer m_z_label; QPointer m_z_reset; /// Contains the current selected light. sight::viz::scene3d::light_adaptor::sptr m_current_light; private Q_SLOTS: /** * @brief Opens a color picker and lets the user choose a new diffuse color. * @see m_diffuseColorBtn. */ void on_edit_diffuse_color(bool /*unused*/); /** * @brief Opens a color picker and lets the user choose a new specular color. * @see m_specularColorBtn. */ void on_edit_specular_color(bool /*unused*/); /** * @brief Sets the new theta offset value on the light adaptor accurately. * @param _value value of the current theta offset. * @see m_thetaSlider. */ void on_edit_theta_offset(int _value); /** * @brief the new phi offset value on the light adaptor accurately. * @param _value value of the current phi offset. * @see m_phiSlider. */ void on_edit_phi_offset(int _value); /** * @brief Sets the new type on the light adaptor accurately. * @param _type value of the current type. * @see m_lightTypeBox */ void on_edit_type(const QString& _type); /** * @brief Toggles the visual feedback of the light. * @param _enable value of the pressed button. * @see m_visualFeedback */ void on_toggle_feedback(bool _enable); /** * @brief Sets the new position on the light adaptor accurately. * @param _value value of the x translation type. * @see m_xTranslation */ void on_edit_x_translation(int _value); /** * @brief Sets the new position on the light adaptor accurately. * @param _value value of the y translation type. * @see m_yTranslation */ void on_edit_y_translation(int _value); /** * @brief Sets the new position on the light adaptor accurately. * @param _value value of the z translation type. * @see m_zTranslation */ void on_edit_z_translation(int _value); /** * @brief Reset the X translation of the light. * @see m_xReset. */ void on_reset_x_translation(bool /*unused*/); /** * @brief Reset the Y translation of the light. * @see m_yReset. */ void on_reset_y_translation(bool /*unused*/); /** * @brief Reset the Z translation of the light. * @see m_zReset. */ void on_reset_z_translation(bool /*unused*/); }; } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/light_selector.cpp000066400000000000000000000377641503402212300211530ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "light_selector.hpp" #include "module/ui/viz/helper/utils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { using sight::viz::scene3d::light_adaptor; using sight::viz::scene3d::layer; //------------------------------------------------------------------------------ const core::com::signals::key_t LIGHT_SELECTED_SIG = "light_selected"; const core::com::slots::key_t INIT_LIGHT_LIST_SLOT = "initLightList"; //------------------------------------------------------------------------------ light_selector::light_selector() noexcept { new_signal(LIGHT_SELECTED_SIG); new_slot(INIT_LIGHT_LIST_SLOT, &light_selector::init_light_list, this); } //------------------------------------------------------------------------------ light_selector::~light_selector() noexcept = default; //------------------------------------------------------------------------------ void light_selector::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void light_selector::starting() { this->create(); const QString service_id = QString::fromStdString(base_id()); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); qt_container->get_qt_container()->setObjectName(service_id); m_layers_box = new QComboBox(); m_layers_box->setObjectName(service_id + "/layersBox"); m_lights_list = new QListWidget(); m_lights_list->setObjectName(service_id + "/lightsList"); m_add_light_btn = new QPushButton("Add light"); m_add_light_btn->setObjectName(service_id + "/" + m_add_light_btn->text()); m_remove_light_btn = new QPushButton("Remove light"); m_remove_light_btn->setObjectName(service_id + "/" + m_remove_light_btn->text()); m_ambient_color_btn = new QPushButton("scene ambient color"); m_ambient_color_btn->setObjectName(service_id + "/" + m_ambient_color_btn->text()); auto* layout_button = new QHBoxLayout; m_check_all_button = new QPushButton(tr("Check all")); m_check_all_button->setObjectName(service_id + "/" + m_check_all_button->text()); layout_button->addWidget(m_check_all_button, 0); m_un_check_all_button = new QPushButton(tr("UnCheck all")); m_un_check_all_button->setObjectName(service_id + "/" + m_un_check_all_button->text()); layout_button->addWidget(m_un_check_all_button, 0); auto* layout = new QVBoxLayout(); layout->addWidget(m_layers_box); layout->addLayout(layout_button); layout->addWidget(m_lights_list); auto* add_remove_layout = new QHBoxLayout(); add_remove_layout->addWidget(m_add_light_btn); add_remove_layout->addWidget(m_remove_light_btn); m_remove_light_btn->setEnabled(false); layout->addLayout(add_remove_layout); layout->addWidget(m_ambient_color_btn); qt_container->set_layout(layout); this->refresh_layers(); this->update_lights_list(); QObject::connect( m_layers_box, qOverload(&QComboBox::activated), this, &light_selector::on_selected_layer_item ); QObject::connect(m_lights_list, &QListWidget::currentItemChanged, this, &light_selector::on_selected_light_item); QObject::connect(m_lights_list, &QListWidget::itemChanged, this, &light_selector::on_checked_light_item); QObject::connect(m_add_light_btn, &QPushButton::clicked, this, &light_selector::on_add_light); QObject::connect(m_remove_light_btn, &QPushButton::clicked, this, &light_selector::on_remove_light); QObject::connect(m_ambient_color_btn, &QPushButton::clicked, this, &light_selector::on_edit_ambient_color); QObject::connect(m_check_all_button, &QPushButton::clicked, this, &light_selector::on_check_all_check_box); QObject::connect(m_un_check_all_button, &QPushButton::clicked, this, &light_selector::on_un_check_all_check_box); } //------------------------------------------------------------------------------ void light_selector::updating() { } //------------------------------------------------------------------------------ void light_selector::stopping() { m_connections.disconnect(); for(auto& light : m_managed_light_adaptors) { light_adaptor::destroy_light_adaptor(light.m_light); } m_managed_light_adaptors.clear(); this->destroy(); } //------------------------------------------------------------------------------ void light_selector::on_selected_layer_item(int _index) { m_current_layer = m_layers[static_cast(_index)]; m_light_adaptors = m_current_layer.lock()->get_light_adaptors(); this->update_lights_list(); } //------------------------------------------------------------------------------ void light_selector::on_selected_light_item(QListWidgetItem* _item, QListWidgetItem* /*unused*/) { if(_item != nullptr) { m_current_light = this->retrieve_light_adaptor(_item->text().toStdString()); auto sig = this->signal(LIGHT_SELECTED_SIG); sig->async_emit(m_current_light); m_remove_light_btn->setEnabled(true); } } //------------------------------------------------------------------------------ void light_selector::on_checked_light_item(QListWidgetItem* _item) { light_adaptor::sptr checked_light_adaptor = this->retrieve_light_adaptor(_item->text().toStdString()); checked_light_adaptor->switch_on(_item->checkState() == Qt::Checked); } //------------------------------------------------------------------------------ void light_selector::on_add_light(bool /*unused*/) { auto qt_container = std::dynamic_pointer_cast( this->get_container() ); QWidget* const container = qt_container->get_qt_container(); auto* light_dialog = new module::ui::viz::new_light_dialog(container); if(light_dialog->exec() == QDialog::Accepted) { std::string light_name = light_dialog->property("lightName").toString().toStdString(); auto existing_light = std::find_if( m_light_adaptors.begin(), m_light_adaptors.end(), [light_name](light_adaptor::sptr _light_adaptor) { return _light_adaptor->get_name() == light_name; }); if(existing_light == m_light_adaptors.end()) { this->create_light_adaptor(light_name); } } } //------------------------------------------------------------------------------ void light_selector::on_remove_light(bool /*unused*/) { if(m_current_light) { layer::sptr current_layer = m_current_layer.lock(); const auto position = std::find_if( m_managed_light_adaptors.begin(), m_managed_light_adaptors.end(), [&](const light& _light) { return _light.m_light == m_current_light; }); if(position != m_managed_light_adaptors.end()) { m_managed_light_adaptors.erase(position); } if(current_layer->is_default_light(m_current_light)) { current_layer->remove_default_light(); } else { light_adaptor::destroy_light_adaptor(m_current_light); } m_current_light.reset(); m_light_adaptors = current_layer->get_light_adaptors(); this->update_lights_list(); m_remove_light_btn->setEnabled(false); auto sig = this->signal(LIGHT_SELECTED_SIG); sig->async_emit(nullptr); current_layer->request_render(); } } //------------------------------------------------------------------------------ void light_selector::on_edit_ambient_color(bool /*unused*/) { auto qt_container = std::dynamic_pointer_cast( this->get_container() ); QWidget* const container = qt_container->get_qt_container(); layer::sptr layer = m_current_layer.lock(); Ogre::ColourValue ogre_color = layer->get_scene_manager()->getAmbientLight(); QColor q_color = QColorDialog::getColor( module::ui::viz::helper::utils::convert_ogre_color_to_q_color(ogre_color), container, "scene ambient color" ); ogre_color = module::ui::viz::helper::utils::convert_q_color_to_ogre_color(q_color); layer->get_scene_manager()->setAmbientLight(ogre_color); layer->request_render(); } //------------------------------------------------------------------------------ void light_selector::init_light_list(layer::sptr _layer) { m_current_layer = m_layers[0]; if(_layer == m_current_layer.lock()) { this->on_selected_layer_item(0); } } //------------------------------------------------------------------------------ void light_selector::refresh_layers() { m_layers_box->clear(); const auto renderers = sight::service::get_services("sight::viz::scene3d::render"); // Fills layer combo box with all enabled layers of each render services for(const auto& srv : renderers) { auto render = std::dynamic_pointer_cast(srv); for(auto& layer_map : render->get_layers()) { const std::string id = layer_map.first; std::string render_id = render->get_id(); m_layers_box->addItem(QString::fromStdString(render_id + " : " + id)); m_layers.push_back(layer_map.second); m_connections.connect( layer_map.second, layer::INIT_LAYER_SIG, this->get_sptr(), INIT_LIGHT_LIST_SLOT ); } } // Default to the first layer if(!m_layers.empty()) { m_current_layer = m_layers[0]; m_light_adaptors = m_current_layer.lock()->get_light_adaptors(); } } //------------------------------------------------------------------------------ void light_selector::update_lights_list() { m_lights_list->clear(); for(const auto& light_adaptor : m_light_adaptors) { QString light_name = light_adaptor->get_name().c_str(); Qt::CheckState light_state = light_adaptor->is_switched_on() ? Qt::Checked : Qt::Unchecked; auto* next_light = new QListWidgetItem(light_name, m_lights_list); next_light->setFlags(next_light->flags() | Qt::ItemIsUserCheckable); next_light->setCheckState(light_state); } } //------------------------------------------------------------------------------ void light_selector::create_light_adaptor(const std::string& _name) { layer::sptr current_layer = m_current_layer.lock(); if(current_layer) { data::color::sptr light_diffuse_color = std::make_shared(); data::color::sptr light_specular_color = std::make_shared(); light_adaptor::sptr light_adaptor = light_adaptor::create_light_adaptor( light_diffuse_color, light_specular_color ); light_adaptor->set_type(Ogre::Light::LT_DIRECTIONAL); light_adaptor->set_layer_id(current_layer->layer_id()); light_adaptor->set_render_service(current_layer->render_service()); service::config_t config; config.add("config..name", this->get_id() + "_light"); config.add("config..layer", current_layer->layer_id()); light_adaptor->set_config(config); light_adaptor->configure(); light_adaptor->set_name(_name); light_adaptor->start(); m_managed_light_adaptors.push_back({light_adaptor, light_diffuse_color, light_specular_color}); m_light_adaptors = current_layer->get_light_adaptors(); this->update_lights_list(); const auto material_services = sight::service::get_services("sight::module::viz::scene3d::adaptor::material"); for(const auto& srv : material_services) { auto material_adaptor = std::dynamic_pointer_cast(srv); if(material_adaptor->layer_id() == current_layer->layer_id()) { // Update materials of the scene to take the new light into account material_adaptor->update(); } } } } //------------------------------------------------------------------------------ light_adaptor::sptr light_selector::retrieve_light_adaptor(const std::string& _name) const { auto it = std::find_if( m_light_adaptors.begin(), m_light_adaptors.end(), [_name](light_adaptor::sptr _light_adaptor) { return _light_adaptor->get_name() == _name; }); return it != m_light_adaptors.end() ? *it : nullptr; } //------------------------------------------------------------------------------ void light_selector::on_check_all_check_box() { this->on_check_all_boxes(true); } //------------------------------------------------------------------------------ void light_selector::on_un_check_all_check_box() { this->on_check_all_boxes(false); } //------------------------------------------------------------------------------ void light_selector::on_check_all_boxes(bool _visible) { for(int i = 0 ; i < m_lights_list->count() ; ++i) { auto* item = m_lights_list->item(i); item->setCheckState(_visible ? Qt::Checked : Qt::Unchecked); } } //------------------------------------------------------------------------------ new_light_dialog::new_light_dialog(QWidget* _parent) : QDialog(_parent), m_light_name_edit(new QLineEdit(this)) { m_light_name_lbl = new QLabel("Name :", this); m_ok_btn = new QPushButton("Ok", this); auto* light_name_layout = new QHBoxLayout(); light_name_layout->addWidget(m_light_name_lbl); light_name_layout->addWidget(m_light_name_edit); auto* new_light_layout = new QVBoxLayout(); new_light_layout->addLayout(light_name_layout); new_light_layout->addWidget(m_ok_btn); this->setWindowTitle("New light"); this->setModal(true); this->setLayout(new_light_layout); QObject::connect(m_ok_btn, &QPushButton::clicked, this, &new_light_dialog::on_ok_btn); } //------------------------------------------------------------------------------ new_light_dialog::~new_light_dialog() { QObject::disconnect(m_ok_btn, &QPushButton::clicked, this, &new_light_dialog::on_ok_btn); } //------------------------------------------------------------------------------ void new_light_dialog::on_ok_btn(bool /*unused*/) { if(!m_light_name_edit->text().isEmpty()) { this->setProperty("lightName", QVariant(m_light_name_edit->text())); this->accept(); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/light_selector.hpp000066400000000000000000000140421503402212300211400ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { /** * @brief User interface to select light adaptor from a selected layer or create a new one. * * @section Signals Signals * - \b light_selected(viz::scene3d::light_adaptor::sptr): Emitted when a light is selected in the list widget. * * @section Slots Slots * - \b initLightList(viz::scene3d::layer::sptr): populates the list of available light adaptors for the current * layer. * * @section XML XML Configuration * @code{.xml} * */ class light_selector final : public QObject, public sight::ui::editor { Q_OBJECT public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(light_selector, sight::ui::editor); /// Initializes signals and slots. light_selector() noexcept; /// Destroys the service. ~light_selector() noexcept override; protected: /// Configures the service. void configuring() override; /// Sets the connections and the UI elements. void starting() override; /// Does nothing. void updating() override; /// Destroys the connections and cleans the container. void stopping() override; private Q_SLOTS: /// SLOT: called when a layer is selected. /// Sets the current layer and initializes the light adaptors list. void on_selected_layer_item(int _index); /// SLOT: called when a light is selected. /// Loads the selected light parameters in the light editor. void on_selected_light_item(QListWidgetItem* _item, QListWidgetItem* _previous); /// SLOT: called when a light is checked. /// Switched on or off the light according to its current state. void on_checked_light_item(QListWidgetItem* _item); /// SLOT: called when the add light button is clicked. /// Adds a new light to the current scene. void on_add_light(bool /*unused*/); /// SLOT: called when the remove light button is clicked. /// Removes the selected light. void on_remove_light(bool /*unused*/); /// SLOT: called when the scene ambient color button is clicked. /// Opens a color picker and lets the user choose a new ambient color. void on_edit_ambient_color(bool /*unused*/); /// SLOT: called when the "check all" button is clicked. /// Call onCheckAllBoxes(true). void on_check_all_check_box(); /// SLOT: called when the "uncheck all" button is clicked. /// Call onCheckAllBoxes(false). void on_un_check_all_check_box(); private: using light_selected_signal_t = core::com::signal; /// Checks or unchecks all item in m_lightsList. void on_check_all_boxes(bool _visible); void init_light_list(sight::viz::scene3d::layer::sptr _layer); /// Retrieves all the layers from the application thanks to the render services. void refresh_layers(); /// Retrieves light adaptors used in the current layer and stores them in the list widget. void update_lights_list(); /// Creates a new light adaptor. void create_light_adaptor(const std::string& _name); /// Finds the light adaptor with the specified name. sight::viz::scene3d::light_adaptor::sptr retrieve_light_adaptor(const std::string& _name) const; QPointer m_layers_box; QPointer m_lights_list; QPointer m_check_all_button; QPointer m_un_check_all_button; QPointer m_add_light_btn; QPointer m_remove_light_btn; QPointer m_ambient_color_btn; std::vector m_layers; sight::viz::scene3d::layer::wptr m_current_layer; /// Stores all light adaptors (existing in the configuration and those created by this editor). std::vector m_light_adaptors; /// Stores a light adaptor and it's data to keep a reference on them. struct light { sight::viz::scene3d::light_adaptor::sptr m_light; data::color::sptr m_diffuse; data::color::sptr m_specular; }; /// Stores adaptors managed by this editor. std::vector m_managed_light_adaptors; /// Defines the current selected light. sight::viz::scene3d::light_adaptor::sptr m_current_light; /// Handles connections with the layer. core::com::helper::sig_slot_connection m_connections; }; //------------------------------------------------------------------------------ class new_light_dialog final : public QDialog { Q_OBJECT public: new_light_dialog(QWidget* _parent = nullptr); ~new_light_dialog() override; private Q_SLOTS: void on_ok_btn(bool _checked); private: QPointer m_light_name_lbl; QPointer m_light_name_edit; QPointer m_ok_btn; }; } // namespace sight::module::ui::viz. sight-25.1.0/module/ui/viz/material_selector.cpp000066400000000000000000000154321503402212300216260ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "material_selector.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { const core::com::signals::key_t material_selector::SELECTED_SIG = "selected"; static const std::string MATERIAL_RESOURCEGROUP_NAME = "materialsTemplate"; //------------------------------------------------------------------------------ material_selector::material_selector() noexcept { new_signal(SELECTED_SIG); } //------------------------------------------------------------------------------ void material_selector::configuring(const config_t& _config) { this->initialize(); const auto supported_materials_str = _config.get("config..materials", "Default"); std::vector supported_materials; boost::split(m_supported_materials, supported_materials_str, boost::is_any_of(",;")); } //------------------------------------------------------------------------------ void material_selector::starting() { this->create(); const QString service_id = QString::fromStdString(base_id()); auto qt_container = std::dynamic_pointer_cast(this->get_container()); qt_container->get_qt_container()->setObjectName(service_id); // Selection auto* current_material = new QLabel(); current_material->setText("Current material : "); m_material_box = new QComboBox(); m_material_box->setObjectName(service_id + "/materialBox"); std::pair elt; Ogre::ResourceManager::ResourceMapIterator iter = Ogre::MaterialManager::getSingleton().getResourceIterator(); while(iter.hasMoreElements()) { Ogre::ResourcePtr resource = iter.getNext(); const auto mat = std::dynamic_pointer_cast(resource); if(mat && m_supported_materials.contains(mat->getName())) { m_material_box->addItem(QString::fromStdString(mat->getName())); } } m_material_box->setCurrentIndex(0); auto* label_layout = new QHBoxLayout(); label_layout->addWidget(current_material); label_layout->addWidget(m_material_box); // Reload m_reload_button = new QPushButton("Reload"); m_reload_button->setObjectName(service_id + "/" + m_reload_button->text()); auto* layout = new QVBoxLayout(); layout->addLayout(label_layout); layout->addWidget(m_reload_button); qt_container->set_layout(layout); this->updating(); QObject::connect( m_material_box, SIGNAL(currentTextChanged(const QString&)), this, SLOT(on_selected_mode_item(const QString&)) ); QObject::connect(m_reload_button, SIGNAL(clicked()), this, SLOT(on_reload_material())); } //------------------------------------------------------------------------------ void material_selector::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void material_selector::updating() { this->update_material(); } //------------------------------------------------------------------------------ void material_selector::update_material() { const auto reconstruction = m_reconstruction.lock(); data::material::sptr material = reconstruction->get_material(); data::object::sptr field_obj = material->get_field("material"); if(field_obj != nullptr) { data::string::sptr field = std::dynamic_pointer_cast(field_obj); m_material_box->setCurrentText(field->value().c_str()); } } //------------------------------------------------------------------------------ void material_selector::on_selected_mode_item(const QString& _text) { const auto reconstruction = m_reconstruction.lock(); data::material::sptr material = reconstruction->get_material(); data::string::sptr string = std::make_shared(); string->set_value(_text.toStdString()); data::helper::field helper(material); helper.set_field("material", string); helper.notify(); auto sig = this->signal(SELECTED_SIG); sig->async_emit(_text.toStdString()); } //------------------------------------------------------------------------------ void material_selector::on_reload_material() { auto material_name = m_material_box->currentText().toStdString(); Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().getByName( material_name, sight::viz::scene3d::RESOURCE_GROUP ); if(!material) { SIGHT_ERROR("Could not find material" << std::quoted(material_name)); return; } material->reload(); const Ogre::Material::Techniques& techniques = material->getTechniques(); for(auto* const tech : techniques) { SIGHT_ASSERT("technique is not set", tech); const Ogre::Technique::Passes& passes = tech->getPasses(); for(auto* const pass : passes) { SIGHT_ASSERT("No pass found", pass); if(!pass->getVertexProgramName().empty()) { pass->getVertexProgram()->reload(); } if(!pass->getGeometryProgramName().empty()) { pass->getGeometryProgram()->reload(); } if(!pass->getFragmentProgramName().empty()) { pass->getFragmentProgram()->reload(); } } } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/material_selector.hpp000066400000000000000000000062641503402212300216360ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { /** * @brief Allows to select an Ogre material template and apply it to the current reconstruction. * * @section Signals Signals * - \b selected(std::string) : Send the selected material name. * @section XML XML Configuration * * @code{.xml} @endcode * @subsection In-Out In-Out: * - \b reconstruction [sight::data::object]: reconstruction used to select the material to change. */ /** * @brief */ class material_selector : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(material_selector, sight::ui::editor); /// Constructor. material_selector() noexcept; /// Destructor. Does nothing ~material_selector() noexcept override = default; /** * @name Signals API * @{ */ static const core::com::signals::key_t SELECTED_SIG; using selected_signal_t = core::com::signal; /** @} */ protected: /// Get the list of supported materials void configuring(const config_t& _config) override; /// Start the service. Create UI void starting() override; /// Stop the service. Delete UI void stopping() override; /// Update UI depending on current reconstruction void updating() override; protected Q_SLOTS: /// SLOT: Called when a material is selected void on_selected_mode_item(const QString& _text); /// SLOT: Called when the reload button is pushed void on_reload_material(); private: void update_material(); QPointer m_material_box; QPointer m_reload_button; /// Configurable list of material that will be exposed, if they are loaded std::set m_supported_materials {"Default"}; data::ptr m_reconstruction {this, "reconstruction"}; }; } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/plugin.cpp000066400000000000000000000026541503402212300174300ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2023 IRCAD France * Copyright (C) 2014-2017 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "plugin.hpp" #include namespace sight::module::ui::viz { SIGHT_REGISTER_PLUGIN("sight::module::ui::viz::plugin"); //----------------------------------------------------------------------------- plugin::~plugin() noexcept = default; //----------------------------------------------------------------------------- void plugin::start() { } //----------------------------------------------------------------------------- void plugin::stop() noexcept { } } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/plugin.hpp000066400000000000000000000027131503402212300174310ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "sight/module/ui/viz/config.hpp" #include namespace sight::module::ui::viz { /** * @brief This class is started when the module is loaded. */ struct SIGHT_MODULE_UI_VIZ_CLASS_API plugin : public core::runtime::plugin { /** * @brief destructor */ SIGHT_MODULE_UI_VIZ_API ~plugin() noexcept override; // Overrides SIGHT_MODULE_UI_VIZ_API void start() override; // Overrides SIGHT_MODULE_UI_VIZ_API void stop() noexcept override; }; } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/rc/000077500000000000000000000000001503402212300160235ustar00rootroot00000000000000sight-25.1.0/module/ui/viz/rc/plugin.xml000066400000000000000000000070031503402212300200430ustar00rootroot00000000000000 sight::ui::editor sight::module::ui::viz::compositor_parameter_editor Editor allowing to edit shader uniform from a compositor material. sight::ui::editor sight::module::ui::viz::compositor_selector sight::data::map Allows to select an Ogre Compositor and apply it to a layer sight::ui::editor sight::module::ui::viz::core_compositor_editor sight::data::map Allows to select an Ogre Compositor and apply it to a layer sight::ui::editor sight::module::ui::viz::light_editor sight::data::map This service creates a user interface to manage a light adaptor. sight::ui::editor sight::module::ui::viz::light_selector sight::data::map User interface to select light adaptor from a selected layer or create a new one. sight::ui::editor sight::module::ui::viz::material_selector sight::data::reconstruction Allows to select an Ogre material template and apply it to the current reconstruction. sight::ui::action sight::module::ui::viz::screen_selector Selects a screen and sends its index. sight::ui::editor sight::module::ui::viz::shader_parameter_editor sight::data::reconstruction Editor allowing to edit each parameters from each shader of a reconstruction sight::ui::editor sight::module::ui::viz::stereo_selector sight::data::map Allows to select the stereo mode of an Ogre Compositor sight::ui::action sight::module::ui::viz::stereo_toggler Action to enable/disable stereo in an ogre scene layer. sight::ui::editor sight::module::ui::viz::texture_selector sight::data::reconstruction Allows to select a data::image and apply it to the current reconstruction as an Ogre texture sight-25.1.0/module/ui/viz/screen_selector.cpp000066400000000000000000000125131503402212300213040ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2019-2025 IRCAD France * Copyright (C) 2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "screen_selector.hpp" #include #include #include #include #include #include #include namespace sight::module::ui::viz { static const core::com::signals::key_t SCREEN_SELECTED_SIG = "screen_selected"; //------------------------------------------------------------------------------ screen_selector::screen_selector() : m_screen_selected_sig(new_signal(SCREEN_SELECTED_SIG)) { } //------------------------------------------------------------------------------ screen_selector::~screen_selector() = default; //------------------------------------------------------------------------------ void screen_selector::configuring() { this->initialize(); const auto config_tree = this->get_config(); m_mode = config_tree.get("config..mode", m_mode); SIGHT_ERROR_IF( "Unknown selection mode '" + m_mode + "'.", m_mode != "current" && m_mode != "neighbor" && m_mode != "select" ); } //------------------------------------------------------------------------------ void screen_selector::starting() { this->action_service_starting(); } //------------------------------------------------------------------------------ void screen_selector::updating() { int screen_num = -1; if(m_mode == "select") { screen_num = sight::module::ui::viz::screen_selector::select_screen(); } else { const auto& screens = QGuiApplication::screens(); if(const auto* const active_window = qApp->activeWindow(); active_window) { if(const auto* const window_screen = QGuiApplication::screenAt(active_window->mapToGlobal(active_window->rect().center())); window_screen) { screen_num = int(screens.indexOf(window_screen)); if(m_mode == "neighbor") { ++screen_num; } } } if(screen_num >= screens.count()) { screen_num = int(screens.indexOf(QGuiApplication::primaryScreen())); } } if(screen_num >= 0) { m_screen_selected_sig->async_emit(screen_num); } } //------------------------------------------------------------------------------ void screen_selector::stopping() { this->action_service_stopping(); } //------------------------------------------------------------------------------ int screen_selector::select_screen() { QStringList screen_names; int screen_number = 0; if(QGuiApplication::screens().size() <= 1) { return 0; } for(QScreen* screen : QGuiApplication::screens()) { const QString number_str = QString::number(screen_number++) + "."; // Compute the screen's diagonal length in inches. constexpr qreal inches_per_millimeter = 0.03937008; const auto screen_size = screen->physicalSize(); const qreal diagonal_length_mm = std::sqrt( screen_size.width() * screen_size.width() + screen_size.height() * screen_size.height() ); const qreal diagonal_length_inches = diagonal_length_mm * inches_per_millimeter; const QString diagonal = QString::number(diagonal_length_inches, 'f', 1) + "\""; const auto geom = screen->geometry(); const QString resolution = "[" + QString::number(geom.width()) + "x" + QString::number(geom.height()) + "]"; QString display_name = screen->manufacturer() + " " + screen->model(); if(display_name.size() == 1) { display_name = screen->name(); } screen_names << number_str + " " + diagonal + " " + resolution + " " + display_name; } bool ok_clicked = false; QString selected_item = QInputDialog::getItem( nullptr, "Select a screen.", "Screen:", screen_names, 0, false, &ok_clicked ); std::int64_t ret_screen = -1; if(ok_clicked) { auto name_it = std::find(screen_names.cbegin(), screen_names.cend(), selected_item); if(name_it != screen_names.cend()) { ret_screen = std::distance(screen_names.cbegin(), name_it); } } return static_cast(ret_screen); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/screen_selector.hpp000066400000000000000000000054711503402212300213160ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2019-2024 IRCAD France * Copyright (C) 2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include namespace sight::module::ui::viz { /** * @brief Selects a screen and sends its index. * * This service is initially meant to choose a screen for fullscreen rendering. * * @section Signals Signals * - \b screen_selected(int): sends the selected screen's index. * * @section XML XML Configuration * * @code{.xml} * * @subsection Configuration Configuration * - \b mode (optional, values=select|current|neighbor): screen selection method: * - \b select: pop a dialog to let the user choose between all available screens (if there are more than one). * - \b current: select the screen on which the application is currently displayed. * - \b neighbor: select a neighboring screen if there is one. */ class screen_selector final : public sight::ui::action { public: SIGHT_DECLARE_SERVICE(screen_selector, sight::ui::action); /// Constructor. screen_selector(); /// Destructor. ~screen_selector() final; protected: /// Configures the selection mode. void configuring() final; /// Starts the action service. void starting() final; /// Selects a screen based on the selection mode. void updating() final; /// Stops the action service. void stopping() final; private: /// Type of signal sent when a screen is selected. using screen_selected_signal_t = core::com::signal; /// Prompts a dialog letting the user select a screen if there are multiple monitors. static int select_screen(); /// Signal sent when a screen is selected. screen_selected_signal_t::sptr m_screen_selected_sig; /// Selection mode can be 'select', 'current' or 'neighbor' (cf XML configuration). std::string m_mode {"select"}; }; } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/shader_parameter_editor.cpp000066400000000000000000000166161503402212300230110ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "shader_parameter_editor.hpp" #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { //------------------------------------------------------------------------------ void shader_parameter_editor::starting() { { const auto rec = m_reconstruction.lock(); data::material::sptr material = rec->get_material(); m_connections.connect(material, data::material::MODIFIED_SIG, this->get_sptr(), service::slots::UPDATE); } this->create(); const QString service_id = QString::fromStdString(base_id()); auto qt_container = std::dynamic_pointer_cast(this->get_container()); qt_container->get_qt_container()->setObjectName(service_id); m_sizer = new QVBoxLayout(); m_sizer->setContentsMargins(0, 0, 0, 0); qt_container->set_layout(m_sizer); this->updating(); } //------------------------------------------------------------------------------ void shader_parameter_editor::stopping() { m_connections.disconnect(); this->clear(); this->destroy(); } //------------------------------------------------------------------------------ void shader_parameter_editor::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void shader_parameter_editor::updating() { this->clear(); this->update_gui_info(); this->fill_gui(); } //------------------------------------------------------------------------------ void shader_parameter_editor::clear() { service::base::sptr obj_service = m_editor_info.srv.lock(); if(obj_service) { obj_service->stop(); sight::ui::registry::unregister_sid_container(m_editor_info.uuid); sight::service::remove(obj_service); m_sizer->removeWidget(m_editor_info.editor_panel->get_qt_container()); m_editor_info.editor_panel->destroy_container(); m_editor_info.editor_panel.reset(); } } //------------------------------------------------------------------------------ void shader_parameter_editor::update_gui_info() { /// Getting all Material adaptors const auto reconstruction = m_reconstruction.lock(); const auto srv_vec = sight::service::get_services("sight::module::viz::scene3d::adaptor::material"); /// Stop if no Material adaptors have been find if(srv_vec.empty()) { SIGHT_WARN("No module::viz::scene3d::adaptor::material found in the application"); return; } /// Try to find the material adaptor working with the same data::material /// as the one contained by the current reconstruction sight::viz::scene3d::adaptor::sptr mat_service; for(const auto& srv : srv_vec) { if(srv->inout("material").lock()->get_id() == reconstruction->get_material()->get_id()) { mat_service = std::dynamic_pointer_cast(srv); break; } } SIGHT_ASSERT("Material adaptor corresponding to the current Reconstruction must exist", mat_service); bool found = false; // Is there at least one parameter that we can handle ? for(const auto& w_param_srv : mat_service->get_registered_services()) { const auto param_srv = w_param_srv.lock(); if(param_srv->get_classname() == "sight::module::viz::scene3d::adaptor::shader_parameter") { /// filter object types const auto shader_obj = param_srv->inout(sight::viz::scene3d::parameter_adaptor::PARAMETER_INOUT).lock(); const object_classname_t obj_type = shader_obj->get_classname(); if(obj_type == "sight::data::boolean" || obj_type == "sight::data::real" || obj_type == "sight::data::integer" || obj_type == "sight::data::array" || obj_type == "sight::data::ivec2" || obj_type == "sight::data::dvec2" || obj_type == "sight::data::ivec3" || obj_type == "sight::data::dvec3" || obj_type == "sight::data::ivec4" || obj_type == "sight::data::dvec4") { found = true; break; } } } if(!found) { return; } /// Getting this widget's container auto qt_container = std::dynamic_pointer_cast(this->get_container()); QWidget* container = qt_container->get_qt_container(); auto* p2 = new QWidget(container); m_editor_info.editor_panel = sight::ui::qt::container::widget::make(); m_editor_info.editor_panel->set_qt_container(p2); const std::string uuid = this->get_id(); m_editor_info.uuid = uuid + "-editor"; sight::ui::registry::register_sid_container(m_editor_info.uuid, m_editor_info.editor_panel); auto editor_service = sight::service::add("sight::module::ui::qt::settings", m_editor_info.uuid); m_editor_info.srv = editor_service; service::config_t editor_config; // Get all ShaderParameter subservices from the corresponding Material adaptor std::size_t i = 0; for(const auto& w_adaptor : mat_service->get_registered_services()) { const auto adaptor = w_adaptor.lock(); if(adaptor->get_classname() == "sight::module::viz::scene3d::adaptor::shader_parameter") { auto param_adaptor = std::dynamic_pointer_cast(adaptor); auto param_config = module::ui::viz::helper::parameter_editor::create_config(param_adaptor); const auto obj = param_adaptor->inout(sight::viz::scene3d::parameter_adaptor::PARAMETER_INOUT).lock(); editor_service->set_inout(obj.get_shared(), "keys", true, false, i++); if(!param_config.empty()) { editor_config.add_child("ui.item", param_config); } } } editor_service->set_config(editor_config); editor_service->configure(); editor_service->start(); } //------------------------------------------------------------------------------ void shader_parameter_editor::fill_gui() { auto editor_service = m_editor_info.srv.lock(); if(editor_service) { m_sizer->addWidget(m_editor_info.editor_panel->get_qt_container(), 0); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/shader_parameter_editor.hpp000066400000000000000000000064621503402212300230140ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include namespace sight::module::ui::viz { /** * @brief Editor allowing to edit each parameters from each shader of a reconstruction * * @section XML XML Configuration * * @code{.xml} @endcode * @subsection In-Out In-Out: * - \b reconstruction [sight::data::reconstruction]: reconstruction whose paremeters should be edited. */ class shader_parameter_editor final : public sight::ui::editor { public: SIGHT_DECLARE_SERVICE(shader_parameter_editor, sight::ui::editor); using editor_implementation_t = std::string; using object_classname_t = std::string; using object_id = std::string; using editor_map_t = std::map; /// Constructor. shader_parameter_editor() noexcept = default; /// Destructor. Destroy UI ~shader_parameter_editor() noexcept final = default; protected: /// Initialize the container void configuring() final; /// Start the service, slot connections, widget initialization . void starting() final; /// Destroy the service and the container. void stopping() final; /// Update the interface. void updating() final; private: /// Clear the current container void clear(); /// Retrieves the shader parameters attached to the Reconstruction object and stores them into a collection void update_gui_info(); /// Instanciates the needed ui editors according to the stored informations void fill_gui(); /// Internal class that contain the informations concerning the editor that is created. struct shader_editor_info { std::string uuid; sight::ui::qt::container::widget::sptr editor_panel; service::base::wptr srv; }; shader_editor_info m_editor_info; /// Connection to the material core::com::helper::sig_slot_connection m_connections; QVBoxLayout* m_sizer {}; data::ptr m_reconstruction {this, "reconstruction"}; }; } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/stereo_selector.cpp000066400000000000000000000111071503402212300213240ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "stereo_selector.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { //------------------------------------------------------------------------------ stereo_selector::stereo_selector() noexcept = default; //------------------------------------------------------------------------------ stereo_selector::~stereo_selector() noexcept = default; //------------------------------------------------------------------------------ void stereo_selector::starting() { this->create(); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); m_layers_box = new QComboBox(); m_mode_box = new QComboBox(); auto* layout = new QHBoxLayout(); layout->addWidget(m_layers_box); layout->addWidget(m_mode_box); qt_container->set_layout(layout); this->refresh_renderers(); m_mode_box->addItem("Mono"); m_mode_box->addItem("Stereo 5 views"); m_mode_box->addItem("Stereo 8 views"); auto layer = m_current_layer.lock(); if(layer) { m_mode_box->setCurrentIndex(static_cast(m_current_layer.lock()->get_stereo_mode())); } QObject::connect(m_layers_box, SIGNAL(activated(int)), this, SLOT(on_selected_layer_item(int))); QObject::connect(m_mode_box, SIGNAL(activated(int)), this, SLOT(on_selected_mode_item(int))); } //------------------------------------------------------------------------------ void stereo_selector::stopping() { this->destroy(); } //------------------------------------------------------------------------------ void stereo_selector::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void stereo_selector::updating() { } //--------------------------------------------------------------------------C--- void stereo_selector::on_selected_layer_item(int _index) { // Update the current layer m_current_layer = m_layers[static_cast(_index)]; } //------------------------------------------------------------------------------ void stereo_selector::on_selected_mode_item(int _index) { using sight::viz::scene3d::compositor::core; m_current_layer.lock()->set_stereo_mode( _index == 1 ? core::stereo_mode_t::autostereo_5 : _index == 2 ? core::stereo_mode_t::autostereo_8 : core::stereo_mode_t::none ); } //------------------------------------------------------------------------------ void stereo_selector::refresh_renderers() { m_layers_box->clear(); // Fill layer box with all enabled layers const auto renderers = sight::service::get_services("sight::viz::scene3d::render"); for(const auto& srv : renderers) { auto render = std::dynamic_pointer_cast(srv); for(auto& layer_map : render->get_layers()) { const std::string& id = layer_map.first; if(id != sight::viz::scene3d::render::render::layer::BACKGROUND) { m_layers_box->addItem(QString::fromStdString(id)); m_layers.push_back(layer_map.second); } } } if(!m_layers.empty()) { m_current_layer = m_layers[0]; } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/stereo_selector.hpp000066400000000000000000000051761503402212300213420ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include namespace sight::module::ui::viz { /** * @brief Allows to select the stereo mode of an Ogre Compositor */ class stereo_selector : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(stereo_selector, sight::ui::editor); /// Constructor. stereo_selector() noexcept; /// Destructor. Does nothing ~stereo_selector() noexcept override; protected: /** * @brief method description: * @code{.xml} param @endcode * - \b Parameter : parameter description. */ void configuring() override; /// Sets the connections and the UI elements void starting() override; /// Destroys the connections and cleans the container void stopping() override; /// Does nothing void updating() override; private Q_SLOTS: /// Slot: called when a layer is selected /// Sets the current layer void on_selected_layer_item(int _index); /// Slot: called when a mode is selected void on_selected_mode_item(int _index); private: /// Retrieves all the layers from the application thanks to the render services void refresh_renderers(); QPointer m_layers_box; QPointer m_mode_box; std::vector m_layers; sight::viz::scene3d::layer::wptr m_current_layer; }; } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/stereo_toggler.cpp000066400000000000000000000074231503402212300211550ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2018-2023 IRCAD France * Copyright (C) 2018-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "stereo_toggler.hpp" #include #include namespace sight::module::ui::viz { static const core::com::signals::key_t STEREO_ACTIVE_SIG = "stereoActive"; //------------------------------------------------------------------------------ stereo_toggler::stereo_toggler() : m_stereo_active_sig(new_signal(STEREO_ACTIVE_SIG)) { } //------------------------------------------------------------------------------ stereo_toggler::~stereo_toggler() = default; //------------------------------------------------------------------------------ void stereo_toggler::configuring() { this->initialize(); const config_t config = this->get_config().get_child("config."); m_layer_id = config.get("layer"); SIGHT_ASSERT("Empty layer ID.", !m_layer_id.empty()); const std::string stereo_mode = config.get("stereoMode", ""); if(stereo_mode == "interlaced") { m_stereo_mode = stereo_mode_t::stereo; } else if(stereo_mode == "AutoStereo5") { m_stereo_mode = stereo_mode_t::autostereo_5; } else if(stereo_mode == "AutoStereo8") { m_stereo_mode = stereo_mode_t::autostereo_8; } else { SIGHT_ERROR("Unknown stereo mode: '" + stereo_mode + "'. stereo_toggler will do nothing."); } } //------------------------------------------------------------------------------ void stereo_toggler::starting() { this->action_service_starting(); } //------------------------------------------------------------------------------ void stereo_toggler::updating() { if(this->confirm_action()) { const auto renderers = sight::service::get_services("sight::viz::scene3d::render"); const bool enable_stereo = this->checked() && this->enabled(); const auto stereo_mode = enable_stereo ? m_stereo_mode : stereo_mode_t::none; for(const auto& srv : renderers) { auto render_srv = std::dynamic_pointer_cast(srv); auto layer_map = render_srv->get_layers(); auto layer_it = layer_map.find(m_layer_id); if(layer_it != layer_map.end()) { auto& layer = layer_it->second; layer->set_stereo_mode(stereo_mode); layer->request_render(); } else { SIGHT_WARN("No layer named '" + m_layer_id + "' in render service '" + render_srv->get_id() + "'."); } } m_stereo_active_sig->async_emit(enable_stereo); } } //------------------------------------------------------------------------------ void stereo_toggler::stopping() { this->action_service_stopping(); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/stereo_toggler.hpp000066400000000000000000000053001503402212300211520ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2018-2024 IRCAD France * Copyright (C) 2018-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::ui::viz { /** * @brief Action to enable/disable stereo in an ogre scene layer. * * @section Signals Signals * - \b stereoActive(bool): notifies if stereo has been enabled. * * @section XML XML Configuration * * @code{.xml} * * @subsection Configuration Configuration * - \b stereoMode (values=interlaced|AutoStereo5|AutoStereo8): mode to activate. 'auto_stereo' modes are * for glasses-free stereo screens with N viewpoints. The 'interlaced' mode is for typical polarized screens with * interlaced image pairs. * */ class stereo_toggler : public sight::ui::action { public: SIGHT_DECLARE_SERVICE(stereo_toggler, sight::ui::action); /// Constructor. stereo_toggler(); /// Destructor. ~stereo_toggler() override; protected: /// Configures the service. void configuring() override; /// Starts the action service. void starting() override; /// Enables stereo if active, disables it otherwise. void updating() override; /// Stops the action service. void stopping() override; private: using stereo_mode_t = sight::viz::scene3d::compositor::core::stereo_mode_t; using stereo_active_sig_t = core::com::signal; /// layer in which we enable/disable stereo. std::string m_layer_id; /// Mode that is toggled. sight::viz::scene3d::compositor::core::stereo_mode_t m_stereo_mode {stereo_mode_t::none}; /// Sent at each update, notifies if stereo is enabled. stereo_active_sig_t::sptr m_stereo_active_sig; }; } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/test/000077500000000000000000000000001503402212300163765ustar00rootroot00000000000000sight-25.1.0/module/ui/viz/test/ut/000077500000000000000000000000001503402212300170265ustar00rootroot00000000000000sight-25.1.0/module/ui/viz/test/ut/CMakeLists.txt000066400000000000000000000010401503402212300215610ustar00rootroot00000000000000sight_add_target(module_ui_viz_ut TYPE TEST) find_package(Qt6 QUIET COMPONENTS Gui REQUIRED) target_link_libraries(${SIGHT_TARGET} PRIVATE Qt6::Gui) set_target_properties(${SIGHT_TARGET} PROPERTIES AUTOMOC TRUE) target_compile_definitions(${SIGHT_TARGET} PUBLIC "QT_NO_KEYWORDS") target_include_directories(${SIGHT_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/module/ui/viz/include) add_dependencies(${SIGHT_TARGET} data geometry_data module_service module_ui module_ui_viz) target_link_libraries(${SIGHT_TARGET} PUBLIC core utest_data viz_scene3d) sight-25.1.0/module/ui/viz/test/ut/utils_test.cpp000066400000000000000000000052061503402212300217340ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2017 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "utils_test.hpp" #include "../../../../../module/ui/viz/helper/utils.hpp" // Registers the fixture into the 'registry' CPPUNIT_TEST_SUITE_REGISTRATION(sight::module::ui::viz::ut::utils_test); namespace sight::module::ui::viz::ut { //------------------------------------------------------------------------------ void utils_test::setUp() { } //------------------------------------------------------------------------------ void utils_test::tearDown() { } //------------------------------------------------------------------------------ void utils_test::convert_ogre_color_to_q_color() { QColor ref_color(255, 255, 255); QColor result_color = module::ui::viz::helper::utils::convert_ogre_color_to_q_color(Ogre::ColourValue()); CPPUNIT_ASSERT(result_color.red() == ref_color.red()); CPPUNIT_ASSERT(result_color.green() == ref_color.green()); CPPUNIT_ASSERT(result_color.blue() == ref_color.blue()); CPPUNIT_ASSERT(result_color.alpha() == ref_color.alpha()); } //------------------------------------------------------------------------------ void utils_test::convert_q_color_to_ogre_color() { Ogre::ColourValue ref_color; Ogre::ColourValue result_color = module::ui::viz::helper::utils::convert_q_color_to_ogre_color(QColor(255, 255, 255)); CPPUNIT_ASSERT(static_cast(result_color.r) == static_cast(ref_color.r)); CPPUNIT_ASSERT(static_cast(result_color.g) == static_cast(ref_color.g)); CPPUNIT_ASSERT(static_cast(result_color.b) == static_cast(ref_color.b)); CPPUNIT_ASSERT(static_cast(result_color.a) == static_cast(ref_color.a)); } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz::ut sight-25.1.0/module/ui/viz/test/ut/utils_test.hpp000066400000000000000000000026511503402212300217420ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2017 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include namespace sight::module::ui::viz::ut { class utils_test : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE(utils_test); CPPUNIT_TEST(convert_ogre_color_to_q_color); CPPUNIT_TEST(convert_q_color_to_ogre_color); CPPUNIT_TEST_SUITE_END(); public: // interface void setUp() override; void tearDown() override; static void convert_ogre_color_to_q_color(); static void convert_q_color_to_ogre_color(); }; } // namespace sight::module::ui::viz::ut sight-25.1.0/module/ui/viz/texture_selector.cpp000066400000000000000000000130661503402212300215310ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "texture_selector.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::ui::viz { //------------------------------------------------------------------------------ texture_selector::texture_selector() noexcept = default; //------------------------------------------------------------------------------ texture_selector::~texture_selector() noexcept = default; //------------------------------------------------------------------------------ void texture_selector::starting() { this->create(); const QString service_id = QString::fromStdString(base_id()); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); qt_container->get_qt_container()->setObjectName(service_id); m_load_button = new QPushButton(QString("Load texture")); m_load_button->setObjectName(service_id + "/" + m_load_button->text()); m_load_button->setToolTip(QString("Selected organ's texture")); m_load_button->setMinimumSize(m_load_button->sizeHint()); m_delete_button = new QPushButton(QString("Remove texture")); m_delete_button->setObjectName(service_id + "/" + m_delete_button->text()); m_delete_button->setToolTip(QString("Remove organ's texture")); m_delete_button->setMinimumSize(m_delete_button->sizeHint()); auto* layout = new QVBoxLayout(); layout->addWidget(m_load_button, 0); layout->addWidget(m_delete_button, 0); qt_container->set_layout(layout); qt_container->set_enabled(true); QObject::connect(m_load_button, &QPushButton::clicked, this, &self_t::on_load_button); QObject::connect(m_delete_button, &QPushButton::clicked, this, &self_t::on_delete_button); this->updating(); } //------------------------------------------------------------------------------ void texture_selector::stopping() { QObject::disconnect(m_load_button, &QPushButton::clicked, this, &self_t::on_load_button); QObject::disconnect(m_delete_button, &QPushButton::clicked, this, &self_t::on_delete_button); this->destroy(); } //------------------------------------------------------------------------------ void texture_selector::configuring() { this->initialize(); } //------------------------------------------------------------------------------ void texture_selector::updating() { } //------------------------------------------------------------------------------ void texture_selector::on_load_button() { const auto reconstruction = m_reconstruction.lock(); SIGHT_ASSERT("No associated Reconstruction", reconstruction); data::material::sptr material = reconstruction->get_material(); data::image::sptr image = material->get_diffuse_texture(); bool existing_texture = (image != nullptr); // We have to instantiate a new image if necessary if(!existing_texture) { image = std::make_shared(); material->set_diffuse_texture(image); } auto srv = sight::service::add("sight::module::ui::io::selector"); srv->set_inout(image, io::service::DATA_KEY); srv->configure(); srv->start(); srv->update(); srv->stop(); sight::service::remove(srv); // If we didn't have to create a new texture, we can notify the associated image if(existing_texture) { auto sig = image->signal(data::object::MODIFIED_SIG); sig->emit(); } else { auto sig = material->signal( data::material::ADDED_TEXTURE_SIG ); sig->emit(image); } } //------------------------------------------------------------------------------ void texture_selector::on_delete_button() { const auto reconstruction = m_reconstruction.lock(); SIGHT_ASSERT("No associated Reconstruction", reconstruction); data::material::sptr material = reconstruction->get_material(); data::image::sptr image = material->get_diffuse_texture(); if(image) { material->set_diffuse_texture(nullptr); auto sig = material->signal( data::material::REMOVED_TEXTURE_SIG ); sig->emit(image); } } //------------------------------------------------------------------------------ } // namespace sight::module::ui::viz sight-25.1.0/module/ui/viz/texture_selector.hpp000066400000000000000000000047761503402212300215460ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include class QPushButton; namespace sight::module::ui::viz { /** * @brief Allows to select a data::image and apply it to the current reconstruction as an Ogre texture * * @section XML XML Configuration * * @code{.xml} @endcode * @subsection In-Out In-Out: * - \b reconstruction [sight::data::reconstruction]: reconstruction where the texture should be applied. */ class texture_selector : public QObject, public sight::ui::editor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(texture_selector, sight::ui::editor); texture_selector() noexcept; ~texture_selector() noexcept override; protected: /** * @brief method description: * @code{.xml} @endcode */ void configuring() override; /// Start the service. Create UI void starting() override; /// Stop the service. Delete UI void stopping() override; /// Do nothing void updating() override; protected Q_SLOTS: void on_load_button(); void on_delete_button(); private: QPointer m_load_button; QPointer m_delete_button; data::ptr m_reconstruction {this, "reconstruction"}; }; } // namespace sight::module::ui::viz sight-25.1.0/module/viz/000077500000000000000000000000001503402212300150025ustar00rootroot00000000000000sight-25.1.0/module/viz/CMakeLists.txt000066400000000000000000000002111503402212300175340ustar00rootroot00000000000000add_subdirectory(scene2d) add_subdirectory(scene3d) add_subdirectory(scene3d_qt) add_subdirectory(scene3d_test) add_subdirectory(sample) sight-25.1.0/module/viz/sample/000077500000000000000000000000001503402212300162635ustar00rootroot00000000000000sight-25.1.0/module/viz/sample/CMakeLists.txt000066400000000000000000000003021503402212300210160ustar00rootroot00000000000000sight_add_target(module_viz_sample TYPE MODULE) add_dependencies(module_viz_sample module_viz_scene3d module_viz_scene3d_qt module_ui_qt) target_link_libraries(module_viz_sample PUBLIC ui_qt) sight-25.1.0/module/viz/sample/README.md000066400000000000000000000006701503402212300175450ustar00rootroot00000000000000# sight::module::viz::sample Module containing sample user interface widgets displaying sight data. ## Services - **image**: renders a 3D negatoscope of a medical image. - **mesh**: renders a 3D mesh. ## How to use it ### CMake ```cmake add_dependencies(my_target module_viz_sample ... ) ``` ### XML Please consult the [doxygen](https://sight.pages.ircad.fr/sight) of each service to learn more about its use in xml configurations. sight-25.1.0/module/viz/sample/image.cpp000066400000000000000000000104541503402212300200550ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2024 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/sample/image.hpp" #include #include #include namespace sight::module::viz::sample { //------------------------------------------------------------------------------ image::image() noexcept = default; //------------------------------------------------------------------------------ image::~image() noexcept = default; //------------------------------------------------------------------------------ void image::configuring() { this->sight::ui::service::initialize(); } //------------------------------------------------------------------------------ void image::starting() { this->sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast( this->get_container() ); const auto generic_scene_id = this->get_id() + "-genericScene"; sight::ui::registry::register_sid_container(generic_scene_id, qt_container); // create and register the render service service::config_t render_config; render_config.put("scene.background..color", "#36393E"); render_config.put("scene.layer..id", "default"); render_config.put("scene.layer..order", "1"); render_config.put("scene.layer..transparency", ""); service::config_t interactor_cfg; interactor_cfg.put(".uid", this->get_id() + "interactorAdaptor"); service::config_t negato_cfg; negato_cfg.put(".uid", this->get_id() + "negato3DAdaptor"); render_config.add_child("scene.layer.adaptor", interactor_cfg); render_config.add_child("scene.layer.adaptor", negato_cfg); m_render_srv = sight::service::add("sight::viz::scene3d::render"); m_render_srv->set_config(render_config); m_render_srv->set_id(generic_scene_id); m_render_srv->configure(); m_interactor_srv = sight::service::add("sight::module::viz::scene3d::adaptor::trackball_camera"); m_interactor_srv->set_id(this->get_id() + "interactorAdaptor"); m_interactor_srv->configure(); // Create default transfer function m_tf = data::transfer_function::create_default_tf(); auto image = m_image.lock(); service::config_t negato_config; negato_config.put("config..interactive", "true"); m_negato_srv = sight::service::add("sight::module::viz::scene3d::adaptor::negato3d"); m_negato_srv->set_config(negato_config); m_negato_srv->set_input(image.get_shared(), "image", true); m_negato_srv->set_inout(m_tf, "tf", true); m_negato_srv->set_id(this->get_id() + "negato3DAdaptor"); m_negato_srv->configure(); m_render_srv->start().wait(); m_interactor_srv->start().wait(); m_negato_srv->start().wait(); } //------------------------------------------------------------------------------ void image::updating() { } //------------------------------------------------------------------------------ void image::stopping() { m_negato_srv->stop().wait(); m_interactor_srv->stop().wait(); m_render_srv->stop().wait(); sight::ui::registry::unregister_sid_container(this->get_id() + "-genericScene"); sight::service::remove(m_negato_srv); sight::service::remove(m_interactor_srv); sight::service::remove(m_render_srv); m_negato_srv.reset(); m_interactor_srv.reset(); m_render_srv.reset(); m_tf.reset(); this->destroy(); } } // namespace sight::module::viz::sample. sight-25.1.0/module/viz/sample/image.hpp000066400000000000000000000046041503402212300200620ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::viz::sample { /** * @brief Render a 3D negatoscope of a medical image. * * @section XML XML Configuration * @code{.xml} * * @subsection Input Input * - \b image [sight::data::image]: image to display. */ class image : public sight::ui::service { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(image, sight::ui::service); /// Creates the service. image() noexcept; /// Destroys the service. ~image() noexcept override; protected: /// Checks if the input image is autoconnect. void configuring() override; /// Registers and starts sub-services. void starting() override; /// Does nothing. void updating() override; /// Stops and unregisters sub-services. void stopping() override; private: /// Contains the render service. service::base::sptr m_render_srv {nullptr}; /// Contains the trackball interactor adaptor. service::base::sptr m_interactor_srv {nullptr}; /// Contains the negato adaptor. service::base::sptr m_negato_srv {nullptr}; /// Default transfer function data::transfer_function::sptr m_tf; data::ptr m_image {this, "image"}; }; } // namespace sight::module::viz::sample. sight-25.1.0/module/viz/sample/mesh.cpp000066400000000000000000000135001503402212300177220ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2025 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/sample/mesh.hpp" #include #include #include #include namespace sight::module::viz::sample { const core::com::slots::key_t mesh::UPDATE_CAM_POSITION_SLOT = "update_cam_position"; static const core::com::slots::key_t UPDATE_CAM_TRANSFORM_SLOT = "update_cam_transform"; const core::com::signals::key_t mesh::CAM_UPDATED_SIG = "cam_updated"; //------------------------------------------------------------------------------ mesh::mesh() noexcept { new_slot(UPDATE_CAM_POSITION_SLOT, &mesh::update_cam_position, this); new_slot(UPDATE_CAM_TRANSFORM_SLOT, &mesh::update_cam_transform, this); new_signal(CAM_UPDATED_SIG); } //------------------------------------------------------------------------------ void mesh::configuring() { this->sight::ui::service::initialize(); } //------------------------------------------------------------------------------ void mesh::starting() { this->sight::ui::service::create(); auto qt_container = std::dynamic_pointer_cast(this->get_container()); const auto generic_scene_id = this->get_id() + "-genericScene"; sight::ui::registry::register_sid_container(generic_scene_id, qt_container); auto mesh = m_mesh.lock(); // create and register the render service // create the frame configuration service::config_t render_config; render_config.put("scene.background..color", "#36393E"); render_config.put("scene.layer..id", "default"); render_config.put("scene.layer..order", "1"); service::config_t interactor_cfg; interactor_cfg.put(".uid", this->get_id() + "interactorAdaptor"); service::config_t negato_cfg; negato_cfg.put(".uid", this->get_id() + "meshAdaptor"); service::config_t camera_cfg; camera_cfg.put(".uid", this->get_id() + "cameraAdaptor"); render_config.add_child("scene.layer.adaptor", interactor_cfg); render_config.add_child("scene.layer.adaptor", negato_cfg); render_config.add_child("scene.layer.adaptor", camera_cfg); m_render_srv = sight::service::add("sight::viz::scene3d::render"); m_render_srv->set_config(render_config); m_render_srv->set_id(generic_scene_id); m_render_srv->configure(); service::config_t interactor_config; m_interactor_srv = sight::service::add("sight::module::viz::scene3d::adaptor::trackball_camera"); m_interactor_srv->set_id(this->get_id() + "interactorAdaptor"); m_interactor_srv->configure(); service::config_t mesh_cfg; mesh_cfg.put("config..autoresetcamera", true); m_mesh_srv = sight::service::add("sight::module::viz::scene3d::adaptor::mesh"); m_mesh_srv->set_config(mesh_cfg); m_mesh_srv->set_input(std::const_pointer_cast(mesh->get_const_sptr()), "mesh", true); m_mesh_srv->set_id(this->get_id() + "meshAdaptor"); m_mesh_srv->configure(); m_camera_transform = std::make_shared(); m_connections.connect( m_camera_transform, data::object::MODIFIED_SIG, this->get_sptr(), UPDATE_CAM_TRANSFORM_SLOT ); m_camera_srv = sight::service::add("sight::module::viz::scene3d::adaptor::camera"); m_camera_srv->set_inout(m_camera_transform->get_sptr(), "transform", true); m_camera_srv->set_id(this->get_id() + "cameraAdaptor"); m_camera_srv->configure(); m_render_srv->start().wait(); m_interactor_srv->start().wait(); m_camera_srv->start().wait(); m_mesh_srv->start().wait(); } //------------------------------------------------------------------------------ void mesh::updating() { } //------------------------------------------------------------------------------ void mesh::stopping() { m_camera_srv->stop().wait(); m_mesh_srv->stop().wait(); m_interactor_srv->stop().wait(); m_render_srv->stop().wait(); sight::ui::registry::unregister_sid_container(this->get_id() + "-genericScene"); sight::service::remove(m_camera_srv); sight::service::remove(m_mesh_srv); sight::service::remove(m_interactor_srv); sight::service::remove(m_render_srv); m_camera_srv.reset(); m_mesh_srv.reset(); m_interactor_srv.reset(); m_render_srv.reset(); m_connections.disconnect(); m_camera_transform.reset(); this->destroy(); } //------------------------------------------------------------------------------ void mesh::update_cam_position(data::matrix4::sptr _transform) { m_camera_transform->shallow_copy(_transform); m_camera_srv->slot("transform")->async_run(); } //------------------------------------------------------------------------------ void mesh::update_cam_transform() { this->async_emit(this, CAM_UPDATED_SIG, m_camera_transform); } //------------------------------------------------------------------------------ } // namespace sight::module::viz::sample. sight-25.1.0/module/viz/sample/mesh.hpp000066400000000000000000000064361503402212300177410ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::viz::sample { /** * @brief Render a 3D mesh. * * @section Slots Slots * - \b update_cam_position(): update the camera position. * - \b update_cam_transform(): send the signal cam_updated(). * * @section Signals Signals * - \b cam_updated(): emitted when the camera move. * * @section XML XML Configuration * @code{.xml} * * @subsection Input Input * - \b mesh [sight::data::mesh]: mesh to display. */ class mesh : public sight::ui::service { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(mesh, sight::ui::service); static const core::com::slots::key_t UPDATE_CAM_POSITION_SLOT; static const core::com::signals::key_t CAM_UPDATED_SIG; using cam_updated_signal_t = core::com::signal; /// Creates slots and the signal. mesh() noexcept; /// Destroys the service. ~mesh() noexcept override = default; private: /// Checks if the input mesh is autoconnect. void configuring() override; /// Registers and starts sub-services. void starting() override; /// Does nothing. void updating() override; /// Stops and unregisters sub-services. void stopping() override; private: /// SLOT: receives new camera transform and update the camera. void update_cam_position(data::matrix4::sptr _transform); /// SLOT: receives new camera transform from the camera service and trigger the signal. void update_cam_transform(); /// Contains the render service. sight::service::base::sptr m_render_srv; /// Contains the trackball interactor adaptor. service::base::sptr m_interactor_srv; /// Contains the mesh adaptor. service::base::sptr m_mesh_srv; /// Contains the camera adaptor. service::base::sptr m_camera_srv; /// Contains the transformation adaptor. data::matrix4::sptr m_camera_transform; /// Stores connection with the camera transform. core::com::helper::sig_slot_connection m_connections; data::ptr m_mesh {this, "mesh"}; }; } // namespace sight::module::viz::sample. sight-25.1.0/module/viz/sample/plugin.cpp000066400000000000000000000037171503402212300202750ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2023 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/sample/plugin.hpp" #include namespace sight::module::viz::sample { //------------------------------------------------------------------------------ SIGHT_REGISTER_PLUGIN("sight::module::viz::sample::plugin"); //------------------------------------------------------------------------------ plugin::~plugin() noexcept = default; //------------------------------------------------------------------------------ void plugin::start() { // This module is intended to be used for the first tutorials. // Thus we load the adaptors model here instead in the .xml of the // application to hide the complexity. auto module = core::runtime::load_module("sight::module::viz::scene3d"); SIGHT_FATAL_IF("'sight::module::viz::scene3d' module failed to load.", !module); } //------------------------------------------------------------------------------ void plugin::stop() noexcept { } //------------------------------------------------------------------------------ } // namespace sight::module::viz::sample. sight-25.1.0/module/viz/sample/plugin.hpp000066400000000000000000000027631503402212300203020ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "sight/module/viz/sample/config.hpp" #include namespace sight::module::viz::sample { /// This class is started when the module is loaded. class SIGHT_MODULE_VIZ_SAMPLE_CLASS_API plugin final : public core::runtime::plugin { public: /// Destroys the plugin. ~plugin() noexcept override; /// Creates the Ogre log manager. SIGHT_MODULE_VIZ_SAMPLE_API void start() override; /// Stops the plugin, destroys Ogre resources. SIGHT_MODULE_VIZ_SAMPLE_API void stop() noexcept override; }; } // namespace sight::module::viz::sample. sight-25.1.0/module/viz/sample/rc/000077500000000000000000000000001503402212300166675ustar00rootroot00000000000000sight-25.1.0/module/viz/sample/rc/plugin.xml000066400000000000000000000014651503402212300207150ustar00rootroot00000000000000 sight::ui::container sight::module::viz::sample::image sight::data::image Render a 3D negatoscope of a medical image. sight::ui::container sight::module::viz::sample::mesh sight::data::mesh Render a 3D mesh. sight-25.1.0/module/viz/scene2d/000077500000000000000000000000001503402212300163255ustar00rootroot00000000000000sight-25.1.0/module/viz/scene2d/CMakeLists.txt000066400000000000000000000007721503402212300210730ustar00rootroot00000000000000sight_add_target(module_viz_scene2d TYPE MODULE) find_package(Qt6 QUIET COMPONENTS Widgets REQUIRED) target_link_libraries(${SIGHT_TARGET} PUBLIC Qt6::Widgets) set_target_properties(${SIGHT_TARGET} PROPERTIES AUTOMOC TRUE) target_compile_definitions(${SIGHT_TARGET} PUBLIC "QT_NO_KEYWORDS") target_link_libraries( ${SIGHT_TARGET} PUBLIC core data ui_qt viz viz_scene2d service ui ) add_dependencies(${SIGHT_TARGET} module_ui_qt) sight-25.1.0/module/viz/scene2d/README.md000066400000000000000000000022061503402212300176040ustar00rootroot00000000000000 # sight::module::viz::scene2D This module contains services to display various 2D elements. The services are implementations of the adaptor present in lib/viz/scene2D ## Services - **axis** Renders an axis on the scene2d - **grid2d** Renders a grid on the scene2d - **histogram** Displays the histogram of an image - **line** Draws a line on the scene2D - **transfer_function** Displays a map of TF and interact with them. - **negato** Displays one slice of an 3D image. - **square** Draws a square on the scene2D. - **transfer_function** Displays and edits a transfer function from a medical image. - **viewport_interactor** Manages the camera on the scene2D view - **viewport_range_selector** Allows to select a delimited range of a viewport. It uses a graphical delimiter (called shutter) that can be moved from both left to right and right to left directions (in those cases, shutter's width is changing). - **viewport_updater** Manages the camera on the view (by updating the viewport object) ## How to use it ## CMake ```cmake add_dependencies(my_target ... module_viz_scene2D ) ``` sight-25.1.0/module/viz/scene2d/adaptor/000077500000000000000000000000001503402212300177575ustar00rootroot00000000000000sight-25.1.0/module/viz/scene2d/adaptor/axis.cpp000066400000000000000000000422501503402212300214320ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene2d/adaptor/axis.hpp" #include #include #include #include #include namespace sight::module::viz::scene2d::adaptor { axis::axis() noexcept = default; //-------------------------------------------------------------------------------------------------- axis::~axis() noexcept = default; //-------------------------------------------------------------------------------------------------- void axis::starting() { m_layer = new QGraphicsItemGroup(); this->build_axis(); this->build_labels(); // Adjust the layer's position and zValue depending on the associated axis m_layer->setPos(m_x_axis->origin(), m_y_axis->origin()); m_layer->setZValue(m_z_value); // Add to the scene the unique item which gather the whole set of rectangle graphic items: this->get_scene_2d_render()->get_scene()->addItem(m_layer); this->updating(); } //-------------------------------------------------------------------------------------------------- void axis::stopping() { delete m_layer; } //-------------------------------------------------------------------------------------------------- void axis::configuring() { this->configure_params(); // Looks for 'xAxis', 'yAxis' and 'zValue' const config_t srv_config = this->get_config(); const config_t config = srv_config.get_child("config."); // 'color' if(config.count("color") != 0U) { sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_line.color, config.get("color")); } // 'align' attribute configuration m_align = config.get("align"); SIGHT_ASSERT( "'align' attribute is missing. Please add an 'align' attribute " "with value 'left', 'right', 'top' or 'bottom'", !m_align.empty() ); SIGHT_ASSERT( "Unsupported value for 'align' attribute.", m_align == "left" || m_align == "right" || m_align == "top" || m_align == "bottom" ); // axis bounds SIGHT_ASSERT("'min' attribute is missing.", config.count("min")); SIGHT_ASSERT("'max' attribute is missing.", config.count("max")); m_min = config.get("min"); m_max = config.get("max"); SIGHT_FATAL_IF("'max' attribute should be greater than 'min'.", m_max <= m_min); // Ticks size m_line.tick_size = config.get("tickSize", m_line.tick_size); // Step m_interval = config.get("interval", m_interval); const auto label_config = srv_config.get_child_optional("config.labels."); if(label_config.has_value()) { // Font size configuration m_labels.font_size = label_config->get("fontSize", m_labels.font_size); // Show unit m_labels.show_unit = label_config->get("showUnit", m_labels.show_unit); // Unit text configuration m_labels.displayed_unit = label_config->get("unit", ""); // Color configuration if(label_config->count("color") != 0U) { sight::viz::scene2d::data::init_qt_pen::set_pen_color( m_labels.pen, label_config->get("color"), m_opacity ); } else { sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_labels.pen, "white", m_opacity); } } } //--------------------------------------------------------------------------------------------------------------- void axis::build_axis() { m_line.color.setCosmetic(true); const int nb_values = static_cast(std::ceil((m_max - m_min) / m_interval)) + 1; for(int i = 0 ; i < nb_values ; ++i) { auto* tick = new QGraphicsLineItem(0, 0, 0, 0); tick->setPen(m_line.color); m_line.ticks.push_back(tick); m_layer->addToGroup(tick); } m_line.line = new QGraphicsLineItem(); m_line.line->setPen(m_line.color); m_layer->addToGroup(m_line.line); } //--------------------------------------------------------------------------------------------------------------- void axis::build_labels() { SIGHT_ASSERT("m_interval can not be equal to 0", m_interval != 0); m_labels.brush = QBrush(m_labels.pen.color()); m_labels.pen.setCosmetic(true); // We may not have a viewport with the same range magnitude on both axis, thus specifying the font size with only // 1 dimension does not work. The font would be deformed. // Since we can't specify a width and height for the font size, we are forced to use a QTransform to scale the font // and keep a correct display ratio. // We would like to set the point size to 1.f and scale the text later with a QTransform according to m_"fontSize", // but under Windows, rendering a font with a tiny size like 1,2, etc... gives incorrect results in terms of // letter spacing and weight. Thus we "pretend" to use a font of a more common size like 10 and take that extra // scale into account in our QTransform (in updateLabels()) m_labels.font.setPixelSize(static_cast(m_labels.extra_scale)); m_labels.font.setKerning(true); m_labels.font.setFixedPitch(true); m_labels.values.clear(); double val = this->get_start_val(); const int range = static_cast(std::ceil(this->get_end_val() - val)); const int nb_values = static_cast((std::ceil(range / m_interval))) + 1; std::string format; if(m_align == "left") { format = " %1"; } else if(m_align == "right") { format = "%1 "; } else { format = "%1"; } // Build the values as graphic items for(int i = 0 ; i < nb_values ; ++i) { auto* text = new QGraphicsSimpleTextItem(); text->setText(QString::fromStdString(format).arg(val)); text->setFont(m_labels.font); text->setBrush(m_labels.brush); m_labels.values.push_back(text); val += m_interval; } // Add the values to the item group for(QGraphicsItem* item : m_labels.values) { m_layer->addToGroup(item); } // Build the unit graphic item m_labels.unit = new QGraphicsSimpleTextItem(); m_labels.unit->setText(QString::fromStdString(m_labels.displayed_unit)); m_labels.unit->setFont(m_labels.font); m_labels.unit->setBrush(m_labels.brush); // Add it to the items to be displayed if required if(m_labels.show_unit) { m_layer->addToGroup(m_labels.unit); } } //--------------------------------------------------------------------------------------- double axis::get_start_val() const { return std::floor(m_min / m_interval) * m_interval; } //--------------------------------------------------------------------------------------- double axis::get_end_val() const { return std::ceil(m_max / m_interval) * m_interval; } //--------------------------------------------------------------------------------------------------------------- void axis::updating() { update_axis(); update_labels(); } //--------------------------------------------------------------------------------------- void axis::update_axis() { const auto viewport = m_viewport.lock(); const auto view_to_viewport = this->view_to_viewport(*viewport); const std::size_t nb_values = m_line.ticks.size(); const double min = this->get_start_val(); const double max = this->get_end_val(); using sight::viz::scene2d::vec2d_t; double pos = NAN; vec2d_t tick_size; vec2d_t tick_pos {0., 0.}; if(m_align == "bottom") { tick_size = this->map_adaptor_to_scene((vec2d_t(0, m_line.tick_size))); const double tick_pos_y = 0.; for(std::size_t i = 0 ; i < nb_values ; ++i) { pos = min + static_cast(i) * m_interval; tick_pos = this->map_adaptor_to_scene((vec2d_t(pos, tick_pos_y))); m_line.ticks.at(i)->setLine( tick_pos.x, tick_pos.y, tick_pos.x, tick_pos.y - tick_size.y * view_to_viewport.y ); } m_line.line->setLine(min, tick_pos.y, max, tick_pos.y); } else if(m_align == "top") { tick_size = this->map_adaptor_to_scene((vec2d_t(0, m_line.tick_size))); const double tick_pos_y = 1.0; for(std::size_t i = 0 ; i < nb_values ; ++i) { pos = min + static_cast(i) * m_interval; tick_pos = this->map_adaptor_to_scene((vec2d_t(pos, tick_pos_y))); m_line.ticks.at(i)->setLine( tick_pos.x, tick_pos.y, tick_pos.x, tick_pos.y - tick_size.y * view_to_viewport.y ); } m_line.line->setLine(min, tick_pos.y, max, tick_pos.y); } else if(m_align == "left") { tick_size = this->map_adaptor_to_scene((vec2d_t(m_line.tick_size, 0))); const double tick_pos_x = viewport->x(); for(std::size_t i = 0 ; i < nb_values ; ++i) { pos = min + static_cast(i) * m_interval; tick_pos = this->map_adaptor_to_scene((vec2d_t(tick_pos_x, pos))); m_line.ticks.at(i)->setLine( tick_pos.x, tick_pos.y, tick_pos.x + tick_size.x * view_to_viewport.x, tick_pos.y ); } m_line.line->setLine(tick_pos.x, min, tick_pos.x, tick_pos.y); } else if(m_align == "right") { tick_size = this->map_adaptor_to_scene((vec2d_t(m_line.tick_size, 0))); const double tick_pos_x = viewport->x() + viewport->width(); for(std::size_t i = 0 ; i < nb_values ; ++i) { pos = min + static_cast(i) * m_interval; tick_pos = this->map_adaptor_to_scene((vec2d_t(tick_pos_x, pos))); m_line.ticks.at(i)->setLine( tick_pos.x - tick_size.x * view_to_viewport.x, tick_pos.y, tick_pos.x, tick_pos.y ); } m_line.line->setLine(tick_pos.x, min, tick_pos.x, tick_pos.y); } } //--------------------------------------------------------------------------------------- void axis::update_labels() { using sight::viz::scene2d::vec2d_t; const auto viewport = m_viewport.lock(); const auto view_to_viewport = this->view_to_viewport(*viewport); const auto size_vp = vec2d_t(m_labels.font_size, m_labels.font_size) * view_to_viewport / m_labels.extra_scale; const vec2d_t interval_sc = glm::abs(this->map_adaptor_to_scene(vec2d_t(m_interval, m_interval))); QTransform transform; transform.scale(size_vp.x, size_vp.y); const std::size_t values_size = m_labels.values.size(); double val = get_start_val(); const double viewport_x = viewport->x(); const double viewport_width = viewport->width(); const double viewport_height = viewport->height(); if(m_align == "left" || m_align == "right") { double coeff = 0.F; double text_pos_x = NAN; if(m_align == "left") { text_pos_x = viewport_x; } else { coeff = -1.F; text_pos_x = viewport_x + viewport_width; } for(std::size_t i = 0 ; i < values_size ; ++i) { const vec2d_t coord = this->map_adaptor_to_scene((vec2d_t(text_pos_x, val))); const vec2d_t size = this->map_adaptor_to_scene( vec2d_t( m_labels.values[i]->boundingRect().width(), m_labels.values[i]->boundingRect().height() ) ); m_labels.values[i]->setTransform(transform); m_labels.values[i]->setPos(coord.x + coeff * size.x * size_vp.x, coord.y); val += m_interval; } // Always displays the maximum value but we may hide other values depending on available size double prev_available_size = 0.; for(std::size_t i = 1 ; i < values_size ; ++i) { const std::size_t idx = values_size - i - 1; const vec2d_t size = vec2d_t( m_labels.values[idx]->boundingRect().width(), m_labels.values[idx]->boundingRect().height() ); const double margin = 0.8; const auto size_y = std::abs(size.y * margin * size_vp.y); if(size_y < interval_sc.y + prev_available_size) { m_labels.values[idx]->setVisible(true); prev_available_size = 0.; } else { m_labels.values[idx]->setVisible(false); prev_available_size += interval_sc.y; } } m_labels.unit->setTransform(transform); val = viewport_height * 0.8F; const vec2d_t size = this->map_adaptor_to_scene( vec2d_t( m_labels.unit->boundingRect().width(), m_labels.unit->boundingRect().height() ) ); const vec2d_t coord = this->map_adaptor_to_scene((vec2d_t(text_pos_x, val))); coeff = (m_align == "left") ? 1 : -1.5; m_labels.unit->setPos(coord.x + coeff * 2 * size.x * size_vp.x, coord.y); } else // axis centered on top or bottom { float coeff = 0.5F; const double text_pos_y = (m_align == "bottom") ? 0 : 1.1; for(std::size_t i = 0 ; i < values_size ; ++i) { const vec2d_t coord = this->map_adaptor_to_scene(vec2d_t(val, text_pos_y)); const vec2d_t size = this->map_adaptor_to_scene( vec2d_t( m_labels.values[i]->boundingRect().width(), m_labels.values[i]->boundingRect().height() ) ); m_labels.values[i]->setTransform(transform); m_labels.values[i]->setPos(coord.x - size.x / 2 * size_vp.x, coord.y - coeff * size.y / 2 * size_vp.y); val += m_interval; } // Always displays the maximum value but we may hide other values depending on available size double prev_available_size = 0.; for(std::size_t i = 1 ; i < values_size ; ++i) { const vec2d_t size = vec2d_t( m_labels.values[i]->boundingRect().width(), m_labels.values[i]->boundingRect().height() ); const double margin = 1.25; const auto size_x = size.x * margin * size_vp.x; if(size_x < interval_sc.x + prev_available_size) { m_labels.values[i]->setVisible(true); prev_available_size = 0.; } else { m_labels.values[i]->setVisible(false); prev_available_size += interval_sc.x; } } m_labels.unit->setTransform(transform); const vec2d_t size = this->map_adaptor_to_scene( vec2d_t( m_labels.unit->boundingRect().width(), m_labels.unit->boundingRect().height() ) ); const vec2d_t coord = this->map_adaptor_to_scene(vec2d_t(viewport_x + viewport_width * .5, text_pos_y)); coeff = (m_align == "top") ? 1 : -1.5; m_labels.unit->setPos(coord.x - size.x * size_vp.x, coord.y + coeff * size.y * size_vp.y); } } //--------------------------------------------------------------------------------------- void axis::process_interaction(sight::viz::scene2d::data::event& _event) { if(_event.type() == sight::viz::scene2d::data::event::resize) { this->updating(); } } //---------------------------------------------------------------------------------------------------------- service::connections_t axis::auto_connections() const { connections_t connections; connections.push(VIEWPORT_INPUT, sight::viz::scene2d::data::viewport::MODIFIED_SIG, service::slots::UPDATE); return connections; } //-------------------------------------------------------------------------------------------------- } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/axis.hpp000066400000000000000000000124171503402212300214410ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include namespace sight::module::viz::scene2d::adaptor { /** * @brief Render an axis on the scene2d. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection In In * - \b viewport [sight::viz::scene2d::data::viewport]: object listened to update axis. * * @subsection Configuration Configuration: * - \b config (mandatory): contains the adaptor configuration * - \b align (mandatory): axis alignment, left', 'right', 'top' or 'bottom' * - \b min (mandatory): min axis bound * - \b max (mandatory): max axis bound * - \b tickSize (optional, default=1.0): axis tick size * - \b interval (optional, default=1.0): axis interval * - \b xAxis (optional): x axis associated to the adaptor * - \b yAxis (optional): y axis associated to the adaptor * - \b zValue (optional, default=0): z value of the layer * - \b opacity (optional, default=1.0): adaptor opacity (float) * - \b color (optional, default black): color of the axis */ class axis : public sight::viz::scene2d::adaptor { public: SIGHT_DECLARE_SERVICE(axis, sight::viz::scene2d::adaptor); axis() noexcept; ~axis() noexcept override; protected: void starting() override; void stopping() override; void updating() override; void configuring() override; /// Manage the given events void process_interaction(sight::viz::scene2d::data::event& _event) override; connections_t auto_connections() const override; private: /// Builds axis graphic items. void build_axis(); /// Builds labels graphic items. void build_labels(); /// double get_start_val() const; /// double get_end_val() const; /// Displays axis graphic items. void update_axis(); /// Displays labels graphic items. void update_labels(); // Specify where the axis must be aligned: left, right, top or bottom. // Left and right side axis are aligned/floating relatively to the view. std::string m_align; /// A layer that gathers all the graphic items. QGraphicsItemGroup* m_layer {nullptr}; /// Minimal value of the axis. double m_min {0.}; /// Maximal value of the axis. double m_max {0.}; /// The required interval between two consecutive values of the axis. double m_interval {1.}; struct line_t { /// Size of a tick. double tick_size {1.0}; /// Color. QPen color {Qt::white}; /// The line of the axis. QGraphicsLineItem* line {nullptr}; /// The graphic items that refer to ticks of the axis. std::vector ticks; }; line_t m_line; struct labels { /// The unit of this values as text. QGraphicsSimpleTextItem* unit {nullptr}; /// Tells if the unit of the axis must be displayed. bool show_unit {true}; /// The unit of the axis (as text) that will be displayed. std::string displayed_unit; /// Specify where the axis must be aligned: left, right, top or bottom. /// Left and right side axis are aligned/floating relatively to the view. std::string align; /// A vector containing QGraphicsItem-s representing the scale values of the axis. std::vector values; /// The font applied to grid's values. QFont font; /// The color applied to grid's values. QBrush brush; /// The pen. QPen pen {Qt::white}; /// The step which defines when a value of the axis should be displayed if there is not /// enough space to display all the values. int step {1}; /// Size of the font used for rendering grid values. int font_size {8}; /// Extra scale factor to force a minimum size of the font - platform dependent (set in the constructor) double extra_scale {10.}; }; labels m_labels; static constexpr std::string_view VIEWPORT_INPUT = "viewport"; data::ptr m_viewport {this, VIEWPORT_INPUT}; }; } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/grid2d.cpp000066400000000000000000000203251503402212300216400ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2018 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene2d/adaptor/grid2d.hpp" #include #include #include #include #include using sight::viz::scene2d::vec2d_t; namespace sight::module::viz::scene2d::adaptor { //--------------------------------------------------------------------------------------------------------------- const core::com::slots::key_t grid2d::SET_GRID_SPACING_SLOT = "set_grid_spacing"; //--------------------------------------------------------------------------------------------------------------- grid2d::grid2d() noexcept { new_slot(SET_GRID_SPACING_SLOT, &sight::module::viz::scene2d::adaptor::grid2d::set_grid_spacing, this); } //--------------------------------------------------------------------------------------------------------------- grid2d::~grid2d() noexcept = default; //--------------------------------------------------------------------------------------------------------------- void grid2d::configuring() { this->configure_params(); const config_t config = this->get_config().get_child("config."); SIGHT_ASSERT("Attribute 'xMin' is missing", config.count("xMin")); SIGHT_ASSERT("Attribute 'xMax' is missing", config.count("xMax")); SIGHT_ASSERT("Attribute 'yMin' is missing", config.count("yMin")); SIGHT_ASSERT("Attribute 'yMax' is missing", config.count("yMax")); // Set the x/y min/max values m_x_min = config.get("xMin"); m_x_max = config.get("xMax"); m_y_min = config.get("yMin"); m_y_max = config.get("yMax"); // If the corresponding attributes are present in the config, set the xSpacing, ySpacing between // the lines and color of the lines: if(config.count("xSpacing") != 0U) { m_x_spacing = config.get("xSpacing"); } if(config.count("ySpacing") != 0U) { m_y_spacing = config.get("ySpacing"); } if(config.count("color") != 0U) { sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_pen, config.get("color"), m_opacity); } } //--------------------------------------------------------------------------------------------------------------- void grid2d::draw() { using sight::viz::scene2d::vec2d_t; SIGHT_ASSERT("m_xSpacing can not be equal to 0", m_x_spacing != 0.F); SIGHT_ASSERT("m_ySpacing can not be equal to 0", m_y_spacing != 0.F); // Remove all lines from the scene for(const auto& line : m_lines) { this->get_scene_2d_render()->get_scene()->removeItem(line); } // Clear the lines vector m_lines.clear(); this->get_scene_2d_render()->get_scene()->removeItem(m_layer); m_layer = new QGraphicsItemGroup(); // Calculate the start, end and step on x for the lines const float x_start_val = get_x_start_val(); // Allows to start drawing the grid from 0 with the correct step const float x_end_val = get_x_end_val(); // Allows to start drawing the grid from 0 with the correct step // Calculate the start, end and step on y for the lines const float y_start_val = get_y_start_val(); // Allows to start drawing the grid from 0 with the correct step const float y_end_val = get_y_end_val(); // Allows to start drawing the grid from 0 with the correct step // Holds the current computed coordinates: vec2d_t coord1; vec2d_t coord2; // Draw the horizontal lines float y_val = y_start_val; for(std::size_t i = 0 ; i < static_cast((y_end_val - y_start_val) / m_y_spacing) ; i++) { coord1 = this->map_adaptor_to_scene((vec2d_t(x_start_val, y_val))); coord2 = this->map_adaptor_to_scene((vec2d_t(x_end_val, y_val))); auto* line = new QGraphicsLineItem(coord1.x, coord1.y, coord2.x, coord2.y); // Set the line the pen and push it back in to the lines vector line->setPen(m_pen); m_lines.push_back(line); y_val += m_y_spacing; } // Draw the vertical lines float x_val = x_start_val; for(std::size_t i = 0 ; i < static_cast((x_end_val - x_start_val) / m_x_spacing) ; i++) { coord1 = this->map_adaptor_to_scene((vec2d_t(x_val, y_start_val))); coord2 = this->map_adaptor_to_scene((vec2d_t(x_val, y_end_val))); auto* line = new QGraphicsLineItem(coord1.x, coord1.y, coord2.x, coord2.y); // Set the line the pen and push it back in to the lines vector line->setPen(m_pen); m_lines.push_back(line); x_val += m_x_spacing; } // Add the lines contained in the lines vector to the layer for(auto& m_line : m_lines) { m_layer->addToGroup(m_line); } // Set the layer position (according to the related axis) and zValue m_layer->setPos(m_x_axis->origin(), m_y_axis->origin()); m_layer->setZValue(m_z_value); // Add the layer containing grid's lines to the scene this->get_scene_2d_render()->get_scene()->addItem(m_layer); } //--------------------------------------------------------------------------------------------------------------- void grid2d::starting() { // Initialize the layer m_layer = new QGraphicsItemGroup(); // Set the pen a style m_pen.setStyle(Qt::DashLine); m_pen.setCosmetic(true); this->updating(); } //--------------------------------------------------------------------------------------------------------------- float grid2d::get_x_start_val() const { return std::floor(m_x_min / m_x_spacing) * m_x_spacing; } //--------------------------------------------------------------------------------------------------------------- float grid2d::get_x_end_val() const { return std::floor(m_x_max / m_x_spacing) * m_x_spacing; } //--------------------------------------------------------------------------------------------------------------- float grid2d::get_y_start_val() const { return std::floor(m_y_min / m_y_spacing) * m_y_spacing; } //--------------------------------------------------------------------------------------------------------------- float grid2d::get_y_end_val() const { return std::floor(m_y_max / m_y_spacing) * m_y_spacing; } //--------------------------------------------------------------------------------------------------------------- void grid2d::set_grid_spacing(double _x, double _y, std::string _key) { if(_key == "spacing") { m_x_spacing = static_cast(_x); m_y_spacing = static_cast(_y); this->draw(); } } //--------------------------------------------------------------------------------------------------------------- void grid2d::updating() { this->draw(); } //--------------------------------------------------------------------------------------------------------------- void grid2d::process_interaction(sight::viz::scene2d::data::event& _event) { if(_event.type() == sight::viz::scene2d::data::event::resize) { this->updating(); } } //--------------------------------------------------------------------------------------------------------------- void grid2d::stopping() { // Clear the lines vector m_lines.clear(); // Remove the layer (and therefore all its related items) from the scene this->get_scene_2d_render()->get_scene()->removeItem(m_layer); } } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/grid2d.hpp000066400000000000000000000106101503402212300216410ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include namespace sight::module::viz::scene2d::adaptor { /** * @brief Render grid on the scene2d. * * @section Slots Slots * - \b set_grid_spacing(double, double, std::string) : set the grid spacing in x and y. It is intended to be connected * to * a double2Changed signal of a parameters service; the string key parameter should be set to "spacing". * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection Configuration Configuration: * - \b config (mandatory): contains the adaptor configuration * - \b xAxis (optional): x axis associated to the adaptor * - \b yAxis (optional): y axis associated to the adaptor * - \b zValue (optional, default=0): z value of the layer * - \b opacity (optional, default=1.0): adaptor opacity (float) * - \b color (optional, default black): color of the axis * - \b xMin (optional): Set the minimum x value of the grid. * - \b xMax (optional): Set the maximum x value of the grid. * - \b yMin (optional): Set the minimum y value of the grid. * - \b yMax (optional): Set the maximum y value of the grid. * - \b xSpacing (optional, default value: 10): Set the grid spacing (space between 2 consecutive lines) in x. * - \b ySpacing (optional, default value: 10): Set the grid spacing (space between 2 consecutive lines) in y. */ class grid2d : public sight::viz::scene2d::adaptor { public: SIGHT_DECLARE_SERVICE(grid2d, sight::viz::scene2d::adaptor); /** * @name Slots API * @{ */ static const core::com::slots::key_t SET_GRID_SPACING_SLOT; /** @} */ /// Constructor, set the x and y spacing to 10 grid2d() noexcept; /// Basic destructor, do nothing ~grid2d() noexcept override; protected: void configuring() override; /// Initialize the layer, set the pen style to DashLine and call the draw() function. void starting() override; void updating() override; /// Clean the lines vector and remove the layer from the scene. void stopping() override; /// Manage the given events void process_interaction(sight::viz::scene2d::data::event& _event) override; private: /// Calculate the x/y start/end values, create the lines, set'em m_pen, push'em back in /// the m_lines vector, add'em to the layer, set the layer position and zValue and add it /// to the scene. void draw(); /// Returns x-axis starting value float get_x_start_val() const; /// Returns x-axis ending value float get_x_end_val() const; /// Returns y-axis starting value float get_y_start_val() const; /// Returns y-axis ending value float get_y_end_val() const; /// Slot: This method is used to set the grid spacing from two double parameters. void set_grid_spacing(double _x, double _y, std::string _key); /// Bounds of the grid and spacing values for each axis. float m_x_min {0.F}, m_x_max {0.F}, m_y_min {0.F}, m_y_max {0.F}, m_x_spacing {10.F}, m_y_spacing {10.F}; /// The pen. QPen m_pen; /// A vector containing QGraphicsItems representing the lines of the grid. std::vector m_lines; /// The layer. QGraphicsItemGroup* m_layer {nullptr}; }; } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/histogram.cpp000066400000000000000000000322301503402212300224600ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene2d/adaptor/histogram.hpp" #include #include #include #include #include #include namespace sight::module::viz::scene2d::adaptor { const core::com::slots::key_t IMAGE_CHANGE_SLOT = "imageChange"; //--------------------------------------------------------------------------------------------------------- histogram::histogram() noexcept { new_slot(IMAGE_CHANGE_SLOT, &histogram::on_image_change, this); } //--------------------------------------------------------------------------------------------------------- histogram::~histogram() noexcept = default; //--------------------------------------------------------------------------------------------------------- void histogram::configuring() { this->configure_params(); // Looks for 'xAxis', 'yAxis' and 'zValue' const config_t srv_config = this->get_config(); const config_t config = srv_config.get_child("config."); m_scale = m_y_axis->get_scale(); if(config.count("color") != 0U) { sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_color, config.get("color")); } const auto cursor_config = srv_config.get_child_optional("config.cursor"); if(cursor_config.has_value()) { std::string color; m_cursor_enabled = true; color = cursor_config->get(".color", ""); sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_cursor_color, color); color = cursor_config->get(".borderColor", ""); sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_cursor_border_color, color); m_cursor_size = cursor_config->get(".size", m_cursor_size); color = cursor_config->get(".textColor", "#FFFFFF"); sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_cursor_label_color, color); m_font_size = cursor_config->get(".fontSize", m_font_size); } } //--------------------------------------------------------------------------------------------------------- void histogram::starting() { if(m_cursor_enabled) { m_cursor_layer = new QGraphicsItemGroup(); // Adjust the layer's position and zValue depending on the associated axis m_cursor_layer->setPos(m_x_axis->origin(), m_y_axis->origin()); m_cursor_layer->setZValue(m_z_value); this->get_scene_2d_render()->get_scene()->addItem(m_cursor_layer); m_cursor_item = new QGraphicsEllipseItem(); m_cursor_item->setBrush(m_cursor_color.color()); m_cursor_item->setPen(m_cursor_border_color); m_cursor_item->setZValue(m_z_value); m_color.setCosmetic(true); m_cursor_layer->addToGroup(m_cursor_item); // We set the point size to 1.f but we will scale the text later according to m_fontSize QFont font; font.setPointSize(1); font.setKerning(true); font.setFixedPitch(true); m_cursor_label = new QGraphicsSimpleTextItem(); m_cursor_label->setBrush(QBrush(m_cursor_label_color.color())); m_cursor_label->setFont(font); m_cursor_label->setVisible(false); m_cursor_layer->addToGroup(m_cursor_label); } const auto image = m_image.lock(); m_histogram = std::make_unique(image.get_shared()); on_image_change(); } //--------------------------------------------------------------------------------------------------------- void histogram::updating() { if(m_layer != nullptr) { this->get_scene_2d_render()->get_scene()->removeItem(m_layer); delete m_layer; } m_layer = new QGraphicsItemGroup(); const auto image = m_image.lock(); if(data::helper::medical_image::check_image_validity(image.get_shared())) { const auto values = m_histogram->sample(m_histogram_bins_width); if(!values.empty()) { // Update color with opacity QColor color = m_color.color(); color.setAlphaF(static_cast(m_opacity)); m_color.setColor(color); const double min = m_histogram->min(); const auto bins_width = static_cast(m_histogram_bins_width); QBrush brush = QBrush(m_color.color()); // Build the graphic items: for(std::size_t i = 0 ; i < values.size() ; ++i) { glm::dvec2 pt1 = this->map_adaptor_to_scene({min + static_cast(i) * bins_width, values[i]}); glm::dvec2 pt2 = this->map_adaptor_to_scene({min + static_cast(i + 1) * bins_width, values[i]}); QPainterPath painter(QPointF(pt1.x, 0)); painter.lineTo(pt1.x, pt1.y); painter.lineTo(pt2.x, pt1.y); painter.lineTo(pt2.x, 0); auto* item = new QGraphicsPathItem(painter); item->setPath(painter); item->setBrush(brush); item->setPen(Qt::NoPen); m_layer->addToGroup(item); } // Adjust the layer's position and zValue depending on the associated axis m_layer->setPos(m_x_axis->origin(), m_y_axis->origin()); m_layer->setZValue(m_z_value); // Add to the scene the unique item which gather the whole set of rectangle graphic items: this->get_scene_2d_render()->get_scene()->addItem(m_layer); } } } //--------------------------------------------------------------------------------------------------------- void histogram::stopping() { m_histogram.reset(); if(m_layer != nullptr) { this->get_scene_2d_render()->get_scene()->removeItem(m_layer); delete m_layer; m_layer = nullptr; } if(m_cursor_layer != nullptr) { this->get_scene_2d_render()->get_scene()->removeItem(m_cursor_layer); delete m_cursor_layer; m_cursor_layer = nullptr; } } //--------------------------------------------------------------------------------------------------------- void histogram::process_interaction(sight::viz::scene2d::data::event& _event) { if(!this->started()) { return; } bool update_pointed_pos = false; // Vertical scaling if(_event.type() == sight::viz::scene2d::data::event::mouse_wheel_up) { if(_event.get_modifier() == sight::viz::scene2d::data::event::control_modifier) { m_histogram_bins_width = std::min( std::max(std::size_t(1), static_cast(m_histogram->max() - m_histogram->min())), m_histogram_bins_width * 2 ); this->updating(); } else { double scale = SCALE; if(_event.get_modifier() == sight::viz::scene2d::data::event::shift_modifier) { scale = FAST_SCALE; } m_scale *= scale; m_layer->setTransform(QTransform::fromScale(1., scale), true); _event.set_accepted(true); m_y_axis->set_scale(m_scale); auto viewport = m_viewport.lock(); auto sig = viewport->signal(data::object::MODIFIED_SIG); sig->async_emit(); } update_pointed_pos = true; } else if(_event.type() == sight::viz::scene2d::data::event::mouse_wheel_down) { if(_event.get_modifier() == sight::viz::scene2d::data::event::control_modifier) { m_histogram_bins_width = std::max(std::size_t(1), m_histogram_bins_width / 2); this->updating(); } else { double scale = SCALE; if(_event.get_modifier() == sight::viz::scene2d::data::event::shift_modifier) { scale = FAST_SCALE; } m_scale /= scale; m_layer->setTransform(QTransform::fromScale(1., 1. / scale), true); _event.set_accepted(true); m_y_axis->set_scale(m_scale); auto viewport = m_viewport.lock(); auto sig = viewport->signal(data::object::MODIFIED_SIG); sig->async_emit(); } update_pointed_pos = true; } else if(_event.type() == sight::viz::scene2d::data::event::mouse_move) { update_pointed_pos = true; } else if(_event.type() == sight::viz::scene2d::data::event::mouse_button_press) { m_is_interacting = true; update_pointed_pos = true; } else if(_event.type() == sight::viz::scene2d::data::event::mouse_button_release) { m_is_interacting = false; update_pointed_pos = true; } else if(_event.type() == sight::viz::scene2d::data::event::enter_event) { m_entered = true; update_pointed_pos = true; } else if(_event.type() == sight::viz::scene2d::data::event::leave_event) { m_entered = false; update_pointed_pos = true; } if(update_pointed_pos) { this->update_current_point(_event); } } //---------------------------------------------------------------------------------------------------------- service::connections_t histogram::auto_connections() const { return { {IMAGE_INPUT, data::image::MODIFIED_SIG, IMAGE_CHANGE_SLOT}, {IMAGE_INPUT, data::image::BUFFER_MODIFIED_SIG, IMAGE_CHANGE_SLOT} }; } //--------------------------------------------------------------------------------------------------------- void histogram::update_current_point(sight::viz::scene2d::data::event& _event) { if(m_cursor_enabled) { const auto values = m_histogram->sample(m_histogram_bins_width); const double histogram_min_value = m_histogram->min(); const auto histogram_bins_width = static_cast(m_histogram_bins_width); // Event coordinates in scene glm::dvec2 scene_coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord()); const double hist_index = scene_coord.x; const double index = hist_index - histogram_min_value; const std::size_t nb_values = values.size() * m_histogram_bins_width; const auto viewport = m_viewport.lock(); const auto view_to_viewport = this->view_to_viewport(*viewport); if(index >= 0.F && index < static_cast(nb_values) && m_entered) { glm::dvec2 coord; coord.x = scene_coord.x; coord.y = static_cast(values.at(static_cast(index / histogram_bins_width))) * m_scale; // Draw the cursor const glm::dvec2 diameter = glm::dvec2(m_cursor_size, m_cursor_size) * view_to_viewport; const double x = coord.x - diameter.x / 2; const double y = coord.y - diameter.y / 2; m_cursor_item->setRect(x, y, diameter.x, diameter.y); m_cursor_item->setVisible(true); if(m_is_interacting) { // Draw the cursor text const glm::dvec2 scale = glm::dvec2(m_font_size, m_font_size) * view_to_viewport; // Event coordinates in scene m_cursor_label->setText(QString::number(static_cast((coord.x)))); QTransform transform; transform.scale(scale.x, scale.y); m_cursor_label->setTransform(transform); m_cursor_label->setPos(coord.x + scale.x * 0.5, coord.y - scale.y * 0.5); m_cursor_label->setVisible(true); } else { m_cursor_label->setVisible(false); } } else { m_cursor_item->setVisible(false); m_cursor_label->setVisible(false); } } } //------------------------------------------------------------------------------ void histogram::on_image_change() { m_histogram->compute(); m_histogram_bins_width = std::max( static_cast(1), static_cast(m_histogram->max() - m_histogram->min()) / 50 ); this->updating(); } } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/histogram.hpp000066400000000000000000000135341503402212300224730ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::viz::scene2d::adaptor { /** * @brief adaptor implementation for histogram data. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection In In * - \b histogram [sight::data::image]: source image. * * @subsection In-Out In-Out * - \b point [sight::data::point](optional): histogram point, used to show information at the current histogram index * pointed by the mouse. * * @subsection Configuration Configuration: * - \b config (mandatory): contains the adaptor configuration * - \b color (optional, default black) : the background color of the histogram * - \b xAxis (optional): x axis associated to the adaptor * - \b yAxis (optional): y axis associated to the adaptor * - \b zValue (optional, default=0): z value of the layer * - \b cursor (optional): enable the display of a cursor shown on mouse hover * - \b color (optional, default black): inner color of the cursor shown on mouse hover * - \b borderColor (optional, default black): border color of the cursor shown on mouse hover * - \b size (optional, default 6.0): cursor size * - \b labelColor (optional, default="#FFFFFFFF"): color of the cursor label * - \b fontSize (optional, default="8"): size of the font used to display the current cursor value. */ class histogram : public sight::viz::scene2d::adaptor { public: SIGHT_DECLARE_SERVICE(histogram, sight::viz::scene2d::adaptor); histogram() noexcept; ~histogram() noexcept override; protected: void configuring() override; void starting() override; void updating() override; void stopping() override; void process_interaction(sight::viz::scene2d::data::event& _event) override; /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection * * Connect histogram::MODIFIED_SIG to this::service::slots::UPDATE */ service::connections_t auto_connections() const override; /// Ratio used for vertical scaling static constexpr double SCALE = 1.1; static constexpr double FAST_SCALE = 2.2; private: /// Update the value of m_ordinateValueUID according to the value pointed by mouse cursor. void update_current_point(sight::viz::scene2d::data::event& _event); /// Update image related properties when it changes void on_image_change(); /// Color used for graphic item's inner and border color QPen m_color {Qt::green}; /// Opacity double m_opacity {0.8}; /// Current vertical scaling ratio double m_scale {1.}; /// Width of histogram bins std::size_t m_histogram_bins_width {5}; // layer for the histogram, may be rescaled on mouse wheel event QGraphicsItemGroup* m_layer {nullptr}; // layer for the cursor, never rescaled QGraphicsItemGroup* m_cursor_layer {nullptr}; /// Enables the display of a cursor bool m_cursor_enabled {false}; /// Color used for the inner color of the cursor shown on mouse hover QPen m_cursor_color; /// Color used for the border color of the cursor shown on mouse hover QPen m_cursor_border_color; /// Point size of the cursor shown on mouse hover double m_cursor_size {6.F}; /// Defines the color used for graphic item's inner color. QPen m_cursor_label_color; /// Stores the item which display the current values of the associated histogram pointed by this cursor. QGraphicsSimpleTextItem* m_cursor_label {nullptr}; /// Defines the size of the font used for rendering the current value of this tracker. int m_font_size {8}; /// Sets the display status. bool m_is_interacting {false}; /// True when the mouse is hover the widget. bool m_entered {false}; // A graphics item that is located onto histogram's upper border and moves along this border // according to the position of mouse's cursor. The goal of this graphical index is to show // the associated value within the histogram pointed buy this index. QGraphicsEllipseItem* m_cursor_item {nullptr}; /// Helper to compute the image histogram std::unique_ptr m_histogram; static constexpr std::string_view IMAGE_INPUT = "image"; static constexpr std::string_view VIEWPORT_INOUT = "viewport"; data::ptr m_image {this, IMAGE_INPUT}; data::ptr m_viewport {this, VIEWPORT_INOUT}; }; } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/line.cpp000066400000000000000000000074531503402212300214230ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2017 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene2d/adaptor/line.hpp" #include #include #include using sight::viz::scene2d::vec2d_t; namespace sight::module::viz::scene2d::adaptor { line::line() noexcept = default; //--------------------------------------------------------------------------------------------------------------- line::~line() noexcept = default; //--------------------------------------------------------------------------------------------------------------- void line::configuring() { this->configure_params(); const config_t config = this->get_config().get_child("config."); SIGHT_ASSERT("Attribute 'x1' is missing", config.count("x1")); SIGHT_ASSERT("Attribute 'x2' is missing", config.count("x2")); SIGHT_ASSERT("Attribute 'y1' is missing", config.count("y1")); SIGHT_ASSERT("Attribute 'y2' is missing", config.count("y2")); // Set the beginning and ending coordinates values m_x1 = config.get("x1"); m_x2 = config.get("x2"); m_y1 = config.get("y1"); m_y2 = config.get("y2"); // If the corresponding attributes are present in the config, set the color of the line if(config.count("color") != 0U) { sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_pen, config.get("color")); } } //--------------------------------------------------------------------------------------------------------------- void line::draw() { using sight::viz::scene2d::vec2d_t; const vec2d_t pt1 = this->map_adaptor_to_scene((vec2d_t(m_x1, m_y1))); const vec2d_t pt2 = this->map_adaptor_to_scene((vec2d_t(m_x2, m_y2))); // Draw the line auto* line = new QGraphicsLineItem(pt1.x, pt1.y, pt2.x, pt2.y); // Set the line the pen line->setPen(m_pen); // Add the line to the layer m_layer->addToGroup(line); // Set the layer position (according to the related axis) and zValue m_layer->setPos(m_x_axis->origin(), m_y_axis->origin()); m_layer->setZValue(m_z_value); // Add the layer to the scene this->get_scene_2d_render()->get_scene()->addItem(m_layer); } //--------------------------------------------------------------------------------------------------------------- void line::starting() { // Initialize the layer m_layer = new QGraphicsItemGroup(); m_pen.setCosmetic(true); this->draw(); } //--------------------------------------------------------------------------------------------------------------- void line::updating() { this->draw(); } //--------------------------------------------------------------------------------------------------------------- void line::stopping() { // Remove the layer (and therefore its related line item) from the scene this->get_scene_2d_render()->get_scene()->removeItem(m_layer); } } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/line.hpp000066400000000000000000000055011503402212300214200ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2021 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include namespace sight::module::viz::scene2d::adaptor { /** * @brief Line adaptor. Draw a line on the scene2D * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection Configuration Configuration: * - \b config (mandatory): contains the adaptor configuration * - \b x1 (mandatory): Set the line beginning coordinate x value. * - \b x2 (mandatory): Set the line ending coordinate x value. * - \b y1 (mandatory): Set the line beginning coordinate y value. * - \b y2 (mandatory): Set the line ending coordinate y value. * - \b xAxis (optional): x axis associated to the adaptor * - \b yAxis (optional): y axis associated to the adaptor * - \b zValue (optional, default=0): z value of the layer * - \b color (optional, default black): color of the line */ class line : public sight::viz::scene2d::adaptor { public: SIGHT_DECLARE_SERVICE(line, sight::viz::scene2d::adaptor); /// Basic constructor, do nothing. line() noexcept; /// Basic destructor, do nothing. ~line() noexcept override; protected: void configuring() override; /// Initialize the layer and call the draw() function. void starting() override; /// Do nothing. void updating() override; /// Remove the layer from the scene. void stopping() override; private: /// Create the line, set it m_pen, add it to the layer, set the layer position and zValue and add it to the scene. void draw(); /// The coordinates of the line. float m_x1 {0.F}, m_x2 {0.F}, m_y1 {0.F}, m_y2 {0.F}; /// The pen. QPen m_pen; /// The layer. QGraphicsItemGroup* m_layer {nullptr}; }; } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/negato.cpp000066400000000000000000000444021503402212300217440ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene2d/adaptor/negato.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene2d::adaptor { static const core::com::slots::key_t UPDATE_SLICE_INDEX_SLOT = "updateSliceIndex"; static const core::com::slots::key_t UPDATE_SLICE_TYPE_SLOT = "updateSliceType"; static const core::com::slots::key_t UPDATE_BUFFER_SLOT = "updateBuffer"; static const core::com::slots::key_t UPDATE_VISIBILITY_SLOT = "update_visibility"; static const core::com::slots::key_t UPDATE_TF_SLOT = "update_tf"; namespace medHelper = data::helper::medical_image; //----------------------------------------------------------------------------- negato::negato() noexcept { new_slot(UPDATE_SLICE_INDEX_SLOT, &negato::update_slice_index, this); new_slot(UPDATE_SLICE_TYPE_SLOT, &negato::update_slice_type, this); new_slot(UPDATE_BUFFER_SLOT, &negato::update_buffer, this); new_slot(UPDATE_VISIBILITY_SLOT, &negato::update_visibility, this); new_slot(UPDATE_TF_SLOT, &negato::update_tf, this); } //----------------------------------------------------------------------------- negato::~negato() noexcept = default; //----------------------------------------------------------------------------- void negato::configuring() { this->configure_params(); const config_t config = this->get_config().get_child("config."); if(config.count("orientation") != 0U) { const auto orientation_value = config.get("orientation"); if(orientation_value == "axial") { m_axis = axis_t::axial; } else if(orientation_value == "sagittal") { m_axis = axis_t::sagittal; } else if(orientation_value == "frontal") { m_axis = axis_t::frontal; } } if(config.count("changeSliceType") != 0U) { const auto change_value = config.get("changeSliceType"); if(change_value == "true") { m_change_slice_type_allowed = true; } else if(change_value == "false") { m_change_slice_type_allowed = false; } } } //----------------------------------------------------------------------------- void negato::update_buffer_from_image(QImage* _img) { if(_img == nullptr) { return; } // Window min/max const auto tf = m_tf.const_lock(); // Window max auto image = m_image.lock(); const data::image::size_t size = image->size(); const auto* img_buff = static_cast(image->buffer()); const std::size_t image_z_offset = size[0] * size[1]; std::uint8_t* p_dest = _img->bits(); // Fill image according to current slice type: if(m_axis == axis_t::sagittal) // sagittal { const auto sagital_index = static_cast(m_sagittal_index); for(std::size_t z = 0 ; z < size[2] ; ++z) { const std::size_t z_offset = (size[2] - 1 - z) * image_z_offset; const std::size_t zx_offset = z_offset + sagital_index; for(std::size_t y = 0 ; y < size[1] ; ++y) { const QRgb val = sight::module::viz::scene2d::adaptor::negato::get_q_image_val( img_buff[zx_offset + y * size[0]], *tf ); *p_dest++ = static_cast(qRed(val)); *p_dest++ = static_cast(qGreen(val)); *p_dest++ = static_cast(qBlue(val)); } } } else if(m_axis == axis_t::frontal) // frontal { const auto frontal_index = static_cast(m_frontal_index); const std::size_t y_offset = frontal_index * size[0]; for(std::size_t z = 0 ; z < size[2] ; ++z) { const std::size_t z_offset = (size[2] - 1 - z) * image_z_offset; const std::size_t zy_offset = z_offset + y_offset; for(std::size_t x = 0 ; x < size[0] ; ++x) { const QRgb val = sight::module::viz::scene2d::adaptor::negato::get_q_image_val(img_buff[zy_offset + x], *tf); *p_dest++ = static_cast(qRed(val)); *p_dest++ = static_cast(qGreen(val)); *p_dest++ = static_cast(qBlue(val)); } } } else if(m_axis == axis_t::axial) // axial { const auto axial_index = static_cast(m_axial_index); const std::size_t z_offset = axial_index * image_z_offset; for(std::size_t y = 0 ; y < size[1] ; ++y) { const std::size_t y_offset = y * size[0]; const std::size_t zy_offset = z_offset + y_offset; for(std::size_t x = 0 ; x < size[0] ; ++x) { const QRgb val = sight::module::viz::scene2d::adaptor::negato::get_q_image_val(img_buff[zy_offset + x], *tf); *p_dest++ = static_cast(qRed(val)); *p_dest++ = static_cast(qGreen(val)); *p_dest++ = static_cast(qBlue(val)); } } } QPixmap m_pixmap = QPixmap::fromImage(*m_q_img); m_pixmap_item->setPixmap(m_pixmap); } //----------------------------------------------------------------------------- QRgb negato::get_q_image_val(const std::int16_t _value, const data::transfer_function& _tf) { const data::transfer_function::color_t color = _tf.sample(_value); // use QImage::Format_RGBA8888 in QImage if you need alpha value return qRgb(static_cast(color.r * 255), static_cast(color.g * 255), static_cast(color.b * 255)); } //--------------------------------------------------------------------------- QImage* negato::create_q_image() { data::image::size_t size; data::image::spacing_t spacing; data::image::origin_t origin; { auto img = m_image.lock(); if(!data::helper::medical_image::check_image_validity(img.get_shared())) { return nullptr; } size = img->size(); spacing = img->spacing(); origin = img->origin(); } std::array q_image_spacing {}; std::array q_image_origin {}; std::array q_image_size {}; switch(m_axis) { case axis_t::x_axis: // sagittal this->m_y_axis->set_scale(-1); q_image_size[0] = static_cast(size[1]); q_image_size[1] = static_cast(size[2]); q_image_spacing[0] = spacing[1]; q_image_spacing[1] = spacing[2]; q_image_origin[0] = origin[1] - 0.5F * spacing[1]; q_image_origin[1] = -(origin[2] + static_cast(size[2]) * spacing[2] - 0.5 * spacing[2]); break; case axis_t::y_axis: // frontal q_image_size[0] = static_cast(size[0]); q_image_size[1] = static_cast(size[2]); q_image_spacing[0] = spacing[0]; q_image_spacing[1] = spacing[2]; q_image_origin[0] = origin[0] - 0.5F * spacing[0]; q_image_origin[1] = -(origin[2] + static_cast(size[2]) * spacing[2] - 0.5 * spacing[2]); break; case axis_t::z_axis: // axial q_image_size[0] = static_cast(size[0]); q_image_size[1] = static_cast(size[1]); q_image_spacing[0] = spacing[0]; q_image_spacing[1] = spacing[1]; q_image_origin[0] = origin[0] - 0.5 * spacing[0]; q_image_origin[1] = origin[1] - 0.5 * spacing[1]; break; default: SIGHT_FATAL("Unsupported value for orientation"); break; } // Create empty QImage auto* image = new QImage(q_image_size[0], q_image_size[1], QImage::Format_RGB888); // Place m_pixmapItem m_pixmap_item->resetTransform(); m_pixmap_item->setTransform(QTransform::fromScale(q_image_spacing[0], q_image_spacing[1]), true); m_pixmap_item->setPos(q_image_origin[0], q_image_origin[1]); // Force bounding box recomputing ( Qt bug ) m_layer->removeFromGroup(m_pixmap_item); m_layer->addToGroup(m_pixmap_item); // Update image scene this->get_scene_2d_render()->update_scene_size(0.20F); return image; } //----------------------------------------------------------------------------- void negato::starting() { auto tf = m_tf.lock(); auto image = m_image.lock(); m_axial_index = std::max(0, int(medHelper::get_slice_index(*image, medHelper::axis_t::axial).value_or(0))); m_frontal_index = std::max(0, int(medHelper::get_slice_index(*image, medHelper::axis_t::frontal).value_or(0))); m_sagittal_index = std::max(0, int(medHelper::get_slice_index(*image, medHelper::axis_t::sagittal).value_or(0))); m_pixmap_item = new QGraphicsPixmapItem(); m_pixmap_item->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); m_pixmap_item->setTransformationMode(Qt::SmoothTransformation); m_layer = new QGraphicsItemGroup(); m_layer->resetTransform(); m_layer->addToGroup(m_pixmap_item); m_layer->setPos(m_x_axis->origin(), m_y_axis->origin()); m_layer->setZValue(m_z_value); this->get_scene_2d_render()->get_scene()->addItem(m_layer); m_q_img = this->create_q_image(); this->update_buffer_from_image(m_q_img); this->get_scene_2d_render()->update_scene_size(1.F); } //----------------------------------------------------------------------------- void negato::updating() { m_q_img = this->create_q_image(); this->update_buffer_from_image(m_q_img); } //----------------------------------------------------------------------------- void negato::update_slice_index(int _axial, int _frontal, int _sagittal) { if(_sagittal != m_sagittal_index || _frontal != m_frontal_index || _axial != m_axial_index) { m_sagittal_index = _sagittal; m_frontal_index = _frontal; m_axial_index = _axial; } this->update_buffer_from_image(m_q_img); } //----------------------------------------------------------------------------- void negato::update_slice_type(int _from, int _to) { if(m_change_slice_type_allowed) { if(_to == static_cast(m_axis)) { m_axis = static_cast(_from); } else if(_from == static_cast(m_axis)) { m_axis = static_cast(_to); } // manages the modification of axes if(m_axis == axis_t::z_axis) { this->m_y_axis->set_scale(1); } else { this->m_y_axis->set_scale(-1); } this->updating(); } } //----------------------------------------------------------------------------- void negato::update_visibility(bool _is_visible) { if(_is_visible) // display the scene { m_layer->setVisible(true); } else // remove the layer from the scene { m_layer->setVisible(false); } } //----------------------------------------------------------------------------- void negato::update_buffer() { this->update_buffer_from_image(m_q_img); } //------------------------------------------------------------------------------ void negato::update_tf() { this->update_buffer_from_image(m_q_img); } //----------------------------------------------------------------------------- void negato::stopping() { this->get_scene_2d_render()->get_scene()->removeItem(m_layer); delete m_q_img; delete m_pixmap_item; delete m_layer; } //----------------------------------------------------------------------------- void negato::process_interaction(sight::viz::scene2d::data::event& _event) { // if a key is pressed if(_event.type() == sight::viz::scene2d::data::event::key_release) { // if pressed key is 'R' if(_event.get_key() == Qt::Key_R) { // get image origin QRectF rec_image = m_pixmap_item->sceneBoundingRect(); const auto scene_viewport = m_viewport.lock(); double scene_width = static_cast(this->get_scene_2d_render()->get_view()->width()); double scene_height = static_cast(this->get_scene_2d_render()->get_view()->height()); double ratio_y_on_x_image = rec_image.height() / rec_image.width(); double scene_ratio = scene_height / scene_width; if(scene_ratio > ratio_y_on_x_image) // used scene ratio { double width_view_port_new = rec_image.width(); double height_view_port_new = width_view_port_new * scene_ratio; // computes new y origin double new_origine_y = rec_image.y() - (height_view_port_new - rec_image.height()) / 2.F; scene_viewport->set_x(rec_image.x()); scene_viewport->set_y(new_origine_y); scene_viewport->set_width(width_view_port_new); scene_viewport->set_height(height_view_port_new); } else { double height_view_port_new = rec_image.height(); double width_view_port_new = height_view_port_new / scene_ratio; // computes new x origin double new_origine_x = rec_image.x() - (width_view_port_new - rec_image.width()) / 2.F; scene_viewport->set_x(new_origine_x); scene_viewport->set_y(rec_image.y()); scene_viewport->set_width(width_view_port_new); scene_viewport->set_height(height_view_port_new); } auto viewport_object = m_viewport.lock(); this->get_scene_2d_render()->get_view()->update_from_viewport(*viewport_object); } //image pixel if(_event.get_key() == Qt::Key_F) { m_pixmap_item->setTransformationMode(Qt::FastTransformation); this->updating(); } //image smooth if(_event.get_key() == Qt::Key_S) { m_pixmap_item->setTransformationMode(Qt::SmoothTransformation); this->updating(); } } sight::viz::scene2d::vec2d_t coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord()); coord.x = coord.x / m_layer->scale(); coord.y = coord.y / m_layer->scale(); if(_event.type() == sight::viz::scene2d::data::event::mouse_button_press && _event.get_button() == sight::viz::scene2d::data::event::right_button && _event.get_modifier() == sight::viz::scene2d::data::event::no_modifier) { m_point_is_captured = true; m_old_coord = _event.get_coord(); _event.set_accepted(true); } else if(m_point_is_captured) { if(_event.type() == sight::viz::scene2d::data::event::mouse_move) { sight::viz::scene2d::vec2d_t new_coord = _event.get_coord(); this->change_image_min_max_from_coord(m_old_coord, new_coord); m_old_coord = new_coord; _event.set_accepted(true); } else if(_event.get_button() == sight::viz::scene2d::data::event::right_button && _event.type() == sight::viz::scene2d::data::event::mouse_button_release) { m_point_is_captured = false; _event.set_accepted(true); } } } //----------------------------------------------------------------------------- void negato::change_image_min_max_from_coord( sight::viz::scene2d::vec2d_t& /*unused*/, sight::viz::scene2d::vec2d_t& _new_coord ) { const auto tf = m_tf.lock(); const double min = tf->window_min_max().first; const double max = tf->window_min_max().second; const double window = _new_coord.x - m_old_coord.x; const double level = _new_coord.y - m_old_coord.y; const double img_window = max - min; const double img_level = min + img_window / 2.0; const double new_img_level = img_level + level; const double new_img_window = img_window + img_window * window / 100.0; this->updating(); // Send signal tf->set_window(new_img_window); tf->set_level(new_img_level); tf->async_emit(this, data::transfer_function::WINDOWING_MODIFIED_SIG, new_img_window, new_img_level); } //------------------------------------------------------------------------------ service::connections_t negato::auto_connections() const { return { {IMAGE_IN, data::image::MODIFIED_SIG, service::slots::UPDATE}, {IMAGE_IN, data::image::SLICE_TYPE_MODIFIED_SIG, UPDATE_SLICE_INDEX_SLOT}, {IMAGE_IN, data::image::SLICE_INDEX_MODIFIED_SIG, UPDATE_SLICE_TYPE_SLOT}, {IMAGE_IN, data::image::BUFFER_MODIFIED_SIG, UPDATE_BUFFER_SLOT}, {TF_INOUT, data::transfer_function::MODIFIED_SIG, UPDATE_TF_SLOT}, {TF_INOUT, data::transfer_function::POINTS_MODIFIED_SIG, UPDATE_TF_SLOT}, {TF_INOUT, data::transfer_function::WINDOWING_MODIFIED_SIG, UPDATE_TF_SLOT} }; } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/negato.hpp000066400000000000000000000126621503402212300217540ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::viz::scene2d::adaptor { /** * @brief adaptor implementation to display one slice of an image. * * @section Slots Slots * - \b updateSliceIndex() : update image slice index * - \b updateSliceType() : update image slice type * - \b updateBuffer() : update image buffer * - \b update_visibility() : update image visibility * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection In-Out In-Out * - \b image [sight::data::image]: image to display. * - \b tf [sight::data::transfer_function] (optional): the current TransferFunction. If it is not defined, we use the * image's default transferFunction (CT-GreyLevel). * * @subsection Configuration Configuration: * - \b config (mandatory): contains the adaptor configuration * - \b xAxis (optional): x axis associated to the adaptor * - \b yAxis (optional): y axis associated to the adaptor * - \b zValue (optional, default=0): z value of the layer * - \b opacity (optional, default=1.0): adaptor opacity (float) * - \b orientation (optional, default axial): image orientation, axial, sagittal or frontal * - \b changeSliceType (optional, default true): specify if the negato allow slice type events */ class negato : public sight::viz::scene2d::adaptor { public: SIGHT_DECLARE_SERVICE(negato, sight::viz::scene2d::adaptor); negato() noexcept; ~negato() noexcept override; /** * @brief Returns proposals to connect service slots to associated object signals, * this method is used for obj/srv auto connection * * Connect image::MODIFIED_SIG to this::service::slots::UPDATE * Connect image::SLICE_INDEX_MODIFIED_SIG to this::UPDATE_SLICE_INDEX_SLOT * Connect image::SLICE_TYPE_MODIFIED_SIG to this::UPDATE_SLICE_TYPE_SLOT * Connect image::BUFFER_MODIFIED_SIG to this::UPDATE_BUFFER_SLOT */ service::connections_t auto_connections() const override; protected: void configuring() override; void starting() override; void updating() override; void stopping() override; void process_interaction(sight::viz::scene2d::data::event& _event) override; /// Slot: updates the TF void update_tf(); private: /** * @name Slots * @{ */ /// Slot: update image slice index void update_slice_index(int _axial, int _frontal, int _sagittal); /// Slot: update image slice type void update_slice_type(int _from, int _to); /// Slot: update image buffer void update_buffer(); /// Slot: update image visibility void update_visibility(bool _is_visible); /** * @} */ QImage* create_q_image(); void update_buffer_from_image(QImage* _img); void change_image_min_max_from_coord( sight::viz::scene2d::vec2d_t& _old_coord, sight::viz::scene2d::vec2d_t& _new_coord ); static QRgb get_q_image_val(std::int16_t _value, const data::transfer_function& _tf); QImage* m_q_img {nullptr}; QGraphicsPixmapItem* m_pixmap_item {nullptr}; QGraphicsItemGroup* m_layer {nullptr}; using axis_t = data::helper::medical_image::axis_t; /// The current orientation of the negato axis_t m_axis {axis_t::z_axis}; /// Used during negato interaction to manage window/level bool m_point_is_captured {false}; /// Ref. position when changing image window/level sight::viz::scene2d::vec2d_t m_old_coord {}; /// Specify if the negato allow slice type events bool m_change_slice_type_allowed {true}; static constexpr std::string_view IMAGE_IN = "image"; static constexpr std::string_view TF_INOUT = "tf"; sight::data::ptr m_image {this, IMAGE_IN}; sight::data::ptr m_tf {this, TF_INOUT}; sight::data::ptr m_viewport {this, "viewport"}; /// Stores current slice index on each orientation. std::int64_t m_axial_index {-1}; std::int64_t m_frontal_index {-1}; std::int64_t m_sagittal_index {-1}; }; } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/render.cpp000066400000000000000000000020071503402212300217410ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2023 IRCAD France * Copyright (C) 2017 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include #include /// Register render service in Services registry. sight-25.1.0/module/viz/scene2d/adaptor/square.cpp000066400000000000000000000144321503402212300217670ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene2d/adaptor/square.hpp" #include #include #include namespace sight::module::viz::scene2d::adaptor { const core::com::slots::key_t square::SET_DOUBLE_PARAMETER_SLOT = "set_double_parameter"; //----------------------------------------------------------------------------- square::square() noexcept { new_slot(SET_DOUBLE_PARAMETER_SLOT, &square::set_double_parameter, this); } //----------------------------------------------------------------------------- square::~square() noexcept = default; //----------------------------------------------------------------------------- void square::configuring() { this->configure_params(); const config_t config = this->get_config().get_child("config."); SIGHT_ASSERT("Attribute 'x' is missing", config.count("x")); SIGHT_ASSERT("Attribute 'y' is missing", config.count("y")); SIGHT_ASSERT("Attribute 'size' is missing", config.count("size")); m_coord.x = config.get("x"); m_coord.y = config.get("y"); m_size = config.get("size"); m_auto_refresh = config.get("autoRefresh", m_auto_refresh); m_interaction = config.get("interaction", m_interaction); if(config.count("color") != 0U) { this->set_color(config.get("color")); } } //----------------------------------------------------------------------------- void square::starting() { m_layer = new QGraphicsItemGroup(); m_rec = new QGraphicsRectItem(m_coord.x, m_coord.y, m_size, m_size); QPen pen; pen.setColor(Qt::GlobalColor(Qt::black)); m_rec->setPen(pen); m_rec->setBrush(m_color); m_layer->addToGroup(m_rec); m_layer->setOpacity(0.8); m_layer->setPos(m_x_axis->origin(), m_y_axis->origin()); m_layer->setZValue(m_z_value); this->get_scene_2d_render()->get_scene()->addItem(m_layer); } //----------------------------------------------------------------------------- void square::updating() { m_rec->setPos(m_coord.x, m_coord.y); } //----------------------------------------------------------------------------- void square::stopping() { this->get_scene_2d_render()->get_scene()->removeItem(m_layer); } //----------------------------------------------------------------------------- void square::set_color(const std::string& _color) { m_color = QColor(QString::fromStdString(_color)); if(!m_color.isValid()) { // Default value: black (if the color id cannot be parsed) m_color = Qt::GlobalColor(Qt::white); SIGHT_WARN("Color " + _color + " unknown, default value used."); } } //----------------------------------------------------------------------------- void square::process_interaction(sight::viz::scene2d::data::event& _event) { if(m_interaction) { if(_event.type() == sight::viz::scene2d::data::event::mouse_button_press && _event.get_button() == sight::viz::scene2d::data::event::left_button) { if(this->coord_view_is_in_item(_event.get_coord(), m_rec)) { m_point_is_captured = true; m_old_coord = this->coord_view_to_coord_item(_event.get_coord(), m_rec); m_rec->setBrush(Qt::yellow); _event.set_accepted(true); } } else if(m_point_is_captured && _event.type() == sight::viz::scene2d::data::event::mouse_move) { sight::viz::scene2d::vec2d_t new_coord = this->coord_view_to_coord_item(_event.get_coord(), m_rec); m_rec->moveBy(new_coord.x - m_old_coord.x, new_coord.y - m_old_coord.y); m_old_coord = new_coord; _event.set_accepted(true); } else if(m_point_is_captured && _event.type() == sight::viz::scene2d::data::event::mouse_button_release) { m_rec->setBrush(m_color); m_point_is_captured = false; _event.set_accepted(true); } } } //----------------------------------------------------------------------------- bool square::coord_view_is_in_item(const sight::viz::scene2d::vec2d_t& _coord, QGraphicsItem* _item) { sight::viz::scene2d::vec2d_t scene_point = this->get_scene_2d_render()->map_to_scene(_coord); QPointF sp(scene_point.x, scene_point.y); QPointF ip = _item->mapFromScene(sp); return _item->contains(ip); } //----------------------------------------------------------------------------- sight::viz::scene2d::vec2d_t square::coord_view_to_coord_item( const sight::viz::scene2d::vec2d_t& _coord, QGraphicsItem* /*unused*/ ) { sight::viz::scene2d::vec2d_t scene_point = this->get_scene_2d_render()->map_to_scene(_coord); //QPointF sp ( scenePoint.x, scenePoint.getY() ); //QPointF ip = item->mapFromScene( sp ); //return sight::viz::scene2d::vec2d_t( ip.x(), ip.y() ); return scene_point; } //----------------------------------------------------------------------------- void square::set_double_parameter(const double _val, std::string _key) { this->configure_params(); if(_key == "X") { m_coord.x = _val; } else if(_key == "Y") { m_coord.y = _val; } else { SIGHT_ERROR("The slot key : '" + _key + "' is not handled"); } if(m_auto_refresh) { this->updating(); } } } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/square.hpp000066400000000000000000000064301503402212300217730ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::viz::scene2d::adaptor { /** * @brief Create a square on the scene2D * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Configuration Configuration: * - \b config (mandatory): contains the adaptor configuration * - \b x (mandatory): specify x square coordinate * - \b y (mandatory): specify y square coordinate * - \b size (mandatory): specify size of the square * - \b zValue (optional, default=0): z value of the layer * - \b color (optional, default=black): color of the square * - \b autoRefresh (optional, default=true): specify if the position is updated as soon as a new position is * received * - \b interaction (optional, default=true): enable or disable mouse interaction with the square * * @section Slots Slots * -\b set_double_parameter(double, std::string): set the double parameters 'x' and 'y' */ class square : public sight::viz::scene2d::adaptor { public: SIGHT_DECLARE_SERVICE(square, sight::viz::scene2d::adaptor); square() noexcept; ~square() noexcept override; protected: void configuring() override; void starting() override; void updating() override; void stopping() override; void process_interaction(sight::viz::scene2d::data::event& _event) override; void set_color(const std::string& _color); bool coord_view_is_in_item(const sight::viz::scene2d::vec2d_t& _coord, QGraphicsItem* _item); sight::viz::scene2d::vec2d_t coord_view_to_coord_item( const sight::viz::scene2d::vec2d_t& _coord, QGraphicsItem* _item ); private: sight::viz::scene2d::vec2d_t m_coord {}; std::uint32_t m_size {0}; QColor m_color; QGraphicsItemGroup* m_layer {nullptr}; QGraphicsRectItem* m_rec {nullptr}; sight::viz::scene2d::vec2d_t m_old_coord {}; bool m_point_is_captured {false}; bool m_auto_refresh {true}; bool m_interaction {true}; static const core::com::slots::key_t SET_DOUBLE_PARAMETER_SLOT; void set_double_parameter(double _val, std::string _key); }; } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/transfer_function.cpp000066400000000000000000001762021503402212300242240ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2025 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene2d/adaptor/transfer_function.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene2d::adaptor { static const core::com::slots::key_t UPDATE_TF_SLOT = "update_tf"; //----------------------------------------------------------------------------- transfer_function::transfer_function() noexcept : m_event_filter(new QTimer()) { new_slot(UPDATE_TF_SLOT, &transfer_function::update_tf, this); } //----------------------------------------------------------------------------- transfer_function::~transfer_function() noexcept { delete m_event_filter; } //----------------------------------------------------------------------------- void transfer_function::configuring() { this->configure_params(); const config_t tree = this->get_config(); const auto config = tree.get_child("config."); const std::string polygon_color = config.get("lineColor", "#FFFFFF"); sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_polygons_pen, polygon_color); const std::string point_color = config.get("pointColor", "#FFFFFF"); sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_points_pen, point_color); m_second_opacity = config.get("secondOpacity", m_second_opacity); m_point_size = config.get("pointSize", m_point_size); m_interactive = config.get("interactive", m_interactive); } //------------------------------------------------------------------------------ void transfer_function::starting() { // Sets the current TF. { // Sets the current TF used to highlight it in the editor. const auto tf = m_tf.const_lock(); if(tf && !tf->pieces().empty()) { m_current_tf = tf->pieces().front(); } else { SIGHT_FATAL("The current TF mustn't be null"); } } // Adds the layer item to the scene. m_layer = new QGraphicsItemGroup(); this->get_scene_2d_render()->get_scene()->addItem(m_layer); m_points_pen.setCosmetic(true); m_points_pen.setWidthF(0); m_polygons_pen.setCosmetic(true); m_polygons_pen.setWidthF(0); // Creates all entities. this->updating(); } //------------------------------------------------------------------------------ service::connections_t transfer_function::auto_connections() const { connections_t connections; connections.push(VIEWPORT_INPUT, sight::viz::scene2d::data::viewport::MODIFIED_SIG, service::slots::UPDATE); connections.push(CURRENT_TF_INOUT, data::object::MODIFIED_SIG, UPDATE_TF_SLOT); connections.push(CURRENT_TF_INOUT, data::transfer_function::WINDOWING_MODIFIED_SIG, service::slots::UPDATE); connections.push(CURRENT_TF_INOUT, data::transfer_function::POINTS_MODIFIED_SIG, service::slots::UPDATE); return connections; } //----------------------------------------------------------------------------- void transfer_function::updating() { // Clears old data. this->destroy_tf_polygons(); this->destroy_tf_points(); this->release_tf_data(); // Creates all TF. this->create_tf_points(); this->create_tf_polygons(); // Builds the layer. this->build_layer(); } //----------------------------------------------------------------------------- void transfer_function::stopping() { this->destroy_tf_polygons(); this->destroy_tf_points(); this->release_tf_data(); } //----------------------------------------------------------------------------- void transfer_function::release_tf_data() { for(piece_view* const piece_view : m_piece_view) { delete piece_view; } m_piece_view.clear(); } //----------------------------------------------------------------------------- void transfer_function::create_tf_points() { SIGHT_ASSERT("The current TF mustn't be null", m_current_tf); // Iterates over each TF to create a piece view. const auto tf = m_tf.const_lock(); if(!tf->pieces().empty()) { int z_index = 0; for(const auto& tf_data : tf->pieces()) { // Sets the z-index of the current TF over all others. const bool is_current = m_current_tf == tf_data; int index = is_current ? static_cast(tf->pieces().size()) : z_index; // Pushes the piece view to the vector. m_piece_view.push_back(this->create_piece_view(tf_data, index)); ++z_index; } } } //----------------------------------------------------------------------------- void transfer_function::destroy_tf_points() { // Removes TF point items from the scene and clear the TF point vector of each piece view. for(piece_view* const piece_view : m_piece_view) { for(std::pair& tf_point : piece_view->m_tf_points) { this->get_scene_2d_render()->get_scene()->removeItem(tf_point.second); delete tf_point.second; } piece_view->m_tf_points.clear(); } } //----------------------------------------------------------------------------- transfer_function::piece_view* transfer_function::create_piece_view( const data::transfer_function_piece::sptr _tf, int _z_index ) { auto viewport = m_viewport.lock(); const double scene_width = this->get_scene_2d_render()->get_view()->width(); const double scene_height = this->get_scene_2d_render()->get_view()->height(); // Computes point size in screen space and keep the smallest size (relatively to width or height). double point_size = scene_width * m_point_size; if(point_size > scene_height * m_point_size) { point_size = scene_height * m_point_size; } const double viewport_width = viewport->width(); const double viewport_height = viewport->height(); // Computes point size from screen space to viewport space. const double point_width = (viewport_width * point_size) / scene_width; const double point_height = (viewport_height * point_size) / scene_height; // Creates the piece view and fill basic informations. auto* piece_view = new struct piece_view (); piece_view->m_tf = _tf; piece_view->m_z_index = _z_index; // Fills piece view point with color points. for(const auto& elt : *_tf) { // Computes TF value from TF space to window/level space. const data::transfer_function::value_t value = _tf->map_value_to_window(elt.first); // Creates the color. const data::transfer_function::color_t color_t = elt.second; const vec2d_t val_color(value, std::min(color_t.a, 1.)); vec2d_t coord = this->map_adaptor_to_scene(val_color); // Builds a point item, set its color, pen and zIndex. auto* point = new QGraphicsEllipseItem( coord.x - point_width / 2, coord.y - point_height / 2, point_width, point_height ); QColor color(static_cast(color_t.r * 255), static_cast(color_t.g * 255), static_cast(color_t.b * 255)); point->setBrush(QBrush(color)); point->setPen(m_points_pen); point->setZValue(piece_view->m_z_index * 2 + 1); // Pushes it back into the point vector if(_tf->window() > 0) { piece_view->m_tf_points.emplace_back(coord, point); } // If the window is negative, the TF is reversed and point must be sort in reverse. else { piece_view->m_tf_points.insert(piece_view->m_tf_points.begin(), std::make_pair(coord, point)); } } return piece_view; } //----------------------------------------------------------------------------- void transfer_function::create_tf_polygons() { // Iterates over all piece views to create polygons. for(piece_view* const piece_view : m_piece_view) { this->create_tf_polygon(piece_view); } } //----------------------------------------------------------------------------- void transfer_function::create_tf_polygon(piece_view* const _piece_view) { if(m_interactive) { const auto viewport = m_viewport.lock(); QVector position; QLinearGradient grad; const std::pair& first_tf_point = _piece_view->m_tf_points.front(); const std::pair& last_tf_point = _piece_view->m_tf_points.back(); const QGraphicsEllipseItem* const first_point = first_tf_point.second; double x_begin = first_tf_point.first.x; double x_end = last_tf_point.first.x; if(_piece_view->m_tf->clamped()) { position.append(QPointF(x_begin, 0)); } else { if(x_begin > viewport->x()) { x_begin = viewport->x() - 10; position.append(QPointF(x_begin, 0)); position.append(QPointF(x_begin, first_tf_point.first.y)); } else { position.append(QPointF(x_begin, 0)); } if(x_end < viewport->x() + viewport->width()) { x_end = viewport->x() + viewport->width() + 10; } } grad.setColorAt(0, first_point->brush().color()); grad.setStart(x_begin, 0); grad.setFinalStop(x_end, 0); const double distance_max = x_end - x_begin; // Iterates on TF points vector to add line and polygon items to the polygons vector. if(_piece_view->m_tf->get_interpolation_mode() == data::transfer_function::interpolation_mode::linear) { sight::module::viz::scene2d::adaptor::transfer_function::build_linear_polygons( _piece_view, position, grad, distance_max ); } else { sight::module::viz::scene2d::adaptor::transfer_function::build_nearest_polygons( _piece_view, position, grad, distance_max ); } if(!_piece_view->m_tf->clamped()) { if(x_end == viewport->x() + viewport->width() + 10) { position.append(QPointF(x_end, last_tf_point.first.y)); } const double last_point_x = last_tf_point.first.x; grad.setColorAt((last_point_x - x_begin) / distance_max, last_tf_point.second->brush().color()); } position.append(QPointF(x_end, 0)); grad.setColorAt(1, last_tf_point.second->brush().color()); auto* const poly = new QGraphicsPolygonItem(QPolygonF(position)); // Sets gradient, opacity and pen to the polygon poly->setBrush(QBrush(grad)); poly->setPen(m_polygons_pen); poly->setZValue(_piece_view->m_z_index * 2); // If the z-index is the highest, it's the current one. if(static_cast(_piece_view->m_z_index) == m_piece_view.size()) { poly->setOpacity(m_opacity); } else { poly->setOpacity(m_second_opacity); } // Pushes the polygon back into the polygons vector _piece_view->m_tf_polygons.push_back(poly); } else { const auto viewport = m_viewport.lock(); auto* const view = this->get_scene_2d_render()->get_view(); const double viewport_width = viewport->width(); const double step = viewport_width / view->width(); const uint size = static_cast(viewport_width / step); double f = viewport->left(); for(uint i = 0 ; i < size ; ++i) { const auto color = _piece_view->m_tf->sample(f); vec2d_t pt = this->map_adaptor_to_scene({f, color.a}); QColor q_color; q_color.setRgbF(float(color.r), float(color.g), float(color.b), 1.0F); QBrush brush = QBrush(q_color); auto* rect = new QGraphicsRectItem(pt.x, 0.0, step, pt.y); rect->setBrush(brush); rect->setPen(Qt::NoPen); _piece_view->m_tf_polygons.push_back(rect); f += step; } } } //----------------------------------------------------------------------------- void transfer_function::destroy_tf_polygons() { // Removes polygon items from the scene and clear the polygon vector. for(piece_view* const piece_view : m_piece_view) { this->destroy_tf_polygon(piece_view); } } //----------------------------------------------------------------------------- void transfer_function::destroy_tf_polygon(piece_view* _piece_view) { for(auto& poly : _piece_view->m_tf_polygons) { this->get_scene_2d_render()->get_scene()->removeItem(poly); delete poly; } // Removes polygon items from the scene and clear the polygon vector. _piece_view->m_tf_polygons.clear(); } //----------------------------------------------------------------------------- void transfer_function::build_linear_polygons( piece_view* const _piece_view, QVector& _position, QLinearGradient& _grad, double _distance_max ) { const std::vector >& tf_points = _piece_view->m_tf_points; for(auto tf_point_it = tf_points.cbegin() ; tf_point_it != tf_points.cend() - 1 ; ++tf_point_it) { const QPointF p1(tf_point_it->first.x, tf_point_it->first.y); const QPointF p2((tf_point_it + 1)->first.x, (tf_point_it + 1)->first.y); _position.append(p1); _position.append(p2); // Builds the gradient _grad.setColorAt((p1.x() - _position[0].x()) / _distance_max, (tf_point_it->second)->brush().color()); } } //----------------------------------------------------------------------------- void transfer_function::build_nearest_polygons( piece_view* const _piece_view, QVector& _position, QLinearGradient& _grad, double _distance_max ) { const std::vector >& tf_points = _piece_view->m_tf_points; for(auto tf_point_it = tf_points.cbegin() ; tf_point_it != tf_points.cend() - 1 ; ++tf_point_it) { const QPointF p1(tf_point_it->first.x, tf_point_it->first.y); const QPointF p4((tf_point_it + 1)->first.x, (tf_point_it + 1)->first.y); const QPointF p2(p1.x() + (p4.x() - p1.x()) / 2., p1.y()); const QPointF p3(p2.x(), p4.y()); _position.append(p1); _position.append(p2); _position.append(p3); _position.append(p4); const double d1 = (p1.x() - _position[0].x()) / _distance_max; const double d2 = (p2.x() - _position[0].x()) / _distance_max; const double d3 = d2 + std::numeric_limits::epsilon(); const double d4 = (p4.x() - _position[0].x()) / _distance_max; const QColor c1 = (tf_point_it->second)->brush().color(); const QColor c4 = ((tf_point_it + 1)->second)->brush().color(); _grad.setColorAt(d1, c1); _grad.setColorAt(d2, c1); _grad.setColorAt(d3, c4); _grad.setColorAt(d4, c4); } } //----------------------------------------------------------------------------- void transfer_function::build_layer() { // Adds graphics items vectors to the layer. for(piece_view* const piece_view : m_piece_view) { for(std::pair& tf_point : piece_view->m_tf_points) { m_layer->addToGroup(tf_point.second); } for(auto& poly : piece_view->m_tf_polygons) { m_layer->addToGroup(poly); } } // Adjusts the layer's position and zValue depending on the associated axis. m_layer->setPos(m_x_axis->origin(), m_y_axis->origin()); m_layer->setZValue(m_z_value); } //----------------------------------------------------------------------------- void transfer_function::set_current_tf(piece_view* const _piece_view) { // Sets the new current TF. SIGHT_ASSERT("The current TF mustn't be null", m_current_tf); // Find the old piece view. piece_view* const current_piece_view = *(std::find_if( m_piece_view.begin(), m_piece_view.end(), [&](const piece_view* _piece_view) { return _piece_view->m_tf == m_current_tf; })); // Changes the current piece view. m_current_tf = _piece_view->m_tf; // Recomputes z-index and set the z-index of the selected TF over all others. int z_index = 0; for(piece_view* piece_view : m_piece_view) { piece_view->m_z_index = piece_view->m_tf == m_current_tf ? static_cast(m_piece_view.size()) : z_index; for(std::pair& point : piece_view->m_tf_points) { point.second->setZValue(piece_view->m_z_index * 2 + 1); } ++z_index; } // Re-draw polygons since the current TF as changed. this->destroy_tf_polygon(current_piece_view); this->create_tf_polygon(current_piece_view); this->destroy_tf_polygon(_piece_view); this->create_tf_polygon(_piece_view); this->build_layer(); } //----------------------------------------------------------------------------- std::vector transfer_function::get_matching_piece_view( const sight::viz::scene2d::data::event& _event ) const { // Finds all piece views that match the clicked coord. std::vector matching_piece_view; const QPoint scene_pos = QPoint( static_cast(_event.get_coord().x), static_cast(_event.get_coord().y) ); QList items = this->get_scene_2d_render()->get_view()->items(scene_pos); // Fills the piece view vector with clicked ones. for(piece_view* const piece_view : m_piece_view) { // Checks if a polygon is clicked. if(items.indexOf(piece_view->m_tf_polygons.front()) >= 0) { matching_piece_view.push_back(piece_view); } } return matching_piece_view; } //----------------------------------------------------------------------------- void transfer_function::process_interaction(sight::viz::scene2d::data::event& _event) { if(!m_interactive) { return; } SIGHT_ASSERT("The current TF mustn't be null", m_current_tf); // If it's a resize event, all the scene must be recomputed. if(_event.type() == sight::viz::scene2d::data::event::resize) { this->updating(); _event.set_accepted(true); return; } // If a point as already been captured. if(m_captured_tf_point != nullptr) { if(_event.get_button() == sight::viz::scene2d::data::event::left_button && _event.type() == sight::viz::scene2d::data::event::mouse_button_release) { // Releases capture point. this->left_button_release_event(); _event.set_accepted(true); return; } } // If a piece view as already been captured. if(m_captured_tf.first) { if(_event.type() == sight::viz::scene2d::data::event::mouse_move) { // Changes the piece view level. this->mouse_move_on_piece_view_event(_event); _event.set_accepted(true); return; } if(_event.get_button() == sight::viz::scene2d::data::event::mid_button && _event.type() == sight::viz::scene2d::data::event::mouse_button_release) { // Releases capture piece view. this->mid_button_release_event(); _event.set_accepted(true); return; } } const QPoint scene_pos = QPoint( static_cast(_event.get_coord().x), static_cast(_event.get_coord().y) ); QList items = this->get_scene_2d_render()->get_view()->items(scene_pos); // Checks if a point is clicked. for(piece_view* const piece_view : m_piece_view) { for(std::pair& tf_point : piece_view->m_tf_points) { // If a point has already been captured. if(m_captured_tf_point == &tf_point) { if(_event.type() == sight::viz::scene2d::data::event::mouse_move) { // Moves the captured point. this->mouse_move_on_point_event(piece_view, _event); _event.set_accepted(true); return; } } else if(items.indexOf(tf_point.second) >= 0) { // If there is a double click on a point, open a color dialog. if(_event.get_button() == sight::viz::scene2d::data::event::left_button && _event.type() == sight::viz::scene2d::data::event::mouse_button_double_click) { this->left_button_double_click_on_point_event(piece_view, tf_point); _event.set_accepted(true); return; } // If left button is pressed on a point, set the TF as current. if(_event.get_button() == sight::viz::scene2d::data::event::left_button && _event.type() == sight::viz::scene2d::data::event::mouse_button_press) { this->left_button_click_on_point_event(piece_view, tf_point); _event.set_accepted(true); return; } // If right button is pressed on a point, remove it. if(_event.get_button() == sight::viz::scene2d::data::event::right_button && _event.type() == sight::viz::scene2d::data::event::mouse_button_press && piece_view->m_tf == m_current_tf && piece_view->m_tf_points.size() > 2) { this->right_button_click_on_point_event(piece_view, tf_point); _event.set_accepted(true); return; } } } } // Adds a new TF point. if(_event.get_button() == sight::viz::scene2d::data::event::left_button && _event.type() == sight::viz::scene2d::data::event::mouse_button_double_click) { // Cancel the previous single click interaction. m_event_filter->stop(); this->left_button_double_click_event(_event); _event.set_accepted(true); return; } // If left button is pressed, set the nearest TF as current. if(_event.get_button() == sight::viz::scene2d::data::event::left_button && _event.type() == sight::viz::scene2d::data::event::mouse_button_press) { // Cancel the previous event if it's needed. m_event_filter->stop(); delete m_event_filter; // Validates the event in 250ms, this allow to the double click event to cancel the interaction. m_event_filter = new QTimer(); QTimer::connect( m_event_filter, &QTimer::timeout, this, [_event, this]() { this->left_button_click_event(_event); }); m_event_filter->setSingleShot(true); m_event_filter->start(250); // _event.setAccepted(true); return; } // If middle button is pressed, select the current TF to adjust the window/level. if(_event.get_button() == sight::viz::scene2d::data::event::mid_button && _event.type() == sight::viz::scene2d::data::event::mouse_button_press) { this->mid_button_click_event(_event); return; } // If right button is pressed, open a context menu to manage multiple actions. if(_event.get_button() == sight::viz::scene2d::data::event::right_button && _event.type() == sight::viz::scene2d::data::event::mouse_button_press) { this->right_button_c_lick_event(_event); _event.set_accepted(true); return; } // If the middle button wheel moves, change the whole piece view opacity. if(_event.get_button() == sight::viz::scene2d::data::event::no_button && (_event.type() == sight::viz::scene2d::data::event::mouse_wheel_down || _event.type() == sight::viz::scene2d::data::event::mouse_wheel_up)) { this->mid_button_wheel_move_event(_event); return; } } //----------------------------------------------------------------------------- void transfer_function::left_button_click_event(const sight::viz::scene2d::data::event& _event) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); std::vector matching_piece_view = this->get_matching_piece_view(_event); if(!matching_piece_view.empty()) { if(matching_piece_view.size() == 1) { if(matching_piece_view[0]->m_tf != m_current_tf) { this->set_current_tf(matching_piece_view[0]); } } // Finds the closest one. else { const auto tf = m_tf.lock(); sight::viz::scene2d::vec2d_t click_coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord()); float closest_distance = std::numeric_limits::max(); piece_view* new_current_piece_view = nullptr; for(piece_view* piece_view : matching_piece_view) { // Finds nearest position of the iterate piece view. float local_closest_distance = std::numeric_limits::max(); struct piece_view* local_new_current_piece_view = nullptr; for(std::size_t i = 0 ; i <= piece_view->m_tf_points.size() ; ++i) { vec2d_t tf_point1; vec2d_t tf_point2; // Creates the first fictional TF point. if(i == 0) { tf_point2 = piece_view->m_tf_points[i].first; if(piece_view->m_tf->clamped()) { // The first point is the same a the real first but with a zero alpha channel. tf_point1 = vec2d_t(tf_point2.x, 0); } else { // The first point is the same a the real but with an infinite lower value. const auto viewport = m_viewport.lock(); tf_point1 = vec2d_t(viewport->x(), tf_point2.y); } } // Creates the last fictional TF point. else if(i == piece_view->m_tf_points.size()) { tf_point1 = piece_view->m_tf_points[i - 1].first; if(piece_view->m_tf->clamped()) { // The last point is the same a the real last but with a zero alpha channel. tf_point2 = vec2d_t(tf_point1.x, 0); } else { // The last point is the same a the real but with an infinite upper value. const auto viewport = m_viewport.lock(); tf_point2 = vec2d_t(viewport->x() + viewport->width(), tf_point1.y); } } // Retrieves two TF points. else { tf_point1 = piece_view->m_tf_points[i - 1].first; tf_point2 = piece_view->m_tf_points[i].first; } // Gets a line/point projection. const QLineF line(tf_point1.x, tf_point1.y, tf_point2.x, tf_point2.y); QLineF perpendicular_line(click_coord.x, click_coord.y, click_coord.x, 0); perpendicular_line.setAngle(90.F + line.angle()); QPointF intersect_point; line.intersects(perpendicular_line, &intersect_point); const QVector2D origin(static_cast(click_coord.x), static_cast(click_coord.y)); float distance = std::numeric_limits::max(); // Checks if the intersection belong the segment. if(intersect_point.x() >= tf_point1.x && intersect_point.x() <= tf_point2.x) { const QVector2D intersect(intersect_point); const QVector2D projection = origin - intersect; distance = projection.length(); } // Elses the lower distance is between the point and one of the segment edge. else { const QVector2D first_line(static_cast(click_coord.x - tf_point1.x), static_cast(click_coord.y - tf_point1.y)); const QVector2D second_line(static_cast(click_coord.x - tf_point2.x), static_cast(click_coord.y - tf_point2.y)); distance = first_line.length(); if(second_line.length() < distance) { distance = second_line.length(); } } if(distance < local_closest_distance) { local_closest_distance = distance; local_new_current_piece_view = piece_view; } } if(local_closest_distance < closest_distance) { closest_distance = local_closest_distance; new_current_piece_view = local_new_current_piece_view; } } SIGHT_ASSERT("newCurrentPieceView is null", new_current_piece_view != nullptr); // Sets the new current TF. if(new_current_piece_view->m_tf != m_current_tf) { this->set_current_tf(new_current_piece_view); } } } } //----------------------------------------------------------------------------- void transfer_function::left_button_click_on_point_event( piece_view* const _piece_view, std::pair& _tf_point ) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); // Stores the captured TF point in case it's moved. m_captured_tf_point = &_tf_point; // Sets the selected point pen to lighter to get a visual feedback that the selected point is selected. const QColor color = _tf_point.second->brush().color(); QPen tf_point_pen(color); tf_point_pen.setCosmetic(true); _tf_point.second->setPen(tf_point_pen); // Sets the new current TF. if(_piece_view->m_tf != m_current_tf) { this->set_current_tf(_piece_view); } } //----------------------------------------------------------------------------- void transfer_function::mouse_move_on_point_event( piece_view* const _piece_view, const sight::viz::scene2d::data::event& _event ) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); const auto tf = m_tf.lock(); // m_capturedTFPoint must be previously sets by // leftButtonClickOnPointEvent(PieceView* const, std::pair< vec2d_t, QGraphicsEllipseItem* >&) SIGHT_ASSERT("The captured TF point must exist", m_captured_tf_point); const auto point_it = std::find( _piece_view->m_tf_points.begin(), _piece_view->m_tf_points.end(), *m_captured_tf_point ); SIGHT_ASSERT("The captured point is not found", point_it != _piece_view->m_tf_points.end()); // Gets the previous point of the TF. auto previous_point = point_it; if(*m_captured_tf_point != _piece_view->m_tf_points.front()) { --previous_point; } // Gets the next point of the TF. auto next_point = point_it; if(*m_captured_tf_point != _piece_view->m_tf_points.back()) { ++next_point; } // Gets position informations of the previous and the next point. const double previous_point_x_coord = previous_point->first.x; const double next_point_x_coord = next_point->first.x; // Gets the actual mouse point coordinates. sight::viz::scene2d::vec2d_t new_coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord(), true); // Clamps new y coord between -1 and 0. new_coord.y = std::clamp(new_coord.y, -1., 0.); // Clamps new coord in the viewport. { auto viewport = m_viewport.lock(); new_coord = glm::clamp( new_coord, glm::dvec2(viewport->left(), viewport->top()), glm::dvec2(viewport->right(), viewport->bottom()) ); } // Clamps new x coord between the previous and the next one. const double delta = 1.; if(*m_captured_tf_point == _piece_view->m_tf_points.front()) { if(new_coord.x >= next_point_x_coord) { new_coord.x = next_point_x_coord - delta; } } else if(*m_captured_tf_point == _piece_view->m_tf_points.back()) { if(new_coord.x <= previous_point_x_coord) { new_coord.x = previous_point_x_coord + delta; } } else { if(new_coord.x <= previous_point_x_coord) { new_coord.x = previous_point_x_coord + delta; } else if(new_coord.x >= next_point_x_coord) { new_coord.x = next_point_x_coord - delta; } } // Moves the selected TF point by the difference between the old coordinates and the new ones. m_captured_tf_point->second->moveBy( new_coord.x - m_captured_tf_point->first.x, new_coord.y - m_captured_tf_point->first.y ); // Stores new coordinates to the captured one. m_captured_tf_point->first.x = new_coord.x; m_captured_tf_point->first.y = new_coord.y; // Re-draw the current polygons. this->destroy_tf_polygon(_piece_view); this->create_tf_polygon(_piece_view); this->build_layer(); // Updates the TF with the new point position. std::size_t point_index = std::size_t(point_it - _piece_view->m_tf_points.begin()); // If the window is negative, the TF point list is reversed compared to the TF data. if(_piece_view->m_tf->window() < 0) { point_index = _piece_view->m_tf_points.size() - 1 - point_index; } const data::transfer_function_piece::sptr tf_piece = _piece_view->m_tf; // Retrieves the TF point. auto tf_data_it = tf_piece->cbegin(); for(unsigned i = 0 ; i < point_index ; ++i) { tf_data_it++; } // Gets the TF point information data::transfer_function::value_t old_tf_value = tf_data_it->first; data::transfer_function::color_t color = tf_data_it->second; // Gets new window/level min max value in the window/level space. const double min = _piece_view->m_tf_points.begin()->first.x; const double max = _piece_view->m_tf_points.rbegin()->first.x; // Computes TF value from window/level space to TF space. const data::transfer_function::value_t new_tf_value = tf_piece->map_value_from_window(new_coord.x); // Removes the old TF point. tf_piece->erase(old_tf_value); // Updates the color alpha channel. color.a = std::abs(new_coord.y); // Adds the new TF point. tf_piece->insert({new_tf_value, color}); // Updates the window/level. if(tf_piece->window() > 0) { tf_piece->set_window_min_max(data::transfer_function::min_max_t(min, max)); } else { tf_piece->set_window_min_max(data::transfer_function::min_max_t(max, min)); } tf->fit_window(); tf->async_emit(this, data::transfer_function::POINTS_MODIFIED_SIG); } //----------------------------------------------------------------------------- void transfer_function::left_button_release_event() { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); // Removes the highlighting of the captured point. m_captured_tf_point->second->setPen(m_points_pen); m_captured_tf_point = nullptr; } //----------------------------------------------------------------------------- void transfer_function::right_button_click_on_point_event( piece_view* const _piece_view, std::pair& _tf_point ) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); const auto tf = m_tf.lock(); // Updates the TF. auto point_it = std::find(_piece_view->m_tf_points.begin(), _piece_view->m_tf_points.end(), _tf_point); SIGHT_ASSERT("The captured point is not found", point_it != _piece_view->m_tf_points.end()); std::size_t point_index = std::size_t(point_it - _piece_view->m_tf_points.begin()); const data::transfer_function_piece::sptr tf_piece = _piece_view->m_tf; { // If the window is negative, the TF point list is reversed compared to the TF data. const double window = tf_piece->window(); if(window <= 0) { point_index = _piece_view->m_tf_points.size() - 1 - point_index; } // Retrieves the TF point. auto tf_data_it = tf_piece->cbegin(); for(unsigned i = 0 ; i < point_index ; ++i) { tf_data_it++; } // Removes the TF point. const data::transfer_function::value_t tf_value = tf_data_it->first; tf_piece->erase(tf_value); // Gets new window/level min max value in the window/level space. double min = _piece_view->m_tf_points.begin()->first.x; double max = _piece_view->m_tf_points.rbegin()->first.x; // If the removed point is the last or the first, the min max is wrong and need to be updated. if((point_index == 0 && window >= 0) || (point_index == _piece_view->m_tf_points.size() - 1 && window < 0)) { min = (_piece_view->m_tf_points.begin() + 1)->first.x; } else if((point_index == _piece_view->m_tf_points.size() - 1 && window >= 0) || (point_index == 0 && window < 0)) { max = (_piece_view->m_tf_points.rbegin() + 1)->first.x; } // Updates the window/level. if(window > 0) { tf_piece->set_window_min_max(data::transfer_function::min_max_t(min, max)); } else { tf_piece->set_window_min_max(data::transfer_function::min_max_t(max, min)); } } tf->fit_window(); tf->async_emit(this, data::transfer_function::POINTS_MODIFIED_SIG); this->get_scene_2d_render()->get_scene()->removeItem(point_it->second); delete point_it->second; _piece_view->m_tf_points.erase(point_it); // Re-draw the current polygons. this->destroy_tf_polygon(_piece_view); this->create_tf_polygon(_piece_view); this->build_layer(); } //----------------------------------------------------------------------------- void transfer_function::left_button_double_click_on_point_event( piece_view* const _piece_view, std::pair& _tf_point ) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); QColor old_color; { // Lock in a scope to avoid potential dead_locks because of the exec() implied by QColorDialog static function const auto tf = m_tf.lock(); // Opens a QColorDialog with the selected circle color and the tf point alpha as default rgba color. old_color = _tf_point.second->brush().color(); old_color.setAlphaF(float(-_tf_point.first.y)); } QColor new_color = QColorDialog::getColor( old_color, this->get_scene_2d_render()->get_view(), QString("Choose the point color"), QColorDialog::ShowAlphaChannel ); if(new_color.isValid()) { // Updates the TF. auto point_it = std::find(_piece_view->m_tf_points.begin(), _piece_view->m_tf_points.end(), _tf_point); SIGHT_ASSERT("The captured point is not found", point_it != _piece_view->m_tf_points.end()); std::size_t point_index = std::size_t(point_it - _piece_view->m_tf_points.begin()); const auto tf_piece = _piece_view->m_tf; { // If the window is negative, the TF point list is reversed compared to the TF data. if(tf_piece->window() < 0) { point_index = _piece_view->m_tf_points.size() - 1 - point_index; } // Retrieves the TF point. auto tf_data_it = tf_piece->cbegin(); for(unsigned i = 0 ; i < point_index ; ++i) { tf_data_it++; } // Removes the TF point. data::transfer_function::value_t tf_value = tf_data_it->first; tf_piece->erase(tf_value); // Adds the new one with the new color. data::transfer_function::color_t color_t(new_color.red() / 255., new_color.green() / 255., new_color.blue() / 255., old_color.alpha() / 255.); tf_piece->insert({tf_value, color_t}); } const auto tf = m_tf.lock(); tf->fit_window(); tf->async_emit(this, data::transfer_function::POINTS_MODIFIED_SIG); // Updates the displayed TF point. new_color.setAlpha(255); _tf_point.second->setBrush(QBrush(new_color)); // Re-draw the current polygons. this->destroy_tf_polygon(_piece_view); this->create_tf_polygon(_piece_view); this->build_layer(); } } //----------------------------------------------------------------------------- void transfer_function::left_button_double_click_event(const sight::viz::scene2d::data::event& _event) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); sight::viz::scene2d::vec2d_t new_coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord()); new_coord.y = std::clamp(new_coord.y, -1., 0.); { const auto tf = m_tf.lock(); // Finds the current piece view. SIGHT_ASSERT("The current TF mustn't be null", m_current_tf); piece_view* const current_piece_view = *(std::find_if( m_piece_view.begin(), m_piece_view.end(), [&](const piece_view* _piece_view) { return _piece_view->m_tf == m_current_tf; })); const auto tf_piece = current_piece_view->m_tf; { data::transfer_function::color_t new_color; // The new coord becomes the new first TF point, get the current first color in the list. if(new_coord.x < current_piece_view->m_tf_points.front().first.x) { const QColor first_color = current_piece_view->m_tf_points.front().second->brush().color(); new_color = data::transfer_function::color_t( first_color.red() / 255., first_color.green() / 255., first_color.blue() / 255., -new_coord.y ); } // The new coord becomes the new last TF point, get the current last color in the list. else if(new_coord.x > current_piece_view->m_tf_points.back().first.x) { const QColor first_color = current_piece_view->m_tf_points.back().second->brush().color(); new_color = data::transfer_function::color_t( first_color.red() / 255., first_color.green() / 255., first_color.blue() / 255., -new_coord.y ); } // Gets an interpolate color since the new point is between two others. else { new_color = tf_piece->sample(new_coord.x); new_color.a = -new_coord.y; } // Adds the new TF point. const data::transfer_function::value_t tf_value = tf_piece->map_value_from_window(new_coord.x); tf_piece->insert({tf_value, new_color}); // Gets new window/level min max value in the window/level space. const double min = std::min(current_piece_view->m_tf_points.begin()->first.x, new_coord.x); const double max = std::max(current_piece_view->m_tf_points.rbegin()->first.x, new_coord.x); // Updates the window/level. if(tf_piece->window() > 0) { tf_piece->set_window_min_max(data::transfer_function::min_max_t(min, max)); } else { tf_piece->set_window_min_max(data::transfer_function::min_max_t(max, min)); } } tf->fit_window(); tf->async_emit(this, data::transfer_function::POINTS_MODIFIED_SIG); } // Re-draw all the scene. this->updating(); } //----------------------------------------------------------------------------- void transfer_function::mid_button_click_event(sight::viz::scene2d::data::event& _event) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); const auto tf = m_tf.lock(); // Finds all piece views that match the clicked coord. std::vector matching_piece_view = this->get_matching_piece_view(_event); // Checks if the current tf is in the matching list. const auto matching_it = std::find_if( matching_piece_view.begin(), matching_piece_view.end(), [&](const piece_view* _piece_view) { return _piece_view->m_tf == m_current_tf; }); // Moves the window/level only if the mouse if over the current TF. if(matching_it != matching_piece_view.end()) { this->get_scene_2d_render()->get_view()->setCursor(Qt::ClosedHandCursor); sight::viz::scene2d::vec2d_t window_level_coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord()); // Stores the level in window/level space and the window in screen space. m_captured_tf = std::make_pair( (*matching_it)->m_tf, sight::viz::scene2d::vec2d_t(window_level_coord.x, _event.get_coord().y) ); _event.set_accepted(true); } } //----------------------------------------------------------------------------- void transfer_function::mouse_move_on_piece_view_event(const sight::viz::scene2d::data::event& _event) { // m_capturedTF must be previously sets by midButtonClickEvent(const sight::viz::scene2d::data::Event& _event) SIGHT_ASSERT("The captured pieceView must exist", m_captured_tf.first); sight::viz::scene2d::vec2d_t window_level_coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord()); const auto min_max = m_captured_tf.first->min_max(); const auto min = m_captured_tf.first->map_value_to_window(min_max.first); const auto max = m_captured_tf.first->map_value_to_window(min_max.second); // Prevent the whole curve to move outside the viewport. { auto viewport = m_viewport.lock(); window_level_coord = glm::clamp( window_level_coord, glm::dvec2(viewport->left() + (m_captured_tf.second.x - min), viewport->top()), glm::dvec2(viewport->right() - (max - m_captured_tf.second.x), viewport->bottom()) ); } // The level delta is in window/level space. const double level_delta = window_level_coord.x - m_captured_tf.second.x; // The window delta is in screen space. const double window_delta = _event.get_coord().y - m_captured_tf.second.y; // Updates the TF. const data::transfer_function_piece::sptr tf_piece = m_captured_tf.first; { const auto tf = m_tf.lock(); tf_piece->set_window(tf_piece->window() - window_delta); tf_piece->set_level(tf_piece->level() + level_delta); tf->fit_window(); tf->async_emit(this, data::transfer_function::WINDOWING_MODIFIED_SIG, tf->window(), tf->level()); } // Stores the level in window/level space and the window in screen space. m_captured_tf.second = sight::viz::scene2d::vec2d_t(window_level_coord.x, _event.get_coord().y); // Re-draw all the scene. this->updating(); } //----------------------------------------------------------------------------- void transfer_function::mid_button_release_event() { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); this->get_scene_2d_render()->get_view()->setCursor(Qt::ArrowCursor); m_captured_tf.first = nullptr; } //----------------------------------------------------------------------------- void transfer_function::right_button_c_lick_event(const sight::viz::scene2d::data::event& _event) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); // Finds all piece views that match the clicked coord. std::vector matching_piece_view = this->get_matching_piece_view(_event); // Creates the menu. auto* const trapeze_action = new QAction("Add trapeze"); QObject::connect( trapeze_action, &QAction::triggered, this, [_event, this]() { this->add_trapeze(_event); }); auto* const left_ramp_action = new QAction("Add left ramp"); QObject::connect( left_ramp_action, &QAction::triggered, this, [_event, this]() { this->add_left_ramp(_event); }); auto* const right_ramp_action = new QAction("Add right ramp"); QObject::connect( right_ramp_action, &QAction::triggered, this, [_event, this]() { this->add_right_ramp(_event); }); auto* const context_menu = new QMenu(); { // Checks if the current tf is in the matching list. const auto matching_it = std::find_if( matching_piece_view.begin(), matching_piece_view.end(), [&](const piece_view* _piece_view) { return _piece_view->m_tf == m_current_tf; }); // Adds the delete action if the current TF is clicked. if(matching_it != matching_piece_view.end()) { { const auto tf = m_tf.const_lock(); // Adds the delete action if there is more than one TF. if(tf->pieces().size() > 1) { auto* delete_action = new QAction("Delete"); QObject::connect( delete_action, &QAction::triggered, this, &transfer_function::remove_current_tf ); context_menu->addAction(delete_action); } } // Adds the clamp action. auto* const clamp_action = new QAction("Clamp"); clamp_action->setCheckable(true); clamp_action->setChecked(m_current_tf->clamped()); QObject::connect(clamp_action, &QAction::triggered, this, &transfer_function::clamp_current_tf); context_menu->addAction(clamp_action); // Adds the interpolation mode action. auto* const linear_action = new QAction("Linear"); linear_action->setCheckable(true); linear_action->setChecked( m_current_tf->get_interpolation_mode() == data::transfer_function::interpolation_mode::linear ); QObject::connect(linear_action, &QAction::triggered, this, &transfer_function::toggle_linear_current_tf); context_menu->addAction(linear_action); } } context_menu->addAction(trapeze_action); context_menu->addAction(left_ramp_action); context_menu->addAction(right_ramp_action); // Opens the menu. context_menu->exec(QCursor::pos()); delete context_menu; } //----------------------------------------------------------------------------- void transfer_function::mid_button_wheel_move_event(sight::viz::scene2d::data::event& _event) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); // Finds all piece views that match the current coord. std::vector matching_piece_view = this->get_matching_piece_view(_event); // Checks if the current tf is in the matching list. const auto matching_it = std::find_if( matching_piece_view.begin(), matching_piece_view.end(), [&](const piece_view* _piece_view) { return _piece_view->m_tf == m_current_tf; }); // Change the opacity only if the mouse if over the current TF. if(matching_it != matching_piece_view.end()) { data::transfer_function_piece::sptr tf_piece; { const auto tf = m_tf.lock(); const auto& pieces = tf->pieces(); SIGHT_ASSERT( "inout '" + std::string(CURRENT_TF_INOUT) + "' must have at least on TF inside.", !tf->pieces().empty() ); // Finds the tf SIGHT_ASSERT("The current TF mustn't be null", m_current_tf); tf_piece = *std::find_if(pieces.begin(), pieces.end(), [&](const auto& _p){return _p == m_current_tf;}); // Updates the current TF. const double scale = _event.type() == sight::viz::scene2d::data::event::mouse_wheel_down ? 0.9 : 1.1; // Scale data for(auto& data : *tf_piece) { data.second.a = data.second.a * scale; } tf->fit_window(); tf->async_emit(this, data::transfer_function::POINTS_MODIFIED_SIG); } // Re-draw all the scene. this->updating(); _event.set_accepted(true); } } //----------------------------------------------------------------------------- void transfer_function::remove_current_tf() { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); { SIGHT_ASSERT("The current TF mustn't be null", m_current_tf); const auto tf = m_tf.lock(); SIGHT_ASSERT("Transfer function is not set.", tf); SIGHT_ASSERT("Transfer function must have more than one TF piece inside.", tf->pieces().size() > 1); auto& pieces = tf->pieces(); std::erase_if(pieces, [&](const auto& _piece){return _piece == m_current_tf;}); // Sets the new current TF. m_current_tf = pieces.front(); tf->fit_window(); tf->async_emit(this, data::object::MODIFIED_SIG); } // Re-draw all the scene here since swapping method as not been called. this->updating(); } //----------------------------------------------------------------------------- void transfer_function::clamp_current_tf(bool _clamp) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); const auto tf = m_tf.lock(); SIGHT_ASSERT("Transfer function is not set.", tf); const auto& pieces = tf->pieces(); auto tf_piece = *std::find_if(pieces.begin(), pieces.end(), [&](const auto& _p){return _p == m_current_tf;}); tf_piece->set_clamped(_clamp); tf->fit_window(); tf->async_emit(this, data::transfer_function::POINTS_MODIFIED_SIG); piece_view* current_piece_view = *(std::find_if( m_piece_view.begin(), m_piece_view.end(), [&](const piece_view* _piece_view) { return _piece_view->m_tf == m_current_tf; })); // Re-draw the current polygons. this->destroy_tf_polygon(current_piece_view); this->create_tf_polygon(current_piece_view); this->build_layer(); } //----------------------------------------------------------------------------- void transfer_function::toggle_linear_current_tf(bool _linear) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); const auto tf = m_tf.lock(); SIGHT_ASSERT("Transfer function is not set.", tf); const auto& pieces = tf->pieces(); auto tf_piece = *std::find_if(pieces.begin(), pieces.end(), [&](const auto& _p){return _p == m_current_tf;}); tf_piece->set_interpolation_mode( _linear ? data::transfer_function::interpolation_mode::linear : data::transfer_function::interpolation_mode::nearest ); tf->fit_window(); tf->async_emit(this, data::transfer_function::POINTS_MODIFIED_SIG); piece_view* current_piece_view = *(std::find_if( m_piece_view.begin(), m_piece_view.end(), [&](const piece_view* _piece_view) { return _piece_view->m_tf == m_current_tf; })); // Re-draw the current polygons. this->destroy_tf_polygon(current_piece_view); this->create_tf_polygon(current_piece_view); this->build_layer(); } //----------------------------------------------------------------------------- void transfer_function::add_new_tf(const data::transfer_function_piece::sptr _tf) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); { const auto tf = m_tf.lock(); SIGHT_ASSERT("inout '" + std::string(CURRENT_TF_INOUT) + "' does not exist.", tf); // Adds the new TF. tf->pieces().push_back(_tf); tf->fit_window(); tf->async_emit(this, data::object::MODIFIED_SIG); } // Creates the new piece view. piece_view* new_piece_view = this->create_piece_view(_tf, 0); // Pushes the piece view to the vector. m_piece_view.push_back(new_piece_view); this->create_tf_polygon(new_piece_view); this->build_layer(); // Updates the current TF. this->set_current_tf(new_piece_view); } //----------------------------------------------------------------------------- void transfer_function::add_left_ramp(const sight::viz::scene2d::data::event& _event) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); const auto left_ramp = std::make_shared(); left_ramp->insert({0.0, data::transfer_function::color_t(1.0, 1.0, 1.0, 1.0)}); left_ramp->insert({1.0, data::transfer_function::color_t()}); left_ramp->set_clamped(false); left_ramp->set_window(500.); left_ramp->set_level(50.); // Updates the window/level. sight::viz::scene2d::vec2d_t new_coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord()); const data::transfer_function::value_t window = left_ramp->window(); data::transfer_function::value_t min = new_coord.x - window / 2.; data::transfer_function::value_t max = min + window; left_ramp->set_window_min_max(data::transfer_function::min_max_t(min, max)); this->add_new_tf(left_ramp); } //----------------------------------------------------------------------------- void transfer_function::add_right_ramp(const sight::viz::scene2d::data::event& _event) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); // Creates the new TF. const auto right_ramp = std::make_shared(); right_ramp->insert({0.0, data::transfer_function::color_t()}); right_ramp->insert({1.0, data::transfer_function::color_t(1.0, 1.0, 1.0, 1.0)}); right_ramp->set_clamped(false); right_ramp->set_window(500.); right_ramp->set_level(50.); // Updates the window/level. sight::viz::scene2d::vec2d_t new_coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord()); const data::transfer_function::value_t window = right_ramp->window(); data::transfer_function::value_t min = new_coord.x - window / 2.; data::transfer_function::value_t max = min + window; right_ramp->set_window_min_max(data::transfer_function::min_max_t(min, max)); this->add_new_tf(right_ramp); } //----------------------------------------------------------------------------- void transfer_function::add_trapeze(const sight::viz::scene2d::data::event& _event) { SIGHT_ASSERT("Interactions disabled, this code should not reached", m_interactive); // Creates the new TF. const auto trapeze = std::make_shared(); trapeze->insert({0.0, data::transfer_function::color_t()}); trapeze->insert({1. / 3., data::transfer_function::color_t(1.0, 1.0, 1.0, 1.0)}); trapeze->insert({2. / 3., data::transfer_function::color_t(1.0, 1.0, 1.0, 1.0)}); trapeze->insert({1.0, data::transfer_function::color_t()}); trapeze->set_clamped(true); trapeze->set_window(500.); trapeze->set_level(50.); // Updates the window/level. sight::viz::scene2d::vec2d_t new_coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord()); const data::transfer_function::value_t window = trapeze->window(); data::transfer_function::value_t min = new_coord.x - window / 2.; data::transfer_function::value_t max = min + window; trapeze->set_window_min_max(data::transfer_function::min_max_t(min, max)); this->add_new_tf(trapeze); } //------------------------------------------------------------------------------ void transfer_function::update_tf() { // Sets the current TF. { // Sets the current TF used to highlight it in the editor. const auto tf = m_tf.const_lock(); if(tf && !tf->pieces().empty()) { m_current_tf = tf->pieces().front(); } else { SIGHT_FATAL("The current TF mustn't be null"); } } updating(); } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/transfer_function.hpp000066400000000000000000000404611503402212300242260ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2024 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::viz::scene2d::adaptor { /** * @brief Defines an adaptor to display a map of TF and interact with them. * * The following actions are available: * - Left mouse click: selects a new current TF or move the current clicked TF point. * - Left mouse double click: adds a new TF point to the current TF or open a color dialog * to change the current clicked TF point. * - Middle mouse click: adjusts the transfer function level and window by moving * the mouse up/down and left/right respectively. * - Right mouse click: remove the current clicked TF point or open a context menu * to manage multiple actions which are 'delete', 'add ramp', 'clamp' or 'linear'. * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection Input Input * - \b viewport [sight::viz::scene2d::data::viewport]: object listened to update the adaptor. * - \b tf [sight::data::transfer_function](optional): current transfer function used to change editor selection. * It should be the same TF as the output. * * @subsection Configuration Configuration * - \b config (mandatory): contains the adaptor configuration. * - \b xAxis (optional): x axis associated to the adaptor. * - \b yAxis (optional): y axis associated to the adaptor. * - \b zValue (optional, default="0"): z value of the layer. * - \b lineColor (optional, default="#FFFFFF"): color of the lines between the points. * - \b pointColor (optional, default="#FFFFFF"): outline color of circles representing the TF points. * - \b secondOpacity (optional, default="0.0"): opacity of TF that not the current one. * - \b pointSize (optional, default="0.03"): size of TF points in a ratio relative to the window. * - \b opacity (optional, default="1.0"): opacity of the gradient. * - \b interactive (optional, true/false, default="false"): enables interactions. */ class transfer_function : public QObject, public sight::viz::scene2d::adaptor { Q_OBJECT public: SIGHT_DECLARE_SERVICE(transfer_function, sight::viz::scene2d::adaptor); /// Creates the adaptor. transfer_function() noexcept; /// Destroys the adaptor. ~transfer_function() noexcept override; protected: /// Configures the adaptor. void configuring() override; /** * @brief Initializes the current TF, the layer and draw all TF. * * @see updating() */ void starting() override; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect sight::viz::scene2d::data::viewport::MODIFIED_SIG of s_VIEWPORT_INPUT to * module::viz::scene2d::adaptor::transfer_function::service::slots::UPDATE. * Connect data::object::MODIFIED_SIG of s_TF_POOL_INOUT to * module::viz::scene2d::adaptor::transfer_function::service::slots::UPDATE. * Connect data::map::ADDED_OBJECTS_SIGof s_TF_POOL_INOUT to * module::viz::scene2d::adaptor::transfer_function::service::slots::UPDATE. * Connect data::map::REMOVED_OBJECTS_SIG of s_TF_POOL_INOUT to * module::viz::scene2d::adaptor::transfer_function::service::slots::UPDATE. */ connections_t auto_connections() const override; /// Release all graphics items and draw all TF, all TF connections a established here. void updating() override; /// Release all graphic items and disconect all TF in the map. void stopping() override; private: using vec2d_t = sight::viz::scene2d::vec2d_t; /// Represents a sub-TF which is a TF of the input map. struct piece_view { /// Contains the TF data. data::transfer_function_piece::sptr m_tf; /// Sets the z value in the local layer. int m_z_index {0}; /// Contains a set of graphic point and its coordinates in the window/level space. std::vector > m_tf_points; /// Contains the graphic gradient. std::vector m_tf_polygons; }; /// Deletes piece views in @ref m_pieceView and clears them. void release_tf_data(); /** * @brief Creates pieceView for each TF in the map, fills basic data and create graphic points. * * @see createPieceView(const data::transfer_function::sptr _tf, int _zIndex) */ void create_tf_points(); /// Removes all graphic points in @ref m_pieceView from the layer and deletes them. void destroy_tf_points(); /// Creates a piece view from a TF, fills basic data and creates graphic points. piece_view* create_piece_view(const data::transfer_function_piece::sptr _tf, int _z_index); /// Creates the gradient of each piece view and stores it in each element of @ref m_pieceView. void create_tf_polygons(); /** * @brief Creates lines and gradient polygons of a pieceView. * @param _piece_view the pieceView used to create the gradient and store the generated graphic item. */ void create_tf_polygon(piece_view* _piece_view); /** * @brief Removes all graphic gradient in @ref m_pieceView from the layer and deletes them. * * @see destroyTFPolygons() */ void destroy_tf_polygons(); /** * @brief Removes graphic gradient of the pieceView from the layer and deletes them. * @param _piece_view to where destory the polygon. */ void destroy_tf_polygon(piece_view* _piece_view); /** * @brief Creates lines and linear gradient polygons of a pieceView. * @param _piece_view the pieceView used to create the gradient. * @param _position the position vector to fill. * @param _grad the gradient to create. * @param _distance_max the maximum distance used by the gradient. */ static void build_linear_polygons( piece_view* _piece_view, QVector& _position, QLinearGradient& _grad, double _distance_max ); /** * @brief Creates lines and nearest gradient polygons of a pieceView. * @param _piece_view the pieceView used to create the gradient. * @param _position the position vector to fill. * @param _grad the gradient to create. * @param _distance_max the maximum distance used by the gradient. */ static void build_nearest_polygons( piece_view* _piece_view, QVector& _position, QLinearGradient& _grad, double _distance_max ); /// Adds graphic items of @ref m_pieceView to @ref m_layer at the right z-index. void build_layer(); /** * @brief Changes @ref m_currentTF with the new one. * * Sets the new current TF as output of this adaptor and updates z-index of each pieceView in @ref m_pieceView. * * @param _piece_view the new current pieceView. */ void set_current_tf(piece_view* _piece_view); /** * @brief Get pieceView that match the clicked coord of the event. * @param _event the 2D scene event. * @return A list of pieceView. */ std::vector get_matching_piece_view(const sight::viz::scene2d::data::event& _event) const; /** * @brief Filters the event to call the right methods from mouse informations. * @param _event the 2D scene event. * * The following actions are available: * - Left mouse click: selects a new current TF or move the current clicked TF point. * - Left mouse double click: adds a new TF point to the current TF or open a color dialog * to change the current clicked TF point. * - Middle mouse click: adjusts the transfer function level and window by moving * the mouse up/down and left/right respectively. * - Right mouse click: remove the current clicked TF point or open a context menu * to manage multiple actions which are 'delete', 'add ramp', 'clamp' or 'linear'. * - Wheel move: updates the whole current TF opacity. */ void process_interaction(sight::viz::scene2d::data::event& _event) override; /** * @brief Finds the nearest pieceView and set it a the current one. * @param _event the 2D scene event. * * @see setCurrentTF(PieceView* const) */ void left_button_click_event(const sight::viz::scene2d::data::event& _event); /** * @brief Sets @ref m_capturedTFPoint and highlight the captured clicked point. * @param _piece_view the selected pieceView. * @param _tf_point the selected TF point. */ void left_button_click_on_point_event( piece_view* _piece_view, std::pair& _tf_point ); /** * @brief Move @ref m_capturedTFPoint to the new mouse position and update the related TF. * @param _event the 2D scene event. * * @pre m_capturedTFPoint must be previously sets. * @see leftButtonClickOnPointEvent(PieceView* const, std::pair< vec2d_t, QGraphicsEllipseItem* >&) */ void mouse_move_on_point_event(piece_view* _piece_view, const sight::viz::scene2d::data::event& _event); /** * @brief Resets the captured TF point highlighting and sets @ref m_capturedTFPoint to null. * * @pre m_capturedTFPoint must be previously sets. * @see leftButtonClickOnPointEvent(PieceView* const, std::pair< vec2d_t, QGraphicsEllipseItem* >&) */ void left_button_release_event(); /** * @brief Removes a TF point from the current pieceView and update the related TF. * @param _piece_view the selected pieceView. * @param _tf_point the selected TF point. */ void right_button_click_on_point_event( piece_view* _piece_view, std::pair& _tf_point ); /** * @brief Changes the TF point color by opening a color dialog and update the related TF. * @param _piece_view the selected pieceView. * @param _tf_point the selected TF point. */ void left_button_double_click_on_point_event( piece_view* _piece_view, std::pair& _tf_point ); /** * @brief Adds a new TF point to the current pieceView and update the related TF. * @param _event the 2D scene event. */ void left_button_double_click_event(const sight::viz::scene2d::data::event& _event); /** * @brief Sets @ref m_capturedTF if the clicked coord if over the current TF. * @param _event the 2D scene event. */ void mid_button_click_event(sight::viz::scene2d::data::event& _event); /** * @brief Update the window/level of the current TF relatively to the mouse movement. * @param _event the 2D scene event. * * @pre m_capturedTF must be previously sets. * @see midButtonClickEvent(const sight::viz::scene2d::data::Event&) */ void mouse_move_on_piece_view_event(const sight::viz::scene2d::data::event& _event); /** * @brief Resets @ref m_capturedTF. * * @pre m_capturedTF must be previously sets. * @see midButtonClickEvent(const sight::viz::scene2d::data::Event&) */ void mid_button_release_event(); /** * @brief Open a context menu to delete or create TF. * @param _event the 2D scene event. */ void right_button_c_lick_event(const sight::viz::scene2d::data::event& _event); /** * @brief Updates the whole current TF opacity. * @param _event the 2D scene event. */ void mid_button_wheel_move_event(sight::viz::scene2d::data::event& _event); /// Deletes the current TF and change the current TF. void remove_current_tf(); /** * @brief Sets if the current TF is clamped or not. * @param _clamp the clamp status. */ void clamp_current_tf(bool _clamp); /** * @brief Sets if the current TF interpolation mode is linear or nearest. * @param _linear uses true is the interpolation mode must be linear. */ void toggle_linear_current_tf(bool _linear); /** * @brief Adds a new TF to the map and re draw the scene. * @param _tf the new TF to add. */ void add_new_tf(const data::transfer_function_piece::sptr _tf); /** * @brief Adds a left ramp pieceView and update the map. * @param _event the 2D scene event. * * @see addNewTF(const data::transfer_function::sptr, const data::map::key_t&) */ void add_left_ramp(const sight::viz::scene2d::data::event& _event); /** * @brief Adds a right ramp pieceView and update the map. * @param _event the 2D scene event. * * @see addNewTF(const data::transfer_function::sptr, const data::map::key_t&) */ void add_right_ramp(const sight::viz::scene2d::data::event& _event); /** * @brief Adds a trapeze pieceView and update the map. * @param _event the 2D scene event. * * @see addNewTF(const data::transfer_function::sptr, const data::map::key_t&) */ void add_trapeze(const sight::viz::scene2d::data::event& _event); /// Updates the transfer function. void update_tf(); /// Defines the size of TF points in a ratio relative to the window. float m_point_size {0.03F}; /// Defines the pen used by gradients. QPen m_polygons_pen; /// Defines the pen used by TF points. QPen m_points_pen; /// Defines the opacity used for TF except for the current one. float m_second_opacity {0.0F}; /// Sets if interactions are enable or not. bool m_interactive {true}; /// Stores all created piece view. std::vector m_piece_view; /// Stores the main layer. QGraphicsItemGroup* m_layer {}; /** * We never know if a single click might be followed by another single click effectively resulting in a double * click. Qt does it and delivers events for double clicks (QEvent.MouseButtonDblClick). On the other hand Qt still * delivers events for single clicks (QEvent.MouseButtonPress) even in the case of a double click, but only one. * We must differentiate them correctly. We do it with an additional timer that needs to be a bit longer than the * inbuilt Qt timer for detecting double clicks. */ QTimer* m_event_filter {nullptr}; /// Stores the current working TF. data::transfer_function_piece::sptr m_current_tf {nullptr}; /// Stores the captured clicked point. std::pair* m_captured_tf_point {nullptr}; /// Stores the captured clicked TF and the current mouse position, /// the first coord is in the window/level space and the second in screen space, /// it allows to adjust the window/level of the current TF. std::pair m_captured_tf; static constexpr std::string_view VIEWPORT_INPUT = "viewport"; static constexpr std::string_view CURRENT_TF_INOUT = "tf"; data::ptr m_viewport {this, VIEWPORT_INPUT}; data::ptr m_tf {this, CURRENT_TF_INOUT}; }; } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/viewport_interactor.cpp000066400000000000000000000122641503402212300246010ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2017 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene2d/adaptor/viewport_interactor.hpp" #include #include #include namespace sight::module::viz::scene2d::adaptor { viewport_interactor::viewport_interactor() noexcept = default; //----------------------------------------------------------------------------- viewport_interactor::~viewport_interactor() noexcept = default; //----------------------------------------------------------------------------- void viewport_interactor::configuring() { } //----------------------------------------------------------------------------- void viewport_interactor::starting() { } //----------------------------------------------------------------------------- void viewport_interactor::stopping() { } //----------------------------------------------------------------------------- void viewport_interactor::updating() { } //----------------------------------------------------------------------------- void viewport_interactor::process_interaction(sight::viz::scene2d::data::event& _event) { if(_event.type() == sight::viz::scene2d::data::event::mouse_wheel_up && _event.get_modifier() == sight::viz::scene2d::data::event::shift_modifier) { this->zoom(true); } else if(_event.type() == sight::viz::scene2d::data::event::mouse_wheel_down && _event.get_modifier() == sight::viz::scene2d::data::event::shift_modifier) { this->zoom(false); } else if(_event.type() == sight::viz::scene2d::data::event::mouse_button_press && _event.get_button() == sight::viz::scene2d::data::event::left_button && _event.get_modifier() == sight::viz::scene2d::data::event::shift_modifier) { m_viewport_is_translated = true; m_last_coord_event = _event.get_coord(); } else if(m_viewport_is_translated) { if(_event.type() == sight::viz::scene2d::data::event::mouse_move) { sight::viz::scene2d::vec2d_t coord = _event.get_coord(); const auto viewport = m_viewport.lock(); const double dx = coord.x - m_last_coord_event.x; const double x_trans = dx * viewport->width() / static_cast(this->get_scene_2d_render()->get_view()->width()); const double dy = coord.y - m_last_coord_event.y; const double y_trans = dy * viewport->height() / static_cast(this->get_scene_2d_render()->get_view()->height()); viewport->set_x(viewport->x() - x_trans); viewport->set_y(viewport->y() - y_trans); this->get_scene_2d_render()->get_view()->update_from_viewport(*viewport); m_last_coord_event = coord; } else if(_event.type() == sight::viz::scene2d::data::event::mouse_button_release) { m_viewport_is_translated = false; } } } //----------------------------------------------------------------------------- void viewport_interactor::zoom(bool _zoom_in) { const auto scene_viewport = m_viewport.lock(); double y = scene_viewport->y(); double x = scene_viewport->x(); double width = scene_viewport->width(); double height = scene_viewport->height(); const double zoom_percent = 10.F / 100.0F; const double center_x = x + width / 2.0F; const double center_y = y + height / 2.0F; double new_width = NAN; double new_height = NAN; if(_zoom_in) { new_width = width * zoom_percent; new_height = height * zoom_percent; } else { new_width = -width * zoom_percent; new_height = -height * zoom_percent; } new_width += width; new_height += height; x = center_x - new_width / 2; y = center_y - new_height / 2; width = new_width; height = new_height; // Set viewport scene_viewport->set_x(x); scene_viewport->set_y(y); scene_viewport->set_width(width); scene_viewport->set_height(height); auto viewport_object = m_viewport.lock(); this->get_scene_2d_render()->get_view()->update_from_viewport(*viewport_object); } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/viewport_interactor.hpp000066400000000000000000000037751503402212300246150ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include namespace sight::module::viz::scene2d::adaptor { /** * @brief Adaptor implementation that manages the camera on the view. * * @section XML XML Configuration * * @code{.xml} @endcode * */ class viewport_interactor : public sight::viz::scene2d::adaptor { public: SIGHT_DECLARE_SERVICE(viewport_interactor, sight::viz::scene2d::adaptor); viewport_interactor() noexcept; ~viewport_interactor() noexcept override; protected: void configuring() override; void starting() override; void updating() override; void stopping() override; void process_interaction(sight::viz::scene2d::data::event& _event) override; void zoom(bool _zoom_in); private: bool m_viewport_is_translated {}; sight::viz::scene2d::vec2d_t m_last_coord_event {}; sight::data::ptr m_viewport {this, "viewport"}; }; } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/viewport_range_selector.cpp000066400000000000000000000356621503402212300254320ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene2d/adaptor/viewport_range_selector.hpp" #include #include #include #include #include #include #include #include namespace sight::module::viz::scene2d::adaptor { static const std::string INITIAL_WIDTH_CONFIG = "initialWidth"; static const std::string INITIAL_POS_CONFIG = "initialPos"; static const std::string COLOR_CONFIG = "color"; static const core::com::slots::key_t UPDATE_VIEWPORT_SLOT = "updateViewport"; //--------------------------------------------------------------------------------------------------------------- viewport_range_selector::viewport_range_selector() { new_slot(UPDATE_VIEWPORT_SLOT, [this]{this->update_viewport(false);}); } //------------------------------------------------------------------------------ void viewport_range_selector::configuring() { this->configure_params(); const config_t config = this->get_config().get_child("config."); m_initial_width = config.get(INITIAL_WIDTH_CONFIG, m_initial_width); m_initial_x = config.get(INITIAL_POS_CONFIG, m_initial_x); const std::string color = config.get(COLOR_CONFIG, "#FFFFFF"); sight::viz::scene2d::data::init_qt_pen::set_pen_color(m_color, color, m_opacity); } //---------------------------------------------------------------------------------------------------------- service::connections_t viewport_range_selector::auto_connections() const { return { {SELECTED_VIEWPORT_INOUT, sight::viz::scene2d::data::viewport::MODIFIED_SIG, service::slots::UPDATE}, {IMAGE_INPUT, sight::data::image::MODIFIED_SIG, service::slots::UPDATE}, {IMAGE_INPUT, sight::data::image::BUFFER_MODIFIED_SIG, service::slots::UPDATE}, {TF_INPUT, sight::data::transfer_function::POINTS_MODIFIED_SIG, UPDATE_VIEWPORT_SLOT} }; } //--------------------------------------------------------------------------------------------------------------- void viewport_range_selector::starting() { { auto* const scene = this->get_scene_2d_render()->get_scene(); const auto scene_viewport = m_viewport.lock(); const double viewport_width = scene_viewport->width_or(scene->sceneRect().width()); const double default_width = 2. * viewport_width / 4.; if(m_initial_width > viewport_width || m_initial_width < m_click_catch_range) { SIGHT_WARN("Set viewport width to a default value instead of the given one because it can't be accepted."); m_initial_width = default_width; } const double default_pos = (viewport_width - m_initial_width) / 2.; if(m_initial_x < scene_viewport->x_or(scene->sceneRect().x()) || (m_initial_x + m_initial_width) > viewport_width) { SIGHT_WARN("Set viewport position to a default value since the given one is not correct."); m_initial_x = default_pos; } } { auto* const scene = this->get_scene_2d_render()->get_scene(); auto viewport = m_viewport.lock(); // If the viewport Y and height are not set, scale the viewport to the height of the scene { viewport->set_y(viewport->y_or(scene->sceneRect().y())); viewport->set_height(viewport->height_or(scene->sceneRect().height())); } } this->updating(); } //--------------------------------------------------------------------------------------------------------------- void viewport_range_selector::stopping() { } //--------------------------------------------------------------------------------------------------------------- void viewport_range_selector::updating() { this->update_viewport(true); } //--------------------------------------------------------------------------------------------------------------- void viewport_range_selector::update_viewport(bool _signal_selected_viewport) { const auto tf = m_tf.lock(); const auto image = m_image.lock(); m_min = (tf || image) ? std::numeric_limits::max() : m_initial_x; m_max = (tf || image) ? std::numeric_limits::lowest() : m_initial_x + m_initial_width; const glm::dvec2 pair = this->map_scene_to_adaptor(glm::dvec2(m_initial_x, 1.0)); QRectF rect(pair.x, 0, m_initial_width * m_x_axis->get_scale(), pair.y); if(tf) { std::tie(m_min, m_max) = tf->window_min_max(); } if(image) { std::tie(m_image_min, m_image_max) = data::helper::medical_image::get_min_max(image.get_shared()); m_min = std::min(m_min, m_image_min); m_max = std::max(m_max, m_image_max); } // Fit the shutter from the current range rect.setRect(m_min, rect.y(), m_max - m_min, rect.height()); if(m_shutter == nullptr) { m_shutter = new QGraphicsRectItem(rect); m_shutter->setBrush(m_color.color()); m_shutter->setPen(Qt::NoPen); m_layer = new QGraphicsItemGroup(); m_layer->addToGroup(m_shutter); // Adjust the layer's position and zValue depending on the associated axis m_layer->setPos(m_x_axis->origin(), m_y_axis->origin()); m_layer->setZValue(m_z_value); this->get_scene_2d_render()->get_scene()->addItem(m_layer); } else { m_shutter->setRect(rect); m_layer->removeFromGroup(m_shutter); m_layer->addToGroup(m_shutter); } // update the viewports update_viewport_from_shutter(rect.x(), rect.y(), rect.width(), rect.height()); auto viewport = m_viewport.lock(); viewport->set_x(m_min); viewport->set_width(m_max - m_min); this->get_scene_2d_render()->get_view()->update_from_viewport(*viewport); viewport->async_emit(this, data::object::MODIFIED_SIG); if(_signal_selected_viewport) { auto selected_viewport = m_selected_viewport.lock(); selected_viewport->async_emit(this, data::object::MODIFIED_SIG); } m_click_catch_range = static_cast(m_max - m_min) / 100; } //--------------------------------------------------------------------------------------------------------------- void viewport_range_selector::process_interaction(sight::viz::scene2d::data::event& _event) { // Event coordinates in scene glm::dvec2 coord; coord = this->get_scene_2d_render()->map_to_scene(_event.get_coord()); // Shutter coordinates in scene const glm::dvec2 shutter_coord_pair = this->map_adaptor_to_scene(glm::dvec2(m_shutter->rect().x(), m_shutter->rect().y())); const double shutter_width = m_shutter->rect().width() * m_x_axis->get_scale(); const QRectF scene_rect = this->get_scene_2d_render()->get_scene()->sceneRect(); const bool on_shutter_left = mouse_on_shutter_left(coord); const bool on_shutter_right = mouse_on_shutter_right(coord); const bool on_shutter_middle = mouse_on_shutter_middle(coord); QRectF rect = m_shutter->rect(); if(_event.type() == sight::viz::scene2d::data::event::mouse_button_press) { if(on_shutter_left) { m_is_left_interacting = true; } else if(on_shutter_right) { m_is_right_interacting = true; } else if(on_shutter_middle) { this->get_scene_2d_render()->get_view()->setCursor(Qt::ClosedHandCursor); // Interaction when clicking on the center of the shutter m_is_interacting = true; m_drag_start_point = coord; m_drag_start_shutter_pos.x = shutter_coord_pair.x; m_drag_start_shutter_pos.y = shutter_coord_pair.y; } } else if(_event.type() == sight::viz::scene2d::data::event::mouse_button_release) { m_is_interacting = false; m_is_left_interacting = false; m_is_right_interacting = false; // Reset cursor if(on_shutter_middle) { this->get_scene_2d_render()->get_view()->setCursor(Qt::OpenHandCursor); } else { this->get_scene_2d_render()->get_view()->setCursor(Qt::ArrowCursor); } } else if(_event.type() == sight::viz::scene2d::data::event::mouse_move) { // If the mouse is moving onto the shutter, without interactions, the cursor is changed to an other cursor // that symbolize the available interactions if(!m_is_left_interacting && !m_is_right_interacting && !m_is_interacting) { if(on_shutter_left || on_shutter_right) { this->get_scene_2d_render()->get_view()->setCursor(Qt::SizeHorCursor); // horizontal double arrow } else if(on_shutter_middle) { this->get_scene_2d_render()->get_view()->setCursor(Qt::OpenHandCursor); // open hand, for moving the // whole shutter } else { this->get_scene_2d_render()->get_view()->setCursor(Qt::ArrowCursor); // reset the cursor to the // default cursor } } bool update = false; // if a viewport update will be requested const auto image = m_image.lock(); const double left_side_boundary = image ? m_min : scene_rect.x(); const double right_side_boundary = image ? m_max : scene_rect.width() - scene_rect.x(); if(m_is_left_interacting) { // Shutter right side position const double right_side = rect.x() + rect.width(); if(coord.x < right_side - m_click_catch_range) { rect.setX(std::max(left_side_boundary, coord.x)); } else { rect.setX(right_side - m_click_catch_range); } update = true; } else if(m_is_right_interacting) { const double new_width = coord.x - shutter_coord_pair.x; const double shutter_right_pos = shutter_coord_pair.x + new_width; if(new_width > m_click_catch_range) // Shutter's width must be greater than the allowed picking range { if(shutter_right_pos <= right_side_boundary) { rect.setWidth(new_width); } else { rect.setWidth(right_side_boundary - shutter_coord_pair.x); } } else { rect.setWidth(m_click_catch_range); } update = true; } else if(m_is_interacting) { const double offset = coord.x - m_drag_start_point.x; const double new_x = m_drag_start_shutter_pos.x + offset; const double shutter_right_pos = new_x + shutter_width; if(new_x >= left_side_boundary && shutter_right_pos < right_side_boundary) { rect.setX(new_x); } else if(new_x < left_side_boundary) { rect.setX(left_side_boundary); } else if(shutter_right_pos >= right_side_boundary) { rect.setX(right_side_boundary - shutter_width); } rect.setWidth(shutter_width); update = true; } if(update) { // Update graphical shutter m_shutter->setRect(rect); m_layer->removeFromGroup(m_shutter); m_layer->addToGroup(m_shutter); // Update object this->update_viewport_from_shutter(rect.x(), rect.y(), rect.width(), rect.height()); { auto selected_viewport = m_selected_viewport.lock(); selected_viewport->async_emit(this, data::object::MODIFIED_SIG); } } } } //--------------------------------------------------------------------------------------------------------------- void viewport_range_selector::update_viewport_from_shutter(double _x, double _y, double _width, double _height) { auto selected_viewport = m_selected_viewport.lock(); const glm::dvec2 from_scene_coord = this->map_scene_to_adaptor(glm::dvec2(_x, _y)); const glm::dvec2 pair = this->map_scene_to_adaptor(glm::dvec2(_width, _height)); selected_viewport->set_x(from_scene_coord.x); selected_viewport->set_width(pair.x); } //--------------------------------------------------------------------------------------------------------------- bool viewport_range_selector::mouse_on_shutter_middle(glm::dvec2 _coord) { glm::dvec2 shutter_coord_pair; shutter_coord_pair = this->map_adaptor_to_scene({m_shutter->rect().x(), m_shutter->rect().y()}); return (_coord.x > m_shutter->rect().x() + m_click_catch_range) && (_coord.x < m_shutter->rect().x() + m_shutter->rect().width() - m_click_catch_range); } //--------------------------------------------------------------------------------------------------------------- bool viewport_range_selector::mouse_on_shutter_left(glm::dvec2 _coord) { glm::dvec2 shutter_coord_pair = this->map_adaptor_to_scene({m_shutter->rect().x(), m_shutter->rect().y()}); return (_coord.x >= shutter_coord_pair.x - m_click_catch_range) && (_coord.x <= shutter_coord_pair.x + m_click_catch_range); } //--------------------------------------------------------------------------------------------------------------- bool viewport_range_selector::mouse_on_shutter_right(glm::dvec2 _coord) { const glm::dvec2 shutter_coord_pair = this->map_adaptor_to_scene({m_shutter->rect().x(), m_shutter->rect().y()}); const double shutter_right_pos = shutter_coord_pair.x + m_shutter->rect().width() * m_x_axis->get_scale(); return (_coord.x >= shutter_right_pos - m_click_catch_range) && (_coord.x <= shutter_right_pos + m_click_catch_range); } //--------------------------------------------------------------------------------------------------------------- } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/viewport_range_selector.hpp000066400000000000000000000175341503402212300254350ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace s2d = sight::viz::scene2d; namespace sight::module::viz::scene2d::adaptor { /** * @brief The viewport range selector adaptor allows to select a delimited range of a viewport. * It uses a graphical delimiter (called shutter) that can be moved from both left to right * and right to left directions (in those cases, shutter's width is changing). * * Clicking onto the approximate center of the shutter allows the user to change its position, * according to mouse's cursor position (width won't change). * * Clicking onto the approximate left/right (respectively) border of the shutter allows the * user to change the width of the shutter: the right/left (respectively) border doesn't move * during resizing. * * Each change onto the shutter will cause this adaptor to update the managed * sight::viz::scene2d::data::viewport object. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection In-Out In-Out * - \b viewport [sight::viz::scene2d::data::viewport]: viewport object used to display this adaptor. If the viewport * is not initialized, it will be updated to fit the scene size. * - \b selectedviewport [sight::viz::scene2d::data::viewport]: viewport object whose range is modified. * - \b image [sight::data::image] (optional): if specified, computes \b selectedviewport from the image range instead * of the * initialPos and initialWidth parameters. * * @subsection In In * - \b tfPool [sight::data::map]: if specified, computes \b viewport viewport from the transfer function range. * * @subsection Configuration Configuration: * - \b config (mandatory): contains the adaptor configuration. * - \b xAxis (optional): x axis associated to the adaptor. * - \b yAxis (optional): y axis associated to the adaptor. * - \b zValue (optional, default="0"): z value of the layer. * - \b initialPos (optional, default="0."): initial position of the shutter on the X axis. * - \b initialWidth (optional, default="0"."): initial width of the shutter. * - \b color (optional, default="#FFFFFF"): inner color. * - \b opacity (optional, default="1.0"): opacity of the gradient. * * @pre This adaptor is intended to be used with a module::viz::scene2d::adaptor::ViewportUpdater adaptor. */ class viewport_range_selector : public sight::viz::scene2d::adaptor { public: SIGHT_DECLARE_SERVICE(viewport_range_selector, sight::viz::scene2d::adaptor); viewport_range_selector(); /// Configures the adaptor. void configuring() override; /// Creates graphic items. void starting() override; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect sight::viz::scene2d::data::viewport::MODIFIED_SIG of s_VIEWPORT_INPUT to * module::viz::scene2d::adaptor::viewport_range_selector::service::slots::UPDATE. */ connections_t auto_connections() const override; /// Does nothing. void updating() override; /// Does nothing. void stopping() override; private: /** * @brief Filters the event to call the right methods from mouse informations. * @param _event the 2D scene event. */ void process_interaction(sight::viz::scene2d::data::event& _event) override; /// Recomputes the viewport. Can signal or not the data depending from where it is called. void update_viewport(bool _signal_selected_viewport); /** * @brief Update the viewport object according to the current state of the shutter. * @param _x x position of the shutter. * @param _y y position of the shutter. * @param _width width of the shutter. * @param _height height of the shutter. */ void update_viewport_from_shutter(double _x, double _y, double _width, double _height); /// Tells if the mouse cursor is at the good position to start interacting on shutter's left border. bool mouse_on_shutter_left(glm::dvec2 _coord); /// Tells if the mouse cursor is at the good position to start interacting on shutter's right border. bool mouse_on_shutter_right(glm::dvec2 _coord); /// Tells if the mouse cursor is at the good position to start interacting on shutter's middle part. bool mouse_on_shutter_middle(glm::dvec2 _coord); /// Stores the graphic item that represents the shutter. QGraphicsRectItem* m_shutter {nullptr}; /// Sets if there is interaction onto shutter's left border. bool m_is_left_interacting {false}; /// Sets if there is interaction onto shutter's right border. bool m_is_right_interacting {false}; /// Sets if there is interaction onto the whole shutter. bool m_is_interacting {false}; /// Sets if there is a dragging interaction. glm::dvec2 m_drag_start_point {0., 0.}; /// Defines the shutter position when dragging starts. glm::dvec2 m_drag_start_shutter_pos {0., 0.}; /// Sets the spacing value for an easier picking onto shutter borders. int m_click_catch_range {1}; /// Stores the main layer. QGraphicsItemGroup* m_layer {nullptr}; /// Defines the initial position of the shutter on the X axis. double m_initial_x {0.F}; /// Defines the initial width of the shutter. double m_initial_width {1.}; /// Defines the color used for graphic item's. QPen m_color; /// Cache the minimum intensity found in an image double m_image_min {std::numeric_limits::max()}; /// Cache the maximum intensity found in an image double m_image_max {std::numeric_limits::lowest()}; /// Cache the minimum intensity found in an image or in the transfer function double m_min {std::numeric_limits::max()}; /// Cache the maximum intensity found in an image or in the transfer function double m_max {std::numeric_limits::lowest()}; static constexpr std::string_view VIEWPORT_INOUT = "viewport"; static constexpr std::string_view SELECTED_VIEWPORT_INOUT = "selectedViewport"; static constexpr std::string_view IMAGE_INPUT = "image"; static constexpr std::string_view TF_INPUT = "tf"; data::ptr m_viewport {this, VIEWPORT_INOUT}; data::ptr m_selected_viewport {this, SELECTED_VIEWPORT_INOUT}; sight::data::ptr m_image {this, IMAGE_INPUT, true}; data::ptr m_tf {this, TF_INPUT, true}; }; } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/viewport_updater.cpp000066400000000000000000000046741503402212300241010ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene2d/adaptor/viewport_updater.hpp" #include namespace sight::module::viz::scene2d::adaptor { //----------------------------------------------------------------------------- void viewport_updater::configuring() { this->configure_params(); } //----------------------------------------------------------------------------- void viewport_updater::starting() { { // If the viewport Y and height are not set, scale the viewport to the height of the scene auto viewport = m_viewport.lock(); auto* scene = this->get_scene_2d_render()->get_scene(); viewport->set_y(viewport->y_or(scene->sceneRect().y())); viewport->set_height(viewport->height_or(scene->sceneRect().height())); } this->updating(); } //----------------------------------------------------------------------------- void viewport_updater::stopping() { } //----------------------------------------------------------------------------- void viewport_updater::updating() { auto viewport = m_viewport.lock(); this->get_scene_2d_render()->get_view()->update_from_viewport(*viewport); } //---------------------------------------------------------------------------------------------------------- service::connections_t viewport_updater::auto_connections() const { connections_t connections; connections.push(VIEWPORT_INOUT, sight::viz::scene2d::data::viewport::MODIFIED_SIG, service::slots::UPDATE); return connections; } } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/adaptor/viewport_updater.hpp000066400000000000000000000061441503402212300241000ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2019 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::viz::scene2d::adaptor { /** * @brief Adaptor implementation that manages the camera on the view (by updating the viewport object). * * The viewport_updater updates the viewport of the scene it is attached to, by picking values from the * managed sight::viz::scene2d::data::viewport object. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection In In * - \b image [sight::data::image] (optional): scales the viewport range with the values from an image. If provided, * this will update both the scene view and the viewport passed as argument. * * @subsection In-Out In-Out * - \b viewport [sight::viz::scene2d::data::viewport]: the viewport object this updater takes values from. * If the viewport is not initialized, it will be updated to fit the scene size. * * @subsection Configuration Configuration: * - \b config: contains the adaptor configuration * - \b xAxis (optional): x axis associated to the adaptor * - \b yAxis (optional): y axis associated to the adaptor * - \b zValue (optional, default=0): z value of the layer * * This adaptor is intended to be used with a module::viz::scene2d::adaptor::viewport_range_selector adaptor. */ class viewport_updater : public sight::viz::scene2d::adaptor { public: SIGHT_DECLARE_SERVICE(viewport_updater, sight::viz::scene2d::adaptor); viewport_updater() noexcept = default; protected: void configuring() override; void starting() override; void updating() override; void stopping() override; connections_t auto_connections() const override; private: static constexpr std::string_view VIEWPORT_INOUT = "viewport"; sight::data::ptr m_viewport {this, VIEWPORT_INOUT }; }; } // namespace sight::module::viz::scene2d::adaptor sight-25.1.0/module/viz/scene2d/plugin.cpp000066400000000000000000000030761503402212300203350ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2015 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "plugin.hpp" namespace sight::module::viz::scene2d { //----------------------------------------------------------------------------- SIGHT_REGISTER_PLUGIN("sight::module::viz::scene2d::plugin"); //----------------------------------------------------------------------------- plugin::~plugin() noexcept = default; //----------------------------------------------------------------------------- void plugin::start() { } //----------------------------------------------------------------------------- void plugin::stop() noexcept { } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene2d sight-25.1.0/module/viz/scene2d/plugin.hpp000066400000000000000000000026571503402212300203460ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include namespace sight::module::viz::scene2d { /** * @brief This class is started when the modules are loaded. */ struct plugin : public core::runtime::plugin { /** * @brief destructor */ ~plugin() noexcept override; // Overrides SIGHT_MODULE_VIZ_SCENE2D_API void start() override; // Overrides SIGHT_MODULE_VIZ_SCENE2D_API void stop() noexcept override; }; } // namespace sight::module::viz::scene2d sight-25.1.0/module/viz/scene2d/rc/000077500000000000000000000000001503402212300167315ustar00rootroot00000000000000sight-25.1.0/module/viz/scene2d/rc/plugin.xml000066400000000000000000000066361503402212300207640ustar00rootroot00000000000000 sight::viz::scene2d::adaptor sight::module::viz::scene2d::adaptor::axis sight::viz::scene2d::data::viewport Render an axis on the scene2d. sight::viz::scene2d::adaptor sight::module::viz::scene2d::adaptor::grid2d Render grid on the scene2d. sight::viz::scene2d::adaptor sight::module::viz::scene2d::adaptor::histogram sight::viz::scene2d::data::viewport sight::data::image Displays the histogram of an image. sight::viz::scene2d::adaptor sight::module::viz::scene2d::adaptor::line Line adaptor. Draw a line on the scene2D sight::viz::scene2d::adaptor sight::module::viz::scene2d::adaptor::transfer_function sight::viz::scene2d::data::viewport sight::data::transfer_function Defines an adaptor to display a map of TF and interact with them. sight::viz::scene2d::adaptor sight::module::viz::scene2d::adaptor::negato adaptor implementation to display one slice of an image. sight::viz::render sight::viz::scene2d::render sight::viz::scene2d::adaptor sight::module::viz::scene2d::adaptor::square Create a square on the scene2D sight::viz::scene2d::adaptor sight::module::viz::scene2d::adaptor::viewport_interactor Adaptor implementation that manages the camera on the view. sight::viz::scene2d::adaptor sight::module::viz::scene2d::adaptor::viewport_range_selector The viewport range selector adaptor allows to select a delimited range of a viewport. sight::viz::scene2d::adaptor sight::module::viz::scene2d::adaptor::viewport_updater Adaptor implementation that manages the camera on the view (by updating the viewport object). sight-25.1.0/module/viz/scene3d/000077500000000000000000000000001503402212300163265ustar00rootroot00000000000000sight-25.1.0/module/viz/scene3d/CMakeLists.txt000066400000000000000000000001751503402212300210710ustar00rootroot00000000000000sight_add_target(module_viz_scene3d TYPE MODULE) target_link_libraries(module_viz_scene3d PUBLIC viz_scene3d geometry_data) sight-25.1.0/module/viz/scene3d/README.md000066400000000000000000000047621503402212300176160ustar00rootroot00000000000000# sight::module::viz::scene3d Contains adaptors available for the generic scene. ## Services - **axis**: shows a simple coordinate system. - **camera**: transforms a Sight camera to an Ogre camera. - **compositor_parameter**: binds a Sight data to a shader uniform from a specific compositor. - **fragments_info**: takes a snapshot of layer fragments information and output it as a sight::data::image. - **frustum**: displays the frustum of a sight::data::camera. - **frustumList**: displays a new Frustum each time the transform is updated. - **light**: adds a light to the scene manager. - **line**: shows a simple line. - **material**: adapts a sight::data::material, allowing to tweak material parameters. - **mesh**: shows individual meshes. - **meshList**: shows a list of mesh. - **model_series**: shows a modelSeries. It creates an adaptor for each reconstruction in the model. - **negato2d**: displays a 2D negato. - **negato2d_camera**: lets the user move an orthographic camera to visualize medical images in 2D. - **negato3d**: displays a 3D negato. - **orientation_marker**: displays an orientation "marker", marker is represented by a human body mesh, its orientation follows camera movement. - **picker**: performs picking queries on mouse clicks and forwards the picked data through a signal. - **point_list**: shows a point list using billboards generated by a geometry shader. - **reconstruction**: displays a reconstruction. - **render_stats**: displays rendering statistics in the window overlay. - **resize_viewport**: resizes and moves viewports. - **shader_parameter**: sends a Sight data as a shader parameter. - **shape_extruder**: allows to draw a 2D shape with a lasso tool, and create an extruded mesh from it. - **text**: displays a text object in the center or along the window borders. - **texture**: maps a texture on a mesh. This is done via module::viz::scene3d::adaptor::material. - **trackball_camera**: lets the user move the camera around a point of interest using the mouse and keyboard. - **transform**: binds a sight::data::matrix4 to a scene node. - **vector**: shows a simple vector. - **video**: renders a video frame from a 2D-image. - **volume_render**: displays a volume rendering. - **voxel_picker**: performs picking queries on an image voxel using slices. ## How to use it ### CMake ```cmake add_dependencies(my_target module_viz_scene3d ... ) ``` ### XML Please consult the [doxygen](https://sight.pages.ircad.fr/sight) of each service to learn more about its use in xml configurations. sight-25.1.0/module/viz/scene3d/adaptor/000077500000000000000000000000001503402212300177605ustar00rootroot00000000000000sight-25.1.0/module/viz/scene3d/adaptor/axis.cpp000066400000000000000000000344321503402212300214360ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2025 IRCAD France * Copyright (C) 2017-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/axis.hpp" #include "module/viz/scene3d/adaptor/transform.hpp" #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { //----------------------------------------------------------------------------- axis::axis() noexcept { new_slot("update_origin_color", &axis::update_origin_color, this); } //----------------------------------------------------------------------------- void axis::update_origin_color(sight::data::color::sptr _new_color) { if(m_origin == nullptr) { return; } Ogre::ColourValue color((*_new_color)[0], (*_new_color)[1], (*_new_color)[2], (*_new_color)[3]); m_origin_material->material()->setAmbient(color); } //----------------------------------------------------------------------------- void axis::configuring() { this->configure_params(); const config_t config = this->get_config(); const std::string transform_id = config.get( module::viz::scene3d::adaptor::transform::TRANSFORM_CONFIG, gen_id("transform") ); this->set_transform_id(transform_id); static const std::string s_LENGTH_CONFIG = CONFIG + "length"; static const std::string s_LABEL_CONFIG = CONFIG + "label"; static const std::string s_FONT_SIZE_CONFIG = CONFIG + "fontSize"; static const std::string s_ORIGIN_CONFIG = CONFIG + "origin"; static const std::string s_AXIS_CONFIG = CONFIG + "axis"; static const std::string s_AXIS_NAME = CONFIG + "name"; m_length = config.get(s_LENGTH_CONFIG, m_length); m_enable_label = config.get(s_LABEL_CONFIG, m_enable_label); m_font_size = config.get(s_FONT_SIZE_CONFIG, m_font_size); m_origin_visibility = config.get(s_ORIGIN_CONFIG, m_origin_visibility); m_axis_visibility = config.get(s_AXIS_CONFIG, m_axis_visibility); m_axis_name = config.get(s_AXIS_NAME, m_axis_name); // Force disable label if axisVisibility is false. m_enable_label = m_axis_visibility ? m_enable_label : false; } //----------------------------------------------------------------------------- void axis::starting() { adaptor::init(); this->render_service()->make_current(); Ogre::SceneNode* const root_scene_node = this->get_scene_manager()->getRootSceneNode(); Ogre::SceneNode* const transform_node = this->get_or_create_transform_node(root_scene_node); m_scene_node = transform_node->createChildSceneNode(gen_id("main_node")); Ogre::SceneManager* const scene_mgr = this->get_scene_manager(); if(m_origin_visibility) { m_origin = scene_mgr->createManualObject(gen_id("origin")); } if(m_axis_visibility) { m_x_line = scene_mgr->createManualObject(gen_id("x_line")); m_y_line = scene_mgr->createManualObject(gen_id("y_line")); m_z_line = scene_mgr->createManualObject(gen_id("z_line")); m_x_cone = scene_mgr->createManualObject(gen_id("x_cone")); m_y_cone = scene_mgr->createManualObject(gen_id("y_cone")); m_z_cone = scene_mgr->createManualObject(gen_id("z_cone")); } const auto material_name = gen_id("material"); m_material = std::make_unique(material_name); m_material->set_layout(data::mesh::attribute::point_normals | data::mesh::attribute::point_colors); m_material->set_shading(data::material::shading_t::phong, this->layer()->num_lights()); const auto origin_material_name = gen_id("origin_material"); m_origin_material = std::make_unique(origin_material_name); m_origin_material->set_layout(data::mesh::attribute::point_normals | data::mesh::attribute::point_colors); m_origin_material->set_shading(data::material::shading_t::ambient, this->layer()->num_lights()); // Sizes const float origin_radius = m_length * 0.1F; const float cylinder_length = m_length * 0.85F; const float cylinder_radius = m_length * 0.025F; const float cone_length = m_length - cylinder_length; const float cone_radius = cylinder_radius * 2.F; const unsigned sample = 64; // origin if(m_origin_visibility) { const auto origin_color = *m_origin_color; sight::viz::scene3d::helper::manual_object::create_sphere( m_origin, m_origin_material->name(), Ogre::ColourValue(origin_color[0], origin_color[1], origin_color[2], origin_color[3]), origin_radius, sample ); m_scene_node->attachObject(m_origin); } Ogre::Camera* const cam = this->layer()->get_default_camera(); if(m_axis_visibility) { // X axis sight::viz::scene3d::helper::manual_object::create_cylinder( m_x_line, m_material->name(), Ogre::ColourValue(Ogre::ColourValue::Red), cylinder_radius, cylinder_length, sample ); Ogre::SceneNode* const x_line_node = m_scene_node->createChildSceneNode(); x_line_node->attachObject(m_x_line); x_line_node->pitch(Ogre::Degree(90)); // Y axis sight::viz::scene3d::helper::manual_object::create_cylinder( m_y_line, m_material->name(), Ogre::ColourValue(Ogre::ColourValue::Green), cylinder_radius, cylinder_length, sample ); Ogre::SceneNode* const y_line_node = m_scene_node->createChildSceneNode(); y_line_node->attachObject(m_y_line); y_line_node->roll(Ogre::Degree(90)); // Z axis sight::viz::scene3d::helper::manual_object::create_cylinder( m_z_line, m_material->name(), Ogre::ColourValue(Ogre::ColourValue::Blue), cylinder_radius, cylinder_length, sample ); Ogre::SceneNode* const z_line_node = m_scene_node->createChildSceneNode(); z_line_node->attachObject(m_z_line); z_line_node->yaw(Ogre::Degree(-90)); // X cone sight::viz::scene3d::helper::manual_object::create_cone( m_x_cone, m_material->name(), Ogre::ColourValue(Ogre::ColourValue::Red), cone_radius, cone_length, sample ); Ogre::SceneNode* const x_cone_node = m_scene_node->createChildSceneNode(); if(m_enable_label) { m_axis_labels[0] = sight::viz::scene3d::text::make(this->layer()); m_axis_labels[0]->set_text("X"); m_axis_labels[0]->set_font_size(m_font_size); m_axis_labels[0]->attach_to_node(x_cone_node, this->layer()->get_default_camera()); } x_cone_node->attachObject(m_x_cone); x_cone_node->translate(cylinder_length, 0.F, 0.F); // Y cone sight::viz::scene3d::helper::manual_object::create_cone( m_y_cone, m_material->name(), Ogre::ColourValue(Ogre::ColourValue::Green), cone_radius, cone_length, sample ); Ogre::SceneNode* const y_cone_node = m_scene_node->createChildSceneNode(); y_cone_node->attachObject(m_y_cone); if(m_enable_label) { m_axis_labels[1] = sight::viz::scene3d::text::make(this->layer()); m_axis_labels[1]->set_text("Y"); m_axis_labels[1]->set_font_size(m_font_size); m_axis_labels[1]->attach_to_node(y_cone_node, cam); } y_cone_node->translate(0.F, cylinder_length, 0.F); y_cone_node->roll(Ogre::Degree(90)); // Z cone sight::viz::scene3d::helper::manual_object::create_cone( m_z_cone, m_material->name(), Ogre::ColourValue(Ogre::ColourValue::Blue), cone_radius, cone_length, sample ); Ogre::SceneNode* const z_cone_node = m_scene_node->createChildSceneNode(); z_cone_node->attachObject(m_z_cone); if(m_enable_label) { m_axis_labels[2] = sight::viz::scene3d::text::make(this->layer()); m_axis_labels[2]->set_text("Z"); m_axis_labels[2]->set_font_size(m_font_size); m_axis_labels[2]->attach_to_node(z_cone_node, this->layer()->get_default_camera()); } z_cone_node->translate(0.F, 0.F, cylinder_length); z_cone_node->yaw(Ogre::Degree(-90)); } // Display Name if provided. if(!m_axis_name.empty()) { m_axis_name_txt = sight::viz::scene3d::text::make(this->layer()); m_axis_name_txt->set_text(m_axis_name); m_axis_name_txt->set_font_size(m_font_size); m_axis_name_txt->attach_to_node(m_scene_node, cam); const auto origin_color = *m_origin_color; m_axis_name_txt->set_text_color(Ogre::ColourValue(origin_color[0], origin_color[1], origin_color[2])); } this->apply_visibility(); this->request_render(); } //----------------------------------------------------------------------------- void axis::updating() { this->render_service()->make_current(); Ogre::SceneNode* const transform_node = this->get_transform_node(); if(transform_node != nullptr) { bool render_requested = false; bool has_image = false; data::image::origin_t position {0.0, 0.0, 0.0}; data::image::orientation_t orientation { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; { const auto image = m_image.lock(); if(image) { has_image = true; position = image->origin(); orientation = image->orientation(); } } if(has_image) { // Decompose the matrix transform_node->setOrientation( Ogre::Matrix3 { static_cast(orientation[0]), static_cast(orientation[1]), static_cast(orientation[2]), static_cast(orientation[3]), static_cast(orientation[4]), static_cast(orientation[5]), static_cast(orientation[6]), static_cast(orientation[7]), static_cast(orientation[8]) }); transform_node->setPosition( Ogre::Vector3 {static_cast(position[0]), static_cast(position[1]), static_cast(position[2]) }); transform_node->setScale(Ogre::Vector3 {1.0, 1.0, 1.0}); render_requested = true; } if(render_requested) { this->request_render(); } } } //----------------------------------------------------------------------------- void axis::stopping() { this->render_service()->make_current(); Ogre::SceneManager* const scene_mgr = this->get_scene_manager(); if(m_scene_node != nullptr) { if(m_axis_visibility) { m_scene_node->removeAndDestroyAllChildren(); } } if(m_enable_label) { for(auto& label : m_axis_labels) { SIGHT_ASSERT("label should not be null", label); label->detach_from_node(); label.reset(); } } if(m_axis_name_txt != nullptr) { m_axis_name_txt->detach_from_node(); m_axis_name_txt.reset(); } if(m_origin_visibility) { scene_mgr->destroyManualObject(m_origin); } if(m_axis_visibility) { scene_mgr->destroyManualObject(m_x_line); scene_mgr->destroyManualObject(m_y_line); scene_mgr->destroyManualObject(m_z_line); scene_mgr->destroyManualObject(m_x_cone); scene_mgr->destroyManualObject(m_y_cone); scene_mgr->destroyManualObject(m_z_cone); } Ogre::SceneNode* const transform_node = this->get_transform_node(); if(transform_node != nullptr) { transform_node->removeAndDestroyChild(gen_id("main_node")); } m_material.reset(); m_origin_material.reset(); adaptor::deinit(); } //----------------------------------------------------------------------------- void axis::set_visible(bool _visible) { if(m_scene_node != nullptr) { m_scene_node->setVisible(_visible); if(m_enable_label) { for(auto& label : m_axis_labels) { SIGHT_ASSERT("label should not be null", label); label->set_visible(_visible); } } if(m_axis_name_txt != nullptr) { m_axis_name_txt->set_visible(_visible); } } this->updating(); } //------------------------------------------------------------------------------ service::connections_t axis::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(m_image, data::object::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); return connections; } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/axis.hpp000066400000000000000000000134471503402212300214460ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2025 IRCAD France * Copyright (C) 2017-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include namespace sight::data { class Material; } // namespace sight::data namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor shows a simple coordinate system. * * @section Slots Slots * - \b update_visibility(bool): sets whether the axis is shown or not. * - \b toggle_visibility(): toggle whether the axis is shown or not. * - \b show(): shows the axis. * - \b hide(): hides the axis. * - \b update_origin_color(data::color::sptr): update origin sphere with provided color. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Input Input: * - \b image [sight::data::image, optional]: if specified, the axis adaptor will use the origin and direction * of the image. * * @subsection Configuration Configuration: * - \b transform (optional, string, default=""): the name of the Ogre transform node where to attach the mesh, as it * was specified in the transform adaptor. * - \b visible (optional, bool, default=true): the visibility of the axis. * - \b origin (optional, bool, default=false): the origin visibility. * - \b length (optional, float, default=50.f): axis length in scene units. * - \b label (optional, bool, default=true): display axis names. * - \b fontSize (optional, unsigned int, default=16): label font size in points. * - \b name (optional, string): displayed name of the axis (default empty). * * @subsection Properties Properties: * - \b origin_color (optional, hexadecimal, default=#FFFFFF): the color of the axis origin. */ class axis final : public sight::viz::scene3d::adaptor, public sight::viz::scene3d::transformable { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(axis, sight::viz::scene3d::adaptor); /// Sets default parameters and initializes necessary members. axis() noexcept; /// Destroys the adaptor. ~axis() noexcept final = default; protected: /// Configures the adaptor. void configuring() final; /// Creates manual objects in the default ogre resource group. void starting() final; /// Sends a render request. void updating() final; /// Deletes ogre's resources. void stopping() final; /** * @brief Sets the axis visibility. * @param _visible the visibility status of the axis. */ void set_visible(bool _visible) final; sight::service::connections_t auto_connections() const final; private: /// SLOT: internal function to update origin color using provided data::color. void update_origin_color(sight::data::color::sptr _new_color); /// Contains the material data. sight::viz::scene3d::material::standard::uptr m_material; /// Contains the material for the origin (to change its color dynamically). sight::viz::scene3d::material::standard::uptr m_origin_material; /// Defines the axis length in scene units. float m_length {50.F}; /// Enables the visibility of axis labels. bool m_enable_label {true}; /// Contains the origin of the axis. Ogre::ManualObject* m_origin {nullptr}; /// Enables the origin visibility. bool m_origin_visibility {false}; /// Enables the axes visibility. bool m_axis_visibility {true}; /// Contains the line along the x axis. Ogre::ManualObject* m_x_line {nullptr}; /// Contains the line along the y axis. Ogre::ManualObject* m_y_line {nullptr}; /// Contains the line along the z axis. Ogre::ManualObject* m_z_line {nullptr}; /// Contains the arrow of the x axis. Ogre::ManualObject* m_x_cone {nullptr}; /// Contains the arrow of the y axis. Ogre::ManualObject* m_y_cone {nullptr}; /// Contains the arrow of the z axis. Ogre::ManualObject* m_z_cone {nullptr}; /// Contains the scene node where all of manual objects are attached. Ogre::SceneNode* m_scene_node {nullptr}; /// Stores labels attached to each axis. std::array m_axis_labels {{nullptr, nullptr, nullptr}}; sight::viz::scene3d::text::sptr m_axis_name_txt {nullptr}; /// Defines labels font size in points. std::size_t m_font_size {12}; /// Axis name, default empty. std::string m_axis_name; /// Optional input image sight::data::ptr m_image {this, "image", true}; /// Defines the origin color. sight::data::property m_origin_color {this, "origin_color", {1., 1., 1., 1.}}; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/camera.cpp000066400000000000000000000336031503402212300217210ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2024 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/camera.hpp" #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { static const core::com::slots::key_t TRANSFORM_SLOT = "transform"; static const core::com::slots::key_t CALIBRATE_SLOT = "calibrate"; static const core::com::slots::key_t UPDATE_TF_SLOT = "updateTransformation"; //----------------------------------------------------------------------------- struct camera::CameraNodeListener : public Ogre::MovableObject::Listener { camera* m_layer {nullptr}; //------------------------------------------------------------------------------ explicit CameraNodeListener(camera* _renderer) : m_layer(_renderer) { } //------------------------------------------------------------------------------ void objectMoved(Ogre::MovableObject* /*unused*/) override { m_layer->update_tf_3d(); } }; //------------------------------------------------------------------------------ camera::camera() noexcept { new_slot(TRANSFORM_SLOT, [this](){lazy_update(update_flags::TRANSFORM);}); new_slot(CALIBRATE_SLOT, [this](){lazy_update(update_flags::CALIBRATION);}); new_slot(UPDATE_TF_SLOT, &camera::update_tf_3d, this); } //------------------------------------------------------------------------------ void camera::configuring() { this->configure_params(); const config_t config = this->get_config(); const auto projection_type = config.get("config..projection", "perspective"); if(projection_type == "orthographic") { m_use_orthographic_projection = true; } else if(projection_type == "perspective") { m_use_orthographic_projection = false; } else { SIGHT_ERROR( "projection type '" + projection_type + "' is not managed use either 'perspective'(default) or 'orthographic'" ); } } //------------------------------------------------------------------------------ void camera::starting() { adaptor::init(); m_camera = this->layer()->get_default_camera(); if(m_use_orthographic_projection) { m_camera->setProjectionType(Ogre::ProjectionType::PT_ORTHOGRAPHIC); // inform layer since some computations are a bit different from perspective. this->layer()->set_orthographic_camera(true); } m_camera_node_listener = new CameraNodeListener(this); m_camera->setListener(m_camera_node_listener); m_layer_connection.connect( this->layer(), sight::viz::scene3d::layer::CAMERA_RANGE_UPDATED_SIG, this->get_sptr(), CALIBRATE_SLOT ); m_layer_connection.connect( this->layer(), sight::viz::scene3d::layer::RESIZE_LAYER_SIG, this->get_sptr(), CALIBRATE_SLOT ); this->lazy_update(); this->updating(); } //----------------------------------------------------------------------------- service::connections_t camera::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(TRANSFORM_INOUT, data::matrix4::MODIFIED_SIG, TRANSFORM_SLOT); connections.push(CALIBRATION_INPUT, data::camera::MODIFIED_SIG, CALIBRATE_SLOT); connections.push(CALIBRATION_INPUT, data::camera::INTRINSIC_CALIBRATED_SIG, CALIBRATE_SLOT); connections.push(CAMERA_SET_INPUT, data::camera_set::MODIFIED_SIG, CALIBRATE_SLOT); connections.push(CAMERA_SET_INPUT, data::camera_set::EXTRINSIC_CALIBRATED_SIG, CALIBRATE_SLOT); return connections; } //------------------------------------------------------------------------------ void camera::updating() { if(update_needed(update_flags::TRANSFORM)) { if(m_calibration_done || this->calibrate()) { Ogre::Affine3 ogre_matrix; { const auto transform = m_transform.lock(); // Received input line and column data from Sight transformation matrix for(std::size_t lt = 0 ; lt < 4 ; lt++) { for(std::size_t ct = 0 ; ct < 4 ; ct++) { ogre_matrix[ct][lt] = static_cast((*transform)(ct, lt)); } } } // Decompose the camera matrix Ogre::Vector3 position; Ogre::Vector3 scale; Ogre::Quaternion orientation; ogre_matrix.decomposition(position, scale, orientation); // Reverse view-up and direction for AR const Ogre::Quaternion rotate_y(Ogre::Degree(180), Ogre::Vector3(0, 1, 0)); const Ogre::Quaternion rotate_z(Ogre::Degree(180), Ogre::Vector3(0, 0, 1)); orientation = orientation * rotate_z * rotate_y; // Flag to skip update_tf3D() when called from the camera listener m_skip_update = true; Ogre::Node* parent = m_camera->getParentNode(); // Reset the camera position parent->setPosition(0, 0, 0); parent->setOrientation(Ogre::Quaternion::IDENTITY); // Update the camera position parent->rotate(orientation); parent->translate(position); } } if(update_needed(update_flags::CALIBRATION)) { this->calibrate(); } this->update_done(); this->request_render(); } //------------------------------------------------------------------------------ void camera::stopping() { m_layer_connection.disconnect(); if(m_camera_node_listener != nullptr) { m_camera->setListener(nullptr); delete m_camera_node_listener; m_camera_node_listener = nullptr; } adaptor::deinit(); } //------------------------------------------------------------------------------ void camera::update_tf_3d() { if(m_skip_update) { // We were called from the listener after update() was called, so skip that m_skip_update = false; return; } const Ogre::SceneNode* cam_node = m_camera->getParentSceneNode(); const Ogre::Quaternion& orientation = cam_node->getOrientation(); Ogre::Matrix3 mat33; orientation.ToRotationMatrix(mat33); const Ogre::Vector3& position = cam_node->getPosition(); Ogre::Matrix4 new_trans_mat; for(std::size_t i = 0 ; i < 3 ; i++) { for(std::size_t j = 0 ; j < 3 ; j++) { // Set the 3x3 matrix representing the rotation new_trans_mat[i][j] = mat33[i][j]; } } for(std::size_t i = 0 ; i < 3 ; i++) { // Last column represents translation new_trans_mat[i][3] = position[i]; // Set the last lines to 0 new_trans_mat[3][i] = 0; } // And the last value new_trans_mat[3][3] = 1; // Now nullify the reverse of the view-up and direction Ogre::Quaternion rotate; const Ogre::Quaternion rotate_y(Ogre::Degree(180), Ogre::Vector3(0, 1, 0)); const Ogre::Quaternion rotate_z(Ogre::Degree(180), Ogre::Vector3(0, 0, 1)); rotate = rotate_z * rotate_y; rotate = rotate.Inverse(); new_trans_mat = new_trans_mat * Ogre::Matrix4(rotate); const auto transform = m_transform.lock(); // Received input line and column data from Sight transformation matrix for(std::size_t lt = 0 ; lt < 4 ; lt++) { for(std::size_t ct = 0 ; ct < 4 ; ct++) { (*transform)(ct, lt) = static_cast(new_trans_mat[ct][lt]); } } transform->async_emit(this, data::object::MODIFIED_SIG); } //------------------------------------------------------------------------------ void camera::set_near_clip_distance(Ogre::Real _near_clip_distance) { SIGHT_ASSERT("The associated camera doesn't exist.", m_camera); m_camera->setNearClipDistance(_near_clip_distance); } //------------------------------------------------------------------------------ void camera::set_far_clip_distance(Ogre::Real _far_clip_distance) { SIGHT_ASSERT("The associated camera doesn't exist.", m_camera); m_camera->setFarClipDistance(_far_clip_distance); } //----------------------------------------------------------------------------- void camera::set_aspect_ratio(Ogre::Real _ratio) { SIGHT_ASSERT("The associated camera doesn't exist.", m_camera); m_aspect_ratio = _ratio; SIGHT_ASSERT("Width and height should be strictly positive", !std::isnan(_ratio)); m_camera->setAspectRatio(m_aspect_ratio); } //----------------------------------------------------------------------------- bool camera::calibrate() { const auto camera_set = m_camera_set.lock(); const auto camera_calibration = m_camera_calibration.lock(); SIGHT_WARN_IF( "A '" << CALIBRATION_INPUT << "' input was set but will not be used because a '" << CAMERA_SET_INPUT << "' was defined as well", camera_set && camera_calibration ); if(camera_set) { this->calibrate_camera_set(*camera_set); } else if(camera_calibration) { this->calibrate_mono_camera(*camera_calibration); } else { const auto width = static_cast(m_camera->getViewport()->getActualWidth()); const auto height = static_cast(m_camera->getViewport()->getActualHeight()); if(width <= 0 || height <= 0) { SIGHT_ERROR("Width and height should be strictly positive"); return false; } const float aspect_ratio = width / height; m_camera->setAspectRatio(aspect_ratio); } m_calibration_done = true; return true; } //------------------------------------------------------------------------------ void camera::calibrate_mono_camera(const data::camera& _cam) { const auto width = static_cast(m_camera->getViewport()->getActualWidth()); const auto height = static_cast(m_camera->getViewport()->getActualHeight()); const auto near_clip = static_cast(m_camera->getNearClipDistance()); const auto far_clip = static_cast(m_camera->getFarClipDistance()); if(_cam.get_is_calibrated()) { Ogre::Matrix4 m = sight::viz::scene3d::helper::camera::compute_projection_matrix( _cam, width, height, near_clip, far_clip ); m_camera->setCustomProjectionMatrix(true, m); } } //------------------------------------------------------------------------------ void camera::calibrate_camera_set(const data::camera_set& _cs) { const auto width = static_cast(m_camera->getViewport()->getActualWidth()); const auto height = static_cast(m_camera->getViewport()->getActualHeight()); const auto near_clip = static_cast(m_camera->getNearClipDistance()); const auto far_clip = static_cast(m_camera->getFarClipDistance()); const std::size_t nb_cams = _cs.size(); SIGHT_WARN_IF( "There are no cameras in the '" << CAMERA_SET_INPUT << "', the default projection transform" "will be used.", nb_cams == 0 ); auto layer = this->layer(); // Calibrate only the first camera when stereo is not enabled. if(layer->get_stereo_mode() == sight::viz::scene3d::compositor::core::stereo_mode_t::none && nb_cams > 0) { this->calibrate_mono_camera(*_cs.get_camera(0)); } else { sight::viz::scene3d::layer::camera_calibrations_t calibrations; Ogre::Matrix4 extrinsic_mx(Ogre::Matrix4::IDENTITY); for(std::size_t i = 0 ; i < nb_cams ; ++i) { const data::camera::csptr camera = _cs.get_camera(i); if(camera->get_is_calibrated()) { const auto intrinsic_proj_mx = sight::viz::scene3d::helper::camera::compute_projection_matrix( *camera, width, height, near_clip, far_clip ); calibrations.push_back(intrinsic_proj_mx * extrinsic_mx); } else { SIGHT_ERROR("Camera #" << i << " is not calibrated."); } // In fwRenderOgre we define extrinsic calibrations as being the transform from the // first camera to the current one. if(i < nb_cams - 1) { const data::matrix4::csptr extrinsic = _cs.get_extrinsic_matrix(i + 1); extrinsic_mx = sight::viz::scene3d::utils::to_ogre_matrix(extrinsic) * extrinsic_mx; } } if(!calibrations.empty()) { layer->set_camera_calibrations(calibrations); } } } //------------------------------------------------------------------------------ } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/camera.hpp000066400000000000000000000135031503402212300217230ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "module/viz/scene3d/adaptor/transform.hpp" #include #include #include #include #include namespace sight::data { class Camera; } // namespace sight::data namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor transforms a Sight camera to an Ogre camera. * * @section Slots Slots * - \b update_tf3D(): called when the Ogre transform matrix has been updated and updates the transform service * accordingly. * - \b calibrate(): applies calibration information to Ogre camera. * * @section XML XML Configuration * * * @code{.xml} * @endcode * * @subsection Input Input * - \b calibration [sight::data::camera] (optional): camera containing calibration information. * - \b calibration [sight::data::camera_set] (optional): camera series containing calibration information. * * @subsection InOut InOut * - \b transform [sight::data::matrix4]: transform matrix for the camera. */ class camera final : public sight::viz::scene3d::adaptor { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(camera, sight::viz::scene3d::adaptor); /// Creates the adaptor and initialize slots. camera() noexcept; /// Destroys the adaptor. ~camera() noexcept override = default; protected: /// Configures the adaptor. void configuring() override; /// Installs layer connections and calibrates the camera if it exists. void starting() override; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::matrix4::MODIFIED_SIG of s_TRANSFORM_INOUT to service::slots::UPDATE * Connect data::camera::INTRINSIC_CALIBRATED_SIG of s_CALIBRATION_INPUT to CALIBRATE_SLOT * Connect data::camera_set::MODIFIED_SIG of s_CAMERA_SET_INPUT to CALIBRATE_SLOT * Connect data::camera_set::EXTRINSIC_CALIBRATED_SIG of s_CAMERA_SET_INPUT to CALIBRATE_SLOT */ service::connections_t auto_connections() const override; /// Sets the camera's transform. void updating() override; /// Removes layer connections. void stopping() override; private: /// Calibrates the scene's camera(s) using the input calibration(s). bool calibrate(); /** * @brief Computes the camera's projection matrix using its intrinsic parameters and sets it in the scene. * @param _cam data use to retreive the camera intrinsic parameters. */ void calibrate_mono_camera(const data::camera& _cam); /// Computes a projection matrix for each camera in the series and set them in the layer. /// This matrix is equal to the intrinsic times the extrinsic matrix. void calibrate_camera_set(const data::camera_set& _cs); /// Updates Transformation Matrix. void update_tf_3d(); /// Defines the near clipping plane position. void set_near_clip_distance(Ogre::Real _near_clip_distance); /// Defines the far clipping plane position. void set_far_clip_distance(Ogre::Real _far_clip_distance); /// Defines the aspect ratio. void set_aspect_ratio(Ogre::Real _ratio); /// Contains the Ogre camera managed by this adaptor. Ogre::Camera* m_camera {nullptr}; /// Defines the aspect ratio for the frustum viewport. Ogre::Real m_aspect_ratio {0.F}; /// Handles connection with the layer. core::com::helper::sig_slot_connection m_layer_connection; /// Defines the camera listener class used to pass the projection matrix for autostereo shaders. struct CameraNodeListener; CameraNodeListener* m_camera_node_listener {nullptr}; /// This avoids a self-call to update_tf3D() when we update() the camera bool m_skip_update {false}; /// Indicates if the calibration has been done successfully. If not, no update can be done without a calibrate bool m_calibration_done {false}; enum class update_flags : std::uint8_t { TRANSFORM, CALIBRATION }; static constexpr std::string_view CALIBRATION_INPUT = "calibration"; static constexpr std::string_view CAMERA_SET_INPUT = "camera_set"; static constexpr std::string_view TRANSFORM_INOUT = "transform"; data::ptr m_camera_calibration {this, CALIBRATION_INPUT, true}; data::ptr m_camera_set {this, CAMERA_SET_INPUT, true}; data::ptr m_transform {this, TRANSFORM_INOUT}; bool m_use_orthographic_projection {false}; }; //------------------------------------------------------------------------------ } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/compositor_parameter.cpp000066400000000000000000000156071503402212300247330ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/compositor_parameter.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { class CompositorListener : public Ogre::CompositorInstance::Listener { public: CompositorListener(Ogre::Viewport* /*unused*/, compositor_parameter::sptr _adaptor) : m_adaptor(_adaptor) { } ~CompositorListener() override = default; //------------------------------------------------------------------------------ void notifyMaterialSetup(Ogre::uint32 /*pass_id*/, Ogre::MaterialPtr& _mat) override { auto adaptor = m_adaptor.lock(); SIGHT_ASSERT("Adaptor has expired.", adaptor); adaptor->update_value(_mat); } //------------------------------------------------------------------------------ void notifyResourcesCreated(bool /*forResizeOnly*/) override { auto adaptor = m_adaptor.lock(); SIGHT_ASSERT("Adaptor has expired.", adaptor); adaptor->lazy_update(); } //------------------------------------------------------------------------------ void notifyResourcesReleased(bool /*forResizeOnly*/) override { } private: /// Associated Sight adaptor module::viz::scene3d::adaptor::compositor_parameter::wptr m_adaptor; }; static const core::com::slots::key_t ADD_LISTENER_SLOT = "addListener"; //----------------------------------------------------------------------------- compositor_parameter::compositor_parameter() noexcept { new_slot(ADD_LISTENER_SLOT, &compositor_parameter::add_listener, this); } //------------------------------------------------------------------------------ void compositor_parameter::configuring() { this->parameter_adaptor::configuring(); const config_t config = this->get_config(); static const std::string s_COMPOSITOR_NAME_CONFIG = CONFIG + "compositor_name"; m_compositor_name = config.get(s_COMPOSITOR_NAME_CONFIG); SIGHT_ERROR_IF("'" + s_COMPOSITOR_NAME_CONFIG + "' attribute not set", m_compositor_name.empty()); } //------------------------------------------------------------------------------ void compositor_parameter::starting() { adaptor::init(); sight::viz::scene3d::layer::sptr layer = this->layer(); if(!visible()) { this->slot(slots::UPDATE_VISIBILITY)->async_run(visible()); } this->add_listener(); m_resize_connection.connect( layer, sight::viz::scene3d::layer::RESIZE_LAYER_SIG, this->get_sptr(), ADD_LISTENER_SLOT ); } //------------------------------------------------------------------------------ void compositor_parameter::stopping() { m_resize_connection.disconnect(); this->render_service()->make_current(); this->parameter_adaptor::stopping(); sight::viz::scene3d::layer::sptr layer = this->layer(); Ogre::CompositorChain* comp_chain = Ogre::CompositorManager::getSingleton().getCompositorChain(layer->get_viewport()); auto* compositor = comp_chain->getCompositor(m_compositor_name); // Association of a listener attached to this adaptor to the configured compositor compositor->removeListener(m_listener); delete m_listener; m_listener = nullptr; adaptor::deinit(); } //----------------------------------------------------------------------------- void compositor_parameter::set_visible(bool _enable) { this->render_service()->make_current(); const auto layer = this->layer(); Ogre::CompositorChain* comp_chain = Ogre::CompositorManager::getSingleton().getCompositorChain(layer->get_viewport()); auto* compositor = comp_chain->getCompositor(m_compositor_name); SIGHT_ASSERT("The given compositor '" + m_compositor_name + "' doesn't exist in the compositor chain", compositor); if(!_enable && m_listener != nullptr) { compositor->removeListener(m_listener); delete m_listener; m_listener = nullptr; } layer->update_compositor_state(m_compositor_name, _enable); if(_enable) { // Association of a listener attached to this adaptor to the configured compositor m_listener = new CompositorListener( layer->get_viewport(), std::dynamic_pointer_cast(this->get_sptr()) ); compositor->addListener(m_listener); } } //------------------------------------------------------------------------------ void compositor_parameter::update_value(Ogre::MaterialPtr& _mat) { this->set_material(_mat); this->parameter_adaptor::updating(); } //------------------------------------------------------------------------------ void compositor_parameter::add_listener() { this->render_service()->make_current(); sight::viz::scene3d::layer::sptr layer = this->layer(); Ogre::CompositorChain* comp_chain = Ogre::CompositorManager::getSingleton().getCompositorChain(layer->get_viewport()); auto* compositor = comp_chain->getCompositor(m_compositor_name); SIGHT_ASSERT("The given compositor '" + m_compositor_name + "' doesn't exist in the compositor chain", compositor); if(m_listener != nullptr) { compositor->removeListener(m_listener); delete m_listener; m_listener = nullptr; } // Association of a listener attached to this adaptor to the configured compositor m_listener = new CompositorListener( layer->get_viewport(), std::dynamic_pointer_cast(this->get_sptr()) ); compositor->addListener(m_listener); } //------------------------------------------------------------------------------ } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/compositor_parameter.hpp000066400000000000000000000110021503402212300247210ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include namespace sight::module::viz::scene3d::adaptor { class CompositorListener; /** * @brief This adaptor binds a Sight data to a shader uniform from a specific compositor. * * @section Slots Slots * - \b update_visibility(bool): sets whether the compositor is enabled or not. * - \b toggle_visibility(): toggle whether the compositor is enabled or not. * - \b show(): enables the compositor. * - \b hide(): enables the compositor. * - \b set_bool_parameter(bool, std::string): set the uniform from an integer value. * - \b set_color_parameter(std::array, std::string): set the uniform from a color value. * - \b set_int_parameter(int, std::string): set the uniform from an integer value. * - \b set_int2_parameter(int, int, std::string): set the uniform from two integer values. * - \b set_int3_parameter(int, int, int, std::string): set the uniform from three integer values. * - \b setFloatParameter(float, std::string): set the uniform from an float value. * - \b set_double_parameter(double, std::string): set the uniform from an double value. * - \b set_double2_parameter(double, double, std::string): set the uniform from two double values. * - \b set_double3_parameter(double, double, double, std::string): set the uniform from three double values. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection InOut InOut: * - \b parameter [sight::data::object]: parameter containing the data to upload. * * @subsection Configuration Configuration: * - \b visible (optional, bool, default=true): defines if the compositor is enabled or not. * - \b compositor_name (mandatory, string): the name of the associated Ogre compositor. * - \b parameter (mandatory, string): name of the shader parameter to set. * - \b technique (optional, string, default=""): name of the technique, default to the first in the compositor. * - \b shader_type (optional, string, default=""): the type of the shader (vertex, geometry, fragment). Default to * fragment. */ class compositor_parameter final : public sight::viz::scene3d::parameter_adaptor { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(compositor_parameter, sight::viz::scene3d::parameter_adaptor); /// Creates the adaptor. compositor_parameter() noexcept; /// Destroys the adaptor. ~compositor_parameter() noexcept override = default; protected: /// Configures the adaptor. void configuring() override; /// Creates the compositor listener. void starting() override; /// Removes the compositor listener. void stopping() override; /** * @brief Sets the compositor status. * @param _enable the status of the compositor. */ void set_visible(bool _enable) override; private: /// Updates parameter according to the attached data::object. void update_value(Ogre::MaterialPtr& _mat); /// Adds the listener void add_listener(); /// Defines the material name. std::string m_compositor_name; /// Contains the Ogre compositor listener, we need to keep a pointer to unregister it. CompositorListener* m_listener {nullptr}; /// Handles connection with the layer. core::com::helper::sig_slot_connection m_resize_connection; friend class CompositorListener; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/event.cpp000066400000000000000000000375731503402212300216240ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "event.hpp" #include #include #include #include namespace sight::module::viz::scene3d::adaptor { const core::com::signals::key_t event::TRIGGERED = "triggered"; using interaction_info = sight::viz::scene3d::window_interactor::interaction_info; enum class mouse_buttons : std::uint8_t { none = 0, left = 0 << 1, middle = 0 << 2, right = 0 << 3 }; //------------------------------------------------------------------------------ std::uint8_t& operator|=(std::uint8_t& _a, mouse_buttons _b) { return _a |= static_cast(_b); } //------------------------------------------------------------------------------ std::uint8_t operator&(std::uint8_t _a, mouse_buttons _b) { return _a & static_cast(_b); } event::event() { new_signal(TRIGGERED); } //------------------------------------------------------------------------------ bool event::check( interaction_info::interaction_enum _type, std::optional _button, std::optional _modifiers, std::optional _key ) { bool ok = m_filters.empty(); for(const filter& filter : m_filters) { if(std::ranges::find(filter.type, _type) == filter.type.end()) { continue; } if(_button) { mouse_buttons mouse_button = mouse_buttons::none; if(*_button == left) { mouse_button = mouse_buttons::left; } else if(*_button == middle) { mouse_button = mouse_buttons::middle; } else if(*_button == right) { mouse_button = mouse_buttons::right; } if(!(filter.buttons == 0 || ((filter.buttons & mouse_button) != 0U))) { continue; } } else if(filter.buttons != 0) { continue; } if(!((!filter.modifiers && !_modifiers) || (filter.modifiers && _modifiers && *filter.modifiers == *_modifiers))) { continue; } if(!((filter.keys.empty() && !_key) || (_key && std::ranges::find(filter.keys, _key) != filter.keys.end()))) { continue; } ok = true; } return ok; } //------------------------------------------------------------------------------ void event::mouse_move_event(mouse_button _button, modifier _mods, int _x, int _y, int _dx, int _dy) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::mousemove; if(check(interaction_type, _button, _mods, {})) { interaction_info info; info.interaction_type = interaction_type; info.button = _button; info.modifiers = _mods; info.x = _x; info.y = _y; info.dx = _dx; info.dy = _dy; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::wheel_event(modifier _mods, double _angle_delta, int _x, int _y) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::wheelmove; if(check(interaction_type, {}, _mods, {})) { interaction_info info; info.interaction_type = interaction_type; info.modifiers = _mods; info.delta = _angle_delta; info.x = _x; info.y = _y; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::button_release_event(mouse_button _button, modifier _mods, int _x, int _y) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::buttonrelease; if(check(interaction_type, _button, _mods, {})) { interaction_info info; info.interaction_type = interaction_type; info.button = _button; info.modifiers = _mods; info.x = _x; info.y = _y; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::button_press_event(mouse_button _button, modifier _mods, int _x, int _y) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::buttonpress; if(check(interaction_type, _button, _mods, {})) { interaction_info info; info.interaction_type = interaction_type; info.button = _button; info.modifiers = _mods; info.x = _x; info.y = _y; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::button_double_press_event(mouse_button _button, modifier _mods, int _x, int _y) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::buttondoublepress; if(check(interaction_type, _button, _mods, {})) { interaction_info info; info.interaction_type = interaction_type; info.button = _button; info.modifiers = _mods; info.x = _x; info.y = _y; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::key_press_event(int _key, modifier _mods, int _mouse_x, int _mouse_y) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::keypress; if(check(interaction_type, {}, _mods, _key)) { interaction_info info; info.interaction_type = interaction_type; info.key = _key; info.modifiers = _mods; info.x = _mouse_x; info.y = _mouse_y; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::key_release_event(int _key, modifier _mods, int _mouse_x, int _mouse_y) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::keyrelease; if(check(interaction_type, {}, _mods, _key)) { interaction_info info; info.interaction_type = interaction_type; info.key = _key; info.modifiers = _mods; info.x = _mouse_x; info.y = _mouse_y; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::resize_event(int _width, int _height) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::resize; if(check(interaction_type, {}, {}, {})) { interaction_info info; info.interaction_type = interaction_type; info.x = _width; info.y = _height; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::pinch_gesture_event(double _scale_factor, int _center_x, int _center_y) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::pinch_gesture; if(check(interaction_type, {}, {}, {})) { interaction_info info; info.interaction_type = interaction_type; info.delta = _scale_factor; info.x = _center_x; info.y = _center_y; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::pan_gesture_move_event(int _x, int _y, int _dx, int _dy) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::pan_gesture_move; if(check(interaction_type, {}, {}, {})) { interaction_info info; info.interaction_type = interaction_type; info.x = _x; info.y = _y; info.dx = _dx; info.dy = _dy; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::pan_gesture_release_event(int _x, int _y, int _dx, int _dy) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::pan_gesture_release; if(check(interaction_type, {}, {}, {})) { interaction_info info; info.interaction_type = interaction_type; info.x = _x; info.y = _y; info.dx = _dx; info.dy = _dy; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ void event::long_tap_gesture_event(int _x, int _y) { interaction_info::interaction_enum interaction_type = interaction_info::interaction_enum::long_tap_gesture; if(check(interaction_type, {}, {}, {})) { interaction_info info; info.interaction_type = interaction_type; info.x = _x; info.y = _y; signal(TRIGGERED)->async_emit(info); } } //------------------------------------------------------------------------------ static interaction_info::interaction_enum string_to_interaction_enum(const std::string& _s) { if(_s == "mouseMove") { return interaction_info::interaction_enum::mousemove; } if(_s == "wheel") { return interaction_info::interaction_enum::wheelmove; } if(_s == "buttonRelease") { return interaction_info::interaction_enum::buttonrelease; } if(_s == "buttonPress") { return interaction_info::interaction_enum::buttonpress; } if(_s == "buttonDoublePress") { return interaction_info::interaction_enum::buttondoublepress; } if(_s == "keyPress") { return interaction_info::interaction_enum::keypress; } if(_s == "keyRelease") { return interaction_info::interaction_enum::keyrelease; } if(_s == "resize") { return interaction_info::interaction_enum::resize; } if(_s == "pinchGesture") { return interaction_info::interaction_enum::pinch_gesture; } if(_s == "panGestureMove") { return interaction_info::interaction_enum::pan_gesture_move; } if(_s == "panGestureRelease") { return interaction_info::interaction_enum::pan_gesture_release; } SIGHT_THROW("Unknown interaction: " + _s); } //------------------------------------------------------------------------------ void event::configuring() { configure_params(); const config_t config = this->get_config(); auto [begin, end] = config.equal_range("event"); for(const auto& [_, event] : boost::make_iterator_range(begin, end)) { filter filter; std::vector types; boost::split(types, event.get(".type"), boost::is_any_of(" ")); std::ranges::transform(types, std::back_inserter(filter.type), string_to_interaction_enum); if(boost::optional buttons = event.get_optional("buttons")) { std::vector button_list; boost::split(button_list, *buttons, boost::is_any_of(" ")); for(const std::string& button : button_list) { std::string lower_button = boost::to_lower_copy(button); if(lower_button == "left") { filter.buttons |= mouse_buttons::left; } else if(lower_button == "middle") { filter.buttons |= mouse_buttons::middle; } else if(lower_button == "right") { filter.buttons |= mouse_buttons::right; } else { SIGHT_THROW("Invalid mouse button name: " + button); } } } if(boost::optional modifiers = event.get_optional(".modifiers")) { filter.modifiers = modifier::none; std::vector modifier_list; boost::split(modifier_list, *modifiers, boost::is_any_of(" ")); for(const std::string& modifier : modifier_list) { std::string lower_modifier = boost::to_lower_copy(modifier); if(lower_modifier == "shift") { *filter.modifiers |= modifier::shift; } else if(lower_modifier == "ctrl") { *filter.modifiers |= modifier::control; } else if(lower_modifier == "alt") { *filter.modifiers |= modifier::alt; } else if(lower_modifier == "meta") { *filter.modifiers |= modifier::meta; } else if(lower_modifier == "none") { // do nothing } else { SIGHT_THROW("Invalid keyboard modifier name: " + modifier); } } } if(boost::optional keys = event.get_optional(".keys")) { std::vector key_list; boost::split(key_list, *keys, boost::is_any_of(" ")); for(const std::string& key : key_list) { filter.keys.push_back(std::stoi(key, nullptr, 0)); } } m_filters.push_back(filter); } } //------------------------------------------------------------------------------ void event::starting() { adaptor::init(); this->render_service()->make_current(); auto interactor = std::dynamic_pointer_cast(this->get_sptr()); this->layer()->add_interactor(interactor); } //------------------------------------------------------------------------------ void event::updating() { } //------------------------------------------------------------------------------ void event::stopping() { auto interactor = std::dynamic_pointer_cast(this->get_sptr()); this->layer()->remove_interactor(interactor); adaptor::deinit(); } } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/event.hpp000066400000000000000000000117471503402212300216240ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2024 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor echoes the input events its scene got. The input events may be filtered in the configuration. * * @section Signals Signals * - \b triggered(sight::viz::scene3d::window_interactor::interaction_info): An event was triggered * * @section XML XML Configuration * @code{.xml} @endcode * The snippet above will create a event which will sends signal when the left mouse button is simple-clicked or * double-clicked without modifiers, and when the space key of the keyboard is pressed while holding the Control key. * * @subsection Configuration Configuration: * - \b event * - \b type (string): A space-separated list of events which will trigger a signal. Might be: "mouseMove", "wheel", * "buttonRelease", "buttonPress", "buttonDoublePress", "keyPress", "keyRelease", "resize", pinchGesture", * "panGestureMove", "panGestureRelease", "longTapGesture", "pan2GestureMove" or "pan2GestureRelease". * - \b buttons (optional, string): A space-separated list of mouse buttons to be held during the event. Might be * "left", "middle" or "right". Only valid with event types "mouseMove", "buttonRelease", "buttonPress" * and "buttonDoublePress". * - \b modifiers (optional, string): A space-separated list of keyboard modifiers to be held. Only valid with event * types "mouseMove", "wheel", "buttonRelease", "buttonPress", "buttonDoublePress", "keyPress" and * "keyRelease". Might be "shift", "ctrl", "alt" or "meta". * - \b keys (optional, string): A space-separated list of keys to be clicked as key codes. Only valid with event * types "keyPress" and "keyRelease". @see https://doc.qt.io/qt-5/qt.html#Key-enum */ class event final : public sight::viz::scene3d::adaptor, public sight::viz::scene3d::interactor::base { public: SIGHT_DECLARE_SERVICE(event, sight::viz::scene3d::adaptor); event(); void mouse_move_event( mouse_button _button, modifier _mods, int _x, int _y, int _dx, int _dy ) final; void wheel_event(modifier _mods, double _angle_delta, int _x, int _y) final; void button_release_event(mouse_button _button, modifier _mods, int _x, int _y) final; void button_press_event(mouse_button _button, modifier _mods, int _x, int _y) final; void button_double_press_event(mouse_button _button, modifier _mods, int _x, int _y) final; void key_press_event(int _key, modifier _mods, int _mouse_x, int _mouse_y) final; void key_release_event(int _key, modifier _mods, int _mouse_x, int _mouse_y) final; void resize_event(int _width, int _height) final; void pinch_gesture_event(double _scale_factor, int _center_x, int _center_y) final; void pan_gesture_move_event(int _x, int _y, int _dx, int _dy) final; void pan_gesture_release_event(int _x, int _y, int _dx, int _dy) final; void long_tap_gesture_event(int _x, int _y) final; static const core::com::signals::key_t TRIGGERED; using triggered_signal_t = core::com::signal; private: struct filter { std::vector type; std::uint8_t buttons = 0; std::optional modifiers; std::vector keys; }; bool check( sight::viz::scene3d::window_interactor::interaction_info::interaction_enum _type, std::optional _button, std::optional _modifiers, std::optional _key ); void configuring() final; void starting() final; void updating() final; void stopping() final; std::vector m_filters; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/fragments_info.cpp000066400000000000000000000333251503402212300234730ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2025 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/fragments_info.hpp" #include #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { struct fragments_info_material_listener final : public Ogre::MaterialManager::Listener { ~fragments_info_material_listener() override = default; //------------------------------------------------------------------------------ Ogre::Technique* handleSchemeNotFound( std::uint16_t /*schemeIndex*/, const Ogre::String& _scheme_name, Ogre::Material* _original_material, std::uint16_t /*lodIndex*/, const Ogre::Renderable* /*rend*/ ) override { Ogre::Technique* new_tech = nullptr; if(_scheme_name == "PrimitiveID_MS") { Ogre::Technique* default_tech = _original_material->getTechnique(0); new_tech = sight::viz::scene3d::helper::technique::copy_to_material( default_tech, _scheme_name, _original_material ); const Ogre::Technique::Passes& passes = new_tech->getPasses(); for(auto* const pass : passes) { pass->setCullingMode(Ogre::CULL_NONE); pass->setManualCullingMode(Ogre::MANUAL_CULL_NONE); pass->setFragmentProgram("PrimitiveID_FP"); } } return new_tech; } }; static std::unique_ptr s_material_listener = nullptr; static const core::com::slots::key_t RESIZE_VIEWPORT_SLOT = "resize_viewport"; //----------------------------------------------------------------------------- fragments_info::fragments_info() noexcept { new_slot(RESIZE_VIEWPORT_SLOT, &fragments_info::resize_viewport, this); } //----------------------------------------------------------------------------- void fragments_info::configuring() { // adaptor handles the layerID. this->configure_params(); const config_t config = this->get_config(); m_width = config.get(CONFIG + "width", m_width); m_height = config.get(CONFIG + "height", m_height); m_flip_image = config.get(CONFIG + "flip", m_flip_image); // If Both width & height are found we fix the size. if(m_width > 0 && m_height > 0) { m_fixed_size = true; } } //----------------------------------------------------------------------------- void fragments_info::starting() { adaptor::init(); this->render_service()->make_current(); m_compositor_name = gen_id("Snapshot_C"); m_target_name = gen_id("global_RTT"); m_target_primitive_id_name = gen_id("primitiveID_RTT"); const sight::viz::scene3d::layer::sptr layer = this->layer(); layer->get_render_target()->addListener(this); if(!s_material_listener) { s_material_listener = std::make_unique(); Ogre::MaterialManager::getSingleton().addListener(s_material_listener.get()); } // Fixed size. if(m_fixed_size) { this->create_compositor(m_width, m_height); } // If not listen to the resize event of the layer. else { Ogre::Viewport* const viewport = layer->get_viewport(); const auto h = viewport->getActualHeight(); const auto w = viewport->getActualWidth(); this->create_compositor(w, h); m_resize_connection.connect( this->layer(), sight::viz::scene3d::layer::RESIZE_LAYER_SIG, this->get_sptr(), RESIZE_VIEWPORT_SLOT ); } } //----------------------------------------------------------------------------- void fragments_info::updating() noexcept { } //----------------------------------------------------------------------------- void fragments_info::stopping() { m_resize_connection.disconnect(); this->destroy_compositor(); const sight::viz::scene3d::layer::sptr layer = this->layer(); layer->get_render_target()->removeListener(this); adaptor::deinit(); } //----------------------------------------------------------------------------- void fragments_info::create_compositor(int _width, int _height) { /* Creates the following compositor: compositor 'm_compositorName' { technique { // The depth attachment only exist if the depth is needed. texture 'm_targetName' '_width' '_height' PF_R8G8B8 PF_FLOAT32_R global_scope // We use a local depth texture since Ogre doesn't allow global depth textures. // It will be copied by ForwardDepth_M. texture local_RTT target_width target_height PF_DEPTH32 local_scope // This target pass only exist if the depth is needed. target local_RTT { input previous } target 'm_targetName' { input previous // This pass only exist if the depth is needed. pass render_quad { material ForwardDepth_M input 0 local_RTT } } target_output { input none pass render_quad { material Forward input 0 m_targetName 0 } } } }*/ const bool retrieve_depth = m_depth.lock().operator bool(); const bool retrieve_primitive_id = m_primitive.lock().operator bool(); Ogre::CompositorManager& cmp_manager = Ogre::CompositorManager::getSingleton(); m_compositor = cmp_manager.create( m_compositor_name, sight::viz::scene3d::RESOURCE_GROUP ); Ogre::CompositionTechnique* const technique = m_compositor->createTechnique(); Ogre::CompositionTechnique::TextureDefinition* global_target = nullptr; global_target = technique->createTextureDefinition(m_target_name); global_target->scope = Ogre::CompositionTechnique::TextureScope::TS_GLOBAL; global_target->formatList.push_back(Ogre::PixelFormat::PF_A8B8G8R8); global_target->height = static_cast(_height); global_target->width = static_cast(_width); if(!retrieve_depth) { Ogre::CompositionTargetPass* const global_target_pass = technique->createTargetPass(); { global_target_pass->setOutputName(m_target_name); global_target_pass->setInputMode(Ogre::CompositionTargetPass::InputMode::IM_PREVIOUS); } } else { global_target->formatList.push_back(Ogre::PixelFormat::PF_FLOAT32_R); const std::string local_name("local_RTT"); Ogre::CompositionTechnique::TextureDefinition* local_target = nullptr; local_target = technique->createTextureDefinition(local_name); local_target->scope = Ogre::CompositionTechnique::TextureScope::TS_LOCAL; local_target->formatList.push_back(Ogre::PixelFormat::PF_DEPTH32); local_target->height = static_cast(_height); local_target->width = static_cast(_width); Ogre::CompositionTargetPass* const local_target_pass = technique->createTargetPass(); { local_target_pass->setOutputName(local_name); local_target_pass->setInputMode(Ogre::CompositionTargetPass::InputMode::IM_PREVIOUS); } Ogre::CompositionTargetPass* const global_target_pass = technique->createTargetPass(); { global_target_pass->setOutputName(m_target_name); global_target_pass->setInputMode(Ogre::CompositionTargetPass::InputMode::IM_PREVIOUS); Ogre::CompositionPass* const target_output_comp_pass = global_target_pass->createPass( Ogre::CompositionPass::PT_RENDERQUAD ); target_output_comp_pass->setMaterialName("ForwardDepth_M"); target_output_comp_pass->setInput(0, local_name); } } if(retrieve_primitive_id) { Ogre::CompositionTechnique::TextureDefinition* global_target_primitive_id = nullptr; global_target_primitive_id = technique->createTextureDefinition(m_target_primitive_id_name); global_target_primitive_id->scope = Ogre::CompositionTechnique::TextureScope::TS_GLOBAL; global_target_primitive_id->formatList.push_back(Ogre::PixelFormat::PF_R32_SINT); global_target_primitive_id->height = static_cast(_height); global_target_primitive_id->width = static_cast(_width); Ogre::CompositionTargetPass* const global_primitive_target_pass = technique->createTargetPass(); { global_primitive_target_pass->setOutputName(m_target_primitive_id_name); global_primitive_target_pass->setInputMode(Ogre::CompositionTargetPass::InputMode::IM_NONE); global_primitive_target_pass->setMaterialScheme("PrimitiveID_MS"); global_primitive_target_pass->createPass(Ogre::CompositionPass::PT_CLEAR); global_primitive_target_pass->createPass(Ogre::CompositionPass::PT_RENDERSCENE); } } Ogre::CompositionTargetPass* const target_output_pass = technique->getOutputTargetPass(); { Ogre::CompositionPass* const target_output_comp_pass = target_output_pass->createPass( Ogre::CompositionPass::PT_RENDERQUAD ); target_output_comp_pass->setMaterialName("Forward_M"); target_output_comp_pass->setInput(0, m_target_name, 0); } const auto layer = this->layer(); layer->add_available_compositor(m_compositor_name); layer->update_compositor_state(m_compositor_name, true); } //----------------------------------------------------------------------------- void fragments_info::destroy_compositor() { Ogre::CompositorManager& cmp_manager = Ogre::CompositorManager::getSingleton(); const auto layer = this->layer(); layer->update_compositor_state(m_compositor_name, false); cmp_manager.removeCompositor(layer->get_viewport(), m_compositor_name); cmp_manager.remove(m_compositor); m_compositor.reset(); } //----------------------------------------------------------------------------- void fragments_info::resize_viewport() { const auto layer = this->layer(); auto rect = layer->get_viewport()->getActualDimensions(); // Sometimes, the size can be null, we need to avoid resizing since a global texture needs absolute values. if(rect.width() != 0 && rect.height() != 0) { this->destroy_compositor(); this->create_compositor(static_cast(rect.width()), static_cast(rect.height())); layer->get_render_target()->addListener(this); } } //----------------------------------------------------------------------------- void fragments_info::postRenderTargetUpdate(const Ogre::RenderTargetEvent& /*evt*/) { { const auto image = m_image.lock(); if(image) { const Ogre::TexturePtr text = m_compositor->getTextureInstance(m_target_name, 0); sight::viz::scene3d::utils::convert_from_ogre_texture(text, image.get_shared(), m_flip_image); const auto sig = image->signal(data::object::MODIFIED_SIG); sig->async_emit(); } } { const auto depth = m_depth.lock(); if(depth) { const Ogre::TexturePtr depth_text = m_compositor->getTextureInstance(m_target_name, 1); sight::viz::scene3d::utils::convert_from_ogre_texture(depth_text, depth.get_shared(), m_flip_image); const auto depth_sig = depth->signal(data::object::MODIFIED_SIG); depth_sig->async_emit(); } } { const auto primitive_id = m_primitive.lock(); if(primitive_id) { const Ogre::TexturePtr primitive_id_text = m_compositor->getTextureInstance(m_target_primitive_id_name, 0); sight::viz::scene3d::utils::convert_from_ogre_texture( primitive_id_text, primitive_id.get_shared(), m_flip_image ); const auto primitive_id_sig = primitive_id->signal(data::object::MODIFIED_SIG); primitive_id_sig->async_emit(); } } } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/fragments_info.hpp000066400000000000000000000123211503402212300234710ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2024 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor takes a snapshot of layer fragments information and output it as a data::image. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection InOut InOut: * - \b image [sight::data::image] (optional): image containing the snapshot of the layer color buffer. * - \b depth [sight::data::image] (optional): image containing the snapshot of the layer depth buffer. * - \b primitiveID [sight::data::image] (optional): image containing the primitive ID of the layer. * * @subsection Configuration Configuration: * - \b width (optional): fixed width of snapshot. * - \b height (optional): fixed height of snaphot. * NOTE: if width & height are missing (or one of them), size of the snapshot will be connected to the layer: * if the layer is resized the snaphot will be resized. * - \b flip (optional): flip result images, default: false. */ class fragments_info final : public sight::viz::scene3d::adaptor, public Ogre::RenderTargetListener { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(fragments_info, sight::viz::scene3d::adaptor); /// Initializes the adaptor. fragments_info() noexcept; /// Destroys the adaptor. ~fragments_info() noexcept override = default; /// Resizes the global render target void resize_viewport(); /// Calls updating(). This is called right after the layer render target has been rendered. void postRenderTargetUpdate(const Ogre::RenderTargetEvent& /*evt*/) override; protected: /// Configures the layer and retrieves the size of the output image. void configuring() override; /// Initializes adaptor and connection to layer signals. void starting() override; /// Updates the service. Convert render target texture to data::image. void updating() noexcept override; /// Destroys adaptor, only calls module::viz::scene3d::adaptor::destroyCompositor(). void stopping() override; private: /** * @brief Creates the compositor which copy the layer color buffer to a global render target. * @param _width width of the global render target (color and depth). * @param _height height of the global render target (color and depth). */ void create_compositor(int _width, int _height); /// Destroys compositor. void destroy_compositor(); /// Contains the created compositor. Ogre::CompositorPtr m_compositor {nullptr}; /// Defines the compositor name. std::string m_compositor_name; /// Defines the global render target name used to get back color and depth. std::string m_target_name; /// Defines the global render target name used to get back the primitive ID. std::string m_target_primitive_id_name; /// Enables the fixed size, if width & height parameters are found in config xml, use fixed size. /// If not use the layer's viewport size and listen the resize event. bool m_fixed_size {false}; /// Defines the width and the height of the compositor's render target. /// Only used if width & height are found in of xml configuration int m_width {-1}; /// Defines the compositor target fixed height. int m_height {-1}; /// Flips Ogre texture when converting to sight, can be useful when using VTK to save images. bool m_flip_image {false}; /// Handles connection with the layer. core::com::helper::sig_slot_connection m_resize_connection; static constexpr std::string_view IMAGE_INOUT = "image"; data::ptr m_image {this, IMAGE_INOUT}; static constexpr std::string_view DEPTH_INOUT = "depth"; data::ptr m_depth {this, DEPTH_INOUT}; static constexpr std::string_view PRIMITIVE_ID_INOUT = "primitiveID"; data::ptr m_primitive {this, PRIMITIVE_ID_INOUT}; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/frustum.cpp000066400000000000000000000130461503402212300221750ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2018-2025 IRCAD France * Copyright (C) 2018-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/frustum.hpp" #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { //------------------------------------------------------------------------------ void frustum::configuring() { this->configure_params(); const config_t config = this->get_config(); this->set_transform_id( config.get( sight::viz::scene3d::transformable::TRANSFORM_CONFIG, gen_id("transform") ) ); static const std::string s_NEAR_CONFIG = CONFIG + "near"; static const std::string s_FAR_CONFIG = CONFIG + "far"; static const std::string s_COLOR_CONFIG = CONFIG + "color"; m_near = config.get(s_NEAR_CONFIG, m_near); m_far = config.get(s_FAR_CONFIG, m_far); m_color = config.get(s_COLOR_CONFIG, m_color); } //----------------------------------------------------------------------------- void frustum::starting() { adaptor::init(); // Create camera m_ogre_camera = this->get_scene_manager()->createCamera(gen_id(CAMERA_INPUT)); m_ogre_camera->setVisible(visible()); // Clipping if(m_near != 0.F) { m_ogre_camera->setNearClipDistance(m_near); } if(m_far != 0.F) { m_ogre_camera->setFarClipDistance(m_far); } // Set data to camera this->set_ogre_cam_from_data(); // Add camera to ogre scene Ogre::SceneNode* root_scene_node = this->get_scene_manager()->getRootSceneNode(); Ogre::SceneNode* trans_node = this->get_or_create_transform_node(root_scene_node); trans_node->attachObject(m_ogre_camera); // Set position trans_node->setPosition(Ogre::Vector3(0, 0, 0)); trans_node->setDirection(Ogre::Vector3(Ogre::Real(0), Ogre::Real(0), Ogre::Real(1))); // Create material for the frustum m_material = std::make_unique(gen_id("material")); m_material->set_shading(sight::data::material::shading_t::ambient, this->layer()->num_lights()); sight::data::color color(m_color); m_material->material()->setDiffuse(Ogre::ColourValue(color[0], color[1], color[2], color[3])); m_frustum = this->get_scene_manager()->createManualObject(gen_id("frustum")); sight::viz::scene3d::helper::manual_object::create_frustum(m_frustum, m_material->name(), *m_ogre_camera); trans_node->attachObject(m_frustum); this->request_render(); } //----------------------------------------------------------------------------- service::connections_t frustum::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(CAMERA_INPUT, data::camera::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(CAMERA_INPUT, data::camera::INTRINSIC_CALIBRATED_SIG, adaptor::slots::LAZY_UPDATE); return connections; } //----------------------------------------------------------------------------- void frustum::updating() { this->set_ogre_cam_from_data(); m_frustum->setVisible(visible()); this->update_done(); this->request_render(); } //----------------------------------------------------------------------------- void frustum::stopping() { m_frustum->detachFromParent(); m_ogre_camera->detachFromParent(); this->get_scene_manager()->destroyManualObject(m_frustum); this->get_scene_manager()->destroyCamera(m_ogre_camera); m_ogre_camera = nullptr; m_material.reset(); adaptor::deinit(); } //----------------------------------------------------------------------------- void frustum::set_ogre_cam_from_data() { const auto camera = m_camera.lock(); if(camera->get_is_calibrated()) { const auto width = static_cast(camera->get_width()); const auto height = static_cast(camera->get_height()); Ogre::Matrix4 m = sight::viz::scene3d::helper::camera::compute_projection_matrix(*camera, width, height, m_near, m_far); m_ogre_camera->setCustomProjectionMatrix(true, m); } else { SIGHT_WARN("The camera '" + std::string(CAMERA_INPUT) + "' is not calibrated"); } } //----------------------------------------------------------------------------- void frustum::set_visible(bool _is_visible) { m_frustum->setVisible(_is_visible); this->request_render(); } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/frustum.hpp000066400000000000000000000106241503402212300222010ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2018-2025 IRCAD France * Copyright (C) 2018-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor displays the frustum of a data::camera. * * @section Slots Slots * - \b update_visibility(bool): sets whether the frustum is shown or not. * - \b toggle_visibility(): toggles whether the frustum is shown or not. * - \b show(): shows the frustum. * - \b hide(): hides the frustum. * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection Input Input: * - \b camera [sight::data::camera]: camera containing calibration information. * * @subsection Configuration Configuration: * - \b transform (optional, string, default=""): transform applied to the frustum's scene node * - \b near (optional, float, default=1.0): near clipping distance of the Ogre::Camera * - \b far (optional, float, default=20.0): far clipping distance of the Ogre::Camera * - \b color (optional, hexadecimal, default=0xFF0000): frustum's color * - \b visible (optional, bool, default=true): the visibility of the adaptor. */ class frustum final : public sight::viz::scene3d::adaptor, public sight::viz::scene3d::transformable { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(frustum, sight::viz::scene3d::adaptor); /// Sets default parameters and initializes necessary members. frustum() noexcept = default; /// Destroys the adaptor. ~frustum() noexcept override = default; protected: /// Configures. void configuring() override; /// Manually creates a frustum. void starting() override; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::camera::INTRINSIC_CALIBRATED_SIG of s_CAMERA_INPUT to adaptor::slots::LAZY_UPDATE * Connect data::camera::MODIFIED_SIG of s_CAMERA_INPUT to adaptor::slots::LAZY_UPDATE */ service::connections_t auto_connections() const override; /// Deletes the frustum. void stopping() override; /// Checks if the camera has changed, and updates it if it has. void updating() override; /** * @brief Sets the frustum visibility. * @param _visible the visibility status of the frustum. */ void set_visible(bool _visible) override; private: /// Sets Ogre::Camera from data::camera parameters. void set_ogre_cam_from_data(); /// Contains the manual object of the line. Ogre::ManualObject* m_frustum {nullptr}; /// Contains the Ogre's camera (frustum) representing data::camera position and parameters. Ogre::Camera* m_ogre_camera {nullptr}; /// Contains the material. sight::viz::scene3d::material::standard::uptr m_material; /// Defines the near clipping distance. float m_near {1.F}; /// Defines the far clipping distance. float m_far {20.F}; /// Defines the color of frustum. std::string m_color {"#FF0000"}; static constexpr std::string_view CAMERA_INPUT = "camera"; sight::data::ptr m_camera {this, CAMERA_INPUT}; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/frustum_list.cpp000066400000000000000000000164201503402212300232270ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2018-2025 IRCAD France * Copyright (C) 2018-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/frustum_list.hpp" #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { static const core::com::slots::key_t CLEAR_SLOT = "clear"; //----------------------------------------------------------------------------- frustum_list::frustum_list() noexcept { new_slot(CLEAR_SLOT, &frustum_list::clear, this); } //----------------------------------------------------------------------------- void frustum_list::configuring() { this->configure_params(); const config_t config = this->get_config(); this->set_transform_id( config.get( sight::viz::scene3d::transformable::TRANSFORM_CONFIG, this->gen_id("transform") ) ); static const std::string s_NEAR_CONFIG = CONFIG + "near"; static const std::string s_FAR_CONFIG = CONFIG + "far"; static const std::string s_COLOR_CONFIG = CONFIG + "color"; static const std::string s_NB_MAX_CONFIG = CONFIG + "nbMax"; m_near = config.get(s_NEAR_CONFIG, m_near); m_far = config.get(s_FAR_CONFIG, m_far); m_color = config.get(s_COLOR_CONFIG, m_color); m_capacity = config.get(s_NB_MAX_CONFIG, m_capacity); } //----------------------------------------------------------------------------- void frustum_list::starting() { adaptor::init(); m_frustum_list.set_capacity(m_capacity); // Create material m_material = std::make_unique(gen_id("material")); m_material->set_shading(sight::data::material::shading_t::ambient, this->layer()->num_lights()); sight::data::color color(m_color); m_material->material()->setDiffuse(Ogre::ColourValue(color[0], color[1], color[2], color[3])); } //----------------------------------------------------------------------------- service::connections_t frustum_list::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(TRANSFORM_INPUT, data::matrix4::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); return connections; } //----------------------------------------------------------------------------- void frustum_list::updating() { this->add_frustum(); this->update_done(); this->request_render(); } //----------------------------------------------------------------------------- void frustum_list::stopping() { this->clear(); m_material.reset(); adaptor::deinit(); } //----------------------------------------------------------------------------- void frustum_list::set_visible(bool _visible) { Ogre::SceneNode* root_scene_node = this->get_scene_manager()->getRootSceneNode(); Ogre::SceneNode* trans_node = this->get_or_create_transform_node(root_scene_node); trans_node->setVisible(_visible); } //----------------------------------------------------------------------------- void frustum_list::add_frustum() { //Get camera parameters auto* scene_manager = this->get_scene_manager(); const auto camera_data = m_camera.lock(); const auto current_index_str = std::to_string(m_current_cam_index); Ogre::Camera* ogre_camera = scene_manager->createCamera(gen_id("camera" + current_index_str)); Ogre::SceneNode* root_scene_node = scene_manager->getRootSceneNode(); Ogre::SceneNode* trans_node = this->get_or_create_transform_node(root_scene_node); trans_node->attachObject(ogre_camera); // Clipping if(m_near != 0.F) { ogre_camera->setNearClipDistance(m_near); } if(m_far != 0.F) { ogre_camera->setFarClipDistance(m_far); } if(camera_data->get_is_calibrated()) { // Set data to camera const auto width = static_cast(camera_data->get_width()); const auto height = static_cast(camera_data->get_height()); Ogre::Matrix4 m = sight::viz::scene3d::helper::camera::compute_projection_matrix(*camera_data, width, height, m_near, m_far); ogre_camera->setCustomProjectionMatrix(true, m); if(m_frustum_list.full()) { //Remove the oldest one auto f = m_frustum_list.back(); f.first->detachFromParent(); scene_manager->destroyManualObject(f.first); } auto* const frustum = scene_manager->createManualObject(gen_id("frustum" + current_index_str)); auto* const frustum_node = root_scene_node->createChildSceneNode("Node_" + current_index_str); sight::viz::scene3d::helper::manual_object::create_frustum(frustum, m_material->name(), *ogre_camera); this->set_transfrom_to_node(frustum_node); frustum_node->attachObject(frustum); //Add the new one m_frustum_list.push_front({frustum, frustum_node}); m_current_cam_index++; this->updating(); } else { SIGHT_ERROR("Camera isn't calibrated"); } } //----------------------------------------------------------------------------- void frustum_list::set_transfrom_to_node(Ogre::SceneNode* _node) { const auto transform = m_transform.lock(); Ogre::Affine3 ogre_mat; for(std::size_t lt = 0 ; lt < 4 ; lt++) { for(std::size_t ct = 0 ; ct < 4 ; ct++) { ogre_mat[ct][lt] = static_cast((*transform)(ct, lt)); } } // Decompose the matrix Ogre::Vector3 position; Ogre::Vector3 scale; Ogre::Quaternion orientation; ogre_mat.decomposition(position, scale, orientation); const Ogre::Quaternion rotate_x(Ogre::Degree(180), Ogre::Vector3(1, 0, 0)); const Ogre::Quaternion rotate_z(Ogre::Degree(180), Ogre::Vector3(0, 0, 1)); orientation = orientation * rotate_z * rotate_x; _node->setOrientation(orientation); _node->setPosition(position); } //----------------------------------------------------------------------------- void frustum_list::clear() { for(const auto& f : m_frustum_list) { f.first->detachFromParent(); this->get_scene_manager()->destroyManualObject(f.first); } m_frustum_list.clear(); } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/frustum_list.hpp000066400000000000000000000121331503402212300232310ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2018-2025 IRCAD France * Copyright (C) 2018-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor displays a new Frustum each time the transform is updated. * The number of Frustum is fixed, if the maximum number of Frustum is reached the oldest one will be replaced. * * @section Slots Slots * - \b update_visibility(bool): sets whether frustums are shown or not. * - \b toggle_visibility(): toggles whether frustums are shown or not. * - \b show(): shows frustums. * - \b hide(): hides frustums. * - \b update(bool): adds a frustum in the list and displays it. * - \b clear(): clears frustum list. * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection Input Input: * - \b camera [sight::data::camera]: data::camera that handles calibration parameters * - \b transform [sight::data::matrix4]: each time this transform is modified, a frustum is created. * * @subsection Configuration Configuration: * - \b near (optional, float, default=1.0): near clipping distance of the Ogre::Camera * - \b far (optional, float, default=20.0): far clipping distance of the Ogre::Camera * - \b color (optional, hexadecimal, default=0x0000FF): frustum's color * - \b transform (optional, string, default=""): transform applied to the frustumList's scene node * - \b visible (optional, bool, default=true): the visibility of the adaptor. */ class frustum_list final : public sight::viz::scene3d::adaptor, public sight::viz::scene3d::transformable { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(frustum_list, sight::viz::scene3d::adaptor); /// Creates slots. frustum_list() noexcept; /// Destroys the adaptor. ~frustum_list() noexcept override = default; protected: /// Configures. void configuring() override; /// Initializes the material. void starting() override; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::matrix4::MODIFIED_SIG of s_TRANSFORM_INPUT to adaptor::slots::LAZY_UPDATE */ service::connections_t auto_connections() const override; /// Updates the adaptor by attaching new cameras to scene nodes. void updating() override; /// Clears data. void stopping() override; /** * @brief Sets the frustum list visibility. * @param _visible the visibility status of the frustum list. */ void set_visible(bool _visible) override; private: /// SLOT: clears frustum list. void clear(); /// SLOT: adds a frustum in the list and displays it. void add_frustum(); /// Transform the data::Transform into position / oriention of the scene node. void set_transfrom_to_node(Ogre::SceneNode* _node); /// Defines the near clipping distance. float m_near {1.F}; /// Defines the far clipping distance. float m_far {20.F}; /// Defines the color of frustum. std::string m_color {"#0000FF"}; /// Defines the maximum capacity of frustum list. unsigned int m_capacity {50}; using frustum_t = std::pair; /// Stores a circular list of frustum adaptors. boost::circular_buffer m_frustum_list {}; /// Uses to generate unique ID for each Ogre::Camera. std::size_t m_current_cam_index {0}; /// Contains the material. sight::viz::scene3d::material::standard::uptr m_material; static constexpr std::string_view TRANSFORM_INPUT = "transform"; sight::data::ptr m_camera {this, "camera"}; sight::data::ptr m_transform {this, TRANSFORM_INPUT}; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/grid.cpp000066400000000000000000000223671503402212300214230ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/grid.hpp" #include "module/viz/scene3d/adaptor/transform.hpp" #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { static const core::com::slots::key_t UPDATE_LENGTH_SLOT = "updateSize"; //----------------------------------------------------------------------------- /// Synchronizes the grid layer camera with the adaptor layer camera struct grid::camera_listener : public Ogre::Camera::Listener { Ogre::Camera* m_camera; //------------------------------------------------------------------------------ explicit camera_listener(Ogre::Camera* _camera) : m_camera(_camera) { } //------------------------------------------------------------------------------ void cameraPreRenderScene(Ogre::Camera* _grid_camera) final { _grid_camera->getParentSceneNode()->setPosition(m_camera->getParentSceneNode()->getPosition()); _grid_camera->getParentSceneNode()->setOrientation(m_camera->getParentSceneNode()->getOrientation()); } }; //----------------------------------------------------------------------------- grid::grid() noexcept { new_slot(UPDATE_LENGTH_SLOT, &grid::update_size, this); } //----------------------------------------------------------------------------- void grid::configuring() { this->configure_params(); const config_t config = this->get_config(); // parsing transform or create an "empty" one this->set_transform_id( config.get( sight::viz::scene3d::transformable::TRANSFORM_CONFIG, gen_id("transform") ) ); m_size = config.get(CONFIG + "size", m_size); const std::string color = config.get(CONFIG + "color", "#FFFFFF"); std::array rgba {}; data::tools::color::hexa_string_to_rgba(color, rgba); m_color.r = static_cast(rgba[0]) / 255.F; m_color.g = static_cast(rgba[1]) / 255.F; m_color.b = static_cast(rgba[2]) / 255.F; m_color.a = static_cast(rgba[3]) / 255.F; m_dash_length = config.get(CONFIG + "dashLength", m_dash_length); m_step = config.get(CONFIG + "step", m_step); m_elevation = config.get(CONFIG + "elevation", m_elevation); } //----------------------------------------------------------------------------- void grid::starting() { adaptor::init(); this->render_service()->make_current(); auto grid_layer = render_service()->layer(sight::viz::scene3d::render::render::layer::GRID); auto* scene_mgr = grid_layer->get_scene_manager(); m_line = scene_mgr->createManualObject(gen_id("grid")); // Set the line as dynamic, so we can update it later on, when the length changes m_line->setDynamic(true); // Set the material const auto mtl_name = gen_id("material"); m_material = std::make_unique(mtl_name); m_material->set_layout(data::mesh::attribute::point_colors); m_material->set_shading(data::material::shading_t::ambient, this->layer()->num_lights()); auto* camera = layer()->get_default_camera(); m_camera_listener = new camera_listener(camera); auto* grid_camera = grid_layer->get_default_camera(); grid_camera->addListener(m_camera_listener); // Draw the line this->draw_grid(false); this->attach_node(m_line); this->set_visible(visible()); } //----------------------------------------------------------------------------- void grid::updating() { if(visible()) { this->render_service()->make_current(); // Draw this->draw_grid(true); } this->update_done(); this->request_render(); } //----------------------------------------------------------------------------- void grid::stopping() { this->render_service()->make_current(); if(m_camera_listener != nullptr) { auto grid_layer = render_service()->layer(sight::viz::scene3d::render::render::layer::GRID); auto* grid_camera = grid_layer->get_default_camera(); grid_camera->removeListener(m_camera_listener); delete m_camera_listener; m_camera_listener = nullptr; } m_material.reset(); if(m_line != nullptr) { m_line->detachFromParent(); auto* scene_mgr = render_service()->layer(sight::viz::scene3d::render::render::layer::GRID)->get_scene_manager(); scene_mgr->destroyManualObject(m_line); m_line = nullptr; } adaptor::deinit(); } //----------------------------------------------------------------------------- void grid::attach_node(Ogre::MovableObject* _object) { auto* scene_mgr = render_service()->layer(sight::viz::scene3d::render::render::layer::GRID)->get_scene_manager(); Ogre::SceneNode* root_scene_node = scene_mgr->getRootSceneNode(); Ogre::SceneNode* trans_node = this->get_or_create_transform_node(root_scene_node); SIGHT_ASSERT("Transform node shouldn't be null", trans_node); if(const auto orientation = m_orientation.lock(); orientation) { trans_node->yaw(Ogre::Degree(Ogre::Real(orientation->value()[0]))); trans_node->pitch(Ogre::Degree(Ogre::Real(orientation->value()[1]))); trans_node->roll(Ogre::Degree(Ogre::Real(orientation->value()[2]))); } if(const auto position = m_position.lock(); position) { const auto pos = Ogre::Vector3( Ogre::Real(position->value()[0]), Ogre::Real(position->value()[1]), Ogre::Real(position->value()[2]) ); trans_node->setPosition(pos); } trans_node->setVisible(visible()); trans_node->attachObject(_object); } //----------------------------------------------------------------------------- void grid::draw_grid(bool _existing_line) { if(!_existing_line) { m_line->begin( m_material->name(), Ogre::RenderOperation::OT_LINE_LIST, sight::viz::scene3d::RESOURCE_GROUP ); } else { m_line->beginUpdate(0); } const auto half_size = m_size * 0.5F; const auto step_size = m_size / static_cast(m_step); float x = -half_size; for(std::size_t i = 0 ; i <= m_step ; ++i) { sight::viz::scene3d::helper::manual_object::draw_dashed_line( m_line, Ogre::Vector3(x, m_elevation, -half_size), Ogre::Vector3(x, m_elevation, half_size), m_dash_length, m_dash_length, m_color ); x += step_size; } float y = -half_size; for(std::size_t i = 0 ; i <= m_step ; ++i) { sight::viz::scene3d::helper::manual_object::draw_dashed_line( m_line, Ogre::Vector3(-half_size, m_elevation, y), Ogre::Vector3(half_size, m_elevation, y), m_dash_length, m_dash_length, m_color ); y += step_size; } m_line->end(); } //----------------------------------------------------------------------------- void grid::set_visible(bool /*_visible*/) { auto* scene_mgr = render_service()->layer(sight::viz::scene3d::render::render::layer::GRID)->get_scene_manager(); Ogre::SceneNode* root_scene_node = scene_mgr->getRootSceneNode(); Ogre::SceneNode* trans_node = this->get_or_create_transform_node(root_scene_node); if(const auto orientation = m_orientation.lock(); orientation) { trans_node->yaw(Ogre::Degree(Ogre::Real(orientation->value()[0]))); trans_node->pitch(Ogre::Degree(Ogre::Real(orientation->value()[1]))); trans_node->roll(Ogre::Degree(Ogre::Real(orientation->value()[2]))); } if(const auto position = m_position.lock(); position) { const auto pos = Ogre::Vector3( Ogre::Real(position->value()[0]), Ogre::Real(position->value()[1]), Ogre::Real(position->value()[2]) ); trans_node->setPosition(pos); } trans_node->setVisible(visible()); this->updating(); } //----------------------------------------------------------------------------- void grid::update_size(float _size) { m_size = _size; this->updating(); } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/grid.hpp000066400000000000000000000112761503402212300214250ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2023-2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor shows a grid that is intended to provide a horizon. * * @section Slots Slots * - \b update_visibility(bool): Sets whether the line is shown or not. * - \b toggle_visibility(): Toggle whether the line is shown or not. * - \b show(): shows the line. * - \b hide(): hides the line. * - \b updateSize(float): Update the size of the grid * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Configuration Configuration: * - \b transform (optional, string, default=""): the name of the Ogre transform node where to attach the mesh, as it * was specified in the transform adaptor * - \b size (optional, float, default=50.0): size of a side of the grid, in mm. * - \b dashLength (optional, float, default=2.5): length of a dash, in mm. * - \b color (optional, hexadecimal, default=#FFFFFFFF): color of the grid lines. * - \b visible (optional, bool, default=true): the visibility of the adaptor. * * * @subsection properties properties: * - \b position (optional, data::dvec3): if set, the grid will be move to this position (x, y, z in mm). * - \b orientation (optional, data::dvec3) : if set the grid will use this orientation (yaw, pitch, roll in degrees). */ class grid final : public sight::viz::scene3d::adaptor, public sight::viz::scene3d::transformable { public: SIGHT_DECLARE_SERVICE(grid, sight::viz::scene3d::adaptor); grid() noexcept; ~grid() noexcept override = default; protected: /// Configures the adaptor void configuring() override; /// Creates a mesh in the Default Ogre resource group void starting() override; /// Checks if the data::mesh has changed, and updates it void updating() override; /// Deletes the mesh after unregistering the service, and shutting down connections void stopping() override; /** * @brief Sets the line visibility. * @param _visible the visibility status of the line. */ void set_visible(bool _visible) override; private: /** * @brief Attaches a node in the scene graph. * @param _object node to attach. */ void attach_node(Ogre::MovableObject* _object); /** * @brief Draws the grid. * @param _existing_line use true if the line already exists. */ void draw_grid(bool _existing_line); /** * @brief SLOT: updates the size of the grid. * @param _size size of one side of the grid. */ void update_size(float _size); /// Contains the Ogre material. sight::viz::scene3d::material::standard::uptr m_material; /// Contains the manual object of the line. Ogre::ManualObject* m_line {nullptr}; /// Defines the size of a side of the grid, in mm. float m_size {50.F}; /// Number of lines in each direction (x,z) of the grid std::size_t m_step {10}; /// Elevation of the grid in the Y axis, in mm. float m_elevation {-500.F}; /// Defines the color of the line. Ogre::ColourValue m_color; /// Defines the length of one dash, in mm. float m_dash_length {2.5F}; struct camera_listener; camera_listener* m_camera_listener {nullptr}; sight::data::property m_position {this, "position", {0.0, 0.0, 0.0}}; sight::data::property m_orientation {this, "orientation", {0.0, 0.0, 0.0}}; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/light.cpp000066400000000000000000000300411503402212300215710ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/light.hpp" #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { static const core::com::slots::key_t SET_X_OFFSET_SLOT = "setXOffset"; static const core::com::slots::key_t SET_Y_OFFSET_SLOT = "setYOffset"; static const service::base::key_t DIFFUSE_COLOR_INOUT = "diffuseColor"; static const service::base::key_t SPECULAR_COLOR_INOUT = "specularColor"; SIGHT_REGISTER_SCENE3D_LIGHT( sight::module::viz::scene3d::adaptor::light, sight::viz::scene3d::light_adaptor::REGISTRY_KEY ) //------------------------------------------------------------------------------ light::light() noexcept { new_slot(SET_X_OFFSET_SLOT, &light::set_theta_offset, this); new_slot(SET_Y_OFFSET_SLOT, &light::set_phi_offset, this); } //------------------------------------------------------------------------------ void light::configuring() { this->configure_params(); const config_t config = this->get_config(); this->set_transform_id( config.get( sight::viz::scene3d::transformable::TRANSFORM_CONFIG, gen_id("transform") ) ); static const std::string s_NAME_CONFIG = CONFIG + "name"; static const std::string s_SWITCHED_ON_CONFIG = CONFIG + "switchedOn"; static const std::string s_THETA_OFFSET_CONFIG = CONFIG + "thetaOffset"; static const std::string s_PHI_OFFSET_CONFIG = CONFIG + "phiOffset"; m_light_name = config.get(s_NAME_CONFIG); m_switched_on = config.get(s_SWITCHED_ON_CONFIG, m_switched_on); m_theta_offset = config.get(s_THETA_OFFSET_CONFIG, m_theta_offset); m_phi_offset = config.get(s_PHI_OFFSET_CONFIG, m_phi_offset); } //------------------------------------------------------------------------------ void light::starting() { adaptor::init(); this->render_service()->make_current(); Ogre::SceneManager* const scene_mgr = this->get_scene_manager(); m_light = scene_mgr->createLight(gen_id("" + m_light_name)); // Sets the default light direction to the camera's view direction, m_light->setType(m_light_type); m_light->setVisible(m_switched_on); Ogre::SceneNode* root_scene_node = this->get_scene_manager()->getRootSceneNode(); Ogre::SceneNode* trans_node = this->get_or_create_transform_node(root_scene_node); m_light_node = trans_node->createChildSceneNode(gen_id("light")); m_light_node->attachObject(m_light); if(m_theta_offset != 0.F || m_phi_offset != 0.F) { this->set_theta_offset(m_theta_offset); this->set_phi_offset(m_phi_offset); } if(m_light_name != sight::viz::scene3d::layer::DEFAULT_LIGHT_NAME) { // Creates the visual feedback m_material = std::make_unique(gen_id("material")); m_material->set_layout(data::mesh::attribute::point_normals | data::mesh::attribute::point_colors); m_material->set_shading(sight::data::material::shading_t::phong, this->layer()->num_lights()); // Size, these value allow to display light with good enough ratio. const float origin_radius = m_length * 0.1F; const float cylinder_length = m_length - m_length / 10; const float cylinder_radius = m_length / 80; const float cone_length = m_length - cylinder_length; const float cone_radius = cylinder_radius * 2; const unsigned sample = 64; // Creates the commun sphere position m_light_position = scene_mgr->createManualObject(gen_id("origin")); sight::viz::scene3d::helper::manual_object::create_sphere( m_light_position, m_material->name(), Ogre::ColourValue(0.98F, 0.96F, 0.62F, 1.0F), origin_radius, sample ); m_light_position->setVisible(m_visual_feedback); m_light_node->attachObject(m_light_position); // Create the directional light feedback m_directional_feedback.first = scene_mgr->createManualObject(gen_id("line")); m_directional_feedback.second = scene_mgr->createManualObject(gen_id("cone")); sight::viz::scene3d::helper::manual_object::create_cylinder( m_directional_feedback.first, m_material->name(), Ogre::ColourValue(0.F, 0.F, 1.F, 1.0F), cylinder_radius, cylinder_length, sample ); Ogre::SceneNode* line_node = m_light_node->createChildSceneNode(gen_id("lineNode")); line_node->attachObject(m_directional_feedback.first); line_node->yaw(Ogre::Degree(-90)); sight::viz::scene3d::helper::manual_object::create_cone( m_directional_feedback.second, m_material->name(), Ogre::ColourValue(0.F, 0.F, 1.F, 1.0F), cone_radius, cone_length, sample ); Ogre::SceneNode* cone_node = m_light_node->createChildSceneNode(gen_id("coneNode")); cone_node->attachObject(m_directional_feedback.second); cone_node->translate(0.F, 0.F, cylinder_length); cone_node->yaw(Ogre::Degree(-90)); m_directional_feedback.first->setVisible(m_visual_feedback && m_light_type == Ogre::Light::LT_DIRECTIONAL); m_directional_feedback.second->setVisible(m_visual_feedback && m_light_type == Ogre::Light::LT_DIRECTIONAL); } updating(); } //----------------------------------------------------------------------------- service::connections_t light::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(DIFFUSE_COLOR_INOUT, data::color::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(SPECULAR_COLOR_INOUT, data::color::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); return connections; } //------------------------------------------------------------------------------ void light::updating() { const auto light_diffuse_color = m_diffuse.lock(); const auto light_specular_color = m_specular.lock(); this->render_service()->make_current(); const Ogre::ColourValue diffuse_color(light_diffuse_color->red(), light_diffuse_color->green(), light_diffuse_color->blue(), light_diffuse_color->alpha()); const Ogre::ColourValue specular_color(light_specular_color->red(), light_specular_color->green(), light_specular_color->blue(), light_specular_color->alpha()); m_light->setDiffuseColour(diffuse_color); m_light->setSpecularColour(specular_color); m_light->setType(m_light_type); this->update_done(); this->request_render(); } //------------------------------------------------------------------------------ void light::stopping() { this->render_service()->make_current(); this->unregister_services(); Ogre::SceneManager* const scene_mgr = this->get_scene_manager(); if(m_light_name != sight::viz::scene3d::layer::DEFAULT_LIGHT_NAME) { m_light_node->removeAndDestroyAllChildren(); scene_mgr->destroyManualObject(m_light_position); scene_mgr->destroyManualObject(m_directional_feedback.first); scene_mgr->destroyManualObject(m_directional_feedback.second); m_light_position = nullptr; m_directional_feedback.first = nullptr; m_directional_feedback.second = nullptr; m_material.reset(); } scene_mgr->destroyLight(m_light); scene_mgr->destroySceneNode(m_light_node); m_light = nullptr; m_light_node = nullptr; adaptor::deinit(); } //------------------------------------------------------------------------------ void light::set_diffuse_color(Ogre::ColourValue _diffuse_color) { const auto light_diffuse_color = m_diffuse.lock(); light_diffuse_color->set_rgba(_diffuse_color.r, _diffuse_color.g, _diffuse_color.b, _diffuse_color.a); m_light->setDiffuseColour(_diffuse_color); this->request_render(); } //------------------------------------------------------------------------------ void light::set_specular_color(Ogre::ColourValue _specular_color) { const auto light_specular_color = m_specular.lock(); light_specular_color->set_rgba(_specular_color.r, _specular_color.g, _specular_color.b, _specular_color.a); m_light->setSpecularColour(_specular_color); this->request_render(); } //------------------------------------------------------------------------------ void light::switch_on(bool _on) { m_switched_on = _on; if(m_light != nullptr) { m_light->setVisible(m_switched_on); this->request_render(); } } //------------------------------------------------------------------------------ void light::set_theta_offset(float _theta_offset) { this->render_service()->make_current(); const float theta_delta = _theta_offset - m_theta_offset; m_theta_offset = _theta_offset; Ogre::Radian theta_offset_rad_delta(Ogre::Degree(static_cast(theta_delta))); Ogre::Vector3 y_axis = m_light_node->getOrientation().yAxis(); m_light_node->rotate(y_axis, theta_offset_rad_delta, Ogre::Node::TS_WORLD); this->request_render(); } //------------------------------------------------------------------------------ void light::set_phi_offset(float _phi_offset) { this->render_service()->make_current(); const float phi_delta = _phi_offset - m_phi_offset; m_phi_offset = _phi_offset; Ogre::Radian phi_offset_rad_delta(Ogre::Degree(static_cast(phi_delta))); Ogre::Vector3 x_axis = m_light_node->getOrientation().xAxis(); m_light_node->rotate(x_axis, phi_offset_rad_delta, Ogre::Node::TS_WORLD); this->request_render(); } //------------------------------------------------------------------------------ void light::enable_visual_feedback(bool _enable) { m_visual_feedback = _enable; if(m_light_position != nullptr) { m_light_position->setVisible(m_visual_feedback); m_directional_feedback.first->setVisible(m_visual_feedback && m_light_type == Ogre::Light::LT_DIRECTIONAL); m_directional_feedback.second->setVisible(m_visual_feedback && m_light_type == Ogre::Light::LT_DIRECTIONAL); } } //------------------------------------------------------------------------------ void light::set_type(Ogre::Light::LightTypes _type) { m_light_type = _type; if(m_directional_feedback.first != nullptr) { m_directional_feedback.first->setVisible(m_visual_feedback && m_light_type == Ogre::Light::LT_DIRECTIONAL); m_directional_feedback.second->setVisible(m_visual_feedback && m_light_type == Ogre::Light::LT_DIRECTIONAL); } } //------------------------------------------------------------------------------ } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/light.hpp000066400000000000000000000224211503402212300216010ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2025 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace Ogre { class Light; } // namespace Ogre namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptors adds a light to the scene manager. * * @section Slots Slots * - \b setThetaOffset(float): called when the theta offset is changed and moves the light accordingly. * - \b setPhiOffset(float): called when the phi offset is changed and moves the light accordingly. * * @section XML XML Configuration * @code{.xml} * @endcode * * @subsection In-Out In-Out * - \b diffuseColor [sight::data::color]: diffuse color of the light. * - \b specularColor [sight::data::color]: specular color of the light. * * @subsection Configuration Configuration: * - \b name (mandatory, string): defines a name for the associated Ogre light. * - \b transform (optional, string, default=""): transform applied to the frustum's scene node. * - \b switchedOn (optional, bool, default=true): defines if the light is activated or not. * - \b thetaOffset (optional, float, default=0.0): angle in degrees defining the rotation of the light around x axis. * - \b phiOffset (optional, float, default=0.0): angle in degrees defining the rotation of the light around y axis. */ class light final : public sight::viz::scene3d::light_adaptor { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(light, sight::viz::scene3d::light_adaptor); /// Creates the service. light() noexcept; /// Destroys the adaptor. ~light() noexcept final = default; /** * @brief Gets the name of the light. * @return The name of the light. */ const std::string& get_name() const final; /** * @brief Sets the name of the light. * @param _name the new light name. */ void set_name(const std::string& _name) final; /** * @brief Gets the type of the light. * @return The light type. */ Ogre::Light::LightTypes type() const final; /** * @brief Sets the type of the light. * @param _type the new light type. */ void set_type(Ogre::Light::LightTypes _type) final; /** * @brief Gets the diffuse color of the light. * @return The light diffuse color. */ Ogre::ColourValue get_diffuse_color() const final; /** * @brief Sets the diffuse color of the light. * @param _diffuse_color the new light diffuse color. */ void set_diffuse_color(Ogre::ColourValue _diffuse_color) final; /** * @brief Gets the specular color of the light. * @return The light specular color. */ Ogre::ColourValue get_specular_color() const final; /** * @brief Sets the specular color of the light. * @param _specular_color the new light specular color. */ void set_specular_color(Ogre::ColourValue _specular_color) final; /** * @brief Gets the light activation state. * @return The light activation state. */ bool is_switched_on() const final; /** * @brief Sets the light activation state. * @param _on the light new activation state. */ void switch_on(bool _on) final; /** * @brief Gets the angle in degrees defining the rotation of the light around x axis. * @return The theta offset of the light. */ float get_theta_offset() const final; /** * @brief Sets the angle in degrees defining the rotation of the light around x axis. * @param _theta_offset the value of the theta offset. * @pre The type of the light must be Ogre::Light::LT_DIRECTIONAL to used this value. */ void set_theta_offset(float _theta_offset) final; /** * @brief Gets the angle in degrees defining the rotation of the light around y axis. * @return The phi of the light. */ float get_phi_offset() const final; /** * @brief Sets the angle in degrees defining the rotation of the light around y axis. * @param _phi_offset the phi of the theta offset. * @pre The type of the light must be Ogre::Light::LT_DIRECTIONAL to used this value. */ void set_phi_offset(float _phi_offset) final; /** * @brief Enables the light visual feedback. * @param _enable the visual feedback visibility state. */ void enable_visual_feedback(bool _enable) final; /** * @brief Indicates if the visual feedback is enabled. * @return True if the visual feedback is activated. */ bool is_visual_feedback_on() const final; protected: /// Configures the service. void configuring() final; /// Adds a new light to the scene manager. void starting() final; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::color::MODIFIED_SIG of s_DIFFUSE_COLOR_INOUT to service::slots::UPDATE * Connect data::color::MODIFIED_SIG of s_DIFFUSE_COLOR_INOUT to service::slots::UPDATE */ service::connections_t auto_connections() const final; /// Updates the light position and orientation. void updating() final; /// Removes the light from the scene manager. void stopping() final; private: /// Contains the Ogre light managed by this adaptor. Ogre::Light* m_light {nullptr}; /// Defines the name of the associated Ogre light. std::string m_light_name; /// Sets the type of the associated Ogre light. Ogre::Light::LightTypes m_light_type {Ogre::Light::LT_DIRECTIONAL}; /// Enables the light. bool m_switched_on {true}; /// Defines the angle in degrees defining the rotation of the light around x axis. float m_theta_offset {0.F}; /// Defines the angle in degrees defining the rotation of the light around y axis. float m_phi_offset {0.F}; /// Contains the node used to attach the light Ogre::SceneNode* m_light_node {nullptr}; /// Defines the visual feedback visibility state. bool m_visual_feedback {false}; /// Contains the material used for the feedback visualization. sight::viz::scene3d::material::standard::uptr m_material; /// Contains the sphere at the light position. Ogre::ManualObject* m_light_position {nullptr}; /// Defines the length of the visual feedback object. float m_length {50.F}; /// Contains objects used for the directional light visual feedback. std::pair m_directional_feedback {nullptr, nullptr}; }; //------------------------------------------------------------------------------ inline const std::string& light::get_name() const { return m_light_name; } //------------------------------------------------------------------------------ inline void light::set_name(const std::string& _name) { m_light_name = _name; } //------------------------------------------------------------------------------ inline Ogre::Light::LightTypes light::type() const { return m_light_type; } //------------------------------------------------------------------------------ inline Ogre::ColourValue light::get_diffuse_color() const { return m_light->getDiffuseColour(); } //------------------------------------------------------------------------------ inline Ogre::ColourValue light::get_specular_color() const { return m_light->getSpecularColour(); } //------------------------------------------------------------------------------ inline bool light::is_switched_on() const { return m_switched_on; } //------------------------------------------------------------------------------ inline float light::get_theta_offset() const { return m_theta_offset; } //------------------------------------------------------------------------------ inline float light::get_phi_offset() const { return m_phi_offset; } //------------------------------------------------------------------------------ inline bool light::is_visual_feedback_on() const { return m_visual_feedback; } //------------------------------------------------------------------------------ } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/line.cpp000066400000000000000000000140141503402212300214130ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2017-2025 IRCAD France * Copyright (C) 2017-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/line.hpp" #include "module/viz/scene3d/adaptor/transform.hpp" #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { //----------------------------------------------------------------------------- service::connections_t line::auto_connections() const { return { {m_length, data::object::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE}, {m_color, data::object::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE}, {m_dash_length, data::object::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE}, {m_dashed, data::object::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE} }; } //----------------------------------------------------------------------------- void line::configuring() { this->configure_params(); const config_t config = this->get_config(); // parsing transform or create an "empty" one this->set_transform_id( config.get( sight::viz::scene3d::transformable::TRANSFORM_CONFIG, gen_id("transform") ) ); } //----------------------------------------------------------------------------- void line::starting() { adaptor::init(); this->render_service()->make_current(); Ogre::SceneManager* scene_mgr = this->get_scene_manager(); m_line = scene_mgr->createManualObject(gen_id("line")); // Set the line as dynamic, so we can update it later on, when the length changes m_line->setDynamic(true); // Set the material m_material = std::make_unique(gen_id("material")); m_material->set_layout(sight::data::mesh::attribute::point_colors); m_material->set_shading(sight::data::material::shading_t::ambient, this->layer()->num_lights()); // Draw the line this->draw_line(false); // Set the bounding box of your Manual Object Ogre::Vector3 bb_min(-0.1F, -0.1F, 0.F); Ogre::Vector3 bb_max(0.1F, 0.1F, static_cast(*m_length)); Ogre::AxisAlignedBox box(bb_min, bb_max); m_line->setBoundingBox(box); this->attach_node(m_line); this->set_visible(visible()); } //----------------------------------------------------------------------------- void line::updating() { if(visible()) { this->render_service()->make_current(); // Draw this->draw_line(true); // Set the bounding box of your Manual Object Ogre::Vector3 bb_min(-0.1F, -0.1F, 0.F); Ogre::Vector3 bb_max(0.1F, 0.1F, static_cast(*m_length)); Ogre::AxisAlignedBox box(bb_min, bb_max); m_line->setBoundingBox(box); } this->update_done(); this->request_render(); } //----------------------------------------------------------------------------- void line::stopping() { this->render_service()->make_current(); m_material.reset(); if(m_line != nullptr) { m_line->detachFromParent(); this->get_scene_manager()->destroyManualObject(m_line); m_line = nullptr; } adaptor::deinit(); } //----------------------------------------------------------------------------- void line::attach_node(Ogre::MovableObject* _object) { Ogre::SceneNode* root_scene_node = this->get_scene_manager()->getRootSceneNode(); Ogre::SceneNode* trans_node = this->get_or_create_transform_node(root_scene_node); SIGHT_ASSERT("Transform node shouldn't be null", trans_node); trans_node->setVisible(visible()); trans_node->attachObject(_object); } //----------------------------------------------------------------------------- void line::draw_line(bool _existing_line) { if(!_existing_line) { m_line->begin( m_material->name(), Ogre::RenderOperation::OT_LINE_LIST, sight::viz::scene3d::RESOURCE_GROUP ); } else { m_line->beginUpdate(0); } const auto color = *m_color; Ogre::ColourValue ogre_color(color[0], color[1], color[2], color[3]); m_line->colour(ogre_color); const auto length = static_cast(*m_length); const auto dash_length = static_cast(*m_dash_length); if(*m_dashed) { float f = 0.F; for(std::size_t i = 0 ; i <= static_cast(length / (dash_length * 2)) ; i++) { m_line->position(0, 0, f); m_line->position(0, 0, f + static_cast(dash_length)); f += dash_length * 2; } } else { m_line->position(0, 0, 0); m_line->position(0, 0, length); } m_line->end(); } //----------------------------------------------------------------------------- void line::set_visible(bool /*_visible*/) { Ogre::SceneNode* root_scene_node = this->get_scene_manager()->getRootSceneNode(); Ogre::SceneNode* trans_node = this->get_or_create_transform_node(root_scene_node); trans_node->setVisible(visible()); this->updating(); } } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/line.hpp000066400000000000000000000110311503402212300214140ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2018-2025 IRCAD France * Copyright (C) 2018-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include namespace sight::data { class Material; } // namespace sight::data namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor shows a simple line. * * @section Slots Slots * - \b update_visibility(bool): Sets whether the line is shown or not. * - \b toggle_visibility(): Toggle whether the line is shown or not. * - \b show(): shows the line. * - \b hide(): hides the line. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Configuration Configuration: * - \b transform (optional, string, default=""): the name of the Ogre transform node where to attach the mesh, as it * was specified in the transform adaptor * @subsection Properties Properties: * - \b length (optional, float, default=50.0): length of the line in mm (default 50) * - \b dash_length (optional, float, default=2.5): length of a dash * - \b color (optional, hexadecimal, default=#FFFFFF): color of the line * - \b dashed (optional, bool, default=false): display a dashed line instead of a solid line * - \b visible (optional, bool, default=true): the visibility of the adaptor. */ class line final : public sight::viz::scene3d::adaptor, public sight::viz::scene3d::transformable { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(line, sight::viz::scene3d::adaptor); /// Sets default parameters and initializes necessary members. line() noexcept = default; /// Destroys the adaptor. ~line() noexcept override = default; protected: /// Connects the input matrix modified to the update slot. sight::service::connections_t auto_connections() const final; /// Configures the adaptor void configuring() override; /// Creates a mesh in the Default Ogre resource group void starting() override; /// Checks if the data::mesh has changed, and updates it if it has. void updating() override; /// Deletes the mesh after unregistering the service, and shutting connections. void stopping() override; /** * @brief Sets the line visibility. * @param _visible the visibility status of the line. */ void set_visible(bool _visible) override; private: /** * @brief Attaches a node in the scene graph. * @param _object node to attach. */ void attach_node(Ogre::MovableObject* _object); /** * @brief Draws a line. * @param _existing_line use true if the line already exist. */ void draw_line(bool _existing_line); /** * @brief SLOT: updates length of the line. * @param _length length of the line (in mm). */ void update_length(float _length); /// Contains the material. sight::viz::scene3d::material::standard::uptr m_material; /// Contains the manual object of the line. Ogre::ManualObject* m_line {nullptr}; sight::data::property m_length {this, "length", 50.0}; sight::data::property m_color {this, "color", {1.0, 1.0, 1.0, 1.0}}; sight::data::property m_dashed {this, "dashed", false}; sight::data::property m_dash_length {this, "dash_length", 2.5}; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/material.cpp000066400000000000000000000521251503402212300222670ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/material.hpp" #include "module/viz/scene3d/adaptor/shader_parameter.hpp" #include "module/viz/scene3d/adaptor/texture.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { static const std::string S_SHADER_PARAM_FIELD = "shader_param"; //------------------------------------------------------------------------------ material::material() noexcept { new_signal(signals::CHANGED); new_slot(slots::UPDATE_FIELD, &material::update_field, this); new_slot(slots::SWAP_TEXTURE, &material::swap_texture, this); new_slot(slots::ADD_TEXTURE, &material::create_texture_adaptor, this); new_slot(slots::REMOVE_TEXTURE, &material::remove_texture_adaptor, this); } //------------------------------------------------------------------------------ void material::configuring(const config_t& _config) { this->configure_params(); m_material_template_name = _config.get(CONFIG + "material_template", m_material_template_name); m_material_name = _config.get(CONFIG + "material_name", this->get_id()); m_shading_mode = core::ptree::get_and_deprecate( _config, CONFIG + "shading", CONFIG + "shadingMode", "26.0", m_shading_mode ); m_texture_name = core::ptree::get_and_deprecate( _config, CONFIG + "texture_name", CONFIG + "textureName", "26.0", m_texture_name ); const auto representation_mode = core::ptree::get_and_deprecate( _config, CONFIG + "representation", CONFIG + "representationMode", "26.0", "SURFACE" ); // Make sure the representation is properly defined m_representation_mode = sight::data::material::string_to_representation_mode(representation_mode); } //------------------------------------------------------------------------------ void material::configure( const std::string& _id, const std::string& _name, sight::viz::scene3d::render::sptr _service, const std::string& _layer, const std::string& _shading_mode, const std::string& _template ) { this->base::configure(service::config_t()); this->set_id(_id); this->set_material_name(_name); this->set_render_service(_service); this->set_layer_id(_layer); this->set_shading_mode(_shading_mode); this->set_material_template_name(_template); } //------------------------------------------------------------------------------ void material::starting() { adaptor::init(); { auto material = m_material_data.lock(); if(not material) { m_internal_material = std::make_shared(); this->set_inout(m_internal_material, m_material_data.key()); material = m_material_data.lock(); } if(!m_shading_mode.empty()) { data::material::shading_t shading_mode = data::material::shading_t::phong; if(m_shading_mode == "ambient") { shading_mode = data::material::shading_t::ambient; } else if(m_shading_mode == "flat") { shading_mode = data::material::shading_t::flat; } // Force the shading mode of the material if it has been set in the configuration of the adaptor material->set_shading_mode(shading_mode); } material->set_representation_mode(m_representation_mode); if(const auto material_field = material->get_field("material", nullptr); material_field != nullptr) { data::string::csptr string = std::dynamic_pointer_cast(material_field); SIGHT_ASSERT("Material name field must be a sight::data::string", string); m_material_template_name = string->get_value(); } else { data::string::sptr string = std::make_shared(); string->set_value(m_material_template_name); data::helper::field helper(material.get_shared()); helper.set_field("material", string); helper.notify(); } if(m_material_template_name == sight::viz::scene3d::material::standard::TEMPLATE) { m_standard_material_impl = std::make_unique(m_material_name); } else { m_material_impl = std::make_unique( m_material_name, m_material_template_name ); } } this->create_shader_parameter_adaptors(); // A texture adaptor is configured in the XML scene, we can retrieve it if(!m_texture_name.empty()) { if(!m_tex_adaptor) { this->set_texture_name(m_texture_name); } if(m_tex_adaptor->get_texture_name().empty()) { m_tex_adaptor->set_render_service(this->render_service()); m_tex_adaptor->set_layer_id(m_layer_id); } m_texture_connection.connect( m_tex_adaptor, module::viz::scene3d::adaptor::texture::TEXTURE_SWAPPED_SIG, this->get_sptr(), slots::SWAP_TEXTURE ); if(m_tex_adaptor->started()) { this->swap_texture(); } } else { this->create_texture_adaptor(); } const auto config_tree = this->get_config(); if(config_tree.find("config") != config_tree.not_found()) { this->updating(); } } //------------------------------------------------------------------------------ service::connections_t material::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(m_material_data.key(), data::material::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(m_material_data.key(), data::material::ADDED_FIELDS_SIG, slots::UPDATE_FIELD); connections.push(m_material_data.key(), data::material::CHANGED_FIELDS_SIG, slots::UPDATE_FIELD); connections.push(m_material_data.key(), data::material::ADDED_TEXTURE_SIG, slots::ADD_TEXTURE); connections.push(m_material_data.key(), data::material::REMOVED_TEXTURE_SIG, slots::REMOVE_TEXTURE); return connections; } //------------------------------------------------------------------------------ void material::updating() { const auto material = m_material_data.lock(); if(m_standard_material_impl) { // Set up representation mode m_standard_material_impl->set_polygon_mode(material->get_representation_mode()); // Set ambient and diffuse, we will require them to be set for the "selected pass" in update_options_mode() m_standard_material_impl->set_ambient_diffuse(material.get_shared()); // Sets the permutation names to select vp and fp, again needed in update_options_mode() m_standard_material_impl->set_shading( material->get_shading_mode(), this->layer()->num_lights(), this->has_diffuse_texture(), m_tex_adaptor ? m_tex_adaptor->get_use_alpha() : false ); // This may copy techniques passes, thus everything should be set before m_standard_material_impl->update_options_mode(material->get_options_mode()); } else { // Set up representation mode m_material_impl->set_polygon_mode(material->get_representation_mode()); m_material_impl->set_ambient_diffuse(material.get_shared()); } this->update_done(); this->request_render(); } //------------------------------------------------------------------------------ void material::stopping() { m_material_impl.reset(); m_texture_connection.disconnect(); this->unregister_services(); if(const auto material = m_material_data.lock(); material->get_field(S_SHADER_PARAM_FIELD)) { material->remove_field(S_SHADER_PARAM_FIELD); } m_internal_material.reset(); adaptor::deinit(); } //------------------------------------------------------------------------------ void material::create_shader_parameter_adaptors() { auto material = this->get_material(); SIGHT_ASSERT("Material '" + m_material_template_name + "'' not found", material); std::map uniform_values; const auto material_data = m_material_data.lock(); if(auto uniform_field = material_data->get_field("uniforms", nullptr); uniform_field != nullptr) { const auto uniform_field_str = std::dynamic_pointer_cast(uniform_field); SIGHT_ASSERT("Uniform field must be a sight::data::string", uniform_field_str); std::vector uniforms; boost::split(uniforms, uniform_field_str->value(), boost::is_any_of("|")); for(const auto& uniform : uniforms) { std::vector key_values; boost::split(key_values, uniform, boost::is_any_of("=")); SIGHT_ASSERT("Uniforms value should be passed as key=value1;value2|key=value3", key_values.size() == 2); uniform_values[key_values[0]] = key_values[1]; } } const auto constants = sight::viz::scene3d::helper::shading::find_material_constants(*material); for(const auto& constant : constants) { const std::string& constant_name = std::get<0>(constant); const auto& constant_type = std::get<1>(constant); const auto& constant_value = std::get<3>(constant); sight::data::object::sptr obj; const config_t config = this->get_config(); if(const auto inouts_cfg = config.get_child_optional("inout"); inouts_cfg.has_value()) { const auto group = inouts_cfg->get(".group", ""); if(group == "uniforms") { std::size_t i = 0; for(const auto& it_cfg : boost::make_iterator_range(inouts_cfg->equal_range("key"))) { const auto name = it_cfg.second.get(".name"); SIGHT_ASSERT("Missing 'name' tag.", !name.empty()); if(name == constant_name) { obj = m_uniforms[i].lock().get_shared(); } ++i; } } } // Look first in the shader parameter map of the material in case another adaptor created it if(obj == nullptr) { if(auto field = material_data->get_field(S_SHADER_PARAM_FIELD)) { if(auto it = field->find(constant_name); it != field->end()) { obj = it->second; } } } if(obj == nullptr) { obj = sight::viz::scene3d::helper::shading::create_object_from_shader_parameter( constant_type, constant_value ); obj->set_id(core::id::join(this->get_id(), constant_name)); // Add the object to the shaderParameter map of the Material to keep the object alive data::map::sptr map = material_data->set_default_field(S_SHADER_PARAM_FIELD, std::make_shared()); (*map)[constant_name] = obj; } if(const auto uniform_value = uniform_values.find(constant_name); uniform_value != uniform_values.end()) { const auto uniform_str = std::dynamic_pointer_cast(obj); SIGHT_ASSERT("Uniform data must be a sight::data::string", uniform_str); uniform_str->from_string(uniform_value->second); } const auto shader_type = std::get<2>(constant); const std::string shader_type_str = shader_type == Ogre::GPT_VERTEX_PROGRAM ? "vertex" : shader_type == Ogre::GPT_FRAGMENT_PROGRAM ? "fragment" : "geometry"; const core::id::type id = core::id::join(this->get_id(), shader_type_str, constant_name); // Creates an Ogre adaptor and associates it with the Sight object auto srv = this->register_service( "sight::module::viz::scene3d::adaptor::shader_parameter", id ); srv->set_inout(obj, "parameter", true); // Naming convention for shader parameters srv->set_render_service(this->render_service()); service::config_t srv_config; srv_config.add("config..parameter", constant_name); srv_config.add("config..shader_type", shader_type_str); srv_config.add("config..material_name", m_material_name); srv->set_layer_id(m_layer_id); srv->set_config(srv_config); srv->configure(); srv->start(); } } //------------------------------------------------------------------------------ void material::set_texture_name(const std::string& _texture_name) { if(_texture_name.empty()) { m_tex_adaptor = nullptr; } else { auto texture_adaptors = this->render_service()->get_adaptors(); auto result = std::find_if( texture_adaptors.begin(), texture_adaptors.end(), [_texture_name](const module::viz::scene3d::adaptor::texture::sptr& _srv) { return _srv->get_texture_name() == _texture_name; }); SIGHT_ASSERT( "texture adaptor managing texture '" + _texture_name + "' is not found", result != texture_adaptors.end() ); m_tex_adaptor = *result; } m_texture_name = _texture_name; } //------------------------------------------------------------------------------ void material::update_field(data::fields_container_t _fields) { for(const auto& elt : _fields) { if(elt.first == "material") { this->unregister_services("sight::module::viz::scene3d::adaptor::shader_parameter"); { const auto material = m_material_data.lock(); data::string::csptr string = std::dynamic_pointer_cast(elt.second); if(string->value() == m_material_template_name) { // Avoid useless update if this is the same template material continue; } this->set_material_template_name(string->get_value()); static const int s_I = 0; m_material_name = m_material_name + std::to_string(s_I); if(m_material_template_name == sight::viz::scene3d::material::standard::TEMPLATE) { m_material_impl.reset(); m_standard_material_impl = std::make_unique(m_material_name); this->emit(signals::CHANGED, m_standard_material_impl->material()); } else { m_standard_material_impl.reset(); m_material_impl = std::make_unique( m_material_name, m_material_template_name ); this->emit(signals::CHANGED, m_material_impl->material()); } if(material->get_field(S_SHADER_PARAM_FIELD)) { material->remove_field(S_SHADER_PARAM_FIELD); } } this->create_shader_parameter_adaptors(); this->updating(); if(m_tex_adaptor) { if(m_standard_material_impl) { // When resetting the material template, all techniques and passes will be destroyed, // so we need to reset the texture unit states if(Ogre::TexturePtr current_texture = m_tex_adaptor->get_texture()) { m_material_impl->set_texture(sight::viz::scene3d::material::standard::TEXTURE, current_texture); } } else { SIGHT_ERROR("Texture not supported for other materials than the standard one.") } } } } } //------------------------------------------------------------------------------ void material::swap_texture() { if(m_standard_material_impl) { SIGHT_ASSERT("Missing texture adaptor", m_tex_adaptor); Ogre::TexturePtr current_texture = m_tex_adaptor->get_texture(); SIGHT_ASSERT("texture not set in texture adaptor", current_texture); // Update the shaders const auto material = m_material_data.lock(); m_standard_material_impl->set_shading( material->get_shading_mode(), this->layer()->num_lights(), this->has_diffuse_texture(), m_tex_adaptor->get_use_alpha() ); m_standard_material_impl->set_texture(sight::viz::scene3d::material::standard::TEXTURE, current_texture); this->request_render(); } else { SIGHT_ERROR("Texture not supported for other materials than the standard one.") } } //------------------------------------------------------------------------------ void material::create_texture_adaptor() { SIGHT_ASSERT("texture adaptor already configured in XML", m_texture_name.empty()); const auto material = m_material_data.lock(); // If the associated material has a texture, we have to create a texture adaptor to handle it if(material->get_diffuse_texture()) { // Creates an Ogre adaptor and associates it with the Sight texture object auto texture = material->get_diffuse_texture(); m_tex_adaptor = this->register_service( "sight::module::viz::scene3d::adaptor::texture" ); m_tex_adaptor->set_input(texture, "image", true); m_tex_adaptor->set_id(gen_id(m_tex_adaptor->get_id())); m_tex_adaptor->set_render_service(this->render_service()); m_tex_adaptor->set_layer_id(m_layer_id); const std::string material_name = material->get_id(); m_tex_adaptor->set_texture_name(material_name + "_Texture"); m_texture_connection.connect( m_tex_adaptor, module::viz::scene3d::adaptor::texture::TEXTURE_SWAPPED_SIG, this->get_sptr(), module::viz::scene3d::adaptor::material::slots::SWAP_TEXTURE ); m_tex_adaptor->configure(); m_tex_adaptor->start(); } } //------------------------------------------------------------------------------ void material::remove_texture_adaptor() { SIGHT_ASSERT("Missing texture adaptor", m_tex_adaptor); SIGHT_ASSERT("texture adaptor already configured in XML", m_texture_name.empty()); if(m_standard_material_impl) { this->render_service()->make_current(); m_texture_connection.disconnect(); this->unregister_services("sight::module::viz::scene3d::adaptor::texture"); m_tex_adaptor.reset(); // Update the shaders const auto material = m_material_data.lock(); m_standard_material_impl->set_shading( material->get_shading_mode(), this->layer()->num_lights(), this->has_diffuse_texture(), false ); this->request_render(); } else { SIGHT_ERROR("Texture not supported for other materials than the standard one.") } } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/material.hpp000066400000000000000000000274611503402212300223010ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "module/viz/scene3d/adaptor/texture.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::data { class Material; } // namespace sight::data namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor adapts a data::material, allowing to tweak material parameters and shader uniforms. * Both the material and the uniform can be specified as XML configuration parameters as described below. * They can also be passed directly from the material data, with two specific data fields: * - material: string containing the Ogre material name * - uniforms: "|"" separated list of uniform keys/values, where values can be interpreted as a * sight::data::string_serializable i.e. "u_float=1.0|u_vec3=1.0;5.0;6.0". * * @section Slots Slots * - \b update_field(data::fields_container_t): listen to the fields in the data::material. * - \b swap_texture(): listen to the module::viz::scene3d::adaptor::texture changes. * - \b add_texture(): called when a texture is added in the data::material. * - \b remove_texture(): called when a texture is removed in the data::material. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection In-Out In-Out * - \b material [sight::data::material]: adapted material. The material may be modified to comply to the configuration * of the adaptor. * - \b uniforms: list of data to bind to material uniforms. * * @subsection Configuration Configuration: * - \b material_template (optional, string, default=""): name of the base Ogre material/ * - \b material_name (optional, string, default=""): name of the Ogre material. This is necessary to bind a * sight::module::viz::scene3d:mesh or a sight::module::viz::scene3d:model_series to this material; * simply specify the same Ogre material in its configuration. * - \b texture_name (optional, string, default=""): the Ogre texture name used the material. Use it if you want to * reference a texture managed by an another module::viz::scene3d::adaptor::texture. * - \b shading (optional, none/flat/phong, default=phong): name of the used shading mode. * - \b normalLength (optional, default=0.1): factor defining the length of the normals. * - \b representation (optional, SURFACE/POINT/WIREFRAME/EDGE, default=SURFACE): * representation mode as in data::material. */ class material final : public sight::viz::scene3d::adaptor { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(material, sight::viz::scene3d::adaptor); struct signals { using changed_t = core::com::signal; static inline const signal_key_t CHANGED = "changed"; }; struct slots { static const inline slot_key_t UPDATE_FIELD = "update_field"; static const inline slot_key_t SWAP_TEXTURE = "swap_texture"; static const inline slot_key_t ADD_TEXTURE = "add_texture"; static const inline slot_key_t REMOVE_TEXTURE = "remove_texture"; }; static const inline std::string MATERIAL_INOUT = "material"; /// Initializes slots. material() noexcept; /// Destroys the adaptor. ~material() noexcept final = default; /// Configures the adaptor without using the XML configuration. void configure( const std::string& _id, const std::string& _name, sight::viz::scene3d::render::sptr _service, const std::string& _layer, const std::string& _shading_mode = "", const std::string& _template = sight::viz::scene3d::material::standard::TEMPLATE ); /// Gets Ogre associated material. Ogre::MaterialPtr get_material(); /// Gets material name. std::string get_material_name() const; /// Retrieves the associated texture name. void set_texture_name(const std::string& _texture_name); /// Sets material name. void set_material_name(const std::string& _material_name); /// Sets material template name. void set_material_template_name(const std::string& _material_name); /// Tells if there is a texture currently bound. bool has_diffuse_texture() const; /// Gets the shading mode. const std::string& get_shading_mode() const; /// Sets the shading mode. void set_shading_mode(const std::string& _shading_mode); /// Gets the representation mode. data::material::representation_t representation_mode() const; /// Sets the representation mode. void set_representation_mode(data::material::representation_t _representation_mode); /// Gets the internal material code. sight::viz::scene3d::material::generic* get_material_impl() const; protected: /// Configures the adaptor. void configuring(const config_t& config) final; /// Creates the material. void starting() final; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::material::MODIFIED_SIG of s_MATERIAL_INOUT to service::slots::UPDATE * Connect data::material::ADDED_FIELDS_SIG of s_MATERIAL_INOUT to UPDATE_FIELD * Connect data::material::CHANGED_FIELDS_SIG of s_MATERIAL_INOUT to UPDATE_FIELD * Connect data::material::ADDED_TEXTURE_SIG of s_MATERIAL_INOUT to ADD_TEXTURE * Connect data::material::REMOVED_TEXTURE_SIG of s_MATERIAL_INOUT to REMOVE_TEXTURE */ service::connections_t auto_connections() const final; /// Updates fixed function pipeline parameters. void updating() final; /// Release Ogre resources. void stopping() final; private: /** * @brief SLOT: updates the material from the input data fields. * @param _fields fields to update, only "material" and "uniforms" are taken into account. */ void update_field(data::fields_container_t _fields); /// SLOT: swaps the texture of the material. void swap_texture(); /// SLOT: creates a texture adaptor when a texture is added to the material. /// This method is also called from the starting in order to create the texture adaptor if the material has a /// default texture. void create_texture_adaptor(); /// SLOT: removes the texture adaptor when the texture is removed from the material. void remove_texture_adaptor(); /// Creates shader parameters adaptors from resources. void create_shader_parameter_adaptors(); /// Defines the material name. It is auto generated. std::string m_material_name; /// Defines the default template name, given by xml configuration. /// It must refer an existing Ogre material which will be used in order to instantiate m_material std::string m_material_template_name {sight::viz::scene3d::material::standard::TEMPLATE}; /// Contains the texture adaptor the material adaptor is listening to. module::viz::scene3d::adaptor::texture::sptr m_tex_adaptor {nullptr}; /// Defines the texture name. std::string m_texture_name; /// Stores supported schemes. std::vector m_schemes_supported; /// Handles connections with texture adaptor. core::com::helper::sig_slot_connection m_texture_connection; /// Defines the configured shading mode. std::string m_shading_mode; /// Defines the configured representation mode. sight::data::material::representation_t m_representation_mode {sight::data::material::representation_t::surface}; /// Implementation when we instantiate the standard material sight::viz::scene3d::material::standard::uptr m_standard_material_impl; /// Implementation of the material. sight::viz::scene3d::material::generic::uptr m_material_impl; /// Used to keep alive the material data if it is not provided in the configuration sight::data::material::sptr m_internal_material; data::ptr m_material_data {this, MATERIAL_INOUT, true}; data::ptr_vector m_uniforms {this, "uniforms", true}; }; //------------------------------------------------------------------------------ inline Ogre::MaterialPtr material::get_material() { return Ogre::MaterialManager::getSingleton().getByName(m_material_name, sight::viz::scene3d::RESOURCE_GROUP); } //------------------------------------------------------------------------------ inline void material::set_material_template_name(const std::string& _material_name) { m_material_template_name = _material_name; } //------------------------------------------------------------------------------ inline void material::set_material_name(const std::string& _material_name) { m_material_name = _material_name; } //------------------------------------------------------------------------------ inline std::string material::get_material_name() const { return m_material_name; } //------------------------------------------------------------------------------ inline bool material::has_diffuse_texture() const { return m_tex_adaptor && m_tex_adaptor->is_valid(); } //------------------------------------------------------------------------------ inline const std::string& material::get_shading_mode() const { return m_shading_mode; } //------------------------------------------------------------------------------ inline void material::set_shading_mode(const std::string& _shading_mode) { m_shading_mode = _shading_mode; } //------------------------------------------------------------------------------ inline data::material::representation_t material::representation_mode() const { return m_representation_mode; } //------------------------------------------------------------------------------ inline void material::set_representation_mode(data::material::representation_t _representation_mode) { m_representation_mode = _representation_mode; } //------------------------------------------------------------------------------ inline sight::viz::scene3d::material::generic* material::get_material_impl() const { if(m_material_impl != nullptr) { return m_material_impl.get(); } else { return m_standard_material_impl.get(); } } //------------------------------------------------------------------------------ } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/mesh.cpp000066400000000000000000000507541503402212300214330ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/mesh.hpp" #include "module/viz/scene3d/adaptor/material.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { static const core::com::slots::key_t MODIFY_MESH_SLOT = "modifyMesh"; static const core::com::slots::key_t MODIFY_COLORS_SLOT = "modifyColors"; static const core::com::slots::key_t MODIFY_POINT_TEX_COORDS_SLOT = "modifyTexCoords"; static const core::com::slots::key_t MODIFY_VERTICES_SLOT = "modifyVertices"; static const core::com::slots::key_t CHANGE_MATERIAL_SLOT = "change_material"; //----------------------------------------------------------------------------- mesh::mesh() noexcept { m_material = std::make_shared(); new_slot(MODIFY_MESH_SLOT, [this](){lazy_update(update_flags::MESH);}); new_slot(MODIFY_COLORS_SLOT, [this](){lazy_update(update_flags::COLORS);}); new_slot(MODIFY_POINT_TEX_COORDS_SLOT, [this](){lazy_update(update_flags::TEX_COORDS);}); new_slot(MODIFY_VERTICES_SLOT, [this](){lazy_update(update_flags::VERTICES);}); new_slot( CHANGE_MATERIAL_SLOT, [this](Ogre::MaterialPtr _material) { SIGHT_ASSERT("Entity null", m_entity); m_entity->setMaterial(_material); m_material_name = _material->getName(); SIGHT_ASSERT("Adaptor is null", m_material_adaptor); m_material_adaptor->get_material_impl()->set_layout(*m_mesh_geometry); }); } //----------------------------------------------------------------------------- mesh::~mesh() noexcept { if(m_entity != nullptr) { Ogre::SceneManager* scene_mgr = this->get_scene_manager(); scene_mgr->destroyEntity(m_entity); } } //----------------------------------------------------------------------------- void mesh::configuring() { this->configure_params(); const config_t config = this->get_config(); const std::string color = config.get(CONFIG + "color", ""); SIGHT_ASSERT("Material not found", m_material); m_material->diffuse()->from_string(color.empty() ? "#FFFFFFFF" : color); m_auto_reset_camera = config.get(CONFIG + "autoresetcamera", true); // If a material is configured in the XML scene, we keep its name to retrieve the adaptor later // Else we keep the name of the configured Ogre material (if it exists), // it will be passed to the created material if(const auto material_name = config.get_optional(CONFIG + "material_name"); material_name.has_value()) { m_material_name = material_name.value(); } else { // An existing Ogre material will be used for this mesh m_material_template_name = config.get(CONFIG + "material_template", m_material_template_name); // The mesh adaptor will pass the texture name to the created material adaptor m_texture_name = core::ptree::get_and_deprecate( config, CONFIG + "texture_name", CONFIG + "textureName", "26.0", m_texture_name ); m_shading_mode = core::ptree::get_and_deprecate( config, CONFIG + "shading", CONFIG + "shadingMode", "26.0", m_shading_mode ); } this->set_transform_id( config.get( sight::viz::scene3d::transformable::TRANSFORM_CONFIG, gen_id("transform") ) ); m_is_dynamic = config.get(CONFIG + "dynamic", m_is_dynamic); m_is_dynamic_vertices = config.get(CONFIG + "dynamic_vertices", m_is_dynamic_vertices); const auto hexa_mask = core::ptree::get_and_deprecate( config, CONFIG + "query_flags", CONFIG + "queryFlags", "26.0" ); if(not hexa_mask.empty()) { SIGHT_ASSERT( "Hexadecimal values should start with '0x'" "Given value : " + hexa_mask, hexa_mask.length() > 2 && hexa_mask.substr(0, 2) == "0x" ); m_query_flags = static_cast(std::stoul(hexa_mask, nullptr, 16)); } } //----------------------------------------------------------------------------- void mesh::starting() { adaptor::init(); this->render_service()->make_current(); if(this->get_transform_id().empty()) { this->set_transform_id(this->gen_id("TF")); } m_mesh_geometry = std::make_shared(this->get_id()); m_mesh_geometry->set_dynamic(m_is_dynamic); m_mesh_geometry->set_dynamic_vertices(m_is_dynamic_vertices); // We have to create a new material adaptor only if this adaptor is instantiated by a reconstruction adaptor // or if no material adaptor uid has been configured m_use_new_material_adaptor = m_material_name.empty(); if(!m_use_new_material_adaptor) { // A material adaptor has been configured in the XML scene auto mtl_adaptors = this->render_service()->get_adaptors(); auto result = std::find_if( mtl_adaptors.begin(), mtl_adaptors.end(), [this](const module::viz::scene3d::adaptor::material::sptr& _srv) { return _srv->get_material_name() == m_material_name; }); m_material_adaptor = *result; m_material_connection.connect( m_material_adaptor, module::viz::scene3d::adaptor::material::signals::CHANGED, this->get_sptr(), CHANGE_MATERIAL_SLOT ); SIGHT_ASSERT( "material adaptor managing material'" + m_material_name + "' is not found", result != mtl_adaptors.end() ); m_material = m_material_adaptor->inout(material::MATERIAL_INOUT).lock().get_shared(); } const auto mesh = m_mesh.lock(); this->update_mesh(mesh.get_shared()); } //----------------------------------------------------------------------------- service::connections_t mesh::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(MESH_IN, data::mesh::VERTEX_MODIFIED_SIG, MODIFY_VERTICES_SLOT); connections.push(MESH_IN, data::mesh::POINT_COLORS_MODIFIED_SIG, MODIFY_COLORS_SLOT); connections.push(MESH_IN, data::mesh::CELL_COLORS_MODIFIED_SIG, MODIFY_COLORS_SLOT); connections.push(MESH_IN, data::mesh::POINT_TEX_COORDS_MODIFIED_SIG, MODIFY_POINT_TEX_COORDS_SLOT); connections.push(MESH_IN, data::mesh::MODIFIED_SIG, MODIFY_MESH_SLOT); return connections; } //----------------------------------------------------------------------------- void mesh::updating() { if((m_is_dynamic || m_is_dynamic_vertices) && (!get_visibility() || !this->render_service()->is_shown_on_screen())) { return; } if(update_needed(update_flags::MESH)) { const auto mesh = m_mesh.lock(); if(m_mesh_geometry->has_color_layer_changed(mesh.get_shared())) { Ogre::SceneManager* scene_mgr = this->get_scene_manager(); SIGHT_ASSERT("Ogre::SceneManager is null", scene_mgr); m_mesh_geometry->clear_mesh(*scene_mgr); } this->update_mesh(mesh.get_shared()); } else if(update_needed(update_flags::VERTICES)) { this->modify_vertices(); } else if(update_needed(update_flags::COLORS)) { this->modify_point_colors(); } else if(update_needed(update_flags::TEX_COORDS)) { this->modify_tex_coords(); } this->update_done(); } //----------------------------------------------------------------------------- void mesh::stopping() { this->render_service()->make_current(); Ogre::SceneManager* scene_mgr = this->get_scene_manager(); SIGHT_ASSERT("Ogre::SceneManager is null", scene_mgr); this->unregister_services(); m_mesh_geometry->clear_mesh(*scene_mgr); m_material_adaptor.reset(); if(m_entity != nullptr) { scene_mgr->destroyEntity(m_entity); m_entity = nullptr; } m_mesh_geometry.reset(); adaptor::deinit(); } //----------------------------------------------------------------------------- void module::viz::scene3d::adaptor::mesh::set_visible(bool _visible) { if(m_entity != nullptr) { m_entity->setVisible(_visible); m_mesh_geometry->set_visible(_visible); if(m_auto_reset_camera && _visible) { this->render_service()->reset_camera_coordinates(m_layer_id); } this->request_render(); } } //----------------------------------------------------------------------------- void mesh::update_mesh(data::mesh::csptr _mesh) { Ogre::SceneManager* scene_mgr = this->get_scene_manager(); SIGHT_ASSERT("Ogre::SceneManager is null", scene_mgr); const std::size_t ui_num_vertices = _mesh->num_points(); if(ui_num_vertices == 0) { SIGHT_DEBUG("Empty mesh"); if(m_entity != nullptr) { scene_mgr->destroyEntity(m_entity); m_entity = nullptr; } m_mesh_geometry->clear_mesh(*scene_mgr); return; } this->render_service()->make_current(); m_mesh_geometry->update_mesh(_mesh); //------------------------------------------ // Update vertex layers //------------------------------------------ m_mesh_geometry->update_vertices(_mesh); m_mesh_geometry->update_colors(_mesh); m_mesh_geometry->update_tex_coords(_mesh); //------------------------------------------ // Create entity and attach it in the scene graph //------------------------------------------ if(m_entity == nullptr) { m_entity = m_mesh_geometry->create_entity(*scene_mgr); m_entity->setVisible(visible()); m_entity->setQueryFlags(m_query_flags); } else { // Re-initialize the entity in order to trigger the build of the sub-entities list // We need them to exist now as we will set the material after that m_entity->_initialise(true); } //------------------------------------------ // Create sub-services //------------------------------------------ if(m_use_new_material_adaptor) { this->update_new_material_adaptor(_mesh); } else { this->update_xml_material_adaptor(); } this->attach_node(m_entity); auto r2vb_renderables = m_mesh_geometry->update_r2vb( _mesh, *scene_mgr, m_material_adaptor->get_material_name() ); for(auto* renderable : r2vb_renderables.second) { if(r2vb_renderables.first) { if(renderable->material == nullptr) { const std::string mtl_name = gen_id(renderable->getName()); renderable->material = std::make_unique( mtl_name, m_material_template_name ); renderable->set_render_to_buffer_material(mtl_name); renderable->material->set_primitive_type(renderable->get_input_primitive_type()); } renderable->material->set_layout(*m_mesh_geometry); renderable->material->set_shading(m_material->get_shading_mode()); renderable->material->set_ambient_diffuse(m_material); // Attach r2vb object in the scene graph renderable->setQueryFlags(m_query_flags); this->attach_node(renderable); } else { this->get_scene_manager()->destroyMovableObject(renderable); } } m_mesh_geometry->set_visible(visible()); if(m_auto_reset_camera) { this->layer()->reset_camera_coordinates(); } this->request_render(); } //------------------------------------------------------------------------------ void mesh::update_new_material_adaptor(data::mesh::csptr _mesh) { if(!m_material_adaptor) { if(m_entity != nullptr) { m_material_adaptor = this->register_service( "sight::module::viz::scene3d::adaptor::material" ); m_material_adaptor->set_inout(m_material, "material", true); config_t material_adp_config; material_adp_config.put("config..material_template", m_material_template_name); if(!m_uniforms.empty()) { std::size_t i = 0; for(const auto& uniform_data : m_uniforms) { m_material_adaptor->set_inout(uniform_data.second->lock().get_shared(), "uniforms", true, {}, i++); } const auto config = this->get_config(); if(const auto inouts_cfg = config.get_child_optional("inout"); inouts_cfg.has_value()) { const auto group = inouts_cfg->get(".group"); if(group == "uniforms") { material_adp_config.add_child("inout", inouts_cfg.value()); } } } const std::string mtl_name = core::id::join(_mesh->get_id(), m_material_adaptor->get_id()); SIGHT_ASSERT("Template name empty", !m_material_template_name.empty()); m_material_adaptor->service::base::configure(material_adp_config); m_material_adaptor->set_id(gen_id(m_material_adaptor->get_id())); m_material_adaptor->set_material_name(mtl_name); m_material_adaptor->set_render_service(this->render_service()); m_material_adaptor->set_layer_id(m_layer_id); m_material_adaptor->set_shading_mode(m_shading_mode); m_material_adaptor->set_material_template_name(m_material_template_name); m_material_adaptor->set_representation_mode(m_material->get_representation_mode()); // We know that we are in the case of a R2VB material, so no need to set the diffuse texture (no FP...) m_material_adaptor->set_texture_name(m_texture_name); m_material_adaptor->start(); m_material_adaptor->get_material_impl()->set_layout(*m_mesh_geometry); m_material_adaptor->update(); m_entity->setMaterialName(m_material_adaptor->get_material_name(), sight::viz::scene3d::RESOURCE_GROUP); m_material_connection.connect( m_material_adaptor, module::viz::scene3d::adaptor::material::signals::CHANGED, this->get_sptr(), CHANGE_MATERIAL_SLOT ); } } else if(m_material_adaptor->inout(material::MATERIAL_INOUT).lock() != m_material) { m_material_adaptor->get_material_impl()->set_layout(*m_mesh_geometry); } else { m_entity->setMaterialName(m_material_adaptor->get_material_name(), sight::viz::scene3d::RESOURCE_GROUP); m_material_adaptor->get_material_impl()->set_layout(*m_mesh_geometry); m_material_adaptor->slot(service::slots::UPDATE)->run(); } } //------------------------------------------------------------------------------ void mesh::update_xml_material_adaptor() { SIGHT_THROW_IF( "Can not provide both a user-defined material adaptor and uniforms.", !m_uniforms.empty() ); if(m_material_adaptor->updating_status() == updating_status::notupdating) { if(m_material_adaptor->get_material_name().empty()) { const auto mesh = m_mesh.lock(); std::string mesh_name = mesh->get_id(); m_material_adaptor->set_material_name(mesh_name + "_Material"); } if(m_entity != nullptr) { m_entity->setMaterialName(m_material_adaptor->get_material_name()); m_material_adaptor->get_material_impl()->set_layout(*m_mesh_geometry); m_material_adaptor->slot(service::slots::UPDATE)->run(); } } else if(m_material_adaptor->inout(material::MATERIAL_INOUT).lock() != m_material) { m_material_adaptor->get_material_impl()->set_layout(*m_mesh_geometry); } } //----------------------------------------------------------------------------- void mesh::modify_vertices() { if((m_is_dynamic || m_is_dynamic_vertices) && (!get_visibility() || !this->render_service()->is_shown_on_screen())) { return; } // Keep the make current outside to avoid too many context changes when we update multiple attributes this->render_service()->make_current(); const auto mesh = m_mesh.lock(); m_mesh_geometry->update_vertices(mesh.get_shared()); Ogre::SceneManager* const scene_mgr = this->get_scene_manager(); m_mesh_geometry->update_r2vb( mesh.get_shared(), *scene_mgr, m_material_adaptor->get_material_name() ); // Necessary to update the bounding box in the adaptor //m_materialAdaptor->slot(service::slots::UPDATE)->async_run(); if(m_auto_reset_camera) { this->render_service()->reset_camera_coordinates(m_layer_id); } this->request_render(); } //----------------------------------------------------------------------------- void mesh::modify_point_colors() { if((m_is_dynamic || m_is_dynamic_vertices) && (!get_visibility() || !this->render_service()->is_shown_on_screen())) { return; } // Keep the make current outside to avoid too many context changes when we update multiple attributes this->render_service()->make_current(); const auto mesh = m_mesh.lock(); if(m_mesh_geometry->has_color_layer_changed(mesh.get_shared())) { Ogre::SceneManager* scene_mgr = this->get_scene_manager(); SIGHT_ASSERT("Ogre::SceneManager is null", scene_mgr); m_mesh_geometry->clear_mesh(*scene_mgr); this->update_mesh(mesh.get_shared()); } else { m_mesh_geometry->update_colors(mesh.get_shared()); } this->request_render(); } //----------------------------------------------------------------------------- void mesh::modify_tex_coords() { if((m_is_dynamic || m_is_dynamic_vertices) && (!get_visibility() || !this->render_service()->is_shown_on_screen())) { return; } // Keep the make current outside to avoid too many context changes when we update multiple attributes this->render_service()->make_current(); const auto mesh = m_mesh.lock(); m_mesh_geometry->update_tex_coords(mesh.get_shared()); this->request_render(); } //----------------------------------------------------------------------------- void mesh::attach_node(Ogre::MovableObject* _node) { Ogre::SceneNode* root_scene_node = this->get_scene_manager()->getRootSceneNode(); Ogre::SceneNode* trans_node = this->get_or_create_transform_node(root_scene_node); Ogre::SceneNode* node = _node->getParentSceneNode(); if(node != trans_node) { _node->detachFromParent(); trans_node->attachObject(_node); } } //----------------------------------------------------------------------------- void mesh::request_render() { m_mesh_geometry->invalidate_r2vb(); sight::viz::scene3d::adaptor::request_render(); } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/mesh.hpp000066400000000000000000000315571503402212300214400ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "module/viz/scene3d/adaptor/material.hpp" #include "module/viz/scene3d/adaptor/transform.hpp" #include #include #include #include #include #include namespace sight::data { class Material; } // namespace sight::data namespace sight::data { class mesh; } // namespace sight::data namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor shows individual meshes. * * This class handles the conversion of data::mesh to Ogre3d. It can handle triangles, edges, quads or tetrahedrons. * For the quads and tetrahedrons, we generate the triangles in a pre-process, using the render to vertex buffer (r2vb) * feature to avoid the cost of geometry shaders when using multi-pass rendering techniques. * * An Ogre entity is created from the mesh. A second mesh and a second entity are created as an input for the r2vb. * Thus, the main mesh will contains only triangles or edges, while the second entity contains only quads or tetras. * An Ogre material is also created, and then managed by a material adaptor (specified in the configuration otherwise * a new one is generated). * * To handle the per-primitive color of data::mesh we also rely on geometry shaders, and thus on r2vb. We build a * texture containing the color for each primitive. This texture is fetched inside the geometry shader using the * primitive id. * * @section Slots Slots * - \b update_visibility(bool): sets whether the mesh is to be seen or not. * - \b toggle_visibility(): toggle whether the mesh is shown or not. * - \b show(): shows the mesh. * - \b hide(): hides the mesh. * - \b update(): called when the mesh is modified. * - \b modifyColors(): called when the point colors are modified. * - \b modifyTexCoords(): called when the texture coordinates are modified. * - \b modifyVertices(): called when the vertices are modified. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Input Input * - \b mesh [sight::data::mesh]: adapted mesh. * - \b uniforms: list of data to bind to material uniforms. They will be passed to the underlying material adaptor. * * @subsection Configuration Configuration: * - \b autoresetcamera (optional, true/false, default=true): reset the camera when this mesh is modified, "true" or *"false". * - \b transform (optional, string, default=""): the name of the Ogre transform node where to attach the mesh, as it * was specified * in the transform adaptor. * - \b visible (optional, bool, default=true): set the initial visibility of the mesh. * Either of the following (whether a material is configured in the XML scene or not) : * - \b material_name (optional, string, default=""): name of the Ogre material, as defined in the * module::viz::scene3d::adaptor::material you want to be bound to. * Only if there is no material configured in the XML scene (in this case, it has to retrieve the material * template, the texture adaptor and the shading mode) : * - \b material_template (optional, string, default=""): the name of the base Ogre material for the internally created * material. * - \b texture_name (optional, default=""): the name of the Ogre texture that the mesh will use. * - \b shading (optional, none/flat/phong/ambient, default=phong): name of the used shading mode. * - \b query_flags (optional, uint32, default=0x40000000): Used for picking. Picked only by pickers whose mask that * match the flag. */ class mesh final : public sight::viz::scene3d::adaptor, public sight::viz::scene3d::transformable { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(mesh, sight::viz::scene3d::adaptor); /// Sets default parameters and initializes necessary members. mesh() noexcept; /// Destroys Ogre resources. ~mesh() noexcept override; /** * @brief Gets the associated material. * @return The material. */ data::material::sptr get_material() const; /** * @brief Sets the current material. * @param _material new material. */ void set_material(data::material::sptr _material); /** * @brief Sets the material template Name. * @param _material_name material name. */ void set_material_template_name(const std::string& _material_name); /** * @brief Enables/disables automatic reset on camera. * @param _auto_reset_camera use true to activate it. */ void set_auto_reset_camera(bool _auto_reset_camera); /** * @brief Gets the associated entity. * @return The entity. */ Ogre::Entity* get_entity() const; /** * @brief Gets the mesh visibility. * @return True if the mesh is visible. */ bool get_visibility() const; /** * @brief Sets meshes vertex buffer to dynamic state (only has effect if called before service starting/update). * @param _is_dynamic use true to use dynamic vertex buffer. */ void set_dynamic_vertices(bool _is_dynamic); /** * @brief Sets meshes and indices buffers to dynamic state (only has effect if called before service * starting/update). * @param _is_dynamic use true to use dynamic vertex and indices buffer. */ void set_dynamic(bool _is_dynamic); /** * @brief Sets the query flag. * @param _query_flags value of the query flag. */ void set_query_flags(std::uint32_t _query_flags); /// Flags the r2vb objects as dirty and asks the render service to update. void request_render() override; /** * @brief Sets the mesh visibility. * @param _visible the visibility status of the mesh. */ void set_visible(bool _visible) override; protected: /// Configures the adaptor. void configuring() override; /// Creates a Mesh in the Default Ogre resource group. void starting() override; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::mesh::VERTEX_MODIFIED_SIG to MODIFY_VERTICES_SLOT * Connect data::mesh::POINT_COLORS_MODIFIED_SIG to MODIFY_COLORS_SLOT * Connect data::mesh::CELL_COLORS_MODIFIED_SIG to MODIFY_COLORS_SLOT * Connect data::mesh::POINT_TEX_COORDS_MODIFIED_SIG to MODIFY_POINT_TEX_COORDS_SLOT * Connect data::mesh::MODIFIED_SIG to service::slots::UPDATE */ service::connections_t auto_connections() const override; /// Deletes the mesh after unregistering the service, and shutting connections. void stopping() override; /// Updates the mesh. void updating() override; private: /// Updates mesh vertices. void modify_vertices(); /// Updates mesh colors. void modify_point_colors(); /// Updates mesh texture coordinates. void modify_tex_coords(); /** * @brief Updates the mesh, checks if color, number of vertices have changed, and updates them. * @param _mesh used for the update. */ void update_mesh(data::mesh::csptr _mesh); /** * @brief Associates a new material to the managed mesh. * With this method, mesh is responsible for creating a material. * @param _mesh used to create the material service. */ void update_new_material_adaptor(data::mesh::csptr _mesh); /// Updates the associated material adaptor. /// This method is called when a material adaptor has been configured in the XML scene. void update_xml_material_adaptor(); /** * @brief Attaches a node in the scene graph. * @param _node the node to attach. */ void attach_node(Ogre::MovableObject* _node); /// Defines whether the camera must be auto reset when a mesh is updated or not. bool m_auto_reset_camera {true}; /// Contains the node in the scene graph where the mesh is attached. Ogre::Entity* m_entity {nullptr}; /// Contains the Ogre material adaptor. module::viz::scene3d::adaptor::material::sptr m_material_adaptor {nullptr}; /// Defines the attached material name (when configured by XML). std::string m_material_name; /// Contains the material data. data::material::sptr m_material {nullptr}; /// Defines the attached material's name. std::string m_material_template_name {sight::viz::scene3d::material::standard::TEMPLATE}; /// Defines the attached texture adaptor name. std::string m_texture_name; /// Defines if the mesh adaptor has to create a new material adaptor or simply use the one that is XML configured. bool m_use_new_material_adaptor {false}; /// Defines the configured shading mode. std::string m_shading_mode; /// Defines if the mesh changes dynamically, defined in m_configuration. bool m_is_dynamic {false}; /// Defines if the vertices change dynamically, defined in m_configuration. bool m_is_dynamic_vertices {false}; /// Ogre mesh. sight::viz::scene3d::mesh::sptr m_mesh_geometry {nullptr}; /// Stores material adaptors attached to the r2vb objects. std::map m_r2vb_material_adaptor; /// Handles connections with texture adaptor. core::com::helper::sig_slot_connection m_material_connection; /// Defines the mask used for picking request. std::uint32_t m_query_flags {Ogre::SceneManager::ENTITY_TYPE_MASK}; enum class update_flags : std::uint8_t { MESH, VERTICES, COLORS, TEX_COORDS }; static constexpr std::string_view MESH_IN = "mesh"; data::ptr m_mesh {this, MESH_IN}; data::ptr_vector m_uniforms {this, "uniforms", true}; }; //------------------------------------------------------------------------------ inline data::material::sptr mesh::get_material() const { return m_material; } //------------------------------------------------------------------------------ inline void mesh::set_material(data::material::sptr _material) { m_material = _material; } //------------------------------------------------------------------------------ inline void mesh::set_material_template_name(const std::string& _material_name) { m_material_template_name = _material_name; } //------------------------------------------------------------------------------ inline void mesh::set_auto_reset_camera(bool _auto_reset_camera) { m_auto_reset_camera = _auto_reset_camera; } //------------------------------------------------------------------------------ inline Ogre::Entity* mesh::get_entity() const { return m_entity; } //------------------------------------------------------------------------------ inline bool mesh::get_visibility() const { return m_entity != nullptr ? m_entity->getVisible() : visible(); } //------------------------------------------------------------------------------ inline void mesh::set_dynamic(bool _is_dynamic) { m_is_dynamic = _is_dynamic; if(m_mesh_geometry) { m_mesh_geometry->set_dynamic(_is_dynamic); } } //------------------------------------------------------------------------------ inline void mesh::set_dynamic_vertices(bool _is_dynamic) { m_is_dynamic_vertices = _is_dynamic; if(m_mesh_geometry) { m_mesh_geometry->set_dynamic_vertices(_is_dynamic); } } //------------------------------------------------------------------------------ inline void mesh::set_query_flags(uint32_t _query_flags) { m_query_flags = _query_flags; } //------------------------------------------------------------------------------ } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/mesh_list.cpp000066400000000000000000000256441503402212300224660ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2021-2025 IRCAD France * Copyright (C) 2021 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "mesh_list.hpp" #include namespace sight::module::viz::scene3d::adaptor { static const core::com::slots::key_t ADD_SLOT = "add"; static const core::com::slots::key_t CLEAR_SLOT = "clear"; //----------------------------------------------------------------------------- mesh_list::mesh_list() noexcept { new_slot(ADD_SLOT, &mesh_list::add, this); new_slot(CLEAR_SLOT, &mesh_list::clear, this); } //----------------------------------------------------------------------------- mesh_list::~mesh_list() noexcept = default; //----------------------------------------------------------------------------- void mesh_list::configuring() { this->configure_params(); const config_t config = this->get_config(); static const std::string s_CAPACITY_CONFIG = CONFIG + "capacity"; static const std::string s_DROPPING_CONFIG = CONFIG + "drop"; static const std::string s_TEXTURE_ALPHA_CONFIG = CONFIG + "textureAlpha"; m_capacity = config.get(s_CAPACITY_CONFIG, m_capacity); m_drop_data = config.get(s_DROPPING_CONFIG, m_drop_data); SIGHT_ASSERT("Capacity must be > 0", m_capacity > 0); SIGHT_ASSERT("Drop ratio must be > 0", m_drop_data > 0); m_generate_alpha = config.get(s_TEXTURE_ALPHA_CONFIG, m_generate_alpha); } //----------------------------------------------------------------------------- void mesh_list::starting() { adaptor::init(); // Get the inputs. const auto transform_in_out = m_transform.lock(); const auto image_input = m_texture.lock(); // initialise N meshes adaptor for(std::size_t i = 0 ; i < m_capacity ; ++i) { // Matrix and image are copied because the input ones will change. Mesh is not copied because we want to use // the same mesh of all the adaptors const auto transform = data::matrix4::copy(transform_in_out.get_shared()); const auto image = data::image::copy(image_input.get_shared()); // Create adaptors configurations const std::string transform_id = gen_id(transform->get_id()); service::config_t config; config.add("config..layer", m_layer_id); config.add("config.." + std::string(TRANSFORM_INPUT), transform_id); config.add("config..autoresetcamera", "false"); // Create the transform adaptor. const sight::viz::scene3d::adaptor::sptr transform_adaptor = this->register_service( "sight::module::viz::scene3d::adaptor::transform" ); transform_adaptor->set_layer_id(m_layer_id); transform_adaptor->set_render_service(this->render_service()); transform_adaptor->set_inout(transform, "transform", true); transform_adaptor->configure(config); transform_adaptor->start(); SIGHT_ASSERT("transform is not started", transform_adaptor->started()); // Create the texture adaptor const sight::viz::scene3d::adaptor::sptr texture_adaptor = this->register_service("sight::module::viz::scene3d::adaptor::texture"); service::config_t texture_config = config; texture_config.add("config..texture_name", image->get_id()); texture_config.add("config..useAlpha", "true"); texture_adaptor->set_layer_id(m_layer_id); texture_adaptor->set_render_service(this->render_service()); texture_adaptor->set_input(image, "image", false); texture_adaptor->configure(texture_config); texture_adaptor->start(); SIGHT_ASSERT("texture is not started", texture_adaptor->started()); // Creates the mesh adaptor. const sight::viz::scene3d::adaptor::sptr mesh_adaptor = this->register_service("sight::module::viz::scene3d::adaptor::mesh"); service::config_t mesh_config = config; mesh_config.add("config..texture_name", image->get_id()); mesh_adaptor->set_layer_id(m_layer_id); mesh_adaptor->set_render_service(this->render_service()); { const auto mesh = m_mesh.lock(); mesh_adaptor->set_input(mesh.get_shared(), "mesh", true); } mesh_adaptor->configure(mesh_config); mesh_adaptor->update_visibility(false); mesh_adaptor->start(); SIGHT_ASSERT("mesh is not started", mesh_adaptor->started()); // Store data. mesh_instance instance {transform, image, transform_adaptor, mesh_adaptor, texture_adaptor}; m_meshes.push_back(instance); } } //----------------------------------------------------------------------------- service::connections_t mesh_list::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(TRANSFORM_INPUT, data::matrix4::MODIFIED_SIG, ADD_SLOT); return connections; } //----------------------------------------------------------------------------- void mesh_list::updating() { } //----------------------------------------------------------------------------- void mesh_list::stopping() { for(const auto& instance : m_meshes) { this->unregister_service(instance.m_transform); this->unregister_service(instance.m_mesh); this->unregister_service(instance.m_texture); } m_meshes.clear(); m_mesh_count = 0; adaptor::deinit(); } //----------------------------------------------------------------------------- void mesh_list::set_visible(bool _visible) { for(const auto& instance : m_meshes) { if(instance.m_is_enabled) { const sight::viz::scene3d::adaptor::sptr mesh = instance.m_mesh; mesh->update_visibility(_visible); } } } //----------------------------------------------------------------------------- void mesh_list::add() { if(m_drop_count % m_drop_data == 0) { // Reset the drop count. m_drop_count = 0; auto& instance = m_meshes.at(m_mesh_count); ++m_mesh_count; if(m_mesh_count == m_capacity) { m_mesh_count = 0; } instance.m_is_enabled = true; const sight::viz::scene3d::adaptor::sptr texture_adp = instance.m_texture; { const auto image = texture_adp->input("image").lock(); const auto texture_input = m_texture.lock(); if(m_generate_alpha && texture_input->type() == core::type::UINT8 && (texture_input->pixel_format() == data::image::pixel_format_t::gray_scale || texture_input->num_components() == 1)) { // transform the image into RGBA with a transparent texture if(texture_input->allocated_size_in_bytes() * 4 != instance.m_image->allocated_size_in_bytes()) { instance.m_image->copy_information(texture_input.get_shared()); instance.m_image->resize( instance.m_image->size(), instance.m_image->type(), data::image::pixel_format_t::rgba ); } auto in_itr = texture_input->begin(); auto in_end = texture_input->end(); auto itr = instance.m_image->begin(); for( ; in_itr != in_end ; ++in_itr, ++itr) { itr->r = *in_itr; itr->g = *in_itr; itr->b = *in_itr; itr->a = *in_itr; } } else if(m_generate_alpha && texture_input->type() == core::type::UINT8 && (texture_input->pixel_format() == data::image::pixel_format_t::rgb || texture_input->num_components() == 3)) { // transform the image into RGBA with a transparent texture if(texture_input->allocated_size_in_bytes() * 4 / 3 != instance.m_image->allocated_size_in_bytes()) { instance.m_image->copy_information(texture_input.get_shared()); instance.m_image->resize( instance.m_image->size(), instance.m_image->type(), data::image::pixel_format_t::rgba ); } auto in_itr = texture_input->begin(); auto in_end = texture_input->end(); auto itr = instance.m_image->begin(); for( ; in_itr != in_end ; ++in_itr, ++itr) { itr->r = in_itr->r; itr->g = in_itr->g; itr->b = in_itr->b; // luminance itr->a = static_cast(0.2126 * in_itr->r + 0.7152 * in_itr->g + 0.0722 * in_itr->b); } } else { instance.m_image->deep_copy(texture_input.get_shared()); } } texture_adp->update(); // update the texture const sight::viz::scene3d::adaptor::sptr transform_adp = instance.m_transform; { // set current matrix const auto transform = transform_adp->inout("transform").lock(); const auto transform_in_out = m_transform.lock(); transform->deep_copy(transform_in_out.get_shared()); } transform_adp->update(); // update mesh adaptor visibility const sight::viz::scene3d::adaptor::sptr mesh_adp = instance.m_mesh; mesh_adp->update_visibility(visible()); } ++m_drop_count; } //----------------------------------------------------------------------------- void mesh_list::clear() { this->set_visible(false); for(auto& instance : m_meshes) { instance.m_is_enabled = false; } m_mesh_count = 0; } } // namespace sight::module::viz::scene3d::adaptor sight-25.1.0/module/viz/scene3d/adaptor/mesh_list.hpp000066400000000000000000000122561503402212300224660ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2021-2024 IRCAD France * Copyright (C) 2021 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor shows a list of mesh. * * @section Slots Slots * - \b add(): add a new mesh at the transformation matrix position. * - \b clear(): clear all meshes. * - \b update_visibility(bool): sets whether the mesh is to be seen or not. * - \b toggle_visibility(): toggle whether the mesh is shown or not. * - \b show(): shows the mesh. * - \b hide(): hides the mesh. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Input Input * - \b texture [sight::data::image]: image displayed as a texture on the mesh * - \b transform [sight::data::matrix4]: Sight transform matrix. * * @subsection In-Out In-Out * - \b mesh [sight::data::mesh]: adapted mesh. It can not be a read-only data because we may generate normals or add * some * fields. * * @subsection Configuration Configuration: * - \b capacity (optional, int, default=50): maximum capacity of the list. * - \b drop (optional, int, default=1): defines the ratio of matrices to display: (1/drop). * - \b textureAlpha (optional, bool, default=false): generates alpha value for the texture if the image contains only * 1 or 3 channels. It may be slower. */ class mesh_list final : public sight::viz::scene3d::adaptor { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(mesh_list, sight::viz::scene3d::adaptor); /// Sets default parameters and initializes necessary members. mesh_list() noexcept; /// Destroys the service. ~mesh_list() noexcept override; protected: /// Configures the adaptor. void configuring() override; /// Initializes the adaptor. void starting() override; /** * @brief Proposal to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::matrix4::MODIFIED_SIG of s_TRANSFORM_INOUT to ADD_MESH_SLOT */ service::connections_t auto_connections() const override; /// Does nothing. void updating() override; /// Unregisters all services. /// @see clear() void stopping() override; /** * @brief Sets the list visibility. * @param _visible the visibility status of the list. */ void set_visible(bool _visible) override; private: /// Stores all services and data needed to add a new mesh. struct mesh_instance { data::matrix4::sptr m_matrix; data::image::sptr m_image; sight::viz::scene3d::adaptor::sptr m_transform; sight::viz::scene3d::adaptor::sptr m_mesh; sight::viz::scene3d::adaptor::sptr m_texture; bool m_is_enabled {false}; ///< set to true when the instance has been added once }; /// Adds a new mesh at the input matrix position. void add(); /// Clear all meshes. void clear(); /// Stores all registered meshes instances. std::vector m_meshes; /// Defines the current index mesh to update in the mesh vector. unsigned int m_mesh_count {0}; /// Defines the maximum capacity of the meshes list. unsigned int m_capacity {50}; /// Defines the number of matrices to drop before adding a new adaptor. unsigned int m_drop_data {1}; /// Counts the number of received signal to add a data, then drop the signal if (m_dropCount%m_dropData != 0). unsigned int m_drop_count {0}; /// Generates alpha value for the texture if the image contains only 1 or 3 channels. It may be slower. bool m_generate_alpha {false}; static constexpr std::string_view TRANSFORM_INPUT = "transform"; sight::data::ptr m_texture {this, "texture"}; sight::data::ptr m_transform {this, TRANSFORM_INPUT}; sight::data::ptr m_mesh {this, "mesh"}; }; } // namespace sight::module::viz::scene3d::adaptor sight-25.1.0/module/viz/scene3d/adaptor/model_series.cpp000066400000000000000000000204021503402212300231340ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/model_series.hpp" #include "module/viz/scene3d/adaptor/mesh.hpp" #include "module/viz/scene3d/adaptor/reconstruction.hpp" #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { //----------------------------------------------------------------------------- static const core::com::slots::key_t CHANGE_FIELD_SLOT = "changeField"; //------------------------------------------------------------------------------ model_series::model_series() noexcept { new_slot(CHANGE_FIELD_SLOT, &model_series::show_reconstructions_on_field_changed, this); } //------------------------------------------------------------------------------ void model_series::configuring() { this->configure_params(); const config_t config = this->get_config(); this->set_transform_id( config.get( sight::viz::scene3d::transformable::TRANSFORM_CONFIG, gen_id("transform") ) ); static const std::string s_AUTORESET_CAMERA_CONFIG = CONFIG + "autoresetcamera"; static const std::string s_MATERIAL_TEMPLATE_CONFIG = CONFIG + "material_template"; static const std::string s_DYNAMIC_CONFIG = CONFIG + "dynamic"; static const std::string s_DYNAMIC_VERTICES_CONFIG = CONFIG + "dynamic_vertices"; static const std::string s_QUERY_CONFIG = CONFIG + "queryFlags"; m_auto_reset_camera = config.get(s_AUTORESET_CAMERA_CONFIG, true); m_material_template_name = config.get(s_MATERIAL_TEMPLATE_CONFIG, m_material_template_name); m_is_dynamic = config.get(s_DYNAMIC_CONFIG, m_is_dynamic); m_is_dynamic_vertices = config.get(s_DYNAMIC_VERTICES_CONFIG, m_is_dynamic_vertices); const auto hexa_mask = core::ptree::get_and_deprecate( config, CONFIG + "query_flags", CONFIG + "queryFlags", "26.0" ); if(not hexa_mask.empty()) { SIGHT_ASSERT( "Hexadecimal values should start with '0x'" "Given value : " + hexa_mask, hexa_mask.length() > 2 && hexa_mask.substr(0, 2) == "0x" ); m_query_flags = static_cast(std::stoul(hexa_mask, nullptr, 16)); } if(config.get_optional(CONFIG + "visible") || config.get_optional("properties..visible")) { m_is_visible_tag = true; } } //------------------------------------------------------------------------------ void model_series::starting() { adaptor::init(); this->updating(); } //----------------------------------------------------------------------------- service::connections_t model_series::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(MODEL_INPUT, data::model_series::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(MODEL_INPUT, data::model_series::RECONSTRUCTIONS_ADDED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(MODEL_INPUT, data::model_series::RECONSTRUCTIONS_REMOVED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(MODEL_INPUT, data::model_series::ADDED_FIELDS_SIG, CHANGE_FIELD_SLOT); connections.push(MODEL_INPUT, data::model_series::REMOVED_FIELDS_SIG, CHANGE_FIELD_SLOT); connections.push(MODEL_INPUT, data::model_series::CHANGED_FIELDS_SIG, CHANGE_FIELD_SLOT); return connections; } //------------------------------------------------------------------------------ void model_series::updating() { // Retrieves the associated Sight ModelSeries object const auto model_series = m_model.lock(); this->unregister_services(); for(const auto& reconstruction : model_series->get_reconstruction_db()) { auto adaptor = this->register_service( "sight::module::viz::scene3d::adaptor::reconstruction" ); adaptor->set_input(reconstruction, "reconstruction", true); bool is_visible = visible(); if(const auto visibility_field = model_series->get_field("ShowReconstructions"); visibility_field) { const bool show_rec = std::dynamic_pointer_cast(visibility_field)->value(); is_visible = is_visible && show_rec; } config_t rec_adaptor_config; rec_adaptor_config.put("properties..visible", is_visible); rec_adaptor_config.put("config..material_template", m_material_template_name); if(!m_uniforms.empty()) { std::size_t i = 0; for(const auto& uniform_data : m_uniforms) { adaptor->set_inout(uniform_data.second->lock().get_shared(), "uniforms", true, {}, i++); } const auto config = this->get_config(); if(const auto inouts_cfg = config.get_child_optional("inout"); inouts_cfg.has_value()) { const auto group = inouts_cfg->get(".group"); if(group == "uniforms") { rec_adaptor_config.add_child("inout", inouts_cfg.value()); } } } adaptor->configure(rec_adaptor_config); // We use the default service ID to get a unique number because a ModelSeries contains several Reconstructions adaptor->set_id(this->get_id(), adaptor->get_id()); adaptor->set_render_service(this->render_service()); adaptor->set_layer_id(m_layer_id); adaptor->set_transform_id(this->get_transform_id()); adaptor->set_auto_reset_camera(m_auto_reset_camera); adaptor->set_query_flags(m_query_flags); adaptor->start(); module::viz::scene3d::adaptor::mesh::sptr mesh_adaptor = adaptor->get_mesh_adaptor(); mesh_adaptor->set_dynamic(m_is_dynamic); mesh_adaptor->set_dynamic_vertices(m_is_dynamic_vertices); } this->update_done(); this->request_render(); } //------------------------------------------------------------------------------ void model_series::stopping() { this->unregister_services(); adaptor::deinit(); } //------------------------------------------------------------------------------ void model_series::set_visible(bool _visible) { auto adaptors = this->get_registered_services(); for(const auto& adaptor : adaptors) { auto rec_adaptor = std::dynamic_pointer_cast(adaptor.lock()); rec_adaptor->update_visibility(_visible); } } //------------------------------------------------------------------------------ void model_series::show_reconstructions_on_field_changed() { const auto model_series = m_model.lock(); const bool show_rec = model_series->get_field("ShowReconstructions", std::make_shared(true))->value(); auto adaptors = this->get_registered_services(); for(const auto& adaptor : adaptors) { auto rec_adaptor = std::dynamic_pointer_cast(adaptor.lock()); rec_adaptor->update_visibility(show_rec); } } } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/model_series.hpp000066400000000000000000000145101503402212300231440ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include namespace sight::data { class Material; } // namespace sight::data namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor shows a modelSeries. It creates an adaptor for each reconstruction in the model. * * @section Slots Slots * - \b changeField(): update all reconstructions visibility. * - \b update_visibility(bool): sets whether all reconstructions are shown or not. * - \b toggle_visibility(): toggle whether all reconstructions are shown or not. * - \b show(): shows all reconstructions. * - \b hide(): hides all reconstructions. * * @section XML XML Configuration * * @code{.xml} @endcode * * @subsection In-Out In-Out: * - \b model [sight::data::model_series]: adapted model series. * - \b uniforms: list of data to bind to material uniforms. * * @subsection Configuration Configuration: * - \b transform (optional, string, default=""): the transformation matrix to associate to the adaptor. * - \b material (optional, string, default=""): the name of the base Ogre material to pass to the mesh adaptors. * - \b autoresetcamera (optional, true/false, default=true): reset the camera when this mesh is modified, "true" or *"false". * - \b dynamic (optional, true/false, default=false): if the modelSeries topology is likely to be updated frequently. * This is * a performance hint that will choose a specific GPU memory pool accordingly. * - \b dynamic_vertices (optional, true/false, default=false): if the modelSeries geometry is likely to be updated * frequently. * This is a performance hint that will choose a specific GPU memory pool accordingly. * - \b query_flags (optional, uint32, default=0x40000000): Used for picking. Picked only by pickers whose mask that * match the flag. * - \b visible (optional, true/false, default=true): Used to define the default visibility of the modelSeries. If the * tag is not present, the visibility will be set by the value of the modelSeries field. If the tag is present, * the visibility is set by the value of this tag. * - \b material_template (optional, string, default=""): the name of the base Ogre material for the internally created * material. */ class model_series final : public sight::viz::scene3d::adaptor, public sight::viz::scene3d::transformable { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(model_series, sight::viz::scene3d::adaptor); /// Initialisa slots. model_series() noexcept; /// Destroys the adaptor. ~model_series() noexcept final = default; protected: /// Configures the adaptor. void configuring() final; /// Starts the service and updates it. void starting() final; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::model_series::VERTEX_MODIFIED_SIG to service::slots::UPDATE * Connect data::model_series::RECONSTRUCTIONS_ADDED_SIG to service::slots::UPDATE * Connect data::model_series::RECONSTRUCTIONS_REMOVED_SIG to service::slots::UPDATE * Connect data::model_series::ADDED_FIELDS_SIG to CHANGE_FIELD_SLOT * Connect data::model_series::REMOVED_FIELDS_SIG to CHANGE_FIELD_SLOT * Connect data::model_series::CHANGED_FIELDS_SIG to CHANGE_FIELD_SLOT */ service::connections_t auto_connections() const final; /// Redraws all (stops then restarts sub services). void updating() final; /// Closes connections and unregisters service. void stopping() final; /** * @brief Sets the model series visibility. * @param _visible the visibility status of the model series. */ void set_visible(bool _visible) final; private: /// SLOT: updates all reconstructions visibility from the input data field. void show_reconstructions_on_field_changed(); /// Defines if the camera must be reset automatically bool m_auto_reset_camera {true}; /// Defines the texture name. std::string m_texture_adaptor_uid; /// Defines the material template name. std::string m_material_template_name {sight::viz::scene3d::material::standard::TEMPLATE}; /// Defines if the model series is dynamic. bool m_is_dynamic {false}; /// Defines if the model series' vertices are dynamic. bool m_is_dynamic_vertices {false}; /// Defines the mask used for picking request. std::uint32_t m_query_flags {Ogre::SceneManager::ENTITY_TYPE_MASK}; /// Defines if the visibility tag is present in the configuration. bool m_is_visible_tag {false}; static constexpr std::string_view MODEL_INPUT = "model"; data::ptr m_model {this, MODEL_INPUT}; data::ptr_vector m_uniforms {this, "uniforms", true}; }; //------------------------------------------------------------------------------ } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/negato.cpp000066400000000000000000000326241503402212300217500ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/negato.hpp" #include #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { //----------------------------------------------------------------------------- service::connections_t negato::auto_connections() const { service::connections_t connections = { {m_image, data::image::MODIFIED_SIG, slots::UPDATE_IMAGE}, {m_image, data::image::BUFFER_MODIFIED_SIG, slots::UPDATE_IMAGE_BUFFER}, {m_image, data::image::SLICE_TYPE_MODIFIED_SIG, slots::SLICE_TYPE}, {m_image, data::image::SLICE_INDEX_MODIFIED_SIG, slots::SLICE_INDEX}, {m_tf, data::transfer_function::MODIFIED_SIG, slots::UPDATE_TF}, {m_tf, data::transfer_function::POINTS_MODIFIED_SIG, slots::UPDATE_TF}, {m_tf, data::transfer_function::WINDOWING_MODIFIED_SIG, slots::UPDATE_TF} }; return connections + adaptor::auto_connections(); } //------------------------------------------------------------------------------ negato::negato() noexcept { // Auto-connected slots new_slot(slots::UPDATE_IMAGE, [this](){lazy_update(update_flags::IMAGE);}); new_slot(slots::UPDATE_IMAGE_BUFFER, [this](){lazy_update(update_flags::IMAGE_BUFFER);}); new_slot(slots::UPDATE_TF, [this](){lazy_update(update_flags::TF);}); // Interaction slots new_slot(slots::SLICE_TYPE, &negato::change_slice_type, this); new_slot(slots::SLICE_INDEX, &negato::change_slice_index, this); new_slot(slots::UPDATE_SLICES_FROM_WORLD, &negato::update_slices_from_world, this); new_signal(signals::SLICE_INDEX_CHANGED); new_signal(signals::PICKED_VOXEL); } //------------------------------------------------------------------------------ void negato::configuring(const config_t& _config) { this->configure_params(); static const std::string s_FILTERING_CONFIG = CONFIG + "filtering"; static const std::string s_TF_ALPHA_CONFIG = CONFIG + "tf_alpha"; static const std::string s_BORDER_CONFIG = CONFIG + "border"; static const std::string s_INTERACTIVE_CONFIG = CONFIG + "interactive"; static const std::string s_PRIORITY_CONFIG = CONFIG + "priority"; if(const auto filtering_cfg = _config.get_optional(s_FILTERING_CONFIG); filtering_cfg.has_value()) { sight::viz::scene3d::plane::filter_t filtering(sight::viz::scene3d::plane::filter_t::linear); if(filtering_cfg.value() == "none") { filtering = sight::viz::scene3d::plane::filter_t::none; } else if(filtering_cfg.value() == "anisotropic") { filtering = sight::viz::scene3d::plane::filter_t::anisotropic; } this->set_filtering(filtering); } const auto hexa_mask = core::ptree::get_and_deprecate( _config, CONFIG + "query_flags", CONFIG + "queryFlags", "26.0" ); if(not hexa_mask.empty()) { SIGHT_ASSERT( "Hexadecimal values should start with '0x'" "Given value : " + hexa_mask, hexa_mask.length() > 2 && hexa_mask.substr(0, 2) == "0x" ); m_query_flags = static_cast(std::stoul(hexa_mask, nullptr, 16)); } m_enable_alpha = _config.get(s_TF_ALPHA_CONFIG, m_enable_alpha); m_border = _config.get(s_BORDER_CONFIG, m_border); m_interactive = _config.get(s_INTERACTIVE_CONFIG, m_interactive); m_priority = _config.get(s_PRIORITY_CONFIG, m_priority); const std::string transform_id = _config.get(sight::viz::scene3d::transformable::TRANSFORM_CONFIG, gen_id("transform")); this->set_transform_id(transform_id); } //------------------------------------------------------------------------------ void negato::starting() { adaptor::init(); this->render_service()->make_current(); { // 3D source texture instantiation const auto image = m_image.lock(); m_3d_ogre_texture = std::make_shared(image.get_shared()); // TF texture initialization const auto tf = m_tf.lock(); m_gpu_tf = std::make_unique(tf.get_shared()); } // scene node's instantiation Ogre::SceneNode* const root_scene_node = this->get_scene_manager()->getRootSceneNode(); Ogre::SceneNode* const transform_node = this->get_or_create_transform_node(root_scene_node); m_origin_scene_node = transform_node->createChildSceneNode(); m_negato_scene_node = m_origin_scene_node->createChildSceneNode(); const auto mask = m_mask.lock(); const auto* const material_name = mask != nullptr ? "Negato_mask" : (*m_classification == std::string("post")) ? "Negato" : "Negato_pre"; if(mask) { m_mask_texture = std::make_shared(mask.get_shared()); } // Instantiation of the planes for(auto& plane : m_planes) { plane.first = std::make_shared( this->get_id(), m_negato_scene_node, this->get_scene_manager(), m_3d_ogre_texture, m_mask_texture, m_filtering, material_name, m_border, m_slices_cross, 1.0F, static_cast(*m_depth_bias) ); } if(m_auto_reset_camera) { this->render_service()->reset_camera_coordinates(m_layer_id); } this->update_image(true); if(m_interactive) { auto interactor = std::dynamic_pointer_cast(this->get_sptr()); this->layer()->add_interactor(interactor, m_priority); m_picking_cross = std::make_unique( this->get_id(), *this->get_scene_manager(), *m_negato_scene_node ); } // Set the visibility of the 3D Negato this->set_visible(visible()); } //------------------------------------------------------------------------------ void negato::stopping() { this->render_service()->make_current(); if(m_interactive) { auto interactor = std::dynamic_pointer_cast(this->get_sptr()); this->layer()->remove_interactor(interactor); } m_picked_plane.reset(); std::ranges::for_each(m_planes, [](auto& _p){_p.first.reset();}); auto* const scene_manager = this->get_scene_manager(); m_negato_scene_node->removeAndDestroyAllChildren(); scene_manager->destroySceneNode(m_negato_scene_node); m_negato_scene_node = nullptr; m_origin_scene_node->removeAndDestroyAllChildren(); scene_manager->destroySceneNode(m_origin_scene_node); m_origin_scene_node = nullptr; m_picking_cross.reset(); m_mask_texture.reset(); m_3d_ogre_texture.reset(); m_gpu_tf.reset(); adaptor::deinit(); } //------------------------------------------------------------------------------ void negato::updating() { if(update_needed(update_flags::IMAGE)) { this->update_image(true); } else if(update_needed(update_flags::IMAGE_BUFFER)) { this->update_image(false); } else if(update_needed(update_flags::TF)) { this->update_tf(); } this->update_done(); this->request_render(); } //------------------------------------------------------------------------------ void negato::update_image(bool _new) { if(!m_3d_ogre_texture) { // The adaptor hasn't start yet (the window is maybe not visible) return; } this->render_service()->make_current(); int axial_idx = 0; int frontal_idx = 0; int sagittal_idx = 0; { const auto image = m_image.lock(); if(!data::helper::medical_image::check_image_validity(image.get_shared())) { return; } // Update the texture m_3d_ogre_texture->update(); if(m_mask_texture) { m_mask_texture->update(); } if(_new) { const auto spacing = sight::viz::scene3d::utils::get_ogre_spacing(*image); m_origin_scene_node->setPosition(sight::viz::scene3d::utils::get_ogre_origin(*image)); m_origin_scene_node->setOrientation(sight::viz::scene3d::utils::get_ogre_orientation(*image)); // Fits the planes to the new texture for(const auto& plane : m_planes) { plane.first->update(plane.second, spacing, m_enable_alpha); plane.first->set_query_flags(m_query_flags); plane.first->set_render_queuer_group_and_priority(sight::viz::scene3d::rq::NEGATO_WIDGET_ID, 0); } // Update Slice namespace medical_image = data::helper::medical_image; axial_idx = std::max(0, int(medical_image::get_slice_index(*image, axis_t::axial).value_or(0))); frontal_idx = std::max(0, int(medical_image::get_slice_index(*image, axis_t::frontal).value_or(0))); sagittal_idx = std::max(0, int(medical_image::get_slice_index(*image, axis_t::sagittal).value_or(0))); } } if(_new) { this->change_slice_index(axial_idx, frontal_idx, sagittal_idx); this->set_visible(visible()); // Update transfer function in Gpu programs this->update_tf(); } } //------------------------------------------------------------------------------ void negato::change_slice_index(int _axial_index, int _frontal_index, int _sagittal_index) { const auto image = m_image.lock(); this->render_service()->make_current(); auto img_size = image->size(); // Sometimes, the image can contain only one slice, // it results into a division by 0 when the range is transformed between [0-1]. // So we increase the image size to 2 to divide by 1. img_size[0] = img_size[0] == 1 ? 2 : img_size[0]; img_size[1] = img_size[1] == 1 ? 2 : img_size[1]; img_size[2] = img_size[2] == 1 ? 2 : img_size[2]; m_current_slice_index = { static_cast(_sagittal_index) / (static_cast(img_size[0] - 1)), static_cast(_frontal_index) / (static_cast(img_size[1] - 1)), static_cast(_axial_index) / (static_cast(img_size[2] - 1)) }; for(auto& plane : m_planes) { plane.first->change_slice(m_current_slice_index); } this->signal(signals::SLICE_INDEX_CHANGED)->emit(); this->request_render(); } //------------------------------------------------------------------------------ void negato::update_slices_from_world(double _x, double _y, double _z) { const auto image = m_image.lock(); Ogre::Vector3 point = {static_cast(_x), static_cast(_y), static_cast(_z)}; Ogre::Vector3i slice_idx; try { slice_idx = sight::viz::scene3d::utils::world_to_slices(*image, point); } catch(core::exception& e) { SIGHT_WARN("Cannot update slice index: " << e.what()); return; } const auto sig = image->signal(data::image::SLICE_INDEX_MODIFIED_SIG); sig->async_emit(slice_idx[2], slice_idx[1], slice_idx[0]); } //------------------------------------------------------------------------------ void negato::update_tf() { this->render_service()->make_current(); m_gpu_tf->update(); // Sends the TF texture to the negato-related passes std::ranges::for_each(m_planes, [this](auto& _p){_p.first->set_tf_data(*m_gpu_tf.get());}); } //------------------------------------------------------------------------------ void negato::update_windowing(double _dw, double _dl) { const double new_window = m_initial_window + _dw; const double new_level = m_initial_level - _dl; { const auto image = m_image.const_lock(); const auto tf = m_tf.lock(); tf->set_window(std::copysign(std::max(1.0, std::abs(new_window)), new_window)); tf->set_level(new_level); tf->async_emit(data::transfer_function::WINDOWING_MODIFIED_SIG, new_window, new_level); } } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/negato.hpp000066400000000000000000000211611503402212300217470ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2025 IRCAD France * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This is the base interface for 2D and 3D negato classes. */ class negato : public sight::viz::scene3d::adaptor, public sight::viz::scene3d::transformable, public sight::viz::scene3d::interactor::base { public: using axis_t = data::helper::medical_image::axis_t; struct signals { using slice_index_changed_t = core::com::signal; using picked_voxel_t = core::com::signal; static inline const core::com::signals::key_t SLICE_INDEX_CHANGED = "slice_index_changed"; static inline const core::com::signals::key_t PICKED_VOXEL = "picked_voxel"; }; struct slots { static inline const core::com::slots::key_t UPDATE_IMAGE = "update_image"; static inline const core::com::slots::key_t UPDATE_IMAGE_BUFFER = "update_image_buffer"; static inline const core::com::slots::key_t UPDATE_TF = "update_tf"; static inline const core::com::slots::key_t SLICE_TYPE = "slice_type"; static inline const core::com::slots::key_t SLICE_INDEX = "slice_index"; static inline const core::com::slots::key_t UPDATE_SLICES_FROM_WORLD = "update_slices_from_world"; }; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::image::MODIFIED of s_IMAGE_INOUT to service::slots::UPDATE * Connect data::image::BUFFER_MODIFIED of s_IMAGE_INOUT to service::slots::UPDATE * Connect data::image::SLICE_TYPE_MODIFIED of s_IMAGE_INOUT to SLICE_TYPE * Connect data::image::SLICE_INDEX_MODIFIED of s_IMAGE_INOUT to SLICE_INDEX */ service::connections_t auto_connections() const override; /// Creates the service and initializes slots. negato() noexcept; /// Destroys the adaptor. ~negato() noexcept override = default; protected: /// Configures the service. void configuring(const config_t& _config) override; /// Instantiates the texture, material, pass and texture unit state. /// Sets the connection between attached data and the received slot. void starting() final; /// Remove all resources, disconnects connections. void stopping() final; /// Uploads the input image into the texture buffer and recomputes the negato geometry. void updating() override; /** * @brief Picks the intensity value at the (_x, _y) screen position. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void pick_intensity(int _x, int _y); /** * @brief Updates the transfer function window and level by adding the input values. * @param _dw window delta. * @param _dl level delta. */ void update_windowing(double _dw, double _dl); /// Updates the displayed transfer function. void update_tf(); private: /// Sets the filtering type. void set_filtering(sight::viz::scene3d::plane::filter_t _filtering); /** Uploads the input image into the texture buffer and recomputes the negato geometry. * @param _new true if the image was reallocated, false if only pixel values changed. */ void update_image(bool _new); /** * @brief SLOT: updates the image slice type. * @param _from origin of the orientation. * @param _to destination of the orientation. */ virtual void change_slice_type(int _from, int _to) = 0; /** * @brief SLOT: updates the image slice index. * @param _axial_index new axial slice index. * @param _frontal_index new frontal slice index. * @param _sagittal_index new sagittal slice index. */ void change_slice_index(int _axial_index, int _frontal_index, int _sagittal_index); /** * @brief SLOT: Update slices index to match x,y,z world coordinates * @param _x world coordinates in double. * @param _y world coordinates in double. * @param _z world coordinates in double. */ void update_slices_from_world(double _x, double _y, double _z); protected: /// Contains the widget displayed to pick intensities. std::unique_ptr m_picking_cross; /// Stores the planes and their axes on which we will apply our texture. std::vector > m_planes; /// Contains the plane that the user is currently interacting with. sight::viz::scene3d::plane::sptr m_picked_plane {nullptr}; /// Contains the scene node allowing to move the entire negato. Ogre::SceneNode* m_negato_scene_node {nullptr}; /// Defines if the other slices planes are displayed. bool m_slices_cross {false}; /// Stores the current slice index for each axis. std::array m_current_slice_index {0.F, 0.F, 0.F}; /// Defines the transfer function window value at the time the interaction started. double m_initial_window {0.F}; /// Defines the transfer function level value at the time the interaction started. double m_initial_level {0.F}; /// Defines the mouse position at the time the windowing interaction started. Ogre::Vector2i m_initial_pos {-1, -1}; /// Enables/disables the usage of the transfer function alpha channel. bool m_enable_alpha {false}; /// Enables whether the camera must be auto reset when a mesh is updated or not. bool m_auto_reset_camera {false}; /// Defines the mask used for picking request. std::uint32_t m_query_flags {Ogre::SceneManager::ENTITY_TYPE_MASK}; sight::data::ptr m_image {this, "image"}; sight::data::ptr m_tf {this, "tf"}; private: /// Contains the texture which will be displayed on the negato. sight::viz::scene3d::texture::sptr m_3d_ogre_texture; /// Contains the optional mask texture which will be applied on top of the negato. sight::viz::scene3d::texture::sptr m_mask_texture; /// Contains and manages the textures used to store the transfer function (GPU point of view). sight::viz::scene3d::transfer_function::uptr m_gpu_tf; /// Enables whether or not interactions are enabled on the negato. bool m_interactive {true}; /// Contains the scene node used for image origin and orientation. Ogre::SceneNode* m_origin_scene_node {nullptr}; /// Defines the filtering type for this negato. sight::viz::scene3d::plane::filter_t m_filtering {sight::viz::scene3d::plane::filter_t::none}; /// Defines if the plane border is used or not. bool m_border {true}; /// Sets the order in which interactions take place in the scene. int m_priority {1}; enum class update_flags : std::uint8_t { IMAGE, IMAGE_BUFFER, TF }; sight::data::ptr m_mask {this, "mask", true}; sight::data::property m_classification {this, "classification", std::string("post")}; sight::data::property m_depth_bias {this, "depth_bias", 0.}; }; //------------------------------------------------------------------------------ inline void negato::set_filtering(sight::viz::scene3d::plane::filter_t _filtering) { m_filtering = _filtering; } } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/negato2d.cpp000066400000000000000000000147571503402212300222050ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/negato2d.hpp" #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { //------------------------------------------------------------------------------ negato2d::negato2d() noexcept { new_signal(signals::SLICE_INDEX_CHANGED); m_planes.resize(1); } //------------------------------------------------------------------------------ void negato2d::configuring(const config_t& _config) { negato::configuring(_config); const auto axis = core::ptree::get_and_deprecate( _config, CONFIG + "orientation", CONFIG + "sliceIndex", "26.0", "axial" ); if(axis == "axial") { m_planes[0].second = axis_t::z_axis; } else if(axis == "frontal") { m_planes[0].second = axis_t::y_axis; } else if(axis == "sagittal") { m_planes[0].second = axis_t::x_axis; } m_slices_cross = core::ptree::get_and_deprecate( _config, CONFIG + "cross", CONFIG + "slicesCross", "26.0", true ); } //------------------------------------------------------------------------------ void negato2d::change_slice_type(int _from, int _to) { const auto image = m_image.lock(); const auto to_axis = static_cast(_to); const auto from_axis = static_cast(_from); const auto plane_axis = m_planes[0].second; const auto new_axis = m_planes[0].second == to_axis ? from_axis : plane_axis == from_axis ? to_axis : plane_axis; if(plane_axis != new_axis) { m_planes[0].second = new_axis; this->render_service()->make_current(); const auto spacing = sight::viz::scene3d::utils::get_ogre_spacing(*image); m_planes[0].first->update(m_planes[0].second, spacing, m_enable_alpha); // Update threshold if necessary this->update_tf(); m_planes[0].first->change_slice(m_current_slice_index); this->request_render(); } } //------------------------------------------------------------------------------ void negato2d::mouse_move_event(mouse_button _button, modifier /*_mods*/, int _x, int _y, int /*_dx*/, int /*_dy*/) { if(m_picked) { if(_button == mouse_button::left) { this->pick_intensity(_x, _y); } else if(_button == mouse_button::right) { const auto dx = static_cast(_x - m_initial_pos[0]); const auto dy = static_cast(m_initial_pos[1] - _y); this->update_windowing(dx, dy); } this->layer()->cancel_further_interaction(); } } //------------------------------------------------------------------------------ void negato2d::button_press_event(mouse_button _button, modifier /*_mods*/, int _x, int _y) { namespace interactor_3d = sight::viz::scene3d::interactor; m_picking_cross->set_visible(false); if(_button == mouse_button::left) { this->pick_intensity(_x, _y); } else if(_button == mouse_button::right && interactor_3d::base::is_in_layer( _x, _y, this->layer(), m_layer_order_dependant )) { const auto tf = m_tf.const_lock(); m_initial_level = tf->level(); m_initial_window = tf->window(); m_initial_pos = {_x, _y}; m_picked = true; } if(m_picked) { this->layer()->cancel_further_interaction(); } } //------------------------------------------------------------------------------ void negato2d::button_release_event(mouse_button /*_button*/, modifier /*_mods*/, int /*_x*/, int /*_y*/) { m_picked = false; m_picking_cross->set_visible(false); this->signal(signals::PICKED_VOXEL)->async_emit(""); } //------------------------------------------------------------------------------ void negato2d::set_visible(bool _visible) { if(m_negato_scene_node != nullptr) { m_negato_scene_node->setVisible(_visible); this->request_render(); } } //------------------------------------------------------------------------------ void negato2d::pick_intensity(int _x, int _y) { Ogre::SceneManager* sm = this->get_scene_manager(); const auto result = sight::viz::scene3d::utils::pick_object(_x, _y, Ogre::SceneManager::ENTITY_TYPE_MASK, *sm); if(result.has_value()) { if(m_planes[0].first->get_movable_object() == result->first) { m_picked = true; const auto image = m_image.lock(); if(!data::helper::medical_image::check_image_validity(image.get_shared())) { return; } const auto cross_lines = m_planes[0].first->compute_cross(result->second, *image); m_picking_cross->update(cross_lines[0], cross_lines[1], cross_lines[2], cross_lines[3]); const auto picking_text = sight::viz::scene3d::utils::pick_image(*image, result->second); this->signal(signals::PICKED_VOXEL)->async_emit(picking_text); this->request_render(); } } } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/negato2d.hpp000066400000000000000000000154521503402212300222030ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor displays a 2D negato. * * @section Signals Signals * - \b sliceIndexChanged(): emitted when the slice index changed. * * @section Slots Slots * - \b sliceType(int, int): updates image slice index . * - \b sliceIndex(int, int, int): updates image slice type. * - \b update_visibility(bool): shows or hides the negato. * - \b toggle_visibility(): toggle whether the negato is shown or not. * - \b show(): shows the negato. * - \b hide(): hides the negato. * - \b update_slices_from_world(double, double, double): updates image slices indexes according to a 3d world point * or landmark. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Input Input: * - \b image [sight::data::image]: image to display. * - \b mask [sight::data::image] (optional): mask to apply onto the image. Values < 0.5 are considered masked. * * @subsection In-Out In-Out * - \b tf [sight::data::transfer_function] (optional): the current TransferFunction. If it is not defined, we use the * image's default transferFunction (CT-GreyLevel). * * @subsection Configuration Configuration: * - \b orientation (optional, axial/frontal/sagittal, default=axial): orientation of the negato. * - \b filtering (optional, none/linear/anisotropic, default=none): texture filter type of the negato. * - \b tf_alpha (optional, bool, default=false): if true, the alpha channel of the transfer function is used. * - \b border (optional, bool, default=true): displays a border around the plane. * - \b cross (optional, bool, default=true): display the two other slices location as two lines. * - \b transform (optional, string, default=""): the name of the Ogre transform node where to attach the negato, as it * was specified in the transform adaptor. * - \b interactive (optional, bool, default=false): enables interactions on the negato. * * @subsection Properties Properties: * - \b classification (optional, pre/post, default=pre): classification of voxels. "pre" means the filtering is applied * after the sampling of the transfer function, and "post" after. When using labelled images, it is highly recommended * to use "pre", otherwise it is likely that class of objects can be confounded. * - \b visible (optional, bool, default=true): the visibility of the adaptor. * - \b depth_bias: useful to superimpose multiple negatos on top of each other */ class negato2d final : public sight::module::viz::scene3d::adaptor::negato { public: using axis_t = data::helper::medical_image::axis_t; /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(negato2d, sight::module::viz::scene3d::adaptor::negato); /// Creates the service and initializes slots. negato2d() noexcept; /// Destroys the adaptor. ~negato2d() noexcept final = default; protected: /// Configures the service. void configuring(const config_t& _config) final; /** * @brief Sets the negato visibility. * @param _visible the visibility status of the negato. */ void set_visible(bool _visible) final; private: /** * @brief Interacts with the negato if it was picked by pressing any mouse button. * * Interactions will take place while holding down the button. The following actions are available: * - Left mouse click: shows a cross widget to select a voxel and retrieve its intensity. * @param _button mouse button pressed. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. * @param _dx the cursor's width displacement since the last event. * @param _dy the cursor's height displacement since the last event. */ void mouse_move_event(mouse_button _button, modifier /*_mods*/, int _x, int _y, int _dx, int _dy) final; /** * @brief Attempts to pick the negato and starts interactions if picking was successful. * @param _button mouse button pressed. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void button_press_event(mouse_button _button, modifier /*_mods*/, int _x, int _y) final; /// Ends all interactions, regardless of the input. void button_release_event(mouse_button /*_button*/, modifier /*_mods*/, int /*_x*/, int /*_y*/) final; /// Sets the filtering type. void set_filtering(sight::viz::scene3d::plane::filter_t _filtering); /** Uploads the input image into the texture buffer and recomputes the negato geometry. * @param _new true if the image was reallocated, false if only pixel values changed. */ void update_image(bool _new); /** * @brief SLOT: updates the image slice type. * @param _from origin of the orientation. * @param _to destination of the orientation. */ void change_slice_type(int _from, int _to) override; /** * @brief Picks the intensity value at the (_x, _y) screen position. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void pick_intensity(int _x, int _y); /// True if the plane is being picked bool m_picked {false}; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/negato2d_camera.cpp000066400000000000000000000465721503402212300235150ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2019-2025 IRCAD France * Copyright (C) 2019-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/negato2d_camera.hpp" #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { static const core::com::slots::key_t RESET_CAMERA_SLOT = "reset_camera"; static const core::com::slots::key_t RESIZE_VIEWPORT_SLOT = "resize_viewport"; static const core::com::slots::key_t CHANGE_ORIENTATION_SLOT = "changeOrientation"; namespace interactor_3d = sight::viz::scene3d::interactor; //----------------------------------------------------------------------------- negato2d_camera::negato2d_camera() noexcept { new_slot(RESET_CAMERA_SLOT, &negato2d_camera::reset_camera, this); new_slot(RESIZE_VIEWPORT_SLOT, &negato2d_camera::resize_viewport, this); new_slot(CHANGE_ORIENTATION_SLOT, &negato2d_camera::change_orientation, this); } //----------------------------------------------------------------------------- void negato2d_camera::configuring() { this->configure_params(); const config_t config = this->get_config(); m_priority = config.get(CONFIG + "priority", m_priority); m_layer_order_dependant = config.get(CONFIG + "layerOrderDependant", m_layer_order_dependant); const std::string orientation = config.get(CONFIG + "orientation", "sagittal"); SIGHT_ERROR_IF( "Unknown orientation: '" + orientation + "'. Orientation can be either 'axial', 'frontal' or 'sagittal'.", orientation != "axial" && orientation != "frontal" && orientation != "sagittal" ); if(orientation == "axial") { m_axis = axis_t::z_axis; } else if(orientation == "frontal") { m_axis = axis_t::y_axis; } else if(orientation == "sagittal") { m_axis = axis_t::x_axis; } m_margin = config.get(CONFIG + "margin", m_margin); m_scale = config.get(CONFIG + "scale", m_scale); m_is_interactive = config.get(CONFIG + "interactive", m_is_interactive); } //----------------------------------------------------------------------------- void negato2d_camera::starting() { adaptor::init(); const auto layer = this->layer(); auto interactor = std::dynamic_pointer_cast(this->get_sptr()); layer->add_interactor(interactor, m_priority); Ogre::Camera* const cam = this->layer()->get_default_camera(); cam->setProjectionType(Ogre::ProjectionType::PT_ORTHOGRAPHIC); m_layer_connection.connect( this->layer(), sight::viz::scene3d::layer::RESIZE_LAYER_SIG, this->get_sptr(), RESIZE_VIEWPORT_SLOT ); this->reset_camera(); } //----------------------------------------------------------------------------- void negato2d_camera::updating() noexcept { // Only used for the TF helper. } //----------------------------------------------------------------------------- void negato2d_camera::stopping() { m_layer_connection.disconnect(); const auto layer = this->layer(); auto interactor = std::dynamic_pointer_cast(this->get_sptr()); layer->remove_interactor(interactor); adaptor::deinit(); } // ---------------------------------------------------------------------------- service::connections_t negato2d_camera::auto_connections() const { service::connections_t connections = { {IMAGE_INOUT, data::image::MODIFIED_SIG, RESET_CAMERA_SLOT}, {IMAGE_INOUT, data::image::SLICE_TYPE_MODIFIED_SIG, CHANGE_ORIENTATION_SLOT} }; return connections + adaptor::auto_connections(); } //----------------------------------------------------------------------------- void negato2d_camera::wheel_event(modifier _modifier, double _delta, int _x, int _y) { if(!m_is_interactive) { return; } const auto layer = this->layer(); if(interactor_3d::base::is_in_layer(_x, _y, layer, m_layer_order_dependant)) { // CTRL + wheel = Zoom in/out. if(_modifier == modifier::control) { const auto* const viewport = layer->get_viewport(); auto* const camera = layer->get_default_camera(); auto* const cam_node = camera->getParentNode(); constexpr float mouse_wheel_scale = 0.05F; const float zoom_amount = static_cast(-_delta) * mouse_wheel_scale; // Compute the mouse's position in the camera's view. const Ogre::Vector3 screen_pos(static_cast(_x), static_cast(_y), Ogre::Real(0)); auto mouse_pos_view = sight::viz::scene3d::helper::camera::convert_screen_space_to_view_space(*camera, screen_pos); // Zoom in. const float ortho_height = camera->getOrthoWindowHeight(); const float new_ortho_height = ortho_height + (ortho_height / zoom_amount); const float clamped_height = std::max(new_ortho_height, 1e-7F); // Make sure the height is strictly // greater // than 0 const auto vp_width = static_cast(viewport->getActualWidth()); const auto vp_height = static_cast(viewport->getActualHeight()); SIGHT_ASSERT("Width and height should be strictly positive", vp_width > 0 && vp_height > 0); camera->setAspectRatio(vp_width / vp_height); camera->setOrthoWindowHeight(clamped_height); // Compute the mouse's position in the zoomed view. auto new_mouse_pos_view = sight::viz::scene3d::helper::camera::convert_screen_space_to_view_space(*camera, screen_pos); // Translate the camera back to the cursor's previous position. if(auto* origin_node = layer->camera_origin_node(); origin_node != nullptr) { const auto camera_to_origin = get_camera_to_origin_transform(); mouse_pos_view = camera_to_origin * mouse_pos_view; new_mouse_pos_view = camera_to_origin * new_mouse_pos_view; } cam_node->translate(mouse_pos_view - new_mouse_pos_view); m_has_moved = true; this->request_render(); } // Wheel alone or other modifier -> moving along slices (SHIFT to speed-up) else { namespace medical_image = data::helper::medical_image; const auto image = m_image.lock(); // Get Index const int current_index = static_cast(medical_image::get_slice_index(*image, m_axis).value_or(0)); const int max_slice = static_cast(image->size()[m_axis] - 1); if(max_slice <= 0) { // Do nothing, image doesn't have slices. return; } SIGHT_ASSERT("Slice index field missing", current_index != -1); // From: https://doc.qt.io/qt-5/qwheelevent.html#angleDelta // Most mouse types work in steps of 15 degrees, // in which case the delta value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees. // Here we assume that each 120 units of delta correspond to one increment of mouse wheel // So we move forward/backward of 1 slice each 120 units. int slice_move = static_cast(_delta) / 120; // Speed up SHIFT+ wheel: "scrolls" 5% of total slices at each wheel move. if(_modifier == modifier::shift) { slice_move *= int(std::round(static_cast(max_slice) * 5.F / 100.F)); } // TODO: We may test for finer-resolution wheels and wait for another event before moving. auto new_slice_index = current_index + slice_move; // Limit to [0: max_slice]. if(new_slice_index > max_slice) { new_slice_index = max_slice; } else if(new_slice_index < 0) { new_slice_index = 0; } medical_image::set_slice_index(*image, m_axis, new_slice_index); // Get up-to-date index values. const std::array idx { static_cast(medical_image::get_slice_index(*image, axis_t::sagittal).value_or(0)), static_cast(medical_image::get_slice_index(*image, axis_t::frontal).value_or(0)), static_cast(medical_image::get_slice_index(*image, axis_t::axial).value_or(0)) }; m_has_moved = true; // Send signal. auto sig = image->signal( data::image::SLICE_INDEX_MODIFIED_SIG ); sig->async_emit(idx[2], idx[1], idx[0]); } } } //------------------------------------------------------------------------------ void negato2d_camera::pinch_gesture_event(double _scale_factor, int _center_x, int _center_y) { if(!m_is_interactive) { return; } // * 42 / 0.05 is a magic number to get a similar behavior as the mouse wheel wheel_event(modifier::control, (_scale_factor * 42) / 0.05, _center_x, _center_y); } //------------------------------------------------------------------------------ void negato2d_camera::pan_gesture_move_event(int _x, int _y, int _dx, int _dy) { if(!m_is_interactive) { return; } m_is_interacting = true; mouse_move_event(mouse_button::middle, {}, _x, _y, _dx, _dy); } //------------------------------------------------------------------------------ void negato2d_camera::pan_gesture_release_event(int /*_x*/, int /*_y*/, int /*_dx*/, int /*_dy*/) { m_is_interacting = false; } // ---------------------------------------------------------------------------- void negato2d_camera::mouse_move_event( interactor_3d::base::mouse_button _button, modifier /*_mods*/, int _x, int _y, int _dx, int _dy ) { if(!m_is_interactive) { return; } if(m_is_interacting && _button == mouse_button::middle) { const auto layer = this->layer(); auto* const camera = layer->get_default_camera(); auto* const cam_node = camera->getParentNode(); const Ogre::Vector3 delta_screen_pos(static_cast(_x - _dx), static_cast(_y - _dy), Ogre::Real(0)); const Ogre::Vector3 screen_pos(static_cast(_x), static_cast(_y), Ogre::Real(0)); auto previous_mouse_pos_view = sight::viz::scene3d::helper::camera::convert_screen_space_to_view_space(*camera, delta_screen_pos); auto mouse_pos_view = sight::viz::scene3d::helper::camera::convert_screen_space_to_view_space(*camera, screen_pos); if(auto* origin_node = layer->camera_origin_node(); origin_node != nullptr) { const auto camera_to_origin = get_camera_to_origin_transform(); mouse_pos_view = camera_to_origin * mouse_pos_view; previous_mouse_pos_view = camera_to_origin * previous_mouse_pos_view; } cam_node->translate(mouse_pos_view - previous_mouse_pos_view); m_has_moved = true; this->request_render(); } } //----------------------------------------------------------------------------- void negato2d_camera::button_press_event(interactor_3d::base::mouse_button _button, modifier /*_mods*/, int _x, int _y) { if(!m_is_interactive) { return; } const auto layer = this->layer(); if(_button == mouse_button::middle) { m_is_interacting = interactor_3d::base::is_in_layer(_x, _y, layer, m_layer_order_dependant); } } //----------------------------------------------------------------------------- void negato2d_camera::button_release_event( interactor_3d::base::mouse_button /*_button*/, modifier /*_mods*/, int /*_x*/, int /*_y*/ ) { m_is_interacting = false; } //----------------------------------------------------------------------------- void negato2d_camera::key_press_event(int _key, modifier /*_mods*/, int _x, int _y) { if(!m_is_interactive) { return; } const auto layer = this->layer(); if(interactor_3d::base::is_in_layer(_x, _y, layer, m_layer_order_dependant) && (_key == 'R' || _key == 'r')) { this->reset_camera(); } } //----------------------------------------------------------------------------- void negato2d_camera::reset_camera() { // This method is called when the image buffer is modified, // we need to retrieve the TF here if it came from the image. const auto layer = this->layer(); const auto* const viewport = layer->get_viewport(); auto* const camera = layer->get_default_camera(); auto* const cam_node = camera->getParentNode(); const auto vp_width = static_cast(viewport->getActualWidth()); const auto vp_height = static_cast(viewport->getActualHeight()); SIGHT_ASSERT("Width and height should be strictly positive", vp_width > 0 && vp_height > 0); const float vp_ratio = vp_width / vp_height; camera->setAspectRatio(vp_ratio); // HACK: Temporarily set the near clip distance here because the layer doesn't handle orthographic cameras. //camera->setNearClipDistance(1e-3F); cam_node->setPosition(Ogre::Vector3::ZERO); cam_node->resetOrientation(); const auto image = m_image.const_lock(); const auto& size = image->size(); const auto& spacing = image->spacing(); float ratio = 0; double width = 0.; double height = 0.; switch(m_axis) { case axis_t::x_axis: if(size[1] <= 0 || size[2] <= 0) { return; } cam_node->rotate(Ogre::Vector3::UNIT_Y, Ogre::Degree(-90.F)); cam_node->rotate(Ogre::Vector3::UNIT_Z, Ogre::Degree(-90.F)); width = static_cast(size[1]) * spacing[1]; height = static_cast(size[2]) * spacing[2]; ratio = static_cast(width / height); break; case axis_t::y_axis: if(size[0] <= 0 || size[2] <= 0) { return; } cam_node->rotate(Ogre::Vector3::UNIT_X, Ogre::Degree(90.F)); width = static_cast(size[0]) * spacing[0]; height = static_cast(size[2]) * spacing[2]; ratio = static_cast(width / height); break; case axis_t::z_axis: if(size[0] <= 0 || size[1] <= 0) { return; } cam_node->rotate(Ogre::Vector3::UNIT_Z, Ogre::Degree(180.F)); cam_node->rotate(Ogre::Vector3::UNIT_Y, Ogre::Degree(180.F)); width = static_cast(size[0]) * spacing[0]; height = static_cast(size[1]) * spacing[1]; ratio = static_cast(width / height); break; } if(vp_ratio > ratio || m_scale) { const auto h = static_cast(height); // Zoom out the camera with the margin, allow the image to not be stuck on the viewport. camera->setOrthoWindowHeight(h + h * m_margin); } else { const auto w = static_cast(width); // Zoom out the camera with the margin, allow the image to not be stuck on the viewport. camera->setOrthoWindowWidth(w + w * m_margin); } const auto axis = static_cast(m_axis); Ogre::Vector3 cam_pos( static_cast(static_cast(size[0]) * spacing[0] * 0.5), static_cast(static_cast(size[1]) * spacing[1] * 0.5), static_cast(static_cast(size[2]) * spacing[2] * 0.5) ); const auto distance_pos = static_cast(-static_cast(size[axis]) * spacing[axis]); // Special case, distance is equal to 0, so move camera to -1. cam_pos[axis] = core::is_equal(0.F, distance_pos) ? -1.F : distance_pos; // Special case: single slice image, move the camera to -1.0 if(size[axis] == 1) { cam_pos[axis] = -1.F; } cam_node->setPosition(cam_pos); if(auto* origin_node = layer->camera_origin_node(); origin_node != nullptr) { origin_node->setPosition(sight::viz::scene3d::utils::get_ogre_origin(*image)); origin_node->setOrientation(sight::viz::scene3d::utils::get_ogre_orientation(*image)); } m_has_moved = false; this->request_render(); } //------------------------------------------------------------------------------ void negato2d_camera::resize_viewport() { if(!m_has_moved) { this->reset_camera(); } } //----------------------------------------------------------------------------- void negato2d_camera::change_orientation(int _from, int _to) { const auto to_axis = static_cast(_to); const auto from_axis = static_cast(_from); if(m_axis == to_axis) { m_axis = from_axis; this->reset_camera(); } else if(m_axis == from_axis) { m_axis = to_axis; this->reset_camera(); } } //------------------------------------------------------------------------------ Ogre::Matrix4 negato2d_camera::get_camera_to_origin_transform() const { const auto image = m_image.const_lock(); const auto origin = sight::viz::scene3d::utils::get_ogre_origin(*image); const auto& orientation = image->orientation(); const Ogre::Matrix4 world_transform = { static_cast(orientation[0]), static_cast(orientation[1]), static_cast(orientation[2]), origin[0], static_cast(orientation[3]), static_cast(orientation[4]), static_cast(orientation[5]), origin[1], static_cast(orientation[6]), static_cast(orientation[7]), static_cast(orientation[8]), origin[2], 0, 0, 0, 1 }; return world_transform.inverse(); } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/negato2d_camera.hpp000066400000000000000000000237341503402212300235150ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2019-2025 IRCAD France * Copyright (C) 2019-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor lets the user move an orthographic camera to visualize medical images in 2D. * * The camera can be moved along its screen plane and zoom on a precise scene location. * For more convenience this adaptor can make use of an optional input image to reset the camera's orientation * when the image's slice type changes. * Although this service was designed with negato visualization in mind, it could be used to render regular scenes * from a 2D orthographic perspective. * * @warning may not work as intended when used with another camera adaptor on the same layer and must be started * after all others one. * * @section Slots Slots * - \b reset_camera(): zooms out the camera to see the whole scene. * - \b changeOrientation(int,int): sets the camera's orientation to one of the image's axes. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection In-Out In-Out * - \b image [sight::data::image](mandatory): image viewed in negato mode, used for auto connections only. * Modification signals can be used to reset the camera's position and orientation. Useless without * auto_connect="true". * * @subsection Configuration Configuration: * - \b priority (optional, int, default=0): interaction priority, higher priority interactions are performed first. * - \b layerOrderDependant (optional, bool, default=true): define if interaction must take into account above layers. * - \b orientation (optional, sagittal/frontal/axial, default=sagittal): the camera's orientation at start. * - \b margin (optional, default=0.1): margin to the border of the viewport, in percentage of the highest of width * or height. * - \b scale (optional, default=false):whether to scale the orthographic plane to the viewport or * keep it at its original resolution. * - \b interactive (optional, default=true), enable or disable interactions. */ class negato2d_camera final : public sight::viz::scene3d::adaptor, public sight::viz::scene3d::interactor::base { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(negato2d_camera, sight::viz::scene3d::adaptor); /// Creates the service and initializes slots. negato2d_camera() noexcept; /// Destroys the adaptor. ~negato2d_camera() noexcept override = default; /** * @brief Moving along slices (SHIFT to speed-up) or Zooms in the scene at the current cursor position. * @param _delta distance that the wheel is rotated. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void wheel_event(modifier /*_modifier*/, double _delta, int _x, int _y) final; /** * @brief Zooms in the scene at the current cursor position. * @param _scale_factor distance of the fingers * @param _center_x the width coordinate of the center of the pinch * @param _center_y the height coordinate of the center of the pinch */ void pinch_gesture_event(double _scale_factor, int _center_x, int _center_y) final; /** * @brief Moves the camera along the projection plane. * @param _x the last width coordinate of the finger * @param _y the last height coordinate of the finger * @param _dx width displacement of the finger since the last event. * @param _dy height displacement of the finger since the last event. */ void pan_gesture_move_event(int _x, int _y, int _dx, int _dy) final; /** * @brief Ends Moving the camera along the projection plane. * @param _x the last width coordinate of the finger * @param _y the last height coordinate of the finger * @param _dx width displacement of the finger since the last event. * @param _dy height displacement of the finger since the last event. */ void pan_gesture_release_event(int _x, int _y, int _dx, int _dy) final; /** * @brief Interacts with the negato if it was picked by pressing any mouse button. * * Interactions will take place while holding down the button. The following actions are available: * - Middle mouse click: moves the camera along the projection plane. * - Right mouse click: adjust the transfer function level and window by moving * the mouse up/down and left/right respectively. * * @param _button mouse button pressed. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void mouse_move_event( sight::viz::scene3d::interactor::base::mouse_button _button, modifier /*_mods*/, int _x, int _y, int _dx, int _dy ) final; /** * @brief Verifies if the button is pressed within the camera's viewport and enables mouse movements if that is the * case. * @param _button mouse button pressed. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void button_press_event( sight::viz::scene3d::interactor::base::mouse_button _button, modifier /*_mods*/, int _x, int _y ) final; /** * @brief Disables mouse movements. * @param _button mouse button pressed. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void button_release_event( sight::viz::scene3d::interactor::base::mouse_button _button, modifier /*_mods*/, int _x, int _y ) final; /** * @brief Resets the camera when the 'R' key is pressed. * @param _key key button pressed. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void key_press_event(int _key, modifier /*_mods*/, int _x, int _y) final; protected: /// Configures the layer, interaction priority and camera orientation. void configuring() final; /// Adds negato camera interactions to the layer. void starting() final; /** * @brief Proposals to connect service slots to associated object signals. * @return A map of each proposed connection. * * Connect data::image::MODIFIED_SIG of s_IMAGE_INPUT to RESET_CAMERA_SLOT * Connect data::image::SLICE_TYPE_MODIFIED_SIG of s_IMAGE_INPUT to CHANGE_ORIENTATION_SLOT * Connect data::image::SLICE_INDEX_MODIFIED_SIG of s_IMAGE_INPUT to MOVE_BACK_SLOT */ service::connections_t auto_connections() const final; /// Does nothing. void updating() noexcept final; /// Removes negato camera interactions from the layer. void stopping() final; private: using axis_t = data::helper::medical_image::axis_t; /// SLOT: resets the camera's zoom. void reset_camera(); /// SLOT: resets the display when resizing. void resize_viewport(); /** * @brief SLOT: sets the camera's orientation to one of the image's axes. * @param _from origin of the orientation. * @param _to destination of the orientation. */ void change_orientation(int _from, int _to); /// Updates the transfer function window and level by adding the input values. void update_windowing(double _dw, double _dl); /// Get the inverse transformation matrix from camera to origin Ogre::Matrix4 get_camera_to_origin_transform() const; /// Defines the current interaction status. bool m_is_interacting {false}; /// Defines the image current orientation. axis_t m_axis {axis_t::z_axis}; /// Defines the interaction priority. int m_priority {0}; /// Defines the transfer function window value at the time the interaction started. double m_initial_window {0.F}; /// Defines the transfer function level value at the time the interaction started. double m_initial_level {0.F}; /// Defines the mouse position at the time the windowing interaction started. Ogre::Vector2i m_initial_pos {-1, -1}; /// Defines the margin to the border of the viewport. float m_margin {0.1F}; /// This allows us to reset the camera when Qt refreshes the size of the viewport after the start of the adaptor bool m_has_moved {false}; /// Enable/disable scaling (like video adaptor). bool m_scale {false}; /// Enable/disable interactions. bool m_is_interactive {true}; /// Handles connection with the layer. core::com::helper::sig_slot_connection m_layer_connection; static constexpr std::string_view IMAGE_INOUT = "image"; sight::data::ptr m_image {this, IMAGE_INOUT}; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/negato3d.cpp000066400000000000000000000200711503402212300221700ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ // cspell:ignore NOLINTNEXTLINE NOLINT #include "module/viz/scene3d/adaptor/negato3d.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { //------------------------------------------------------------------------------ negato3d::negato3d() noexcept { new_slot(slots::SET_TRANSPARENCY, &negato3d::set_transparency, this); m_planes.resize(3); for(int orientation_num = 0 ; auto& plane : m_planes) { plane.second = static_cast(orientation_num++); } } //------------------------------------------------------------------------------ void negato3d::configuring(const config_t& _config) { negato::configuring(_config); static const std::string s_AUTORESET_CAMERA_CONFIG = CONFIG + "autoresetcamera"; m_auto_reset_camera = _config.get(s_AUTORESET_CAMERA_CONFIG, true); } //------------------------------------------------------------------------------ void negato3d::change_slice_type(int /*unused*/, int /*unused*/) { } //----------------------------------------------------------------------------- void negato3d::set_transparency(double _transparency) { SIGHT_ASSERT("Service not started", this->started()); const float opacity = 1.F - static_cast(_transparency); std::ranges::for_each(m_planes, [opacity](auto& _p){_p.first->set_entity_opacity(opacity);}); this->request_render(); } //------------------------------------------------------------------------------ void negato3d::set_visible(bool _visible) { std::ranges::for_each(m_planes, [_visible](auto& _p){_p.first->set_visible(_visible);}); if(m_auto_reset_camera) { this->render_service()->reset_camera_coordinates(m_layer_id); } this->request_render(); } //------------------------------------------------------------------------------ void negato3d::set_planes_query_flags(std::uint32_t _flags) { std::ranges::for_each(m_planes, [_flags](auto& _p){_p.first->set_query_flags(_flags);}); } //------------------------------------------------------------------------------ void negato3d::mouse_move_event(mouse_button _button, modifier /*_mods*/, int _x, int _y, int /*_dx*/, int /*_dy*/) { if(m_picked_plane) { if(_button == mouse_button::middle) { this->move_slices(_x, _y); } else if(_button == mouse_button::right) { const auto dx = static_cast(_x - m_initial_pos[0]); const auto dy = static_cast(m_initial_pos[1] - _y); this->update_windowing(dx, dy); } else if(_button == mouse_button::left) { this->pick_intensity(_x, _y); } this->layer()->cancel_further_interaction(); } } //------------------------------------------------------------------------------ void negato3d::button_press_event(mouse_button _button, modifier /*_mods*/, int _x, int _y) { m_picked_plane.reset(); m_picking_cross->set_visible(false); if(_button == mouse_button::middle) { this->move_slices(_x, _y); } else if(_button == mouse_button::right) { if(this->get_picked_slices(_x, _y) != std::nullopt) { const auto tf = m_tf.const_lock(); m_initial_level = tf->level(); m_initial_window = tf->window(); m_initial_pos = {_x, _y}; } } else if(_button == mouse_button::left) { this->pick_intensity(_x, _y); } if(m_picked_plane) { this->layer()->cancel_further_interaction(); } } //------------------------------------------------------------------------------ void negato3d::button_release_event(mouse_button /*_button*/, modifier /*_mods*/, int /*_x*/, int /*_y*/) { if(m_picked_plane) { m_picked_plane->set_render_queuer_group_and_priority(sight::viz::scene3d::rq::SURFACE_ID, 0); m_picked_plane.reset(); } m_picking_cross->set_visible(false); this->signal(signals::PICKED_VOXEL)->async_emit(""); this->set_planes_query_flags(m_query_flags); // Make all planes pickable again. } //------------------------------------------------------------------------------ void negato3d::move_slices(int _x, int _y) { const auto pick_res = this->get_picked_slices( _x, _y ); if(pick_res.has_value()) { const auto image = m_image.lock(); auto picked_pt = pick_res.value(); std::ranges::for_each( m_planes, [this](auto& _p) { if(_p.first != m_picked_plane) { _p.first->set_query_flags(0x0); } }); const auto picked_voxel = geometry::data::world_to_image(*image, picked_pt, true, true); image->async_emit( data::image::SLICE_INDEX_MODIFIED_SIG, int(picked_voxel[2]), int(picked_voxel[1]), int(picked_voxel[0]) ); } } //------------------------------------------------------------------------------ void negato3d::pick_intensity(int _x, int _y) { auto sig = this->signal(signals::PICKED_VOXEL); if(sig->num_connections() > 0) { const auto picked_pos = this->get_picked_slices(_x, _y); if(picked_pos.has_value()) { const auto image = m_image.lock(); if(!data::helper::medical_image::check_image_validity(image.get_shared())) { return; } const auto cross_lines = m_picked_plane->compute_cross(*picked_pos, *image); m_picking_cross->update(cross_lines[0], cross_lines[1], cross_lines[2], cross_lines[3]); const auto picking_text = sight::viz::scene3d::utils::pick_image(*image, *picked_pos); sig->async_emit(picking_text); this->request_render(); // Render the picked plane before the widget. m_picked_plane->set_render_queuer_group_and_priority(sight::viz::scene3d::rq::NEGATO_WIDGET_ID, 0); } } } //------------------------------------------------------------------------------ std::optional negato3d::get_picked_slices(int _x, int _y) { Ogre::SceneManager* sm = this->get_scene_manager(); const auto result = sight::viz::scene3d::utils::pick_object(_x, _y, m_query_flags, *sm); if(result.has_value()) { const auto is_picked = [&result](const auto& _p){return _p.first->get_movable_object() == result->first;}; const auto it = std::ranges::find_if(m_planes, is_picked); // NOLINT(readability-qualified-auto,llvm-qualified-auto) if(it != m_planes.cend()) { m_picked_plane = it->first; return result->second; } } return std::nullopt; } } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/negato3d.hpp000066400000000000000000000175161503402212300222070ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor displays a 3D negato. * * @section Signals Signals * - \b picked_voxel(string): sends the coordinates and intensity of the voxel picked by the cross widget. * * @section Slots Slots * - \b update_image(): update the image display to show the new content. * - \b sliceType(int, int): update image slice index . * - \b sliceIndex(int, int, int): update image slice type. * - \b set_transparency(double): sets the global transparency of the three image planes. * - \b update_visibility(bool): sets whether the negato is shown or not. * - \b toggle_visibility(): toggle whether the negato is shown or not. * - \b show(): shows the negato. * - \b hide(): hides the negato. * - \b update_slices_from_world(double, double, double): updates image slices indexes according to a 3d world point * or landmark. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Input Input: * - \b image [sight::data::image]: image to display. * - \b mask [sight::data::image] (optional): mask to apply onto the image. Values < 0.5 are considered masked. * * @subsection In-Out In-Out: * - \b tf [sight::data::transfer_function]: the current TransferFunction. If it is not defined, we use the * image's default transferFunction (CT-GreyLevel). * * @subsection Configuration Configuration: * - \b sliceIndex (optional, axial/frontal/sagittal, default=axial): orientation of the negato. * - \b filtering (optional, none/linear/anisotropic, default=none): texture filter type of the negato. * - \b tfAlpha (optional, bool, default=false): if true, the alpha channel of the transfer function is used. * - \b interactive (optional, bool, default=false): enables interactions on the negato. * - \b priority (optional, int, default=1): interaction priority of the negato. * - \b transform (optional, string, default=""): the name of the Ogre transform node where to attach the negato, as it * was specified in the transform adaptor. * - \b query_flags (optional, uint32, default=0x40000000): Mask set to planes for picking request. * - \b border (optional, bool, default=true): allows to display plane borders. * * @subsection Properties Properties: * - \b classification (optional, pre/post, default=pre): classification of voxels. "pre" means the filtering is applied * after the sampling of the transfer function, and "post" after. When using labelled images, it is highly recommended * to use "pre", otherwise it is likely that class of objects can be confounded. * - \b visible (optional, bool, default=true): set the initial visibility of the 3D negato. * - \b depth_bias: useful to superimpose multiple negatos on top of each other */ class negato3d final : public sight::module::viz::scene3d::adaptor::negato { public: using axis_t = data::helper::medical_image::axis_t; /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(negato3d, sight::module::viz::scene3d::adaptor::negato); struct slots { static inline const core::com::slots::key_t SET_TRANSPARENCY = "set_transparency"; }; /// Creates slots. negato3d() noexcept; /// Destroys the adaptor. ~negato3d() noexcept final = default; protected: /// Configures the service. void configuring(const config_t& _config) final; /** * @brief Sets the negato visibility. * @param _visible the visibility status of the negato. */ void set_visible(bool _visible) final; private: /** * @brief Interacts with the negato if it was picked by pressing any mouse button. * * Interactions will take place while holding down the button. The following actions are available: * - Left mouse click: shows a cross widget to select a voxel and retrieve its intensity. * - Middle mouse click: move the slice's intersection to where the mouse cursor is. * - Right mouse click: adjust the transfer function level and window by moving * the mouse up/down and left/right respectively. * * @param _button mouse button pressed. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. * @param _dx the cursor's width displacement since the last event. * @param _dy the cursor's height displacement since the last event. */ void mouse_move_event(mouse_button _button, modifier /*_mods*/, int _x, int _y, int _dx, int _dy) final; /** * @brief Attempts to pick the negato and starts interactions if picking was successful. * @param _button mouse button pressed. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void button_press_event(mouse_button _button, modifier /*_mods*/, int _x, int _y) final; /// Ends all interactions, regardless of the input. void button_release_event(mouse_button /*_button*/, modifier /*_mods*/, int /*_x*/, int /*_y*/) final; /** * @brief Sets the slice intersection at the (_x, _y) screen position if possible. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void move_slices(int _x, int _y); /** * @brief Picks the intensity value at the (_x, _y) screen position. * @param _x current width coordinate of the mouse cursor. * @param _y current height coordinate of the mouse cursor. */ void pick_intensity(int _x, int _y); /** Uploads the input image into the texture buffer and recomputes the negato geometry. * @param _new true if the image was reallocated, false if only pixel values changed. */ void update_image(bool _new); /// SLOT: updates the image slice type. void change_slice_type(int /*unused*/, int /*unused*/) override; /// SLOT: sets the planes's opacity. void set_transparency(double _transparency); /// Sets the picking flags on all three negato planes. void set_planes_query_flags(std::uint32_t _flags); /// Attemps to pick the negato planes, returns the world space position of the intersection if successful. std::optional get_picked_slices(int _x, int _y); }; //------------------------------------------------------------------------------ } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/orientation_marker.cpp000066400000000000000000000121621503402212300243620ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2025 IRCAD France * Copyright (C) 2020-2021 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "orientation_marker.hpp" #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { //----------------------------------------------------------------------------- void orientation_marker::configuring() { this->configure_params(); const config_t config = this->get_config(); // Set the resource this use, if it has been set via xml m_patient_mesh_rc = config.get(CONFIG + "resource", m_patient_mesh_rc); m_marker_depth = config.get(CONFIG + "depth", m_marker_depth); } //----------------------------------------------------------------------------- void orientation_marker::starting() { adaptor::init(); this->render_service()->make_current(); Ogre::SceneNode* const root_scene_node = this->get_scene_manager()->getRootSceneNode(); m_scene_node = root_scene_node->createChildSceneNode(gen_id("mainNode")); Ogre::SceneManager* const scene_mgr = this->get_scene_manager(); // Set the material const auto mtl_name = gen_id("material"); m_material = std::make_unique(mtl_name); m_material->set_shading(data::material::shading_t::phong, this->layer()->num_lights()); // Loads and attaches the marker m_patient_entity = scene_mgr->createEntity(m_patient_mesh_rc); m_patient_entity->setMaterialName(m_material->name(), sight::viz::scene3d::RESOURCE_GROUP); m_scene_node->attachObject(m_patient_entity); auto& mesh_mgr = Ogre::MeshManager::getSingleton(); auto mesh = mesh_mgr.createOrRetrieve(m_patient_mesh_rc, sight::viz::scene3d::RESOURCE_GROUP); mesh.first->load(); this->apply_visibility(); this->request_render(); } //----------------------------------------------------------------------------- void orientation_marker::updating() { this->update_camera_matrix(); this->update_done(); this->request_render(); } //------------------------------------------------------------------------------ void orientation_marker::update_camera_matrix() { // Copy orientation matrix to Ogre. Ogre::Matrix3 ogre_matrix; { const auto transform = m_matrix.lock(); // Fill the ogreMatrix. for(std::size_t lt = 0 ; lt < 3 ; lt++) { for(std::size_t ct = 0 ; ct < 3 ; ct++) { ogre_matrix[ct][lt] = static_cast((*transform)(ct, lt)); } } } // Convert to quaternion. Ogre::Quaternion orientation(ogre_matrix); const Ogre::Quaternion rotate_x(Ogre::Degree(180), Ogre::Vector3(1, 0, 0)); // Reset the camera position & orientation, since s_MATRIX_IN is a global transform. m_scene_node->setPosition(0, 0, 0); // Reverse X axis. m_scene_node->setOrientation(rotate_x); // Update the camera position // Inverse camera matrix (since we move the mesh) m_scene_node->rotate(orientation.Inverse()); // Place it at a fixed position m_scene_node->translate(0.F, 0.F, m_marker_depth); } //----------------------------------------------------------------------------- void orientation_marker::stopping() { Ogre::SceneManager* const scene_mgr = this->get_scene_manager(); m_patient_entity->detachFromParent(); scene_mgr->destroyEntity(m_patient_entity); m_patient_entity = nullptr; m_material.reset(); adaptor::deinit(); } //----------------------------------------------------------------------------- void orientation_marker::set_visible(bool _visible) { if(m_scene_node != nullptr) { m_scene_node->setVisible(_visible); } this->updating(); } //----------------------------------------------------------------------------- service::connections_t orientation_marker::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(MATRIX_IN, data::matrix4::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); return connections; } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/orientation_marker.hpp000066400000000000000000000071321503402212300243700ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2025 IRCAD France * Copyright (C) 2020-2021 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief Displays an orientation "marker", marker is represented by a human body mesh, * its orientation follows camera movement. * * @section Slots Slots * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Input Input * - \b matrix [sight::data::matrix4]: matrix to follow (usually camera Matrix). * @subsection Configuration Configuration: * - \b resource (optional, string): name of the resource to use for the marker. * - \b depth (optional, float): value of depth (z) where marker will be positioned, greater value to zoom-in , lower * to zoom-out. */ class orientation_marker final : public sight::viz::scene3d::adaptor { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(orientation_marker, sight::viz::scene3d::adaptor); /// Constructor orientation_marker() noexcept = default; /// Destructor ~orientation_marker() noexcept final = default; protected: /// Configures the service's parameters void configuring() final; /// Initializes and starts child services void starting() final; /// Updates internal matrix from the input transform void updating() final; /// Unregisters child services void stopping() final; /// Sets the visibility of the adaptor void set_visible(bool _visible) final; /// Connects input matrix MODIFIED to UPDATE slot. service::connections_t auto_connections() const final; private: /// Contains the material used to display the patient. sight::viz::scene3d::material::standard::uptr m_material {nullptr}; /// Updates the internal camera matrix from the input transform void update_camera_matrix(); /// Contains the scene node where all of manual objects are attached. Ogre::SceneNode* m_scene_node {nullptr}; /// Stores the entity associated to the marker mesh Ogre::Entity* m_patient_entity {nullptr}; /// Resource used for the marker std::string m_patient_mesh_rc {"human.mesh"}; /// Z coordinate of marker position, increase to zoom in, decrease to zoom out. float m_marker_depth = -32.F; static constexpr std::string_view MATRIX_IN = "matrix"; sight::data::ptr m_matrix {this, MATRIX_IN}; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/picker.cpp000066400000000000000000000065041503402212300217460ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2019-2025 IRCAD France * Copyright (C) 2019-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/picker.hpp" #include #include namespace sight::module::viz::scene3d::adaptor { static const core::com::signals::key_t PICKED_SIG = "picked"; //----------------------------------------------------------------------------- picker::picker() noexcept { m_picked_sig = new_signal(PICKED_SIG); } //----------------------------------------------------------------------------- void picker::configuring() { this->configure_params(); const config_t config = this->get_config(); static const std::string s_PRIORITY_CONFIG = CONFIG + "priority"; static const std::string s_LAYER_ORDER_DEPENDANT_CONFIG = CONFIG + "layerOrderDependant"; m_priority = config.get(s_PRIORITY_CONFIG, m_priority); m_layer_order_dependant = config.get(s_LAYER_ORDER_DEPENDANT_CONFIG, m_layer_order_dependant); const auto hexa_mask = core::ptree::get_and_deprecate( config, CONFIG + "query_mask", CONFIG + "queryMask", "26.0", "" ); if(!hexa_mask.empty()) { SIGHT_ASSERT( "Hexadecimal values should start with '0x'" "Given value : " + hexa_mask, hexa_mask.length() > 2 && hexa_mask.substr(0, 2) == "0x" ); m_query_mask = static_cast(std::stoul(hexa_mask, nullptr, 16)); } } //----------------------------------------------------------------------------- void picker::starting() { adaptor::init(); const auto layer = this->layer(); m_interactor = std::make_shared( layer, m_layer_order_dependant ); m_interactor->set_query_mask(m_query_mask); m_interactor->set_point_clicked_sig(m_picked_sig); layer->add_interactor(m_interactor, m_priority); } //----------------------------------------------------------------------------- void picker::updating() noexcept { } //----------------------------------------------------------------------------- void picker::stopping() { const auto layer = this->layer(); layer->remove_interactor(m_interactor); adaptor::deinit(); } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/picker.hpp000066400000000000000000000066061503402212300217560ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2019-2024 IRCAD France * Copyright (C) 2019-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor performs picking queries on mouse clicks and forwards the picked data through a signal. * * See @see sight::viz::scene3d::interactor::mesh_picker_interactor to learn more about the interactions * provided by this service. * * @section Signals Signals * - \b picked(data::tools::picking_info): emitted when a picking query is successful. * * @section XML XML Configuration * @code{.xml} @endcode * * @subsection Configuration Configuration: * - \b priority (optional, int, default=0): picking priority, higher priority interactions are performed first. * - \b queryMask (optional, uint32, default=0xFFFFFFFF): filters out entities with mismatching flags when picking. * - \b layerOrderDependant (optional, bool, default=true): define if interaction must take into account above layers. */ class picker final : public sight::viz::scene3d::adaptor { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(picker, sight::viz::scene3d::adaptor); /// Initializes the adaptor. picker() noexcept; /// Destroys the adaptor. ~picker() noexcept final = default; protected: /// Configures the picker's query mask and priority. void configuring() final; /// Creates the interactor and adds it to the layer. void starting() final; /// Updates the service. Doesn't do anything here. void updating() noexcept final; /// Destroys the interactor and removes it from the layer. void stopping() final; private: /// Determines the execution order of the picking interactor. int m_priority {2}; /// Defines the mask used to filter out entities when picking. std::uint32_t m_query_mask {0xFFFFFFFF}; /// Defines if the interaction must take into account above layers. bool m_layer_order_dependant {true}; /// Contains the interactor managed by the adaptor. std::shared_ptr m_interactor; /// Defines the signal sent on picking events. sight::viz::scene3d::interactor::mesh_picker_interactor::point_clicked_sig_t::sptr m_picked_sig; }; } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/point_list.cpp000066400000000000000000000433011503402212300226510ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "module/viz/scene3d/adaptor/point_list.hpp" #include "module/viz/scene3d/adaptor/material.hpp" #include "viz/scene3d/ogre.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sight::module::viz::scene3d::adaptor { static const std::string BILLBOARD_SIZE_UNIFORM = "u_billboardSize"; //----------------------------------------------------------------------------- point_list::point_list() noexcept { m_material = std::make_shared(); } //----------------------------------------------------------------------------- point_list::~point_list() noexcept { if(m_entity != nullptr) { Ogre::SceneManager* scene_mgr = this->get_scene_manager(); scene_mgr->destroyEntity(m_entity); } } //----------------------------------------------------------------------------- void point_list::set_visible(bool _visible) { if(m_entity != nullptr) { this->render_service()->make_current(); m_entity->setVisible(_visible); m_mesh_geometry->set_visible(_visible); this->request_render(); } } //----------------------------------------------------------------------------- void point_list::configuring() { this->configure_params(); const config_t config = this->get_config(); static const std::string s_COLOR_CONFIG = CONFIG + "color"; static const std::string s_VISIBLE_CONFIG = CONFIG + "visible"; static const std::string s_AUTORESET_CAMERA_CONFIG = CONFIG + "autoresetcamera"; static const std::string s_MATERIAL_TEMPLATE_CONFIG = CONFIG + "material_template"; static const std::string s_FIXED_SIZE_CONFIG = CONFIG + "fixedSize"; static const std::string s_RADIUS_CONFIG = CONFIG + "radius"; static const std::string s_DISPLAY_LABEL_CONFIG = CONFIG + "displayLabel"; static const std::string s_LABEL_COLOR_CONFIG = CONFIG + "labelColor"; static const std::string s_FONT_SOURCE_CONFIG = CONFIG + "fontSource"; static const std::string s_FONT_SIZE_CONFIG = CONFIG + "fontSize"; const std::string color = config.get(s_COLOR_CONFIG, ""); SIGHT_ASSERT("Material not found", m_material); m_material->diffuse()->from_string(color.empty() ? "#FFFFFFFF" : color); m_auto_reset_camera = config.get(s_AUTORESET_CAMERA_CONFIG, true); if(config.count(s_MATERIAL_TEMPLATE_CONFIG) != 0U) { // An existing Ogre material will be used for this mesh m_custom_material = true; m_material_template_name = config.get(s_MATERIAL_TEMPLATE_CONFIG); } else if(config.get(s_FIXED_SIZE_CONFIG, false)) { m_material_template_name = "Billboard_FixedSize"; } // The mesh adaptor will pass the texture name to the created material adaptor m_texture_name = core::ptree::get_and_deprecate( config, CONFIG + "texture_name", CONFIG + "textureName", "26.0", m_texture_name ); this->set_transform_id( config.get( sight::viz::scene3d::transformable::TRANSFORM_CONFIG, gen_id("transform") ) ); const auto hexa_mask = core::ptree::get_and_deprecate( config, CONFIG + "query_flags", CONFIG + "queryFlags", "26.0" ); if(not hexa_mask.empty()) { SIGHT_ASSERT( "Hexadecimal values should start with '0x'" "Given value : " + hexa_mask, hexa_mask.length() > 2 && hexa_mask.substr(0, 2) == "0x" ); m_query_flags = static_cast(std::stoul(hexa_mask, nullptr, 16)); } m_font_source = config.get(s_FONT_SOURCE_CONFIG, m_font_source); m_font_size = config.get(s_FONT_SIZE_CONFIG, m_font_size); m_radius = config.get(s_RADIUS_CONFIG, m_radius); m_display_label = config.get(s_DISPLAY_LABEL_CONFIG, m_display_label); const std::string label_color = config.get(s_LABEL_COLOR_CONFIG, "#FFFFFF"); m_label_color = std::make_shared(); m_label_color->from_string(label_color); } //----------------------------------------------------------------------------- void point_list::starting() { adaptor::init(); this->render_service()->make_current(); m_mesh_geometry = std::make_shared(this->get_id()); m_mesh_geometry->set_dynamic(true); Ogre::SceneNode* root_scene_node = this->get_scene_manager()->getRootSceneNode(); m_scene_node = this->get_or_create_transform_node(root_scene_node); this->updating(); this->apply_visibility(); } //----------------------------------------------------------------------------- service::connections_t point_list::auto_connections() const { service::connections_t connections = adaptor::auto_connections(); connections.push(m_point_list, data::point_list::POINT_ADDED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(m_point_list, data::point_list::POINT_REMOVED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(m_point_list, data::point_list::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(m_points, data::object::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(m_mesh, data::mesh::VERTEX_MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); connections.push(m_mesh, data::mesh::MODIFIED_SIG, adaptor::slots::LAZY_UPDATE); return connections; } //----------------------------------------------------------------------------- void point_list::stopping() { this->render_service()->make_current(); this->unregister_services(); this->destroy_label(); Ogre::SceneManager* scene_mgr = this->get_scene_manager(); SIGHT_ASSERT("Ogre::SceneManager is null", scene_mgr); m_mesh_geometry->clear_mesh(*scene_mgr); if(m_entity != nullptr) { scene_mgr->destroyEntity(m_entity); m_entity = nullptr; } m_mesh_geometry.reset(); adaptor::deinit(); } //----------------------------------------------------------------------------- void point_list::updating() { this->render_service()->make_current(); if(!get_visibility()) { return; } this->destroy_label(); if(const auto point_list = m_point_list.lock(); point_list) { this->update_mesh(point_list.get_shared()); } else { if(not m_points.empty()) { const auto tmp_list = std::make_shared(); std::ranges::for_each( m_points, [&tmp_list](const auto& _x) { const auto point = _x.second->const_lock(); tmp_list->get_points().push_back(std::const_pointer_cast(point.get_shared())); }); this->update_mesh(tmp_list); } else { if(const auto mesh = m_mesh.lock(); mesh) { if(!m_custom_material && mesh->has()) { m_material_template_name += "_PerPointColor"; } this->update_mesh(mesh.get_shared()); } else { SIGHT_ERROR("No 'pointList', 'points' or 'mesh' specified.") } } } this->update_done(); this->request_render(); } //------------------------------------------------------------------------------ void point_list::create_label(const data::point_list::csptr& _point_list) { auto render_srv = this->render_service(); std::size_t i = 0; std::string label_number = std::to_string(i); for(const auto& point : _point_list->get_points()) { const auto label = point->get_label(); if(!label.empty()) { label_number = label; } else { label_number = std::to_string(i); } auto text_label = sight::viz::scene3d::text::make(this->layer()); text_label->set_font_size(m_font_size); text_label->set_text(label_number); text_label->set_text_color( Ogre::ColourValue( m_label_color->red(), m_label_color->green(), m_label_color->blue() ) ); auto* node = m_scene_node->createChildSceneNode(gen_id(label_number)); m_nodes.push_back(node); text_label->attach_to_node(node, this->layer()->get_default_camera()); m_labels.push_back(text_label); const auto& coord = *point; node->translate(static_cast(coord[0]), static_cast(coord[1]), static_cast(coord[2])); i++; } } //------------------------------------------------------------------------------ void point_list::destroy_label() { std::ranges::for_each(m_labels, [](auto& _label){_label->detach_from_node();}); m_labels.clear(); std::ranges::for_each(m_nodes, [this](auto& _node){m_scene_node->removeAndDestroyChild(_node);}); m_nodes.clear(); } //----------------------------------------------------------------------------- void point_list::update_mesh(const data::point_list::csptr& _point_list) { Ogre::SceneManager* scene_mgr = this->get_scene_manager(); SIGHT_ASSERT("Ogre::SceneManager is null", scene_mgr); detach_and_destroy_entity(); const std::size_t ui_num_vertices = _point_list->get_points().size(); if(ui_num_vertices == 0) { SIGHT_DEBUG("Empty mesh"); m_mesh_geometry->clear_mesh(*scene_mgr); return; } if(m_display_label) { this->create_label(_point_list); } this->render_service()->make_current(); m_mesh_geometry->update_mesh(_point_list); //------------------------------------------ // Create entity and attach it in the scene graph //------------------------------------------ if(m_entity == nullptr) { m_entity = m_mesh_geometry->create_entity(*scene_mgr); m_entity->setVisible(visible()); m_entity->setQueryFlags(m_query_flags); } //------------------------------------------ // Update vertex layers //------------------------------------------ m_mesh_geometry->update_vertices(_point_list); //------------------------------------------ // Create sub-services //------------------------------------------ this->update_material_adaptor(_point_list->get_id()); this->attach_node(m_entity); m_mesh_geometry->set_visible(visible()); if(m_auto_reset_camera) { this->render_service()->reset_camera_coordinates(m_layer_id); } } //------------------------------------------------------------------------------ void point_list::update_mesh(const data::mesh::csptr& _mesh) { Ogre::SceneManager* scene_mgr = this->get_scene_manager(); SIGHT_ASSERT("Ogre::SceneManager is null", scene_mgr); detach_and_destroy_entity(); const std::size_t ui_num_vertices = _mesh->num_points(); if(ui_num_vertices == 0) { SIGHT_DEBUG("Empty mesh"); m_mesh_geometry->clear_mesh(*scene_mgr); return; } this->render_service()->make_current(); m_mesh_geometry->update_mesh(std::const_pointer_cast(_mesh), true); //------------------------------------------ // Create entity and attach it in the scene graph //------------------------------------------ if(m_entity == nullptr) { m_entity = m_mesh_geometry->create_entity(*scene_mgr); m_entity->setVisible(visible()); m_entity->setQueryFlags(m_query_flags); } //------------------------------------------ // Update vertex layers //------------------------------------------ m_mesh_geometry->update_vertices(_mesh); m_mesh_geometry->update_colors(_mesh); //------------------------------------------ // Create sub-services //------------------------------------------ this->update_material_adaptor(_mesh->get_id()); this->attach_node(m_entity); m_mesh_geometry->set_visible(visible()); if(m_auto_reset_camera) { this->render_service()->reset_camera_coordinates(m_layer_id); } } //------------------------------------------------------------------------------ void point_list::update_material_adaptor(const std::string& _mesh_id) { if(!m_material_adaptor) { if(m_entity != nullptr) { m_material_adaptor = this->register_service( "sight::module::viz::scene3d::adaptor::material" ); m_material_adaptor->set_inout(m_material, "material", true); SIGHT_ASSERT("Template name empty", !m_material_template_name.empty()); config_t material_adp_config; material_adp_config.put("config..material_template", m_material_template_name); if(!m_uniforms.empty()) { std::size_t i = 0; for(const auto& uniform_data : m_uniforms) { m_material_adaptor->set_inout(uniform_data.second->lock().get_shared(), "uniforms", true, {}, i++); } const auto config = this->get_config(); if(const auto inouts_cfg = config.get_child_optional("inout"); inouts_cfg.has_value()) { const auto group = inouts_cfg->get(".group"); if(group == "uniforms") { material_adp_config.add_child("inout", inouts_cfg.value()); } } } const std::string mtl_name = core::id::join(_mesh_id, m_material_adaptor->get_id()); SIGHT_ASSERT("Template name empty", !m_material_template_name.empty()); m_material_adaptor->service::base::configure(material_adp_config); m_material_adaptor->set_id(gen_id(m_material_adaptor->get_id())); m_material_adaptor->set_material_name(mtl_name); m_material_adaptor->set_render_service(this->render_service()); m_material_adaptor->set_layer_id(m_layer_id); m_material_adaptor->set_material_template_name(m_material_template_name); m_material_adaptor->start(); auto* material_impl = m_material_adaptor->get_material_impl(); material_impl->set_layout(*m_mesh_geometry); material_impl->set_geometry_uniform(BILLBOARD_SIZE_UNIFORM, m_radius); m_entity->setMaterialName(m_material_adaptor->get_material_name()); if(!m_texture_name.empty()) { const auto texture = Ogre::TextureManager::getSingleton().load( m_texture_name, sight::viz::scene3d::RESOURCE_GROUP ); material_impl->set_texture("sprite", texture); } m_material_adaptor->update(); } } else if(m_material_adaptor->inout(material::MATERIAL_INOUT).lock() != m_material) { auto* material_impl = m_material_adaptor->get_material_impl(); material_impl->set_layout(*m_mesh_geometry); material_impl->set_geometry_uniform(BILLBOARD_SIZE_UNIFORM, m_radius); } else { auto* material_impl = m_material_adaptor->get_material_impl(); material_impl->set_layout(*m_mesh_geometry); material_impl->set_geometry_uniform(BILLBOARD_SIZE_UNIFORM, m_radius); m_entity->setMaterialName(m_material_adaptor->get_material_name()); m_material_adaptor->slot(adaptor::slots::LAZY_UPDATE)->run(); } } //----------------------------------------------------------------------------- void point_list::attach_node(Ogre::MovableObject* _node) { SIGHT_ASSERT("transform Node shouldn't be null", m_scene_node); m_scene_node->attachObject(_node); // Needed to recompute world bounding boxes of the scene node using its attached mesh bounds m_scene_node->_update(true, false); } //----------------------------------------------------------------------------- void point_list::detach_and_destroy_entity() { if(m_entity != nullptr) { Ogre::SceneManager* const scene_mgr = this->get_scene_manager(); if(m_scene_node != nullptr) { m_scene_node->detachObject(m_entity); } scene_mgr->destroyEntity(m_entity); m_entity = nullptr; } } //----------------------------------------------------------------------------- } // namespace sight::module::viz::scene3d::adaptor. sight-25.1.0/module/viz/scene3d/adaptor/point_list.hpp000066400000000000000000000247561503402212300226730ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2014-2025 IRCAD France * Copyright (C) 2014-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "module/viz/scene3d/adaptor/material.hpp" #include "module/viz/scene3d/adaptor/transform.hpp" #include #include #include #include #include #include #include namespace sight::data { class Material; } // namespace sight::data namespace sight::data { class mesh; } // namespace sight::data namespace sight::module::viz::scene3d::adaptor { /** * @brief This adaptor shows a point list using billboards generated by a geometry shader. * * This class handles the display of a data::point_list or a data::mesh. Both are exclusive you can only specify * one of the two. * * @section Slots Slots * - \b update_visibility(bool): Sets whether the points are visible or not. * - \b toggle_visibility(): Toggle whether the points are visible or not. * - \b show(): shows the video. * - \b hide(): hides the video. * * @section XML XML Configuration * @code{.xml} * The service can be configured in three different ways, with 3 different input data: * - a sight::data::point_list: tuto01_hello_world Tutorial 1: Hello World! tuto01_hello_world/tuto.ico sight-25.1.0/tutorial/xml/tuto01_hello_world/rc/tuto.ico000066400000000000000000000217061503402212300231770ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= tuto02_data_service_basic Tutorial 2: Data and service basic tuto02_data_service_basic/tuto.ico tuto02_data_service_basic/img-test.vtk sight-25.1.0/tutorial/xml/tuto02_data_service_basic/rc/tuto.ico000066400000000000000000000217061503402212300244600ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= tuto03_data_service Tutorial 3: Data and service tuto03_data_service/tuto.ico open_image_act/clicked image_reader_srv/update sight-25.1.0/tutorial/xml/tuto03_data_service/rc/tuto.ico000066400000000000000000000217061503402212300233200ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= tuto04_signal_slot Tutorial 4: Signals and slots tuto04_signal_slot/tuto.ico rendering1_srv/cam_updated rendering2_srv/update_cam_position rendering3_srv/update_cam_position rendering2_srv/cam_updated rendering1_srv/update_cam_position open_mesh_act/clicked mesh_reader_srv/update sight-25.1.0/tutorial/xml/tuto04_signal_slot/rc/tuto.ico000066400000000000000000000217061503402212300232060ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= tuto05_mesher Tutorial 5: Mesh a segmentation mask tuto05_mesher/tuto.ico shortcut_srv/activated create_mesh_act/update create_mesh_act/clicked mesher_srv/update mesher_srv/completed extract_mesh_srv/update extract_mesh_srv/updated save_mesh_act/enable open_image_act/clicked image_reader_srv/update save_image_act/enable save_image_act/clicked image_writer_srv/update save_mesh_act/clicked mesh_writer_srv/update sight-25.1.0/tutorial/xml/tuto05_mesher/rc/tuto.ico000066400000000000000000000217061503402212300221540ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= tuto06_filter Tutorial 6: Filter an image tuto06_filter/tuto.ico 50 open_image_act/updated image_reader_srv/update image_filter_act/updated image_filter_srv/update image_flipper_act/updated image_flipper_srv/flip_axis_y sight-25.1.0/tutorial/xml/tuto06_filter/rc/tuto.ico000066400000000000000000000217061503402212300221570ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= tuto07_generic_scene tuto07_generic_scene tuto07_generic_scene/tuto.ico true sight::module::ui::icons/cross.svg sight::module::ui::icons/layers.svg 40 24 true false sight::module::ui::icons/frame.svg mesh_reader_srv/job_created image_reader_srv/job_created texture_reader_srv/job_created progress_bar_view/show_job snapshot_srv/clicked pdf_writer/update image_writer_srv/update show_negato_srv/toggled negato_adp/update_visibility open_image_act/updated open_mesh_act/updated open_texture_act/updated fullscreen_act/updated windowed_act/updated speed_dial_srv/fold fullscreen_act/screen_selected generic_scene_srv/enable_fullscreen generic_scene_srv/fullscreen_set windowed_act/show fullscreen_act/hide windowed_act/clicked generic_scene_srv/disable_fullscreen generic_scene_srv/fullscreen_unset windowed_act/hide fullscreen_act/show open_image_act/clicked image_reader_srv/update open_mesh_act/clicked mesh_reader_srv/update open_texture_act/clicked texture_reader_srv/update sight-25.1.0/tutorial/xml/tuto07_generic_scene/rc/tuto.ico000066400000000000000000000217061503402212300234640ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= tuto08_mesher_with_generic_scene Tutorial 8: Mesher and generic scene tuto08_mesher_with_generic_scene/tuto.ico false sight::module::ui::icons/frame.svg true sight::module::ui::icons/cross.svg sight::module::ui::icons/layers.svg 40 16 true @organ_name image_reader_srv/job_created mesher_srv/job_created model_series_writer_srv/job_created progress_bar_view/show_job list_organ_editor_srv/reconstruction_selected updater_reconst_srv/add snapshot_srv/clicked image_writer_srv/update show_negato_srv/toggled negato_adp/update_visibility open_image_act/clicked image_reader_srv/update save_model_series_act/clicked model_series_writer_srv/update create_mesh_act/clicked mesher_srv/update sight-25.1.0/tutorial/xml/tuto08_mesher_with_generic_scene/rc/tuto.ico000066400000000000000000000217061503402212300260630ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= tuto09_matrix_transform_in_gs Tutorial 9: Matrix transformation and concatenation tuto09_matrix_transform_in_gs/tuto.ico open_mesh_act/clicked mesh_reader_srv/update sight-25.1.0/tutorial/xml/tuto09_matrix_transform_in_gs/rc/tuto.ico000066400000000000000000000217061503402212300254530ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= tuto10_launch_basic_config::basic_frame_config Basic frame config main_frame/closed sight-25.1.0/tutorial/xml/tuto10_launch_basic_config/rc/plugin.xml000066400000000000000000000104361503402212300251540ustar00rootroot00000000000000 tuto10_launch_basic_config Tutorial 10: Launch a configuration tuto10_launch_basic_config/tuto.ico open_image_act/clicked image_reader_srv/update sight-25.1.0/tutorial/xml/tuto10_launch_basic_config/rc/tuto.ico000066400000000000000000000217061503402212300246250ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= Tuto11Picker_AppCfg tuto11_picker tuto11_picker/tuto.ico false sight::module::ui::icons/frame.svg true sight::module::ui::icons/cross.svg sight::module::ui::icons/layers.svg 40 16 true snapshotSrv/clicked imageWriterSrv/update showNegatoSrv/toggled negatoAdp/update_visibility pickerInteractorAdp/picked pointEditorSrv/get_interaction openImageAct/clicked imageReaderSrv/update openMeshAct/clicked meshReaderSrv/update sight-25.1.0/tutorial/xml/tuto11_picker/rc/tuto.ico000066400000000000000000000217061503402212300221430ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:= Tuto12Scene2D_AppCfg tuto12_scene2d tuto12_scene2d/tuto.ico spacingEditorSrv/double2_changed gridAdp/set_grid_spacing depthRangeSliderSrv/double_changed square1Adp/set_double_parameter sight-25.1.0/tutorial/xml/tuto12_scene2d/rc/tuto.ico000066400000000000000000000217061503402212300222120ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:=. * ***********************************************************************/ #include "algo_mesh_deformation.hpp" #include #include #include namespace data = sight::data; namespace geometry = sight::geometry; namespace tuto13_mesh_generator_cpp { //----------------------------------------------------------------------------- algo_mesh_deformation::algo_mesh_deformation() noexcept = default; //----------------------------------------------------------------------------- algo_mesh_deformation::~algo_mesh_deformation() noexcept = default; //----------------------------------------------------------------------------- void algo_mesh_deformation::set_param( data::mesh::sptr _mesh, const unsigned int _nb_step, const unsigned int _amplitude ) { m_mesh = _mesh; m_nb_step = _nb_step; m_amplitude = _amplitude; m_direction = 1; m_nb_points = _mesh->num_points(); m_nb_cells = _mesh->num_cells(); } //----------------------------------------------------------------------------- void algo_mesh_deformation::compute_deformation( data::mesh::sptr _mesh, const unsigned int _nb_step, const unsigned int _amplitude ) { if(m_mesh.expired() || m_nb_points != _mesh->num_points() || m_nb_cells != _mesh->num_cells() || !_mesh->has()) { this->set_param(_mesh, _nb_step, _amplitude); this->init_simu(); } else { this->compute_simu(); } } //----------------------------------------------------------------------------- void algo_mesh_deformation::init_simu() { const auto mesh = m_mesh.lock(); m_origin_mesh = data::object::copy(mesh); m_step = 0; if(!m_mesh.lock()->has()) { geometry::data::mesh::colorize_mesh_points(mesh); } const auto dump_lock = mesh->dump_lock(); float max = std::numeric_limits::min(); float min = std::numeric_limits::max(); for(const auto& p : mesh->crange()) { const float coord = p.y; if(coord < min) { min = coord; } if(coord > max) { max = coord; } } m_y_center = (max - min) / 2 + min; } //----------------------------------------------------------------------------- void algo_mesh_deformation::compute_simu() { namespace core = sight::core; namespace point = data::iterator::point; m_step += m_direction; if(m_step == static_cast(m_nb_step)) { m_direction = -1; } else if(m_step == 0) { m_direction = 1; } const float scale = static_cast(m_step) / static_cast(m_nb_step); const auto mesh = m_mesh.lock(); const auto dump_lock = mesh->dump_lock(); const auto orig_dump_lock = m_origin_mesh->dump_lock(); const auto orig_range = m_origin_mesh->czip_range(); const auto range = mesh->zip_range(); for(const auto& [orig, cur] : boost::combine(orig_range, range)) { const auto& [pt1, c1] = orig; auto&& [pt2, c2] = cur; pt2.x = pt1.x; if(pt1.y - m_y_center > 0) { pt2.y = pt1.y + (pt1.y - m_y_center) * scale; c2.r = core::tools::numeric_round_cast(255 * scale); } else { c2.r = 0; } pt2.z = pt1.z; } geometry::data::mesh::generate_point_normals(mesh); } //----------------------------------------------------------------------------- } // namespace tuto13_mesh_generator_cpp. sight-25.1.0/tutorial/xml/tuto13_mesh_generator_cpp/algo_mesh_deformation.hpp000066400000000000000000000051641503402212300274770ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2024 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include #include #include #include namespace tuto13_mesh_generator_cpp { class SIGHT_TUTO13_MESH_GENERATOR_CPP_CLASS_API algo_mesh_deformation final { public: /// Creates an instance. SIGHT_TUTO13_MESH_GENERATOR_CPP_API algo_mesh_deformation() noexcept; /// Destroys the instance. SIGHT_TUTO13_MESH_GENERATOR_CPP_API ~algo_mesh_deformation() noexcept; /// Computes the deformation or sets and initializes the algorithm if necessary. SIGHT_TUTO13_MESH_GENERATOR_CPP_API void compute_deformation( sight::data::mesh::sptr _mesh, unsigned int _nb_step, unsigned int _amplitude ); private: /** * @brief Sets the algorithm parameters. * @param _mesh the mesh to deform. * @param _nb_step the number of simulated acquisitions to compute between inspiration and expiration. * @param _amplitude */ void set_param( sight::data::mesh::sptr _mesh, unsigned int _nb_step, unsigned int _amplitude ); /// Initializes the simulated acquisition. void init_simu(); /// Computes the simulated acquisition for the given step between inspiration and expiration. void compute_simu(); /// Defines the number of simulated acquisitions to compute between inspiration and expiration. unsigned int m_nb_step {0}; unsigned int m_amplitude {40}; int m_step {0}; int m_direction {1}; std::size_t m_nb_points {0}; std::size_t m_nb_cells {0}; float m_y_center {0}; sight::data::mesh::wptr m_mesh; sight::data::mesh::sptr m_origin_mesh; }; } // namespace tuto13_mesh_generator_cpp. sight-25.1.0/tutorial/xml/tuto13_mesh_generator_cpp/mesh_modifier.cpp000066400000000000000000000126301503402212300257530ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "mesh_modifier.hpp" #include #include #include namespace tuto13_mesh_generator_cpp { static const std::string FUNCTOR_CONFIG = "functor"; //----------------------------------------------------------------------------- void mesh_modifier::configuring(const config_t& _config) { this->initialize(); const config_t config = _config.get_child("config."); m_functor = config.get(FUNCTOR_CONFIG); SIGHT_ASSERT( "Wrong functor name", m_functor == "ShakeMeshPoint" || m_functor == "ColorizeMeshPoints" || m_functor == "ColorizeMeshCells" || m_functor == "ComputePointNormals" || m_functor == "ComputeCellNormals" || m_functor == "ShakePointNormals" || m_functor == "ShakeCellNormals" || m_functor == "MeshDeformation" ); } //----------------------------------------------------------------------------- void mesh_modifier::starting() { this->action_service_starting(); } //------------------------------------------------------------------------------ void mesh_modifier::updating() { namespace data = sight::data; namespace geometry = sight::geometry; namespace ui = sight::ui; const auto mesh = m_mesh.lock(); try { if(m_functor == "ShakeMeshPoint") { geometry::data::mesh::shake_point(mesh.get_shared()); data::mesh::signal_t::sptr sig; sig = mesh->signal(data::mesh::VERTEX_MODIFIED_SIG); sig->async_emit(); } else if(m_functor == "ColorizeMeshCells") { geometry::data::mesh::colorize_mesh_cells(mesh.get_shared()); data::mesh::signal_t::sptr sig; sig = mesh->signal(data::mesh::CELL_COLORS_MODIFIED_SIG); sig->async_emit(); } else if(m_functor == "ColorizeMeshPoints") { geometry::data::mesh::colorize_mesh_points(mesh.get_shared()); data::mesh::signal_t::sptr sig; sig = mesh->signal(data::mesh::POINT_COLORS_MODIFIED_SIG); sig->async_emit(); } else if(m_functor == "ComputeCellNormals") { geometry::data::mesh::generate_cell_normals(mesh.get_shared()); data::mesh::signal_t::sptr sig; sig = mesh->signal( data::mesh::CELL_NORMALS_MODIFIED_SIG ); sig->async_emit(); } else if(m_functor == "ComputePointNormals") { geometry::data::mesh::generate_point_normals(mesh.get_shared()); data::mesh::signal_t::sptr sig; sig = mesh->signal( data::mesh::POINT_NORMALS_MODIFIED_SIG ); sig->async_emit(); } else if(m_functor == "ShakeCellNormals") { geometry::data::mesh::shake_cell_normals(mesh.get_shared()); data::mesh::signal_t::sptr sig; sig = mesh->signal(data::mesh::CELL_NORMALS_MODIFIED_SIG); sig->async_emit(); } else if(m_functor == "ShakePointNormals") { geometry::data::mesh::shake_point_normals(mesh.get_shared()); data::mesh::signal_t::sptr sig; sig = mesh->signal( data::mesh::POINT_NORMALS_MODIFIED_SIG ); sig->async_emit(); } else if(m_functor == "MeshDeformation") { m_animator.compute_deformation(mesh.get_shared(), 100, 50); const auto sig = mesh->signal(data::object::MODIFIED_SIG); { sight::core::com::connection::blocker block(sig->get_connection(slot(sight::service::slots::UPDATE))); sig->async_emit(); } } } catch(const std::exception& e) { std::stringstream ss; ss << "Warning during generating : " << e.what(); ui::dialog::message::show( "Warning", ss.str(), ui::dialog::message::warning ); } } //----------------------------------------------------------------------------- void mesh_modifier::stopping() { this->action_service_stopping(); } //----------------------------------------------------------------------------- } // namespace tuto13_mesh_generator_cpp. sight-25.1.0/tutorial/xml/tuto13_mesh_generator_cpp/mesh_modifier.hpp000066400000000000000000000056301503402212300257620ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2009-2023 IRCAD France * Copyright (C) 2012-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "algo_mesh_deformation.hpp" #include #include namespace tuto13_mesh_generator_cpp { /** * @brief This action modifies a mesh using specified functor in configuration. * The purpose is to test all possibilities provide by the new mesh structure. * * @section XML XML Configuration * @code{.xml} @endcode * @subsection InOut InOut * - \b mesh [sight::data::mesh]: mesh to modify. * * @subsection Configuration Configuration * - \b functor (mandatory, string): the functor name used to generate mesh. * - ShakeMeshPoint: shakes mesh points. * - ColorizeMeshPoints: colorizes mesh points. * - ColorizeMeshCells: colorizes mesh cells. * - ComputePointNormals: computes point normals. * - ComputeCellNormals: computes cell normals * - ShakePointNormals: shakes point normals. * - ShakeCellNormals: shakes cell normals. * - MeshDeformation: deforms the mesh. */ class mesh_modifier final : public sight::ui::action { public: /// Generates default methods as New, dynamicCast, ... SIGHT_DECLARE_SERVICE(mesh_modifier, sight::ui::action); /// Destroyes the action. ~mesh_modifier() noexcept override = default; protected: /// Configures the action. void configuring(const config_t& _config) override; /// Starts the action. void starting() override; /// Modifies the mesh using the selected functor. void updating() override; /// Stops the action. void stopping() override; private: /// Defines the functor name used to generate mesh. std::string m_functor; /// Defines the algorithm used to animate a deformation on the mesh. tuto13_mesh_generator_cpp::algo_mesh_deformation m_animator; sight::data::ptr m_mesh {this, "mesh"}; }; } // namespace tuto13_mesh_generator_cpp. sight-25.1.0/tutorial/xml/tuto13_mesh_generator_cpp/plugin.cpp000066400000000000000000000030641503402212300244400ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2020-2023 IRCAD France * Copyright (C) 2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #include "plugin.hpp" namespace tuto13_mesh_generator_cpp { //----------------------------------------------------------------------------- SIGHT_REGISTER_PLUGIN("tuto13_mesh_generator_cpp::plugin"); //----------------------------------------------------------------------------- plugin::~plugin() noexcept = default; //----------------------------------------------------------------------------- void plugin::start() { } //----------------------------------------------------------------------------- void plugin::stop() noexcept { } //----------------------------------------------------------------------------- } // namespace tuto13_mesh_generator_cpp. sight-25.1.0/tutorial/xml/tuto13_mesh_generator_cpp/plugin.hpp000066400000000000000000000030761503402212300244500ustar00rootroot00000000000000/************************************************************************ * * Copyright (C) 2018-2024 IRCAD France * Copyright (C) 2018-2020 IHU Strasbourg * * This file is part of Sight. * * Sight is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sight. If not, see . * ***********************************************************************/ #pragma once #include "sight/tuto13_mesh_generator_cpp/config.hpp" #include namespace tuto13_mesh_generator_cpp { /// This class is started when the module is loaded. class SIGHT_TUTO13_MESH_GENERATOR_CPP_CLASS_API plugin final : public sight::core::runtime::plugin { public: /// Destroys the plugin. SIGHT_TUTO13_MESH_GENERATOR_CPP_API ~plugin() noexcept override; /// Starts the plugin, does nothing here. SIGHT_TUTO13_MESH_GENERATOR_CPP_API void start() override; /// Stops the plugin, does nothing here. SIGHT_TUTO13_MESH_GENERATOR_CPP_API void stop() noexcept override; }; } // namespace tuto13_mesh_generator_cpp. sight-25.1.0/tutorial/xml/tuto13_mesh_generator_cpp/rc/000077500000000000000000000000001503402212300230375ustar00rootroot00000000000000sight-25.1.0/tutorial/xml/tuto13_mesh_generator_cpp/rc/plugin.xml000066400000000000000000000202151503402212300250570ustar00rootroot00000000000000 sight::ui::action tuto13_mesh_generator_cpp::mesh_modifier sight::data::mesh tuto13_mesh_generator_cpp_AppCfg tuto13_mesh_generator_cpp tuto13_mesh_generator_cpp/tuto.ico meshReaderSrv/job_created meshWriterSrv/job_created progressBarView/show_job openMeshAct/clicked meshReaderSrv/update saveMeshAct/clicked meshWriterSrv/update sight-25.1.0/tutorial/xml/tuto13_mesh_generator_cpp/rc/tuto.ico000066400000000000000000000217061503402212300245340ustar00rootroot00000000000000hF v 00( LLKMMMRQQTTSUUUYWVXXY\[Z_\[[]]]_`^abjhghijilmmprrvwsvxtwyy{|}|~{~}A?)"##)96&/ # !'*(8%,1326%,4:=}2Mck&>[vd@UEN9pnJE+S%|*~'ȞQiFCCCmL6%6uc_-9;|gˆj><88(DI|ne0@ G ayͼ?O8uuh'.v?r⨮.$$HsՔVuR)4u١Cۇ ұvGm ʏLrgH6Q}Ak ځžHgeEvv@h=vWQ x;9=1^w3{nwOP[s\%8푠:~MP^~'/M!O[X:/0 @frK WQ{OoE~W^W^9J)N|67=ϋe'Px(@NM,Ww t.=}~/MuA.$}--B@RHsHʇCK'e%n!SK{F R\p8u*@y!{A'` "eyS`@MI.$-dv;%^R8LO""`+-KS@SHHHӐ2XD$IĖk)!ǐiDo;"g\n-ܿ;P s&2; Ӵl\}++,f[.ḳ;[޺Uc ېqϧ Yנf,޽(X"Ċ3jU3~*_$=&Xw]/\DoƥV)#u\r.HY .ayN_I&inA ZW!s>| \1szO'GGo?#Uwsx16~ŷyv*řtßKzky0qH6Lѯ<7g'U 8/sU g'ӓYVB kw7dࠦ Ȥ;@#wp_}j> p} o펑[>]բC,vvZ[ʮnF|l?K |\x'U?c] WzM[ 61tY`-e,albb/Bk+a/xx_34vP|sޏ-XCr*{%m\-UT`Xg*7^ъVVjb8e= +?F)QWb,B_OxǣIENDB`sight-25.1.0/tutorial/xml/tuto14_gui/rc/icons/monkey.png000066400000000000000000000042101503402212300231000ustar00rootroot00000000000000PNG  IHDR szzsBIT|d?IDATXk]U{s;tӖN[E1- HDԀH?1cb"4FAJ[ ZNBNNŤj\?;YljlHc̭qoFD%œ#B&LP՘+UxocA^9շm;;%fĺ]6u(Am)t㴭`yy>0T{_sC;-i[*9;}ل MX6P\``GW-RR uveRō$C2&q"Y#8eh$ҴZhYo_{fwJ~Mܒ|p]W<ULD/'+cG5UsQFEAiNFSLb˦~N^(UYU/ʵd"!ܣrgf-OoGmp6-cc4  ȸڕ=L|4cXVBS(pvpv|aOaeL36[cMo&0Z!J0x"͊e%kcha agvGAƱy]H;5Ҷ]my:[  SEFF"KgB}`i82XرL:L2emw`ywޕ "@Xv:}a#/(dSri}…R,1I)%PB*T; BȘbB5܃THe` RI1/ S,$M@7W9|_tJ?$1 1ЗuƉ߲qyډᡔ5}xvڻVʴb6 ÀZi;ĩG#Zph0^DI UÙ`w[)$,ĔDHIF5OQR& 5R%0#SX s& ԛm 0 H.BHvսKb߈YDԚVL¶;27b3eW~DRtz邀PR`NSR֒k 0X#`&h"~+Z4 +) Z‵ /!"]o_՘x ٠ 1W:u IENDB`sight-25.1.0/tutorial/xml/tuto14_gui/rc/plugin.xml000066400000000000000000000502721503402212300220060ustar00rootroot00000000000000 SubToolBarCfg hide_logo_shortcut/checked view_5_visibility_act/check hide_logo_shortcut/unchecked view_5_visibility_act/uncheck view_5_visibility_act/is_checked Tuto14Gui_AppCfg tuto14_gui tuto14_gui/tuto.ico