diff --git a/Modules/Core/include/mitkDataStorage.h b/Modules/Core/include/mitkDataStorage.h index a50eb15616..a189f5e852 100644 --- a/Modules/Core/include/mitkDataStorage.h +++ b/Modules/Core/include/mitkDataStorage.h @@ -1,440 +1,448 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKDATASTORAGE_H_HEADER_INCLUDED_ #define MITKDATASTORAGE_H_HEADER_INCLUDED_ #include "itkObject.h" #include "itkSimpleFastMutexLock.h" #include "itkVectorContainer.h" #include "mitkDataNode.h" #include "mitkGeometry3D.h" #include "mitkMessage.h" #include #include namespace mitk { class NodePredicateBase; class DataNode; class BaseRenderer; //##Documentation //## @brief Data management class that handles 'was created by' relations //## //## The DataStorage provides data storage and management functionality. //## It handles a 'was created by' relation by associating each data object with a //## set of source objects, that this object was created from. //## Thus, nodes are stored in a noncyclical directed graph data structure. //## If a new node is added to the DataStorage, AddNodeEvent is emitted. //## If a node is removed, RemoveNodeEvent is emitted. //## //## //## \ingroup DataStorage class MITKCORE_EXPORT DataStorage : public itk::Object { public: mitkClassMacroItkParent(DataStorage, itk::Object); //##Documentation //## @brief A Container of objects that is used as a result set of GetSubset() query operations (Set of //SmartPointers // to DataNodes). typedef itk::VectorContainer SetOfObjects; //##Documentation //## @brief Adds a DataNode containing a data object to its internal storage //## //## This Method adds a new data object to the DataStorage. The new object is //## passed in the first parameter. The second parameter is a set //## of source objects, that were used to create this object. The new object will have //## a 'was created from' relation to its source objects. //## the addition of a new object will fire the notification mechanism. //## If the node parameter is nullptr or if the DataNode has already been added, //## an exception will be thrown. virtual void Add(mitk::DataNode *node, const mitk::DataStorage::SetOfObjects *parents = nullptr) = 0; //##Documentation //## @brief Convenience method to add a node that has one parent //## void Add(mitk::DataNode *node, mitk::DataNode *parent); //##Documentation //## @brief Removes node from the DataStorage //## virtual void Remove(const mitk::DataNode *node) = 0; //##Documentation //## @brief Checks if a node exists in the DataStorage //## virtual bool Exists(const mitk::DataNode *node) const = 0; //##Documentation //## @brief Removes a set of nodes from the DataStorage //## void Remove(const mitk::DataStorage::SetOfObjects *nodes); //##Documentation //## @brief returns a set of data objects that meet the given condition(s) //## //## GetSubset returns a set of objects with a specific data type that meet the condition(s) //## specified in the condition parameter. Conditions can be //## - data type of the data object //## - is source object of specific object (e.g. all source objects of node x) //## - has property with specific value (e.g. OrganType is Liver) //## - negation of any condition //## - conjunction of a set of conditions //## - disjunction of a set of conditions //## Conditions are implemented as predicates using the Composite Design Pattern //## (see definition of NodePredicateBase for details). //## The method returns a set of SmartPointers to the DataNodes that fulfill the //## conditions. A set of all objects can be retrieved with the GetAll() method; SetOfObjects::ConstPointer GetSubset(const NodePredicateBase *condition) const; + //##Documentation + //## @brief returns the topmost node of the set of nodes, meeting special property conditions + //## + //## GetTopLayerNode returns a node that is visible and has the highest layer of a set of given nodes. + //## The property list, which is used to find the visibility- and layer-property is is specified by the + //## given base renderer. + mitk::DataNode::Pointer GetTopLayerNode(mitk::DataStorage::SetOfObjects::ConstPointer nodes, const mitk::Point3D worldPosition, const mitk::BaseRenderer* renderer); + //##Documentation //## @brief returns a set of source objects for a given node that meet the given condition(s). //## virtual SetOfObjects::ConstPointer GetSources(const mitk::DataNode *node, const NodePredicateBase *condition = nullptr, bool onlyDirectSources = true) const = 0; //##Documentation //## @brief returns a set of derived objects for a given node. //## //## GetDerivations() returns a set of objects that are derived from the DataNode node. //## This means, that node was used to create the returned objects. If the parameter //## onlyDirectDerivations is set to true (default value), only objects that directly have //## node as one of their source objects will be returned. Otherwise, objects that are //## derived from derivations of node are returned too. //## The derived objects can be filtered with a predicate object as described in the GetSubset() //## method by providing a predicate as the condition parameter. virtual SetOfObjects::ConstPointer GetDerivations(const mitk::DataNode *node, const NodePredicateBase *condition = nullptr, bool onlyDirectDerivations = true) const = 0; //##Documentation //## @brief returns a set of all data objects that are stored in the data storage //## virtual SetOfObjects::ConstPointer GetAll() const = 0; //##Documentation //## @brief Convenience method to get the first node that matches the predicate condition //## mitk::DataNode *GetNode(const NodePredicateBase *condition = nullptr) const; //##Documentation //## @brief Convenience method to get the first node with a given name //## mitk::DataNode *GetNamedNode(const char *name) const; //##Documentation //## @brief Convenience method to get the first node with a given name //## mitk::DataNode *GetNamedNode(const std::string name) const { return this->GetNamedNode(name.c_str()); } //##Documentation //## @brief Convenience method to get the first node with a given name that is derived from sourceNode //## mitk::DataNode *GetNamedDerivedNode(const char *name, const mitk::DataNode *sourceNode, bool onlyDirectDerivations = true) const; //##Documentation //## @brief Convenience method to get the first data object of a given data type with a given name //## template DataType *GetNamedObject(const char *name) const { if (name == nullptr) return nullptr; mitk::DataNode *n = this->GetNamedNode(name); if (n == nullptr) return nullptr; else return dynamic_cast(n->GetData()); } //##Documentation //## @brief Convenience method to get the first data object of a given data type with a given name //## template DataType *GetNamedObject(const std::string name) const { return this->GetNamedObject(name.c_str()); } //##Documentation //## @brief Convenience method to get the first data object of a given data type with a given name that is derived // from a specific node //## template DataType *GetNamedDerivedObject(const char *name, const mitk::DataNode *sourceNode, bool onlyDirectDerivations = true) const { if (name == nullptr) return nullptr; mitk::DataNode *n = this->GetNamedDerivedNode(name, sourceNode, onlyDirectDerivations); if (n == nullptr) return nullptr; else return dynamic_cast(n->GetData()); } //##Documentation //## @brief Returns a list of used grouptags //## const DataNode::GroupTagList GetGroupTags() const; /*ITK Mutex */ mutable itk::SimpleFastMutexLock m_MutexOne; /* Public Events */ typedef Message1 DataStorageEvent; //##Documentation //## @brief AddEvent is emitted whenever a new node has been added to the DataStorage. //## //## Observers should register to this event by calling myDataStorage->AddNodeEvent.AddListener(myObject, // MyObject::MyMethod). //## After registering, myObject->MyMethod() will be called every time a new node has been added to the DataStorage. //## Observers should unregister by calling myDataStorage->AddNodeEvent.RemoveListener(myObject, //MyObject::MyMethod). //## Note: AddEvents are _not_ emitted if a node is added to DataStorage by adding it to the the underlying //DataTree! // member variable is not needed to be locked in multi threaded scenarios since the DataStorageEvent is a typedef // for // a Message1 object which is thread safe DataStorageEvent AddNodeEvent; //##Documentation //## @brief RemoveEvent is emitted directly before a node is removed from the DataStorage. //## //## Observers should register to this event by calling myDataStorage->RemoveNodeEvent.AddListener(myObject, // MyObject::MyMethod). //## After registering, myObject->MyMethod() will be called every time a new node has been added to the DataStorage. //## Observers should unregister by calling myDataStorage->RemoveNodeEvent.RemoveListener(myObject, // MyObject::MyMethod). //## Note: RemoveEvents are also emitted if a node was removed from the DataStorage by deleting it from the //underlying // DataTree // member variable is not needed to be locked in multi threaded scenarios since the DataStorageEvent is a typedef // for // a Message1 object which is thread safe DataStorageEvent RemoveNodeEvent; //##Documentation //## @brief ChangedEvent is emitted directly after a node was changed. //## //## Observers should register to this event by calling myDataStorage->ChangedNodeEvent.AddListener(myObject, // MyObject::MyMethod). //## After registering, myObject->MyMethod() will be called every time a new node has been changed. //## Observers should unregister by calling myDataStorage->ChangedNodeEvent.RemoveListener(myObject, // MyObject::MyMethod). //## Internally the DataStorage listens to itk::ModifiedEvents on the nodes and forwards them //## to the listeners of this event. // member variable is not needed to be locked in multi threaded scenarios since the DataStorageEvent is a typedef // for // a Message1 object which is thread safe DataStorageEvent ChangedNodeEvent; //##Documentation //## @brief DeleteNodeEvent is emitted directly before a node is deleted. //## //## Observers should register to this event by calling myDataStorage->DeleteNodeEvent.AddListener(myObject, // MyObject::MyMethod). //## After registering, myObject->MyMethod() will be called when a node is deleted. //## Observers should unregister by calling myDataStorage->DeleteNodeEvent.RemoveListener(myObject, // MyObject::MyMethod). //## Internally the DataStorage listens to itk::DeleteEvents on the nodes and forwards them //## to the listeners of this event. // member variable is not needed to be locked in multi threaded scenarios since the DataStorageEvent is a typedef // for // a Message1 object which is thread safe DataStorageEvent DeleteNodeEvent; DataStorageEvent InteractorChangedNodeEvent; //##Documentation //## @brief Compute the axis-parallel bounding geometry of the input objects //## //## Throws std::invalid_argument exception if input is nullptr //## @param input set of objects of the DataStorage to be included in the bounding geometry //## @param boolPropertyKey if a BoolProperty with this boolPropertyKey exists for a node (for @a renderer) //## and is set to @a false, the node is ignored for the bounding-box calculation. //## @param renderer see @a boolPropertyKey //## @param boolPropertyKey2 a second condition that is applied additionally to @a boolPropertyKey mitk::TimeGeometry::Pointer ComputeBoundingGeometry3D(const SetOfObjects *input, const char *boolPropertyKey = nullptr, const mitk::BaseRenderer *renderer = nullptr, const char *boolPropertyKey2 = nullptr) const; //##Documentation //## @brief Compute the axis-parallel bounding geometry of the data tree //## (bounding box, minimal spacing of the considered nodes, live-span) //## //## it -> an iterator to a data tree structure //## @param boolPropertyKey if a BoolProperty with this boolPropertyKey exists for a node (for @a renderer) //## and is set to @a false, the node is ignored for the bounding-box calculation. //## @param renderer see @a boolPropertyKey //## @param boolPropertyKey2 a second condition that is applied additionally to @a boolPropertyKey mitk::TimeGeometry::Pointer ComputeBoundingGeometry3D(const char *boolPropertyKey = nullptr, const mitk::BaseRenderer *renderer = nullptr, const char *boolPropertyKey2 = nullptr) const; //##Documentation //## @brief Compute the axis-parallel bounding geometry of all visible parts of the //## data tree bounding box, minimal spacing of the considered nodes, live-span) //## //## Simply calls ComputeBoundingGeometry3D(it, "visible", renderer, boolPropertyKey). //## it -> an iterator of a data tree structure //## @param renderer the reference to the renderer //## @param boolPropertyKey if a BoolProperty with this boolPropertyKey exists for a node (for @a renderer) //## and is set to @a false, the node is ignored for the bounding-box calculation. mitk::TimeGeometry::Pointer ComputeVisibleBoundingGeometry3D(const mitk::BaseRenderer *renderer = nullptr, const char *boolPropertyKey = nullptr); //##Documentation //## @brief Compute the bounding box of data tree structure //## it -> an iterator to a data tree structure //## @param boolPropertyKey if a BoolProperty with this boolPropertyKey exists for a node (for @a renderer) //## and is set to @a false, the node is ignored for the bounding-box calculation. //## @param renderer see @a boolPropertyKey //## @param boolPropertyKey2 a second condition that is applied additionally to @a boolPropertyKey mitk::BoundingBox::Pointer ComputeBoundingBox(const char *boolPropertyKey = nullptr, const mitk::BaseRenderer *renderer = nullptr, const char *boolPropertyKey2 = nullptr); //##Documentation //## \brief Compute the bounding box of all visible parts of the data tree structure, for general //## rendering or renderer specific visibility property checking //## //## Simply calls ComputeBoundingBox(it, "visible", renderer, boolPropertyKey). //## it -> an iterator of a data tree structure //## @param renderer the reference to the renderer //## @param boolPropertyKey if a BoolProperty with this boolPropertyKey exists for a node (for @a renderer) //## and is set to @a false, the node is ignored for the bounding-box calculation. mitk::BoundingBox::Pointer ComputeVisibleBoundingBox(const mitk::BaseRenderer *renderer = nullptr, const char *boolPropertyKey = nullptr) { return ComputeBoundingBox("visible", renderer, boolPropertyKey); } //##Documentation //## @brief Compute the time-bounds of the contents of a data tree structure //## //## The methods returns only [-infinity, +infinity], if all data-objects have an infinite live-span. Otherwise, //## all data-objects with infinite live-span are ignored. //## it -> an iterator to a data tree structure //## @param boolPropertyKey if a BoolProperty with this boolPropertyKey exists for a node (for @a renderer) //## and is set to @a false, the node is ignored for the time-bounds calculation. //## @param renderer see @a boolPropertyKey //## @param boolPropertyKey2 a second condition that is applied additionally to @a boolPropertyKey mitk::TimeBounds ComputeTimeBounds(const char *boolPropertyKey, const mitk::BaseRenderer *renderer, const char *boolPropertyKey2); //##Documentation //## @brief Compute the time-bounds of all visible parts of the data tree structure, for general //## rendering or renderer specific visibility property checking //## //## The methods returns only [-infinity, +infinity], if all data-objects have an infinite live-span. Otherwise, //## all data-objects with infinite live-span are ignored. //## Simply calls ComputeTimeBounds(it, "visible", renderer, boolPropertyKey). //## @param it an iterator to a data tree structure //## @param boolPropertyKey if a BoolProperty with this boolPropertyKey exists for a node (for @a renderer) //## and is set to @a false, the node is ignored for the time-bounds calculation. //## @param renderer see @a boolPropertyKey mitk::TimeBounds ComputeTimeBounds(const mitk::BaseRenderer *renderer, const char *boolPropertyKey) { return ComputeTimeBounds("visible", renderer, boolPropertyKey); } //##Documentation //## @brief Defines whether or not NodeChangedEvent is invoked . //## //## This method can be used to set m_BlockNodeModifiedEvents. //## //## If this flag is true, NodeChangedEvent is not invoked when a //## DataNode is modified. This might be undesired when setting //## many properties on a datanode and you do not want anyone to //## react. void BlockNodeModifiedEvents(bool block); protected: //##Documentation //## @brief EmitAddNodeEvent emits the AddNodeEvent //## //## This method should be called by subclasses to emit the AddNodeEvent void EmitAddNodeEvent(const mitk::DataNode *node); //##Documentation //## @brief EmitRemoveNodeEvent emits the RemoveNodeEvent //## //## This method should be called by subclasses to emit the RemoveNodeEvent void EmitRemoveNodeEvent(const mitk::DataNode *node); void OnNodeInteractorChanged(itk::Object *caller, const itk::EventObject &event); //##Documentation //## @brief OnNodeModified listens to modified events of DataNodes. //## //## The node is hidden behind the caller parameter, which has to be casted first. //## If the cast succeeds the ChangedNodeEvent is emitted with this node. void OnNodeModifiedOrDeleted(const itk::Object *caller, const itk::EventObject &event); //##Documentation //## @brief Adds a Modified-Listener to the given Node. void AddListeners(const mitk::DataNode *_Node); //##Documentation //## @brief Removes a Modified-Listener from the given Node. void RemoveListeners(const mitk::DataNode *_Node); //##Documentation //## @brief Saves Modified-Observer Tags for each node in order to remove the event listeners again. std::map m_NodeModifiedObserverTags; std::map m_NodeInteractorChangedObserverTags; //##Documentation //## @brief Saves Delete-Observer Tags for each node in order to remove the event listeners again. std::map m_NodeDeleteObserverTags; //##Documentation //## @brief If this class changes nodes itself, set this to TRUE in order //## to suppress NodeChangedEvent to be emitted. bool m_BlockNodeModifiedEvents; //##Documentation //## @brief Standard Constructor for ::New() instantiation DataStorage(); //##Documentation //## @brief Standard Destructor virtual ~DataStorage(); //##Documentation //## @brief Filters a SetOfObjects by the condition. If no condition is provided, the original set is returned SetOfObjects::ConstPointer FilterSetOfObjects(const SetOfObjects *set, const NodePredicateBase *condition) const; //##Documentation //## @brief Prints the contents of the DataStorage to os. Do not call directly, call ->Print() instead virtual void PrintSelf(std::ostream &os, itk::Indent indent) const override; }; } // namespace mitk #endif /* MITKDATASTORAGE_H_HEADER_INCLUDED_ */ diff --git a/Modules/Core/include/mitkDisplayActionEventBroadcast.h b/Modules/Core/include/mitkDisplayActionEventBroadcast.h index a548c87e48..421421f0de 100644 --- a/Modules/Core/include/mitkDisplayActionEventBroadcast.h +++ b/Modules/Core/include/mitkDisplayActionEventBroadcast.h @@ -1,218 +1,217 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKDISPLAYACTIONEVENTBROADCAST_H #define MITKDISPLAYACTIONEVENTBROADCAST_H #include "mitkInteractionEventObserver.h" #include namespace mitk { /** * @brief This class serves as an event state machine while simultaneously observing interaction events. * It connects the actions from the event state machine .xml-file with concrete functions of this class. */ class MITKCORE_EXPORT DisplayActionEventBroadcast : public EventStateMachine, public InteractionEventObserver { public: mitkClassMacro(DisplayActionEventBroadcast, EventStateMachine) itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** * By this function this observer is notified about about every 'InteractionEvent'. * The interaction event is passed to the state machine in order to use its infrastructure. * For more information see @see InteractionEventObserver. * * @par interactionEvent The event that was observed and triggered this notification. * @par isHandled Flag that indicates if a 'DataInteractor' has already handled the event. */ virtual void Notify(InteractionEvent* interactionEvent, bool isHandled) override; protected: DisplayActionEventBroadcast(); virtual ~DisplayActionEventBroadcast() override; /** * @brief Connects the action names used in the state machine pattern with functions implemented within this InteractionEventObserver. */ void ConnectActionsAndFunctions() override; /** * @brief This function is executed when a config object is set / changed (via 'SetEventConfig' or 'AddEventConfig' in 'InteractionEventObserver'). * It is used to read out the parameters set in the configuration file and to set the member variables accordingly. */ virtual void ConfigurationChanged() override; /** * @brief Filters the event resp. the sender of the event. * * @par interactionEvent The event whose sender has to be checked * @par data node The data node is ignored in this specific implementation. * * @return True, if the sender of the event is a valid sender and the sending renderer is a 2D-renderer. False, if not. */ virtual bool FilterEvents(InteractionEvent* interactionEvent, DataNode* dataNode) override; ////////////////////////////////////////////////////////////////////////// // Functions to react to interaction events (actions) ////////////////////////////////////////////////////////////////////////// /** * @brief Check if the given interaction event is actually an 'InteractionPositionEvent'. * * @par interactionEvent The interaction event that is checked. * * @return True, if the given event can be dynamically cast to an 'InteractionPositionEvent'. False, if not. */ - virtual bool CheckPositionEvent(const InteractionEvent* interactionEvent); + bool CheckPositionEvent(const InteractionEvent* interactionEvent); - virtual bool CheckRotationPossible(const InteractionEvent* interactionEvent); + bool CheckRotationPossible(const InteractionEvent* interactionEvent); - virtual bool CheckSwivelPossible(const InteractionEvent* interactionEvent); + bool CheckSwivelPossible(const InteractionEvent* interactionEvent); - virtual void Init(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void Init(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - virtual void Move(StateMachineAction* stateMachineAction , InteractionEvent* interactionEvent); + void Move(StateMachineAction* stateMachineAction , InteractionEvent* interactionEvent); - virtual void SetCrosshair(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void SetCrosshair(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - virtual void Zoom(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void Zoom(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - virtual void Scroll(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void Scroll(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - virtual void ScrollOneUp(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void ScrollOneUp(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - virtual void ScrollOneDown(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void ScrollOneDown(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - virtual void AdjustLevelWindow(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void AdjustLevelWindow(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - virtual void StartRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void StartRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - virtual void EndRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void EndRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - virtual void Rotate(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void Rotate(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - virtual void Swivel(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void Swivel(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); private: - virtual void UpdateStatusbar(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); + void UpdateStatusbar(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); bool GetBoolProperty(mitk::PropertyList::Pointer propertyList, const char* propertyName, bool defaultValue); - /** * @brief Reference to the service registration of the observer. * This is needed to unregister the observer on unload. */ us::ServiceRegistration m_ServiceRegistration; /** * @brief Determines if this broadcast class reacts to events that already have been processed by a DataInteractor. * The default value is false. */ bool m_AlwaysReact; /** * @brief Coordinate of the mouse pointer at beginning of an interaction (translated to mm unit). */ mitk::Point2D m_StartCoordinateInMM; /** * @brief Coordinate of the mouse pointer in the last step within an interaction. */ mitk::Point2D m_LastDisplayCoordinate; /** * @brief Coordinate of the mouse pointer in the last step within an interaction (translated to mm unit). */ mitk::Point2D m_LastCoordinateInMM; /** * \brief Current coordinates of the pointer. */ mitk::Point2D m_CurrentDisplayCoordinate; /** * @brief Defines the behavior at the end of a data set. * If set to true, it will restart at end of data set from the beginning. */ bool m_AutoRepeat; /** * @brief Defines how many slices are scrolled per pixel that the mouse pointer was moved. * By default the modifier is 4. This means that when the user moves the cursor by 4 pixels in Y-direction, * the scene is scrolled by one slice. If the user has moved the the cursor by 20 pixels, the scene is * scrolled by 5 slices. * * If the cursor has moved less than m_IndexToSliceModifier pixels, the scene is scrolled by one slice. */ int m_IndexToSliceModifier; /** * @brief Defines the scroll behavior. * Default is up/down movement of pointer performs scrolling */ std::string m_ScrollDirection; /** * @brief Defines how the axis of interaction influences scroll behavior. */ bool m_InvertScrollDirection; /** * @brief Defines the zoom behavior. * Default is up/down movement of pointer performs zooming */ std::string m_ZoomDirection; /** * @brief Defines how the axis of interaction influences zoom behavior. */ bool m_InvertZoomDirection; /** * @brief Factor to adjust zooming speed. */ float m_ZoomFactor; /** * @brief Defines how the axis of interaction influences move behavior. */ bool m_InvertMoveDirection; /** * @brief Defines the level-window behavior. * Default is left/right movement of pointer modifies the level. */ std::string m_LevelDirection; /** * @brief Defines how the axis of interaction influences level-window behavior. */ bool m_InvertLevelWindowDirection; /** * @brief Determines if the angle between crosshair remains fixed when rotating. */ bool m_LinkPlanes; typedef std::vector SNCVector; SNCVector m_RotatableSNCs; SNCVector m_SNCsToBeRotated; Point3D m_LastCursorPosition; Point3D m_CenterOfRotation; Point2D m_ReferenceCursor; Vector3D m_RotationPlaneNormal; Vector3D m_RotationPlaneXVector; Vector3D m_RotationPlaneYVector; Vector3D m_PreviousRotationAxis; ScalarType m_PreviousRotationAngle; }; } // end namespace #endif // MITKDISPLAYACTIONEVENTBROADCAST_H diff --git a/Modules/Core/include/mitkDisplayActionEventFunctions.h b/Modules/Core/include/mitkDisplayActionEventFunctions.h index 4cefc1f7ac..519d022c6e 100644 --- a/Modules/Core/include/mitkDisplayActionEventFunctions.h +++ b/Modules/Core/include/mitkDisplayActionEventFunctions.h @@ -1,83 +1,90 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKDISPLAYACTIONEVENTFUNCTIONS_H #define MITKDISPLAYACTIONEVENTFUNCTIONS_H #include #include "mitkStdFunctionCommand.h" namespace mitk { namespace DisplayActionEventFunctions { /** - * @brief Returns a command that reacts on the 'DisplayMoveEvent'. - * The command performs a move of the camera controller of the sending renderer by a vector + * @brief Returns an 'std::function' that can be used to react on the 'DisplayMoveEvent'. + * The function performs a move of the camera controller of the sending renderer by a vector * that was previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction MoveSenderCameraAction(); /** - * @brief Returns a command that reacts on the 'DisplaySetCrosshairEvent'. - * The command performs a slice selection of the slice navigation controller and will set - * the cross hair for all render windows. + * @brief Returns an 'std::function' that can be used to react on the 'DisplaySetCrosshairEvent'. + * The function performs a slice selection of the slice navigation controller and will set + * the cross hair for all 2D-render windows. * The new position was previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction SetCrosshairAction(); /** - * @brief Returns a command that reacts on the 'DisplayZoomEvent'. - * The command performs a zoom of the camera controller of the sending renderer by a zoom factor + * @brief Returns an 'std::function' that can be used to react on the 'DisplayZoomEvent'. + * The function performs a zoom of the camera controller of the sending renderer by a zoom factor * that was previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction ZoomSenderCameraAction(); /** - * @brief Returns a command that reacts on the 'DisplayScrollEvent'. - * The command performs a slice scrolling of the slice navigation controller of the sending renderer. + * @brief Returns an 'std::function' that can be used to react on the 'DisplayScrollEvent'. + * The function performs a slice scrolling of the slice navigation controller of the sending renderer. * The new position was previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction ScrollSliceStepperAction(); /** * @brief Returns an 'std::function' that can be used to react on the 'DisplaySetLevelWindowEvent'. * The function sets the 'levelwindow' property of the topmost visible image that is display by the sending renderer. * The level and window value for this property were previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction SetLevelWindowAction(); + /** + * @brief Returns an 'std::function' that can be used to react on the 'DisplayMoveEvent'. + * The function performs a move of the camera controller of all renderer (synchronized) + * by a vector that was previously determined by the mouse interaction event. + * The renderer need to be managed by the same rendering manager. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction MoveCameraSynchronizedAction(); /** - * @brief - * - * + * @brief Returns an 'std::function' that can be used to react on the 'DisplaySetCrosshairEvent'. + * The function performs a slice selection of the slice navigation controller and will set + * the cross hair for all 2D-render windows. + * The new position was previously determined by the mouse interaction event. + * #TODO: currently there is no need to distinguish between this and the non-synchronized version */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction SetCrosshairSynchronizedAction(); /** - * @brief - * - * + * @brief Returns an 'std::function' that can be used to react on the 'DisplayZoomEvent'. + * The function performs a zoom of the camera controller of all 2D-renderer (synchronized) + * by a zoom factor that was previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction ZoomCameraSynchronizedAction(); /** - * @brief - * - * + * @brief Returns an 'std::function' that can be used to react on the 'DisplayScrollEvent'. + * The function performs a slice scrolling of the slice navigation controller of all 2D-renderer (synchronized). + * The new position was previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction ScrollSliceStepperSynchronizedAction(); } // end namespace DisplayActionEventFunctions } // end namespace mitk #endif // MITKDISPLAYACTIONEVENTFUNCTIONS_H diff --git a/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp b/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp index 9ee76de4c3..c345aa5337 100644 --- a/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp +++ b/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp @@ -1,675 +1,659 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDisplayActionEventBroadcast.h" // us #include "usGetModuleContext.h" #include "usModuleContext.h" // mitk core module #include "mitkDisplayActionEvents.h" #include "mitkInteractionPositionEvent.h" #include "mitkLine.h" mitk::DisplayActionEventBroadcast::DisplayActionEventBroadcast() : m_AlwaysReact(false) , m_AutoRepeat(false) , m_IndexToSliceModifier(4) , m_InvertScrollDirection(false) , m_InvertZoomDirection(false) , m_ZoomFactor(2) , m_InvertMoveDirection(false) , m_InvertLevelWindowDirection(false) , m_LinkPlanes(true) { m_StartCoordinateInMM.Fill(0); m_LastDisplayCoordinate.Fill(0); m_LastCoordinateInMM.Fill(0); m_CurrentDisplayCoordinate.Fill(0); // register the broadcast class (itself) as an interaction event observer via micro services us::ServiceProperties props; props["name"] = std::string("DisplayActionEventBroadcast"); m_ServiceRegistration = us::GetModuleContext()->RegisterService(this, props); } mitk::DisplayActionEventBroadcast::~DisplayActionEventBroadcast() { m_ServiceRegistration.Unregister(); } void mitk::DisplayActionEventBroadcast::Notify(InteractionEvent* interactionEvent, bool isHandled) { // the event is passed to the state machine interface to be handled if (!isHandled || m_AlwaysReact) { HandleEvent(interactionEvent, nullptr); } } void mitk::DisplayActionEventBroadcast::ConnectActionsAndFunctions() { CONNECT_CONDITION("check_position_event", CheckPositionEvent); CONNECT_CONDITION("check_can_rotate", CheckRotationPossible); CONNECT_CONDITION("check_can_swivel", CheckSwivelPossible); CONNECT_FUNCTION("init", Init); CONNECT_FUNCTION("move", Move); CONNECT_FUNCTION("zoom", Zoom); CONNECT_FUNCTION("scroll", Scroll); CONNECT_FUNCTION("ScrollOneUp", ScrollOneUp); CONNECT_FUNCTION("ScrollOneDown", ScrollOneDown); CONNECT_FUNCTION("levelWindow", AdjustLevelWindow); CONNECT_FUNCTION("setCrosshair", SetCrosshair); CONNECT_FUNCTION("updateStatusbar", UpdateStatusbar) CONNECT_FUNCTION("startRotation", StartRotation); CONNECT_FUNCTION("endRotation", EndRotation); CONNECT_FUNCTION("rotate", Rotate); CONNECT_FUNCTION("swivel", Swivel); } void mitk::DisplayActionEventBroadcast::ConfigurationChanged() { mitk::PropertyList::Pointer properties = GetAttributes(); // allwaysReact std::string strAlwaysReact = ""; + m_AlwaysReact = false; if (properties->GetStringProperty("alwaysReact", strAlwaysReact)) { if (strAlwaysReact == "true") { m_AlwaysReact = true; } - else - { - m_AlwaysReact = false; - } - } - else - { - m_AlwaysReact = false; } // auto repeat std::string strAutoRepeat = ""; + m_AutoRepeat = false; if (properties->GetStringProperty("autoRepeat", strAutoRepeat)) { if (strAutoRepeat == "true") { m_AutoRepeat = true; } - else - { - m_AutoRepeat = false; - } } // pixel movement for scrolling one slice std::string strPixelPerSlice = ""; + m_IndexToSliceModifier = 4; if (properties->GetStringProperty("pixelPerSlice", strPixelPerSlice)) { m_IndexToSliceModifier = atoi(strPixelPerSlice.c_str()); } - else - { - m_IndexToSliceModifier = 4; - } // scroll direction if (!properties->GetStringProperty("scrollDirection", m_ScrollDirection)) { m_ScrollDirection = "updown"; } m_InvertScrollDirection = GetBoolProperty(properties, "invertScrollDirection", false); // zoom direction if (!properties->GetStringProperty("zoomDirection", m_ZoomDirection)) { m_ZoomDirection = "updown"; } m_InvertZoomDirection = GetBoolProperty(properties, "invertZoomDirection", false); - m_InvertMoveDirection = GetBoolProperty(properties, "invertMoveDirection", false); if (!properties->GetStringProperty("levelWindowDirection", m_LevelDirection)) { m_LevelDirection = "leftright"; } m_InvertLevelWindowDirection = GetBoolProperty(properties, "invertLevelWindowDirection", false); // coupled rotation std::string strCoupled = ""; + m_LinkPlanes = false; if (properties->GetStringProperty("coupled", strCoupled)) { if (strCoupled == "true") { m_LinkPlanes = true; } - else - { - m_LinkPlanes = false; - } } // zoom factor std::string strZoomFactor = ""; properties->GetStringProperty("zoomFactor", strZoomFactor); m_ZoomFactor = .05; if (atoi(strZoomFactor.c_str()) > 0) { m_ZoomFactor = 1.0 + (atoi(strZoomFactor.c_str()) / 100.0); } } bool mitk::DisplayActionEventBroadcast::FilterEvents(InteractionEvent* interactionEvent, DataNode * /*dataNode*/) { if (nullptr == interactionEvent->GetSender()) { return false; } if (BaseRenderer::Standard3D == interactionEvent->GetSender()->GetMapperID()) { return false; } return true; } bool mitk::DisplayActionEventBroadcast::CheckPositionEvent(const InteractionEvent *interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } return true; } bool mitk::DisplayActionEventBroadcast::CheckRotationPossible(const InteractionEvent *interactionEvent) { // Decide between moving and rotation slices. /* Detailed logic: 1. Find the SliceNavigationController that has sent the event: this one defines our rendering plane and will NOT be rotated. Needs not even be counted or checked. 2. Inspect every other SliceNavigationController - calculate the line intersection of this SliceNavigationController's plane with our rendering plane - if there is NO interesection, ignore and continue - IF there is an intersection - check the mouse cursor's distance from that line. 0. if the line is NOT near the cursor, remember the plane as "one of the other planes" (which can be rotated in "locked" mode) 1. on first line near the cursor, just remember this intersection line as THE other plane that we want to rotate 2. on every consecutive line near the cursor, check if the line is geometrically identical to the line that we want to rotate - if yes, we just push this line to the "other" lines and rotate it along - if no, then we have a situation where the mouse is near two other lines (e.g. crossing point) and don't want to rotate */ const auto* posEvent = dynamic_cast(interactionEvent); if (nullptr == posEvent) { return false; } BaseRenderer* clickedRenderer = posEvent->GetSender(); const PlaneGeometry* ourViewportGeometry = clickedRenderer->GetCurrentWorldPlaneGeometry(); if (nullptr == ourViewportGeometry) { return false; } Point3D cursorPosition = posEvent->GetPositionInWorld(); const auto spacing = ourViewportGeometry->GetSpacing(); const PlaneGeometry *geometryToBeRotated = nullptr; // this one is under the mouse cursor const PlaneGeometry *anyOtherGeometry = nullptr; // this is also visible (for calculation of intersection ONLY) Line3D intersectionLineWithGeometryToBeRotated; bool hitMultipleLines(false); m_SNCsToBeRotated.clear(); const double threshholdDistancePixels = 12.0; auto renWindows = interactionEvent->GetSender()->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { SliceNavigationController *snc = BaseRenderer::GetInstance(renWin)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard3D) continue; const PlaneGeometry *otherRenderersRenderPlane = snc->GetCurrentPlaneGeometry(); if (nullptr == otherRenderersRenderPlane) { continue; // ignore, we don't see a plane } // check if there is an intersection Line3D intersectionLine; // between rendered/clicked geometry and the one being analyzed if (!ourViewportGeometry->IntersectionLine(otherRenderersRenderPlane, intersectionLine)) { continue; // we ignore this plane, it's parallel to our plane } // check distance from intersection line const double distanceFromIntersectionLine = intersectionLine.Distance(cursorPosition) / spacing[snc->GetDefaultViewDirection()]; // far away line, only remember for linked rotation if necessary if (distanceFromIntersectionLine > threshholdDistancePixels) { anyOtherGeometry = otherRenderersRenderPlane; // we just take the last one, so overwrite each iteration (we just // need some crossing point) // TODO what about multiple crossings? NOW we have undefined behavior / random crossing point is used if (m_LinkPlanes) { m_SNCsToBeRotated.push_back(snc); } } else // close to cursor { if (nullptr == geometryToBeRotated) // first one close to the cursor { geometryToBeRotated = otherRenderersRenderPlane; intersectionLineWithGeometryToBeRotated = intersectionLine; m_SNCsToBeRotated.push_back(snc); } else { // compare to the line defined by geometryToBeRotated: if identical, just rotate this otherRenderersRenderPlane // together with the primary one // if different, DON'T rotate if (intersectionLine.IsParallel(intersectionLineWithGeometryToBeRotated) && intersectionLine.Distance(intersectionLineWithGeometryToBeRotated.GetPoint1()) < mitk::eps) { m_SNCsToBeRotated.push_back(snc); } else { hitMultipleLines = true; } } } } bool moveSlices(true); if (geometryToBeRotated && anyOtherGeometry && ourViewportGeometry && !hitMultipleLines) { // assure all three are valid, so calculation of center of rotation can be done moveSlices = false; } // question in state machine is: "rotate?" if (moveSlices) // i.e. NOT rotate { return false; } else { // we DO have enough information for rotation // remember where the last cursor position ON THE LINE has been observed m_LastCursorPosition = intersectionLineWithGeometryToBeRotated.Project(cursorPosition); // find center of rotation by intersection with any of the OTHER lines if (anyOtherGeometry->IntersectionPoint(intersectionLineWithGeometryToBeRotated, m_CenterOfRotation)) { return true; } else { return false; } } return false; } bool mitk::DisplayActionEventBroadcast::CheckSwivelPossible(const InteractionEvent *interactionEvent) { const ScalarType ThresholdDistancePixels = 6.0; // Decide between moving and rotation: if we're close to the crossing // point of the planes, moving mode is entered, otherwise // rotation/swivel mode const auto *posEvent = dynamic_cast(interactionEvent); BaseRenderer *renderer = interactionEvent->GetSender(); if (!posEvent || !renderer) { return false; } const Point3D& cursor = posEvent->GetPositionInWorld(); m_SNCsToBeRotated.clear(); const PlaneGeometry* clickedGeometry(nullptr); const PlaneGeometry* otherGeometry1(nullptr); const PlaneGeometry* otherGeometry2(nullptr); auto registeredRenderWindows = interactionEvent->GetSender()->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renWin : registeredRenderWindows) { SliceNavigationController* snc = BaseRenderer::GetInstance(renWin)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard3D) continue; // unsigned int slice = (*iter)->GetSlice()->GetPos(); // unsigned int time = (*iter)->GetTime()->GetPos(); const PlaneGeometry *planeGeometry = snc->GetCurrentPlaneGeometry(); if (!planeGeometry) continue; if (snc == renderer->GetSliceNavigationController()) { clickedGeometry = planeGeometry; m_SNCsToBeRotated.push_back(snc); } else { if (otherGeometry1 == nullptr) { otherGeometry1 = planeGeometry; } else { otherGeometry2 = planeGeometry; } if (m_LinkPlanes) { // If planes are linked, apply rotation to all planes m_SNCsToBeRotated.push_back(snc); } } } Line3D line; Point3D point; if ((clickedGeometry != nullptr) && (otherGeometry1 != nullptr) && (otherGeometry2 != nullptr) && clickedGeometry->IntersectionLine(otherGeometry1, line) && otherGeometry2->IntersectionPoint(line, point)) { m_CenterOfRotation = point; if (m_CenterOfRotation.EuclideanDistanceTo(cursor) < ThresholdDistancePixels) { return false; } else { m_ReferenceCursor = posEvent->GetPointerPositionOnScreen(); // Get main axes of rotation plane and store it for rotation step m_RotationPlaneNormal = clickedGeometry->GetNormal(); ScalarType xVector[] = { 1.0, 0.0, 0.0 }; ScalarType yVector[] = { 0.0, 1.0, 0.0 }; clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(xVector), m_RotationPlaneXVector); clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(yVector), m_RotationPlaneYVector); m_RotationPlaneNormal.Normalize(); m_RotationPlaneXVector.Normalize(); m_RotationPlaneYVector.Normalize(); m_PreviousRotationAxis.Fill(0.0); m_PreviousRotationAxis[2] = 1.0; m_PreviousRotationAngle = 0.0; return true; } } else { return false; } return false; } void mitk::DisplayActionEventBroadcast::Init(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { auto* positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); m_CurrentDisplayCoordinate = m_LastDisplayCoordinate; positionEvent->GetSender()->DisplayToPlane(m_LastDisplayCoordinate, m_StartCoordinateInMM); m_LastCoordinateInMM = m_StartCoordinateInMM; } void mitk::DisplayActionEventBroadcast::Move(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { auto* positionEvent = static_cast(interactionEvent); BaseRenderer* sender = interactionEvent->GetSender(); Vector2D moveVector = m_LastDisplayCoordinate - positionEvent->GetPointerPositionOnScreen(); if (m_InvertMoveDirection) { moveVector *= -1.0; } moveVector *= sender->GetScaleFactorMMPerDisplayUnit(); // #TODO: put here? // store new display coordinate m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate move event with computed geometry values InvokeEvent(DisplayMoveEvent(interactionEvent, moveVector)); } void mitk::DisplayActionEventBroadcast::SetCrosshair(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { auto* positionEvent = static_cast(interactionEvent); Point3D position = positionEvent->GetPositionInWorld(); // propagate set crosshair event with computed geometry values InvokeEvent(DisplaySetCrosshairEvent(interactionEvent, position)); } void mitk::DisplayActionEventBroadcast::Zoom(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { auto* positionEvent = static_cast(interactionEvent); float factor = 1.0; float distance = 0; if (m_ZoomDirection == "updown") { distance = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; } else { distance = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; } if (m_InvertZoomDirection) { distance *= -1.0; } // set zooming speed if (distance < 0.0) { factor = 1.0 / m_ZoomFactor; } else if (distance > 0.0) { factor = 1.0 * m_ZoomFactor; } // store new display coordinates m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate zoom event with computed geometry values InvokeEvent(DisplayZoomEvent(interactionEvent, factor, m_StartCoordinateInMM)); } void mitk::DisplayActionEventBroadcast::Scroll(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { auto* positionEvent = static_cast(interactionEvent); int sliceDelta = 0; // scroll direction if (m_ScrollDirection == "updown") { - sliceDelta = static_cast(m_LastDisplayCoordinate[1] - positionEvent->GetPointerPositionOnScreen()[1]); + sliceDelta = static_cast(m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]); } else { - sliceDelta = static_cast(m_LastDisplayCoordinate[0] - positionEvent->GetPointerPositionOnScreen()[0]); + sliceDelta = static_cast(m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]); } if (m_InvertScrollDirection) { sliceDelta *= -1; } // set how many pixels the mouse has to be moved to scroll one slice // if the mouse has been moved less than 'm_IndexToSliceModifier', pixels slice ONE slice only if (sliceDelta > 0 && sliceDelta < m_IndexToSliceModifier) { sliceDelta = m_IndexToSliceModifier; } else if (sliceDelta < 0 && sliceDelta > -m_IndexToSliceModifier) { sliceDelta = -m_IndexToSliceModifier; } sliceDelta /= m_IndexToSliceModifier; // store new display coordinates m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate scroll event with computed geometry values InvokeEvent(DisplayScrollEvent(interactionEvent, sliceDelta)); } void mitk::DisplayActionEventBroadcast::ScrollOneUp(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { int sliceDelta = 1; if (m_InvertScrollDirection) { sliceDelta = -1; } // propagate scroll event with a single slice delta (increase) InvokeEvent(DisplayScrollEvent(interactionEvent, sliceDelta)); } void mitk::DisplayActionEventBroadcast::ScrollOneDown(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { int sliceDelta = -1; if (m_InvertScrollDirection) { sliceDelta = 1; } + // propagate scroll event with a single slice delta (decrease) InvokeEvent(DisplayScrollEvent(interactionEvent, sliceDelta)); } void mitk::DisplayActionEventBroadcast::AdjustLevelWindow(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { auto* positionEvent = static_cast(interactionEvent); mitk::ScalarType level; mitk::ScalarType window; if (m_LevelDirection == "leftright") { level = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; window = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; } else { level = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; window = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; } if (m_InvertLevelWindowDirection) { level *= -1; window *= -1; } level *= static_cast(2); window *= static_cast(2); // store new display coordinates m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate set level window event with the level and window delta InvokeEvent(DisplaySetLevelWindowEvent(interactionEvent, level, window)); } void mitk::DisplayActionEventBroadcast::StartRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { // nothing here; no event sent } void mitk::DisplayActionEventBroadcast::EndRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { // nothing here; no event sent } void mitk::DisplayActionEventBroadcast::Rotate(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { // nothing here; no event sent } void mitk::DisplayActionEventBroadcast::Swivel(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { // nothing here; no event sent } void mitk::DisplayActionEventBroadcast::UpdateStatusbar(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { } bool mitk::DisplayActionEventBroadcast::GetBoolProperty(mitk::PropertyList::Pointer propertyList, const char* propertyName, bool defaultValue) { std::string valueAsString; if (!propertyList->GetStringProperty(propertyName, valueAsString)) { return defaultValue; } else { if (valueAsString == "true") { return true; } else { return false; } } }