Skip to content
Snippets Groups Projects
Select Git revision
  • 0196c5ec8ea4dff2aaece58419e6d539828259c0
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/windowsReleaseTest
  • release/releaseTest
  • release/releaseWindowsTest
  • 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
  • 4.0.0
  • 2.2.0
  • 2.1.0
  • 2.0.1
  • 2.0.0
  • 1.4.1
  • 1.4.0
  • 1.3.0
  • 1.2.0
  • 1.1.0
31 results

message_engine.cpp

Blame
    • Sébastien Blin's avatar
      0196c5ec
      sip: maintain SIP connections with contacts · 0196c5ec
      Sébastien Blin authored and Adrien Béraud's avatar Adrien Béraud committed
      This patch includes major changes on how messages are transmitted. It's the first
      patch that uses the ConnectionManager. Each time a device is detected via the DHT,
      Jami will ask a new p2p connection with that device and then will open a new SIP
      channel. Also, the same mechanism exists when the user try to send a new message
      through the DHT.
      
      In the future, that SIP channel will be used to send vCards, presence status,
      read status, etc.
      
      Change-Id: I415553193accd8b1259ffdc037e684524fe03876
      0196c5ec
      History
      sip: maintain SIP connections with contacts
      Sébastien Blin authored and Adrien Béraud's avatar Adrien Béraud committed
      This patch includes major changes on how messages are transmitted. It's the first
      patch that uses the ConnectionManager. Each time a device is detected via the DHT,
      Jami will ask a new p2p connection with that device and then will open a new SIP
      channel. Also, the same mechanism exists when the user try to send a new message
      through the DHT.
      
      In the future, that SIP channel will be used to send vCards, presence status,
      read status, etc.
      
      Change-Id: I415553193accd8b1259ffdc037e684524fe03876
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    categorizedcontactmodel.cpp 23.39 KiB
    /****************************************************************************
     *   Copyright (C) 2013-2018 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 "categorizedcontactmodel.h"
    
    //Qt
    #include <QtCore/QDebug>
    #include <QtCore/QDate>
    #include <QtCore/QMimeData>
    #include <QtCore/QCoreApplication>
    
    //Ring
    #include "callmodel.h"
    #include "categorizedhistorymodel.h"
    #include "contactmethod.h"
    #include "phonedirectorymodel.h"
    #include "historytimecategorymodel.h"
    #include "person.h"
    #include "uri.h"
    #include "mime.h"
    #include "personmodel.h"
    #include "private/sortproxies.h"
    
    class ContactTreeNode;
    
    class ContactTreeNode final
    {
    public:
       friend class CategorizedContactModel;
       friend class CategorizedContactModelPrivate;
       friend class ContactTreeBinder;
    
       enum class NodeType {
          PERSON       ,
          CONTACTMETHOD,
          CATEGORY     ,
       };
    
       //Constructor
       ContactTreeNode( const Person* ct    , CategorizedContactModel* parent);
       ContactTreeNode( ContactMethod* cm   , CategorizedContactModel* parent);
       ContactTreeNode( const QString& name , CategorizedContactModel* parent);
       virtual ~ContactTreeNode();
    
       //Attributes
       const Person*             m_pContact      ;
       ContactMethod*            m_pContactMethod;
       uint                      m_Index         ;
       QString                   m_Name          ;
       NodeType                  m_Type          ;
       QVector<ContactTreeNode*> m_lChildren     ;
       CategorizedContactModel*  m_pModel        ;
    
       //Setter
       inline void setParent (ContactTreeNode* n);
       inline void setVisible( bool visible     );
    
       //Helpers
       void slotChanged                    ();
       void slotContactMethodsChanged      ();
       void slotContactMethodsAboutToChange();
    
    private:
       ContactTreeNode*                 m_pParent       ;
       bool                             m_Visible       ;
       uint                             m_VisibleCounter;
       QVector<QMetaObject::Connection> m_lConections   ;
    };
    
    class CategorizedContactModelPrivate final : public QObject
    {
       Q_OBJECT
    public:
       CategorizedContactModelPrivate(CategorizedContactModel* parent);
    
       //Helpers
       QString category(const Person* ct) const;
    
       //Attributes
       QHash<Person*, time_t>               m_hContactByDate   ;
       QVector<ContactTreeNode*>            m_lCategoryCounter ;
       QHash<QString,ContactTreeNode*>      m_hCategories      ;
       int                                  m_Role             ;
       QStringList                          m_lMimes           ;
       bool                                 m_SortAlphabetical ;
       QString                              m_DefaultCategory  ;
       bool                                 m_UnreachableHidden;
       SortingCategory::ModelTuple*         m_pSortedProxy {nullptr};
       CategorizedContactModel::SortedProxy m_pProxies         ;
    
       //Helper
       ContactTreeNode* getContactTopLevelItem(const QString& category);
       QModelIndex getIndex(int row, int column, ContactTreeNode* parent);
       void reloadTreeVisibility               (ContactTreeNode*);
    
    private:
       CategorizedContactModel* q_ptr;
    
    public Q_SLOTS:
       void reloadCategories();
       void slotContactAdded(const Person* c);
       void slotContactRemoved(const Person* c);
    };
    
    ContactTreeNode::ContactTreeNode(const Person* ct, CategorizedContactModel* parent) :
       m_pContact(ct),m_Index(-1),m_pContactMethod(nullptr),m_Type(ContactTreeNode::NodeType::PERSON),m_pParent(nullptr),m_pModel(parent),m_Visible(true),
       m_VisibleCounter(0)
    {
       m_Visible = m_pContact->isActive() && ((!parent->d_ptr->m_UnreachableHidden) || m_pContact->isReachable());
       m_lConections << QObject::connect(m_pContact,&Person::changed                  ,[this](){ slotChanged                    (); });
       m_lConections << QObject::connect(m_pContact,&Person::phoneNumbersChanged      ,[this](){ slotContactMethodsChanged      (); });
       m_lConections << QObject::connect(m_pContact,&Person::phoneNumbersAboutToChange,[this](){ slotContactMethodsAboutToChange(); });
    }
    
    ContactTreeNode::ContactTreeNode(ContactMethod* cm, CategorizedContactModel* parent) :
       m_pContactMethod(cm),m_Index(-1),m_pContact(nullptr),m_Type(ContactTreeNode::NodeType::CONTACTMETHOD),m_pParent(nullptr),m_pModel(parent),
       m_Visible(true),m_VisibleCounter(0)
    {
       m_lConections << QObject::connect(m_pContactMethod,&ContactMethod::changed,[this](){ slotChanged(); });
    }
    
    ContactTreeNode::ContactTreeNode(const QString& name, CategorizedContactModel* parent) :
       m_pContactMethod(nullptr),m_Index(-1),m_pContact(nullptr),m_Type(ContactTreeNode::NodeType::CATEGORY),m_Name(name),m_pParent(nullptr),
       m_pModel(parent),m_Visible(false),m_VisibleCounter(0)
    {
    }
    
    ContactTreeNode::~ContactTreeNode()
    {
       for (auto c : m_lConections) {
          QObject::disconnect(c);
       }
       for (ContactTreeNode* c : m_lChildren) {
          delete c;
       }
    }
    
    QModelIndex CategorizedContactModelPrivate::getIndex(int row, int column, ContactTreeNode* parent)
    {
       return q_ptr->createIndex(row,column,parent);
    }
    
    void ContactTreeNode::slotChanged()
    {
       const QModelIndex& self = m_pModel->d_ptr->getIndex(m_Index,0,this);
    
       if (!self.isValid()) return;
    
       ContactTreeNode *n = static_cast<ContactTreeNode*>(self.internalPointer());
       if (n->m_Type == ContactTreeNode::NodeType::PERSON)
          n->m_Visible = n->m_pContact && n->m_pContact->isActive();
       emit m_pModel->dataChanged(self,self);
    
       const QModelIndex& tl = m_pModel->index(0,0,self);
       const QModelIndex& br = m_pModel->index(0,m_pModel->rowCount(self),self);
       emit m_pModel->dataChanged(tl, br);
    }
    
    void ContactTreeNode::slotContactMethodsChanged()
    {
       const QModelIndex idx = m_pModel->d_ptr->getIndex(m_Index,0,this);
    
       //After discussion, it was decided that contacts with only 1 phone number should
       //be handled differently and the additional complexity isn't worth it
       if (m_pContact->phoneNumbers().size() > 1) {
          m_pModel->beginInsertRows(idx,0,m_pContact->phoneNumbers().size()-1);
          for (int i = 0; i < m_pContact->phoneNumbers().size(); ++i) {
             ContactTreeNode* n2 = new ContactTreeNode(m_pContact->phoneNumbers()[i],m_pModel);
             n2->m_Index = m_lChildren.size();
             n2->setParent(this);
             m_lChildren << n2;
          }
          m_pModel->endInsertRows();
       }
       emit m_pModel->dataChanged(idx,idx);
    }
    
    void ContactTreeNode::slotContactMethodsAboutToChange()
    {
       const QModelIndex idx = m_pModel->d_ptr->getIndex(m_Index,0,this);
    
       if (m_lChildren.size() > 0) {
          m_pModel->beginRemoveRows(idx,0,m_lChildren.size()-1);
          while (m_lChildren.size()) {
             auto node = m_lChildren.at(0);
             m_lChildren.removeAt(0);
             delete node;
          }
          m_pModel->endRemoveRows();
       }
    }
    
    void ContactTreeNode::setParent(ContactTreeNode* p)
    {
       if (p && p != m_pParent && p->m_Type == NodeType::CATEGORY) {
          p->m_VisibleCounter += m_Visible ? 1 : 0;
          const bool hasChanged = p->m_Visible != (p->m_VisibleCounter > 0);
          p->m_Visible = p->m_VisibleCounter > 0;
          if (hasChanged) {
             const QModelIndex idx = m_pModel->index(p->m_Index,0);
             emit m_pModel->dataChanged(idx,idx);
          }
       }
       m_pParent = p;
    }
    
    void ContactTreeNode::setVisible(bool v)
    {
       if (v != m_Visible) {
          if (m_pParent) {
             m_pParent->m_VisibleCounter += v ? 1 : -1;
             m_pParent->m_Visible = m_pParent->m_VisibleCounter > 0;
          }
          m_Visible = v;
          const QModelIndex idx = m_pModel->index(m_Index,0,m_pParent ?
             m_pModel->index(m_pParent->m_Index,0) : QModelIndex()
          );
          emit m_pModel->dataChanged(idx,idx);
       }
    }
    
    CategorizedContactModelPrivate::CategorizedContactModelPrivate(CategorizedContactModel* parent) : QObject(parent), q_ptr(parent),
    m_lCategoryCounter(),m_Role(Qt::DisplayRole),m_SortAlphabetical(true),m_UnreachableHidden(false),m_pSortedProxy(nullptr)
    {
    
    }
    
    //
    CategorizedContactModel::CategorizedContactModel(int role) : QAbstractItemModel(QCoreApplication::instance()),d_ptr(new CategorizedContactModelPrivate(this))
    {
       setObjectName("CategorizedContactModel");
       d_ptr->m_Role    = role;
       d_ptr->m_lCategoryCounter.reserve(32);
       d_ptr->m_lMimes << RingMimes::PLAIN_TEXT << RingMimes::PHONENUMBER;
    
       connect(&PersonModel::instance(),&PersonModel::newPersonAdded,d_ptr.data(),&CategorizedContactModelPrivate::slotContactAdded);
       connect(&PersonModel::instance(),&PersonModel::personRemoved,d_ptr.data(),&CategorizedContactModelPrivate::slotContactRemoved);
    
       for(int i=0; i < PersonModel::instance().rowCount();i++) {
          Person* p = qvariant_cast<Person*>(PersonModel::instance().index(i,0).data((int)Person::Role::Object));
          d_ptr->slotContactAdded(p);
       }
    
    }
    
    CategorizedContactModel::~CategorizedContactModel()
    {
       foreach(ContactTreeNode* item,d_ptr->m_lCategoryCounter) {
          delete item;
       }
    
       if (d_ptr->m_pSortedProxy)
          delete d_ptr->m_pSortedProxy;
    }
    
    CategorizedContactModel& CategorizedContactModel::instance()
    {
        static auto instance = new CategorizedContactModel;
        return *instance;
    }
    
    QHash<int,QByteArray> CategorizedContactModel::roleNames() const
    {
       static QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
       static bool initRoles = false;
       if (!initRoles) {
          initRoles = true;
          roles.insert((int)Person::Role::Organization      ,QByteArray("organization")     );
          roles.insert((int)Person::Role::Group             ,QByteArray("group")            );
          roles.insert((int)Person::Role::Department        ,QByteArray("department")       );
          roles.insert((int)Person::Role::PreferredEmail    ,QByteArray("preferredEmail")   );
          roles.insert((int)Person::Role::FormattedLastUsed ,QByteArray("formattedLastUsed"));
          roles.insert((int)Person::Role::IndexedLastUsed   ,QByteArray("indexedLastUsed")  );
          roles.insert((int)Person::Role::DatedLastUsed     ,QByteArray("datedLastUsed")    );
          roles.insert((int)Person::Role::Filter            ,QByteArray("filter")           );
          roles.insert((int)Person::Role::DropState         ,QByteArray("dropState")        );
       }
       return roles;
    }
    
    ContactTreeNode* CategorizedContactModelPrivate::getContactTopLevelItem(const QString& category)
    {
       if (!m_hCategories[category]) {
          ContactTreeNode* item = new ContactTreeNode(category,q_ptr);
          m_hCategories[category] = item;
          item->m_Index = m_lCategoryCounter.size();
    //       emit layoutAboutToBeChanged();
          q_ptr->beginInsertRows(QModelIndex(),m_lCategoryCounter.size(),m_lCategoryCounter.size()); {
             m_lCategoryCounter << item;
          } q_ptr->endInsertRows();
    //       emit layoutChanged();
       }
       ContactTreeNode* item = m_hCategories[category];
       return item;
    }
    
    void CategorizedContactModelPrivate::reloadCategories()
    {
       emit q_ptr->layoutAboutToBeChanged(); //FIXME far from optimal
       m_hCategories.clear();
       q_ptr->beginRemoveRows(QModelIndex(),0,m_lCategoryCounter.size()-1);
       foreach(ContactTreeNode* item,m_lCategoryCounter) {
          delete item;
       }
       q_ptr->endRemoveRows();
       m_lCategoryCounter.clear();
       for(int i=0; i < PersonModel::instance().rowCount();i++) {
          Person* cont = qvariant_cast<Person*>(PersonModel::instance().index(i,0).data((int)Person::Role::Object));
          slotContactAdded(cont);
       }
       emit q_ptr->layoutChanged();
    }
    
    void CategorizedContactModelPrivate::slotContactRemoved(const Person* c) {
        Q_UNUSED(c)
    
        reloadCategories();
    }
    
    void CategorizedContactModelPrivate::slotContactAdded(const Person* c)
    {
       if (!c) return;
    
       const QString val = category(c);
       ContactTreeNode* item = getContactTopLevelItem(val);
       ContactTreeNode* contactNode = new ContactTreeNode(c,q_ptr);
       contactNode->setParent(item);
       contactNode->m_Index = item->m_lChildren.size();
       //emit layoutAboutToBeChanged();
       q_ptr->beginInsertRows(q_ptr->index(item->m_Index,0,QModelIndex()),item->m_lChildren.size(),item->m_lChildren.size()); {
          item->m_lChildren << contactNode;
       } q_ptr->endInsertRows();
       reloadTreeVisibility(item);
    
       if (c->phoneNumbers().size() > 1) {
          q_ptr->beginInsertRows(q_ptr->createIndex(contactNode->m_Index,0,contactNode),0,c->phoneNumbers().size() - 1);
          foreach (ContactMethod* m, c->phoneNumbers() ) { //TODO check if this can be merged with slotContactMethodCountChanged
             ContactTreeNode* n2 = new ContactTreeNode(m,q_ptr);
             n2->m_Index = contactNode->m_lChildren.size();
             n2->setParent(contactNode);
             contactNode->m_lChildren << n2;
          }
          q_ptr->endInsertRows();
       }
    
       //emit layoutChanged();
    }
    
    bool CategorizedContactModel::setData( const QModelIndex& index, const QVariant &value, int role)
    {
       Q_UNUSED(index)
       Q_UNUSED(value)
       Q_UNUSED(role )
    
       return false;
    }
    
    QVariant CategorizedContactModel::data( const QModelIndex& index, int role) const
    {
       if (!index.isValid())
          return QVariant();
    
       ContactTreeNode* modelItem = (ContactTreeNode*)index.internalPointer();
       switch (modelItem->m_Type) {
          case ContactTreeNode::NodeType::CATEGORY:
          switch (role) {
             case Qt::DisplayRole:
                return static_cast<const ContactTreeNode*>(modelItem)->m_Name;
             case (int)Person::Role::IndexedLastUsed:
                return index.child(0,0).data((int)Person::Role::IndexedLastUsed);
             default:
                break;
          }
          break;
       case ContactTreeNode::NodeType::PERSON:
          return modelItem->m_pContact->roleData(role);
       case ContactTreeNode::NodeType::CONTACTMETHOD: /* && (role == Qt::DisplayRole)) {*/
          return modelItem->m_pContactMethod->roleData(role);
       }
       return QVariant();
    }
    
    QVariant CategorizedContactModel::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 CategorizedContactModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
    {
       Q_UNUSED( action )
       setData(parent,-1,static_cast<int>(Call::Role::DropState));
       if (data->hasFormat(RingMimes::CALLID)) {
          const QByteArray encodedCallId = data->data( RingMimes::CALLID    );
          const QModelIndex targetIdx    = index   ( row,column,parent );
          Call* call                     = CallModel::instance().fromMime ( encodedCallId        );
          if (call && targetIdx.isValid()) {
             ContactTreeNode* modelItem = (ContactTreeNode*)targetIdx.internalPointer();
             switch (modelItem->m_Type) {
                case ContactTreeNode::NodeType::PERSON: {
                   const Person* ct = modelItem->m_pContact;
                   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 ContactTreeNode::NodeType::CONTACTMETHOD: {
                   const ContactMethod* nb  = modelItem->m_pContactMethod;
                   if (nb) {
                      call->setTransferNumber(nb->uri());
                      CallModel::instance().transfer(call,nb);
                   }
                } break;
                case ContactTreeNode::NodeType::CATEGORY:
                   break;
             }
          }
       }
       return false;
    }
    
    
    int CategorizedContactModel::rowCount( const QModelIndex& parent ) const
    {
       if (!parent.isValid() || !parent.internalPointer())
          return d_ptr->m_lCategoryCounter.size();
       const ContactTreeNode* parentNode = static_cast<ContactTreeNode*>(parent.internalPointer());
    
       if (parentNode)
          return parentNode->m_lChildren.size();
    
       return 0;
    }
    
    Qt::ItemFlags CategorizedContactModel::flags( const QModelIndex& index ) const
    {
       if (!index.isValid())
          return Qt::NoItemFlags;
    
       const ContactTreeNode* modelNode = static_cast<ContactTreeNode*>(index.internalPointer());
    
       return (modelNode->m_Visible) ? Qt::ItemIsEnabled | Qt::ItemIsSelectable | (
          modelNode->m_pParent? (Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled) : Qt::NoItemFlags
       ) : Qt::NoItemFlags;
    }
    
    int CategorizedContactModel::columnCount ( const QModelIndex& parent) const
    {
       Q_UNUSED(parent)
       return 1;
    }
    
    QModelIndex CategorizedContactModel::parent( const QModelIndex& index) const
    {
       if (!index.isValid() || !index.internalPointer())
          return QModelIndex();
    
       const ContactTreeNode* modelItem = static_cast<ContactTreeNode*>(index.internalPointer());
    
       if (modelItem && modelItem->m_pParent)
          return createIndex(modelItem->m_pParent->m_Index,0,modelItem->m_pParent);
    
       return QModelIndex();
    }
    
    QModelIndex CategorizedContactModel::index( int row, int column, const QModelIndex& parent) const
    {
       if (!parent.isValid() && row < d_ptr->m_lCategoryCounter.size()) {
          return createIndex(row,column,d_ptr->m_lCategoryCounter[row]);
       }
    
       ContactTreeNode* parentNode = static_cast<ContactTreeNode*>(parent.internalPointer());
    
       if (parentNode && row < parentNode->m_lChildren.size())
          return createIndex(row,column,parentNode->m_lChildren[row]);
    
       return QModelIndex();
    }
    
    QStringList CategorizedContactModel::mimeTypes() const
    {
       return d_ptr->m_lMimes;
    }
    
    QMimeData* CategorizedContactModel::mimeData(const QModelIndexList &indexes) const
    {
       QMimeData *mimeData = new QMimeData();
       foreach (const QModelIndex &index, indexes) {
          if (index.isValid()) {
             const ContactTreeNode* modelItem = static_cast<ContactTreeNode*>(index.internalPointer());
             switch(modelItem->m_Type) {
                case ContactTreeNode::NodeType::PERSON: {
                   //Contact
                   const Person* ct = modelItem->m_pContact;
                   if (ct) {
                      if (ct->phoneNumbers().size() == 1) {
                         mimeData->setData(RingMimes::PHONENUMBER , ct->phoneNumbers()[0]->toHash().toUtf8());
                      }
                      mimeData->setData(RingMimes::CONTACT , ct->uid());
                   }
                   return mimeData;
                   }
                case ContactTreeNode::NodeType::CONTACTMETHOD: {
                   //Phone number
                   const QString text = data(index, Qt::DisplayRole).toString();
                   const ContactTreeNode* n = static_cast<ContactTreeNode*>(index.internalPointer());
                   if (n->m_pContactMethod) {
                      mimeData->setData(RingMimes::PLAIN_TEXT , text.toUtf8());
                      mimeData->setData(RingMimes::PHONENUMBER, n->m_pContactMethod->toHash().toUtf8());
                      return mimeData;
                   }
    
                   } break;
                case ContactTreeNode::NodeType::CATEGORY:
                   return nullptr;
             };
          }
       }
       return mimeData;
    }
    
    ///Return valid payload types
    int CategorizedContactModel::acceptedPayloadTypes()
    {
       return CallModel::DropPayloadType::CALL;
    }
    
    
    
    /*****************************************************************************
     *                                                                           *
     *                                  Helpers                                  *
     *                                                                           *
     ****************************************************************************/
    
    
    QString CategorizedContactModelPrivate::category(const Person* ct) const {
       if (!ct)
          return QString();
    
       QString cat = ct->roleData(m_Role).toString();
    
       if (cat.size() && m_SortAlphabetical)
          cat = cat[0].toUpper();
       else if (!cat.size())
          cat = m_DefaultCategory;
    
       return cat;
    }
    
    void CategorizedContactModel::setRole(int role)
    {
       if (role != d_ptr->m_Role) {
          d_ptr->m_Role = role;
          d_ptr->reloadCategories();
       }
    }
    
    void CategorizedContactModel::setSortAlphabetical(bool alpha)
    {
       d_ptr->m_SortAlphabetical = alpha;
    }
    
    bool CategorizedContactModel::isSortAlphabetical() const
    {
       return d_ptr->m_SortAlphabetical;
    }
    
    void CategorizedContactModel::setDefaultCategory(const QString& cat)
    {
       d_ptr->m_DefaultCategory = cat;
    }
    
    QString CategorizedContactModel::defaultCategory() const
    {
       return d_ptr->m_DefaultCategory;
    }
    
    bool CategorizedContactModel::areUnreachableHidden() const
    {
       return d_ptr->m_UnreachableHidden;
    }
    
    /**
     * Show/hide contacts that are currently unreachable
     *
     * @warning This method will reloaded person reachability status,
     * this require large amount of CPU when many contacts are present
     */
    void CategorizedContactModel::setUnreachableHidden(bool val)
    {
       if (d_ptr->m_UnreachableHidden != val) {
          d_ptr->m_UnreachableHidden = val;
          d_ptr->reloadTreeVisibility(nullptr);
       }
    }
    
    void CategorizedContactModelPrivate::reloadTreeVisibility( ContactTreeNode* node )
    {
       if (!node) {
          for(ContactTreeNode* n : m_hCategories)
             reloadTreeVisibility(n);
          return;
       }
    
       switch(node->m_Type) {
          case ContactTreeNode::NodeType::PERSON       :
             node->setVisible(node->m_pContact->isActive() && ((!m_UnreachableHidden) || node->m_pContact->isReachable()));
             break;
          case ContactTreeNode::NodeType::CONTACTMETHOD:
             //Nothing to do
             break;
          case ContactTreeNode::NodeType::CATEGORY     :
             for (ContactTreeNode* n : node->m_lChildren)
                reloadTreeVisibility(n);
             break;
       };
    }
    
    QSortFilterProxyModel* CategorizedContactModel::SortedProxy::model() const
    {
        if (!CategorizedContactModel::instance().d_ptr->m_pSortedProxy)
            CategorizedContactModel::instance().d_ptr->m_pSortedProxy = SortingCategory::getContactProxy();
    
        return CategorizedContactModel::instance().d_ptr->m_pSortedProxy->model;
    }
    
    QAbstractItemModel* CategorizedContactModel::SortedProxy::categoryModel() const
    {
        if (!CategorizedContactModel::instance().d_ptr->m_pSortedProxy)
            CategorizedContactModel::instance().d_ptr->m_pSortedProxy = SortingCategory::getContactProxy();
    
        return CategorizedContactModel::instance().d_ptr->m_pSortedProxy->categories;
    }
    
    QItemSelectionModel* CategorizedContactModel::SortedProxy::categorySelectionModel() const
    {
        if (!CategorizedContactModel::instance().d_ptr->m_pSortedProxy)
            CategorizedContactModel::instance().d_ptr->m_pSortedProxy = SortingCategory::getContactProxy();
    
        return CategorizedContactModel::instance().d_ptr->m_pSortedProxy->selectionModel;
    }
    
    CategorizedContactModel::SortedProxy& CategorizedContactModel::SortedProxy::instance()
    {
        return CategorizedContactModel::instance().d_ptr->m_pProxies;
    }
    
    #include <categorizedcontactmodel.moc>