Skip to content
Snippets Groups Projects
Commit 3b8d2641 authored by Alexandre Lision's avatar Alexandre Lision
Browse files

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
parent bf3e0be5
Branches
Tags
No related merge requests found
...@@ -632,9 +632,9 @@ void ContactMethod::contactRebased(Person* other) ...@@ -632,9 +632,9 @@ void ContactMethod::contactRebased(Person* other)
{ {
d_ptr->m_PrimaryName_cache = other->formattedName(); d_ptr->m_PrimaryName_cache = other->formattedName();
d_ptr->primaryNameChanged(d_ptr->m_PrimaryName_cache); 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); d_ptr->rebased(this);
} }
......
/************************************************************************************ /************************************************************************************
* Copyright (C) 2015 by Savoir-Faire Linux * * Copyright (C) 2015 by Savoir-faire Linux *
* Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> * * 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 * * This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public * * modify it under the terms of the GNU Lesser General Public *
...@@ -27,6 +28,10 @@ ...@@ -27,6 +28,10 @@
#include <personmodel.h> #include <personmodel.h>
#include <contactmethod.h> #include <contactmethod.h>
#include <phonedirectorymodel.h> #include <phonedirectorymodel.h>
#include <callmodel.h>
#include <categorizedhistorymodel.h>
#include <media/recordingmodel.h>
#include <media/textrecording.h>
struct CallGroup struct CallGroup
{ {
...@@ -58,7 +63,7 @@ struct RecentViewNode ...@@ -58,7 +63,7 @@ struct RecentViewNode
RecentViewNode* m_pParent ; RecentViewNode* m_pParent ;
QList<RecentViewNode*> m_lChildren; QList<RecentViewNode*> m_lChildren;
union { union {
Person* m_pPerson ; const Person* m_pPerson ;
ContactMethod* m_pContactMethod; ContactMethod* m_pContactMethod;
Call* m_pCall ; Call* m_pCall ;
CallGroup* m_pCallGroup ; CallGroup* m_pCallGroup ;
...@@ -76,14 +81,15 @@ public: ...@@ -76,14 +81,15 @@ public:
RecentModelPrivate(RecentModel* p); RecentModelPrivate(RecentModel* p);
/* /*
* m_lTopLevelReverted hold the elements in the reverse order of * m_lTopLevelReverted hold the elements in the reverse order of
* QAbstractItemModel::index. This cause most of the energy to be * QAbstractItemModel::index. This cause most of the energy to be
* in the bottom half of the vector, preventing std::move every time * in the bottom half of the vector, preventing std::move every time
* someone is contacted * someone is contacted
*/ */
QList<RecentViewNode*> m_lTopLevelReverted; QList<RecentViewNode*> m_lTopLevelReverted;
QHash<Person*,RecentViewNode*> m_hPersonsToNodes ; QHash<const Person*,RecentViewNode*> m_hPersonsToNodes ;
QHash<ContactMethod*,RecentViewNode*> m_hCMsToNodes ; QHash<ContactMethod*,RecentViewNode*> m_hCMsToNodes ;
QList<Call*> m_lCallBucket ;
//Helper //Helper
void insertNode(RecentViewNode* n, time_t t, bool isNew); void insertNode(RecentViewNode* n, time_t t, bool isNew);
...@@ -93,9 +99,13 @@ private: ...@@ -93,9 +99,13 @@ private:
RecentModel* q_ptr; RecentModel* q_ptr;
public Q_SLOTS: 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 slotLastUsedChanged (ContactMethod* cm, time_t t );
void slotContactChanged (ContactMethod* cm, Person* np, Person* op); 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) RecentModelPrivate::RecentModelPrivate(RecentModel* p) : q_ptr(p)
...@@ -105,23 +115,26 @@ 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)) RecentModel::RecentModel(QObject* parent) : QAbstractItemModel(parent), d_ptr(new RecentModelPrivate(this))
{ {
connect(PersonModel::instance() , &PersonModel::lastUsedTimeChanged , d_ptr, &RecentModelPrivate::slotLastUsedTimeChanged); 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::lastUsedChanged, d_ptr, &RecentModelPrivate::slotLastUsedChanged );
connect(PhoneDirectoryModel::instance(), &PhoneDirectoryModel::contactChanged , d_ptr, &RecentModelPrivate::slotContactChanged ); 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 //Fill the contacts
for (int i=0; i < PersonModel::instance()->rowCount(); i++) { 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), PersonModel::instance()->index(i,0),
static_cast<int>(Person::Role::Object) static_cast<int>(Person::Role::Object)
)); ));
if (p && p->lastUsedTime()) if (person && person->lastUsedTime())
d_ptr->slotLastUsedTimeChanged(p, p->lastUsedTime()); d_ptr->slotLastUsedTimeChanged(person, person->lastUsedTime());
} }
//Fill the "orphan" contact methods //Fill the "orphan" contact methods
for (int i=0; i < PhoneDirectoryModel::instance()->rowCount(); i++) { for (int i = 0; i < PhoneDirectoryModel::instance()->rowCount(); i++) {
ContactMethod* cm = qvariant_cast<ContactMethod*>(PhoneDirectoryModel::instance()->data( auto cm = qvariant_cast<ContactMethod*>(PhoneDirectoryModel::instance()->data(
PhoneDirectoryModel::instance()->index(i,0), PhoneDirectoryModel::instance()->index(i,0),
static_cast<int>(PhoneDirectoryModel::Role::Object) static_cast<int>(PhoneDirectoryModel::Role::Object)
)); ));
...@@ -129,6 +142,12 @@ RecentModel::RecentModel(QObject* parent) : QAbstractItemModel(parent), d_ptr(ne ...@@ -129,6 +142,12 @@ RecentModel::RecentModel(QObject* parent) : QAbstractItemModel(parent), d_ptr(ne
if (cm && cm->lastUsed() && !cm->contact()) if (cm && cm->lastUsed() && !cm->contact())
d_ptr->slotLastUsedChanged(cm, cm->lastUsed()); 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() RecentModel::~RecentModel()
...@@ -172,8 +191,47 @@ time_t RecentViewNode::lastUsed() const ...@@ -172,8 +191,47 @@ time_t RecentViewNode::lastUsed() const
RecentModel* RecentModel::instance() RecentModel* RecentModel::instance()
{ {
RecentModel* m_spInstance = new RecentModel(QCoreApplication::instance()); static RecentModel* instance = new RecentModel(QCoreApplication::instance());
return m_spInstance; 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 QHash<int,QByteArray> RecentModel::roleNames() const
...@@ -226,7 +284,6 @@ int RecentModel::rowCount( const QModelIndex& parent ) const ...@@ -226,7 +284,6 @@ int RecentModel::rowCount( const QModelIndex& parent ) const
return d_ptr->m_lTopLevelReverted.size(); return d_ptr->m_lTopLevelReverted.size();
RecentViewNode* node = static_cast<RecentViewNode*>(parent.internalPointer()); RecentViewNode* node = static_cast<RecentViewNode*>(parent.internalPointer());
return node->m_lChildren.size(); return node->m_lChildren.size();
} }
...@@ -306,15 +363,18 @@ void RecentModelPrivate::insertNode(RecentViewNode* n, time_t t, bool isNew) ...@@ -306,15 +363,18 @@ void RecentModelPrivate::insertNode(RecentViewNode* n, time_t t, bool isNew)
return a->lastUsed() < t2->m_Index; 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 //Begin the transaction
if (!isNew) { if (!isNew) {
if (newPos == n->m_Index) if (newPos == n->m_Index)
return; //Nothing to do return; //Nothing to do
q_ptr->beginMoveRows(QModelIndex(), n->m_Index, n->m_Index, QModelIndex(), newPos ? newPos+1 : newPos ); 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); m_lTopLevelReverted.removeAt(m_lTopLevelReverted.size()-1-n->m_Index);
} }
...@@ -322,22 +382,9 @@ void RecentModelPrivate::insertNode(RecentViewNode* n, time_t t, bool isNew) ...@@ -322,22 +382,9 @@ void RecentModelPrivate::insertNode(RecentViewNode* n, time_t t, bool isNew)
q_ptr->beginInsertRows(QModelIndex(),newPos,newPos); q_ptr->beginInsertRows(QModelIndex(),newPos,newPos);
//Apply the transaction //Apply the transaction
const int updateBound = n->m_Index; m_lTopLevelReverted.insert(m_lTopLevelReverted.size() - newPos,n);
if (m_lTopLevelReverted.last()->lastUsed() <= t) { for (int i = 0 ; i < m_lTopLevelReverted.size(); ++i) {
m_lTopLevelReverted[i]->m_Index = m_lTopLevelReverted.size() - 1 - i;
//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;
} }
//Notify that the transaction is complete //Notify that the transaction is complete
...@@ -346,32 +393,41 @@ void RecentModelPrivate::insertNode(RecentViewNode* n, time_t t, bool isNew) ...@@ -346,32 +393,41 @@ void RecentModelPrivate::insertNode(RecentViewNode* n, time_t t, bool isNew)
else else
q_ptr->endInsertRows(); q_ptr->endInsertRows();
//Uncomment if there is issues #if 0
//qDebug() << "\n\nList:" << m_lTopLevelReverted.size() << isNew; //Uncomment if there is issues
//for (int i = 0; i<m_lTopLevelReverted.size();i++) qDebug() << "\n\nList:" << m_lTopLevelReverted.size() << isNew;
// 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 (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) void RecentModelPrivate::removeNode(RecentViewNode* n)
{ {
const int idx = n->m_Index; const int idx = n->m_Index;
const int size = m_lTopLevelReverted.size();
q_ptr->beginRemoveRows(QModelIndex(), idx, idx); q_ptr->beginRemoveRows(QModelIndex(), idx, idx);
//If this assert, the data is corrupted anyway, it will crash later on m_lTopLevelReverted.removeOne(n);
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--;
if (idx < m_lTopLevelReverted.size()) {
for (int i = 0; i <= idx; i++) {
m_lTopLevelReverted[i]->m_Index--;
}
}
q_ptr->endRemoveRows(); 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]; RecentViewNode* n = m_hPersonsToNodes[p];
const bool isNew = !n; const bool isNew = !n;
...@@ -383,9 +439,15 @@ void RecentModelPrivate::slotLastUsedTimeChanged(Person* p, time_t t) ...@@ -383,9 +439,15 @@ void RecentModelPrivate::slotLastUsedTimeChanged(Person* p, time_t t)
n->m_pParent = nullptr ; n->m_pParent = nullptr ;
n->m_Index = 0 ; n->m_Index = 0 ;
m_hPersonsToNodes[p] = n ; 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); insertNode(n, t, isNew);
slotUpdate();
} }
void RecentModelPrivate::slotLastUsedChanged(ContactMethod* cm, time_t t) void RecentModelPrivate::slotLastUsedChanged(ContactMethod* cm, time_t t)
...@@ -403,19 +465,91 @@ void RecentModelPrivate::slotLastUsedChanged(ContactMethod* cm, time_t t) ...@@ -403,19 +465,91 @@ void RecentModelPrivate::slotLastUsedChanged(ContactMethod* cm, time_t t)
n->m_Index = 0 ; n->m_Index = 0 ;
m_hCMsToNodes[cm] = n ; m_hCMsToNodes[cm] = n ;
} }
insertNode(n, t, isNew); insertNode(n, t, isNew);
slotUpdate();
} }
} }
///Remove the contact method once they are associated with a contact ///Remove the contact method once they are associated with a contact
void RecentModelPrivate::slotContactChanged(ContactMethod* cm, Person* np, Person* op) void RecentModelPrivate::slotContactChanged(ContactMethod* cm, Person* np, Person* op)
{ {
if (op) Q_UNUSED(np)
return;
RecentViewNode* n = m_hCMsToNodes[cm]; RecentViewNode* n = m_hCMsToNodes[cm];
if (n) if (n)
removeNode(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);
}
}
/************************************************************************************ /************************************************************************************
* Copyright (C) 2015 by Savoir-Faire Linux * * Copyright (C) 2015 by Savoir-faire Linux *
* Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> * * 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 * * This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public * * modify it under the terms of the GNU Lesser General Public *
...@@ -23,6 +24,7 @@ ...@@ -23,6 +24,7 @@
#include <typedefs.h> #include <typedefs.h>
class RecentModelPrivate; class RecentModelPrivate;
class Call;
class LIB_EXPORT RecentModel : public QAbstractItemModel class LIB_EXPORT RecentModel : public QAbstractItemModel
{ {
...@@ -41,6 +43,9 @@ public: ...@@ -41,6 +43,9 @@ public:
virtual QHash<int,QByteArray> roleNames() const override; virtual QHash<int,QByteArray> roleNames() const override;
static RecentModel* instance(); static RecentModel* instance();
bool hasActiveCall(const QModelIndex& parent);
Call* getActiveCall(const QModelIndex& parent);
private: private:
explicit RecentModel(QObject* parent = nullptr); explicit RecentModel(QObject* parent = nullptr);
virtual ~RecentModel(); virtual ~RecentModel();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment