diff --git a/Modules/QtWidgets/include/QmitkDataStorageAbstractView.h b/Modules/QtWidgets/include/QmitkDataStorageAbstractView.h index e1da6da984..ebbdaa1304 100644 --- a/Modules/QtWidgets/include/QmitkDataStorageAbstractView.h +++ b/Modules/QtWidgets/include/QmitkDataStorageAbstractView.h @@ -1,190 +1,192 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QMITKDATASTORAGEABSTRACTVIEW_H #define QMITKDATASTORAGEABSTRACTVIEW_H #include // mitk core #include #include -// qt widgets module +// qtwidgets module #include // qt #include #include /* * @brief This abstract widget provides base functionality for each concrete data storage viewer widget. * * It sets the viewer widget as a node event listener of the data storage. It accepts a node predicate * and forwards this filter to the 'QmitkIDataStorageViewModel', the actual model of the data storage viewer. * * The 'QmitkDataStorageAbstractView' offers a public slot and signal that can be used to set / propagate the selected * nodes in the current view: * The 'SetCurrentSelection'-slot finds the model indices of the given selected nodes and changes the selection of the * internal view's selection model accordingly. * The 'CurrentSelectionChanged'-signal sends a list of selected nodes to it's environment. * The 'CurrentSelectionChanged'-signal is emitted by the 'ModelSelectionChanged'-function, which transforms a model * selection into a data node list. The 'ModelSelectionChanged'-function is called when the selection of view's * selection model changes. * * The 'QmitkDataStorageAbstractView' provides three empty functions, 'NodeAdded', 'NodeChanged' and 'NodeRemoved', that * may be implemented by the subclasses. These functions allow to react to the 'AddNodeEvent', 'ChangedNodeEvent' and * 'RemoveNodeEvent' of the data storage. This might be useful to force an update on the custom view to correctly * represent the content of the data storage. */ class MITKQTWIDGETS_EXPORT QmitkDataStorageAbstractView : public QWidget { Q_OBJECT public: virtual ~QmitkDataStorageAbstractView() = 0; /* * @brief Sets the data storage and adds listener for node events. * * @par dataStorage A pointer to the data storage to set. */ void SetDataStorage(mitk::DataStorage* dataStorage); /* * @brief Sets the node predicate and updates the model data, according to the node predicate. * * @par nodePredicate A pointer to node predicate. */ void SetNodePredicate(mitk::NodePredicateBase* nodePredicate); /* * @brief Set the widget to change the current selection according to the filter. * * If true, an incoming selection will be filtered (reduced) to only those nodes that are visible by the current view. * An outgoing selection can then at most contain the filtered nodes. * If false, the incoming non-visible selection will be stored and later added to the outgoing selection, * to include the original selection that could not be modified. * The part of the original selection, that is non-visible are the nodes that are not * * @par selectOnlyVisibleNodes The bool value to define the selection modus. */ void SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes); Q_SIGNALS: /* * @brief A base signal that can be emitted in any subclass of this widget * * @par nodes A list of data nodes that are newly selected. */ void CurrentSelectionChanged(QList nodes); public Q_SLOTS: /* - * @brief Transform a data node list into a model selection and set this as a new selection of the selection model of - * the private member view. + * @brief Transform a list of data nodes into a model selection and set this as a new selection of the + * selection model of the private member 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(QList selectedNodes); private Q_SLOTS: /* * @brief Transform a model selection into a data node list and emit the - * "CurrentSelectionChanged(QList)"-signal. + * "CurrentSelectionChanged"-signal. * * The function adds the selected nodes from the original selection that could not be modified, if * 'm_SelectOnlyVisibleNodes' is false. * This slot is internally connected to the 'selectionChanged'-signal of the selection model of the private member view. * * @par selected The newly selected items. * @par deselected The newly deselected items. */ - void ModelSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void ChangeModelSelection(const QItemSelection& selected, const QItemSelection& deselected); protected: /* * @brief Set the model that should be used for the view in this widget. * * This model must return mitk::DataNode::Pointer objects for model indexes * when the role is QmitkDataNodeRole. * This model must return mitk::DataNode* objects for model indexes * when the role is QmitkDataNodeRawPointerRole. * * @par model The model to set. */ void SetModel(QmitkIDataStorageViewModel* model); /* * @brief Set the view that is included in this widget. * * @par view The view to set. */ void SetView(QAbstractItemView* view); QmitkDataStorageAbstractView(QWidget* parent = nullptr); QmitkDataStorageAbstractView(mitk::DataStorage* dataStorage, QWidget* parent = nullptr); mitk::DataStorage* m_DataStorage; mitk::NodePredicateBase* m_NodePredicate; bool m_SelectOnlyVisibleNodes; QList m_NonVisibleSelection; QmitkIDataStorageViewModel* m_Model; QAbstractItemView* m_View; private: /* * @brief Resets the model member after the data storage has been set /changed. */ void DataStorageChanged(); /* * @brief Update the model data after the node predicate has been set / changed. */ void NodePredicateChanged(); /* * @brief Callback for the data storage node added event. May be reimplemented * by subclasses. * * @par node The data node that was added. */ virtual void NodeAdded(const mitk::DataNode* node); /* * @brief Callback for the data storage node changed event. May be reimplemented * by subclasses. * * @par node The data node that was changed. */ virtual void NodeChanged(const mitk::DataNode* node); /* * @brief Callback for the data storage node removed event. May be reimplemented * by subclasses. * * @par node The data node that was removed. */ virtual void NodeRemoved(const mitk::DataNode* node); QList GetSelectedNodes() const; QList FilterNodeList(const QList& nodes) const; + bool IsEqualToCurrentSelection(QList& selectedNodes); + }; #endif // QMITKDATASTORAGEABSTRACTVIEW_H diff --git a/Modules/QtWidgets/src/QmitkDataStorageAbstractView.cpp b/Modules/QtWidgets/src/QmitkDataStorageAbstractView.cpp index e9b2daaaee..b4201d3585 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageAbstractView.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageAbstractView.cpp @@ -1,254 +1,262 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkDataStorageAbstractView.h" -// qtwidgets +// qtwidgets module #include "QmitkCustomVariants.h" #include "QmitkEnums.h" QmitkDataStorageAbstractView::QmitkDataStorageAbstractView(QWidget* parent/* = nullptr*/) : QWidget(parent) , m_DataStorage(nullptr) , m_NodePredicate(nullptr) , m_SelectOnlyVisibleNodes(false) , m_Model(nullptr) , m_View(nullptr) { // nothing here } QmitkDataStorageAbstractView::QmitkDataStorageAbstractView(mitk::DataStorage* dataStorage, QWidget* parent/* = nullptr*/) : QWidget(parent) , m_DataStorage(nullptr) , m_NodePredicate(nullptr) , m_SelectOnlyVisibleNodes(false) , m_Model(nullptr) , m_View(nullptr) { SetDataStorage(dataStorage); } QmitkDataStorageAbstractView::~QmitkDataStorageAbstractView() { if (nullptr != m_DataStorage) { // remove listener from old data storage m_DataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageAbstractView::NodeAdded)); m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageAbstractView::NodeRemoved)); m_DataStorage->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageAbstractView::NodeChanged)); } } void QmitkDataStorageAbstractView::SetDataStorage(mitk::DataStorage* dataStorage) { if (m_DataStorage == dataStorage) { return; } if (nullptr != m_DataStorage) { // remove listener from old data storage m_DataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageAbstractView::NodeAdded)); m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageAbstractView::NodeRemoved)); m_DataStorage->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageAbstractView::NodeChanged)); } m_DataStorage = dataStorage; if (nullptr != dataStorage) { // add listener for new data storage m_DataStorage->AddNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkDataStorageAbstractView::NodeAdded)); m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkDataStorageAbstractView::NodeRemoved)); m_DataStorage->ChangedNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkDataStorageAbstractView::NodeChanged)); } // update model if the data storage has been changed DataStorageChanged(); } void QmitkDataStorageAbstractView::DataStorageChanged() { if (nullptr == m_Model) { return; } m_Model->SetDataStorage(m_DataStorage); } void QmitkDataStorageAbstractView::SetNodePredicate(mitk::NodePredicateBase* nodePredicate) { if (m_NodePredicate == nodePredicate) { return; } m_NodePredicate = nodePredicate; // update model if the node predicate has been changed NodePredicateChanged(); } void QmitkDataStorageAbstractView::NodePredicateChanged() { m_Model->SetNodePredicate(m_NodePredicate); } void QmitkDataStorageAbstractView::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) { m_SelectOnlyVisibleNodes = selectOnlyVisibleNodes; } void QmitkDataStorageAbstractView::SetCurrentSelection(QList selectedNodes) { // filter input nodes and return the modified input node list QList filteredNodes = FilterNodeList(selectedNodes); - // get the currently selected nodes from the model - QList currentlySelectedNodes = GetSelectedNodes(); - // compare currently selected nodes with filtered input node list - if (currentlySelectedNodes.size() == filteredNodes.size()) + bool equal = IsEqualToCurrentSelection(filteredNodes); + if (equal) { - // lambda to compare node pointer inside both lists - auto lambda = [](mitk::DataNode::Pointer lhs, mitk::DataNode::Pointer rhs) { return lhs == rhs; }; - bool equal = std::equal(filteredNodes.begin(), filteredNodes.end(), currentlySelectedNodes.begin(), currentlySelectedNodes.end(), lambda); - if (equal) - { - // node lists are equal, no need to update the selection model - return; - } + return; } if (!m_SelectOnlyVisibleNodes) { // store the unmodified selection m_NonVisibleSelection = selectedNodes; // remove the nodes in the original selection that are already contained in the filtered input node list // this will keep the selection of the original nodes that are not presented by the current view unmodified, but allows to change the selection of the filtered nodes // later, the nodes of the 'm_NonVisibleSelection' member have to be added again to the list of selected (filtered) nodes, if a selection is sent from the current view (see 'ModelSelectionChanged') auto lambda = [&filteredNodes](mitk::DataNode::Pointer original) { return filteredNodes.contains(original); }; m_NonVisibleSelection.erase(std::remove_if(m_NonVisibleSelection.begin(), m_NonVisibleSelection.end(), lambda), m_NonVisibleSelection.end()); } // create new selection by retrieving the corresponding indices of the (filtered) nodes QItemSelection newCurrentSelection; for (const auto& node : filteredNodes) { QModelIndexList matched = m_Model->match(m_Model->index(0, 0), QmitkDataNodeRole, QVariant::fromValue(node), 1, Qt::MatchRecursive); if (!matched.empty()) { newCurrentSelection.select(matched.front(), matched.front()); } } m_View->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect); } -void QmitkDataStorageAbstractView::ModelSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) +void QmitkDataStorageAbstractView::ChangeModelSelection(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { QList nodes = GetSelectedNodes(); if (!m_SelectOnlyVisibleNodes) { // add the non-visible nodes from the original selection nodes.append(m_NonVisibleSelection); } emit CurrentSelectionChanged(nodes); } void QmitkDataStorageAbstractView::SetModel(QmitkIDataStorageViewModel* model) { m_Model = model; - DataStorageChanged(); + m_Model->SetDataStorage(m_DataStorage); + m_Model->SetNodePredicate(m_NodePredicate); } void QmitkDataStorageAbstractView::SetView(QAbstractItemView* view) { if (nullptr != m_View) { - disconnect(m_View->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(ModelSelectionChanged(const QItemSelection&, const QItemSelection&))); + disconnect(m_View->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(ChangeModelSelection(const QItemSelection&, const QItemSelection&))); } m_View = view; - connect(m_View->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(ModelSelectionChanged(const QItemSelection&, const QItemSelection&))); + connect(m_View->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(ChangeModelSelection(const QItemSelection&, const QItemSelection&))); } void QmitkDataStorageAbstractView::NodeAdded(const mitk::DataNode* node) { // nothing here; can be overwritten in subclass } void QmitkDataStorageAbstractView::NodeChanged(const mitk::DataNode* node) { // nothing here; can be overwritten in subclass } void QmitkDataStorageAbstractView::NodeRemoved(const mitk::DataNode* node) { // nothing here; can be overwritten in subclass } QList QmitkDataStorageAbstractView::GetSelectedNodes() const { QList nodes; QModelIndexList selectedIndexes = m_View->selectionModel()->selectedIndexes(); for (const auto& index : selectedIndexes) { QVariant qvariantDataNode = m_Model->data(index, QmitkDataNodeRole); if (qvariantDataNode.canConvert()) { nodes.push_back(qvariantDataNode.value()); } } return nodes; } QList QmitkDataStorageAbstractView::FilterNodeList(const QList& nodes) const { if (nodes.isEmpty()) { return QList(); } if (nullptr == m_NodePredicate) { // no filter set return nodes; } QList result; for (const auto& node : nodes) { if (true == m_NodePredicate->CheckNode(node)) { result.push_back(node); } } return result; } + +bool QmitkDataStorageAbstractView::IsEqualToCurrentSelection(QList& selectedNodes) +{ + // get the currently selected nodes from the model + QList currentlySelectedNodes = GetSelectedNodes(); + + if (currentlySelectedNodes.size() == selectedNodes.size()) + { + // lambda to compare node pointer inside both lists + auto lambda = [](mitk::DataNode::Pointer lhs, mitk::DataNode::Pointer rhs) { return lhs == rhs; }; + //bool equal = std::equal(filteredNodes.begin(), filteredNodes.end(), currentlySelectedNodes.begin(), currentlySelectedNodes.end(), lambda); + return std::is_permutation(selectedNodes.begin(), selectedNodes.end(), currentlySelectedNodes.begin(), currentlySelectedNodes.end(), lambda); + } + + return false; +}