Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/releaseTest
  • release/releaseWindowsTest
  • release/windowsReleaseTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 1.0.0
  • 0.3.0
  • 0.2.1
  • 0.2.0
  • 0.1.0
25 results

contactproxymodel.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    contactproxymodel.cpp 20.29 KiB
    /****************************************************************************
     *   Copyright (C) 2013-2014 by Savoir-Faire Linux                          *
     *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
     *                                                                          *
     *   This library is free software; you can redistribute it and/or          *
     *   modify it under the terms of the GNU Lesser General Public             *
     *   License as published by the Free Software Foundation; either           *
     *   version 2.1 of the License, or (at your option) any later version.     *
     *                                                                          *
     *   This library is distributed in the hope that it will be useful,        *
     *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      *
     *   Lesser General Public License for more details.                        *
     *                                                                          *
     *   You should have received a copy of the GNU General Public License      *
     *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
     ***************************************************************************/
    #include "contactproxymodel.h"
    
    //Qt
    #include <QtCore/QDebug>
    #include <QtCore/QDate>
    #include <QtCore/QMimeData>
    #include <QtCore/QCoreApplication>
    
    //SFLPhone
    #include "callmodel.h"
    #include "historymodel.h"
    #include "phonenumber.h"
    #include "phonedirectorymodel.h"
    #include "historytimecategorymodel.h"
    #include "contact.h"
    #include "uri.h"
    #include "contactmodel.h"
    
    class ContactTreeNode;
    
    class TopLevelItem : public CategorizedCompositeNode {
       friend class ContactProxyModel;
       friend class ContactTreeBinder;
       public:
          virtual QObject* getSelf() const;
          virtual ~TopLevelItem();
       private:
          explicit TopLevelItem(const QString& name) : CategorizedCompositeNode(CategorizedCompositeNode::Type::TOP_LEVEL),m_Name(name),
          m_lChildren(),m_Index(-1){
             m_lChildren.reserve(32);
          }
          QVector<ContactTreeNode*> m_lChildren;
          QString m_Name;
          int m_Index;
    };
    
    class ContactTreeNode : public CategorizedCompositeNode {
    public:
       ContactTreeNode(Contact* ct, ContactProxyModel* parent);
       ~ContactTreeNode();
       Contact* m_pContact;
       TopLevelItem* m_pParent3;
       uint m_Index;
       virtual QObject* getSelf() const;
       ContactTreeBinder* m_pBinder;
    };
    
    TopLevelItem::~TopLevelItem() {
       while(m_lChildren.size()) {
          ContactTreeNode* node = m_lChildren[0];
          m_lChildren.remove(0);
          delete node;
       }
    }
    
    ContactTreeNode::ContactTreeNode(Contact* ct, ContactProxyModel* parent) : CategorizedCompositeNode(CategorizedCompositeNode::Type::CONTACT),
       m_pContact(ct),m_pParent3(nullptr),m_Index(-1)
    {
       m_pBinder = new ContactTreeBinder(parent,this);
    }
    
    ContactTreeNode::~ContactTreeNode()
    {
       delete m_pBinder;
    }
    
    QObject* ContactTreeNode::getSelf() const
    {
       return m_pContact;
    }
    
    QObject* TopLevelItem::getSelf() const
    {
       return nullptr;
    }
    
    ContactTreeBinder::ContactTreeBinder(ContactProxyModel* m,ContactTreeNode* n) :
       QObject(),m_pTreeNode(n),m_pModel(m)
    {
       connect(n->m_pContact,SIGNAL(changed()),this,SLOT(slotContactChanged()));
       connect(n->m_pContact,SIGNAL(phoneNumberCountChanged(int,int)),this,SLOT(slotPhoneNumberCountChanged(int,int)));
       connect(n->m_pContact,SIGNAL(phoneNumberCountAboutToChange(int,int)),this,SLOT(slotPhoneNumberCountAboutToChange(int,int)));
    }
    
    
    void ContactTreeBinder::slotContactChanged()
    {
       const QModelIndex idx = m_pModel->index(m_pTreeNode->m_Index,0,m_pModel->index(m_pTreeNode->m_pParent3->m_Index,0));
       const QModelIndex lastPhoneIdx = m_pModel->index(m_pTreeNode->m_pContact->phoneNumbers().size()-1,0,idx);
       emit m_pModel->dataChanged(idx,idx);
       if (lastPhoneIdx.isValid()) //Need to be done twice
          emit m_pModel->dataChanged(m_pModel->index(0,0,idx),lastPhoneIdx);
    }
    
    void ContactTreeBinder::slotStatusChanged()
    {
       
    }
    
    void ContactTreeBinder::slotPhoneNumberCountChanged(int count, int oldCount)
    {
       const QModelIndex idx = m_pModel->index(m_pTreeNode->m_Index,0,m_pModel->index(m_pTreeNode->m_pParent3->m_Index,0));
       if (count > oldCount) {
          const QModelIndex lastPhoneIdx = m_pModel->index(oldCount-1,0,idx);
          m_pModel->beginInsertRows(idx,oldCount,count-1);
          m_pModel->endInsertRows();
       }
       emit m_pModel->dataChanged(idx,idx);
    }
    
    void ContactTreeBinder::slotPhoneNumberCountAboutToChange(int count, int oldCount)
    {
       const QModelIndex idx = m_pModel->index(m_pTreeNode->m_Index,0,m_pModel->index(m_pTreeNode->m_pParent3->m_Index,0));
       if (count < oldCount) {
          //If count == 1, disable all children
          m_pModel->beginRemoveRows(idx,count == 1?0:count,oldCount-1);
          m_pModel->endRemoveRows();
       }
    }
    
    //
    ContactProxyModel::ContactProxyModel(int role, bool showAll) : QAbstractItemModel(QCoreApplication::instance()),
    m_Role(role),m_ShowAll(showAll),m_lCategoryCounter()
    {
       setObjectName("ContactProxyModel");
       m_lCategoryCounter.reserve(32);
       m_lMimes << MIME_PLAIN_TEXT << MIME_PHONENUMBER;
       connect(ContactModel::instance(),SIGNAL(reloaded()),this,SLOT(reloadCategories()));
       connect(ContactModel::instance(),SIGNAL(newContactAdded(Contact*)),this,SLOT(slotContactAdded(Contact*)));
       QHash<int, QByteArray> roles = roleNames();
       roles.insert(ContactModel::Role::Organization      ,QByteArray("organization")     );
       roles.insert(ContactModel::Role::Group             ,QByteArray("group")            );
       roles.insert(ContactModel::Role::Department        ,QByteArray("department")       );
       roles.insert(ContactModel::Role::PreferredEmail    ,QByteArray("preferredEmail")   );
       roles.insert(ContactModel::Role::FormattedLastUsed ,QByteArray("formattedLastUsed"));
       roles.insert(ContactModel::Role::IndexedLastUsed   ,QByteArray("indexedLastUsed")  );
       roles.insert(ContactModel::Role::DatedLastUsed     ,QByteArray("datedLastUsed")    );
       roles.insert(ContactModel::Role::Filter            ,QByteArray("filter")           );
       roles.insert(ContactModel::Role::DropState         ,QByteArray("dropState")        );
       setRoleNames(roles);
    }
    
    ContactProxyModel::~ContactProxyModel()
    {
       foreach(TopLevelItem* item,m_lCategoryCounter) {
          delete item;
       }
    }
    
    TopLevelItem* ContactProxyModel::getTopLevelItem(const QString& category)
    {
       if (!m_hCategories[category]) {
          TopLevelItem* item = new TopLevelItem(category);
          m_hCategories[category] = item;
          item->m_Index = m_lCategoryCounter.size();
    //       emit layoutAboutToBeChanged();
          beginInsertRows(QModelIndex(),m_lCategoryCounter.size(),m_lCategoryCounter.size()); {
             m_lCategoryCounter << item;
          } endInsertRows();
    //       emit layoutChanged();
       }
       TopLevelItem* item = m_hCategories[category];
       return item;
    }
    
    void ContactProxyModel::reloadCategories()
    {
       emit layoutAboutToBeChanged();
       beginResetModel();
       m_hCategories.clear();
       beginRemoveRows(QModelIndex(),0,m_lCategoryCounter.size()-1);
       foreach(TopLevelItem* item,m_lCategoryCounter) {
          delete item;
       }
       endRemoveRows();
       m_lCategoryCounter.clear();
       foreach(const Contact* cont, ContactModel::instance()->contacts()) {
          if (cont) {
             const QString val = category(cont);
             TopLevelItem* item = getTopLevelItem(val);
             ContactTreeNode* contactNode = new ContactTreeNode(const_cast<Contact*>(cont),this);
             contactNode->m_pParent3 = item;
             contactNode->m_Index = item->m_lChildren.size();
             item->m_lChildren << contactNode;
          }
       }
       endResetModel();
       emit layoutChanged();
    }
    
    void ContactProxyModel::slotContactAdded(Contact* c)
    {
       if (!c) return;
       const QString val = category(c);
       TopLevelItem* item = getTopLevelItem(val);
       ContactTreeNode* contactNode = new ContactTreeNode(c,this);
       contactNode->m_pParent3 = item;
       contactNode->m_Index = item->m_lChildren.size();
       //emit layoutAboutToBeChanged();
       beginInsertRows(index(item->m_Index,0,QModelIndex()),item->m_lChildren.size(),item->m_lChildren.size()); {
          item->m_lChildren << contactNode;
       } endInsertRows();
       //emit layoutChanged();
    }
    
    bool ContactProxyModel::setData( const QModelIndex& index, const QVariant &value, int role)
    {
       if (index.isValid() && index.parent().isValid()) {
          CategorizedCompositeNode* modelItem = (CategorizedCompositeNode*)index.internalPointer();
          if (role == ContactModel::Role::DropState) {
             modelItem->setDropState(value.toInt());
             emit dataChanged(index, index);
             return true;
          }
       }
       return false;
    }
    
    QVariant ContactProxyModel::data( const QModelIndex& index, int role) const
    {
       if (!index.isValid())
          return QVariant();
    
       CategorizedCompositeNode* modelItem = (CategorizedCompositeNode*)index.internalPointer();
       switch (modelItem->type()) {
          case CategorizedCompositeNode::Type::TOP_LEVEL:
          switch (role) {
             case Qt::DisplayRole:
                return static_cast<const TopLevelItem*>(modelItem)->m_Name;
             case ContactModel::Role::IndexedLastUsed:
                return index.child(0,0).data(ContactModel::Role::IndexedLastUsed);
             case ContactModel::Role::Active:
                return true;
             default:
                break;
          }
          break;
       case CategorizedCompositeNode::Type::CONTACT:{
          const Contact* c = static_cast<Contact*>(modelItem->getSelf());
          switch (role) {
             case Qt::DisplayRole:
                return QVariant(c->formattedName());
             case ContactModel::Role::Organization:
                return QVariant(c->organization());
             case ContactModel::Role::Group:
                return QVariant(c->group());
             case ContactModel::Role::Department:
                return QVariant(c->department());
             case ContactModel::Role::PreferredEmail:
                return QVariant(c->preferredEmail());
             case ContactModel::Role::DropState:
                return QVariant(modelItem->dropState());
             case ContactModel::Role::FormattedLastUsed:
                return QVariant(HistoryTimeCategoryModel::timeToHistoryCategory(c->phoneNumbers().lastUsedTimeStamp()));
             case ContactModel::Role::IndexedLastUsed:
                return QVariant((int)HistoryTimeCategoryModel::timeToHistoryConst(c->phoneNumbers().lastUsedTimeStamp()));
             case ContactModel::Role::Active:
                return c->isActive();
             case ContactModel::Role::DatedLastUsed:
                return QVariant(QDateTime::fromTime_t( c->phoneNumbers().lastUsedTimeStamp()));
             case ContactModel::Role::Filter:
                return c->filterString();
             default:
                break;
          }
          break;
       }
       case CategorizedCompositeNode::Type::NUMBER: /* && (role == Qt::DisplayRole)) {*/
       case CategorizedCompositeNode::Type::CALL:
       case CategorizedCompositeNode::Type::BOOKMARK:
       default:
          switch (role) {
             case ContactModel::Role::Active:
                return true;
          }
          break;
       };
       return QVariant();
    }
    
    QVariant ContactProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
    {
       Q_UNUSED(section)
       if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
          return QVariant(tr("Contacts"));
       return QVariant();
    }
    
    bool ContactProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
    {
       Q_UNUSED( action )
       setData(parent,-1,Call::Role::DropState);
       if (data->hasFormat(MIME_CALLID)) {
          const QByteArray encodedCallId = data->data( MIME_CALLID    );
          const QModelIndex targetIdx    = index   ( row,column,parent );
          Call* call                     = CallModel::instance()->getCall ( encodedCallId        );
          if (call && targetIdx.isValid()) {
             CategorizedCompositeNode* modelItem = (CategorizedCompositeNode*)targetIdx.internalPointer();
             switch (modelItem->type()) {
                case CategorizedCompositeNode::Type::CONTACT: {
                   const Contact* ct = static_cast<Contact*>(modelItem->getSelf());
                   if (ct) {
                      switch(ct->phoneNumbers().size()) {
                         case 0: //Do nothing when there is no phone numbers
                            return false;
                         case 1: //Call when there is one
                            CallModel::instance()->transfer(call,ct->phoneNumbers()[0]);
                            break;
                         default:
                            //TODO
                            break;
                      };
                   }
                } break;
                case CategorizedCompositeNode::Type::NUMBER: {
                   const Contact::PhoneNumbers nbs = *static_cast<Contact::PhoneNumbers*>(modelItem);
                   const PhoneNumber*          nb  = nbs[row];
                   if (nb) {
                      call->setTransferNumber(nb->uri());
                      CallModel::instance()->transfer(call,nb);
                   }
                } break;
                case CategorizedCompositeNode::Type::CALL:
                case CategorizedCompositeNode::Type::BOOKMARK:
                case CategorizedCompositeNode::Type::TOP_LEVEL:
                   break;
             }
          }
       }
       return false;
    }
    
    
    int ContactProxyModel::rowCount( const QModelIndex& parent ) const
    {
       if (!parent.isValid() || !parent.internalPointer())
          return m_lCategoryCounter.size();
       const CategorizedCompositeNode* parentNode = static_cast<CategorizedCompositeNode*>(parent.internalPointer());
       switch(parentNode->type()) {
          case CategorizedCompositeNode::Type::TOP_LEVEL:
             return static_cast<const TopLevelItem*>(parentNode)->m_lChildren.size();
          case CategorizedCompositeNode::Type::CONTACT: {
             const Contact* ct = static_cast<Contact*>(parentNode->getSelf());
             const int size = ct->phoneNumbers().size();
             //Do not return the number if there is only one, it will be drawn part of the contact
             return size==1?0:size;
          }
          case CategorizedCompositeNode::Type::CALL:
          case CategorizedCompositeNode::Type::NUMBER:
          case CategorizedCompositeNode::Type::BOOKMARK:
          default:
             return 0;
       };
    }
    
    Qt::ItemFlags ContactProxyModel::flags( const QModelIndex& index ) const
    {
       if (!index.isValid())
          return Qt::NoItemFlags;
       return Qt::ItemIsEnabled | Qt::ItemIsSelectable | (index.parent().isValid()?Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled:Qt::ItemIsEnabled);
    }
    
    int ContactProxyModel::columnCount ( const QModelIndex& parent) const
    {
       Q_UNUSED(parent)
       return 1;
    }
    
    QModelIndex ContactProxyModel::parent( const QModelIndex& index) const
    {
       if (!index.isValid() || !index.internalPointer())
          return QModelIndex();
       const CategorizedCompositeNode* modelItem = static_cast<CategorizedCompositeNode*>(index.internalPointer());
       switch (modelItem->type()) {
          case CategorizedCompositeNode::Type::CONTACT: {
             const TopLevelItem* tl = ((ContactTreeNode*)(modelItem))->m_pParent3;
             return createIndex(tl->m_Index,0,(void*)tl);
          }
          break;
          case CategorizedCompositeNode::Type::NUMBER: {
             const ContactTreeNode* parentNode = static_cast<const ContactTreeNode*>(modelItem->parentNode());
             return createIndex(parentNode->m_Index, 0, (void*)parentNode);
          }
          case CategorizedCompositeNode::Type::TOP_LEVEL:
          case CategorizedCompositeNode::Type::BOOKMARK:
          case CategorizedCompositeNode::Type::CALL:
          default:
             return QModelIndex();
             break;
       };
    }
    
    QModelIndex ContactProxyModel::index( int row, int column, const QModelIndex& parent) const
    {
       if (parent.isValid() && parent.internalPointer()) {
          CategorizedCompositeNode* parentNode = static_cast<CategorizedCompositeNode*>(parent.internalPointer());
          switch(parentNode->type()) {
             case CategorizedCompositeNode::Type::TOP_LEVEL: {
                TopLevelItem* tld = static_cast<TopLevelItem*>(parentNode);
                if (tld && row < tld->m_lChildren.size())
                   return createIndex(row,column,(void*)tld->m_lChildren[row]);
             }
                break;
             case CategorizedCompositeNode::Type::CONTACT: {
                const ContactTreeNode* ctn = (ContactTreeNode*)parentNode;
                const Contact*          ct = (Contact*)ctn->getSelf()    ;
                if (ct->phoneNumbers().size()>row) {
                   const_cast<Contact::PhoneNumbers*>(&ct->phoneNumbers())->setParentNode((CategorizedCompositeNode*)ctn);
                   return createIndex(row,column,(void*)&ct->phoneNumbers());
                }
             }
                break;
             case CategorizedCompositeNode::Type::CALL:
             case CategorizedCompositeNode::Type::BOOKMARK:
             case CategorizedCompositeNode::Type::NUMBER:
                break;
          };
       }
       else if (row < m_lCategoryCounter.size()){
          //Return top level
          return createIndex(row,column,(void*)m_lCategoryCounter[row]);
       }
       return QModelIndex();
    }
    
    QStringList ContactProxyModel::mimeTypes() const
    {
       return m_lMimes;
    }
    
    QMimeData* ContactProxyModel::mimeData(const QModelIndexList &indexes) const
    {
       QMimeData *mimeData = new QMimeData();
       foreach (const QModelIndex &index, indexes) {
          if (index.isValid()) {
             const CategorizedCompositeNode* modelItem = static_cast<CategorizedCompositeNode*>(index.internalPointer());
             switch(modelItem->type()) {
                case CategorizedCompositeNode::Type::CONTACT: {
                   //Contact
                   const Contact* ct = static_cast<Contact*>(modelItem->getSelf());
                   if (ct) {
                      if (ct->phoneNumbers().size() == 1) {
                         mimeData->setData(MIME_PHONENUMBER , ct->phoneNumbers()[0]->toHash().toUtf8());
                      }
                      mimeData->setData(MIME_CONTACT , ct->uid());
                   }
                   return mimeData;
                   } break;
                case CategorizedCompositeNode::Type::NUMBER: {
                   //Phone number
                   const QString text = data(index, Qt::DisplayRole).toString();
                   const Contact::PhoneNumbers nbs = *static_cast<Contact::PhoneNumbers*>(index.internalPointer());
                   const PhoneNumber*          nb  = nbs[index.row()];
                   mimeData->setData(MIME_PLAIN_TEXT , text.toUtf8());
                   mimeData->setData(MIME_PHONENUMBER, nb->toHash().toUtf8());
                   return mimeData;
                   } break;
                case CategorizedCompositeNode::Type::TOP_LEVEL:
                case CategorizedCompositeNode::Type::CALL:
                case CategorizedCompositeNode::Type::BOOKMARK:
                default:
                   return nullptr;
             };
          }
       }
       return mimeData;
    }
    
    ///Return valid payload types
    int ContactProxyModel::acceptedPayloadTypes()
    {
       return CallModel::DropPayloadType::CALL;
    }
    
    
    
    /*****************************************************************************
     *                                                                           *
     *                                  Helpers                                  *
     *                                                                           *
     ****************************************************************************/
    
    
    QString ContactProxyModel::category(const Contact* ct) const {
       if (!ct)
          return QString();
       QString cat;
       switch (m_Role) {
          case ContactModel::Role::Organization:
             cat = ct->organization();
             break;
          case ContactModel::Role::Group:
             cat = ct->group();
             break;
          case ContactModel::Role::Department:
             cat = ct->department();
             break;
          case ContactModel::Role::PreferredEmail:
             cat = ct->preferredEmail();
             break;
          case ContactModel::Role::FormattedLastUsed:
             cat = HistoryTimeCategoryModel::timeToHistoryCategory(ct->phoneNumbers().lastUsedTimeStamp());
             break;
          case ContactModel::Role::IndexedLastUsed:
             cat = QString::number((int)HistoryTimeCategoryModel::timeToHistoryConst(ct->phoneNumbers().lastUsedTimeStamp()));
             break;
          case ContactModel::Role::DatedLastUsed:
             cat = QDateTime::fromTime_t(ct->phoneNumbers().lastUsedTimeStamp()).toString();
             break;
          default:
             cat = ct->formattedName();
       }
       if (cat.size() && !m_ShowAll)
          cat = cat[0].toUpper();
       return cat;
    }
    
    void ContactProxyModel::setRole(int role)
    {
       if (role != m_Role) {
          m_Role = role;
          reloadCategories();
       }
    }
    
    void ContactProxyModel::setShowAll(bool showAll)
    {
       if (showAll != m_ShowAll) {
          m_ShowAll = showAll;
          reloadCategories();
       }
    }