diff --git a/Modules/QtWidgets/files.cmake b/Modules/QtWidgets/files.cmake index fd248df801..fdd28ea7bd 100644 --- a/Modules/QtWidgets/files.cmake +++ b/Modules/QtWidgets/files.cmake @@ -1,83 +1,86 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES QmitkApplicationCursor.cpp +QmitkDataStorageAbstractView.cpp QmitkDataStorageComboBox.cpp QmitkDataStorageListModel.cpp QmitkDataStorageTableModel.cpp QmitkDataStorageTreeModel.cpp QmitkFileReaderOptionsDialog.cpp QmitkFileReaderWriterOptionsWidget.cpp QmitkFileWriterOptionsDialog.cpp QmitkIOUtil.cpp QmitkLevelWindowPresetDefinitionDialog.cpp QmitkLevelWindowRangeChangeDialog.cpp QmitkLevelWindowWidgetContextMenu.cpp QmitkLevelWindowWidget.cpp QmitkLineEditLevelWindowWidget.cpp QmitkMemoryUsageIndicatorView.cpp QmitkMimeTypes.cpp QmitkNodeDescriptor.cpp QmitkNodeDescriptorManager.cpp QmitkRenderWindowMenu.cpp QmitkProgressBar.cpp QmitkPropertiesTableEditor.cpp QmitkPropertiesTableModel.cpp QmitkPropertyDelegate.cpp QmitkRegisterClasses.cpp QmitkRenderingManager.cpp QmitkRenderingManagerFactory.cpp QmitkRenderWindow.cpp QmitkServiceListWidget.cpp QmitkSliderLevelWindowWidget.cpp QmitkStdMultiWidget.cpp QmitkMouseModeSwitcher.cpp QmitkDataStorageFilterProxyModel.cpp QmitkDataStorageComboBoxWithSelectNone.cpp QmitkPropertyItem.cpp QmitkPropertyItemDelegate.cpp QmitkPropertyItemModel.cpp ) set(MOC_H_FILES + include/QmitkIDataStorageViewModel.h + include/QmitkDataStorageAbstractView.h include/QmitkDataStorageComboBox.h include/QmitkDataStorageTableModel.h include/QmitkFileReaderOptionsDialog.h include/QmitkFileReaderWriterOptionsWidget.h include/QmitkFileWriterOptionsDialog.h include/QmitkLevelWindowPresetDefinitionDialog.h include/QmitkLevelWindowRangeChangeDialog.h include/QmitkLevelWindowWidgetContextMenu.h include/QmitkLevelWindowWidget.h include/QmitkLineEditLevelWindowWidget.h include/QmitkMemoryUsageIndicatorView.h include/QmitkNodeDescriptor.h include/QmitkNodeDescriptorManager.h include/QmitkRenderWindowMenu.h include/QmitkProgressBar.h include/QmitkPropertiesTableEditor.h include/QmitkPropertyDelegate.h include/QmitkRenderingManager.h include/QmitkRenderWindow.h include/QmitkServiceListWidget.h include/QmitkSliderLevelWindowWidget.h include/QmitkStdMultiWidget.h include/QmitkMouseModeSwitcher.h include/QmitkDataStorageComboBoxWithSelectNone.h include/QmitkPropertyItemDelegate.h include/QmitkPropertyItemModel.h ) set(UI_FILES src/QmitkFileReaderOptionsDialog.ui src/QmitkFileWriterOptionsDialog.ui src/QmitkLevelWindowPresetDefinition.ui src/QmitkLevelWindowWidget.ui src/QmitkLevelWindowRangeChange.ui src/QmitkMemoryUsageIndicator.ui src/QmitkServiceListWidgetControls.ui ) set(QRC_FILES resource/Qmitk.qrc ) diff --git a/Modules/QtWidgets/include/QmitkDataStorageAbstractView.h b/Modules/QtWidgets/include/QmitkDataStorageAbstractView.h new file mode 100644 index 0000000000..188cef44b1 --- /dev/null +++ b/Modules/QtWidgets/include/QmitkDataStorageAbstractView.h @@ -0,0 +1,190 @@ +/*=================================================================== + +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 + +// qtwidgets +#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::Pointer 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. + * + * 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. + * + * 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); + +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::Pointer 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; + +}; + +#endif // QMITKDATASTORAGEABSTRACTVIEW_H diff --git a/Modules/QtWidgets/include/QmitkIDataStorageViewModel.h b/Modules/QtWidgets/include/QmitkIDataStorageViewModel.h new file mode 100644 index 0000000000..164e1fa512 --- /dev/null +++ b/Modules/QtWidgets/include/QmitkIDataStorageViewModel.h @@ -0,0 +1,62 @@ +/*=================================================================== + +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 QMITKIDATASTORAGEVIEWMODEL_H +#define QMITKIDATASTORAGEVIEWMODEL_H + +#include + +// mitk core +#include +#include + +// qt +#include + +/* +* @brief This interface extends the 'QAbstractItemModel' to accept an 'mitk::DataStorage' and a 'mitk::NodePredicateBase'. +* +* The interface is used inside the 'QmitkDataStorageAbstractView' and its concrete implementations to store the +* temporarily shown data nodes of the data storage. These nodes may be a subset of all the nodes inside the data +* storage, if a specific node predicate is set. +* +* A model that implements this interface has to return mitk::DataNode::Pointer objects for model indices when the +* role is QmitkDataNodeRole. +*/ +class MITKQTWIDGETS_EXPORT QmitkIDataStorageViewModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + /* + * @brief Sets the data storage. + * + * This function is called inside the "QmitkDataStorageAbstractView" to propagate a new data storage to the model. + * + * @par dataStorage A pointer to the data storage to set. + */ + virtual void SetDataStorage(mitk::DataStorage* dataStorage) = 0; + /* + * @brief Sets the node predicate and updates the model data, according to the node predicate. + * + * This function is called inside the "QmitkDataStorageAbstractView" to propagate a new node predicate to the model. + * + * @par nodePredicate A pointer to node predicate. + */ + virtual void SetNodePredicate(mitk::NodePredicateBase::Pointer nodePredicate) = 0; +}; + +#endif // QMITKIDATASTORAGEVIEWMODEL_H diff --git a/Modules/QtWidgets/src/QmitkDataStorageAbstractView.cpp b/Modules/QtWidgets/src/QmitkDataStorageAbstractView.cpp new file mode 100644 index 0000000000..dcb39d87d4 --- /dev/null +++ b/Modules/QtWidgets/src/QmitkDataStorageAbstractView.cpp @@ -0,0 +1,244 @@ +/*=================================================================== + +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 +#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 || nullptr == 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; + + // 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() +{ + m_Model->SetDataStorage(m_DataStorage); +} + +void QmitkDataStorageAbstractView::SetNodePredicate(mitk::NodePredicateBase::Pointer 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()) + { + // 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; + } + } + + 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)); + } + + // 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), QmitkDataNodeRawPointerRole, QVariant::fromValue(node), 1, Qt::MatchRecursive); + if (!matched.empty()) + { + newCurrentSelection.select(matched.front(), matched.front()); + } + } + + m_View->selectionModel()->select(newCurrentSelection, QItemSelectionModel::Select); +} + +void QmitkDataStorageAbstractView::ModelSelectionChanged(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; +} + +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&))); + } + + m_View = view; + connect(m_View->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(ModelSelectionChanged(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 (m_NodePredicate.IsNull()) + { + // no filter set + return nodes; + } + + QList result; + for (const auto& node : nodes) + { + if (true == m_NodePredicate->CheckNode(node)) + { + result.push_back(node); + } + } + + return result; +}