Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
categorizedhistorymodel.cpp 24.28 KiB
/****************************************************************************
 *   Copyright (C) 2012-2015 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 "categorizedhistorymodel.h"

//C include
#include <time.h>

//Qt include
#include <QMimeData>
#include <QCoreApplication>

//Ring lib
#include "mime.h"
#include "dbus/callmanager.h"
#include "dbus/configurationmanager.h"
#include "call.h"
#include "person.h"
#include "contactmethod.h"
#include "callmodel.h"
#include "collectioneditor.h"
#include "historytimecategorymodel.h"
#include "lastusednumbermodel.h"
#include "collectioninterface.h"
#include "delegates/itemmodelstateserializationdelegate.h"

/*****************************************************************************
 *                                                                           *
 *                             Private classes                               *
 *                                                                           *
 ****************************************************************************/

class HistoryTopLevelItem;

class CategorizedHistoryModelPrivate : public QObject
{
   Q_OBJECT
public:
   CategorizedHistoryModelPrivate(CategorizedHistoryModel* parent);

   //Model
   class HistoryItem : public CategorizedCompositeNode {
   public:
      explicit HistoryItem(Call* call);
      virtual ~HistoryItem();
      virtual QObject* getSelf() const;
      Call* call() const;
      int m_Index;
      HistoryTopLevelItem* m_pParent;
      HistoryItemNode* m_pNode;
   private:
      Call* m_pCall;
   };

   //Helpers
   HistoryTopLevelItem* getCategory(const Call* call);

   //Attributes
   static CallMap m_sHistoryCalls;

   //Model categories
   QVector<HistoryTopLevelItem*>       m_lCategoryCounter ;
   QHash<int,HistoryTopLevelItem*>     m_hCategories      ;
   QHash<QString,HistoryTopLevelItem*> m_hCategoryByName  ;
   int                          m_Role             ;
   QStringList                  m_lMimes           ;

private:
   CategorizedHistoryModel* q_ptr;

public Q_SLOTS:
   void add(Call* call);
   void reloadCategories();
   void slotChanged(const QModelIndex& idx);
};

class HistoryTopLevelItem : public CategorizedCompositeNode,public QObject {
   friend class CategorizedHistoryModel;
   friend class CategorizedHistoryModelPrivate;
public:
   virtual QObject* getSelf() const;
   virtual ~HistoryTopLevelItem();
   int m_Index;
   int m_AbsoluteIndex;
   QVector<CategorizedHistoryModelPrivate::HistoryItem*> m_lChildren;
private:
   explicit HistoryTopLevelItem(const QString& name, int index);
   QString m_NameStr;
   int modelRow;
};

class HistoryItemNode : public QObject //TODO remove this once Qt4 support is dropped
{
   Q_OBJECT
public:
   HistoryItemNode(CategorizedHistoryModel* m, Call* c, CategorizedHistoryModelPrivate::HistoryItem* backend);
   Call* m_pCall;
private:
   CategorizedHistoryModelPrivate::HistoryItem* m_pBackend;
   CategorizedHistoryModel* m_pModel;
private Q_SLOTS:
   void slotNumberChanged();
Q_SIGNALS:
   void changed(const QModelIndex& idx);
};

HistoryItemNode::HistoryItemNode(CategorizedHistoryModel* m, Call* c, CategorizedHistoryModelPrivate::HistoryItem* backend) :
m_pCall(c),m_pBackend(backend),m_pModel(m){
   connect(c,SIGNAL(changed()),this,SLOT(slotNumberChanged()));
}

void HistoryItemNode::slotNumberChanged()
{
   emit changed(m_pModel->index(m_pBackend->m_Index,0,m_pModel->index(m_pBackend->m_pParent->m_AbsoluteIndex,0)));
}

CategorizedHistoryModel* CategorizedHistoryModel::m_spInstance    = nullptr;
CallMap       CategorizedHistoryModelPrivate::m_sHistoryCalls          ;

HistoryTopLevelItem::HistoryTopLevelItem(const QString& name, int index) : 
   CategorizedCompositeNode(CategorizedCompositeNode::Type::TOP_LEVEL),QObject(nullptr),m_Index(index),m_NameStr(name),
   m_AbsoluteIndex(-1),modelRow(-1)
{}

HistoryTopLevelItem::~HistoryTopLevelItem() {
   const int idx = CategorizedHistoryModel::m_spInstance->d_ptr->m_lCategoryCounter.indexOf(this);
   if (idx != -1)
      CategorizedHistoryModel::m_spInstance->d_ptr->m_lCategoryCounter.remove(idx);
   while(m_lChildren.size()) {
      CategorizedHistoryModelPrivate::HistoryItem* item = m_lChildren[0];
      m_lChildren.remove(0);
      delete item;
   }
}

QObject* HistoryTopLevelItem::getSelf() const
{
   return const_cast<HistoryTopLevelItem*>(this);
}

CategorizedHistoryModelPrivate::HistoryItem::HistoryItem(Call* call) : CategorizedCompositeNode(CategorizedCompositeNode::Type::CALL),m_pCall(call),
m_Index(0),m_pParent(nullptr),m_pNode(nullptr)
{
   
}

CategorizedHistoryModelPrivate::HistoryItem::~HistoryItem()
{
   delete m_pNode;
}


QObject* CategorizedHistoryModelPrivate::HistoryItem::getSelf() const
{
   return const_cast<Call*>(m_pCall);
}

Call* CategorizedHistoryModelPrivate::HistoryItem::call() const
{
   return m_pCall;
}


/*****************************************************************************
 *                                                                           *
 *                                 Constructor                               *
 *                                                                           *
 ****************************************************************************/

CategorizedHistoryModelPrivate::CategorizedHistoryModelPrivate(CategorizedHistoryModel* parent) : QObject(parent), q_ptr(parent),
m_Role(static_cast<int>(Call::Role::FuzzyDate))
{
}

///Constructor
CategorizedHistoryModel::CategorizedHistoryModel():QAbstractItemModel(QCoreApplication::instance()),CollectionManagerInterface<Call>(this),
d_ptr(new CategorizedHistoryModelPrivate(this))
{
   m_spInstance  = this;
   d_ptr->m_lMimes << RingMimes::PLAIN_TEXT << RingMimes::PHONENUMBER << RingMimes::HISTORYID;
} //initHistory

///Destructor
CategorizedHistoryModel::~CategorizedHistoryModel()
{
   for (int i=0; i<d_ptr->m_lCategoryCounter.size();i++) {
      delete d_ptr->m_lCategoryCounter[i];
   }
   while(d_ptr->m_lCategoryCounter.size()) {
      HistoryTopLevelItem* item = d_ptr->m_lCategoryCounter[0];
      d_ptr->m_lCategoryCounter.remove(0);

      delete item;
   }
   m_spInstance = nullptr;
}

QHash<int,QByteArray> CategorizedHistoryModel::roleNames() const
{
   static QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
   static bool initRoles = false;
   if (!initRoles) {
      initRoles = true;
      roles.insert(static_cast<int>(Call::Role::Name          ) ,QByteArray("name"          ));
      roles.insert(static_cast<int>(Call::Role::Number        ) ,QByteArray("number"        ));
      roles.insert(static_cast<int>(Call::Role::Direction     ) ,QByteArray("direction"     ));
      roles.insert(static_cast<int>(Call::Role::Date          ) ,QByteArray("date"          ));
      roles.insert(static_cast<int>(Call::Role::Length        ) ,QByteArray("length"        ));
      roles.insert(static_cast<int>(Call::Role::FormattedDate ) ,QByteArray("formattedDate" ));
      roles.insert(static_cast<int>(Call::Role::HasRecording  ) ,QByteArray("hasRecording"  ));
      roles.insert(static_cast<int>(Call::Role::Historystate  ) ,QByteArray("historyState"  ));
      roles.insert(static_cast<int>(Call::Role::Filter        ) ,QByteArray("filter"        ));
      roles.insert(static_cast<int>(Call::Role::FuzzyDate     ) ,QByteArray("fuzzyDate"     ));
      roles.insert(static_cast<int>(Call::Role::IsBookmark    ) ,QByteArray("isBookmark"    ));
      roles.insert(static_cast<int>(Call::Role::Security      ) ,QByteArray("security"      ));
      roles.insert(static_cast<int>(Call::Role::Department    ) ,QByteArray("department"    ));
      roles.insert(static_cast<int>(Call::Role::Email         ) ,QByteArray("email"         ));
      roles.insert(static_cast<int>(Call::Role::Organisation  ) ,QByteArray("organisation"  ));
      roles.insert(static_cast<int>(Call::Role::Object        ) ,QByteArray("object"        ));
      roles.insert(static_cast<int>(Call::Role::Photo         ) ,QByteArray("photo"         ));
      roles.insert(static_cast<int>(Call::Role::State         ) ,QByteArray("state"         ));
      roles.insert(static_cast<int>(Call::Role::StartTime     ) ,QByteArray("startTime"     ));
      roles.insert(static_cast<int>(Call::Role::StopTime      ) ,QByteArray("stopTime"      ));
      roles.insert(static_cast<int>(Call::Role::DropState     ) ,QByteArray("dropState"     ));
      roles.insert(static_cast<int>(Call::Role::DTMFAnimState ) ,QByteArray("dTMFAnimState" ));
      roles.insert(static_cast<int>(Call::Role::LastDTMFidx   ) ,QByteArray("lastDTMFidx"   ));
      roles.insert(static_cast<int>(Call::Role::IsRecording   ) ,QByteArray("isRecording"   ));
   }
   return roles;
}

///Singleton
CategorizedHistoryModel* CategorizedHistoryModel::instance()
{
   if (!m_spInstance)
      m_spInstance = new CategorizedHistoryModel();
   return m_spInstance;
}


/*****************************************************************************
 *                                                                           *
 *                           History related code                            *
 *                                                                           *
 ****************************************************************************/
///Get the top level item based on a call
HistoryTopLevelItem* CategorizedHistoryModelPrivate::getCategory(const Call* call)
{
   HistoryTopLevelItem* category = nullptr;
   static QString name;
   int index = -1;
   if (m_Role == static_cast<int>(Call::Role::FuzzyDate)) {
      index = call->roleData(Call::Role::FuzzyDate).toInt();
      name = HistoryTimeCategoryModel::indexToName(index);
      category = m_hCategories[index];
   }
   else {
      name = call->roleData(m_Role).toString();
      category = m_hCategoryByName[name];
   }
   if (!category) {
      category = new HistoryTopLevelItem(name,index);
      category->modelRow = m_lCategoryCounter.size();
      //emit layoutAboutToBeChanged(); //Not necessary
      CategorizedHistoryModel::instance()->beginInsertRows(QModelIndex(),m_lCategoryCounter.size(),m_lCategoryCounter.size());
      category->m_AbsoluteIndex = m_lCategoryCounter.size();
      m_lCategoryCounter << category;
      m_hCategories    [index] = category;
      m_hCategoryByName[name ] = category;
      CategorizedHistoryModel::instance()->endInsertRows();
      //emit layoutChanged();
   }
   return category;
}


const CallMap CategorizedHistoryModel::getHistoryCalls() const
{
   return d_ptr->m_sHistoryCalls;
}

///Add to history
void CategorizedHistoryModelPrivate::add(Call* call)
{
   if (!call || call->lifeCycleState() != Call::LifeCycleState::FINISHED || !call->startTimeStamp()) {
      return;
   }

//    if (!m_HavePersonModel && call->contactBackend()) {
//       connect(((QObject*)call->contactBackend()),SIGNAL(collectionChanged()),this,SLOT(reloadCategories()));
//       m_HavePersonModel = true;
//    }//TODO implement reordering

   emit q_ptr->newHistoryCall(call);
   HistoryTopLevelItem* tl = getCategory(call);
   const QModelIndex& parentIdx = q_ptr->index(tl->modelRow,0);
   q_ptr->beginInsertRows(parentIdx,tl->m_lChildren.size(),tl->m_lChildren.size());
   CategorizedHistoryModelPrivate::HistoryItem* item = new CategorizedHistoryModelPrivate::HistoryItem(call);
   item->m_pParent = tl;
   item->m_pNode = new HistoryItemNode(q_ptr,call,item);
   connect(item->m_pNode,SIGNAL(changed(QModelIndex)),this,SLOT(slotChanged(QModelIndex)));
   item->m_Index = tl->m_lChildren.size();
   tl->m_lChildren << item;

   //Try to prevent startTimeStamp() collisions, it technically doesn't work as time_t are signed
   //we don't care
   m_sHistoryCalls[(call->startTimeStamp() << 10)+qrand()%1024] = call;
   q_ptr->endInsertRows();
   LastUsedNumberModel::instance()->addCall(call);
   emit q_ptr->historyChanged();

   /*
   // Loop until it find a compatible backend
   //HACK only support a single active history backend
   if (!call->collection()) {
      foreach (CollectionInterface* backend, q_ptr->collections(CollectionInterface::ADD)) {
         if (backend->editor<Call>()->addNew(call)) {
            call->setCollection(backend);
            break;
         }
      }
   }*/
}

///Set if the history has a limit
void CategorizedHistoryModel::setHistoryLimited(bool isLimited)
{
   if (!isLimited)
      DBus::ConfigurationManager::instance().setHistoryLimit(0);
}

///Set the number of days before history items are discarded
void CategorizedHistoryModel::setHistoryLimit(int numberOfDays)
{
   DBus::ConfigurationManager::instance().setHistoryLimit(numberOfDays);
}

///Is history items are being deleted after "historyLimit()" days
bool CategorizedHistoryModel::isHistoryLimited() const
{
   return DBus::ConfigurationManager::instance().getHistoryLimit() != 0;
}

///Number of days before items are discarded (0 = never)
int CategorizedHistoryModel::historyLimit() const
{
   return DBus::ConfigurationManager::instance().getHistoryLimit();
}


/*****************************************************************************
 *                                                                           *
 *                              Model related                                *
 *                                                                           *
 ****************************************************************************/

void CategorizedHistoryModelPrivate::reloadCategories()
{
   q_ptr->beginResetModel();
   m_hCategories.clear();
   m_hCategoryByName.clear();
   foreach(HistoryTopLevelItem* item, m_lCategoryCounter) {
      delete item;
   }
   m_lCategoryCounter.clear();
   foreach(Call* call, m_sHistoryCalls) {
      HistoryTopLevelItem* category = getCategory(call);
      if (category) {
         HistoryItem* item = new HistoryItem(call);
         item->m_Index = category->m_lChildren.size();
         item->m_pNode = new HistoryItemNode(q_ptr,call,item);
         connect(item->m_pNode,SIGNAL(changed(QModelIndex)),this,SLOT(slotChanged(QModelIndex)));
         item->m_pParent = category;
         category->m_lChildren << item;
      }
      else
         qDebug() << "ERROR count";
   }
   q_ptr->endResetModel();
   emit q_ptr->layoutAboutToBeChanged();
   emit q_ptr->layoutChanged();
   emit q_ptr->dataChanged(q_ptr->index(0,0),q_ptr->index(q_ptr->rowCount()-1,0));
}

void CategorizedHistoryModelPrivate::slotChanged(const QModelIndex& idx)
{
   emit q_ptr->dataChanged(idx,idx);
}

bool CategorizedHistoryModel::setData( const QModelIndex& idx, const QVariant &value, int role)
{
   if (idx.isValid() && idx.parent().isValid()) {
      CategorizedCompositeNode* modelItem = (CategorizedCompositeNode*)idx.internalPointer();
      if (role == static_cast<int>(Call::Role::DropState)) {
         modelItem->setDropState(value.toInt());
         emit dataChanged(idx, idx);
      }
   }
   return false;
}

QVariant CategorizedHistoryModel::data( const QModelIndex& idx, int role) const
{
   if (!idx.isValid())
      return QVariant();
   CategorizedCompositeNode* modelItem = static_cast<CategorizedCompositeNode*>(idx.internalPointer());
   switch (modelItem->type()) {
      case CategorizedCompositeNode::Type::TOP_LEVEL:
      switch (role) {
         case Qt::DisplayRole:
            return static_cast<HistoryTopLevelItem*>(modelItem)->m_NameStr;
         case static_cast<int>(Call::Role::FuzzyDate):
         case static_cast<int>(Call::Role::Date):
            return d_ptr->m_lCategoryCounter.size() - static_cast<HistoryTopLevelItem*>(modelItem)->m_Index;
         default:
            break;
      }
      break;
   case CategorizedCompositeNode::Type::CALL:
      if (role == static_cast<int>(Call::Role::DropState))
         return QVariant(modelItem->dropState());
      else {
         const int parRow = idx.parent().row();
         const HistoryTopLevelItem* parTli = d_ptr->m_lCategoryCounter[parRow];

         //HACK force a reload
         #if QT_VERSION >= 0x050400
         if (parTli->isActive() && !parTli->m_lChildren[idx.row()]->call()->isActive()) {
            QTimer::singleShot(0,[this,idx]() {
               emit const_cast<CategorizedHistoryModel*>(this)->dataChanged(idx,idx);
            });
         }
         #endif

         if (d_ptr->m_lCategoryCounter.size() > parRow && parRow >= 0 && parTli && parTli->m_lChildren.size() > idx.row())
            return parTli->m_lChildren[idx.row()]->call()->roleData((Call::Role)role);
      }
      break;
   case CategorizedCompositeNode::Type::NUMBER:
   case CategorizedCompositeNode::Type::BOOKMARK:
   case CategorizedCompositeNode::Type::CONTACT:
   default:
      break;
   };
   return QVariant();
}

QVariant CategorizedHistoryModel::headerData(int section, Qt::Orientation orientation, int role) const
{
   Q_UNUSED(section)
   if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
      return QVariant(tr("History"));
   if (role == Qt::InitialSortOrderRole)
      return QVariant(Qt::DescendingOrder);
   return QVariant();
}

int CategorizedHistoryModel::rowCount( const QModelIndex& parentIdx ) const
{
   if ((!parentIdx.isValid()) || (!parentIdx.internalPointer())) {
      return d_ptr->m_lCategoryCounter.size();
   }
   else {
      CategorizedCompositeNode* node = static_cast<CategorizedCompositeNode*>(parentIdx.internalPointer());
      switch(node->type()) {
         case CategorizedCompositeNode::Type::TOP_LEVEL:
            return ((HistoryTopLevelItem*)node)->m_lChildren.size();
         case CategorizedCompositeNode::Type::CALL:
         case CategorizedCompositeNode::Type::NUMBER:
         case CategorizedCompositeNode::Type::BOOKMARK:
         case CategorizedCompositeNode::Type::CONTACT:
         default:
            return 0;
      };
   }
}

Qt::ItemFlags CategorizedHistoryModel::flags( const QModelIndex& idx ) const
{
   if (!idx.isValid())
      return Qt::NoItemFlags;

   CategorizedCompositeNode* node = static_cast<CategorizedCompositeNode*>(idx.internalPointer());
   const bool hasParent = node->type() != CategorizedCompositeNode::Type::TOP_LEVEL;
   const bool isEnabled = node->type() == CategorizedCompositeNode::Type::CALL && ((Call*)((CategorizedCompositeNode*)(idx.internalPointer()))->getSelf())->isActive();

   return (isEnabled?Qt::ItemIsEnabled:Qt::NoItemFlags) | Qt::ItemIsSelectable | (hasParent?Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled:Qt::ItemIsEnabled);
}

int CategorizedHistoryModel::columnCount ( const QModelIndex& parentIdx) const
{
   Q_UNUSED(parentIdx)
   return 1;
}

QModelIndex CategorizedHistoryModel::parent( const QModelIndex& idx) const
{
   if (!idx.isValid() || !idx.internalPointer()) {
      return QModelIndex();
   }
   CategorizedCompositeNode* modelItem = static_cast<CategorizedCompositeNode*>(idx.internalPointer());
   if (modelItem && modelItem->type() == CategorizedCompositeNode::Type::CALL) {
      const Call* call = (Call*)((CategorizedCompositeNode*)(idx.internalPointer()))->getSelf();
      //TODO this is called way to often to use getCategory, make sure getSelf return the node and cache this
      HistoryTopLevelItem* tli = d_ptr->getCategory(call);
      if (tli)
         return CategorizedHistoryModel::index(tli->modelRow,0);
   }
   return QModelIndex();
}

QModelIndex CategorizedHistoryModel::index( int row, int column, const QModelIndex& parentIdx) const
{
   if (!parentIdx.isValid()) {
      if (row >= 0 && d_ptr->m_lCategoryCounter.size() > row) {
         return createIndex(row,column,(void*)d_ptr->m_lCategoryCounter[row]);
      }
   }
   else {
      CategorizedCompositeNode* node = static_cast<CategorizedCompositeNode*>(parentIdx.internalPointer());
      switch(node->type()) {
         case CategorizedCompositeNode::Type::TOP_LEVEL:
            if (((HistoryTopLevelItem*)node)->m_lChildren.size() > row)
               return createIndex(row,column,(void*)static_cast<CategorizedCompositeNode*>(((HistoryTopLevelItem*)node)->m_lChildren[row]));
            break;
         case CategorizedCompositeNode::Type::CALL:
         case CategorizedCompositeNode::Type::NUMBER:
         case CategorizedCompositeNode::Type::BOOKMARK:
         case CategorizedCompositeNode::Type::CONTACT:
            break;
      };
   }
   return QModelIndex();
}

///Called when dynamically adding calls, otherwise the proxy filter will segfault
bool CategorizedHistoryModel::insertRows( int row, int count, const QModelIndex & parent)
{
   if (parent.isValid()) {
      beginInsertRows(parent,row,row+count-1);
      endInsertRows();
      return true;
   }
   return false;
}

QStringList CategorizedHistoryModel::mimeTypes() const
{
   return d_ptr->m_lMimes;
}

QMimeData* CategorizedHistoryModel::mimeData(const QModelIndexList &indexes) const
{
   QMimeData *mimeData2 = new QMimeData();
   foreach (const QModelIndex &idx, indexes) {
      if (idx.isValid()) {
         const QString text = data(idx, static_cast<int>(Call::Role::Number)).toString();
         mimeData2->setData(RingMimes::PLAIN_TEXT , text.toUtf8());
         const Call* call = (Call*)((CategorizedCompositeNode*)(idx.internalPointer()))->getSelf();
         mimeData2->setData(RingMimes::PHONENUMBER, call->peerContactMethod()->toHash().toUtf8());
         CategorizedCompositeNode* node = static_cast<CategorizedCompositeNode*>(idx.internalPointer());
         if (node->type() == CategorizedCompositeNode::Type::CALL)
            mimeData2->setData(RingMimes::HISTORYID  , static_cast<Call*>(node->getSelf())->dringId().toUtf8());
         return mimeData2;
      }
   }
   return mimeData2;
}

bool CategorizedHistoryModel::dropMimeData(const QMimeData *mime, Qt::DropAction action, int row, int column, const QModelIndex &parentIdx)
{
   Q_UNUSED(row)
   Q_UNUSED(column)
   Q_UNUSED(action)
   setData(parentIdx,-1,static_cast<int>(Call::Role::DropState));
   QByteArray encodedContactMethod = mime->data( RingMimes::PHONENUMBER );
   QByteArray encodedPerson     = mime->data( RingMimes::CONTACT     );

   if (parentIdx.isValid() && mime->hasFormat( RingMimes::CALLID)) {
      QByteArray encodedCallId      = mime->data( RingMimes::CALLID      );
      Call* call = CallModel::instance()->fromMime(encodedCallId);
      if (call) {
         const QModelIndex& idx = index(row,column,parentIdx);
         if (idx.isValid()) {
            const Call* target = (Call*)((CategorizedCompositeNode*)(idx.internalPointer()))->getSelf();
            if (target) {
               CallModel::instance()->transfer(call,target->peerContactMethod());
               return true;
            }
         }
      }
   }
   return false;
}

void CategorizedHistoryModel::collectionAddedCallback(CollectionInterface* backend)
{
   Q_UNUSED(backend)
}

///Call all collections that support clearing
bool CategorizedHistoryModel::clearAllCollections() const
{
   foreach (CollectionInterface* backend, collections()) { //TODO use the filter API
      if (backend->supportedFeatures() & CollectionInterface::CLEAR) {
         backend->clear();
      }
   }
   return true;
}

bool CategorizedHistoryModel::addItemCallback(const Call* item)
{
   d_ptr->add(const_cast<Call*>(item));
   return true;
}

bool CategorizedHistoryModel::removeItemCallback(const Call* item)
{
   Q_UNUSED(item)
   emit const_cast<Call*>(item)->changed();
   return false;
}

///Return valid payload types
int CategorizedHistoryModel::acceptedPayloadTypes() const
{
   return CallModel::DropPayloadType::CALL;
}

void CategorizedHistoryModel::setCategoryRole(int role)
{
   if (d_ptr->m_Role != role) {
      d_ptr->m_Role = role;
      d_ptr->reloadCategories();
   }
}

#include <categorizedhistorymodel.moc>