Branch 'kolab/integration/4.13.0' - korganizer/akonadicollectionview.cpp korganizer/views

Sandro Knauß knauss at kolabsys.com
Thu Oct 2 20:00:14 CEST 2014


 korganizer/akonadicollectionview.cpp                           |   10 
 korganizer/views/collectionview/controller.cpp                 |  276 +++++++++-
 korganizer/views/collectionview/controller.h                   |   28 -
 korganizer/views/collectionview/reparentingmodel.cpp           |   34 +
 korganizer/views/collectionview/reparentingmodel.h             |    2 
 korganizer/views/collectionview/tests/reparentingmodeltest.cpp |   50 +
 6 files changed, 367 insertions(+), 33 deletions(-)

New commits:
commit bf819e0eb26fba226b79fbcb0f38a0f977b4210c
Author: Sandro Knauß <knauss at kolabsys.com>
Date:   Thu Oct 2 19:03:26 2014 +0200

    Implement ldap search in calendar selection
    
    KOLAB: #3543

diff --git a/korganizer/akonadicollectionview.cpp b/korganizer/akonadicollectionview.cpp
index 3b0a808..c562255 100644
--- a/korganizer/akonadicollectionview.cpp
+++ b/korganizer/akonadicollectionview.cpp
@@ -581,6 +581,16 @@ AkonadiCollectionView::AkonadiCollectionView( CalendarView *view, bool hasContex
 
   topLayout->addWidget( mStackedWidget );
 
+  KMessageWidget *msgWidget = new KMessageWidget(this);
+  msgWidget->setCloseButtonVisible(false);
+  msgWidget->setMessageType(KMessageWidget::Positive);
+  msgWidget->setObjectName(QLatin1String("msgwidget"));
+  msgWidget->setVisible(false);
+  msgWidget->setText(i18n("searching..."));
+  connect(mController, SIGNAL(searching(bool)),
+          msgWidget, SLOT(setVisible(bool)));
+  topLayout->addWidget(msgWidget);
+
   connect( mBaseModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
            this, SLOT(rowsInserted(QModelIndex,int,int)) );
 
diff --git a/korganizer/views/collectionview/controller.cpp b/korganizer/views/collectionview/controller.cpp
index 03f8949..8f7808c 100644
--- a/korganizer/views/collectionview/controller.cpp
+++ b/korganizer/views/collectionview/controller.cpp
@@ -28,6 +28,7 @@
 #include <Akonadi/CollectionFetchScope>
 #include <Akonadi/AttributeFactory>
 #include <KIcon>
+#include <KLocale>
 #include <KCalCore/Event>
 #include <KCalCore/Journal>
 #include <KCalCore/Todo>
@@ -80,7 +81,7 @@ QVariant CollectionNode::data(int role) const
         return mCheckState;
     }
     if (role == Qt::ToolTipRole) {
-        return QString(QLatin1String("Collection: ") + mCollection.name() + QString::number(mCollection.id()));
+        return QString(i18n("Collection: ") + mCollection.name() + QLatin1String(" ") + QString::number(mCollection.id()));
     }
     if (role == IsSearchResultRole) {
         return isSearchNode;
@@ -128,7 +129,7 @@ bool PersonNode::operator==(const Node &node) const
 {
     const PersonNode *personNode = dynamic_cast<const PersonNode*>(&node);
     if (personNode) {
-        return (personNode->mPerson.name == mPerson.name);
+        return (personNode->mPerson.uid == mPerson.uid);
     }
     return false;
 }
@@ -145,7 +146,11 @@ void PersonNode::setChecked(bool enabled)
 QVariant PersonNode::data(int role) const
 {
     if (role == Qt::DisplayRole) {
-        return mPerson.name;
+        QString name = mPerson.name;
+        if (!mPerson.ou.isEmpty()) {
+            name += QLatin1String(" (") + mPerson.ou + QLatin1String(")");
+        }
+        return name;
     }
     if (role == Qt::DecorationRole) {
         return KIcon(QLatin1String("meeting-participant"));
@@ -157,7 +162,14 @@ QVariant PersonNode::data(int role) const
         return mCheckState;
     }
     if (role == Qt::ToolTipRole) {
-        return QString(QLatin1String("Person: ") + mPerson.name);
+        QString tooltip = i18n("Person: ") + mPerson.name;
+        if (!mPerson.mail.isEmpty()) {
+            tooltip += QLatin1String("\n") + i18n("Mail: ") + mPerson.mail;
+        }
+        if (!mPerson.ou.isEmpty()) {
+            tooltip += QLatin1String("\n") + i18n("Organization Unit: ") + mPerson.ou;
+        }
+        return tooltip;
     }
     if (role == PersonRole) {
         return QVariant::fromValue(mPerson);
@@ -214,6 +226,9 @@ void PersonNodeManager::checkSourceIndex(const QModelIndex &sourceIndex)
             kDebug() << "Found user folder, creating person node " << col.displayName();
             Person person;
             person.name = col.displayName();
+            person.mail = QString::fromUtf8(attr->mail());
+            person.ou = QString::fromUtf8(attr->ou());
+            person.uid = col.name();
             person.rootCollection = col.id();
 
             model.addNode(ReparentingModel::Node::Ptr(new PersonNode(model, person)));
@@ -231,6 +246,9 @@ void PersonNodeManager::checkSourceIndexRemoval(const QModelIndex &sourceIndex)
             kDebug() << "Found user folder, removing person node " << col.displayName();
             Person person;
             person.name = col.displayName();
+            person.mail = QString::fromUtf8(attr->mail());
+            person.ou = QString::fromUtf8(attr->ou());
+            person.uid = col.name();
             person.rootCollection = col.id();
             model.removeNode(PersonNode(model, person));
         }
@@ -312,6 +330,7 @@ static Akonadi::Collection replaceParent(Akonadi::Collection col, const Akonadi:
     Q_FOREACH (const Akonadi::Collection &c, ancestors) {
         if (col == c) {
             col = c;
+            break;
         }
     }
     col.setParentCollection(parent);
@@ -343,6 +362,22 @@ PersonSearchJob::PersonSearchJob(const QString& searchString, QObject* parent)
     : KJob(parent),
     mSearchString(searchString)
 {
+    connect(&mLdapSearch, SIGNAL(searchData(const QList<KLDAP::LdapResultObject> &)),
+            SLOT(onLDAPSearchData(const QList<KLDAP::LdapResultObject> &)));
+
+    connect(&mLdapSearch, SIGNAL(searchDone()),
+            SLOT(onLDAPSearchDone()));
+}
+
+PersonSearchJob::~PersonSearchJob()
+{
+    mLdapSearch.cancelSearch();
+}
+
+bool PersonSearchJob::kill(KJob::KillVerbosity verbosity)
+{
+    mLdapSearch.cancelSearch();
+    return KJob::kill(verbosity);
 }
 
 void PersonSearchJob::start()
@@ -358,9 +393,14 @@ void PersonSearchJob::start()
     }
     kDebug() << "Found persons " << collections.size();
 
+    mCollectionSearchDone = false;
+    mLdapSearchDone = false;
+
+    mLdapSearch.startSearch(QLatin1String("*") + mSearchString);
+
     if (collections.isEmpty()) {
         //We didn't find anything
-        emitResult();
+        mCollectionSearchDone = true;
         return;
     }
 
@@ -369,34 +409,176 @@ void PersonSearchJob::start()
     fetchJob->fetchScope().setListFilter(Akonadi::CollectionFetchScope::NoFilter);
     connect(fetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), this, SLOT(onCollectionsReceived(Akonadi::Collection::List)));
     connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onCollectionsFetched(KJob*)));
-    //TODO query ldap for available persons and their folders.
-    //TODO identify imap folders as person folders and list them here (after indexing them in baloo).
-    //
+
     //The IMAP resource should add a "Person" attribute to the collections in the person namespace,
     //the ldap query can then be used to update the name (entitydisplayattribute) for the person.
 }
 
+void PersonSearchJob::onLDAPSearchData(const QList< KLDAP::LdapResultObject > &list)
+{
+    QList<Person> persons;
+    Q_FOREACH(const KLDAP::LdapResultObject &item, list) {
+        Person person;
+        person.name = QString::fromUtf8(item.object.value(QLatin1String("cn")));
+        person.mail = QString::fromUtf8(item.object.value(QLatin1String("mail")));
+
+        const int depth = item.object.dn().depth();
+        for ( int i = 0; i < depth; ++i ) {
+            const QString rdnStr = item.object.dn().rdnString(i);
+            if ( rdnStr.startsWith(QLatin1String("ou="), Qt::CaseInsensitive) ) {
+                person.ou = rdnStr.mid(3);
+                break;
+            }
+        }
+        const QStringList &parts = person.mail.split(QLatin1Char('@'));
+        if (parts.count() == 2) {
+            const QString &uid = parts.at(0);
+            person.uid = uid;
+            if (mMatches.contains(uid)) {
+                const Person &p = mMatches.value(uid);
+                if (p.mail != person.mail ) {
+                    if (p.rootCollection > -1) {
+                        person.rootCollection = p.rootCollection;
+                        person.updateDisplayName = p.updateDisplayName;
+                        updatePersonCollection(person);
+                        mMatches.insert(uid, person);
+                    } else {
+                        kWarning() << "That should not happen: we found two times persons with the same uid ("<< uid << "), but differnet name:" << p.name << "vs" << person.name;
+                    }
+                }
+            } else {            //New person found
+                mMatches.insert(uid, person);
+                persons << person;
+            }
+        } else {
+            kWarning() << item.object.dn().toString() << ": invalid email address" << person.mail;
+        }
+    }
+    if (persons.count() > 0) {
+        emit personsFound(persons);
+    }
+}
+
+void PersonSearchJob::onLDAPSearchDone()
+{
+    mLdapSearchDone = true;
+    if (mCollectionSearchDone) {
+        emitResult();
+    }
+}
+
 void PersonSearchJob::onCollectionsReceived(const Akonadi::Collection::List &list)
 {
+    QList<Person> persons;
     Q_FOREACH(const Akonadi::Collection &col, list) {
         Person person;
-        person.name = col.displayName();
+        const QString &uid = col.name();
+        const CollectionIdentificationAttribute *const attr = col.attribute<CollectionIdentificationAttribute>();
+        const Akonadi::EntityDisplayAttribute *const displayname = col.attribute<Akonadi::EntityDisplayAttribute>();
         person.rootCollection = col.id();
-        mMatches << person;
+        person.uid = uid;
+        if (attr) {
+            person.ou = QString::fromUtf8(attr->ou());
+            person.mail = QString::fromUtf8(attr->mail());
+            person.name = QString::fromUtf8(attr->identifier());
+            if (!displayname || displayname->displayName().isEmpty() || displayname->displayName() == person.name) {
+                person.updateDisplayName = true;
+            }
+        } else {
+            person.name = col.displayName();
+            if (!displayname || displayname->displayName().isEmpty()) {
+                person.updateDisplayName = true;
+            }
+        }
+        if (mMatches.contains(uid)) {
+            Person p = mMatches.value(uid);
+            if (p.rootCollection > -1) {
+                //two collection with the same uid ?! 
+                kWarning() << "Two collections match to same person" << p.rootCollection << person.rootCollection;
+            } else if (p.mail != person.mail) {
+                p.rootCollection = person.rootCollection;
+                p.updateDisplayName = person.updateDisplayName;
+                updatePersonCollection(p);
+            } else {
+                mMatches.insert(uid, person);
+                emit personUpdate(person);
+            }
+        } else {
+            mMatches.insert(uid, person);
+            persons << person;
+        }
+    }
+
+    if (persons.count() > 0) {
+        emit personsFound(persons);
     }
 }
 
+void PersonSearchJob::updatePersonCollection(const Person &person)
+{
+    Akonadi::Collection c(person.rootCollection);
+    CollectionIdentificationAttribute *identification = c.attribute<CollectionIdentificationAttribute>(Akonadi::Entity::AddIfMissing);
+
+    if (person.updateDisplayName) {
+        Akonadi::EntityDisplayAttribute *displayname  = c.attribute<Akonadi::EntityDisplayAttribute >(Akonadi::Entity::AddIfMissing);
+        displayname->setDisplayName(person.name);
+    }
+
+    //identification->setIdentifier("Other Users/" + person.uid);
+    identification->setIdentifier(person.name.toUtf8());
+    identification->setName(person.name.toUtf8());
+    identification->setCollectionNamespace("usertoplevel");
+    identification->setMail(person.mail.toUtf8());
+    identification->setOu(person.ou.toUtf8());
+
+    Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob( c, this );
+    connect(job, SIGNAL(result(KJob*)), this, SLOT(modifyResult(KJob*)));
+}
+
 void PersonSearchJob::onCollectionsFetched(KJob *job)
 {
     if (job->error()) {
         kWarning() << job->errorString();
     }
-    emitResult();
+    mCollectionSearchDone = true;
+    if (mLdapSearchDone) {
+        emitResult();
+    }
 }
 
 QList<Person> PersonSearchJob::matches() const
 {
-    return mMatches;
+    return mMatches.values();
+}
+
+void PersonSearchJob::modifyResult(KJob *job)
+{
+    if (job->error()) {
+        kWarning() << job->errorString();
+        return;
+    }
+
+    const Akonadi::CollectionModifyJob *modifyJob = static_cast<Akonadi::CollectionModifyJob*>(job);
+    const Akonadi::Collection &col = modifyJob->collection();
+
+    const CollectionIdentificationAttribute *const attr = col.attribute<CollectionIdentificationAttribute>();
+    const Akonadi::EntityDisplayAttribute *const displayname = col.attribute<Akonadi::EntityDisplayAttribute>();
+    const QString &uid = col.name();
+    Person &person = mMatches[col.name()];
+    person.rootCollection = col.id();
+    person.uid = uid;
+    if (attr) {
+        person.ou = QString::fromUtf8(attr->ou());
+        person.mail = QString::fromUtf8(attr->mail());
+        person.name = QString::fromUtf8(attr->identifier());
+        if (!displayname || displayname->displayName().isEmpty() || displayname->displayName() == person.name) {
+            person.updateDisplayName = true;
+        }
+    }
+    kDebug() << "modified person to" << person.uid << person.name << person.rootCollection;
+
+    mMatches.insert(person.uid, person);
+    emit personUpdate(person);
 }
 
 
@@ -426,10 +608,15 @@ void Controller::setSearchString(const QString &searchString)
     mSearchModel->clear();
     emit searchIsActive(!searchString.isEmpty());
     if (searchString.size() < 2) {
+        emit searching(false);
         return;
     }
 
+    emit searching(true);
+
     mPersonSearchJob = new PersonSearchJob(searchString, this);
+    connect(mPersonSearchJob, SIGNAL(personsFound(QList<Person>)), this, SLOT(onPersonsFound(QList<Person>)));
+    connect(mPersonSearchJob, SIGNAL(personUpdate(Person)), this, SLOT(onPersonUpdate(Person)));
     connect(mPersonSearchJob, SIGNAL(result(KJob*)), this, SLOT(onPersonsFound(KJob*)));
     mPersonSearchJob->start();
 
@@ -440,6 +627,9 @@ void Controller::setSearchString(const QString &searchString)
 
 void Controller::onCollectionsFound(KJob* job)
 {
+    if (!mPersonSearchJob) {
+        emit searching(false);
+    }
     if (job->error()) {
         kWarning() << job->errorString();
         mCollectionSearchJob = 0;
@@ -456,21 +646,34 @@ void Controller::onCollectionsFound(KJob* job)
     mCollectionSearchJob = 0;
 }
 
-void Controller::onPersonsFound(KJob* job)
+void Controller::onPersonsFound(const QList<Person> &persons)
 {
-    if (job->error()) {
-        kWarning() << job->errorString();
-        mPersonSearchJob = 0;
-        return;
-    }
-    Q_ASSERT(mPersonSearchJob == static_cast<PersonSearchJob*>(job));
-    Q_FOREACH(const Person &p, mPersonSearchJob->matches()) {
+    Q_FOREACH(const Person &p, persons) {
         PersonNode *personNode = new PersonNode(*mSearchModel, p);
         personNode->isSearchNode = true;
         //toggled by the checkbox, results in person getting added to main model
         // connect(&personNode->emitter, SIGNAL(enabled(bool, Person)), this, SLOT(onPersonEnabled(bool, Person)));
         mSearchModel->addNode(ReparentingModel::Node::Ptr(personNode));
     }
+}
+
+void Controller::onPersonUpdate(const Person &person)
+{
+    PersonNode *personNode = new PersonNode(*mSearchModel, person);
+    personNode->isSearchNode = true;
+    mSearchModel->updateNode(ReparentingModel::Node::Ptr(personNode));
+}
+
+void Controller::onPersonsFound(KJob* job)
+{
+    if (!mCollectionSearchJob) {
+        emit searching(false);
+    }
+    if (job->error()) {
+        kWarning() << job->errorString();
+        mPersonSearchJob = 0;
+        return;
+    }
     mPersonSearchJob = 0;
 }
 
@@ -536,19 +739,44 @@ void Controller::onPersonCollectionsFetched(KJob* job)
 
 void Controller::addPerson(const Person &person)
 {
-    kDebug() << person.name;
+    kDebug() << person.uid << person.name << person.rootCollection;
+
+    if (person.rootCollection == -1) {
+        Baloo::PIM::CollectionQuery query;
+        query.setNamespace(QStringList() << QLatin1String("usertoplevel"));
+        query.pathMatches(QLatin1String("/Other Users/")+person.uid);
+        query.setLimit(200);
+        Baloo::PIM::ResultIterator it = query.exec();
+        Akonadi::Collection::List collections;
+        while (it.next()) {
+            collections << Akonadi::Collection(it.id());
+        }
+        kDebug() << "Found collections " << collections.size() << "for" << person.name;
+        //TODO: use the found collection and update attribute
+    }
+
     PersonNode *personNode = new PersonNode(*mPersonModel, person);
     personNode->setChecked(true);
     mPersonModel->addNode(ReparentingModel::Node::Ptr(personNode));
 
-    setCollectionState(Akonadi::Collection(person.rootCollection), Referenced, true);
+    if (person.rootCollection > -1) {
+        setCollectionState(Akonadi::Collection(person.rootCollection), Referenced, true);
+    } else {
+        kDebug() << "well this only a ldap search object without a collection";
+        //TODO: use freebusy data into calendar
+    }
 }
 
 void Controller::removePerson(const Person &person)
 {
-    kDebug() << person.name;
+    kDebug() << person.uid << person.name << person.rootCollection;
     mPersonModel->removeNode(PersonNode(*mPersonModel, person));
 
-    setCollectionState(Akonadi::Collection(person.rootCollection), Disabled, true);
+    if (person.rootCollection > -1) {
+        setCollectionState(Akonadi::Collection(person.rootCollection), Disabled, true);
+    } else {
+        kDebug() << "well this only a ldap search object without a collection";
+        //TODO: delete freebusy data from calendar
+    }
 }
 
diff --git a/korganizer/views/collectionview/controller.h b/korganizer/views/collectionview/controller.h
index a90a851..916078e 100644
--- a/korganizer/views/collectionview/controller.h
+++ b/korganizer/views/collectionview/controller.h
@@ -29,6 +29,8 @@
 #include <Akonadi/Collection>
 #include "reparentingmodel.h"
 
+#include <libkdepim/ldap/ldapclientsearch.h>
+
 enum DataRoles {
     PersonRole = Akonadi::EntityTreeModel::UserRole + 1,
     IsSearchResultRole,
@@ -45,8 +47,12 @@ enum NodeTypeRoles {
 
 struct Person
 {
-    Person(): rootCollection(-1){};
+    Person(): rootCollection(-1), updateDisplayName(false) {};
     QString name;
+    QString uid;
+    QString ou;
+    QString mail;
+    bool updateDisplayName;
     Akonadi::Collection::Id rootCollection;
     
     //FIXME not sure we actually require those two
@@ -156,18 +162,33 @@ class PersonSearchJob : public KJob
     Q_OBJECT
 public:
     explicit PersonSearchJob(const QString &searchString, QObject* parent = 0);
+    virtual ~PersonSearchJob();
 
     virtual void start();
 
     QList<Person> matches() const;
 
+Q_SIGNALS:
+    void personsFound(const QList<Person> &persons);
+    void personUpdate(const Person &person);
+
+public Q_SLOTS:
+    bool kill(KillVerbosity verbosity=Quietly);
+
 private Q_SLOTS:
     void onCollectionsReceived(const Akonadi::Collection::List &);
     void onCollectionsFetched(KJob *);
+    void onLDAPSearchData(const QList<KLDAP::LdapResultObject> &);
+    void onLDAPSearchDone();
+    void updatePersonCollection(const Person &person);
+    void modifyResult(KJob *job);
 
 private:
     QString mSearchString;
-    QList<Person> mMatches;
+    QHash<QString, Person> mMatches;
+    KLDAP::LdapClientSearch mLdapSearch;
+    bool mCollectionSearchDone;
+    bool mLdapSearchDone;
 };
 
 /**
@@ -195,12 +216,15 @@ public:
 
 Q_SIGNALS:
     void searchIsActive(bool);
+    void searching(bool);
 
 public Q_SLOTS:
     void setSearchString(const QString &);
 
 private Q_SLOTS:
     void onCollectionsFound(KJob *job);
+    void onPersonsFound(const QList<Person> &persons);
+    void onPersonUpdate(const Person &person);
     void onPersonsFound(KJob *job);
     void onPersonCollectionsFetched(KJob *job);
 
diff --git a/korganizer/views/collectionview/reparentingmodel.cpp b/korganizer/views/collectionview/reparentingmodel.cpp
index 6e73df0..824f31c 100644
--- a/korganizer/views/collectionview/reparentingmodel.cpp
+++ b/korganizer/views/collectionview/reparentingmodel.cpp
@@ -274,6 +274,23 @@ void ReparentingModel::doAddNode(const Node::Ptr &node)
     }
 }
 
+void ReparentingModel::updateNode(const ReparentingModel::Node::Ptr &node)
+{
+    Q_FOREACH(const ReparentingModel::Node::Ptr &existing, mProxyNodes) {
+        if (*existing == *node) {
+            node->parent = existing->parent;
+            int r = row(existing.data());
+            existing->parent->children.replace(r, node);
+            const QModelIndex i = index(node.data());
+            Q_ASSERT(i.row() == r);
+            emit dataChanged(i, i);
+            return;
+        }
+    }
+
+    kWarning() << "no node to update"; 
+}
+
 void ReparentingModel::removeNode(const ReparentingModel::Node& node)
 {
     //If there is an addNode in progress for that node, abort it.
@@ -761,21 +778,30 @@ Qt::ItemFlags ReparentingModel::flags(const QModelIndex& index) const
     return sourceModel()->flags(mapToSource(index));
 }
 
-QModelIndex ReparentingModel::index(Node *node) const
+int ReparentingModel::row(ReparentingModel::Node *node) const
 {
     Q_ASSERT(node);
     if (node == &mRootNode) {
-        return QModelIndex();
+        return -1;
     }
     Q_ASSERT(validateNode(node));
     int row = 0;
     Q_FOREACH(const Node::Ptr &c, node->parent->children) {
         if (c.data() == node) {
-            break;
+            return row;
         }
         row++;
     }
-    return createIndex(row, 0, node);
+    return -1;
+}
+
+QModelIndex ReparentingModel::index(Node *node) const
+{
+    const int r = row(node);
+    if (r < 0) {
+        return QModelIndex();
+    }
+    return createIndex(r, 0, node);
 }
 
 QModelIndex ReparentingModel::parent(const QModelIndex& child) const
diff --git a/korganizer/views/collectionview/reparentingmodel.h b/korganizer/views/collectionview/reparentingmodel.h
index ad73ba8..4f88c84 100644
--- a/korganizer/views/collectionview/reparentingmodel.h
+++ b/korganizer/views/collectionview/reparentingmodel.h
@@ -87,6 +87,7 @@ public:
 
     void setNodeManager(const NodeManager::Ptr &nodeManager);
     void addNode(const Node::Ptr &node);
+    void updateNode(const Node::Ptr &node);
     void removeNode(const Node &node);
     void setNodes(const QList<Node::Ptr> &nodes);
     void clear();
@@ -126,6 +127,7 @@ private:
     void reparentSourceNodes(const Node::Ptr &proxyNode);
     void rebuildAll();
     QModelIndex index(Node *node) const;
+    int row(Node *node) const;
     Node *getReparentNode(const QModelIndex &sourceIndex);
     Node *getParentNode(const QModelIndex &sourceIndex);
     bool validateNode(const Node *node) const;
diff --git a/korganizer/views/collectionview/tests/reparentingmodeltest.cpp b/korganizer/views/collectionview/tests/reparentingmodeltest.cpp
index 4fe7dbb..f7ddd70 100644
--- a/korganizer/views/collectionview/tests/reparentingmodeltest.cpp
+++ b/korganizer/views/collectionview/tests/reparentingmodeltest.cpp
@@ -30,9 +30,10 @@
 class DummyNode : public ReparentingModel::Node
 {
 public:
-    DummyNode(ReparentingModel &personModel, const QString &name)
+    DummyNode(ReparentingModel &personModel, const QString &name, const QString &data=QString())
     : ReparentingModel::Node(personModel),
-    mName(name)
+    mName(name),
+    mData(data)
     {}
 
     virtual ~DummyNode(){};
@@ -49,10 +50,14 @@ private:
     virtual QVariant data(int role) const {
         if (role == Qt::DisplayRole) {
             return mName;
+        } else if (role == Qt::UserRole) {
+            return mData;
         }
         return QVariant();
     }
     virtual bool setData(const QVariant& variant, int role){
+        Q_UNUSED(variant);
+        Q_UNUSED(role);
         return false;
     }
     virtual bool isDuplicateOf(const QModelIndex& sourceIndex) {
@@ -64,6 +69,7 @@ private:
     }
 
     QString mName;
+    QString mData;
 };
 
 class ModelSignalSpy : public QObject {
@@ -80,6 +86,7 @@ public:
 
     QStringList mSignals;
     QModelIndex parent;
+    QModelIndex topLeft, bottomRight;
     int start;
     int end;
 
@@ -99,8 +106,10 @@ public Q_SLOTS:
     void onRowsMoved(QModelIndex,int,int,QModelIndex,int) {
         mSignals << QLatin1String("rowsMoved");
     }
-    void onDataChanged(QModelIndex,QModelIndex) {
+    void onDataChanged(QModelIndex t,QModelIndex b) {
         mSignals << QLatin1String("dataChanged");
+        topLeft = t;
+        bottomRight = b;
     }
     void onLayoutChanged() {
         mSignals << QLatin1String("layoutChanged");
@@ -137,6 +146,7 @@ private Q_SLOTS:
     void testDeduplicateNested();
     void testDeduplicateProxyNodeFirst();
     void testNestedDeduplicateProxyNodeFirst();
+    void testUpdateNode();
     void testReparent();
     void testReparentResetWithoutCrash();
     void testAddReparentedSourceItem();
@@ -352,6 +362,40 @@ void ReparentingModelTest::testNestedDeduplicateProxyNodeFirst()
     //TODO ensure we actually have the source index and not the proxy index
 }
 
+/**
+ * updateNode should update the node datas
+ */
+void ReparentingModelTest::testUpdateNode()
+{
+    QStandardItemModel sourceModel;
+    ReparentingModel reparentingModel;
+    reparentingModel.setSourceModel(&sourceModel);
+    reparentingModel.addNode(ReparentingModel::Node::Ptr(new DummyNode(reparentingModel, QLatin1String("proxy1"), QLatin1String("blub"))));
+
+    QTest::qWait(0);
+
+    QModelIndex index = getIndex("proxy1", reparentingModel);
+    QCOMPARE(reparentingModel.rowCount(QModelIndex()), 1);
+    QVERIFY(index.isValid());
+    QCOMPARE(reparentingModel.data(index,Qt::UserRole).toString(), QLatin1String("blub"));
+
+    ModelSignalSpy spy(reparentingModel);
+    reparentingModel.updateNode(ReparentingModel::Node::Ptr(new DummyNode(reparentingModel, QLatin1String("proxy1"), QLatin1String("new data"))));
+    QTest::qWait(0);
+
+    QModelIndex i2 = getIndex("proxy1", reparentingModel);
+    QCOMPARE(i2.column(), index.column());
+    QCOMPARE(i2.row(), index.row());
+
+    QCOMPARE(spy.mSignals.count(), 1);
+    QCOMPARE(spy.mSignals.takeLast(),QLatin1String("dataChanged"));
+    QCOMPARE(spy.topLeft, i2);
+    QCOMPARE(spy.bottomRight, i2);
+
+    QCOMPARE(reparentingModel.rowCount(QModelIndex()), 1);
+    QCOMPARE(reparentingModel.data(i2,Qt::UserRole).toString(), QLatin1String("new data"));
+}
+
 void ReparentingModelTest::testReparent()
 {
     QStandardItemModel sourceModel;




More information about the commits mailing list