/**************************************************************************** * Copyright (C) 2013 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 "abstractcontactbackend.h" #include "callmodel.h" #include "historymodel.h" #include "phonenumber.h" #include "phonedirectorymodel.h" #include "historytimecategorymodel.h" #include "contact.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_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_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(AbstractContactBackend* parent,int role, bool showAll) : QAbstractItemModel(QCoreApplication::instance()), m_pModel(parent),m_Role(role),m_ShowAll(showAll),m_lCategoryCounter() { setObjectName("ContactProxyModel"); m_lCategoryCounter.reserve(32); m_lMimes << MIME_PLAIN_TEXT << MIME_PHONENUMBER; connect(m_pModel,SIGNAL(collectionChanged()),this,SLOT(reloadCategories())); connect(m_pModel,SIGNAL(newContactAdded(Contact*)),this,SLOT(slotContactAdded(Contact*))); QHash<int, QByteArray> roles = roleNames(); roles.insert(AbstractContactBackend::Role::Organization ,QByteArray("organization") ); roles.insert(AbstractContactBackend::Role::Group ,QByteArray("group") ); roles.insert(AbstractContactBackend::Role::Department ,QByteArray("department") ); roles.insert(AbstractContactBackend::Role::PreferredEmail ,QByteArray("preferredEmail") ); roles.insert(AbstractContactBackend::Role::FormattedLastUsed ,QByteArray("formattedLastUsed")); roles.insert(AbstractContactBackend::Role::IndexedLastUsed ,QByteArray("indexedLastUsed") ); roles.insert(AbstractContactBackend::Role::DatedLastUsed ,QByteArray("datedLastUsed") ); roles.insert(AbstractContactBackend::Role::Filter ,QByteArray("filter") ); roles.insert(AbstractContactBackend::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() { beginResetModel(); m_hCategories.clear(); foreach(TopLevelItem* item,m_lCategoryCounter) { delete item; } m_lCategoryCounter.clear(); foreach(Contact* cont, m_pModel->getContactList()) { if (cont) { const QString val = category(cont); TopLevelItem* item = getTopLevelItem(val); ContactTreeNode* contactNode = new ContactTreeNode(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) { 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 == AbstractContactBackend::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 AbstractContactBackend::Role::IndexedLastUsed: return index.child(0,0).data(AbstractContactBackend::Role::IndexedLastUsed); case AbstractContactBackend::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 AbstractContactBackend::Role::Organization: return QVariant(c->organization()); case AbstractContactBackend::Role::Group: return QVariant(c->group()); case AbstractContactBackend::Role::Department: return QVariant(c->department()); case AbstractContactBackend::Role::PreferredEmail: return QVariant(c->preferredEmail()); case AbstractContactBackend::Role::DropState: return QVariant(modelItem->dropState()); case AbstractContactBackend::Role::FormattedLastUsed: return QVariant(HistoryTimeCategoryModel::timeToHistoryCategory(c->phoneNumbers().lastUsedTimeStamp())); case AbstractContactBackend::Role::IndexedLastUsed: return QVariant((int)HistoryTimeCategoryModel::timeToHistoryConst(c->phoneNumbers().lastUsedTimeStamp())); case AbstractContactBackend::Role::Active: return c->isActive(); case AbstractContactBackend::Role::DatedLastUsed: return QVariant(QDateTime::fromTime_t( c->phoneNumbers().lastUsedTimeStamp())); case AbstractContactBackend::Role::Filter: { //Strip non essential characters like accents from the filter string QString normStripppedC; foreach(QChar char2,QString(c->formattedName()+'\n'+c->organization()+'\n'+c->group()+'\n'+ c->department()+'\n'+c->preferredEmail()).toLower().normalized(QString::NormalizationForm_KD) ) { if (!char2.combiningClass()) normStripppedC += char2; } return normStripppedC; } default: break; } break; } case CategorizedCompositeNode::Type::NUMBER: /* && (role == Qt::DisplayRole)) {*/ case CategorizedCompositeNode::Type::CALL: case CategorizedCompositeNode::Type::BOOKMARK: default: switch (role) { case AbstractContactBackend::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()) { CategorizedCompositeNode* parentNode = static_cast<CategorizedCompositeNode*>(parent.internalPointer()); switch(parentNode->type()) { case CategorizedCompositeNode::Type::TOP_LEVEL: { TopLevelItem* tld = static_cast<TopLevelItem*>(parentNode); 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,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().toUtf8()); } 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(Contact* ct) const { QString cat; switch (m_Role) { case AbstractContactBackend::Role::Organization: cat = ct->organization(); break; case AbstractContactBackend::Role::Group: cat = ct->group(); break; case AbstractContactBackend::Role::Department: cat = ct->department(); break; case AbstractContactBackend::Role::PreferredEmail: cat = ct->preferredEmail(); break; case AbstractContactBackend::Role::FormattedLastUsed: cat = HistoryTimeCategoryModel::timeToHistoryCategory(ct->phoneNumbers().lastUsedTimeStamp()); break; case AbstractContactBackend::Role::IndexedLastUsed: cat = QString::number((int)HistoryTimeCategoryModel::timeToHistoryConst(ct->phoneNumbers().lastUsedTimeStamp())); break; case AbstractContactBackend::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(); } }