Merge lp:~renatofilho/address-book-app/cpp-most-called-model into lp:~phablet-team/address-book-app/staging

Proposed by Renato Araujo Oliveira Filho
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
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 MostCalledContactsModel in C++ as a proxy model for HistoryEventModel;

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Gustavo Pichorim Boiko (boiko) wrote :

Code looks good and works as expected!

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:206
http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-ci/196/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/1502
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/1298
    SUCCESS: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-amd64-ci/196
    SUCCESS: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-armhf-ci/196
        deb: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-armhf-ci/196/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-i386-ci/196
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/1784
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/2473
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/2473/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/9204
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/1068
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/1446
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/1446/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/phablet-team-address-book-app-staging-ci/196/rebuild

review: Approve (continuous-integration)
Revision history for this message
Leo Arias (elopio) :
review: Approve (changes to qml tests)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:207
http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-ci/197/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/1506
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/1305
    SUCCESS: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-amd64-ci/197
    SUCCESS: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-armhf-ci/197
        deb: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-armhf-ci/197/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-i386-ci/197
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/1788
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/2479
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/2479/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/9213
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/1076
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/1455
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/1455/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/phablet-team-address-book-app-staging-ci/197/rebuild

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

FAILED: Autolanding.
More details in the following jenkins job:
http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-autolanding/32/
Executed test runs:
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/1508
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/1306
    SUCCESS: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-amd64-autolanding/32
    SUCCESS: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-armhf-autolanding/32
        deb: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-armhf-autolanding/32/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/phablet-team-address-book-app-staging-utopic-i386-autolanding/32
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/1790
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/2481
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/2481/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/9215
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/1077
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/1456
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/1456/artifact/work/output/*zip*/output.zip

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

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 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

Subscribers

People subscribed via source and target branches