diff --git a/Modules/QtWidgets/files.cmake b/Modules/QtWidgets/files.cmake index cc23808ef6..cac3b8c1ae 100644 --- a/Modules/QtWidgets/files.cmake +++ b/Modules/QtWidgets/files.cmake @@ -1,174 +1,181 @@ 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(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/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/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..6f5575799b --- /dev/null +++ b/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h @@ -0,0 +1,148 @@ +/*============================================================================ + +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, 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 slots, 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 set the current selection and the selection mode of the given node selection widget. + * It can be used to initialize a newly connected / synchronized. + * The required values are stored in this class internally. + * + * @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/resource/Qmitk.qrc b/Modules/QtWidgets/resource/Qmitk.qrc index f38e7de208..773e1502be 100644 --- a/Modules/QtWidgets/resource/Qmitk.qrc +++ b/Modules/QtWidgets/resource/Qmitk.qrc @@ -1,29 +1,33 @@ 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 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/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/QmitkRenderWindowDataNodeTableModel.cpp b/Modules/QtWidgets/src/QmitkRenderWindowDataNodeTableModel.cpp new file mode 100644 index 0000000000..a3c9062704 --- /dev/null +++ b/Modules/QtWidgets/src/QmitkRenderWindowDataNodeTableModel.cpp @@ -0,0 +1,376 @@ +/*============================================================================ + +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) +{ + 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()) + { + 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/QmitkSynchronizedNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp new file mode 100644 index 0000000000..da730d116e --- /dev/null +++ b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp @@ -0,0 +1,663 @@ +/*============================================================================ + +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) + { + // 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/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