diff --git a/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h index ddafc6b443..104403f81f 100644 --- a/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h @@ -1,101 +1,103 @@ /*============================================================================ 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 SelectAll(); void SetSynchronized(bool synchronize); bool IsSynchronized() const; Q_SIGNALS: void SelectionModeChanged(bool selectAll); + void DeregisterSynchronization(); 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); bool IsParentNodeSelected(const mitk::DataNode* dataNode) const; 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 index ad672d5223..8975764392 100644 --- a/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h +++ b/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h @@ -1,150 +1,160 @@ /*============================================================================ 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; + using NodeList = QmitkSynchronizedNodeSelectionWidget::NodeList; QmitkSynchronizedWidgetConnector(); /* * @brief This function connects the different signals and slots of this instance and the given * given node selection widget, such that changes to the current list of nodes * and the selection mode can be forwarded or received. * The connections are as follows: * - QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged * -> QmitkSynchronizedWidgetConnector::ChangeSelection * - QmitkSynchronizedWidgetConnector::NodeSelectionChanged * -> QmitkAbstractNodeSelectionWidget::SetCurrentSelection * - QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged * -> QmitkSynchronizedWidgetConnector::ChangeSelectionMode * - QmitkSynchronizedWidgetConnector::SelectionModeChanged * -> QmitkSynchronizedNodeSelectionWidget::SetSelectAll * * @param nodeSelectionWidget The synchronized node selection widget to be connected / synchronized. */ - void ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; + void ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget); /* * @brief This function disconnects the different signals and slot of this instance and the given * given node selection widget, such that changes to the current list of nodes * and the selection mode cannot be forwarded or received anymore. * * @param nodeSelectionWidget The synchronized node selection widget to be disconnected / desynchronized. */ - void DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; + void DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget); /* * @brief This function sets the current selection and the selection mode of the given node selection widget * to the values of this instance. The required values are stored in this class internally. * It can be used to newly initialize the given node selection widget. * * @param nodeSelectionWidget The synchronized node selection widget for which the * current selection and the selection mode should be set. */ void SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; /* * @brief Get the current internal node selection. * * @return NodeList The current internal node selection stored as a member variable. */ NodeList GetNodeSelection() const; /* * @brief Get the current internal selection mode. * * @return The current internal selection mode stored as a member variable. */ bool GetSelectionMode() const; Q_SIGNALS: /* * @brief A signal that will be emitted by the 'ChangeSelection'-slot. * This happens if a new selection / list of nodes is set from outside of this class, * e.g. from a QmitkSynchronizedNodeSelectionWidget. * This signal is connected to the 'SetCurrentSelection'-slot of each * QmitkSynchronizedNodeSelectionWidget to propagate the new selection. * * @param nodes A list of data nodes that are newly selected. */ void NodeSelectionChanged(NodeList nodes); /* * @brief A signal that will be emitted by the 'ChangeSelectionMode'-slot. * This happens if the selection mode is change from outside of this class, * e.g. from a QmitkSynchronizedNodeSelectionWidget. * This signal is connected to the 'SetSelectAll'-slot of each * QmitkSynchronizedNodeSelectionWidget to propagate the selection mode. * * @param selectAll True, if the selection mode is changed to "select all" nodes. * False otherwise. */ void SelectionModeChanged(bool selectAll); public Q_SLOTS: /* * @brief Set a new internal selection and send this new selection to connected * QmitkSynchronizedNodeSelectionWidgets using the 'NodeSelectionChanged'-signal. * * This slot itself is connected to the 'CurrentSelectionChanged'-signal of each * QmitkSynchronizedNodeSelectionWidget to receive a new selection. * * @param nodes A list of data nodes that are newly selected. */ void ChangeSelection(NodeList nodes); /* * @brief Set a new selection mode and send this new selection mode to connected * QmitkSynchronizedNodeSelectionWidgets using the 'SelectionModeChanged'-signal. * * This slot itself is connected to the 'SelectionModeChanged'-signal of each * QmitkSynchronizedNodeSelectionWidget to receive a new selection mode. * * @param selectAll True, if the selection mode is changed to "select all" nodes. * False otherwise. */ void ChangeSelectionMode(bool selectAll); + /* + * @brief Decrease the internal counter of connections to keep track of how many + * QmitkSynchronizedNodeSelectionWidgets are synchronized. + * + * This slot itself is connected to the 'DeregisterSynchronization'-signal of each + * QmitkSynchronizedNodeSelectionWidget to get notified when a synchronized + * widget is deleted. + */ + void DeregisterWidget(); private: NodeList m_InternalSelection; bool m_SelectAll; + unsigned int m_ConnectionCounter; }; #endif diff --git a/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp index ac64be0664..aae35e9ac6 100644 --- a/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp @@ -1,701 +1,709 @@ /*============================================================================ 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() { + bool isSynchronized = this->IsSynchronized(); + if (isSynchronized) + { + emit DeregisterSynchronization(); + return; + } + 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 synchronized, + // 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) { - // 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); - } + // Delete the relevant renderer-specific properties for the node using the current base renderer. + mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); } } void QmitkSynchronizedNodeSelectionWidget::SetBaseRenderer(mitk::BaseRenderer* baseRenderer) { if (m_BaseRenderer == baseRenderer) { // no need to do something return; } if (nullptr == baseRenderer) { return; } auto oldBaseRenderer = m_BaseRenderer.Lock(); m_BaseRenderer = baseRenderer; auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } bool isSynchronized = this->IsSynchronized(); if (isSynchronized) { // If the model is synchronized, // all nodes use global / default properties. // No renderer-specific property lists should exist // so there is no need to transfer any property values. } else { // If the model is not synchronized, // we know that renderer-specific properties exist for all nodes. // These properties need to be removed from the nodes and // we need to transfer their values to new renderer-specific properties. auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // Set the relevant renderer-specific properties for the node using the new base renderer. // By transferring the values from the old property list, // the same property-state is kept when switching to another base renderer. mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(node, baseRenderer, oldBaseRenderer); // Delete the relevant renderer-specific properties for the node using the old base renderer. mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, oldBaseRenderer); } } this->Initialize(); } void QmitkSynchronizedNodeSelectionWidget::SetSelectAll(bool selectAll) { if (selectAll == m_Controls.selectionModeCheckBox->isChecked()) { // no need to do something return; } m_Controls.selectionModeCheckBox->setChecked(selectAll); } bool QmitkSynchronizedNodeSelectionWidget::GetSelectAll() const { return m_Controls.selectionModeCheckBox->isChecked(); } void QmitkSynchronizedNodeSelectionWidget::SetSynchronized(bool synchronize) { if (synchronize == this->IsSynchronized()) { // no need to do something return; } auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } if (synchronize) { // set the base renderer of the model to nullptr, such that global properties are used m_StorageModel->SetCurrentRenderer(nullptr); // If the model is synchronized, // we know that the model was not synchronized before. // That means that all nodes use renderer-specific properties, // but now all nodes need global properties. // Thus we need to remove the renderer-specific properties of all nodes of the // datastorage. auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // For helper / hidden nodes: // If the node predicate does not match, do not remove the renderer-specific property // This is relevant for the crosshair data nodes, which are only visible inside their // corresponding render window. if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { // Delete the relevant renderer-specific properties for the node using the current base renderer. mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); } } } else { // set the base renderer of the model to current base renderer, such that renderer-specific properties are used m_StorageModel->SetCurrentRenderer(baseRenderer); // If the model is not synchronized anymore, // we know that the model was synchronized before. // That means that all nodes use global / default properties, // but now all nodes need renderer-specific properties. // Thus we need to modify the renderer-specific properties of all nodes of the // datastorage: // - hide those nodes, which are not part of the newly selected nodes. // - keep the property values of those nodes, which are part of the new selection AND // have been selected before auto currentNodeSelection = this->GetCurrentInternalSelection(); auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // check if the node is part of the current selection auto finding = std::find(std::begin(currentNodeSelection), std::end(currentNodeSelection), node); if (finding != std::end(currentNodeSelection)) // node found / part of the current selection { // Set the relevant renderer-specific properties for the node using the curent base renderer. // By transferring the values from the global / default property list, // the same property-state is kept when switching to non-synchronized mode. mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(node, baseRenderer, nullptr); } else { // If the node is not part of the selection, unset the relevant renderer-specific properties. // This will unset the "visible" and "layer" property for the renderer-specific property list and // hide the node for this renderer. // ATTENTION: This is required, since the synchronized property needs to be overwritten // to make sure that the visibility is correctly set for the specific base renderer. this->DeselectNode(node); } } } // Since the synchronization might lead to a different node order depending on the layer properties, the render window // needs to be updated. // Explicitly request an update since a renderer-specific property change does not mark the node as modified. // see https://phabricator.mitk.org/T22322 mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); } bool QmitkSynchronizedNodeSelectionWidget::IsSynchronized() const { return m_StorageModel->GetCurrentRenderer().IsNull(); } void QmitkSynchronizedNodeSelectionWidget::OnModelUpdated() { m_Controls.tableView->resizeRowsToContents(); m_Controls.tableView->resizeColumnsToContents(); } void QmitkSynchronizedNodeSelectionWidget::OnSelectionModeChanged(bool selectAll) { emit SelectionModeChanged(selectAll); if (selectAll) { - auto dataStorage = m_DataStorage.Lock(); - if (dataStorage.IsNull()) - { - return; - } - - auto allNodes = m_NodePredicate ? dataStorage->GetSubset(m_NodePredicate) : dataStorage->GetAll(); - NodeList currentSelection; - for (auto& node : *allNodes) - { - currentSelection.append(node); - } - - this->HandleChangeOfInternalSelection(currentSelection); + this->SelectAll(); } } 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; } // For helper / hidden nodes if (m_NodePredicate.IsNotNull() && !m_NodePredicate->CheckNode(node)) { // If the node predicate does not match, do not add the node to the current selection. // Leave the visibility as it is. 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. // We want to add the incoming node to our selection, if the node is a child node // of an already selected node. // Nodes added to the selection will be made visible. if (m_Controls.selectionModeCheckBox->isChecked() || this->IsParentNodeSelected(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 function will call 'QmitkSynchronizedNodeSelectionWidget::ReviseSelectionChanged' // which will take care of the visibility-property for newly added node. this->HandleChangeOfInternalSelection(currentSelection); } else { // If the widget is in "local-selection" state (selectionModeCheckBox unchecked), // the new incoming 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 widget is not synchronized, all nodes use renderer-specific properties. // Thus we need to modify the renderer-specific properties of the 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); } bool QmitkSynchronizedNodeSelectionWidget::IsParentNodeSelected(const mitk::DataNode* dataNode) const { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return false; } auto currentSelection = this->GetCurrentInternalSelection(); auto parentNodes = dataStorage->GetSources(dataNode, m_NodePredicate, false); for (auto it = parentNodes->Begin(); it != parentNodes->End(); ++it) { const mitk::DataNode* parentNode = it->Value(); auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), parentNode); if (finding != std::end(currentSelection)) // parent node found { // at least one parent node is part of the selection return true; } } return false; } 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); } } + +void QmitkSynchronizedNodeSelectionWidget::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); +} diff --git a/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp b/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp index 8091ac673f..52949b0cea 100644 --- a/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp +++ b/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp @@ -1,101 +1,134 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // 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) + , m_ConnectionCounter(0) { } -void QmitkSynchronizedWidgetConnector::ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const +void QmitkSynchronizedWidgetConnector::ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) { 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); + + connect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::DeregisterSynchronization, + this, &QmitkSynchronizedWidgetConnector::DeregisterWidget); + + m_ConnectionCounter++; } -void QmitkSynchronizedWidgetConnector::DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const +void QmitkSynchronizedWidgetConnector::DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) { 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); + + disconnect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::DeregisterSynchronization, + this, &QmitkSynchronizedWidgetConnector::DeregisterWidget); + + this->DeregisterWidget(); } void QmitkSynchronizedWidgetConnector::SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const { - // widget is newly synchronized / connected so an initial setup needs to be made - nodeSelectionWidget->SetCurrentSelection(m_InternalSelection); + // We need to explicitly differentiate when "Select All" is active, since the internal selection + // might not contain all nodes. When no selection widget is synchronized, the m_InternalSelection + // won't be updated. + if (m_SelectAll) + { + nodeSelectionWidget->SelectAll(); + } + else + { + 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); } } + +void QmitkSynchronizedWidgetConnector::DeregisterWidget() +{ + m_ConnectionCounter--; + + // When no more widgets are synchronized anymore, turn on SelectAll to avoid losing + // nodes that are added until synchronization of a widget is turned on again. + if (m_ConnectionCounter == 0) + { + m_SelectAll = true; + } +}