diff --git a/Modules/Core/files.cmake b/Modules/Core/files.cmake index 96d0f01cc4..eb40ccde9d 100644 --- a/Modules/Core/files.cmake +++ b/Modules/Core/files.cmake @@ -1,327 +1,327 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkCoreActivator.cpp mitkCoreObjectFactoryBase.cpp mitkCoreObjectFactory.cpp mitkCoreServices.cpp mitkException.cpp Algorithms/mitkBaseDataSource.cpp Algorithms/mitkClippedSurfaceBoundsCalculator.cpp Algorithms/mitkCompareImageDataFilter.cpp Algorithms/mitkCompositePixelValueToString.cpp Algorithms/mitkConvert2Dto3DImageFilter.cpp Algorithms/mitkDataNodeSource.cpp Algorithms/mitkExtractSliceFilter.cpp Algorithms/mitkExtractSliceFilter2.cpp Algorithms/mitkHistogramGenerator.cpp Algorithms/mitkImageChannelSelector.cpp Algorithms/mitkImageSliceSelector.cpp Algorithms/mitkImageSource.cpp Algorithms/mitkImageTimeSelector.cpp Algorithms/mitkImageToImageFilter.cpp Algorithms/mitkImageToSurfaceFilter.cpp Algorithms/mitkMultiComponentImageDataComparisonFilter.cpp Algorithms/mitkPlaneGeometryDataToSurfaceFilter.cpp Algorithms/mitkPointSetSource.cpp Algorithms/mitkPointSetToPointSetFilter.cpp Algorithms/mitkRGBToRGBACastImageFilter.cpp Algorithms/mitkSubImageSelector.cpp Algorithms/mitkSurfaceSource.cpp Algorithms/mitkSurfaceToImageFilter.cpp Algorithms/mitkSurfaceToSurfaceFilter.cpp Algorithms/mitkUIDGenerator.cpp Algorithms/mitkVolumeCalculator.cpp Algorithms/mitkTemporalJoinImagesFilter.cpp Controllers/mitkBaseController.cpp Controllers/mitkCallbackFromGUIThread.cpp Controllers/mitkCameraController.cpp Controllers/mitkCameraRotationController.cpp Controllers/mitkCrosshairManager.cpp Controllers/mitkLimitedLinearUndo.cpp Controllers/mitkOperationEvent.cpp Controllers/mitkPlanePositionManager.cpp Controllers/mitkProgressBar.cpp Controllers/mitkRenderingManager.cpp Controllers/mitkSliceNavigationController.cpp Controllers/mitkSliceNavigationHelper.cpp Controllers/mitkStatusBar.cpp Controllers/mitkStepper.cpp Controllers/mitkTestManager.cpp Controllers/mitkUndoController.cpp Controllers/mitkVerboseLimitedLinearUndo.cpp Controllers/mitkVtkLayerController.cpp DataManagement/mitkAnatomicalStructureColorPresets.cpp DataManagement/mitkArbitraryTimeGeometry.cpp DataManagement/mitkAbstractTransformGeometry.cpp DataManagement/mitkAnnotationProperty.cpp DataManagement/mitkApplicationCursor.cpp DataManagement/mitkApplyTransformMatrixOperation.cpp DataManagement/mitkBaseData.cpp DataManagement/mitkBaseGeometry.cpp DataManagement/mitkBaseProperty.cpp DataManagement/mitkChannelDescriptor.cpp DataManagement/mitkClippingProperty.cpp DataManagement/mitkColorProperty.cpp + DataManagement/mitkCrosshairData.cpp DataManagement/mitkDataNode.cpp DataManagement/mitkDataStorage.cpp DataManagement/mitkEnumerationProperty.cpp DataManagement/mitkFloatPropertyExtension.cpp DataManagement/mitkGeometry3D.cpp DataManagement/mitkGeometryData.cpp DataManagement/mitkGeometryTransformHolder.cpp DataManagement/mitkGroupTagProperty.cpp DataManagement/mitkGenericIDRelationRule.cpp DataManagement/mitkIdentifiable.cpp DataManagement/mitkImageAccessorBase.cpp DataManagement/mitkImageCaster.cpp DataManagement/mitkImageCastPart1.cpp DataManagement/mitkImageCastPart2.cpp DataManagement/mitkImageCastPart3.cpp DataManagement/mitkImageCastPart4.cpp DataManagement/mitkImage.cpp DataManagement/mitkImageDataItem.cpp DataManagement/mitkImageDescriptor.cpp DataManagement/mitkImageReadAccessor.cpp DataManagement/mitkImageStatisticsHolder.cpp DataManagement/mitkImageVtkAccessor.cpp DataManagement/mitkImageVtkReadAccessor.cpp DataManagement/mitkImageVtkWriteAccessor.cpp DataManagement/mitkImageWriteAccessor.cpp DataManagement/mitkIntPropertyExtension.cpp DataManagement/mitkIPersistenceService.cpp DataManagement/mitkIPropertyAliases.cpp DataManagement/mitkIPropertyDescriptions.cpp DataManagement/mitkIPropertyExtensions.cpp DataManagement/mitkIPropertyFilters.cpp DataManagement/mitkIPropertyOwner.cpp DataManagement/mitkIPropertyPersistence.cpp DataManagement/mitkIPropertyProvider.cpp DataManagement/mitkLandmarkProjectorBasedCurvedGeometry.cpp DataManagement/mitkLandmarkProjector.cpp DataManagement/mitkLevelWindow.cpp DataManagement/mitkLevelWindowManager.cpp DataManagement/mitkLevelWindowPreset.cpp DataManagement/mitkLevelWindowProperty.cpp DataManagement/mitkLine.cpp DataManagement/mitkLookupTable.cpp DataManagement/mitkLookupTableProperty.cpp DataManagement/mitkLookupTables.cpp # specializations of GenericLookupTable DataManagement/mitkMaterial.cpp DataManagement/mitkMemoryUtilities.cpp DataManagement/mitkModalityProperty.cpp DataManagement/mitkModifiedLock.cpp DataManagement/mitkNodePredicateAnd.cpp DataManagement/mitkNodePredicateBase.cpp DataManagement/mitkNodePredicateCompositeBase.cpp DataManagement/mitkNodePredicateData.cpp DataManagement/mitkNodePredicateDataType.cpp DataManagement/mitkNodePredicateDataUID.cpp DataManagement/mitkNodePredicateDimension.cpp DataManagement/mitkNodePredicateFunction.cpp DataManagement/mitkNodePredicateGeometry.cpp DataManagement/mitkNodePredicateNot.cpp DataManagement/mitkNodePredicateOr.cpp DataManagement/mitkNodePredicateProperty.cpp DataManagement/mitkNodePredicateDataProperty.cpp DataManagement/mitkNodePredicateSubGeometry.cpp DataManagement/mitkNumericConstants.cpp DataManagement/mitkPlaneGeometry.cpp DataManagement/mitkPlaneGeometryData.cpp DataManagement/mitkPlaneOperation.cpp DataManagement/mitkPlaneOrientationProperty.cpp DataManagement/mitkPointOperation.cpp DataManagement/mitkPointSet.cpp DataManagement/mitkPointSetShapeProperty.cpp DataManagement/mitkProperties.cpp DataManagement/mitkPropertyAliases.cpp DataManagement/mitkPropertyDescriptions.cpp DataManagement/mitkPropertyExtension.cpp DataManagement/mitkPropertyExtensions.cpp DataManagement/mitkPropertyFilter.cpp DataManagement/mitkPropertyFilters.cpp DataManagement/mitkPropertyKeyPath.cpp DataManagement/mitkPropertyList.cpp DataManagement/mitkPropertyListReplacedObserver.cpp DataManagement/mitkPropertyNameHelper.cpp DataManagement/mitkPropertyObserver.cpp DataManagement/mitkPropertyPersistence.cpp DataManagement/mitkPropertyPersistenceInfo.cpp DataManagement/mitkPropertyRelationRuleBase.cpp DataManagement/mitkProportionalTimeGeometry.cpp DataManagement/mitkRenderingModeProperty.cpp DataManagement/mitkResliceMethodProperty.cpp DataManagement/mitkRestorePlanePositionOperation.cpp DataManagement/mitkRotationOperation.cpp DataManagement/mitkScaleOperation.cpp DataManagement/mitkSlicedData.cpp DataManagement/mitkSlicedGeometry3D.cpp DataManagement/mitkSmartPointerProperty.cpp DataManagement/mitkStandaloneDataStorage.cpp DataManagement/mitkStringProperty.cpp DataManagement/mitkSurface.cpp DataManagement/mitkSurfaceOperation.cpp DataManagement/mitkSourceImageRelationRule.cpp DataManagement/mitkThinPlateSplineCurvedGeometry.cpp DataManagement/mitkTimeGeometry.cpp DataManagement/mitkTransferFunction.cpp DataManagement/mitkTransferFunctionInitializer.cpp DataManagement/mitkTransferFunctionProperty.cpp DataManagement/mitkTemporoSpatialStringProperty.cpp DataManagement/mitkUIDManipulator.cpp DataManagement/mitkVector.cpp DataManagement/mitkVectorProperty.cpp DataManagement/mitkVtkInterpolationProperty.cpp DataManagement/mitkVtkRepresentationProperty.cpp DataManagement/mitkVtkResliceInterpolationProperty.cpp DataManagement/mitkVtkScalarModeProperty.cpp DataManagement/mitkWeakPointerProperty.cpp DataManagement/mitkIPropertyRelations.cpp DataManagement/mitkPropertyRelations.cpp Interactions/mitkAction.cpp Interactions/mitkBindDispatcherInteractor.cpp Interactions/mitkDataInteractor.cpp Interactions/mitkDispatcher.cpp Interactions/mitkDisplayActionEventBroadcast.cpp Interactions/mitkDisplayActionEventFunctions.cpp Interactions/mitkDisplayActionEventHandler.cpp Interactions/mitkDisplayActionEventHandlerDesynchronized.cpp Interactions/mitkDisplayActionEventHandlerStd.cpp Interactions/mitkDisplayActionEventHandlerSynchronized.cpp Interactions/mitkDisplayCoordinateOperation.cpp Interactions/mitkEventConfig.cpp Interactions/mitkEventFactory.cpp Interactions/mitkEventRecorder.cpp Interactions/mitkEventStateMachine.cpp Interactions/mitkInteractionEventConst.cpp Interactions/mitkInteractionEvent.cpp Interactions/mitkInteractionEventHandler.cpp Interactions/mitkInteractionEventObserver.cpp Interactions/mitkInteractionKeyEvent.cpp Interactions/mitkInteractionPositionEvent.cpp Interactions/mitkInteractionSchemeSwitcher.cpp Interactions/mitkInternalEvent.cpp Interactions/mitkMouseDoubleClickEvent.cpp Interactions/mitkMouseMoveEvent.cpp Interactions/mitkMousePressEvent.cpp Interactions/mitkMouseReleaseEvent.cpp Interactions/mitkMouseWheelEvent.cpp Interactions/mitkPointSetDataInteractor.cpp Interactions/mitkSinglePointDataInteractor.cpp Interactions/mitkStateMachineAction.cpp Interactions/mitkStateMachineCondition.cpp Interactions/mitkStateMachineContainer.cpp Interactions/mitkStateMachineState.cpp Interactions/mitkStateMachineTransition.cpp Interactions/mitkVtkEventAdapter.cpp Interactions/mitkVtkInteractorStyle.cxx Interactions/mitkXML2EventParser.cpp IO/mitkAbstractFileIO.cpp IO/mitkAbstractFileReader.cpp IO/mitkAbstractFileWriter.cpp IO/mitkCustomMimeType.cpp IO/mitkFileReader.cpp IO/mitkFileReaderRegistry.cpp IO/mitkFileReaderSelector.cpp IO/mitkFileReaderWriterBase.cpp IO/mitkFileWriter.cpp IO/mitkFileWriterRegistry.cpp IO/mitkFileWriterSelector.cpp IO/mitkGeometry3DToXML.cpp IO/mitkIFileIO.cpp IO/mitkIFileReader.cpp IO/mitkIFileWriter.cpp IO/mitkGeometryDataReaderService.cpp IO/mitkGeometryDataWriterService.cpp IO/mitkImageGenerator.cpp IO/mitkImageVtkLegacyIO.cpp IO/mitkImageVtkXmlIO.cpp IO/mitkIMimeTypeProvider.cpp IO/mitkIOConstants.cpp IO/mitkIOMimeTypes.cpp IO/mitkIOUtil.cpp IO/mitkItkImageIO.cpp IO/mitkItkLoggingAdapter.cpp IO/mitkLegacyFileReaderService.cpp IO/mitkLegacyFileWriterService.cpp IO/mitkLocaleSwitch.cpp IO/mitkLog.cpp IO/mitkMimeType.cpp IO/mitkMimeTypeProvider.cpp IO/mitkOperation.cpp IO/mitkPixelType.cpp IO/mitkPointSetReaderService.cpp IO/mitkPointSetWriterService.cpp IO/mitkProportionalTimeGeometryToXML.cpp IO/mitkRawImageFileReader.cpp IO/mitkStandardFileLocations.cpp IO/mitkSurfaceStlIO.cpp IO/mitkSurfaceVtkIO.cpp IO/mitkSurfaceVtkLegacyIO.cpp IO/mitkSurfaceVtkXmlIO.cpp IO/mitkUtf8Util.cpp IO/mitkVtkLoggingAdapter.cpp IO/mitkPreferenceListReaderOptionsFunctor.cpp IO/mitkIOMetaInformationPropertyConstants.cpp IO/mitkIPreferences.cpp IO/mitkPreferences.cpp IO/mitkIPreferencesService.cpp IO/mitkPreferencesService.cpp IO/mitkIPreferencesStorage.cpp IO/mitkXMLPreferencesStorage.cpp Rendering/mitkAbstractAnnotationRenderer.cpp Rendering/mitkAnnotationUtils.cpp Rendering/mitkBaseRenderer.cpp Rendering/mitkBaseRendererHelper.cpp - #Rendering/mitkGLMapper.cpp Moved to deprecated LegacyGL Module + Rendering/mitkCrosshairVtkMapper2D.cpp Rendering/mitkGradientBackground.cpp Rendering/mitkImageVtkMapper2D.cpp Rendering/mitkMapper.cpp Rendering/mitkAnnotation.cpp Rendering/mitkPlaneGeometryDataMapper2D.cpp Rendering/mitkPlaneGeometryDataVtkMapper3D.cpp Rendering/mitkPointSetVtkMapper2D.cpp Rendering/mitkPointSetVtkMapper3D.cpp Rendering/mitkRenderWindowBase.cpp Rendering/mitkRenderWindow.cpp Rendering/mitkRenderWindowFrame.cpp - #Rendering/mitkSurfaceGLMapper2D.cpp Moved to deprecated LegacyGL Module Rendering/mitkSurfaceVtkMapper2D.cpp Rendering/mitkSurfaceVtkMapper3D.cpp Rendering/mitkVtkEventProvider.cpp Rendering/mitkVtkMapper.cpp Rendering/mitkVtkPropRenderer.cpp Rendering/mitkVtkWidgetRendering.cpp Rendering/vtkMitkLevelWindowFilter.cpp Rendering/vtkMitkRectangleProp.cpp Rendering/vtkMitkRenderProp.cpp Rendering/vtkMitkThickSlicesFilter.cpp Rendering/vtkNeverTranslucentTexture.cpp ) set(RESOURCE_FILES Interactions/globalConfig.xml Interactions/DisplayInteraction.xml Interactions/DisplayConfigMITKBase.xml Interactions/DisplayConfigPACSBase.xml Interactions/DisplayConfigCrosshair.xml Interactions/DisplayConfigRotation.xml Interactions/DisplayConfigActivateCoupling.xml Interactions/DisplayConfigSwivel.xml Interactions/DisplayConfigPACSPan.xml Interactions/DisplayConfigPACSScroll.xml Interactions/DisplayConfigPACSZoom.xml Interactions/DisplayConfigPACSLevelWindow.xml Interactions/DisplayConfigBlockLMB.xml Interactions/PointSet.xml Interactions/PointSetConfig.xml mitkLevelWindowPresets.xml mitkAnatomicalStructureColorPresets.xml ) diff --git a/Modules/Core/include/mitkCrosshairData.h b/Modules/Core/include/mitkCrosshairData.h new file mode 100644 index 0000000000..c346164791 --- /dev/null +++ b/Modules/Core/include/mitkCrosshairData.h @@ -0,0 +1,46 @@ +/*============================================================================ + +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 mitkCrosshairData_h +#define mitkCrosshairData_h + +#include +#include + +namespace mitk +{ + class MITKCORE_EXPORT CrosshairData : public BaseData + { + public: + + mitkClassMacro(CrosshairData, BaseData); + itkFactorylessNewMacro(Self); + + itkGetConstMacro(Position, Point3D); + itkSetMacro(Position, Point3D); + + void SetRequestedRegionToLargestPossibleRegion() override; + bool RequestedRegionIsOutsideOfTheBufferedRegion() override; + bool VerifyRequestedRegion() override; + void SetRequestedRegion(const itk::DataObject* data) override; + + protected: + + CrosshairData(); + ~CrosshairData() override; + + Point3D m_Position; + + }; +} + +#endif diff --git a/Modules/Core/include/mitkCrosshairManager.h b/Modules/Core/include/mitkCrosshairManager.h index 575b9b0ab8..90713fc9fb 100644 --- a/Modules/Core/include/mitkCrosshairManager.h +++ b/Modules/Core/include/mitkCrosshairManager.h @@ -1,105 +1,147 @@ /*============================================================================ 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 mitkCrosshairManager_h #define mitkCrosshairManager_h #include #include +#include #include -#include -#include - -#include -#include namespace mitk { /** - * \brief The CrosshairManager takes care of the correct settings for the plane geometries - * that form the crosshair. + * \brief The CrosshairManager takes care of the correct settings for the crosshair. * - * The CrosshairManager keeps track of a TimeGeometry, which is used to compute - * the three different plane geometries (axial, coronal, sagittal). - * These three plane geometries can be added to / removed from the data storage - * using this CrosshairManager. Adding / removing them to / from the data storage + * The CrosshairManager keeps track of a crosshair data node, + * which is used to compute the lines of a crosshair. + * These crosshair data node can be added to / removed from the data storage + * using this CrosshairManager. Adding / removing the node to / from the data storage * will change the rendering behavior for the crosshair - only data nodes * inside the data storage will be rendered. - * - * */ class MITKCORE_EXPORT CrosshairManager : public itk::Object { public: mitkClassMacroItkParent(CrosshairManager, itk::Object); - mitkNewMacro2Param(Self, DataStorage*, BaseRenderer*); - - itkSetObjectMacro(DataStorage, DataStorage); - itkSetObjectMacro(BaseRenderer, BaseRenderer); - - /** - * \brief Set the input time geometry out of which the oriented geometries will be created. - */ - void ComputeOrientedTimeGeometries(const TimeGeometry* geometry); + mitkNewMacro1Param(Self, BaseRenderer*); + /** + * \brief Set the position of the managed crosshair to the given 3D position. + * + * \pre The crosshair data of this manager needs to be not null in order to + * set a new position. + * \throw mitk::Exception, if the crosshair data of this manager is null. + * + * \param selectedPoint The 3D point that will be newly set for the crosshair data. + */ void SetCrosshairPosition(const Point3D& selectedPoint); + /** + * \brief Update the position of the managed crosshair to the current slice + * position of the given slice navigation controller, depending on the view + * direction of the slice navigation controller. + * + * \pre The crosshair data of this manager needs to be not null in order to + * update the crosshair position. + * \throw mitk::Exception, if the crosshair data of this manager is null. + * + * \param sliceNavigationController The slice navigation controller whose current position + * will be used for updating the corresponding 3D coordinate + * of the crosshair data's position. + */ + void UpdateCrosshairPosition(const SliceNavigationController* sliceNavigationController); + /** + * \brief Get the position of the managed crosshair. + * + * \pre The crosshair data of this manager needs to be not null in order to + * get the current crosshair position. + * \throw mitk::Exception, if the crosshair data of this manager is null. + * + * \return The 3D point that represents the position of the crosshair data. + */ Point3D GetCrosshairPosition() const; - - void UpdateSlice(const SliceNavigationController* sliceNavigationController); - - void SetCrosshairVisibility(bool visible); - bool GetCrosshairVisibility() const; + /** + * \brief Set the visibility of the managed crosshair to the given value. + * + * \pre The crosshair data of this manager needs to be not null in order to + * set the visibility. + * \throw mitk::Exception, if the crosshair data of this manager is null. + * + * \param visible A boolean value that will define the visibility of the crosshair. + * \param baseRenderer A base renderer for which the renderer-specific visibility property + * should be set. + */ + void SetCrosshairVisibility(bool visible, const BaseRenderer* baseRenderer); + /** + * \brief Get the visibility of the managed crosshair. + * + * \pre The crosshair data of this manager needs to be not null in order to + * get the visibility. + * \throw mitk::Exception, if the crosshair data of this manager is null. + * + * \param baseRenderer A base renderer for which the renderer-specific visibility property + * should be get. + * \return The boolean value that represents the visibility of the crosshair. + */ + bool GetCrosshairVisibility(const BaseRenderer* baseRenderer) const; + /** + * \brief Set the gap size of the managed crosshair to the given value. + * + * \pre The crosshair data of this manager needs to be not null in order to + * set the gap size. + * \throw mitk::Exception, if the crosshair data of this manager is null. + * + * \param gapSize The size of the gap in the center of the crosshair. + */ void SetCrosshairGap(unsigned int gapSize); - - void AddPlanesToDataStorage(); - void RemovePlanesFromDataStorage(); + /** + * \brief Add the managed crosshair data node to the given data storage. + * + * \pre The crosshair data node of this manager needs to be not null in order to + * add the node to the data storage. + * \throw mitk::Exception, if the crosshair data node of this manager is null. + * \pre The given data storage needs to be not null in order to add the node to + * the data storage. + * \throw mitk::Exception, if the given data storage is null. + * + * \param dataStorage The data storage to which the crosshair data node should be added. + */ + void AddCrosshairNodeToDataStorage(DataStorage* dataStorage); + /** + * \brief Remove the managed crosshair data node from the given data storage. + * + * \pre The crosshair data node of this manager needs to be not null in order to + * remove the node from the data storage. + * \throw mitk::Exception, if the crosshair data node of this manager is null. + * \pre The given data storage needs to be not null in order to remove the node + * from the data storage. + * \throw mitk::Exception, if the given data storage is null. + * + * \param dataStorage The data storage from which the crosshair data node should be removed. + */ + void RemoveCrosshairNodeFromDataStorage(DataStorage* dataStorage); protected: - CrosshairManager(DataStorage* dataStorage, BaseRenderer* baseRenderer); + CrosshairManager(BaseRenderer* baseRenderer); ~CrosshairManager(); - void InitializePlaneProperties(DataNode::Pointer planeNode, const std::string& planeName); - void InitializePlaneData(DataNode::Pointer planeNode, const TimeGeometry* timeGeometry, unsigned int& slice); - void UpdatePlaneSlice(DataNode::Pointer planeNode, const TimeGeometry* timeGeometry, unsigned int slice); - void SetCrosshairPosition(const Point3D& selectedPoint, - DataNode::Pointer planeNode, - const TimeGeometry* timeGeometry, - unsigned int& slice); - void AddPlaneToDataStorage(DataNode::Pointer planeNode, DataNode::Pointer parent); - - DataStorage* m_DataStorage; - BaseRenderer* m_BaseRenderer; - - TimeGeometry::ConstPointer m_InputTimeGeometry; - TimeGeometry::Pointer m_AxialTimeGeometry; - TimeGeometry::Pointer m_CoronalTimeGeometry; - TimeGeometry::Pointer m_SagittalTimeGeometry; - - unsigned int m_AxialSlice; - unsigned int m_CoronalSlice; - unsigned int m_SagittalSlice; - - /** - * @brief The 3 helper objects which contain the plane geometry. - */ - DataNode::Pointer m_AxialPlaneNode; - DataNode::Pointer m_CoronalPlaneNode; - DataNode::Pointer m_SagittalPlaneNode; - DataNode::Pointer m_ParentNodeForGeometryPlanes; + DataNode::Pointer m_CrosshairDataNode; + CrosshairData::Pointer m_CrosshairData; }; -} // namespace mitk +} #endif diff --git a/Modules/Core/include/mitkCrosshairVtkMapper2D.h b/Modules/Core/include/mitkCrosshairVtkMapper2D.h new file mode 100644 index 0000000000..301c4114f9 --- /dev/null +++ b/Modules/Core/include/mitkCrosshairVtkMapper2D.h @@ -0,0 +1,93 @@ +/*============================================================================ + +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 mitkCrosshairVtkMapper2D_h +#define mitkCrosshairVtkMapper2D_h + +#include + +#include +#include +#include + +#include + +class vtkActor2D; +class vtkCellArray; +class vtkPropAssembly; +class vtkPolyDataMapper2D; + +namespace mitk +{ + /** + * @brief Vtk-based 2D mapper for rendering a crosshair using vtk mapper. + * + * CrosshairData is used to retrieve the selected position, which is stored + * in the processed data node. + * The crosshair is created by drawing lines through the selected position, + * adding an additional gap in the center of the crosshair and creating vtk poly + * data from the created points and lines. + */ + class MITKCORE_EXPORT CrosshairVtkMapper2D : public VtkMapper + { + public: + + mitkClassMacro(CrosshairVtkMapper2D, VtkMapper); + itkFactorylessNewMacro(Self); + + const CrosshairData* GetInput() const; + + /** \brief Checks whether this mapper needs to update itself and generate data. */ + void Update(mitk::BaseRenderer* renderer) override; + + vtkProp* GetVtkProp(BaseRenderer* renderer) override; + + static void SetDefaultProperties(DataNode* node, BaseRenderer* renderer = nullptr, bool overwrite = false); + + protected: + + CrosshairVtkMapper2D(); + ~CrosshairVtkMapper2D() override; + + /** \brief Internal class holding the mapper, actor, etc. for each of the 2D render windows */ + class LocalStorage : public Mapper::BaseLocalStorage + { + public: + + LocalStorage(); + ~LocalStorage() override; + + // actor + vtkSmartPointer m_CrosshairActor; + vtkSmartPointer m_Mapper; + vtkSmartPointer m_CrosshairAssembly; + }; + + /** \brief The LocalStorageHandler holds all LocalStorages for the 2D render windows. */ + LocalStorageHandler m_LSH; + + /* \brief Applies the color and opacity properties and calls CreateVTKRenderObjects */ + void GenerateDataForRenderer(BaseRenderer *renderer) override; + + void CreateVtkCrosshair(BaseRenderer *renderer); + + void DrawLine(Point3D p0, Point3D p1, vtkCellArray* lines, vtkPoints* points); + + void ApplyAllProperties(BaseRenderer* renderer); + void ApplyColorAndOpacityProperties2D(BaseRenderer* renderer, vtkActor2D* actor); + + const int defaultGapSize; + }; + +} + +#endif diff --git a/Modules/Core/include/mitkPlaneGeometryDataMapper2D.h b/Modules/Core/include/mitkPlaneGeometryDataMapper2D.h index 35811f0e84..2747ffd558 100644 --- a/Modules/Core/include/mitkPlaneGeometryDataMapper2D.h +++ b/Modules/Core/include/mitkPlaneGeometryDataMapper2D.h @@ -1,144 +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. ============================================================================*/ #ifndef mitkPlaneGeometryDataMapper2D_h #define mitkPlaneGeometryDataMapper2D_h #include "mitkBaseRenderer.h" #include "mitkVtkMapper.h" #include #include class vtkActor2D; class vtkPropAssembly; -class vtkFloatArray; class vtkCellArray; class vtkPolyDataMapper2D; namespace mitk { /** * @brief Vtk-based 2D mapper for rendering a crosshair with the plane geometry. * * This mapper uses the mitkPlaneGeometryData from the three helper objects in * the StdMultiWidget to render a crosshair in all 2D render windows. The crosshair * is assembled as lines and rendered with a vtkPolyDataMapper. The mapper * requires multiple plane geometry to compute the correct crosshair position. * The plane bounds are computed using either ReferenceGeometry if it is present or * the plane geometry itself otherwise. * The mapper offers the following properties: * \b Crosshair.Line width: The thickness of the crosshair. * \b Crosshair.Gap Size: The gap between the lines in pixels. * \b Crosshair.Orientation Decoration: Adds a PlaneOrientationProperty, which * indicates the direction of the plane normal. See mitkPlaneOrientationProperty. * * @ingroup Mapper */ class MITKCORE_EXPORT PlaneGeometryDataMapper2D : public VtkMapper { public: mitkClassMacro(PlaneGeometryDataMapper2D, VtkMapper); itkFactorylessNewMacro(Self); itkCloneMacro(Self); virtual const mitk::PlaneGeometryData *GetInput() const; /** \brief returns the a prop assembly */ vtkProp *GetVtkProp(mitk::BaseRenderer *renderer) override; /** Applies properties specific to this mapper */ virtual void ApplyAllProperties(BaseRenderer *renderer); - void UpdateVtkTransform(mitk::BaseRenderer *renderer) override; - /** \brief set the default properties for this mapper */ static void SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer = nullptr, bool overwrite = false); /** \brief Internal class holding the mapper, actor, etc. for each of the 3 2D render windows */ class LocalStorage : public mitk::Mapper::BaseLocalStorage { public: /* constructor */ LocalStorage(); /* destructor */ ~LocalStorage() override; // actor vtkSmartPointer m_CrosshairActor; vtkSmartPointer m_CrosshairHelperLineActor; vtkSmartPointer m_ArrowActor; vtkSmartPointer m_HelperLinesmapper; vtkSmartPointer m_Arrowmapper; vtkSmartPointer m_Mapper; vtkSmartPointer m_CrosshairAssembly; }; /** \brief The LocalStorageHandler holds all (three) LocalStorages for the three 2D render windows. */ mitk::LocalStorageHandler m_LSH; protected: /* constructor */ PlaneGeometryDataMapper2D(); /* destructor */ ~PlaneGeometryDataMapper2D() override; /* \brief Applies the color and opacity properties and calls CreateVTKRenderObjects */ void GenerateDataForRenderer(mitk::BaseRenderer *renderer) override; void CreateVtkCrosshair(BaseRenderer *renderer); static bool TestPointInPlaneGeometry(const PlaneGeometry *planeGeometry, const Point3D &point); static bool TestPointInReferenceGeometry(const BaseGeometry *referenceGeometry, const Point3D &point); static bool CutCrossLineWithPlaneGeometry(const PlaneGeometry *planeGeometry, Line3D &crossLine); static bool CutCrossLineWithReferenceGeometry(const BaseGeometry *referenceGeometry, Line3D &crossLine); - /** - * \brief Returns the thick slice mode for the given datanode. - * - * This method returns the value of the 'reslice.thickslices' property for - * the given datanode. - * '0': thick slice mode disabled - * '1': thick slice mode enabled - * - * The variable 'thickSlicesNum' contains the value of the 'reslice.thickslices.num' - * property that defines how many slices are shown at once. - */ - int DetermineThickSliceMode(DataNode *dn, int &thickSlicesNum); - void DrawLine(Point3D p0, Point3D p1, vtkCellArray *lines, vtkPoints *points); // member variables holding the current value of the properties used in this mapper typedef std::vector NodesVectorType; NodesVectorType m_OtherPlaneGeometries; typedef std::set AllInstancesContainer; static AllInstancesContainer s_AllInstances; bool m_RenderOrientationArrows; bool m_ArrowOrientationPositive; - mitk::ScalarType m_DepthValue; void ApplyColorAndOpacityProperties2D(BaseRenderer *renderer, vtkActor2D *actor); void DrawOrientationArrow(vtkSmartPointer triangles, vtkSmartPointer triPoints, double triangleSizeMM, Vector3D &orthogonalVector, Point3D &point1, Point3D &point2); }; } // namespace mitk #endif diff --git a/Modules/Core/src/Controllers/mitkCrosshairManager.cpp b/Modules/Core/src/Controllers/mitkCrosshairManager.cpp index 8bd39e7449..2a5d497f4c 100644 --- a/Modules/Core/src/Controllers/mitkCrosshairManager.cpp +++ b/Modules/Core/src/Controllers/mitkCrosshairManager.cpp @@ -1,414 +1,167 @@ /*============================================================================ 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 "mitkCrosshairManager.h" -#include -#include -#include +#include #include -#include -#include - -mitk::CrosshairManager::CrosshairManager(DataStorage* dataStorage, BaseRenderer* baseRenderer) - : m_DataStorage(dataStorage) - , m_BaseRenderer(baseRenderer) - , m_InputTimeGeometry() - , m_AxialTimeGeometry() - , m_CoronalTimeGeometry() - , m_SagittalTimeGeometry() -{ - m_AxialPlaneNode = mitk::DataNode::New(); - m_CoronalPlaneNode = mitk::DataNode::New(); - m_SagittalPlaneNode = mitk::DataNode::New(); - - std::string rendererName = std::string(m_BaseRenderer->GetName()); - this->InitializePlaneProperties(m_AxialPlaneNode, std::string(rendererName + "axial.plane")); - this->InitializePlaneProperties(m_CoronalPlaneNode, std::string(rendererName + "coronal.plane")); - this->InitializePlaneProperties(m_SagittalPlaneNode, std::string(rendererName + "sagittal.plane")); - - m_ParentNodeForGeometryPlanes = mitk::DataNode::New(); - m_ParentNodeForGeometryPlanes->SetProperty("name", mitk::StringProperty::New(rendererName)); - m_ParentNodeForGeometryPlanes->SetProperty("helper object", mitk::BoolProperty::New(true)); -} -mitk::CrosshairManager::~CrosshairManager() -{ - this->RemovePlanesFromDataStorage(); -} +#include -void mitk::CrosshairManager::ComputeOrientedTimeGeometries(const TimeGeometry* geometry) +mitk::CrosshairManager::CrosshairManager(BaseRenderer* baseRenderer) { - if (nullptr != geometry) - { - if (geometry->GetBoundingBoxInWorld()->GetDiagonalLength2() < eps) - { - itkWarningMacro("setting an empty bounding-box"); - geometry = nullptr; - } - } - - if (m_InputTimeGeometry == geometry) - { - return; - } - - m_InputTimeGeometry = geometry; + m_CrosshairDataNode = mitk::DataNode::New(); - if (m_InputTimeGeometry.IsNull()) - { - return; - } + std::string rendererName = std::string(baseRenderer->GetName()); - if (0 == m_InputTimeGeometry->CountTimeSteps()) - { - return; - } + m_CrosshairDataNode->SetProperty("reslice.thickslices", mitk::ResliceMethodProperty::New()); + m_CrosshairDataNode->SetProperty("reslice.thickslices.num", mitk::IntProperty::New(5)); + m_CrosshairDataNode->SetProperty("Crosshair.Gap Size", mitk::IntProperty::New(32)); - try - { - m_AxialTimeGeometry = SliceNavigationHelper::CreateOrientedTimeGeometry( - m_InputTimeGeometry, AnatomicalPlane::Axial, false, false, true); - m_CoronalTimeGeometry = SliceNavigationHelper::CreateOrientedTimeGeometry( - m_InputTimeGeometry, AnatomicalPlane::Coronal, false, true, false); - m_SagittalTimeGeometry = SliceNavigationHelper::CreateOrientedTimeGeometry( - m_InputTimeGeometry, AnatomicalPlane::Sagittal, true, true, false); - } - catch (const mitk::Exception& e) - { - MITK_ERROR << "Unable to create oriented time geometries\n" - << "Reason: " << e.GetDescription(); - } + // set the crosshair only visible for this specific renderer + m_CrosshairDataNode->SetVisibility(false); + m_CrosshairDataNode->SetVisibility(true, baseRenderer); + m_CrosshairDataNode->SetProperty("name", mitk::StringProperty::New(std::string(rendererName + "crosshairData"))); + m_CrosshairDataNode->SetProperty("helper object", mitk::BoolProperty::New(true)); - this->InitializePlaneData(m_AxialPlaneNode, m_AxialTimeGeometry, m_AxialSlice); - this->InitializePlaneData(m_CoronalPlaneNode, m_CoronalTimeGeometry, m_CoronalSlice); - this->InitializePlaneData(m_SagittalPlaneNode, m_SagittalTimeGeometry, m_SagittalSlice); + m_CrosshairData = CrosshairData::New(); + m_CrosshairDataNode->SetData(m_CrosshairData); + m_CrosshairDataNode->SetMapper(mitk::BaseRenderer::Standard2D, mitk::CrosshairVtkMapper2D::New()); +} - RenderingManager::GetInstance()->RequestUpdate(m_BaseRenderer->GetRenderWindow()); +mitk::CrosshairManager::~CrosshairManager() +{ } void mitk::CrosshairManager::SetCrosshairPosition(const Point3D& selectedPoint) { - if (m_AxialPlaneNode.IsNull() || m_CoronalPlaneNode.IsNull() || m_SagittalPlaneNode.IsNull()) + if (m_CrosshairData.IsNull()) { - return; + mitkThrow() << "No crosshair data available. Crosshair is in an invalid state."; } - if (m_AxialTimeGeometry.IsNull() || m_CoronalTimeGeometry.IsNull() || m_SagittalTimeGeometry.IsNull()) - { - return; - } - - this->SetCrosshairPosition(selectedPoint, m_AxialPlaneNode, m_AxialTimeGeometry, m_AxialSlice); - this->SetCrosshairPosition(selectedPoint, m_CoronalPlaneNode, m_CoronalTimeGeometry, m_CoronalSlice); - this->SetCrosshairPosition(selectedPoint, m_SagittalPlaneNode, m_SagittalTimeGeometry, m_SagittalSlice); + m_CrosshairData->SetPosition(selectedPoint); } -mitk::Point3D mitk::CrosshairManager::GetCrosshairPosition() const +void mitk::CrosshairManager::UpdateCrosshairPosition(const SliceNavigationController* sliceNavigationController) { - if (m_InputTimeGeometry.IsNull()) + if (m_CrosshairData.IsNull()) { - // return null-point since we don't show the crosshair anyway - return Point3D(0.0); + mitkThrow() << "No crosshair data available. Crosshair is in an invalid state."; } - Point3D point = m_InputTimeGeometry->GetCenterInWorld(); - - PlaneGeometry* axialPlaneGeometry = nullptr; - PlaneGeometry* coronalPlaneGeometry = nullptr; - PlaneGeometry* sagittalPlaneGeometry = nullptr; + Point3D crosshairPosition = m_CrosshairData->GetPosition(); - // get the currently selected time point - auto selectedTimePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - try - { - axialPlaneGeometry = - SliceNavigationHelper::GetCurrentPlaneGeometry(m_AxialTimeGeometry, selectedTimePoint, m_AxialSlice); - coronalPlaneGeometry = - SliceNavigationHelper::GetCurrentPlaneGeometry(m_CoronalTimeGeometry, selectedTimePoint, m_CoronalSlice); - sagittalPlaneGeometry = - SliceNavigationHelper::GetCurrentPlaneGeometry(m_SagittalTimeGeometry, selectedTimePoint, m_SagittalSlice); - } - catch (const mitk::Exception& e) - { - MITK_ERROR << "Unable to get plane geometries. Using default center point.\n" - << "Reason: " << e.GetDescription(); - } - - Line3D line; - if (nullptr != axialPlaneGeometry && nullptr != coronalPlaneGeometry && - axialPlaneGeometry->IntersectionLine(coronalPlaneGeometry, line)) - { - if (nullptr != sagittalPlaneGeometry && sagittalPlaneGeometry->IntersectionPoint(line, point)) - { - return point; - } - } - - // return input geometry center point if no intersection point was found - return point; -} - -void mitk::CrosshairManager::UpdateSlice(const SliceNavigationController* sliceNavigationController) -{ auto viewDirection = sliceNavigationController->GetViewDirection(); unsigned int slicePosition = sliceNavigationController->GetSlice()->GetPos(); switch (viewDirection) { case AnatomicalPlane::Original: return; case AnatomicalPlane::Axial: { - m_AxialSlice = slicePosition; - this->UpdatePlaneSlice(m_AxialPlaneNode, m_AxialTimeGeometry, m_AxialSlice); + crosshairPosition[2] = slicePosition; break; } case AnatomicalPlane::Coronal: { - m_CoronalSlice = slicePosition; - this->UpdatePlaneSlice(m_CoronalPlaneNode, m_CoronalTimeGeometry, m_CoronalSlice); + crosshairPosition[1] = slicePosition; break; } case AnatomicalPlane::Sagittal: { - m_SagittalSlice = slicePosition; - this->UpdatePlaneSlice(m_SagittalPlaneNode, m_SagittalTimeGeometry, m_SagittalSlice); + crosshairPosition[0] = slicePosition; break; } } -} -void mitk::CrosshairManager::SetCrosshairVisibility(bool visible) -{ - if (m_AxialPlaneNode.IsNull() || m_CoronalPlaneNode.IsNull() || m_SagittalPlaneNode.IsNull()) - { - return; - } - - m_AxialPlaneNode->SetVisibility(visible, m_BaseRenderer); - m_CoronalPlaneNode->SetVisibility(visible, m_BaseRenderer); - m_SagittalPlaneNode->SetVisibility(visible, m_BaseRenderer); + m_CrosshairData->SetPosition(crosshairPosition); } -bool mitk::CrosshairManager::GetCrosshairVisibility() const +mitk::Point3D mitk::CrosshairManager::GetCrosshairPosition() const { - if (m_AxialPlaneNode.IsNull() || m_CoronalPlaneNode.IsNull() || m_SagittalPlaneNode.IsNull()) - { - return false; - } - - bool axialVisibility = false; - if (m_AxialPlaneNode->GetVisibility(axialVisibility, m_BaseRenderer)) + if (m_CrosshairData.IsNull()) { - bool coronalVisibility = false; - if (m_CoronalPlaneNode->GetVisibility(coronalVisibility, m_BaseRenderer)) - { - bool sagittalVisibility = false; - if (m_SagittalPlaneNode->GetVisibility(sagittalVisibility, m_BaseRenderer)) - { - if (axialVisibility == coronalVisibility && coronalVisibility == sagittalVisibility) - { - return axialVisibility; - } - } - } + mitkThrow() << "No crosshair data available. Crosshair is in an invalid state."; } - mitkThrow() << "Invalid state of plane visibility."; -} - -void mitk::CrosshairManager::SetCrosshairGap(unsigned int gapSize) -{ - m_AxialPlaneNode->SetIntProperty("Crosshair.Gap Size", gapSize); - m_CoronalPlaneNode->SetIntProperty("Crosshair.Gap Size", gapSize); - m_SagittalPlaneNode->SetIntProperty("Crosshair.Gap Size", gapSize); + return m_CrosshairData->GetPosition(); } -void mitk::CrosshairManager::AddPlanesToDataStorage() +void mitk::CrosshairManager::SetCrosshairVisibility(bool visible, const BaseRenderer* baseRenderer) { - if (nullptr == m_DataStorage) + if (m_CrosshairDataNode.IsNull()) { - return; + mitkThrow() << "No crosshair data node available. Crosshair is in an invalid state."; } - if (m_AxialPlaneNode.IsNull() || m_CoronalPlaneNode.IsNull() - || m_SagittalPlaneNode.IsNull() || m_ParentNodeForGeometryPlanes.IsNull()) - { - return; - } - - this->AddPlaneToDataStorage(m_ParentNodeForGeometryPlanes, nullptr); - this->AddPlaneToDataStorage(m_AxialPlaneNode, m_ParentNodeForGeometryPlanes); - this->AddPlaneToDataStorage(m_CoronalPlaneNode, m_ParentNodeForGeometryPlanes); - this->AddPlaneToDataStorage(m_SagittalPlaneNode, m_ParentNodeForGeometryPlanes); + m_CrosshairDataNode->SetVisibility(visible, baseRenderer); } -void mitk::CrosshairManager::RemovePlanesFromDataStorage() +bool mitk::CrosshairManager::GetCrosshairVisibility(const BaseRenderer* baseRenderer) const { - if (nullptr == m_DataStorage) + if (m_CrosshairDataNode.IsNull()) { - return; + mitkThrow() << "No crosshair data node available. Crosshair is in an invalid state."; } - if (m_AxialPlaneNode.IsNotNull() && m_CoronalPlaneNode.IsNotNull() - && m_SagittalPlaneNode.IsNotNull() && m_ParentNodeForGeometryPlanes.IsNotNull()) + bool visibility = false; + if (m_CrosshairDataNode->GetVisibility(visibility, baseRenderer)) { - m_DataStorage->Remove(m_AxialPlaneNode); - m_DataStorage->Remove(m_CoronalPlaneNode); - m_DataStorage->Remove(m_SagittalPlaneNode); - m_DataStorage->Remove(m_ParentNodeForGeometryPlanes); + return false; } -} - -void mitk::CrosshairManager::InitializePlaneProperties(DataNode::Pointer planeNode, const std::string& planeName) -{ - planeNode->SetProperty("reslice.thickslices", mitk::ResliceMethodProperty::New()); - planeNode->SetProperty("reslice.thickslices.num", mitk::IntProperty::New(5)); - planeNode->SetProperty("Crosshair.Gap Size", mitk::IntProperty::New(32)); - // set the crosshair only visible for this specific renderer - planeNode->SetVisibility(false); - planeNode->SetVisibility(true, m_BaseRenderer); - planeNode->SetProperty("name", mitk::StringProperty::New(planeName)); - planeNode->SetProperty("helper object", mitk::BoolProperty::New(true)); + return visibility; } -void mitk::CrosshairManager::InitializePlaneData(DataNode::Pointer planeNode, const TimeGeometry* timeGeometry, unsigned int& slice) +void mitk::CrosshairManager::SetCrosshairGap(unsigned int gapSize) { - if (planeNode.IsNull()) + if (m_CrosshairDataNode.IsNull()) { - return; + mitkThrow() << "No crosshair data node available. Crosshair is in an invalid state."; } - if (nullptr == timeGeometry) - { - return; - } - - // get the BaseGeometry of the selected time point - auto selectedTimePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto currentGeometry = timeGeometry->GetGeometryForTimePoint(selectedTimePoint); - if (nullptr == currentGeometry) - { - // time point not valid for the time geometry - mitkThrow() << "Cannot extract a base geometry. A time point is selected that is not covered by" - << "the given time geometry. Selected time point: " << selectedTimePoint; - } - - const auto* slicedGeometry = dynamic_cast(currentGeometry.GetPointer()); - if (nullptr == slicedGeometry) - { - return; - } - - slice = slicedGeometry->GetSlices() / 2; // center slice - PlaneGeometryData::Pointer planeData = PlaneGeometryData::New(); - planeData->SetPlaneGeometry(slicedGeometry->GetPlaneGeometry(slice)); - planeNode->SetData(planeData); - planeNode->SetMapper(mitk::BaseRenderer::Standard2D, mitk::PlaneGeometryDataMapper2D::New()); + m_CrosshairDataNode->SetIntProperty("Crosshair.Gap Size", gapSize); } -void mitk::CrosshairManager::UpdatePlaneSlice(DataNode::Pointer planeNode, const TimeGeometry* timeGeometry, unsigned int slice) +void mitk::CrosshairManager::AddCrosshairNodeToDataStorage(DataStorage* dataStorage) { - mitk::PlaneGeometry* planeGeometry = nullptr; - // get the currently selected time point - auto selectedTimePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - try + if (m_CrosshairDataNode.IsNull()) { - planeGeometry = SliceNavigationHelper::GetCurrentPlaneGeometry(timeGeometry, selectedTimePoint, slice); - } - catch (const mitk::Exception& e) - { - MITK_ERROR << "Unable to get plane geometries\n" - << "Reason: " << e.GetDescription(); + mitkThrow() << "No crosshair data node available. Crosshair is in an invalid state."; } - if (nullptr == planeGeometry) + if (nullptr == dataStorage) { - return; + mitkThrow() << "Datastorage is invalid. Cannot add crosshair node."; } - PlaneGeometryData::Pointer planeData = dynamic_cast(planeNode->GetData()); - if (nullptr == planeData) + if (!dataStorage->Exists(m_CrosshairDataNode)) { - return; + dataStorage->Add(m_CrosshairDataNode); } - - planeData->SetPlaneGeometry(planeGeometry); - planeNode->SetData(planeData); } -void mitk::CrosshairManager::SetCrosshairPosition(const Point3D& selectedPoint, - DataNode::Pointer planeNode, - const TimeGeometry* timeGeometry, - unsigned int& slice) +void mitk::CrosshairManager::RemoveCrosshairNodeFromDataStorage(DataStorage* dataStorage) { - int selectedSlice = -1; - try - { - selectedSlice = SliceNavigationHelper::SelectSliceByPoint(timeGeometry, selectedPoint); - } - catch (const mitk::Exception& e) + if (m_CrosshairDataNode.IsNull()) { - MITK_ERROR << "Unable to select a slice by the given point " << selectedPoint << "\n" - << "Reason: " << e.GetDescription(); + mitkThrow() << "No crosshair data node available. Crosshair is in an invalid state."; } - if (-1 == selectedSlice) + if (nullptr == dataStorage) { - return; + mitkThrow() << "Datastorage is invalid. Cannot remove crosshair node."; } - slice = selectedSlice; - mitk::PlaneGeometry* planeGeometry = nullptr; - // get the currently selected time point - auto selectedTimePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - try - { - planeGeometry = SliceNavigationHelper::GetCurrentPlaneGeometry(timeGeometry, selectedTimePoint, slice); - } - catch (const mitk::Exception& e) - { - MITK_ERROR << "Unable to get plane geometries\n" - << "Reason: " << e.GetDescription(); - } - - if (nullptr == planeGeometry) - { - return; - } - - PlaneGeometryData::Pointer planeData = dynamic_cast(planeNode->GetData()); - if (nullptr == planeData) - { - return; - } - - planeData->SetPlaneGeometry(planeGeometry); - planeNode->SetData(planeData); -} - -void mitk::CrosshairManager::AddPlaneToDataStorage(DataNode::Pointer planeNode, DataNode::Pointer parent) -{ - if (!m_DataStorage->Exists(planeNode) - && (nullptr == parent || m_DataStorage->Exists(parent))) - { - try - { - m_DataStorage->Add(planeNode, parent); - } - catch (std::invalid_argument& /*e*/) - { - return; - } - } + dataStorage->Remove(m_CrosshairDataNode); } diff --git a/Modules/Core/src/DataManagement/mitkCrosshairData.cpp b/Modules/Core/src/DataManagement/mitkCrosshairData.cpp new file mode 100644 index 0000000000..59bee7d17f --- /dev/null +++ b/Modules/Core/src/DataManagement/mitkCrosshairData.cpp @@ -0,0 +1,39 @@ +/*============================================================================ + +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 + +mitk::CrosshairData::CrosshairData() +{ +} + +mitk::CrosshairData::~CrosshairData() +{ +} + +void mitk::CrosshairData::SetRequestedRegionToLargestPossibleRegion() +{ +} + +bool mitk::CrosshairData::RequestedRegionIsOutsideOfTheBufferedRegion() +{ + return false; +} + +bool mitk::CrosshairData::VerifyRequestedRegion() +{ + return true; +} + +void mitk::CrosshairData::SetRequestedRegion(const itk::DataObject*) +{ +} diff --git a/Modules/Core/src/Rendering/mitkBaseRenderer.cpp b/Modules/Core/src/Rendering/mitkBaseRenderer.cpp index e4777c3b35..d0685e131f 100644 --- a/Modules/Core/src/Rendering/mitkBaseRenderer.cpp +++ b/Modules/Core/src/Rendering/mitkBaseRenderer.cpp @@ -1,772 +1,776 @@ /*============================================================================ 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 "mitkBaseRenderer.h" #include "mitkBaseRendererHelper.h" #include "mitkMapper.h" #include "mitkResliceMethodProperty.h" // Geometries #include "mitkSlicedGeometry3D.h" #include "mitkVtkLayerController.h" #include "mitkInteractionConst.h" #include "mitkProperties.h" #include "mitkWeakPointerProperty.h" // VTK #include #include #include #include #include namespace mitk { itkEventMacroDefinition(RendererResetEvent, itk::AnyEvent); } mitk::BaseRenderer::BaseRendererMapType mitk::BaseRenderer::baseRendererMap; mitk::BaseRenderer *mitk::BaseRenderer::GetInstance(vtkRenderWindow *renWin) { for (auto mapit = baseRendererMap.begin(); mapit != baseRendererMap.end(); ++mapit) { if ((*mapit).first == renWin) return (*mapit).second; } return nullptr; } void mitk::BaseRenderer::AddInstance(vtkRenderWindow *renWin, BaseRenderer *baseRenderer) { if (renWin == nullptr || baseRenderer == nullptr) return; // ensure that no BaseRenderer is managed twice mitk::BaseRenderer::RemoveInstance(renWin); baseRendererMap.insert(BaseRendererMapType::value_type(renWin, baseRenderer)); } void mitk::BaseRenderer::RemoveInstance(vtkRenderWindow *renWin) { auto mapit = baseRendererMap.find(renWin); if (mapit != baseRendererMap.end()) baseRendererMap.erase(mapit); } mitk::BaseRenderer *mitk::BaseRenderer::GetByName(const std::string &name) { for (auto mapit = baseRendererMap.begin(); mapit != baseRendererMap.end(); ++mapit) { if ((*mapit).second->m_Name == name) return (*mapit).second; } return nullptr; } vtkRenderWindow *mitk::BaseRenderer::GetRenderWindowByName(const std::string &name) { for (auto mapit = baseRendererMap.begin(); mapit != baseRendererMap.end(); ++mapit) { if ((*mapit).second->m_Name == name) return (*mapit).first; } return nullptr; } mitk::BaseRenderer::BaseRendererMapType mitk::BaseRenderer::GetSpecificRenderWindows(MapperSlotId mapper) { BaseRendererMapType allRenderWindows; for (auto mapit = baseRendererMap.begin(); mapit != baseRendererMap.end(); ++mapit) { if (mapper == mapit->second->GetMapperID()) { allRenderWindows.insert(BaseRendererMapType::value_type(mapit->first, mapit->second)); } } return allRenderWindows; } mitk::BaseRenderer::BaseRendererMapType mitk::BaseRenderer::GetAll2DRenderWindows() { return GetSpecificRenderWindows(BaseRenderer::Standard2D); } mitk::BaseRenderer::BaseRendererMapType mitk::BaseRenderer::GetAll3DRenderWindows() { return GetSpecificRenderWindows(BaseRenderer::Standard3D); } mitk::BaseRenderer::BaseRenderer(const char *name, vtkRenderWindow *renWin) : m_RenderWindow(nullptr), m_VtkRenderer(nullptr), m_MapperID(StandardMapperSlot::Standard2D), m_DataStorage(nullptr), m_LastUpdateTime(0), m_CameraController(nullptr), m_CameraRotationController(nullptr), m_SliceNavigationController(nullptr), m_WorldTimeGeometry(nullptr), + m_InteractionReferenceGeometry(nullptr), m_CurrentWorldGeometry(nullptr), m_CurrentWorldPlaneGeometry(nullptr), m_Slice(0), m_TimeStep(), m_CurrentWorldPlaneGeometryUpdateTime(), m_TimeStepUpdateTime(), m_KeepDisplayedRegion(true), + m_ReferenceGeometryAligned(true), m_CurrentWorldPlaneGeometryData(nullptr), m_CurrentWorldPlaneGeometryNode(nullptr), m_CurrentWorldPlaneGeometryTransformTime(0), m_Name(name), m_EmptyWorldGeometry(true), m_NumberOfVisibleLODEnabledMappers(0) { m_Bounds[0] = 0; m_Bounds[1] = 0; m_Bounds[2] = 0; m_Bounds[3] = 0; m_Bounds[4] = 0; m_Bounds[5] = 0; if (name != nullptr) { m_Name = name; } else { m_Name = "unnamed renderer"; itkWarningMacro(<< "Created unnamed renderer. Bad for serialization. Please choose a name."); } if (renWin != nullptr) { m_RenderWindow = renWin; m_RenderWindow->Register(nullptr); } else { itkWarningMacro(<< "Created mitkBaseRenderer without vtkRenderWindow present."); } // instances.insert( this ); // adding this BaseRenderer to the List of all BaseRenderer m_BindDispatcherInteractor = new mitk::BindDispatcherInteractor(GetName()); WeakPointerProperty::Pointer rendererProp = WeakPointerProperty::New((itk::Object *)this); m_CurrentWorldPlaneGeometry = mitk::PlaneGeometry::New(); m_CurrentWorldPlaneGeometryData = mitk::PlaneGeometryData::New(); m_CurrentWorldPlaneGeometryData->SetPlaneGeometry(m_CurrentWorldPlaneGeometry); m_CurrentWorldPlaneGeometryNode = mitk::DataNode::New(); m_CurrentWorldPlaneGeometryNode->SetData(m_CurrentWorldPlaneGeometryData); m_CurrentWorldPlaneGeometryNode->GetPropertyList()->SetProperty("renderer", rendererProp); m_CurrentWorldPlaneGeometryNode->GetPropertyList()->SetProperty("layer", IntProperty::New(1000)); m_CurrentWorldPlaneGeometryNode->SetProperty("reslice.thickslices", mitk::ResliceMethodProperty::New()); m_CurrentWorldPlaneGeometryNode->SetProperty("reslice.thickslices.num", mitk::IntProperty::New(1)); m_CurrentWorldPlaneGeometryTransformTime = m_CurrentWorldPlaneGeometryNode->GetVtkTransform()->GetMTime(); m_SliceNavigationController = mitk::SliceNavigationController::New(); m_SliceNavigationController->SetRenderer(this); m_SliceNavigationController->ConnectGeometrySendEvent(this); m_SliceNavigationController->ConnectGeometryUpdateEvent(this); m_SliceNavigationController->ConnectGeometrySliceEvent(this); m_SliceNavigationController->ConnectGeometryTimeEvent(this); m_CameraRotationController = mitk::CameraRotationController::New(); m_CameraRotationController->SetRenderWindow(m_RenderWindow); m_CameraRotationController->AcquireCamera(); m_CameraController = mitk::CameraController::New(); m_CameraController->SetRenderer(this); m_VtkRenderer = vtkRenderer::New(); m_VtkRenderer->SetMaximumNumberOfPeels(16); if (AntiAliasing::FastApproximate == RenderingManager::GetInstance()->GetAntiAliasing()) m_VtkRenderer->UseFXAAOn(); if (nullptr == mitk::VtkLayerController::GetInstance(m_RenderWindow)) mitk::VtkLayerController::AddInstance(m_RenderWindow, m_VtkRenderer); mitk::VtkLayerController::GetInstance(m_RenderWindow)->InsertSceneRenderer(m_VtkRenderer); } mitk::BaseRenderer::~BaseRenderer() { if (m_VtkRenderer != nullptr) { m_VtkRenderer->Delete(); m_VtkRenderer = nullptr; } if (m_CameraController.IsNotNull()) m_CameraController->SetRenderer(nullptr); mitk::VtkLayerController::RemoveInstance(m_RenderWindow); RemoveAllLocalStorages(); m_DataStorage = nullptr; if (m_BindDispatcherInteractor != nullptr) { delete m_BindDispatcherInteractor; } if (m_RenderWindow != nullptr) { m_RenderWindow->Delete(); m_RenderWindow = nullptr; } } void mitk::BaseRenderer::RemoveAllLocalStorages() { this->InvokeEvent(RendererResetEvent()); std::list::iterator it; for (it = m_RegisteredLocalStorageHandlers.begin(); it != m_RegisteredLocalStorageHandlers.end(); ++it) (*it)->ClearLocalStorage(this, false); m_RegisteredLocalStorageHandlers.clear(); } void mitk::BaseRenderer::RegisterLocalStorageHandler(mitk::BaseLocalStorageHandler *lsh) { m_RegisteredLocalStorageHandlers.push_back(lsh); } void mitk::BaseRenderer::UnregisterLocalStorageHandler(mitk::BaseLocalStorageHandler *lsh) { m_RegisteredLocalStorageHandlers.remove(lsh); } void mitk::BaseRenderer::SetDataStorage(DataStorage *storage) { if (storage != m_DataStorage && storage != nullptr) { m_DataStorage = storage; m_BindDispatcherInteractor->SetDataStorage(m_DataStorage); this->Modified(); } } mitk::Dispatcher::Pointer mitk::BaseRenderer::GetDispatcher() const { return m_BindDispatcherInteractor->GetDispatcher(); } void mitk::BaseRenderer::Resize(int w, int h) { m_RenderWindow->SetSize(w, h); } void mitk::BaseRenderer::InitRenderer(vtkRenderWindow *renderwindow) { if (m_RenderWindow != renderwindow) { if (m_RenderWindow != nullptr) { m_RenderWindow->Delete(); } m_RenderWindow = renderwindow; if (m_RenderWindow != nullptr) { m_RenderWindow->Register(nullptr); } } RemoveAllLocalStorages(); if (m_CameraController.IsNotNull()) { m_CameraController->SetRenderer(this); } } void mitk::BaseRenderer::InitSize(int w, int h) { m_RenderWindow->SetSize(w, h); } void mitk::BaseRenderer::SetWorldTimeGeometry(const mitk::TimeGeometry* geometry) { if (m_WorldTimeGeometry == geometry) { return; } m_WorldTimeGeometry = geometry; this->UpdateCurrentGeometries(); } void mitk::BaseRenderer::SetInteractionReferenceGeometry(const TimeGeometry* geometry) { if (m_InteractionReferenceGeometry == geometry) { return; } m_InteractionReferenceGeometry = geometry; this->UpdateCurrentGeometries(); } void mitk::BaseRenderer::SetSlice(unsigned int slice) { if (m_Slice == slice) { return; } m_Slice = slice; this->UpdateCurrentGeometries(); } void mitk::BaseRenderer::SetTimeStep(unsigned int timeStep) { if (m_TimeStep == timeStep) { return; } m_TimeStep = timeStep; m_TimeStepUpdateTime.Modified(); this->UpdateCurrentGeometries(); } mitk::TimeStepType mitk::BaseRenderer::GetTimeStep(const mitk::BaseData* data) const { if ((data == nullptr) || (data->IsInitialized() == false)) { return -1; } return data->GetTimeGeometry()->TimePointToTimeStep(GetTime()); } mitk::ScalarType mitk::BaseRenderer::GetTime() const { if (m_WorldTimeGeometry.IsNull()) { return 0; } else { ScalarType timeInMS = m_WorldTimeGeometry->TimeStepToTimePoint(GetTimeStep()); if (timeInMS == itk::NumericTraits::NonpositiveMin()) return 0; else return timeInMS; } } void mitk::BaseRenderer::SetGeometry(const itk::EventObject& geometrySendEvent) { const auto* sendEvent = dynamic_cast(&geometrySendEvent); if (nullptr == sendEvent) { return; } SetWorldTimeGeometry(sendEvent->GetTimeGeometry()); } void mitk::BaseRenderer::UpdateGeometry(const itk::EventObject& geometryUpdateEvent) { const auto* updateEvent = dynamic_cast(&geometryUpdateEvent); if (nullptr == updateEvent) { return; } if (m_CurrentWorldGeometry.IsNull()) { return; } const auto* slicedWorldGeometry = dynamic_cast(m_CurrentWorldGeometry.GetPointer()); if (slicedWorldGeometry) { PlaneGeometry* geometry2D = slicedWorldGeometry->GetPlaneGeometry(m_Slice); SetCurrentWorldPlaneGeometry(geometry2D); // calls Modified() } } void mitk::BaseRenderer::SetGeometrySlice(const itk::EventObject& geometrySliceEvent) { const auto* sliceEvent = dynamic_cast(&geometrySliceEvent); if (nullptr == sliceEvent) { return; } this->SetSlice(sliceEvent->GetPos()); } void mitk::BaseRenderer::SetGeometryTime(const itk::EventObject& geometryTimeEvent) { const auto* timeEvent = dynamic_cast(&geometryTimeEvent); if (nullptr == timeEvent) { return; } this->SetTimeStep(timeEvent->GetPos()); } void mitk::BaseRenderer::SendUpdateSlice() { m_CurrentWorldPlaneGeometryUpdateTime.Modified(); } void mitk::BaseRenderer::SetMapperID(MapperSlotId id) { if (m_MapperID != id) { bool useDepthPeeling = Standard3D == id; m_VtkRenderer->SetUseDepthPeeling(useDepthPeeling); m_VtkRenderer->SetUseDepthPeelingForVolumes(useDepthPeeling); m_MapperID = id; this->Modified(); } } int* mitk::BaseRenderer::GetSize() const { return m_RenderWindow->GetSize(); } int* mitk::BaseRenderer::GetViewportSize() const { return m_VtkRenderer->GetSize(); } const double* mitk::BaseRenderer::GetBounds() const { return m_Bounds; } void mitk::BaseRenderer::RequestUpdate() { SetConstrainZoomingAndPanning(true); RenderingManager::GetInstance()->RequestUpdate(m_RenderWindow); } void mitk::BaseRenderer::ForceImmediateUpdate() { RenderingManager::GetInstance()->ForceImmediateUpdate(m_RenderWindow); } unsigned int mitk::BaseRenderer::GetNumberOfVisibleLODEnabledMappers() const { return m_NumberOfVisibleLODEnabledMappers; } void mitk::BaseRenderer::SetSliceNavigationController(mitk::SliceNavigationController *SlicenavigationController) { if (SlicenavigationController == nullptr) return; // copy worldgeometry SlicenavigationController->SetInputWorldTimeGeometry(SlicenavigationController->GetCreatedWorldGeometry()); SlicenavigationController->Update(); // set new m_SliceNavigationController = SlicenavigationController; m_SliceNavigationController->SetRenderer(this); if (m_SliceNavigationController.IsNotNull()) { m_SliceNavigationController->ConnectGeometrySendEvent(this); m_SliceNavigationController->ConnectGeometryUpdateEvent(this); m_SliceNavigationController->ConnectGeometrySliceEvent(this); m_SliceNavigationController->ConnectGeometryTimeEvent(this); } } void mitk::BaseRenderer::DisplayToWorld(const Point2D& displayPoint, Point3D& worldIndex) const { if (m_MapperID == BaseRenderer::Standard2D) { double display[3], * world; // For the right z-position in display coordinates, take the focal point, convert it to display and use it for // correct depth. double* displayCoord; double cameraFP[4]; // Get camera focal point and position. Convert to display (screen) // coordinates. We need a depth value for z-buffer. this->GetVtkRenderer()->GetActiveCamera()->GetFocalPoint(cameraFP); cameraFP[3] = 0.0; this->GetVtkRenderer()->SetWorldPoint(cameraFP[0], cameraFP[1], cameraFP[2], cameraFP[3]); this->GetVtkRenderer()->WorldToDisplay(); displayCoord = this->GetVtkRenderer()->GetDisplayPoint(); // now convert the display point to world coordinates display[0] = displayPoint[0]; display[1] = displayPoint[1]; display[2] = displayCoord[2]; this->GetVtkRenderer()->SetDisplayPoint(display); this->GetVtkRenderer()->DisplayToWorld(); world = this->GetVtkRenderer()->GetWorldPoint(); for (int i = 0; i < 3; i++) { worldIndex[i] = world[i] / world[3]; } } else if (m_MapperID == BaseRenderer::Standard3D) { // Seems to be the same code as above, but subclasses may contain different implementations. PickWorldPoint(displayPoint, worldIndex); } return; } void mitk::BaseRenderer::DisplayToPlane(const Point2D &displayPoint, Point2D &planePointInMM) const { if (m_MapperID == BaseRenderer::Standard2D) { Point3D worldPoint; this->DisplayToWorld(displayPoint, worldPoint); m_CurrentWorldPlaneGeometry->Map(worldPoint, planePointInMM); } else if (m_MapperID == BaseRenderer::Standard3D) { MITK_WARN << "No conversion possible with 3D mapper."; return; } return; } void mitk::BaseRenderer::WorldToDisplay(const Point3D &worldIndex, Point2D &displayPoint) const { double world[4], *display; world[0] = worldIndex[0]; world[1] = worldIndex[1]; world[2] = worldIndex[2]; world[3] = 1.0; this->GetVtkRenderer()->SetWorldPoint(world); this->GetVtkRenderer()->WorldToDisplay(); display = this->GetVtkRenderer()->GetDisplayPoint(); displayPoint[0] = display[0]; displayPoint[1] = display[1]; return; } void mitk::BaseRenderer::WorldToView(const mitk::Point3D &worldIndex, mitk::Point2D &viewPoint) const { double world[4], *view; world[0] = worldIndex[0]; world[1] = worldIndex[1]; world[2] = worldIndex[2]; world[3] = 1.0; this->GetVtkRenderer()->SetWorldPoint(world); this->GetVtkRenderer()->WorldToView(); view = this->GetVtkRenderer()->GetViewPoint(); this->GetVtkRenderer()->ViewToNormalizedViewport(view[0], view[1], view[2]); viewPoint[0] = view[0] * this->GetViewportSize()[0]; viewPoint[1] = view[1] * this->GetViewportSize()[1]; return; } void mitk::BaseRenderer::PlaneToDisplay(const Point2D &planePointInMM, Point2D &displayPoint) const { Point3D worldPoint; m_CurrentWorldPlaneGeometry->Map(planePointInMM, worldPoint); this->WorldToDisplay(worldPoint, displayPoint); return; } void mitk::BaseRenderer::PlaneToView(const Point2D &planePointInMM, Point2D &viewPoint) const { Point3D worldPoint; m_CurrentWorldPlaneGeometry->Map(planePointInMM, worldPoint); this->WorldToView(worldPoint,viewPoint); return; } double mitk::BaseRenderer::GetScaleFactorMMPerDisplayUnit() const { if (this->GetMapperID() == BaseRenderer::Standard2D) { // GetParallelScale returns half of the height of the render window in mm. // Divided by the half size of the Display size in pixel givest the mm per pixel. return this->GetVtkRenderer()->GetActiveCamera()->GetParallelScale() * 2.0 / GetViewportSize()[1]; } else return 1.0; } mitk::Point2D mitk::BaseRenderer::GetDisplaySizeInMM() const { Point2D dispSizeInMM; dispSizeInMM[0] = GetSizeX() * GetScaleFactorMMPerDisplayUnit(); dispSizeInMM[1] = GetSizeY() * GetScaleFactorMMPerDisplayUnit(); return dispSizeInMM; } mitk::Point2D mitk::BaseRenderer::GetViewportSizeInMM() const { Point2D dispSizeInMM; dispSizeInMM[0] = GetViewportSize()[0] * GetScaleFactorMMPerDisplayUnit(); dispSizeInMM[1] = GetViewportSize()[1] * GetScaleFactorMMPerDisplayUnit(); return dispSizeInMM; } mitk::Point2D mitk::BaseRenderer::GetOriginInMM() const { Point2D originPx; originPx[0] = m_VtkRenderer->GetOrigin()[0]; originPx[1] = m_VtkRenderer->GetOrigin()[1]; Point2D displayGeometryOriginInMM; DisplayToPlane(originPx, displayGeometryOriginInMM); // top left of the render window (Origin) return displayGeometryOriginInMM; } void mitk::BaseRenderer::SetConstrainZoomingAndPanning(bool constrain) { m_ConstrainZoomingAndPanning = constrain; if (m_ConstrainZoomingAndPanning) { this->GetCameraController()->AdjustCameraToPlane(); } } void mitk::BaseRenderer::UpdateCurrentGeometries() { + m_ReferenceGeometryAligned = true; + if (m_WorldTimeGeometry.IsNull()) { // simply mark the base renderer as modified Modified(); return; } if (m_TimeStep >= m_WorldTimeGeometry->CountTimeSteps()) { m_TimeStep = m_WorldTimeGeometry->CountTimeSteps() - 1; } auto slicedWorldGeometry = dynamic_cast(m_WorldTimeGeometry->GetGeometryForTimeStep(m_TimeStep).GetPointer()); if (slicedWorldGeometry != nullptr) { if (m_Slice >= slicedWorldGeometry->GetSlices()) { m_Slice = slicedWorldGeometry->GetSlices() - 1; } SetCurrentWorldGeometry(slicedWorldGeometry); SetCurrentWorldPlaneGeometry(slicedWorldGeometry->GetPlaneGeometry(m_Slice)); m_ReferenceGeometryAligned = BaseRendererHelper::IsRendererGeometryAlignedWithGeometry(this, m_InteractionReferenceGeometry); } } void mitk::BaseRenderer::SetCurrentWorldPlaneGeometry(const mitk::PlaneGeometry* geometry2d) { if (m_CurrentWorldPlaneGeometry == geometry2d) { return; } m_CurrentWorldPlaneGeometry = geometry2d->Clone(); m_CurrentWorldPlaneGeometryData->SetPlaneGeometry(m_CurrentWorldPlaneGeometry); m_CurrentWorldPlaneGeometryUpdateTime.Modified(); Modified(); } void mitk::BaseRenderer::SetCurrentWorldGeometry(const mitk::BaseGeometry* geometry) { if (m_CurrentWorldGeometry == geometry) { return; } m_CurrentWorldGeometry = geometry; if (geometry == nullptr) { m_Bounds[0] = 0; m_Bounds[1] = 0; m_Bounds[2] = 0; m_Bounds[3] = 0; m_Bounds[4] = 0; m_Bounds[5] = 0; m_EmptyWorldGeometry = true; return; } BoundingBox::Pointer boundingBox = m_CurrentWorldGeometry->CalculateBoundingBoxRelativeToTransform(nullptr); const BoundingBox::BoundsArrayType& worldBounds = boundingBox->GetBounds(); m_Bounds[0] = worldBounds[0]; m_Bounds[1] = worldBounds[1]; m_Bounds[2] = worldBounds[2]; m_Bounds[3] = worldBounds[3]; m_Bounds[4] = worldBounds[4]; m_Bounds[5] = worldBounds[5]; if (boundingBox->GetDiagonalLength2() <= mitk::eps) { m_EmptyWorldGeometry = true; } else { m_EmptyWorldGeometry = false; } } void mitk::BaseRenderer::PrintSelf(std::ostream &os, itk::Indent indent) const { os << indent << " MapperID: " << m_MapperID << std::endl; os << indent << " Slice: " << m_Slice << std::endl; os << indent << " TimeStep: " << m_TimeStep << std::endl; os << indent << " CurrentWorldPlaneGeometry: "; if (m_CurrentWorldPlaneGeometry.IsNull()) os << "nullptr" << std::endl; else m_CurrentWorldPlaneGeometry->Print(os, indent); os << indent << " CurrentWorldPlaneGeometryUpdateTime: " << m_CurrentWorldPlaneGeometryUpdateTime << std::endl; os << indent << " CurrentWorldPlaneGeometryTransformTime: " << m_CurrentWorldPlaneGeometryTransformTime << std::endl; Superclass::PrintSelf(os, indent); } diff --git a/Modules/Core/src/Rendering/mitkCrosshairVtkMapper2D.cpp b/Modules/Core/src/Rendering/mitkCrosshairVtkMapper2D.cpp new file mode 100644 index 0000000000..9545f4fd01 --- /dev/null +++ b/Modules/Core/src/Rendering/mitkCrosshairVtkMapper2D.cpp @@ -0,0 +1,262 @@ +/*============================================================================ + +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 "mitkCrosshairVtkMapper2D.h" + +// mitk includes +#include +#include +#include + +// vtk includes +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + void SwapInvertedCoordinates(mitk::Point2D& displayBoundsMinimum, mitk::Point2D& displayBoundsMaximum) + { + for (int i = 0; i < 2; ++i) + { + if (displayBoundsMaximum[i] < displayBoundsMinimum[i]) + { + auto temporarySwapVariable = displayBoundsMaximum[i]; + displayBoundsMaximum[i] = displayBoundsMinimum[i]; + displayBoundsMinimum[i] = temporarySwapVariable; + } + } + } +} + +const mitk::CrosshairData* mitk::CrosshairVtkMapper2D::GetInput() const +{ + return static_cast(this->GetDataNode()->GetData()); +} + +mitk::CrosshairVtkMapper2D::CrosshairVtkMapper2D() + : defaultGapSize(32) +{ +} + +mitk::CrosshairVtkMapper2D::~CrosshairVtkMapper2D() +{ +} + +void mitk::CrosshairVtkMapper2D::Update(mitk::BaseRenderer* renderer) +{ + const auto* node = this->GetDataNode(); + if (nullptr == node) + { + return; + } + + bool visible = true; + node->GetVisibility(visible, renderer, "visible"); + if (!visible) + { + // Do not render crosshair for base renderer, if the + // crosshair data node is not visible in that base renderer. + return; + } + + const auto* data = static_cast(node->GetData()); + if (nullptr == data) + { + // Do not render crosshair for base renderer, if the + // base data node is no crosshair data. + return; + } + + this->GenerateDataForRenderer(renderer); +} + +vtkProp* mitk::CrosshairVtkMapper2D::GetVtkProp(BaseRenderer* renderer) +{ + LocalStorage* ls = m_LSH.GetLocalStorage(renderer); + return ls->m_CrosshairAssembly; +} + +void mitk::CrosshairVtkMapper2D::SetDefaultProperties(DataNode* node, + BaseRenderer* renderer, + bool overwrite) +{ + CoreServicePointer aliases(CoreServices::GetPropertyAliases()); + node->AddProperty("Line width", FloatProperty::New(1), renderer, overwrite); + aliases->AddAlias("line width", "Crosshair.Line Width", ""); + node->AddProperty("Crosshair.Gap Size", IntProperty::New(32), renderer, overwrite); + + Superclass::SetDefaultProperties(node, renderer, overwrite); +} + +void mitk::CrosshairVtkMapper2D::GenerateDataForRenderer(BaseRenderer* renderer) +{ + LocalStorage* ls = m_LSH.GetLocalStorage(renderer); + ls->UpdateGenerateDataTime(); + + this->CreateVtkCrosshair(renderer); + + this->ApplyAllProperties(renderer); +} + +void mitk::CrosshairVtkMapper2D::CreateVtkCrosshair(BaseRenderer* renderer) +{ + LocalStorage* ls = m_LSH.GetLocalStorage(renderer); + ls->m_CrosshairActor->SetVisibility(0); + + CrosshairData::ConstPointer input = this->GetInput(); + Point3D crosshairPosition = input->GetPosition(); + + // check if node is inside the renderer geometry + bool isInside = renderer->GetWorldTimeGeometry()->IsWorldPointInside(crosshairPosition); + if (!isInside) + { + // no need to draw crosshair lines + return; + } + + int gapSize = defaultGapSize; + this->GetDataNode()->GetPropertyValue("Crosshair.Gap Size", gapSize, nullptr); + + vtkSmartPointer lines = vtkSmartPointer::New(); + vtkSmartPointer points = vtkSmartPointer::New(); + vtkSmartPointer linesPolyData = vtkSmartPointer::New(); + + // convert 3D crosshair crosshairPosition to 2D point + Point2D displayPoint; + renderer->WorldToDisplay(crosshairPosition, displayPoint); + Point2D displayPointOne = displayPoint; + Point2D displayPointTwo = displayPoint; + Point3D worldPointOne; + Point3D worldPointTwo; + + // convert geometry bounds from 3D to 2D + const auto worldBounds = renderer->GetWorldTimeGeometry()->GetBoundsInWorld(); + Point3D worldBoundsMinimum; + worldBoundsMinimum[0] = worldBounds[0]; + worldBoundsMinimum[1] = worldBounds[2]; + worldBoundsMinimum[2] = worldBounds[4]; + + Point3D worldBoundsMaximum; + worldBoundsMaximum[0] = worldBounds[1]; + worldBoundsMaximum[1] = worldBounds[3]; + worldBoundsMaximum[2] = worldBounds[5]; + + Point2D displayBoundsMinimum; + Point2D displayBoundsMaximum; + renderer->WorldToDisplay(worldBoundsMinimum, displayBoundsMinimum); + renderer->WorldToDisplay(worldBoundsMaximum, displayBoundsMaximum); + + SwapInvertedCoordinates(displayBoundsMinimum, displayBoundsMaximum); + + // define points for the first / left half of the crosshair x-line + displayPointOne[0] = displayBoundsMinimum[0]; + displayPointTwo[0] = displayPoint[0] - gapSize; + renderer->DisplayToWorld(displayPointOne, worldPointOne); + renderer->DisplayToWorld(displayPointTwo, worldPointTwo); + this->DrawLine(worldPointOne, worldPointTwo, lines, points); + + // define points for the second / right half of the crosshair x-line + displayPointOne[0] = displayPoint[0] + gapSize; + displayPointTwo[0] = displayBoundsMaximum[0]; + renderer->DisplayToWorld(displayPointOne, worldPointOne); + renderer->DisplayToWorld(displayPointTwo, worldPointTwo); + this->DrawLine(worldPointOne, worldPointTwo, lines, points); + + // reset 2D points + displayPointOne = displayPoint; + displayPointTwo = displayPoint; + + // define points for the first / bottom half of the crosshair y-line + displayPointOne[1] = displayBoundsMinimum[1]; + displayPointTwo[1] = displayPoint[1] - gapSize; + renderer->DisplayToWorld(displayPointOne, worldPointOne); + renderer->DisplayToWorld(displayPointTwo, worldPointTwo); + this->DrawLine(worldPointOne, worldPointTwo, lines, points); + + // define points for the second / top half of the crosshair y-line + displayPointOne[1] = displayPoint[1] + gapSize; + displayPointTwo[1] = displayBoundsMaximum[1]; + renderer->DisplayToWorld(displayPointOne, worldPointOne); + renderer->DisplayToWorld(displayPointTwo, worldPointTwo); + this->DrawLine(worldPointOne, worldPointTwo, lines, points); + + // set vtk data with the created points and lines + linesPolyData->SetPoints(points); + linesPolyData->SetLines(lines); + ls->m_Mapper->SetInputData(linesPolyData); + ls->m_CrosshairActor->SetMapper(ls->m_Mapper); + ls->m_CrosshairActor->SetVisibility(1); +} + +void mitk::CrosshairVtkMapper2D::DrawLine(Point3D p0, Point3D p1, vtkCellArray* lines, vtkPoints* points) +{ + vtkIdType pidStart = points->InsertNextPoint(p0[0], p0[1], p0[2]); + vtkIdType pidEnd = points->InsertNextPoint(p1[0], p1[1], p1[2]); + + vtkSmartPointer lineVtk = vtkSmartPointer::New(); + lineVtk->GetPointIds()->SetId(0, pidStart); + lineVtk->GetPointIds()->SetId(1, pidEnd); + + lines->InsertNextCell(lineVtk); +} + +void mitk::CrosshairVtkMapper2D::ApplyAllProperties(BaseRenderer* renderer) +{ + LocalStorage* ls = m_LSH.GetLocalStorage(renderer); + ApplyColorAndOpacityProperties2D(renderer, ls->m_CrosshairActor); + + float thickness; + this->GetDataNode()->GetFloatProperty("Line width", thickness, renderer); + ls->m_CrosshairActor->GetProperty()->SetLineWidth(thickness); +} + +void mitk::CrosshairVtkMapper2D::ApplyColorAndOpacityProperties2D(BaseRenderer* renderer, vtkActor2D* actor) +{ + float rgba[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + DataNode* node = GetDataNode(); + + // check for color prop and use it for rendering if it exists + node->GetColor(rgba, renderer, "color"); + // check for opacity prop and use it for rendering if it exists + node->GetOpacity(rgba[3], renderer, "opacity"); + + double drgba[4] = { rgba[0], rgba[1], rgba[2], rgba[3] }; + actor->GetProperty()->SetColor(drgba); + actor->GetProperty()->SetOpacity(drgba[3]); +} + +mitk::CrosshairVtkMapper2D::LocalStorage::LocalStorage() +{ + m_CrosshairAssembly = vtkSmartPointer::New(); + m_CrosshairActor = vtkSmartPointer::New(); + m_Mapper = vtkSmartPointer::New(); + + m_CrosshairActor->SetMapper(m_Mapper); + m_CrosshairActor->SetVisibility(0); + m_CrosshairAssembly->AddPart(m_CrosshairActor); + + vtkCoordinate *tcoord = vtkCoordinate::New(); + tcoord->SetCoordinateSystemToWorld(); + m_Mapper->SetTransformCoordinate(tcoord); + tcoord->Delete(); +} + +mitk::CrosshairVtkMapper2D::LocalStorage::~LocalStorage() +{ +} diff --git a/Modules/Core/src/Rendering/mitkPlaneGeometryDataMapper2D.cpp b/Modules/Core/src/Rendering/mitkPlaneGeometryDataMapper2D.cpp index e6c3e2ec9a..bdca8d7012 100644 --- a/Modules/Core/src/Rendering/mitkPlaneGeometryDataMapper2D.cpp +++ b/Modules/Core/src/Rendering/mitkPlaneGeometryDataMapper2D.cpp @@ -1,706 +1,676 @@ /*============================================================================ 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 "mitkPlaneGeometryDataMapper2D.h" // mitk includes #include "mitkVtkPropRenderer.h" #include #include #include #include #include -#include #include #include #include // vtk includes #include #include #include -#include #include #include #include #include #include #include /// #include #include #include #include namespace { /// Some simple interval arithmetic template class SimpleInterval { public: SimpleInterval(T start = T(), T end = T()) : m_LowerBoundary(std::min(start, end)), m_UpperBoundary(std::max(start, end)) { } T GetLowerBoundary() const { return m_LowerBoundary; } T GetUpperBoundary() const { return m_UpperBoundary; } bool empty() const { return m_LowerBoundary == m_UpperBoundary; } bool operator<(const SimpleInterval &otherInterval) const { return this->m_UpperBoundary < otherInterval.GetLowerBoundary(); } private: T m_LowerBoundary; T m_UpperBoundary; }; template class IntervalSet { public: typedef SimpleInterval IntervalType; IntervalSet(IntervalType startingInterval) { m_IntervalsContainer.insert(std::move(startingInterval)); } void operator-=(const IntervalType &interval) { // equal_range will find all the intervals in the interval set which intersect with the input interval // due to the nature of operator< of SimpleInterval auto range = m_IntervalsContainer.equal_range(interval); for (auto iter = range.first; iter != range.second;) { auto subtractionResult = SubtractIntervals(*iter, interval); // Remove the old interval from the set iter = m_IntervalsContainer.erase(iter); for (auto &&interval : subtractionResult) { if (!interval.empty()) { // Add the new interval to the set // emplace_hint adds the element at the closest valid place before the hint iterator, // which is exactly where the new interval should be iter = m_IntervalsContainer.insert(iter, std::move(interval)); ++iter; } } } } IntervalSet operator-(const IntervalType &interval) { IntervalSet result = *this; result -= interval; return result; } typedef std::set IntervalsContainer; const IntervalsContainer &getIntervals() const { return m_IntervalsContainer; } private: IntervalsContainer m_IntervalsContainer; std::array SubtractIntervals(const IntervalType &firstInterval, const IntervalType &secondInterval) { assert(secondInterval.GetUpperBoundary() >= firstInterval.GetLowerBoundary() && firstInterval.GetUpperBoundary() >= secondInterval.GetLowerBoundary()); // Non-intersecting intervals should never reach here if (secondInterval.GetLowerBoundary() < firstInterval.GetLowerBoundary()) { if (firstInterval.GetUpperBoundary() < secondInterval.GetUpperBoundary()) { std::array result = {{IntervalType(), IntervalType()}}; return result; // firstInterval completely enclosed } std::array result = { {IntervalType(firstInterval.GetUpperBoundary(), secondInterval.GetUpperBoundary()), IntervalType()}}; return result; // secondInterval removes the beginning of firstInterval } if (firstInterval.GetUpperBoundary() < secondInterval.GetUpperBoundary()) { std::array result = { {IntervalType(firstInterval.GetLowerBoundary(), secondInterval.GetLowerBoundary()), IntervalType()}}; return result; // secondInterval removes the end of firstInterval } std::array result = { {IntervalType(firstInterval.GetLowerBoundary(), secondInterval.GetLowerBoundary()), IntervalType(secondInterval.GetUpperBoundary(), firstInterval.GetUpperBoundary())}}; return result; // secondInterval is completely enclosed in firstInterval and removes the middle } }; } mitk::PlaneGeometryDataMapper2D::AllInstancesContainer mitk::PlaneGeometryDataMapper2D::s_AllInstances; // input for this mapper ( = PlaneGeometryData) const mitk::PlaneGeometryData *mitk::PlaneGeometryDataMapper2D::GetInput() const { return static_cast(GetDataNode()->GetData()); } mitk::PlaneGeometryDataMapper2D::PlaneGeometryDataMapper2D() - : m_RenderOrientationArrows(false), m_ArrowOrientationPositive(true), m_DepthValue(1.0f) + : m_RenderOrientationArrows(false), m_ArrowOrientationPositive(true) { s_AllInstances.insert(this); } mitk::PlaneGeometryDataMapper2D::~PlaneGeometryDataMapper2D() { s_AllInstances.erase(this); } vtkProp *mitk::PlaneGeometryDataMapper2D::GetVtkProp(mitk::BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); return ls->m_CrosshairAssembly; } void mitk::PlaneGeometryDataMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { BaseLocalStorage *ls = m_LSH.GetLocalStorage(renderer); // The PlaneGeometryDataMapper2D mapper is special in that the rendering of // OTHER PlaneGeometryDatas affects how we render THIS PlaneGeometryData // (for the gap at the point where they intersect). A change in any of the // other PlaneGeometryData nodes could mean that we render ourself // differently, so we check for that here. for (auto it = s_AllInstances.begin(); it != s_AllInstances.end(); ++it) { bool generateDataRequired = ls->IsGenerateDataRequired(renderer, this, (*it)->GetDataNode()); if (generateDataRequired) break; } ls->UpdateGenerateDataTime(); // Collect all other PlaneGeometryDatas that are being mapped by this mapper m_OtherPlaneGeometries.clear(); for (auto it = s_AllInstances.begin(); it != s_AllInstances.end(); ++it) { Self *otherInstance = *it; // Skip ourself if (otherInstance == this) continue; mitk::DataNode *otherNode = otherInstance->GetDataNode(); if (!otherNode) continue; // Skip other PlaneGeometryData nodes that are not visible on this renderer if (!otherNode->IsVisible(renderer)) continue; auto *otherData = dynamic_cast(otherNode->GetData()); if (!otherData) continue; auto *otherGeometry = dynamic_cast(otherData->GetPlaneGeometry()); if (otherGeometry && !dynamic_cast(otherData->GetPlaneGeometry())) { m_OtherPlaneGeometries.push_back(otherNode); } } CreateVtkCrosshair(renderer); ApplyAllProperties(renderer); } void mitk::PlaneGeometryDataMapper2D::CreateVtkCrosshair(mitk::BaseRenderer *renderer) { bool visible = true; LocalStorage *ls = m_LSH.GetLocalStorage(renderer); ls->m_CrosshairActor->SetVisibility(0); ls->m_ArrowActor->SetVisibility(0); ls->m_CrosshairHelperLineActor->SetVisibility(0); GetDataNode()->GetVisibility(visible, renderer, "visible"); if (!visible) { return; } PlaneGeometryData::ConstPointer input = this->GetInput(); mitk::DataNode *geometryDataNode = renderer->GetCurrentWorldPlaneGeometryNode(); const PlaneGeometryData *rendererWorldPlaneGeometryData = dynamic_cast(geometryDataNode->GetData()); // intersecting with ourself? if (input.IsNull() || input.GetPointer() == rendererWorldPlaneGeometryData) { return; // nothing to do in this case } const auto *inputPlaneGeometry = dynamic_cast(input->GetPlaneGeometry()); const auto *worldPlaneGeometry = dynamic_cast(rendererWorldPlaneGeometryData->GetPlaneGeometry()); if (worldPlaneGeometry && dynamic_cast(worldPlaneGeometry) == nullptr && inputPlaneGeometry && dynamic_cast(input->GetPlaneGeometry()) == nullptr) { const BaseGeometry *referenceGeometry = inputPlaneGeometry->GetReferenceGeometry(); // calculate intersection of the plane data with the border of the // world geometry rectangle Point3D point1, point2; Line3D crossLine; // Calculate the intersection line of the input plane with the world plane if (worldPlaneGeometry->IntersectionLine(inputPlaneGeometry, crossLine)) { bool hasIntersection = referenceGeometry ? CutCrossLineWithReferenceGeometry(referenceGeometry, crossLine) : CutCrossLineWithPlaneGeometry(inputPlaneGeometry, crossLine); if (!hasIntersection) { return; } point1 = crossLine.GetPoint1(); point2 = crossLine.GetPoint2(); vtkSmartPointer lines = vtkSmartPointer::New(); vtkSmartPointer points = vtkSmartPointer::New(); vtkSmartPointer linesPolyData = vtkSmartPointer::New(); // Now iterate through all other lines displayed in this window and // calculate the positions of intersection with the line to be // rendered; these positions will be stored in lineParams to form a // gap afterwards. auto otherPlanesIt = m_OtherPlaneGeometries.begin(); auto otherPlanesEnd = m_OtherPlaneGeometries.end(); int gapSize = 32; this->GetDataNode()->GetPropertyValue("Crosshair.Gap Size", gapSize, nullptr); auto intervals = IntervalSet(SimpleInterval(0, 1)); ScalarType lineLength = point1.EuclideanDistanceTo(point2); ScalarType gapInMM = gapSize * renderer->GetScaleFactorMMPerDisplayUnit(); float gapSizeParam = gapInMM / lineLength; if (gapSize != 0) { while (otherPlanesIt != otherPlanesEnd) { bool ignorePlane = false; (*otherPlanesIt)->GetPropertyValue("Crosshair.Ignore", ignorePlane); if (ignorePlane) { ++otherPlanesIt; continue; } auto *otherPlaneGeometry = static_cast( static_cast((*otherPlanesIt)->GetData())->GetPlaneGeometry()); if (otherPlaneGeometry != inputPlaneGeometry && otherPlaneGeometry != worldPlaneGeometry) { double intersectionParam; if (otherPlaneGeometry->IntersectionPointParam(crossLine, intersectionParam) && intersectionParam > 0 && intersectionParam < 1) { Point3D point = crossLine.GetPoint() + intersectionParam * crossLine.GetDirection(); bool intersectionPointInsideOtherPlane = otherPlaneGeometry->HasReferenceGeometry() ? TestPointInReferenceGeometry(otherPlaneGeometry->GetReferenceGeometry(), point) : TestPointInPlaneGeometry(otherPlaneGeometry, point); if (intersectionPointInsideOtherPlane) { intervals -= SimpleInterval(intersectionParam - gapSizeParam, intersectionParam + gapSizeParam); } } } ++otherPlanesIt; } } for (const auto &interval : intervals.getIntervals()) { this->DrawLine(crossLine.GetPoint(interval.GetLowerBoundary()), crossLine.GetPoint(interval.GetUpperBoundary()), lines, points); } // Add the points to the dataset linesPolyData->SetPoints(points); // Add the lines to the dataset linesPolyData->SetLines(lines); Vector3D orthogonalVector; orthogonalVector = inputPlaneGeometry->GetNormal(); worldPlaneGeometry->Project(orthogonalVector, orthogonalVector); orthogonalVector.Normalize(); // Visualize ls->m_Mapper->SetInputData(linesPolyData); ls->m_CrosshairActor->SetMapper(ls->m_Mapper); // Determine if we should draw the area covered by the thick slicing, default is false. // This will also show the area of slices that do not have thick slice mode enabled bool showAreaOfThickSlicing = false; GetDataNode()->GetBoolProperty("reslice.thickslices.showarea", showAreaOfThickSlicing); // determine the pixelSpacing in that direction double thickSliceDistance = SlicedGeometry3D::CalculateSpacing( referenceGeometry ? referenceGeometry->GetSpacing() : inputPlaneGeometry->GetSpacing(), orthogonalVector); IntProperty *intProperty = nullptr; if (GetDataNode()->GetProperty(intProperty, "reslice.thickslices.num") && intProperty) thickSliceDistance *= intProperty->GetValue() + 0.5; else showAreaOfThickSlicing = false; // not the nicest place to do it, but we have the width of the visible bloc in MM here // so we store it in this fancy property GetDataNode()->SetFloatProperty("reslice.thickslices.sizeinmm", thickSliceDistance * 2); ls->m_CrosshairActor->SetVisibility(1); vtkSmartPointer arrowPolyData = vtkSmartPointer::New(); ls->m_Arrowmapper->SetInputData(arrowPolyData); if (this->m_RenderOrientationArrows) { ScalarType triangleSizeMM = 7.0 * renderer->GetScaleFactorMMPerDisplayUnit(); vtkSmartPointer triangles = vtkSmartPointer::New(); vtkSmartPointer triPoints = vtkSmartPointer::New(); DrawOrientationArrow(triangles, triPoints, triangleSizeMM, orthogonalVector, point1, point2); DrawOrientationArrow(triangles, triPoints, triangleSizeMM, orthogonalVector, point2, point1); arrowPolyData->SetPoints(triPoints); arrowPolyData->SetPolys(triangles); ls->m_ArrowActor->SetVisibility(1); } // Visualize vtkSmartPointer helperlinesPolyData = vtkSmartPointer::New(); ls->m_HelperLinesmapper->SetInputData(helperlinesPolyData); if (showAreaOfThickSlicing) { vtkSmartPointer helperlines = vtkSmartPointer::New(); // vectorToHelperLine defines how to reach the helperLine from the mainLine // got the right direction, so we multiply the width Vector3D vecToHelperLine = orthogonalVector * thickSliceDistance; this->DrawLine(point1 - vecToHelperLine, point2 - vecToHelperLine, helperlines, points); this->DrawLine(point1 + vecToHelperLine, point2 + vecToHelperLine, helperlines, points); // Add the points to the dataset helperlinesPolyData->SetPoints(points); // Add the lines to the dataset helperlinesPolyData->SetLines(helperlines); ls->m_CrosshairActor->GetProperty()->SetLineStipplePattern(0xf0f0); ls->m_CrosshairActor->GetProperty()->SetLineStippleRepeatFactor(1); ls->m_CrosshairHelperLineActor->SetVisibility(1); } } } } bool mitk::PlaneGeometryDataMapper2D::TestPointInPlaneGeometry(const PlaneGeometry *planeGeometry, const Point3D &point) { Point2D mappedPoint; planeGeometry->Map(point, mappedPoint); planeGeometry->WorldToIndex(mappedPoint, mappedPoint); return (planeGeometry->GetBounds()[0] < mappedPoint[0] && mappedPoint[0] < planeGeometry->GetBounds()[1] && planeGeometry->GetBounds()[2] < mappedPoint[1] && mappedPoint[1] < planeGeometry->GetBounds()[3]); } bool mitk::PlaneGeometryDataMapper2D::TestPointInReferenceGeometry(const BaseGeometry *referenceGeometry, const Point3D &point) { return referenceGeometry->IsInside(point); } bool mitk::PlaneGeometryDataMapper2D::CutCrossLineWithPlaneGeometry(const PlaneGeometry *planeGeometry, Line3D &crossLine) { Point2D indexLinePoint; Vector2D indexLineDirection; planeGeometry->Map(crossLine.GetPoint(), indexLinePoint); planeGeometry->Map(crossLine.GetPoint(), crossLine.GetDirection(), indexLineDirection); planeGeometry->WorldToIndex(indexLinePoint, indexLinePoint); planeGeometry->WorldToIndex(indexLineDirection, indexLineDirection); mitk::Point2D intersectionPoints[2]; // Then, clip this line with the (transformed) bounding box of the // reference geometry. int nIntersections = Line3D::RectangleLineIntersection(planeGeometry->GetBounds()[0], planeGeometry->GetBounds()[2], planeGeometry->GetBounds()[1], planeGeometry->GetBounds()[3], indexLinePoint, indexLineDirection, intersectionPoints[0], intersectionPoints[1]); if (nIntersections < 2) { return false; } planeGeometry->IndexToWorld(intersectionPoints[0], intersectionPoints[0]); planeGeometry->IndexToWorld(intersectionPoints[1], intersectionPoints[1]); Point3D point1, point2; planeGeometry->Map(intersectionPoints[0], point1); planeGeometry->Map(intersectionPoints[1], point2); crossLine.SetPoints(point1, point2); return true; } bool mitk::PlaneGeometryDataMapper2D::CutCrossLineWithReferenceGeometry(const BaseGeometry *referenceGeometry, Line3D &crossLine) { Point3D boundingBoxMin, boundingBoxMax; boundingBoxMin = referenceGeometry->GetCornerPoint(0); boundingBoxMax = referenceGeometry->GetCornerPoint(7); Point3D indexLinePoint; Vector3D indexLineDirection; referenceGeometry->WorldToIndex(crossLine.GetPoint(), indexLinePoint); referenceGeometry->WorldToIndex(crossLine.GetDirection(), indexLineDirection); referenceGeometry->WorldToIndex(boundingBoxMin, boundingBoxMin); referenceGeometry->WorldToIndex(boundingBoxMax, boundingBoxMax); Point3D point1, point2; // Then, clip this line with the (transformed) bounding box of the // reference geometry. int nIntersections = Line3D::BoxLineIntersection(boundingBoxMin[0], boundingBoxMin[1], boundingBoxMin[2], boundingBoxMax[0], boundingBoxMax[1], boundingBoxMax[2], indexLinePoint, indexLineDirection, point1, point2); if (nIntersections < 2) { return false; } referenceGeometry->IndexToWorld(point1, point1); referenceGeometry->IndexToWorld(point2, point2); crossLine.SetPoints(point1, point2); return true; } void mitk::PlaneGeometryDataMapper2D::DrawLine(mitk::Point3D p0, mitk::Point3D p1, vtkCellArray *lines, vtkPoints *points) { vtkIdType pidStart = points->InsertNextPoint(p0[0], p0[1], p0[2]); vtkIdType pidEnd = points->InsertNextPoint(p1[0], p1[1], p1[2]); vtkSmartPointer lineVtk = vtkSmartPointer::New(); lineVtk->GetPointIds()->SetId(0, pidStart); lineVtk->GetPointIds()->SetId(1, pidEnd); lines->InsertNextCell(lineVtk); } void mitk::PlaneGeometryDataMapper2D::DrawOrientationArrow(vtkSmartPointer triangles, vtkSmartPointer triPoints, double triangleSizeMM, Vector3D &orthogonalVector, Point3D &point1, Point3D &point2) { // Draw arrows to indicate plane orientation // Vector along line Vector3D v1 = point2 - point1; v1.Normalize(); v1 *= triangleSizeMM; // Orthogonal vector Vector3D v2 = orthogonalVector; v2 *= triangleSizeMM; if (!this->m_ArrowOrientationPositive) v2 *= -1.0; // Initialize remaining triangle coordinates accordingly Point3D p1 = point1 + v1 * 2.0; Point3D p2 = point1 + v1 + v2; vtkIdType t0 = triPoints->InsertNextPoint(point1[0], point1[1], point1[2]); // start of the line vtkIdType t1 = triPoints->InsertNextPoint(p1[0], p1[1], p1[2]); // point on line vtkIdType t2 = triPoints->InsertNextPoint(p2[0], p2[1], p2[2]); // direction point vtkSmartPointer triangle = vtkSmartPointer::New(); triangle->GetPointIds()->SetId(0, t0); triangle->GetPointIds()->SetId(1, t1); triangle->GetPointIds()->SetId(2, t2); triangles->InsertNextCell(triangle); } -int mitk::PlaneGeometryDataMapper2D::DetermineThickSliceMode(DataNode *dn, int &thickSlicesNum) -{ - int thickSlicesMode = 0; - // determine the state and the extend of the thick-slice mode - mitk::ResliceMethodProperty *resliceMethodEnumProperty = nullptr; - if (dn->GetProperty(resliceMethodEnumProperty, "reslice.thickslices") && resliceMethodEnumProperty) - thickSlicesMode = resliceMethodEnumProperty->GetValueAsId(); - - IntProperty *intProperty = nullptr; - if (dn->GetProperty(intProperty, "reslice.thickslices.num") && intProperty) - { - thickSlicesNum = intProperty->GetValue(); - if (thickSlicesNum < 1) - thickSlicesNum = 0; - if (thickSlicesNum > 10) - thickSlicesNum = 10; - } - - if (thickSlicesMode == 0) - thickSlicesNum = 0; - - return thickSlicesMode; -} - void mitk::PlaneGeometryDataMapper2D::ApplyAllProperties(BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); ApplyColorAndOpacityProperties2D(renderer, ls->m_CrosshairActor); ApplyColorAndOpacityProperties2D(renderer, ls->m_CrosshairHelperLineActor); ApplyColorAndOpacityProperties2D(renderer, ls->m_ArrowActor); float thickness; this->GetDataNode()->GetFloatProperty("Line width", thickness, renderer); ls->m_CrosshairActor->GetProperty()->SetLineWidth(thickness); ls->m_CrosshairHelperLineActor->GetProperty()->SetLineWidth(thickness); PlaneOrientationProperty *decorationProperty; this->GetDataNode()->GetProperty(decorationProperty, "decoration", renderer); if (decorationProperty != nullptr) { if (decorationProperty->GetPlaneDecoration() == PlaneOrientationProperty::PLANE_DECORATION_POSITIVE_ORIENTATION) { m_RenderOrientationArrows = true; m_ArrowOrientationPositive = true; } else if (decorationProperty->GetPlaneDecoration() == PlaneOrientationProperty::PLANE_DECORATION_NEGATIVE_ORIENTATION) { m_RenderOrientationArrows = true; m_ArrowOrientationPositive = false; } else { m_RenderOrientationArrows = false; } } } void mitk::PlaneGeometryDataMapper2D::ApplyColorAndOpacityProperties2D(BaseRenderer *renderer, vtkActor2D *actor) { float rgba[4] = {1.0f, 1.0f, 1.0f, 1.0f}; DataNode *node = GetDataNode(); // check for color prop and use it for rendering if it exists node->GetColor(rgba, renderer, "color"); // check for opacity prop and use it for rendering if it exists node->GetOpacity(rgba[3], renderer, "opacity"); double drgba[4] = {rgba[0], rgba[1], rgba[2], rgba[3]}; actor->GetProperty()->SetColor(drgba); actor->GetProperty()->SetOpacity(drgba[3]); } void mitk::PlaneGeometryDataMapper2D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { mitk::CoreServicePointer aliases(mitk::CoreServices::GetPropertyAliases()); node->AddProperty("Line width", mitk::FloatProperty::New(1), renderer, overwrite); aliases->AddAlias("line width", "Crosshair.Line Width", ""); node->AddProperty("Crosshair.Gap Size", mitk::IntProperty::New(32), renderer, overwrite); node->AddProperty("decoration", mitk::PlaneOrientationProperty::New(PlaneOrientationProperty::PLANE_DECORATION_NONE), renderer, overwrite); aliases->AddAlias("decoration", "Crosshair.Orientation Decoration", ""); Superclass::SetDefaultProperties(node, renderer, overwrite); } -void mitk::PlaneGeometryDataMapper2D::UpdateVtkTransform(mitk::BaseRenderer * /*renderer*/) -{ -} - mitk::PlaneGeometryDataMapper2D::LocalStorage::LocalStorage() { m_CrosshairAssembly = vtkSmartPointer::New(); m_CrosshairActor = vtkSmartPointer::New(); m_ArrowActor = vtkSmartPointer::New(); m_CrosshairHelperLineActor = vtkSmartPointer::New(); m_HelperLinesmapper = vtkSmartPointer::New(); m_Mapper = vtkSmartPointer::New(); m_Arrowmapper = vtkSmartPointer::New(); m_CrosshairActor->SetMapper(m_Mapper); m_ArrowActor->SetMapper(m_Arrowmapper); m_CrosshairHelperLineActor->SetMapper(m_HelperLinesmapper); m_CrosshairActor->SetVisibility(0); m_ArrowActor->SetVisibility(0); m_CrosshairHelperLineActor->SetVisibility(0); m_CrosshairAssembly->AddPart(m_CrosshairActor); m_CrosshairAssembly->AddPart(m_ArrowActor); m_CrosshairAssembly->AddPart(m_CrosshairHelperLineActor); vtkCoordinate *tcoord = vtkCoordinate::New(); tcoord->SetCoordinateSystemToWorld(); m_HelperLinesmapper->SetTransformCoordinate(tcoord); m_Mapper->SetTransformCoordinate(tcoord); // tcoord->SetCoordinateSystemToNormalizedDisplay(); m_Arrowmapper->SetTransformCoordinate(tcoord); tcoord->Delete(); } mitk::PlaneGeometryDataMapper2D::LocalStorage::~LocalStorage() { } diff --git a/Modules/Core/src/mitkCoreObjectFactory.cpp b/Modules/Core/src/mitkCoreObjectFactory.cpp index 084c19c0b3..7ea47dcdfa 100644 --- a/Modules/Core/src/mitkCoreObjectFactory.cpp +++ b/Modules/Core/src/mitkCoreObjectFactory.cpp @@ -1,454 +1,460 @@ /*============================================================================ 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 "mitkCoreObjectFactory.h" #include "mitkConfig.h" #include "mitkColorProperty.h" #include "mitkDataNode.h" #include "mitkEnumerationProperty.h" #include "mitkGeometry3D.h" #include "mitkGeometryData.h" #include "mitkImage.h" #include "mitkLevelWindowProperty.h" #include "mitkLookupTable.h" #include "mitkLookupTableProperty.h" #include "mitkPlaneGeometry.h" #include "mitkPlaneGeometryData.h" #include "mitkPlaneGeometryDataMapper2D.h" #include "mitkPlaneGeometryDataVtkMapper3D.h" #include "mitkPointSet.h" #include "mitkPointSetVtkMapper2D.h" #include "mitkPointSetVtkMapper3D.h" #include "mitkProperties.h" #include "mitkPropertyList.h" #include "mitkSlicedGeometry3D.h" #include "mitkSmartPointerProperty.h" #include "mitkStringProperty.h" #include "mitkSurface.h" #include "mitkSurface.h" #include "mitkSurfaceVtkMapper2D.h" #include "mitkSurfaceVtkMapper3D.h" #include "mitkTimeGeometry.h" #include "mitkTransferFunctionProperty.h" #include "mitkVtkInterpolationProperty.h" #include "mitkVtkRepresentationProperty.h" #include "mitkVtkResliceInterpolationProperty.h" #include // Legacy Support: #include #include #include +#include +#include + void mitk::CoreObjectFactory::RegisterExtraFactory(CoreObjectFactoryBase *factory) { MITK_DEBUG << "CoreObjectFactory: registering extra factory of type " << factory->GetNameOfClass(); m_ExtraFactories.insert(CoreObjectFactoryBase::Pointer(factory)); // Register Legacy Reader and Writer this->RegisterLegacyReaders(factory); this->RegisterLegacyWriters(factory); } void mitk::CoreObjectFactory::UnRegisterExtraFactory(CoreObjectFactoryBase *factory) { MITK_DEBUG << "CoreObjectFactory: un-registering extra factory of type " << factory->GetNameOfClass(); this->UnRegisterLegacyWriters(factory); this->UnRegisterLegacyReaders(factory); try { m_ExtraFactories.erase(factory); } catch ( const std::exception &e ) { MITK_ERROR << "Caught exception while unregistering: " << e.what(); } } mitk::CoreObjectFactory::Pointer mitk::CoreObjectFactory::GetInstance() { static mitk::CoreObjectFactory::Pointer instance; if (instance.IsNull()) { instance = mitk::CoreObjectFactory::New(); } return instance; } mitk::CoreObjectFactory::~CoreObjectFactory() { for (auto iter = m_LegacyReaders.begin(); iter != m_LegacyReaders.end(); ++iter) { for (auto &elem : iter->second) { delete elem; } } for (auto iter = m_LegacyWriters.begin(); iter != m_LegacyWriters.end(); ++iter) { for (auto &elem : iter->second) { delete elem; } } } void mitk::CoreObjectFactory::SetDefaultProperties(mitk::DataNode *node) { if (node == nullptr) return; mitk::DataNode::Pointer nodePointer = node; - mitk::Image::Pointer image = dynamic_cast(node->GetData()); - if (image.IsNotNull() && image->IsInitialized()) + mitk::Image* image = dynamic_cast(node->GetData()); + if (nullptr != image && image->IsInitialized()) { mitk::ImageVtkMapper2D::SetDefaultProperties(node); } - mitk::PlaneGeometryData::Pointer planeGeometry = dynamic_cast(node->GetData()); - if (planeGeometry.IsNotNull()) + if (nullptr != dynamic_cast(node->GetData())) { mitk::PlaneGeometryDataMapper2D::SetDefaultProperties(node); } - mitk::Surface::Pointer surface = dynamic_cast(node->GetData()); - if (surface.IsNotNull()) + if (nullptr != dynamic_cast(node->GetData())) { mitk::SurfaceVtkMapper2D::SetDefaultProperties(node); mitk::SurfaceVtkMapper3D::SetDefaultProperties(node); } - mitk::PointSet::Pointer pointSet = dynamic_cast(node->GetData()); - if (pointSet.IsNotNull()) + if (nullptr != dynamic_cast(node->GetData())) { mitk::PointSetVtkMapper2D::SetDefaultProperties(node); mitk::PointSetVtkMapper3D::SetDefaultProperties(node); } + + if (nullptr != dynamic_cast(node->GetData())) + { + mitk::CrosshairVtkMapper2D::SetDefaultProperties(node); + } + for (auto it = m_ExtraFactories.begin(); it != m_ExtraFactories.end(); ++it) { (*it)->SetDefaultProperties(node); } } mitk::CoreObjectFactory::CoreObjectFactory() { static bool alreadyDone = false; if (!alreadyDone) { CreateFileExtensionsMap(); // RegisterLegacyReaders(this); // RegisterLegacyWriters(this); alreadyDone = true; } } mitk::Mapper::Pointer mitk::CoreObjectFactory::CreateMapper(mitk::DataNode *node, MapperSlotId id) { mitk::Mapper::Pointer newMapper = nullptr; mitk::Mapper::Pointer tmpMapper = nullptr; // check whether extra factories provide mapper for (auto it = m_ExtraFactories.begin(); it != m_ExtraFactories.end(); ++it) { tmpMapper = (*it)->CreateMapper(node, id); if (tmpMapper.IsNotNull()) newMapper = tmpMapper; } if (newMapper.IsNull()) { mitk::BaseData *data = node->GetData(); if (id == mitk::BaseRenderer::Standard2D) { if ((dynamic_cast(data) != nullptr)) { newMapper = mitk::ImageVtkMapper2D::New(); newMapper->SetDataNode(node); } else if ((dynamic_cast(data) != nullptr)) { newMapper = mitk::PlaneGeometryDataMapper2D::New(); newMapper->SetDataNode(node); } else if ((dynamic_cast(data) != nullptr)) { newMapper = mitk::SurfaceVtkMapper2D::New(); // cast because SetDataNode is not virtual auto *castedMapper = dynamic_cast(newMapper.GetPointer()); castedMapper->SetDataNode(node); } else if ((dynamic_cast(data) != nullptr)) { newMapper = mitk::PointSetVtkMapper2D::New(); newMapper->SetDataNode(node); } } else if (id == mitk::BaseRenderer::Standard3D) { if ((dynamic_cast(data) != nullptr)) { newMapper = mitk::PlaneGeometryDataVtkMapper3D::New(); newMapper->SetDataNode(node); } else if ((dynamic_cast(data) != nullptr)) { newMapper = mitk::SurfaceVtkMapper3D::New(); newMapper->SetDataNode(node); } else if ((dynamic_cast(data) != nullptr)) { newMapper = mitk::PointSetVtkMapper3D::New(); newMapper->SetDataNode(node); } } } return newMapper; } std::string mitk::CoreObjectFactory::GetFileExtensions() { MultimapType aMap; for (auto it = m_ExtraFactories.begin(); it != m_ExtraFactories.end(); ++it) { aMap = (*it)->GetFileExtensionsMap(); this->MergeFileExtensions(m_FileExtensionsMap, aMap); } this->CreateFileExtensions(m_FileExtensionsMap, m_FileExtensions); return m_FileExtensions.c_str(); } void mitk::CoreObjectFactory::MergeFileExtensions(MultimapType &fileExtensionsMap, MultimapType inputMap) { std::pair pairOfIter; for (auto it = inputMap.begin(); it != inputMap.end(); ++it) { bool duplicateFound = false; pairOfIter = fileExtensionsMap.equal_range((*it).first); for (auto it2 = pairOfIter.first; it2 != pairOfIter.second; ++it2) { // cout << " [" << (*it).first << ", " << (*it).second << "]" << endl; std::string aString = (*it2).second; if (aString.compare((*it).second) == 0) { // cout << " DUP!! [" << (*it).first << ", " << (*it).second << "]" << endl; duplicateFound = true; break; } } if (!duplicateFound) { fileExtensionsMap.insert(std::pair((*it).first, (*it).second)); } } } mitk::CoreObjectFactoryBase::MultimapType mitk::CoreObjectFactory::GetFileExtensionsMap() { return m_FileExtensionsMap; } void mitk::CoreObjectFactory::CreateFileExtensionsMap() { /* m_FileExtensionsMap.insert(std::pair("*.dcm", "DICOM files")); m_FileExtensionsMap.insert(std::pair("*.DCM", "DICOM files")); m_FileExtensionsMap.insert(std::pair("*.dc3", "DICOM files")); m_FileExtensionsMap.insert(std::pair("*.DC3", "DICOM files")); m_FileExtensionsMap.insert(std::pair("*.gdcm", "DICOM files")); m_FileExtensionsMap.insert(std::pair("*.seq", "DKFZ Pic")); m_FileExtensionsMap.insert(std::pair("*.seq.gz", "DKFZ Pic")); m_FileExtensionsMap.insert(std::pair("*.dcm", "Sets of 2D slices")); m_FileExtensionsMap.insert(std::pair("*.gdcm", "Sets of 2D slices")); */ } std::string mitk::CoreObjectFactory::GetSaveFileExtensions() { MultimapType aMap; for (auto it = m_ExtraFactories.begin(); it != m_ExtraFactories.end(); ++it) { aMap = (*it)->GetSaveFileExtensionsMap(); this->MergeFileExtensions(m_SaveFileExtensionsMap, aMap); } this->CreateFileExtensions(m_SaveFileExtensionsMap, m_SaveFileExtensions); return m_SaveFileExtensions.c_str(); } mitk::CoreObjectFactoryBase::MultimapType mitk::CoreObjectFactory::GetSaveFileExtensionsMap() { return m_SaveFileExtensionsMap; } mitk::CoreObjectFactory::FileWriterList mitk::CoreObjectFactory::GetFileWriters() { FileWriterList allWriters = m_FileWriters; // sort to merge lists later on typedef std::set FileWriterSet; FileWriterSet fileWritersSet; fileWritersSet.insert(allWriters.begin(), allWriters.end()); // collect all extra factories for (auto it = m_ExtraFactories.begin(); it != m_ExtraFactories.end(); ++it) { FileWriterList list2 = (*it)->GetFileWriters(); // add them to the sorted set fileWritersSet.insert(list2.begin(), list2.end()); } // write back to allWriters to return a list allWriters.clear(); allWriters.insert(allWriters.end(), fileWritersSet.begin(), fileWritersSet.end()); return allWriters; } void mitk::CoreObjectFactory::MapEvent(const mitk::Event *, const int) { } std::string mitk::CoreObjectFactory::GetDescriptionForExtension(const std::string &extension) { std::multimap fileExtensionMap = GetSaveFileExtensionsMap(); for (auto it = fileExtensionMap.begin(); it != fileExtensionMap.end(); ++it) if (it->first == extension) return it->second; return ""; // If no matching extension was found, return empty string } void mitk::CoreObjectFactory::RegisterLegacyReaders(mitk::CoreObjectFactoryBase *factory) { // We are not really interested in the string, just call the method since // many readers initialize the map the first time when this method is called factory->GetFileExtensions(); std::map> extensionsByCategories; std::multimap fileExtensionMap = factory->GetFileExtensionsMap(); for (auto it = fileExtensionMap.begin(); it != fileExtensionMap.end(); ++it) { std::string extension = it->first; // remove "*." extension = extension.erase(0, 2); extensionsByCategories[it->second].push_back(extension); } for (auto &extensionsByCategorie : extensionsByCategories) { m_LegacyReaders[factory].push_back( new mitk::LegacyFileReaderService(extensionsByCategorie.second, extensionsByCategorie.first)); } } void mitk::CoreObjectFactory::UnRegisterLegacyReaders(mitk::CoreObjectFactoryBase *factory) { auto iter = m_LegacyReaders.find(factory); if (iter != m_LegacyReaders.end()) { for (auto &elem : iter->second) { delete elem; } m_LegacyReaders.erase(iter); } } void mitk::CoreObjectFactory::RegisterLegacyWriters(mitk::CoreObjectFactoryBase *factory) { // Get all external Writers mitk::CoreObjectFactory::FileWriterList writers = factory->GetFileWriters(); // We are not really interested in the string, just call the method since // many writers initialize the map the first time when this method is called factory->GetSaveFileExtensions(); MultimapType fileExtensionMap = factory->GetSaveFileExtensionsMap(); for (auto it = writers.begin(); it != writers.end(); ++it) { std::vector extensions = (*it)->GetPossibleFileExtensions(); if (extensions.empty()) continue; std::string description; for (auto ext = extensions.begin(); ext != extensions.end(); ++ext) { if (ext->empty()) continue; std::string extension = *ext; std::string extensionWithStar = extension; if (extension.find_first_of('*') == 0) { // remove "*." extension = extension.substr(0, extension.size() - 2); } else { extensionWithStar.insert(extensionWithStar.begin(), '*'); } for (auto fileExtensionIter = fileExtensionMap.begin(); fileExtensionIter != fileExtensionMap.end(); ++fileExtensionIter) { if (fileExtensionIter->first == extensionWithStar) { description = fileExtensionIter->second; break; } } if (!description.empty()) break; } if (description.empty()) { description = std::string("Legacy ") + (*it)->GetNameOfClass() + " Reader"; } mitk::FileWriter::Pointer fileWriter(it->GetPointer()); mitk::LegacyFileWriterService *lfws = new mitk::LegacyFileWriterService(fileWriter, description); m_LegacyWriters[factory].push_back(lfws); } } void mitk::CoreObjectFactory::UnRegisterLegacyWriters(mitk::CoreObjectFactoryBase *factory) { auto iter = m_LegacyWriters.find(factory); if (iter != m_LegacyWriters.end()) { for (auto &elem : iter->second) { delete elem; } m_LegacyWriters.erase(iter); } } diff --git a/Modules/QtWidgets/CMakeLists.txt b/Modules/QtWidgets/CMakeLists.txt index 8800bae003..8d685ebd5c 100644 --- a/Modules/QtWidgets/CMakeLists.txt +++ b/Modules/QtWidgets/CMakeLists.txt @@ -1,8 +1,9 @@ MITK_CREATE_MODULE( INCLUDE_DIRS PRIVATE resource # for xpm includes DEPENDS MitkPlanarFigure MitkAnnotation PACKAGE_DEPENDS PUBLIC VTK|GUISupportQt+RenderingQt Qt5|Widgets+OpenGL+Core CTK|CTKWidgets + PRIVATE nlohmann_json ) add_subdirectory(test) diff --git a/Modules/QtWidgets/files.cmake b/Modules/QtWidgets/files.cmake index cc23808ef6..35d8f96e42 100644 --- a/Modules/QtWidgets/files.cmake +++ b/Modules/QtWidgets/files.cmake @@ -1,174 +1,185 @@ 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 QmitkMimeTypes.cpp QmitkMultiNodeSelectionWidget.cpp QmitkMultiWidgetConfigurationToolBar.cpp QmitkMultiWidgetLayoutManager.cpp QmitkMultiWidgetLayoutSelectionWidget.cpp QmitkNodeDescriptor.cpp QmitkNodeSelectionButton.cpp QmitkNodeSelectionConstants.cpp QmitkNodeSelectionDialog.cpp QmitkNodeSelectionListItemWidget.cpp QmitkNodeSelectionPreferenceHelper.cpp QmitkNodeDescriptor.cpp QmitkColoredNodeDescriptor.cpp QmitkNodeDescriptorManager.cpp QmitkProgressBar.cpp QmitkPropertiesTableEditor.cpp QmitkPropertiesTableModel.cpp QmitkPropertyDelegate.cpp QmitkRegisterClasses.cpp QmitkRenderingManager.cpp QmitkRenderWindowDataStorageTreeModel.cpp QmitkRenderingManagerFactory.cpp QmitkRenderWindow.cpp QmitkRenderWindowMenu.cpp QmitkRenderWindowUtilityWidget.cpp QmitkRenderWindowWidget.cpp QmitkRenderWindowContextDataStorageInspector.cpp mitkRenderWindowLayerController.cpp mitkRenderWindowLayerUtilities.cpp mitkRenderWindowViewDirectionController.cpp QmitkServiceListWidget.cpp QmitkSingleNodeSelectionWidget.cpp QmitkSliceNavigationWidget.cpp QmitkSliderLevelWindowWidget.cpp QmitkStdMultiWidget.cpp QmitkStepperAdapter.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 QmitkSimpleTextOverlayWidget.cpp QmitkButtonOverlayWidget.cpp QmitkNodeDetailsDialog.cpp + QmitkRenderWindowDataNodeTableModel.cpp + QmitkSynchronizedNodeSelectionWidget.cpp + QmitkSynchronizedWidgetConnector.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/QmitkMultiNodeSelectionWidget.h include/QmitkMultiWidgetConfigurationToolBar.h include/QmitkMultiWidgetLayoutManager.h include/QmitkMultiWidgetLayoutSelectionWidget.h include/QmitkNodeDescriptor.h include/QmitkNodeSelectionButton.h include/QmitkNodeSelectionDialog.h include/QmitkNodeSelectionListItemWidget.h include/QmitkColoredNodeDescriptor.h include/QmitkNodeDescriptorManager.h include/QmitkProgressBar.h include/QmitkPropertiesTableEditor.h include/QmitkPropertyDelegate.h include/QmitkRenderingManager.h include/QmitkRenderWindow.h include/QmitkRenderWindowDataStorageTreeModel.h include/QmitkRenderWindowMenu.h include/QmitkRenderWindowUtilityWidget.h include/QmitkRenderWindowWidget.h include/QmitkRenderWindowContextDataStorageInspector.h include/mitkRenderWindowLayerController.h include/mitkRenderWindowLayerUtilities.h include/mitkRenderWindowViewDirectionController.h include/QmitkServiceListWidget.h include/QmitkSingleNodeSelectionWidget.h include/QmitkSliceNavigationWidget.h include/QmitkSliderLevelWindowWidget.h include/QmitkStdMultiWidget.h include/QmitkMxNMultiWidget.h include/QmitkStepperAdapter.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/QmitkSimpleTextOverlayWidget.h include/QmitkButtonOverlayWidget.h include/QmitkNodeDetailsDialog.h + include/QmitkRenderWindowDataNodeTableModel.h + include/QmitkSynchronizedNodeSelectionWidget.h + include/QmitkSynchronizedWidgetConnector.h ) set(UI_FILES src/QmitkFileReaderOptionsDialog.ui src/QmitkFileWriterOptionsDialog.ui src/QmitkLevelWindowPresetDefinition.ui src/QmitkLevelWindowWidget.ui src/QmitkLevelWindowRangeChange.ui src/QmitkMemoryUsageIndicator.ui src/QmitkMultiNodeSelectionWidget.ui src/QmitkMultiWidgetLayoutSelectionWidget.ui src/QmitkNodeSelectionDialog.ui src/QmitkNodeSelectionListItemWidget.ui src/QmitkRenderWindowContextDataStorageInspector.ui src/QmitkServiceListWidgetControls.ui src/QmitkSingleNodeSelectionWidget.ui src/QmitkSliceNavigationWidget.ui src/QmitkDataStorageListInspector.ui src/QmitkDataStorageTreeInspector.ui src/QmitkDataStorageSelectionHistoryInspector.ui + src/QmitkSynchronizedNodeSelectionWidget.ui +) + +set(RESOURCE_FILES + mxnLayout_twoRowsEachDirection.json ) set(QRC_FILES resource/Qmitk.qrc ) diff --git a/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h index a3ee95aece..fda201043c 100644 --- a/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h @@ -1,258 +1,266 @@ /*============================================================================ 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 QmitkAbstractNodeSelectionWidget_h #define QmitkAbstractNodeSelectionWidget_h #include #include #include #include #include class QmitkAbstractDataStorageModel; /** * \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; /** Other node container type often used in the code base.*/ using ConstNodeStdVector = std::vector; /** Returns the selected nodes, as emitted with CurrentSelectionChanged*/ NodeList GetSelectedNodes() const; /** Convinience method that returns the selected nodes as ConstNodeStdVector. This is a type also often used in the mitk code base.*/ ConstNodeStdVector GetSelectedNodesStdVector() 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 (a selection) 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 desired. */ 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 desired. */ void SetEmptyInfo(QString info); /** Set the caption of the popup that is displayed to alter the selection. * The string can contain HTML code, if desired. */ 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 desired. */ 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. + * variable. This member is called directly before the node will be removed from the current selection. + * Default implementation does nothing. + * Derived widgets can override the method if they want to handle to-be-removed nodes before. */ virtual void OnNodeRemovedFromStorage(const mitk::DataNode* node); + /** Method is called when a node is modified. The modified node is passed as 'caller' variable. + * Default implementation handles changes that are related to the node predicate: + * - If the node does not fit the node predicate anymore, it will be removed. + * - If the node was part of the external selection and now fits the node predicate, + * a new selection is compiled and emitted. + * Derived widgets can override the method if they want to react on modified nodes. + */ + virtual void OnNodeModified(const itk::Object* caller, const itk::EventObject& event); + /** 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 diff --git a/Modules/QtWidgets/include/QmitkMultiWidgetConfigurationToolBar.h b/Modules/QtWidgets/include/QmitkMultiWidgetConfigurationToolBar.h index bcbbe11acd..18bb853e67 100644 --- a/Modules/QtWidgets/include/QmitkMultiWidgetConfigurationToolBar.h +++ b/Modules/QtWidgets/include/QmitkMultiWidgetConfigurationToolBar.h @@ -1,66 +1,70 @@ /*============================================================================ 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 QmitkMultiWidgetConfigurationToolBar_h #define QmitkMultiWidgetConfigurationToolBar_h #include "MitkQtWidgetsExports.h" #include +#include + // qt #include class QmitkAbstractMultiWidget; class QmitkMultiWidgetLayoutSelectionWidget; /** * @brief * * */ class MITKQTWIDGETS_EXPORT QmitkMultiWidgetConfigurationToolBar : public QToolBar { Q_OBJECT public: QmitkMultiWidgetConfigurationToolBar(QmitkAbstractMultiWidget* multiWidget); ~QmitkMultiWidgetConfigurationToolBar() override; Q_SIGNALS: void LayoutSet(int row, int column); + void SaveLayout(std::ostream* outStream); + void LoadLayout(const nlohmann::json* jsonData); void Synchronized(bool synchronized); void InteractionSchemeChanged(mitk::InteractionSchemeSwitcher::InteractionScheme scheme); protected Q_SLOTS: void OnSetLayout(); void OnSynchronize(); void OnInteractionSchemeChanged(); private: void InitializeToolBar();; void AddButtons(); QmitkAbstractMultiWidget* m_MultiWidget; QAction* m_SynchronizeAction; QAction* m_InteractionSchemeChangeAction; QmitkMultiWidgetLayoutSelectionWidget* m_LayoutSelectionPopup; }; #endif diff --git a/Modules/QtWidgets/include/QmitkMultiWidgetLayoutSelectionWidget.h b/Modules/QtWidgets/include/QmitkMultiWidgetLayoutSelectionWidget.h index b741d09378..14d9fed068 100644 --- a/Modules/QtWidgets/include/QmitkMultiWidgetLayoutSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkMultiWidgetLayoutSelectionWidget.h @@ -1,54 +1,65 @@ /*============================================================================ 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 QmitkMultiWidgetLayoutSelectionWidget_h #define QmitkMultiWidgetLayoutSelectionWidget_h #include "MitkQtWidgetsExports.h" #include "ui_QmitkMultiWidgetLayoutSelectionWidget.h" +#include + // qt #include "QWidget" /** * @brief * * */ class MITKQTWIDGETS_EXPORT QmitkMultiWidgetLayoutSelectionWidget : public QWidget { Q_OBJECT public: QmitkMultiWidgetLayoutSelectionWidget(QWidget* parent = nullptr); Q_SIGNALS: void LayoutSet(int row, int column); + // needs to be connected via Qt::DirectConnection (usually default), to ensure the stream pointers validity + void SaveLayout(std::ostream* outStream); + + void LoadLayout(const nlohmann::json* jsonData); + private Q_SLOTS: void OnTableItemSelectionChanged(); void OnSetLayoutButtonClicked(); + void OnSaveLayoutButtonClicked(); + void OnLoadLayoutButtonClicked(); + void OnLayoutPresetSelected(int index); private: void Init(); Ui::QmitkMultiWidgetLayoutSelectionWidget ui; + std::map m_PresetMap; }; #endif diff --git a/Modules/QtWidgets/include/QmitkMxNMultiWidget.h b/Modules/QtWidgets/include/QmitkMxNMultiWidget.h index dbc9613c5d..702191a81e 100644 --- a/Modules/QtWidgets/include/QmitkMxNMultiWidget.h +++ b/Modules/QtWidgets/include/QmitkMxNMultiWidget.h @@ -1,119 +1,135 @@ /*============================================================================ 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 QmitkMxNMultiWidget_h #define QmitkMxNMultiWidget_h -// qt widgets module #include "MitkQtWidgetsExports.h" + +// qt widgets module #include "QmitkAbstractMultiWidget.h" +#include +#include + +#include + +class QSplitter; /** * @brief The 'QmitkMxNMultiWidget' is a 'QmitkAbstractMultiWidget' that is used to display multiple render windows at once. * Render windows can dynamically be added and removed to change the layout of the multi widget. This * is done by using the 'SetLayout'-function to define a layout. This will automatically add or remove * the appropriate number of render window widgets. */ class MITKQTWIDGETS_EXPORT QmitkMxNMultiWidget : public QmitkAbstractMultiWidget { Q_OBJECT public: QmitkMxNMultiWidget(QWidget* parent = nullptr, Qt::WindowFlags f = 0, const QString& multiWidgetName = "mxnmulti"); ~QmitkMxNMultiWidget(); void InitializeMultiWidget() override; void Synchronize(bool synchronized) override; QmitkRenderWindow* GetRenderWindow(const QString& widgetName) const override; QmitkRenderWindow* GetRenderWindow(const mitk::AnatomicalPlane& orientation) const override; void SetActiveRenderWindowWidget(RenderWindowWidgetPointer activeRenderWindowWidget) override; /** * @brief Initialize the active render windows of the MxNMultiWidget to the given geometry. * * @param geometry The geometry to be used to initialize / update the * active render window's time and slice navigation controller. * @param resetCamera If true, the camera and crosshair will be reset to the default view (centered, no zoom). * If false, the current crosshair position and the camera zoom will be stored and reset * after the reference geometry has been updated. */ void InitializeViews(const mitk::TimeGeometry* geometry, bool resetCamera) override; /** * @brief Forward the given time geometry to all base renderers, so that they can store it as their * interaction reference geometry. * This will update the alignment status of the reference geometry for each base renderer. * For more details, see 'BaseRenderer::SetInteractionReferenceGeometry'. * Overridem from 'QmitkAbstractMultiWidget'. */ void SetInteractionReferenceGeometry(const mitk::TimeGeometry* referenceGeometry) override; /** * @brief Returns true if the render windows are coupled; false if not. * * For the MxNMultiWidget the render windows are typically decoupled. */ bool HasCoupledRenderWindows() const override; void SetSelectedPosition(const mitk::Point3D& newPosition, const QString& widgetName) override; const mitk::Point3D GetSelectedPosition(const QString& widgetName) const override; void SetCrosshairVisibility(bool visible) override; bool GetCrosshairVisibility() const override; void SetCrosshairGap(unsigned int gapSize) override; void ResetCrosshair() override; void SetWidgetPlaneMode(int userMode) override; mitk::SliceNavigationController* GetTimeNavigationController(); - void AddPlanesToDataStorage(); - void RemovePlanesFromDataStorage(); + void EnableCrosshair(); + void DisableCrosshair(); public Q_SLOTS: // mouse events void wheelEvent(QWheelEvent* e) override; void mousePressEvent(QMouseEvent* e) override; void moveEvent(QMoveEvent* e) override; + void LoadLayout(const nlohmann::json* jsonData); + void SaveLayout(std::ostream* outStream); Q_SIGNALS: void WheelMoved(QWheelEvent *); void Moved(); + void UpdateUtilityWidgetViewPlanes(); protected: void RemoveRenderWindowWidget() override; private: void SetLayoutImpl() override; void SetInteractionSchemeImpl() override { } - void CreateRenderWindowWidget(); + QmitkAbstractMultiWidget::RenderWindowWidgetPointer CreateRenderWindowWidget(); + void SetInitialSelection(); + void ToggleSynchronization(QmitkSynchronizedNodeSelectionWidget* synchronizedWidget); + + static nlohmann::json BuildJSONFromLayout(const QSplitter* splitter); + QSplitter* BuildLayoutFromJSON(const nlohmann::json* jsonData, unsigned int* windowCounter, QSplitter* parentSplitter = nullptr); mitk::SliceNavigationController* m_TimeNavigationController; + std::unique_ptr m_SynchronizedWidgetConnector; bool m_CrosshairVisibility; }; #endif diff --git a/Modules/QtWidgets/include/QmitkRenderWindowContextDataStorageInspector.h b/Modules/QtWidgets/include/QmitkRenderWindowContextDataStorageInspector.h index 2434015247..f4d17a5f8d 100644 --- a/Modules/QtWidgets/include/QmitkRenderWindowContextDataStorageInspector.h +++ b/Modules/QtWidgets/include/QmitkRenderWindowContextDataStorageInspector.h @@ -1,90 +1,88 @@ /*============================================================================ 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 QmitkRenderWindowContextDataStorageInspector_h #define QmitkRenderWindowContextDataStorageInspector_h // qt widgets module #include "MitkQtWidgetsExports.h" #include "ui_QmitkRenderWindowContextDataStorageInspector.h" // render window manager module #include #include // qt widgets module #include // mitk core #include /** * The 'QmitkRenderWindowContextDataStorageInspector' offers a GUI to manipulate the base renderer / render windows of the MITK workbench. * * In order to use this widget, a (e.g.) plugin has to set the controlled renderer, which will be forwarded to * a render window view direction controller. */ class MITKQTWIDGETS_EXPORT QmitkRenderWindowContextDataStorageInspector : public QmitkAbstractDataStorageInspector { Q_OBJECT public: QmitkRenderWindowContextDataStorageInspector(QWidget* parent = nullptr, mitk::BaseRenderer* renderer = nullptr); // override from 'QmitkAbstractDataStorageInspector' /** * @brief See 'QmitkAbstractDataStorageInspector' */ QAbstractItemView* GetView() override; /** * @brief See 'QmitkAbstractDataStorageInspector' */ const QAbstractItemView* GetView() const override; /** * @brief See 'QmitkAbstractDataStorageInspector' */ void SetSelectionMode(SelectionMode mode) override; /** * @brief See 'QmitkAbstractDataStorageInspector' */ SelectionMode GetSelectionMode() const override; QItemSelectionModel* GetDataNodeSelectionModel() const; Q_SIGNALS: void ReinitAction(QList selectedNodes); void ResetAction(QList selectedNodes); private Q_SLOTS: void ModelRowsInserted(const QModelIndex& parent, int start, int end); - void ResetRenderer(); - void OnContextMenuRequested(const QPoint& pos); void OnReinit(); void OnReset(); private: void Initialize() override; void SetUpConnections(); Ui::QmitkRenderWindowContextDataStorageInspector m_Controls; std::unique_ptr m_StorageModel; std::unique_ptr m_RenderWindowLayerController; }; #endif diff --git a/Modules/QtWidgets/include/QmitkRenderWindowDataNodeTableModel.h b/Modules/QtWidgets/include/QmitkRenderWindowDataNodeTableModel.h new file mode 100644 index 0000000000..e0c8d3f4b0 --- /dev/null +++ b/Modules/QtWidgets/include/QmitkRenderWindowDataNodeTableModel.h @@ -0,0 +1,87 @@ +/*============================================================================ + +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 QmitkRenderWindowDataNodeTableModel_h +#define QmitkRenderWindowDataNodeTableModel_h + +#include + +//mitk core +#include +#include +#include +#include + +// qt widgets module +#include +#include +#include + +#include + +/* +* @brief The 'QmitkRenderWindowDataNodeTableModel' is a table model that extends the 'QAbstractItemModel'. +*/ +class MITKQTWIDGETS_EXPORT QmitkRenderWindowDataNodeTableModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + + QmitkRenderWindowDataNodeTableModel(QObject* parent = nullptr); + + void UpdateModelData(); + + void SetDataStorage(mitk::DataStorage* dataStorage); + void SetCurrentRenderer(mitk::BaseRenderer* baseRenderer); + mitk::BaseRenderer::Pointer GetCurrentRenderer() const; + + using NodeList = QList; + void SetCurrentSelection(NodeList selectedNodes); + NodeList GetCurrentSelection() const; + + // override from 'QAbstractItemModel' + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + QVariant data(const QModelIndex& index, int role) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + Qt::DropActions supportedDropActions() const override; + Qt::DropActions supportedDragActions() const override; + QStringList mimeTypes() const override; + QMimeData* mimeData(const QModelIndexList& indexes) const override; + + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; + +Q_SIGNALS: + + void ModelUpdated(); + +private: + + std::unique_ptr m_RenderWindowLayerController; + mitk::WeakPointer m_BaseRenderer; + NodeList m_CurrentSelection; + + QIcon m_VisibleIcon; + QIcon m_InvisibleIcon; + QIcon m_ArrowIcon; + QIcon m_TimesIcon; +}; + +#endif diff --git a/Modules/QtWidgets/include/QmitkRenderWindowDataStorageTreeModel.h b/Modules/QtWidgets/include/QmitkRenderWindowDataStorageTreeModel.h index 6fae6f6630..b29ac1e636 100644 --- a/Modules/QtWidgets/include/QmitkRenderWindowDataStorageTreeModel.h +++ b/Modules/QtWidgets/include/QmitkRenderWindowDataStorageTreeModel.h @@ -1,132 +1,131 @@ /*============================================================================ 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 QmitkRenderWindowDataStorageTreeModel_h #define QmitkRenderWindowDataStorageTreeModel_h // render window manager UI model #include "MitkQtWidgetsExports.h" // render window manager module #include "mitkRenderWindowLayerController.h" #include "mitkRenderWindowLayerUtilities.h" //mitk core #include // qt widgets module #include #include /* * @brief The 'QmitkRenderWindowDataStorageTreeModel' is a tree model derived from the 'QmitkAbstractDataStorageModel'. */ class MITKQTWIDGETS_EXPORT QmitkRenderWindowDataStorageTreeModel : public QmitkAbstractDataStorageModel { Q_OBJECT public: QmitkRenderWindowDataStorageTreeModel(QObject* parent = nullptr); // override from 'QmitkAbstractDataStorageModel' /** * @brief See 'QmitkAbstractDataStorageModel' */ void DataStorageChanged() override; /** * @brief See 'QmitkAbstractDataStorageModel' */ void NodePredicateChanged() override; /** * @brief See 'QmitkAbstractDataStorageModel' */ void NodeAdded(const mitk::DataNode* node) override; /** * @brief See 'QmitkAbstractDataStorageModel' */ void NodeChanged(const mitk::DataNode* node) override; /** * @brief See 'QmitkAbstractDataStorageModel' */ void NodeRemoved(const mitk::DataNode* node) override; // override from 'QAbstractItemModel' QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex& parent) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; Qt::DropActions supportedDropActions() const override; Qt::DropActions supportedDragActions() const override; QStringList mimeTypes() const override; QMimeData* mimeData(const QModelIndexList& indexes) const override; bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; void SetControlledRenderer(mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer); void SetCurrentRenderer(mitk::BaseRenderer* baseRenderer); mitk::BaseRenderer::Pointer GetCurrentRenderer() const; private: void ResetTree(); void UpdateModelData(); /** * @brief Adjust the layer property according to the current tree. * The function will set the "layer" property of each underlying data node so that it fits the * the actual hierarchy represented by the current tree. */ void AdjustLayerProperty(); /** * @brief Fill a vector of tree items in a depth-first order (child-first). */ void TreeToVector(QmitkDataStorageTreeModelInternalItem* parent, std::vector& treeAsVector) const; /** * @brief Add the given data node to the tree of the given renderer. * The given renderer specifies the "layer"-property that is used for adding the new tree item * to the tree. The "layer"-property may be different for each renderer resulting in a * different tree for each renderer. * * @param dataNode The data node that should be added. * @param renderer The base renderer to which the data node should be added. */ void AddNodeInternal(const mitk::DataNode* dataNode, const mitk::BaseRenderer* renderer); /** * @brief Remove the tree item that contains the given data node. Removing an item may * leave the child items of the removed item without a parent. In this case * the children have to be moved inside the tree so the tree has to be rebuild * according to the current status of the data storage. * * @param dataNode The data node that should be removed. */ void RemoveNodeInternal(const mitk::DataNode* dataNode); mitk::DataNode* GetParentNode(const mitk::DataNode* node) const; QmitkDataStorageTreeModelInternalItem* GetItemByIndex(const QModelIndex& index) const; QModelIndex GetIndexByItem(QmitkDataStorageTreeModelInternalItem* item) const; std::unique_ptr m_RenderWindowLayerController; - mitk::RenderWindowLayerUtilities::RendererVector m_ControlledRenderer; QmitkDataStorageTreeModelInternalItem* m_Root; mitk::WeakPointer m_BaseRenderer; }; #endif diff --git a/Modules/QtWidgets/include/QmitkRenderWindowUtilityWidget.h b/Modules/QtWidgets/include/QmitkRenderWindowUtilityWidget.h index 05cde2fb5e..8df33517f5 100644 --- a/Modules/QtWidgets/include/QmitkRenderWindowUtilityWidget.h +++ b/Modules/QtWidgets/include/QmitkRenderWindowUtilityWidget.h @@ -1,70 +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 QmitkRenderWindowUtilityWidget_h #define QmitkRenderWindowUtilityWidget_h -// qt widgets module #include "MitkQtWidgetsExports.h" -#include "QmitkSliceNavigationWidget.h" -#include "QmitkStepperAdapter.h" -#include "QmitkRenderWindow.h" -#include "QmitkRenderWindowContextDataStorageInspector.h" -#include "mitkRenderWindowViewDirectionController.h" -// mitk core -#include "mitkDataStorage.h" +// qt widgets module +#include +#include +#include +#include +#include // qt #include #include #include #include +namespace mitk +{ + class DataStorage; +} + +class QmitkRenderWindow; + class MITKQTWIDGETS_EXPORT QmitkRenderWindowUtilityWidget : public QWidget { Q_OBJECT public: + QmitkRenderWindowUtilityWidget( QWidget* parent = nullptr, QmitkRenderWindow* renderWindow = nullptr, mitk::DataStorage* dataStorage = nullptr ); ~QmitkRenderWindowUtilityWidget() override; - void SetInvertedSliceNavigation(bool inverted); + void ToggleSynchronization(bool synchronized); -Q_SIGNALS: + void SetGeometry(const itk::EventObject& event); - void ReinitAction(QList selectedNodes); - void ResetAction(QList selectedNodes); +public Q_SLOTS: + void UpdateViewPlaneSelection(); -private: +Q_SIGNALS: - QHBoxLayout* m_Layout; - QMenuBar* m_MenuBar; + void SynchronizationToggled(QmitkSynchronizedNodeSelectionWidget* synchronizedWidget); - QmitkRenderWindow* m_RenderWindow; - mitk::DataStorage* m_DataStorage; +private: - QmitkRenderWindowContextDataStorageInspector* m_RenderWindowInspector; + mitk::BaseRenderer* m_BaseRenderer; + QmitkSynchronizedNodeSelectionWidget* m_NodeSelectionWidget; QmitkSliceNavigationWidget* m_SliceNavigationWidget; QmitkStepperAdapter* m_StepperAdapter; + std::unique_ptr m_RenderWindowLayerController; std::unique_ptr m_RenderWindowViewDirectionController; QComboBox* m_ViewDirectionSelector; void ChangeViewDirection(const QString& viewDirection); + }; #endif diff --git a/Modules/QtWidgets/include/QmitkRenderWindowWidget.h b/Modules/QtWidgets/include/QmitkRenderWindowWidget.h index c3420b49ef..98f734fec0 100644 --- a/Modules/QtWidgets/include/QmitkRenderWindowWidget.h +++ b/Modules/QtWidgets/include/QmitkRenderWindowWidget.h @@ -1,130 +1,123 @@ /*============================================================================ 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 QmitkRenderWindowWidget_h #define QmitkRenderWindowWidget_h -// qt widgets module #include "MitkQtWidgetsExports.h" -#include "QmitkRenderWindow.h" -#include "QmitkRenderWindowUtilityWidget.h" + +// qt widgets module +#include // mitk core #include #include #include // qt #include #include +#include class vtkCornerAnnotation; /** * @brief The 'QmitkRenderWindowWidget' is a QFrame that holds a render window * and some associates properties, e.g. decorations. * Decorations are corner annotation (text and color), frame color or background color * and can be set using this class. * The 'QmitkRenderWindowWidget' is used inside a 'QmitkAbstractMultiWidget', where a map contains * several render window widgets to create the multi widget display. * This class uses a CrosshairManager, which allows to use plane geometries as crosshair. */ class MITKQTWIDGETS_EXPORT QmitkRenderWindowWidget : public QFrame { Q_OBJECT public: QmitkRenderWindowWidget( QWidget* parent = nullptr, const QString& widgetName = "", - mitk::DataStorage* dataStorage = nullptr, - bool windowControls = false); + mitk::DataStorage* dataStorage = nullptr); ~QmitkRenderWindowWidget() override; void SetDataStorage(mitk::DataStorage* dataStorage); const QString& GetWidgetName() const { return m_WidgetName; }; QmitkRenderWindow* GetRenderWindow() const { return m_RenderWindow; }; mitk::SliceNavigationController* GetSliceNavigationController() const; void RequestUpdate(); void ForceImmediateUpdate(); + void AddUtilityWidget(QWidget* utilityWidget); + void SetGradientBackgroundColors(const mitk::Color& upper, const mitk::Color& lower); void ShowGradientBackground(bool enable); std::pair GetGradientBackgroundColors() const { return m_GradientBackgroundColors; }; bool IsGradientBackgroundOn() const; void SetDecorationColor(const mitk::Color& color); mitk::Color GetDecorationColor() const { return m_DecorationColor; }; void ShowColoredRectangle(bool show); bool IsColoredRectangleVisible() const; void ShowCornerAnnotation(bool show); bool IsCornerAnnotationVisible() const; void SetCornerAnnotationText(const std::string& cornerAnnotation); std::string GetCornerAnnotationText() const; bool IsRenderWindowMenuActivated() const; void SetCrosshairVisibility(bool visible); bool GetCrosshairVisibility(); void SetCrosshairGap(unsigned int gapSize); - void AddPlanesToDataStorage(); - void RemovePlanesFromDataStorage(); + void EnableCrosshair(); + void DisableCrosshair(); void SetCrosshairPosition(const mitk::Point3D& newPosition); mitk::Point3D GetCrosshairPosition() const; void SetGeometry(const itk::EventObject& event); void SetGeometrySlice(const itk::EventObject& event); -private Q_SLOTS: - - void OnReinitAction(QList selectedNodes); - - void OnResetAction(QList selectedNodes); +public Q_SLOTS: void OnResetGeometry(); private: void InitializeGUI(); void InitializeDecorations(); - void ComputeInvertedSliceNavigation(); void ResetGeometry(const mitk::TimeGeometry* referenceGeometry); QString m_WidgetName; QVBoxLayout* m_Layout; mitk::DataStorage* m_DataStorage; QmitkRenderWindow* m_RenderWindow; mitk::CrosshairManager::Pointer m_CrosshairManager; std::pair m_GradientBackgroundColors; mitk::Color m_DecorationColor; vtkSmartPointer m_CornerAnnotation; - QmitkRenderWindowUtilityWidget* m_UtilityWidget; - - bool m_WindowControls; - }; #endif diff --git a/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h new file mode 100644 index 0000000000..875df531b2 --- /dev/null +++ b/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h @@ -0,0 +1,100 @@ +/*============================================================================ + +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 QmitkSynchronizedNodeSelectionWidget_h +#define QmitkSynchronizedNodeSelectionWidget_h + +#include + +#include "ui_QmitkSynchronizedNodeSelectionWidget.h" + +// mitk core +#include + +// qt widgets module +#include +#include + +/* +* @brief The 'QmitkSynchronizedNodeSelectionWidget' implements the 'QmitkAbstractNodeSelectionWidget' +* by providing a table view, using a 'QmitkRenderWindowDataNodeTableModel' and extending it +* with base renderer-specific functionality. +* +* Given a base renderer, the selection widget is able to display and access render window specific properties +* of the selected nodes, making it possible to switch between a "synchronized" and "desynchronized" selection +* state. +* The widget can be used to decide if all data nodes of the data storage should be selected or +* only an individually selected set of nodes, defined by a 'QmitkNodeSelectionDialog'. +* If individual nodes are selected / removed from the selection, the widget can inform other +* 'QmitkSynchronizedNodeSelectionWidget' about the current selection, if desired. +* Additionally the widget allows to reinitialize the corresponding base renderer with a specific +* data node geometry. +*/ +class MITKQTWIDGETS_EXPORT QmitkSynchronizedNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget +{ + Q_OBJECT + +public: + + QmitkSynchronizedNodeSelectionWidget(QWidget* parent); + ~QmitkSynchronizedNodeSelectionWidget(); + + using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; + + void SetBaseRenderer(mitk::BaseRenderer* baseRenderer); + + void SetSelectAll(bool selectAll); + bool GetSelectAll() const; + void SetSynchronized(bool synchronize); + bool IsSynchronized() const; + +Q_SIGNALS: + + void SelectionModeChanged(bool selectAll); + +private Q_SLOTS: + + void OnModelUpdated(); + void OnSelectionModeChanged(bool selectAll); + void OnEditSelection(); + void OnTableClicked(const QModelIndex& index); + +protected: + + void SetUpConnections(); + void Initialize(); + + void UpdateInfo() override; + void OnDataStorageChanged() override; + void OnNodePredicateChanged() override; + void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) override; + void OnInternalSelectionChanged() override; + bool AllowEmissionOfSelection(const NodeList& emissionCandidates) const override; + void OnNodeAddedToStorage(const mitk::DataNode* node) override; + void OnNodeModified(const itk::Object* caller, const itk::EventObject& event) override; + +private: + + void ReviseSynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection); + void ReviseDesynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection); + void ReinitNode(const mitk::DataNode* dataNode); + void RemoveFromInternalSelection(mitk::DataNode* dataNode); + void DeselectNode(mitk::DataNode* dataNode); + + Ui::QmitkSynchronizedNodeSelectionWidget m_Controls; + mitk::WeakPointer m_BaseRenderer; + + std::unique_ptr m_StorageModel; + +}; + +#endif diff --git a/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h b/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h new file mode 100644 index 0000000000..ad672d5223 --- /dev/null +++ b/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h @@ -0,0 +1,150 @@ +/*============================================================================ + +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 QmitkSynchronizedWidgetConnector_h +#define QmitkSynchronizedWidgetConnector_h + +#include + +// mitk core +#include + +// mitk qt widgets +#include + +// qt +#include + +/* +* @brief This class connects different 'QmitkSynchronizedNodeSelectionWidget', such that +* they can synchronize their current node selection and their current selection mode. +* +* In order to synchronize a new node selection widget with other already connected +* node selection widgets, 'ConnectWidget(const QmitkSynchronizedNodeSelectionWidget*)' has to be used. +* In order to desynchronize a node selection widget, +* 'DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget*)' has to be used. +* If a new node selection has been connected / synchronized, +* 'SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget*' can be used to initialy set +* the current selection and the current selection mode. +* For this, both values are stored in this class internally. +*/ +class MITKQTWIDGETS_EXPORT QmitkSynchronizedWidgetConnector : public QObject +{ + Q_OBJECT + +public: + + using NodeList = QList; + + QmitkSynchronizedWidgetConnector(); + + /* + * @brief This function connects the different signals and slots of this instance and the given + * given node selection widget, such that changes to the current list of nodes + * and the selection mode can be forwarded or received. + * The connections are as follows: + * - QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged + * -> QmitkSynchronizedWidgetConnector::ChangeSelection + * - QmitkSynchronizedWidgetConnector::NodeSelectionChanged + * -> QmitkAbstractNodeSelectionWidget::SetCurrentSelection + * - QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged + * -> QmitkSynchronizedWidgetConnector::ChangeSelectionMode + * - QmitkSynchronizedWidgetConnector::SelectionModeChanged + * -> QmitkSynchronizedNodeSelectionWidget::SetSelectAll + * + * @param nodeSelectionWidget The synchronized node selection widget to be connected / synchronized. + */ + void ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; + /* + * @brief This function disconnects the different signals and slot of this instance and the given + * given node selection widget, such that changes to the current list of nodes + * and the selection mode cannot be forwarded or received anymore. + * + * @param nodeSelectionWidget The synchronized node selection widget to be disconnected / desynchronized. + */ + void DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; + /* + * @brief This function sets the current selection and the selection mode of the given node selection widget + * to the values of this instance. The required values are stored in this class internally. + * It can be used to newly initialize the given node selection widget. + * + * @param nodeSelectionWidget The synchronized node selection widget for which the + * current selection and the selection mode should be set. + */ + void SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; + /* + * @brief Get the current internal node selection. + * + * @return NodeList The current internal node selection stored as a member variable. + */ + NodeList GetNodeSelection() const; + /* + * @brief Get the current internal selection mode. + * + * @return The current internal selection mode stored as a member variable. + */ + bool GetSelectionMode() const; + +Q_SIGNALS: + /* + * @brief A signal that will be emitted by the 'ChangeSelection'-slot. + * This happens if a new selection / list of nodes is set from outside of this class, + * e.g. from a QmitkSynchronizedNodeSelectionWidget. + * This signal is connected to the 'SetCurrentSelection'-slot of each + * QmitkSynchronizedNodeSelectionWidget to propagate the new selection. + * + * @param nodes A list of data nodes that are newly selected. + */ + void NodeSelectionChanged(NodeList nodes); + /* + * @brief A signal that will be emitted by the 'ChangeSelectionMode'-slot. + * This happens if the selection mode is change from outside of this class, + * e.g. from a QmitkSynchronizedNodeSelectionWidget. + * This signal is connected to the 'SetSelectAll'-slot of each + * QmitkSynchronizedNodeSelectionWidget to propagate the selection mode. + * + * @param selectAll True, if the selection mode is changed to "select all" nodes. + * False otherwise. + */ + void SelectionModeChanged(bool selectAll); + +public Q_SLOTS: + /* + * @brief Set a new internal selection and send this new selection to connected + * QmitkSynchronizedNodeSelectionWidgets using the 'NodeSelectionChanged'-signal. + * + * This slot itself is connected to the 'CurrentSelectionChanged'-signal of each + * QmitkSynchronizedNodeSelectionWidget to receive a new selection. + * + * @param nodes A list of data nodes that are newly selected. + */ + void ChangeSelection(NodeList nodes); + /* + * @brief Set a new selection mode and send this new selection mode to connected + * QmitkSynchronizedNodeSelectionWidgets using the 'SelectionModeChanged'-signal. + * + * This slot itself is connected to the 'SelectionModeChanged'-signal of each + * QmitkSynchronizedNodeSelectionWidget to receive a new selection mode. + * + * @param selectAll True, if the selection mode is changed to "select all" nodes. + * False otherwise. + */ + void ChangeSelectionMode(bool selectAll); + +private: + + NodeList m_InternalSelection; + bool m_SelectAll; + +}; + +#endif diff --git a/Modules/QtWidgets/include/mitkRenderWindowLayerController.h b/Modules/QtWidgets/include/mitkRenderWindowLayerController.h index 86a48c5e75..7df2a26072 100644 --- a/Modules/QtWidgets/include/mitkRenderWindowLayerController.h +++ b/Modules/QtWidgets/include/mitkRenderWindowLayerController.h @@ -1,174 +1,115 @@ /*============================================================================ 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 mitkRenderWindowLayerController_h #define mitkRenderWindowLayerController_h // qt widgets module #include "MitkQtWidgetsExports.h" #include "mitkRenderWindowLayerUtilities.h" // mitk core #include #include #include namespace mitk { /** - * The RenderWindowLayerController is used to manipulate the 'layer', 'fixedLayer' and 'visible' property of a given data node. + * The RenderWindowLayerController is used to manipulate the 'layer' and 'visible' property of a given data node. * The 'layer' property is used to denote the layer level of a data node. Data from nodes on higher layer level are rendered * on top of data from nodes on lower layer level. It can be changed using the 'MoveNode*'-functions. * * To view the data of a data node only in a specific renderer, the "InsertLayerNode'-function should be used. It inserts the * given node into the specified renderer and sets the corresponding properties. - * To hide the data in the common renderer view (all renderer), the 'HideDataNodeInAllRenderer'-function can be used. * Inserting and showing a data node in a specific renderer / render window, will overwrite the properties of the common renderer view. * * For more information about the data node properties for specific renderer, see mitk::DataNode- and mitk::PropertyList-classes. * * Functions with 'mitk::BaseRenderer* renderer' have 'nullptr' as their default argument. Using the nullptr - * these functions operate on all base renderer. Giving a specific base renderer will modify the node only for the given renderer. + * these functions operate on the general property list. + * Giving a specific base renderer will modify the property list of the given renderer. */ class MITKQTWIDGETS_EXPORT RenderWindowLayerController { public: RenderWindowLayerController(); /** * @brief Set the data storage on which to work. */ void SetDataStorage(DataStorage::Pointer dataStorage); - /** - * @brief Set the controlled base renderer. - */ - void SetControlledRenderer(RenderWindowLayerUtilities::RendererVector controlledRenderer); // wrapper functions to modify the layer order / visibility of render window data /** - * @brief Set the given node as the base node of the given renderer. - * - * @param dataNode The data node whose layer is to be modified. - * @param renderer Pointer to the renderer instance for which the data node property should be modified. - * If it is a nullptr (default) all controlled renderer will be affected. - */ - void SetBaseDataNode(DataNode* dataNode, const BaseRenderer* renderer = nullptr); - /** * @brief Insert the given data node at the specified layer for the given renderer. * * @param dataNode The data node that should be inserted. * @param layer The layer value for the "layer" property of the data node (insertion level). "layer = RenderWindowLayerUtilities::TOP_LAYER_INDEX" (default) inserts the given data node at the top of the node stack (topmost layer). * @param renderer Pointer to the renderer instance for which the data node should be inserted. * If it is a nullptr (default) all controlled renderer will be affected. * - * @post After a successful call, the "fixedLayer" and "visibility" property will be true and the "layer" property will be set correctly. + * @post After a successful call, the "visibility" property will be true and the "layer" property will be set correctly. */ void InsertLayerNode(DataNode* dataNode, int layer = RenderWindowLayerUtilities::TOP_LAYER_INDEX, const BaseRenderer* renderer = nullptr); /** - * @brief Remove the given data node for the given renderer. - * - * @param dataNode The data node that should be removed. - * @param renderer Pointer to the renderer instance for which the data node should be removed. - * If it is a nullptr (default) all controlled renderer will be affected. - * - * @post After a successful call, the "fixedLayer" and "visibility" property will be false and the "layer" property will be deleted. - */ - void RemoveLayerNode(DataNode* dataNode, const BaseRenderer* renderer = nullptr); - /** * @brief Move the data node to the given layer. This will change only the "layer" property. * * @param dataNode The data node that should be moved. - * @param newLayer + * @param layer The layer to which the data node should be moved. * @param renderer Pointer to the renderer instance for which the data node should be moved. - * If it is a nullptr (default) all controlled renderer will be affected. + * If it is a nullptr (default) all controlled renderer will be affected and the + * common property list will be modified. */ - bool MoveNodeToPosition(DataNode* dataNode, int newLayer, const BaseRenderer* renderer = nullptr); + bool MoveNodeToPosition(DataNode* dataNode, int layer, const BaseRenderer* renderer = nullptr); /** * @brief Set the node in the given renderer as the topmost layer. This will change only the "layer" property. * * @param dataNode The data node that should be moved. * @param renderer Pointer to the renderer instance for which the data node should be moved. * If it is a nullptr (default) all controlled renderer will be affected. */ bool MoveNodeToFront(DataNode* dataNode, const BaseRenderer* renderer = nullptr); /** * @brief Set the node in the given renderer as the lowermost layer. This will change only the "layer" property. * * @param dataNode The data node that should be moved. * @param renderer Pointer to the renderer instance for which the data node should be moved. * If it is a nullptr (default) all controlled renderer will be affected. */ bool MoveNodeToBack(DataNode* dataNode, const BaseRenderer* renderer = nullptr); /** * @brief Move the node in the given renderer one layer down. This will change only the "layer" property. * * @param dataNode The data node that should be moved. * @param renderer Pointer to the renderer instance for which the data node should be moved. * If it is a nullptr (default) all controlled renderer will be affected. */ bool MoveNodeUp(DataNode* dataNode, const BaseRenderer* renderer = nullptr); /** * @brief Move the node in the given renderer one layer up. This will change only the "layer" property. * * @param dataNode The data node that should be moved. * @param renderer Pointer to the renderer instance for which the data node should be moved. * If it is a nullptr (default) all controlled renderer will be affected. */ bool MoveNodeDown(DataNode* dataNode, const BaseRenderer* renderer = nullptr); - /** - * @brief Set the visibility of the given data node for the given renderer. - * - * @param visibility Boolean to set the "visible" property of the given data node. - * @param dataNode The data node that should be moved. - * @param renderer Pointer to the renderer instance for which the data node should be modified. - * If it is a nullptr (default) all controlled renderer will be affected. - * - * @post After a successful call , the "visibility" property will be set to the "visibility" value. - */ - void SetVisibilityOfDataNode(bool visibility, DataNode* dataNode, const BaseRenderer* renderer = nullptr); - /** - * @brief Hide the given data node by setting the "visible" property of the data node for - * all controlled renderer to false. - * Later setting the "visible" property of the data node for a certain renderer will overwrite - * the same property of the common renderer. - * - * @param dataNode The data node that should be hid. - * - * @post After a successful call , the "visibility" property will be set to the false. - */ - void HideDataNodeInAllRenderer(const DataNode* dataNode); - /** - * @brief Reset the given render window: - * If "onlyVisibility = true": set all data nodes for the given render window to invisible, except for the base node. - * If "onlyVisibility = false": remove all data nodes from the render window, except for the base node. - * - * @param onlyVisibility Boolean to define the reset mode. - * @param renderer Pointer to the renderer instance for which the data node should be reset. - * If it is a nullptr (default) all controlled renderer will be affected. - * - * @post After a successful call , the "visibility" property will be set to the "false" value (except for the base node). - * If "onlyVisibility = false": additionally the "fixedLayer" property will be false and the "layer" property will be deleted. - */ - void ResetRenderer(bool onlyVisibility = true, const BaseRenderer* renderer = nullptr); private: - void InsertLayerNodeInternal(DataNode* dataNode, int layer, const BaseRenderer* renderer = nullptr); - DataStorage::Pointer m_DataStorage; - RenderWindowLayerUtilities::RendererVector m_ControlledRenderer; }; } // namespace mitk #endif diff --git a/Modules/QtWidgets/include/mitkRenderWindowLayerUtilities.h b/Modules/QtWidgets/include/mitkRenderWindowLayerUtilities.h index 6c81c3b451..e953e9355c 100644 --- a/Modules/QtWidgets/include/mitkRenderWindowLayerUtilities.h +++ b/Modules/QtWidgets/include/mitkRenderWindowLayerUtilities.h @@ -1,77 +1,67 @@ /*============================================================================ 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 mitkRenderWindowLayerUtilities_h #define mitkRenderWindowLayerUtilities_h // qt widgets module #include "MitkQtWidgetsExports.h" // mitk core #include #include #include #include /** * @brief Render window layer helper functions to retrieve the currently valid layer stack */ namespace mitk { namespace RenderWindowLayerUtilities { typedef std::vector RendererVector; typedef std::map> LayerStack; - /** - * The base data node of a renderer is supposed to be on layer 0 (zero), which should be the lowest layer in a render window. - */ - const int BASE_LAYER_INDEX = 0; /** * The top layer index, denoting that no valid (positive) layer index is given and therefore the index should be resolved into the topmost layer index. */ const int TOP_LAYER_INDEX = -1; /** * @brief Return the stack of layers of the given renderer as std::map, which guarantees ordering of the layers. - * Stacked layers are only included if they have their "fixedLayer" property set to true and their "layer" property set. + * Stacked layers are only included if they have their "layer" property set. * * If "renderer" = nullptr: a layer stack won't be created and an empty "LayerStack" will be returned. - * If "withBaseNode" = true: include the base node in the layer stack, if existing. - * If "withBaseNode" = false: exclude the base node from the layer stack. * * @param dataStorage Pointer to a data storage instance whose data nodes should be checked and possibly be included. * @param renderer Pointer to the renderer instance for which the layer stack should be generated. - * @param withBaseNode Boolean to decide whether the base node should be included in or excluded from the layer stack. - */ - MITKQTWIDGETS_EXPORT LayerStack GetLayerStack(const DataStorage* dataStorage, const BaseRenderer* renderer, bool withBaseNode); - /** - * @brief Helper function to get a node predicate that can be used to filter render window specific data nodes. - * The data nodes must have set a 'fixed layer' property for the given renderer. - * - * @param renderer Pointer to the renderer instance for which the 'fixed layer' should be true. - * @return The node predicate to filter 'fixed layer' data nodes. */ - MITKQTWIDGETS_EXPORT NodePredicateBase::Pointer GetRenderWindowPredicate(const BaseRenderer* renderer); + MITKQTWIDGETS_EXPORT LayerStack GetLayerStack(const DataStorage* dataStorage, const BaseRenderer* renderer); /** * @brief Set renderer-specific properties to mark a data node as 'managed by the specific renderer'. - * In order for a renderer to manage a data node, the 'fixedLayer' property has to be set for the given renderer. - * Additionally, the 'visible' and the 'layer' property are set and allow to individually render a set of nodes - * with a specific renderer. + * In order for a renderer to manage a data node, the 'visible' and the 'layer' property are set and + * allow to individually render a set of nodes with a specific renderer. * The last two mentioned properties are set so that they initially have the same value as the corresponding * global property. */ - MITKQTWIDGETS_EXPORT void SetRenderWindowProperties(mitk::DataNode* dataNode, const BaseRenderer* renderer); + MITKQTWIDGETS_EXPORT void SetRenderWindowProperties(DataNode* dataNode, const BaseRenderer* renderer); + + MITKQTWIDGETS_EXPORT void DeleteRenderWindowProperties(DataNode* dataNode, const BaseRenderer* renderer); + + MITKQTWIDGETS_EXPORT void TransferRenderWindowProperties(DataNode* dataNode, const BaseRenderer* newRenderer, const BaseRenderer* oldRenderer); + + } // namespace RenderWindowLayerUtilities } // namespace mitk #endif diff --git a/Modules/QtWidgets/resource/Qmitk.qrc b/Modules/QtWidgets/resource/Qmitk.qrc index 9af3f566b3..377d75c396 100644 --- a/Modules/QtWidgets/resource/Qmitk.qrc +++ b/Modules/QtWidgets/resource/Qmitk.qrc @@ -1,30 +1,34 @@ Binaerbilder_48.png Images_48.png PointSet_48.png Segmentation_48.png Surface_48.png mm_pointer.png mm_scroll.png mm_zoom.png mm_contrast.png mm_pan.png LabelSetImage_48.png mwLayout.png mwSynchronized.png mwDesynchronized.png mwMITK.png mwPACS.png star-solid.svg history-solid.svg tree_inspector.svg list-solid.svg favorite_add.svg favorite_remove.svg hourglass-half-solid.svg times.svg reset.svg + lock.svg + unlock.svg + invisible.svg + visible.svg SegmentationTaskListIcon.svg diff --git a/Modules/SegmentationUI/resources/invisible.svg b/Modules/QtWidgets/resource/invisible.svg similarity index 100% rename from Modules/SegmentationUI/resources/invisible.svg rename to Modules/QtWidgets/resource/invisible.svg diff --git a/Modules/SegmentationUI/resources/lock.svg b/Modules/QtWidgets/resource/lock.svg similarity index 100% rename from Modules/SegmentationUI/resources/lock.svg rename to Modules/QtWidgets/resource/lock.svg diff --git a/Modules/QtWidgets/resource/mxnLayout_twoRowsEachDirection.json b/Modules/QtWidgets/resource/mxnLayout_twoRowsEachDirection.json new file mode 100644 index 0000000000..74c2ad3ee7 --- /dev/null +++ b/Modules/QtWidgets/resource/mxnLayout_twoRowsEachDirection.json @@ -0,0 +1,52 @@ +{ + "version": "1.0", + "name": "Two rows with each view direction", + "content": [ + { + "content": [ + { + "isWindow": true, + "size": 403, + "viewDirection": "Axial" + }, + { + "isWindow": true, + "size": 403, + "viewDirection": "Sagittal" + }, + { + "isWindow": true, + "size": 403, + "viewDirection": "Coronal" + } + ], + "isWindow": false, + "size": 418, + "vertical": false + }, + { + "content": [ + { + "isWindow": true, + "size": 403, + "viewDirection": "Axial" + }, + { + "isWindow": true, + "size": 403, + "viewDirection": "Sagittal" + }, + { + "isWindow": true, + "size": 403, + "viewDirection": "Coronal" + } + ], + "isWindow": false, + "size": 418, + "vertical": false + } + ], + "isWindow": false, + "vertical": true +} diff --git a/Modules/SegmentationUI/resources/unlock.svg b/Modules/QtWidgets/resource/unlock.svg similarity index 100% rename from Modules/SegmentationUI/resources/unlock.svg rename to Modules/QtWidgets/resource/unlock.svg diff --git a/Modules/SegmentationUI/resources/visible.svg b/Modules/QtWidgets/resource/visible.svg similarity index 100% rename from Modules/SegmentationUI/resources/visible.svg rename to Modules/QtWidgets/resource/visible.svg diff --git a/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp index 1b3d6c9e98..acc64021c6 100644 --- a/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp @@ -1,426 +1,426 @@ /*============================================================================ 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(); } QmitkAbstractNodeSelectionWidget::ConstNodeStdVector QmitkAbstractNodeSelectionWidget::GetSelectedNodesStdVector() const { auto result = this->GetSelectedNodes(); return ConstNodeStdVector(result.begin(), result.end()); } 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) { if (!EqualNodeSelections(m_CurrentInternalSelection, 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 (const auto &node : qAsConst(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*/) { } +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(); + } + } + } + } +} + QmitkAbstractNodeSelectionWidget::NodeList QmitkAbstractNodeSelectionWidget::CompileEmitSelection() const { NodeList result = m_CurrentInternalSelection; if (!m_SelectOnlyVisibleNodes) { for (const 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/src/QmitkMultiWidgetConfigurationToolBar.cpp b/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp index 60c85e5724..1987f7653f 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp +++ b/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp @@ -1,109 +1,111 @@ /*============================================================================ 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 "QmitkMultiWidgetConfigurationToolBar.h" // mitk qt widgets module #include "QmitkAbstractMultiWidget.h" #include "QmitkMultiWidgetLayoutSelectionWidget.h" QmitkMultiWidgetConfigurationToolBar::QmitkMultiWidgetConfigurationToolBar(QmitkAbstractMultiWidget* multiWidget) : QToolBar(multiWidget) , m_MultiWidget(multiWidget) { QToolBar::setOrientation(Qt::Vertical); QToolBar::setIconSize(QSize(17, 17)); InitializeToolBar(); } QmitkMultiWidgetConfigurationToolBar::~QmitkMultiWidgetConfigurationToolBar() { // nothing here } void QmitkMultiWidgetConfigurationToolBar::InitializeToolBar() { // create popup to show a widget to modify the multi widget layout m_LayoutSelectionPopup = new QmitkMultiWidgetLayoutSelectionWidget(this); m_LayoutSelectionPopup->hide(); AddButtons(); connect(m_LayoutSelectionPopup, &QmitkMultiWidgetLayoutSelectionWidget::LayoutSet, this, &QmitkMultiWidgetConfigurationToolBar::LayoutSet); + connect(m_LayoutSelectionPopup, &QmitkMultiWidgetLayoutSelectionWidget::SaveLayout, this, &QmitkMultiWidgetConfigurationToolBar::SaveLayout); + connect(m_LayoutSelectionPopup, &QmitkMultiWidgetLayoutSelectionWidget::LoadLayout, this, &QmitkMultiWidgetConfigurationToolBar::LoadLayout); } void QmitkMultiWidgetConfigurationToolBar::AddButtons() { QAction* setLayoutAction = new QAction(QIcon(":/Qmitk/mwLayout.png"), tr("Set multi widget layout"), this); connect(setLayoutAction, &QAction::triggered, this, &QmitkMultiWidgetConfigurationToolBar::OnSetLayout); QToolBar::addAction(setLayoutAction); m_SynchronizeAction = new QAction(QIcon(":/Qmitk/mwDesynchronized.png"), tr("Synchronize render windows"), this); m_SynchronizeAction->setCheckable(true); m_SynchronizeAction->setChecked(false); connect(m_SynchronizeAction, &QAction::triggered, this, &QmitkMultiWidgetConfigurationToolBar::OnSynchronize); QToolBar::addAction(m_SynchronizeAction); m_InteractionSchemeChangeAction = new QAction(QIcon(":/Qmitk/mwMITK.png"), tr("Change to PACS interaction"), this); m_InteractionSchemeChangeAction->setCheckable(true); m_InteractionSchemeChangeAction->setChecked(false); connect(m_InteractionSchemeChangeAction, &QAction::triggered, this, &QmitkMultiWidgetConfigurationToolBar::OnInteractionSchemeChanged); QToolBar::addAction(m_InteractionSchemeChangeAction); } void QmitkMultiWidgetConfigurationToolBar::OnSetLayout() { if (nullptr != m_MultiWidget) { m_LayoutSelectionPopup->setWindowFlags(Qt::Popup); m_LayoutSelectionPopup->move(this->cursor().pos().x() - m_LayoutSelectionPopup->width(), this->cursor().pos().y()); m_LayoutSelectionPopup->show(); } } void QmitkMultiWidgetConfigurationToolBar::OnSynchronize() { bool synchronized = m_SynchronizeAction->isChecked(); if (synchronized) { m_SynchronizeAction->setIcon(QIcon(":/Qmitk/mwSynchronized.png")); m_SynchronizeAction->setText(tr("Desynchronize render windows")); } else { m_SynchronizeAction->setIcon(QIcon(":/Qmitk/mwDesynchronized.png")); m_SynchronizeAction->setText(tr("Synchronize render windows")); } m_SynchronizeAction->setChecked(synchronized); emit Synchronized(synchronized); } void QmitkMultiWidgetConfigurationToolBar::OnInteractionSchemeChanged() { bool PACSInteractionScheme = m_InteractionSchemeChangeAction->isChecked(); if (PACSInteractionScheme) { m_InteractionSchemeChangeAction->setIcon(QIcon(":/Qmitk/mwPACS.png")); m_InteractionSchemeChangeAction->setText(tr("Change to MITK interaction")); emit InteractionSchemeChanged(mitk::InteractionSchemeSwitcher::PACSStandard); } else { m_InteractionSchemeChangeAction->setIcon(QIcon(":/Qmitk/mwMITK.png")); m_InteractionSchemeChangeAction->setText(tr("Change to PACS interaction")); emit InteractionSchemeChanged(mitk::InteractionSchemeSwitcher::MITKStandard); } m_InteractionSchemeChangeAction->setChecked(PACSInteractionScheme); } diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp index 0575c4a260..0578c2e0d5 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp @@ -1,76 +1,134 @@ /*============================================================================ 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 "QmitkMultiWidgetLayoutSelectionWidget.h" +#include + +#include +#include +#include +#include + QmitkMultiWidgetLayoutSelectionWidget::QmitkMultiWidgetLayoutSelectionWidget(QWidget* parent/* = 0*/) : QWidget(parent) { Init(); } void QmitkMultiWidgetLayoutSelectionWidget::Init() { ui.setupUi(this); auto stylesheet = "QTableWidget::item{background-color: white;}\nQTableWidget::item:selected{background-color: #1C97EA;}"; ui.tableWidget->setStyleSheet(stylesheet); connect(ui.tableWidget, &QTableWidget::itemSelectionChanged, this, &QmitkMultiWidgetLayoutSelectionWidget::OnTableItemSelectionChanged); connect(ui.setLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnSetLayoutButtonClicked); + connect(ui.loadLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnLoadLayoutButtonClicked); + connect(ui.saveLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnSaveLayoutButtonClicked); + connect(ui.selectDefaultLayoutComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &QmitkMultiWidgetLayoutSelectionWidget::OnLayoutPresetSelected); + + ui.selectDefaultLayoutComboBox->addItem("Select a layout preset"); + auto presetResources = us::GetModuleContext()->GetModule()->FindResources("/", "mxnLayout_*.json", false); + for (const auto& resource : presetResources) + { + us::ModuleResourceStream jsonStream(resource); + auto data = nlohmann::json::parse(jsonStream); + auto resourceName = data["name"].get(); + ui.selectDefaultLayoutComboBox->addItem(QString::fromStdString(resourceName)); + m_PresetMap[ui.selectDefaultLayoutComboBox->count() - 1] = data; + } } void QmitkMultiWidgetLayoutSelectionWidget::OnTableItemSelectionChanged() { QItemSelectionModel* selectionModel = ui.tableWidget->selectionModel(); int row = 0; int column = 0; QModelIndexList indices = selectionModel->selectedIndexes(); if (indices.size() > 0) { row = indices[0].row(); column = indices[0].column(); QModelIndex topLeft = ui.tableWidget->model()->index(0, 0, QModelIndex()); QModelIndex bottomRight = ui.tableWidget->model()->index(row, column, QModelIndex()); QItemSelection cellSelection; cellSelection.select(topLeft, bottomRight); selectionModel->select(cellSelection, QItemSelectionModel::Select); } } void QmitkMultiWidgetLayoutSelectionWidget::OnSetLayoutButtonClicked() { int row = 0; int column = 0; QModelIndexList indices = ui.tableWidget->selectionModel()->selectedIndexes(); if (indices.size() > 0) { // find largest row and column for (const auto& modelIndex : qAsConst(indices)) { if (modelIndex.row() > row) { row = modelIndex.row(); } if (modelIndex.column() > column) { column = modelIndex.column(); } } close(); emit LayoutSet(row+1, column+1); } + ui.selectDefaultLayoutComboBox->setCurrentIndex(0); +} + +void QmitkMultiWidgetLayoutSelectionWidget::OnSaveLayoutButtonClicked() +{ + QString filename = QFileDialog::getSaveFileName(nullptr, "Select where to save the current layout", "", "MITK Window Layout (*.json)"); + if (filename.isEmpty()) + return; + + auto outStream = std::ofstream(filename.toStdString()); + emit SaveLayout(&outStream); +} + +void QmitkMultiWidgetLayoutSelectionWidget::OnLoadLayoutButtonClicked() +{ + QString filename = QFileDialog::getOpenFileName(nullptr, "Load a layout file", "", "MITK Window Layouts (*.json)"); + if (filename.isEmpty()) + return; + + ui.selectDefaultLayoutComboBox->setCurrentIndex(0); + + std::ifstream f(filename.toStdString()); + auto jsonData = nlohmann::json::parse(f); + emit LoadLayout(&jsonData); +} + +void QmitkMultiWidgetLayoutSelectionWidget::OnLayoutPresetSelected(int index) +{ + if (index == 0) + { + // First entry is only for description + return; + } + + auto jsonData = m_PresetMap[index]; + close(); + emit LoadLayout(&jsonData); } diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui index 5c2d9e4af1..391d5b8cb2 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui +++ b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui @@ -1,70 +1,123 @@ QmitkMultiWidgetLayoutSelectionWidget 0 0 - 230 - 230 + 224 + 290 QAbstractItemView::NoEditTriggers false false 3 4 false 50 50 false 50 50 Set multi widget layout + + + + + 0 + 0 + + + + + 0 + 0 + + + + 1 + + + 0 + + + Qt::Horizontal + + + + + + + false + + + + + + + + + + + + Save layout + + + + + + + Load layout + + + + + diff --git a/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp b/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp index d3efda2364..b95255b87c 100644 --- a/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp +++ b/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp @@ -1,392 +1,632 @@ /*============================================================================ 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 "QmitkMxNMultiWidget.h" -#include "QmitkRenderWindowWidget.h" // mitk core #include #include #include +#include +#include +#include + +// mitk qt widget +#include +#include // qt #include #include +#include + +#include QmitkMxNMultiWidget::QmitkMxNMultiWidget(QWidget* parent, Qt::WindowFlags f/* = 0*/, const QString& multiWidgetName/* = "mxnmulti"*/) : QmitkAbstractMultiWidget(parent, f, multiWidgetName) , m_TimeNavigationController(nullptr) + , m_SynchronizedWidgetConnector(std::make_unique()) , m_CrosshairVisibility(false) { m_TimeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); } QmitkMxNMultiWidget::~QmitkMxNMultiWidget() { auto allRenderWindows = this->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { m_TimeNavigationController->Disconnect(renderWindow->GetSliceNavigationController()); } } void QmitkMxNMultiWidget::InitializeMultiWidget() { SetLayout(1, 1); SetDisplayActionEventHandler(std::make_unique()); auto displayActionEventHandler = GetDisplayActionEventHandler(); if (nullptr != displayActionEventHandler) { displayActionEventHandler->InitActions(); } + + this->SetInitialSelection(); } void QmitkMxNMultiWidget::Synchronize(bool synchronized) { if (synchronized) { SetDisplayActionEventHandler(std::make_unique()); } else { SetDisplayActionEventHandler(std::make_unique()); } auto displayActionEventHandler = GetDisplayActionEventHandler(); if (nullptr != displayActionEventHandler) { displayActionEventHandler->InitActions(); } } QmitkRenderWindow* QmitkMxNMultiWidget::GetRenderWindow(const QString& widgetName) const { if ("axial" == widgetName || "sagittal" == widgetName || "coronal" == widgetName || "3d" == widgetName) { return GetActiveRenderWindowWidget()->GetRenderWindow(); } return QmitkAbstractMultiWidget::GetRenderWindow(widgetName); } QmitkRenderWindow* QmitkMxNMultiWidget::GetRenderWindow(const mitk::AnatomicalPlane& /*orientation*/) const { // currently no mapping between plane orientation and render windows // simply return the currently active render window return GetActiveRenderWindowWidget()->GetRenderWindow(); } void QmitkMxNMultiWidget::SetActiveRenderWindowWidget(RenderWindowWidgetPointer activeRenderWindowWidget) { auto currentActiveRenderWindowWidget = GetActiveRenderWindowWidget(); if (currentActiveRenderWindowWidget == activeRenderWindowWidget) { return; } // reset the decoration color of the previously active render window widget if (nullptr != currentActiveRenderWindowWidget) { auto decorationColor = currentActiveRenderWindowWidget->GetDecorationColor(); QColor hexColor(decorationColor[0] * 255, decorationColor[1] * 255, decorationColor[2] * 255); currentActiveRenderWindowWidget->setStyleSheet("QmitkRenderWindowWidget { border: 2px solid " + hexColor.name(QColor::HexRgb) + "; }"); } // set the new decoration color of the currently active render window widget if (nullptr != activeRenderWindowWidget) { activeRenderWindowWidget->setStyleSheet("QmitkRenderWindowWidget { border: 2px solid #FF6464; }"); } QmitkAbstractMultiWidget::SetActiveRenderWindowWidget(activeRenderWindowWidget); } void QmitkMxNMultiWidget::InitializeViews(const mitk::TimeGeometry* geometry, bool resetCamera) { auto* renderingManager = mitk::RenderingManager::GetInstance(); mitk::Point3D currentPosition = mitk::Point3D(); unsigned int imageTimeStep = 0; if (!resetCamera) { // store the current position to set it again later, if the camera should not be reset currentPosition = this->GetSelectedPosition(""); // store the current time step to set it again later, if the camera should not be reset const auto currentTimePoint = renderingManager->GetTimeNavigationController()->GetSelectedTimePoint(); if (geometry->IsValidTimePoint(currentTimePoint)) { imageTimeStep = geometry->TimePointToTimeStep(currentTimePoint); } } // initialize active render window renderingManager->InitializeView( this->GetActiveRenderWindowWidget()->GetRenderWindow()->GetVtkRenderWindow(), geometry, resetCamera); if (!resetCamera) { this->SetSelectedPosition(currentPosition, ""); renderingManager->GetTimeNavigationController()->GetTime()->SetPos(imageTimeStep); } } void QmitkMxNMultiWidget::SetInteractionReferenceGeometry(const mitk::TimeGeometry* referenceGeometry) { // Set the interaction reference referenceGeometry for all render windows. auto allRenderWindows = this->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { auto* baseRenderer = mitk::BaseRenderer::GetInstance(renderWindow->GetRenderWindow()); baseRenderer->SetInteractionReferenceGeometry(referenceGeometry); } } bool QmitkMxNMultiWidget::HasCoupledRenderWindows() const { return false; } void QmitkMxNMultiWidget::SetSelectedPosition(const mitk::Point3D& newPosition, const QString& widgetName) { RenderWindowWidgetPointer renderWindowWidget; if (widgetName.isNull() || widgetName.isEmpty()) { renderWindowWidget = GetActiveRenderWindowWidget(); } else { renderWindowWidget = GetRenderWindowWidget(widgetName); } if (nullptr != renderWindowWidget) { renderWindowWidget->GetSliceNavigationController()->SelectSliceByPoint(newPosition); return; } MITK_ERROR << "Position can not be set for an unknown render window widget."; } const mitk::Point3D QmitkMxNMultiWidget::GetSelectedPosition(const QString& widgetName) const { RenderWindowWidgetPointer renderWindowWidget; if (widgetName.isNull() || widgetName.isEmpty()) { renderWindowWidget = GetActiveRenderWindowWidget(); } else { renderWindowWidget = GetRenderWindowWidget(widgetName); } if (nullptr != renderWindowWidget) { return renderWindowWidget->GetCrosshairPosition(); } MITK_ERROR << "Crosshair position can not be retrieved."; return mitk::Point3D(0.0); } void QmitkMxNMultiWidget::SetCrosshairVisibility(bool visible) { // get the specific render window that sent the signal QmitkRenderWindow* renderWindow = qobject_cast(sender()); if (nullptr == renderWindow) { return; } auto renderWindowWidget = this->GetRenderWindowWidget(renderWindow); renderWindowWidget->SetCrosshairVisibility(visible); } bool QmitkMxNMultiWidget::GetCrosshairVisibility() const { // get the specific render window that sent the signal QmitkRenderWindow* renderWindow = qobject_cast(sender()); if (nullptr == renderWindow) { return false; } auto renderWindowWidget = this->GetRenderWindowWidget(renderWindow); return renderWindowWidget->GetCrosshairVisibility(); } void QmitkMxNMultiWidget::SetCrosshairGap(unsigned int gapSize) { auto renderWindowWidgets = this->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { renderWindowWidget.second->SetCrosshairGap(gapSize); } } void QmitkMxNMultiWidget::ResetCrosshair() { auto dataStorage = GetDataStorage(); if (nullptr == dataStorage) { return; } // get the specific render window that sent the signal QmitkRenderWindow* renderWindow = qobject_cast(sender()); if (nullptr == renderWindow) { return; } mitk::RenderingManager::GetInstance()->InitializeViewByBoundingObjects(renderWindow->GetRenderWindow(), dataStorage); SetWidgetPlaneMode(mitk::InteractionSchemeSwitcher::MITKStandard); } void QmitkMxNMultiWidget::SetWidgetPlaneMode(int userMode) { MITK_DEBUG << "Changing crosshair mode to " << userMode; switch (userMode) { case 0: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKStandard); break; case 1: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKRotationUncoupled); break; case 2: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKRotationCoupled); break; case 3: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKSwivel); break; } } mitk::SliceNavigationController* QmitkMxNMultiWidget::GetTimeNavigationController() { return m_TimeNavigationController; } -void QmitkMxNMultiWidget::AddPlanesToDataStorage() +void QmitkMxNMultiWidget::EnableCrosshair() { auto renderWindowWidgets = this->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { - renderWindowWidget.second->AddPlanesToDataStorage(); + renderWindowWidget.second->EnableCrosshair(); } } -void QmitkMxNMultiWidget::RemovePlanesFromDataStorage() +void QmitkMxNMultiWidget::DisableCrosshair() { auto renderWindowWidgets = this->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { - renderWindowWidget.second->RemovePlanesFromDataStorage(); + renderWindowWidget.second->DisableCrosshair(); } } ////////////////////////////////////////////////////////////////////////// // PUBLIC SLOTS // MOUSE EVENTS ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidget::wheelEvent(QWheelEvent* e) { emit WheelMoved(e); } void QmitkMxNMultiWidget::mousePressEvent(QMouseEvent*) { // nothing here, but necessary for mouse interactions (.xml-configuration files) } void QmitkMxNMultiWidget::moveEvent(QMoveEvent* e) { QWidget::moveEvent(e); // it is necessary to readjust the position of the overlays as the MultiWidget has moved // unfortunately it's not done by QmitkRenderWindow::moveEvent -> must be done here emit Moved(); } void QmitkMxNMultiWidget::RemoveRenderWindowWidget() { auto renderWindowWidgets = this->GetRenderWindowWidgets(); auto iterator = renderWindowWidgets.find(this->GetNameFromIndex(this->GetNumberOfRenderWindowWidgets() - 1)); if (iterator == renderWindowWidgets.end()) { return; } // disconnect each signal of this render window widget RenderWindowWidgetPointer renderWindowWidgetToRemove = iterator->second; m_TimeNavigationController->Disconnect(renderWindowWidgetToRemove->GetSliceNavigationController()); QmitkAbstractMultiWidget::RemoveRenderWindowWidget(); } ////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidget::SetLayoutImpl() { int requiredRenderWindowWidgets = GetRowCount() * GetColumnCount(); int existingRenderWindowWidgets = GetRenderWindowWidgets().size(); int difference = requiredRenderWindowWidgets - existingRenderWindowWidgets; while (0 < difference) { // more render window widgets needed CreateRenderWindowWidget(); --difference; } while (0 > difference) { // less render window widgets needed RemoveRenderWindowWidget(); ++difference; } auto firstRenderWindowWidget = GetFirstRenderWindowWidget(); if (nullptr != firstRenderWindowWidget) { SetActiveRenderWindowWidget(firstRenderWindowWidget); } GetMultiWidgetLayoutManager()->SetLayoutDesign(QmitkMultiWidgetLayoutManager::LayoutDesign::DEFAULT); } -void QmitkMxNMultiWidget::CreateRenderWindowWidget() +QmitkAbstractMultiWidget::RenderWindowWidgetPointer QmitkMxNMultiWidget::CreateRenderWindowWidget() { // create the render window widget and connect signal / slot QString renderWindowWidgetName = GetNameFromIndex(GetNumberOfRenderWindowWidgets()); - RenderWindowWidgetPointer renderWindowWidget = std::make_shared(this, renderWindowWidgetName, GetDataStorage(), true); + RenderWindowWidgetPointer renderWindowWidget = std::make_shared(this, renderWindowWidgetName, GetDataStorage()); renderWindowWidget->SetCornerAnnotationText(renderWindowWidgetName.toStdString()); AddRenderWindowWidget(renderWindowWidgetName, renderWindowWidget); auto renderWindow = renderWindowWidget->GetRenderWindow(); + + QmitkRenderWindowUtilityWidget* utilityWidget = new QmitkRenderWindowUtilityWidget(this, renderWindow, GetDataStorage()); + renderWindowWidget->AddUtilityWidget(utilityWidget); + + connect(utilityWidget, &QmitkRenderWindowUtilityWidget::SynchronizationToggled, + this, &QmitkMxNMultiWidget::ToggleSynchronization); + connect(this, &QmitkMxNMultiWidget::UpdateUtilityWidgetViewPlanes, + utilityWidget, &QmitkRenderWindowUtilityWidget::UpdateViewPlaneSelection); + + // needs to be done after 'QmitkRenderWindowUtilityWidget::ToggleSynchronization' has been connected + // initially synchronize the node selection widget + utilityWidget->ToggleSynchronization(true); + auto layoutManager = GetMultiWidgetLayoutManager(); connect(renderWindow, &QmitkRenderWindow::LayoutDesignChanged, layoutManager, &QmitkMultiWidgetLayoutManager::SetLayoutDesign); connect(renderWindow, &QmitkRenderWindow::ResetView, this, &QmitkMxNMultiWidget::ResetCrosshair); connect(renderWindow, &QmitkRenderWindow::CrosshairVisibilityChanged, this, &QmitkMxNMultiWidget::SetCrosshairVisibility); connect(renderWindow, &QmitkRenderWindow::CrosshairRotationModeChanged, this, &QmitkMxNMultiWidget::SetWidgetPlaneMode); // connect time navigation controller to react on referenceGeometry time events with the render window's slice naviation controller m_TimeNavigationController->ConnectGeometryTimeEvent(renderWindow->GetSliceNavigationController()); // reverse connection between the render window's slice navigation controller and the time navigation controller renderWindow->GetSliceNavigationController()->ConnectGeometryTimeEvent(m_TimeNavigationController); + + return renderWindowWidget; +} + +void QmitkMxNMultiWidget::LoadLayout(const nlohmann::json* jsonData) +{ + if ((*jsonData).is_null()) + { + QMessageBox::warning(this, "Load layout", "Could not read window layout"); + return; + } + + unsigned int windowCounter = 0; + + try + { + auto version = jsonData->at("version").get(); + if (version != "1.0") + { + QMessageBox::warning(this, "Load layout", "Unknown layout version, could not load"); + return; + } + + delete this->layout(); + auto content = BuildLayoutFromJSON(jsonData, &windowCounter); + auto hBoxLayout = new QHBoxLayout(this); + this->setLayout(hBoxLayout); + hBoxLayout->addWidget(content); + emit UpdateUtilityWidgetViewPlanes(); + } + catch (nlohmann::json::out_of_range& e) + { + MITK_ERROR << "Error in loading window layout from JSON: " << e.what(); + return; + } + + while (GetNumberOfRenderWindowWidgets() > windowCounter) + { + RemoveRenderWindowWidget(); + } + + EnableCrosshair(); +} + +void QmitkMxNMultiWidget::SaveLayout(std::ostream* outStream) +{ + if (outStream == nullptr) + { + return; + } + + auto layout = this->layout(); + if (layout == nullptr) + return; + + // There should only ever be one item: a splitter + auto widget = layout->itemAt(0)->widget(); + auto splitter = dynamic_cast(widget); + if (!splitter) + { + MITK_ERROR << "Tried to save unexpected layout format. Make sure the layout of this instance contains a single QSplitter."; + return; + } + + auto layoutJSON = BuildJSONFromLayout(splitter); + layoutJSON["version"] = "1.0"; + layoutJSON["name"] = "Custom Layout"; + + *outStream << std::setw(4) << layoutJSON << std::endl; + +} + +nlohmann::json QmitkMxNMultiWidget::BuildJSONFromLayout(const QSplitter* splitter) +{ + nlohmann::json resultJSON; + resultJSON["isWindow"] = false; + resultJSON["vertical"] = (splitter->orientation() == Qt::Vertical) ? true : false; + auto sizes = splitter->sizes(); + + auto content = nlohmann::json::array(); + + auto countSplitter = splitter->count(); + for (int i = 0; i < countSplitter; ++i) + { + auto widget = splitter->widget(i); + nlohmann::json widgetJSON; + if (auto widgetSplitter = dynamic_cast(widget); widgetSplitter) + { + widgetJSON = BuildJSONFromLayout(widgetSplitter); + } + else if (auto widgetWindow = dynamic_cast(widget); widgetWindow) + { + widgetJSON["isWindow"] = true; + widgetJSON["viewDirection"] = widgetWindow->GetSliceNavigationController()->GetViewDirectionAsString(); + } + widgetJSON["size"] = sizes[i]; + content.push_back(widgetJSON); + } + resultJSON["content"] = content; + return resultJSON; +} + +QSplitter* QmitkMxNMultiWidget::BuildLayoutFromJSON(const nlohmann::json* jsonData, unsigned int* windowCounter, QSplitter* parentSplitter) +{ + + bool vertical = jsonData->at("vertical").get(); + auto orientation = vertical ? Qt::Vertical : Qt::Horizontal; + + auto split = new QSplitter(orientation, parentSplitter); + QList sizes; + + for (auto object : jsonData->at("content")) + { + bool isWindow = object["isWindow"].get(); + int size = object["size"].get(); + sizes.append(size); + + if (isWindow) + { + auto viewDirection = object["viewDirection"].get(); + mitk::AnatomicalPlane viewPlane = mitk::AnatomicalPlane::Sagittal; + if (viewDirection == "Axial") + { + viewPlane = mitk::AnatomicalPlane::Axial; + } + else if (viewDirection == "Coronal") + { + viewPlane = mitk::AnatomicalPlane::Coronal; + } + else if (viewDirection == "Original") + { + viewPlane = mitk::AnatomicalPlane::Original; + } + else if (viewDirection == "Sagittal") + { + viewPlane = mitk::AnatomicalPlane::Sagittal; + } + + QmitkAbstractMultiWidget::RenderWindowWidgetPointer window = nullptr; + QString renderWindowName; + QmitkAbstractMultiWidget::RenderWindowWidgetMap::iterator it; + + // repurpose existing render windows as far as they already exist + if (*windowCounter < GetRenderWindowWidgets().size()) + { + renderWindowName = this->GetNameFromIndex(*windowCounter); + auto renderWindowWidgets = GetRenderWindowWidgets(); + it = renderWindowWidgets.find(renderWindowName); + if (it != renderWindowWidgets.end()) + { + window = it->second; + } + else + { + MITK_ERROR << "Could not find render window " << renderWindowName.toStdString() << ", although it should be there."; + } + } + + if (window == nullptr) + { + window = CreateRenderWindowWidget(); + } + + window->GetSliceNavigationController()->SetDefaultViewDirection(viewPlane); + window->GetSliceNavigationController()->Update(); + split->addWidget(window.get()); + window->show(); + (*windowCounter)++; + } + else + { + auto subSplitter = BuildLayoutFromJSON(&object, windowCounter, split); + split->addWidget(subSplitter); + } + } + split->setSizes(sizes); + + return split; + +} + +void QmitkMxNMultiWidget::SetInitialSelection() +{ + auto dataStorage = this->GetDataStorage(); + if (nullptr == dataStorage) + { + return; + } + + mitk::NodePredicateAnd::Pointer noHelperObjects = mitk::NodePredicateAnd::New(); + noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); + noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); + auto allNodes = dataStorage->GetSubset(noHelperObjects); + QmitkSynchronizedNodeSelectionWidget::NodeList currentSelection; + for (auto& node : *allNodes) + { + currentSelection.append(node); + } + + m_SynchronizedWidgetConnector->ChangeSelection(currentSelection); +} + +void QmitkMxNMultiWidget::ToggleSynchronization(QmitkSynchronizedNodeSelectionWidget* synchronizedWidget) +{ + bool synchronized = synchronizedWidget->IsSynchronized(); + + if (synchronized) + { + m_SynchronizedWidgetConnector->ConnectWidget(synchronizedWidget); + m_SynchronizedWidgetConnector->SynchronizeWidget(synchronizedWidget); + } + else + { + m_SynchronizedWidgetConnector->DisconnectWidget(synchronizedWidget); + } } diff --git a/Modules/QtWidgets/src/QmitkRenderWindow.cpp b/Modules/QtWidgets/src/QmitkRenderWindow.cpp index 3776aeabf5..1f5676dd42 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindow.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindow.cpp @@ -1,507 +1,513 @@ /*============================================================================ 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 "QmitkRenderWindow.h" #include "mitkInteractionKeyEvent.h" #include "mitkInternalEvent.h" #include "mitkMouseDoubleClickEvent.h" #include "mitkMouseMoveEvent.h" #include "mitkMousePressEvent.h" #include "mitkMouseReleaseEvent.h" #include "mitkMouseWheelEvent.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include QmitkRenderWindow::QmitkRenderWindow(QWidget *parent, const QString &name, mitk::VtkPropRenderer *) : QVTKOpenGLNativeWidget(parent) , m_ResendQtEvents(true) , m_MenuWidget(nullptr) , m_MenuWidgetActivated(false) , m_LayoutIndex(QmitkRenderWindowMenu::LayoutIndex::Axial) , m_GeometryViolationWarningOverlay(nullptr) { m_InternalRenderWindow = vtkSmartPointer::New(); m_InternalRenderWindow->SetMultiSamples(0); m_InternalRenderWindow->SetAlphaBitPlanes(0); setRenderWindow(m_InternalRenderWindow); Initialize(name.toStdString().c_str()); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setSizePolicy(sizePolicy); // setup overlay widget to show a warning message with a button m_GeometryViolationWarningOverlay = new QmitkButtonOverlayWidget(this); m_GeometryViolationWarningOverlay->setVisible(false); m_GeometryViolationWarningOverlay->SetOverlayText( QStringLiteral("

Interaction is not possible because the " "render window geometry
does not match the interaction reference geometry.

")); m_GeometryViolationWarningOverlay->SetButtonText("Reset geometry"); m_GeometryViolationWarningOverlay->SetButtonIcon(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/reset.svg"))); connect(m_GeometryViolationWarningOverlay, &QmitkButtonOverlayWidget::Clicked, this, &QmitkRenderWindow::ResetGeometry); } QmitkRenderWindow::~QmitkRenderWindow() { Destroy(); // Destroy mitkRenderWindowBase } void QmitkRenderWindow::SetResendQtEvents(bool resend) { m_ResendQtEvents = resend; } void QmitkRenderWindow::SetLayoutIndex(QmitkRenderWindowMenu::LayoutIndex layoutIndex) { m_LayoutIndex = layoutIndex; if (nullptr != m_MenuWidget) { m_MenuWidget->SetLayoutIndex(layoutIndex); } } QmitkRenderWindowMenu::LayoutIndex QmitkRenderWindow::GetLayoutIndex() { if (nullptr != m_MenuWidget) { return m_MenuWidget->GetLayoutIndex(); } else { return QmitkRenderWindowMenu::LayoutIndex::Axial; } } void QmitkRenderWindow::UpdateLayoutDesignList(QmitkRenderWindowMenu::LayoutDesign layoutDesign) { if (nullptr != m_MenuWidget) { m_MenuWidget->UpdateLayoutDesignList(layoutDesign); } } void QmitkRenderWindow::UpdateCrosshairVisibility(bool visible) { m_MenuWidget->UpdateCrosshairVisibility(visible); } void QmitkRenderWindow::UpdateCrosshairRotationMode(int mode) { m_MenuWidget->UpdateCrosshairRotationMode(mode); } void QmitkRenderWindow::ActivateMenuWidget(bool state) { if (nullptr == m_MenuWidget) { m_MenuWidget = new QmitkRenderWindowMenu(this, nullptr, m_Renderer); m_MenuWidget->SetLayoutIndex(m_LayoutIndex); } + if (m_MenuWidgetActivated == state) + { + // no new state; nothing to do + return; + } + m_MenuWidgetActivated = state; if (m_MenuWidgetActivated) { connect(m_MenuWidget, &QmitkRenderWindowMenu::LayoutDesignChanged, this, &QmitkRenderWindow::LayoutDesignChanged); connect(m_MenuWidget, &QmitkRenderWindowMenu::ResetView, this, &QmitkRenderWindow::ResetView); connect(m_MenuWidget, &QmitkRenderWindowMenu::CrosshairVisibilityChanged, this, &QmitkRenderWindow::CrosshairVisibilityChanged); connect(m_MenuWidget, &QmitkRenderWindowMenu::CrosshairRotationModeChanged, this, &QmitkRenderWindow::CrosshairRotationModeChanged); } else { disconnect(m_MenuWidget, &QmitkRenderWindowMenu::LayoutDesignChanged, this, &QmitkRenderWindow::LayoutDesignChanged); disconnect(m_MenuWidget, &QmitkRenderWindowMenu::ResetView, this, &QmitkRenderWindow::ResetView); disconnect(m_MenuWidget, &QmitkRenderWindowMenu::CrosshairVisibilityChanged, this, &QmitkRenderWindow::CrosshairVisibilityChanged); disconnect(m_MenuWidget, &QmitkRenderWindowMenu::CrosshairRotationModeChanged, this, &QmitkRenderWindow::CrosshairRotationModeChanged); m_MenuWidget->hide(); } } void QmitkRenderWindow::ShowOverlayMessage(bool show) { m_GeometryViolationWarningOverlay->setVisible(show); } void QmitkRenderWindow::moveEvent(QMoveEvent *event) { QVTKOpenGLNativeWidget::moveEvent(event); // after a move the overlays need to be positioned emit moved(); } void QmitkRenderWindow::showEvent(QShowEvent *event) { QVTKOpenGLNativeWidget::showEvent(event); // this singleshot is necessary to have the overlays positioned correctly after initial show // simple call of moved() is no use here!! QTimer::singleShot(0, this, SIGNAL(moved())); } bool QmitkRenderWindow::event(QEvent* e) { mitk::InteractionEvent::Pointer mitkEvent = nullptr; mitk::Point2D mousePosition; bool updateStatusBar = false; switch (e->type()) { case QEvent::MouseMove: { auto me = static_cast(e); mousePosition = this->GetMousePosition(me); mitkEvent = mitk::MouseMoveEvent::New(m_Renderer, mousePosition, GetButtonState(me), GetModifiers(me)); updateStatusBar = true; break; } case QEvent::MouseButtonPress: { auto me = static_cast(e); mitkEvent = mitk::MousePressEvent::New( m_Renderer, GetMousePosition(me), GetButtonState(me), GetModifiers(me), GetEventButton(me)); break; } case QEvent::MouseButtonRelease: { auto me = static_cast(e); mitkEvent = mitk::MouseReleaseEvent::New( m_Renderer, GetMousePosition(me), GetButtonState(me), GetModifiers(me), GetEventButton(me)); break; } case QEvent::MouseButtonDblClick: { auto me = static_cast(e); mitkEvent = mitk::MouseDoubleClickEvent::New( m_Renderer, GetMousePosition(me), GetButtonState(me), GetModifiers(me), GetEventButton(me)); break; } case QEvent::Wheel: { auto we = static_cast(e); mousePosition = this->GetMousePosition(we); mitkEvent = mitk::MouseWheelEvent::New(m_Renderer, mousePosition, GetButtonState(we), GetModifiers(we), GetDelta(we)); updateStatusBar = true; break; } case QEvent::KeyPress: { auto ke = static_cast(e); mitkEvent = mitk::InteractionKeyEvent::New(m_Renderer, GetKeyLetter(ke), GetModifiers(ke)); break; } case QEvent::Resize: { if (nullptr != m_MenuWidget) m_MenuWidget->MoveWidgetToCorrectPos(); } default: { break; } } if (mitkEvent != nullptr) { if (this->HandleEvent(mitkEvent.GetPointer())) { return m_ResendQtEvents ? false : true; } } if (updateStatusBar) { this->UpdateStatusBar(mousePosition); } return QVTKOpenGLNativeWidget::event(e); } void QmitkRenderWindow::enterEvent(QEvent *e) { auto* baseRenderer = mitk::BaseRenderer::GetInstance(this->GetRenderWindow()); this->ShowOverlayMessage(!baseRenderer->GetReferenceGeometryAligned()); if (nullptr != m_MenuWidget) m_MenuWidget->ShowMenu(); QVTKOpenGLNativeWidget::enterEvent(e); } void QmitkRenderWindow::leaveEvent(QEvent *e) { auto statusBar = mitk::StatusBar::GetInstance(); statusBar->DisplayGreyValueText(""); this->ShowOverlayMessage(false); if (nullptr != m_MenuWidget) m_MenuWidget->HideMenu(); QVTKOpenGLNativeWidget::leaveEvent(e); } void QmitkRenderWindow::resizeGL(int w, int h) { QVTKOpenGLNativeWidget::resizeGL(w, h); mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(renderWindow()); } void QmitkRenderWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat("application/x-mitk-datanodes")) { event->accept(); } } void QmitkRenderWindow::dropEvent(QDropEvent *event) { QList dataNodeList = QmitkMimeTypes::ToDataNodePtrList(event->mimeData()); if (!dataNodeList.empty()) { emit NodesDropped(this, dataNodeList.toVector().toStdVector()); } } void QmitkRenderWindow::DeferredHideMenu() { MITK_DEBUG << "QmitkRenderWindow::DeferredHideMenu"; if (nullptr != m_MenuWidget) { m_MenuWidget->HideMenu(); } } mitk::Point2D QmitkRenderWindow::GetMousePosition(QMouseEvent *me) const { mitk::Point2D point; point[0] = me->x(); // We need to convert the y component, as the display and vtk have other definitions for the y direction point[1] = m_Renderer->GetSizeY() - me->y(); return point; } mitk::Point2D QmitkRenderWindow::GetMousePosition(QWheelEvent *we) const { mitk::Point2D point; point[0] = we->x(); // We need to convert the y component, as the display and vtk have other definitions for the y direction point[1] = m_Renderer->GetSizeY() - we->y(); return point; } mitk::InteractionEvent::MouseButtons QmitkRenderWindow::GetEventButton(QMouseEvent *me) const { mitk::InteractionEvent::MouseButtons eventButton; switch (me->button()) { case Qt::LeftButton: eventButton = mitk::InteractionEvent::LeftMouseButton; break; case Qt::RightButton: eventButton = mitk::InteractionEvent::RightMouseButton; break; case Qt::MidButton: eventButton = mitk::InteractionEvent::MiddleMouseButton; break; default: eventButton = mitk::InteractionEvent::NoButton; break; } return eventButton; } mitk::InteractionEvent::MouseButtons QmitkRenderWindow::GetButtonState(QMouseEvent *me) const { mitk::InteractionEvent::MouseButtons buttonState = mitk::InteractionEvent::NoButton; if (me->buttons() & Qt::LeftButton) { buttonState = buttonState | mitk::InteractionEvent::LeftMouseButton; } if (me->buttons() & Qt::RightButton) { buttonState = buttonState | mitk::InteractionEvent::RightMouseButton; } if (me->buttons() & Qt::MidButton) { buttonState = buttonState | mitk::InteractionEvent::MiddleMouseButton; } return buttonState; } mitk::InteractionEvent::ModifierKeys QmitkRenderWindow::GetModifiers(QInputEvent *me) const { mitk::InteractionEvent::ModifierKeys modifiers = mitk::InteractionEvent::NoKey; if (me->modifiers() & Qt::ALT) { modifiers = modifiers | mitk::InteractionEvent::AltKey; } if (me->modifiers() & Qt::CTRL) { modifiers = modifiers | mitk::InteractionEvent::ControlKey; } if (me->modifiers() & Qt::SHIFT) { modifiers = modifiers | mitk::InteractionEvent::ShiftKey; } return modifiers; } mitk::InteractionEvent::MouseButtons QmitkRenderWindow::GetButtonState(QWheelEvent *we) const { mitk::InteractionEvent::MouseButtons buttonState = mitk::InteractionEvent::NoButton; if (we->buttons() & Qt::LeftButton) { buttonState = buttonState | mitk::InteractionEvent::LeftMouseButton; } if (we->buttons() & Qt::RightButton) { buttonState = buttonState | mitk::InteractionEvent::RightMouseButton; } if (we->buttons() & Qt::MidButton) { buttonState = buttonState | mitk::InteractionEvent::MiddleMouseButton; } return buttonState; } std::string QmitkRenderWindow::GetKeyLetter(QKeyEvent *ke) const { // Converting Qt Key Event to string element. std::string key = ""; int tkey = ke->key(); if (tkey < 128) { // standard ascii letter key = (char)toupper(tkey); } else { // special keys switch (tkey) { case Qt::Key_Return: key = mitk::InteractionEvent::KeyReturn; break; case Qt::Key_Enter: key = mitk::InteractionEvent::KeyEnter; break; case Qt::Key_Escape: key = mitk::InteractionEvent::KeyEsc; break; case Qt::Key_Delete: key = mitk::InteractionEvent::KeyDelete; break; case Qt::Key_Up: key = mitk::InteractionEvent::KeyArrowUp; break; case Qt::Key_Down: key = mitk::InteractionEvent::KeyArrowDown; break; case Qt::Key_Left: key = mitk::InteractionEvent::KeyArrowLeft; break; case Qt::Key_Right: key = mitk::InteractionEvent::KeyArrowRight; break; case Qt::Key_F1: key = mitk::InteractionEvent::KeyF1; break; case Qt::Key_F2: key = mitk::InteractionEvent::KeyF2; break; case Qt::Key_F3: key = mitk::InteractionEvent::KeyF3; break; case Qt::Key_F4: key = mitk::InteractionEvent::KeyF4; break; case Qt::Key_F5: key = mitk::InteractionEvent::KeyF5; break; case Qt::Key_F6: key = mitk::InteractionEvent::KeyF6; break; case Qt::Key_F7: key = mitk::InteractionEvent::KeyF7; break; case Qt::Key_F8: key = mitk::InteractionEvent::KeyF8; break; case Qt::Key_F9: key = mitk::InteractionEvent::KeyF9; break; case Qt::Key_F10: key = mitk::InteractionEvent::KeyF10; break; case Qt::Key_F11: key = mitk::InteractionEvent::KeyF11; break; case Qt::Key_F12: key = mitk::InteractionEvent::KeyF12; break; case Qt::Key_End: key = mitk::InteractionEvent::KeyEnd; break; case Qt::Key_Home: key = mitk::InteractionEvent::KeyPos1; break; case Qt::Key_Insert: key = mitk::InteractionEvent::KeyInsert; break; case Qt::Key_PageDown: key = mitk::InteractionEvent::KeyPageDown; break; case Qt::Key_PageUp: key = mitk::InteractionEvent::KeyPageUp; break; case Qt::Key_Space: key = mitk::InteractionEvent::KeySpace; break; } } return key; } int QmitkRenderWindow::GetDelta(QWheelEvent *we) const { return we->delta(); } void QmitkRenderWindow::UpdateStatusBar(mitk::Point2D pointerPositionOnScreen) { mitk::Point3D worldPosition; m_Renderer->ForceImmediateUpdate(); m_Renderer->DisplayToWorld(pointerPositionOnScreen, worldPosition); auto statusBar = mitk::StatusBar::GetInstance(); statusBar->DisplayRendererInfo(worldPosition, m_Renderer->GetTime()); } diff --git a/Modules/QtWidgets/src/QmitkRenderWindowContextDataStorageInspector.cpp b/Modules/QtWidgets/src/QmitkRenderWindowContextDataStorageInspector.cpp index 664f99a2f6..cff14a7f17 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindowContextDataStorageInspector.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindowContextDataStorageInspector.cpp @@ -1,144 +1,137 @@ /*============================================================================ 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. ============================================================================*/ // render window manager UI module #include "QmitkRenderWindowContextDataStorageInspector.h" #include #include #include #include #include // qt #include #include QmitkRenderWindowContextDataStorageInspector::QmitkRenderWindowContextDataStorageInspector( QWidget* parent /* =nullptr */, mitk::BaseRenderer* renderer /* = nullptr */) : QmitkAbstractDataStorageInspector(parent) { m_Controls.setupUi(this); mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer{ renderer }; m_RenderWindowLayerController = std::make_unique(); - m_RenderWindowLayerController->SetControlledRenderer(controlledRenderer); m_StorageModel = std::make_unique(this); m_StorageModel->SetControlledRenderer(controlledRenderer); m_Controls.renderWindowTreeView->setModel(m_StorageModel.get()); m_Controls.renderWindowTreeView->setHeaderHidden(true); m_Controls.renderWindowTreeView->setEditTriggers(QAbstractItemView::NoEditTriggers); m_Controls.renderWindowTreeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.renderWindowTreeView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_Controls.renderWindowTreeView->setAlternatingRowColors(true); m_Controls.renderWindowTreeView->setDragEnabled(true); m_Controls.renderWindowTreeView->setDropIndicatorShown(true); m_Controls.renderWindowTreeView->setAcceptDrops(true); m_Controls.renderWindowTreeView->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Controls.renderWindowTreeView, &QTreeView::customContextMenuRequested, this, &QmitkRenderWindowContextDataStorageInspector::OnContextMenuRequested); SetUpConnections(); if (nullptr == renderer) { return; } m_StorageModel->SetCurrentRenderer(renderer); } QAbstractItemView* QmitkRenderWindowContextDataStorageInspector::GetView() { return m_Controls.renderWindowTreeView; } const QAbstractItemView* QmitkRenderWindowContextDataStorageInspector::GetView() const { return m_Controls.renderWindowTreeView; } void QmitkRenderWindowContextDataStorageInspector::SetSelectionMode(SelectionMode mode) { m_Controls.renderWindowTreeView->setSelectionMode(mode); } QmitkRenderWindowContextDataStorageInspector::SelectionMode QmitkRenderWindowContextDataStorageInspector::GetSelectionMode() const { return m_Controls.renderWindowTreeView->selectionMode(); } QItemSelectionModel* QmitkRenderWindowContextDataStorageInspector::GetDataNodeSelectionModel() const { return m_Controls.renderWindowTreeView->selectionModel(); } void QmitkRenderWindowContextDataStorageInspector::Initialize() { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) return; m_StorageModel->SetDataStorage(dataStorage); mitk::NodePredicateAnd::Pointer noHelperObjects = mitk::NodePredicateAnd::New(); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_StorageModel->SetNodePredicate(noHelperObjects); m_RenderWindowLayerController->SetDataStorage(dataStorage); m_Connector->SetView(m_Controls.renderWindowTreeView); } void QmitkRenderWindowContextDataStorageInspector::SetUpConnections() { connect(m_StorageModel.get(), &QAbstractItemModel::rowsInserted, this, &QmitkRenderWindowContextDataStorageInspector::ModelRowsInserted); } void QmitkRenderWindowContextDataStorageInspector::ModelRowsInserted(const QModelIndex& parent, int /*start*/, int /*end*/) { m_Controls.renderWindowTreeView->setExpanded(parent, true); } -void QmitkRenderWindowContextDataStorageInspector::ResetRenderer() -{ - m_RenderWindowLayerController->ResetRenderer(true, m_StorageModel->GetCurrentRenderer()); - m_Controls.renderWindowTreeView->clearSelection(); -} - void QmitkRenderWindowContextDataStorageInspector::OnContextMenuRequested(const QPoint& pos) { QMenu contextMenu; contextMenu.addAction(tr("Reinit with node"), this, &QmitkRenderWindowContextDataStorageInspector::OnReinit); contextMenu.addAction(tr("Reset to node geometry"), this, &QmitkRenderWindowContextDataStorageInspector::OnReset); contextMenu.exec(this->mapToGlobal(pos)); } void QmitkRenderWindowContextDataStorageInspector::OnReinit() { auto nodes = this->GetSelectedNodes(); emit ReinitAction(nodes); } void QmitkRenderWindowContextDataStorageInspector::OnReset() { auto nodes = this->GetSelectedNodes(); emit ResetAction(nodes); } diff --git a/Modules/QtWidgets/src/QmitkRenderWindowDataNodeTableModel.cpp b/Modules/QtWidgets/src/QmitkRenderWindowDataNodeTableModel.cpp new file mode 100644 index 0000000000..a4c6cb67ae --- /dev/null +++ b/Modules/QtWidgets/src/QmitkRenderWindowDataNodeTableModel.cpp @@ -0,0 +1,370 @@ +/*============================================================================ + +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 "QmitkRenderWindowDataNodeTableModel.h" + +// mitk core +#include + +// qt widgets module +#include +#include +#include +#include +#include + +#include +#include + +QmitkRenderWindowDataNodeTableModel::QmitkRenderWindowDataNodeTableModel(QObject* parent /*= nullptr*/) + : QAbstractItemModel(parent) +{ + m_RenderWindowLayerController = std::make_unique(); + + m_VisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); + m_InvisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); + m_ArrowIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/reset.svg")); + m_TimesIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/times.svg")); +} + +void QmitkRenderWindowDataNodeTableModel::UpdateModelData() +{ + auto baseRenderer = m_BaseRenderer.Lock(); + + auto greaterThan = [&baseRenderer](const mitk::DataNode* dataNodeLeft, const mitk::DataNode* dataNodeRight) + { + int layerLeft = -1; + int layerRight = -1; + + bool layerLeftFound = dataNodeLeft->GetIntProperty("layer", layerLeft, baseRenderer); + bool layerRightFound = dataNodeRight->GetIntProperty("layer", layerRight, baseRenderer); + + if (layerLeftFound && layerRightFound) + { + return layerLeft > layerRight; + } + + return true; + }; + + // sort node selection + beginResetModel(); + std::sort(m_CurrentSelection.begin(), m_CurrentSelection.end(), greaterThan); + endResetModel(); + + emit ModelUpdated(); +} + +void QmitkRenderWindowDataNodeTableModel::SetDataStorage(mitk::DataStorage* dataStorage) +{ + m_RenderWindowLayerController->SetDataStorage(dataStorage); +} + +void QmitkRenderWindowDataNodeTableModel::SetCurrentRenderer(mitk::BaseRenderer* baseRenderer) +{ + if (m_BaseRenderer == baseRenderer) + { + // resetting the same base renderer does nothing + return; + } + + m_BaseRenderer = baseRenderer; + + // update the model, since a new base renderer could have set the relevant node-properties differently + this->UpdateModelData(); +} + +mitk::BaseRenderer::Pointer QmitkRenderWindowDataNodeTableModel::GetCurrentRenderer() const +{ + return m_BaseRenderer.Lock(); +} + +void QmitkRenderWindowDataNodeTableModel::SetCurrentSelection(NodeList selectedNodes) +{ + m_CurrentSelection = selectedNodes; + + // update the model: sort the current internal selection + this->UpdateModelData(); +} + +QmitkRenderWindowDataNodeTableModel::NodeList QmitkRenderWindowDataNodeTableModel::GetCurrentSelection() const +{ + return m_CurrentSelection; +} + +QModelIndex QmitkRenderWindowDataNodeTableModel::index(int row, int column, const QModelIndex& parent /*= QModelIndex()*/) const +{ + bool hasIndex = this->hasIndex(row, column, parent); + if (hasIndex) + { + return this->createIndex(row, column); + } + + return QModelIndex(); +} + +QModelIndex QmitkRenderWindowDataNodeTableModel::parent(const QModelIndex& /*child*/) const +{ + return QModelIndex(); +} + +int QmitkRenderWindowDataNodeTableModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const +{ + if (parent.isValid()) + { + return 0; + } + + return static_cast(m_CurrentSelection.size()); +} + +int QmitkRenderWindowDataNodeTableModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const +{ + if (parent.isValid()) + { + return 0; + } + + return 4; +} + +Qt::ItemFlags QmitkRenderWindowDataNodeTableModel::flags(const QModelIndex &index) const +{ + if (this != index.model()) + { + return Qt::NoItemFlags; + } + + if (index.isValid()) + { + if (index.column() == 0) + { + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | + Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + } + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + } + else + { + return Qt::ItemIsDropEnabled; + } +} + +QVariant QmitkRenderWindowDataNodeTableModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || this != index.model()) + { + return QVariant(); + } + + if (index.row() < 0 || index.row() >= static_cast(m_CurrentSelection.size())) + { + return QVariant(); + } + + mitk::DataNode* dataNode = m_CurrentSelection.at(index.row()); + + if (role == QmitkDataNodeRole) + { + return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); + } + + if (role == QmitkDataNodeRawPointerRole) + { + return QVariant::fromValue(dataNode); + } + + if (index.column() == 0) // data node information column + { + QString nodeName = QString::fromStdString(dataNode->GetName()); + if (nodeName.isEmpty()) + { + nodeName = "unnamed"; + } + + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + return nodeName; + } + + if (role == Qt::ToolTipRole) + { + return QVariant("Name of the data node."); + } + + if (role == Qt::DecorationRole) + { + QmitkNodeDescriptor* nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(dataNode); + return QVariant(nodeDescriptor->GetIcon(dataNode)); + } + } + + if (index.column() == 1) // node visibility column + { + if (role == Qt::DecorationRole) + { + auto baseRenderer = m_BaseRenderer.Lock(); + bool visibility = false; + dataNode->GetVisibility(visibility, baseRenderer); + + return visibility ? QVariant(m_VisibleIcon) : QVariant(m_InvisibleIcon); + } + + if (role == Qt::EditRole) + { + auto baseRenderer = m_BaseRenderer.Lock(); + bool visibility = false; + dataNode->GetVisibility(visibility, baseRenderer); + + return QVariant(visibility); + } + } + + if (index.column() == 2) // reset geometry column + { + if (role == Qt::DecorationRole) + { + return QVariant(m_ArrowIcon); + } + } + + if (index.column() == 3) // remove node column + { + if (role == Qt::DecorationRole) + { + return QVariant(m_TimesIcon); + } + } + + return QVariant(); +} + +bool QmitkRenderWindowDataNodeTableModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid() || this != index.model()) + { + return false; + } + + if (index.row() < 0 || index.row() >= static_cast(m_CurrentSelection.size())) + { + return false; + } + + mitk::DataNode* dataNode = m_CurrentSelection.at(index.row()); + + if (index.column() == 0) // data node information column + { + if (role == Qt::EditRole && !value.toString().isEmpty()) + { + dataNode->SetName(value.toString().toStdString()); + emit dataChanged(index, index); + return true; + } + } + + if (index.column() == 1) // data node visibility column + { + if (role == Qt::EditRole) + { + auto baseRenderer = m_BaseRenderer.Lock(); + bool visibility = value.toBool(); + dataNode->SetVisibility(visibility, baseRenderer); + + if (baseRenderer.IsNotNull()) + { + // Explicitly request an update since a renderer-specific property change does not mark the node as modified. + // see https://phabricator.mitk.org/T22322 + mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); + } + + emit dataChanged(index, index); + return true; + } + } + + return false; +} + +Qt::DropActions QmitkRenderWindowDataNodeTableModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +Qt::DropActions QmitkRenderWindowDataNodeTableModel::supportedDragActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +QStringList QmitkRenderWindowDataNodeTableModel::mimeTypes() const +{ + QStringList types = QAbstractItemModel::mimeTypes(); + types << QmitkMimeTypes::DataNodePtrs; + return types; +} + +QMimeData* QmitkRenderWindowDataNodeTableModel::mimeData(const QModelIndexList& indexes) const +{ + QMimeData* mimeData = new QMimeData(); + QByteArray encodedData; + + QDataStream stream(&encodedData, QIODevice::WriteOnly); + + for (const auto& index : indexes) + { + if (index.isValid()) + { + auto dataNode = data(index, QmitkDataNodeRawPointerRole).value(); + stream << reinterpret_cast(dataNode); + } + } + + mimeData->setData(QmitkMimeTypes::DataNodePtrs, encodedData); + return mimeData; +} + +bool QmitkRenderWindowDataNodeTableModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int /*row*/, int /*column*/, const QModelIndex& parent) +{ + if (action == Qt::IgnoreAction) + { + return true; + } + + if (!data->hasFormat(QmitkMimeTypes::DataNodePtrs)) + { + return false; + } + + if (parent.isValid()) + { + auto baseRenderer = m_BaseRenderer.Lock(); + int layer = -1; + auto dataNode = this->data(parent, QmitkDataNodeRawPointerRole).value(); + if (nullptr != dataNode) + { + dataNode->GetIntProperty("layer", layer, baseRenderer); + } + + auto dataNodeList = QmitkMimeTypes::ToDataNodePtrList(data); + for (const auto& dataNode : qAsConst(dataNodeList)) + { + m_RenderWindowLayerController->MoveNodeToPosition(dataNode, layer, baseRenderer); + } + + this->UpdateModelData(); + + return true; + } + + return false; +} diff --git a/Modules/QtWidgets/src/QmitkRenderWindowDataStorageTreeModel.cpp b/Modules/QtWidgets/src/QmitkRenderWindowDataStorageTreeModel.cpp index 3d761af13c..cebc96e009 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindowDataStorageTreeModel.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindowDataStorageTreeModel.cpp @@ -1,628 +1,606 @@ /*============================================================================ 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. ============================================================================*/ // render window manager UI module #include "QmitkRenderWindowDataStorageTreeModel.h" #include - -// mitk core -#include - // qt widgets module #include "QmitkCustomVariants.h" #include "QmitkEnums.h" #include "QmitkMimeTypes.h" #include "QmitkNodeDescriptorManager.h" QmitkRenderWindowDataStorageTreeModel::QmitkRenderWindowDataStorageTreeModel(QObject* parent /*= nullptr*/) : QmitkAbstractDataStorageModel(parent) , m_Root(nullptr) { m_RenderWindowLayerController = std::make_unique(); ResetTree(); } void QmitkRenderWindowDataStorageTreeModel::DataStorageChanged() { m_RenderWindowLayerController->SetDataStorage(m_DataStorage.Lock()); ResetTree(); UpdateModelData(); } void QmitkRenderWindowDataStorageTreeModel::NodePredicateChanged() { ResetTree(); UpdateModelData(); } void QmitkRenderWindowDataStorageTreeModel::NodeAdded(const mitk::DataNode* node) { - for (const auto renderer : m_ControlledRenderer) - { - // add the node to each render window - mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(const_cast(node), renderer); - } - auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } - mitk::NodePredicateBase::Pointer renderWindowPredicate = mitk::RenderWindowLayerUtilities::GetRenderWindowPredicate(baseRenderer); - mitk::NodePredicateAnd::Pointer combinedNodePredicate = mitk::NodePredicateAnd::New(); - combinedNodePredicate->AddPredicate(renderWindowPredicate); if (m_NodePredicate.IsNotNull()) { - combinedNodePredicate->AddPredicate(m_NodePredicate); - } - if (combinedNodePredicate->CheckNode(node)) - { - AddNodeInternal(node, baseRenderer); + if (m_NodePredicate->CheckNode(node)) + { + AddNodeInternal(node, baseRenderer); + } } } void QmitkRenderWindowDataStorageTreeModel::NodeChanged(const mitk::DataNode* node) { auto item = m_Root->Find(node); if (nullptr != item) { auto parentItem = item->GetParent(); // as the root node should not be removed one should always have a parent item if (nullptr == parentItem) { return; } auto index = createIndex(item->GetIndex(), 0, item); emit dataChanged(index, index); } } void QmitkRenderWindowDataStorageTreeModel::NodeRemoved(const mitk::DataNode* node) { RemoveNodeInternal(node); } QModelIndex QmitkRenderWindowDataStorageTreeModel::index(int row, int column, const QModelIndex& parent) const { auto item = GetItemByIndex(parent); if (nullptr != item) { item = item->GetChild(row); } if (nullptr == item) { return QModelIndex(); } return createIndex(row, column, item); } QModelIndex QmitkRenderWindowDataStorageTreeModel::parent(const QModelIndex& parent) const { auto item = GetItemByIndex(parent); if (nullptr != item) { item = item->GetParent(); } if(nullptr == item) { return QModelIndex(); } if (item == m_Root) { return QModelIndex(); } return createIndex(item->GetIndex(), 0, item); } int QmitkRenderWindowDataStorageTreeModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const { auto item = GetItemByIndex(parent); if (nullptr == item) { return 0; } return item->GetChildCount(); } int QmitkRenderWindowDataStorageTreeModel::columnCount(const QModelIndex&/* parent = QModelIndex()*/) const { if (0 == m_Root->GetChildCount()) { // no items stored, no need to display columns return 0; } return 1; } QVariant QmitkRenderWindowDataStorageTreeModel::data(const QModelIndex& index, int role) const { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return QVariant(); } if (!index.isValid() || this != index.model()) { return QVariant(); } auto item = GetItemByIndex(index); if (nullptr == item) { return QVariant(); } auto dataNode = item->GetDataNode(); if (nullptr == dataNode) { return QVariant(); } if (Qt::CheckStateRole == role) { bool visibility = false; dataNode->GetVisibility(visibility, baseRenderer); if (visibility) { return Qt::Checked; } else { return Qt::Unchecked; } } else if (Qt::DisplayRole == role) { return QVariant(QString::fromStdString(dataNode->GetName())); } else if (Qt::ToolTipRole == role) { return QVariant("Name of the data node."); } else if (Qt::DecorationRole == role) { QmitkNodeDescriptor* nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(dataNode); return nodeDescriptor->GetIcon(dataNode); } else if (Qt::UserRole == role || QmitkDataNodeRawPointerRole == role) { // user role always returns a reference to the data node, // which can be used to modify the data node in the data storage return QVariant::fromValue(dataNode); } else if (QmitkDataNodeRole == role) { return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); } return QVariant(); } bool QmitkRenderWindowDataStorageTreeModel::setData(const QModelIndex& index, const QVariant& value, int role /*= Qt::EditRole*/) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return false; } if (!index.isValid() || this != index.model()) { return false; } auto item = GetItemByIndex(index); if (nullptr == item) { return false; } auto dataNode = item->GetDataNode(); if (nullptr == dataNode) { return false; } if (Qt::EditRole == role && !value.toString().isEmpty()) { dataNode->SetName(value.toString().toStdString().c_str()); emit dataChanged(index, index); return true; } if (Qt::CheckStateRole == role) { Qt::CheckState newCheckState = static_cast(value.toInt()); bool isVisible = newCheckState; dataNode->SetVisibility(isVisible, baseRenderer); emit dataChanged(index, index); mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); return true; } return false; } Qt::ItemFlags QmitkRenderWindowDataStorageTreeModel::flags(const QModelIndex& index) const { if (this != index.model()) { return Qt::NoItemFlags; } if (!index.isValid()) { return Qt::ItemIsDropEnabled; } auto item = GetItemByIndex(index); if (nullptr == item) { return Qt::NoItemFlags; } const auto dataNode = item->GetDataNode(); if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(dataNode)) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } return Qt::NoItemFlags; } Qt::DropActions QmitkRenderWindowDataStorageTreeModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } Qt::DropActions QmitkRenderWindowDataStorageTreeModel::supportedDragActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList QmitkRenderWindowDataStorageTreeModel::mimeTypes() const { QStringList types = QAbstractItemModel::mimeTypes(); types << QmitkMimeTypes::DataNodePtrs; return types; } QMimeData* QmitkRenderWindowDataStorageTreeModel::mimeData(const QModelIndexList& indexes) const { QMimeData* mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); for (const auto& index : indexes) { if (index.isValid()) { auto dataNode = data(index, QmitkDataNodeRawPointerRole).value(); stream << reinterpret_cast(dataNode); } } mimeData->setData(QmitkMimeTypes::DataNodePtrs, encodedData); return mimeData; } bool QmitkRenderWindowDataStorageTreeModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int /*row*/, int /*column*/, const QModelIndex& parent) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return false; } if (action == Qt::IgnoreAction) { return true; } if (!data->hasFormat(QmitkMimeTypes::DataNodePtrs)) { return false; } if (!parent.isValid()) { return false; } int layer = -1; auto dataNode = this->data(parent, QmitkDataNodeRawPointerRole).value(); if (nullptr != dataNode) { dataNode->GetIntProperty("layer", layer, baseRenderer); } auto dataNodeList = QmitkMimeTypes::ToDataNodePtrList(data); for (const auto& dataNode : qAsConst(dataNodeList)) { m_RenderWindowLayerController->MoveNodeToPosition(dataNode, layer, baseRenderer); } ResetTree(); UpdateModelData(); AdjustLayerProperty(); return true; } void QmitkRenderWindowDataStorageTreeModel::SetControlledRenderer(mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer) { - m_RenderWindowLayerController->SetControlledRenderer(controlledRenderer); - m_ControlledRenderer = controlledRenderer; - ResetTree(); auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } for (const auto& renderer : controlledRenderer) { if (nullptr == renderer) { continue; } auto allDataNodes = dataStorage->GetAll(); for (const auto& dataNode : *allDataNodes) { // add the node to each render window mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(dataNode, renderer); } } } void QmitkRenderWindowDataStorageTreeModel::SetCurrentRenderer(mitk::BaseRenderer* baseRenderer) { if (m_BaseRenderer == baseRenderer) { return; } // base renderer changed // reset tree to build a new renderer-specific item hierarchy m_BaseRenderer = baseRenderer; ResetTree(); UpdateModelData(); } mitk::BaseRenderer::Pointer QmitkRenderWindowDataStorageTreeModel::GetCurrentRenderer() const { return m_BaseRenderer.Lock(); } void QmitkRenderWindowDataStorageTreeModel::ResetTree() { beginResetModel(); if (nullptr != m_Root) { m_Root->Delete(); } mitk::DataNode::Pointer rootDataNode = mitk::DataNode::New(); rootDataNode->SetName("Data Storage"); m_Root = new QmitkDataStorageTreeModelInternalItem(rootDataNode); endResetModel(); } void QmitkRenderWindowDataStorageTreeModel::UpdateModelData() { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNotNull()) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNotNull()) { auto allDataNodes = dataStorage->GetAll(); for (const auto& dataNode : *allDataNodes) { // add the node to each render window mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(dataNode, baseRenderer); } - mitk::NodePredicateBase::Pointer renderWindowPredicate = mitk::RenderWindowLayerUtilities::GetRenderWindowPredicate(baseRenderer); - mitk::NodePredicateAnd::Pointer combinedNodePredicate = mitk::NodePredicateAnd::New(); - combinedNodePredicate->AddPredicate(renderWindowPredicate); if (m_NodePredicate.IsNotNull()) { - combinedNodePredicate->AddPredicate(m_NodePredicate); - } - - auto filteredDataNodes = dataStorage->GetSubset(combinedNodePredicate); - for (const auto& dataNode : *filteredDataNodes) - { - AddNodeInternal(dataNode, baseRenderer); + auto filteredDataNodes = dataStorage->GetSubset(m_NodePredicate); + for (const auto& dataNode : *filteredDataNodes) + { + AddNodeInternal(dataNode, baseRenderer); + } } } } } void QmitkRenderWindowDataStorageTreeModel::AdjustLayerProperty() { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } std::vector treeAsVector; TreeToVector(m_Root, treeAsVector); int i = treeAsVector.size() - 1; for (auto it = treeAsVector.begin(); it != treeAsVector.end(); ++it) { auto dataNode = (*it)->GetDataNode(); dataNode->SetIntProperty("layer", i, baseRenderer); --i; } } void QmitkRenderWindowDataStorageTreeModel::TreeToVector(QmitkDataStorageTreeModelInternalItem* parent, std::vector& treeAsVector) const { QmitkDataStorageTreeModelInternalItem* item; for (int i = 0; i < parent->GetChildCount(); ++i) { item = parent->GetChild(i); TreeToVector(item, treeAsVector); treeAsVector.push_back(item); } } void QmitkRenderWindowDataStorageTreeModel::AddNodeInternal(const mitk::DataNode* dataNode, const mitk::BaseRenderer* renderer) { if (nullptr == dataNode || m_DataStorage.IsExpired() || nullptr != m_Root->Find(dataNode)) { return; } // find out if we have a root node auto parentItem = m_Root; QModelIndex index; auto parentDataNode = GetParentNode(dataNode); if (nullptr != parentDataNode) // no top level data node { parentItem = m_Root->Find(parentDataNode); if (nullptr == parentItem) { // parent node not contained in the tree; add it NodeAdded(parentDataNode); parentItem = m_Root->Find(parentDataNode); if (nullptr == parentItem) { // could not find and add the parent tree; abort return; } } // get the index of this parent with the help of the grand parent index = createIndex(parentItem->GetIndex(), 0, parentItem); } int firstRowWithASiblingBelow = 0; int nodeLayer = -1; dataNode->GetIntProperty("layer", nodeLayer, renderer); for (const auto& siblingItem : parentItem->GetChildren()) { int siblingLayer = -1; auto siblingNode = siblingItem->GetDataNode(); if (nullptr != siblingNode) { siblingNode->GetIntProperty("layer", siblingLayer, renderer); } if (nodeLayer > siblingLayer) { break; } ++firstRowWithASiblingBelow; } beginInsertRows(index, firstRowWithASiblingBelow, firstRowWithASiblingBelow); auto newNode = new QmitkDataStorageTreeModelInternalItem(const_cast(dataNode)); parentItem->InsertChild(newNode, firstRowWithASiblingBelow); endInsertRows(); } void QmitkRenderWindowDataStorageTreeModel::RemoveNodeInternal(const mitk::DataNode* dataNode) { if (nullptr == dataNode || nullptr == m_Root) { return; } auto item = m_Root->Find(dataNode); if (nullptr == item) { return; } auto parentItem = item->GetParent(); auto parentIndex = GetIndexByItem(parentItem); auto children = item->GetChildren(); beginRemoveRows(parentIndex, item->GetIndex(), item->GetIndex()); parentItem->RemoveChild(item); delete item; endRemoveRows(); if (!children.empty()) { // rebuild tree because children could not be at the top level ResetTree(); UpdateModelData(); } } mitk::DataNode* QmitkRenderWindowDataStorageTreeModel::GetParentNode(const mitk::DataNode* node) const { mitk::DataNode* dataNode = nullptr; auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return dataNode; } auto sources = dataStorage->GetSources(node); if (sources->empty()) { return dataNode; } return sources->front(); } QmitkDataStorageTreeModelInternalItem* QmitkRenderWindowDataStorageTreeModel::GetItemByIndex(const QModelIndex& index) const { if (index.isValid()) { return static_cast(index.internalPointer()); } return m_Root; } QModelIndex QmitkRenderWindowDataStorageTreeModel::GetIndexByItem(QmitkDataStorageTreeModelInternalItem* item) const { if (item == m_Root) { return QModelIndex(); } return createIndex(item->GetIndex(), 0, item); } diff --git a/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp b/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp index 8fc6d892a7..9e08a160f7 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp @@ -1,95 +1,181 @@ /*============================================================================ 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 "QmitkRenderWindowUtilityWidget.h" #include +// mitk core +#include +#include +#include +#include + +// mitk qt widgets +#include +#include + +// itk +#include + QmitkRenderWindowUtilityWidget::QmitkRenderWindowUtilityWidget( QWidget* parent/* = nullptr */, QmitkRenderWindow* renderWindow/* = nullptr */, mitk::DataStorage* dataStorage/* = nullptr */) - : m_Layout(nullptr) - , m_MenuBar(nullptr) - , m_RenderWindow(renderWindow) - , m_DataStorage(dataStorage) - , m_RenderWindowInspector(nullptr) + : m_NodeSelectionWidget(nullptr) , m_SliceNavigationWidget(nullptr) , m_StepperAdapter(nullptr) , m_ViewDirectionSelector(nullptr) { - m_Layout = new QHBoxLayout(this); - m_Layout->setMargin(0); - - auto* baseRenderer = mitk::BaseRenderer::GetInstance(m_RenderWindow->GetVtkRenderWindow()); - auto* sliceNavigationController = m_RenderWindow->GetSliceNavigationController(); - m_RenderWindowInspector = new QmitkRenderWindowContextDataStorageInspector(parent, baseRenderer); - m_RenderWindowInspector->SetDataStorage(m_DataStorage); - m_RenderWindowInspector->setObjectName(QStringLiteral("m_RenderWindowManipulatorWidget")); - connect(m_RenderWindowInspector, &QmitkRenderWindowContextDataStorageInspector::ReinitAction, - this, &QmitkRenderWindowUtilityWidget::ReinitAction); - connect(m_RenderWindowInspector, &QmitkRenderWindowContextDataStorageInspector::ResetAction, - this, &QmitkRenderWindowUtilityWidget::ResetAction); - - m_MenuBar = new QMenuBar(this); - m_MenuBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - auto menu = m_MenuBar->addMenu("Data"); - QWidgetAction* newAct = new QWidgetAction(menu); - newAct->setDefaultWidget(m_RenderWindowInspector); - menu->addAction(newAct); - m_Layout->addWidget(m_MenuBar); + this->setParent(parent); + auto layout = new QHBoxLayout(this); + layout->setMargin(0); + + mitk::NodePredicateAnd::Pointer noHelperObjects = mitk::NodePredicateAnd::New(); + noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); + noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); + + m_BaseRenderer = mitk::BaseRenderer::GetInstance(renderWindow->GetVtkRenderWindow()); + + m_NodeSelectionWidget = new QmitkSynchronizedNodeSelectionWidget(parent); + m_NodeSelectionWidget->SetBaseRenderer(m_BaseRenderer); + m_NodeSelectionWidget->SetDataStorage(dataStorage); + m_NodeSelectionWidget->SetNodePredicate(noHelperObjects); + + auto menuBar = new QMenuBar(this); + menuBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + auto dataMenu = menuBar->addMenu("Data"); + QWidgetAction* dataAction = new QWidgetAction(dataMenu); + dataAction->setDefaultWidget(m_NodeSelectionWidget); + dataMenu->addAction(dataAction); + layout->addWidget(menuBar); + + auto* synchPushButton = new QPushButton(this); + auto* synchIcon = new QIcon(); + auto synchronizeSvg = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); + auto desynchronizeSvg = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); + synchIcon->addPixmap(synchronizeSvg.pixmap(64), QIcon::Normal, QIcon::On); + synchIcon->addPixmap(desynchronizeSvg.pixmap(64), QIcon::Normal, QIcon::Off); + synchPushButton->setIcon(*synchIcon); + synchPushButton->setToolTip("Synchronize / desynchronize data management"); + synchPushButton->setCheckable(true); + synchPushButton->setChecked(true); + connect(synchPushButton, &QPushButton::clicked, + this, &QmitkRenderWindowUtilityWidget::ToggleSynchronization); + layout->addWidget(synchPushButton); + auto* sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); m_SliceNavigationWidget = new QmitkSliceNavigationWidget(this); m_StepperAdapter = new QmitkStepperAdapter(m_SliceNavigationWidget, sliceNavigationController->GetSlice()); - m_Layout->addWidget(m_SliceNavigationWidget); + layout->addWidget(m_SliceNavigationWidget); - mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer{ baseRenderer }; + mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer{ m_BaseRenderer }; m_RenderWindowViewDirectionController = std::make_unique(); m_RenderWindowViewDirectionController->SetControlledRenderer(controlledRenderer); m_RenderWindowViewDirectionController->SetDataStorage(dataStorage); m_ViewDirectionSelector = new QComboBox(this); QStringList viewDirections{ "axial", "coronal", "sagittal"}; m_ViewDirectionSelector->insertItems(0, viewDirections); connect(m_ViewDirectionSelector, &QComboBox::currentTextChanged, this, &QmitkRenderWindowUtilityWidget::ChangeViewDirection); - auto viewDirection = sliceNavigationController->GetDefaultViewDirection(); + UpdateViewPlaneSelection(); + + layout->addWidget(m_ViewDirectionSelector); + + // finally add observer, after all relevant objects have been created / initialized + sliceNavigationController->ConnectGeometrySendEvent(this); +} + +QmitkRenderWindowUtilityWidget::~QmitkRenderWindowUtilityWidget() +{ +} + +void QmitkRenderWindowUtilityWidget::ToggleSynchronization(bool synchronized) +{ + m_NodeSelectionWidget->SetSynchronized(synchronized); + emit SynchronizationToggled(m_NodeSelectionWidget); +} + +void QmitkRenderWindowUtilityWidget::SetGeometry(const itk::EventObject& event) +{ + if (!mitk::SliceNavigationController::GeometrySendEvent(nullptr, 0).CheckEvent(&event)) + { + return; + } + + const auto* sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); + auto viewDirection = sliceNavigationController->GetViewDirection(); + unsigned int axis = 0; switch (viewDirection) { + case mitk::AnatomicalPlane::Original: + return; case mitk::AnatomicalPlane::Axial: - m_ViewDirectionSelector->setCurrentIndex(0); + { + axis = 2; break; + } case mitk::AnatomicalPlane::Coronal: - m_ViewDirectionSelector->setCurrentIndex(1); + { + axis = 1; break; + } case mitk::AnatomicalPlane::Sagittal: - m_ViewDirectionSelector->setCurrentIndex(2); - break; - default: + { + axis = 0; break; } - m_Layout->addWidget(m_ViewDirectionSelector); -} + } -QmitkRenderWindowUtilityWidget::~QmitkRenderWindowUtilityWidget() -{ -} + const auto* inputTimeGeometry = sliceNavigationController->GetInputWorldTimeGeometry(); + const mitk::BaseGeometry* rendererGeometry = m_BaseRenderer->GetCurrentWorldGeometry(); -void QmitkRenderWindowUtilityWidget::SetInvertedSliceNavigation(bool inverted) -{ - m_SliceNavigationWidget->SetInverseDirection(inverted); + mitk::TimeStepType timeStep = sliceNavigationController->GetTime()->GetPos(); + mitk::BaseGeometry::ConstPointer geometry = inputTimeGeometry->GetGeometryForTimeStep(timeStep); + + mitk::AffineTransform3D::MatrixType matrix = geometry->GetIndexToWorldTransform()->GetMatrix(); + matrix.GetVnlMatrix().normalize_columns(); + mitk::AffineTransform3D::MatrixType::InternalMatrixType inverseMatrix = matrix.GetInverse(); + + int dominantAxis = itk::Function::Max3(inverseMatrix[0][axis], inverseMatrix[1][axis], inverseMatrix[2][axis]); + + bool referenceGeometryAxisInverted = inverseMatrix[dominantAxis][axis] < 0; + bool rendererZAxisInverted = rendererGeometry->GetAxisVector(2)[axis] < 0; + + m_SliceNavigationWidget->SetInverseDirection(referenceGeometryAxisInverted != rendererZAxisInverted); } void QmitkRenderWindowUtilityWidget::ChangeViewDirection(const QString& viewDirection) { m_RenderWindowViewDirectionController->SetViewDirectionOfRenderer(viewDirection.toStdString()); } + +void QmitkRenderWindowUtilityWidget::UpdateViewPlaneSelection() +{ + const auto sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); + const auto viewDirection = sliceNavigationController->GetDefaultViewDirection(); + switch (viewDirection) + { + case mitk::AnatomicalPlane::Axial: + m_ViewDirectionSelector->setCurrentIndex(0); + break; + case mitk::AnatomicalPlane::Coronal: + m_ViewDirectionSelector->setCurrentIndex(1); + break; + case mitk::AnatomicalPlane::Sagittal: + m_ViewDirectionSelector->setCurrentIndex(2); + break; + default: + break; + } +} diff --git a/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp b/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp index fa26a2dc61..4b05156f91 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp @@ -1,439 +1,322 @@ /*============================================================================ 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 "QmitkRenderWindowWidget.h" -#include -#include -#include - -// itk -#include - // vtk #include #include QmitkRenderWindowWidget::QmitkRenderWindowWidget(QWidget* parent/* = nullptr*/, const QString& widgetName/* = ""*/, - mitk::DataStorage* dataStorage/* = nullptr*/, - bool windowControls/* = false */) + mitk::DataStorage* dataStorage/* = nullptr*/) : QFrame(parent) , m_WidgetName(widgetName) , m_DataStorage(dataStorage) , m_RenderWindow(nullptr) , m_CrosshairManager(nullptr) - , m_UtilityWidget(nullptr) - , m_WindowControls(windowControls) { this->InitializeGUI(); } QmitkRenderWindowWidget::~QmitkRenderWindowWidget() { auto sliceNavigationController = this->GetSliceNavigationController(); if (nullptr != sliceNavigationController) { sliceNavigationController->SetCrosshairEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkRenderWindowWidget::SetCrosshairPosition)); } + this->DisableCrosshair(); } void QmitkRenderWindowWidget::SetDataStorage(mitk::DataStorage* dataStorage) { if (dataStorage == m_DataStorage) { return; } m_DataStorage = dataStorage; if (nullptr != m_RenderWindow) { mitk::BaseRenderer::GetInstance(m_RenderWindow->renderWindow())->SetDataStorage(dataStorage); } - - m_CrosshairManager->SetDataStorage(m_DataStorage); } mitk::SliceNavigationController* QmitkRenderWindowWidget::GetSliceNavigationController() const { return m_RenderWindow->GetSliceNavigationController(); } void QmitkRenderWindowWidget::RequestUpdate() { mitk::RenderingManager::GetInstance()->RequestUpdate(m_RenderWindow->renderWindow()); } void QmitkRenderWindowWidget::ForceImmediateUpdate() { mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(m_RenderWindow->renderWindow()); } +void QmitkRenderWindowWidget::AddUtilityWidget(QWidget* utilityWidget) +{ + m_Layout->insertWidget(0, utilityWidget); +} + void QmitkRenderWindowWidget::SetGradientBackgroundColors(const mitk::Color& upper, const mitk::Color& lower) { vtkRenderer* vtkRenderer = m_RenderWindow->GetRenderer()->GetVtkRenderer(); if (nullptr == vtkRenderer) { return; } m_GradientBackgroundColors.first = upper; m_GradientBackgroundColors.second = lower; vtkRenderer->SetBackground(lower[0], lower[1], lower[2]); vtkRenderer->SetBackground2(upper[0], upper[1], upper[2]); ShowGradientBackground(true); } void QmitkRenderWindowWidget::ShowGradientBackground(bool show) { m_RenderWindow->GetRenderer()->GetVtkRenderer()->SetGradientBackground(show); } bool QmitkRenderWindowWidget::IsGradientBackgroundOn() const { return m_RenderWindow->GetRenderer()->GetVtkRenderer()->GetGradientBackground(); } void QmitkRenderWindowWidget::SetDecorationColor(const mitk::Color& color) { m_DecorationColor = color; m_CornerAnnotation->GetTextProperty()->SetColor(m_DecorationColor[0], m_DecorationColor[1], m_DecorationColor[2]); QColor hexColor(m_DecorationColor[0] * 255, m_DecorationColor[1] * 255, m_DecorationColor[2] * 255); setStyleSheet("QmitkRenderWindowWidget { border: 2px solid " + hexColor.name(QColor::HexRgb) + "; }"); } void QmitkRenderWindowWidget::ShowColoredRectangle(bool show) { if (show) { setFrameStyle(QFrame::Box | QFrame::Plain); } else { setFrameStyle(NoFrame); } } bool QmitkRenderWindowWidget::IsColoredRectangleVisible() const { return frameStyle() > 0; } void QmitkRenderWindowWidget::ShowCornerAnnotation(bool show) { m_CornerAnnotation->SetVisibility(show); } bool QmitkRenderWindowWidget::IsCornerAnnotationVisible() const { return m_CornerAnnotation->GetVisibility() > 0; } void QmitkRenderWindowWidget::SetCornerAnnotationText(const std::string& cornerAnnotation) { m_CornerAnnotation->SetText(0, cornerAnnotation.c_str()); } std::string QmitkRenderWindowWidget::GetCornerAnnotationText() const { return std::string(m_CornerAnnotation->GetText(0)); } bool QmitkRenderWindowWidget::IsRenderWindowMenuActivated() const { return m_RenderWindow->GetActivateMenuWidgetFlag(); } void QmitkRenderWindowWidget::SetCrosshairVisibility(bool visible) { - m_CrosshairManager->SetCrosshairVisibility(visible); + m_CrosshairManager->SetCrosshairVisibility(visible, m_RenderWindow->GetRenderer()); this->RequestUpdate(); } bool QmitkRenderWindowWidget::GetCrosshairVisibility() { - return m_CrosshairManager->GetCrosshairVisibility(); + return m_CrosshairManager->GetCrosshairVisibility(m_RenderWindow->GetRenderer()); } void QmitkRenderWindowWidget::SetCrosshairGap(unsigned int gapSize) { m_CrosshairManager->SetCrosshairGap(gapSize); } -void QmitkRenderWindowWidget::AddPlanesToDataStorage() +void QmitkRenderWindowWidget::EnableCrosshair() { - m_CrosshairManager->AddPlanesToDataStorage(); + m_CrosshairManager->AddCrosshairNodeToDataStorage(m_DataStorage); } -void QmitkRenderWindowWidget::RemovePlanesFromDataStorage() +void QmitkRenderWindowWidget::DisableCrosshair() { - m_CrosshairManager->RemovePlanesFromDataStorage(); + m_CrosshairManager->RemoveCrosshairNodeFromDataStorage(m_DataStorage); } void QmitkRenderWindowWidget::InitializeGUI() { m_Layout = new QVBoxLayout(this); m_Layout->setMargin(0); setLayout(m_Layout); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setContentsMargins(0, 0, 0, 0); if (nullptr == m_DataStorage) { return; } mitk::RenderingManager::GetInstance()->SetDataStorage(m_DataStorage); // create render window for this render window widget m_RenderWindow = new QmitkRenderWindow(this, m_WidgetName, nullptr); m_RenderWindow->SetLayoutIndex(mitk::AnatomicalPlane::Sagittal); connect(m_RenderWindow, &QmitkRenderWindow::ResetGeometry, this, &QmitkRenderWindowWidget::OnResetGeometry); - auto sliceNavigationController = this->GetSliceNavigationController(); + auto* sliceNavigationController = this->GetSliceNavigationController(); sliceNavigationController->SetDefaultViewDirection(mitk::AnatomicalPlane::Sagittal); - if (m_WindowControls) - { - m_UtilityWidget = new QmitkRenderWindowUtilityWidget(this, m_RenderWindow, m_DataStorage); - m_Layout->addWidget(m_UtilityWidget); - connect(m_UtilityWidget, &QmitkRenderWindowUtilityWidget::ReinitAction, - this, &QmitkRenderWindowWidget::OnReinitAction); - connect(m_UtilityWidget, &QmitkRenderWindowUtilityWidget::ResetAction, - this, &QmitkRenderWindowWidget::OnResetAction); - } - m_Layout->addWidget(m_RenderWindow); // set colors and corner annotation InitializeDecorations(); // use crosshair manager - m_CrosshairManager = mitk::CrosshairManager::New(m_DataStorage, m_RenderWindow->GetRenderer()); + m_CrosshairManager = mitk::CrosshairManager::New(m_RenderWindow->GetRenderer()); sliceNavigationController->SetCrosshairEvent.AddListener( mitk::MessageDelegate1( this, &QmitkRenderWindowWidget::SetCrosshairPosition)); // finally add observer, after all relevant objects have been created / initialized sliceNavigationController->ConnectGeometrySendEvent(this); sliceNavigationController->ConnectGeometrySliceEvent(this); mitk::TimeGeometry::ConstPointer timeGeometry = m_DataStorage->ComputeBoundingGeometry3D(m_DataStorage->GetAll()); mitk::RenderingManager::GetInstance()->InitializeView(m_RenderWindow->GetVtkRenderWindow(), timeGeometry); } void QmitkRenderWindowWidget::InitializeDecorations() { vtkRenderer* vtkRenderer = m_RenderWindow->GetRenderer()->GetVtkRenderer(); if (nullptr == vtkRenderer) { return; } // initialize background color gradients float black[3] = { 0.0f, 0.0f, 0.0f }; SetGradientBackgroundColors(black, black); // initialize annotation text and decoration color setFrameStyle(QFrame::Box | QFrame::Plain); m_CornerAnnotation = vtkSmartPointer::New(); m_CornerAnnotation->SetText(0, "Sagittal"); m_CornerAnnotation->SetMaximumFontSize(12); if (0 == vtkRenderer->HasViewProp(m_CornerAnnotation)) { vtkRenderer->AddViewProp(m_CornerAnnotation); } float white[3] = { 1.0f, 1.0f, 1.0f }; SetDecorationColor(mitk::Color(white)); } void QmitkRenderWindowWidget::SetCrosshairPosition(const mitk::Point3D& newPosition) { m_CrosshairManager->SetCrosshairPosition(newPosition); this->RequestUpdate(); } mitk::Point3D QmitkRenderWindowWidget::GetCrosshairPosition() const { return m_CrosshairManager->GetCrosshairPosition(); } void QmitkRenderWindowWidget::SetGeometry(const itk::EventObject& event) { if (!mitk::SliceNavigationController::GeometrySendEvent(nullptr, 0).CheckEvent(&event)) { return; } - auto sliceNavigationController = this->GetSliceNavigationController(); - const auto* inputTimeGeometry = sliceNavigationController->GetInputWorldTimeGeometry(); - m_CrosshairManager->ComputeOrientedTimeGeometries(inputTimeGeometry); - - if (m_WindowControls) + const auto* planeGeometry = this->GetSliceNavigationController()->GetCurrentPlaneGeometry(); + if (nullptr == planeGeometry) { - this->ComputeInvertedSliceNavigation(); + mitkThrow() << "No valid plane geometry set. Render window is in an invalid state."; } + + return SetCrosshairPosition(planeGeometry->GetCenter()); } void QmitkRenderWindowWidget::SetGeometrySlice(const itk::EventObject& event) { if (!mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0).CheckEvent(&event)) { return; } - auto sliceNavigationController = this->GetSliceNavigationController(); - m_CrosshairManager->UpdateSlice(sliceNavigationController); -} - -void QmitkRenderWindowWidget::ComputeInvertedSliceNavigation() -{ - auto sliceNavigationController = this->GetSliceNavigationController(); - auto viewDirection = sliceNavigationController->GetViewDirection(); - unsigned int axis = 0; - switch (viewDirection) - { - case mitk::AnatomicalPlane::Original: - return; - case mitk::AnatomicalPlane::Axial: - { - axis = 2; - break; - } - case mitk::AnatomicalPlane::Coronal: - { - axis = 1; - break; - } - case mitk::AnatomicalPlane::Sagittal: - { - axis = 0; - break; - } - } - - const auto* inputTimeGeometry = sliceNavigationController->GetInputWorldTimeGeometry(); - const mitk::BaseGeometry* rendererGeometry = m_RenderWindow->GetRenderer()->GetCurrentWorldGeometry(); - - // todo: check timepoint / timestep - mitk::TimeStepType timeStep = sliceNavigationController->GetTime()->GetPos(); - mitk::BaseGeometry::ConstPointer geometry = inputTimeGeometry->GetGeometryForTimeStep(timeStep); - - mitk::AffineTransform3D::MatrixType matrix = geometry->GetIndexToWorldTransform()->GetMatrix(); - matrix.GetVnlMatrix().normalize_columns(); - mitk::AffineTransform3D::MatrixType::InternalMatrixType inverseMatrix = matrix.GetInverse(); - - int dominantAxis = itk::Function::Max3(inverseMatrix[0][axis], inverseMatrix[1][axis], inverseMatrix[2][axis]); - - bool referenceGeometryAxisInverted = inverseMatrix[dominantAxis][axis] < 0; - bool rendererZAxisInverted = rendererGeometry->GetAxisVector(2)[axis] < 0; - - m_UtilityWidget->SetInvertedSliceNavigation(referenceGeometryAxisInverted != rendererZAxisInverted); -} - -void QmitkRenderWindowWidget::OnReinitAction(QList selectedNodes) -{ - if (selectedNodes.empty()) - { - return; - } - - auto* baseRenderer = mitk::BaseRenderer::GetInstance(m_RenderWindow->renderWindow()); - auto boundingBoxPredicate = mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("includeInBoundingBox", mitk::BoolProperty::New(false), baseRenderer)); - mitk::DataStorage::SetOfObjects::Pointer nodes = mitk::DataStorage::SetOfObjects::New(); - for (const auto& dataNode : selectedNodes) - { - if (boundingBoxPredicate->CheckNode(dataNode)) - { - nodes->InsertElement(nodes->Size(), dataNode); - } - } - - if (nodes->empty()) - { - return; - } - - if (1 == nodes->Size()) - { - auto selectedImage = dynamic_cast(nodes->ElementAt(0)->GetData()); - - if (nullptr != selectedImage) - { - mitk::RenderingManager::GetInstance()->InitializeView(baseRenderer->GetRenderWindow(), selectedImage->GetTimeGeometry()); - return; - } - } - - auto boundingGeometry = m_DataStorage->ComputeBoundingGeometry3D(nodes, "visible", baseRenderer); - mitk::RenderingManager::GetInstance()->InitializeView(baseRenderer->GetRenderWindow(), boundingGeometry); -} - -void QmitkRenderWindowWidget::OnResetAction(QList selectedNodes) -{ - if (selectedNodes.empty()) - { - return; - } - - auto selectedImage = dynamic_cast(selectedNodes.front()->GetData()); - if (nullptr == selectedImage) - { - return; - } - - const mitk::TimeGeometry* referenceGeometry = selectedImage->GetTimeGeometry(); - this->ResetGeometry(referenceGeometry); + const auto* sliceNavigationController = this->GetSliceNavigationController(); + m_CrosshairManager->UpdateCrosshairPosition(sliceNavigationController); } void QmitkRenderWindowWidget::OnResetGeometry() { - auto* baseRenderer = mitk::BaseRenderer::GetInstance(m_RenderWindow->GetRenderWindow()); + const auto* baseRenderer = mitk::BaseRenderer::GetInstance(m_RenderWindow->GetRenderWindow()); const auto* interactionReferenceGeometry = baseRenderer->GetInteractionReferenceGeometry(); this->ResetGeometry(interactionReferenceGeometry); m_RenderWindow->ShowOverlayMessage(false); } void QmitkRenderWindowWidget::ResetGeometry(const mitk::TimeGeometry* referenceGeometry) { if (nullptr == referenceGeometry) { return; } mitk::TimeStepType imageTimeStep = 0; // store the current position to set it again later, if the camera should not be reset mitk::Point3D currentPosition = this->GetCrosshairPosition(); // store the current time step to set it again later, if the camera should not be reset auto* renderingManager = mitk::RenderingManager::GetInstance(); const auto currentTimePoint = renderingManager->GetTimeNavigationController()->GetSelectedTimePoint(); if (referenceGeometry->IsValidTimePoint(currentTimePoint)) { imageTimeStep = referenceGeometry->TimePointToTimeStep(currentTimePoint); } - auto* baseRenderer = mitk::BaseRenderer::GetInstance(m_RenderWindow->renderWindow()); + const auto* baseRenderer = mitk::BaseRenderer::GetInstance(m_RenderWindow->renderWindow()); renderingManager->InitializeView(baseRenderer->GetRenderWindow(), referenceGeometry, false); // reset position and time step this->GetSliceNavigationController()->SelectSliceByPoint(currentPosition); renderingManager->GetTimeNavigationController()->GetTime()->SetPos(imageTimeStep); } diff --git a/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp new file mode 100644 index 0000000000..5f746472e1 --- /dev/null +++ b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp @@ -0,0 +1,670 @@ +/*============================================================================ + +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. + +============================================================================*/ + +// mitk qt widgets module +#include +#include +#include +#include + +// mitk core module +#include +#include +#include +#include + +QmitkSynchronizedNodeSelectionWidget::QmitkSynchronizedNodeSelectionWidget(QWidget* parent) + : QmitkAbstractNodeSelectionWidget(parent) +{ + m_Controls.setupUi(this); + + m_StorageModel = std::make_unique(this); + + m_Controls.tableView->setModel(m_StorageModel.get()); + m_Controls.tableView->horizontalHeader()->setVisible(false); + m_Controls.tableView->verticalHeader()->setVisible(false); + m_Controls.tableView->setSelectionMode(QAbstractItemView::SingleSelection); + m_Controls.tableView->setSelectionBehavior(QAbstractItemView::SelectRows); + m_Controls.tableView->setContextMenuPolicy(Qt::CustomContextMenu); + + this->SetUpConnections(); + this->Initialize(); +} + +QmitkSynchronizedNodeSelectionWidget::~QmitkSynchronizedNodeSelectionWidget() +{ + auto baseRenderer = m_BaseRenderer.Lock(); + if (baseRenderer.IsNull()) + { + return; + } + + auto dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNull()) + { + return; + } + + bool isSynchronized = this->IsSynchronized(); + if (!isSynchronized) + { + // If the model is not synchronizes, + // we know that renderer-specific properties exist for all nodes. + // These properties need to be removed from the nodes. + auto allNodes = dataStorage->GetAll(); + for (auto& node : *allNodes) + { + // Delete the relevant renderer-specific properties for the node using the current base renderer. + mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); + } + } +} + +void QmitkSynchronizedNodeSelectionWidget::SetBaseRenderer(mitk::BaseRenderer* baseRenderer) +{ + if (m_BaseRenderer == baseRenderer) + { + // no need to do something + return; + } + + if (nullptr == baseRenderer) + { + return; + } + + auto oldBaseRenderer = m_BaseRenderer.Lock(); + m_BaseRenderer = baseRenderer; + + auto dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNull()) + { + return; + } + + bool isSynchronized = this->IsSynchronized(); + if (isSynchronized) + { + // If the model is synchronized, + // all nodes use global / default properties. + // No renderer-specific property lists should exist + // so there is no need to transfer any property values. + } + else + { + // If the model is not synchronized, + // we know that renderer-specific properties exist for all nodes. + // These properties need to be removed from the nodes and + // we need to transfer their values to new renderer-specific properties. + auto allNodes = dataStorage->GetAll(); + for (auto& node : *allNodes) + { + // Set the relevant renderer-specific properties for the node using the new base renderer. + // By transferring the values from the old property list, + // the same property-state is kept when switching to another base renderer. + mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(node, baseRenderer, oldBaseRenderer); + // Delete the relevant renderer-specific properties for the node using the old base renderer. + mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, oldBaseRenderer); + } + } + + this->Initialize(); +} + +void QmitkSynchronizedNodeSelectionWidget::SetSelectAll(bool selectAll) +{ + if (selectAll == m_Controls.selectionModeCheckBox->isChecked()) + { + // no need to do something + return; + } + + m_Controls.selectionModeCheckBox->setChecked(selectAll); +} + +bool QmitkSynchronizedNodeSelectionWidget::GetSelectAll() const +{ + return m_Controls.selectionModeCheckBox->isChecked(); +} + +void QmitkSynchronizedNodeSelectionWidget::SetSynchronized(bool synchronize) +{ + if (synchronize == this->IsSynchronized()) + { + // no need to do something + return; + } + + auto baseRenderer = m_BaseRenderer.Lock(); + if (baseRenderer.IsNull()) + { + return; + } + + auto dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNull()) + { + return; + } + + if (synchronize) + { + // set the base renderer of the model to nullptr, such that global properties are used + m_StorageModel->SetCurrentRenderer(nullptr); + + // If the model is synchronized, + // we know that the model was not synchronized before. + // That means that all nodes use renderer-specific properties, + // but now all nodes need global properties. + // Thus we need to remove the renderer-specific properties of all nodes of the + // datastorage. + auto allNodes = dataStorage->GetAll(); + for (auto& node : *allNodes) + { + // For helper / hidden nodes: + // If the node predicate does not match, do not remove the renderer-specific property + // This is relevant for the crosshair data nodes, which are only visible inside their + // corresponding render window. + if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) + { + // Delete the relevant renderer-specific properties for the node using the current base renderer. + mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); + } + } + } + else + { + // set the base renderer of the model to current base renderer, such that renderer-specific properties are used + m_StorageModel->SetCurrentRenderer(baseRenderer); + + // If the model is not synchronized anymore, + // we know that the model was synchronized before. + // That means that all nodes use global / default properties, + // but now all nodes need renderer-specific properties. + // Thus we need to modify the renderer-specific properties of all nodes of the + // datastorage: + // - hide those nodes, which are not part of the newly selected nodes. + // - keep the property values of those nodes, which are part of the new selection AND + // have been selected before + auto currentNodeSelection = this->GetCurrentInternalSelection(); + auto allNodes = dataStorage->GetAll(); + for (auto& node : *allNodes) + { + // check if the node is part of the current selection + auto finding = std::find(std::begin(currentNodeSelection), std::end(currentNodeSelection), node); + if (finding != std::end(currentNodeSelection)) // node found / part of the current selection + { + // Set the relevant renderer-specific properties for the node using the curent base renderer. + // By transferring the values from the global / default property list, + // the same property-state is kept when switching to non-synchronized mode. + mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(node, baseRenderer, nullptr); + } + else + { + // If the node is not part of the selection, unset the relevant renderer-specific properties. + // This will unset the "visible" and "layer" property for the renderer-specific property list and + // hide the node for this renderer. + // ATTENTION: This is required, since the synchronized property needs to be overwritten + // to make sure that the visibility is correctly set for the specific base renderer. + this->DeselectNode(node); + } + } + } + + // Since the synchronization might lead to a different node order depending on the layer properties, the render window + // needs to be updated. + // Explicitly request an update since a renderer-specific property change does not mark the node as modified. + // see https://phabricator.mitk.org/T22322 + mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); +} + +bool QmitkSynchronizedNodeSelectionWidget::IsSynchronized() const +{ + return m_StorageModel->GetCurrentRenderer().IsNull(); +} + +void QmitkSynchronizedNodeSelectionWidget::OnModelUpdated() +{ + m_Controls.tableView->resizeRowsToContents(); + m_Controls.tableView->resizeColumnsToContents(); +} + +void QmitkSynchronizedNodeSelectionWidget::OnSelectionModeChanged(bool selectAll) +{ + emit SelectionModeChanged(selectAll); + + if (selectAll) + { + auto dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNull()) + { + return; + } + + auto allNodes = m_NodePredicate ? dataStorage->GetSubset(m_NodePredicate) : dataStorage->GetAll(); + NodeList currentSelection; + for (auto& node : *allNodes) + { + currentSelection.append(node); + } + + this->HandleChangeOfInternalSelection(currentSelection); + } +} + +void QmitkSynchronizedNodeSelectionWidget::OnEditSelection() +{ + QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this); + + dialog->SetDataStorage(m_DataStorage.Lock()); + dialog->SetNodePredicate(m_NodePredicate); + dialog->SetCurrentSelection(m_StorageModel->GetCurrentSelection()); + dialog->SetSelectionMode(QAbstractItemView::MultiSelection); + + m_Controls.changeSelectionButton->setChecked(true); + if (dialog->exec()) + { + m_Controls.selectionModeCheckBox->setChecked(false); + emit SelectionModeChanged(false); + + auto selectedNodes = dialog->GetSelectedNodes(); + this->HandleChangeOfInternalSelection(selectedNodes); + } + + m_Controls.changeSelectionButton->setChecked(false); + + delete dialog; +} + +void QmitkSynchronizedNodeSelectionWidget::OnTableClicked(const QModelIndex& index) +{ + if (!index.isValid() || m_StorageModel.get() != index.model()) + { + return; + } + + auto baseRenderer = m_BaseRenderer.Lock(); + if (baseRenderer.IsNull()) + { + return; + } + + QVariant dataNodeVariant = index.data(QmitkDataNodeRole); + auto dataNode = dataNodeVariant.value(); + + if (index.column() == 1) // node visibility column + { + bool visibiliy = index.data(Qt::EditRole).toBool(); + m_StorageModel->setData(index, QVariant(!visibiliy), Qt::EditRole); + return; + } + + if (index.column() == 2) // reinit node column + { + this->ReinitNode(dataNode); + return; + } + + if (index.column() == 3) // remove node column + { + this->RemoveFromInternalSelection(dataNode); + return; + } +} + +void QmitkSynchronizedNodeSelectionWidget::SetUpConnections() +{ + connect(m_StorageModel.get(), &QmitkRenderWindowDataNodeTableModel::ModelUpdated, + this, &QmitkSynchronizedNodeSelectionWidget::OnModelUpdated); + + connect(m_Controls.selectionModeCheckBox, &QCheckBox::clicked, + this, &QmitkSynchronizedNodeSelectionWidget::OnSelectionModeChanged); + connect(m_Controls.changeSelectionButton, &QPushButton::clicked, + this, &QmitkSynchronizedNodeSelectionWidget::OnEditSelection); + + connect(m_Controls.tableView, &QTableView::clicked, + this, &QmitkSynchronizedNodeSelectionWidget::OnTableClicked); +} + +void QmitkSynchronizedNodeSelectionWidget::Initialize() +{ + auto baseRenderer = m_BaseRenderer.Lock(); + auto dataStorage = m_DataStorage.Lock(); + m_StorageModel->SetDataStorage(dataStorage); + m_StorageModel->SetCurrentRenderer(baseRenderer); + + if (baseRenderer.IsNull() || dataStorage.IsNull()) + { + m_Controls.selectionModeCheckBox->setEnabled(false); + m_Controls.changeSelectionButton->setEnabled(false); + // reset the model if no data storage is defined + m_StorageModel->removeRows(0, m_StorageModel->rowCount()); + return; + } + + // Use the new data storage / node predicate to correctly set the list of + // currently selected data nodes for the model. + // If a new data storage or node predicate has been defined, + // we switch to the "selectAll" mode and synchronize the selection for simplicity. + // enable UI + m_Controls.selectionModeCheckBox->setEnabled(true); + m_Controls.changeSelectionButton->setEnabled(true); + + m_Controls.selectionModeCheckBox->setChecked(true); + + // set the base renderer of the model to nullptr, such that global properties are used (synchronized mode) + m_StorageModel->SetCurrentRenderer(nullptr); +} + +void QmitkSynchronizedNodeSelectionWidget::UpdateInfo() +{ + +} + +void QmitkSynchronizedNodeSelectionWidget::OnDataStorageChanged() +{ + this->Initialize(); +} + +void QmitkSynchronizedNodeSelectionWidget::OnNodePredicateChanged() +{ + this->Initialize(); +} + +void QmitkSynchronizedNodeSelectionWidget::ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) +{ + auto baseRenderer = m_BaseRenderer.Lock(); + if (baseRenderer.IsNull()) + { + return; + } + + bool isSynchronized = this->IsSynchronized(); + if (isSynchronized) + { + this->ReviseSynchronizedSelectionChanged(oldInternalSelection, newInternalSelection); + } + else + { + this->ReviseDesynchronizedSelectionChanged(oldInternalSelection, newInternalSelection); + } + + // Since a new selection might have a different rendering tree the render windows + // need to be updated. + // Explicitly request an update since a renderer-specific property change does not mark the node as modified. + // see https://phabricator.mitk.org/T22322 + mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); +} + +void QmitkSynchronizedNodeSelectionWidget::OnInternalSelectionChanged() +{ + m_StorageModel->SetCurrentSelection(this->GetCurrentInternalSelection()); +} + +bool QmitkSynchronizedNodeSelectionWidget::AllowEmissionOfSelection(const NodeList& /*emissionCandidates*/) const +{ + return this->IsSynchronized(); +} + +void QmitkSynchronizedNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* node) +{ + auto baseRenderer = m_BaseRenderer.Lock(); + if (baseRenderer.IsNull()) + { + return; + } + + // The selection mode determines if we want to show all nodes from the data storage + // or use a local selected list of nodes. + // We need to hide each new incoming data node, if we use a local selection, + // since we do not want to show / select newly added nodes immediately. + // We need to add the incoming node to our selection, if the selection mode check box + // is checked. + if (m_Controls.selectionModeCheckBox->isChecked()) + { + if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) + { + auto currentSelection = this->GetCurrentInternalSelection(); + // Check if the nodes is already part of the internal selection. + // That can happen if another render window already added the new node and sent out the new, updated + // selection to be synchronized. + auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), node); + if (finding != std::end(currentSelection)) // node found + { + // node already part of the selection + return; + } + + currentSelection.append(const_cast(node)); + this->HandleChangeOfInternalSelection(currentSelection); + } + + // For helper / hidden nodes: + // If the node predicate does not match, show the node but do not add + // it to the current selection. + return; + } + + // If the model is in "local-selection" state (selectionModeCheckBox unchecked), + // the node needs to be hid. + // Here it depends on the synchronization-state which properties need + // to be modified. + if (this->IsSynchronized()) + { + // If the node will not be part of the new selection, hide the node. + const_cast(node)->SetVisibility(false); + } + else + { + // If the model is not synchronized, all nodes use renderer-specific properties. + // Thus we need to modify the renderer-specific properties of a new node: + // - hide the node, which is not part of the selection + this->DeselectNode(const_cast(node)); + } +} + +void QmitkSynchronizedNodeSelectionWidget::OnNodeModified(const itk::Object* caller, const itk::EventObject& event) +{ + auto baseRenderer = m_BaseRenderer.Lock(); + if (baseRenderer.IsNull()) + { + return; + } + + if (!itk::ModifiedEvent().CheckEvent(&event)) + { + return; + } + + auto node = dynamic_cast(caller); + + if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) + { + auto currentSelection = this->GetCurrentInternalSelection(); + // check if the node to be modified is part of the current selection + auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), node); + if (finding == std::end(currentSelection)) // node not found + { + // node not part of the selection + return; + } + + // We know that the node is relevant, but we don't know if the node modification was relevant + // for the rendering. We just request an update here. + // Explicitly request an update since a renderer-specific property change does not mark the node as modified. + // see https://phabricator.mitk.org/T22322 + mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); + m_StorageModel->UpdateModelData(); + } +} + +void QmitkSynchronizedNodeSelectionWidget::ReviseSynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) +{ + // If the model is synchronized, all nodes use global / default properties. + // Thus we need to modify the global properties of the selection: + // - a) show those nodes, which are part of the new selection AND have not been + // selected before + // - b) keep the property values of those nodes, which are part of the new selection AND + // have been selected before + // - c) hide those nodes, which are part of the old selection AND + // have not been newly selected + for (auto& node : newInternalSelection) + { + // check if the node is part of the old selection + auto finding = std::find(std::begin(oldInternalSelection), std::end(oldInternalSelection), node); + if (finding == std::end(oldInternalSelection)) // node not found + { + // If the node is part of the new selection and was not already part of the old selection, + // set the relevant renderer-specific properties. + // This will set the "visible" property for the global / default property list + // and show the node for this renderer. + node->SetVisibility(true); // item a) + } + // else: item b): node that was already selected before does not need to be modified + } + + for (auto& node : oldInternalSelection) + { + // check if the node is part of the new selection + auto finding = std::find(std::begin(newInternalSelection), std::end(newInternalSelection), node); + if (finding == std::end(newInternalSelection)) // node not found + { + // If the node is not part of the new selection, hide the node. + node->SetVisibility(false); // item c) + } + // else: item b): node that was already selected before does not need to be modified + } +} + +void QmitkSynchronizedNodeSelectionWidget::ReviseDesynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) +{ + auto baseRenderer = m_BaseRenderer.Lock(); + if (baseRenderer.IsNull()) + { + return; + } + + // If the model is not synchronized, all nodes need renderer-specific properties. + // Thus we need to modify the renderer-specific properties of the selection: + // - a) set the renderer-specific properties of those nodes, which are part of the new selection AND + // have not been selected before (see 'SelectNode') + // - b) show those nodes, which are part of the new selection AND have not been + // selected before + // - c) keep the property values of those nodes, which are part of the new selection AND + // have been selected before + // - d) hide those nodes, which are part of the old selection AND + // have not been newly selected + // - e) set the renderer-specific properties of those nodes, which are part of the old selection AND + // have not been newly selected, to denote which nodes are selected + for (auto& node : newInternalSelection) + { + // check if the node is part of the old selection + auto finding = std::find(std::begin(oldInternalSelection), std::end(oldInternalSelection), node); + if (finding == std::end(oldInternalSelection)) // node not found + { + // If the node is part of the new selection and was not already part of the old selection, + // set the relevant renderer-specific properties. + // This will set the "visible" and "layer" property for the renderer-specific property list + // such that the global / default property list values are overwritten + mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(node, baseRenderer); // item a) + // Explicitly set the visibility to true for selected nodes to show them in the render window. + node->SetVisibility(true, baseRenderer); // item b) + } + // else: item c): node that was already selected before does not need to be modified + } + + for (auto& node : oldInternalSelection) + { + // check if the node is part of the new selection + auto finding = std::find(std::begin(newInternalSelection), std::end(newInternalSelection), node); + if (finding == std::end(newInternalSelection)) // node not found + { + // If the node is not part of the new selection, unset the relevant renderer-specific properties. + // This will unset the "visible" and "layer" property for the renderer-specific property list and + // hide the node for this renderer. + // ATTENTION: This is required, since the synchronized global property needs to be overwritten + // to make sure that the visibility is correctly set for the specific base renderer. + this->DeselectNode(node); // item d) and e) + } + // else: item c): node that was already selected before does not need to be modified + } +} + +void QmitkSynchronizedNodeSelectionWidget::ReinitNode(const mitk::DataNode* dataNode) +{ + auto baseRenderer = m_BaseRenderer.Lock(); + if (baseRenderer.IsNull()) + { + return; + } + + auto selectedImage = dynamic_cast(dataNode->GetData()); + if (nullptr == selectedImage) + { + return; + } + + auto boundingBoxPredicate = mitk::NodePredicateNot::New( + mitk::NodePredicateProperty::New("includeInBoundingBox", mitk::BoolProperty::New(false), baseRenderer)); + if (!boundingBoxPredicate->CheckNode(dataNode)) + { + return; + } + + mitk::RenderingManager::GetInstance()->InitializeView(baseRenderer->GetRenderWindow(), selectedImage->GetTimeGeometry()); +} + +void QmitkSynchronizedNodeSelectionWidget::RemoveFromInternalSelection(mitk::DataNode* dataNode) +{ + auto baseRenderer = m_BaseRenderer.Lock(); + if (baseRenderer.IsNull()) + { + return; + } + + if (this->IsSynchronized()) + { + // If the model is synchronized, all nodes use global / default properties. + // Thus we need to modify the global property of the node. + // Explicitly set the visibility to false for unselected nodes to hide them in the render window. + dataNode->SetVisibility(false); + } + + m_Controls.selectionModeCheckBox->setChecked(false); + emit SelectionModeChanged(false); + + this->RemoveNodeFromSelection(dataNode); +} + +void QmitkSynchronizedNodeSelectionWidget::DeselectNode(mitk::DataNode* dataNode) +{ + auto baseRenderer = m_BaseRenderer.Lock(); + if (baseRenderer.IsNull()) + { + return; + } + + if (nullptr == dataNode) + { + return; + } + + if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(dataNode)) + { + // If the node should not be part of the selection, set the relevant renderer-specific properties. + // This will set the "visible" and "layer" property for the renderer-specific property list, + // such that the global / default property list values are overwritten. + mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(dataNode, baseRenderer); + // Explicitly set the visibility to false for the node to hide them in the render window. + dataNode->SetVisibility(false, baseRenderer); + } +} diff --git a/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.ui b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.ui new file mode 100644 index 0000000000..f1def382b3 --- /dev/null +++ b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.ui @@ -0,0 +1,66 @@ + + + QmitkSynchronizedNodeSelectionWidget + + + + 0 + 0 + 350 + 300 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::InternalMove + + + + + + + Select all nodes + + + true + + + + + + + Change selection + + + true + + + + + + + + diff --git a/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp b/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp new file mode 100644 index 0000000000..8091ac673f --- /dev/null +++ b/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp @@ -0,0 +1,101 @@ +/*============================================================================ + +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. + +============================================================================*/ + +// mitk gui qt common plugin +#include "QmitkSynchronizedWidgetConnector.h" + +bool NodeListsEqual(const QmitkSynchronizedWidgetConnector::NodeList& selection1, const QmitkSynchronizedWidgetConnector::NodeList& selection2) +{ + if (selection1.size() != selection2.size()) + { + return false; + } + + // lambda to compare node pointer inside both lists + auto lambda = [](mitk::DataNode::Pointer lhs, mitk::DataNode::Pointer rhs) + { + return lhs == rhs; + }; + + return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin(), selection2.end(), lambda); +} + +QmitkSynchronizedWidgetConnector::QmitkSynchronizedWidgetConnector() + : m_SelectAll(true) +{ + +} + +void QmitkSynchronizedWidgetConnector::ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const +{ + connect(nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + this, &QmitkSynchronizedWidgetConnector::ChangeSelection); + + connect(this, &QmitkSynchronizedWidgetConnector::NodeSelectionChanged, + nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::SetCurrentSelection); + + connect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged, + this, &QmitkSynchronizedWidgetConnector::ChangeSelectionMode); + + connect(this, &QmitkSynchronizedWidgetConnector::SelectionModeChanged, + nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetSelectAll); +} + +void QmitkSynchronizedWidgetConnector::DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const +{ + disconnect(nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + this, &QmitkSynchronizedWidgetConnector::ChangeSelection); + + disconnect(this, &QmitkSynchronizedWidgetConnector::NodeSelectionChanged, + nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::SetCurrentSelection); + + disconnect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged, + this, &QmitkSynchronizedWidgetConnector::ChangeSelectionMode); + + disconnect(this, &QmitkSynchronizedWidgetConnector::SelectionModeChanged, + nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetSelectAll); +} + +void QmitkSynchronizedWidgetConnector::SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const +{ + // widget is newly synchronized / connected so an initial setup needs to be made + nodeSelectionWidget->SetCurrentSelection(m_InternalSelection); + nodeSelectionWidget->SetSelectAll(m_SelectAll); +} + +QmitkSynchronizedWidgetConnector::NodeList QmitkSynchronizedWidgetConnector::GetNodeSelection() const +{ + return m_InternalSelection; +} + +bool QmitkSynchronizedWidgetConnector::GetSelectionMode() const +{ + return m_SelectAll; +} + +void QmitkSynchronizedWidgetConnector::ChangeSelection(NodeList nodes) +{ + if (!NodeListsEqual(m_InternalSelection, nodes)) + { + m_InternalSelection = nodes; + emit NodeSelectionChanged(m_InternalSelection); + } +} + +void QmitkSynchronizedWidgetConnector::ChangeSelectionMode(bool selectAll) +{ + if (m_SelectAll!= selectAll) + { + m_SelectAll = selectAll; + emit SelectionModeChanged(m_SelectAll); + } +} diff --git a/Modules/QtWidgets/src/mitkRenderWindowLayerController.cpp b/Modules/QtWidgets/src/mitkRenderWindowLayerController.cpp index 37cbe9cde5..4b8a8c91e3 100644 --- a/Modules/QtWidgets/src/mitkRenderWindowLayerController.cpp +++ b/Modules/QtWidgets/src/mitkRenderWindowLayerController.cpp @@ -1,563 +1,318 @@ /*============================================================================ 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. ============================================================================*/ // render window manager module #include "mitkRenderWindowLayerController.h" mitk::RenderWindowLayerController::RenderWindowLayerController() : m_DataStorage(nullptr) { // nothing here } void mitk::RenderWindowLayerController::SetDataStorage(DataStorage::Pointer dataStorage) { if (m_DataStorage != dataStorage) { // set the new data storage m_DataStorage = dataStorage; } } -void mitk::RenderWindowLayerController::SetControlledRenderer(RenderWindowLayerUtilities::RendererVector controlledRenderer) -{ - if (m_ControlledRenderer != controlledRenderer) - { - // set the new set of controlled renderer - m_ControlledRenderer = controlledRenderer; - } -} - -void mitk::RenderWindowLayerController::SetBaseDataNode(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) -{ - if (nullptr == dataNode) - { - return; - } - - if (nullptr == renderer) - { - // set the data node as base data node in all controlled renderer - for (const auto& renderer : m_ControlledRenderer) - { - if (nullptr != renderer) - { - SetBaseDataNode(dataNode, renderer); - } - } - } - else - { - // get the layer stack with the base data node of the current renderer - RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, true); - if (!stackedLayers.empty()) - { - // see if base layer exists - RenderWindowLayerUtilities::LayerStack::iterator layerStackIterator = stackedLayers.find(RenderWindowLayerUtilities::BASE_LAYER_INDEX); - if (layerStackIterator != stackedLayers.end()) - { - // remove the current base data node from the current renderer - layerStackIterator->second->GetPropertyList(renderer)->DeleteProperty("layer"); - layerStackIterator->second->SetBoolProperty("fixedLayer", false, renderer); - layerStackIterator->second->SetVisibility(false, renderer); - } - } - - // "RenderWindowLayerUtilities::BASE_LAYER_INDEX" indicates the base data node --> set as new background - dataNode->SetIntProperty("layer", RenderWindowLayerUtilities::BASE_LAYER_INDEX, renderer); - dataNode->SetBoolProperty("fixedLayer", true, renderer); - dataNode->SetVisibility(true, renderer); - dataNode->Modified(); - mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); - } -} - void mitk::RenderWindowLayerController::InsertLayerNode(DataNode* dataNode, int layer /*= RenderWindowLayerUtilities::TOP_LAYER_INDEX*/, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return; } - if (nullptr == renderer) - { - // insert data node in all controlled renderer - for (const auto& renderer : m_ControlledRenderer) - { - if (nullptr != renderer) - { - InsertLayerNode(dataNode, layer, renderer); - } - } - } - else - { - if (RenderWindowLayerUtilities::BASE_LAYER_INDEX == layer) - { - // "RenderWindowLayerUtilities::BASE_LAYER_INDEX" indicates the base data node --> set as new background (overwrite current base node if needed) - SetBaseDataNode(dataNode, renderer); - } - else - { - InsertLayerNodeInternal(dataNode, layer, renderer); - } - } -} - -void mitk::RenderWindowLayerController::InsertLayerNodeInternal(DataNode* dataNode, int newLayer, const BaseRenderer* renderer /*= nullptr*/) -{ RenderWindowLayerUtilities::SetRenderWindowProperties(dataNode, renderer); - // get the layer stack without the base node of the current renderer - RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); + // get the layer stack of the current renderer + RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer); if (stackedLayers.empty()) { // no layer stack for the current renderer - if (RenderWindowLayerUtilities::TOP_LAYER_INDEX == newLayer) + if (RenderWindowLayerUtilities::TOP_LAYER_INDEX == layer) { - // set given layer as first layer above base layer (= 1) - newLayer = 1; - // alternatively: no layer stack for the current renderer -> insert as background node - //SetBaseDataNode(dataNode, renderer); + // set given layer as first layer + layer = 0; } } else { - if (RenderWindowLayerUtilities::TOP_LAYER_INDEX == newLayer) + if (RenderWindowLayerUtilities::TOP_LAYER_INDEX == layer) { // get the first value (highest int-key -> topmost layer) // + 1 indicates inserting the node above the topmost layer - newLayer = stackedLayers.begin()->first + 1; + layer = stackedLayers.begin()->first + 1; } else { - MoveNodeToPosition(dataNode, newLayer, renderer); + MoveNodeToPosition(dataNode, layer, renderer); return; } } // update data storage (the "data node model") - dataNode->SetIntProperty("layer", newLayer, renderer); + dataNode->SetIntProperty("layer", layer, renderer); dataNode->Modified(); - mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); -} - -void mitk::RenderWindowLayerController::RemoveLayerNode(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) -{ - if (nullptr == dataNode) - { - return; - } - if (nullptr == renderer) { - // remove data node from all controlled renderer - for (const auto& renderer : m_ControlledRenderer) - { - if (nullptr != renderer) - { - RemoveLayerNode(dataNode, renderer); - } - } + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } else { - // "remove" node from the renderer list - dataNode->GetPropertyList(renderer)->DeleteProperty("layer"); - dataNode->SetBoolProperty("fixedLayer", false, renderer); - dataNode->SetVisibility(false, renderer); - dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); } } bool mitk::RenderWindowLayerController::MoveNodeToPosition(DataNode* dataNode, int newLayer, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return false; } - if (nullptr == renderer) + // get the layer stack of the current renderer + RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer); + if (!stackedLayers.empty()) { - // move data node to position in all controlled renderer - for (const auto& renderer : m_ControlledRenderer) + // get the current layer value of the given data node + int currentLayer; + bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); + if (wasFound && currentLayer != newLayer) { - if (nullptr != renderer) + // move the given data node to the specified layer + dataNode->SetIntProperty("layer", newLayer, renderer); + + int upperBound; + int lowerBound; + int step; + if (currentLayer < newLayer) { - MoveNodeToPosition(dataNode, newLayer, renderer); - // we don't store/need the returned boolean value - return false; + // move node up + upperBound = newLayer + 1; + lowerBound = currentLayer + 1; + step = -1; // move all other nodes one step down } - } - } - else - { - // get the layer stack without the base node of the current renderer - RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); - if (!stackedLayers.empty()) - { - // get the current layer value of the given data node - int currentLayer; - bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); - if (wasFound && currentLayer != newLayer) + else { - // move the given data node to the specified layer - dataNode->SetIntProperty("layer", newLayer, renderer); - - int upperBound; - int lowerBound; - int step; - if (currentLayer < newLayer) - { - // move node up - upperBound = newLayer + 1; - lowerBound = currentLayer + 1; - step = -1; // move all other nodes one step down - } - else - { - upperBound = currentLayer; - lowerBound = newLayer; - step = 1; // move all other nodes one step up - } + upperBound = currentLayer; + lowerBound = newLayer; + step = 1; // move all other nodes one step up + } - // move all other data nodes between the upper and the lower bound - for (auto& layer : stackedLayers) + // move all other data nodes between the upper and the lower bound + for (auto& layer : stackedLayers) + { + if (layer.second != dataNode && layer.first < upperBound && layer.first >= lowerBound) { - if (layer.second != dataNode && layer.first < upperBound && layer.first >= lowerBound) - { - layer.second->SetIntProperty("layer", layer.first + step, renderer); - } - // else: current data node is the selected data node or - // was previously already above the selected data node or - // was previously already below the new layer position + layer.second->SetIntProperty("layer", layer.first + step, renderer); } - dataNode->Modified(); + // else: current data node is the selected data node or + // was previously already above the selected data node or + // was previously already below the new layer position + } + dataNode->Modified(); + if (nullptr == renderer) + { + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } + else + { mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); - return true; } - // else: data node has no layer information or is already at the specified position + + return true; } - // else: do not work with empty layer stack + // else: data node has no layer information or is already at the specified position } + // else: do not work with empty layer stack return false; } bool mitk::RenderWindowLayerController::MoveNodeToFront(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return false; } - if (nullptr == renderer) + // get the layer stack of the current renderer + RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer); + if (!stackedLayers.empty()) { - // move data node to front in all controlled renderer - for (const auto& renderer : m_ControlledRenderer) + // get the first value (highest int-key -> topmost layer) + int topmostLayer = stackedLayers.begin()->first; + // get the current layer value of the given data node + int currentLayer; + bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); + if (wasFound && currentLayer < topmostLayer) { - if (nullptr != renderer) + // move the current data node above the current topmost layer + dataNode->SetIntProperty("layer", topmostLayer+1, renderer); + dataNode->Modified(); + if (nullptr == renderer) { - MoveNodeToFront(dataNode, renderer); - // we don't store/need the returned boolean value - return false; + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } - } - } - else - { - // get the layer stack without the base node of the current renderer - RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); - if (!stackedLayers.empty()) - { - // get the first value (highest int-key -> topmost layer) - int topmostLayer = stackedLayers.begin()->first; - // get the current layer value of the given data node - int currentLayer; - bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); - if (wasFound && currentLayer < topmostLayer) + else { - // move the current data node above the current topmost layer - dataNode->SetIntProperty("layer", topmostLayer+1, renderer); - dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); - return true; } - // else: data node has no layer information or is already the topmost layer node + return true; } - // else: do not work with empty layer stack + // else: data node has no layer information or is already the topmost layer node } + // else: do not work with empty layer stack return false; } bool mitk::RenderWindowLayerController::MoveNodeToBack(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return false; } - if (nullptr == renderer) - { - // move data node to back in all controlled renderer - for (const auto& renderer : m_ControlledRenderer) - { - if (nullptr != renderer) - { - MoveNodeToBack(dataNode, renderer); - // we don't store/need the returned boolean value - return false; - } - } - } - else + // get the layer stack of the current renderer + RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer); + if (!stackedLayers.empty()) { - // get the layer stack without the base node of the current renderer - RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); - if (!stackedLayers.empty()) + // get the last value (lowest int-key) + int lowermostLayer = stackedLayers.rbegin()->first; + // get the current layer value of the given data node + int currentLayer; + bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); + if (wasFound && currentLayer > lowermostLayer) { - // get the last value (lowest int-key) - // cannot be the base layer as the base node was excluded by the 'GetLayerStack'-function - int lowermostLayer = stackedLayers.rbegin()->first; - // get the current layer value of the given data node - int currentLayer; - bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); - if (wasFound && currentLayer > lowermostLayer) + // move the current data node to the current lowermost layer + dataNode->SetIntProperty("layer", lowermostLayer, renderer); + // move all other data nodes one layer up + for (auto& layer : stackedLayers) { - // move the current data node to the current lowermost layer - dataNode->SetIntProperty("layer", lowermostLayer, renderer); - // move all other data nodes one layer up - for (auto& layer : stackedLayers) + if (layer.second != dataNode && layer.first < currentLayer) { - if (layer.second != dataNode && layer.first < currentLayer) - { - layer.second->SetIntProperty("layer", layer.first + 1, renderer); - } - // else: current data node is the selected data node or - // was previously already above the selected data node + layer.second->SetIntProperty("layer", layer.first + 1, renderer); } - dataNode->Modified(); + // else: current data node is the selected data node or + // was previously already above the selected data node + } + dataNode->Modified(); + if (nullptr == renderer) + { + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + + } + else + { mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); - return true; } - // else: data node has no layer information or is already the lowermost layer node + return true; } - // else: do not work with empty layer stack + // else: data node has no layer information or is already the lowermost layer node } + // else: do not work with empty layer stack return false; } bool mitk::RenderWindowLayerController::MoveNodeUp(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return false; } - if (nullptr == renderer) - { - // move data node down in all controlled renderer - for (const auto& renderer : m_ControlledRenderer) - { - if (nullptr != renderer) - { - MoveNodeUp(dataNode, renderer); - // we don't store/need the returned boolean value - return false; - } - } - } - else + // get the layer stack of the current renderer + RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer); + if (!stackedLayers.empty()) { - // get the layer stack without the base node of the current renderer - RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); - if (!stackedLayers.empty()) + // get the current layer value of the given data node + int currentLayer; + bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); + if (wasFound) { - // get the current layer value of the given data node - int currentLayer; - bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); - if (wasFound) + // get the current layer in the map of stacked layers + RenderWindowLayerUtilities::LayerStack::const_iterator layerStackIterator = stackedLayers.find(currentLayer); + if (layerStackIterator != stackedLayers.end() && layerStackIterator != stackedLayers.begin()) { - // get the current layer in the map of stacked layers - RenderWindowLayerUtilities::LayerStack::const_iterator layerStackIterator = stackedLayers.find(currentLayer); - if (layerStackIterator != stackedLayers.end() && layerStackIterator != stackedLayers.begin()) + // found the element in the map, at different position than 'begin' -> + // current node is not on the topmost layer and therefore can be moved one layer up + // swap the layers of the dataNode and the dataNode on the next higher layer (previous map element) + RenderWindowLayerUtilities::LayerStack::const_iterator prevLayerStackIterator = std::prev(layerStackIterator); + dataNode->SetIntProperty("layer", prevLayerStackIterator->first, renderer); + prevLayerStackIterator->second->SetIntProperty("layer", currentLayer, renderer); + dataNode->Modified(); + if (nullptr == renderer) + { + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + + } + else { - // found the element in the map, at different position than 'begin' -> - // current node is not on the topmost layer and therefore can be moved one layer up - // swap the layers of the dataNode and the dataNode on the next higher layer (previous map element) - RenderWindowLayerUtilities::LayerStack::const_iterator prevLayerStackIterator = std::prev(layerStackIterator); - dataNode->SetIntProperty("layer", prevLayerStackIterator->first, renderer); - prevLayerStackIterator->second->SetIntProperty("layer", currentLayer, renderer); - dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); - return true; } - // else: layer stack does not contain a layer with the 'currentLayer'data node or - // layer is already the topmost layer node + return true; } - // else: data node has no layer information + // else: layer stack does not contain a layer with the 'currentLayer'data node or + // layer is already the topmost layer node } - // else: do not work with empty layer stack + // else: data node has no layer information } + // else: do not work with empty layer stack return false; } bool mitk::RenderWindowLayerController::MoveNodeDown(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return false; } - if (nullptr == renderer) - { - // move data node up in all controlled renderer - for (const auto& renderer : m_ControlledRenderer) - { - if (nullptr != renderer) - { - MoveNodeDown(dataNode, renderer); - // we don't store/need the returned boolean value - return false; - } - } - } - else + // get the layer stack of the current renderer + RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer); + if (!stackedLayers.empty()) { - // get the layer stack without the base node of the current renderer - RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); - if (!stackedLayers.empty()) + // get the current layer value of the given data node + int currentLayer; + bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); + if (wasFound) { - // get the current layer value of the given data node - int currentLayer; - bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); - if (wasFound) + // get the current layer in the map of stacked layers + RenderWindowLayerUtilities::LayerStack::const_iterator layerStackIterator = stackedLayers.find(currentLayer); + if (layerStackIterator != stackedLayers.end()) { - // get the current layer in the map of stacked layers - RenderWindowLayerUtilities::LayerStack::const_iterator layerStackIterator = stackedLayers.find(currentLayer); - if (layerStackIterator != stackedLayers.end()) + // found the element in the map ... + RenderWindowLayerUtilities::LayerStack::const_iterator nextLayerStackIterator = std::next(layerStackIterator); + if (nextLayerStackIterator != stackedLayers.end()) { - // found the element in the map ... - RenderWindowLayerUtilities::LayerStack::const_iterator nextLayerStackIterator = std::next(layerStackIterator); - if (nextLayerStackIterator != stackedLayers.end()) - { - // ... and found a successor -> - // current node is not on the lowermost layer and therefore can be moved one layer down - // swap the layers of the dataNode and the dataNode on the next lower layer (next map element) - dataNode->SetIntProperty("layer", nextLayerStackIterator->first, renderer); - nextLayerStackIterator->second->SetIntProperty("layer", currentLayer, renderer); - dataNode->Modified(); - mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); - return true; - } - // else: data node is already the lowermost layer node + // ... and found a successor -> + // current node is not on the lowermost layer and therefore can be moved one layer down + // swap the layers of the dataNode and the dataNode on the next lower layer (next map element) + dataNode->SetIntProperty("layer", nextLayerStackIterator->first, renderer); + nextLayerStackIterator->second->SetIntProperty("layer", currentLayer, renderer); + dataNode->Modified(); + mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); + return true; } - // else: layer stack does not contain a layer with the 'currentLayer' + // else: data node is already the lowermost layer node } - // else: data node has no layer information + // else: layer stack does not contain a layer with the 'currentLayer' } - // else: do not work with empty layer stack + // else: data node has no layer information } + // else: do not work with empty layer stack return false; } - -void mitk::RenderWindowLayerController::SetVisibilityOfDataNode(bool visibility, DataNode* dataNode, const BaseRenderer* renderer /*=nullptr*/) -{ - if (nullptr == dataNode) - { - return; - } - - if (nullptr == renderer) - { - // set visibility of data node in all controlled renderer - for (const auto& renderer : m_ControlledRenderer) - { - if (nullptr != renderer) - { - SetVisibilityOfDataNode(visibility, dataNode, renderer); - } - } - } - else - { - dataNode->SetVisibility(visibility, renderer); - dataNode->Modified(); - mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); - } -} - -void mitk::RenderWindowLayerController::HideDataNodeInAllRenderer(const DataNode* dataNode) -{ - if (nullptr == dataNode) - { - return; - } - - for (const auto& renderer : m_ControlledRenderer) - { - if (nullptr != renderer) - { - dataNode->GetPropertyList(renderer)->SetBoolProperty("visible", false); - } - } - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void mitk::RenderWindowLayerController::ResetRenderer(bool onlyVisibility /*= true*/, const BaseRenderer* renderer /*= nullptr*/) -{ - if (nullptr == renderer) - { - // reset all controlled renderer - for (const auto& renderer : m_ControlledRenderer) - { - if (nullptr != renderer) - { - ResetRenderer(onlyVisibility, renderer); - } - } - } - else - { - // get the layer stack with the base node of the current renderer - RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, true); - if (!stackedLayers.empty()) - { - for (const auto& layer : stackedLayers) - { - int layerLevel; - layer.second->GetIntProperty("layer", layerLevel, renderer); - if (RenderWindowLayerUtilities::BASE_LAYER_INDEX == layerLevel) - { - // set base data node visibility to true - layer.second->SetVisibility(true, renderer); - } - else - { - // set visibility of all other data nodes to false - layer.second->SetVisibility(false, renderer); - - // modify layer node - if (!onlyVisibility) - { - // clear mode: additionally remove layer node from current renderer - layer.second->GetPropertyList(renderer)->DeleteProperty("layer"); - layer.second->SetBoolProperty("fixedLayer", false, renderer); - } - } - layer.second->Modified(); - } - - mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); - } - } -} diff --git a/Modules/QtWidgets/src/mitkRenderWindowLayerUtilities.cpp b/Modules/QtWidgets/src/mitkRenderWindowLayerUtilities.cpp index e2e8c375ba..50e8866c3f 100644 --- a/Modules/QtWidgets/src/mitkRenderWindowLayerUtilities.cpp +++ b/Modules/QtWidgets/src/mitkRenderWindowLayerUtilities.cpp @@ -1,82 +1,125 @@ /*============================================================================ 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. ============================================================================*/ // render window manager module #include "mitkRenderWindowLayerUtilities.h" // mitk core #include -mitk::RenderWindowLayerUtilities::LayerStack mitk::RenderWindowLayerUtilities::GetLayerStack(const DataStorage* dataStorage, const BaseRenderer* renderer, bool withBaseNode) +mitk::RenderWindowLayerUtilities::LayerStack mitk::RenderWindowLayerUtilities::GetLayerStack(const DataStorage* dataStorage, const BaseRenderer* renderer) { LayerStack stackedLayers; - if (nullptr == dataStorage || nullptr == renderer) + if (nullptr == dataStorage) { - // no nodes to stack or no renderer selected + // no nodes to stack return stackedLayers; } int layer = -1; - NodePredicateBase::Pointer fixedLayerPredicate = GetRenderWindowPredicate(renderer); - DataStorage::SetOfObjects::ConstPointer filteredDataNodes = dataStorage->GetSubset(fixedLayerPredicate); - for (DataStorage::SetOfObjects::ConstIterator it = filteredDataNodes->Begin(); it != filteredDataNodes->End(); ++it) + + auto allDataNodes = dataStorage->GetAll(); + for (DataStorage::SetOfObjects::ConstIterator it = allDataNodes->Begin(); it != allDataNodes->End(); ++it) { DataNode::Pointer dataNode = it->Value(); if (dataNode.IsNull()) { continue; } bool layerFound = dataNode->GetIntProperty("layer", layer, renderer); if (layerFound) { - if (BASE_LAYER_INDEX != layer|| withBaseNode) - { - // data node is not on the base layer or the base layer should be included anyway - stackedLayers.insert(std::make_pair(layer, dataNode)); - } + stackedLayers.insert(std::make_pair(layer, dataNode)); } } return stackedLayers; } -mitk::NodePredicateBase::Pointer mitk::RenderWindowLayerUtilities::GetRenderWindowPredicate(const BaseRenderer* renderer) -{ - NodePredicateBase::Pointer fixedLayerPredicate = - NodePredicateProperty::New("fixedLayer", BoolProperty::New(true), renderer); - - return fixedLayerPredicate; -} - void mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(mitk::DataNode* dataNode, const BaseRenderer* renderer) { - dataNode->SetBoolProperty("fixedLayer", true, renderer); // use visibility of existing renderer or common renderer // common renderer is used if renderer-specific property does not exist bool visible = false; bool visibilityProperty = dataNode->GetVisibility(visible, renderer); if (true == visibilityProperty) { // found a visibility property dataNode->SetVisibility(visible, renderer); } // use layer of existing renderer or common renderer // common renderer is used if renderer-specific property does not exist int layer = -1; bool layerProperty = dataNode->GetIntProperty("layer", layer, renderer); if (true == layerProperty) { // found a layer property dataNode->SetIntProperty("layer", layer, renderer); } } + +void mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(mitk::DataNode* dataNode, const BaseRenderer* renderer) +{ + if (nullptr == renderer) + { + MITK_ERROR << "Cannot remove general properties. Please provide a specific base renderer."; + return; + } + + try + { + dataNode->RemoveProperty("visible", renderer->GetName()); + } + catch (mitk::Exception& e) + { + MITK_DEBUG << "Exception caught: " << e.GetDescription() << " Nothing to remove."; + } + + try + { + dataNode->RemoveProperty("layer", renderer->GetName()); + } + catch (mitk::Exception& e) + { + MITK_ERROR << "Exception caught: " << e.GetDescription() << " Nothing to remove."; + } +} + +void mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(DataNode* dataNode, const BaseRenderer* newRenderer, const BaseRenderer* oldRenderer) +{ + if (nullptr == newRenderer) + { + MITK_ERROR << "Cannot transfer properties. Please provide a specific base renderer for the new renderer-specific properties."; + return; + } + + // use visibility of existing renderer or common renderer + // common renderer is used if renderer-specific property does not exist + bool visible = false; + bool visibilityProperty = dataNode->GetVisibility(visible, oldRenderer); + if (true == visibilityProperty) + { + // found a visibility property + dataNode->SetVisibility(visible, newRenderer); + } + + // use layer of existing renderer or common renderer + // common renderer is used if renderer-specific property does not exist + int layer = -1; + bool layerProperty = dataNode->GetIntProperty("layer", layer, oldRenderer); + if (true == layerProperty) + { + // found a layer property + dataNode->SetIntProperty("layer", layer, newRenderer); + } +} diff --git a/Modules/QtWidgets/src/mitkRenderWindowViewDirectionController.cpp b/Modules/QtWidgets/src/mitkRenderWindowViewDirectionController.cpp index 7410b0ec9d..21a70620e6 100644 --- a/Modules/QtWidgets/src/mitkRenderWindowViewDirectionController.cpp +++ b/Modules/QtWidgets/src/mitkRenderWindowViewDirectionController.cpp @@ -1,137 +1,134 @@ /*============================================================================ 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 "mitkRenderWindowViewDirectionController.h" // mitk core #include #include #include mitk::RenderWindowViewDirectionController::RenderWindowViewDirectionController() : m_DataStorage(nullptr) { // nothing here } void mitk::RenderWindowViewDirectionController::SetDataStorage(DataStorage::Pointer dataStorage) { if (m_DataStorage != dataStorage) { // set the new data storage m_DataStorage = dataStorage; } } void mitk::RenderWindowViewDirectionController::SetControlledRenderer(RenderWindowLayerUtilities::RendererVector controlledRenderer) { if (m_ControlledRenderer != controlledRenderer) { // set the new set of controlled renderer m_ControlledRenderer = controlledRenderer; } } void mitk::RenderWindowViewDirectionController::SetViewDirectionOfRenderer(const std::string& viewDirection, BaseRenderer* renderer/* =nullptr*/) { if (nullptr == renderer) { // set visibility of data node in all controlled renderer for (auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { SetViewDirectionOfRenderer(viewDirection, renderer); } } } else { mitk::SliceNavigationController* sliceNavigationController = renderer->GetSliceNavigationController(); if ("axial" == viewDirection) { sliceNavigationController->SetDefaultViewDirection(AnatomicalPlane::Axial); } else if ("coronal" == viewDirection) { sliceNavigationController->SetDefaultViewDirection(AnatomicalPlane::Coronal); } else if ("sagittal" == viewDirection) { sliceNavigationController->SetDefaultViewDirection(AnatomicalPlane::Sagittal); } if ("3D" == viewDirection) { renderer->SetMapperID(mitk::BaseRenderer::Standard3D); } else { renderer->SetMapperID(mitk::BaseRenderer::Standard2D); } // initialize the views to the bounding geometry InitializeViewByBoundingObjects(renderer); } } void mitk::RenderWindowViewDirectionController::SetViewDirectionOfRenderer(AnatomicalPlane viewDirection , BaseRenderer* renderer/* =nullptr*/) { if (nullptr == renderer) { // set visibility of data node in all controlled renderer for (auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { SetViewDirectionOfRenderer(viewDirection, renderer); } } } else { mitk::SliceNavigationController* sliceNavigationController = renderer->GetSliceNavigationController(); sliceNavigationController->SetDefaultViewDirection(viewDirection); // initialize the views to the bounding geometry InitializeViewByBoundingObjects(renderer); } } void mitk::RenderWindowViewDirectionController::InitializeViewByBoundingObjects(const BaseRenderer* renderer) { if (nullptr == m_DataStorage || nullptr == renderer) { return; } // get all nodes that have not set "includeInBoundingBox" to false mitk::NodePredicateProperty::Pointer includeInBoundingBox = mitk::NodePredicateProperty::New("includeInBoundingBox", mitk::BoolProperty::New(false)); mitk::NodePredicateNot::Pointer notIncludeInBoundingBox = mitk::NodePredicateNot::New(includeInBoundingBox); // get all non-helper objects mitk::NodePredicateProperty::Pointer helperObject = mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(true)); mitk::NodePredicateNot::Pointer notAHelperObject = mitk::NodePredicateNot::New(helperObject); - // get all nodes that have a fixed layer for the given renderer - mitk::NodePredicateProperty::Pointer fixedLayer = mitk::NodePredicateProperty::New("fixedLayer", mitk::BoolProperty::New(true), renderer); - // combine node predicates - mitk::NodePredicateAnd::Pointer combinedNodePredicate = mitk::NodePredicateAnd::New(notIncludeInBoundingBox, notAHelperObject, fixedLayer); + mitk::NodePredicateAnd::Pointer combinedNodePredicate = mitk::NodePredicateAnd::New(notIncludeInBoundingBox, notAHelperObject); mitk::DataStorage::SetOfObjects::ConstPointer filteredDataNodes = m_DataStorage->GetSubset(combinedNodePredicate); // calculate bounding geometry of these nodes auto bounds = m_DataStorage->ComputeBoundingGeometry3D(filteredDataNodes, "visible", renderer); // initialize the views to the bounding geometry mitk::RenderingManager::GetInstance()->InitializeView(renderer->GetRenderWindow(), bounds); } diff --git a/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageInspector.h b/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageInspector.h index 201f8cf854..ee454680c7 100644 --- a/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageInspector.h +++ b/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageInspector.h @@ -1,92 +1,88 @@ /*============================================================================ 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 QmitkRenderWindowDataStorageInspector_h #define QmitkRenderWindowDataStorageInspector_h // render window manager UI module #include "MitkRenderWindowManagerUIExports.h" #include "ui_QmitkRenderWindowDataStorageInspector.h" // render window manager module #include #include #include // qt widgets module #include /** * The 'QmitkRenderWindowDataStorageInspector' offers a GUI to manipulate the base renderer / render windows of the MITK workbench. * * In order to use this widget, a (e.g.) plugin has to set the controlled renderer, which will be forwarded to * a render window view direction controller. */ class MITKRENDERWINDOWMANAGERUI_EXPORT QmitkRenderWindowDataStorageInspector : public QmitkAbstractDataStorageInspector { Q_OBJECT public: QmitkRenderWindowDataStorageInspector(QWidget* parent = nullptr); // override from 'QmitkAbstractDataStorageInspector' /** * @brief See 'QmitkAbstractDataStorageInspector' */ QAbstractItemView* GetView() override; /** * @brief See 'QmitkAbstractDataStorageInspector' */ const QAbstractItemView* GetView() const override; /** * @brief See 'QmitkAbstractDataStorageInspector' */ void SetSelectionMode(SelectionMode mode) override; /** * @brief See 'QmitkAbstractDataStorageInspector' */ SelectionMode GetSelectionMode() const override; /** * @brief Set the controlled base renderer. */ void SetControlledRenderer(mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer); /** * @brief Set the currently selected render window. * * @param renderWindowId the text inside the combo box */ void SetActiveRenderWindow(const QString& renderWindowId); private Q_SLOTS: void ModelRowsInserted(const QModelIndex& parent, int start, int end); - void SetAsBaseLayer(); - - void ResetRenderer(); - void ChangeViewDirection(const QString& viewDirection); private: void Initialize() override; void SetUpConnections(); Ui::QmitkRenderWindowDataStorageInspector m_Controls; std::unique_ptr m_StorageModel; std::unique_ptr m_RenderWindowLayerController; std::unique_ptr m_RenderWindowViewDirectionController; }; #endif diff --git a/Modules/RenderWindowManagerUI/src/QmitkDataStorageLayerStackModel.cpp b/Modules/RenderWindowManagerUI/src/QmitkDataStorageLayerStackModel.cpp index 59232e7a5a..441c29e276 100644 --- a/Modules/RenderWindowManagerUI/src/QmitkDataStorageLayerStackModel.cpp +++ b/Modules/RenderWindowManagerUI/src/QmitkDataStorageLayerStackModel.cpp @@ -1,234 +1,234 @@ /*============================================================================ 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 // qt widgets module #include "QmitkCustomVariants.h" #include "QmitkEnums.h" QmitkDataStorageLayerStackModel::QmitkDataStorageLayerStackModel(QObject* parent/* = nullptr*/) : QmitkAbstractDataStorageModel(parent) { // nothing here } void QmitkDataStorageLayerStackModel::DataStorageChanged() { UpdateModelData(); } void QmitkDataStorageLayerStackModel::NodePredicateChanged() { UpdateModelData(); } void QmitkDataStorageLayerStackModel::NodeAdded(const mitk::DataNode* /*node*/) { // nothing here; layers (nodes) are only added after button click } void QmitkDataStorageLayerStackModel::NodeChanged(const mitk::DataNode* /*node*/) { UpdateModelData(); } void QmitkDataStorageLayerStackModel::NodeRemoved(const mitk::DataNode* /*node*/) { UpdateModelData(); } void QmitkDataStorageLayerStackModel::SetCurrentRenderer(const std::string& renderWindowName) { if (!m_DataStorage.IsExpired()) { m_BaseRenderer = mitk::BaseRenderer::GetByName(renderWindowName); UpdateModelData(); } } mitk::BaseRenderer* QmitkDataStorageLayerStackModel::GetCurrentRenderer() const { return m_BaseRenderer.Lock(); } QModelIndex QmitkDataStorageLayerStackModel::index(int row, int column, const QModelIndex& parent) const { bool hasIndex = this->hasIndex(row, column, parent); if (hasIndex) { return this->createIndex(row, column); } return QModelIndex(); } QModelIndex QmitkDataStorageLayerStackModel::parent(const QModelIndex& /*child*/) const { return QModelIndex(); } Qt::ItemFlags QmitkDataStorageLayerStackModel::flags(const QModelIndex& index) const { Qt::ItemFlags flags = Qt::NoItemFlags; if (index.isValid() && index.model() == this) { flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; if (0 == index.column()) { flags |= Qt::ItemIsUserCheckable; } } return flags; } QVariant QmitkDataStorageLayerStackModel::headerData(int section, Qt::Orientation orientation, int role) const { if (Qt::Horizontal == orientation && Qt::DisplayRole == role) { if (0 == section) { return QVariant("Visibility"); } else if (1 == section) { return QVariant("Data node"); } } return QVariant(); } int QmitkDataStorageLayerStackModel::rowCount(const QModelIndex& parent/* = QModelIndex()*/) const { if (parent.isValid()) { return 0; } return static_cast(m_TempLayerStack.size()); } int QmitkDataStorageLayerStackModel::columnCount(const QModelIndex& parent/* = QModelIndex()*/) const { if (parent.isValid()) { return 0; } return 2; } QVariant QmitkDataStorageLayerStackModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.model() != this) { return QVariant(); } if ((index.row()) < 0 || index.row() >= static_cast(m_TempLayerStack.size())) { return QVariant(); } RenderWindowLayerUtilities::LayerStack::const_iterator layerStackIt = m_TempLayerStack.begin(); std::advance(layerStackIt, index.row()); mitk::DataNode* dataNode = layerStackIt->second; if (Qt::CheckStateRole == role && 0 == index.column()) { bool visibility = false; dataNode->GetVisibility(visibility, m_BaseRenderer.Lock()); if (visibility) { return Qt::Checked; } else { return Qt::Unchecked; } } else if (Qt::DisplayRole == role && 1 == index.column()) { return QVariant(QString::fromStdString(dataNode->GetName())); } else if (Qt::ToolTipRole == role) { if (0 == index.column()) { return QVariant("Show/hide data node."); } else if (1 == index.column()) { return QVariant("Name of the data node."); } } else if (QmitkDataNodeRole == role) { return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); } else if (QmitkDataNodeRawPointerRole == role) { return QVariant::fromValue(dataNode); } return QVariant(); } bool QmitkDataStorageLayerStackModel::setData(const QModelIndex &index, const QVariant &value, int role /*= Qt::EditRole*/) { if (!index.isValid() || index.model() != this) { return false; } if ((index.row()) < 0 || index.row() >= static_cast(m_TempLayerStack.size())) { return false; } auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNotNull()) { RenderWindowLayerUtilities::LayerStack::const_iterator layerStackIt = m_TempLayerStack.begin(); std::advance(layerStackIt, index.row()); mitk::DataNode* dataNode = layerStackIt->second; if (Qt::CheckStateRole == role) { Qt::CheckState newCheckState = static_cast(value.toInt()); if (Qt::PartiallyChecked == newCheckState || Qt::Checked == newCheckState) { dataNode->SetVisibility(true, baseRenderer); } else { dataNode->SetVisibility(false, baseRenderer); } emit dataChanged(index, index); mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); return true; } } return false; } void QmitkDataStorageLayerStackModel::UpdateModelData() { // update the model, so that the table will be filled with the nodes according to the current // data storage and base renderer beginResetModel(); // get the current layer stack of the given base renderer - m_TempLayerStack = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage.Lock(), m_BaseRenderer.Lock(), true); + m_TempLayerStack = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage.Lock(), m_BaseRenderer.Lock()); endResetModel(); } diff --git a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.cpp b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.cpp index 8094567bc2..f489241706 100644 --- a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.cpp +++ b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.cpp @@ -1,167 +1,142 @@ /*============================================================================ 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. ============================================================================*/ // render window manager UI module #include "QmitkRenderWindowDataStorageInspector.h" #include "QmitkCustomVariants.h" // mitk core #include // qt #include QmitkRenderWindowDataStorageInspector::QmitkRenderWindowDataStorageInspector(QWidget* parent /*=nullptr*/) : QmitkAbstractDataStorageInspector(parent) { m_Controls.setupUi(this); // initialize the render window layer controller and the render window view direction controller m_RenderWindowLayerController = std::make_unique(); m_RenderWindowViewDirectionController = std::make_unique(); m_StorageModel = std::make_unique(this); m_Controls.renderWindowTreeView->setModel(m_StorageModel.get()); m_Controls.renderWindowTreeView->setHeaderHidden(true); m_Controls.renderWindowTreeView->setEditTriggers(QAbstractItemView::NoEditTriggers); m_Controls.renderWindowTreeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.renderWindowTreeView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_Controls.renderWindowTreeView->setAlternatingRowColors(true); m_Controls.renderWindowTreeView->setDragEnabled(true); m_Controls.renderWindowTreeView->setDropIndicatorShown(true); m_Controls.renderWindowTreeView->setAcceptDrops(true); m_Controls.renderWindowTreeView->setContextMenuPolicy(Qt::CustomContextMenu); SetUpConnections(); } QAbstractItemView* QmitkRenderWindowDataStorageInspector::GetView() { return m_Controls.renderWindowTreeView; } const QAbstractItemView* QmitkRenderWindowDataStorageInspector::GetView() const { return m_Controls.renderWindowTreeView; } void QmitkRenderWindowDataStorageInspector::SetSelectionMode(SelectionMode mode) { m_Controls.renderWindowTreeView->setSelectionMode(mode); } QmitkRenderWindowDataStorageInspector::SelectionMode QmitkRenderWindowDataStorageInspector::GetSelectionMode() const { return m_Controls.renderWindowTreeView->selectionMode(); } void QmitkRenderWindowDataStorageInspector::Initialize() { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) return; m_StorageModel->SetDataStorage(dataStorage); m_StorageModel->SetNodePredicate(m_NodePredicate); m_RenderWindowLayerController->SetDataStorage(dataStorage); m_RenderWindowViewDirectionController->SetDataStorage(dataStorage); m_Connector->SetView(m_Controls.renderWindowTreeView); } void QmitkRenderWindowDataStorageInspector::SetUpConnections() { connect(m_StorageModel.get(), &QAbstractItemModel::rowsInserted, this, &QmitkRenderWindowDataStorageInspector::ModelRowsInserted); - connect(m_Controls.pushButtonSetAsBaseLayer, &QPushButton::clicked, this, &QmitkRenderWindowDataStorageInspector::SetAsBaseLayer); - connect(m_Controls.pushButtonResetRenderer, &QPushButton::clicked, this, &QmitkRenderWindowDataStorageInspector::ResetRenderer); - QSignalMapper* changeViewDirectionSignalMapper = new QSignalMapper(this); changeViewDirectionSignalMapper->setMapping(m_Controls.radioButtonAxial, QString("axial")); changeViewDirectionSignalMapper->setMapping(m_Controls.radioButtonCoronal, QString("coronal")); changeViewDirectionSignalMapper->setMapping(m_Controls.radioButtonSagittal, QString("sagittal")); changeViewDirectionSignalMapper->setMapping(m_Controls.radioButton3D, QString("3D")); connect(changeViewDirectionSignalMapper, static_cast(&QSignalMapper::mapped), this, &QmitkRenderWindowDataStorageInspector::ChangeViewDirection); - connect(m_Controls.radioButtonAxial, &QPushButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); - connect(m_Controls.radioButtonCoronal, &QPushButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); - connect(m_Controls.radioButtonSagittal, &QPushButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); - connect(m_Controls.radioButton3D, &QPushButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); + connect(m_Controls.radioButtonAxial, &QRadioButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); + connect(m_Controls.radioButtonCoronal, &QRadioButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); + connect(m_Controls.radioButtonSagittal, &QRadioButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); + connect(m_Controls.radioButton3D, &QRadioButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); } void QmitkRenderWindowDataStorageInspector::SetControlledRenderer(mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer) { m_StorageModel->SetControlledRenderer(controlledRenderer); - m_RenderWindowLayerController->SetControlledRenderer(controlledRenderer); m_RenderWindowViewDirectionController->SetControlledRenderer(controlledRenderer); } void QmitkRenderWindowDataStorageInspector::SetActiveRenderWindow(const QString& renderWindowId) { mitk::BaseRenderer* selectedRenderer = mitk::BaseRenderer::GetByName(renderWindowId.toStdString()); if (nullptr == selectedRenderer) { return; } m_StorageModel->SetCurrentRenderer(selectedRenderer); mitk::AnatomicalPlane viewDirection = selectedRenderer->GetSliceNavigationController()->GetDefaultViewDirection(); switch (viewDirection) { case mitk::AnatomicalPlane::Axial: m_Controls.radioButtonAxial->setChecked(true); break; case mitk::AnatomicalPlane::Coronal: m_Controls.radioButtonCoronal->setChecked(true); break; case mitk::AnatomicalPlane::Sagittal: m_Controls.radioButtonSagittal->setChecked(true); break; default: break; } } void QmitkRenderWindowDataStorageInspector::ModelRowsInserted(const QModelIndex& parent, int /*start*/, int /*end*/) { m_Controls.renderWindowTreeView->setExpanded(parent, true); } -void QmitkRenderWindowDataStorageInspector::SetAsBaseLayer() -{ - QModelIndex selectedIndex = m_Controls.renderWindowTreeView->currentIndex(); - if (selectedIndex.isValid()) - { - QVariant qvariantDataNode = m_StorageModel->data(selectedIndex, Qt::UserRole); - if (qvariantDataNode.canConvert()) - { - mitk::DataNode* dataNode = qvariantDataNode.value(); - m_RenderWindowLayerController->SetBaseDataNode(dataNode, m_StorageModel->GetCurrentRenderer()); - m_Controls.renderWindowTreeView->clearSelection(); - } - } -} - -void QmitkRenderWindowDataStorageInspector::ResetRenderer() -{ - m_RenderWindowLayerController->ResetRenderer(true, m_StorageModel->GetCurrentRenderer()); - m_Controls.renderWindowTreeView->clearSelection(); -} - void QmitkRenderWindowDataStorageInspector::ChangeViewDirection(const QString& viewDirection) { m_RenderWindowViewDirectionController->SetViewDirectionOfRenderer(viewDirection.toStdString(), m_StorageModel->GetCurrentRenderer()); } diff --git a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.ui b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.ui index 785e20c50c..0ad31773e2 100644 --- a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.ui +++ b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.ui @@ -1,92 +1,78 @@ QmitkRenderWindowDataStorageInspector 0 0 340 246 0 0 0 0 Render window manager Render window overview - - - - Reset render window - - - - - - - Set as base layer - - - - + Axial true - + Coronal - + Sagittal - + 3D diff --git a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageListModel.cpp b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageListModel.cpp index 9b6face241..efba98a992 100644 --- a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageListModel.cpp +++ b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageListModel.cpp @@ -1,351 +1,350 @@ /*============================================================================ 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. ============================================================================*/ // render window manager UI module #include "QmitkRenderWindowDataStorageListModel.h" // qt widgets module #include "QmitkCustomVariants.h" #include "QmitkEnums.h" #include "QmitkMimeTypes.h" #include "QmitkNodeDescriptorManager.h" QmitkRenderWindowDataStorageListModel::QmitkRenderWindowDataStorageListModel(QObject* parent /*= nullptr*/) : QmitkAbstractDataStorageModel(parent) { m_RenderWindowLayerController = std::make_unique(); } void QmitkRenderWindowDataStorageListModel::DataStorageChanged() { m_RenderWindowLayerController->SetDataStorage(m_DataStorage.Lock()); UpdateModelData(); } void QmitkRenderWindowDataStorageListModel::NodePredicateChanged() { UpdateModelData(); } void QmitkRenderWindowDataStorageListModel::NodeAdded(const mitk::DataNode* node) { // add a node to each render window specific list (or to a global list initially) AddDataNodeToAllRenderer(const_cast(node)); UpdateModelData(); } void QmitkRenderWindowDataStorageListModel::NodeChanged(const mitk::DataNode* /*node*/) { // nothing here, since the "'NodeChanged'-event is currently sent far too often } void QmitkRenderWindowDataStorageListModel::NodeRemoved(const mitk::DataNode* /*node*/) { // update model data to create a new list without the removed data node UpdateModelData(); } QModelIndex QmitkRenderWindowDataStorageListModel::index(int row, int column, const QModelIndex& parent) const { bool hasIndex = this->hasIndex(row, column, parent); if (hasIndex) { return this->createIndex(row, column); } return QModelIndex(); } QModelIndex QmitkRenderWindowDataStorageListModel::parent(const QModelIndex& /*child*/) const { return QModelIndex(); } int QmitkRenderWindowDataStorageListModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const { if (parent.isValid()) { return 0; } return static_cast(m_LayerStack.size()); } int QmitkRenderWindowDataStorageListModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const { if (parent.isValid()) { return 0; } return 1; } QVariant QmitkRenderWindowDataStorageListModel::data(const QModelIndex& index, int role) const { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return QVariant(); } if (!index.isValid() || this != index.model()) { return QVariant(); } if (index.row() < 0 || index.row() >= static_cast(m_LayerStack.size())) { return QVariant(); } mitk::RenderWindowLayerUtilities::LayerStack::const_iterator layerStackIt = m_LayerStack.begin(); std::advance(layerStackIt, index.row()); mitk::DataNode* dataNode = layerStackIt->second; if (Qt::CheckStateRole == role) { bool visibility = false; dataNode->GetVisibility(visibility, baseRenderer); if (visibility) { return Qt::Checked; } else { return Qt::Unchecked; } } else if (Qt::DisplayRole == role) { return QVariant(QString::fromStdString(dataNode->GetName())); } else if (Qt::ToolTipRole == role) { return QVariant("Name of the data node."); } else if (Qt::DecorationRole == role) { QmitkNodeDescriptor* nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(dataNode); return nodeDescriptor->GetIcon(dataNode); } else if (Qt::UserRole == role || QmitkDataNodeRawPointerRole == role) { // user role always returns a reference to the data node, // which can be used to modify the data node in the data storage return QVariant::fromValue(dataNode); } else if (QmitkDataNodeRole == role) { return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); } return QVariant(); } bool QmitkRenderWindowDataStorageListModel::setData(const QModelIndex& index, const QVariant& value, int role /*= Qt::EditRole*/) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return false; } if (!index.isValid() || this != index.model()) { return false; } if (index.row() < 0 || index.row() >= static_cast(m_LayerStack.size())) { return false; } mitk::RenderWindowLayerUtilities::LayerStack::const_iterator layerStackIt = m_LayerStack.begin(); std::advance(layerStackIt, index.row()); mitk::DataNode* dataNode = layerStackIt->second; if (Qt::CheckStateRole == role) { Qt::CheckState newCheckState = static_cast(value.toInt()); bool isVisible = newCheckState; dataNode->SetVisibility(isVisible, baseRenderer); emit dataChanged(index, index); mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); return true; } return false; } Qt::ItemFlags QmitkRenderWindowDataStorageListModel::flags(const QModelIndex &index) const { if (this != index.model()) { return Qt::NoItemFlags; } if (!index.isValid()) { return Qt::ItemIsDropEnabled; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } Qt::DropActions QmitkRenderWindowDataStorageListModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } Qt::DropActions QmitkRenderWindowDataStorageListModel::supportedDragActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList QmitkRenderWindowDataStorageListModel::mimeTypes() const { QStringList types = QAbstractItemModel::mimeTypes(); types << QmitkMimeTypes::DataNodePtrs; return types; } QMimeData* QmitkRenderWindowDataStorageListModel::mimeData(const QModelIndexList& indexes) const { QMimeData* mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); for (const auto& index : indexes) { if (index.isValid()) { auto dataNode = data(index, QmitkDataNodeRawPointerRole).value(); stream << reinterpret_cast(dataNode); } } mimeData->setData(QmitkMimeTypes::DataNodePtrs, encodedData); return mimeData; } bool QmitkRenderWindowDataStorageListModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int /*row*/, int column, const QModelIndex& parent) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return false; } if (action == Qt::IgnoreAction) { return true; } if (!data->hasFormat(QmitkMimeTypes::DataNodePtrs)) { return false; } if (column > 0) { return false; } if (parent.isValid()) { int layer = -1; auto dataNode = this->data(parent, QmitkDataNodeRawPointerRole).value(); if (nullptr != dataNode) { dataNode->GetIntProperty("layer", layer, baseRenderer); } auto dataNodeList = QmitkMimeTypes::ToDataNodePtrList(data); for (const auto& dataNode : qAsConst(dataNodeList)) { m_RenderWindowLayerController->MoveNodeToPosition(dataNode, layer, baseRenderer); } UpdateModelData(); return true; } return false; } -void QmitkRenderWindowDataStorageListModel::SetControlledRenderer(mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer) +void QmitkRenderWindowDataStorageListModel::SetControlledRenderer(mitk::RenderWindowLayerUtilities::RendererVector /*controlledRenderer*/) { - m_RenderWindowLayerController->SetControlledRenderer(controlledRenderer); auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNotNull()) { mitk::DataStorage::SetOfObjects::ConstPointer allDataNodes = dataStorage->GetAll(); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allDataNodes->Begin(); it != allDataNodes->End(); ++it) { mitk::DataNode::Pointer dataNode = it->Value(); if (dataNode.IsNull()) { continue; } AddDataNodeToAllRenderer(dataNode); } } } void QmitkRenderWindowDataStorageListModel::SetCurrentRenderer(mitk::BaseRenderer* baseRenderer) { if (m_BaseRenderer == baseRenderer) { return; } m_BaseRenderer = baseRenderer; if (!m_BaseRenderer.IsExpired()) { UpdateModelData(); } } mitk::BaseRenderer::Pointer QmitkRenderWindowDataStorageListModel::GetCurrentRenderer() const { return m_BaseRenderer.Lock(); } void QmitkRenderWindowDataStorageListModel::AddDataNodeToAllRenderer(mitk::DataNode* dataNode) { m_RenderWindowLayerController->InsertLayerNode(dataNode); } void QmitkRenderWindowDataStorageListModel::UpdateModelData() { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNotNull()) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNotNull()) { // update the model, so that it will be filled with the nodes of the new data storage beginResetModel(); // get the current layer stack of the given base renderer - m_LayerStack = mitk::RenderWindowLayerUtilities::GetLayerStack(dataStorage, baseRenderer, true); + m_LayerStack = mitk::RenderWindowLayerUtilities::GetLayerStack(dataStorage, baseRenderer); endResetModel(); } } } diff --git a/Modules/SegmentationUI/resources/SegmentationUI.qrc b/Modules/SegmentationUI/resources/SegmentationUI.qrc index 02157a1290..c3ac5072c8 100644 --- a/Modules/SegmentationUI/resources/SegmentationUI.qrc +++ b/Modules/SegmentationUI/resources/SegmentationUI.qrc @@ -1,33 +1,29 @@ BooleanDifference_48x48.png BooleanIntersection_48x48.png BooleanUnion_48x48.png BooleanLabelA_32x32.png BooleanLabelB_32x32.png Dilate_48x48.png Erode_48x48.png Closing_48x48.png Opening_48x48.png FillHoles_48x48.png DeleteLayer_48x48.png PreviousLayer_48x48.png NextLayer_48x48.png AddLayer_48x48.png LockExterior_48x48.png UnlockExterior_48x48.png NewLabel_48x48.png NewSegmentation_48x48.png - visible.svg - invisible.svg - lock.svg - unlock.svg MergeLabels.png RemoveLabel.png EraseLabel.png CreateSurface.png CreateMask.png RandomColor.png RenameLabel.png diff --git a/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp b/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp index 3c98177cf9..7675c4257e 100644 --- a/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp +++ b/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp @@ -1,234 +1,237 @@ /*============================================================================ 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 "QmitkMxNMultiWidgetEditor.h" #include #include #include #include #include #include // mxn multi widget editor plugin #include "QmitkMultiWidgetDecorationManager.h" // mitk qt widgets module #include #include #include // qt #include const QString QmitkMxNMultiWidgetEditor::EDITOR_ID = "org.mitk.editors.mxnmultiwidget"; struct QmitkMxNMultiWidgetEditor::Impl final { Impl(); ~Impl() = default; QmitkInteractionSchemeToolBar* m_InteractionSchemeToolBar; QmitkMultiWidgetConfigurationToolBar* m_ConfigurationToolBar; }; QmitkMxNMultiWidgetEditor::Impl::Impl() : m_InteractionSchemeToolBar(nullptr) , m_ConfigurationToolBar(nullptr) { // nothing here } ////////////////////////////////////////////////////////////////////////// // QmitkMxNMultiWidgetEditor ////////////////////////////////////////////////////////////////////////// QmitkMxNMultiWidgetEditor::QmitkMxNMultiWidgetEditor() : QmitkAbstractMultiWidgetEditor() , m_Impl(std::make_unique()) { // nothing here } QmitkMxNMultiWidgetEditor::~QmitkMxNMultiWidgetEditor() { GetSite()->GetPage()->RemovePartListener(this); } berry::IPartListener::Events::Types QmitkMxNMultiWidgetEditor::GetPartEventTypes() const { return Events::CLOSED | Events::OPENED | Events::HIDDEN | Events::VISIBLE; } void QmitkMxNMultiWidgetEditor::PartClosed(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { - multiWidget->RemovePlanesFromDataStorage(); multiWidget->ActivateMenuWidget(false); } } } void QmitkMxNMultiWidgetEditor::PartOpened(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { - multiWidget->AddPlanesToDataStorage(); + multiWidget->EnableCrosshair(); multiWidget->ActivateMenuWidget(true); } } } void QmitkMxNMultiWidgetEditor::PartHidden(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->ActivateMenuWidget(false); } } } void QmitkMxNMultiWidgetEditor::PartVisible(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->ActivateMenuWidget(true); } } } void QmitkMxNMultiWidgetEditor::OnLayoutSet(int row, int column) { const auto &multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { QmitkAbstractMultiWidgetEditor::OnLayoutSet(row, column); - multiWidget->AddPlanesToDataStorage(); + multiWidget->EnableCrosshair(); } } void QmitkMxNMultiWidgetEditor::OnInteractionSchemeChanged(mitk::InteractionSchemeSwitcher::InteractionScheme scheme) { const auto &multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { return; } if (mitk::InteractionSchemeSwitcher::PACSStandard == scheme) { m_Impl->m_InteractionSchemeToolBar->setVisible(true); } else { m_Impl->m_InteractionSchemeToolBar->setVisible(false); } QmitkAbstractMultiWidgetEditor::OnInteractionSchemeChanged(scheme); } ////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidgetEditor::SetFocus() { const auto& multiWidget = GetMultiWidget(); if (nullptr != multiWidget) { multiWidget->setFocus(); } } void QmitkMxNMultiWidgetEditor::CreateQtPartControl(QWidget* parent) { QHBoxLayout *layout = new QHBoxLayout(parent); layout->setContentsMargins(0, 0, 0, 0); auto* preferences = this->GetPreferences(); auto multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { multiWidget = new QmitkMxNMultiWidget(parent, 0, nullptr); // create left toolbar: interaction scheme toolbar to switch how the render window navigation behaves in PACS mode if (nullptr == m_Impl->m_InteractionSchemeToolBar) { m_Impl->m_InteractionSchemeToolBar = new QmitkInteractionSchemeToolBar(parent); layout->addWidget(m_Impl->m_InteractionSchemeToolBar); } m_Impl->m_InteractionSchemeToolBar->SetInteractionEventHandler(multiWidget->GetInteractionEventHandler()); multiWidget->SetDataStorage(GetDataStorage()); multiWidget->InitializeMultiWidget(); SetMultiWidget(multiWidget); } layout->addWidget(multiWidget); // create right toolbar: configuration toolbar to change the render window widget layout if (nullptr == m_Impl->m_ConfigurationToolBar) { m_Impl->m_ConfigurationToolBar = new QmitkMultiWidgetConfigurationToolBar(multiWidget); layout->addWidget(m_Impl->m_ConfigurationToolBar); } connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::LayoutSet, this, &QmitkMxNMultiWidgetEditor::OnLayoutSet); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::Synchronized, this, &QmitkMxNMultiWidgetEditor::OnSynchronize); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::InteractionSchemeChanged, this, &QmitkMxNMultiWidgetEditor::OnInteractionSchemeChanged); + connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::SaveLayout, + static_cast(GetMultiWidget()), &QmitkMxNMultiWidget::SaveLayout, Qt::DirectConnection); + connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::LoadLayout, + static_cast(GetMultiWidget()), &QmitkMxNMultiWidget::LoadLayout); GetSite()->GetPage()->AddPartListener(this); OnPreferencesChanged(preferences); } void QmitkMxNMultiWidgetEditor::OnPreferencesChanged(const mitk::IPreferences* preferences) { const auto& multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { return; } // update decoration preferences //m_Impl->m_MultiWidgetDecorationManager->DecorationPreferencesChanged(preferences); int crosshairGapSize = preferences->GetInt("crosshair gap size", 32); multiWidget->SetCrosshairGap(crosshairGapSize); // zooming and panning preferences bool constrainedZooming = preferences->GetBool("Use constrained zooming and panning", true); mitk::RenderingManager::GetInstance()->SetConstrainedPanningZooming(constrainedZooming); bool PACSInteractionScheme = preferences->GetBool("PACS like mouse interaction", false); OnInteractionSchemeChanged(PACSInteractionScheme ? mitk::InteractionSchemeSwitcher::PACSStandard : mitk::InteractionSchemeSwitcher::MITKStandard); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataSetOpenInAction.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataSetOpenInAction.cpp index 3156db0e06..73ac1edd38 100644 --- a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataSetOpenInAction.cpp +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataSetOpenInAction.cpp @@ -1,160 +1,156 @@ /*============================================================================ 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. ============================================================================*/ // semantic relations plugin #include "QmitkDataSetOpenInAction.h" // mitk core #include #include // render window manager module #include #include // semantic relations module #include "mitkNodePredicates.h" // mitk gui qt application plugin #include // qt #include #include namespace OpenInAction { void Run(mitk::DataStorage::Pointer dataStorage, mitk::DataNode::Pointer imageNode, mitk::BaseRenderer* renderer /*= nullptr*/) { if (dataStorage.IsNull()) { return; } if (imageNode.IsNull()) { return; } auto renderWindowLayerController = std::make_unique(); renderWindowLayerController->SetDataStorage(dataStorage); - // get currently fixed layer nodes of the specified renderer + // get current layer stack of the specified renderer // remove them from the specified renderer - auto layerStack = mitk::RenderWindowLayerUtilities::GetLayerStack(dataStorage, renderer, true); - for (auto& fixedLayer : layerStack) + auto layerStack = mitk::RenderWindowLayerUtilities::GetLayerStack(dataStorage, renderer); + for (auto& layerNode : layerStack) { // hide all nodes of the specified renderer - fixedLayer.second->SetVisibility(false, renderer); - fixedLayer.second->Modified(); + layerNode.second->SetVisibility(false, renderer); + layerNode.second->Modified(); } - // add the selected data node to the specified renderer - // only needed in case the render window manager plugin is not used - imageNode->SetBoolProperty("fixedLayer", true, renderer); - // make is visible + // make the selected data node visible imageNode->SetVisibility(true, renderer); imageNode->Modified(); // move node to front, which also request a render update renderWindowLayerController->MoveNodeToFront(imageNode, renderer); QList visibleNodes; visibleNodes.push_back(imageNode); // get all corresponding segmentations of this node mitk::DataStorage::SetOfObjects::ConstPointer segmentationNodes = dataStorage->GetDerivations(imageNode, mitk::NodePredicates::GetSegmentationPredicate(), false); for (auto it = segmentationNodes->Begin(); it != segmentationNodes->End(); ++it) { auto segmentation = it->Value(); - segmentation->SetBoolProperty("fixedLayer", true, renderer); segmentation->SetVisibility(true, renderer); // move node to front, which also request a render update renderWindowLayerController->MoveNodeToFront(segmentation, renderer); visibleNodes.push_back(segmentation); } ReinitAction::Run(berry::IWorkbenchPartSite::Pointer(), dataStorage, visibleNodes, renderer); } } QmitkDataSetOpenInAction::QmitkDataSetOpenInAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite) : QmitkDataNodeOpenInAction(parent, workbenchPartSite) { setText(tr("Open in")); InitializeAction(); } QmitkDataSetOpenInAction::QmitkDataSetOpenInAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite) : QmitkDataNodeOpenInAction(parent, berry::IWorkbenchPartSite::Pointer(workbenchPartSite)) { setText(tr("Open in")); InitializeAction(); } void QmitkDataSetOpenInAction::InitializeAction() { setCheckable(true); setMenu(new QMenu); connect(menu(), &QMenu::aboutToShow, this, &QmitkDataSetOpenInAction::OnMenuAboutToShow); SetControlledRenderer(); } void QmitkDataSetOpenInAction::OnMenuAboutToShow() { menu()->clear(); QAction* action; QStringList rendererNames; for (const auto& renderer : m_ControlledRenderer) { rendererNames.append(renderer->GetName()); } rendererNames.sort(); for (const auto& rendererName : rendererNames) { action = menu()->addAction(rendererName); connect(action, &QAction::triggered, this, &QmitkDataSetOpenInAction::OnActionTriggered); } } void QmitkDataSetOpenInAction::OnActionTriggered(bool /*checked*/) { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } auto dataNode = GetSelectedNode(); if (dataNode.IsNull()) { return; } QAction* senderAction = qobject_cast(QObject::sender()); if (nullptr == senderAction) { return; } std::string selectedRenderer = senderAction->text().toStdString(); mitk::BaseRenderer* renderer = mitk::BaseRenderer::GetByName(selectedRenderer); if (nullptr == renderer) { return; } OpenInAction::Run(dataStorage, dataNode, renderer); }