From 3b8d2641c3f645718305f2f25c12eb3201f43ecc Mon Sep 17 00:00:00 2001
From: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
Date: Wed, 16 Sep 2015 16:26:02 -0400
Subject: [PATCH] smartlist: working implementation

The RecentModel reorders ContactMethods and Persons based on the latest
interaction with each one.
When an ongoing call is added, it is linked to the associated node and appears
below it

Issue: #75334
Change-Id: I7d68a4ca7620386122a74e3f4e6b1e1a20e4cf8e
---
 src/contactmethod.cpp |   4 +-
 src/recentmodel.cpp   | 242 ++++++++++++++++++++++++++++++++----------
 src/recentmodel.h     |   7 +-
 3 files changed, 196 insertions(+), 57 deletions(-)

diff --git a/src/contactmethod.cpp b/src/contactmethod.cpp
index 84a3878c..27cd53ce 100644
--- a/src/contactmethod.cpp
+++ b/src/contactmethod.cpp
@@ -632,9 +632,9 @@ void ContactMethod::contactRebased(Person* other)
 {
    d_ptr->m_PrimaryName_cache = other->formattedName();
    d_ptr->primaryNameChanged(d_ptr->m_PrimaryName_cache);
-   d_ptr->changed();
+   setPerson(other);
 
-   //It is a "partial" rebase, so the ContactMethod data stay the same
+   d_ptr->changed();
    d_ptr->rebased(this);
 }
 
diff --git a/src/recentmodel.cpp b/src/recentmodel.cpp
index 464a96c2..40395a6e 100644
--- a/src/recentmodel.cpp
+++ b/src/recentmodel.cpp
@@ -1,6 +1,7 @@
 /************************************************************************************
- *   Copyright (C) 2015 by Savoir-Faire Linux                                       *
+ *   Copyright (C) 2015 by Savoir-faire Linux                                       *
  *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>         *
+ *            Alexandre Lision <alexandre.lision@savoirfairelinux.com>              *
  *                                                                                  *
  *   This library is free software; you can redistribute it and/or                  *
  *   modify it under the terms of the GNU Lesser General Public                     *
@@ -27,6 +28,10 @@
 #include <personmodel.h>
 #include <contactmethod.h>
 #include <phonedirectorymodel.h>
+#include <callmodel.h>
+#include <categorizedhistorymodel.h>
+#include <media/recordingmodel.h>
+#include <media/textrecording.h>
 
 struct CallGroup
 {
@@ -58,7 +63,7 @@ struct RecentViewNode
    RecentViewNode*        m_pParent  ;
    QList<RecentViewNode*> m_lChildren;
    union {
-      Person*        m_pPerson       ;
+      const Person*  m_pPerson       ;
       ContactMethod* m_pContactMethod;
       Call*          m_pCall         ;
       CallGroup*     m_pCallGroup    ;
@@ -76,14 +81,15 @@ public:
    RecentModelPrivate(RecentModel* p);
 
    /*
-    * m_lTopLevelReverted hold the elements in the reverse order of
-    * QAbstractItemModel::index. This cause most of the energy to be
-    * in the bottom half of the vector, preventing std::move every time
-    * someone is contacted
-    */
+   * m_lTopLevelReverted hold the elements in the reverse order of
+   * QAbstractItemModel::index. This cause most of the energy to be
+   * in the bottom half of the vector, preventing std::move every time
+   * someone is contacted
+   */
    QList<RecentViewNode*>                m_lTopLevelReverted;
-   QHash<Person*,RecentViewNode*>        m_hPersonsToNodes  ;
+   QHash<const Person*,RecentViewNode*>  m_hPersonsToNodes  ;
    QHash<ContactMethod*,RecentViewNode*> m_hCMsToNodes      ;
+   QList<Call*>                          m_lCallBucket      ;
 
    //Helper
    void insertNode(RecentViewNode* n, time_t t, bool isNew);
@@ -93,9 +99,13 @@ private:
    RecentModel* q_ptr;
 
 public Q_SLOTS:
-   void slotLastUsedTimeChanged(Person*        p , time_t t              );
+   void slotLastUsedTimeChanged(const Person*  p , time_t t              );
+   void slotPersonAdded        (const Person*  p                         );
    void slotLastUsedChanged    (ContactMethod* cm, time_t t              );
    void slotContactChanged     (ContactMethod* cm, Person* np, Person* op);
+   void slotCallAdded          (Call* call       , Call* parent          );
+   void slotCallStateChanged   (Call* call       , Call::State previousState);
+   void slotUpdate             (                                         );
 };
 
 RecentModelPrivate::RecentModelPrivate(RecentModel* p) : q_ptr(p)
@@ -105,23 +115,26 @@ RecentModelPrivate::RecentModelPrivate(RecentModel* p) : q_ptr(p)
 RecentModel::RecentModel(QObject* parent) : QAbstractItemModel(parent), d_ptr(new RecentModelPrivate(this))
 {
    connect(PersonModel::instance()        , &PersonModel::lastUsedTimeChanged    , d_ptr, &RecentModelPrivate::slotLastUsedTimeChanged);
+   connect(PersonModel::instance()        , &PersonModel::newPersonAdded         , d_ptr, &RecentModelPrivate::slotPersonAdded        );
    connect(PhoneDirectoryModel::instance(), &PhoneDirectoryModel::lastUsedChanged, d_ptr, &RecentModelPrivate::slotLastUsedChanged    );
    connect(PhoneDirectoryModel::instance(), &PhoneDirectoryModel::contactChanged , d_ptr, &RecentModelPrivate::slotContactChanged     );
+   connect(CallModel::instance()          , &CallModel::callAdded                , d_ptr, &RecentModelPrivate::slotCallAdded          );
+   connect(CallModel::instance()          , &CallModel::callStateChanged         , d_ptr, &RecentModelPrivate::slotCallStateChanged   );
 
    //Fill the contacts
    for (int i=0; i < PersonModel::instance()->rowCount(); i++) {
-      Person* p = qvariant_cast<Person*>(PersonModel::instance()->data(
+      auto person = qvariant_cast<Person*>(PersonModel::instance()->data(
          PersonModel::instance()->index(i,0),
          static_cast<int>(Person::Role::Object)
       ));
 
-      if (p && p->lastUsedTime())
-         d_ptr->slotLastUsedTimeChanged(p, p->lastUsedTime());
+      if (person && person->lastUsedTime())
+         d_ptr->slotLastUsedTimeChanged(person, person->lastUsedTime());
    }
 
    //Fill the "orphan" contact methods
-   for (int i=0; i < PhoneDirectoryModel::instance()->rowCount(); i++) {
-      ContactMethod* cm = qvariant_cast<ContactMethod*>(PhoneDirectoryModel::instance()->data(
+   for (int i = 0; i < PhoneDirectoryModel::instance()->rowCount(); i++) {
+      auto cm = qvariant_cast<ContactMethod*>(PhoneDirectoryModel::instance()->data(
          PhoneDirectoryModel::instance()->index(i,0),
          static_cast<int>(PhoneDirectoryModel::Role::Object)
       ));
@@ -129,6 +142,12 @@ RecentModel::RecentModel(QObject* parent) : QAbstractItemModel(parent), d_ptr(ne
       if (cm && cm->lastUsed() && !cm->contact())
          d_ptr->slotLastUsedChanged(cm, cm->lastUsed());
    }
+
+   //Fill node with history data
+   //const CallMap callMap = CategorizedHistoryModel::instance()->getHistoryCalls();
+   //Q_FOREACH(auto const &call , callMap) {
+   //    d_ptr->slotCallAdded(call, nullptr);
+   //}
 }
 
 RecentModel::~RecentModel()
@@ -172,8 +191,47 @@ time_t RecentViewNode::lastUsed() const
 
 RecentModel* RecentModel::instance()
 {
-   RecentModel* m_spInstance =  new RecentModel(QCoreApplication::instance());
-   return m_spInstance;
+   static RecentModel* instance = new RecentModel(QCoreApplication::instance());
+   return instance;
+}
+
+/**
+ * Check if given index has an ongoing call
+ * returns true if one of its child is also in the CallModel
+ */
+bool RecentModel::hasActiveCall(const QModelIndex &idx)
+{
+   if (not idx.isValid())
+      return false;
+
+   auto node = static_cast<RecentViewNode*>(idx.internalPointer());
+
+   auto reverseEnd = std::make_reverse_iterator(node->m_lChildren.begin());
+   auto it = std::find_if (std::make_reverse_iterator(node->m_lChildren.end()),
+            reverseEnd, [] (RecentViewNode* child) {
+                     return child->m_Type == RecentViewNode::Type::CALL;
+               });
+
+   return it != reverseEnd;
+}
+
+/**
+ * Return the first found ongoing call of the given parent index
+ */
+Call* RecentModel::getActiveCall(const QModelIndex &idx)
+{
+   if (not idx.isValid())
+      return nullptr;
+
+   RecentViewNode* node = static_cast<RecentViewNode*>(idx.internalPointer());
+
+   auto reverseEnd = std::make_reverse_iterator(node->m_lChildren.begin());
+   auto it = std::find_if (std::make_reverse_iterator(node->m_lChildren.end()),
+            reverseEnd, [] (RecentViewNode* child) {
+                     return child->m_Type == RecentViewNode::Type::CALL;
+               });
+
+   return it != reverseEnd ? (*it)->m_uContent.m_pCall : nullptr;
 }
 
 QHash<int,QByteArray> RecentModel::roleNames() const
@@ -226,7 +284,6 @@ int RecentModel::rowCount( const QModelIndex& parent ) const
       return d_ptr->m_lTopLevelReverted.size();
 
    RecentViewNode* node = static_cast<RecentViewNode*>(parent.internalPointer());
-
    return node->m_lChildren.size();
 }
 
@@ -306,15 +363,18 @@ void RecentModelPrivate::insertNode(RecentViewNode* n, time_t t, bool isNew)
          return a->lastUsed() < t2->m_Index;
       });
 
-      newPos = (*lower)->m_Index+1;
+      // the value pointed by the iterator returned by this function may also
+      // be equivalent to val, and not only greater
+      if (!isNew && n->m_Index == (*lower)->m_Index) {
+         newPos = (*lower)->m_Index;
+      } else
+         newPos = (*lower)->m_Index+1;
    }
 
    //Begin the transaction
    if (!isNew) {
-
       if (newPos == n->m_Index)
          return; //Nothing to do
-
       q_ptr->beginMoveRows(QModelIndex(), n->m_Index, n->m_Index, QModelIndex(), newPos ? newPos+1 : newPos );
       m_lTopLevelReverted.removeAt(m_lTopLevelReverted.size()-1-n->m_Index);
    }
@@ -322,22 +382,9 @@ void RecentModelPrivate::insertNode(RecentViewNode* n, time_t t, bool isNew)
       q_ptr->beginInsertRows(QModelIndex(),newPos,newPos);
 
    //Apply the transaction
-   const int updateBound = n->m_Index;
-   if (m_lTopLevelReverted.last()->lastUsed() <= t) {
-
-      //TODO this happen often and is O(N), inverting m_Index would "fix" this
-      for (int i = m_lTopLevelReverted.size()-1; i >= updateBound; i--)
-         m_lTopLevelReverted[m_lTopLevelReverted.size()-1-i]->m_Index = i+1;
-
-      m_lTopLevelReverted << n;
-   }
-   else {
-      m_lTopLevelReverted.insert(m_lTopLevelReverted.size()-newPos,n);
-      n->m_Index = newPos;
-
-      for (int i = m_lTopLevelReverted.size()-1; i >= updateBound; i--)
-         m_lTopLevelReverted[m_lTopLevelReverted.size()-1-i]->m_Index = i;
-
+   m_lTopLevelReverted.insert(m_lTopLevelReverted.size() - newPos,n);
+   for (int i = 0 ; i < m_lTopLevelReverted.size(); ++i) {
+      m_lTopLevelReverted[i]->m_Index = m_lTopLevelReverted.size() - 1 - i;
    }
 
    //Notify that the transaction is complete
@@ -346,32 +393,41 @@ void RecentModelPrivate::insertNode(RecentViewNode* n, time_t t, bool isNew)
    else
       q_ptr->endInsertRows();
 
-   //Uncomment if there is issues
-   //qDebug() << "\n\nList:" << m_lTopLevelReverted.size() << isNew;
-   //for (int i = 0; i<m_lTopLevelReverted.size();i++)
-   //  qDebug() << "|||" << m_lTopLevelReverted[i]->lastUsed() << m_lTopLevelReverted[i]->m_Index << q_ptr->data(q_ptr->index(m_lTopLevelReverted.size()-1-i,0),Qt::DisplayRole);
-
+#if 0
+    //Uncomment if there is issues
+    qDebug() << "\n\nList:" << m_lTopLevelReverted.size() << isNew;
+    for (int i = 0; i<m_lTopLevelReverted.size();i++) {
+        qDebug() << "|||" << m_lTopLevelReverted[i]->lastUsed() << m_lTopLevelReverted[i]->m_Index << q_ptr->data(q_ptr->index(m_lTopLevelReverted.size()-1-i,0),Qt::DisplayRole);
+        for (auto child : m_lTopLevelReverted[i]->m_lChildren) {
+            qDebug() << "|||" << "|||" << child << child->m_uContent.m_pCall->formattedName();
+        }
+     }
+#endif
 }
 
 void RecentModelPrivate::removeNode(RecentViewNode* n)
 {
    const int idx  = n->m_Index;
-   const int size = m_lTopLevelReverted.size();
 
    q_ptr->beginRemoveRows(QModelIndex(), idx, idx);
 
-   //If this assert, the data is corrupted anyway, it will crash later on
-   Q_ASSERT(m_lTopLevelReverted[size-idx-1] == n);
-
-   m_lTopLevelReverted.removeAt(size-idx-1);
-
-   for (int i = 0; i <= idx; i++)
-      m_lTopLevelReverted[i]->m_Index--;
+   m_lTopLevelReverted.removeOne(n);
 
+   if (idx < m_lTopLevelReverted.size()) {
+      for (int i = 0; i <= idx; i++) {
+         m_lTopLevelReverted[i]->m_Index--;
+      }
+   }
    q_ptr->endRemoveRows();
 }
 
-void RecentModelPrivate::slotLastUsedTimeChanged(Person* p, time_t t)
+void RecentModelPrivate::slotPersonAdded(const Person* p)
+{
+   if (p)
+      slotLastUsedTimeChanged(p, p->lastUsedTime());
+}
+
+void RecentModelPrivate::slotLastUsedTimeChanged(const Person* p, time_t t)
 {
    RecentViewNode* n = m_hPersonsToNodes[p];
    const bool isNew = !n;
@@ -383,9 +439,15 @@ void RecentModelPrivate::slotLastUsedTimeChanged(Person* p, time_t t)
       n->m_pParent            = nullptr                     ;
       n->m_Index              = 0                           ;
       m_hPersonsToNodes[p]    = n                           ;
+      Q_FOREACH(auto cm, p->phoneNumbers()) {
+         if (auto cmNode = m_hCMsToNodes[cm])
+            n->m_lChildren.append(cmNode->m_lChildren);
+      }
+
    }
 
    insertNode(n, t, isNew);
+   slotUpdate();
 }
 
 void RecentModelPrivate::slotLastUsedChanged(ContactMethod* cm, time_t t)
@@ -403,19 +465,91 @@ void RecentModelPrivate::slotLastUsedChanged(ContactMethod* cm, time_t t)
          n->m_Index                     = 0                                   ;
          m_hCMsToNodes[cm]              = n                                   ;
       }
-
       insertNode(n, t, isNew);
+      slotUpdate();
    }
 }
 
 ///Remove the contact method once they are associated with a contact
 void RecentModelPrivate::slotContactChanged(ContactMethod* cm, Person* np, Person* op)
 {
-   if (op)
-      return;
-
+   Q_UNUSED(np)
    RecentViewNode* n = m_hCMsToNodes[cm];
 
    if (n)
       removeNode(n);
 }
+
+void RecentModelPrivate::slotCallStateChanged(Call* call, Call::State previousState)
+{
+   //qDebug() << "STATE CHANGED:" << call->peerContactMethod();
+   RecentViewNode* n;
+   if (auto p = call->peerContactMethod()->contact()) {
+      n = m_hPersonsToNodes[p];
+   } else {
+      n = m_hCMsToNodes[call->peerContactMethod()];
+   }
+   if (!n)
+      return;
+
+   if (call->state() == Call::State::OVER) {
+      // Find the active call in children of this node and remove it
+      auto itEnd = n->m_lChildren.end();
+      auto it = std::find_if (n->m_lChildren.begin(),
+                  itEnd, [call] (RecentViewNode* child) {
+                        return child->m_uContent.m_pCall == call;
+                  });
+
+      if (it == itEnd) {
+         // Call can be in the bucket (after callAdded) but not inserted
+         // because it failed before lastUsedChanged to be emitted
+         m_lCallBucket.removeOne(call);
+         return;
+      }
+
+      q_ptr->beginRemoveRows(q_ptr->index(n->m_Index,0), (*it)->m_Index, (*it)->m_Index);
+      n->m_lChildren.removeAt((*it)->m_Index);
+      q_ptr->endRemoveRows();
+   } else
+      emit q_ptr->dataChanged(q_ptr->index(n->m_Index,0),q_ptr->index(n->m_Index,0));
+}
+
+void RecentModelPrivate::slotCallAdded(Call* call, Call* parent)
+{
+   Q_UNUSED(parent)
+
+   RecentViewNode* n = nullptr;
+   if (auto p = call->peerContactMethod()->contact()) {
+      n = m_hPersonsToNodes[p];
+   } else {
+      n = m_hCMsToNodes[call->peerContactMethod()];
+   }
+   if (!n && !m_lCallBucket.contains(call)) {
+      m_lCallBucket.append(call);
+      connect(call, &Call::lifeCycleStateChanged, this, &RecentModelPrivate::slotUpdate);
+      return;
+   } else if (n && m_lCallBucket.contains(call)) {
+      m_lCallBucket.removeOne(call);
+      //TODO: remove the connection store callbucket as a map key = call value = metaconnection
+   } else if (!n && m_lCallBucket.contains(call)) {
+      return;
+   }
+
+   auto callNode = new RecentViewNode();
+   callNode->m_Type = RecentViewNode::Type::CALL;
+   callNode->m_uContent.m_pCall = call;
+   callNode->m_pParent = n;
+   callNode->m_Index = n->m_lChildren.size();
+
+   q_ptr->beginInsertRows(q_ptr->index(n->m_Index,0), n->m_lChildren.size(), n->m_lChildren.size());
+   n->m_lChildren.append(callNode);
+   q_ptr->endInsertRows();
+}
+
+void RecentModelPrivate::slotUpdate()
+{
+   Q_FOREACH(auto call, m_lCallBucket) {
+      qDebug() << "trying to empty bucket call:" << call;
+      slotCallAdded(call, nullptr);
+   }
+}
diff --git a/src/recentmodel.h b/src/recentmodel.h
index db21e50d..30a66bd2 100644
--- a/src/recentmodel.h
+++ b/src/recentmodel.h
@@ -1,6 +1,7 @@
 /************************************************************************************
- *   Copyright (C) 2015 by Savoir-Faire Linux                                       *
+ *   Copyright (C) 2015 by Savoir-faire Linux                                       *
  *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>         *
+ *            Alexandre Lision <alexandre.lision@savoirfairelinux.com>              *
  *                                                                                  *
  *   This library is free software; you can redistribute it and/or                  *
  *   modify it under the terms of the GNU Lesser General Public                     *
@@ -23,6 +24,7 @@
 #include <typedefs.h>
 
 class RecentModelPrivate;
+class Call;
 
 class LIB_EXPORT RecentModel : public QAbstractItemModel
 {
@@ -41,6 +43,9 @@ public:
    virtual QHash<int,QByteArray> roleNames() const override;
 
    static RecentModel* instance();
+
+   bool hasActiveCall(const QModelIndex& parent);
+   Call* getActiveCall(const QModelIndex& parent);
 private:
    explicit RecentModel(QObject* parent = nullptr);
    virtual ~RecentModel();
-- 
GitLab