File indexing completed on 2024-05-12 16:01:34
0001 /* 0002 * SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kis_node_filter_proxy_model.h" 0008 0009 #include <QSet> 0010 #include <boost/optional.hpp> 0011 0012 #include "kis_node.h" 0013 #include "kis_node_model.h" 0014 #include "kis_node_manager.h" 0015 #include "kis_signal_compressor.h" 0016 #include "kis_signal_auto_connection.h" 0017 0018 #include "kis_image.h" 0019 0020 0021 struct KisNodeFilterProxyModel::Private 0022 { 0023 Private() 0024 : nodeModel(0), 0025 activeNodeCompressor(1000, KisSignalCompressor::FIRST_INACTIVE) 0026 {} 0027 0028 KisNodeModel *nodeModel; 0029 KisNodeSP pendingActiveNode; 0030 KisNodeSP activeNode; 0031 QSet<int> acceptedColorLabels; 0032 boost::optional<QString> activeTextFilter; 0033 KisSignalCompressor activeNodeCompressor; 0034 bool isUpdatingFilter = false; 0035 KisSignalAutoConnectionsStore modelConnections; 0036 0037 bool checkIndexAllowedRecursively(QModelIndex srcIndex); 0038 }; 0039 0040 KisNodeFilterProxyModel::KisNodeFilterProxyModel(QObject *parent) 0041 : QSortFilterProxyModel(parent), 0042 m_d(new Private) 0043 { 0044 connect(&m_d->activeNodeCompressor, SIGNAL(timeout()), SLOT(slotUpdateCurrentNodeFilter()), Qt::QueuedConnection); 0045 } 0046 0047 KisNodeFilterProxyModel::~KisNodeFilterProxyModel() 0048 { 0049 } 0050 0051 void KisNodeFilterProxyModel::setNodeModel(KisNodeModel *model) 0052 { 0053 m_d->modelConnections.clear(); 0054 m_d->modelConnections.addConnection(model, SIGNAL(sigBeforeBeginRemoveRows(const QModelIndex &, int, int)), 0055 this, SLOT(slotBeforeBeginRemoveRows(const QModelIndex &, int, int))); 0056 0057 m_d->nodeModel = model; 0058 setSourceModel(model); 0059 } 0060 0061 bool KisNodeFilterProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) 0062 { 0063 if (m_d->isUpdatingFilter && role == KisNodeModel::ActiveRole) { 0064 return false; 0065 } 0066 0067 return QSortFilterProxyModel::setData(index, value, role); 0068 } 0069 0070 bool KisNodeFilterProxyModel::Private::checkIndexAllowedRecursively(QModelIndex srcIndex) 0071 { 0072 if (!srcIndex.isValid()) return false; 0073 0074 KisNodeSP node = nodeModel->nodeFromIndex(srcIndex); 0075 const bool nodeTextFilterMatch = (!activeTextFilter || node->name().contains(activeTextFilter.get(), Qt::CaseInsensitive)); 0076 0077 // directParentTextFilterMatch -- There's an argument to be made that searching for a parent name should show 0078 // all of the direct children of said text-search. For now, it will remain unused. 0079 const bool directParentTextFilterMatch = (!activeTextFilter || (node->parent() && node->parent()->name().contains(activeTextFilter.get(), Qt::CaseInsensitive))); 0080 Q_UNUSED(directParentTextFilterMatch); 0081 0082 const bool nodeColorMatch = (acceptedColorLabels.count() == 0 || acceptedColorLabels.contains(node->colorLabelIndex())); 0083 if ( node == activeNode || 0084 ( nodeColorMatch && nodeTextFilterMatch )) { 0085 return true; 0086 } 0087 0088 bool result = false; 0089 0090 const int numChildren = srcIndex.model()->rowCount(srcIndex); 0091 for (int i = 0; i < numChildren; i++) { 0092 QModelIndex child = nodeModel->index(i, 0, srcIndex); 0093 if (checkIndexAllowedRecursively(child)) { 0094 result = true; 0095 break; 0096 } 0097 } 0098 0099 return result; 0100 } 0101 0102 bool KisNodeFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 0103 { 0104 KIS_ASSERT_RECOVER(m_d->nodeModel) { return true; } 0105 0106 const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); 0107 if (!index.isValid()) return false; 0108 0109 KisNodeSP node = m_d->nodeModel->nodeFromIndex(index); 0110 0111 return !node || 0112 (m_d->acceptedColorLabels.isEmpty() && !m_d->activeTextFilter) || 0113 m_d->checkIndexAllowedRecursively(index); 0114 } 0115 0116 KisNodeSP KisNodeFilterProxyModel::nodeFromIndex(const QModelIndex &index) const 0117 { 0118 KIS_ASSERT_RECOVER(m_d->nodeModel) { return 0; } 0119 0120 QModelIndex srcIndex = mapToSource(index); 0121 return m_d->nodeModel->nodeFromIndex(srcIndex); 0122 } 0123 0124 QModelIndex KisNodeFilterProxyModel::indexFromNode(KisNodeSP node) const 0125 { 0126 KIS_ASSERT_RECOVER(m_d->nodeModel) { return QModelIndex(); } 0127 0128 QModelIndex srcIndex = m_d->nodeModel->indexFromNode(node); 0129 return mapFromSource(srcIndex); 0130 } 0131 0132 void KisNodeFilterProxyModel::setAcceptedLabels(const QSet<int> &value) 0133 { 0134 m_d->acceptedColorLabels = value; 0135 invalidateFilter(); 0136 } 0137 0138 void KisNodeFilterProxyModel::setTextFilter(const QString &text) 0139 { 0140 m_d->activeTextFilter = !text.isEmpty() ? boost::make_optional(text) : boost::none; 0141 invalidateFilter(); 0142 } 0143 0144 void KisNodeFilterProxyModel::setActiveNode(KisNodeSP node) 0145 { 0146 // NOTE: the current node might change due to beginRemoveRows, in such case 0147 // we must ensure we don't trigger recursive model invalidation. 0148 0149 // the new node may temporary become null when the last layer 0150 // of the document in removed 0151 m_d->pendingActiveNode = node; 0152 m_d->activeNodeCompressor.start(); 0153 } 0154 0155 void KisNodeFilterProxyModel::slotUpdateCurrentNodeFilter() 0156 { 0157 m_d->activeNode = m_d->pendingActiveNode; 0158 0159 /** 0160 * During the filter update the model might emit "current changed" signals, 0161 * which (in their turn) will issue setData(..., KisNodeModel::ActiveRole) 0162 * call, leading to a double recursion. Which, obviously, crashes Krita. 0163 * 0164 * Right now, just blocking the KisNodeModel::ActiveRole call is the 0165 * most obvious solution for the problem. 0166 */ 0167 m_d->isUpdatingFilter = true; 0168 invalidateFilter(); 0169 m_d->isUpdatingFilter = false; 0170 } 0171 0172 void KisNodeFilterProxyModel::slotBeforeBeginRemoveRows(const QModelIndex &parent, int start, int end) 0173 { 0174 for (int row = start; row <= end; row++) { 0175 const QModelIndex sourceIndex = sourceModel()->index(row, 0, parent); 0176 const QModelIndex mappedIndex = mapFromSource(sourceIndex); 0177 0178 if (mappedIndex.isValid()) { 0179 emit sigBeforeBeginRemoveRows(mappedIndex.parent(), mappedIndex.row(), mappedIndex.row()); 0180 } 0181 } 0182 } 0183 0184 void KisNodeFilterProxyModel::unsetDummiesFacade() 0185 { 0186 m_d->nodeModel->setDummiesFacade(0, 0, 0, 0, 0); 0187 m_d->pendingActiveNode = 0; 0188 m_d->activeNode = 0; 0189 }