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

//Std
#include <atomic>

//Qt
#include <QtCore/QDebug>
#include <QtCore/QCoreApplication>
#include <QtCore/QMimeData>
#include <QtCore/QItemSelectionModel>

//Ring library
#include "call.h"
#include "uri.h"
#include "phonedirectorymodel.h"
#include "contactmethod.h"
#include "accountmodel.h"
#include "availableaccountmodel.h"
#include "dbus/metatypes.h"
#include "dbus/callmanager.h"
#include "dbus/configurationmanager.h"
#include "dbus/instancemanager.h"
#include "private/videorenderermanager.h"
#include "private/imconversationmanagerprivate.h"
#include "mime.h"
#include "typedefs.h"
#include "collectioninterface.h"
#include "dbus/videomanager.h"
#include "categorizedhistorymodel.h"
#include "globalinstances.h"
#include "interfaces/contactmethodselectori.h"
#include "personmodel.h"
#include "useractionmodel.h"
#include "video/renderer.h"
#include "media/audio.h"
#include "media/video.h"
#include "private/media_p.h"
#include "call_const.h"

//System
#include <unistd.h>
#include <errno.h>

//Private
#include "private/call_p.h"

//Define
///InternalStruct: internal representation of a call
struct InternalStruct {
   InternalStruct() : m_pParent(nullptr),call_real(nullptr),conference(false){}
   Call*                  call_real  ;
   QModelIndex            index      ;
   QList<InternalStruct*> m_lChildren;
   bool                   conference ;
   InternalStruct*        m_pParent  ;
};

class CallModelPrivate final : public QObject
{
   Q_OBJECT
public:
      CallModelPrivate(CallModel* parent);
      void init();
      Call* addCall2         ( Call* call                , Call* parent = nullptr );
      Call* addConference    ( const QString& confID                              );
      void  removeConference ( const QString& confId                              );
      void  removeCall       ( Call* call       , bool noEmit = false             );
      Call* addIncomingCall  ( const QString& callId                              );
      Call* addExistingCall  ( const QString& callId, const QString& state);

      //Attributes
      QList<InternalStruct*> m_lInternalModel;
      QHash< Call*       , InternalStruct* > m_shInternalMapping ;
      QHash< QString     , InternalStruct* > m_shDringId         ;
      QItemSelectionModel* m_pSelectionModel;
      UserActionModel*     m_pUserActionModel;


      //Helpers
      bool isPartOf(const QModelIndex& confIdx, Call* call);
      void removeConference       ( Call* conf                    );
      void removeInternal(InternalStruct* internal);
      static QStringList getCallList();

   private:
      CallModel* q_ptr;

   private Q_SLOTS:
      void slotCallStateChanged   ( const QString& callID    , const QString &state, int code);
      void slotIncomingCall       ( const QString& accountID , const QString & callID );
      void slotIncomingConference ( const QString& confID                             );
      void slotChangingConference ( const QString& confID    , const QString &state   );
      void slotConferenceRemoved  ( const QString& confId                             );
      void slotAddPrivateCall     ( Call* call                                        );
      void slotNewRecordingAvail  ( const QString& callId    , const QString& filePath);
      void slotCallChanged        (                                                   );
      void slotStateChanged       ( Call::State newState, Call::State previousState   );
      void slotDTMFPlayed         ( const QString& str                                );
      void slotRecordStateChanged ( const QString& callId    , bool state             );
      void slotAudioMuted         ( const QString& callId    , bool state             );
      void slotVideoMutex         ( const QString& callId    , bool state             );
      void slotPeerHold           ( const QString& callId    , bool state             );
};


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

///Singleton
CallModel& CallModel::instance()
{
    static auto instance = new CallModel();

    // Fix loop-dependency issue between constructors
    static std::atomic_flag init_flag {ATOMIC_FLAG_INIT};
    if (not init_flag.test_and_set())
        instance->d_ptr->init();

    return *instance;
}

CallModelPrivate::CallModelPrivate(CallModel* parent) : QObject(parent),q_ptr(parent),m_pSelectionModel(nullptr),
m_pUserActionModel(nullptr)
{

}

///Retrieve current and older calls from the daemon, fill history, model and enable drag n' drop
CallModel::CallModel() : QAbstractItemModel(QCoreApplication::instance()),d_ptr(new CallModelPrivate(this))
{
   //Register with the daemon
   InstanceManager::instance();
   setObjectName("CallModel");
   #ifdef ENABLE_VIDEO
   VideoRendererManager::instance();
   #endif

   //Necessary to receive text message
   IMConversationManagerPrivate::instance();
} //CallModel

///Constructor (there fix an initializationn loop)
void CallModelPrivate::init()
{
    CallManagerInterface& callManager = CallManager::instance();
#ifdef ENABLE_VIDEO
    VideoManager::instance();
#endif

    //SLOTS
    /*             SENDER                          SIGNAL                     RECEIVER                    SLOT                   */
    /**/connect(&callManager, SIGNAL(callStateChanged(QString,QString,int))  , this , SLOT(slotCallStateChanged(QString,QString,int)));
    /**/connect(&callManager, SIGNAL(incomingCall(QString,QString,QString))   , this , SLOT(slotIncomingCall(QString,QString))       );
    /**/connect(&callManager, SIGNAL(conferenceCreated(QString))              , this , SLOT(slotIncomingConference(QString))         );
    /**/connect(&callManager, SIGNAL(conferenceChanged(QString,QString))      , this , SLOT(slotChangingConference(QString,QString)) );
    /**/connect(&callManager, SIGNAL(conferenceRemoved(QString))              , this , SLOT(slotConferenceRemoved(QString))          );
    /**/connect(&callManager, SIGNAL(recordPlaybackFilepath(QString,QString)) , this , SLOT(slotNewRecordingAvail(QString,QString))  );
    /**/connect(&callManager, SIGNAL(recordingStateChanged(QString,bool))     , this , SLOT(slotRecordStateChanged(QString,bool)));
    /**/connect(&callManager, SIGNAL(audioMuted(QString,bool))                , this , SLOT(slotAudioMuted(QString,bool)));
    /**/connect(&callManager, SIGNAL(videoMuted(QString,bool))                , this , SLOT(slotVideoMutex(QString,bool)));
    /**/connect(&callManager, SIGNAL(peerHold(QString,bool))                  , this , SLOT(slotPeerHold(QString,bool)));
    /*                                                                                                                           */

    connect(&CategorizedHistoryModel::instance(),SIGNAL(newHistoryCall(Call*)),this,SLOT(slotAddPrivateCall(Call*)));

    registerCommTypes();

    const QStringList callList = getCallList();
    foreach (const QString& callId, callList) {
        Call* tmpCall = CallPrivate::buildExistingCall(callId);
        addCall2(tmpCall);
    }

    const QStringList confList = callManager.getConferenceList();
    foreach (const QString& confId, confList) {
        Call* conf = addConference(confId);
        emit q_ptr->conferenceCreated(conf);
    }
}

///Destructor
CallModel::~CallModel()
{
   const QList<Call*> keys = d_ptr->m_shInternalMapping.keys();
   const QList<InternalStruct*> values = d_ptr->m_shInternalMapping.values();
   foreach (Call* call, keys )
      delete call;
   foreach (InternalStruct* s,  values )
      delete s;
   d_ptr->m_shInternalMapping.clear  ();
   d_ptr->m_shDringId.clear();

   //Unregister from the daemon
   InstanceManagerInterface& instance = InstanceManager::instance();
   Q_NOREPLY instance.Unregister(getpid());
#ifdef ENABLE_LIBWRAP

#else
   instance.connection().disconnectFromBus(instance.connection().baseService());
#endif //ENABLE_LIBWRAP
}

QHash<int,QByteArray> CallModel::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::HasAVRecording  ) ,QByteArray("hasAVRecording")  );
      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::IsAVRecording   ) ,QByteArray("isAVRecording")   );
      roles.insert(static_cast<int>(Call::Role::LifeCycleState  ) ,QByteArray("lifeCycleState")  );
      roles.insert(static_cast<int>(Call::Role::DateOnly        ) ,QByteArray("dateOnly")        );
      roles.insert(static_cast<int>(Call::Role::DateTime        ) ,QByteArray("dateTime")        );
   }
   return roles;
}

QItemSelectionModel* CallModel::selectionModel() const
{
   if (!d_ptr->m_pSelectionModel) {
      d_ptr->m_pSelectionModel = new QItemSelectionModel(const_cast<CallModel*>(this));
   }
   return d_ptr->m_pSelectionModel;
}

/**
 * Use the selection model to extract the current Call
 *
 * @return The selection call or nullptr
 */
Call* CallModel::selectedCall() const
{
   return getCall(selectionModel()->currentIndex());
}

/**
 * Select a call or remove the selection if the call is invalid
 *
 * @param call the call to select
 */
void CallModel::selectCall(Call* call) const
{
   const QModelIndex idx = getIndex(call);

   selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
}

/**
 * Select and return the dialing call. If there is none, one will be added
 *
 * @see CallModel::dialingCall
 * @return the dialing call
 */
Call* CallModel::selectDialingCall(const QString& peerName, Account* account)
{
   Call* c = dialingCall(peerName,account);

   selectCall(c);

   return c;
}

/*****************************************************************************
 *                                                                           *
 *                         Access related functions                          *
 *                                                                           *
 ****************************************************************************/

///Return the active call count
int CallModel::size()
{
   return d_ptr->m_lInternalModel.size();
}

///Return the action call list
CallList CallModel::getActiveCalls()
{
   CallList callList;
   foreach(InternalStruct* internalS, d_ptr->m_lInternalModel) {
      callList.push_back(internalS->call_real);
      if (internalS->m_lChildren.size()) {
         foreach(InternalStruct* childInt,internalS->m_lChildren) {
            callList.push_back(childInt->call_real);
         }
      }
   }
   return callList;
} //getCallList

///Return all conferences
CallList CallModel::getActiveConferences()
{
   CallList confList;

   //That way it can not be invalid
   const QStringList confListS = CallManager::instance().getConferenceList();
   foreach (const QString& confId, confListS) {
      InternalStruct* internalS = d_ptr->m_shDringId[confId];
      if (!internalS) {
         qDebug() << "Warning: Conference not found, creating it, this should not happen";
         Call* conf = d_ptr->addConference(confId);
         confList << conf;
         emit conferenceCreated(conf);
      }
      else
         confList << internalS->call_real;
   }
   return confList;
} //getConferenceList

bool CallModel::hasConference() const
{
   foreach(const InternalStruct* s, d_ptr->m_lInternalModel) {
      if (s->m_lChildren.size())
         return true;
   }
   return false;
}

QList<Call*> CallModel::getConferenceParticipants(Call* conf) const
{
    QList<Call*> participantCallList;

    const auto internalConf = d_ptr->m_shInternalMapping[conf];
    foreach (const auto s, internalConf->m_lChildren) {
        participantCallList << s->call_real;
    }
    return participantCallList;
}

bool CallModel::isConnected() const
{
#ifdef ENABLE_LIBWRAP
   return InstanceManager::instance().isConnected();
#else
   return InstanceManager::instance().connection().isConnected();
#endif //ENABLE_LIBWRAP
}

bool CallModel::isValid()
{
   return CallManager::instance().isValid();
}


UserActionModel* CallModel::userActionModel() const
{
   if (!d_ptr->m_pUserActionModel)
      d_ptr->m_pUserActionModel = new UserActionModel(const_cast<CallModel*>(this));
   return d_ptr->m_pUserActionModel;
}


/*****************************************************************************
 *                                                                           *
 *                            Call related code                              *
 *                                                                           *
 ****************************************************************************/

///Get the call associated with this index
Call* CallModel::getCall( const QModelIndex& idx ) const
{
   if (idx.isValid() && idx.data(static_cast<int>(Call::Role::Object)).canConvert<Call*>())
      return qvariant_cast<Call*>(idx.data(static_cast<int>(Call::Role::Object)));
   return nullptr;
}

///Get the call associated with this ID
Call* CallModel::getCall( const QString& callId ) const
{
   if (d_ptr->m_shDringId.value(callId)) {
      return d_ptr->m_shDringId[callId]->call_real;
   }
   return nullptr;
}

///Make sure all signals can be mapped back to Call objects
void CallModel::registerCall(Call* call)
{
   d_ptr->m_shDringId[ call->dringId() ] = d_ptr->m_shInternalMapping[call];
}

///Add a call in the model structure, the call must exist before being added to the model
Call* CallModelPrivate::addCall2(Call* call, Call* parentCall)
{
   //The current History implementation doesn't support conference
   //if something try to add an history conference, something went wrong
   if (!call
    || ((parentCall && parentCall->lifeCycleState() == Call::LifeCycleState::FINISHED)
    && (call->lifeCycleState() == Call::LifeCycleState::FINISHED))) {

      qWarning() << "Trying to add an invalid call to the tree" << call;
      Q_ASSERT(false);

      //WARNING this will trigger an assert later on, but isn't critical enough in release mode.
      //HACK This return an invalid object that should be equivalent to NULL but won't require
      //nullptr check everywhere in the code. It is safer to use an invalid object rather than
      //causing a NULL dereference
      return new Call(QString(),QString());
   }
   if (m_shInternalMapping[call]) {
      qWarning() << "Trying to add a call that already have been added" << call;
      Q_ASSERT(false);
   }

   //Even history call currently need to be tracked in CallModel, this may change
   InternalStruct* aNewStruct = new InternalStruct;
   aNewStruct->call_real  = call;
   aNewStruct->conference = false;

   m_shInternalMapping  [ call       ] = aNewStruct;
   if (call->lifeCycleState() != Call::LifeCycleState::FINISHED) {
      q_ptr->beginInsertRows(QModelIndex(),m_lInternalModel.size(),m_lInternalModel.size());
      m_lInternalModel << aNewStruct;
      q_ptr->endInsertRows();
   }

   //Dialing calls don't have remote yet, it will be added later
   if (call->hasRemote())
      m_shDringId[ call->dringId() ] = aNewStruct;

   //If the call is already finished, there is no point to track it here
   if (call->lifeCycleState() != Call::LifeCycleState::FINISHED) {
      emit q_ptr->callAdded(call,parentCall);
      const QModelIndex idx = q_ptr->index(m_lInternalModel.size()-1,0,QModelIndex());
      emit q_ptr->dataChanged(idx, idx);
      connect(call, &Call::changed, this, &CallModelPrivate::slotCallChanged);
      connect(call,&Call::stateChanged,this,&CallModelPrivate::slotStateChanged);
      connect(call,SIGNAL(dtmfPlayed(QString)),this,SLOT(slotDTMFPlayed(QString)));
      connect(call,&Call::videoStarted,[this,call](Video::Renderer* r) {
         emit q_ptr->rendererAdded(call, r);
      });
      connect(call,&Call::videoStopped,[this,call](Video::Renderer* r) {
         emit q_ptr->rendererRemoved(call, r);
      });
      connect(call,&Call::mediaAdded, [this,call](Media::Media* media) {
         emit q_ptr->mediaAdded(call,media);
      });
      connect(call,&Call::mediaStateChanged, [this,call](Media::Media* media, const Media::Media::State s, const Media::Media::State m) {
         emit q_ptr->mediaStateChanged(call,media,s,m);
      });

      foreach(auto m, call->allMedia())
         emit q_ptr->mediaAdded(call, m);

      emit q_ptr->layoutChanged();
   }
   return call;
} //addCall

///Return the current or create a new dialing call from peer ContactMethod
Call* CallModel::dialingCall(ContactMethod* cm, Call* parent)
{
   auto call = dialingCall(QString(), nullptr, parent);
   call->setPeerContactMethod(cm);
   return call;
}

///Return the current or create a new dialing call from peer name and the account
Call* CallModel::dialingCall(const QString& peerName, Account* account, Call* parent)
{
   //Having multiple dialing calls could be supported, but for now we decided not to
   //handle this corner case as it will create issues of its own
   foreach (Call* call, getActiveCalls()) {
      if (call->lifeCycleState() == Call::LifeCycleState::CREATION)
         return call;
   }

   return d_ptr->addCall2(CallPrivate::buildDialingCall(peerName, account, parent));
}  //dialingCall

///Create a new incoming call when the daemon is being called
Call* CallModelPrivate::addIncomingCall(const QString& callId)
{
   qDebug() << "New incoming call:" << callId;

   // Since november 2015, calls are alowed to be declared with a state change
   // if it has been done, then they should be ignored
   // contains can be true and contain nullptr if it was accessed without
   // contains() first
   Call* call = nullptr;
   if (not m_shDringId.value(callId)) {

      call = CallPrivate::buildIncomingCall(callId);

      //The call can already have been invalidated by the daemon, then do nothing
      if (!call)
         return nullptr;

      call = addCall2(call);

      //The call can already have been invalidated by the daemon, then do nothing
      if (!call)
         return nullptr;

   } else {
       qDebug() << "The call" << callId << "already exist, avoiding re-creation";
       call = m_shDringId[callId]->call_real;
   }

   //Call without account is not possible
   if (call->account()) {
       //WARNING: This code will need to be moved if we decide to drop
       //incomingCall signal
      if (call->account()->isAutoAnswer()) {
         call->performAction(Call::Action::ACCEPT);
      }
   }
   else {
      qDebug() << "Incoming call from an invalid account";
      throw tr("Invalid account");
   }
   return call;
}

Call* CallModelPrivate::addExistingCall(const QString& callId, const QString& state)
{
    Q_UNUSED(state)

    qDebug() << "New foreign call:" << callId;
    auto call = CallPrivate::buildExistingCall(callId);

    //The call can already have been invalidated by the daemon, then do nothing
    if (!call)
        return {};

    call = addCall2(call);

    //The call can already have been invalidated by the daemon, then do nothing
    if (!call)
        return {};

    //Call without account is not possible
    if (not call->account()) {
        qDebug() << "Foreign call from an invalid account";
        throw tr("Invalid account");
    }

    return call;
}

///Properly remove an internal from the Qt model
void CallModelPrivate::removeInternal(InternalStruct* internal)
{
   if (!internal) return;

   const int idx = m_lInternalModel.indexOf(internal);
   //Exit if the call is not found
   if (idx == -1) {
      qDebug() << "Cannot remove " << internal->call_real << ": call not found in tree";
      return;
   }

   q_ptr->beginRemoveRows(QModelIndex(),idx,idx);
   //disconnect from all the signal of this call, since it no longer needs to be updated in the model
   internal->call_real->disconnect(this);
   m_lInternalModel.removeAt(idx);
   q_ptr->endRemoveRows();
}

/**
 * LibRingClient doesn't [need to] handle INACTIVE calls
 * This method make sure they never get into the system.
 *
 * It's not very efficient, but do the job. This method isn't
 * called very often anyway.
 */
QStringList CallModelPrivate::getCallList()
{
   CallManagerInterface& callManager = CallManager::instance();
   const QStringList callList = callManager.getCallList();
   QStringList ret;

   for (const QString& callId : callList) {
      QMap<QString, QString> details = callManager.getCallDetails(callId);
      if (details[DRing::Call::Details::CALL_STATE] != DRing::Call::StateEvent::INACTIVE)
         ret << callId;
   }

   return ret;
}

///Remove a call and update the internal structure
void CallModelPrivate::removeCall(Call* call, bool noEmit)
{
   Q_UNUSED(noEmit)
   InternalStruct* internal = m_shInternalMapping[call];
   if (!internal || !call) {
      qDebug() << "Cannot remove " << (internal?internal->call_real:nullptr) << ": call not found";
      return;
   }

   if (internal != nullptr) {
      removeInternal(internal);
      //NOTE Do not free the memory, it can still be used elsewhere or in modelindexes
   }

   //Restore calls to the main list if they are not really over
   if (internal->m_lChildren.size()) {
      foreach(InternalStruct* child,internal->m_lChildren) {
         if (child->call_real->state() != Call::State::OVER && child->call_real->state() != Call::State::ERROR) {
            q_ptr->beginInsertRows(QModelIndex(),m_lInternalModel.size(),m_lInternalModel.size());
            m_lInternalModel << child;
            q_ptr->endInsertRows();
         }
      }
   }

   //Be sure to reset these properties (just in case)
   call->setProperty("DTMFAnimState",0);
   call->setProperty("dropState",0);

   //The daemon often fail to emit the right signal, cleanup manually
   foreach(InternalStruct* topLevel, m_lInternalModel) {
      if (topLevel->call_real->type() == Call::Type::CONFERENCE &&
         (!topLevel->m_lChildren.size()
            //HACK Make a simple validation to prevent ERROR->ERROR->ERROR state loop for conferences
            || topLevel->m_lChildren.first()->call_real->state() == Call::State::ERROR
            || topLevel->m_lChildren.last() ->call_real->state() == Call::State::ERROR))
            removeConference(topLevel->call_real);
   }
//    if (!noEmit)
      emit q_ptr->layoutChanged();
} //removeCall


QModelIndex CallModel::getIndex(Call* call) const
{
   if (!call)
      return QModelIndex();

   InternalStruct* internal = d_ptr->m_shInternalMapping[call];
   int idx = d_ptr->m_lInternalModel.indexOf(internal);
   if (idx != -1) {
      return index(idx,0);
   }
   else {
      foreach(InternalStruct* str,d_ptr->m_lInternalModel) {
         idx = str->m_lChildren.indexOf(internal);
         if (idx != -1)
            return index(idx,0,index(d_ptr->m_lInternalModel.indexOf(str),0));
      }
   }
   return QModelIndex();
}

///Transfer "toTransfer" to "target" and wait to see it it succeeded
void CallModel::attendedTransfer(Call* toTransfer, Call* target)
{
   if ((!toTransfer) || (!target)) return;
   Q_NOREPLY CallManager::instance().attendedTransfer(toTransfer->dringId(),target->dringId());

   //TODO [Daemon] Implement this correctly
   toTransfer->d_ptr->changeCurrentState(Call::State::OVER);
   target->d_ptr->changeCurrentState(Call::State::OVER);
} //attendedTransfer
///Transfer this call to  "target" number
void CallModel::transfer(Call* toTransfer, const ContactMethod* target)
{
   qDebug() << "Transferring call " << toTransfer << "to" << target->uri();
   toTransfer->setTransferNumber        ( target->uri()            );
   toTransfer->performAction            ( Call::Action::TRANSFER   );
   toTransfer->d_ptr->changeCurrentState( Call::State::TRANSFERRED );
   toTransfer->performAction            ( Call::Action::ACCEPT     );
   toTransfer->d_ptr->changeCurrentState( Call::State::OVER        );
   emit toTransfer->isOver();
} //transfer

/*****************************************************************************
 *                                                                           *
 *                         Conference related code                           *
 *                                                                           *
 ****************************************************************************/

///Add a new conference, get the call list and update the interface as needed
Call* CallModelPrivate::addConference(const QString& confID)
{
   qDebug() << "Notified of a new conference " << confID;
   CallManagerInterface& callManager = CallManager::instance();
   const QStringList callList = callManager.getParticipantList(confID);
   qDebug() << "Paticiapants are:" << callList;

   if (!callList.size()) {
      qDebug() << "This conference (" + confID + ") contain no call";
      return nullptr;
   }

   if (!m_shDringId.value(callList[0])) {
      qDebug() << "Invalid call";
      return nullptr;
   }

   Call* newConf = nullptr;
   if (m_shDringId[callList[0]]->call_real->account())
      newConf =  new Call(confID, m_shDringId[callList[0]]->call_real->account()->id());

   if (newConf) {
      InternalStruct* aNewStruct = new InternalStruct;
      aNewStruct->call_real  = newConf;
      aNewStruct->conference = true;

      m_shInternalMapping[newConf]  = aNewStruct;
      m_shDringId[confID] = aNewStruct;
      q_ptr->beginInsertRows(QModelIndex(),m_lInternalModel.size(),m_lInternalModel.size());
      m_lInternalModel << aNewStruct;
      q_ptr->endInsertRows();

      foreach(const QString& callId,callList) {
         InternalStruct* callInt = m_shDringId[callId];
         if (callInt) {
            if (callInt->m_pParent && callInt->m_pParent != aNewStruct)
               callInt->m_pParent->m_lChildren.removeAll(callInt);
            removeInternal(callInt);
            callInt->m_pParent = aNewStruct;
            callInt->call_real->setProperty("dropState",0);
            if (aNewStruct->m_lChildren.indexOf(callInt) == -1) {
               auto parent = q_ptr->index(m_lInternalModel.indexOf(aNewStruct), 0, QModelIndex());
               q_ptr->beginInsertRows(parent, aNewStruct->m_lChildren.size(), aNewStruct->m_lChildren.size());
               aNewStruct->m_lChildren << callInt;
               q_ptr->endInsertRows();
            }
         }
         else {
            qDebug() << "References to unknown call";
         }
      }
      const QModelIndex idx = q_ptr->index(m_lInternalModel.size()-1,0,QModelIndex());
      emit q_ptr->dataChanged(idx, idx);
      emit q_ptr->layoutChanged();
      connect(newConf, &Call::changed, this, &CallModelPrivate::slotCallChanged);
      connect(newConf,&Call::videoStarted,[this,newConf](Video::Renderer* r) {
         emit q_ptr->rendererAdded(newConf, r);
      });
      connect(newConf,&Call::videoStopped,[this,newConf](Video::Renderer* r) {
         emit q_ptr->rendererRemoved(newConf, r);
      });
   }

   return newConf;
} //addConference

bool CallModel::createJoinOrMergeConferenceFromCall(Call* call1, Call* call2)
{
  if (!call1 || !call2) return false;
  qDebug() << "Joining call: " << call1 << " and " << call2;
  if (call1->type() == Call::Type::CONFERENCE)
      return addParticipant(call2, call1);
  else if (call2->type() == Call::Type::CONFERENCE)
      return addParticipant(call1, call2);
  else if (call1->type() == Call::Type::CONFERENCE && call2->type() == Call::Type::CONFERENCE)
      return mergeConferences(call1, call2);
  else
    Q_NOREPLY CallManager::instance().joinParticipant(call1->dringId(),call2->dringId());
  return true;
} //createConferenceFromCall

///Add a new participant to a conference
bool CallModel::addParticipant(Call* call2, Call* conference)
{
   if (conference->type() == Call::Type::CONFERENCE) {
      Q_NOREPLY CallManager::instance().addParticipant(call2->dringId(), conference->dringId());
      return true;
   }
   else {
      qDebug() << "This is not a conference";
      return false;
   }
} //addParticipant

///Remove a participant from a conference
bool CallModel::detachParticipant(Call* call)
{
   Q_NOREPLY CallManager::instance().detachParticipant(call->dringId());
   return true;
}

///Merge two conferences
bool CallModel::mergeConferences(Call* conf1, Call* conf2)
{
   Q_NOREPLY CallManager::instance().joinConference(conf1->dringId(),conf2->dringId());
   return true;
}

///Remove a conference from the model and the TreeView
void CallModelPrivate::removeConference(const QString &confId)
{
   if (m_shDringId.value(confId))
      qDebug() << "Ending conversation containing " << m_shDringId[confId]->m_lChildren.size() << " participants";
   removeConference(q_ptr->getCall(confId));
}

///Remove a conference using it's call object
void CallModelPrivate::removeConference(Call* call)
{
   const InternalStruct* internal = m_shInternalMapping[call];
   if (!internal) {
      qDebug() << "Cannot remove conference: call not found";
      return;
   }
   removeCall(call,true);

   // currently the daemon does not emit a  Call/Conference changed signal to indicate that the
   // conference is over so we change the conf state here (since this is called when we get the
   // "removeConference" signal from the daemon)
   call->d_ptr->changeCurrentState(Call::State::OVER);
}


/*****************************************************************************
 *                                                                           *
 *                                  Model                                    *
 *                                                                           *
 ****************************************************************************/

///This model doesn't support direct write, only the dragState hack
bool CallModel::setData( const QModelIndex& idx, const QVariant &value, int role)
{
   if (idx.isValid()) {
      if (role == static_cast<int>(Call::Role::DropState)) {
         Call* call = getCall(idx);
         if (call)
            call->setProperty("dropState",value.toInt());
         emit dataChanged(idx,idx);
      }
      else if (role == Qt::EditRole) {
         const QString number = value.toString();
         Call* call = getCall(idx);
         if (call && number != call->dialNumber()) {
            call->setDialNumber(number);
            emit dataChanged(idx,idx);
            return true;
         }
      }
      else if (role == static_cast<int>(Call::Role::DTMFAnimState)) {
         Call* call = getCall(idx);
         if (call) {
            call->setProperty("DTMFAnimState",value.toInt());
            emit dataChanged(idx,idx);
            return true;
         }
      }
      else if (role == static_cast<int>(Call::Role::DropPosition)) {
         Call* call = getCall(idx);
         if (call) {
            call->setProperty("dropPosition",value.toInt());
            emit dataChanged(idx,idx);
            return true;
         }
      }
   }
   return false;
}

///Get information relative to the index
QVariant CallModel::data( const QModelIndex& idx, int role) const
{
   if (!idx.isValid())
      return QVariant();
   Call* call = nullptr;
   if (!idx.parent().isValid() && d_ptr->m_lInternalModel.size() > idx.row() && d_ptr->m_lInternalModel[idx.row()])
      call = d_ptr->m_lInternalModel[idx.row()]->call_real;
   else if (idx.parent().isValid() && d_ptr->m_lInternalModel.size() > idx.parent().row()) {
      InternalStruct* intList = d_ptr->m_lInternalModel[idx.parent().row()];
      if (intList->conference == true && intList->m_lChildren.size() > idx.row() && intList->m_lChildren[idx.row()])
         call = intList->m_lChildren[idx.row()]->call_real;
   }
   return call?call->roleData((Call::Role)role):QVariant();
}

///Header data
QVariant CallModel::headerData(int section, Qt::Orientation orientation, int role) const
{
   Q_UNUSED(section)
   if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
      return QVariant(tr("Calls"));
   return QVariant();
}

///The number of conference and stand alone calls
int CallModel::rowCount( const QModelIndex& parentIdx ) const
{
   if (!parentIdx.isValid() || !parentIdx.internalPointer())
      return d_ptr->m_lInternalModel.size();

   const InternalStruct* modelItem = static_cast<InternalStruct*>(parentIdx.internalPointer());
   if (modelItem) {
      if (modelItem->call_real->type() == Call::Type::CONFERENCE && modelItem->m_lChildren.size() > 0)
         return modelItem->m_lChildren.size();
      else if (modelItem->call_real->type() == Call::Type::CONFERENCE)
         qWarning() << modelItem->call_real << "have"
            << modelItem->m_lChildren.size() << "and"
            << ((modelItem->call_real->type() == Call::Type::CONFERENCE)?"is":"is not") << "a conference";
   }
   return 0;
}

///Make everything selectable and drag-able
Qt::ItemFlags CallModel::flags( const QModelIndex& idx ) const
{
   if (!idx.isValid())
      return Qt::NoItemFlags;

   const InternalStruct* modelItem = static_cast<InternalStruct*>(idx.internalPointer());
   if (modelItem ) {
      const Call* c = modelItem->call_real;
      return Qt::ItemIsEnabled|Qt::ItemIsSelectable
         | Qt::ItemIsDragEnabled
         | ((c->type() != Call::Type::CONFERENCE)?(Qt::ItemIsDropEnabled):Qt::ItemIsEnabled)
         | ((c->lifeCycleState() == Call::LifeCycleState::CREATION)?Qt::ItemIsEditable:Qt::NoItemFlags);
   }
   return Qt::NoItemFlags;
}

///Return valid payload types
int CallModel::acceptedPayloadTypes()
{
   return DropPayloadType::CALL | DropPayloadType::HISTORY | DropPayloadType::CONTACT | DropPayloadType::NUMBER | DropPayloadType::TEXT;
}

///There is always 1 column
int CallModel::columnCount ( const QModelIndex& parentIdx) const
{
   const InternalStruct* modelItem = static_cast<InternalStruct*>(parentIdx.internalPointer());
   if (modelItem) {
      return (modelItem->call_real->type() == Call::Type::CONFERENCE)?1:0;
   }
   else if (parentIdx.isValid())
      return 0;
   return 1;
}

///Return the conference if 'index' is part of one
QModelIndex CallModel::parent( const QModelIndex& idx) const
{
   if (!idx.isValid())
      return QModelIndex();
   const InternalStruct* modelItem = (InternalStruct*)idx.internalPointer();
   if (modelItem && modelItem->m_pParent) {
      const int rowidx = d_ptr->m_lInternalModel.indexOf(modelItem->m_pParent);
      if (rowidx != -1) {
         return CallModel::index(rowidx,0,QModelIndex());
      }
   }
   return QModelIndex();
}

///Get the call index at row,column (active call only)
QModelIndex CallModel::index( int row, int column, const QModelIndex& parentIdx) const
{
   if (row >= 0 && !parentIdx.isValid() && d_ptr->m_lInternalModel.size() > row) {
      return createIndex(row,column,d_ptr->m_lInternalModel[row]);
   }
   else if (row >= 0 && parentIdx.isValid() && d_ptr->m_lInternalModel[parentIdx.row()]->m_lChildren.size() > row) {
      return createIndex(row,column,d_ptr->m_lInternalModel[parentIdx.row()]->m_lChildren[row]);
   }

   return QModelIndex();
}

QStringList CallModel::mimeTypes() const
{
   static QStringList mimes;
   if (!mimes.size()) {
      mimes << RingMimes::PLAIN_TEXT << RingMimes::PHONENUMBER << RingMimes::CALLID << "text/html";
   }
   return mimes;
}

QMimeData* CallModel::mimeData(const QModelIndexList& indexes) const
{

   if (indexes.size() == 1) {
      const QModelIndex idx = indexes.first();
      if (idx.isValid()) {
         Call* call = getCall(idx);
         if (call) {
            return call->mimePayload();
         }
      }
   }

   //TODO handle/hardcode something for multiple selections / composite MIME

   return new QMimeData();
}

bool CallModelPrivate::isPartOf(const QModelIndex& confIdx, Call* call)
{
   if (!confIdx.isValid() || !call) return false;

   for (int i=0;i<confIdx.model()->rowCount(confIdx);i++) { //TODO use model one directly
      Call* c = q_ptr->getCall(confIdx);
      if (c && c->dringId() == call->dringId()) {
         return true;
      }
   }
   return false;
}

Call* CallModel::fromMime( const QByteArray& fromMime) const
{
   return getCall(QString(fromMime));
}

bool CallModel::dropMimeData(const QMimeData* mimedata, Qt::DropAction action, int row, int column, const QModelIndex& parentIdx )
{
   Q_UNUSED(action)
   const QModelIndex targetIdx = index( row,column,parentIdx );
   if (mimedata->hasFormat(RingMimes::CALLID)) {
      const QByteArray encodedCallId = mimedata->data( RingMimes::CALLID    );
      Call* call                     = fromMime ( encodedCallId        );
      Call* target                   = getCall  ( targetIdx            );
      Call* targetParent             = getCall  ( targetIdx.parent()   );

      //Call or conference dropped on itself -> cannot transfer or merge, so exit now
      if (target == call) {
         qDebug() << "Call/Conf dropped on itself (doing nothing)";
         return false;
      }
      else if (!call) {
         qDebug() << "Call not found";
         return false;
      }

      switch (mimedata->property("dropAction").toInt()) {
         case Call::DropAction::Conference:
            //Call or conference dropped on part of itself -> cannot merge conference with itself
            if (d_ptr->isPartOf(targetIdx,call) || d_ptr->isPartOf(targetIdx.parent(),call) || (call && targetParent && targetParent == call)) {
               qDebug() << "Call/Conf dropped on its own conference (doing nothing)";
               return false;
            }
            //Conference dropped on a conference -> merge both conferences
            else if (call && target && call->type() == Call::Type::CONFERENCE && target->type() == Call::Type::CONFERENCE) {
               qDebug() << "Merge conferences" << call << "and" << target;
               mergeConferences(call,target);
               return true;
            }
            //Conference dropped on a call part of a conference -> merge both conferences
            else if (call && call->type() == Call::Type::CONFERENCE && targetParent) {
               qDebug() << "Merge conferences" << call << "and" << targetParent;
               mergeConferences(call,getCall(targetIdx.parent()));
               return true;
            }
            //Drop a call on a conference -> add it to the conference
            else if (target && (targetIdx.parent().isValid() || target->type() == Call::Type::CONFERENCE)) {
               Call* conf = target->type() == Call::Type::CONFERENCE?target:qvariant_cast<Call*>(targetIdx.parent().data(static_cast<int>(Call::Role::Object)));
               if (conf) {
                  qDebug() << "Adding call " << call << "to conference" << conf;
                  addParticipant(call,conf);
               return true;
               }
            }
            //Conference dropped on a call
            else if (target && call && rowCount(getIndex(call))) {
               qDebug() << "Conference dropped on a call: adding call to conference";
               addParticipant(target,call);
               return true;
            }
            //Call dropped on a call
            else if (call && target && !targetIdx.parent().isValid()) {
               qDebug() << "Call dropped on a call: creating a conference";
               createJoinOrMergeConferenceFromCall(call,target);
               return true;
            }
            break;
         case Call::DropAction::Transfer:
            qDebug() << "Performing an attended transfer";
            attendedTransfer(call,target);
            break;
         default:
            break;
      }
   }
   else if (mimedata->hasFormat(RingMimes::PHONENUMBER)) {
      const QByteArray encodedContactMethod = mimedata->data( RingMimes::PHONENUMBER );
      Call* target = getCall(targetIdx);
      qDebug() << "Phone number" << encodedContactMethod << "on call" << target;
      Call* newCall = dialingCall(QString(),target->account());
      ContactMethod* nb = PhoneDirectoryModel::instance().fromHash(encodedContactMethod);
      newCall->setDialNumber(nb);
      newCall->performAction(Call::Action::ACCEPT);
      createJoinOrMergeConferenceFromCall(newCall,target);
   }
   else if (mimedata->hasFormat(RingMimes::CONTACT)) {
      const QByteArray encodedPerson = mimedata->data(RingMimes::CONTACT);
      Call* target = getCall(targetIdx);
      qDebug() << "Contact" << encodedPerson << "on call" << target;
      try {
         const ContactMethod* number = GlobalInstances::contactMethodSelector().number(
         PersonModel::instance().getPersonByUid(encodedPerson));
         if (!number->uri().isEmpty()) {
            Call* newCall = dialingCall();
            newCall->setDialNumber(number);
            newCall->performAction(Call::Action::ACCEPT);
            createJoinOrMergeConferenceFromCall(newCall,target);
         }
         else {
            qDebug() << "Person not found";
         }
      }
      catch (...) {
         qDebug() << "There is nothing to handle contact";
      }
   }
   return false;
}


/*****************************************************************************
 *                                                                           *
 *                                   Slots                                   *
 *                                                                           *
 ****************************************************************************/

///When a call state change
void CallModelPrivate::slotCallStateChanged(const QString& callID, const QString& stateName, int code)
{

   //This code is part of the CallModel interface too
   qDebug() << "Call State Changed for call  " << callID << " . New state : " << stateName;
   InternalStruct* internal = m_shDringId[callID];
   Call* call = nullptr;

   if (!internal && stateName == DRing::Call::StateEvent::CONNECTING)
       return;

   if(!internal) {
      qDebug() << "Call not found" << callID << "new state" << stateName;
      addExistingCall(callID, stateName);
      return;
   } else {
      call = internal->call_real;

      QString sn = stateName;

      //Ring account handle "busy" differently from other types
      if (call->account()
       && call->account()->protocol() == Account::Protocol::RING
       && sn == CallPrivate::StateChange::HUNG_UP
       && code == ECONNREFUSED
      )
         sn = CallPrivate::StateChange::BUSY;

      qDebug() << "Call found" << call << call->state();
      const Call::LifeCycleState oldLifeCycleState = call->lifeCycleState();
      const Call::State          oldState          = call->state();
      call->d_ptr->stateChanged(sn);
      //Remove call when they end normally, keep errors and failure one
      if ((sn == CallPrivate::StateChange::HUNG_UP)
         || ((oldState == Call::State::OVER) && (call->state() == Call::State::OVER))
         || (oldLifeCycleState != Call::LifeCycleState::FINISHED && call->state() == Call::State::OVER)) {
         removeCall(call);
      }
   }

   //Add to history
   if (call->lifeCycleState() == Call::LifeCycleState::FINISHED) {
      if (!call->collection()) {
         foreach (CollectionInterface* backend, CategorizedHistoryModel::instance().collections(CollectionInterface::SupportedFeatures::ADD)) {
            if (backend->editor<Call>()->addNew(call))
               call->setCollection(backend);
         }
      }
   }

} //slotCallStateChanged

///When a new call is incoming
void CallModelPrivate::slotIncomingCall(const QString& accountID, const QString& callID)
{
   Q_UNUSED(accountID)
   qDebug() << "Signal : Incoming Call ! ID = " << callID;
   Call* c = addIncomingCall(callID);

   if (c)
      emit q_ptr->incomingCall(c);
}

///When a new conference is incoming
void CallModelPrivate::slotIncomingConference(const QString& confID)
{
   if (!q_ptr->getCall(confID)) {
      Call* conf = addConference(confID);
      qDebug() << "Adding conference" << conf << confID;
      emit q_ptr->conferenceCreated(conf);
   }
}

///When a conference change
void CallModelPrivate::slotChangingConference(const QString &confID, const QString& state)
{
   InternalStruct* confInt = m_shDringId[confID];
   if (!confInt) {
      qDebug() << "Error: conference not found";
      return;
   }
   Call* conf = confInt->call_real;
   qDebug() << "Changing conference state" << conf << confID;
   if (conf && conf) { //Prevent a race condition between call and conference
      if (!q_ptr->getIndex(conf).isValid()) {
         qWarning() << "The conference item does not exist";
         return;
      }

      conf->d_ptr->stateChanged(state);
      CallManagerInterface& callManager = CallManager::instance();
      const QStringList participants = callManager.getParticipantList(confID);

      qDebug() << "The conf has" << confInt->m_lChildren.size() << "calls, daemon has" <<participants.size();

      //First remove old participants, add them back to the top level list
      foreach(InternalStruct* child,confInt->m_lChildren) {
         if (participants.indexOf(child->call_real->dringId()) == -1 && child->call_real->lifeCycleState() != Call::LifeCycleState::FINISHED) {
            qDebug() << "Remove" << child->call_real << "from" << conf;
            child->m_pParent = nullptr;
            q_ptr->beginInsertRows(QModelIndex(),m_lInternalModel.size(),m_lInternalModel.size());
            m_lInternalModel << child;
            q_ptr->endInsertRows();
         }
      }

      auto confIdx = q_ptr->index(m_lInternalModel.indexOf(confInt),0,QModelIndex());
      q_ptr->beginRemoveRows(confIdx,0,confInt->m_lChildren.size());
      confInt->m_lChildren.clear();
      q_ptr->endRemoveRows();

      foreach(const QString& callId,participants) {
         InternalStruct* callInt = m_shDringId[callId];
         if (callInt) {
            if (callInt->m_pParent && callInt->m_pParent != confInt)
               callInt->m_pParent->m_lChildren.removeAll(callInt);
            removeInternal(callInt);
            callInt->m_pParent = confInt;
            q_ptr->beginInsertRows(confIdx, confInt->m_lChildren.size(), confInt->m_lChildren.size());
            confInt->m_lChildren << callInt;
            q_ptr->endInsertRows();
         }
         else {
            qDebug() << "Participants not found";
         }
      }

      //The daemon often fail to emit the right signal, cleanup manually
      foreach(InternalStruct* topLevel, m_lInternalModel) {
         if (topLevel->call_real->type() == Call::Type::CONFERENCE && !topLevel->m_lChildren.size()) {
            removeConference(topLevel->call_real);
         }
      }

      //Test if there is no inconsistencies between the daemon and the client
      const QStringList deamonCallList = getCallList();
      foreach(const QString& callId, deamonCallList) {
         const QMap<QString,QString> callDetails = callManager.getCallDetails(callId);
         InternalStruct* callInt = m_shDringId[callId];
         if (callInt) {
            const QString confId = callDetails[DRing::Call::Details::CONF_ID];
            if (callInt->m_pParent) {
               if (!confId.isEmpty()  && callInt->m_pParent->call_real->dringId() != confId) {
                  qWarning() << "Conference parent mismatch";
               }
               else if (confId.isEmpty() ){
                  qWarning() << "Call:" << callId << "should not be part of a conference";
                  callInt->m_pParent = nullptr;
               }
            }
            else if (!confId.isEmpty()) {
               qWarning() << "Found an orphan call";
               InternalStruct* confInt2 = m_shDringId[confId];
               if (confInt2 && confInt2->call_real->type() == Call::Type::CONFERENCE
                && (callInt->call_real->type() != Call::Type::CONFERENCE)) {
                  removeInternal(callInt);
                  if (confInt2->m_lChildren.indexOf(callInt) == -1) {
                     auto confIdx2 = q_ptr->index(m_lInternalModel.indexOf(confInt2), 0, QModelIndex());
                     q_ptr->beginInsertRows(confIdx2, confInt2->m_lChildren.size(), confInt2->m_lChildren.size());
                     confInt2->m_lChildren << callInt;
                     q_ptr->endInsertRows();
                  }
               }
            }
            callInt->call_real->setProperty("dropState",0);
         }
         else
            qWarning() << "Conference: Call from call list not found in internal list";
      }
      //TODO force reload all conferences too

      emit q_ptr->layoutChanged();
      emit q_ptr->dataChanged(confIdx, confIdx);
      emit q_ptr->conferenceChanged(conf);
   }
   else {
      qDebug() << "Trying to affect a conference that does not exist (anymore)";
   }
} //slotChangingConference

///When a conference is removed
void CallModelPrivate::slotConferenceRemoved(const QString &confId)
{
   Call* conf = q_ptr->getCall(confId);
   removeConference(confId);
   emit q_ptr->layoutChanged();
   emit q_ptr->conferenceRemoved(conf);
}

///Make the call aware it has a recording
void CallModelPrivate::slotNewRecordingAvail( const QString& callId, const QString& filePath)
{
   Call* c = q_ptr->getCall(callId);
   if (c)
      c->d_ptr->setRecordingPath(filePath);
}

void CallModelPrivate::slotStateChanged(Call::State newState, Call::State previousState)
{
   Q_UNUSED(newState)

   Call* call = qobject_cast<Call*>(sender());
   if (call)
      emit q_ptr->callStateChanged(call, previousState);
}

///Update model if the data change
void CallModelPrivate::slotCallChanged()
{
    auto call = qobject_cast<Call*>(sender());
    if (!call) return;

    switch(call->state()) {
        //Transfer is "local" state, it doesn't require the daemon, so it need to be
        //handled "manually" instead of relying on the backend signals
        case Call::State::TRANSFERRED:
            emit q_ptr->callStateChanged(call, Call::State::TRANSFERRED);
            break;
        //Same goes for some errors
        case Call::State::COUNT__:
        case Call::State::ERROR:
            removeCall(call);
            break;
        //Over can be caused by local events
        case Call::State::ABORTED:
        case Call::State::OVER:
            removeCall(call);
            break;
        //Let the daemon handle the others
        case Call::State::INCOMING:
        case Call::State::RINGING:
        case Call::State::INITIALIZATION:
        case Call::State::CONNECTED:
        case Call::State::CURRENT:
        case Call::State::DIALING:
        case Call::State::NEW:
        case Call::State::HOLD:
        case Call::State::FAILURE:
        case Call::State::BUSY:
        case Call::State::TRANSF_HOLD:
        case Call::State::CONFERENCE:
        case Call::State::CONFERENCE_HOLD:
            break;
    };

    const QModelIndex idx = q_ptr->getIndex(call);
    emit q_ptr->dataChanged(idx,idx);
}

///Add call slot
void CallModelPrivate::slotAddPrivateCall(Call* call) {
   if (m_shInternalMapping[call])
      return;
   addCall2(call,nullptr);
}

///Notice views that a dtmf have been played
void CallModelPrivate::slotDTMFPlayed( const QString& str )
{
   Call* call = qobject_cast<Call*>(QObject::sender());
   if (str.size()==1) {
      int idx = 0;
      char s = str.toLower().toLatin1()[0];
      if (s >= '1' && s <= '9'     ) idx = s - '1'     ;
      else if (s >= 'a' && s <= 'v') idx = (s - 'a')/3 ;
      else if (s >= 'w' && s <= 'z') idx = 8           ;
      else if (s == '0'            ) idx = 10          ;
      else if (s == '*'            ) idx = 9           ;
      else if (s == '#'            ) idx = 11          ;
      else                           idx = -1          ;
      call->setProperty("latestDtmfIdx",idx);
   }
   const QModelIndex& idx = q_ptr->getIndex(call);
   q_ptr->setData(idx,50, static_cast<int>(Call::Role::DTMFAnimState));
}

///Called when a recording state change
void CallModelPrivate::slotRecordStateChanged (const QString& callId, bool state)
{
   if (auto call = q_ptr->getCall(callId)) {

      call->d_ptr->m_mIsRecording[ Media::Media::Type::AUDIO ].setAt( Media::Media::Direction::IN  , state);
      call->d_ptr->m_mIsRecording[ Media::Media::Type::AUDIO ].setAt( Media::Media::Direction::OUT , state);
      call->d_ptr->m_mIsRecording[ Media::Media::Type::VIDEO ].setAt( Media::Media::Direction::IN  , state);
      call->d_ptr->m_mIsRecording[ Media::Media::Type::VIDEO ].setAt( Media::Media::Direction::OUT , state);

      emit call->changed();
   }
}

void CallModelPrivate::slotAudioMuted( const QString& callId, bool state)
{
   Call* call = q_ptr->getCall(callId);

   if (call) {
      auto a = call->firstMedia<Media::Audio>(Media::Media::Direction::OUT);
      if (state)
         a->Media::d_ptr->muteConfirmed();
      else
         a->Media::d_ptr->unmuteConfirmed();
   }
}

void CallModelPrivate::slotVideoMutex( const QString& callId, bool state)
{
   Call* call = q_ptr->getCall(callId);

   if (call) {
      auto v = call->firstMedia<Media::Video>(Media::Media::Direction::OUT);
      if (state)
         v->Media::d_ptr->muteConfirmed();
      else
         v->Media::d_ptr->unmuteConfirmed();
   }
}

void CallModelPrivate::slotPeerHold( const QString& callId, bool state)
{
   Call* call = q_ptr->getCall(callId);

   if (call)
      call->d_ptr->peerHoldChanged(state);
}

#include <callmodel.moc>