--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/qtmobility/plugins/contacts/wince/contactconversions.cpp Fri Apr 16 15:51:22 2010 +0300
@@ -0,0 +1,1636 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Mobility Components.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QDebug>
+#include <QByteArray>
+#include <QBuffer>
+#include <QUrl>
+#include <QPixmap>
+#include "qcontactmanager.h"
+#include "qtcontacts.h"
+#include "qcontactwincebackend_p.h"
+
+QTM_USE_NAMESPACE
+
+/*!
+ * Convert from the supplied CEPROPVAL \a val into a QVariant.
+ */
+static QVariant CEPropValToQVariant(const CEPROPVAL& val)
+{
+ if (val.propid != PIMPR_INVALID_ID) {
+ switch(TypeFromPropID(val.propid)) {
+ case CEVT_BOOL:
+ return QVariant(val.val.boolVal);
+ case CEVT_I2:
+ return QVariant(val.val.iVal);
+ case CEVT_UI2:
+ return QVariant(val.val.uiVal);
+ case CEVT_I4:
+ return QVariant(val.val.lVal);
+ case CEVT_UI4:
+ case CEVT_PIM_AUTO_I4:
+ return QVariant((quint32)val.val.ulVal);
+ case CEVT_R8:
+ return QVariant(val.val.dblVal);
+ case CEVT_FILETIME:
+ {
+ // Convert FILETIME to QDateTime
+ if (val.val.filetime.dwHighDateTime != 0 || val.val.filetime.dwLowDateTime != 0) {
+ SYSTEMTIME st;
+ if(FileTimeToSystemTime(&val.val.filetime, &st))
+ return QVariant(QDateTime(QDate(st.wYear, st.wMonth, st.wDay), QTime(st.wHour, st.wMinute, st.wSecond, st.wMilliseconds)));
+ }
+ break; // Fall through to return at bottom
+ }
+ case CEVT_LPWSTR:
+ return QVariant(QString::fromWCharArray(val.val.lpwstr));
+
+ case CEVT_BLOB: // Not used yet
+ case CEVT_PIM_STREAM: // Not used yet
+ break;
+ }
+ }
+ return QVariant();
+}
+
+
+
+class WcsdupHelper {
+public:
+ WcsdupHelper() {}
+ ~WcsdupHelper() {
+ clear();
+ }
+ LPWSTR dup(const ushort* str) {
+ LPWSTR newStr = wcsdup(str);
+ m_list.push_back(newStr);
+ return newStr;
+ }
+ void clear() {
+ foreach (LPWSTR str, m_list) {
+ free(str);
+ }
+ m_list.clear();
+ }
+private:
+ QList<LPWSTR> m_list;
+};
+
+static WcsdupHelper wcsdupHelper;
+
+/*!
+ * Convert the supplied QString \a value into a CEPROPVAL with the supplied \a id.
+ *
+ * The caller is responsible for freeing the LPWSTR within the CEPROPVAL.
+ *
+ * The datatype of the \a id is not checked.
+ */
+static CEPROPVAL convertToCEPropVal(const CEPROPID& id, const QString& value)
+{
+ CEPROPVAL val;
+ val.propid = id;
+ val.wFlags = 0;
+ val.wLenData = 0;
+ if (!value.isEmpty()) {
+ val.val.lpwstr = wcsdupHelper.dup(value.utf16());
+ } else {
+ val.val.lpwstr = 0;
+ }
+
+ return val;
+}
+
+/*!
+ * Convert the supplied QDateTime \a value into a CEPROPVAL with the supplied \a id.
+ *
+ * The datatype of the \a id is not checked.
+ */
+static CEPROPVAL convertToCEPropVal(const CEPROPID& id, const QDateTime& value)
+{
+ CEPROPVAL val;
+ val.propid = id;
+ val.wFlags = 0;
+ val.wLenData = 0;
+
+ SYSTEMTIME st;
+ st.wDay = value.date().day();
+ st.wMonth = value.date().month();
+ st.wYear = value.date().year();
+ st.wHour = value.time().hour();
+ st.wMinute = value.time().minute();
+ st.wSecond = value.time().second();
+ st.wMilliseconds = value.time().msec();
+
+ SystemTimeToFileTime(&st, &val.val.filetime); // XXX check for error?
+
+ return val;
+}
+
+// Structures for mapping stuffs
+
+// Our fields to POOM
+// QMap<definition, {QMap<field, poomid>, datatype, maxnumber}>
+typedef bool (*processContactPoomElement)(const QContactWinCEEngine* e, IItem* contact, const QContactDetail& detail, QVector<CEPROPVAL>& props);
+
+// POOM to us
+// something like
+// {PIMPR_,PIMPR_} -> function
+// Might then need PIMPR -> bag above
+
+struct PoomContactElement;
+typedef void (*processPoomContactElement)(const QContactWinCEEngine* e, IItem* contact, const QVariantList& values, QContact& ret);
+
+struct PoomContactElement {
+ QList<CEPROPID> poom;
+ processPoomContactElement func;
+};
+
+static void setIfNotEmpty(QContactDetail& detail, const QString& field, const QString& value)
+{
+ if (!value.isEmpty())
+ detail.setValue(field, value);
+}
+
+static void processName(const QContactWinCEEngine* /*engine*/, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ QContactName name;
+ setIfNotEmpty(name, QContactName::FieldPrefix, values[0].toString());
+ setIfNotEmpty(name, QContactName::FieldFirst, values[1].toString());
+ setIfNotEmpty(name, QContactName::FieldMiddle, values[2].toString());
+ setIfNotEmpty(name, QContactName::FieldLast, values[3].toString());
+ setIfNotEmpty(name, QContactName::FieldSuffix, values[4].toString());
+ setIfNotEmpty(name, QContactName::FieldCustomLabel, values[5].toString());
+ if (!name.isEmpty())
+ ret.saveDetail(&name);
+}
+
+static bool GetStreamSize(IStream* pStream, ULONG* pulSize)
+{
+ HRESULT hr;
+ LARGE_INTEGER li = {0};
+ ULARGE_INTEGER uliZero = {0};
+ ULARGE_INTEGER uli;
+
+ if (pStream != NULL && pulSize != NULL) {
+ hr = pStream->Seek(li, STREAM_SEEK_END, &uli);
+ if (SUCCEEDED(hr)) {
+ *pulSize = uli.LowPart;
+ hr = pStream->Seek(li, STREAM_SEEK_SET, &uliZero);
+ return SUCCEEDED(hr);
+ }
+ }
+ return false;
+}
+
+static bool loadAvatarData(IItem* contact, QByteArray* data)
+{
+ HRESULT hr;
+ SimpleComPointer<IStream> pStream = NULL;
+ ULONG ulSize;
+ // Extract the picture from the contact
+ hr = contact->OpenProperty(PIMPR_PICTURE, GENERIC_READ, &pStream);
+ if (FAILED(hr))
+ return false;
+
+ hr = GetStreamSize(pStream, &ulSize);
+ if (FAILED(hr))
+ return false;
+
+ // In some cases, the property may exist even if there is no picture.
+ // Make sure we can access the stream and don't have a 0 byte stream
+ if (ulSize > 0 && pStream != NULL) {
+
+ ULONG ulSize, readSize;
+ if (!GetStreamSize(pStream, &ulSize))
+ return false;
+
+ // Prepares the data buffer
+ data->resize(ulSize);
+
+ // Read all into the data buffer until reach the end of stream
+ readSize = 0;
+ char* p = data->data();
+ while(ulSize && SUCCEEDED(hr)) {
+ p += readSize;
+ hr = pStream->Read(p, ulSize, &readSize);
+ ulSize -= readSize;
+ }
+ if (FAILED(hr))
+ return false;
+
+ }
+ return true;
+}
+
+static bool saveAvatarData(IItem* contact, const QByteArray& data)
+{
+ HRESULT hr;
+ SimpleComPointer<IStream> pStream = NULL;
+
+ hr = contact->OpenProperty(PIMPR_PICTURE, GENERIC_WRITE, &pStream);
+ if (FAILED(hr))
+ return false;
+
+ ULONG ulWrittenSize;
+ pStream->Write(data.data(), data.size(), &ulWrittenSize);
+
+ if (FAILED(hr))
+ return false;
+
+ hr = pStream->Commit(0);
+
+ if (FAILED(hr))
+ return false;
+
+ hr = contact->Save();
+
+ if (FAILED(hr))
+ return false;
+
+ return true;
+}
+
+
+
+static void processAvatar(const QContactWinCEEngine* /*engine*/, IItem* contact, const QVariantList& values, QContact& ret)
+{
+ QContactAvatar avatar;
+
+ QByteArray data;
+ if (loadAvatarData(contact, &data)) {
+ if (!data.isEmpty()) {
+ QPixmap pixmap;
+ pixmap.loadFromData(data, "PNG");
+ avatar.setPixmap(pixmap);
+ }
+ }
+
+ if (values[0].toString().isEmpty()) {
+ if (!data.isEmpty()) {
+ QUrl url(QUrl::fromEncoded(data.toPercentEncoding()));
+ url.setScheme("data");
+ avatar.setAvatar(url.toString());
+ }
+ } else {
+ avatar.setAvatar(values[0].toString());
+ }
+
+ setIfNotEmpty(avatar, QContactAvatar::FieldSubType, values[1].toString());
+
+
+ if (!avatar.isEmpty())
+ ret.saveDetail(&avatar);
+}
+
+static void processAddress(const QContactWinCEEngine*, const QString& context, const QVariantList& values, QContact& ret)
+{
+ QContactAddress address;
+ address.setContexts(context);
+ setIfNotEmpty(address, QContactAddress::FieldStreet, values[0].toString());
+ setIfNotEmpty(address, QContactAddress::FieldPostcode, values[1].toString());
+ setIfNotEmpty(address, QContactAddress::FieldLocality, values[2].toString());
+ setIfNotEmpty(address, QContactAddress::FieldRegion, values[3].toString());
+ setIfNotEmpty(address, QContactAddress::FieldCountry, values[4].toString());
+ if (!address.isEmpty())
+ ret.saveDetail(&address);
+}
+
+static void processHomeAddress(const QContactWinCEEngine* e, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ processAddress(e, QContactDetail::ContextHome, values, ret);
+}
+
+static void processWorkAddress(const QContactWinCEEngine* e, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ processAddress(e, QContactDetail::ContextWork, values, ret);
+}
+
+static void processOtherAddress(const QContactWinCEEngine* e, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ processAddress(e, QContactDetail::ContextOther, values, ret);
+}
+
+static void processEmails(const QContactWinCEEngine*, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ // First value is our additional metadata..
+ // takes the form of a single character for each email address for the context
+ // Since email addresses don't bubble up if you remove one, we don't store the
+ // full value. but we could..
+ QString meta = values[0].toString();
+
+ // Just create an email address for each value (we're expecting 3)
+ for (int j=1; j < values.size(); j++) {
+ QVariant v = values.at(j);
+ if (!v.isNull()) {
+ QContactEmailAddress e;
+ e.setValue(QContactEmailAddress::FieldEmailAddress, v);
+ QChar m = meta.at(j - 1);
+ if (m == 'H')
+ e.setContexts(QContactDetail::ContextHome);
+ else if (m == 'W')
+ e.setContexts(QContactDetail::ContextWork);
+ ret.saveDetail(&e);
+ }
+ }
+}
+
+static void processPhones(const QContactWinCEEngine*, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ // Just like emails, the first value is our additional metadata
+ // metadata for phone numbers is somewhat crazy.
+ // We use the following characters:
+
+ //
+ // space = default for type
+ // H = Home context, no type modifier
+ // W = Work context, no type modifier
+ // O = Other context
+ // N = No context
+
+ // In general, they should only apply to PROPIDs that don't fully specify both
+ // (e.g. home_fax is fully specified, mobile is not), but we occasionally run out
+ // of places for anonymous numbers and stick them in different places.
+
+ // Indexing is the same as the input list
+
+ QString meta = values[0].toString();
+
+ for (int i=0; i < values.count() - 1; i++) {
+ const QVariant& v = values.at(i + 1);
+ if (!v.isNull()) {
+ QContactPhoneNumber number;
+ number.setValue(QContactPhoneNumber::FieldNumber, v);
+ QChar m = 0;
+ switch(i) {
+ case 0: // Business phone
+ case 1:
+ m = meta.at(i);
+ if (m == ' ')
+ m = 'W';
+ number.setSubTypes(QContactPhoneNumber::SubTypeVoice);
+ break;
+ case 2: // Car
+ m = meta.at(i);
+ number.setSubTypes(QContactPhoneNumber::SubTypeCar);
+ break;
+ case 3: // Mobile
+ m = meta.at(i);
+ number.setSubTypes(QContactPhoneNumber::SubTypeMobile);
+ break;
+ case 4: // Home phones
+ case 5:
+ m = meta.at(i);
+ if (m == ' ')
+ m = 'H';
+ number.setSubTypes(QContactPhoneNumber::SubTypeVoice);
+ break;
+ case 6: // Pager
+ m = meta.at(i);
+ number.setSubTypes(QContactPhoneNumber::SubTypePager);
+ break;
+ case 7: // Radio telephone (??)
+ m = meta.at(i);
+ number.setSubTypes(QContactPhoneNumber::SubTypeMobile);
+ break;
+ case 8: // SIM entry
+ break;
+ case 9: // Home fax
+ m = meta.at(i);
+ if (m == ' ')
+ m = 'H';
+ number.setSubTypes(QContactPhoneNumber::SubTypeFacsimile);
+ break;
+ case 10: // Business fax
+ m = meta.at(i);
+ if (m == ' ')
+ m = 'W';
+ number.setSubTypes(QContactPhoneNumber::SubTypeFacsimile);
+ break;
+ }
+
+ if (m == 'H')
+ number.setContexts(QContactDetail::ContextHome);
+ else if (m == 'W')
+ number.setContexts(QContactDetail::ContextWork);
+ else if (m == 'O')
+ number.setContexts(QContactDetail::ContextOther);
+
+ ret.saveDetail(&number);
+ }
+ }
+}
+
+static void processDates(const QContactWinCEEngine*, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ // We get anniversary, then birthday
+ if (!values[0].toDate().isNull()) {
+ QContactAnniversary ann;
+ ann.setOriginalDate(values[0].toDate());
+ ret.saveDetail(&ann);
+ }
+ if (!values[1].toDate().isNull()) {
+ QContactBirthday bday;
+ bday.setDate(values[1].toDate());
+ ret.saveDetail(&bday);
+ }
+}
+
+static void processId(const QContactWinCEEngine* e, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ QContactId id;
+ id.setLocalId(values.at(0).toUInt());
+ id.setManagerUri(e->managerUri());
+ ret.setId(id);
+}
+
+static void processNickname(const QContactWinCEEngine*, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ QContactNickname nick;
+ setIfNotEmpty(nick, QContactNickname::FieldNickname, values[0].toString());
+
+ if (!nick.isEmpty())
+ ret.saveDetail(&nick);
+}
+
+static void processWebpage(const QContactWinCEEngine*, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ QContactUrl url;
+ setIfNotEmpty(url, QContactUrl::FieldUrl, values[0].toString());
+
+ if (!url.isEmpty())
+ ret.saveDetail(&url);
+}
+
+static void processOrganisation(const QContactWinCEEngine*, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ QContactOrganization org;
+ setIfNotEmpty(org, QContactOrganization::FieldName, values[0].toString());
+ setIfNotEmpty(org, QContactOrganization::FieldDepartment, values[1].toString());
+ setIfNotEmpty(org, QContactOrganization::FieldLocation, values[2].toString());
+ setIfNotEmpty(org, QContactOrganization::FieldTitle, values[3].toString());
+ setIfNotEmpty(org, QContactOrganization::FieldAssistantName, values[4].toString());
+
+ if (!org.isEmpty())
+ ret.saveDetail(&org);
+}
+
+static void processFamily(const QContactWinCEEngine*, IItem* /*contact*/, const QVariantList& values, QContact& ret)
+{
+ QContactFamily family;
+ setIfNotEmpty(family, QContactFamily::FieldSpouse, values[0].toString());
+ setIfNotEmpty(family, QContactFamily::FieldChildren, values[1].toString());
+
+ if (!family.isEmpty())
+ ret.saveDetail(&family);
+}
+
+static void contactP2QTransforms(CEPROPID phoneMeta, CEPROPID emailMeta, CEPROPID avatarMeta, CEPROPID avatarTypeMeta, QHash<CEPROPID, PoomContactElement>& prophash, QVector<CEPROPID>& propids)
+{
+ static QHash<CEPROPID, PoomContactElement> hash;
+ static QVector<CEPROPID> ids;
+
+ if (hash.count() == 0) {
+ QList<PoomContactElement> list;
+
+ // ID
+ PoomContactElement id;
+ id.poom << PIMPR_OID;
+ id.func = processId;
+ list.append(id);
+
+ // Names
+ PoomContactElement name;
+ name.poom << PIMPR_TITLE << PIMPR_FIRST_NAME << PIMPR_MIDDLE_NAME << PIMPR_LAST_NAME << PIMPR_SUFFIX << PIMPR_FILEAS;
+ name.func = processName;
+ list.append(name);
+
+ // Home address
+ PoomContactElement homeAddress;
+ homeAddress.poom << PIMPR_HOME_ADDRESS_STREET << PIMPR_HOME_ADDRESS_POSTAL_CODE << PIMPR_HOME_ADDRESS_CITY << PIMPR_HOME_ADDRESS_STATE << PIMPR_HOME_ADDRESS_COUNTRY;
+ homeAddress.func = processHomeAddress;
+ list.append(homeAddress);
+
+ // Work address
+ PoomContactElement workAddress;
+ workAddress.poom << PIMPR_BUSINESS_ADDRESS_STREET << PIMPR_BUSINESS_ADDRESS_POSTAL_CODE << PIMPR_BUSINESS_ADDRESS_CITY << PIMPR_BUSINESS_ADDRESS_STATE << PIMPR_BUSINESS_ADDRESS_COUNTRY;
+ workAddress.func = processWorkAddress;
+ list.append(workAddress);
+
+ // Other address
+ PoomContactElement otherAddress;
+ otherAddress.poom << PIMPR_OTHER_ADDRESS_STREET << PIMPR_OTHER_ADDRESS_POSTAL_CODE << PIMPR_OTHER_ADDRESS_CITY << PIMPR_OTHER_ADDRESS_STATE << PIMPR_OTHER_ADDRESS_COUNTRY;
+ otherAddress.func = processOtherAddress;
+ list.append(otherAddress);
+
+ // Emails
+ PoomContactElement emails;
+ emails.poom << emailMeta << PIMPR_EMAIL1_ADDRESS << PIMPR_EMAIL2_ADDRESS << PIMPR_EMAIL3_ADDRESS;
+ emails.func = processEmails;
+ list.append(emails);
+
+ // Phone numbers
+ PoomContactElement phones;
+ phones.poom << phoneMeta << PIMPR_BUSINESS_TELEPHONE_NUMBER << PIMPR_BUSINESS2_TELEPHONE_NUMBER
+ << PIMPR_CAR_TELEPHONE_NUMBER << PIMPR_MOBILE_TELEPHONE_NUMBER
+ << PIMPR_HOME_TELEPHONE_NUMBER << PIMPR_HOME2_TELEPHONE_NUMBER
+ << PIMPR_PAGER_NUMBER << PIMPR_RADIO_TELEPHONE_NUMBER
+ << PIMPR_SIM_PHONE
+ << PIMPR_HOME_FAX_NUMBER << PIMPR_BUSINESS_FAX_NUMBER;
+ phones.func = processPhones;
+ list.append(phones);
+
+ // Dates
+ PoomContactElement dates;
+ dates.poom << PIMPR_ANNIVERSARY << PIMPR_BIRTHDAY;
+ dates.func = processDates;
+ list.append(dates);
+
+ // Nickname
+ PoomContactElement nick;
+ nick.poom << PIMPR_NICKNAME;
+ nick.func = processNickname;
+ list.append(nick);
+
+ // Webpage
+ PoomContactElement web;
+ web.poom << PIMPR_WEB_PAGE;
+ web.func = processWebpage;
+ list.append(web);
+
+ // Organisation
+ PoomContactElement org;
+ org.poom << PIMPR_COMPANY_NAME << PIMPR_DEPARTMENT << PIMPR_OFFICE_LOCATION << PIMPR_JOB_TITLE << PIMPR_ASSISTANT_NAME;
+ org.func = processOrganisation;
+ list.append(org);
+
+ // Family
+ PoomContactElement family;
+ family.poom << PIMPR_SPOUSE << PIMPR_CHILDREN;
+ family.func = processFamily;
+ list.append(family);
+
+ // Avatar
+ PoomContactElement avatar;
+ avatar.poom << avatarMeta << avatarTypeMeta; //PIMPR_PICTURE need to be handled inside the processAvatar() function seperately.
+ avatar.func = processAvatar;
+ list.append(avatar);
+
+
+ // XXX Unhandled:
+ //
+ // PIMPR_ACCOUNT_NAME
+ // PIMPR_CUSTOMERID
+ // PIMPR_GOVERNMENTID
+ //
+ // PIMPR_DISPLAY_NAME
+ //
+ // PIMPR_MANAGER
+ // PIMPR_ASSISTANT_TELEPHONE_NUMBER
+ // PIMPR_COMPANY_TELEPHONE_NUMBER
+ // PIMPR_YOMI_COMPANY
+ //
+ //
+ // PIMPR_IM[123]_ADDRESS
+ //
+ // PIMPR_RINGTONE
+
+ // PIMPR_YOMI_FILEAS
+ // PIMPR_YOMI_FIRSTNAME
+ // PIMPR_YOMI_LASTNAME
+
+ // Now, build the hash
+ foreach(const PoomContactElement& e, list) {
+ foreach(CEPROPID id, e.poom) {
+ ids.append(id);
+ hash.insert(id, e);
+ }
+ }
+ }
+ propids = ids;
+ prophash = hash;
+}
+
+static void addIfNotEmpty(const CEPROPID& id, const QString& value, QVector<CEPROPVAL>& props)
+{
+ if (!value.isEmpty())
+ props.append(convertToCEPropVal(id, value));
+}
+
+static bool processQName(const QContactWinCEEngine*, IItem* /*contact*/, const QContactDetail& detail, QVector<CEPROPVAL>& props)
+{
+ addIfNotEmpty(PIMPR_TITLE, detail.value(QContactName::FieldPrefix), props);
+ addIfNotEmpty(PIMPR_FIRST_NAME, detail.value(QContactName::FieldFirst), props);
+ addIfNotEmpty(PIMPR_MIDDLE_NAME, detail.value(QContactName::FieldMiddle), props);
+ addIfNotEmpty(PIMPR_LAST_NAME, detail.value(QContactName::FieldLast), props);
+ addIfNotEmpty(PIMPR_SUFFIX, detail.value(QContactName::FieldSuffix), props);
+ addIfNotEmpty(PIMPR_FILEAS, detail.value(QContactName::FieldCustomLabel), props);
+ return true;
+}
+
+static bool processQAvatar(const QContactWinCEEngine* engine, IItem* contact, const QContactDetail& detail, QVector<CEPROPVAL>& props)
+{
+ QString avatarData = detail.value(QContactAvatar::FieldAvatar);
+ QPixmap avatarPixmap = detail.value<QPixmap>(QContactAvatar::FieldAvatarPixmap);
+
+ addIfNotEmpty(engine->metaAvatarType(), detail.value(QContactAvatar::FieldSubType), props);
+ addIfNotEmpty(engine->metaAvatar(), avatarData, props);
+
+ if (!avatarPixmap.isNull()) {
+ QByteArray data;
+ QBuffer buffer(&data);
+ buffer.open(QIODevice::WriteOnly);
+ if (!avatarPixmap.save(&buffer, "PNG") || !saveAvatarData(contact, data))
+ return false;
+ }
+ return true;
+}
+
+static bool processQFamily(const QContactWinCEEngine*, IItem* /*contact*/, const QContactDetail& detail, QVector<CEPROPVAL>& props)
+{
+ addIfNotEmpty(PIMPR_SPOUSE, detail.value(QContactFamily::FieldSpouse), props);
+ addIfNotEmpty(PIMPR_CHILDREN, detail.value(QContactFamily::FieldChildren), props);
+ return true;
+}
+
+static bool validateDate(const QVariant& val)
+{
+ /*POOM only support date from 1900 to 2999: http://msdn.microsoft.com/en-us/library/aa910841.aspx */
+ QDate date = val.toDate();
+ return date.year() >= 1900 && date.year() <= 2999;
+}
+
+static bool processQBirthday(const QContactWinCEEngine*, IItem* /*contact*/, const QContactDetail& detail, QVector<CEPROPVAL>& props)
+{
+ if (detail.variantValue(QContactBirthday::FieldBirthday).isValid()) {
+ if (!validateDate(detail.variantValue(QContactBirthday::FieldBirthday)))
+ return false;
+ props.append(convertToCEPropVal(PIMPR_BIRTHDAY, detail.variantValue(QContactBirthday::FieldBirthday).toDateTime()));
+ }
+ return true;
+}
+
+static bool processQAnniversary(const QContactWinCEEngine*, IItem* /*contact*/, const QContactDetail& detail, QVector<CEPROPVAL>& props)
+{
+ if (detail.variantValue(QContactAnniversary::FieldOriginalDate).isValid()) {
+ if (!validateDate(detail.variantValue(QContactAnniversary::FieldOriginalDate)))
+ return false;
+ props.append(convertToCEPropVal(PIMPR_ANNIVERSARY, detail.variantValue(QContactAnniversary::FieldOriginalDate).toDateTime()));
+ }
+ return true;
+}
+
+static bool processQNickname(const QContactWinCEEngine*, IItem* /*contact*/, const QContactDetail& detail, QVector<CEPROPVAL>& props)
+{
+ addIfNotEmpty(PIMPR_NICKNAME, detail.value(QContactNickname::FieldNickname), props);
+ return true;
+}
+
+static bool processQOrganisation(const QContactWinCEEngine*, IItem* /*contact*/, const QContactDetail& detail, QVector<CEPROPVAL>& props)
+{
+ QContactOrganization org(detail);
+
+ addIfNotEmpty(PIMPR_COMPANY_NAME, org.name(), props);
+ if (!org.department().isEmpty())
+ addIfNotEmpty(PIMPR_DEPARTMENT, org.department().at(0), props);
+ addIfNotEmpty(PIMPR_OFFICE_LOCATION, org.location(), props);
+ addIfNotEmpty(PIMPR_JOB_TITLE, org.title(), props);
+ addIfNotEmpty(PIMPR_ASSISTANT_NAME, org.assistantName(), props);
+ return true;
+}
+
+static bool processQWebpage(const QContactWinCEEngine*, IItem* /*contact*/, const QContactDetail& detail, QVector<CEPROPVAL>& props)
+{
+ QContactUrl url(detail);
+
+ addIfNotEmpty(PIMPR_WEB_PAGE, url.url(), props);
+ return true;
+}
+
+/* Bulk setters */
+static void processQPhones(const QList<QContactPhoneNumber>& nums, CEPROPID metaId, QVector<CEPROPVAL>& props)
+{
+ QList<CEPROPID> availableIds;
+ availableIds << PIMPR_BUSINESS_TELEPHONE_NUMBER << PIMPR_BUSINESS2_TELEPHONE_NUMBER
+ << PIMPR_CAR_TELEPHONE_NUMBER << PIMPR_MOBILE_TELEPHONE_NUMBER
+ << PIMPR_HOME_TELEPHONE_NUMBER << PIMPR_HOME2_TELEPHONE_NUMBER
+ << PIMPR_PAGER_NUMBER << PIMPR_RADIO_TELEPHONE_NUMBER
+ << PIMPR_SIM_PHONE
+ << PIMPR_HOME_FAX_NUMBER << PIMPR_BUSINESS_FAX_NUMBER;
+
+ QString meta;
+ meta.fill(' ', availableIds.count()); // init with zero metadata
+
+ QList<QContactPhoneNumber> numbers = nums;
+ QList<QContactPhoneNumber> deferred;
+ for(int j = 0; j < numbers.count() + deferred.count(); /* no j++ */) {
+ const QContactPhoneNumber& number = j < numbers.count() ? numbers.at(j) : deferred.at(j - numbers.count());
+
+ CEPROPID id = PIMPR_INVALID_ID;
+
+ // Map from our attributes to ids
+ if (number.subTypes().contains(QContactPhoneNumber::SubTypeCar))
+ id = PIMPR_CAR_TELEPHONE_NUMBER;
+ else if (number.subTypes().contains(QContactPhoneNumber::SubTypeMobile))
+ id = PIMPR_MOBILE_TELEPHONE_NUMBER;
+ else if (number.subTypes().contains(QContactPhoneNumber::SubTypeFacsimile)) {
+ if (number.contexts().contains(QContactDetail::ContextHome))
+ id = PIMPR_HOME_FAX_NUMBER;
+ else if (number.contexts().contains(QContactDetail::ContextWork))
+ id = PIMPR_BUSINESS_FAX_NUMBER;
+ } else if (number.subTypes().contains(QContactPhoneNumber::SubTypeVoice)) {
+ if (number.contexts().contains(QContactDetail::ContextHome))
+ id = availableIds.contains(PIMPR_HOME_TELEPHONE_NUMBER) ? PIMPR_HOME_TELEPHONE_NUMBER : PIMPR_HOME2_TELEPHONE_NUMBER;
+ else if (number.contexts().contains(QContactDetail::ContextWork))
+ id = availableIds.contains(PIMPR_BUSINESS_TELEPHONE_NUMBER) ? PIMPR_BUSINESS_TELEPHONE_NUMBER : PIMPR_BUSINESS2_TELEPHONE_NUMBER;
+ } else if (number.subTypes().contains(QContactPhoneNumber::SubTypePager))
+ id = PIMPR_PAGER_NUMBER;
+ else if (number.subTypes().isEmpty()) {
+ // We do this anonymous number at the end, if we haven't already deferred it
+ if (j < numbers.count()) {
+ deferred.append(numbers.takeAt(j));
+ continue;
+ }
+
+ // Well, deferred it once, process it now
+ // XXX it might make more sense to reorder these to better match contexts..
+ if (availableIds.contains(PIMPR_HOME_TELEPHONE_NUMBER))
+ id = PIMPR_HOME_TELEPHONE_NUMBER;
+ else if (availableIds.contains(PIMPR_HOME2_TELEPHONE_NUMBER))
+ id = PIMPR_HOME2_TELEPHONE_NUMBER;
+ else if (availableIds.contains(PIMPR_BUSINESS_TELEPHONE_NUMBER))
+ id = PIMPR_BUSINESS_TELEPHONE_NUMBER;
+ else if (availableIds.contains(PIMPR_BUSINESS2_TELEPHONE_NUMBER))
+ id = PIMPR_BUSINESS2_TELEPHONE_NUMBER;
+ }
+
+ // get ready for the next
+ j++;
+
+ // Now see if we still have a slot
+ if (id == PIMPR_INVALID_ID) {
+ qDebug() << "Didn't match source detail:" << number.contexts() << number.subTypes();
+ } else {
+ if (!availableIds.contains(id)) {
+ qDebug() << "Too many phone numbers, store this some other way:" << number.contexts() << number.subTypes();
+ } else {
+ // Set the meta information
+ if (number.contexts().contains(QContactDetail::ContextHome))
+ meta[availableIds.indexOf(id)] = 'H';
+ else if (number.contexts().contains(QContactDetail::ContextWork))
+ meta[availableIds.indexOf(id)] = 'W';
+ else if (number.contexts().contains(QContactDetail::ContextOther))
+ meta[availableIds.indexOf(id)] = 'O';
+ else if (number.contexts().count() == 0)
+ meta[availableIds.indexOf(id)] = 'N';
+
+ props.append(convertToCEPropVal(id, number.number()));
+ availableIds.replace(availableIds.indexOf(id), PIMPR_INVALID_ID); // not available any more
+ }
+ }
+ }
+
+ props.append(convertToCEPropVal(metaId, meta));
+}
+
+static void processQEmails(const QList<QContactEmailAddress>& emails, CEPROPID metaId, QVector<CEPROPVAL>& props)
+{
+ QList<CEPROPID> availableIds;
+ availableIds << PIMPR_EMAIL1_ADDRESS << PIMPR_EMAIL2_ADDRESS << PIMPR_EMAIL3_ADDRESS;
+
+ QString meta;
+
+ foreach(const QContactEmailAddress& email, emails) {
+ CEPROPID id = availableIds.takeFirst();
+ if (id != 0) {
+ if (email.contexts().contains(QContactDetail::ContextHome))
+ meta += "H";
+ else if (email.contexts().contains(QContactDetail::ContextWork))
+ meta += "W";
+ else
+ meta += " ";
+ props.append(convertToCEPropVal(id, email.emailAddress()));
+ } else {
+ qDebug() << "Too many email addresses";
+ break;
+ }
+ }
+ props.append(convertToCEPropVal(metaId, meta));
+}
+
+static void processQAddresses(const QList<QContactAddress>& addresses, QVector<CEPROPVAL>& props)
+{
+ // We do 2 passes - first try to match addresses to contexts
+ // then use whatever's left for the rest
+
+ bool homeAvailable = true;
+ bool workAvailable = true;
+ bool otherAvailable = true;
+
+ QList<QContactAddress> deferred;
+ foreach(const QContactAddress& address, addresses) {
+ if (address.contexts().contains(QContactDetail::ContextHome)) {
+ if (homeAvailable) {
+ addIfNotEmpty(PIMPR_HOME_ADDRESS_CITY, address.locality(), props);
+ addIfNotEmpty(PIMPR_HOME_ADDRESS_COUNTRY, address.country(), props);
+ addIfNotEmpty(PIMPR_HOME_ADDRESS_POSTAL_CODE, address.postcode(), props);
+ addIfNotEmpty(PIMPR_HOME_ADDRESS_STATE, address.region(), props);
+ addIfNotEmpty(PIMPR_HOME_ADDRESS_STREET, address.street(), props);
+ homeAvailable = false;
+ } else {
+ deferred.append(address);
+ }
+ } else if (address.contexts().contains(QContactDetail::ContextWork)) {
+ if (workAvailable) {
+ addIfNotEmpty(PIMPR_BUSINESS_ADDRESS_CITY, address.locality(), props);
+ addIfNotEmpty(PIMPR_BUSINESS_ADDRESS_COUNTRY, address.country(), props);
+ addIfNotEmpty(PIMPR_BUSINESS_ADDRESS_POSTAL_CODE, address.postcode(), props);
+ addIfNotEmpty(PIMPR_BUSINESS_ADDRESS_STATE, address.region(), props);
+ addIfNotEmpty(PIMPR_BUSINESS_ADDRESS_STREET, address.street(), props);
+ workAvailable = false;
+ } else {
+ deferred.append(address);
+ }
+ } else {
+ if (otherAvailable) {
+ addIfNotEmpty(PIMPR_OTHER_ADDRESS_CITY, address.locality(), props);
+ addIfNotEmpty(PIMPR_OTHER_ADDRESS_COUNTRY, address.country(), props);
+ addIfNotEmpty(PIMPR_OTHER_ADDRESS_POSTAL_CODE, address.postcode(), props);
+ addIfNotEmpty(PIMPR_OTHER_ADDRESS_STATE, address.region(), props);
+ addIfNotEmpty(PIMPR_OTHER_ADDRESS_STREET, address.street(), props);
+ otherAvailable = false;
+ } else {
+ deferred.append(address);
+ }
+ }
+ }
+
+ // Now the deferred ones
+ while(deferred.count() > 0) {
+ // If there's nothing left..
+ if (!homeAvailable && !workAvailable && !otherAvailable) {
+ qDebug() << "Too many addresses";
+ return;
+ }
+
+ QContactAddress address = deferred.takeFirst();
+
+ // Well, first choice is to use other
+ // but we really need to save the contexts XXX
+ if (otherAvailable) {
+ addIfNotEmpty(PIMPR_OTHER_ADDRESS_CITY, address.locality(), props);
+ addIfNotEmpty(PIMPR_OTHER_ADDRESS_COUNTRY, address.country(), props);
+ addIfNotEmpty(PIMPR_OTHER_ADDRESS_POSTAL_CODE, address.postcode(), props);
+ addIfNotEmpty(PIMPR_OTHER_ADDRESS_STATE, address.region(), props);
+ addIfNotEmpty(PIMPR_OTHER_ADDRESS_STREET, address.street(), props);
+ otherAvailable = false;
+ } else if (workAvailable) {
+ addIfNotEmpty(PIMPR_BUSINESS_ADDRESS_CITY, address.locality(), props);
+ addIfNotEmpty(PIMPR_BUSINESS_ADDRESS_COUNTRY, address.country(), props);
+ addIfNotEmpty(PIMPR_BUSINESS_ADDRESS_POSTAL_CODE, address.postcode(), props);
+ addIfNotEmpty(PIMPR_BUSINESS_ADDRESS_STATE, address.region(), props);
+ addIfNotEmpty(PIMPR_BUSINESS_ADDRESS_STREET, address.street(), props);
+ workAvailable = false;
+ } else {
+ addIfNotEmpty(PIMPR_HOME_ADDRESS_CITY, address.locality(), props);
+ addIfNotEmpty(PIMPR_HOME_ADDRESS_COUNTRY, address.country(), props);
+ addIfNotEmpty(PIMPR_HOME_ADDRESS_POSTAL_CODE, address.postcode(), props);
+ addIfNotEmpty(PIMPR_HOME_ADDRESS_STATE, address.region(), props);
+ addIfNotEmpty(PIMPR_HOME_ADDRESS_STREET, address.street(), props);
+ homeAvailable = false;
+ }
+ }
+}
+
+static void contactQ2PTransforms(QHash<QString, processContactPoomElement>& ret)
+{
+ static QHash<QString, processContactPoomElement> hash;
+ if (hash.count() == 0) {
+ hash.insert(QContactName::DefinitionName, processQName);
+ hash.insert(QContactAnniversary::DefinitionName, processQAnniversary);
+ hash.insert(QContactBirthday::DefinitionName, processQBirthday);
+ hash.insert(QContactNickname::DefinitionName, processQNickname);
+ hash.insert(QContactOrganization::DefinitionName, processQOrganisation);
+ hash.insert(QContactUrl::DefinitionName, processQWebpage);
+ hash.insert(QContactFamily::DefinitionName, processQFamily);
+ hash.insert(QContactAvatar::DefinitionName, processQAvatar);
+ }
+ ret = hash;
+}
+
+QContact QContactWinCEEngine::convertToQContact(IItem *contact) const
+{
+ QContact ret;
+
+ // Several choices for converting a contact:
+ // - IContact and lots of get_XXXXX
+ // - IItem and one or more GetProps calls
+ // - etc
+ // No idea which one is faster.. needs a benchmark
+ // use IItem for now, since it's a bit more generic and we can
+ // use PIMPR_* rather than the different explicit function calls
+
+ unsigned long cbSize = 0;
+
+ // Map information
+ QHash<CEPROPID, PoomContactElement> hash;
+ QVector<CEPROPID> props;
+
+ // Get our mapping tables
+ contactP2QTransforms(d->m_phonemeta, d->m_emailmeta, d->m_avatartypemeta, d->m_avatarmeta, hash, props);
+
+ CEPROPVAL *propvals = 0;
+ HRESULT hr = contact->GetProps(props.constData(), CEDB_ALLOWREALLOC, props.count(), &propvals, &cbSize, GetProcessHeap());
+
+ if (SUCCEEDED(hr)) {
+ // Loop over each property, adding to a map
+ // since there should only be juicy data retrieved
+ QHash<CEPROPID, QVariant> valueHash;
+ for (int i=0; i < props.count(); i++) {
+ QVariant v = CEPropValToQVariant(propvals[i]);
+ if (!v.isNull()) {
+ valueHash.insert(propvals[i].propid, v);
+ }
+ }
+
+ // Now process our map transform elements, removing things as we go
+ while(valueHash.count() > 0) {
+ CEPROPID id = valueHash.constBegin().key();
+
+ const PoomContactElement& qmap = hash.value(id);
+ if (qmap.func) {
+ // We need to create values for each of qmap.poom
+ // (which means we need to find each value of qmap.poom)
+ // and we remove them as we go
+ QVariantList vl;
+ foreach(const CEPROPID& id, qmap.poom) {
+ vl << valueHash.take(id);
+ }
+ qmap.func(this, contact, vl, ret);
+ } else {
+ qDebug() << "Didn't match property for id:" << QString::number(id, 16);
+ // Remove the ignored value so we don't infinite loop
+ valueHash.take(id);
+ }
+ }
+ HeapFree(GetProcessHeap(), 0, propvals);
+ }
+
+ // Synthesize the display label.
+ QContactManager::Error error;
+ QString synth = synthesizedDisplayLabel(ret, error);
+ ret = setContactDisplayLabel(synth, ret);
+
+ return ret;
+}
+
+bool QContactWinCEEngine::convertFromQContact(const QContact& contact, IItem* item, QContactManager::Error &error) const
+{
+ // We have to create a whole bunch of CEPROPVALs for each detail
+ // This is slightly hampered by the limited storage slots
+
+ QList<QContactDetail> details = contact.details();
+ QHash<QString, processContactPoomElement> transforms;
+
+ contactQ2PTransforms(transforms);
+ processContactPoomElement func;
+
+ QVector<CEPROPVAL> props;
+
+ foreach (const QContactDetail& detail, details) {
+ func = transforms.value(detail.definitionName());
+ if (func) {
+ if (!func(this, item, detail, props)) {
+ error = QContactManager::InvalidDetailError;
+ return false;
+ }
+ }
+ }
+
+ // Now the bulk transforms
+ processQPhones(contact.details<QContactPhoneNumber>(), d->m_phonemeta, props);
+ processQEmails(contact.details<QContactEmailAddress>(), d->m_emailmeta, props);
+ processQAddresses(contact.details<QContactAddress>(), props);
+
+ // Now set it
+ HRESULT hr = item->SetProps(0, props.count(), props.data());
+ if (FAILED(hr)) {
+ qWarning() << QString("Failed to set props: %1 (%2)").arg(hr, 0, 16).arg(HRESULT_CODE(hr), 0, 16);
+ error = QContactManager::UnspecifiedError;
+ }
+
+ wcsdupHelper.clear();
+
+ return true;
+}
+
+/**
+ * Convert the given POOM property \a id into a POOM property name string for POOM native query.
+ * The complete set of Contact property identifiers and their string names for queries.
+ * All the string names copied from: http://msdn.microsoft.com/en-us/library/bb415504.aspx.
+ */
+static QString getPropertyName(const CEPROPID& id)
+{
+ switch (id) {
+ case PIMPR_ACCOUNT_NAME:
+ return "[AccountName]";
+ case PIMPR_ANNIVERSARY:
+ return "[Anniversary]";
+ case PIMPR_ASSISTANT_NAME:
+ return "[AssistantName]";
+ case PIMPR_ASSISTANT_TELEPHONE_NUMBER:
+ return "[AssistantTelephoneNumber]";
+ case PIMPR_BIRTHDAY:
+ return "[Birthday]";
+ case PIMPR_BUSINESS_ADDRESS:
+ return "[BusinessAddress]";
+ case PIMPR_BUSINESS_ADDRESS_CITY:
+ return "[BusinessAddressCity]";
+ case PIMPR_BUSINESS_ADDRESS_COUNTRY:
+ return "[BusinessAddressCountry]";
+ case PIMPR_BUSINESS_ADDRESS_POSTAL_CODE:
+ return "[BusinessAddressPostalCode]";
+ case PIMPR_BUSINESS_ADDRESS_STATE:
+ return "[BusinessAddressState]";
+ case PIMPR_BUSINESS_ADDRESS_STREET:
+ return "[BusinessAddressStreet]";
+ case PIMPR_BUSINESS_FAX_NUMBER:
+ return "[BusinessFaxNumber]";
+ case PIMPR_BUSINESS_TELEPHONE_NUMBER:
+ return "[BusinessTelephoneNumber]";
+ case PIMPR_BUSINESS2_TELEPHONE_NUMBER:
+ return "[Business2TelephoneNumber]";
+ case PIMPR_CAR_TELEPHONE_NUMBER:
+ return "[CarTelephoneNumber]";
+ case PIMPR_CHILDREN:
+ return "[Children]";
+ case PIMPR_COMPANY_NAME:
+ return "[CompanyName]";
+ case PIMPR_COMPANY_TELEPHONE_NUMBER:
+ return "[CompanyTelephoneNumber]";
+#if defined (PIMPR_CONTACT_TYPE)
+ //PIMPR_CONTACT_TYPE is only supported by Windows Mobile 6 and later.
+ case PIMPR_CONTACT_TYPE:
+ return "[ContactType]";
+#endif
+ case PIMPR_CUSTOMERID:
+ return "[CustomerId]";
+ case PIMPR_DEPARTMENT:
+ return "[Department]";
+ case PIMPR_DISPLAY_NAME:
+ return "[DisplayName]";
+ case PIMPR_EMAIL1_ADDRESS:
+ return "[Email1Address]";
+ case PIMPR_EMAIL2_ADDRESS:
+ return "[Email2Address]";
+ case PIMPR_EMAIL3_ADDRESS:
+ return "[Email3Address]";
+ case PIMPR_FILEAS:
+ return "[FileAs]";
+ case PIMPR_FIRST_NAME:
+ return "[FirstName]";
+ case PIMPR_GOVERNMENTID:
+ return "[GovernmentId]";
+ case PIMPR_HOME_ADDRESS:
+ return "[HomeAddress]";
+ case PIMPR_HOME_ADDRESS_CITY:
+ return "[HomeAddressCity]";
+ case PIMPR_HOME_ADDRESS_COUNTRY:
+ return "[HomeAddressCountry]";
+ case PIMPR_HOME_ADDRESS_POSTAL_CODE:
+ return "[HomeAddressPostalCode]";
+ case PIMPR_HOME_ADDRESS_STATE:
+ return "[HomeAddressState]";
+ case PIMPR_HOME_ADDRESS_STREET:
+ return "[HomeAddressStreet]";
+ case PIMPR_HOME_FAX_NUMBER:
+ return "[HomeFaxNumber]";
+ case PIMPR_HOME_TELEPHONE_NUMBER:
+ return "[HomeTelephoneNumber]";
+ case PIMPR_HOME2_TELEPHONE_NUMBER:
+ return "[Home2TelephoneNumber]";
+ case PIMPR_IM1_ADDRESS:
+ return "[IM1Address]";
+ case PIMPR_IM2_ADDRESS:
+ return "[IM2Address]";
+ case PIMPR_IM3_ADDRESS:
+ return "[IM3Address]";
+ case PIMPR_JOB_TITLE:
+ return "[JobTitle]";
+ case PIMPR_LAST_NAME:
+ return "[LastName]";
+ case PIMPR_MANAGER:
+ return "[Manager]";
+ case PIMPR_MIDDLE_NAME:
+ return "[MiddleName]";
+ case PIMPR_MMS:
+ return "[Mms]";
+ case PIMPR_MOBILE_TELEPHONE_NUMBER:
+ return "[MobileTelephoneNumber]";
+ case PIMPR_NICKNAME:
+ return "[Nickname]";
+ case PIMPR_OFFICE_LOCATION:
+ return "[OfficeLocation]";
+ case PIMPR_OTHER_ADDRESS:
+ return "[OtherAddress]";
+ case PIMPR_OTHER_ADDRESS_CITY:
+ return "[OtherAddressCity]";
+ case PIMPR_OTHER_ADDRESS_COUNTRY:
+ return "[OtherAddressCountry]";
+ case PIMPR_OTHER_ADDRESS_POSTAL_CODE:
+ return "[OtherAddressPostalCode]";
+ case PIMPR_OTHER_ADDRESS_STATE:
+ return "[OtherAddressState]";
+ case PIMPR_OTHER_ADDRESS_STREET:
+ return "[OtherAddressStreet]";
+ case PIMPR_PAGER_NUMBER:
+ return "[PagerNumber]";
+ case PIMPR_PICTURE:
+ return "[Picture]";
+ case PIMPR_RADIO_TELEPHONE_NUMBER:
+ return "[RadioTelephoneNumber]";
+ case PIMPR_RINGTONE:
+ return "[RingTone]";
+ case PIMPR_SIM_PHONE:
+ return "[SIMPhone]";
+ case PIMPR_SMARTPROP:
+ return "[SmartProperty]";
+ case PIMPR_SMS:
+ return "[Sms]";
+ case PIMPR_SPOUSE:
+ return "[Spouse]";
+ case PIMPR_SUFFIX:
+ return "[Suffix]";
+ case PIMPR_TITLE:
+ return "[Prefix]";
+ case PIMPR_WEB_PAGE:
+ return "[WebPage]";
+ case PIMPR_YOMI_COMPANY:
+ return "[YomiCompanyName]";
+ case PIMPR_YOMI_FILEAS:
+ return "[YomiFileAs]";
+ case PIMPR_YOMI_FIRSTNAME:
+ return "[YomiFirstName]";
+ case PIMPR_YOMI_LASTNAME:
+ return "[YomiLastName]";
+ }
+ return "";
+}
+
+static QString convertToCEPropValString(const CEPROPID& id, const QVariant& val)
+{
+ if (id != PIMPR_INVALID_ID && val.isValid()) {
+ if ((id & CEVT_LPWSTR) || (id & CEVT_FILETIME)) {
+ return QString("\"%1\"").arg(val.toString());
+ } else {
+ return val.toString();
+ }
+ }
+ return QString();
+}
+
+// Hash for contact detail definition names to POOM prop ids.
+static QHash<QString, CEPROPID> hashForContactDetailToPoomPropId;
+
+#define Q_HASH_CONTACT_DETAIL_TO_POOM_ID(contactClass, contactField, poomId) \
+ hashForContactDetailToPoomPropId.insertMulti(((QString)contactClass::DefinitionName).append((QString)contactClass::contactField), poomId)
+
+void QContactWinCEEngine::buildHashForContactDetailToPoomPropId() const
+{
+ if (hashForContactDetailToPoomPropId.isEmpty()) {
+ //QContactName
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactName, FieldPrefix, PIMPR_TITLE);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactName, FieldFirst, PIMPR_FIRST_NAME);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactName, FieldMiddle, PIMPR_MIDDLE_NAME);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactName, FieldLast, PIMPR_LAST_NAME);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactName, FieldSuffix, PIMPR_SUFFIX);
+
+ // Display label
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactDisplayLabel, FieldLabel, PIMPR_FILEAS);
+
+ // Home address
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldStreet, PIMPR_HOME_ADDRESS_STREET);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldPostcode, PIMPR_HOME_ADDRESS_POSTAL_CODE);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldLocality, PIMPR_HOME_ADDRESS_CITY);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldRegion, PIMPR_HOME_ADDRESS_STATE);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldCountry, PIMPR_HOME_ADDRESS_COUNTRY);
+
+ // Work address
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldStreet, PIMPR_BUSINESS_ADDRESS_STREET);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldPostcode, PIMPR_BUSINESS_ADDRESS_POSTAL_CODE);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldLocality, PIMPR_BUSINESS_ADDRESS_CITY);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldCountry, PIMPR_BUSINESS_ADDRESS_COUNTRY);
+
+ // Other address
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldStreet, PIMPR_OTHER_ADDRESS_STREET);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldPostcode, PIMPR_OTHER_ADDRESS_POSTAL_CODE);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldLocality, PIMPR_OTHER_ADDRESS_CITY);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAddress, FieldCountry, PIMPR_OTHER_ADDRESS_COUNTRY);
+
+ // Emails
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactEmailAddress, FieldEmailAddress, PIMPR_EMAIL1_ADDRESS);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactEmailAddress, FieldEmailAddress, PIMPR_EMAIL2_ADDRESS);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactEmailAddress, FieldEmailAddress, PIMPR_EMAIL3_ADDRESS);
+
+ // Phone numbers
+ //XXX If too many PIM_PR* bind to the same detail field will cause the POOM query string too long...
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_BUSINESS_TELEPHONE_NUMBER);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_BUSINESS2_TELEPHONE_NUMBER);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_CAR_TELEPHONE_NUMBER);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_MOBILE_TELEPHONE_NUMBER);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_HOME_TELEPHONE_NUMBER);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_HOME2_TELEPHONE_NUMBER);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_PAGER_NUMBER);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_RADIO_TELEPHONE_NUMBER);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_SIM_PHONE);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_HOME_FAX_NUMBER);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactPhoneNumber, FieldNumber, PIMPR_BUSINESS_FAX_NUMBER);
+
+ // Dates
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactAnniversary, FieldOriginalDate, PIMPR_ANNIVERSARY);
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactBirthday, FieldBirthday, PIMPR_BIRTHDAY);
+
+ // Nickname
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactNickname, FieldNickname, PIMPR_NICKNAME);
+
+ // Webpage
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactUrl, FieldUrl, PIMPR_WEB_PAGE);
+
+ // Organisation
+ Q_HASH_CONTACT_DETAIL_TO_POOM_ID(QContactOrganization, FieldName, PIMPR_COMPANY_NAME);
+
+ // XXX Todo
+ // Spouse and children
+ }
+}
+
+static QList<CEPROPID> convertToCEPropIds(const QString& detailDefinitionName, const QString& detailFieldName)
+{
+ return hashForContactDetailToPoomPropId.values(QString(detailDefinitionName).append(detailFieldName));
+}
+
+
+/*!
+ * Convert from the supplied QContactFilter \a filter into a POOM query string.
+ * Return empty string if any error occured.
+ */
+QString QContactWinCEEngine::convertFilterToQueryString(const QContactFilter& filter) const
+{
+ QString ret;
+ switch(filter.type()) {
+ case QContactFilter::InvalidFilter:
+ {
+ //Always FALSE
+ ret = "([Oid] = 0 AND [Oid] <> 0)";
+ }
+ break;
+ case QContactFilter::DefaultFilter:
+ {
+ //Always TRUE?
+ ret = "[Oid] <> 0";
+ }
+ break;
+
+ case QContactFilter::LocalIdFilter:
+ {
+ const QContactLocalIdFilter idf(filter);
+ QList<QContactLocalId> ids = idf.ids();
+ if (!ids.isEmpty())
+ {
+ QStringList idList;
+ foreach(const QContactLocalId id, ids) {
+ idList << QString("[Oid] = %1").arg(id);
+ }
+ ret = idList.join(" OR ");
+ ret.prepend('(').append(')');
+ }
+ }
+ break;
+
+ case QContactFilter::ContactDetailFilter:
+ {
+ const QContactDetailFilter cdf(filter);
+ //XXX Only exact match can be supported?
+ if (cdf.matchFlags() == QContactFilter::MatchExactly && cdf.value().isValid()) {
+ QList<CEPROPID> ids = convertToCEPropIds(cdf.detailDefinitionName(), cdf.detailFieldName());
+ if (!ids.isEmpty()) {
+ QStringList strList;
+ foreach (CEPROPID id, ids) {
+ strList << QString("%1 = %2").arg(getPropertyName(id))
+ .arg(convertToCEPropValString(id, cdf.value()));
+ }
+ ret = QString("(%1)").arg(strList.join(" OR "));
+ }
+ }
+ // Fall through to end
+ }
+ break;
+
+ case QContactFilter::ContactDetailRangeFilter:
+ {
+ const QContactDetailRangeFilter cdf(filter);
+ //XXX Only exact match can be supported?
+ if (cdf.matchFlags() == QContactFilter::MatchExactly && (cdf.minValue().isValid() || cdf.maxValue().isValid())) {
+ QList<CEPROPID> ids = convertToCEPropIds(cdf.detailDefinitionName(), cdf.detailFieldName());
+ if (!ids.isEmpty()) {
+ const QString minComp = cdf.rangeFlags() & QContactDetailRangeFilter::ExcludeLower ? ">" : ">=";
+ const QString maxComp = cdf.rangeFlags() & QContactDetailRangeFilter::IncludeUpper ? "<=" : "<";
+
+ QString minCompString, maxCompString;
+ QStringList strList;
+
+ foreach (CEPROPID id, ids) {
+ if (cdf.minValue().isValid()) {
+ minCompString = QString("%1 %2 %3").arg(getPropertyName(id))
+ .arg(minComp)
+ .arg(convertToCEPropValString(id, cdf.minValue()));
+ }
+ if (cdf.maxValue().isValid()) {
+ maxCompString = QString("%1 %2 %3").arg(getPropertyName(id))
+ .arg(maxComp)
+ .arg(convertToCEPropValString(id, cdf.maxValue()));
+ }
+
+ if (!minCompString.isEmpty() && !maxCompString.isEmpty()) {
+ strList << QString("(%1 AND %2)").arg(minCompString).arg(maxCompString);
+ } else {
+ strList << (minCompString.isEmpty() ? maxCompString : minCompString);
+ }
+ }
+ ret = QString("(%1)").arg(strList.join(" OR "));
+ }
+ }
+ // Fall through to end
+ }
+ break;
+
+ case QContactFilter::RelationshipFilter:
+ //XXX Group detail is not supported by WinCE backend
+ break;
+
+ case QContactFilter::ChangeLogFilter:
+ //XXX Timestamp detail is not supported by WinCE backend
+ break;
+
+ case QContactFilter::ActionFilter:
+ {
+ // Find any matching actions, and do a union filter on their filter objects
+ QContactActionFilter af(filter);
+ QList<QContactActionDescriptor> descriptors = QContactAction::actionDescriptors(af.actionName(), af.vendorName(), af.implementationVersion());
+
+ QString str;
+ QStringList strList;
+ for (int j = 0; j < descriptors.count(); j++) {
+ QContactAction* action = QContactAction::action(descriptors.at(j));
+
+ QContactFilter d = action->contactFilter(af.value());
+ delete action; // clean up.
+ if (!QContactManagerEngine::validateActionFilter(d))
+ return QString();
+
+ str = convertFilterToQueryString(d);
+ if (str.isEmpty())
+ return QString();
+ strList << str;
+ }
+
+ if (!strList.isEmpty()) {
+ ret =QString("(%1)").arg(strList.join(" OR "));
+ }
+ // Fall through to end
+ }
+ break;
+
+ case QContactFilter::IntersectionFilter:
+ {
+ const QContactIntersectionFilter bf(filter);
+ const QList<QContactFilter>& terms = bf.filters();
+ if (terms.count() > 0) {
+ QString str;
+ QStringList strList;
+ for(int j = 0; j < terms.count(); j++) {
+ str = convertFilterToQueryString(terms.at(j));
+ if (str.isEmpty())
+ return QString();
+ strList << str;
+ }
+ if (!strList.isEmpty()) {
+ ret =QString("(%1)").arg(strList.join(" AND "));
+ }
+ }
+ // Fall through to end
+ }
+ break;
+
+ case QContactFilter::UnionFilter:
+ {
+ const QContactUnionFilter bf(filter);
+ const QList<QContactFilter>& terms = bf.filters();
+ if (terms.count() > 0) {
+ QString str;
+ QStringList strList;
+ for(int j = 0; j < terms.count(); j++) {
+ str = convertFilterToQueryString(terms.at(j));
+ if (str.isEmpty())
+ return QString();
+ }
+ if (!strList.isEmpty()) {
+ ret =QString("(%1)").arg(strList.join(" OR "));
+ }
+ }
+ // Fall through to end
+ }
+ break;
+ }
+ return ret;
+}
+
+
+/*!
+ * Return a list of QContact ids from the given POOM item \a collection.
+ */
+QList<QContactLocalId> QContactWinCEEngine::convertP2QIdList(const SimpleComPointer<IPOutlookItemCollection>& collection) const
+{
+ SimpleComPointer<IPOlItems2> items;
+ QList<QContactLocalId> ids;
+ if (SUCCEEDED(collection->QueryInterface<IPOlItems2>(&items))) {
+ CEPROPID propid = PIMPR_OID;
+ CEPROPVAL *ppropval = 0;
+
+ int count = 0;
+ items->get_Count(&count);
+
+ ULONG cbSize = 0;
+
+ // Allocate something to start with
+ ppropval = (CEPROPVAL*) HeapAlloc(GetProcessHeap(), 0, sizeof(CEPROPVAL));
+
+ HRESULT hr;
+ for(int i=0; i < count; i++) {
+ hr = items->GetProps(i +1, &propid, 0, 1, &ppropval, &cbSize, NULL);
+ if (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == hr) {
+ ppropval = (CEPROPVAL*) HeapReAlloc(GetProcessHeap(), 0, ppropval, cbSize);
+ hr = items->GetProps(i + 1, &propid, 0, 1, &ppropval, &cbSize, NULL);
+ }
+ if (SUCCEEDED(hr)) {
+ ids << (QContactLocalId) ppropval->val.ulVal;
+ } else {
+ qDebug() << QString("Eternal sadness: %1").arg(HRESULT_CODE(hr), 0, 16);
+ }
+ }
+
+ HeapFree(GetProcessHeap(), 0, ppropval);
+ }
+ return ids;
+}
+
+/*!
+ * Return a list of contacts from the given POOM \a collection.
+ */
+bool QContactWinCEEngine::convertP2QContacts(const SimpleComPointer<IPOutlookItemCollection>& collection, QList<QContact>* contacts) const
+{
+ int itemCount;
+ HRESULT hr;
+ hr = collection->get_Count(&itemCount);
+ if (SUCCEEDED(hr)) {
+ SimpleComPointer<IDispatch> idisp = 0;
+ SimpleComPointer<IItem> iItem;
+ for (int i = 1; i <= itemCount; i++) {
+ HRESULT hr = collection->Item(i, &idisp);
+ if (SUCCEEDED(hr) &&
+ SUCCEEDED(hr = idisp->QueryInterface<IItem>(&iItem))) {
+ contacts->append(convertToQContact(iItem));
+ } else {
+ qDebug() << QString("Eternal sadness: %1").arg(HRESULT_CODE(hr), 0, 16);
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+static bool sortPOOMContacts(const SimpleComPointer<IPOutlookItemCollection>& collection, const QContactSortOrder& sortOrder)
+{
+ HRESULT hr = S_FALSE;
+ QList<CEPROPID> ids = convertToCEPropIds(sortOrder.detailDefinitionName(), sortOrder.detailFieldName());
+
+ if (!ids.isEmpty()) {
+ QString prop = getPropertyName(ids.at(0));
+ int descending = sortOrder.direction() == Qt::DescendingOrder ? 1 : 0;
+ hr = collection->Sort((BSTR)prop.constData(), descending);
+
+ if (!SUCCEEDED(hr)) {
+ qDebug() << QString("Failed to sort contacts: %1 (%2)").arg(hr, 0, 16).arg(HRESULT_CODE(hr), 0, 16);
+ }
+ } else {
+ qDebug() << QString("This detail field(%1:%2) is not supported by wince native sorting")
+ .arg(sortOrder.detailDefinitionName())
+ .arg(sortOrder.detailFieldName());
+ }
+
+ return SUCCEEDED(hr);
+}
+
+QList<QContactLocalId> QContactWinCEEngine::contactIds(const QContactFilter& filter, const QList<QContactSortOrder>& sortOrders, QContactManager::Error& error) const
+{
+ QString query = convertFilterToQueryString(filter);
+
+ if (!query.isEmpty()) {
+ error = QContactManager::NoError;
+ //Filtering contacts with POOM API
+ SimpleComPointer<IPOutlookItemCollection> collection;
+ HRESULT hr = d->m_collection->Restrict((BSTR)(query.constData()), &collection);
+
+ if (SUCCEEDED(hr)) {
+ QList<QContactLocalId> ids;
+
+ //Try native sorting first...
+ if (sortOrders.size() == 1 && sortPOOMContacts(collection, sortOrders.at(0))) {
+ ids = convertP2QIdList(collection);
+ } else {
+ //Multi sort orders or native sorting failed, fall back to the generic sorting
+ QList<QContact> filteredContacts;
+ if (convertP2QContacts(collection, &filteredContacts)) {
+ ids = sortContacts(filteredContacts, sortOrders);
+ } else {
+ error = QContactManager::UnspecifiedError;
+ qDebug() << "Wince contact manager internal error";
+ }
+ }
+ return ids;
+ } else {
+ //Should we fail back to generic filtering here?
+ qDebug() << "Can't filter contacts with query string:" << query << ", HRESULT=" << HRESULT_CODE(hr);
+ }
+ }
+ //Fail back to generic filtering
+ return QContactManagerEngine::contactIds(filter, sortOrders, error);
+}
+
+QList<QContactLocalId> QContactWinCEEngine::contactIds(const QList<QContactSortOrder>& sortOrders, QContactManager::Error& error) const
+{
+ QList<QContactLocalId> ids;
+ error = QContactManager::NoError;
+ if (sortOrders.isEmpty()) {
+ ids = d->m_ids;
+ } else {
+ SimpleComPointer<IPOutlookItemCollection> newCollection;
+ HRESULT hr = d->m_collection->Restrict(TEXT("[Oid] <> 0"), &newCollection);
+
+ if (SUCCEEDED(hr)) {
+ //Try native sorting first...
+ if (sortOrders.size() == 1 && sortPOOMContacts(newCollection, sortOrders.at(0))) {
+ ids = convertP2QIdList(newCollection);
+ return ids;
+ }
+ }
+
+ //Multi sort orders or native sorting failed, fall back to the generic sorting
+ QList<QContact> contacts;
+ if (convertP2QContacts(d->m_collection, &contacts)) {
+ ids = sortContacts(contacts, sortOrders);
+ } else {
+ error = QContactManager::UnspecifiedError;
+ qDebug() << "Wince contact manager internal error";
+ }
+ }
+ return ids;
+}
+