messagingapp/msgui/unifiededitor/src/msgunieditoraddress.cpp
changeset 37 518b245aa84c
parent 25 84d9eb65b26f
child 38 4e4b6adb1024
--- a/messagingapp/msgui/unifiededitor/src/msgunieditoraddress.cpp	Mon May 03 12:29:07 2010 +0300
+++ b/messagingapp/msgui/unifiededitor/src/msgunieditoraddress.cpp	Fri Jun 25 15:47:40 2010 +0530
@@ -16,40 +16,55 @@
  */
 
 // INCLUDES
-#include "debugtraces.h"
+#include <QTimer>
 #include <HbTextItem>
 #include <HbPushButton>
 #include <HbAction>
-#include <hbinputeditorinterface.h>
+#include <hbmessagebox.h>
 #include <cntservicescontact.h>
 #include <xqaiwrequest.h>
 #include <xqappmgr.h>
 #include <telconfigcrkeys.h>        // KCRUidTelephonyConfiguration
 #include <centralrepository.h>
+#include <HbNotificationDialog>
+#include <commonphoneparser.h>      // Common phone number validity checker
+#include <xqconversions.h>
 
 // USER INCLUDES
+#include "debugtraces.h"
 #include "msgunieditoraddress.h"
-#include "msgunifiededitorlineedit.h"
+#include "msgunieditorlineedit.h"
+#include "msgunieditormonitor.h"
+#include "UniEditorGenUtils.h"
 
 const QString PBK_ICON("qtg_mono_contacts");
-const QString SEND_ICON("qtg_mono_send");
+const QString REPLACEMENT_STR("; ");
+const QString COMMA_SEPERATOR(",");
 
 // Constants
 const int KDefaultGsmNumberMatchLength = 7;  //matching unique ph numbers
+#define LOC_SMS_RECIPIENT_LIMIT_REACHED hbTrId("txt_messaging_dialog_number_of_recipients_exceeded")
+#define LOC_MMS_RECIPIENT_LIMIT_REACHED hbTrId("txt_messaging_dpopinfo_unable_to_add_more_recipien")
+#define LOC_DIALOG_OK hbTrId("txt_common_button_ok")
+#define LOC_BUTTON_CANCEL hbTrId("txt_common_button_cancel")
+#define LOC_INVALID_RECIPIENT hbTrId("txt_messaging_dialog_invalid_recipient_found")
+#define LOC_INVALID_RECIPIENT_NOT_ADDED hbTrId("txt_messaging_dialog_invalid_recipient_not_added")
+#define LOC_INVALID_RECIPIENTS_NOT_ADDED hbTrId("txt_messaging_dialog_invalid_recipients_not_added")
+
+
+
+
 
 MsgUnifiedEditorAddress::MsgUnifiedEditorAddress( const QString& label,
-                                                  const QString& pluginPath,
                                                   QGraphicsItem *parent ) :
-HbWidget(parent),
-mPluginPath(pluginPath)
+MsgUnifiedEditorBaseWidget(parent),
+mSkipMaxRecipientQuery(false),
+mAboutToExceedMaxSmsRecipients(false),
+mAboutToExceedMaxMmsRecipients(false),
+mExceedsMaxMmsRecipientsBy(0)
 {
-    #ifdef _DEBUG_TRACES_
-    qDebug() << "MsgUnifiedEditorAddress calling HbStyle::registerPlugin";
-    #endif
+    this->setContentsMargins(0,0,0,0);
 
-    this->setContentsMargins(0,0,0,0);
-    setPluginBaseId(style()->registerPlugin(mPluginPath));
-    
     mLaunchBtn = new HbPushButton(this);
     HbStyle::setItemName(mLaunchBtn,"launchBtn");
     connect(mLaunchBtn,SIGNAL(clicked()),this,SLOT(fetchContacts()));
@@ -61,24 +76,22 @@
 
     mAddressEdit->setMaxRows(40);
     connect(mAddressEdit, SIGNAL(contentsChanged(const QString&)),
-            this, SLOT(onContentsAdded(const QString&)));
+            this, SLOT(onContentsChanged(const QString&)));
 
-    // add "Send" action in VKB
-    HbEditorInterface editorInterface(mAddressEdit);
     mAddressEdit->setInputMethodHints(Qt::ImhPreferNumbers);
-    HbAction *sendAction = new HbAction(HbIcon(SEND_ICON), QString(),this);
-    connect(sendAction, SIGNAL(triggered()),this, SIGNAL(sendMessage()));
-    editorInterface.addAction(sendAction);
-    
-    }
+}
 
 MsgUnifiedEditorAddress::~MsgUnifiedEditorAddress()
 {
-    style()->unregisterPlugin(mPluginPath);
+	//TODO: Should remove this code depending on orbit's reply whether it is needed
+	//to unregister the same plugin registered on two different widgets twice.
+    //style()->unregisterPlugin(mPluginPath);
 }
 
 void MsgUnifiedEditorAddress::fetchContacts()
 {
+    mLaunchBtn->blockSignals(true);
+
     QList<QVariant> args;
     QString serviceName("com.nokia.services.phonebookservices");
     QString operation("fetch(QString,QString,QString)");
@@ -87,39 +100,48 @@
     request = appManager.create(serviceName, "Fetch", operation, true); // embedded
     if ( request == NULL )
         {
-        return;       
+        return;
         }
 
     // Result handlers
     connect (request, SIGNAL(requestOk(const QVariant&)), this, SLOT(handleOk(const QVariant&)));
     connect (request, SIGNAL(requestError(int,const QString&)), this, SLOT(handleError(int,const QString&)));
-    
-    args << QString(tr("Phonebook")); 
+
+    args << QString(tr("Phonebook"));
     args << KCntActionAll;
     args << KCntFilterDisplayAll;
-    
+
     request->setArguments(args);
     request->send();
     delete request;
+
+    //unblock click signal after some delay.
+    QTimer::singleShot(250,this,SLOT(unblockSignals()));
 }
 
 void MsgUnifiedEditorAddress::handleOk(const QVariant& value)
 {
-   CntServicesContactList contactList;
-    contactList = qVariantValue<CntServicesContactList>(value);
+   CntServicesContactList contactList =
+           qVariantValue<CntServicesContactList>(value);
+    int count = contactList.count();
 
-    for(int i = 0; i < contactList.count(); i++ )
+    ConvergedMessageAddressList addrlist;
+    for(int i = 0; i < count; i++ )
     {
-        mAddressMap.insert(contactList[i].mPhoneNumber, contactList[i].mDisplayName);
-        if(!contactList[i].mDisplayName.isEmpty())
+        ConvergedMessageAddress* address =
+                new ConvergedMessageAddress();
+        if(!contactList[i].mPhoneNumber.isEmpty())
         {
-            mAddressEdit->setText(contactList[i].mDisplayName);
+            address->setAddress(contactList[i].mPhoneNumber);
         }
         else
         {
-            mAddressEdit->setText(contactList[i].mPhoneNumber);
+            address->setAddress(contactList[i].mEmailAddress);
         }
+        address->setAlias(contactList[i].mDisplayName);
+        addrlist << address;
     }
+    setAddresses(addrlist);
 }
 
 void MsgUnifiedEditorAddress::handleError(int errorCode, const QString& errorMessage)
@@ -128,31 +150,47 @@
     Q_UNUSED(errorCode)
 }
 
-ConvergedMessageAddressList MsgUnifiedEditorAddress::addresses()
+// ----------------------------------------------------------------------------
+// MsgUnifiedEditorAddress::addresses
+// @see header
+// ----------------------------------------------------------------------------
+ConvergedMessageAddressList MsgUnifiedEditorAddress::addresses(
+        bool removeDuplicates)
 {
-    #ifdef _DEBUG_TRACES_
-    qCritical() << "MsgUnifiedEditorAddress::address start";
+#ifdef _DEBUG_TRACES_
+    qCritical() << "MsgUnifiedEditorAddress::addresses start";
 #endif
     // sync-up map to account for user-actions on edit-field
     syncDeletionsToMap();
     syncAdditionsToMap();
 
-    QStringList uniqueAddr;
-    uniqueAddr = uniqueAddressList();
-
     ConvergedMessageAddressList addresses;
-    foreach(QString addr, uniqueAddr)
+    if(removeDuplicates)
     {
-        ConvergedMessageAddress* address = new ConvergedMessageAddress;
-        address->setAddress(addr);
-        if(!mAddressMap.value(addr).isEmpty())
+        QStringList uniqueAddr;
+        uniqueAddr = uniqueAddressList();
+        foreach(QString addr, uniqueAddr)
         {
+            ConvergedMessageAddress* address = new ConvergedMessageAddress;
+            address->setAddress(addr);
             address->setAlias(mAddressMap.value(addr));
-         }
-        addresses.append(address);
+            addresses.append(address);
+        }
     }
-    #ifdef _DEBUG_TRACES_
-    qCritical() << "MsgUnifiedEditorAddress::address end";
+    else
+    {
+        QMap<QString, QString>::iterator i = mAddressMap.begin();
+        while (i != mAddressMap.end())
+        {
+            ConvergedMessageAddress* address = new ConvergedMessageAddress;
+            address->setAddress(i.key());
+            address->setAlias(i.value());
+            addresses.append(address);
+            i++;
+        }
+    }
+#ifdef _DEBUG_TRACES_
+    qCritical() << "MsgUnifiedEditorAddress::addresses end";
 #endif
     return addresses;
 }
@@ -164,60 +202,177 @@
 
 void MsgUnifiedEditorAddress::setAddresses(ConvergedMessageAddressList addrlist)
 {
-	int count = addrlist.count();
-    for(int i = 0; i < count; i++ )
+    // avoid processing if no info available
+    if(addrlist.count() == 0)
     {
-        mAddressMap.insert(addrlist[i]->address(), addrlist[i]->alias());
-        if(!addrlist[i]->alias().isEmpty())
+        return;
+    }
+
+    // ensure flags are reset before starting the addr addition
+    mAboutToExceedMaxSmsRecipients = false;
+    mAboutToExceedMaxMmsRecipients = false;
+    mExceedsMaxMmsRecipientsBy = 0;
+
+    // first, we check if MMS max-recipient count will exceed
+    int count = addrlist.count();
+	int futureCount = count + MsgUnifiedEditorMonitor::msgAddressCount();
+	if(futureCount > MsgUnifiedEditorMonitor::maxMmsRecipients())
+	{
+	    mAboutToExceedMaxMmsRecipients = true;
+	    mExceedsMaxMmsRecipientsBy =
+	            futureCount - MsgUnifiedEditorMonitor::maxMmsRecipients();
+	}
+	// else, check if SMS max-recipient count will exceed
+	else if(!mSkipMaxRecipientQuery)
+	{
+	    futureCount = count + addressCount();
+	    if( (addressCount() <= MsgUnifiedEditorMonitor::maxSmsRecipients()) &&
+	        (futureCount > MsgUnifiedEditorMonitor::maxSmsRecipients()) )
+	    {
+	        mAboutToExceedMaxSmsRecipients = true;
+	    }
+	}
+
+	int  invalidCount= 0;
+    QString invalidContacts;
+    for(int i = 0; i < count; i++ )
         {
-            mAddressEdit->setText(addrlist[i]->alias());
-        }
-        else
+        bool isValid = false;
+        isValid = checkValidAddress(addrlist.at(i)->address());
+        if(!isValid)
+           {
+            invalidCount ++;
+            // append the comma till last but one contact.
+            // add the invalid ocntacts to the " , " seperated string.
+            if(invalidCount > 1)
+                {
+                invalidContacts.append(COMMA_SEPERATOR);
+                }
+            invalidContacts.append(addrlist.at(i)->alias());
+           }
+       else
+           {
+           mAddressMap.insertMulti(addrlist[i]->address(), addrlist[i]->alias());
+           if(!addrlist[i]->alias().isEmpty())
+              {
+              mAddressEdit->setText(addrlist[i]->alias());
+              }
+           else
+              {
+              mAddressEdit->setText(addrlist[i]->address(), false);
+              }
+           }
+       }
+    if(invalidCount)
         {
-            mAddressEdit->setText(addrlist[i]->address());
+        QString invalidStr;
+        (invalidCount == 1)?(invalidStr = QString(LOC_INVALID_RECIPIENT_NOT_ADDED)) :(invalidStr = QString(LOC_INVALID_RECIPIENTS_NOT_ADDED));
+        // append line seperator
+         invalidStr.append("<br>");
+         invalidStr.append(invalidContacts);
+         HbMessageBox::information(invalidStr);
         }
-    }
+
+    // addition operation complete, reset flags
+    mAboutToExceedMaxSmsRecipients = false;
+    mAboutToExceedMaxMmsRecipients = false;
+    mExceedsMaxMmsRecipientsBy = 0;
 }
 
 int MsgUnifiedEditorAddress::contactMatchDigits()
-    {
+{
     // Read the amount of digits to be used in contact matching
-    // The key is owned by PhoneApp    
-    CRepository* repository = CRepository::NewLC(KCRUidTelConfiguration);
+    // The key is owned by PhoneApp
     int matchDigitCount = 0;
-    if ( repository->Get(KTelMatchDigits, matchDigitCount) == KErrNone )
+    TRAP_IGNORE(
+        CRepository* repository = CRepository::NewLC(KCRUidTelConfiguration);
+        if ( repository->Get(KTelMatchDigits, matchDigitCount) == KErrNone )
         {
-    // Min is 7
-    matchDigitCount = Max(matchDigitCount, KDefaultGsmNumberMatchLength);
+            // Min is 7
+            matchDigitCount = Max(matchDigitCount, KDefaultGsmNumberMatchLength);
         }
-    CleanupStack::PopAndDestroy(); // repository
-
+        CleanupStack::PopAndDestroy(); // repository
+    );
     return matchDigitCount;
+}
 
-    }
-   
-void MsgUnifiedEditorAddress::onContentsAdded(const QString& text)
+void MsgUnifiedEditorAddress::onContentsChanged(const QString& text)
 {
-    if(!text.isEmpty())
+    // Max MMS recipient count check
+    if( mAboutToExceedMaxMmsRecipients ||
+        (MsgUnifiedEditorMonitor::msgAddressCount() >= MsgUnifiedEditorMonitor::maxMmsRecipients()) )
     {
+        if(mAboutToExceedMaxMmsRecipients)
+        {// show discreet note only once
+            --mExceedsMaxMmsRecipientsBy;
+            if(!mExceedsMaxMmsRecipientsBy)
+            {
+                HbNotificationDialog::launchDialog(
+                        LOC_MMS_RECIPIENT_LIMIT_REACHED);
+            }
+            resetToPrevious();
+        }
+        else
+        {
+            // update monitor data
+            emit contentChanged();
+            if(MsgUnifiedEditorMonitor::msgAddressCount() > MsgUnifiedEditorMonitor::maxMmsRecipients())
+            {
+                HbNotificationDialog::launchDialog(
+                        LOC_MMS_RECIPIENT_LIMIT_REACHED);
+                resetToPrevious();
+                // reset monitor data
+                emit contentChanged();
+            }
+            else
+            {
+                mPrevBuffer = text;
+            }
+        }
+        return;
+    }
+
+    // Max SMS recipient count check
+    if( !mSkipMaxRecipientQuery &&
+        (MsgUnifiedEditorMonitor::messageType() == ConvergedMessage::Sms) &&
+        (mAddressEdit->addresses().count() > MsgUnifiedEditorMonitor::maxSmsRecipients()) )
+    {
+        // when we show this dialog, we don't want the intermediate states
+        // to be signalled to us
         disconnect(mAddressEdit, SIGNAL(contentsChanged(const QString&)),
-                this, SLOT(onContentsAdded(const QString&)));
+                this, SLOT(onContentsChanged(const QString&)));
+        QTimer::singleShot(50, this, SLOT(handleRecipientLimitReached()));
+    }
+    else
+    {
+        if(!mAboutToExceedMaxSmsRecipients)
+        {// remember addresses before the block insertion started
+            mPrevBuffer = text;
+        }
         emit contentChanged();
-        connect(mAddressEdit, SIGNAL(contentsChanged(const QString&)),
-                this, SLOT(onContentsRemoved(const QString&)));
     }
 }
 
-void MsgUnifiedEditorAddress::onContentsRemoved(const QString& text)
+void MsgUnifiedEditorAddress::handleRecipientLimitReached()
 {
-    if(text.isEmpty())
-    {
-        disconnect(mAddressEdit, SIGNAL(contentsChanged(const QString&)),
-                this, SLOT(onContentsRemoved(const QString&)));
-        emit contentChanged();
-        connect(mAddressEdit, SIGNAL(contentsChanged(const QString&)),
-                this, SLOT(onContentsAdded(const QString&)));
-    }
+    HbMessageBox* dlg = new HbMessageBox(HbMessageBox::MessageTypeQuestion);
+    dlg->setAttribute(Qt::WA_DeleteOnClose);
+    dlg->setFocusPolicy(Qt::NoFocus);
+    dlg->setTimeout(HbPopup::NoTimeout);
+
+    dlg->setText(LOC_SMS_RECIPIENT_LIMIT_REACHED);
+    
+    dlg->clearActions();
+    HbAction* okAction = new HbAction(LOC_DIALOG_OK,dlg);
+    dlg->addAction(okAction);
+
+    HbAction* cancelAction = new HbAction(LOC_BUTTON_CANCEL,dlg);
+    dlg->addAction(cancelAction);
+
+    dlg->open(this,SLOT(onMaxRecipientsReached(HbAction*)));
+    // reconnect to get back updates
+    connect(mAddressEdit, SIGNAL(contentsChanged(const QString&)),
+            this, SLOT(onContentsChanged(const QString&)));
 }
 
 void MsgUnifiedEditorAddress::syncDeletionsToMap()
@@ -236,6 +391,18 @@
         }
         else
         {
+            // ensure that the matched contact is removed from the
+            // address's list
+            int matchedIndex = addrList.indexOf(i.value());
+            if(matchedIndex == -1)
+            {
+                matchedIndex = addrList.indexOf(i.key());
+            }
+            if(matchedIndex != -1)
+            {
+                addrList.removeAt(matchedIndex);
+            }
+            // now go to next index in map
             ++i;
         }
     }
@@ -243,19 +410,19 @@
 
 void MsgUnifiedEditorAddress::syncAdditionsToMap()
 {
-    QStringList mapAddrList = mAddressMap.values();
-
     // remove already mapped addresses from edit-field
     QStringList userInputAddrList(mAddressEdit->addresses());
+    QStringList mapAddrList = mAddressMap.values();
+    mapAddrList << mAddressMap.keys();
     foreach(QString addr, mapAddrList)
     {
-        userInputAddrList.removeAll(addr);
+        userInputAddrList.removeOne(addr);
     }
 
     // add the unmapped addresses to address-map
     foreach(QString addr, userInputAddrList)
     {
-        mAddressMap.insertMulti(addr, addr);
+        mAddressMap.insertMulti(addr, QString());
     }
 }
 
@@ -267,7 +434,7 @@
     {
         for(int i =j+1;i<mapAddrList.count();i++)
         {
-            if(0 == mapAddrList[j].right(matchDigitCount).compare(mapAddrList[i].right(matchDigitCount)))     
+            if(0 == mapAddrList[j].right(matchDigitCount).compare(mapAddrList[i].right(matchDigitCount)))
             {
                mapAddrList.removeAt(i);
                i--;
@@ -276,6 +443,148 @@
     }
     return mapAddrList;
 }
+
+// ----------------------------------------------------------------------------
+// MsgUnifiedEditorAddress::skipMaxRecipientQuery
+// @see header
+// ----------------------------------------------------------------------------
+void MsgUnifiedEditorAddress::skipMaxRecipientQuery(bool skip)
+{
+    mSkipMaxRecipientQuery = skip;
+}
+
+void MsgUnifiedEditorAddress::setFocus()
+{
+    mAddressEdit->setFocus(Qt::MouseFocusReason);
+}
+
+// ----------------------------------------------------------------------------
+// MsgUnifiedEditorAddress::resetToPrevious
+// @see header
+// ----------------------------------------------------------------------------
+void MsgUnifiedEditorAddress::resetToPrevious()
+{
+    // when we do this reset operation, we don't want the intermediate states
+    // to be signalled to us
+    disconnect(mAddressEdit, SIGNAL(contentsChanged(const QString&)),
+            this, SLOT(onContentsChanged(const QString&)));
+
+    mAddressEdit->clearContent();
+    QStringList list = mPrevBuffer.split(REPLACEMENT_STR,
+            QString::SkipEmptyParts);
+    int count = list.count();
+    QStringList valList = mAddressMap.values();
+    for(int i=0; i<count; i++)
+    {
+        QString addr = list.at(i);
+        if(valList.contains(addr))
+        {
+            mAddressEdit->setText(addr);
+        }
+        else
+        {
+            mAddressEdit->setText(addr, false);
+        }
+    }
+    syncDeletionsToMap();
+    connect(mAddressEdit, SIGNAL(contentsChanged(const QString&)),
+            this, SLOT(onContentsChanged(const QString&)));
+}
+
+// ----------------------------------------------------------------------------
+// MsgUnifiedEditorAddress::onMaxRecipientsReached
+// @see header
+// ----------------------------------------------------------------------------
+void MsgUnifiedEditorAddress::onMaxRecipientsReached(HbAction* action)
+{
+    HbMessageBox *dlg = qobject_cast<HbMessageBox*> (sender());
+    if (action == dlg->actions().at(0)) {
+        // accept new content, update prev-buffer
+        emit contentChanged();
+        mPrevBuffer = mAddressEdit->content();
+    }
+    else {
+        // reject the new content, keep the old
+        resetToPrevious();
+    }
+}
+
+// ----------------------------------------------------------------------------
+// MsgUnifiedEditorAddress::validateContacts
+// @see header
+// ----------------------------------------------------------------------------
+bool MsgUnifiedEditorAddress::validateContacts()
+{
+    UniEditorGenUtils* genUtils = new UniEditorGenUtils;
+
+    // sync-up map to account for user-actions on address-field
+    syncDeletionsToMap();
+    syncAdditionsToMap();
+
+    // get the list of contacts in address-field
+    QStringList fieldAddresses(mAddressEdit->addresses());
+
+    bool isValid = true;
+    foreach(QString addr, fieldAddresses)
+    {
+        // run address validation only if address is unmapped
+        // (i.e. user-inserted)
+        if(mAddressMap.contains(addr))
+        {
+        isValid = checkValidAddress(addr);
+            if(!isValid)
+            {
+                mAddressEdit->highlightInvalidString(addr);
+                QString invalidAddrStr =
+                        QString(LOC_INVALID_RECIPIENT).arg(addr);
+                HbMessageBox* dlg = new HbMessageBox(invalidAddrStr,
+                        HbMessageBox::MessageTypeInformation);
+                dlg->setDismissPolicy(HbPopup::TapInside);
+                dlg->setTimeout(HbPopup::NoTimeout);
+                dlg->setAttribute(Qt::WA_DeleteOnClose, true);
+                dlg->open(this, SLOT(handleInvalidContactDialog(HbAction*)));
+                break;
+            }
+        }
+    }
+
+    return isValid;
+}
+// ----------------------------------------------------------------------------
+// MsgUnifiedEditorAddress::checkValidAddress
+// @see header
+// ----------------------------------------------------------------------------
+bool MsgUnifiedEditorAddress::checkValidAddress(const QString& addr)
+    {
+    bool isValid = false;
+    // 1. perform number validation
+    isValid = CommonPhoneParser::IsValidPhoneNumber(
+            *XQConversions::qStringToS60Desc(addr),
+            CommonPhoneParser::ESMSNumber );
+
+    // 2. if number validity fails, then perform email addr validation
+    UniEditorGenUtils* genUtils = new UniEditorGenUtils;
+    if(!isValid)
+        { // additional check for MMS only
+        isValid = genUtils->IsValidEmailAddress(
+                    *XQConversions::qStringToS60Desc(addr) );
+        }
+    delete genUtils;
+    return isValid;
+    }
+
+void MsgUnifiedEditorAddress::handleInvalidContactDialog(
+        HbAction* act)
+{
+    Q_UNUSED(act);
+    QTimer::singleShot(250, this, SLOT(setFocus()));
+}
+
+void MsgUnifiedEditorAddress::unblockSignals()
+{
+    mLaunchBtn->blockSignals(false);
+}
+
 Q_IMPLEMENT_USER_METATYPE(CntServicesContact)
 Q_IMPLEMENT_USER_METATYPE_NO_OPERATORS(CntServicesContactList)