Merge lp:~boiko/history-service/contact_aware_models into lp:history-service

Proposed by Gustavo Pichorim Boiko
Status: Merged
Approved by: Renato Araujo Oliveira Filho
Approved revision: 175
Merged at revision: 169
Proposed branch: lp:~boiko/history-service/contact_aware_models
Merge into: lp:history-service
Diff against target: 798 lines (+504/-10)
10 files modified
CMakeLists.txt (+1/-0)
Ubuntu/History/CMakeLists.txt (+3/-1)
Ubuntu/History/contactmatcher.cpp (+289/-0)
Ubuntu/History/contactmatcher_p.h (+65/-0)
Ubuntu/History/historyeventmodel.cpp (+62/-4)
Ubuntu/History/historyeventmodel.h (+9/-1)
Ubuntu/History/historythreadmodel.cpp (+57/-3)
Ubuntu/History/historythreadmodel.h (+8/-1)
debian/control (+2/-0)
src/types.h (+8/-0)
To merge this branch: bzr merge lp:~boiko/history-service/contact_aware_models
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Renato Araujo Oliveira Filho (community) Approve
Review via email: mp+233860@code.launchpad.net

Commit message

Add support for matching contact info in the data models.

Description of the change

Add support for matching contact info in the data models.

== Checklist ==
Are there any related MPs required for this MP to build/function as expected? Please list.
No

Is your branch in sync with latest trunk (e.g. bzr pull lp:trunk -> no changes)
Yes

Did you perform an exploratory manual test run of your code change and any related functionality on device or emulator?
Yes

Did you successfully run all tests found in your component's Test Plan (https://wiki.ubuntu.com/Process/Merges/TestPlan/<package-name>) on device or emulator?
Yes

If you changed the UI, was the change specified/approved by design?
N/A

If you changed UI labels, did you update the pot file?
N/A

If you changed the packaging (debian), did you add a core-dev as a reviewer to this MP?
N/A

To post a comment you must log in.
168. By Gustavo Pichorim Boiko

Break long line.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
169. By Gustavo Pichorim Boiko

Fetch only the required data.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) :
Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) :
review: Needs Fixing
170. By Gustavo Pichorim Boiko

Invalidate the cache when the manager changes.

171. By Gustavo Pichorim Boiko

Fix typo.

172. By Gustavo Pichorim Boiko

Create the singleton using static memory to make sure it is properly destroyed
on app finishing, and make its destructor private to prevent other places from
removing it.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gustavo Pichorim Boiko (boiko) wrote :

Diff comments replied.

173. By Gustavo Pichorim Boiko

Remove leftover comment line.

174. By Gustavo Pichorim Boiko

Keep versioning number consistent.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
175. By Gustavo Pichorim Boiko

Properly delete stuff on destruction.

Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) wrote :

looks good now

review: Approve
Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) wrote :

Did you perform an exploratory manual test run of the code change and any related functionality on device or emulator? YES

Did CI run pass? YES

Have you checked that submitter has accurately filled out the submitter checklist and has taken no shortcut? YES

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
176. By Gustavo Pichorim Boiko

Revert the change to debian/control.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2013-11-27 21:04:38 +0000
3+++ CMakeLists.txt 2014-09-12 21:16:24 +0000
4@@ -29,6 +29,7 @@
5 # Instruct CMake to run moc automatically when needed.
6 set(CMAKE_AUTOMOC ON)
7
8+find_package(Qt5Contacts)
9 find_package(Qt5Core)
10 find_package(Qt5DBus)
11 find_package(Qt5Qml)
12
13=== modified file 'Ubuntu/History/CMakeLists.txt'
14--- Ubuntu/History/CMakeLists.txt 2014-08-12 13:54:32 +0000
15+++ Ubuntu/History/CMakeLists.txt 2014-09-12 21:16:24 +0000
16@@ -1,6 +1,7 @@
17 # QML plugin
18
19 set(plugin_SRCS
20+ contactmatcher.cpp
21 historyeventmodel.cpp
22 historygroupedeventsmodel.cpp
23 historythreadgroupingproxymodel.cpp
24@@ -15,6 +16,7 @@
25 )
26
27 set(plugin_HDRS
28+ contactmatcher_p.h
29 historyeventmodel.h
30 historygroupedeventsmodel.h
31 historythreadgroupingproxymodel.h
32@@ -34,7 +36,7 @@
33
34 add_library(history-qml SHARED ${plugin_SRCS} ${plugin_HDRS})
35
36-qt5_use_modules(history-qml Core Qml Quick)
37+qt5_use_modules(history-qml Contacts Core Qml Quick)
38
39 target_link_libraries(history-qml
40 historyservice
41
42=== added file 'Ubuntu/History/contactmatcher.cpp'
43--- Ubuntu/History/contactmatcher.cpp 1970-01-01 00:00:00 +0000
44+++ Ubuntu/History/contactmatcher.cpp 2014-09-12 21:16:24 +0000
45@@ -0,0 +1,289 @@
46+/*
47+ * Copyright (C) 2014 Canonical, Ltd.
48+ *
49+ * Authors:
50+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
51+ *
52+ * This file is part of history-service.
53+ *
54+ * history-service is free software; you can redistribute it and/or modify
55+ * it under the terms of the GNU General Public License as published by
56+ * the Free Software Foundation; version 3.
57+ *
58+ * history-service is distributed in the hope that it will be useful,
59+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
60+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
61+ * GNU General Public License for more details.
62+ *
63+ * You should have received a copy of the GNU General Public License
64+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
65+ */
66+
67+#include "contactmatcher_p.h"
68+#include "phoneutils_p.h"
69+#include "types.h"
70+#include <QContact>
71+#include <QContactAvatar>
72+#include <QContactDisplayLabel>
73+#include <QContactFilter>
74+#include <QContactPhoneNumber>
75+
76+using namespace QtContacts;
77+
78+ContactMatcher::ContactMatcher(QObject *parent) :
79+ QObject(parent), mManager(new QContactManager("galera"))
80+{
81+ connect(mManager,
82+ SIGNAL(contactsAdded(QList<QContactId>)),
83+ SLOT(onContactsAdded(QList<QContactId>)));
84+ connect(mManager,
85+ SIGNAL(contactsChanged(QList<QContactId>)),
86+ SLOT(onContactsChanged(QList<QContactId>)));
87+ connect(mManager,
88+ SIGNAL(contactsRemoved(QList<QContactId>)),
89+ SLOT(onContactsRemoved(QList<QContactId>)));
90+ connect(mManager,
91+ SIGNAL(dataChanged()),
92+ SLOT(onDataChanged()));
93+}
94+
95+ContactMatcher::~ContactMatcher()
96+{
97+ Q_FOREACH(QContactFetchRequest *request, mRequests.keys()) {
98+ request->deleteLater();
99+ }
100+ mRequests.clear();
101+ mContactMap.clear();
102+ mManager->deleteLater();
103+}
104+
105+ContactMatcher *ContactMatcher::instance()
106+{
107+ static ContactMatcher self;
108+ return &self;
109+}
110+
111+QVariantMap ContactMatcher::contactInfo(const QString &phoneNumber)
112+{
113+ // first do a simple string match on the map
114+ if (mContactMap.contains(phoneNumber)) {
115+ return mContactMap[phoneNumber];
116+ }
117+
118+ // now if there was no string match, try phone number matching
119+ Q_FOREACH(const QString &key, mContactMap.keys()) {
120+ if (PhoneUtils::comparePhoneNumbers(key, phoneNumber)) {
121+ return mContactMap[key];
122+ }
123+ }
124+
125+ // and if there was no match, asynchronously request the info, and return an empty map for now
126+ requestContactInfo(phoneNumber);
127+ QVariantMap map;
128+ map[History::FieldPhoneNumber] = phoneNumber;
129+ return map;
130+}
131+
132+QVariantList ContactMatcher::contactInfo(const QStringList &numbers)
133+{
134+ QVariantList contacts;
135+ Q_FOREACH(const QString &number, numbers) {
136+ contacts << contactInfo(number);
137+ }
138+ return contacts;
139+}
140+
141+void ContactMatcher::onContactsAdded(QList<QContactId> ids)
142+{
143+ QList<QContact> contacts = mManager->contacts(ids);
144+
145+ // walk through the list of requested phone numbers
146+ ContactMap::iterator it = mContactMap.begin();
147+ ContactMap::iterator end = mContactMap.end();
148+ for (; it != end; ++it) {
149+ QString phoneNumber = it.key();
150+ // skip entries that already have a match
151+ if (it.value().contains(History::FieldContactId)) {
152+ continue;
153+ }
154+
155+ // now for each entry not populated, check if it matches one of the newly added contacts
156+ bool found = false;
157+ Q_FOREACH(const QContact &contact, contacts) {
158+ Q_FOREACH(const QContactPhoneNumber number, contact.details(QContactDetail::TypePhoneNumber)) {
159+ if (PhoneUtils::comparePhoneNumbers(number.number(), phoneNumber)) {
160+ found = true;
161+ populateInfo(phoneNumber, contact);
162+ break;
163+ }
164+ }
165+
166+ if (found) {
167+ break;
168+ }
169+ }
170+ }
171+}
172+
173+void ContactMatcher::onContactsChanged(QList<QContactId> ids)
174+{
175+ QStringList phoneNumbersToMatch;
176+
177+ QList<QContact> contacts = mManager->contacts(ids);
178+ // walk through the list of requested phone numbers
179+ ContactMap::iterator it = mContactMap.begin();
180+ ContactMap::iterator end = mContactMap.end();
181+ for (; it != end; ++it) {
182+ QVariantMap &contactInfo = it.value();
183+ QString phoneNumber = it.key();
184+
185+ Q_FOREACH(const QContact &contact, contacts) {
186+ bool previousMatch = (contactInfo[History::FieldContactId].toString() == contact.id().toString());
187+ bool found = false;
188+ Q_FOREACH(const QContactPhoneNumber number, contact.details(QContactDetail::TypePhoneNumber)) {
189+ if (PhoneUtils::comparePhoneNumbers(number.number(), phoneNumber)) {
190+ found = true;
191+ break;
192+ }
193+ }
194+
195+ if (found) {
196+ populateInfo(phoneNumber, contact);
197+ break;
198+ } else if (previousMatch) {
199+ // if there was a previous match but it does not match anymore, try to match the phone number
200+ // to a different contact
201+ phoneNumbersToMatch << phoneNumber;
202+ break;
203+ }
204+ }
205+ }
206+
207+ Q_FOREACH(const QString &phoneNumber, phoneNumbersToMatch) {
208+ mContactMap.remove(phoneNumber);
209+ requestContactInfo(phoneNumber);
210+ }
211+}
212+
213+void ContactMatcher::onContactsRemoved(QList<QContactId> ids)
214+{
215+ QStringList phoneNumbersToMatch;
216+
217+ // search for entries that were matching this contact
218+ ContactMap::iterator it = mContactMap.begin();
219+ ContactMap::iterator end = mContactMap.end();
220+ for (; it != end; ++it) {
221+ // skip entries that didn't have a match
222+ if (!it.value().contains(History::FieldContactId)) {
223+ continue;
224+ }
225+
226+ Q_FOREACH(const QContactId &id, ids) {
227+ if (id.toString() == it.value()[History::FieldContactId].toString()) {
228+ phoneNumbersToMatch << it.key();
229+ break;
230+ }
231+ }
232+ }
233+
234+ // now make sure to try a new match on the phone numbers whose contact was removed
235+ Q_FOREACH(const QString &phoneNumber, phoneNumbersToMatch) {
236+ mContactMap.remove(phoneNumber);
237+ requestContactInfo(phoneNumber);
238+ }
239+}
240+
241+void ContactMatcher::onDataChanged()
242+{
243+ // invalidate the cache
244+ QStringList phoneNumbers = mContactMap.keys();
245+ mContactMap.clear();
246+
247+ Q_FOREACH(const QString &phoneNumber, phoneNumbers) {
248+ QVariantMap info;
249+ info[History::FieldPhoneNumber] = phoneNumber;
250+ Q_EMIT contactInfoChanged(phoneNumber, info);
251+ }
252+}
253+
254+void ContactMatcher::onRequestStateChanged(QContactAbstractRequest::State state)
255+{
256+ QContactFetchRequest *request = qobject_cast<QContactFetchRequest*>(sender());
257+ if (!request) {
258+ return;
259+ }
260+
261+ if (!mRequests.contains(request)) {
262+ request->deleteLater();
263+ return;
264+ }
265+
266+ if (state == QContactAbstractRequest::FinishedState) {
267+ request->deleteLater();
268+
269+ QString phoneNumber = mRequests.take(request);
270+ QContact contact;
271+ if (!request->contacts().isEmpty()) {
272+ contact = request->contacts().first();
273+ }
274+ populateInfo(phoneNumber, contact);
275+ } else if (state == QContactAbstractRequest::CanceledState) {
276+ request->deleteLater();
277+ mRequests.remove(request);
278+ }
279+
280+}
281+
282+void ContactMatcher::requestContactInfo(const QString &phoneNumber)
283+{
284+ // check if there is a request already going on for the given contact
285+ Q_FOREACH(const QString number, mRequests.values()) {
286+ if (PhoneUtils::comparePhoneNumbers(number, phoneNumber)) {
287+ // if so, just wait for it to finish
288+ return;
289+ }
290+ }
291+
292+ QContactFetchRequest *request = new QContactFetchRequest(this);
293+ QContactFetchHint hint;
294+ hint.setDetailTypesHint(QList<QContactDetail::DetailType>() << QContactDetail::TypeDisplayLabel
295+ << QContactDetail::TypePhoneNumber
296+ << QContactDetail::TypeAvatar);
297+ request->setFetchHint(hint);
298+ request->setFilter(QContactPhoneNumber::match(phoneNumber));
299+ request->setManager(mManager);
300+ connect(request,
301+ SIGNAL(stateChanged(QContactAbstractRequest::State)),
302+ SLOT(onRequestStateChanged(QContactAbstractRequest::State)));
303+
304+ mRequests[request] = phoneNumber;
305+ request->start();
306+}
307+
308+QVariantList ContactMatcher::toVariantList(const QList<int> &list)
309+{
310+ QVariantList variantList;
311+ Q_FOREACH(int value, list) {
312+ variantList << value;
313+ }
314+ return variantList;
315+}
316+
317+void ContactMatcher::populateInfo(const QString &phoneNumber, const QContact &contact)
318+{
319+ QVariantMap contactInfo;
320+ contactInfo[History::FieldPhoneNumber] = phoneNumber;
321+ if (!contact.isEmpty()) {
322+ contactInfo[History::FieldContactId] = contact.id().toString();
323+ contactInfo[History::FieldAlias] = QContactDisplayLabel(contact.detail(QContactDetail::TypeDisplayLabel)).label();
324+ contactInfo[History::FieldAvatar] = QContactAvatar(contact.detail(QContactDetail::TypeAvatar)).imageUrl().toString();
325+ Q_FOREACH(const QContactPhoneNumber number, contact.details(QContactDetail::TypePhoneNumber)) {
326+ if (PhoneUtils::comparePhoneNumbers(number.number(), phoneNumber)) {
327+ contactInfo[History::FieldPhoneSubTypes] = toVariantList(number.subTypes());
328+ contactInfo[History::FieldPhoneContexts] = toVariantList(number.contexts());
329+ }
330+ }
331+ }
332+ mContactMap[phoneNumber] = contactInfo;
333+ Q_EMIT contactInfoChanged(phoneNumber, contactInfo);
334+}
335
336=== added file 'Ubuntu/History/contactmatcher_p.h'
337--- Ubuntu/History/contactmatcher_p.h 1970-01-01 00:00:00 +0000
338+++ Ubuntu/History/contactmatcher_p.h 2014-09-12 21:16:24 +0000
339@@ -0,0 +1,65 @@
340+/*
341+ * Copyright (C) 2014 Canonical, Ltd.
342+ *
343+ * Authors:
344+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
345+ *
346+ * This file is part of history-service.
347+ *
348+ * history-service is free software; you can redistribute it and/or modify
349+ * it under the terms of the GNU General Public License as published by
350+ * the Free Software Foundation; version 3.
351+ *
352+ * history-service is distributed in the hope that it will be useful,
353+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
354+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
355+ * GNU General Public License for more details.
356+ *
357+ * You should have received a copy of the GNU General Public License
358+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
359+ */
360+
361+#ifndef CONTACTMATCHER_P_H
362+#define CONTACTMATCHER_P_H
363+
364+#include <QObject>
365+#include <QVariantMap>
366+#include <QContactFetchRequest>
367+#include <QContactManager>
368+
369+using namespace QtContacts;
370+
371+typedef QMap<QString, QVariantMap> ContactMap;
372+class ContactMatcher : public QObject
373+{
374+ Q_OBJECT
375+public:
376+ static ContactMatcher *instance();
377+ QVariantMap contactInfo(const QString &phoneNumber);
378+ QVariantList contactInfo(const QStringList &numbers);
379+
380+Q_SIGNALS:
381+ void contactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo);
382+
383+protected Q_SLOTS:
384+ void onContactsAdded(QList<QContactId> ids);
385+ void onContactsChanged(QList<QContactId> ids);
386+ void onContactsRemoved(QList<QContactId> ids);
387+ void onDataChanged();
388+ void onRequestStateChanged(QContactAbstractRequest::State state);
389+
390+protected:
391+ void requestContactInfo(const QString &phoneNumber);
392+ QVariantList toVariantList(const QList<int> &list);
393+ void populateInfo(const QString &phoneNumber, const QContact &contact);
394+
395+private:
396+ explicit ContactMatcher(QObject *parent = 0);
397+ ~ContactMatcher();
398+
399+ ContactMap mContactMap;
400+ QMap<QContactFetchRequest*, QString> mRequests;
401+ QContactManager *mManager;
402+};
403+
404+#endif // CONTACTMATCHER_P_H
405
406=== modified file 'Ubuntu/History/historyeventmodel.cpp'
407--- Ubuntu/History/historyeventmodel.cpp 2014-08-18 19:12:14 +0000
408+++ Ubuntu/History/historyeventmodel.cpp 2014-09-12 21:16:24 +0000
409@@ -1,5 +1,5 @@
410 /*
411- * Copyright (C) 2013 Canonical, Ltd.
412+ * Copyright (C) 2013-2014 Canonical, Ltd.
413 *
414 * Authors:
415 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
416@@ -29,8 +29,11 @@
417 #include "textevent.h"
418 #include "texteventattachment.h"
419 #include "historyqmltexteventattachment.h"
420+#include "phoneutils_p.h"
421 #include "thread.h"
422+#include "types.h"
423 #include "voiceevent.h"
424+#include "contactmatcher_p.h"
425 #include <QDebug>
426 #include <QTimerEvent>
427 #include <QDBusMetaType>
428@@ -38,7 +41,7 @@
429 HistoryEventModel::HistoryEventModel(QObject *parent) :
430 QAbstractListModel(parent), mCanFetchMore(true), mFilter(0),
431 mSort(new HistoryQmlSort(this)), mType(HistoryThreadModel::EventTypeText),
432- mEventWritingTimer(0), mUpdateTimer(0)
433+ mMatchContacts(false), mEventWritingTimer(0), mUpdateTimer(0)
434 {
435 connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SIGNAL(countChanged()));
436 connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(countChanged()));
437@@ -54,6 +57,7 @@
438 mRoles[TimestampRole] = "timestamp";
439 mRoles[DateRole] = "date";
440 mRoles[NewEventRole] = "newEvent";
441+ mRoles[PropertiesRole] = "properties";
442 mRoles[TextMessageRole] = "textMessage";
443 mRoles[TextMessageTypeRole] = "textMessageType";
444 mRoles[TextMessageStatusRole] = "textMessageStatus";
445@@ -63,6 +67,10 @@
446 mRoles[CallMissedRole] = "callMissed";
447 mRoles[CallDurationRole] = "callDuration";
448
449+ connect(ContactMatcher::instance(),
450+ SIGNAL(contactInfoChanged(QString,QVariantMap)),
451+ SLOT(onContactInfoChanged(QString,QVariantMap)));
452+
453 // create the view and get some objects
454 triggerQueryUpdate();
455 }
456@@ -89,7 +97,6 @@
457 {
458 History::TextEvent textEvent;
459 History::VoiceEvent voiceEvent;
460- History::Thread thread;
461
462 switch (event.type()) {
463 case History::EventTypeText:
464@@ -110,7 +117,11 @@
465 result = event.threadId();
466 break;
467 case ParticipantsRole:
468- result = event.participants();
469+ if (mMatchContacts) {
470+ result = ContactMatcher::instance()->contactInfo(event.participants());
471+ } else {
472+ result = event.participants();
473+ }
474 break;
475 case TypeRole:
476 result = event.type();
477@@ -130,6 +141,9 @@
478 case NewEventRole:
479 result = event.newEvent();
480 break;
481+ case PropertiesRole:
482+ result = event.properties();
483+ break;
484 case TextMessageRole:
485 if (!textEvent.isNull()) {
486 result = textEvent.message();
487@@ -274,6 +288,22 @@
488 triggerQueryUpdate();
489 }
490
491+bool HistoryEventModel::matchContacts() const
492+{
493+ return mMatchContacts;
494+}
495+
496+void HistoryEventModel::setMatchContacts(bool value)
497+{
498+ mMatchContacts = value;
499+ Q_EMIT matchContactsChanged();
500+
501+ // mark all indexes as changed
502+ if (rowCount() > 0) {
503+ Q_EMIT dataChanged(index(0), index(rowCount()-1));
504+ }
505+}
506+
507 QString HistoryEventModel::threadIdForParticipants(const QString &accountId, int eventType, const QStringList &participants, int matchFlags, bool create)
508 {
509 if (participants.isEmpty()) {
510@@ -459,6 +489,34 @@
511 // should be handle internally in History::EventView?
512 }
513
514+void HistoryEventModel::onContactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo)
515+{
516+ Q_UNUSED(contactInfo)
517+ if (!mMatchContacts) {
518+ return;
519+ }
520+
521+ QList<QModelIndex> changedIndexes;
522+ int count = rowCount();
523+ for (int i = 0; i < count; ++i) {
524+ // WARNING: do not use mEvents directly to verify which indexes to change as there is the
525+ // HistoryGroupedEventsModel which is based on this model and handles the items in a different way
526+ QModelIndex idx = index(i);
527+ QVariantMap eventProperties = idx.data(PropertiesRole).toMap();
528+ QStringList participants = eventProperties[History::FieldParticipants].toStringList();
529+ Q_FOREACH(const QString &participant, participants) {
530+ if (PhoneUtils::comparePhoneNumbers(participant, phoneNumber)) {
531+ changedIndexes << idx;
532+ }
533+ }
534+ }
535+
536+ // now emit the dataChanged signal to all changed indexes
537+ Q_FOREACH(const QModelIndex &idx, changedIndexes) {
538+ Q_EMIT dataChanged(idx, idx);
539+ }
540+}
541+
542 void HistoryEventModel::timerEvent(QTimerEvent *event)
543 {
544 if (event->timerId() == mEventWritingTimer) {
545
546=== modified file 'Ubuntu/History/historyeventmodel.h'
547--- Ubuntu/History/historyeventmodel.h 2014-08-18 19:12:14 +0000
548+++ Ubuntu/History/historyeventmodel.h 2014-09-12 21:16:24 +0000
549@@ -1,5 +1,5 @@
550 /*
551- * Copyright (C) 2013 Canonical, Ltd.
552+ * Copyright (C) 2013-2014 Canonical, Ltd.
553 *
554 * Authors:
555 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
556@@ -33,6 +33,7 @@
557 Q_PROPERTY(HistoryQmlFilter *filter READ filter WRITE setFilter NOTIFY filterChanged)
558 Q_PROPERTY(HistoryQmlSort *sort READ sort WRITE setSort NOTIFY sortChanged)
559 Q_PROPERTY(HistoryThreadModel::EventType type READ type WRITE setType NOTIFY typeChanged)
560+ Q_PROPERTY(bool matchContacts READ matchContacts WRITE setMatchContacts NOTIFY matchContactsChanged)
561 Q_PROPERTY(bool canFetchMore READ canFetchMore NOTIFY canFetchMoreChanged)
562 Q_ENUMS(Role)
563 public:
564@@ -46,6 +47,7 @@
565 TimestampRole,
566 DateRole,
567 NewEventRole,
568+ PropertiesRole,
569 TextMessageRole,
570 TextMessageTypeRole,
571 TextMessageStatusRole,
572@@ -77,6 +79,9 @@
573 HistoryThreadModel::EventType type() const;
574 void setType(HistoryThreadModel::EventType value);
575
576+ bool matchContacts() const;
577+ void setMatchContacts(bool value);
578+
579 Q_INVOKABLE QString threadIdForParticipants(const QString &accountId,
580 int eventType,
581 const QStringList &participants,
582@@ -94,6 +99,7 @@
583 void filterChanged();
584 void sortChanged();
585 void typeChanged();
586+ void matchContactsChanged();
587 void canFetchMoreChanged();
588
589 protected Q_SLOTS:
590@@ -102,6 +108,7 @@
591 virtual void onEventsAdded(const History::Events &events);
592 virtual void onEventsModified(const History::Events &events);
593 virtual void onEventsRemoved(const History::Events &events);
594+ void onContactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo);
595
596 protected:
597 void timerEvent(QTimerEvent *event);
598@@ -114,6 +121,7 @@
599 HistoryQmlFilter *mFilter;
600 HistoryQmlSort *mSort;
601 HistoryThreadModel::EventType mType;
602+ bool mMatchContacts;
603 QHash<int, QByteArray> mRoles;
604 mutable QMap<History::TextEvent, QList<QVariant> > mAttachmentCache;
605 History::Events mEventWritingQueue;
606
607=== modified file 'Ubuntu/History/historythreadmodel.cpp'
608--- Ubuntu/History/historythreadmodel.cpp 2014-08-18 19:12:14 +0000
609+++ Ubuntu/History/historythreadmodel.cpp 2014-09-12 21:16:24 +0000
610@@ -1,5 +1,5 @@
611 /*
612- * Copyright (C) 2013 Canonical, Ltd.
613+ * Copyright (C) 2013-2014 Canonical, Ltd.
614 *
615 * Authors:
616 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
617@@ -29,6 +29,8 @@
618 #include "texteventattachment.h"
619 #include "historyqmltexteventattachment.h"
620 #include "voiceevent.h"
621+#include "contactmatcher_p.h"
622+#include "phoneutils_p.h"
623 #include <QDebug>
624 #include <QTimerEvent>
625
626@@ -36,7 +38,7 @@
627
628 HistoryThreadModel::HistoryThreadModel(QObject *parent) :
629 QAbstractListModel(parent), mCanFetchMore(true), mFilter(0), mSort(0),
630- mType(EventTypeText), mUpdateTimer(0)
631+ mType(EventTypeText), mMatchContacts(false), mUpdateTimer(0)
632 {
633 // configure the roles
634 mRoles[AccountIdRole] = "accountId";
635@@ -65,6 +67,9 @@
636 connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(countChanged()));
637 connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(countChanged()));
638 connect(this, SIGNAL(modelReset()), SIGNAL(countChanged()));
639+ connect(ContactMatcher::instance(),
640+ SIGNAL(contactInfoChanged(QString,QVariantMap)),
641+ SLOT(onContactInfoChanged(QString,QVariantMap)));
642
643 // create the results view
644 triggerQueryUpdate();
645@@ -113,7 +118,11 @@
646 result = (int) thread.type();
647 break;
648 case ParticipantsRole:
649- result = thread.participants();
650+ if (mMatchContacts) {
651+ result = ContactMatcher::instance()->contactInfo(thread.participants());
652+ } else {
653+ result = thread.participants();
654+ }
655 break;
656 case CountRole:
657 result = thread.count();
658@@ -292,6 +301,22 @@
659 triggerQueryUpdate();
660 }
661
662+bool HistoryThreadModel::matchContacts() const
663+{
664+ return mMatchContacts;
665+}
666+
667+void HistoryThreadModel::setMatchContacts(bool value)
668+{
669+ mMatchContacts = value;
670+ Q_EMIT matchContactsChanged();
671+
672+ // mark all indexes as changed
673+ if (rowCount() > 0) {
674+ Q_EMIT dataChanged(index(0), index(rowCount()-1));
675+ }
676+}
677+
678 QString HistoryThreadModel::threadIdForParticipants(const QString &accountId, int eventType, const QStringList &participants, int matchFlags, bool create)
679 {
680 if (participants.isEmpty()) {
681@@ -439,3 +464,32 @@
682 updateQuery();
683 }
684 }
685+
686+
687+void HistoryThreadModel::onContactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo)
688+{
689+ Q_UNUSED(contactInfo)
690+ if (!mMatchContacts) {
691+ return;
692+ }
693+
694+ QList<QModelIndex> changedIndexes;
695+ int count = rowCount();
696+ for (int i = 0; i < count; ++i) {
697+ // WARNING: do not use mEvents directly to verify which indexes to change as there is the
698+ // HistoryGroupedEventsModel which is based on this model and handles the items in a different way
699+ QModelIndex idx = index(i);
700+ QVariantMap eventProperties = idx.data(PropertiesRole).toMap();
701+ QStringList participants = eventProperties[History::FieldParticipants].toStringList();
702+ Q_FOREACH(const QString &participant, participants) {
703+ if (PhoneUtils::comparePhoneNumbers(participant, phoneNumber)) {
704+ changedIndexes << idx;
705+ }
706+ }
707+ }
708+
709+ // now emit the dataChanged signal to all changed indexes
710+ Q_FOREACH(const QModelIndex &idx, changedIndexes) {
711+ Q_EMIT dataChanged(idx, idx);
712+ }
713+}
714
715=== modified file 'Ubuntu/History/historythreadmodel.h'
716--- Ubuntu/History/historythreadmodel.h 2014-08-18 19:12:14 +0000
717+++ Ubuntu/History/historythreadmodel.h 2014-09-12 21:16:24 +0000
718@@ -1,5 +1,5 @@
719 /*
720- * Copyright (C) 2013 Canonical, Ltd.
721+ * Copyright (C) 2013-2014 Canonical, Ltd.
722 *
723 * Authors:
724 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
725@@ -36,6 +36,7 @@
726 Q_PROPERTY(HistoryQmlFilter *filter READ filter WRITE setFilter NOTIFY filterChanged)
727 Q_PROPERTY(HistoryQmlSort *sort READ sort WRITE setSort NOTIFY sortChanged)
728 Q_PROPERTY(EventType type READ type WRITE setType NOTIFY typeChanged)
729+ Q_PROPERTY(bool matchContacts READ matchContacts WRITE setMatchContacts NOTIFY matchContactsChanged)
730 Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
731 Q_PROPERTY(bool canFetchMore READ canFetchMore NOTIFY canFetchMoreChanged)
732 Q_ENUMS(EventType)
733@@ -109,6 +110,9 @@
734 EventType type() const;
735 void setType(EventType value);
736
737+ bool matchContacts() const;
738+ void setMatchContacts(bool value);
739+
740 Q_INVOKABLE QString threadIdForParticipants(const QString &accountId,
741 int eventType,
742 const QStringList &participants,
743@@ -121,6 +125,7 @@
744 void filterChanged();
745 void sortChanged();
746 void typeChanged();
747+ void matchContactsChanged();
748 void countChanged();
749 void canFetchMoreChanged();
750
751@@ -130,6 +135,7 @@
752 void onThreadsAdded(const History::Threads &threads);
753 void onThreadsModified(const History::Threads &threads);
754 void onThreadsRemoved(const History::Threads &threads);
755+ void onContactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo);
756
757 protected:
758 void timerEvent(QTimerEvent *event);
759@@ -141,6 +147,7 @@
760 HistoryQmlFilter *mFilter;
761 HistoryQmlSort *mSort;
762 EventType mType;
763+ bool mMatchContacts;
764 QHash<int, QByteArray> mRoles;
765 mutable QMap<History::TextEvent, QList<QVariant> > mAttachmentCache;
766 int mUpdateTimer;
767
768=== modified file 'debian/control'
769--- debian/control 2013-12-12 19:22:03 +0000
770+++ debian/control 2014-09-12 21:16:24 +0000
771@@ -17,6 +17,8 @@
772 qt5-default,
773 qtbase5-dev (>= 5.0),
774 qtdeclarative5-dev (>= 5.0),
775+# version 5.0~git... is not greater or equal 5.0, so leave it as 5
776+ qtpim5-dev (>= 5),
777 sqlite3,
778 telepathy-mission-control-5,
779 Standards-Version: 3.9.4
780
781=== modified file 'src/types.h'
782--- src/types.h 2014-08-16 22:11:30 +0000
783+++ src/types.h 2014-09-12 21:16:24 +0000
784@@ -140,6 +140,14 @@
785 static const char* FieldMatchFlags = "matchFlags";
786 static const char* FieldFilters = "filters";
787
788+// contact matching stuff
789+static const char* FieldContactId = "contactId";
790+static const char* FieldAlias = "alias";
791+static const char* FieldAvatar = "avatar";
792+static const char* FieldPhoneNumber = "phoneNumber";
793+static const char* FieldPhoneSubTypes = "phoneSubTypes";
794+static const char* FieldPhoneContexts = "phoneContexts";
795+
796 }
797
798 #endif // TYPES_H

Subscribers

People subscribed via source and target branches