1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 The Qt Company Ltd. |
4 | ** Contact: http://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL21$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 or version 3 as published by the Free |
20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and |
21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the |
22 | ** following information to ensure the GNU Lesser General Public License |
23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and |
24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
25 | ** |
26 | ** As a special exception, The Qt Company gives you certain additional |
27 | ** rights. These rights are described in The Qt Company LGPL Exception |
28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
29 | ** |
30 | ** $QT_END_LICENSE$ |
31 | ** |
32 | ****************************************************************************/ |
33 | |
34 | #include "qdeclarativecontactmodel_p.h" |
35 | |
36 | #include <QtCore/qfile.h> |
37 | #include <QtCore/qhash.h> |
38 | #include <QtCore/qmap.h> |
39 | #include <QtCore/qpointer.h> |
40 | #include <QtCore/qurl.h> |
41 | #include <QtCore/qmimedatabase.h> |
42 | #include <QtCore/qmimetype.h> |
43 | #include <QtCore/qtemporaryfile.h> |
44 | #include <QtCore/qdir.h> |
45 | |
46 | #include <QtGui/qcolor.h> |
47 | #include <QtGui/qpixmap.h> |
48 | |
49 | #include <QtQml/qqmlinfo.h> |
50 | #include <QtQml/qqmlengine.h> |
51 | |
52 | #include <QtContacts/qcontactdetails.h> |
53 | #include <QtContacts/qcontactmanager.h> |
54 | #include <QtContacts/qcontactmanagerengine.h> |
55 | #include <QtContacts/qcontactdetailfilter.h> |
56 | #include <QtContacts/qcontactidfilter.h> |
57 | #include <QtContacts/qcontactintersectionfilter.h> |
58 | #include <QtContacts/qcontactrequests.h> |
59 | |
60 | #include <QtVersit/qversitreader.h> |
61 | #include <QtVersit/qversitwriter.h> |
62 | #include <QtVersit/qversitcontactimporter.h> |
63 | #include <QtVersit/qversitcontactexporter.h> |
64 | |
65 | QTCONTACTS_USE_NAMESPACE |
66 | QTVERSIT_USE_NAMESPACE |
67 | |
68 | QT_BEGIN_NAMESPACE |
69 | |
70 | /*! |
71 | \qmltype ContactModel |
72 | \instantiates QDeclarativeContactModel |
73 | \brief The ContactModel element provides access to contacts from the contacts store. |
74 | \ingroup qml-contacts-main |
75 | \inqmlmodule QtContacts |
76 | |
77 | This element is part of the \b{QtContacts} module. |
78 | |
79 | ContactModel provides a model of contacts from the contacts store. |
80 | The contents of the model can be specified with \l filter, \l sortOrders and \l fetchHint properties. |
81 | Whether the model is automatically updated when the store or \l contacts changes, can be |
82 | controlled with \l ContactModel::autoUpdate property. |
83 | |
84 | There are two ways of accessing the contact data: via model by using views and delegates, |
85 | or alternatively via \l contacts list property. Of the two, the model access is preferred. |
86 | Direct list access (i.e. non-model) is not guaranteed to be in order set by \l sortOrder. |
87 | |
88 | At the moment the model roles provided by ContactModel are display, decoration and \c contact. |
89 | Through the \c contact role can access any data provided by the Contact element. |
90 | |
91 | \sa RelationshipModel, Contact, {QContactManager} |
92 | */ |
93 | |
94 | // Helper class to store contact binary data into a temporary file |
95 | // |
96 | // QContactVcard only supports URL values for images. During a vcard import if the contact cotains |
97 | // an avatar, ringtone or any property formated in a binary data, QVersit will use the |
98 | // ContactExporterResourceHandler to store the binary data. |
99 | // The default implementation of QVersitResourceHandler does not store any data and that |
100 | // causes avatar data loss during the import process. |
101 | // This class will store the data into a temporary file and removes the file when the model gets destroyed. |
102 | class ContactExporterResourceHandler : public QVersitResourceHandler |
103 | { |
104 | public: |
105 | ContactExporterResourceHandler() |
106 | { |
107 | } |
108 | |
109 | ~ContactExporterResourceHandler() |
110 | { |
111 | foreach (const QString& fileName, m_files) |
112 | QFile::remove(fileName); |
113 | |
114 | m_files.clear(); |
115 | } |
116 | |
117 | bool saveResource(const QByteArray& contents, |
118 | const QVersitProperty& property, |
119 | QString* location) |
120 | { |
121 | const QMimeType mt = QMimeDatabase().mimeTypeForData(data: contents); |
122 | QString extension(QStringLiteral("data" )); |
123 | if (mt.isValid()) |
124 | extension = mt.suffixes()[0]; |
125 | |
126 | // use property.name() to create a new file for each binary property (avatar, ringtone, etc...) |
127 | QTemporaryFile tmpFile(QString::fromLatin1(str: "%1/%2_XXXXXX.%3" ) |
128 | .arg(a: QDir::tempPath()) |
129 | .arg(a: property.name().toLower()) |
130 | .arg(a: extension)); |
131 | tmpFile.setAutoRemove(false); |
132 | if (tmpFile.open()) { |
133 | // the location expect a string in url format ex: file:///tmp/filename.png |
134 | *location = QUrl::fromLocalFile(localfile: tmpFile.fileName()).toString(); |
135 | m_files << *location; |
136 | tmpFile.write(data: contents); |
137 | tmpFile.close(); |
138 | return true; |
139 | } |
140 | return false; |
141 | } |
142 | |
143 | bool loadResource(const QString &location, QByteArray *contents, QString *mimeType) |
144 | { |
145 | if (location.isEmpty()) |
146 | return false; |
147 | |
148 | QFile file(location); |
149 | if (!file.open(flags: QIODevice::ReadOnly)) |
150 | return false; |
151 | |
152 | *contents = file.readAll(); |
153 | const QMimeType mt = QMimeDatabase().mimeTypeForData(data: *contents); |
154 | if (mt.isValid()) |
155 | *mimeType = mt.suffixes()[0]; |
156 | |
157 | return !contents->isEmpty(); |
158 | } |
159 | |
160 | QStringList m_files; |
161 | }; |
162 | |
163 | |
164 | class QDeclarativeContactModelPrivate |
165 | { |
166 | public: |
167 | QDeclarativeContactModelPrivate() |
168 | :m_manager(0), |
169 | m_fetchHint(0), |
170 | m_filter(0), |
171 | m_error(QContactManager::NoError), |
172 | m_autoUpdate(true), |
173 | m_componentCompleted(false), |
174 | m_progressiveLoading(true), |
175 | m_updatePendingFlag(QDeclarativeContactModelPrivate::NonePending) |
176 | { |
177 | } |
178 | ~QDeclarativeContactModelPrivate() |
179 | { |
180 | if (m_manager) |
181 | delete m_manager; |
182 | } |
183 | |
184 | enum UpdateTypePending { |
185 | NonePending = 0x0, |
186 | UpdatingContactsPending = 0x1, |
187 | UpdatingCollectionsPending = 0x2 |
188 | }; |
189 | |
190 | QList<QDeclarativeContact*> m_contacts; |
191 | QMap<QContactId, QDeclarativeContact*> m_contactMap; |
192 | QMap<QContactId, QDeclarativeContact*> m_contactFetchedMap; |
193 | QContactManager* m_manager; |
194 | QDeclarativeContactFetchHint* m_fetchHint; |
195 | QList<QDeclarativeContactSortOrder*> m_sortOrders; |
196 | QDeclarativeContactFilter* m_filter; |
197 | |
198 | QVersitReader m_reader; |
199 | QVersitWriter m_writer; |
200 | QStringList m_importProfiles; |
201 | ContactExporterResourceHandler m_resourceHandler; |
202 | |
203 | QContactManager::Error m_error; |
204 | |
205 | bool m_autoUpdate; |
206 | bool m_componentCompleted; |
207 | QUrl m_lastExportUrl; |
208 | QUrl m_lastImportUrl; |
209 | QAtomicInt m_lastRequestId; |
210 | QHash<QContactAbstractRequest *, int> m_requestIdHash; |
211 | QList<QContactFetchRequest*> m_pendingRequests; |
212 | QList<QContact> m_pendingContacts; |
213 | QList<QDeclarativeContactCollection*> m_collections; |
214 | bool m_progressiveLoading; |
215 | int m_updatePendingFlag; |
216 | }; |
217 | |
218 | QDeclarativeContactModel::QDeclarativeContactModel(QObject *parent) : |
219 | QAbstractListModel(parent), |
220 | d(new QDeclarativeContactModelPrivate) |
221 | { |
222 | QHash<int, QByteArray> roleNames; |
223 | roleNames = QAbstractItemModel::roleNames(); |
224 | roleNames.insert(key: ContactRole, value: "contact" ); |
225 | setRoleNames(roleNames); |
226 | |
227 | connect(asender: this, SIGNAL(managerChanged()), SLOT(doUpdate())); |
228 | connect(asender: this, SIGNAL(filterChanged()), SLOT(doContactUpdate())); |
229 | connect(asender: this, SIGNAL(fetchHintChanged()), SLOT(doContactUpdate())); |
230 | connect(asender: this, SIGNAL(sortOrdersChanged()), SLOT(doContactUpdate())); |
231 | |
232 | //import vcard |
233 | connect(sender: &d->m_reader, SIGNAL(stateChanged(QVersitReader::State)), receiver: this, SLOT(startImport(QVersitReader::State))); |
234 | connect(sender: &d->m_writer, SIGNAL(stateChanged(QVersitWriter::State)), receiver: this, SLOT(contactsExported(QVersitWriter::State))); |
235 | } |
236 | |
237 | QDeclarativeContactModel::~QDeclarativeContactModel() |
238 | { |
239 | } |
240 | |
241 | /*! |
242 | \qmlproperty string ContactModel::manager |
243 | |
244 | This property holds the manager uri of the contact backend engine. |
245 | */ |
246 | QString QDeclarativeContactModel::manager() const |
247 | { |
248 | if (d->m_manager) |
249 | return d->m_manager->managerName(); |
250 | return QString(); |
251 | } |
252 | void QDeclarativeContactModel::setManager(const QString& managerName) |
253 | { |
254 | if (d->m_manager && (managerName == d->m_manager->managerName() || managerName == d->m_manager->managerUri())) |
255 | return; |
256 | |
257 | if (d->m_manager) { |
258 | cancelUpdate(); |
259 | delete d->m_manager; |
260 | } |
261 | |
262 | d->m_manager = new QContactManager(managerName); |
263 | |
264 | connect(sender: d->m_manager, SIGNAL(dataChanged()), receiver: this, SLOT(doUpdate())); |
265 | connect(sender: d->m_manager, SIGNAL(contactsAdded(QList<QContactId>)), receiver: this, SLOT(onContactsAdded(QList<QContactId>))); |
266 | connect(sender: d->m_manager, SIGNAL(contactsRemoved(QList<QContactId>)), receiver: this, SLOT(onContactsRemoved(QList<QContactId>))); |
267 | connect(sender: d->m_manager, SIGNAL(contactsChanged(QList<QContactId>,QList<QContactDetail::DetailType>)), receiver: this, SLOT(onContactsChanged(QList<QContactId>))); |
268 | connect(sender: d->m_manager, SIGNAL(collectionsAdded(QList<QContactCollectionId>)), receiver: this, SLOT(fetchCollections())); |
269 | connect(sender: d->m_manager, SIGNAL(collectionsChanged(QList<QContactCollectionId>)), receiver: this, SLOT(fetchCollections())); |
270 | connect(sender: d->m_manager, SIGNAL(collectionsRemoved(QList<QContactCollectionId>)), receiver: this, SLOT(fetchCollections())); |
271 | |
272 | |
273 | if (d->m_error != QContactManager::NoError) { |
274 | d->m_error = QContactManager::NoError; |
275 | emit errorChanged(); |
276 | } |
277 | |
278 | emit managerChanged(); |
279 | } |
280 | |
281 | void QDeclarativeContactModel::componentComplete() |
282 | { |
283 | if (!d->m_manager) |
284 | setManager(QString()); |
285 | |
286 | d->m_componentCompleted = true; |
287 | |
288 | if (d->m_autoUpdate) |
289 | update(); |
290 | } |
291 | /*! |
292 | \qmlproperty bool ContactModel::autoUpdate |
293 | |
294 | This property indicates whether or not the contact model should be updated automatically, default value is true. |
295 | */ |
296 | void QDeclarativeContactModel::setAutoUpdate(bool autoUpdate) |
297 | { |
298 | if (autoUpdate == d->m_autoUpdate) |
299 | return; |
300 | d->m_autoUpdate = autoUpdate; |
301 | emit autoUpdateChanged(); |
302 | } |
303 | |
304 | bool QDeclarativeContactModel::autoUpdate() const |
305 | { |
306 | return d->m_autoUpdate; |
307 | } |
308 | |
309 | void QDeclarativeContactModel::update() |
310 | { |
311 | if (!d->m_componentCompleted || d->m_updatePendingFlag) |
312 | return; |
313 | // Disallow possible duplicate request triggering |
314 | d->m_updatePendingFlag = (QDeclarativeContactModelPrivate::UpdatingContactsPending | QDeclarativeContactModelPrivate::UpdatingCollectionsPending); |
315 | QMetaObject::invokeMethod(obj: this, member: "fetchCollections" , type: Qt::QueuedConnection); |
316 | } |
317 | |
318 | /*! |
319 | \qmlmethod ContactModel::updateContacts() |
320 | |
321 | Manually update the contact model contacts. |
322 | |
323 | \sa ContactModel::update |
324 | \sa ContactModel::updateCollections |
325 | \sa ContactModel::autoUpdate |
326 | */ |
327 | void QDeclarativeContactModel::updateContacts() |
328 | { |
329 | if (!d->m_componentCompleted || d->m_updatePendingFlag) |
330 | return; |
331 | // Disallow possible duplicate request triggering |
332 | d->m_updatePendingFlag = QDeclarativeContactModelPrivate::UpdatingContactsPending; |
333 | QMetaObject::invokeMethod(obj: this, member: "fetchAgain" , type: Qt::QueuedConnection); |
334 | } |
335 | |
336 | /*! |
337 | \qmlmethod ContactModel::updateCollections() |
338 | |
339 | Manually update the contact model collections. |
340 | |
341 | \sa ContactModel::update |
342 | \sa ContactModel::updateContacts |
343 | \sa ContactModel::autoUpdate |
344 | */ |
345 | void QDeclarativeContactModel::updateCollections() |
346 | { |
347 | if (!d->m_componentCompleted || d->m_updatePendingFlag) |
348 | return; |
349 | // Disallow possible duplicate request triggering |
350 | d->m_updatePendingFlag = QDeclarativeContactModelPrivate::UpdatingCollectionsPending; |
351 | QMetaObject::invokeMethod(obj: this, member: "fetchCollections" , type: Qt::QueuedConnection); |
352 | } |
353 | |
354 | /*! |
355 | \qmlmethod ContactModel::cancelUpdate() |
356 | |
357 | Cancel the running contact model content update request. |
358 | |
359 | \sa ContactModel::autoUpdate |
360 | \sa ContactModel::update |
361 | */ |
362 | void QDeclarativeContactModel::cancelUpdate() |
363 | { |
364 | foreach (QContactFetchRequest *req, d->m_pendingRequests) { |
365 | req->cancel(); |
366 | req->deleteLater(); |
367 | } |
368 | d->m_pendingRequests.clear();; |
369 | d->m_updatePendingFlag = QDeclarativeContactModelPrivate::NonePending; |
370 | } |
371 | |
372 | void QDeclarativeContactModel::doContactUpdate() |
373 | { |
374 | if (d->m_autoUpdate) |
375 | updateContacts(); |
376 | } |
377 | |
378 | /*! |
379 | \qmlproperty string ContactModel::error |
380 | |
381 | This property holds the latest error code returned by the contact manager. |
382 | |
383 | This property is read only. |
384 | */ |
385 | QString QDeclarativeContactModel::error() const |
386 | { |
387 | if (d->m_manager) { |
388 | switch (d->m_error) { |
389 | case QContactManager::DoesNotExistError: |
390 | return QStringLiteral("DoesNotExist" ); |
391 | case QContactManager::AlreadyExistsError: |
392 | return QStringLiteral("AlreadyExists" ); |
393 | case QContactManager::InvalidDetailError: |
394 | return QStringLiteral("InvalidDetail" ); |
395 | case QContactManager::InvalidRelationshipError: |
396 | return QStringLiteral("InvalidRelationship" ); |
397 | case QContactManager::LockedError: |
398 | return QStringLiteral("LockedError" ); |
399 | case QContactManager::DetailAccessError: |
400 | return QStringLiteral("DetailAccessError" ); |
401 | case QContactManager::PermissionsError: |
402 | return QStringLiteral("PermissionsError" ); |
403 | case QContactManager::OutOfMemoryError: |
404 | return QStringLiteral("OutOfMemory" ); |
405 | case QContactManager::NotSupportedError: |
406 | return QStringLiteral("NotSupported" ); |
407 | case QContactManager::BadArgumentError: |
408 | return QStringLiteral("BadArgument" ); |
409 | case QContactManager::UnspecifiedError: |
410 | return QStringLiteral("UnspecifiedError" ); |
411 | case QContactManager::VersionMismatchError: |
412 | return QStringLiteral("VersionMismatch" ); |
413 | case QContactManager::LimitReachedError: |
414 | return QStringLiteral("LimitReached" ); |
415 | case QContactManager::InvalidContactTypeError: |
416 | return QStringLiteral("InvalidContactType" ); |
417 | default: |
418 | break; |
419 | } |
420 | } |
421 | return QStringLiteral("NoError" ); |
422 | } |
423 | |
424 | |
425 | /*! |
426 | \qmlproperty list<string> ContactModel::availableManagers |
427 | |
428 | This property holds the list of available manager names. |
429 | This property is read only. |
430 | */ |
431 | QStringList QDeclarativeContactModel::availableManagers() const |
432 | { |
433 | return QContactManager::availableManagers(); |
434 | } |
435 | static QString urlToLocalFileName(const QUrl& url) |
436 | { |
437 | if (!url.isValid()) { |
438 | return url.toString(); |
439 | } else if (url.scheme() == "qrc" ) { |
440 | return url.toString().remove(i: 0, len: 5).prepend(c: ':'); |
441 | } else { |
442 | return url.toLocalFile(); |
443 | } |
444 | |
445 | } |
446 | |
447 | /*! |
448 | \qmlproperty enumeration ContactModel::ImportError |
449 | |
450 | Defines the errors cases for \l ContactModel::importContacts() -function. |
451 | |
452 | \list |
453 | \li ContactModel::ImportNoError Completed successfully, no error. |
454 | \li ContactModel::ImportUnspecifiedError Unspecified error. |
455 | \li ContactModel::ImportIOError Input/output error. |
456 | \li ContactModel::ImportOutOfMemoryError Out of memory error. |
457 | \li ContactModel::ImportNotReadyError Not ready for importing. Only one import operation can be active at a time. |
458 | \li ContactModel::ImportParseError Error during parsing. |
459 | \endlist |
460 | */ |
461 | |
462 | /*! |
463 | \qmlsignal ContactModel::onImportCompleted(ImportError error, URL url, list<string> ids) |
464 | |
465 | This signal is emitted, when \l ContactModel::importContacts() completes. The success of operation |
466 | can be seen on \a error which is defined in \l ContactModel::ImportError. \a url indicates the |
467 | file, which was imported. \a ids contains the imported contacts ids. |
468 | |
469 | If the operation was successful, contacts are now imported to backend. If \l ContactModel::autoUpdate |
470 | is enabled, \l ContactModel::modelChanged will be emitted when imported contacts are also visible on |
471 | \l ContactModel's data model. |
472 | |
473 | \sa ContactModel::importContacts |
474 | */ |
475 | |
476 | /*! |
477 | \qmlmethod void ContactModel::importContacts(url url, list<string> profiles) |
478 | |
479 | Import contacts from a vcard by the given \a url and optional \a profiles. |
480 | Only one import operation can be active at a time. |
481 | Supported profiles are: |
482 | \list |
483 | \li "Sync" Imports contacts in sync mode, currently, this is the same as passing in an empty list, and is generally what you want. |
484 | \li "Backup" imports contacts in backup mode, use this mode if the vCard was generated by exporting in backup mode. |
485 | |
486 | \endlist |
487 | |
488 | \sa QVersitContactHandlerFactory |
489 | \sa QVersitContactHandlerFactory::ProfileSync() |
490 | \sa QVersitContactHandlerFactory::ProfileBackup() |
491 | |
492 | */ |
493 | void QDeclarativeContactModel::importContacts(const QUrl& url, const QStringList& profiles) |
494 | { |
495 | // Reader is capable of handling only one request at the time. |
496 | ImportError importError = ImportNotReadyError; |
497 | if (d->m_reader.state() != QVersitReader::ActiveState) { |
498 | |
499 | d->m_importProfiles = profiles; |
500 | |
501 | //TODO: need to allow download vcard from network |
502 | QFile* file = new QFile(urlToLocalFileName(url)); |
503 | bool ok = file->open(flags: QIODevice::ReadOnly); |
504 | if (ok) { |
505 | d->m_reader.setDevice(file); |
506 | if (d->m_reader.startReading()) { |
507 | d->m_lastImportUrl = url; |
508 | return; |
509 | } |
510 | importError = QDeclarativeContactModel::ImportError(d->m_reader.error()); |
511 | } else { |
512 | importError = ImportIOError; |
513 | } |
514 | } |
515 | emit importCompleted(error: importError, url, ids: QStringList()); |
516 | } |
517 | |
518 | /*! |
519 | \qmlmethod void ContactModel::exportContacts(url url, list<string> profiles, list<variant> declarativeContacts) |
520 | |
521 | Export all contacts of this model into a vcard file to the given \a url by optional \a profiles. |
522 | The optional \a declarativeContacts list can be used to export an arbitrary list of QDeclarativeContact objects |
523 | not necessarily belonging to the data set of this model. |
524 | At the moment only the local file url is supported in export method. |
525 | Also, only one export operation can be active at a time. |
526 | Supported profiles are: |
527 | \list |
528 | \li "Sync" exports contacts in sync mode, currently, this is the same as passing in an empty list, and is generally what you want. |
529 | \li "Backup" exports contacts in backup mode, this will add non-standard properties to the generated vCard |
530 | to try to save every detail of the contacts. Only use this if the vCard is going to be imported using the backup profile. |
531 | #include "moc_qdeclarativecontactmodel_p.cpp" |
532 | \endlist |
533 | |
534 | \sa QVersitContactHandlerFactory |
535 | \sa QVersitContactHandlerFactory::ProfileSync() |
536 | \sa QVersitContactHandlerFactory::ProfileBackup() |
537 | */ |
538 | void QDeclarativeContactModel::exportContacts(const QUrl& url, const QStringList& profiles, const QVariantList &declarativeContacts) |
539 | { |
540 | // Writer is capable of handling only one request at the time. |
541 | ExportError exportError = ExportNotReadyError; |
542 | if (d->m_writer.state() != QVersitWriter::ActiveState) { |
543 | QString profile = profiles.isEmpty()? QString() : profiles.at(i: 0); |
544 | //only one profile string supported now. |
545 | QVersitContactExporter exporter(profile); |
546 | exporter.setResourceHandler(&d->m_resourceHandler); |
547 | |
548 | QList<QContact> contacts; |
549 | if (declarativeContacts.isEmpty()) { |
550 | foreach (QDeclarativeContact* dc, d->m_contacts) { |
551 | contacts.append(t: dc->contact()); |
552 | } |
553 | |
554 | } else { |
555 | foreach (const QVariant &contactVariant, declarativeContacts) { |
556 | QObject *rawObject = contactVariant.value<QObject*>(); |
557 | QDeclarativeContact *dc = qobject_cast<QDeclarativeContact*>(object: rawObject); |
558 | if (dc) { |
559 | contacts.append(t: dc->contact()); |
560 | } |
561 | } |
562 | } |
563 | |
564 | exporter.exportContacts(contacts, versitType: QVersitDocument::VCard30Type); |
565 | QList<QVersitDocument> documents = exporter.documents(); |
566 | QFile* file = new QFile(urlToLocalFileName(url)); |
567 | bool ok = file->open(flags: QIODevice::WriteOnly); |
568 | if (ok) { |
569 | d->m_writer.setDevice(file); |
570 | if (d->m_writer.startWriting(input: documents)) { |
571 | d->m_lastExportUrl = url; |
572 | return; |
573 | } |
574 | exportError = QDeclarativeContactModel::ExportError(d->m_writer.error()); |
575 | } else { |
576 | exportError = ExportIOError; |
577 | } |
578 | } |
579 | emit exportCompleted(error: exportError, url); |
580 | } |
581 | |
582 | void QDeclarativeContactModel::contactsExported(QVersitWriter::State state) |
583 | { |
584 | if (state == QVersitWriter::FinishedState || state == QVersitWriter::CanceledState) { |
585 | delete d->m_writer.device(); |
586 | d->m_writer.setDevice(0); |
587 | emit exportCompleted(error: QDeclarativeContactModel::ExportError(d->m_writer.error()), url: d->m_lastExportUrl); |
588 | } |
589 | } |
590 | |
591 | void QDeclarativeContactModel::onFetchedContactDestroyed(QObject *obj) |
592 | { |
593 | QContactId id = d->m_contactFetchedMap.key(avalue: static_cast<QDeclarativeContact*>(obj)); |
594 | if (!id.isNull()) |
595 | d->m_contactFetchedMap.remove(key: id); |
596 | } |
597 | |
598 | int QDeclarativeContactModel::rowCount(const QModelIndex &parent) const |
599 | { |
600 | Q_UNUSED(parent); |
601 | return d->m_contacts.count(); |
602 | } |
603 | |
604 | |
605 | |
606 | /*! |
607 | \qmlproperty Filter ContactModel::filter |
608 | |
609 | This property holds the filter instance used by the contact model. |
610 | |
611 | \sa Filter |
612 | */ |
613 | QDeclarativeContactFilter* QDeclarativeContactModel::filter() const |
614 | { |
615 | return d->m_filter; |
616 | } |
617 | |
618 | void QDeclarativeContactModel::setFilter(QDeclarativeContactFilter* filter) |
619 | { |
620 | if (d->m_filter != filter) { |
621 | if (d->m_filter) |
622 | disconnect(sender: d->m_filter, SIGNAL(filterChanged()), receiver: this, SIGNAL(filterChanged())); |
623 | d->m_filter = filter; |
624 | if (d->m_filter) |
625 | connect(asender: d->m_filter, SIGNAL(filterChanged()), SIGNAL(filterChanged()), atype: Qt::UniqueConnection); |
626 | emit filterChanged(); |
627 | } |
628 | } |
629 | |
630 | /*! |
631 | \qmlproperty FetchHint ContactModel::fetchHint |
632 | |
633 | This property holds the fetch hint instance used by the contact model. |
634 | |
635 | \sa FetchHint |
636 | */ |
637 | QDeclarativeContactFetchHint* QDeclarativeContactModel::fetchHint() const |
638 | { |
639 | return d->m_fetchHint; |
640 | } |
641 | void QDeclarativeContactModel::setFetchHint(QDeclarativeContactFetchHint* fetchHint) |
642 | { |
643 | if (d->m_fetchHint != fetchHint) { |
644 | if (d->m_fetchHint) |
645 | disconnect(sender: d->m_fetchHint, SIGNAL(fetchHintChanged()), receiver: this, SIGNAL(fetchHintChanged())); |
646 | d->m_fetchHint = fetchHint; |
647 | if (d->m_fetchHint) |
648 | connect(asender: d->m_fetchHint, SIGNAL(fetchHintChanged()), SIGNAL(fetchHintChanged()), atype: Qt::UniqueConnection); |
649 | emit fetchHintChanged(); |
650 | } |
651 | } |
652 | |
653 | /*! |
654 | \qmlproperty list<Contact> ContactModel::contacts |
655 | |
656 | This property holds the list of contacts. |
657 | |
658 | \sa Contact |
659 | */ |
660 | QQmlListProperty<QDeclarativeContact> QDeclarativeContactModel::contacts() |
661 | { |
662 | return QQmlListProperty<QDeclarativeContact>(this, |
663 | 0, |
664 | contacts_append, |
665 | contacts_count, |
666 | contacts_at, |
667 | contacts_clear); |
668 | } |
669 | |
670 | |
671 | |
672 | void QDeclarativeContactModel::contacts_append(QQmlListProperty<QDeclarativeContact>* prop, QDeclarativeContact* contact) |
673 | { |
674 | Q_UNUSED(prop); |
675 | Q_UNUSED(contact); |
676 | qWarning() << Q_FUNC_INFO << "appending contacts is not currently supported" ; |
677 | } |
678 | |
679 | int QDeclarativeContactModel::contacts_count(QQmlListProperty<QDeclarativeContact>* prop) |
680 | { |
681 | return static_cast<QDeclarativeContactModel*>(prop->object)->d->m_contacts.count(); |
682 | } |
683 | |
684 | QDeclarativeContact* QDeclarativeContactModel::contacts_at(QQmlListProperty<QDeclarativeContact>* prop, int index) |
685 | { |
686 | return static_cast<QDeclarativeContactModel*>(prop->object)->d->m_contacts.at(i: index); |
687 | } |
688 | |
689 | void QDeclarativeContactModel::contacts_clear(QQmlListProperty<QDeclarativeContact>* prop) |
690 | { |
691 | QDeclarativeContactModel* model = static_cast<QDeclarativeContactModel*>(prop->object); |
692 | model->clearContacts(); |
693 | emit model->contactsChanged(); |
694 | } |
695 | |
696 | |
697 | /*! |
698 | \qmlproperty list<SortOrder> ContactModel::sortOrders |
699 | |
700 | This property holds a list of sort orders used by the contacts model. |
701 | \sa SortOrder |
702 | */ |
703 | QQmlListProperty<QDeclarativeContactSortOrder> QDeclarativeContactModel::sortOrders() |
704 | { |
705 | return QQmlListProperty<QDeclarativeContactSortOrder>(this, |
706 | 0, |
707 | sortOrder_append, |
708 | sortOrder_count, |
709 | sortOrder_at, |
710 | sortOrder_clear); |
711 | } |
712 | |
713 | void QDeclarativeContactModel::startImport(QVersitReader::State state) |
714 | { |
715 | if (state == QVersitReader::FinishedState || state == QVersitReader::CanceledState) { |
716 | QVersitContactImporter importer(d->m_importProfiles); |
717 | importer.setResourceHandler(&d->m_resourceHandler); |
718 | importer.importDocuments(documents: d->m_reader.results()); |
719 | QList<QContact> contacts = importer.contacts(); |
720 | |
721 | delete d->m_reader.device(); |
722 | d->m_reader.setDevice(0); |
723 | |
724 | QStringList ids; |
725 | |
726 | if (d->m_manager) { |
727 | if (!d->m_manager->saveContacts(contacts: &contacts)) { |
728 | if (d->m_error != d->m_manager->error()) { |
729 | d->m_error = d->m_manager->error(); |
730 | emit errorChanged(); |
731 | } |
732 | } else { |
733 | foreach (const QContact &c, contacts) { |
734 | ids << c.id().toString(); |
735 | } |
736 | } |
737 | } |
738 | |
739 | emit importCompleted(error: QDeclarativeContactModel::ImportError(d->m_reader.error()), url: d->m_lastImportUrl, ids); |
740 | } |
741 | } |
742 | |
743 | /*! |
744 | \qmlsignal ContactModel::contactsFetched(int requestId, list<Contact> fetchedContacts) |
745 | |
746 | This signal is emitted, when a contact fetch request is finished. |
747 | |
748 | \sa ContactModel::fetchContacts |
749 | */ |
750 | |
751 | /*! |
752 | \qmlmethod int ContactModel::fetchContacts(list<string> contactIds) |
753 | |
754 | Starts a request to fetch contacts by the given \a contactIds, and returns the unique ID of this request. |
755 | -1 is returned if the request can't be started. |
756 | |
757 | Note that the contacts fetched won't be added to the model, but can be accessed through the contactsFetched |
758 | signal handler. |
759 | |
760 | \sa ContactModel::contactsFetched |
761 | */ |
762 | int QDeclarativeContactModel::fetchContacts(const QStringList &contactIds) |
763 | { |
764 | if (contactIds.isEmpty()) |
765 | return -1; |
766 | |
767 | QContactFetchByIdRequest *fetchRequest = new QContactFetchByIdRequest(this); |
768 | connect(sender: fetchRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), |
769 | receiver: this, SLOT(onFetchContactsRequestStateChanged(QContactAbstractRequest::State))); |
770 | fetchRequest->setManager(d->m_manager); |
771 | |
772 | QList<QContactId> ids; |
773 | foreach (const QString &contactId, contactIds) |
774 | ids.append(t: QContactId::fromString(idString: contactId)); |
775 | fetchRequest->setIds(ids); |
776 | int requestId = d->m_lastRequestId.fetchAndAddOrdered(valueToAdd: 1); |
777 | d->m_requestIdHash.insert(key: fetchRequest, value: requestId); |
778 | if (fetchRequest->start()) { |
779 | return requestId; |
780 | } else { |
781 | d->m_requestIdHash.remove(key: fetchRequest); |
782 | return -1; |
783 | } |
784 | } |
785 | |
786 | /*! |
787 | \qmlmethod ContactModel::removeCollection(string collectionId) |
788 | Removes asynchronously the contact collection with the given \a collectionId from the backend. |
789 | */ |
790 | void QDeclarativeContactModel::removeCollection(const QString &collectionId) |
791 | { |
792 | QContactCollectionRemoveRequest* req = new QContactCollectionRemoveRequest(this); |
793 | req->setManager(d->m_manager); |
794 | req->setCollectionId(QContactCollectionId::fromString(idString: collectionId)); |
795 | |
796 | connect(sender: req, SIGNAL(stateChanged(QContactAbstractRequest::State)), receiver: this, SLOT(onRequestStateChanged(QContactAbstractRequest::State))); |
797 | |
798 | req->start(); |
799 | } |
800 | |
801 | /*! |
802 | \qmlmethod OContactModel::saveCollection(Collection collection) |
803 | |
804 | Saves asynchronously the given \a collection into the contact backend. |
805 | */ |
806 | void QDeclarativeContactModel::saveCollection(QDeclarativeContactCollection *declColl) |
807 | { |
808 | if (declColl) { |
809 | QContactCollection collection = declColl->collection(); |
810 | QContactCollectionSaveRequest* req = new QContactCollectionSaveRequest(this); |
811 | req->setManager(d->m_manager); |
812 | req->setCollection(collection); |
813 | |
814 | if (declColl->collection().id().isNull()) { |
815 | // if the collection id is empty this means that this is a new collection |
816 | // we need to keep trace of this declarative collection to update with the |
817 | // new Id as soon as this request finish |
818 | QPointer<QDeclarativeContactCollection> pCollection = declColl; |
819 | req->setProperty(name: "DeclarativeCollection" , value: QVariant::fromValue(value: pCollection)); |
820 | } |
821 | |
822 | connect(sender: req, SIGNAL(stateChanged(QContactAbstractRequest::State)), receiver: this, SLOT(onRequestStateChanged(QContactAbstractRequest::State))); |
823 | req->start(); |
824 | } |
825 | } |
826 | |
827 | /*! |
828 | \qmlmethod OContactModel::fetchCollections() |
829 | Fetch asynchronously a list of contact collections from the contact backend. |
830 | */ |
831 | void QDeclarativeContactModel::fetchCollections() |
832 | { |
833 | // fetchCollections() is used for both direct calls and |
834 | // signals from model. For signal from model, check also the |
835 | // autoupdate-flag. |
836 | if (sender() == d->m_manager && !d->m_autoUpdate) { |
837 | return; |
838 | } |
839 | |
840 | QContactCollectionFetchRequest* req = new QContactCollectionFetchRequest(this); |
841 | connect(sender: req,SIGNAL(stateChanged(QContactAbstractRequest::State)), receiver: this, SLOT(collectionsFetched())); |
842 | req->setManager(d->m_manager); |
843 | req->start(); |
844 | } |
845 | |
846 | /*! |
847 | \internal |
848 | */ |
849 | void QDeclarativeContactModel::onFetchContactsRequestStateChanged(QContactAbstractRequest::State state) |
850 | { |
851 | if (state != QContactAbstractRequest::FinishedState) |
852 | return; |
853 | |
854 | QContactFetchByIdRequest *request = qobject_cast<QContactFetchByIdRequest *>(object: sender()); |
855 | Q_ASSERT(request); |
856 | |
857 | checkError(request); |
858 | |
859 | const int requestId = d->m_requestIdHash.value(key: request, defaultValue: -1); |
860 | if (requestId == -1) |
861 | qWarning() << Q_FUNC_INFO << "transaction not found from the request hash" ; |
862 | else |
863 | d->m_requestIdHash.remove(key: request); |
864 | QVariantList list; |
865 | if (request->error() == QContactManager::NoError) { |
866 | QList<QContact> contacts(request->contacts()); |
867 | foreach (const QContact &contact, contacts) { |
868 | // if the contact was already fetched update the contact |
869 | QDeclarativeContact *declarativeContact = d->m_contactFetchedMap.value(akey: contact.id(), adefaultValue: 0); |
870 | if (!declarativeContact) { |
871 | declarativeContact = new QDeclarativeContact(this); |
872 | // Transfer the ownership to QML |
873 | // The model will destroy the contact if it get removed from the backend, otherwise the QML side need to destroy it. |
874 | QQmlEngine::setObjectOwnership(declarativeContact, QQmlEngine::JavaScriptOwnership); |
875 | |
876 | // keep track of contact destruction to remove it from the list if QML destroys it |
877 | connect(asender: declarativeContact, SIGNAL(destroyed(QObject*)), SLOT(onFetchedContactDestroyed(QObject*))); |
878 | |
879 | // we need keep track of the contact to update it if the contact get update on the backend. or destroy it |
880 | // if the contact get removed from the backend |
881 | d->m_contactFetchedMap[contact.id()] = declarativeContact; |
882 | } |
883 | declarativeContact->setContact(contact); |
884 | list.append(t: QVariant::fromValue(value: declarativeContact)); |
885 | } |
886 | } |
887 | emit contactsFetched(requestId, fetchedContacts: list); |
888 | request->deleteLater(); |
889 | } |
890 | |
891 | /*! |
892 | \internal |
893 | */ |
894 | void QDeclarativeContactModel::collectionsFetched() |
895 | { |
896 | QContactCollectionFetchRequest* req = qobject_cast<QContactCollectionFetchRequest*>(object: QObject::sender()); |
897 | Q_ASSERT(req); |
898 | if (req->isFinished() && QContactManager::NoError == req->error()) { |
899 | d->m_updatePendingFlag &= ~QDeclarativeContactModelPrivate::UpdatingCollectionsPending; |
900 | // prepare tables |
901 | QHash<QString, const QContactCollection*> collections; |
902 | foreach (const QContactCollection& collection, req->collections()) { |
903 | collections.insert(key: collection.id().toString(), value: &collection); |
904 | } |
905 | QHash<QString, QDeclarativeContactCollection*> declCollections; |
906 | foreach (QDeclarativeContactCollection* declCollection, d->m_collections) { |
907 | declCollections.insert(key: declCollection->collection().id().toString(), value: declCollection); |
908 | } |
909 | // go tables through |
910 | QHashIterator<QString, const QContactCollection*> collIterator(collections); |
911 | while (collIterator.hasNext()) { |
912 | collIterator.next(); |
913 | if (declCollections.contains(key: collIterator.key())) { |
914 | // collection on both sides, update the declarative collection |
915 | declCollections.value(key: collIterator.key())->setCollection(*collections.value(key: collIterator.key())); |
916 | } else { |
917 | // new collection, add it to declarative collection list |
918 | QDeclarativeContactCollection* declCollection = new QDeclarativeContactCollection(this); |
919 | declCollection->setCollection(*collections.value(key: collIterator.key())); |
920 | d->m_collections.append(t: declCollection); |
921 | } |
922 | } |
923 | QHashIterator<QString, QDeclarativeContactCollection*> declCollIterator(declCollections); |
924 | while (declCollIterator.hasNext()) { |
925 | declCollIterator.next(); |
926 | if (!collections.contains(key: declCollIterator.key())) { |
927 | // collection deleted on the backend side, delete from declarative collection list |
928 | QDeclarativeContactCollection* toBeDeletedColl = declCollections.value(key: declCollIterator.key()); |
929 | d->m_collections.removeOne(t: toBeDeletedColl); |
930 | toBeDeletedColl->deleteLater(); |
931 | } |
932 | } |
933 | emit collectionsChanged(); |
934 | if (d->m_updatePendingFlag & QDeclarativeContactModelPrivate::UpdatingContactsPending) |
935 | QMetaObject::invokeMethod(obj: this, member: "fetchAgain" , type: Qt::QueuedConnection); |
936 | req->deleteLater(); |
937 | } |
938 | checkError(request: req); |
939 | } |
940 | |
941 | void QDeclarativeContactModel::clearContacts() |
942 | { |
943 | qDeleteAll(c: d->m_contacts); |
944 | d->m_contacts.clear(); |
945 | d->m_contactMap.clear(); |
946 | qDeleteAll(c: d->m_contactFetchedMap.values()); |
947 | d->m_contactFetchedMap.clear(); |
948 | } |
949 | |
950 | void QDeclarativeContactModel::fetchAgain() |
951 | { |
952 | QList<QContactSortOrder> sortOrders; |
953 | foreach (QDeclarativeContactSortOrder* so, d->m_sortOrders) { |
954 | sortOrders.append(t: so->sortOrder()); |
955 | } |
956 | QContactFetchRequest* fetchRequest = new QContactFetchRequest(this); |
957 | |
958 | fetchRequest->setManager(d->m_manager); |
959 | fetchRequest->setSorting(sortOrders); |
960 | |
961 | if (d->m_filter){ |
962 | fetchRequest->setFilter(d->m_filter->filter()); |
963 | } else { |
964 | fetchRequest->setFilter(QContactFilter()); |
965 | } |
966 | |
967 | fetchRequest->setFetchHint(d->m_fetchHint ? d->m_fetchHint->fetchHint() : QContactFetchHint()); |
968 | |
969 | connect(sender: fetchRequest, SIGNAL(resultsAvailable()), receiver: this, SLOT(requestUpdated())); |
970 | connect(sender: fetchRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), |
971 | receiver: this, SLOT(fetchRequestStateChanged(QContactAbstractRequest::State))); |
972 | |
973 | // cancel all previous requests |
974 | foreach (QContactFetchRequest *req, d->m_pendingRequests) { |
975 | req->cancel(); |
976 | req->deleteLater(); |
977 | } |
978 | |
979 | d->m_pendingContacts.clear(); |
980 | d->m_pendingRequests.clear(); |
981 | d->m_pendingRequests.append(t: fetchRequest); |
982 | |
983 | // if we have no contacts yet, we can display results as soon as they arrive |
984 | // but if we are updating the model after a sort or filter change, we have to |
985 | // wait for all contacts before processing the update |
986 | d->m_progressiveLoading = d->m_contacts.isEmpty(); |
987 | |
988 | fetchRequest->start(); |
989 | } |
990 | |
991 | void QDeclarativeContactModel::requestUpdated() |
992 | { |
993 | |
994 | QContactFetchRequest* req = qobject_cast<QContactFetchRequest*>(object: QObject::sender()); |
995 | Q_ASSERT(req); |
996 | if (req) { |
997 | QList<QContact> contacts = req->contacts(); |
998 | |
999 | // if we are starting from scratch, we can show contact results as they arrive |
1000 | if (d->m_progressiveLoading) { |
1001 | QList<QDeclarativeContact*> dcs; |
1002 | foreach (const QContact &c, contacts) { |
1003 | if (d->m_contactMap.contains(key: c.id())) { |
1004 | QDeclarativeContact* dc = d->m_contactMap.value(akey: c.id()); |
1005 | dc->setContact(c); |
1006 | } else { |
1007 | QDeclarativeContact* dc = new QDeclarativeContact(this); |
1008 | if (dc) { |
1009 | d->m_contactMap.insert(key: c.id(), value: dc); |
1010 | dc->setContact(c); |
1011 | dcs.append(t: dc); |
1012 | } |
1013 | } |
1014 | } |
1015 | |
1016 | if (dcs.count() > 0) { |
1017 | beginInsertRows(parent: QModelIndex(), first: d->m_contacts.count(), last: d->m_contacts.count() + dcs.count() - 1); |
1018 | // At this point we need to relay on the backend and assume that the partial results are following the fetch sorting property |
1019 | d->m_contacts += dcs; |
1020 | endInsertRows(); |
1021 | |
1022 | emit contactsChanged(); |
1023 | } |
1024 | } else { |
1025 | d->m_pendingContacts << contacts; |
1026 | } |
1027 | |
1028 | checkError(request: req); |
1029 | } |
1030 | } |
1031 | |
1032 | void QDeclarativeContactModel::fetchRequestStateChanged(QContactAbstractRequest::State newState) |
1033 | { |
1034 | if (newState != QContactAbstractRequest::FinishedState) |
1035 | return; |
1036 | |
1037 | d->m_updatePendingFlag &= ~QDeclarativeContactModelPrivate::UpdatingContactsPending; |
1038 | QContactFetchRequest* req = qobject_cast<QContactFetchRequest*>(object: QObject::sender()); |
1039 | Q_ASSERT(req); |
1040 | if (req) { |
1041 | // if we were not processing contacts as soon as they arrive, we need to process them here. |
1042 | if (!d->m_progressiveLoading) { |
1043 | // start by removing the contacts that don't belong to this result set anymore |
1044 | for (int i = d->m_contacts.count()-1; i >= 0; --i) { |
1045 | QDeclarativeContact *contact = d->m_contacts[i]; |
1046 | if (!d->m_pendingContacts.contains(t: contact->contact())) { |
1047 | beginRemoveRows(parent: QModelIndex(), first: i, last: i); |
1048 | d->m_contacts.removeAt(i); |
1049 | d->m_contactMap.remove(key: contact->contact().id()); |
1050 | endRemoveRows(); |
1051 | } |
1052 | } |
1053 | |
1054 | // now insert new contacts and move existing ones to their final positions |
1055 | int count = d->m_pendingContacts.count(); |
1056 | for (int i = 0; i < count; ++i) { |
1057 | QContact c = d->m_pendingContacts[i]; |
1058 | if (!d->m_contactMap.contains(key: c.id())) { |
1059 | QDeclarativeContact* dc = new QDeclarativeContact(this); |
1060 | dc->setContact(c); |
1061 | beginInsertRows(parent: QModelIndex(), first: i, last: i); |
1062 | d->m_contacts.insert(i, t: dc); |
1063 | d->m_contactMap.insert(key: c.id(),value: dc); |
1064 | endInsertRows(); |
1065 | } else { |
1066 | QDeclarativeContact *contact = d->m_contactMap[c.id()]; |
1067 | |
1068 | // If there are duplicates in the pending contacts list, then the current index |
1069 | // can be outside this contact lists range and we need to adjust it to avoid crashing. |
1070 | const int oldIdx = d->m_contacts.indexOf(t: contact); |
1071 | const int newIdx = i < d->m_contacts.size() ? i : d->m_contacts.size() - 1; |
1072 | if (oldIdx != newIdx) { |
1073 | beginMoveRows(sourceParent: QModelIndex(), sourceFirst: oldIdx, sourceLast: oldIdx, destinationParent: QModelIndex(), destinationRow: newIdx); |
1074 | d->m_contacts.move(from: oldIdx, to: newIdx); |
1075 | endMoveRows(); |
1076 | } |
1077 | } |
1078 | } |
1079 | emit contactsChanged(); |
1080 | } |
1081 | |
1082 | // and now clear the pending contact list as the model is up-to-date |
1083 | d->m_pendingContacts.clear(); |
1084 | d->m_pendingRequests.removeOne(t: req); |
1085 | req->deleteLater(); |
1086 | } |
1087 | } |
1088 | |
1089 | /*! |
1090 | \internal |
1091 | */ |
1092 | void QDeclarativeContactModel::doUpdate() |
1093 | { |
1094 | if (d->m_autoUpdate) |
1095 | update(); |
1096 | } |
1097 | |
1098 | /*! |
1099 | \qmlmethod ContactModel::saveContact(Contact contact) |
1100 | |
1101 | Save the given \a contact into the contacts backend. |
1102 | Once saved successfully, the dirty flags of this contact will be reset. |
1103 | |
1104 | \sa Contact::modified |
1105 | */ |
1106 | void QDeclarativeContactModel::saveContact(QDeclarativeContact* dc) |
1107 | { |
1108 | if (dc) { |
1109 | QContactSaveRequest* req = new QContactSaveRequest(this); |
1110 | req->setManager(d->m_manager); |
1111 | req->setContact(dc->contact()); |
1112 | if (dc->contact().id().isNull()) { |
1113 | // if the contact id is empty this means that this contact is a new contact |
1114 | // we need to keep trace of this declarative contact to update with the |
1115 | // new Id as soon as this request finish |
1116 | QPointer<QDeclarativeContact> pContact = dc; |
1117 | req->setProperty(name: "DeclarativeContact" , value: QVariant::fromValue(value: pContact)); |
1118 | } |
1119 | |
1120 | connect(sender: req,SIGNAL(stateChanged(QContactAbstractRequest::State)), receiver: this, SLOT(onRequestStateChanged(QContactAbstractRequest::State))); |
1121 | req->start(); |
1122 | } |
1123 | } |
1124 | |
1125 | void QDeclarativeContactModel::onRequestStateChanged(QContactAbstractRequest::State newState) |
1126 | { |
1127 | if (newState != QContactAbstractRequest::FinishedState) { |
1128 | return; |
1129 | } |
1130 | |
1131 | QContactAbstractRequest *request = qobject_cast<QContactAbstractRequest *>(object: sender()); |
1132 | Q_ASSERT(request); |
1133 | |
1134 | if (request->error() == QContactManager::NoError) { |
1135 | switch (request->type()) { |
1136 | case QContactAbstractRequest::ContactSaveRequest: |
1137 | { |
1138 | QVariant vContact = request->property(name: "DeclarativeContact" ); |
1139 | if (vContact.isValid()) { |
1140 | QPointer<QDeclarativeContact> pContact = vContact.value<QPointer<QDeclarativeContact> >(); |
1141 | // Update contact info. |
1142 | // this is necessary to make sure that the declarative contact get the new contact ID otherwise |
1143 | // the contact Id will be empty |
1144 | QList<QContact> contacts = qobject_cast<QContactSaveRequest*>(object: request)->contacts(); |
1145 | if (pContact && contacts.length() == 1) { |
1146 | pContact->setContact(contacts[0]); |
1147 | } |
1148 | } |
1149 | break; |
1150 | } |
1151 | case QContactAbstractRequest::CollectionSaveRequest: |
1152 | { |
1153 | QVariant vCollection = request->property(name: "DeclarativeCollection" ); |
1154 | if (vCollection.isValid()) { |
1155 | QPointer<QDeclarativeContactCollection> pCollection = vCollection.value<QPointer<QDeclarativeContactCollection> >(); |
1156 | // Update collection info. |
1157 | // this is necessary to make sure that the declarative collection get the new collection ID otherwise |
1158 | // the collection Id will be empty |
1159 | QList<QContactCollection> collections = qobject_cast<QContactCollectionSaveRequest*>(object: request)->collections(); |
1160 | if (pCollection && collections.length() == 1) { |
1161 | pCollection->setCollection(collections[0]); |
1162 | } |
1163 | } |
1164 | break; |
1165 | } |
1166 | default: |
1167 | break; |
1168 | } |
1169 | } |
1170 | checkError(request); |
1171 | request->deleteLater(); |
1172 | } |
1173 | |
1174 | void QDeclarativeContactModel::checkError(const QContactAbstractRequest *request) |
1175 | { |
1176 | if (request) { |
1177 | updateError(error: request->error()); |
1178 | } |
1179 | } |
1180 | |
1181 | void QDeclarativeContactModel::updateError(QContactManager::Error error) |
1182 | { |
1183 | if (d->m_error != error) { |
1184 | d->m_error = error; |
1185 | emit errorChanged(); |
1186 | } |
1187 | } |
1188 | |
1189 | void QDeclarativeContactModel::onContactsAdded(const QList<QContactId>& ids) |
1190 | { |
1191 | if (d->m_autoUpdate && !ids.isEmpty()) { |
1192 | QContactFetchRequest *fetchRequest = createContactFetchRequest(ids); |
1193 | connect(sender: fetchRequest,SIGNAL(stateChanged(QContactAbstractRequest::State)), |
1194 | receiver: this, SLOT(onContactsAddedFetchRequestStateChanged(QContactAbstractRequest::State))); |
1195 | fetchRequest->start(); |
1196 | } |
1197 | } |
1198 | |
1199 | /*! |
1200 | \qmlmethod ContactModel::removeContact(string contactId) |
1201 | Remove the contact from the contacts store by given \a contactId. |
1202 | After removing a contact it is not possible to save it again. |
1203 | \sa Contact::contactId |
1204 | */ |
1205 | void QDeclarativeContactModel::removeContact(QString id) |
1206 | { |
1207 | QList<QString> ids; |
1208 | ids << id; |
1209 | removeContacts(ids); |
1210 | } |
1211 | |
1212 | /*! |
1213 | \qmlmethod ContactModel::removeContacts(list<string> contactIds) |
1214 | Remove the list of contacts from the contacts store by given \a contactIds. |
1215 | \sa Contact::contactId |
1216 | */ |
1217 | |
1218 | void QDeclarativeContactModel::removeContacts(const QStringList &ids) |
1219 | { |
1220 | QContactRemoveRequest* req = new QContactRemoveRequest(this); |
1221 | QList<QContactId> contactIdsAsList; |
1222 | req->setManager(d->m_manager); |
1223 | |
1224 | foreach (const QString& id, ids) { |
1225 | QContactId contactId = QContactId::fromString(idString: id); |
1226 | if (!contactId.isNull()) |
1227 | contactIdsAsList.append(t: contactId); |
1228 | } |
1229 | req->setContactIds(contactIdsAsList); |
1230 | |
1231 | connect(sender: req,SIGNAL(stateChanged(QContactAbstractRequest::State)), receiver: this, SLOT(onRequestStateChanged(QContactAbstractRequest::State))); |
1232 | |
1233 | req->start(); |
1234 | } |
1235 | |
1236 | |
1237 | void QDeclarativeContactModel::onContactsRemoved(const QList<QContactId> &ids) |
1238 | { |
1239 | if (!d->m_autoUpdate) |
1240 | return; |
1241 | |
1242 | bool emitSignal = false; |
1243 | foreach (const QContactId &id, ids) { |
1244 | // delete the contact from fetched map if necessary |
1245 | QDeclarativeContact* contact = d->m_contactFetchedMap.take(key: id); |
1246 | if (contact) |
1247 | contact->deleteLater(); |
1248 | |
1249 | if (d->m_contactMap.contains(key: id)) { |
1250 | int row = 0; |
1251 | //TODO:need a fast lookup |
1252 | for (; row < d->m_contacts.count(); row++) { |
1253 | if (d->m_contacts.at(i: row)->contactId() == id.toString()) |
1254 | break; |
1255 | } |
1256 | |
1257 | if (row < d->m_contacts.count()) { |
1258 | beginRemoveRows(parent: QModelIndex(), first: row, last: row); |
1259 | contact = d->m_contacts.takeAt(i: row); |
1260 | contact->deleteLater(); |
1261 | d->m_contactMap.remove(key: id); |
1262 | endRemoveRows(); |
1263 | emitSignal = true; |
1264 | } |
1265 | } |
1266 | } |
1267 | if (emitSignal) |
1268 | emit contactsChanged(); |
1269 | } |
1270 | |
1271 | void QDeclarativeContactModel::onContactsChanged(const QList<QContactId> &ids) |
1272 | { |
1273 | if (d->m_autoUpdate && !ids.isEmpty()) { |
1274 | QContactFetchRequest *fetchRequest = createContactFetchRequest(ids); |
1275 | connect(sender: fetchRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), |
1276 | receiver: this, SLOT(onContactsChangedFetchRequestStateChanged(QContactAbstractRequest::State))); |
1277 | fetchRequest->start(); |
1278 | } |
1279 | |
1280 | // If any contact in the fetchedList has changed we need to update it. |
1281 | // We need a different query because feched contacts could not be part of the model. |
1282 | // |
1283 | // For example: if the model contains a filter |
1284 | if (!ids.isEmpty()) { |
1285 | QStringList pendingFetch; |
1286 | foreach (const QContactId &id, ids) { |
1287 | QDeclarativeContact* dc = d->m_contactFetchedMap.value(akey: id); |
1288 | if (dc) |
1289 | pendingFetch << dc->contactId(); |
1290 | } |
1291 | if (!pendingFetch.isEmpty()) |
1292 | fetchContacts(contactIds: pendingFetch); |
1293 | } |
1294 | } |
1295 | |
1296 | QContactFetchRequest *QDeclarativeContactModel::createContactFetchRequest(const QList<QContactId> &ids) |
1297 | { |
1298 | QContactFetchRequest *fetchRequest = new QContactFetchRequest(this); |
1299 | fetchRequest->setManager(d->m_manager); |
1300 | fetchRequest->setFetchHint(d->m_fetchHint ? d->m_fetchHint->fetchHint() : QContactFetchHint()); |
1301 | |
1302 | QContactIdFilter idFilter; |
1303 | idFilter.setIds(ids); |
1304 | if (d->m_filter) { |
1305 | QContactIntersectionFilter filter; |
1306 | filter.append(filter: idFilter); // result handling assumes that id filter is the first filter |
1307 | filter.append(filter: d->m_filter->filter()); |
1308 | fetchRequest->setFilter(filter); |
1309 | } else |
1310 | fetchRequest->setFilter(idFilter); |
1311 | return fetchRequest; |
1312 | } |
1313 | |
1314 | QVariant QDeclarativeContactModel::data(const QModelIndex &index, int role) const |
1315 | { |
1316 | //Check if QList itme's index is valid before access it, index should be between 0 and count - 1 |
1317 | if (index.row() < 0 || index.row() >= d->m_contacts.count()) { |
1318 | return QVariant(); |
1319 | } |
1320 | |
1321 | QDeclarativeContact* dc = d->m_contacts.value(i: index.row()); |
1322 | Q_ASSERT(dc); |
1323 | QContact c = dc->contact(); |
1324 | |
1325 | switch(role) { |
1326 | case Qt::DisplayRole: |
1327 | return c.detail(type: QContactDetail::TypeDisplayLabel).value(field: QContactDisplayLabel::FieldLabel); |
1328 | case Qt::DecorationRole: |
1329 | return QPixmap(); |
1330 | case ContactRole: |
1331 | return QVariant::fromValue(value: dc); |
1332 | } |
1333 | return QVariant(); |
1334 | } |
1335 | |
1336 | |
1337 | void QDeclarativeContactModel::sortOrder_append(QQmlListProperty<QDeclarativeContactSortOrder> *p, QDeclarativeContactSortOrder *sortOrder) |
1338 | { |
1339 | QDeclarativeContactModel* model = qobject_cast<QDeclarativeContactModel*>(object: p->object); |
1340 | if (model && sortOrder) { |
1341 | QObject::connect(sender: sortOrder, SIGNAL(sortOrderChanged()), receiver: model, SIGNAL(sortOrdersChanged())); |
1342 | model->d->m_sortOrders.append(t: sortOrder); |
1343 | emit model->sortOrdersChanged(); |
1344 | } |
1345 | } |
1346 | |
1347 | int QDeclarativeContactModel::sortOrder_count(QQmlListProperty<QDeclarativeContactSortOrder> *p) |
1348 | { |
1349 | QDeclarativeContactModel* model = qobject_cast<QDeclarativeContactModel*>(object: p->object); |
1350 | if (model) |
1351 | return model->d->m_sortOrders.size(); |
1352 | return 0; |
1353 | } |
1354 | QDeclarativeContactSortOrder * QDeclarativeContactModel::sortOrder_at(QQmlListProperty<QDeclarativeContactSortOrder> *p, int idx) |
1355 | { |
1356 | QDeclarativeContactModel* model = qobject_cast<QDeclarativeContactModel*>(object: p->object); |
1357 | |
1358 | QDeclarativeContactSortOrder* sortOrder = 0; |
1359 | if (model) { |
1360 | int i = 0; |
1361 | foreach (QDeclarativeContactSortOrder* s, model->d->m_sortOrders) { |
1362 | if (i == idx) { |
1363 | sortOrder = s; |
1364 | break; |
1365 | } else { |
1366 | i++; |
1367 | } |
1368 | } |
1369 | } |
1370 | return sortOrder; |
1371 | } |
1372 | void QDeclarativeContactModel::sortOrder_clear(QQmlListProperty<QDeclarativeContactSortOrder> *p) |
1373 | { |
1374 | QDeclarativeContactModel* model = qobject_cast<QDeclarativeContactModel*>(object: p->object); |
1375 | |
1376 | if (model) { |
1377 | model->d->m_sortOrders.clear(); |
1378 | emit model->sortOrdersChanged(); |
1379 | } |
1380 | } |
1381 | |
1382 | /*! |
1383 | \qmlproperty list<Collection> OContactModel::collections |
1384 | |
1385 | This property holds a list of collections in the contact model. |
1386 | |
1387 | \sa Collection |
1388 | */ |
1389 | QQmlListProperty<QDeclarativeContactCollection> QDeclarativeContactModel::collections() |
1390 | { |
1391 | return QQmlListProperty<QDeclarativeContactCollection>(this, 0, collection_count, collection_at); |
1392 | } |
1393 | |
1394 | int QDeclarativeContactModel::collection_count(QQmlListProperty<QDeclarativeContactCollection> *p) |
1395 | { |
1396 | QDeclarativeContactModel* model = qobject_cast<QDeclarativeContactModel*>(object: p->object); |
1397 | return model ? model->d->m_collections.count() : 0; |
1398 | } |
1399 | |
1400 | QDeclarativeContactCollection *QDeclarativeContactModel::collection_at(QQmlListProperty<QDeclarativeContactCollection> *p, int idx) |
1401 | { |
1402 | QDeclarativeContactModel* model = qobject_cast<QDeclarativeContactModel*>(object: p->object); |
1403 | QDeclarativeContactCollection* collection = 0; |
1404 | if (model) { |
1405 | if (!model->d->m_collections.isEmpty() && idx >= 0 && idx < model->d->m_collections.count()) |
1406 | collection = model->d->m_collections.at(i: idx); |
1407 | } |
1408 | return collection; |
1409 | } |
1410 | /*! |
1411 | \internal |
1412 | |
1413 | It's invoked by the fetch request from onContactsAdded(). |
1414 | */ |
1415 | void QDeclarativeContactModel::onContactsAddedFetchRequestStateChanged(QContactAbstractRequest::State state) |
1416 | { |
1417 | |
1418 | if (state != QContactAbstractRequest::FinishedState) |
1419 | return; |
1420 | QContactFetchRequest *request = qobject_cast<QContactFetchRequest *>(object: sender()); |
1421 | Q_ASSERT(request); |
1422 | |
1423 | checkError(request); |
1424 | |
1425 | if (request->error() == QContactManager::NoError) { |
1426 | QList<QContact> fetchedContacts(request->contacts()); |
1427 | bool contactsAdded = false; |
1428 | foreach (const QContact &c,fetchedContacts) { |
1429 | if (d->m_contactMap.contains(key: c.id())) { |
1430 | qWarning() <<Q_FUNC_INFO <<"contact to be added already exists in the model" ; |
1431 | continue; |
1432 | } |
1433 | QDeclarativeContact* dc = new QDeclarativeContact(this); |
1434 | dc->setContact(c); |
1435 | int index = contactIndex(contact: dc); |
1436 | beginInsertRows(parent: QModelIndex(), first: index, last: index); |
1437 | d->m_contacts.insert(i: index, t: dc); |
1438 | d->m_contactMap.insert(key: c.id(), value: dc); |
1439 | if (!contactsAdded) |
1440 | contactsAdded = true; |
1441 | endInsertRows(); |
1442 | } |
1443 | if (contactsAdded) |
1444 | emit contactsChanged(); |
1445 | } |
1446 | request->deleteLater(); |
1447 | } |
1448 | |
1449 | |
1450 | static bool contactListDoesNotContainContactWithId(const QList<QContact> &contactList, const QContactId &contactId) { |
1451 | foreach (const QContact &contact, contactList) { |
1452 | if (contact.id() == contactId) |
1453 | return false; |
1454 | } |
1455 | return true; |
1456 | } |
1457 | |
1458 | /*! |
1459 | \internal |
1460 | |
1461 | It's invoked by the fetch request from onContactsChanged(). |
1462 | */ |
1463 | void QDeclarativeContactModel::onContactsChangedFetchRequestStateChanged(QContactAbstractRequest::State state) |
1464 | { |
1465 | if (state != QContactAbstractRequest::FinishedState) |
1466 | return; |
1467 | |
1468 | QContactFetchRequest *request = qobject_cast<QContactFetchRequest *>(object: sender()); |
1469 | Q_ASSERT(request); |
1470 | |
1471 | checkError(request); |
1472 | bool contactsUpdated = false; |
1473 | if (request->error() == QContactManager::NoError || request->error() == QContactManager::DoesNotExistError) { |
1474 | QList<QContact> fetchedContacts(request->contacts()); |
1475 | QList<QContactId> requestedContactIds; |
1476 | //read requested contacts ids from the filter |
1477 | if (request->filter().type() == QContactFilter::IdFilter) { |
1478 | QContactIdFilter idFilter(request->filter()); |
1479 | requestedContactIds = idFilter.ids(); |
1480 | } else { |
1481 | QContactIntersectionFilter intersectionFilter(request->filter()); |
1482 | QContactIdFilter idFilter(intersectionFilter.filters().at(i: 0)); // assuming that id filter is the first filter |
1483 | requestedContactIds = idFilter.ids(); |
1484 | } |
1485 | //handle updated contacts which needs removal from model |
1486 | //all contacts requested but not received are removed |
1487 | foreach (const QContactId &id, requestedContactIds) { |
1488 | if (contactListDoesNotContainContactWithId(contactList: fetchedContacts, contactId: id)) { |
1489 | for (int i=0;i<d->m_contacts.size();++i) { |
1490 | if (d->m_contacts.at(i)->contactId() == id.toString()) { |
1491 | beginRemoveRows(parent: QModelIndex(), first: i, last: i); |
1492 | // Remove and delete contact object |
1493 | QDeclarativeContact* dc = d->m_contacts.takeAt(i); |
1494 | dc->deleteLater(); |
1495 | d->m_contactMap.remove(key: id); |
1496 | endRemoveRows(); |
1497 | contactsUpdated = true; |
1498 | } |
1499 | } |
1500 | } |
1501 | } |
1502 | foreach (const QContact &fetchedContact, fetchedContacts) { |
1503 | QString contactIdString(fetchedContact.id().toString()); |
1504 | bool fetchedContactFound = false; |
1505 | for (int i = 0; i < d->m_contacts.size(); ++i) { |
1506 | //handle updated contacts which should be updated in the model |
1507 | if (d->m_contacts.at(i)->contactId() == contactIdString) { |
1508 | QDeclarativeContact* dc = d->m_contacts.at(i); |
1509 | dc->setContact(fetchedContact); |
1510 | |
1511 | // Since the contact can change the position due the sort order we need take care of it |
1512 | // First we need to remove it from previous position and notify the model about that |
1513 | beginRemoveRows(parent: QModelIndex(), first: i, last: i); |
1514 | d->m_contactMap.remove(key: fetchedContact.id()); |
1515 | d->m_contacts.removeAt(i); |
1516 | endRemoveRows(); |
1517 | |
1518 | // Calculate the new position |
1519 | int index = contactIndex(contact: dc); |
1520 | // Notify the model about the new item position |
1521 | beginInsertRows(parent: QModelIndex(), first: index, last: index); |
1522 | d->m_contacts.insert(i: index, t: dc); |
1523 | d->m_contactMap.insert(key: fetchedContact.id(),value: dc); |
1524 | if (!contactsUpdated) |
1525 | contactsUpdated = true; |
1526 | endInsertRows(); |
1527 | |
1528 | fetchedContactFound = true; |
1529 | break; |
1530 | } |
1531 | } |
1532 | //handle updated contacts which needs to be added in the model |
1533 | if (!fetchedContactFound) { |
1534 | QDeclarativeContact* dc = new QDeclarativeContact(this); |
1535 | dc->setContact(fetchedContact); |
1536 | int index = contactIndex(contact: dc); |
1537 | beginInsertRows(parent: QModelIndex(), first: index, last: index); |
1538 | d->m_contacts.insert(i: index, t: dc); |
1539 | d->m_contactMap.insert(key: fetchedContact.id(),value: dc); |
1540 | contactsUpdated = true; |
1541 | endInsertRows(); |
1542 | } |
1543 | } |
1544 | } |
1545 | |
1546 | if (contactsUpdated) |
1547 | emit contactsChanged(); |
1548 | |
1549 | request->deleteLater(); |
1550 | } |
1551 | |
1552 | int QDeclarativeContactModel::contactIndex(const QDeclarativeContact* contact) |
1553 | { |
1554 | if (d->m_sortOrders.count() > 0) { |
1555 | QList<QContactSortOrder> mSortOrders; |
1556 | foreach (QDeclarativeContactSortOrder *sortOrder, d->m_sortOrders) |
1557 | mSortOrders.append(t: sortOrder->sortOrder()); |
1558 | for (int i = 0; i < d->m_contacts.size(); i++) { |
1559 | // check to see if the new contact should be inserted here |
1560 | int comparison = QContactManagerEngine::compareContact(a: d->m_contacts.at(i)->contact(), |
1561 | b: contact->contact(), |
1562 | sortOrders: mSortOrders); |
1563 | //if the contacts are equal or cannot be compared |
1564 | //we return the current position.The default case is if the new contact |
1565 | //should appear before the compared contact in m_contacts |
1566 | if (comparison >= 0) |
1567 | return i; |
1568 | } |
1569 | } |
1570 | return d->m_contacts.size(); |
1571 | } |
1572 | |
1573 | #include "moc_qdeclarativecontactmodel_p.cpp" |
1574 | |
1575 | QT_END_NAMESPACE |
1576 | |