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 }