Merge lp:~renatofilho/address-book-app/cpp-most-called-model into lp:~phablet-team/address-book-app/staging
- cpp-most-called-model
- Merge into staging
Status: | Merged |
---|---|
Approved by: | Renato Araujo Oliveira Filho |
Approved revision: | 207 |
Merged at revision: | 204 |
Proposed branch: | lp:~renatofilho/address-book-app/cpp-most-called-model |
Merge into: | lp:~phablet-team/address-book-app/staging |
Diff against target: |
825 lines (+527/-125) 15 files modified
CMakeLists.txt (+0/-4) README (+1/-1) debian/control (+1/-1) debian/rules (+0/-3) src/imports/CMakeLists.txt (+2/-0) src/imports/Ubuntu/Contacts/CMakeLists.txt (+30/-0) src/imports/Ubuntu/Contacts/ContactListView.qml (+3/-3) src/imports/Ubuntu/Contacts/MostCalledModel.qml (+20/-109) src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp (+302/-0) src/imports/Ubuntu/Contacts/mostcalledproxymodel.h (+95/-0) src/imports/Ubuntu/Contacts/plugin.cpp (+33/-0) src/imports/Ubuntu/Contacts/plugin.h (+34/-0) src/imports/Ubuntu/Contacts/qmldir (+2/-0) tests/qml/CMakeLists.txt (+3/-3) tests/qml/tst_ContactEditor.qml (+1/-1) |
To merge this branch: | bzr merge lp:~renatofilho/address-book-app/cpp-most-called-model |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Leo Arias (community) | changes to qml tests | Approve | |
Gustavo Pichorim Boiko (community) | Approve | ||
Review via email: mp+225244@code.launchpad.net |
Commit message
Implemented MostCalledConta
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:201
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:202
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:203
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Gustavo Pichorim Boiko (boiko) wrote : | # |
Code looks good and works as expected!
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:204
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:206
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Leo Arias (elopio) : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:207
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2014-06-25 19:08:39 +0000 |
3 | +++ CMakeLists.txt 2014-07-02 23:01:15 +0000 |
4 | @@ -79,7 +79,3 @@ |
5 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" |
6 | "${CMAKE_CURRENT_BINARY_DIR}/config.h" |
7 | IMMEDIATE @ONLY) |
8 | - |
9 | -add_custom_target(check |
10 | - DEPENDS test-qml |
11 | -) |
12 | |
13 | === modified file 'README' |
14 | --- README 2014-06-12 01:21:13 +0000 |
15 | +++ README 2014-07-02 23:01:15 +0000 |
16 | @@ -16,7 +16,7 @@ |
17 | ================= |
18 | |
19 | cd build |
20 | -make check |
21 | +make test or ctest |
22 | |
23 | Run the Autopilot tests |
24 | ======================= |
25 | |
26 | === modified file 'debian/control' |
27 | --- debian/control 2014-06-27 23:26:00 +0000 |
28 | +++ debian/control 2014-07-02 23:01:15 +0000 |
29 | @@ -19,6 +19,7 @@ |
30 | qt5-default, |
31 | qtbase5-dev, |
32 | qtdeclarative5-dev, |
33 | + qtpim5-dev, |
34 | xvfb, |
35 | Standards-Version: 3.9.5 |
36 | Homepage: https://launchpad.net/address-book-app |
37 | @@ -40,7 +41,6 @@ |
38 | qtdeclarative5-ubuntu-history0.1, |
39 | qtdeclarative5-ubuntu-keyboard-extensions0.1, |
40 | qtdeclarative5-ubuntu-telephony-phonenumber0.1, |
41 | - qtdeclarative5-ubuntu-telephony0.1, |
42 | ${misc:Depends}, |
43 | ${shlibs:Depends}, |
44 | Description: Address Book application |
45 | |
46 | === modified file 'debian/rules' |
47 | --- debian/rules 2014-06-13 13:59:23 +0000 |
48 | +++ debian/rules 2014-07-02 23:01:15 +0000 |
49 | @@ -17,6 +17,3 @@ |
50 | .PHONY: override_dh_strip |
51 | override_dh_strip: |
52 | dh_strip --dbg-package=address-book-app-dbg |
53 | - |
54 | -override_dh_auto_test: |
55 | - xvfb-run -a -s "-screen 0 1024x768x24" qmltestrunner -import src/imports -input tests/qml |
56 | |
57 | === modified file 'src/imports/CMakeLists.txt' |
58 | --- src/imports/CMakeLists.txt 2014-06-11 21:25:08 +0000 |
59 | +++ src/imports/CMakeLists.txt 2014-07-02 23:01:15 +0000 |
60 | @@ -1,3 +1,5 @@ |
61 | +project(imports) |
62 | + |
63 | set(ADDRESS_BOOK_APP_QMLS |
64 | MainWindow.qml |
65 | ) |
66 | |
67 | === modified file 'src/imports/Ubuntu/Contacts/CMakeLists.txt' |
68 | --- src/imports/Ubuntu/Contacts/CMakeLists.txt 2014-06-27 13:48:19 +0000 |
69 | +++ src/imports/Ubuntu/Contacts/CMakeLists.txt 2014-07-02 23:01:15 +0000 |
70 | @@ -1,3 +1,5 @@ |
71 | +set(CONTACT_COMPONENTS_PLUGIN "ubuntu-contacts-qml") |
72 | + |
73 | set(CONTACT_COMPONENTS_QMLS |
74 | ContactList.js |
75 | ContactAvatar.qml |
76 | @@ -19,9 +21,37 @@ |
77 | qmldir |
78 | ) |
79 | |
80 | +set(CONTACT_COMPONENTS_SRC |
81 | + mostcalledproxymodel.h |
82 | + mostcalledproxymodel.cpp |
83 | + plugin.h |
84 | + plugin.cpp |
85 | +) |
86 | + |
87 | +add_library(${CONTACT_COMPONENTS_PLUGIN} MODULE |
88 | + ${CONTACT_COMPONENTS_SRC} |
89 | +) |
90 | + |
91 | +qt5_use_modules(${CONTACT_COMPONENTS_PLUGIN} Core Contacts Qml Quick) |
92 | + |
93 | # make the files visible on qtcreator |
94 | add_custom_target(contact_components_QmlFiles ALL SOURCES ${CONTACT_COMPONENTS_QMLS}) |
95 | |
96 | if(INSTALL_COMPONENTS) |
97 | install(FILES ${CONTACT_COMPONENTS_QMLS} DESTINATION ${QMLPLUGIN_INSTALL_PREFIX}) |
98 | + install(TARGETS ${CONTACT_COMPONENTS_PLUGIN} DESTINATION ${QMLPLUGIN_INSTALL_PREFIX}) |
99 | +endif() |
100 | + |
101 | + |
102 | +#copy qml files to build dir to make it possible to run without install |
103 | + |
104 | +add_custom_target(copy_qml) |
105 | +foreach(QML_FILE ${CONTACT_COMPONENTS_QMLS}) |
106 | + add_custom_command(TARGET copy_qml PRE_BUILD |
107 | + COMMAND ${CMAKE_COMMAND} -E |
108 | + copy ${CMAKE_CURRENT_SOURCE_DIR}/${QML_FILE} ${CMAKE_CURRENT_BINARY_DIR}/) |
109 | +endforeach() |
110 | + |
111 | +if (NOT ${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) |
112 | + add_dependencies(${CONTACT_COMPONENTS_PLUGIN} copy_qml) |
113 | endif() |
114 | |
115 | === modified file 'src/imports/Ubuntu/Contacts/ContactListView.qml' |
116 | --- src/imports/Ubuntu/Contacts/ContactListView.qml 2014-07-02 22:41:22 +0000 |
117 | +++ src/imports/Ubuntu/Contacts/ContactListView.qml 2014-07-02 23:01:15 +0000 |
118 | @@ -441,14 +441,14 @@ |
119 | model: MostCalledModel { |
120 | id: calledModel |
121 | |
122 | - readonly property bool visible: view.favouritesIsSelected |
123 | + readonly property bool visible: view.favouritesIsSelected |
124 | |
125 | onVisibleChanged: { |
126 | + // update the model every time that it became visible |
127 | if (visible) { |
128 | - filterEntries() |
129 | + model.update() |
130 | } |
131 | } |
132 | - maxCount: 20 |
133 | onInfoRequested: root.infoRequested(contact) |
134 | onDetailClicked: root.detailClicked(contact, detail, action) |
135 | onAddContactClicked: root.addContactClicked(label) |
136 | |
137 | === modified file 'src/imports/Ubuntu/Contacts/MostCalledModel.qml' |
138 | --- src/imports/Ubuntu/Contacts/MostCalledModel.qml 2014-06-30 18:52:11 +0000 |
139 | +++ src/imports/Ubuntu/Contacts/MostCalledModel.qml 2014-07-02 23:01:15 +0000 |
140 | @@ -18,14 +18,12 @@ |
141 | import QtQuick 2.2 |
142 | import QtContacts 5.0 |
143 | import Ubuntu.History 0.1 |
144 | -import Ubuntu.Telephony 0.1 |
145 | +import Ubuntu.Contacts 0.1 |
146 | |
147 | VisualDataModel { |
148 | id: root |
149 | |
150 | - property int maxCount: 10 |
151 | property var contactModel: null |
152 | - property var historyModel |
153 | property int currentIndex: -1 |
154 | |
155 | signal clicked(int index, QtObject contact) |
156 | @@ -33,99 +31,21 @@ |
157 | signal infoRequested(int index, QtObject contact) |
158 | signal addContactClicked(string label) |
159 | |
160 | - function filterEntries() |
161 | - { |
162 | - var contacts = {} |
163 | - var interval = new Date() |
164 | - var secs = (interval.getTime() - 2592000000) // one month ago |
165 | - interval.setTime(secs) |
166 | - |
167 | - var totalCount = 0 |
168 | - var i = 0; |
169 | - while(true) { |
170 | - var event = historyModel.getItem(i) |
171 | - if (!event) { |
172 | - break |
173 | - } |
174 | - |
175 | - if (event.timestamp < interval) { |
176 | - break |
177 | - } |
178 | - |
179 | - var participants = event.participants |
180 | - for (var p=0; p < participants.length; p++) { |
181 | - var phoneNumber = participants[p] |
182 | - if (phoneNumber) { |
183 | - if (contacts[phoneNumber] === undefined) { |
184 | - contacts[phoneNumber] = 1 |
185 | - } else { |
186 | - var count = contacts[phoneNumber] |
187 | - contacts[phoneNumber] = count + 1 |
188 | - } |
189 | - totalCount += 1 |
190 | - } |
191 | - } |
192 | - i++ |
193 | - } |
194 | - |
195 | - listModel.clear() |
196 | - if (totalCount == 0) { |
197 | - return |
198 | - } |
199 | - |
200 | - // sort phones most called first |
201 | - var mostCalledFirst = [] |
202 | - for (var key in contacts) { |
203 | - mostCalledFirst.push([key, contacts[key]]); |
204 | - } |
205 | - |
206 | - mostCalledFirst.sort(function(a, b) { |
207 | - a = a[1]; |
208 | - b = b[1]; |
209 | - |
210 | - return a < b ? -1 : (a > b ? 1 : 0); |
211 | - }); |
212 | - |
213 | - contacts = {} |
214 | - for (var i = 0; i < mostCalledFirst.length; i++) { |
215 | - var key = mostCalledFirst[i][0]; |
216 | - var value = mostCalledFirst[i][1]; |
217 | - contacts[key] = value |
218 | - } |
219 | - |
220 | - // get the avarage frequency |
221 | - var average = totalCount / mostCalledFirst.length |
222 | - |
223 | - for (var phone in contacts) { |
224 | - if (contacts[phone] >= average) { |
225 | - listModel.insert(0, {"participant": phone}) |
226 | - if (listModel.count >= root.maxCount) { |
227 | - return; |
228 | - } |
229 | - } |
230 | - } |
231 | - } |
232 | - |
233 | - model: ListModel { |
234 | - id: listModel |
235 | - } |
236 | - |
237 | - historyModel: HistoryEventModel { |
238 | - |
239 | - function getItem(row) { |
240 | - while ((row >= count) && (canFetchMore())) { |
241 | - fetchMore() |
242 | - } |
243 | - return get(row) |
244 | - } |
245 | - |
246 | - type: HistoryThreadModel.EventTypeVoice |
247 | - sort: HistorySort { |
248 | - sortField: "timestamp" |
249 | - sortOrder: HistorySort.DescendingOrder |
250 | - } |
251 | - } |
252 | - |
253 | + model: MostCalledContactsModel { |
254 | + startInterval: new Date((new Date().getTime() - 2592000000)) // one month ago |
255 | + sourceModel: HistoryEventModel { |
256 | + type: HistoryThreadModel.EventTypeVoice |
257 | + sort: HistorySort { |
258 | + sortField: "timestamp" |
259 | + sortOrder: HistorySort.DescendingOrder |
260 | + } |
261 | + filter: HistoryFilter { |
262 | + filterProperty: "senderId" |
263 | + filterValue: "self" |
264 | + matchFlags: HistoryFilter.MatchCaseSensitive |
265 | + } |
266 | + } |
267 | + } |
268 | |
269 | delegate: ContactDelegate { |
270 | id: contactDelegate |
271 | @@ -138,8 +58,7 @@ |
272 | onAddContactClicked: root.addContactClicked(label) |
273 | |
274 | defaultAvatarUrl: "image://theme/contacts" |
275 | - defaultTitle: participant |
276 | - width: parent.width |
277 | + width: parent ? parent.width : 0 |
278 | titleDetail: ContactDetail.DisplayLabel |
279 | titleFields: [ DisplayLabel.Label ] |
280 | isCurrentItem: root.currentIndex === index |
281 | @@ -169,19 +88,11 @@ |
282 | } |
283 | |
284 | // delegate does not support more than one child |
285 | - contents: Item { |
286 | - ContactWatcher { |
287 | - id: contactWatcher |
288 | - |
289 | - phoneNumber: participant |
290 | - onContactIdChanged: contactFetch.fetchContact(contactId) |
291 | - } |
292 | - |
293 | - ContactFetch { |
294 | + contents: ContactFetch { |
295 | id: contactFetch |
296 | - |
297 | model: contactsModel |
298 | - } |
299 | } |
300 | + |
301 | + Component.onCompleted: contactFetch.fetchContact(contactId) |
302 | } |
303 | } |
304 | |
305 | === added file 'src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp' |
306 | --- src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp 1970-01-01 00:00:00 +0000 |
307 | +++ src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp 2014-07-02 23:01:15 +0000 |
308 | @@ -0,0 +1,302 @@ |
309 | +/* |
310 | + * Copyright (C) 2012-2013 Canonical, Ltd. |
311 | + * |
312 | + * This program is free software; you can redistribute it and/or modify |
313 | + * it under the terms of the GNU General Public License as published by |
314 | + * the Free Software Foundation; version 3. |
315 | + * |
316 | + * This program is distributed in the hope that it will be useful, |
317 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
318 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
319 | + * GNU General Public License for more details. |
320 | + * |
321 | + * You should have received a copy of the GNU General Public License |
322 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
323 | + */ |
324 | + |
325 | +#include "mostcalledproxymodel.h" |
326 | + |
327 | +#include <QtContacts/QContactManager> |
328 | +#include <QtContacts/QContactFilter> |
329 | +#include <QtContacts/QContactPhoneNumber> |
330 | + |
331 | +#include <QDebug> |
332 | + |
333 | +using namespace QtContacts; |
334 | + |
335 | +bool mostCalledContactsModelDataLessThan(const MostCalledContactsModelData &d1, const MostCalledContactsModelData &d2) |
336 | +{ |
337 | + return d1.callCount < d2.callCount; |
338 | +} |
339 | + |
340 | +MostCalledContactsModel::MostCalledContactsModel(QObject *parent) |
341 | + : QAbstractListModel(parent), |
342 | + m_sourceModel(0), |
343 | + m_manager(new QContactManager("galera")), |
344 | + m_maxCount(20), |
345 | + m_average(0), |
346 | + m_outdated(true), |
347 | + m_reloadingModel(false) |
348 | +{ |
349 | + connect(this, SIGNAL(sourceModelChanged(QAbstractItemModel*)), SLOT(markAsOutdated())); |
350 | + connect(this, SIGNAL(maxCountChanged(uint)), SLOT(markAsOutdated())); |
351 | + connect(this, SIGNAL(startIntervalChanged(QDateTime)), SLOT(markAsOutdated())); |
352 | + connect(this, SIGNAL(modelReset()), SIGNAL(outdatedChange(bool))); |
353 | +} |
354 | + |
355 | +MostCalledContactsModel::~MostCalledContactsModel() |
356 | +{ |
357 | +} |
358 | + |
359 | +QVariant MostCalledContactsModel::data(const QModelIndex &index, int role) const |
360 | +{ |
361 | + if (!index.isValid()) { |
362 | + return QVariant(); |
363 | + } |
364 | + |
365 | + int row = index.row(); |
366 | + if ((row >= 0) && (row < m_data.size())) { |
367 | + switch (role) |
368 | + { |
369 | + case MostCalledContactsModel::ContactIdRole: |
370 | + return m_data[row].contactId; |
371 | + case MostCalledContactsModel::PhoneNumberRole: |
372 | + return m_data[row].phoneNumber; |
373 | + case MostCalledContactsModel::CallCountRole: |
374 | + return m_data[row].callCount; |
375 | + default: |
376 | + return QVariant(); |
377 | + } |
378 | + } |
379 | + return QVariant(); |
380 | +} |
381 | + |
382 | +QHash<int, QByteArray> MostCalledContactsModel::roleNames() const |
383 | +{ |
384 | + static QHash<int, QByteArray> roles; |
385 | + if (roles.isEmpty()) { |
386 | + roles.insert(MostCalledContactsModel::ContactIdRole, "contactId"); |
387 | + roles.insert(MostCalledContactsModel::PhoneNumberRole, "phoneNumber"); |
388 | + roles.insert(MostCalledContactsModel::CallCountRole, "callCount"); |
389 | + } |
390 | + return roles; |
391 | +} |
392 | + |
393 | +int MostCalledContactsModel::rowCount(const QModelIndex &) const |
394 | +{ |
395 | + return m_data.size(); |
396 | +} |
397 | + |
398 | +QAbstractItemModel *MostCalledContactsModel::sourceModel() const |
399 | +{ |
400 | + return m_sourceModel; |
401 | +} |
402 | + |
403 | +void MostCalledContactsModel::setSourceModel(QAbstractItemModel *model) |
404 | +{ |
405 | + if (m_sourceModel != model) { |
406 | + if (m_sourceModel) { |
407 | + disconnect(m_sourceModel); |
408 | + } |
409 | + |
410 | + m_sourceModel = model; |
411 | + connect(m_sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(markAsOutdated())); |
412 | + connect(m_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(markAsOutdated())); |
413 | + connect(m_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(markAsOutdated())); |
414 | + connect(m_sourceModel, SIGNAL(modelReset()), SLOT(markAsOutdated())); |
415 | + |
416 | + Q_EMIT sourceModelChanged(m_sourceModel); |
417 | + } |
418 | +} |
419 | + |
420 | + |
421 | +uint MostCalledContactsModel::maxCount() const |
422 | +{ |
423 | + return m_maxCount; |
424 | +} |
425 | + |
426 | +void MostCalledContactsModel::setMaxCount(uint value) |
427 | +{ |
428 | + if (m_maxCount != value) { |
429 | + m_maxCount = value; |
430 | + Q_EMIT maxCountChanged(m_maxCount); |
431 | + } |
432 | +} |
433 | + |
434 | +int MostCalledContactsModel::callAverage() const |
435 | +{ |
436 | + return m_average; |
437 | +} |
438 | + |
439 | +QDateTime MostCalledContactsModel::startInterval() const |
440 | +{ |
441 | + return m_startInterval; |
442 | +} |
443 | + |
444 | +void MostCalledContactsModel::setStartInterval(const QDateTime &value) |
445 | +{ |
446 | + if (m_startInterval != value) { |
447 | + m_startInterval = value; |
448 | + Q_EMIT startIntervalChanged(m_startInterval); |
449 | + } |
450 | +} |
451 | + |
452 | +QVariant MostCalledContactsModel::getSourceData(int row, int role) |
453 | +{ |
454 | + QAbstractItemModel *source = sourceModel(); |
455 | + if (!source) { |
456 | + return QVariant(); |
457 | + } |
458 | + |
459 | + while ((source->rowCount() <= row) && (source->canFetchMore(QModelIndex()))) { |
460 | + source->fetchMore(QModelIndex()); |
461 | + } |
462 | + |
463 | + if (source->rowCount() < row) { |
464 | + return QVariant(); |
465 | + } |
466 | + |
467 | + QModelIndex sourceIndex = source->index(row, 0); |
468 | + return source->data(sourceIndex, role); |
469 | +} |
470 | + |
471 | +QString MostCalledContactsModel::fetchContactId(const QString &phoneNumber) |
472 | +{ |
473 | + QContactFilter filter(QContactPhoneNumber::match(phoneNumber)); |
474 | + QContactFetchHint hint; |
475 | + hint.setDetailTypesHint(QList<QContactDetail::DetailType>() << QContactDetail::TypeGuid); |
476 | + QList<QContact> contacts = m_manager->contacts(filter, QList<QContactSortOrder>() , hint); |
477 | + if (contacts.isEmpty()) { |
478 | + return QString(); |
479 | + } |
480 | + return contacts[0].id().toString(); |
481 | +} |
482 | + |
483 | +void MostCalledContactsModel::update() |
484 | +{ |
485 | + // skip update if not necessary |
486 | + if (!m_outdated || m_reloadingModel) { |
487 | + return; |
488 | + } |
489 | + |
490 | + Q_EMIT beginResetModel(); |
491 | + |
492 | + m_reloadingModel = true; |
493 | + m_outdated = false; |
494 | + m_data.clear(); |
495 | + m_average = 0; |
496 | + |
497 | + if (m_maxCount <= 0) { |
498 | + qWarning() << "update model requested with invalid maxCount"; |
499 | + Q_EMIT endResetModel(); |
500 | + m_reloadingModel = false; |
501 | + return; |
502 | + } |
503 | + |
504 | + if (!m_startInterval.isValid()) { |
505 | + qWarning() << "Update model requested with invalid startInterval"; |
506 | + Q_EMIT endResetModel(); |
507 | + m_reloadingModel = false; |
508 | + return; |
509 | + } |
510 | + |
511 | + QAbstractItemModel *source = sourceModel(); |
512 | + if (!source) { |
513 | + qWarning() << "Update model requested with null source model"; |
514 | + m_outdated = false; |
515 | + Q_EMIT endResetModel(); |
516 | + m_reloadingModel = false; |
517 | + return; |
518 | + } |
519 | + |
520 | + QHash<int, QByteArray> roles = source->roleNames(); |
521 | + int participantsRole = roles.key("participants", -1); |
522 | + int timestampRole = roles.key("timestamp", -1); |
523 | + int row = 0; |
524 | + |
525 | + Q_ASSERT(participantsRole != -1); |
526 | + Q_ASSERT(timestampRole != -1); |
527 | + |
528 | + QMap<QString, QString> phoneToContactCache; |
529 | + QMap<QString, MostCalledContactsModelData > contactsData; |
530 | + |
531 | + // get all call in the interval |
532 | + int totalCalls = 0; |
533 | + while(true) { |
534 | + QVariant date = getSourceData(row, timestampRole); |
535 | + |
536 | + // end of source model |
537 | + if (date.isNull()) { |
538 | + break; |
539 | + } |
540 | + |
541 | + // exit if date is out of interval |
542 | + if (date.toDateTime() < m_startInterval) { |
543 | + break; |
544 | + } |
545 | + |
546 | + QVariant participants = getSourceData(row, participantsRole); |
547 | + if (participants.isValid()) { |
548 | + Q_FOREACH(const QString phone, participants.toStringList()) { |
549 | + QString contactId; |
550 | + if (phoneToContactCache.contains(phone)) { |
551 | + contactId = phoneToContactCache.value(phone); |
552 | + } else { |
553 | + contactId = fetchContactId(phone); |
554 | + } |
555 | + |
556 | + // skip uknown contacts |
557 | + if (contactId.isEmpty()) { |
558 | + continue; |
559 | + } |
560 | + |
561 | + if (contactsData.contains(contactId)) { |
562 | + MostCalledContactsModelData &data = contactsData[contactId]; |
563 | + data.callCount++; |
564 | + } else { |
565 | + MostCalledContactsModelData data; |
566 | + data.contactId = contactId; |
567 | + data.phoneNumber = phone; |
568 | + data.callCount = 1; |
569 | + contactsData.insert(contactId, data); |
570 | + } |
571 | + totalCalls++; |
572 | + } |
573 | + } |
574 | + row++; |
575 | + } |
576 | + |
577 | + if (!contactsData.isEmpty()) { |
578 | + // sort by callCount |
579 | + QList<MostCalledContactsModelData> data = contactsData.values(); |
580 | + qSort(data.begin(), data.end(), mostCalledContactsModelDataLessThan); |
581 | + |
582 | + // average |
583 | + m_average = totalCalls / contactsData.size(); |
584 | + |
585 | + Q_FOREACH(const MostCalledContactsModelData &d, data) { |
586 | + if (d.callCount >= m_average) { |
587 | + m_data << d; |
588 | + } |
589 | + if ((uint) m_data.size() > m_maxCount) { |
590 | + break; |
591 | + } |
592 | + } |
593 | + } |
594 | + |
595 | + Q_EMIT endResetModel(); |
596 | + m_reloadingModel = false; |
597 | +} |
598 | + |
599 | +void MostCalledContactsModel::markAsOutdated() |
600 | +{ |
601 | + // skip if model is being reloaded |
602 | + if (m_reloadingModel) { |
603 | + return; |
604 | + } |
605 | + |
606 | + if (!m_outdated) { |
607 | + m_outdated = true; |
608 | + Q_EMIT outdatedChange(m_outdated); |
609 | + } |
610 | +} |
611 | |
612 | === added file 'src/imports/Ubuntu/Contacts/mostcalledproxymodel.h' |
613 | --- src/imports/Ubuntu/Contacts/mostcalledproxymodel.h 1970-01-01 00:00:00 +0000 |
614 | +++ src/imports/Ubuntu/Contacts/mostcalledproxymodel.h 2014-07-02 23:01:15 +0000 |
615 | @@ -0,0 +1,95 @@ |
616 | +/* |
617 | + * Copyright (C) 2012-2013 Canonical, Ltd. |
618 | + * |
619 | + * This program is free software; you can redistribute it and/or modify |
620 | + * it under the terms of the GNU General Public License as published by |
621 | + * the Free Software Foundation; version 3. |
622 | + * |
623 | + * This program is distributed in the hope that it will be useful, |
624 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
625 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
626 | + * GNU General Public License for more details. |
627 | + * |
628 | + * You should have received a copy of the GNU General Public License |
629 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
630 | + */ |
631 | + |
632 | +#ifndef __MOSTCALLEDCONTACTSMODEL_H__ |
633 | +#define __MOSTCALLEDCONTACTSMODEL_H__ |
634 | + |
635 | +#include <QtCore/QAbstractListModel> |
636 | +#include <QtCore/QScopedPointer> |
637 | +#include <QtCore/QDateTime> |
638 | + |
639 | +#include <QtContacts/QContactManager> |
640 | + |
641 | +struct MostCalledContactsModelData |
642 | +{ |
643 | + QString contactId; |
644 | + QString phoneNumber; |
645 | + int callCount; |
646 | +}; |
647 | + |
648 | +class MostCalledContactsModel : public QAbstractListModel |
649 | +{ |
650 | + Q_OBJECT |
651 | + Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged) |
652 | + Q_PROPERTY(uint maxCount READ maxCount WRITE setMaxCount NOTIFY maxCountChanged) |
653 | + Q_PROPERTY(int callAverage READ callAverage NOTIFY callAverageChanged) |
654 | + Q_PROPERTY(QDateTime startInterval READ startInterval WRITE setStartInterval NOTIFY startIntervalChanged) |
655 | + Q_PROPERTY(bool outdated READ outdated NOTIFY outdatedChange) |
656 | + |
657 | +public: |
658 | + enum Role { |
659 | + ContactIdRole = 0, |
660 | + PhoneNumberRole, |
661 | + CallCountRole |
662 | + }; |
663 | + |
664 | + MostCalledContactsModel(QObject *parent=0); |
665 | + ~MostCalledContactsModel(); |
666 | + |
667 | + QVariant data(const QModelIndex &index, int role) const; |
668 | + QHash<int, QByteArray> roleNames() const; |
669 | + int rowCount(const QModelIndex&) const; |
670 | + |
671 | + QAbstractItemModel *sourceModel() const; |
672 | + void setSourceModel(QAbstractItemModel *model); |
673 | + |
674 | + uint maxCount() const; |
675 | + void setMaxCount(uint value); |
676 | + |
677 | + int callAverage() const; |
678 | + bool outdated() const; |
679 | + |
680 | + QDateTime startInterval() const; |
681 | + void setStartInterval(const QDateTime &value); |
682 | + |
683 | + Q_INVOKABLE void update(); |
684 | + |
685 | +Q_SIGNALS: |
686 | + void maxCountChanged(uint value); |
687 | + void callAverageChanged(int value); |
688 | + void startIntervalChanged(const QDateTime &value); |
689 | + void sourceModelChanged(QAbstractItemModel *value); |
690 | + void outdatedChange(bool value); |
691 | + |
692 | +private Q_SLOTS: |
693 | + void markAsOutdated(); |
694 | + |
695 | +private: |
696 | + QAbstractItemModel *m_sourceModel; |
697 | + QScopedPointer<QtContacts::QContactManager> m_manager; |
698 | + QList<MostCalledContactsModelData> m_data; |
699 | + uint m_maxCount; |
700 | + int m_average; |
701 | + QDateTime m_startInterval; |
702 | + bool m_outdated; |
703 | + bool m_reloadingModel; |
704 | + |
705 | + QString fetchContactId(const QString &phoneNumber); |
706 | + QVariant getSourceData(int row, int role); |
707 | +}; |
708 | + |
709 | + |
710 | +#endif |
711 | |
712 | === added file 'src/imports/Ubuntu/Contacts/plugin.cpp' |
713 | --- src/imports/Ubuntu/Contacts/plugin.cpp 1970-01-01 00:00:00 +0000 |
714 | +++ src/imports/Ubuntu/Contacts/plugin.cpp 2014-07-02 23:01:15 +0000 |
715 | @@ -0,0 +1,33 @@ |
716 | +/* |
717 | + * Copyright (C) 2012-2013 Canonical, Ltd. |
718 | + * |
719 | + * This program is free software; you can redistribute it and/or modify |
720 | + * it under the terms of the GNU General Public License as published by |
721 | + * the Free Software Foundation; version 3. |
722 | + * |
723 | + * This program is distributed in the hope that it will be useful, |
724 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
725 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
726 | + * GNU General Public License for more details. |
727 | + * |
728 | + * You should have received a copy of the GNU General Public License |
729 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
730 | + */ |
731 | + |
732 | +#include "plugin.h" |
733 | +#include "mostcalledproxymodel.h" |
734 | + |
735 | +#include <QQmlEngine> |
736 | +#include <qqml.h> |
737 | + |
738 | +void UbuntuContacts::initializeEngine(QQmlEngine *engine, const char *uri) |
739 | +{ |
740 | + Q_UNUSED(engine); |
741 | + Q_UNUSED(uri); |
742 | +} |
743 | + |
744 | +void UbuntuContacts::registerTypes(const char *uri) |
745 | +{ |
746 | + // @uri Ubuntu.Contacts |
747 | + qmlRegisterType<MostCalledContactsModel>(uri, 0, 1, "MostCalledContactsModel"); |
748 | +} |
749 | |
750 | === added file 'src/imports/Ubuntu/Contacts/plugin.h' |
751 | --- src/imports/Ubuntu/Contacts/plugin.h 1970-01-01 00:00:00 +0000 |
752 | +++ src/imports/Ubuntu/Contacts/plugin.h 2014-07-02 23:01:15 +0000 |
753 | @@ -0,0 +1,34 @@ |
754 | +/* |
755 | + * Copyright (C) 2012-2013 Canonical, Ltd. |
756 | + * |
757 | + * This program is free software; you can redistribute it and/or modify |
758 | + * it under the terms of the GNU General Public License as published by |
759 | + * the Free Software Foundation; version 3. |
760 | + * |
761 | + * This program is distributed in the hope that it will be useful, |
762 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
763 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
764 | + * GNU General Public License for more details. |
765 | + * |
766 | + * You should have received a copy of the GNU General Public License |
767 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
768 | + */ |
769 | + |
770 | + |
771 | +#ifndef _UBUNTU_CONTACTS_H_ |
772 | +#define _UBUNTU_CONTACTS_H_ |
773 | + |
774 | +#include <QQmlContext> |
775 | +#include <QQmlExtensionPlugin> |
776 | + |
777 | +class UbuntuContacts : public QQmlExtensionPlugin |
778 | +{ |
779 | + Q_OBJECT |
780 | + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") |
781 | + |
782 | +public: |
783 | + void initializeEngine(QQmlEngine *engine, const char *uri); |
784 | + void registerTypes(const char *uri); |
785 | +}; |
786 | + |
787 | +#endif //_UBUNTU_CONTACTS_H_ |
788 | |
789 | === modified file 'src/imports/Ubuntu/Contacts/qmldir' |
790 | --- src/imports/Ubuntu/Contacts/qmldir 2014-06-27 19:26:22 +0000 |
791 | +++ src/imports/Ubuntu/Contacts/qmldir 2014-07-02 23:01:15 +0000 |
792 | @@ -1,5 +1,7 @@ |
793 | module Ubuntu.Contacts |
794 | |
795 | +plugin ubuntu-contacts-qml |
796 | + |
797 | ContactAvatar 0.1 ContactAvatar.qml |
798 | ContactDetailOnlineAccountTypeModel 0.1 ContactDetailOnlineAccountTypeModel.qml |
799 | ContactDetailPhoneNumberTypeModel 0.1 ContactDetailPhoneNumberTypeModel.qml |
800 | |
801 | === modified file 'tests/qml/CMakeLists.txt' |
802 | --- tests/qml/CMakeLists.txt 2014-05-29 16:54:46 +0000 |
803 | +++ tests/qml/CMakeLists.txt 2014-07-02 23:01:15 +0000 |
804 | @@ -1,4 +1,4 @@ |
805 | -add_custom_target(test-qml |
806 | - COMMAND QML2_IMPORT_PATH=../../src/imports/ qmltestrunner |
807 | - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} |
808 | +add_test(NAME qmltestrunner |
809 | + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} |
810 | + COMMAND xvfb-run -a -s "-screen 0 1024x768x24" qmltestrunner -import ${imports_BINARY_DIR} -input ${CMAKE_SOURCE_DIR}/tests/qml |
811 | ) |
812 | |
813 | === modified file 'tests/qml/tst_ContactEditor.qml' |
814 | --- tests/qml/tst_ContactEditor.qml 2014-05-29 14:42:37 +0000 |
815 | +++ tests/qml/tst_ContactEditor.qml 2014-07-02 23:01:15 +0000 |
816 | @@ -18,9 +18,9 @@ |
817 | import QtTest 1.0 |
818 | import Ubuntu.Components 0.1 |
819 | import Ubuntu.Test 0.1 |
820 | +import Ubuntu.Contacts 0.1 |
821 | |
822 | import '../../src/imports/ContactEdit' |
823 | -import '../../src/imports/Ubuntu/Contacts' |
824 | |
825 | Item { |
826 |
FAILED: Continuous integration, rev:199 jenkins. qa.ubuntu. com/job/ phablet- team-address- book-app- staging- ci/187/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- utopic- touch/1443/ console jenkins. qa.ubuntu. com/job/ generic- mediumtests- utopic/ 1243/console jenkins. qa.ubuntu. com/job/ phablet- team-address- book-app- staging- utopic- amd64-ci/ 187/console jenkins. qa.ubuntu. com/job/ phablet- team-address- book-app- staging- utopic- armhf-ci/ 187/console jenkins. qa.ubuntu. com/job/ phablet- team-address- book-app- staging- utopic- i386-ci/ 187/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- utopic- armhf/2403/ console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- utopic- amd64/1389/ console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/phablet- team-address- book-app- staging- ci/187/ rebuild
http://