diff --git a/CMake/BuildConfigurations/Minimal.cmake b/CMake/BuildConfigurations/Minimal.cmake new file mode 100644 index 0000000000..91873eef15 --- /dev/null +++ b/CMake/BuildConfigurations/Minimal.cmake @@ -0,0 +1,5 @@ +set(BUILD_TESTING OFF CACHE BOOL "" FORCE) + +set(MITK_USE_BLUEBERRY OFF CACHE BOOL "" FORCE) +set(MITK_USE_CTK OFF CACHE BOOL "" FORCE) +set(MITK_USE_Qt5 OFF CACHE BOOL "" FORCE) diff --git a/CMake/BuildConfigurations/MinimalCmdApps.cmake b/CMake/BuildConfigurations/MinimalCmdApps.cmake new file mode 100644 index 0000000000..5831991dc7 --- /dev/null +++ b/CMake/BuildConfigurations/MinimalCmdApps.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Minimal.cmake) + +if(NOT MITK_USE_SUPERBUILD) + set(BUILD_CoreCmdApps ON CACHE BOOL "" FORCE) +endif() diff --git a/CMake/Whitelists/Minimal.cmake b/CMake/Whitelists/Minimal.cmake index 6bfa892451..d3725a79a3 100644 --- a/CMake/Whitelists/Minimal.cmake +++ b/CMake/Whitelists/Minimal.cmake @@ -1,8 +1,7 @@ set(enabled_modules Core CppMicroServices ) set(enabled_plugins - "" -) \ No newline at end of file +) diff --git a/CMake/Whitelists/MinimalCmdApps.cmake b/CMake/Whitelists/MinimalCmdApps.cmake new file mode 100644 index 0000000000..15b5d443ea --- /dev/null +++ b/CMake/Whitelists/MinimalCmdApps.cmake @@ -0,0 +1,6 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Minimal.cmake) + +list(APPEND enabled_modules + CommandLine + CoreCmdApps +) diff --git a/CMakeExternals/GDCM.cmake b/CMakeExternals/GDCM.cmake index ae0c5cf59a..a8d1577792 100644 --- a/CMakeExternals/GDCM.cmake +++ b/CMakeExternals/GDCM.cmake @@ -1,70 +1,71 @@ #----------------------------------------------------------------------------- # GDCM #----------------------------------------------------------------------------- # Sanity checks if(DEFINED GDCM_DIR AND NOT EXISTS ${GDCM_DIR}) message(FATAL_ERROR "GDCM_DIR variable is defined but corresponds to non-existing directory") endif() # Check if an external ITK build tree was specified. # If yes, use the GDCM from ITK, otherwise ITK will complain if(ITK_DIR) find_package(ITK) if(ITK_GDCM_DIR) set(GDCM_DIR ${ITK_GDCM_DIR}) endif() endif() set(proj GDCM) set(proj_DEPENDENCIES ) set(GDCM_DEPENDS ${proj}) if(NOT DEFINED GDCM_DIR) set(additional_args ) if(CTEST_USE_LAUNCHERS) list(APPEND additional_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() # On Mac some assertions fail that prevent reading certain DICOM files. Bug #19995 if(APPLE) list(APPEND additional_args "-DCMAKE_CXX_FLAGS_DEBUG:STRING=${CMAKE_CXX_FLAGS_DEBUG} -DNDEBUG" ) endif() mitk_query_custom_ep_vars() ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} URL ${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/gdcm-3.0.4.tar.gz URL_MD5 f12dbded708356d5fa0b5ed37ccdb66e CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${additional_args} -DGDCM_BUILD_SHARED_LIBS:BOOL=ON + -DGDCM_BUILD_DOCBOOK_MANPAGES:BOOL=OFF ${${proj}_CUSTOM_CMAKE_ARGS} CMAKE_CACHE_ARGS ${ep_common_cache_args} ${${proj}_CUSTOM_CMAKE_CACHE_ARGS} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} ${${proj}_CUSTOM_CMAKE_CACHE_DEFAULT_ARGS} DEPENDS ${proj_DEPENDENCIES} ) set(GDCM_DIR ${ep_prefix}) mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") find_package(GDCM) endif() diff --git a/CMakeExternals/OpenCV.cmake b/CMakeExternals/OpenCV.cmake index b26c86a806..2a1e7ceb87 100644 --- a/CMakeExternals/OpenCV.cmake +++ b/CMakeExternals/OpenCV.cmake @@ -1,84 +1,85 @@ #----------------------------------------------------------------------------- # OpenCV #----------------------------------------------------------------------------- if(MITK_USE_OpenCV) # Sanity checks if(DEFINED OpenCV_DIR AND NOT EXISTS ${OpenCV_DIR}) message(FATAL_ERROR "OpenCV_DIR variable is defined but corresponds to non-existing directory") endif() set(proj OpenCV) set(proj_DEPENDENCIES) set(OpenCV_DEPENDS ${proj}) if(NOT DEFINED OpenCV_DIR) set(additional_cmake_args -DBUILD_opencv_java:BOOL=OFF -DBUILD_opencv_ts:BOOL=OFF -DBUILD_PERF_TESTS:BOOL=OFF -DBUILD_opencv_python:BOOL=OFF -DBUILD_opencv_python3:BOOL=OFF -DBUILD_opencv_python_bindings_generator:BOOL=OFF #-DBUILD_NEW_PYTHON_SUPPORT:BOOL=OFF ) if(MITK_USE_Qt5) list(APPEND additional_cmake_args -DWITH_QT:BOOL=OFF -DWITH_QT_OPENGL:BOOL=OFF -DQT_QMAKE_EXECUTABLE:FILEPATH=${QT_QMAKE_EXECUTABLE} ) endif() if(CTEST_USE_LAUNCHERS) list(APPEND additional_cmake_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() mitk_query_custom_ep_vars() set(opencv_url ${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/opencv-3.4.8.tar.gz) set(opencv_url_md5 5aa8240c28c00a7dacdf51698e0ced77) ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} URL ${opencv_url} URL_MD5 ${opencv_url_md5} CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} -DBUILD_TESTS:BOOL=OFF -DBUILD_DOCS:BOOL=OFF -DBUILD_EXAMPLES:BOOL=OFF -DBUILD_DOXYGEN_DOCS:BOOL=OFF -DWITH_CUDA:BOOL=OFF -DWITH_VTK:BOOL=OFF -DENABLE_CXX11:BOOL=ON -DWITH_IPP:BOOL=OFF -DBUILD_IPP_IW:BOOL=OFF + -DENABLE_PRECOMPILED_HEADERS:BOOL=OFF ${additional_cmake_args} ${${proj}_CUSTOM_CMAKE_ARGS} CMAKE_CACHE_ARGS ${ep_common_cache_args} ${${proj}_CUSTOM_CMAKE_CACHE_ARGS} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} ${${proj}_CUSTOM_CMAKE_CACHE_DEFAULT_ARGS} DEPENDS ${proj_DEPENDENCIES} ) set(OpenCV_DIR ${ep_prefix}) mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/Modules/BasicImageProcessing/MiniApps/CMakeLists.txt b/Modules/BasicImageProcessing/MiniApps/CMakeLists.txt index c4254f4df3..e0aba755f2 100644 --- a/Modules/BasicImageProcessing/MiniApps/CMakeLists.txt +++ b/Modules/BasicImageProcessing/MiniApps/CMakeLists.txt @@ -1,99 +1,14 @@ option(BUILD_BasicImageProcessingMiniApps "Build commandline tools for Basic Image Processing" OFF) if(BUILD_BasicImageProcessingMiniApps OR MITK_BUILD_ALL_APPS) - - - include_directories( - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ) - - # list of miniapps - # if an app requires additional dependencies - # they are added after a "^^" and separated by "_" - set( basicImageProcessingMiniApps - FileConverter^^MitkCore - ImageTypeConverter^^MitkCore - RectifyImage^^MitkCore - SingleImageArithmetic^^MitkCore_MitkBasicImageProcessing - TwoImageArithmetic^^MitkCore_MitkBasicImageProcessing - ImageAndValueArithmetic^^MitkCore_MitkBasicImageProcessing - MaskRangeBasedFiltering^^MitkCore_MitkBasicImageProcessing - MaskOutlierFiltering^^MitkCore_MitkBasicImageProcessing - ResampleImage^^MitkCore_MitkBasicImageProcessing - ResampleMask^^MitkCore_MitkBasicImageProcessing - LaplacianOfGaussian^^MitkCore_MitkBasicImageProcessing - MultiResolutionPyramid^^MitkCore_MitkBasicImageProcessing - ForwardWavelet^^MitkCore_MitkBasicImageProcessing - ) - - foreach(basicImageProcessingMiniApp ${basicImageProcessingMiniApps}) - # extract mini app name and dependencies - string(REPLACE "^^" "\\;" miniapp_info ${basicImageProcessingMiniApp}) - set(miniapp_info_list ${miniapp_info}) - list(GET miniapp_info_list 0 appname) - list(GET miniapp_info_list 1 raw_dependencies) - string(REPLACE "_" "\\;" dependencies "${raw_dependencies}") - set(dependencies_list ${dependencies}) - - mitk_create_executable(${appname} - DEPENDS MitkCore MitkCommandLine ${dependencies_list} - PACKAGE_DEPENDS ITK - CPP_FILES ${appname}.cpp - ) - # CPP_FILES ${appname}.cpp mitkCommandLineParser.cpp - - if(EXECUTABLE_IS_ENABLED) - - # On Linux, create a shell script to start a relocatable application - if(UNIX AND NOT APPLE) - install(PROGRAMS "${MITK_SOURCE_DIR}/CMake/RunInstalledApp.sh" DESTINATION "." RENAME ${EXECUTABLE_TARGET}.sh) - endif() - - get_target_property(_is_bundle ${EXECUTABLE_TARGET} MACOSX_BUNDLE) - - if(APPLE) - if(_is_bundle) - set(_target_locations ${EXECUTABLE_TARGET}.app) - set(${_target_locations}_qt_plugins_install_dir ${EXECUTABLE_TARGET}.app/Contents/MacOS) - set(_bundle_dest_dir ${EXECUTABLE_TARGET}.app/Contents/MacOS) - set(_qt_plugins_for_current_bundle ${EXECUTABLE_TARGET}.app/Contents/MacOS) - set(_qt_conf_install_dirs ${EXECUTABLE_TARGET}.app/Contents/Resources) - install(TARGETS ${EXECUTABLE_TARGET} BUNDLE DESTINATION . ) - else() - if(NOT MACOSX_BUNDLE_NAMES) - set(_qt_conf_install_dirs bin) - set(_target_locations bin/${EXECUTABLE_TARGET}) - set(${_target_locations}_qt_plugins_install_dir bin) - install(TARGETS ${EXECUTABLE_TARGET} RUNTIME DESTINATION bin) - else() - foreach(bundle_name ${MACOSX_BUNDLE_NAMES}) - list(APPEND _qt_conf_install_dirs ${bundle_name}.app/Contents/Resources) - set(_current_target_location ${bundle_name}.app/Contents/MacOS/${EXECUTABLE_TARGET}) - list(APPEND _target_locations ${_current_target_location}) - set(${_current_target_location}_qt_plugins_install_dir ${bundle_name}.app/Contents/MacOS) - message( " set(${_current_target_location}_qt_plugins_install_dir ${bundle_name}.app/Contents/MacOS) ") - - install(TARGETS ${EXECUTABLE_TARGET} RUNTIME DESTINATION ${bundle_name}.app/Contents/MacOS/) - endforeach() - endif() - endif() - else() - set(_target_locations bin/${EXECUTABLE_TARGET}${CMAKE_EXECUTABLE_SUFFIX}) - set(${_target_locations}_qt_plugins_install_dir bin) - set(_qt_conf_install_dirs bin) - install(TARGETS ${EXECUTABLE_TARGET} RUNTIME DESTINATION bin) - endif() - endif() - endforeach() - - # On Linux, create a shell script to start a relocatable application - if(UNIX AND NOT APPLE) - install(PROGRAMS "${MITK_SOURCE_DIR}/CMake/RunInstalledApp.sh" DESTINATION "." RENAME ${EXECUTABLE_TARGET}.sh) - endif() - - if(EXECUTABLE_IS_ENABLED) - MITK_INSTALL_TARGETS(EXECUTABLES ${EXECUTABLE_TARGET}) - endif() - + mitkFunctionCreateCommandLineApp(NAME SingleImageArithmetic DEPENDS MitkBasicImageProcessing) + mitkFunctionCreateCommandLineApp(NAME TwoImageArithmetic DEPENDS MitkBasicImageProcessing) + mitkFunctionCreateCommandLineApp(NAME ImageAndValueArithmetic DEPENDS MitkBasicImageProcessing) + mitkFunctionCreateCommandLineApp(NAME MaskRangeBasedFiltering DEPENDS MitkBasicImageProcessing) + mitkFunctionCreateCommandLineApp(NAME MaskOutlierFiltering DEPENDS MitkBasicImageProcessing) + mitkFunctionCreateCommandLineApp(NAME ResampleImage DEPENDS MitkBasicImageProcessing) + mitkFunctionCreateCommandLineApp(NAME ResampleMask DEPENDS MitkBasicImageProcessing) + mitkFunctionCreateCommandLineApp(NAME LaplacianOfGaussian DEPENDS MitkBasicImageProcessing) + mitkFunctionCreateCommandLineApp(NAME MultiResolutionPyramid DEPENDS MitkBasicImageProcessing) + mitkFunctionCreateCommandLineApp(NAME ForwardWavelet DEPENDS MitkBasicImageProcessing) endif() diff --git a/Modules/Core/CMakeLists.txt b/Modules/Core/CMakeLists.txt index 7dd2c80b0e..bae8a92060 100644 --- a/Modules/Core/CMakeLists.txt +++ b/Modules/Core/CMakeLists.txt @@ -1,65 +1,66 @@ set(TOOL_CPPS "") # temporary suppress warnings in the following files until image accessors are fully integrated. set_source_files_properties( src/DataManagement/mitkImage.cpp COMPILE_FLAGS -DMITK_NO_DEPRECATED_WARNINGS ) set_source_files_properties( src/Controllers/mitkSliceNavigationController.cpp COMPILE_FLAGS -DMITK_NO_DEPRECATED_WARNINGS ) MITK_CREATE_MODULE( INCLUDE_DIRS PUBLIC ${MITK_BINARY_DIR} PRIVATE src/Algorithms src/Controllers src/DataManagement src/Interactions src/IO src/Rendering ${OPENGL_INCLUDE_DIR} DEPENDS PUBLIC mbilog CppMicroServices PACKAGE_DEPENDS PRIVATE tinyxml OpenGL PUBLIC ITK|ITKTransform+ITKImageGrid+ITKImageFeature+ITKIOImageBase+ITKIOHDF5+ITKIOLSM+ITKIOMRC+ITKIOBioRad+ITKIOGE+ITKIOStimulate+ITKIOBruker+ITKIOMINC # We privately use/link all ITK modules in order to support all IO, Transform, etc. # factories from ITK which are registered "automatically" via a factory manager. PRIVATE ITK PUBLIC VTK|vtkFiltersTexture+vtkFiltersParallel+vtkImagingStencil+vtkImagingMath+vtkInteractionStyle+vtkRenderingOpenGL2+vtkRenderingContextOpenGL2+vtkRenderingVolumeOpenGL2+vtkRenderingFreeType+vtkRenderingLabel+vtkInteractionWidgets+vtkIOGeometry+vtkIOXML PUBLIC Boost|boost SUBPROJECTS MITK-Core # Do not automatically create CppMicroServices initialization code. # Because the VTK 6 "auto-init" functionality injects file-local static # initialization code in every cpp file which includes a VTK header, # static initialization order becomes an issue again. For the Mitk # core library, we need to ensure that the VTK static initialization stuff # happens before the CppMicroServices initialization, since the latter # might already use VTK code which needs to access VTK object factories. # Hence, CppMicroServices initialization code is placed manually within # the mitkCoreActivator.cpp file. NO_INIT ) if(NOT TARGET ${MODULE_TARGET}) message(SEND_ERROR "Core target ${MODULE_TARGET} does not exist") endif() function(_itk_create_factory_register_manager) # In MITK_ITK_Config.cmake, we do *not* include ITK_USE_FILE, which # prevents multiple registrations/unregistrations of ITK IO factories # during library loading/unloading (of MITK libraries). However, we need # "one" place where the IO factories are registered at # least once. This could be the application executable, but every executable would # need to take care of that itself. Instead, we allow the auto registration in the # Mitk Core library. set(NO_DIRECTORY_SCOPED_ITK_COMPILE_DEFINITION 1) find_package(ITK) include(${ITK_USE_FILE}) if(NOT ITK_NO_IO_FACTORY_REGISTER_MANAGER) # We manually add the define which will be of target scope. MITK # patches ITK_USE_FILE to remove the directory scoped compile # definition since it would be propagated to other targets in the # same directory scope but these targets might want to *not* # use the ITK factory manager stuff. target_compile_definitions(${MODULE_TARGET} PRIVATE ITK_IO_FACTORY_REGISTER_MANAGER) endif() endfunction() _itk_create_factory_register_manager() if(MSVC_IDE OR MSVC_VERSION) target_link_libraries(${MODULE_TARGET} PRIVATE psapi.lib) endif() -add_subdirectory(TestingHelper) - -add_subdirectory(test) +if(BUILD_TESTING) + add_subdirectory(TestingHelper) + add_subdirectory(test) +endif() diff --git a/Modules/CoreCmdApps/CMakeLists.txt b/Modules/CoreCmdApps/CMakeLists.txt new file mode 100644 index 0000000000..12d751c887 --- /dev/null +++ b/Modules/CoreCmdApps/CMakeLists.txt @@ -0,0 +1,7 @@ +option(BUILD_CoreCmdApps "Build command-line apps of the MitkCore module" OFF) + +if(BUILD_CoreCmdApps OR MITK_BUILD_ALL_APPS) + mitkFunctionCreateCommandLineApp(NAME FileConverter) + mitkFunctionCreateCommandLineApp(NAME ImageTypeConverter) + mitkFunctionCreateCommandLineApp(NAME RectifyImage) +endif() diff --git a/Modules/BasicImageProcessing/MiniApps/FileConverter.cpp b/Modules/CoreCmdApps/FileConverter.cpp similarity index 100% rename from Modules/BasicImageProcessing/MiniApps/FileConverter.cpp rename to Modules/CoreCmdApps/FileConverter.cpp diff --git a/Modules/BasicImageProcessing/MiniApps/ImageTypeConverter.cpp b/Modules/CoreCmdApps/ImageTypeConverter.cpp similarity index 100% rename from Modules/BasicImageProcessing/MiniApps/ImageTypeConverter.cpp rename to Modules/CoreCmdApps/ImageTypeConverter.cpp diff --git a/Modules/BasicImageProcessing/MiniApps/RectifyImage.cpp b/Modules/CoreCmdApps/RectifyImage.cpp similarity index 100% rename from Modules/BasicImageProcessing/MiniApps/RectifyImage.cpp rename to Modules/CoreCmdApps/RectifyImage.cpp diff --git a/Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.cpp b/Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.cpp index 97736cc969..f38021161a 100644 --- a/Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.cpp +++ b/Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.cpp @@ -1,183 +1,175 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkMaskedAlgorithmHelper.h" #include // Mitk #include // MatchPoint #include "mapMaskedRegistrationAlgorithmInterface.h" #include namespace mitk { MaskedAlgorithmHelper::MaskedAlgorithmHelper(map::algorithm::RegistrationAlgorithmBase* algorithm) : m_AlgorithmBase(algorithm) { } bool MaskedAlgorithmHelper::HasMaskedRegistrationAlgorithmInterface(const map::algorithm::RegistrationAlgorithmBase* algorithm) { using MaskedInterface2D = const ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface<2, 2>; using MaskedInterface3D = const ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface<3, 3>; return dynamic_cast(algorithm) != nullptr && dynamic_cast(algorithm) != nullptr; }; bool MaskedAlgorithmHelper:: CheckSupport(const mitk::Image* movingMask, const mitk::Image* targetMask) const { if (! m_AlgorithmBase) mapDefaultExceptionStaticMacro(<< "Error, cannot check data. Helper has no algorithm defined."); unsigned int movingDim = m_AlgorithmBase->getMovingDimensions(); unsigned int targetDim = m_AlgorithmBase->getTargetDimensions(); bool result = movingDim == targetDim; if ( movingMask) { result = result && (movingMask->GetDimension() == movingDim); - - if (movingDim == 2) - { - typedef itk::Image MaskImageType; - mitk::PixelType maskPixelType = mitk::MakePixelType(); - - result = result && (maskPixelType == movingMask->GetPixelType()); - } - else if (movingDim == 3) - { - typedef itk::Image MaskImageType; - mitk::PixelType maskPixelType = mitk::MakePixelType(); - - result = result && (maskPixelType == movingMask->GetPixelType()); - } } if ( targetMask) { result = result && (targetMask->GetDimension() == targetDim); - - if (movingDim == 2) - { - typedef itk::Image MaskImageType; - mitk::PixelType maskPixelType = mitk::MakePixelType(); - - result = result && (maskPixelType == targetMask->GetPixelType()); - } - else if (movingDim == 3) - { - typedef itk::Image MaskImageType; - mitk::PixelType maskPixelType = mitk::MakePixelType(); - - result = result && (maskPixelType == targetMask->GetPixelType()); - } } if (movingDim == 2) { typedef ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface<2, 2> MaskedInterface; const MaskedInterface* pMaskedReg = dynamic_cast(m_AlgorithmBase.GetPointer()); result = result && pMaskedReg; } else if (movingDim == 3) { typedef ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface<3, 3> MaskedInterface; const MaskedInterface* pMaskedReg = dynamic_cast(m_AlgorithmBase.GetPointer()); result = result && pMaskedReg; } else { result = false; } return result; }; bool MaskedAlgorithmHelper::SetMasks(const mitk::Image* movingMask, const mitk::Image* targetMask) { if (! m_AlgorithmBase) mapDefaultExceptionStaticMacro(<< "Error, cannot set data. Helper has no algorithm defined."); if (! CheckSupport(movingMask, targetMask)) return false; unsigned int movingDim = m_AlgorithmBase->getMovingDimensions(); unsigned int targetDim = m_AlgorithmBase->getTargetDimensions(); if (movingDim!=targetDim) return false; if (movingDim == 2) { return DoSetMasks<2,2>(movingMask, targetMask); } else if (movingDim == 3) { return DoSetMasks<3,3>(movingMask, targetMask); } return false; }; - template + template bool MaskedAlgorithmHelper::DoSetMasks(const mitk::Image* movingMask, const mitk::Image* targetMask) { - typedef itk::SpatialObject MovingSpatialType; - typedef itk::SpatialObject TargetSpatialType; + typedef itk::SpatialObject MovingSpatialType; + typedef itk::SpatialObject TargetSpatialType; - typedef ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface MaskedRegInterface; + typedef ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface MaskedRegInterface; MaskedRegInterface* pAlg = dynamic_cast(m_AlgorithmBase.GetPointer()); if (!pAlg) return false; if (movingMask) { - AccessFixedTypeByItk(movingMask, DoConvertMask, (MaskPixelType), (VImageDimension1)); + AccessFixedDimensionByItk(movingMask, DoConvertMask, VMovingDimension); typename MovingSpatialType::Pointer movingSpatial = dynamic_cast(m_convertResult.GetPointer()); - if (! movingSpatial) mapDefaultExceptionStaticMacro(<< "Error, cannot convert moving mask."); + if (!movingSpatial) mapDefaultExceptionStaticMacro(<< "Error, cannot convert moving mask."); pAlg->setMovingMask(movingSpatial); } else { pAlg->setMovingMask(nullptr); } if (targetMask) { - AccessFixedTypeByItk(targetMask, DoConvertMask, (MaskPixelType), (VImageDimension2)); + AccessFixedDimensionByItk(targetMask, DoConvertMask, VTargetDimension); typename TargetSpatialType::Pointer targetSpatial = dynamic_cast(m_convertResult.GetPointer()); if (! targetSpatial) mapDefaultExceptionStaticMacro(<< "Error, cannot convert moving mask."); pAlg->setTargetMask(targetSpatial); } else { pAlg->setTargetMask(nullptr); } return true; } - template - void MaskedAlgorithmHelper::DoConvertMask(const itk::Image* mask) + template + typename itk::SpatialObject::Pointer + MaskedAlgorithmHelper::ConvertMaskSO(const itk::Image* mask) const { typedef itk::ImageMaskSpatialObject SpatialType; typename SpatialType::Pointer spatial = SpatialType::New(); spatial->SetImage(mask); - m_convertResult = spatial.GetPointer(); + return spatial.GetPointer(); + } + + template + void MaskedAlgorithmHelper::DoConvertMask(const itk::Image* mask) + { + using InImageType = itk::Image; + using MaskImageType = itk::Image; + + typedef itk::CastImageFilter< InImageType, MaskImageType > CastFilterType; + typename CastFilterType::Pointer imageCaster = CastFilterType::New(); + + imageCaster->SetInput(mask); + + auto castedMask = imageCaster->GetOutput(); + imageCaster->Update(); + m_convertResult = ConvertMaskSO(castedMask); } + template + void MaskedAlgorithmHelper::DoConvertMask(const itk::Image* mask) + { + m_convertResult = ConvertMaskSO(mask); + } } diff --git a/Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.h b/Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.h index 515ebc90c8..4d12409887 100644 --- a/Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.h +++ b/Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.h @@ -1,78 +1,86 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkMaskedAlgorithmHelper_h #define mitkMaskedAlgorithmHelper_h +#include "itkSpatialObject.h" //MatchPoint #include "mapRegistrationAlgorithmBase.h" //MITK #include //MITK #include "MitkMatchPointRegistrationExports.h" namespace mitk { /*! \brief MaskedAlgorithmHelper Helper class as an easy bridge to set mitk images as masks for registration algorithms. It is assumed that the Image indicates the mask by pixel values != 0. \remark Currently only 2D-2D and 3D-3D algorithms are supported. - \remark Currently only masks with pixel type unsigned char (default mitk segmentation images) are supported. \remark Current implementation is not thread-save. Just use one Helper class per registration task. */ class MITKMATCHPOINTREGISTRATION_EXPORT MaskedAlgorithmHelper { public: MaskedAlgorithmHelper(map::algorithm::RegistrationAlgorithmBase* algorithm); /** Set one or both masks to an algorithm. * If the algorithm does not support masks it will be ignored. * @remark Set a mask to nullptr if you don't want to set it. * @return Indicates if the masks could be set/was supported by algorithm.*/ bool SetMasks(const mitk::Image* movingMask, const mitk::Image* targetMask); /** Checks if the algorithm supports masks of the passed type.*/ bool CheckSupport(const mitk::Image* movingMask, const mitk::Image* targetMask) const; static bool HasMaskedRegistrationAlgorithmInterface(const map::algorithm::RegistrationAlgorithmBase* algorithm); ~MaskedAlgorithmHelper() {} private: - typedef unsigned char MaskPixelType; + using MaskPixelType = unsigned char; MaskedAlgorithmHelper& operator = (const MaskedAlgorithmHelper&); MaskedAlgorithmHelper(const MaskedAlgorithmHelper&); /**Internal helper that is used by SetMasks if the data are images to set them properly.*/ template bool DoSetMasks(const mitk::Image* movingMask, const mitk::Image* targetMask); - /**Internal helper that is used by SetData if the data are images to set them properly.*/ + /**Internal helper that is used by SetData if the data are images to cast and set them properly.*/ template void DoConvertMask(const itk::Image* mask); + /**Internal helper that is used by SetData if the data are images to set them properly.*/ + template + void DoConvertMask(const itk::Image* mask); + + /**Internal helper that is used to pack the mask image into a spatial object.*/ + template + typename itk::SpatialObject::Pointer ConvertMaskSO(const itk::Image* mask) const; + /**Helper member that containes the result of the last call of DoConvertMask().*/ itk::DataObject::Pointer m_convertResult; map::algorithm::RegistrationAlgorithmBase::Pointer m_AlgorithmBase; }; } #endif diff --git a/Modules/ModuleList.cmake b/Modules/ModuleList.cmake index 4daa70f838..d65ee8faa4 100644 --- a/Modules/ModuleList.cmake +++ b/Modules/ModuleList.cmake @@ -1,85 +1,86 @@ # The entries in the mitk_modules list must be # ordered according to their dependencies. set(MITK_MODULES Core CommandLine + CoreCmdApps AppUtil LegacyIO DataTypesExt Annotation LegacyGL AlgorithmsExt MapperExt DICOMReader DICOMReaderServices DICOMQI DICOMTesting SceneSerializationBase PlanarFigure ImageDenoising ImageExtraction SceneSerialization Gizmo GraphAlgorithms Multilabel Chart ImageStatistics ContourModel SurfaceInterpolation Segmentation QtWidgets QtWidgetsExt ImageStatisticsUI SegmentationUI MatchPointRegistration MatchPointRegistrationUI Classification OpenIGTLink IGTBase IGT CameraCalibration OpenCL OpenCVVideoSupport QtOverlays ToFHardware ToFProcessing ToFUI PhotoacousticsHardware PhotoacousticsAlgorithms PhotoacousticsLib US USUI DicomUI Remeshing Python QtPython Persistence OpenIGTLinkUI IGTUI DicomRT RTUI IOExt XNAT TubeGraph BiophotonicsHardware BoundingShape RenderWindowManager RenderWindowManagerUI SemanticRelations SemanticRelationsUI CEST BasicImageProcessing ModelFit ModelFitUI Pharmacokinetics PharmacokineticsUI DICOMPM REST RESTService DICOMweb ) if(MITK_ENABLE_PIC_READER) list(APPEND MITK_MODULES IpPicSupportIO) endif() diff --git a/Modules/QtWidgets/files.cmake b/Modules/QtWidgets/files.cmake index 970d05401a..73069607e5 100644 --- a/Modules/QtWidgets/files.cmake +++ b/Modules/QtWidgets/files.cmake @@ -1,135 +1,137 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES QmitkAbstractDataStorageModel.cpp QmitkAbstractMultiWidget.cpp + QmitkAbstractNodeSelectionWidget.cpp QmitkApplicationCursor.cpp QmitkDataStorageComboBox.cpp QmitkDataStorageDefaultListModel.cpp QmitkDataStorageHistoryModel.cpp QmitkDataStorageListModel.cpp QmitkDataStorageTableModel.cpp QmitkDataStorageSimpleTreeModel.cpp QmitkDataStorageTreeModel.cpp QmitkDataStorageTreeModelInternalItem.cpp QmitkDnDDataNodeWidget.cpp QmitkFileReaderOptionsDialog.cpp QmitkFileReaderWriterOptionsWidget.cpp QmitkFileWriterOptionsDialog.cpp QmitkInteractionSchemeToolBar.cpp QmitkIOUtil.cpp QmitkLevelWindowPresetDefinitionDialog.cpp QmitkLevelWindowRangeChangeDialog.cpp QmitkLevelWindowWidgetContextMenu.cpp QmitkLevelWindowWidget.cpp QmitkLineEditLevelWindowWidget.cpp QmitkMemoryUsageIndicatorView.cpp QmitkMouseModeSwitcher.cpp QmitkMimeTypes.cpp QmitkMultiWidgetConfigurationToolBar.cpp QmitkMultiWidgetLayoutManager.cpp QmitkMultiWidgetLayoutSelectionWidget.cpp QmitkNodeDescriptor.cpp QmitkColoredNodeDescriptor.cpp QmitkNodeDescriptorManager.cpp QmitkProgressBar.cpp QmitkPropertiesTableEditor.cpp QmitkPropertiesTableModel.cpp QmitkPropertyDelegate.cpp QmitkRegisterClasses.cpp QmitkRenderingManager.cpp QmitkRenderingManagerFactory.cpp QmitkRenderWindow.cpp QmitkRenderWindowMenu.cpp QmitkRenderWindowWidget.cpp QmitkServiceListWidget.cpp QmitkSliderLevelWindowWidget.cpp QmitkStdMultiWidget.cpp QmitkMxNMultiWidget.cpp QmitkDataStorageComboBoxWithSelectNone.cpp QmitkDataStorageFilterProxyModel.cpp QmitkPropertyItem.cpp QmitkPropertyItemDelegate.cpp QmitkPropertyItemModel.cpp QmitkStyleManager.cpp QmitkAbstractDataStorageInspector.cpp QmitkDataStorageFavoriteNodesInspector.cpp QmitkDataStorageListInspector.cpp QmitkDataStorageTreeInspector.cpp QmitkDataStorageSelectionHistoryInspector.cpp QmitkModelViewSelectionConnector.cpp mitkIDataStorageInspectorProvider.cpp mitkQtWidgetsActivator.cpp mitkDataStorageInspectorGenerator.cpp QmitkOverlayWidget.cpp QmitkNodeDetailsDialog.cpp ) set(MOC_H_FILES include/QmitkAbstractDataStorageModel.h include/QmitkAbstractMultiWidget.h + include/QmitkAbstractNodeSelectionWidget.h include/QmitkDataStorageComboBox.h include/QmitkDataStorageTableModel.h include/QmitkDataStorageTreeModel.h include/QmitkDataStorageSimpleTreeModel.h include/QmitkDataStorageDefaultListModel.h include/QmitkDnDDataNodeWidget.h include/QmitkFileReaderOptionsDialog.h include/QmitkFileReaderWriterOptionsWidget.h include/QmitkFileWriterOptionsDialog.h include/QmitkInteractionSchemeToolBar.h include/QmitkLevelWindowPresetDefinitionDialog.h include/QmitkLevelWindowRangeChangeDialog.h include/QmitkLevelWindowWidgetContextMenu.h include/QmitkLevelWindowWidget.h include/QmitkLineEditLevelWindowWidget.h include/QmitkMemoryUsageIndicatorView.h include/QmitkMouseModeSwitcher.h include/QmitkMultiWidgetConfigurationToolBar.h include/QmitkMultiWidgetLayoutManager.h include/QmitkMultiWidgetLayoutSelectionWidget.h include/QmitkNodeDescriptor.h include/QmitkColoredNodeDescriptor.h include/QmitkNodeDescriptorManager.h include/QmitkProgressBar.h include/QmitkPropertiesTableEditor.h include/QmitkPropertyDelegate.h include/QmitkRenderingManager.h include/QmitkRenderWindow.h include/QmitkRenderWindowMenu.h include/QmitkRenderWindowWidget.h include/QmitkServiceListWidget.h include/QmitkSliderLevelWindowWidget.h include/QmitkStdMultiWidget.h include/QmitkMxNMultiWidget.h include/QmitkDataStorageComboBoxWithSelectNone.h include/QmitkPropertyItemDelegate.h include/QmitkPropertyItemModel.h include/QmitkAbstractDataStorageInspector.h include/QmitkDataStorageFavoriteNodesInspector.h include/QmitkDataStorageListInspector.h include/QmitkDataStorageTreeInspector.h include/QmitkDataStorageHistoryModel.h include/QmitkDataStorageSelectionHistoryInspector.h include/QmitkModelViewSelectionConnector.h include/QmitkOverlayWidget.h include/QmitkNodeDetailsDialog.h ) set(UI_FILES src/QmitkFileReaderOptionsDialog.ui src/QmitkFileWriterOptionsDialog.ui src/QmitkLevelWindowPresetDefinition.ui src/QmitkLevelWindowWidget.ui src/QmitkLevelWindowRangeChange.ui src/QmitkMemoryUsageIndicator.ui src/QmitkMultiWidgetLayoutSelectionWidget.ui src/QmitkServiceListWidgetControls.ui src/QmitkDataStorageListInspector.ui src/QmitkDataStorageTreeInspector.ui src/QmitkDataStorageSelectionHistoryInspector.ui ) set(QRC_FILES resource/Qmitk.qrc ) diff --git a/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h new file mode 100644 index 0000000000..c014cc745a --- /dev/null +++ b/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h @@ -0,0 +1,241 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + + +#ifndef QMITK_ABSTRACT_NODE_SELECTION_WIDGET_H +#define QMITK_ABSTRACT_NODE_SELECTION_WIDGET_H + +#include +#include +#include + +#include + +#include + +class QmitkAbstractDataStorageModel; +class QAbstractItemVew; + +/** +* \class QmitkAbstractNodeSelectionWidget +* \brief Abstract base class for the selection of data from a data storage. +*/ +class MITKQTWIDGETS_EXPORT QmitkAbstractNodeSelectionWidget : public QWidget +{ + Q_OBJECT + +public: + explicit QmitkAbstractNodeSelectionWidget(QWidget* parent = nullptr); + virtual ~QmitkAbstractNodeSelectionWidget() override; + + /** + * @brief Sets the data storage that will be used /monitored by widget. + * + * @par dataStorage A pointer to the data storage to set. + */ + void SetDataStorage(mitk::DataStorage* dataStorage); + + /** + * Sets the node predicate and updates the widget, according to the node predicate. + * Implement OnNodePredicateChange() for custom actualization of a derived widget class. + * + * @par nodePredicate A pointer to node predicate. + */ + void SetNodePredicate(const mitk::NodePredicateBase* nodePredicate); + + const mitk::NodePredicateBase* GetNodePredicate() const; + + QString GetInvalidInfo() const; + QString GetEmptyInfo() const; + QString GetPopUpTitel() const; + QString GetPopUpHint() const; + + bool GetSelectionIsOptional() const; + + bool GetSelectOnlyVisibleNodes() const; + + using NodeList = QList; + + /** Returns the selected nodes, as emitted with CurrentSelectionChanged*/ + NodeList GetSelectedNodes() const; + +Q_SIGNALS: + /* + * @brief A signal that will be emitted if the selected node has changed. + * + * @par nodes A list of data nodes that are newly selected. + */ + void CurrentSelectionChanged(NodeList nodes); + +public Q_SLOTS: + /* + * @brief Change the selection modus of the item view's selection model. + * + * If true, an incoming selection will be filtered (reduced) to only those nodes that are visible by the current view. + * An outgoing selection can then at most contain the filtered nodes. + * If false, the incoming non-visible selection will be stored and later added to the outgoing selection, + * to include the original selection that could not be modified. + * The part of the original selection, that is non-visible are the nodes, that do not fullfill the predicate. + * + * @par selectOnlyVisibleNodes The bool value to define the selection modus. + */ + void SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes); + + /* + * @brief Transform a list of data nodes into a model selection and set this as a new selection of the + * selection model of the private member item view. + * + * The function filters the given list of nodes according to the 'm_SelectOnlyVisibleNodes' member variable. If + * necessary, the non-visible nodes are stored. This is done if 'm_SelectOnlyVisibleNodes' is false: In this case + * the selection may be filtered and only a subset of the selected nodes may be visible and therefore (de-)selectable + * in the data storage viewer. By storing the non-visible nodes it is possible to send the new, modified selection + * but also include the selected nodes from the original selection that could not be modified (see 'SetSelectOnlyVisibleNodes'). + * + * @par nodes A list of data nodes that should be newly selected. + */ + void SetCurrentSelection(NodeList selectedNodes); + + /** Set the info text that should be displayed if no (valid) node is selected, + * but a selection is mandatory. + * The string can contain HTML code. if wanted*/ + void SetInvalidInfo(QString info); + + /** Set the info text that should be displayed if no (valid) node is selected, + * but a selection is optional. + * The string can contain HTML code. if wanted*/ + void SetEmptyInfo(QString info); + + /** Set the caption of the popup that is displayed to alter the selection. + * The string can contain HTML code. if wanted*/ + void SetPopUpTitel(QString info); + + /** Set the hint text of the popup that is displayed to alter the selection. + * The string can contain HTML code. if wanted*/ + void SetPopUpHint(QString info); + + /** Set the widget into an optional mode. Optional means that the selection of no valid + node does not mean an invalid state. Thus no node is a valid "node" selection too.*/ + void SetSelectionIsOptional(bool isOptional); + +protected Q_SLOTS: + /** Call to remove a node from the current selection. If the node is part of the current selection, + this will trigger ReviseSelectionChanged(), AllowEmissionOfSelection() and if there is really a change, + will also emit CurrentSelectionChanged.*/ + void RemoveNodeFromSelection(const mitk::DataNode* node); + +protected: + /**Method is called if the display of the selected nodes should be updated (e.g. because the selection changed)*/ + virtual void UpdateInfo() = 0; + + /**Method is called if the predicate has changed, before the selection will be updated according to the new predicate. + The default implementation does nothing. + @remark If you are only interested to know when the selection has changed, overwrite OnInternalSelectionChange().*/ + virtual void OnNodePredicateChanged(); + + /**Method is called if the data storage has changed. The selection will be automatically be reseted afterwards. + The default implementation does nothing.*/ + virtual void OnDataStorageChanged(); + + /** This member function will called when ever a new internal selection has been determined. This can be + used to update the state of internal widgets. The default implementation does nothing.*/ + virtual void OnInternalSelectionChanged(); + + /**Method is called when a node is added to the storage. Default implementation does nothing. + Derived widgets can override the method if they want to react on new nodes in the storage.*/ + virtual void OnNodeAddedToStorage(const mitk::DataNode* node); + + /**Method is called when a node is removed from the storage. The removed node is passed as + variable. This member is called directly before the node will be removed from the current selection if + he was a part. Default implementation does nothing. */ + virtual void OnNodeRemovedFromStorage(const mitk::DataNode* node); + + /**Method is called if the internal selection has changed. It will call following methods, that can be overriden to change + behavior in derived classes: + - pre internal selection change: ReviseSelectionChanged() + - post internal selection change: OnInternalSelectionChanged(), UpdateInfo() and AllowEmissionOfSelection() (via EmitSelection()) + . + If the emission is needed and allowed it will also trigger the emission via EmitSelection().*/ + void HandleChangeOfInternalSelection(NodeList newInternalSelection); + + /**Compiles the list of node that would be emitted. It always contains the internal selection. + Depending on SelectOnlyVisibleNodes it also adds all external select nodes that weren't visible + (failed the predicate).*/ + NodeList CompileEmitSelection() const; + + /** This member function is called if the internal selection is about to be changed by the + base implementation. + This is the slot where derived classes can revise and change the internal selection before widget updates, + signal emissions and other things are triggered. Default implementation does nothing, thus it keeps the + passed internal selection as compiled by the base implementation.*/ + virtual void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection); + + /** This function will be called before the CurrentSelectionChanged signal is emitted. The return value indicates + if the signal should be emitted (true = emission; false = no emission). The default implementation always + returns true. + @param emissionCandidates The nodes that will be emitted if the function returns true.*/ + virtual bool AllowEmissionOfSelection(const NodeList& emissionCandidates) const; + + /** Checks if the new emission differs from the last emission. If this is the case and AllowEmissionOfSelection() + returns true the new selection will be emited. */ + void EmitSelection(const NodeList& emissionCandidates); + + void SetCurrentInternalSelection(NodeList selectedNodes); + const NodeList& GetCurrentInternalSelection() const; + const NodeList& GetCurrentExternalSelection() const; + + mitk::WeakPointer m_DataStorage; + mitk::NodePredicateBase::ConstPointer m_NodePredicate; + + QString m_InvalidInfo; + QString m_EmptyInfo; + QString m_PopUpTitel; + QString m_PopUpHint; + + /** See documentation of SetSelectOnlyVisibleNodes for details*/ + bool m_IsOptional; + /** See documentation of SetSelectionIsOptional for details*/ + bool m_SelectOnlyVisibleNodes; + +private: + /** Helper triggered on the storage delete event */ + void SetDataStorageDeleted(); + + /**Member is called when a node is added to the storage. + Derived widgets can override the method OnNodeAddedToStorage if they want to react on new nodes in the storage.*/ + void NodeAddedToStorage(const mitk::DataNode* node); + + /**Member is called when a node is removed from the storage. It calls OnNodeRemovedFromStorage() and afterwards + it removes the removed node form the selection (if it is part of the current selection). + Derived classes can override OnNodeRemovedFromStorage() to react on the fact that a node might be removed and + their selection might change, because the removed node is part of there selection.*/ + void NodeRemovedFromStorage(const mitk::DataNode* node); + + void OnNodeModified(const itk::Object * /*caller*/, const itk::EventObject &); + + void AddNodeObserver(mitk::DataNode* node); + void RemoveNodeObserver(mitk::DataNode* node); + + unsigned long m_DataStorageDeletedTag; + + NodeList m_CurrentInternalSelection; + NodeList m_CurrentExternalSelection; + + NodeList m_LastEmission; + bool m_LastEmissionAllowance; + + using NodeObserverTagMapType = std::map; + NodeObserverTagMapType m_NodeObserverTags; + + /** Help to prevent recursions due to signal loops when emitting selections.*/ + bool m_RecursionGuard; +}; +#endif // QmitkAbstractNodeSelectionWidget_H diff --git a/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp new file mode 100644 index 0000000000..c352fe31c4 --- /dev/null +++ b/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp @@ -0,0 +1,410 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + + +#include "QmitkAbstractNodeSelectionWidget.h" +#include "QmitkModelViewSelectionConnector.h" + +QmitkAbstractNodeSelectionWidget::QmitkAbstractNodeSelectionWidget(QWidget* parent) : QWidget(parent), m_InvalidInfo("Error. Select data."), +m_EmptyInfo("Empty. Make a selection."), m_PopUpTitel("Select a data node"), m_PopUpHint(""), +m_IsOptional(false), m_SelectOnlyVisibleNodes(true), m_DataStorageDeletedTag(0), m_LastEmissionAllowance(true), m_RecursionGuard(false) +{ +} + +QmitkAbstractNodeSelectionWidget::~QmitkAbstractNodeSelectionWidget() +{ + auto dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNotNull()) + { + // remove Listener for the data storage itself + dataStorage->RemoveObserver(m_DataStorageDeletedTag); + + // remove "add node listener" from data storage + dataStorage->AddNodeEvent.RemoveListener( + mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeAddedToStorage)); + + // remove "remove node listener" from data storage + dataStorage->RemoveNodeEvent.RemoveListener( + mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage)); + } + + for (auto& node : m_CurrentInternalSelection) + { + this->RemoveNodeObserver(node); + } +} + +QmitkAbstractNodeSelectionWidget::NodeList QmitkAbstractNodeSelectionWidget::GetSelectedNodes() const +{ + return this->CompileEmitSelection(); +} + +void QmitkAbstractNodeSelectionWidget::SetDataStorage(mitk::DataStorage* dataStorage) +{ + if (m_DataStorage == dataStorage) + { + return; + } + + auto oldStorage = m_DataStorage.Lock(); + if (oldStorage.IsNotNull()) + { + // remove Listener for the data storage itself + oldStorage->RemoveObserver(m_DataStorageDeletedTag); + + // remove "add node listener" from old data storage + oldStorage->AddNodeEvent.RemoveListener( + mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeAddedToStorage)); + + // remove "remove node listener" from old data storage + oldStorage->RemoveNodeEvent.RemoveListener( + mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage)); + } + + m_DataStorage = dataStorage; + + auto newStorage = m_DataStorage.Lock(); + + if (newStorage.IsNotNull()) + { + // add Listener for the data storage itself + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &QmitkAbstractNodeSelectionWidget::SetDataStorageDeleted); + m_DataStorageDeletedTag = newStorage->AddObserver(itk::DeleteEvent(), command); + + // add "add node listener" for new data storage + newStorage->AddNodeEvent.AddListener( + mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeAddedToStorage)); + + // add remove node listener for new data storage + newStorage->RemoveNodeEvent.AddListener( + mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage)); + } + + this->OnDataStorageChanged(); + + this->HandleChangeOfInternalSelection({}); +} + +void QmitkAbstractNodeSelectionWidget::SetNodePredicate(const mitk::NodePredicateBase* nodePredicate) +{ + if (m_NodePredicate != nodePredicate) + { + m_NodePredicate = nodePredicate; + + this->OnNodePredicateChanged(); + + NodeList newInternalNodes; + + for (auto& node : m_CurrentInternalSelection) + { + if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) + { + newInternalNodes.append(node); + } + } + + if (!m_SelectOnlyVisibleNodes) + { + for (auto& node : m_CurrentExternalSelection) + { + if (!newInternalNodes.contains(node) && (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node))) + { + newInternalNodes.append(node); + } + } + } + + this->HandleChangeOfInternalSelection(newInternalNodes); + } +} + +void QmitkAbstractNodeSelectionWidget::HandleChangeOfInternalSelection(NodeList newInternalSelection) +{ + this->ReviseSelectionChanged(m_CurrentInternalSelection, newInternalSelection); + + this->SetCurrentInternalSelection(newInternalSelection); + + this->OnInternalSelectionChanged(); + + auto newEmission = this->CompileEmitSelection(); + + this->EmitSelection(newEmission); + + this->UpdateInfo(); +} + +void QmitkAbstractNodeSelectionWidget::SetCurrentSelection(NodeList selectedNodes) +{ + if (!m_RecursionGuard) + { + m_CurrentExternalSelection = selectedNodes; + + auto dataStorage = m_DataStorage.Lock(); + NodeList newInternalSelection; + for (auto node : selectedNodes) + { + if (dataStorage.IsNotNull() && dataStorage->Exists(node) && (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node))) + { + newInternalSelection.append(node); + } + } + + this->HandleChangeOfInternalSelection(newInternalSelection); + } +} + +const mitk::NodePredicateBase* QmitkAbstractNodeSelectionWidget::GetNodePredicate() const +{ + return m_NodePredicate; +} + +QString QmitkAbstractNodeSelectionWidget::GetInvalidInfo() const +{ + return m_InvalidInfo; +} + +QString QmitkAbstractNodeSelectionWidget::GetEmptyInfo() const +{ + return m_EmptyInfo; +} + +QString QmitkAbstractNodeSelectionWidget::GetPopUpTitel() const +{ + return m_PopUpTitel; +} + +QString QmitkAbstractNodeSelectionWidget::GetPopUpHint() const +{ + return m_PopUpHint; +} + +bool QmitkAbstractNodeSelectionWidget::GetSelectionIsOptional() const +{ + return m_IsOptional; +} + +bool QmitkAbstractNodeSelectionWidget::GetSelectOnlyVisibleNodes() const +{ + return m_SelectOnlyVisibleNodes; +} + +void QmitkAbstractNodeSelectionWidget::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) +{ + if (m_SelectOnlyVisibleNodes != selectOnlyVisibleNodes) + { + m_SelectOnlyVisibleNodes = selectOnlyVisibleNodes; + + auto newEmission = this->CompileEmitSelection(); + + this->EmitSelection(newEmission); + } +} + +void QmitkAbstractNodeSelectionWidget::SetInvalidInfo(QString info) +{ + m_InvalidInfo = info; + this->UpdateInfo(); +} + +void QmitkAbstractNodeSelectionWidget::SetEmptyInfo(QString info) +{ + m_EmptyInfo = info; + this->UpdateInfo(); +} + +void QmitkAbstractNodeSelectionWidget::SetPopUpTitel(QString info) +{ + m_PopUpTitel = info; +} + +void QmitkAbstractNodeSelectionWidget::SetPopUpHint(QString info) +{ + m_PopUpHint = info; +} + +void QmitkAbstractNodeSelectionWidget::SetSelectionIsOptional(bool isOptional) +{ + m_IsOptional = isOptional; + this->UpdateInfo(); +} + +void QmitkAbstractNodeSelectionWidget::SetDataStorageDeleted() +{ + this->OnDataStorageChanged(); + this->HandleChangeOfInternalSelection({}); +} + +void QmitkAbstractNodeSelectionWidget::ReviseSelectionChanged(const NodeList& /*oldInternalSelection*/, NodeList& /*newInternalSelection*/) +{ +} + +bool QmitkAbstractNodeSelectionWidget::AllowEmissionOfSelection(const NodeList& /*emissionCandidates*/) const +{ + return true; +} + +void QmitkAbstractNodeSelectionWidget::EmitSelection(const NodeList& emissionCandidates) +{ + m_LastEmissionAllowance = this->AllowEmissionOfSelection(emissionCandidates); + if (m_LastEmissionAllowance && !EqualNodeSelections(m_LastEmission, emissionCandidates)) + { + m_RecursionGuard = true; + emit CurrentSelectionChanged(emissionCandidates); + m_RecursionGuard = false; + m_LastEmission = emissionCandidates; + } +} + +void QmitkAbstractNodeSelectionWidget::SetCurrentInternalSelection(NodeList selectedNodes) +{ + for (auto& node : m_CurrentInternalSelection) + { + this->RemoveNodeObserver(node); + } + + m_CurrentInternalSelection = selectedNodes; + + for (auto& node : m_CurrentInternalSelection) + { + this->AddNodeObserver(node); + } +} + +const QmitkAbstractNodeSelectionWidget::NodeList& QmitkAbstractNodeSelectionWidget::GetCurrentInternalSelection() const +{ + return m_CurrentInternalSelection; +} + +const QmitkAbstractNodeSelectionWidget::NodeList& QmitkAbstractNodeSelectionWidget::GetCurrentExternalSelection() const +{ + return m_CurrentExternalSelection; +} + +void QmitkAbstractNodeSelectionWidget::OnNodePredicateChanged() +{ +} + +void QmitkAbstractNodeSelectionWidget::OnDataStorageChanged() +{ +} + +void QmitkAbstractNodeSelectionWidget::OnInternalSelectionChanged() +{ +} + +void QmitkAbstractNodeSelectionWidget::NodeAddedToStorage(const mitk::DataNode* node) +{ + this->OnNodeAddedToStorage(node); +} + +void QmitkAbstractNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* /*node*/) +{ +} + +void QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage(const mitk::DataNode* node) +{ + this->OnNodeRemovedFromStorage(node); + this->RemoveNodeFromSelection(node); +} + +void QmitkAbstractNodeSelectionWidget::OnNodeRemovedFromStorage(const mitk::DataNode* /*node*/) +{ +} + +QmitkAbstractNodeSelectionWidget::NodeList QmitkAbstractNodeSelectionWidget::CompileEmitSelection() const +{ + NodeList result = m_CurrentInternalSelection; + + if (!m_SelectOnlyVisibleNodes) + { + for (auto node : m_CurrentExternalSelection) + { + if (!result.contains(node) && m_NodePredicate.IsNotNull() && !m_NodePredicate->CheckNode(node)) + { + result.append(node); + } + } + } + + return result; +} + +void QmitkAbstractNodeSelectionWidget::RemoveNodeFromSelection(const mitk::DataNode* node) +{ + auto newSelection = m_CurrentInternalSelection; + + auto finding = std::find(std::begin(newSelection), std::end(newSelection), node); + + if (finding != std::end(newSelection)) + { + newSelection.erase(finding); + this->HandleChangeOfInternalSelection(newSelection); + } +} + +void QmitkAbstractNodeSelectionWidget::OnNodeModified(const itk::Object * caller, const itk::EventObject & event) +{ + if (itk::ModifiedEvent().CheckEvent(&event)) + { + auto node = dynamic_cast(caller); + + if (node) + { + if (m_NodePredicate.IsNotNull() && !m_NodePredicate->CheckNode(node)) + { + this->RemoveNodeFromSelection(node); + } + else + { + auto oldAllowance = m_LastEmissionAllowance; + auto newEmission = this->CompileEmitSelection(); + auto nonConstNode = const_cast(node); + if (newEmission.contains(nonConstNode) && (oldAllowance != this->AllowEmissionOfSelection(newEmission))) + { + this->EmitSelection(newEmission); + this->UpdateInfo(); + } + } + } + } +} + +void QmitkAbstractNodeSelectionWidget::AddNodeObserver(mitk::DataNode* node) +{ + if (node) + { + auto modifiedCommand = itk::MemberCommand::New(); + modifiedCommand->SetCallbackFunction(this, &QmitkAbstractNodeSelectionWidget::OnNodeModified); + + auto nodeModifiedObserverTag = node->AddObserver(itk::ModifiedEvent(), modifiedCommand); + + m_NodeObserverTags.insert(std::make_pair(node, nodeModifiedObserverTag)); + } +} + +void QmitkAbstractNodeSelectionWidget::RemoveNodeObserver(mitk::DataNode* node) +{ + if (node) + { + auto finding = m_NodeObserverTags.find(node); + if (finding != std::end(m_NodeObserverTags)) + { + node->RemoveObserver(finding->second); + } + else + { + MITK_ERROR << "Selection widget is in a wrong state. A node should be removed from the internal selection but seems to have no observer. Node:" << node; + } + m_NodeObserverTags.erase(node); + } +} diff --git a/Modules/QtWidgets/test/QmitkAbstractNodeSelectionWidgetTest.cpp b/Modules/QtWidgets/test/QmitkAbstractNodeSelectionWidgetTest.cpp new file mode 100644 index 0000000000..18ecc66640 --- /dev/null +++ b/Modules/QtWidgets/test/QmitkAbstractNodeSelectionWidgetTest.cpp @@ -0,0 +1,615 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitkAbstractNodeSelectionWidget.h" +#include +#include +#include +#include "QmitkModelViewSelectionConnector.h" + +#include +#include + +class TestWidget : public QmitkAbstractNodeSelectionWidget +{ +public: + explicit TestWidget(QWidget* parent = nullptr) : QmitkAbstractNodeSelectionWidget(parent), m_UpdateInfo(0), m_OnNodePredicateChanged(0), m_OnDataStorageChanged(0), + m_OnInternalSelectionChanged(0), m_OnNodeAddedToStorage(0), m_OnNodeRemovedFromStorage(0), m_ReviseSelectionChanged(0), m_AllowEmissionOfSelection(0), m_Allow(true), m_NewSelectionEmited(0) + { + connect(this, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &TestWidget::NewSelectionEmited); + }; + + int m_UpdateInfo; + void UpdateInfo() override + { + m_UpdateInfo++; + }; + + int m_OnNodePredicateChanged; + void OnNodePredicateChanged() override + { + m_OnNodePredicateChanged++; + }; + + int m_OnDataStorageChanged; + void OnDataStorageChanged() override + { + m_OnDataStorageChanged++; + }; + + int m_OnInternalSelectionChanged; + void OnInternalSelectionChanged() override + { + m_OnInternalSelectionChanged++; + }; + + int m_OnNodeAddedToStorage; + void OnNodeAddedToStorage(const mitk::DataNode* /*node*/) override + { + m_OnNodeAddedToStorage++; + }; + + int m_OnNodeRemovedFromStorage; + void OnNodeRemovedFromStorage(const mitk::DataNode* /*node*/) override + { + m_OnNodeRemovedFromStorage++; + }; + + int m_ReviseSelectionChanged; + void ReviseSelectionChanged(const NodeList& /*oldInternalSelection*/, NodeList& /*newInternalSelection*/) override + { + m_ReviseSelectionChanged++; + }; + + mutable int m_AllowEmissionOfSelection; + bool m_Allow; + bool AllowEmissionOfSelection(const NodeList& emissionCandidates) const override + { + m_AllowEmissionOfSelection++; + if (m_Allow) return QmitkAbstractNodeSelectionWidget::AllowEmissionOfSelection(emissionCandidates); + + return false; + }; + + int m_NewSelectionEmited; + QmitkAbstractNodeSelectionWidget::NodeList m_LastNewEmision; + void NewSelectionEmited(NodeList selection) + { + m_NewSelectionEmited++; + m_LastNewEmision = selection; + }; +}; + +class QmitkAbstractNodeSelectionWidgetTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(QmitkAbstractNodeSelectionWidgetTestSuite); + MITK_TEST(SetDataStorageTest); + MITK_TEST(DataStorageEventTest); + MITK_TEST(NodePredicateTest); + MITK_TEST(SelectOnlyVisibleNodesTest); + MITK_TEST(OtherSetterAndGetterTest); + MITK_TEST(AllowEmissionOfSelectionTest); + MITK_TEST(OnNodeModifiedTest); + MITK_TEST(SignalRecursionTest); + CPPUNIT_TEST_SUITE_END(); + + mitk::DataStorage::Pointer m_DataStorage; + mitk::DataNode::Pointer m_Node1; + mitk::DataNode::Pointer m_Node1_2; + mitk::DataNode::Pointer m_Node2; + mitk::DataNode::Pointer m_Node3; + + QApplication* m_TestApp; + +public: + void setUp() override + { + m_DataStorage = mitk::StandaloneDataStorage::New(); + m_Node1 = mitk::DataNode::New(); + m_Node1->SetName("node1_1"); + + m_Node2 = mitk::DataNode::New(); + m_Node2->SetName("node2"); + + m_Node3 = mitk::DataNode::New(); + m_Node3->SetName("node3"); + + m_Node1_2 = mitk::DataNode::New(); + m_Node1_2->SetName("node1_2"); + + m_DataStorage->Add(m_Node1); + m_DataStorage->Add(m_Node2); + m_DataStorage->Add(m_Node3); + m_DataStorage->Add(m_Node1_2); + + int argc = 0; + char** argv = nullptr; + m_TestApp = new QApplication(argc, argv); + } + + mitk::NodePredicateBase::Pointer GeneratTestPredicate(const std::string& name) + { + auto check = [name](const mitk::DataNode * node) + { + return node->GetName().find(name,0) == 0; + }; + auto predicate = mitk::NodePredicateFunction::New(check); + return predicate.GetPointer(); + } + + void tearDown() override + { + delete m_TestApp; + } + + void SetDataStorageTest() + { + TestWidget widget; + + widget.SetDataStorage(nullptr); + CPPUNIT_ASSERT_EQUAL(0, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Set same data storage but triggered change", 0, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(0, widget.m_NewSelectionEmited); + + widget.SetDataStorage(m_DataStorage); + CPPUNIT_ASSERT_EQUAL(1, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(1, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(0, widget.m_NewSelectionEmited); + + widget.SetCurrentSelection({ m_Node1 }); + CPPUNIT_ASSERT_EQUAL(2, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1 }, widget.GetSelectedNodes())); + + widget.SetDataStorage(m_DataStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Set same data storage but triggered change", 1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1 }, widget.GetSelectedNodes())); + + widget.SetDataStorage(nullptr); + CPPUNIT_ASSERT_EQUAL(3, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Set same data storage but triggered change", 2, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(3, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(2, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.GetSelectedNodes())); + } + + void DataStorageEventTest() + { + TestWidget widget; + auto newNode = mitk::DataNode::New(); + + widget.SetDataStorage(m_DataStorage); + m_DataStorage->Add(newNode); + + CPPUNIT_ASSERT_EQUAL(1, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(1, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(0, widget.m_NewSelectionEmited); + + widget.SetCurrentSelection({ newNode }); + m_DataStorage->Remove(m_Node1); + CPPUNIT_ASSERT_EQUAL(2, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ newNode }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ newNode }, widget.GetSelectedNodes())); + + m_DataStorage->Remove(newNode); + CPPUNIT_ASSERT_EQUAL(3, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(3, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(2, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.GetSelectedNodes())); + + widget.SetCurrentSelection({ m_Node2 }); + m_DataStorage = nullptr; + CPPUNIT_ASSERT_EQUAL(5, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(5, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(5, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(5, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(4, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.GetSelectedNodes())); + } + + void NodePredicateTest() + { + TestWidget widget; + + CPPUNIT_ASSERT(nullptr == widget.GetNodePredicate()); + auto testPred = GeneratTestPredicate("node2"); + widget.SetNodePredicate(testPred); + CPPUNIT_ASSERT(testPred == widget.GetNodePredicate()); + + CPPUNIT_ASSERT_EQUAL(1, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(1, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(0, widget.m_NewSelectionEmited); + + widget.SetDataStorage(m_DataStorage); + + widget.SetCurrentSelection({ m_Node3 }); + CPPUNIT_ASSERT_EQUAL(3, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(3, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(0, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.GetSelectedNodes())); + + widget.SetCurrentSelection({ m_Node2 }); + CPPUNIT_ASSERT_EQUAL(4, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(4, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(4, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(4, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node2 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node2 }, widget.GetSelectedNodes())); + + //change predicate while nodes are selected + widget.SetNodePredicate(GeneratTestPredicate("node1")); + CPPUNIT_ASSERT_EQUAL(5, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(5, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(5, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(5, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(2, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.GetSelectedNodes())); + + //change selection to mixed one (valid nodes and invalid ones) + widget.SetCurrentSelection({ m_Node1, m_Node2, m_Node1_2 }); + CPPUNIT_ASSERT_EQUAL(6, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(6, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(6, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(6, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(3, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1, m_Node1_2 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1, m_Node1_2 }, widget.GetSelectedNodes())); + } + + void SelectOnlyVisibleNodesTest() + { + TestWidget widget; + + CPPUNIT_ASSERT_EQUAL(true, widget.GetSelectOnlyVisibleNodes()); + + CPPUNIT_ASSERT(nullptr == widget.GetNodePredicate()); + auto testPred = GeneratTestPredicate("node2"); + widget.SetNodePredicate(testPred); + widget.SetDataStorage(m_DataStorage); + widget.SetCurrentSelection({ m_Node3 }); + CPPUNIT_ASSERT_EQUAL(3, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(3, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(0, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({}, widget.GetSelectedNodes())); + + widget.SetSelectOnlyVisibleNodes(false); + CPPUNIT_ASSERT_EQUAL(false, widget.GetSelectOnlyVisibleNodes()); + CPPUNIT_ASSERT_EQUAL(3, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(3, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(4, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.GetSelectedNodes())); + + //change selection to mixed one (valid nodes and invalid ones) + widget.SetCurrentSelection({ m_Node1, m_Node2, m_Node1_2 }); + CPPUNIT_ASSERT_EQUAL(4, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(4, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(4, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(5, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(2, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1, m_Node2, m_Node1_2 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1, m_Node2, m_Node1_2 }, widget.GetSelectedNodes())); + + //change predicate while nodes are selected + widget.SetNodePredicate(GeneratTestPredicate("node1")); + CPPUNIT_ASSERT_EQUAL(5, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(5, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(5, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(6, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(2, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1, m_Node2, m_Node1_2 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1, m_Node2, m_Node1_2 }, widget.GetSelectedNodes())); + + widget.SetSelectOnlyVisibleNodes(true); + CPPUNIT_ASSERT_EQUAL(true, widget.GetSelectOnlyVisibleNodes()); + CPPUNIT_ASSERT_EQUAL(5, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(5, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(5, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(7, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(3, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1, m_Node1_2 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1, m_Node1_2 }, widget.GetSelectedNodes())); + } + + void OtherSetterAndGetterTest() + { + TestWidget widget; + + CPPUNIT_ASSERT("Error. Select data." == widget.GetInvalidInfo()); + CPPUNIT_ASSERT("Empty. Make a selection." == widget.GetEmptyInfo()); + CPPUNIT_ASSERT("Select a data node" == widget.GetPopUpTitel()); + CPPUNIT_ASSERT("" == widget.GetPopUpHint()); + CPPUNIT_ASSERT(false == widget.GetSelectionIsOptional()); + + widget.SetInvalidInfo("SetInvalidInfo"); + widget.SetEmptyInfo("SetEmptyInfo"); + widget.SetPopUpTitel("SetPopUpTitel"); + widget.SetPopUpHint("SetPopUpHint"); + widget.SetSelectionIsOptional(true); + + CPPUNIT_ASSERT("SetInvalidInfo" == widget.GetInvalidInfo()); + CPPUNIT_ASSERT("SetEmptyInfo" == widget.GetEmptyInfo()); + CPPUNIT_ASSERT("SetPopUpTitel" == widget.GetPopUpTitel()); + CPPUNIT_ASSERT("SetPopUpHint" == widget.GetPopUpHint()); + CPPUNIT_ASSERT(true == widget.GetSelectionIsOptional()); + } + + void AllowEmissionOfSelectionTest() + { + TestWidget widget; + + widget.SetDataStorage(m_DataStorage); + widget.SetCurrentSelection({ m_Node3 }); + CPPUNIT_ASSERT_EQUAL(2, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.GetSelectedNodes())); + + widget.m_Allow = false; + + widget.SetCurrentSelection({ m_Node1 }); + CPPUNIT_ASSERT_EQUAL(3, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(3, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1 }, widget.GetSelectedNodes())); + } + + void OnNodeModifiedTest() + { + TestWidget widget; + + widget.SetDataStorage(m_DataStorage); + widget.SetCurrentSelection({ m_Node3 }); + + //Check OnNodeModified behavour without predicate + m_Node3->SetName("NewName"); + CPPUNIT_ASSERT_EQUAL(2, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.GetSelectedNodes())); + + //simulate behaviour if allowance is changed to true + widget.m_Allow = false; + m_Node3->SetName("NewName_temp"); + widget.m_Allow = true; + m_Node3->SetName("NewName"); + CPPUNIT_ASSERT_EQUAL(4, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(7, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.GetSelectedNodes())); + + //change irrelevant node + m_Node2->SetName("IrrelevantNode"); + CPPUNIT_ASSERT_EQUAL(4, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(7, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node3 }, widget.GetSelectedNodes())); + + //test with predicate set + widget.SetNodePredicate(GeneratTestPredicate("node1")); + m_Node3->SetName("NowAlsoIrrelevantNode"); + CPPUNIT_ASSERT_EQUAL(5, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(3, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(3, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(8, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(2, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ }, widget.GetSelectedNodes())); + + widget.SetCurrentSelection({ m_Node1, m_Node1_2 }); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1, m_Node1_2 }, widget.m_LastNewEmision)); + + m_Node1->SetName("NodeSortedOutByPredicate"); + CPPUNIT_ASSERT_EQUAL(7, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(5, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(5, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(10, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(4, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1_2 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1_2 }, widget.GetSelectedNodes())); + } + + void SignalRecursionTest() + { + TestWidget widget; + widget.SetDataStorage(m_DataStorage); + + TestWidget widget2; + widget2.SetDataStorage(m_DataStorage); + + QmitkAbstractNodeSelectionWidget::connect(&widget, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, &widget2, &QmitkAbstractNodeSelectionWidget::SetCurrentSelection); + QmitkAbstractNodeSelectionWidget::connect(&widget2, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, &widget, &QmitkAbstractNodeSelectionWidget::SetCurrentSelection); + + widget.SetCurrentSelection({ m_Node1 }); + CPPUNIT_ASSERT_EQUAL(2, widget.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(2, widget.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(2, widget.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1 }, widget.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1 }, widget.GetSelectedNodes())); + + CPPUNIT_ASSERT_EQUAL(2, widget2.m_UpdateInfo); + CPPUNIT_ASSERT_EQUAL(0, widget2.m_OnNodePredicateChanged); + CPPUNIT_ASSERT_EQUAL(1, widget2.m_OnDataStorageChanged); + CPPUNIT_ASSERT_EQUAL(2, widget2.m_OnInternalSelectionChanged); + CPPUNIT_ASSERT_EQUAL(0, widget2.m_OnNodeAddedToStorage); + CPPUNIT_ASSERT_EQUAL(0, widget2.m_OnNodeRemovedFromStorage); + CPPUNIT_ASSERT_EQUAL(2, widget2.m_ReviseSelectionChanged); + CPPUNIT_ASSERT_EQUAL(2, widget2.m_AllowEmissionOfSelection); + CPPUNIT_ASSERT_EQUAL(1, widget2.m_NewSelectionEmited); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1 }, widget2.m_LastNewEmision)); + CPPUNIT_ASSERT(EqualNodeSelections({ m_Node1 }, widget2.GetSelectedNodes())); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(QmitkAbstractNodeSelectionWidget) diff --git a/Modules/QtWidgets/test/files.cmake b/Modules/QtWidgets/test/files.cmake index 370a32a208..3009ccc8af 100644 --- a/Modules/QtWidgets/test/files.cmake +++ b/Modules/QtWidgets/test/files.cmake @@ -1,10 +1,13 @@ # test fails easily on MacOS, rarely on Windows, needs to be fixed before permanent activation (bug 15479) -set(MODULE_TESTS ) +set(MODULE_TESTS + QmitkAbstractNodeSelectionWidgetTest.cpp +) if(BUG_15479_FIXED) list(APPEND MODULE_TESTS QmitkThreadedLogTest.cpp) endif() -set(MODULE_CUSTOM_TESTS ${MODULE_TESTS} +set(MODULE_CUSTOM_TESTS QmitkDataStorageListModelTest.cpp + ) diff --git a/Plugins/org.mitk.gui.qt.common/files.cmake b/Plugins/org.mitk.gui.qt.common/files.cmake index 0bde8b2087..b9621a8d79 100755 --- a/Plugins/org.mitk.gui.qt.common/files.cmake +++ b/Plugins/org.mitk.gui.qt.common/files.cmake @@ -1,71 +1,69 @@ set(SRC_CPP_FILES QmitkAbstractMultiWidgetEditor.cpp QmitkAbstractRenderEditor.cpp QmitkAbstractView.cpp QmitkDataNodeSelectionProvider.cpp QmitkDnDFrameWidget.cpp QmitkSelectionServiceConnector.cpp QmitkSliceNavigationListener.cpp QmitkSingleNodeSelectionWidget.cpp QmitkNodeSelectionDialog.cpp - QmitkAbstractNodeSelectionWidget.cpp QmitkMultiNodeSelectionWidget.cpp QmitkMultiWidgetDecorationManager.cpp QmitkNodeSelectionPreferenceHelper.cpp QmitkNodeSelectionButton.cpp QmitkSimpleTextOverlayWidget.cpp ) set(INTERNAL_CPP_FILES QmitkCommonActivator.cpp QmitkDataNodeItemModel.cpp QmitkDataNodeSelection.cpp QmitkViewCoordinator.cpp QmitkNodeSelectionConstants.cpp QmitkNodeSelectionPreferencePage.cpp QmitkNodeSelectionListItemWidget.cpp ) set(UI_FILES src/QmitkSingleNodeSelectionWidget.ui src/QmitkMultiNodeSelectionWidget.ui src/QmitkNodeSelectionDialog.ui src/internal/QmitkNodeSelectionPreferencePage.ui src/internal/QmitkNodeSelectionListItemWidget.ui ) set(MOC_H_FILES src/QmitkAbstractMultiWidgetEditor.h src/QmitkAbstractRenderEditor.h src/QmitkDnDFrameWidget.h src/QmitkSelectionServiceConnector.h src/QmitkSliceNavigationListener.h src/QmitkSingleNodeSelectionWidget.h src/QmitkNodeSelectionDialog.h - src/QmitkAbstractNodeSelectionWidget.h src/QmitkMultiNodeSelectionWidget.h src/QmitkNodeSelectionButton.h src/QmitkSimpleTextOverlayWidget.h src/internal/QmitkCommonActivator.h src/internal/QmitkNodeSelectionPreferencePage.h src/internal/QmitkNodeSelectionListItemWidget.h ) set(CACHED_RESOURCE_FILES plugin.xml resources/times.svg ) set(QRC_FILES resources/common.qrc ) set(CPP_FILES ) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractNodeSelectionWidget.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractNodeSelectionWidget.cpp deleted file mode 100644 index 94a53a1194..0000000000 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractNodeSelectionWidget.cpp +++ /dev/null @@ -1,186 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - - -#include "QmitkAbstractNodeSelectionWidget.h" - -QmitkAbstractNodeSelectionWidget::QmitkAbstractNodeSelectionWidget(QWidget* parent) : QWidget(parent), m_InvalidInfo("Error. Select data."), -m_EmptyInfo("Empty. Make a selection."), m_PopUpTitel("Select a data node"), m_PopUpHint(""), -m_IsOptional(false), m_SelectOnlyVisibleNodes(true) -{ -} - -QmitkAbstractNodeSelectionWidget::~QmitkAbstractNodeSelectionWidget() -{ - if (!m_DataStorage.IsExpired()) - { - auto dataStorage = m_DataStorage.Lock(); - if (dataStorage.IsNotNull()) - { - // remove Listener for the data storage itself - dataStorage->RemoveObserver(m_DataStorageDeletedTag); - - // remove "add node listener" from data storage - dataStorage->AddNodeEvent.RemoveListener( - mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeAddedToStorage)); - - // remove "remove node listener" from data storage - dataStorage->RemoveNodeEvent.RemoveListener( - mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage)); - } - } -} - - -void QmitkAbstractNodeSelectionWidget::SetDataStorage(mitk::DataStorage* dataStorage) -{ - if (m_DataStorage == dataStorage) - { - return; - } - - if (!m_DataStorage.IsExpired()) - { - auto oldStorage = m_DataStorage.Lock(); - - if (oldStorage.IsNotNull()) - { - // remove Listener for the data storage itself - oldStorage->RemoveObserver(m_DataStorageDeletedTag); - - // remove "add node listener" from old data storage - oldStorage->AddNodeEvent.RemoveListener( - mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeAddedToStorage)); - - // remove "remove node listener" from old data storage - oldStorage->RemoveNodeEvent.RemoveListener( - mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage)); - } - } - - m_DataStorage = dataStorage; - - if (!m_DataStorage.IsExpired()) - { - auto newStorage = m_DataStorage.Lock(); - - if (newStorage.IsNotNull()) - { - // add Listener for the data storage itself - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkAbstractNodeSelectionWidget::SetDataStorageDeleted); - m_DataStorageDeletedTag = newStorage->AddObserver(itk::DeleteEvent(), command); - - // add "add node listener" for new data storage - newStorage->AddNodeEvent.AddListener( - mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeAddedToStorage)); - - // add remove node listener for new data storage - newStorage->RemoveNodeEvent.AddListener( - mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage)); - } - } - - // update model if the data storage has been changed - m_DataStorage = dataStorage; - this->OnDataStorageChanged(); - this->UpdateInfo(); -} - -void QmitkAbstractNodeSelectionWidget::SetNodePredicate(const mitk::NodePredicateBase* nodePredicate) -{ - if (m_NodePredicate != nodePredicate) - { - m_NodePredicate = nodePredicate; - - this->OnNodePredicateChanged(nodePredicate); - this->UpdateInfo(); - } -} - -const mitk::NodePredicateBase* QmitkAbstractNodeSelectionWidget::GetNodePredicate() const -{ - return m_NodePredicate; -} - -QString QmitkAbstractNodeSelectionWidget::GetInvalidInfo() const -{ - return m_InvalidInfo; -} - -QString QmitkAbstractNodeSelectionWidget::GetEmptyInfo() const -{ - return m_EmptyInfo; -} - -QString QmitkAbstractNodeSelectionWidget::GetPopUpTitel() const -{ - return m_PopUpTitel; -} - -QString QmitkAbstractNodeSelectionWidget::GetPopUpHint() const -{ - return m_PopUpHint; -} - -bool QmitkAbstractNodeSelectionWidget::GetSelectionIsOptional() const -{ - return m_IsOptional; -} - -bool QmitkAbstractNodeSelectionWidget::GetSelectOnlyVisibleNodes() const -{ - return m_SelectOnlyVisibleNodes; -} - -void QmitkAbstractNodeSelectionWidget::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) -{ - m_SelectOnlyVisibleNodes = selectOnlyVisibleNodes; -} - -void QmitkAbstractNodeSelectionWidget::SetInvalidInfo(QString info) -{ - m_InvalidInfo = info; - this->UpdateInfo(); -} - -void QmitkAbstractNodeSelectionWidget::SetEmptyInfo(QString info) -{ - m_EmptyInfo = info; - this->UpdateInfo(); -} - -void QmitkAbstractNodeSelectionWidget::SetPopUpTitel(QString info) -{ - m_PopUpTitel = info; -} - -void QmitkAbstractNodeSelectionWidget::SetPopUpHint(QString info) -{ - m_PopUpHint = info; -} - -void QmitkAbstractNodeSelectionWidget::SetSelectionIsOptional(bool isOptional) -{ - m_IsOptional = isOptional; - this->UpdateInfo(); -} - -void QmitkAbstractNodeSelectionWidget::SetDataStorageDeleted() -{ - this->SetDataStorage(nullptr); -} - -void QmitkAbstractNodeSelectionWidget::NodeAddedToStorage(const mitk::DataNode* /*node*/) -{ - //default implementation does nothing; -} diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractNodeSelectionWidget.h b/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractNodeSelectionWidget.h deleted file mode 100644 index 017cd17b6e..0000000000 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractNodeSelectionWidget.h +++ /dev/null @@ -1,166 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - - -#ifndef QMITK_ABSTRACT_NODE_SELECTION_WIDGET_H -#define QMITK_ABSTRACT_NODE_SELECTION_WIDGET_H - -#include -#include -#include - -#include "org_mitk_gui_qt_common_Export.h" - -#include - -class QmitkAbstractDataStorageModel; -class QAbstractItemVew; - -/** -* \class QmitkAbstractNodeSelectionWidget -* \brief Abstract base class for the selection of data from a data storage. -*/ -class MITK_QT_COMMON QmitkAbstractNodeSelectionWidget : public QWidget -{ - Q_OBJECT - -public: - explicit QmitkAbstractNodeSelectionWidget(QWidget* parent = nullptr); - ~QmitkAbstractNodeSelectionWidget() override; - - /** - * @brief Sets the data storage that will be used /monitored by widget. - * - * @par dataStorage A pointer to the data storage to set. - */ - void SetDataStorage(mitk::DataStorage* dataStorage); - - /** - * Sets the node predicate and updates the widget, according to the node predicate. - * Implement OnNodePredicateChange() for custom actualization of a derived widget class. - * - * @par nodePredicate A pointer to node predicate. - */ - void SetNodePredicate(const mitk::NodePredicateBase* nodePredicate); - - const mitk::NodePredicateBase* GetNodePredicate() const; - - QString GetInvalidInfo() const; - QString GetEmptyInfo() const; - QString GetPopUpTitel() const; - QString GetPopUpHint() const; - - bool GetSelectionIsOptional() const; - - bool GetSelectOnlyVisibleNodes() const; - - using NodeList = QList; - -Q_SIGNALS: - /* - * @brief A signal that will be emitted if the selected node has changed. - * - * @par nodes A list of data nodes that are newly selected. - */ - void CurrentSelectionChanged(QList nodes); - -public Q_SLOTS: - /* - * @brief Change the selection modus of the item view's selection model. - * - * If true, an incoming selection will be filtered (reduced) to only those nodes that are visible by the current view. - * An outgoing selection can then at most contain the filtered nodes. - * If false, the incoming non-visible selection will be stored and later added to the outgoing selection, - * to include the original selection that could not be modified. - * The part of the original selection, that is non-visible are the nodes that are not - * - * @par selectOnlyVisibleNodes The bool value to define the selection modus. - */ - virtual void SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes); - - /* - * @brief Transform a list of data nodes into a model selection and set this as a new selection of the - * selection model of the private member item view. - * - * The function filters the given list of nodes according to the 'm_SelectOnlyVisibleNodes' member variable. If - * necessary, the non-visible nodes are stored. This is done if 'm_SelectOnlyVisibleNodes' is false: In this case - * the selection may be filtered and only a subset of the selected nodes may be visible and therefore (de-)selectable - * in the data storage viewer. By storing the non-visible nodes it is possible to send the new, modified selection - * but also include the selected nodes from the original selection that could not be modified (see 'SetSelectOnlyVisibleNodes'). - * - * @par nodes A list of data nodes that should be newly selected. - */ - virtual void SetCurrentSelection(NodeList selectedNodes) = 0; - - /** Set the info text that should be displayed if no (valid) node is selected, - * but a selection is mandatory. - * The string can contain HTML code. if wanted*/ - void SetInvalidInfo(QString info); - - /** Set the info text that should be displayed if no (valid) node is selected, - * but a selection is optional. - * The string can contain HTML code. if wanted*/ - void SetEmptyInfo(QString info); - - /** Set the caption of the popup that is displayed to alter the selection. - * The string can contain HTML code. if wanted*/ - void SetPopUpTitel(QString info); - - /** Set the hint text of the popup that is displayed to alter the selection. - * The string can contain HTML code. if wanted*/ - void SetPopUpHint(QString info); - - /** Set the widget into an optional mode. Optional means that the selection of no valid - node does not mean an invalid state. Thus no node is a valid "node" selection too.*/ - void SetSelectionIsOptional(bool isOptional); - -protected: - /**Member is called if the display of the selected nodes should be updated.*/ - virtual void UpdateInfo() = 0; - - /**Member is called if the predicate has changed. Thus the selection might change to. The new (changed) predicate - is passed with the function call. It is the same like this->GetNodePredicate() called in the function call.*/ - virtual void OnNodePredicateChanged(const mitk::NodePredicateBase* newPredicate) = 0; - - /**Member is called if the data storage has changed. Thus the selection might change to.*/ - virtual void OnDataStorageChanged() = 0; - - /**Member is called when a node is added to the storage. Default implementation does nothing. - Derived widgets can override the method if they want to react on new nodes in the storage.*/ - virtual void NodeAddedToStorage(const mitk::DataNode* node); - - /**Member is called when a node is removed from the storage. The removed node is passed as - variable. Derived classes have to implement this method to react on the fact that there selection - might change, because the removed node is part of there selection. */ - virtual void NodeRemovedFromStorage(const mitk::DataNode* node) = 0; - - mitk::WeakPointer m_DataStorage; - mitk::NodePredicateBase::ConstPointer m_NodePredicate; - - QString m_InvalidInfo; - QString m_EmptyInfo; - QString m_PopUpTitel; - QString m_PopUpHint; - - /** See documentation of SetSelectOnlyVisibleNodes for details*/ - bool m_IsOptional; - /** See documentation of SetSelectionIsOptional for details*/ - bool m_SelectOnlyVisibleNodes; - -private: - /** Helper triggered on the storage delete event */ - void SetDataStorageDeleted(); - - unsigned long m_DataStorageDeletedTag; - -}; -#endif // QmitkAbstractNodeSelectionWidget_H diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.cpp index 5700679abe..422ae1ae2e 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.cpp @@ -1,273 +1,158 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMultiNodeSelectionWidget.h" #include #include "QmitkNodeSelectionDialog.h" #include "QmitkCustomVariants.h" #include "internal/QmitkNodeSelectionListItemWidget.h" QmitkMultiNodeSelectionWidget::QmitkMultiNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent) { m_Controls.setupUi(this); m_Overlay = new QmitkSimpleTextOverlayWidget(m_Controls.list); m_Overlay->setVisible(false); m_CheckFunction = [](const NodeList &) { return ""; }; - this->UpdateList(); + this->OnInternalSelectionChanged(); this->UpdateInfo(); connect(m_Controls.btnChange, SIGNAL(clicked(bool)), this, SLOT(OnEditSelection())); } -QmitkMultiNodeSelectionWidget::NodeList QmitkMultiNodeSelectionWidget::CompileEmitSelection() const -{ - NodeList result; - - for (int i = 0; i < m_Controls.list->count(); ++i) - { - QListWidgetItem* item = m_Controls.list->item(i); - - auto node = item->data(Qt::UserRole).value(); - result.append(node); - } - - - if (!m_SelectOnlyVisibleNodes) - { - for (auto node : m_CurrentSelection) - { - if (!result.contains(node)) - { - result.append(node); - } - } - } - - return result; -} - -void QmitkMultiNodeSelectionWidget::OnNodePredicateChanged(const mitk::NodePredicateBase* /*newPredicate*/) -{ - this->UpdateInfo(); - this->UpdateList(); -}; - -void QmitkMultiNodeSelectionWidget::OnDataStorageChanged() -{ - this->UpdateInfo(); - this->UpdateList(); -}; - -QmitkMultiNodeSelectionWidget::NodeList QmitkMultiNodeSelectionWidget::GetSelectedNodes() const -{ - return m_CurrentSelection; -}; - void QmitkMultiNodeSelectionWidget::SetSelectionCheckFunction(const SelectionCheckFunctionType &checkFunction) { m_CheckFunction = checkFunction; auto newEmission = this->CompileEmitSelection(); auto newCheckResponse = m_CheckFunction(newEmission); if (newCheckResponse.empty() && !m_CheckResponse.empty()) { - emit CurrentSelectionChanged(newEmission); + this->EmitSelection(newEmission); } m_CheckResponse = newCheckResponse; this->UpdateInfo(); -}; +} void QmitkMultiNodeSelectionWidget::OnEditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this, m_PopUpTitel, m_PopUpHint); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); dialog->SetCurrentSelection(this->CompileEmitSelection()); dialog->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); m_Controls.btnChange->setChecked(true); if (dialog->exec()) { - auto lastEmission = this->CompileEmitSelection(); - - m_CurrentSelection = dialog->GetSelectedNodes(); - this->UpdateList(); - - auto newEmission = this->CompileEmitSelection(); - - m_CheckResponse = m_CheckFunction(newEmission); - this->UpdateInfo(); - - if (!EqualNodeSelections(lastEmission, newEmission)) - { - if (m_CheckResponse.empty()) - { - emit CurrentSelectionChanged(newEmission); - } - } + this->HandleChangeOfInternalSelection(dialog->GetSelectedNodes()); } m_Controls.btnChange->setChecked(false); delete dialog; -}; +} void QmitkMultiNodeSelectionWidget::UpdateInfo() { if (!m_Controls.list->count()) { if (m_IsOptional) { if (this->isEnabled()) { m_Overlay->SetOverlayText(QStringLiteral("") + m_EmptyInfo + QStringLiteral("")); } else { m_Overlay->SetOverlayText(QStringLiteral("") + m_EmptyInfo + QStringLiteral("")); } } else { if (this->isEnabled()) { m_Overlay->SetOverlayText(QStringLiteral("") + m_InvalidInfo + QStringLiteral("")); } else { m_Overlay->SetOverlayText(QStringLiteral("") + m_InvalidInfo + QStringLiteral("")); } } } else { if (!m_CheckResponse.empty()) { m_Overlay->SetOverlayText(QString::fromStdString(m_CheckResponse)); } } m_Overlay->setVisible(m_Controls.list->count() == 0 || !m_CheckResponse.empty()); for (auto i = 0; i < m_Controls.list->count(); ++i) { auto item = m_Controls.list->item(i); auto widget = qobject_cast(m_Controls.list->itemWidget(item)); - widget->SetClearAllowed(m_IsOptional || m_CurrentSelection.size() > 1); + widget->SetClearAllowed(m_IsOptional || m_Controls.list->count() > 1); } -}; +} -void QmitkMultiNodeSelectionWidget::UpdateList() +void QmitkMultiNodeSelectionWidget::OnInternalSelectionChanged() { m_Controls.list->clear(); - - for (auto node : m_CurrentSelection) + auto currentSelection = this->GetCurrentInternalSelection(); + for (auto& node : currentSelection) { if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { QListWidgetItem *newItem = new QListWidgetItem; newItem->setSizeHint(QSize(0, 40)); QmitkNodeSelectionListItemWidget* widget = new QmitkNodeSelectionListItemWidget; widget->SetSelectedNode(node); - widget->SetClearAllowed(m_IsOptional || m_CurrentSelection.size() > 1); + widget->SetClearAllowed(m_IsOptional || currentSelection.size() > 1); connect(widget, &QmitkNodeSelectionListItemWidget::ClearSelection, this, &QmitkMultiNodeSelectionWidget::OnClearSelection); newItem->setData(Qt::UserRole, QVariant::fromValue(node)); m_Controls.list->addItem(newItem); m_Controls.list->setItemWidget(newItem, widget); } } -}; - -void QmitkMultiNodeSelectionWidget::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) -{ - auto lastEmission = this->CompileEmitSelection(); - - m_SelectOnlyVisibleNodes = selectOnlyVisibleNodes; - - auto newEmission = this->CompileEmitSelection(); - - if (!EqualNodeSelections(lastEmission, newEmission)) - { - m_CheckResponse = m_CheckFunction(newEmission); - if (m_CheckResponse.empty()) - { - emit CurrentSelectionChanged(newEmission); - } - this->UpdateList(); - this->UpdateInfo(); - } -}; - -void QmitkMultiNodeSelectionWidget::SetCurrentSelection(NodeList selectedNodes) -{ - auto lastEmission = this->CompileEmitSelection(); - - m_CurrentSelection = selectedNodes; - this->UpdateList(); - - auto newEmission = this->CompileEmitSelection(); - - if (!EqualNodeSelections(lastEmission, newEmission)) - { - m_CheckResponse = m_CheckFunction(newEmission); - if (m_CheckResponse.empty()) - { - emit CurrentSelectionChanged(newEmission); - } - this->UpdateInfo(); - } -}; +} void QmitkMultiNodeSelectionWidget::OnClearSelection(const mitk::DataNode* node) { - auto finding = std::find(std::begin(m_CurrentSelection), std::end(m_CurrentSelection), node); - m_CurrentSelection.erase(finding); - - this->UpdateList(); - auto newEmission = this->CompileEmitSelection(); - m_CheckResponse = m_CheckFunction(newEmission); - - if (m_CheckResponse.empty()) - { - emit CurrentSelectionChanged(newEmission); - } - this->UpdateInfo(); -}; - -void QmitkMultiNodeSelectionWidget::NodeRemovedFromStorage(const mitk::DataNode* node) -{ - auto finding = std::find(std::begin(m_CurrentSelection), std::end(m_CurrentSelection), node); - - if (finding != std::end(m_CurrentSelection)) - { - this->OnClearSelection(node); - } + this->RemoveNodeFromSelection(node); } void QmitkMultiNodeSelectionWidget::changeEvent(QEvent *event) { if (event->type() == QEvent::EnabledChange) { this->UpdateInfo(); } QmitkAbstractNodeSelectionWidget::changeEvent(event); } + +bool QmitkMultiNodeSelectionWidget::AllowEmissionOfSelection(const NodeList& emissionCandidates) const +{ + m_CheckResponse = m_CheckFunction(emissionCandidates); + return m_CheckResponse.empty(); +} diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.h b/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.h index f4e411234a..747c9c1951 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.h @@ -1,87 +1,77 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITK_MULTI_NODE_SELECTION_WIDGET_H #define QMITK_MULTI_NODE_SELECTION_WIDGET_H #include #include #include #include "QmitkSimpleTextOverlayWidget.h" #include "org_mitk_gui_qt_common_Export.h" #include "ui_QmitkMultiNodeSelectionWidget.h" #include class QmitkAbstractDataStorageModel; class QAbstractItemVew; /** * \class QmitkMultiNodeSelectionWidget * \brief Widget that allows to perform and represents a multiple node selection. */ class MITK_QT_COMMON QmitkMultiNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: explicit QmitkMultiNodeSelectionWidget(QWidget* parent = nullptr); using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; - NodeList GetSelectedNodes() const; - /**Helper function that is used to check the given selection for consistency. Returning an empty string assumes that everything is alright and the selection is valid. If the string is not empty, the content of the string will be used as error message in the overlay to indicate the problem.*/ using SelectionCheckFunctionType = std::function; /**A selection check function can be set. If set the widget uses this function to check the made/set selection. If the selection is valid, everything is fine. If selection is indicated as invalid, it will not be communicated by the widget (no signal emission.*/ void SetSelectionCheckFunction(const SelectionCheckFunctionType &checkFunction); public Q_SLOTS: - void SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) override; - void SetCurrentSelection(NodeList selectedNodes) override; void OnEditSelection(); protected Q_SLOTS: void OnClearSelection(const mitk::DataNode* node); protected: - NodeList CompileEmitSelection() const; - void changeEvent(QEvent *event) override; void UpdateInfo() override; - virtual void UpdateList(); - - void OnNodePredicateChanged(const mitk::NodePredicateBase* newPredicate) override; - void OnDataStorageChanged() override; - void NodeRemovedFromStorage(const mitk::DataNode* node) override; + void OnInternalSelectionChanged() override; - NodeList m_CurrentSelection; + bool AllowEmissionOfSelection(const NodeList& emissionCandidates) const override; QmitkSimpleTextOverlayWidget* m_Overlay; SelectionCheckFunctionType m_CheckFunction; - std::string m_CheckResponse; + mutable std::string m_CheckResponse; Ui_QmitkMultiNodeSelectionWidget m_Controls; }; #endif // QmitkMultiNodeSelectionWidget_H diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.cpp index 0b194e4864..68adfbd9a2 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.cpp @@ -1,268 +1,278 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkNodeSelectionButton.h" // berry includes #include #include #include "QPainter" #include "QTextDocument" #include "QEvent" #include #include // mitk core #include #include #include #include #include #include // vtk #include QPixmap GetPixmapFromImageNode(const mitk::DataNode* dataNode, int height) { if (nullptr == dataNode) { return QPixmap(); } const mitk::Image* image = dynamic_cast(dataNode->GetData()); if ((nullptr == image || !image->IsInitialized()) || // -> must be an image (image->GetPixelType().GetNumberOfComponents() != 1)) // -> for now only single component are allowed { auto descManager = QmitkNodeDescriptorManager::GetInstance(); auto desc = descManager->GetDescriptor(dataNode); auto icon = desc->GetIcon(dataNode); auto fallBackMap = icon.pixmap(height, height); return fallBackMap; } mitk::PlaneGeometry::Pointer planeGeometry = mitk::PlaneGeometry::New(); int sliceNumber = image->GetDimension(2) / 2; planeGeometry->InitializeStandardPlane(image->GetGeometry(), mitk::PlaneGeometry::Axial, sliceNumber); mitk::ExtractSliceFilter::Pointer extractSliceFilter = mitk::ExtractSliceFilter::New(); extractSliceFilter->SetInput(image); extractSliceFilter->SetInterpolationMode(mitk::ExtractSliceFilter::RESLICE_CUBIC); extractSliceFilter->SetResliceTransformByGeometry(image->GetGeometry()); extractSliceFilter->SetWorldGeometry(planeGeometry); extractSliceFilter->SetOutputDimensionality(2); extractSliceFilter->SetVtkOutputRequest(true); extractSliceFilter->Update(); vtkImageData* imageData = extractSliceFilter->GetVtkOutput(); mitk::LevelWindow levelWindow; dataNode->GetLevelWindow(levelWindow); vtkSmartPointer lookupTable = vtkSmartPointer::New(); lookupTable->SetRange(levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound()); lookupTable->SetSaturationRange(0.0, 0.0); lookupTable->SetValueRange(0.0, 1.0); lookupTable->SetHueRange(0.0, 0.0); lookupTable->SetRampToLinear(); vtkSmartPointer levelWindowFilter = vtkSmartPointer::New(); levelWindowFilter->SetLookupTable(lookupTable); levelWindowFilter->SetInputData(imageData); levelWindowFilter->SetMinOpacity(0.0); levelWindowFilter->SetMaxOpacity(1.0); int dims[3]; imageData->GetDimensions(dims); double clippingBounds[] = { 0.0, static_cast(dims[0]), 0.0, static_cast(dims[1]) }; levelWindowFilter->SetClippingBounds(clippingBounds); levelWindowFilter->Update(); imageData = levelWindowFilter->GetOutput(); QImage thumbnailImage(reinterpret_cast(imageData->GetScalarPointer()), dims[0], dims[1], QImage::Format_ARGB32); - thumbnailImage = thumbnailImage.scaledToHeight(height,Qt::SmoothTransformation).rgbSwapped(); + if (dims[0] > dims[1]) + { + thumbnailImage = thumbnailImage.scaledToWidth(height, Qt::SmoothTransformation).rgbSwapped(); + } + else + { + thumbnailImage = thumbnailImage.scaledToHeight(height, Qt::SmoothTransformation).rgbSwapped(); + } + return QPixmap::fromImage(thumbnailImage); } QmitkNodeSelectionButton::QmitkNodeSelectionButton(QWidget *parent) - : QPushButton(parent), m_OutDatedThumpNail(true), m_DataMTime(0), m_IsOptional(true), m_NodeModifiedObserverTag(0), m_NodeObserved(false) + : QPushButton(parent), m_OutDatedThumbNail(true), m_DataMTime(0), m_IsOptional(true), m_NodeModifiedObserverTag(0), m_NodeObserved(false) { } QmitkNodeSelectionButton::~QmitkNodeSelectionButton() { this->RemoveNodeObserver(); this->m_SelectedNode = nullptr; } void QmitkNodeSelectionButton::AddNodeObserver() { if (this->m_SelectedNode.IsNotNull()) { if (m_NodeObserved) { MITK_DEBUG << "Invalid observer state in QmitkNodeSelectionButton. There is already a registered observer. Internal logic is not correct. May be an old observer was not removed."; } auto modifiedCommand = itk::MemberCommand::New(); modifiedCommand->SetCallbackFunction(this, &QmitkNodeSelectionButton::OnNodeModified); // const cast because we need non const nodes and it seems to be the lesser of two evil. // the changes to the node are only on the observer level. The other option would be to // make the public interface require non const nodes, this we don't want to introduce. auto nonconst_node = const_cast(this->m_SelectedNode.GetPointer()); m_NodeModifiedObserverTag = nonconst_node->AddObserver(itk::ModifiedEvent(), modifiedCommand); m_NodeObserved = true; } } void QmitkNodeSelectionButton::RemoveNodeObserver() { if (this->m_SelectedNode.IsNotNull()) { // const cast because we need non const nodes and it seems to be the lesser of two evil. // the changes to the node are only on the observer level. The other option would be to // make the public interface require non const nodes, this we don't want to introduce. auto nonconst_node = const_cast(this->m_SelectedNode.GetPointer()); nonconst_node->RemoveObserver(m_NodeModifiedObserverTag); } m_NodeObserved = false; } void QmitkNodeSelectionButton::OnNodeModified(const itk::Object * /*caller*/, const itk::EventObject & event) { if (itk::ModifiedEvent().CheckEvent(&event)) { this->update(); } } const mitk::DataNode* QmitkNodeSelectionButton::GetSelectedNode() const { return m_SelectedNode; } void QmitkNodeSelectionButton::SetSelectedNode(const mitk::DataNode* node) { if (m_SelectedNode != node) { this->RemoveNodeObserver(); this->m_SelectedNode = node; - this->m_OutDatedThumpNail = true; + this->m_OutDatedThumbNail = true; this->AddNodeObserver(); } this->update(); } void QmitkNodeSelectionButton::SetNodeInfo(QString info) { this->m_Info = info; this->update(); } void QmitkNodeSelectionButton::paintEvent(QPaintEvent *p) { QString stylesheet; ctkPluginContext* context = berry::WorkbenchPlugin::GetDefault()->GetPluginContext(); ctkServiceReference styleManagerRef = context->getServiceReference(); if (styleManagerRef) { auto styleManager = context->getService(styleManagerRef); stylesheet = styleManager->GetStylesheet(); } QPushButton::paintEvent(p); QPainter painter(this); QTextDocument td(this); td.setDefaultStyleSheet(stylesheet); auto widgetSize = this->size(); QPoint origin = QPoint(5, 5); if (this->m_SelectedNode) { auto iconLength = widgetSize.height() - 10; auto node = this->m_SelectedNode; itk::ModifiedTimeType dataMTime = 0; if (m_SelectedNode->GetData()) { dataMTime = m_SelectedNode->GetData()->GetMTime(); } - if (dataMTime>m_DataMTime || this->m_OutDatedThumpNail) + if (dataMTime>m_DataMTime || this->m_OutDatedThumbNail) { - this->m_ThumpNail = GetPixmapFromImageNode(node, iconLength); - this->m_OutDatedThumpNail = false; + this->m_ThumbNail = GetPixmapFromImageNode(node, iconLength); + this->m_OutDatedThumbNail = false; m_DataMTime = dataMTime; } - painter.drawPixmap(origin, m_ThumpNail); + auto thumbNailOrigin = origin; + thumbNailOrigin.setY(thumbNailOrigin.y() + ((iconLength - m_ThumbNail.height()) / 2)); + painter.drawPixmap(thumbNailOrigin, m_ThumbNail); origin.setX(origin.x() + iconLength + 5); if (this->isEnabled()) { td.setHtml(QString::fromStdString("" + node->GetName() + "")); } else { td.setHtml(QString::fromStdString("" + node->GetName() + "")); } } else { if (this->isEnabled()) { if (this->m_IsOptional) { td.setHtml(QString("") + m_Info + QString("")); } else { td.setHtml(QString("") + m_Info + QString("")); } } else { td.setHtml(QString("") + m_Info + QString("")); } } auto textSize = td.size(); origin.setY( (widgetSize.height() - textSize.height()) / 2.); painter.translate(origin); td.drawContents(&painter); } void QmitkNodeSelectionButton::changeEvent(QEvent *event) { if (event->type() == QEvent::EnabledChange) { this->update(); } } bool QmitkNodeSelectionButton::GetSelectionIsOptional() const { return m_IsOptional; } void QmitkNodeSelectionButton::SetSelectionIsOptional(bool isOptional) { m_IsOptional = isOptional; this->update(); } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.h b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.h index 094269650c..ebd5980339 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.h @@ -1,73 +1,73 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITK_NODE_SELECTION_BUTTON_H #define QMITK_NODE_SELECTION_BUTTON_H #include #include #include "org_mitk_gui_qt_common_Export.h" #include "QPushButton" #include "QPixmap" /** Button class that can be used to display informations about a passed node. * If the passed node is a null ptr the node info text will be shown. * In difference to the normal push button text property. The node info can * be formated text (e.g. HTML code; like the tooltip text).*/ class MITK_QT_COMMON QmitkNodeSelectionButton : public QPushButton { Q_OBJECT public: explicit QmitkNodeSelectionButton(QWidget *parent = nullptr); ~QmitkNodeSelectionButton() override; const mitk::DataNode* GetSelectedNode() const; bool GetSelectionIsOptional() const; public Q_SLOTS : virtual void SetSelectedNode(const mitk::DataNode* node); virtual void SetNodeInfo(QString info); /** Set the widget into an optional mode. Optional means that the selection of no valid node does not mean an invalid state. Thus no node is a valid "node" selection too. The state influences if the info text is handled as an information (optional) or a warning (optiona==false).*/ void SetSelectionIsOptional(bool isOptional); protected: void paintEvent(QPaintEvent *p) override; void changeEvent(QEvent *event) override; void AddNodeObserver(); void RemoveNodeObserver(); void OnNodeModified(const itk::Object * /*caller*/, const itk::EventObject &); mitk::DataNode::ConstPointer m_SelectedNode; QString m_Info; - bool m_OutDatedThumpNail; - QPixmap m_ThumpNail; + bool m_OutDatedThumbNail; + QPixmap m_ThumbNail; itk::ModifiedTimeType m_DataMTime; itk::ModifiedTimeType m_SelectionPropMTime; bool m_IsOptional; unsigned long m_NodeModifiedObserverTag; bool m_NodeObserved; }; #endif // QmitkSingleNodeSelectionWidget_H diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp index fbd19ddff3..87bb9cd71a 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp @@ -1,356 +1,230 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSingleNodeSelectionWidget.h" #include #include "mitkNodePredicateFunction.h" #include "mitkNodePredicateAnd.h" #include #include "QmitkNodeSelectionDialog.h" #include "QmitkNodeDetailsDialog.h" QmitkSingleNodeSelectionWidget::QmitkSingleNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent), m_AutoSelectNewNodes(false) { m_Controls.setupUi(this); m_Controls.btnSelect->installEventFilter(this); m_Controls.btnSelect->setVisible(true); m_Controls.btnClear->setVisible(false); m_Controls.btnClear->setIcon(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org.mitk.gui.qt.common/times.svg"))); this->UpdateInfo(); connect(m_Controls.btnClear, SIGNAL(clicked(bool)), this, SLOT(OnClearSelection())); } -QmitkSingleNodeSelectionWidget::~QmitkSingleNodeSelectionWidget() +void QmitkSingleNodeSelectionWidget::ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { -} - -mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::ExtractCurrentValidSelection(const NodeList& nodes) const -{ - mitk::DataNode::Pointer result = nullptr; - - if (!m_DataStorage.IsExpired()) + if (newInternalSelection.empty()) { - auto storage = m_DataStorage.Lock(); - if (storage.IsNotNull()) + if (m_AutoSelectNewNodes) { - for (auto node : nodes) - { - bool valid = storage->Exists(node); - if (valid && m_NodePredicate.IsNotNull()) - { - valid = m_NodePredicate->CheckNode(node); - } + auto autoSelectedNode = this->DetermineAutoSelectNode(oldInternalSelection); - if (valid) - { - result = node; - break; - } + if (autoSelectedNode.IsNotNull()) + { + newInternalSelection.append(autoSelectedNode); } } } - - return result; -} - -QmitkSingleNodeSelectionWidget::NodeList QmitkSingleNodeSelectionWidget::CompileEmitSelection() const -{ - NodeList result; - - if (!m_SelectOnlyVisibleNodes) + else if (newInternalSelection.size()>1) { - result = m_ExternalSelection; + //this widget only allows one internal selected node. + newInternalSelection = { newInternalSelection.front() }; } - - if (m_SelectedNode.IsNotNull() && !result.contains(m_SelectedNode)) - { - result.append(m_SelectedNode); - } - - return result; } -void QmitkSingleNodeSelectionWidget::OnNodePredicateChanged(const mitk::NodePredicateBase* /*newPredicate*/) -{ - auto lastEmission = this->CompileEmitSelection(); - - if (m_NodePredicate.IsNotNull() && m_SelectedNode.IsNotNull() && !m_NodePredicate->CheckNode(m_SelectedNode)) - { - m_SelectedNode = nullptr; - } - - if (m_SelectedNode.IsNull()) - { - m_SelectedNode = this->ExtractCurrentValidSelection(m_ExternalSelection); - } - - this->DoAutoSelectIfNeeded(); - - this->EmitAndUpdateIfNeeded(lastEmission); -}; - -void QmitkSingleNodeSelectionWidget::OnDataStorageChanged() -{ - auto lastEmission = this->CompileEmitSelection(); - - if (m_DataStorage.IsExpired()) - { - m_SelectedNode = nullptr; - } - else if (m_SelectedNode.IsNotNull()) - { - auto storage = m_DataStorage.Lock(); - - if (storage.IsNotNull() && !storage->Exists(m_SelectedNode)) - { - m_SelectedNode = nullptr; - } - } - - if (m_SelectedNode.IsNull()) - { - m_SelectedNode = this->ExtractCurrentValidSelection(m_ExternalSelection); - } - - this->DoAutoSelectIfNeeded(); - - this->EmitAndUpdateIfNeeded(lastEmission); -}; - void QmitkSingleNodeSelectionWidget::OnClearSelection() { if (m_IsOptional) { - NodeList emptyList; - this->SetCurrentSelection(emptyList); + this->SetCurrentSelection({}); } this->UpdateInfo(); } mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::GetSelectedNode() const { - return m_SelectedNode; -}; + mitk::DataNode::Pointer result; + + auto selection = GetCurrentInternalSelection(); + if (!selection.empty()) + { + result = selection.front(); + } + return result; +} bool QmitkSingleNodeSelectionWidget::eventFilter(QObject *obj, QEvent *ev) { if (obj == m_Controls.btnSelect) { if (ev->type() == QEvent::MouseButtonRelease) { auto mouseEv = dynamic_cast(ev); if (!mouseEv) { return false; } if (mouseEv->button() == Qt::LeftButton) { if (this->isEnabled()) { this->EditSelection(); return true; } } else { auto selection = this->CompileEmitSelection(); if (!selection.empty()) { QmitkNodeDetailsDialog infoDialog(selection, this); infoDialog.exec(); return true; } } } } return false; } void QmitkSingleNodeSelectionWidget::EditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this, m_PopUpTitel, m_PopUpHint); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); - NodeList list; - if (m_SelectedNode.IsNotNull()) - { - list.append(m_SelectedNode); - } - dialog->SetCurrentSelection(list); + dialog->SetCurrentSelection(this->GetCurrentInternalSelection()); dialog->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); dialog->SetSelectionMode(QAbstractItemView::SingleSelection); m_Controls.btnSelect->setChecked(true); if (dialog->exec()) { - auto lastEmission = this->CompileEmitSelection(); - - auto nodes = dialog->GetSelectedNodes(); - if (nodes.empty()) - { - m_SelectedNode = nullptr; - } - else - { - m_SelectedNode = nodes.first(); - } - - this->EmitAndUpdateIfNeeded(lastEmission); + this->HandleChangeOfInternalSelection(dialog->GetSelectedNodes()); } m_Controls.btnSelect->setChecked(false); delete dialog; -}; +} void QmitkSingleNodeSelectionWidget::UpdateInfo() { - if (m_SelectedNode.IsNull()) + if (this->GetSelectedNode().IsNull()) { if (m_IsOptional) { m_Controls.btnSelect->SetNodeInfo(m_EmptyInfo); } else { m_Controls.btnSelect->SetNodeInfo(m_InvalidInfo); } m_Controls.btnSelect->SetSelectionIsOptional(m_IsOptional); m_Controls.btnClear->setVisible(false); } else { m_Controls.btnClear->setVisible(m_IsOptional); } - m_Controls.btnSelect->SetSelectedNode(m_SelectedNode); -}; - -void QmitkSingleNodeSelectionWidget::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) -{ - auto lastEmission = this->CompileEmitSelection(); - - m_SelectOnlyVisibleNodes = selectOnlyVisibleNodes; - - this->EmitAndUpdateIfNeeded(lastEmission); -}; + m_Controls.btnSelect->SetSelectedNode(this->GetSelectedNode()); +} -void QmitkSingleNodeSelectionWidget::DoAutoSelectIfNeeded(const mitk::DataNode* ignoreNode) +mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::DetermineAutoSelectNode(const NodeList& ignoreNodes) { - if (m_SelectedNode.IsNull() && m_AutoSelectNewNodes && !m_DataStorage.IsExpired()) + mitk::DataNode::Pointer result; + auto storage = m_DataStorage.Lock(); + if (storage.IsNotNull()) { - auto storage = m_DataStorage.Lock(); - if (storage.IsNotNull()) + auto ignoreCheck = [ignoreNodes](const mitk::DataNode * node) { - auto ignoreCheck = [ignoreNode](const mitk::DataNode * node) + bool result = true; + for (const auto& ignoreNode : ignoreNodes) { - return node != ignoreNode; - }; - mitk::NodePredicateFunction::Pointer isNotIgnoredNode = mitk::NodePredicateFunction::New(ignoreCheck); - mitk::NodePredicateBase::Pointer predicate = isNotIgnoredNode.GetPointer(); - - if (m_NodePredicate.IsNotNull()) - { - predicate = mitk::NodePredicateAnd::New(m_NodePredicate.GetPointer(), predicate.GetPointer()).GetPointer(); + if (node == ignoreNode) + { + result = false; + break; + } } + return result; + }; - m_SelectedNode = storage->GetNode(predicate); - } - } -} + mitk::NodePredicateFunction::Pointer isNotIgnoredNode = mitk::NodePredicateFunction::New(ignoreCheck); + mitk::NodePredicateBase::Pointer predicate = isNotIgnoredNode.GetPointer(); -void QmitkSingleNodeSelectionWidget::EmitAndUpdateIfNeeded(const NodeList& lastEmission) -{ - auto newEmission = this->CompileEmitSelection(); + if (m_NodePredicate.IsNotNull()) + { + predicate = mitk::NodePredicateAnd::New(m_NodePredicate.GetPointer(), predicate.GetPointer()).GetPointer(); + } - if (!EqualNodeSelections(lastEmission, newEmission)) - { - this->UpdateInfo(); - emit CurrentSelectionChanged(newEmission); + result = storage->GetNode(predicate); } -}; + return result; +} void QmitkSingleNodeSelectionWidget::SetCurrentSelectedNode(mitk::DataNode* selectedNode) { NodeList selection; if (selectedNode) { selection.append(selectedNode); } this->SetCurrentSelection(selection); }; -void QmitkSingleNodeSelectionWidget::SetCurrentSelection(NodeList selectedNodes) -{ - auto lastEmission = this->CompileEmitSelection(); - - m_ExternalSelection = selectedNodes; - m_SelectedNode = this->ExtractCurrentValidSelection(selectedNodes); - - this->DoAutoSelectIfNeeded(); - - this->EmitAndUpdateIfNeeded(lastEmission); -}; - -void QmitkSingleNodeSelectionWidget::NodeAddedToStorage(const mitk::DataNode* /*node*/) +void QmitkSingleNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* /*node*/) { - if (m_SelectedNode.IsNull() && m_AutoSelectNewNodes && !m_DataStorage.IsExpired()) + if (this->GetSelectedNode().IsNull() && m_AutoSelectNewNodes) { - auto lastEmission = this->CompileEmitSelection(); + auto autoNode = this->DetermineAutoSelectNode(); - this->DoAutoSelectIfNeeded(); - - this->EmitAndUpdateIfNeeded(lastEmission); - } -} - -void QmitkSingleNodeSelectionWidget::NodeRemovedFromStorage(const mitk::DataNode* node) -{ - if (m_SelectedNode == node && node != nullptr) - { - m_SelectedNode = nullptr; - - //This event is triggerd before the node is realy removed from storage. - //Therefore we have to explictly ignore him in the autoselect search. - this->DoAutoSelectIfNeeded(node); - - auto newEmission = this->CompileEmitSelection(); - - emit CurrentSelectionChanged(newEmission); - this->UpdateInfo(); + if (autoNode.IsNotNull()) + { + this->HandleChangeOfInternalSelection({ autoNode }); + } } } bool QmitkSingleNodeSelectionWidget::GetAutoSelectNewNodes() const { return m_AutoSelectNewNodes; } void QmitkSingleNodeSelectionWidget::SetAutoSelectNewNodes(bool autoSelect) { m_AutoSelectNewNodes = autoSelect; - this->NodeAddedToStorage(nullptr); + this->OnNodeAddedToStorage(nullptr); } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h index 3653dbf262..27635231ca 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h @@ -1,88 +1,80 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITK_SINGLE_NODE_SELECTION_WIDGET_H #define QMITK_SINGLE_NODE_SELECTION_WIDGET_H #include #include #include #include "org_mitk_gui_qt_common_Export.h" #include "ui_QmitkSingleNodeSelectionWidget.h" #include #include class QmitkAbstractDataStorageModel; /** * \class QmitkSingleNodeSelectionWidget * \brief Widget that represents a node selection of (max) one node. It acts like a button. Clicking on it * allows to change the selection. */ class MITK_QT_COMMON QmitkSingleNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: explicit QmitkSingleNodeSelectionWidget(QWidget* parent = nullptr); - ~QmitkSingleNodeSelectionWidget() override; mitk::DataNode::Pointer GetSelectedNode() const; bool GetAutoSelectNewNodes() const; using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; public Q_SLOTS: - void SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) override; - void SetCurrentSelection(NodeList selectedNodes) override; void SetCurrentSelectedNode(mitk::DataNode* selectedNode); /** Sets the auto selection mode (Default is false). If auto select is true and the following conditions are fullfilled, the widget will select a node automatically from the data storage: - a data storage is set - data storage contains at least one node that matches the given predicate - no selection is set.*/ void SetAutoSelectNewNodes(bool autoSelect); protected Q_SLOTS: virtual void OnClearSelection(); protected: - mitk::DataNode::Pointer ExtractCurrentValidSelection(const NodeList& nodes) const; - NodeList CompileEmitSelection() const; + void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) override; bool eventFilter(QObject *obj, QEvent *ev) override; void EditSelection(); void UpdateInfo() override; - void OnNodePredicateChanged(const mitk::NodePredicateBase* newPredicate) override; - void OnDataStorageChanged() override; - void NodeAddedToStorage(const mitk::DataNode* node) override; - void NodeRemovedFromStorage(const mitk::DataNode* node) override; + void OnNodeAddedToStorage(const mitk::DataNode* node) override; - void DoAutoSelectIfNeeded(const mitk::DataNode* ignoreNode = nullptr); - void EmitAndUpdateIfNeeded(const NodeList& lastEmission); + /** Helper function that gets a suitable auto selected node from the datastorage that fits to the predicate settings. + @param ignoreNodes You may pass a list of nodes that must not be choosen as auto selected node. */ + mitk::DataNode::Pointer DetermineAutoSelectNode(const NodeList& ignoreNodes = {}); - NodeList m_ExternalSelection; - mitk::DataNode::Pointer m_SelectedNode; /** See documentation of SetAutoSelectNewNodes for details*/ bool m_AutoSelectNewNodes; Ui_QmitkSingleNodeSelectionWidget m_Controls; }; #endif // QmitkSingleNodeSelectionWidget_H diff --git a/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.cpp b/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.cpp index 7ab9404e83..1ce16e51e0 100644 --- a/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.cpp +++ b/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.cpp @@ -1,277 +1,277 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // data storage viewer test plugin #include "QmitkDataStorageViewerTestView.h" #include "mitkNodePredicateDataType.h" // berry #include // qt #include const std::string QmitkDataStorageViewerTestView::VIEW_ID = "org.mitk.views.datastorageviewertest"; void QmitkDataStorageViewerTestView::SetFocus() { // nothing here } void QmitkDataStorageViewerTestView::CreateQtPartControl(QWidget* parent) { // create GUI widgets m_Controls.setupUi(parent); m_DataStorageDefaultListModel = new QmitkDataStorageDefaultListModel(this); m_DataStorageDefaultListModel->SetDataStorage(GetDataStorage()); m_Controls.selectionListView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_Controls.selectionListView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.selectionListView->setAlternatingRowColors(true); m_Controls.selectionListView->setModel(m_DataStorageDefaultListModel); m_DataStorageDefaultListModel2 = new QmitkDataStorageDefaultListModel(this); m_DataStorageDefaultListModel2->SetDataStorage(GetDataStorage()); m_Controls.selectionListView2->setSelectionMode(QAbstractItemView::ExtendedSelection); m_Controls.selectionListView2->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.selectionListView2->setAlternatingRowColors(true); m_Controls.selectionListView2->setModel(m_DataStorageDefaultListModel2); m_Controls.singleSlot->SetDataStorage(GetDataStorage()); m_Controls.singleSlot->SetEmptyInfo(QStringLiteral("EmptyInfo: Set this to display info in empty state")); m_Controls.singleSlot->SetInvalidInfo(QStringLiteral("InvalidInfo: is displayed for invalid states")); m_Controls.singleSlot->SetPopUpTitel(QStringLiteral("This is the definable caption. Choose your data now!")); m_Controls.singleSlot->SetPopUpHint(QStringLiteral("I am an optional hint, that can be set by the developer

If not set the widget is invisible.")); m_Controls.multiSlot->SetDataStorage(GetDataStorage()); m_Controls.multiSlot->SetEmptyInfo(QStringLiteral("EmptyInfo: Set this to display info in empty state")); m_Controls.multiSlot->SetInvalidInfo(QStringLiteral("InvalidInfo: is displayed for invalid states")); m_Controls.multiSlot->SetPopUpTitel(QStringLiteral("This is the definable caption. Choose your data now!")); m_Controls.multiSlot->SetPopUpHint(QStringLiteral("I am an optional hint, that can be set by the developer

If not set the widget is invisible.")); m_ModelViewSelectionConnector = std::make_unique(); try { m_ModelViewSelectionConnector->SetView(m_Controls.selectionListView); } catch (mitk::Exception& e) { mitkReThrow(e) << "Cannot connect the model-view pair signals and slots."; } m_SelectionServiceConnector = std::make_unique(); m_ModelViewSelectionConnector2 = std::make_unique(); try { m_ModelViewSelectionConnector2->SetView(m_Controls.selectionListView2); } catch (mitk::Exception& e) { mitkReThrow(e) << "Cannot connect the model-view pair signals and slots."; } m_SelectionServiceConnector2 = std::make_unique(); m_SelectionServiceConnector3 = std::make_unique(); m_SelectionServiceConnector4 = std::make_unique(); connect(m_Controls.selectionProviderCheckBox, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionProvider1(bool))); connect(m_Controls.selectionProviderCheckBox2, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionProvider2(bool))); connect(m_Controls.selectionListenerCheckBox, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionListener1(bool))); connect(m_Controls.selectionListenerCheckBox2, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionListener2(bool))); connect(m_Controls.selectionProviderCheckBox3, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionProvider3(bool))); connect(m_Controls.selectionListenerCheckBox3, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionListener3(bool))); connect(m_Controls.checkOnlyVisible, SIGNAL(toggled(bool)), m_Controls.singleSlot, SLOT(SetSelectOnlyVisibleNodes(bool))); connect(m_Controls.checkOptional, SIGNAL(toggled(bool)), m_Controls.singleSlot, SLOT(SetSelectionIsOptional(bool))); connect(m_Controls.checkAuto, SIGNAL(toggled(bool)), m_Controls.singleSlot, SLOT(SetAutoSelectNewNodes(bool))); connect(m_Controls.checkOnlyImages, SIGNAL(toggled(bool)), this, SLOT(OnOnlyImages(bool))); connect(m_Controls.checkEnabled, SIGNAL(toggled(bool)), m_Controls.singleSlot, SLOT(setEnabled(bool))); connect(m_Controls.selectionProviderCheckBox4, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionProvider4(bool))); connect(m_Controls.selectionListenerCheckBox4, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionListener4(bool))); connect(m_Controls.checkOnlyVisible_2, SIGNAL(toggled(bool)), m_Controls.multiSlot, SLOT(SetSelectOnlyVisibleNodes(bool))); connect(m_Controls.checkOptional_2, SIGNAL(toggled(bool)), m_Controls.multiSlot, SLOT(SetSelectionIsOptional(bool))); connect(m_Controls.checkOnlyImages_2, SIGNAL(toggled(bool)), this, SLOT(OnOnlyImages2(bool))); connect(m_Controls.checkOnlyUneven, SIGNAL(toggled(bool)), this, SLOT(OnOnlyUneven(bool))); connect(m_Controls.checkEnabled_2, SIGNAL(toggled(bool)), m_Controls.multiSlot, SLOT(setEnabled(bool))); } void QmitkDataStorageViewerTestView::SetAsSelectionProvider1(bool checked) { if (checked) { m_SelectionServiceConnector->SetAsSelectionProvider(GetSite()->GetSelectionProvider().Cast().GetPointer()); connect(m_ModelViewSelectionConnector.get(), SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector.get(), SLOT(ChangeServiceSelection(QList))); } else { m_SelectionServiceConnector->RemoveAsSelectionProvider(); disconnect(m_ModelViewSelectionConnector.get(), SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector.get(), SLOT(ChangeServiceSelection(QList))); } } void QmitkDataStorageViewerTestView::SetAsSelectionListener1(bool checked) { if (checked) { m_SelectionServiceConnector->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector.get(), SIGNAL(ServiceSelectionChanged(QList)), m_ModelViewSelectionConnector.get(), SLOT(SetCurrentSelection(QList))); } else { m_SelectionServiceConnector->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector.get(), SIGNAL(ServiceSelectionChanged(QList)), m_ModelViewSelectionConnector.get(), SLOT(SetCurrentSelection(QList))); } } void QmitkDataStorageViewerTestView::SetAsSelectionProvider2(bool checked) { if (checked) { m_SelectionServiceConnector2->SetAsSelectionProvider(GetSite()->GetSelectionProvider().Cast().GetPointer()); connect(m_ModelViewSelectionConnector2.get(), SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector2.get(), SLOT(ChangeServiceSelection(QList))); } else { m_SelectionServiceConnector2->RemoveAsSelectionProvider(); disconnect(m_ModelViewSelectionConnector2.get(), SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector2.get(), SLOT(ChangeServiceSelection(QList))); } } void QmitkDataStorageViewerTestView::SetAsSelectionListener2(bool checked) { if (checked) { m_SelectionServiceConnector2->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector2.get(), SIGNAL(ServiceSelectionChanged(QList)), m_ModelViewSelectionConnector2.get(), SLOT(SetCurrentSelection(QList))); } else { m_SelectionServiceConnector2->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector2.get(), SIGNAL(ServiceSelectionChanged(QList)), m_ModelViewSelectionConnector2.get(), SLOT(SetCurrentSelection(QList))); } } void QmitkDataStorageViewerTestView::SetAsSelectionProvider3(bool checked) { if (checked) { m_SelectionServiceConnector3->SetAsSelectionProvider(GetSite()->GetSelectionProvider().Cast().GetPointer()); - connect(m_Controls.singleSlot, SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector3.get(), SLOT(ChangeServiceSelection(QList))); + connect(m_Controls.singleSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } else { m_SelectionServiceConnector3->RemoveAsSelectionProvider(); - disconnect(m_Controls.singleSlot, SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector3.get(), SLOT(ChangeServiceSelection(QList))); + disconnect(m_Controls.singleSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } } void QmitkDataStorageViewerTestView::SetAsSelectionListener3(bool checked) { if (checked) { m_SelectionServiceConnector3->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } else { m_SelectionServiceConnector3->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } } void QmitkDataStorageViewerTestView::SetAsSelectionProvider4(bool checked) { if (checked) { m_SelectionServiceConnector4->SetAsSelectionProvider(GetSite()->GetSelectionProvider().Cast().GetPointer()); - connect(m_Controls.multiSlot, SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector4.get(), SLOT(ChangeServiceSelection(QList))); + connect(m_Controls.multiSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } else { m_SelectionServiceConnector4->RemoveAsSelectionProvider(); - disconnect(m_Controls.multiSlot, SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector4.get(), SLOT(ChangeServiceSelection(QList))); + disconnect(m_Controls.multiSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } } void QmitkDataStorageViewerTestView::SetAsSelectionListener4(bool checked) { if (checked) { m_SelectionServiceConnector4->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.multiSlot, &QmitkMultiNodeSelectionWidget::SetCurrentSelection); } else { m_SelectionServiceConnector4->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.multiSlot, &QmitkMultiNodeSelectionWidget::SetCurrentSelection); } } void QmitkDataStorageViewerTestView::OnOnlyImages(bool checked) { if (checked) { m_Controls.singleSlot->SetNodePredicate(mitk::NodePredicateDataType::New("Image")); } else { m_Controls.singleSlot->SetNodePredicate(nullptr); } }; void QmitkDataStorageViewerTestView::OnOnlyImages2(bool checked) { if (checked) { m_Controls.multiSlot->SetNodePredicate(mitk::NodePredicateDataType::New("Image")); m_Controls.multiSlot->SetInvalidInfo(QStringLiteral("InvalidInfo: is displayed for invalid states. Only images allowed!")); } else { m_Controls.multiSlot->SetNodePredicate(nullptr); m_Controls.multiSlot->SetInvalidInfo(QStringLiteral("InvalidInfo: is displayed for invalid states")); } }; void QmitkDataStorageViewerTestView::OnOnlyUneven(bool checked) { if (checked) { auto checkFunction = [](const QmitkMultiNodeSelectionWidget::NodeList & nodes) { if (!(nodes.size() % 2)) { std::stringstream ss; ss << "

Invalid selection.

The number of selected nodes must be uneven! the current number is " << nodes.size() << ".

"; return ss.str(); } return std::string(); }; m_Controls.multiSlot->SetSelectionCheckFunction(checkFunction); } else { auto checkFunction = [](const QmitkMultiNodeSelectionWidget::NodeList & /*nodes*/) { return std::string(); }; m_Controls.multiSlot->SetSelectionCheckFunction(checkFunction); } }; diff --git a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp index d9be01a646..2226e032cc 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp @@ -1,686 +1,657 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#include "QmitkAnimationItemDelegate.h" -#include "QmitkFFmpegWriter.h" #include "QmitkMovieMakerView.h" +#include + +#include "QmitkAnimationItemDelegate.h" #include "QmitkOrbitAnimationItem.h" #include "QmitkOrbitAnimationWidget.h" #include "QmitkSliceAnimationItem.h" #include "QmitkSliceAnimationWidget.h" -#include "QmitkTimeSliceAnimationWidget.h" #include "QmitkTimeSliceAnimationItem.h" -#include -#include -#include -#include -#include -#include +#include "QmitkTimeSliceAnimationWidget.h" + #include -#include -#include -static QmitkAnimationItem* CreateDefaultAnimation(const QString& widgetKey) -{ - if (widgetKey == "Orbit") - return new QmitkOrbitAnimationItem; +#include - if (widgetKey == "Slice") - return new QmitkSliceAnimationItem; +#include - if (widgetKey == "Time") - return new QmitkTimeSliceAnimationItem; +#include +#include +#include +#include - return nullptr; -} +#include -QString QmitkMovieMakerView::GetFFmpegPath() const +namespace { - berry::IPreferences::Pointer preferences = berry::Platform::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.gui.qt.ext.externalprograms"); + QmitkAnimationItem* CreateDefaultAnimation(const QString& widgetKey) + { + if (widgetKey == "Orbit") + return new QmitkOrbitAnimationItem; - return preferences.IsNotNull() - ? preferences->Get("ffmpeg", "") - : ""; -} + if (widgetKey == "Slice") + return new QmitkSliceAnimationItem; + + if (widgetKey == "Time") + return new QmitkTimeSliceAnimationItem; -static unsigned char* ReadPixels(vtkRenderWindow* renderWindow, int x, int y, int width, int height) -{ - if (renderWindow == nullptr) return nullptr; + } - unsigned char* frame = new unsigned char[width * height * 3]; + QString GetFFmpegPath() + { + auto preferences = berry::Platform::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.gui.qt.ext.externalprograms"); + + return preferences.IsNotNull() + ? preferences->Get("ffmpeg", "") + : ""; + } - renderWindow->MakeCurrent(); - glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, frame); + void ReadPixels(std::unique_ptr& frame, vtkRenderWindow* renderWindow, int x, int y, int width, int height) + { + if (nullptr == renderWindow) + return; - return frame; + renderWindow->MakeCurrent(); + glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, frame.get()); + } } const std::string QmitkMovieMakerView::VIEW_ID = "org.mitk.views.moviemaker"; QmitkMovieMakerView::QmitkMovieMakerView() : m_FFmpegWriter(nullptr), m_Ui(new Ui::QmitkMovieMakerView), m_AnimationModel(nullptr), m_AddAnimationMenu(nullptr), m_RecordMenu(nullptr), m_Timer(nullptr), m_TotalDuration(0.0), m_NumFrames(0), m_CurrentFrame(0) { } QmitkMovieMakerView::~QmitkMovieMakerView() { } void QmitkMovieMakerView::CreateQtPartControl(QWidget* parent) { m_FFmpegWriter = new QmitkFFmpegWriter(parent); m_Ui->setupUi(parent); this->InitializeAnimationWidgets(); this->InitializeAnimationTreeViewWidgets(); this->InitializePlaybackAndRecordWidgets(); this->InitializeTimer(parent); m_Ui->animationWidgetGroupBox->setVisible(false); } void QmitkMovieMakerView::InitializeAnimationWidgets() { m_AnimationWidgets["Orbit"] = new QmitkOrbitAnimationWidget; m_AnimationWidgets["Slice"] = new QmitkSliceAnimationWidget; m_AnimationWidgets["Time"] = new QmitkTimeSliceAnimationWidget; - Q_FOREACH(QWidget* widget, m_AnimationWidgets.values()) + for (const auto& widget : m_AnimationWidgets) { - if (widget != nullptr) + if (nullptr != widget.second) { - widget->setVisible(false); - m_Ui->animationWidgetGroupBoxLayout->addWidget(widget); + widget.second->setVisible(false); + m_Ui->animationWidgetGroupBoxLayout->addWidget(widget.second); } } this->ConnectAnimationWidgets(); } void QmitkMovieMakerView::InitializeAnimationTreeViewWidgets() { this->InitializeAnimationModel(); this->InitializeAddAnimationMenu(); this->ConnectAnimationTreeViewWidgets(); } void QmitkMovieMakerView::InitializePlaybackAndRecordWidgets() { this->InitializeRecordMenu(); this->ConnectPlaybackAndRecordWidgets(); } void QmitkMovieMakerView::InitializeAnimationModel() { m_AnimationModel = new QStandardItemModel(m_Ui->animationTreeView); m_AnimationModel->setHorizontalHeaderLabels(QStringList() << "Animation" << "Timeline"); - m_Ui->animationTreeView->setModel(m_AnimationModel); + m_Ui->animationTreeView->setModel(m_AnimationModel); m_Ui->animationTreeView->setItemDelegate(new QmitkAnimationItemDelegate(m_Ui->animationTreeView)); } void QmitkMovieMakerView::InitializeAddAnimationMenu() { m_AddAnimationMenu = new QMenu(m_Ui->addAnimationButton); - Q_FOREACH(const QString& key, m_AnimationWidgets.keys()) - { - m_AddAnimationMenu->addAction(key); - } + for(const auto& widget : m_AnimationWidgets) + m_AddAnimationMenu->addAction(widget.first); } void QmitkMovieMakerView::InitializeRecordMenu() { - typedef QPair PairOfStrings; + std::array, 4> renderWindows = { + std::make_pair(QStringLiteral("Axial"), QStringLiteral("stdmulti.widget0")), + std::make_pair(QStringLiteral("Sagittal"), QStringLiteral("stdmulti.widget1")), + std::make_pair(QStringLiteral("Coronal"), QStringLiteral("stdmulti.widget2")), + std::make_pair(QStringLiteral("3D"), QStringLiteral("stdmulti.widget3")) + }; m_RecordMenu = new QMenu(m_Ui->recordButton); - QVector renderWindows; - renderWindows.push_back(qMakePair(QString("Axial"), QString("stdmulti.widget0"))); - renderWindows.push_back(qMakePair(QString("Sagittal"), QString("stdmulti.widget1"))); - renderWindows.push_back(qMakePair(QString("Coronal"), QString("stdmulti.widget2"))); - renderWindows.push_back(qMakePair(QString("3D"), QString("stdmulti.widget3"))); - - Q_FOREACH(const PairOfStrings& renderWindow, renderWindows) + for(const auto& renderWindow : renderWindows) { - QAction* action = new QAction(m_RecordMenu); + auto* action = new QAction(m_RecordMenu); action->setText(renderWindow.first); action->setData(renderWindow.second); m_RecordMenu->addAction(action); } } void QmitkMovieMakerView::InitializeTimer(QWidget* parent) { m_Timer = new QTimer(parent); this->OnFPSSpinBoxValueChanged(m_Ui->fpsSpinBox->value()); this->ConnectTimer(); } void QmitkMovieMakerView::ConnectAnimationTreeViewWidgets() { - this->connect(m_AnimationModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), - this, SLOT(OnAnimationTreeViewRowsInserted(const QModelIndex&, int, int))); - - this->connect(m_AnimationModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), - this, SLOT(OnAnimationTreeViewRowsRemoved(const QModelIndex&, int, int))); - - this->connect(m_Ui->animationTreeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), - this, SLOT(OnAnimationTreeViewSelectionChanged(const QItemSelection&, const QItemSelection&))); - - this->connect(m_Ui->moveAnimationUpButton, SIGNAL(clicked()), - this, SLOT(OnMoveAnimationUpButtonClicked())); - - this->connect(m_Ui->moveAnimationDownButton, SIGNAL(clicked()), - this, SLOT(OnMoveAnimationDownButtonClicked())); - - this->connect(m_Ui->addAnimationButton, SIGNAL(clicked()), - this, SLOT(OnAddAnimationButtonClicked())); - - this->connect(m_Ui->removeAnimationButton, SIGNAL(clicked()), - this, SLOT(OnRemoveAnimationButtonClicked())); + connect(m_AnimationModel, &QStandardItemModel::rowsInserted, this, &QmitkMovieMakerView::OnAnimationTreeViewRowsInserted); + connect(m_AnimationModel, &QStandardItemModel::rowsRemoved, this, &QmitkMovieMakerView::OnAnimationTreeViewRowsRemoved); + connect(m_Ui->animationTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QmitkMovieMakerView::OnAnimationTreeViewSelectionChanged); + connect(m_Ui->moveAnimationUpButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnMoveAnimationUpButtonClicked); + connect(m_Ui->moveAnimationDownButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnMoveAnimationDownButtonClicked); + connect(m_Ui->addAnimationButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnAddAnimationButtonClicked); + connect(m_Ui->removeAnimationButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnRemoveAnimationButtonClicked); } void QmitkMovieMakerView::ConnectAnimationWidgets() { - this->connect(m_Ui->startComboBox, SIGNAL(currentIndexChanged(int)), - this, SLOT(OnStartComboBoxCurrentIndexChanged(int))); - - this->connect(m_Ui->durationSpinBox, SIGNAL(valueChanged(double)), - this, SLOT(OnDurationSpinBoxValueChanged(double))); - - this->connect(m_Ui->delaySpinBox, SIGNAL(valueChanged(double)), - this, SLOT(OnDelaySpinBoxValueChanged(double))); + connect(m_Ui->startComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnStartComboBoxCurrentIndexChanged(int))); + connect(m_Ui->durationSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnDurationSpinBoxValueChanged(double))); + connect(m_Ui->delaySpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnDelaySpinBoxValueChanged(double))); } void QmitkMovieMakerView::ConnectPlaybackAndRecordWidgets() { - this->connect(m_Ui->playButton, SIGNAL(toggled(bool)), - this, SLOT(OnPlayButtonToggled(bool))); - - this->connect(m_Ui->stopButton, SIGNAL(clicked()), - this, SLOT(OnStopButtonClicked())); - - this->connect(m_Ui->recordButton, SIGNAL(clicked()), - this, SLOT(OnRecordButtonClicked())); - - this->connect(m_Ui->fpsSpinBox, SIGNAL(valueChanged(int)), - this, SLOT(OnFPSSpinBoxValueChanged(int))); + connect(m_Ui->playButton, &QToolButton::toggled, this, &QmitkMovieMakerView::OnPlayButtonToggled); + connect(m_Ui->stopButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnStopButtonClicked); + connect(m_Ui->recordButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnRecordButtonClicked); + connect(m_Ui->fpsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnFPSSpinBoxValueChanged(int))); } void QmitkMovieMakerView::ConnectTimer() { - this->connect(m_Timer, SIGNAL(timeout()), - this, SLOT(OnTimerTimeout())); + connect(m_Timer, &QTimer::timeout, this, &QmitkMovieMakerView::OnTimerTimeout); } void QmitkMovieMakerView::SetFocus() { m_Ui->addAnimationButton->setFocus(); } void QmitkMovieMakerView::OnMoveAnimationUpButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) { const int selectedRow = selection[0].top(); if (selectedRow > 0) m_AnimationModel->insertRow(selectedRow - 1, m_AnimationModel->takeRow(selectedRow)); } this->CalculateTotalDuration(); } void QmitkMovieMakerView::OnMoveAnimationDownButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) { const int rowCount = m_AnimationModel->rowCount(); const int selectedRow = selection[0].top(); if (selectedRow < rowCount - 1) m_AnimationModel->insertRow(selectedRow + 1, m_AnimationModel->takeRow(selectedRow)); } this->CalculateTotalDuration(); } void QmitkMovieMakerView::OnAddAnimationButtonClicked() { - QAction* action = m_AddAnimationMenu->exec(QCursor::pos()); + auto action = m_AddAnimationMenu->exec(QCursor::pos()); - if (action != nullptr) + if (nullptr != action) { - const QString widgetKey = action->text(); + const auto key = action->text(); m_AnimationModel->appendRow(QList() - << new QStandardItem(widgetKey) - << CreateDefaultAnimation(widgetKey)); + << new QStandardItem(key) + << CreateDefaultAnimation(key)); m_Ui->playbackAndRecordingGroupBox->setEnabled(true); } } void QmitkMovieMakerView::OnPlayButtonToggled(bool checked) { if (checked) { m_Ui->playButton->setIcon(QIcon(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-pause.svg")); m_Ui->playButton->repaint(); m_Timer->start(static_cast(1000.0 / m_Ui->fpsSpinBox->value())); } else { m_Timer->stop(); m_Ui->playButton->setIcon(QIcon(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-start.svg")); m_Ui->playButton->repaint(); } } void QmitkMovieMakerView::OnStopButtonClicked() { m_Ui->playButton->setChecked(false); m_Ui->stopButton->setEnabled(false); m_CurrentFrame = 0; this->RenderCurrentFrame(); } -void QmitkMovieMakerView::OnRecordButtonClicked() // TODO: Refactor +void QmitkMovieMakerView::OnRecordButtonClicked() { + if (0 == m_NumFrames || 0.0 == m_TotalDuration) + return; + const QString ffmpegPath = GetFFmpegPath(); if (ffmpegPath.isEmpty()) { QMessageBox::information(nullptr, "Movie Maker", - "

Set path to FFmpeg1 or Libav2 (avconv) in preferences (Window -> Preferences... (Ctrl+P) -> External Programs) to be able to record your movies to video files.

" - "

If you are using Linux, chances are good that either FFmpeg or Libav is included in the official package repositories.

" - "

[1] Download FFmpeg from ffmpeg.org
" - "[2] Download Libav from libav.org

"); + "

Set path to FFmpeg (ffmpeg.org) in preferences " + "(Window -> Preferences... (Ctrl+P) -> External Programs) " + "to be able to record your movies to video files.

"); return; } m_FFmpegWriter->SetFFmpegPath(GetFFmpegPath()); - QAction* action = m_RecordMenu->exec(QCursor::pos()); + auto action = m_RecordMenu->exec(QCursor::pos()); - if (action == nullptr) + if (nullptr == action) return; - vtkRenderWindow* renderWindow = mitk::BaseRenderer::GetRenderWindowByName(action->data().toString().toStdString()); + auto renderWindow = mitk::BaseRenderer::GetRenderWindowByName(action->data().toString().toStdString()); - if (renderWindow == nullptr) + if (nullptr == renderWindow) return; const int border = 3; const int x = border; const int y = border; int width = renderWindow->GetSize()[0] - border * 2; int height = renderWindow->GetSize()[1] - border * 2; if (width & 1) --width; if (height & 1) --height; if (width < 16 || height < 16) return; m_FFmpegWriter->SetSize(width, height); m_FFmpegWriter->SetFramerate(m_Ui->fpsSpinBox->value()); QString saveFileName = QFileDialog::getSaveFileName(nullptr, "Specify a filename", "", "Movie (*.mp4)"); if (saveFileName.isEmpty()) return; if(!saveFileName.endsWith(".mp4")) saveFileName += ".mp4"; m_FFmpegWriter->SetOutputPath(saveFileName); try { + auto frame = std::make_unique(width * height * 3); m_FFmpegWriter->Start(); for (m_CurrentFrame = 0; m_CurrentFrame < m_NumFrames; ++m_CurrentFrame) { this->RenderCurrentFrame(); - - renderWindow->MakeCurrent(); - unsigned char* frame = ReadPixels(renderWindow, x, y, width, height); - m_FFmpegWriter->WriteFrame(frame); - delete[] frame; + ReadPixels(frame, renderWindow, x, y, width, height); + m_FFmpegWriter->WriteFrame(frame.get()); } m_FFmpegWriter->Stop(); m_CurrentFrame = 0; this->RenderCurrentFrame(); } catch (const mitk::Exception& exception) { m_CurrentFrame = 0; this->RenderCurrentFrame(); QMessageBox::critical(nullptr, "Movie Maker", exception.GetDescription()); } } void QmitkMovieMakerView::OnRemoveAnimationButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) m_AnimationModel->removeRow(selection[0].top()); } void QmitkMovieMakerView::OnAnimationTreeViewRowsInserted(const QModelIndex& parent, int start, int) { this->CalculateTotalDuration(); m_Ui->animationTreeView->selectionModel()->select( m_AnimationModel->index(start, 0, parent), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } void QmitkMovieMakerView::OnAnimationTreeViewRowsRemoved(const QModelIndex&, int, int) { this->CalculateTotalDuration(); this->UpdateWidgets(); } void QmitkMovieMakerView::OnAnimationTreeViewSelectionChanged(const QItemSelection&, const QItemSelection&) { this->UpdateWidgets(); } void QmitkMovieMakerView::OnStartComboBoxCurrentIndexChanged(int index) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { item->SetStartWithPrevious(index); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnDurationSpinBoxValueChanged(double value) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { item->SetDuration(value); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnDelaySpinBoxValueChanged(double value) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { item->SetDelay(value); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnFPSSpinBoxValueChanged(int value) { this->CalculateTotalDuration(); m_Timer->setInterval(static_cast(1000.0 / value)); } void QmitkMovieMakerView::OnTimerTimeout() { this->RenderCurrentFrame(); m_CurrentFrame = std::min(m_NumFrames, m_CurrentFrame + 1); if (m_CurrentFrame >= m_NumFrames) { m_Ui->playButton->setChecked(false); m_CurrentFrame = 0; this->RenderCurrentFrame(); } m_Ui->stopButton->setEnabled(m_CurrentFrame != 0); } void QmitkMovieMakerView::RenderCurrentFrame() { - typedef QPair AnimationInterpolationFactorPair; - const double deltaT = m_TotalDuration / (m_NumFrames - 1); - const QVector activeAnimations = this->GetActiveAnimations(m_CurrentFrame * deltaT); + const auto activeAnimations = this->GetActiveAnimations(m_CurrentFrame * deltaT); - Q_FOREACH(const AnimationInterpolationFactorPair& animation, activeAnimations) + for (const auto& animation : activeAnimations) { - const QVector nextActiveAnimations = this->GetActiveAnimations((m_CurrentFrame + 1) * deltaT); + const auto nextActiveAnimations = this->GetActiveAnimations((m_CurrentFrame + 1) * deltaT); bool lastFrameForAnimation = true; - Q_FOREACH(const AnimationInterpolationFactorPair& nextAnimation, nextActiveAnimations) + for (const auto& nextAnimation : nextActiveAnimations) { if (nextAnimation.first == animation.first) { lastFrameForAnimation = false; break; } } animation.first->Animate(!lastFrameForAnimation ? animation.second : 1.0); } mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); } void QmitkMovieMakerView::UpdateWidgets() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (selection.isEmpty()) { m_Ui->moveAnimationUpButton->setEnabled(false); m_Ui->moveAnimationDownButton->setEnabled(false); m_Ui->removeAnimationButton->setEnabled(false); m_Ui->playbackAndRecordingGroupBox->setEnabled(false); this->HideCurrentAnimationWidget(); } else { const int rowCount = m_AnimationModel->rowCount(); const int selectedRow = selection[0].top(); m_Ui->moveAnimationUpButton->setEnabled(rowCount > 1 && selectedRow != 0); m_Ui->moveAnimationDownButton->setEnabled(rowCount > 1 && selectedRow < rowCount - 1); m_Ui->removeAnimationButton->setEnabled(true); m_Ui->playbackAndRecordingGroupBox->setEnabled(true); this->ShowAnimationWidget(dynamic_cast(m_AnimationModel->item(selectedRow, 1))); } this->UpdateAnimationWidgets(); } void QmitkMovieMakerView::UpdateAnimationWidgets() { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { m_Ui->startComboBox->setCurrentIndex(item->GetStartWithPrevious()); m_Ui->durationSpinBox->setValue(item->GetDuration()); m_Ui->delaySpinBox->setValue(item->GetDelay()); m_Ui->animationGroupBox->setEnabled(true); } else { m_Ui->animationGroupBox->setEnabled(false); } } void QmitkMovieMakerView::HideCurrentAnimationWidget() { if (m_Ui->animationWidgetGroupBox->isVisible()) { m_Ui->animationWidgetGroupBox->setVisible(false); int numWidgets = m_Ui->animationWidgetGroupBoxLayout->count(); for (int i = 0; i < numWidgets; ++i) m_Ui->animationWidgetGroupBoxLayout->itemAt(i)->widget()->setVisible(false); } } void QmitkMovieMakerView::ShowAnimationWidget(QmitkAnimationItem* animationItem) { this->HideCurrentAnimationWidget(); if (animationItem == nullptr) return; const QString widgetKey = animationItem->GetWidgetKey(); - QmitkAnimationWidget* animationWidget = nullptr; + auto animationWidgetIter = m_AnimationWidgets.find(widgetKey); + auto animationWidget = m_AnimationWidgets.end() != animationWidgetIter + ? animationWidgetIter->second + : nullptr; - if (m_AnimationWidgets.contains(widgetKey)) + if (nullptr != animationWidget) { - animationWidget = m_AnimationWidgets[widgetKey]; - - if (animationWidget != nullptr) - { - m_Ui->animationWidgetGroupBox->setTitle(widgetKey); - animationWidget->SetAnimationItem(animationItem); - animationWidget->setVisible(true); - } + m_Ui->animationWidgetGroupBox->setTitle(widgetKey); + animationWidget->SetAnimationItem(animationItem); + animationWidget->setVisible(true); } m_Ui->animationWidgetGroupBox->setVisible(animationWidget != nullptr); } void QmitkMovieMakerView::RedrawTimeline() { if (m_AnimationModel->rowCount() > 1) { m_Ui->animationTreeView->dataChanged( m_AnimationModel->index(0, 1), m_AnimationModel->index(m_AnimationModel->rowCount() - 1, 1)); } } QmitkAnimationItem* QmitkMovieMakerView::GetSelectedAnimationItem() const { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); return !selection.isEmpty() ? dynamic_cast(m_AnimationModel->item(selection[0].top(), 1)) : nullptr; } void QmitkMovieMakerView::CalculateTotalDuration() { const int rowCount = m_AnimationModel->rowCount(); double totalDuration = 0.0; double previousStart = 0.0; for (int i = 0; i < rowCount; ++i) { - QmitkAnimationItem* item = dynamic_cast(m_AnimationModel->item(i, 1)); + auto item = dynamic_cast(m_AnimationModel->item(i, 1)); - if (item == nullptr) + if (nullptr == item) continue; if (item->GetStartWithPrevious()) { totalDuration = std::max(totalDuration, previousStart + item->GetDelay() + item->GetDuration()); } else { previousStart = totalDuration; totalDuration += item->GetDelay() + item->GetDuration(); } } - m_TotalDuration = totalDuration; // TODO totalDuration == 0 - m_NumFrames = static_cast(totalDuration * m_Ui->fpsSpinBox->value()); // TODO numFrames < 2 + m_TotalDuration = totalDuration; + m_NumFrames = static_cast(totalDuration * m_Ui->fpsSpinBox->value()); } -QVector > QmitkMovieMakerView::GetActiveAnimations(double t) const +std::vector> QmitkMovieMakerView::GetActiveAnimations(double t) const { const int rowCount = m_AnimationModel->rowCount(); - QVector > activeAnimations; + std::vector> activeAnimations; double totalDuration = 0.0; double previousStart = 0.0; for (int i = 0; i < rowCount; ++i) { QmitkAnimationItem* item = dynamic_cast(m_AnimationModel->item(i, 1)); if (item == nullptr) continue; if (item->GetDuration() > 0.0) { double start = item->GetStartWithPrevious() ? previousStart + item->GetDelay() : totalDuration + item->GetDelay(); if (start <= t && t <= start + item->GetDuration()) - activeAnimations.push_back(qMakePair(item, (t - start) / item->GetDuration())); + activeAnimations.emplace_back(item, (t - start) / item->GetDuration()); } if (item->GetStartWithPrevious()) { totalDuration = std::max(totalDuration, previousStart + item->GetDelay() + item->GetDuration()); } else { previousStart = totalDuration; totalDuration += item->GetDelay() + item->GetDuration(); } } return activeAnimations; } diff --git a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.h b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.h index 92f30f13b5..85403bc857 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.h +++ b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.h @@ -1,96 +1,97 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkMovieMakerView_h #define QmitkMovieMakerView_h #include +#include +#include + class QmitkAnimationItem; class QmitkAnimationWidget; class QmitkFFmpegWriter; class QMenu; class QStandardItemModel; class QTimer; namespace Ui { class QmitkMovieMakerView; } class QmitkMovieMakerView : public QmitkAbstractView { Q_OBJECT public: static const std::string VIEW_ID; QmitkMovieMakerView(); ~QmitkMovieMakerView() override; void CreateQtPartControl(QWidget* parent) override; void SetFocus() override; private slots: void OnMoveAnimationUpButtonClicked(); void OnMoveAnimationDownButtonClicked(); void OnAddAnimationButtonClicked(); void OnRemoveAnimationButtonClicked(); void OnAnimationTreeViewRowsInserted(const QModelIndex& parent, int start, int end); void OnAnimationTreeViewRowsRemoved(const QModelIndex& parent, int start, int end); void OnAnimationTreeViewSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void OnStartComboBoxCurrentIndexChanged(int index); void OnDurationSpinBoxValueChanged(double value); void OnDelaySpinBoxValueChanged(double value); void OnPlayButtonToggled(bool checked); void OnStopButtonClicked(); void OnRecordButtonClicked(); void OnFPSSpinBoxValueChanged(int value); void OnTimerTimeout(); private: void InitializeAnimationWidgets(); void InitializeAnimationTreeViewWidgets(); void InitializeAnimationModel(); void InitializeAddAnimationMenu(); void InitializePlaybackAndRecordWidgets(); void InitializeRecordMenu(); void InitializeTimer(QWidget* parent); void ConnectAnimationTreeViewWidgets(); void ConnectAnimationWidgets(); void ConnectPlaybackAndRecordWidgets(); void ConnectTimer(); void RenderCurrentFrame(); void UpdateWidgets(); void UpdateAnimationWidgets(); void HideCurrentAnimationWidget(); void ShowAnimationWidget(QmitkAnimationItem* animationItem); void RedrawTimeline(); void CalculateTotalDuration(); QmitkAnimationItem* GetSelectedAnimationItem() const; - QVector > GetActiveAnimations(double t) const; - - QString GetFFmpegPath() const; + std::vector> GetActiveAnimations(double t) const; QmitkFFmpegWriter* m_FFmpegWriter; Ui::QmitkMovieMakerView* m_Ui; QStandardItemModel* m_AnimationModel; - QMap m_AnimationWidgets; + std::map m_AnimationWidgets; QMenu* m_AddAnimationMenu; QMenu* m_RecordMenu; QTimer* m_Timer; double m_TotalDuration; int m_NumFrames; int m_CurrentFrame; }; #endif diff --git a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkSliceAnimationWidget.cpp b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkSliceAnimationWidget.cpp index c8fba8bd0c..bca0c6de77 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkSliceAnimationWidget.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkSliceAnimationWidget.cpp @@ -1,127 +1,127 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSliceAnimationItem.h" #include "QmitkSliceAnimationWidget.h" #include #include -static int GetNumberOfSlices(int renderWindow) +namespace { - const QString renderWindowName = QString("stdmulti.widget%1").arg(renderWindow); - vtkRenderWindow* theRenderWindow = mitk::BaseRenderer::GetRenderWindowByName(renderWindowName.toStdString()); - - if (theRenderWindow != nullptr) + int GetNumberOfSlices(int renderWindow) { - mitk::Stepper* stepper = mitk::BaseRenderer::GetInstance(theRenderWindow)->GetSliceNavigationController()->GetSlice(); + const QString renderWindowName = QString("stdmulti.widget%1").arg(renderWindow); + vtkRenderWindow* theRenderWindow = mitk::BaseRenderer::GetRenderWindowByName(renderWindowName.toStdString()); - if (stepper != nullptr) - return std::max(1, static_cast(stepper->GetSteps())); - } + if (theRenderWindow != nullptr) + { + mitk::Stepper* stepper = mitk::BaseRenderer::GetInstance(theRenderWindow)->GetSliceNavigationController()->GetSlice(); - return 1; + if (stepper != nullptr) + return std::max(1, static_cast(stepper->GetSteps())); + } + + return 1; + } } QmitkSliceAnimationWidget::QmitkSliceAnimationWidget(QWidget* parent) : QmitkAnimationWidget(parent), - m_Ui(new Ui::QmitkSliceAnimationWidget) + m_Ui(new Ui::QmitkSliceAnimationWidget), + m_AnimationItem(nullptr) { m_Ui->setupUi(this); - this->connect(m_Ui->windowComboBox, SIGNAL(currentIndexChanged(int)), - this, SLOT(OnRenderWindowChanged(int))); - - this->connect(m_Ui->sliceRangeWidget, SIGNAL(minimumValueChanged(double)), - this, SLOT(OnFromChanged(double))); - - this->connect(m_Ui->sliceRangeWidget, SIGNAL(maximumValueChanged(double)), - this, SLOT(OnToChanged(double))); - - this->connect(m_Ui->reverseCheckBox, SIGNAL(clicked(bool)), - this, SLOT(OnReverseChanged(bool))); + connect(m_Ui->windowComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnRenderWindowChanged(int))); + connect(m_Ui->sliceRangeWidget, SIGNAL(minimumValueChanged(double)), this, SLOT(OnFromChanged(double))); + connect(m_Ui->sliceRangeWidget, SIGNAL(maximumValueChanged(double)), this, SLOT(OnToChanged(double))); + connect(m_Ui->reverseCheckBox, SIGNAL(clicked(bool)), this, SLOT(OnReverseChanged(bool))); } QmitkSliceAnimationWidget::~QmitkSliceAnimationWidget() { } void QmitkSliceAnimationWidget::SetAnimationItem(QmitkAnimationItem* sliceAnimationItem) { m_AnimationItem = dynamic_cast(sliceAnimationItem); - if (m_AnimationItem == nullptr) + if (nullptr == m_AnimationItem) return; m_Ui->windowComboBox->setCurrentIndex(m_AnimationItem->GetRenderWindow()); const int maximum = GetNumberOfSlices(m_AnimationItem->GetRenderWindow()) - 1; const int from = std::min(m_AnimationItem->GetFrom(), maximum); - const int to = std::min(m_AnimationItem->GetTo(), maximum); + int to = std::max(from, std::min(m_AnimationItem->GetTo(), maximum)); + + if (0 == to) + to = maximum; m_AnimationItem->SetFrom(from); m_AnimationItem->SetTo(to); m_Ui->sliceRangeWidget->setMaximum(maximum); m_Ui->sliceRangeWidget->setValues(from, to); m_Ui->reverseCheckBox->setChecked(m_AnimationItem->GetReverse()); } void QmitkSliceAnimationWidget::OnRenderWindowChanged(int renderWindow) { - if (m_AnimationItem == nullptr) + if (nullptr == m_AnimationItem) return; const int lastSlice = static_cast(GetNumberOfSlices(renderWindow) - 1); if (lastSlice < m_AnimationItem->GetFrom()) m_AnimationItem->SetFrom(lastSlice); if (lastSlice < m_AnimationItem->GetTo()) m_AnimationItem->SetTo(lastSlice); m_Ui->sliceRangeWidget->setMaximum(lastSlice); m_Ui->sliceRangeWidget->setValues(m_AnimationItem->GetFrom(), m_AnimationItem->GetTo()); if (m_AnimationItem->GetRenderWindow() != renderWindow) m_AnimationItem->SetRenderWindow(renderWindow); } void QmitkSliceAnimationWidget::OnFromChanged(double from) { - if (m_AnimationItem == nullptr) + if (nullptr == m_AnimationItem) return; int intFrom = static_cast(from); if (m_AnimationItem->GetFrom() != intFrom) m_AnimationItem->SetFrom(intFrom); } void QmitkSliceAnimationWidget::OnToChanged(double to) { - if (m_AnimationItem == nullptr) + if (nullptr == m_AnimationItem) return; int intTo = static_cast(to); if (m_AnimationItem->GetTo() != intTo) m_AnimationItem->SetTo(intTo); } void QmitkSliceAnimationWidget::OnReverseChanged(bool reverse) { - if (m_AnimationItem == nullptr) + if (nullptr == m_AnimationItem) return; if (m_AnimationItem->GetReverse() != reverse) m_AnimationItem->SetReverse(reverse); } diff --git a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkTimeSliceAnimationWidget.cpp b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkTimeSliceAnimationWidget.cpp index 2e45ee9b37..a5f2e52f7f 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkTimeSliceAnimationWidget.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkTimeSliceAnimationWidget.cpp @@ -1,98 +1,99 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkTimeSliceAnimationItem.h" #include "QmitkTimeSliceAnimationWidget.h" #include #include #include #include -static int GetNumberOfSlices() +namespace { - mitk::Stepper* stepper = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime(); + int GetNumberOfSlices() + { + mitk::Stepper* stepper = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime(); - if (stepper != nullptr) - return std::max(1, static_cast(stepper->GetSteps())); + if (stepper != nullptr) + return std::max(1, static_cast(stepper->GetSteps())); - return 1; + return 1; + } } QmitkTimeSliceAnimationWidget::QmitkTimeSliceAnimationWidget(QWidget* parent) : QmitkAnimationWidget(parent), m_Ui(new Ui::QmitkTimeSliceAnimationWidget) { m_Ui->setupUi(this); - this->connect(m_Ui->sliceRangeWidget, SIGNAL(minimumValueChanged(double)), - this, SLOT(OnFromChanged(double))); - - this->connect(m_Ui->sliceRangeWidget, SIGNAL(maximumValueChanged(double)), - this, SLOT(OnToChanged(double))); - - this->connect(m_Ui->reverseCheckBox, SIGNAL(clicked(bool)), - this, SLOT(OnReverseChanged(bool))); + connect(m_Ui->sliceRangeWidget, SIGNAL(minimumValueChanged(double)), this, SLOT(OnFromChanged(double))); + connect(m_Ui->sliceRangeWidget, SIGNAL(maximumValueChanged(double)), this, SLOT(OnToChanged(double))); + connect(m_Ui->reverseCheckBox, SIGNAL(clicked(bool)), this, SLOT(OnReverseChanged(bool))); } QmitkTimeSliceAnimationWidget::~QmitkTimeSliceAnimationWidget() { } void QmitkTimeSliceAnimationWidget::SetAnimationItem(QmitkAnimationItem* sliceAnimationItem) { m_AnimationItem = dynamic_cast(sliceAnimationItem); - if (m_AnimationItem == nullptr) + if (nullptr == m_AnimationItem) return; const int maximum = GetNumberOfSlices() - 1; const int from = std::min(m_AnimationItem->GetFrom(), maximum); - const int to = std::min(m_AnimationItem->GetTo(), maximum); + int to = std::max(from, std::min(m_AnimationItem->GetTo(), maximum)); + + if (0 == to) + to = maximum; m_AnimationItem->SetFrom(from); m_AnimationItem->SetTo(to); m_Ui->sliceRangeWidget->setMaximum(maximum); m_Ui->sliceRangeWidget->setValues(from, to); m_Ui->reverseCheckBox->setChecked(m_AnimationItem->GetReverse()); } void QmitkTimeSliceAnimationWidget::OnFromChanged(double from) { - if (m_AnimationItem == nullptr) + if (nullptr == m_AnimationItem) return; int intFrom = static_cast(from); if (m_AnimationItem->GetFrom() != intFrom) m_AnimationItem->SetFrom(intFrom); } void QmitkTimeSliceAnimationWidget::OnToChanged(double to) { - if (m_AnimationItem == nullptr) + if (nullptr == m_AnimationItem) return; int intTo = static_cast(to); if (m_AnimationItem->GetTo() != intTo) m_AnimationItem->SetTo(intTo); } void QmitkTimeSliceAnimationWidget::OnReverseChanged(bool reverse) { - if (m_AnimationItem == nullptr) + if (nullptr == m_AnimationItem) return; if (m_AnimationItem->GetReverse() != reverse) m_AnimationItem->SetReverse(reverse); }