<commit_before>/*
 * Turns a list model into a tree allowing nodes to be in multiple groups
 *
 * Copyright (C) 2012 <NAME> <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "abstract-grouping-proxy-model.h"

#include <QSet>
#include <QTimer>

#include <KDebug>


class KTp::AbstractGroupingProxyModel::Private
{
public:
    QAbstractItemModel *source;

    //keep a cache of what groups an item belongs to
    QHash<QPersistentModelIndex, QSet<QString> > groupCache;

    //item -> groups
    QMultiHash<QPersistentModelIndex, ProxyNode*> proxyMap;
    QHash<QString, GroupNode*> groupMap;
};

class ProxyNode : public QStandardItem
{
public:
    ProxyNode(const QPersistentModelIndex &sourceIndex);
    QVariant data(int role) const;
    void changed(); //expose protected method in QStandardItem
    QString group() const;
private:
    const QPersistentModelIndex m_sourceIndex;
};

class GroupNode : public QStandardItem {
public:
    GroupNode(const QString &groupId);
    QString group() const;
    virtual QVariant data(int role) const;
    bool forced() const;
    void changed(); //expose protected method in QStandardItem
    void setForced(bool forced);
private:
    const QString m_groupId;
    bool m_forced;
};


ProxyNode::ProxyNode(const QPersistentModelIndex &sourceIndex):
    QStandardItem(),
    m_sourceIndex(sourceIndex)
{
}

QVariant ProxyNode::data(int role) const
{
    return m_sourceIndex.data(role);
}

void ProxyNode::changed()
{
    QStandardItem::emitDataChanged();
}

QString ProxyNode::group() const
{
    //FIXME is this a hack?
    GroupNode *groupNode = static_cast<GroupNode*>(parent());
    if (groupNode) {
        return groupNode->group();
    }
    return QString();
}



GroupNode::GroupNode(const QString &groupId):
    QStandardItem(),
    m_groupId(groupId),
    m_forced(false)
{
}

QString GroupNode::group() const
{
    return m_groupId;
}

QVariant GroupNode::data(int role) const
{
    KTp::AbstractGroupingProxyModel *proxyModel = qobject_cast<KTp::AbstractGroupingProxyModel*>(model());
    Q_ASSERT(proxyModel);
    return proxyModel->dataForGroup(m_groupId, role);
}

void GroupNode::setForced(bool forced)
{
    m_forced = forced;
}

bool GroupNode::forced() const
{
    return m_forced;
}

void GroupNode::changed()
{
    QStandardItem::emitDataChanged();
}


KTp::AbstractGroupingProxyModel::AbstractGroupingProxyModel(QAbstractItemModel *source):
    QStandardItemModel(source),
    d(new KTp::AbstractGroupingProxyModel::Private())
{
    d->source = source;
    setRoleNames(source->roleNames());

    //we have to process all existing rows in the model, but must never call a virtual method from a constructor as it will crash.
    //we use a single shot timer to get round this
    QTimer::singleShot(0, this, SLOT(onLoad()));
}

KTp::AbstractGroupingProxyModel::~AbstractGroupingProxyModel()
{
    delete d;
}


void KTp::AbstractGroupingProxyModel::forceGroup(const QString &group)
{
    GroupNode* groupNode = itemForGroup(group);
    groupNode->setForced(true);
}

void KTp::AbstractGroupingProxyModel::unforceGroup(const QString &group)
{
    GroupNode* groupNode = d->groupMap[group];
    if (!groupNode) {
        return;
    }

    //mark that this group can be removed when it's empty
    groupNode->setForced(false);

    //if group is already empty remove it
    if (groupNode->rowCount() == 0) {
        takeRow(groupNode->row());
        d->groupMap.remove(groupNode->group());
    }
}

void KTp::AbstractGroupingProxyModel::groupChanged(const QString &group)
{
    GroupNode *node = d->groupMap[group];
    if (node) {
        node->changed();
    }
}


/* Called when source items inserts a row
 *
 * For each new row, create a proxyNode.
 * If it's at the top level add it to the relevant group nodes.
 * Otherwise add it to the relevant proxy nodes in our model
 */

void KTp::AbstractGroupingProxyModel::onRowsInserted(const QModelIndex &sourceParent, int start, int end)
{
    //if top level in root model
    if (!sourceParent.isValid()) {
        for (int i = start; i <= end; i++) {
            QModelIndex index = d->source->index(i, 0, sourceParent);
            Q_FOREACH(const QString &group, groupsForIndex(index)) {
                addProxyNode(index, itemForGroup(group));
            }
        }
    } else {
        for (int i = start; i <= end; i++) {
            QModelIndex index = d->source->index(i, 0, sourceParent);
            QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.find(sourceParent);
            while (it != d->proxyMap.end()  && it.key() == sourceParent) {
                addProxyNode(index, it.value());
                it++;
            }
        }
    }
}

void KTp::AbstractGroupingProxyModel::addProxyNode(const QModelIndex &sourceIndex, QStandardItem *parent)
{
    ProxyNode *proxyNode = new ProxyNode(sourceIndex);
    d->proxyMap.insertMulti(sourceIndex, proxyNode);
    parent->appendRow(proxyNode);

    //add proxy nodes for all children of this sourceIndex
    for (int i=0; i < d->source->rowCount(sourceIndex); i++) {
        addProxyNode(sourceIndex.child(i,0), proxyNode);
    }

}

void KTp::AbstractGroupingProxyModel::removeProxyNodes(const QModelIndex &sourceIndex, const QList<ProxyNode *> &removedItems)
{
    Q_FOREACH(ProxyNode *proxy, removedItems) {
        QStandardItem *parentItem = proxy->parent();
        parentItem->removeRow(proxy->row());
        d->proxyMap.remove(sourceIndex, proxy);

        //if the parent item to this proxy node is now empty, and is a top level item
        if (parentItem->rowCount() == 0 && parentItem->parent() == 0 ) {
            GroupNode* groupNode = dynamic_cast<GroupNode*>(parentItem);

            //do not delete forced groups
            if (groupNode->forced() == false) {
                takeRow(groupNode->row());
                d->groupMap.remove(groupNode->group());
            }
        }
    }
}

/*
 * Called when a row is remove from the source model model
 * Find all existing proxy models and delete thems
*/
void KTp::AbstractGroupingProxyModel::onRowsRemoved(const QModelIndex &sourceParent, int start, int end)
{
    for (int i = start; i<=end; i++) {
        QPersistentModelIndex index = d->source->index(i, 0, sourceParent);
        QList<ProxyNode *> itemsToRemove;

        QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.find(index);
        while (it != d->proxyMap.end()  && it.key() == index) {
            kDebug() << "removing row" << index.data();<commit_msg>Fix crash in AbstractGroupingModel when used in a tree<commit_after>/*
 * Turns a list model into a tree allowing nodes to be in multiple groups
 *
 * Copyright (C) 2012 <NAME> <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "abstract-grouping-proxy-model.h"

#include <QSet>
#include <QTimer>

#include <KDebug>


class KTp::AbstractGroupingProxyModel::Private
{
public:
    QAbstractItemModel *source;

    //keep a cache of what groups an item belongs to
    QHash<QPersistentModelIndex, QSet<QString> > groupCache;

    //item -> groups
    QMultiHash<QPersistentModelIndex, ProxyNode*> proxyMap;
    QHash<QString, GroupNode*> groupMap;
};

class ProxyNode : public QStandardItem
{
public:
    ProxyNode(const QPersistentModelIndex &sourceIndex);
    QVariant data(int role) const;
    void changed(); //expose protected method in QStandardItem
    QString group() const;
private:
    const QPersistentModelIndex m_sourceIndex;
};

class GroupNode : public QStandardItem {
public:
    GroupNode(const QString &groupId);
    QString group() const;
    virtual QVariant data(int role) const;
    bool forced() const;
    void changed(); //expose protected method in QStandardItem
    void setForced(bool forced);
private:
    const QString m_groupId;
    bool m_forced;
};


ProxyNode::ProxyNode(const QPersistentModelIndex &sourceIndex):
    QStandardItem(),
    m_sourceIndex(sourceIndex)
{
}

QVariant ProxyNode::data(int role) const
{
    return m_sourceIndex.data(role);
}

void ProxyNode::changed()
{
    QStandardItem::emitDataChanged();
}

QString ProxyNode::group() const
{
    //FIXME is this a hack?
    GroupNode *groupNode = static_cast<GroupNode*>(parent());
    if (groupNode) {
        return groupNode->group();
    }
    return QString();
}



GroupNode::GroupNode(const QString &groupId):
    QStandardItem(),
    m_groupId(groupId),
    m_forced(false)
{
}

QString GroupNode::group() const
{
    return m_groupId;
}

QVariant GroupNode::data(int role) const
{
    KTp::AbstractGroupingProxyModel *proxyModel = qobject_cast<KTp::AbstractGroupingProxyModel*>(model());
    Q_ASSERT(proxyModel);
    return proxyModel->dataForGroup(m_groupId, role);
}

void GroupNode::setForced(bool forced)
{
    m_forced = forced;
}

bool GroupNode::forced() const
{
    return m_forced;
}

void GroupNode::changed()
{
    QStandardItem::emitDataChanged();
}


KTp::AbstractGroupingProxyModel::AbstractGroupingProxyModel(QAbstractItemModel *source):
    QStandardItemModel(source),
    d(new KTp::AbstractGroupingProxyModel::Private())
{
    d->source = source;
    setRoleNames(source->roleNames());

    //we have to process all existing rows in the model, but must never call a virtual method from a constructor as it will crash.
    //we use a single shot timer to get round this
    QTimer::singleShot(0, this, SLOT(onLoad()));
}

KTp::AbstractGroupingProxyModel::~AbstractGroupingProxyModel()
{
    delete d;
}


void KTp::AbstractGroupingProxyModel::forceGroup(const QString &group)
{
    GroupNode* groupNode = itemForGroup(group);
    groupNode->setForced(true);
}

void KTp::AbstractGroupingProxyModel::unforceGroup(const QString &group)
{
    GroupNode* groupNode = d->groupMap[group];
    if (!groupNode) {
        return;
    }

    //mark that this group can be removed when it's empty
    groupNode->setForced(false);

    //if group is already empty remove it
    if (groupNode->rowCount() == 0) {
        takeRow(groupNode->row());
        d->groupMap.remove(groupNode->group());
    }
}

void KTp::AbstractGroupingProxyModel::groupChanged(const QString &group)
{
    GroupNode *node = d->groupMap[group];
    if (node) {
        node->changed();
    }
}


/* Called when source items inserts a row
 *
 * For each new row, create a proxyNode.
 * If it's at the top level add it to the relevant group nodes.
 * Otherwise add it to the relevant proxy nodes in our model
 */

void KTp::AbstractGroupingProxyModel::onRowsInserted(const QModelIndex &sourceParent, int start, int end)
{
    //if top level in root model
    if (!sourceParent.isValid()) {
        for (int i = start; i <= end; i++) {
            QModelIndex index = d->source->index(i, 0, sourceParent);
            Q_FOREACH(const QString &group, groupsForIndex(index)) {
                addProxyNode(index, itemForGroup(group));
            }
        }
    } else {
        for (int i = start; i <= end; i++) {
            QModelIndex index = d->source->index(i, 0, sourceParent);
            QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.find(sourceParent);
            while (it != d->proxyMap.end()  && it.key() == sourceParent) {
                addProxyNode(index, it.value());
                it++;
            }
        }
    }
}

void KTp::AbstractGroupingProxyModel::addProxyNode(const QModelIndex &sourceIndex, QStandardItem *parent)
{
    ProxyNode *proxyNode = new ProxyNode(sourceIndex);
    d->proxyMap.insertMulti(sourceIndex, proxyNode);
    parent->appendRow(proxyNode);

    //add proxy nodes for all children of this sourceIndex
    for (int i=0; i < d->source->rowCount(sourceIndex); i++) {
        addProxyNode(sourceIndex.child(i,0), proxyNode);
    }

}

void KTp::AbstractGroupingProxyModel::removeProxyNodes(const QModelIndex &sourceIndex, const QList<ProxyNode *> &removedItems)
{
    Q_FOREACH(ProxyNode *proxy, removedItems) {
        QStandardItem *parentItem = proxy->parent();

        //also remove child items of this proxy node from the proxy map
        for (int i = 0 ; i < d->source->rowCount(sourceIndex) ; i++) {
            //we always remove child(0) because we're deleting the first child each time
            removeProxyNodes(sourceIndex.child(i,0), QList<ProxyNode*>() << dynamic_cast<ProxyNode*>(proxy->child(0)));
        }

        parentItem->removeRow(proxy->row());
        d->proxyMap.remove(sourceIndex, proxy);

        //if the parent item to this proxy node is now empty, and is a top level item
        if (parentItem->rowCount() == 0 && parentItem->parent() == 0 ) {
            GroupNode* groupNode = dynamic_cast<GroupNode*>(parentItem);

            //do not delete forced groups
            if (groupNode->forced() == false) {
                takeRow(groupNode->row());
                d->groupMap.remove(groupNode->group());
            }
        }
    }
}

/*
 * Called when a row is remove from the source model model
 * Find all existing proxy models and delete thems
*/
void KTp::AbstractGroupingProxyModel::onRowsRemoved(const QModelIndex &sourceParent, int start, int end)
{
    for (int i = start; i<=end; i++) {
        QPersistentModelIndex index = d->source->index(i, 0, sourceParent);
        QList<ProxyNode *> itemsToRemove;

        QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.find(index);
        while (it != d->proxyMap.end()  && it.key() == index) {
            kDebug() << "removing row" << index.data();
{'fraction_non_alphanumeric': 0.11412641902330893, 'fraction_numerical': 0.003761671256801236, 'mean_word_length': 3.468187274909964, 'pattern_counts': {'":': 0, '<': 58, '<?xml version=': 0, '>': 118, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6429190', 'n_tokens_mistral': 4473, 'n_tokens_neox': 4343, 'n_words': 1295}