Merge lp:~boiko/history-service/contact_aware_models into lp:history-service
- contact_aware_models
- Merge into trunk
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 | ||||
Related bugs: |
|
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:/
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
- 168. By Gustavo Pichorim Boiko
-
Break long line.
PS Jenkins bot (ps-jenkins) wrote : | # |
- 169. By Gustavo Pichorim Boiko
-
Fetch only the required data.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:169
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Renato Araujo Oliveira Filho (renatofilho) : | # |
Renato Araujo Oliveira Filho (renatofilho) : | # |
- 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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:172
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:174
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 175. By Gustavo Pichorim Boiko
-
Properly delete stuff on destruction.
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
looks good now
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
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:175
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 176. By Gustavo Pichorim Boiko
-
Revert the change to debian/control.
Preview Diff
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 |
PASSED: Continuous integration, rev:168 jenkins. qa.ubuntu. com/job/ history- service- ci/146/ jenkins. qa.ubuntu. com/job/ history- service- utopic- amd64-ci/ 41 jenkins. qa.ubuntu. com/job/ history- service- utopic- armhf-ci/ 41 jenkins. qa.ubuntu. com/job/ history- service- utopic- armhf-ci/ 41/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ history- service- utopic- i386-ci/ 41
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/history- service- ci/146/ rebuild
http://