MeeGo 1.2 Harmattan Developer Documentation Develop for the Nokia N9

'Keep in Touch' Example

Files:

This example demonstrates using the QtMobility Messaging API to extract useful information from the messages stored by the system.

The 'Keep in Touch' application locates people you have sent messages to in the past, but with whom you have not communicated recently. It defines a user-selected date range in the past, beginning a certain amount of time ago and finishing more recently. Any address that you sent a message to in this period is located. Any address that you sent a message to more recently than this period will not be displayed. So, now might be a good time to say hello to the people whose addresses are reported!

We find addresses when the user clicks the Search button. First of all we find the dates that define the period that the user has selected.

     QDateTime now(QDateTime::currentDateTime());
     bool useExclusionPeriod(excludeCheckBox->isChecked());

     // Determine the dates that demarcate the selected range
     QDateTime minimumDate(now);
     switch (includePeriod->currentIndex()) {
         case 0: minimumDate = minimumDate.addMonths(-12); break;
         case 1: minimumDate = minimumDate.addMonths(-9); break;
         case 2: minimumDate = minimumDate.addMonths(-6); break;
         case 3: minimumDate = minimumDate.addMonths(-3); break;
         case 4: minimumDate = minimumDate.addMonths(-1); break;
         case 5: minimumDate = minimumDate.addDays(-7); break;
         default: break;
     }

     QDateTime maximumDate(now);
     if (useExclusionPeriod) {
         // We have an exclusion period to apply
         switch (excludePeriod->currentIndex()) {
             case 0: maximumDate = maximumDate.addDays(-7); break;
             case 1: maximumDate = maximumDate.addMonths(-1); break;
             case 2: maximumDate = maximumDate.addMonths(-3); break;
             case 3: maximumDate = maximumDate.addMonths(-6); break;
             case 4: maximumDate = maximumDate.addMonths(-9); break;
             default: break;
         }
     }

We create filter objects that will constrain our message search to the date range that the user has selected:

     // We will include addresses contacted following the minimum date
     QMessageFilter includeFilter(QMessageFilter::byTimeStamp(minimumDate, QMessageDataComparator::GreaterThanEqual));
     // Windows mobile only sets a receptionTimeStamp for sent messsages
     includeFilter |= QMessageFilter::byReceptionTimeStamp(minimumDate, QMessageDataComparator::GreaterThanEqual);

     QMessageFilter excludeFilter;
     if (useExclusionPeriod) {
         // We will exclude addresses contacted following the maximum date
         excludeFilter = QMessageFilter::byTimeStamp(maximumDate, QMessageDataComparator::GreaterThanEqual);
         excludeFilter |= QMessageFilter::byReceptionTimeStamp(maximumDate, QMessageDataComparator::GreaterThanEqual);
     }

Now we combine our filters to precisely specify the messages that we would like to inspect to perform our address search:

     // We only want to match messages that we sent
     QMessageFilter sentFilter(QMessageFilter::byStandardFolder(QMessage::SentFolder));

     // Create the filter needed to locate messages to search for addresses to include
     if (useExclusionPeriod) {
         inclusionFilter = (sentFilter & includeFilter & ~excludeFilter);
     } else {
         inclusionFilter = (sentFilter & includeFilter);
     }

Now that we know what messages to search for, we use an instance of QMessageService to perform our first search:

     if (useExclusionPeriod) {
         // Create the filter needed to locate messages whose address we will exclude
         QMessageFilter exclusionFilter;
         exclusionFilter = (sentFilter & excludeFilter);

         // Start the search for messages containing addresses to exclude
         service.queryMessages(exclusionFilter);
     } else {
         // Only search for messages containing addresses to include
         service.queryMessages(inclusionFilter);

         // Clear the inclusion filter to indicate that we have searched for it
         inclusionFilter = QMessageFilter();
     }

We could have queried the QMessageManager instance directly using the queryMessages function, but that would keep our UI thread busy, and we wouldn't be able to keep our UI functioning at the same time. Using the QMessageService interface allows us to process the search asynchronously.

We receive the notification of matching messages by responding to the QMessageService::messagesFound() signal:

 void AddressFinder::messagesFound(const QMessageIdList &ids)
 {
     // Add these IDs to the relevant set
     if (!inclusionFilter.isEmpty()) {
         exclusionMessages << ids;
     } else {
         inclusionMessages << ids;
     }
 }

When the search is completed, the QMessageService::stateChanged() signal is emitted. If the user specified an exclusion period then we have to perform two searches, so when the first finishes our handler initiates the second; when the second finishes, we begin processing the results:

 void AddressFinder::stateChanged(QMessageService::State newState)
 {
     if (searchAction->isEnabled() == false) {
         if (newState == QMessageService::FinishedState) {
             if (service.error() == QMessageManager::NoError) {
                 if (!inclusionFilter.isEmpty()) {
                     // Now find the included messages
                     service.queryMessages(inclusionFilter);

                     // Clear the inclusion filter to indicate that we have searched for it
                     inclusionFilter = QMessageFilter();
                 } else {
                     // We have found the exclusion and inclusion message sets
                     if (!inclusionMessages.isEmpty()) {
                         // Begin processing the message sets
                         QTimer::singleShot(0, this, SLOT(continueSearch()));

The continueSearch function processes the results of our searches, one message at a time. Each address we sent a message to in the excluded time period is added to our exclusion list. For each address we sent a message to in our included time period, and which is not in the exclusion list, we create a set of the messages that we sent to that address.

 void AddressFinder::continueSearch()
 {
     if (!exclusionMessages.isEmpty()) {
         // Take the first message whose addreses we should exclude
         QMessageId id(exclusionMessages.takeFirst());
         const QMessage message(id);

         // All recipient addresses are to be excluded
         foreach (const QMessageAddress &address, message.to() + message.cc() + message.bcc()) {
             excludedAddresses.insert(simpleAddress(address));
         }
     } else if (!inclusionMessages.isEmpty()) {
         // Take the first message to inspect for suitable addresses
         QMessageId id(inclusionMessages.takeFirst());
         const QMessage message(id);

         QString details;

         // For each recipient of this message
         foreach (const QMessageAddress &address, message.to() + message.cc() + message.bcc()) {
             QString addressOnly(simpleAddress(address));

             // Ignore recipients whose addresses we have added to the exclusion set
             if (!excludedAddresses.contains(addressOnly)) {
                 // Link this message to this address
                 QList<QPair<QString, QMessageId> > &messageList(addressMessages[addressOnly]);
                 if (messageList.isEmpty()) {
                     addressList.append(addressOnly);

                     // Add the recipient to our visible list of contacts to keep in touch with
 #ifdef USE_CONTACTS_COMBOBOX
                     contactList->addItem(contactDisplayName(address), addressOnly);
 #else
                     QListWidgetItem* newListWidgetItem = new QListWidgetItem(contactDisplayName(address));
                     newListWidgetItem->setData(Qt::UserRole, addressOnly);
                     contactList->addItem(newListWidgetItem);
 #endif
                 }

                 if (details.isEmpty()) {
                     // Determine the properties of the message
                     details = QString("[%1] %2").arg(message.date().toString("MMM d")).arg(message.subject());
                 }
                 messageList.append(qMakePair(details, id));
             }
         }
     }

     if (!exclusionMessages.isEmpty() || !inclusionMessages.isEmpty()) {
         // There are more messages to process
         QTimer::singleShot(0, this, SLOT(continueSearch()));
     } else {
         // We're finished our search
         setSearchActionEnabled(true);
 #ifndef USE_SEARCH_BUTTON
         tabChanged(1);
 #endif

 #ifdef USE_CONTACTS_COMBOBOX
         addressSelected(contactList->currentIndex());
 #else
         addressSelected(contactList->currentRow());
 #endif
     }
 }

We will display the resulting list of contacts in our Contacts pane. If the system contains a contact definition that matches the address information we extracted, we will display the label for that contact - this matching process can be accomplished using the QtMobility Contacts API. If we can't find a matching contact, we fall back to using the raw address information for display:

 QString contactDisplayName(const QMessageAddress &address)
 {
     QString addressOnly(simpleAddress(address));

     // See if we can match this address to a contact
     QContactDetailFilter filter;
     if (address.type() == QMessageAddress::Email) {
         // Match contacts on email address data
         filter.setDetailDefinitionName(QContactEmailAddress::DefinitionName, QContactEmailAddress::FieldEmailAddress);
         filter.setValue(addressOnly);
         filter.setMatchFlags(QContactFilter::MatchContains);
     } else if (address.type() == QMessageAddress::Phone) {
         // Match contacts on phone number data
         filter.setDetailDefinitionName(QContactPhoneNumber::DefinitionName, QContactPhoneNumber::FieldNumber);
         filter.setValue(addressOnly);
         filter.setMatchFlags(QContactFilter::MatchPhoneNumber);
     }

     QContactManager manager;
     foreach (const QContactLocalId &contactId, manager.contactIds(filter)) {
         // Any match is acceptable
         const QContact &contact(manager.contact(contactId));
         return contact.displayLabel();
     }

     // We couldn't match anything, so return the original address
     return address.addressee();
 }

If the user selects one of these contact addresses, we fill the Messages combo box with the list of messages that were previously sent to that contact:

 void AddressFinder::addressSelected(int index)
 {
     messageCombo->clear();

     if (index == -1)
         return;

 #ifdef USE_CONTACTS_COMBOBOX
     QString address = contactList->itemData(index).toString();
 #else
     QString address = contactList->item(index)->data(Qt::UserRole).toString();
 #endif

     QString addressOnly(simpleAddress(address));

     // Add the subject of each message to this address to the message pane
     typedef QPair<QString, QMessageId> MessageDetails;
     foreach (const MessageDetails &message, addressMessages[addressOnly]) {
         messageCombo->addItem(message.first);
     }
 }

When the user has selected a message, two further actions become possible. We can use the QMessageService facility to either view the message or to compose a new message to the recipient of the earlier message. To view the message we only need to invoke the show function for the relevant QMessageId:

 void AddressFinder::showMessage()
 {
     int index = messageCombo->currentIndex();
     if (index != -1) {
         // Find the address currently selected
         const QString &selectedAddress(addressList[
 #ifdef USE_CONTACTS_COMBOBOX
                 contactList->currentIndex()
 #else
                 contactList->currentRow()
 #endif
                 ]);

         // Show the message selected
         QMessageId &messageId((addressMessages[selectedAddress])[index].second);
         service.show(messageId);
     }
 }

To compose a response, we will create a new message that forwards the existing message content to the same recipient, and requests that the system display a message composer prepared with the message content we have prepared:

 void AddressFinder::forwardMessage()
 {
     int index = messageCombo->currentIndex();
     if (index != -1) {
         // Find the address currently selected
         const QString &selectedAddress(addressList[
 #ifdef USE_CONTACTS_COMBOBOX
                 contactList->currentIndex()
 #else
                 contactList->currentRow()
 #endif
                 ]);

         // Find the selected message
         QMessageId &messageId((addressMessages[selectedAddress])[index].second);
         QMessage original(messageId);

         // Create a message which forwards the selected message to the same recipient
         QMessage fwd(original.createResponseMessage(QMessage::Forward));
         fwd.setTo(original.to());
         service.compose(fwd);
     }
 }