diff --git a/src/account.cpp b/src/account.cpp index a1411a64c7958e711b2aedb79902dd199112a48b..02e94c6f8d83dc905a96fef271338c8f16d941b4 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -220,7 +220,7 @@ const QString Account::accountDetail(const QString& param) const } else if (m_hAccountDetails.count() > 0) { if (param == Account::MapField::ENABLED) //If an account is invalid, at least does not try to register it - return REGISTRATION_ENABLED_FALSE; + return Account::RegistrationEnabled::NO; if (param == Account::MapField::Registration::STATUS) //If an account is new, then it is unregistered return Account::State::UNREGISTERED; if (protocol() != Account::Protocol::IAX) //IAX accounts lack some fields, be quiet diff --git a/src/account.h b/src/account.h index 914e4eb3de11a0263fcd3528a67176b6ea3c27ab..81f83d12c4154e4e61cf87704b7dae437ab585d4 100644 --- a/src/account.h +++ b/src/account.h @@ -153,6 +153,12 @@ class LIB_EXPORT Account : public QObject { constexpr static const char* REQUEST_TIMEOUT = "Request Timeout" ; }; + class RegistrationEnabled { + public: + constexpr static const char* YES = "true"; + constexpr static const char* NO = "false"; + }; + ~Account(); //Constructors static Account* buildExistingAccountFromId(const QString& _accountId); diff --git a/src/call.cpp b/src/call.cpp index de43fc5efc13bff38dfdc930f3afc65ec33ad0f4..7ae83a3129880f7211b4b0c0ff6dcf57f51d5895 100644 --- a/src/call.cpp +++ b/src/call.cpp @@ -379,14 +379,23 @@ Call* Call::buildHistoryCall(const QMap<QString,QString>& hc) accId = QString(Account::ProtocolName::IP2IP); } + //Try to assiciate a contact now, the real contact object is probably not + //loaded yet, but we can get a placeholder for now +// const QString& contactUsed = hc[ Call::HistoryMapFields::CONTACT_USED ]; //TODO + const QString& contactUid = hc[ Call::HistoryMapFields::CONTACT_UID ]; + + Contact* ct = nullptr; + if (!hc[ Call::HistoryMapFields::CONTACT_UID].isEmpty()) + ct = ContactModel::instance()->getPlaceHolder(contactUid.toAscii()); + Account* acc = AccountListModel::instance()->getAccountById(accId); - PhoneNumber* nb = PhoneDirectoryModel::instance()->getNumber(number,acc); + PhoneNumber* nb = PhoneDirectoryModel::instance()->getNumber(number,ct,acc); + Call* call = new Call(Call::State::OVER, callId, (name == "empty")?QString():name, nb, acc ); call->m_pStopTimeStamp = stopTimeStamp ; call->m_History = true; call->setStartTimeStamp(startTimeStamp); - call->m_HistoryState = historyStateFromType(type); call->m_Account = AccountListModel::instance()->getAccountById(accId); @@ -409,7 +418,7 @@ Call* Call::buildHistoryCall(const QMap<QString,QString>& hc) call->m_Direction = Call::Direction::INCOMING ; else if (call->m_HistoryState == Call::LegacyHistoryState::OUTGOING) call->m_Direction = Call::Direction::OUTGOING ; - else //BUG Pick one, even if it is the wrong one + else //Getting there is a bug. Pick one, even if it is the wrong one call->m_Direction = Call::Direction::OUTGOING ; if (missed) call->m_HistoryState = Call::LegacyHistoryState::MISSED; @@ -419,7 +428,12 @@ Call* Call::buildHistoryCall(const QMap<QString,QString>& hc) if (call->peerPhoneNumber()) { call->peerPhoneNumber()->addCall(call); + + //Reload the glow and number colors connect(call->peerPhoneNumber(),SIGNAL(presentChanged(bool)),call,SLOT(updated())); + + //Change the display name and picture + connect(call->peerPhoneNumber(),SIGNAL(rebased(PhoneNumber*)),call,SLOT(updated())); } return call; diff --git a/src/call.h b/src/call.h index 8903127095d32b07c26df85771a136a539f344fb..3a981fba8ac80be73d411014c9f48c92494cf1c8 100644 --- a/src/call.h +++ b/src/call.h @@ -212,6 +212,9 @@ public: constexpr static const char* TIMESTAMP_STOP = "timestamp_stop" ; constexpr static const char* MISSED = "missed" ; constexpr static const char* DIRECTION = "direction" ; + constexpr static const char* CONTACT_USED = "contact_used" ; + constexpr static const char* CONTACT_UID = "contact_uid" ; + constexpr static const char* NUMBER_TYPE = "number_type" ; }; ///"getCallDetails()" fields diff --git a/src/callmodel.cpp b/src/callmodel.cpp index 244770e8248df4939b03426cb527676b5073cd0c..f190c1120925cae775d90fc43c646b5480fc22e3 100644 --- a/src/callmodel.cpp +++ b/src/callmodel.cpp @@ -285,6 +285,7 @@ Call* CallModel::addCall(Call* call, Call* parentCall) 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; @@ -297,13 +298,15 @@ Call* CallModel::addCall(Call* call, Call* parentCall) } m_sPrivateCallList_callId[ call->id() ] = aNewStruct; - if (call->lifeCycleState() != Call::LifeCycleState::FINISHED) + //If the call is already finished, there is no point to track it here + if (call->lifeCycleState() != Call::LifeCycleState::FINISHED) { emit callAdded(call,parentCall); - const QModelIndex idx = index(m_lInternalModel.size()-1,0,QModelIndex()); - emit dataChanged(idx, idx); - connect(call,SIGNAL(changed(Call*)),this,SLOT(slotCallChanged(Call*))); - connect(call,SIGNAL(dtmfPlayed(QString)),this,SLOT(slotDTMFPlayed(QString))); - emit layoutChanged(); + const QModelIndex idx = index(m_lInternalModel.size()-1,0,QModelIndex()); + emit dataChanged(idx, idx); + connect(call,SIGNAL(changed(Call*)),this,SLOT(slotCallChanged(Call*))); + connect(call,SIGNAL(dtmfPlayed(QString)),this,SLOT(slotDTMFPlayed(QString))); + emit layoutChanged(); + } return call; } //addCall diff --git a/src/contact.cpp b/src/contact.cpp index dc0d922c910e9b58cb9e40c3714b96e87401238d..c651ed04f746f045c3e8fee569d0f306dc130847 100644 --- a/src/contact.cpp +++ b/src/contact.cpp @@ -46,8 +46,55 @@ public: Contact::PhoneNumbers m_Numbers ; bool m_Active ; AbstractContactBackend* m_pBackend ; + bool m_isPlaceHolder ; + + //Helper code to help handle multiple parents + QList<Contact*> m_lParents; + + //As a single D-Pointer can have multiple parent (when merged), all emit need + //to use a proxy to make sure everybody is notified + void presenceChanged( PhoneNumber* ); + void statusChanged ( bool ); + void changed ( ); + void phoneNumberCountChanged(int,int); + void phoneNumberCountAboutToChange(int,int); }; +void ContactPrivate::changed() +{ + foreach (Contact* c,m_lParents) { + emit c->changed(); + } +} + +void ContactPrivate::presenceChanged( PhoneNumber* n ) +{ + foreach (Contact* c,m_lParents) { + emit c->presenceChanged(n); + } +} + +void ContactPrivate::statusChanged ( bool s ) +{ + foreach (Contact* c,m_lParents) { + emit c->statusChanged(s); + } +} + +void ContactPrivate::phoneNumberCountChanged(int n,int o) +{ + foreach (Contact* c,m_lParents) { + emit c->phoneNumberCountChanged(n,o); + } +} + +void ContactPrivate::phoneNumberCountAboutToChange(int n,int o) +{ + foreach (Contact* c,m_lParents) { + emit c->phoneNumberCountAboutToChange(n,o); + } +} + ContactPrivate::ContactPrivate(Contact* contact, AbstractContactBackend* parent):m_pPhoto(nullptr), m_Numbers(contact),m_DisplayPhoto(nullptr),m_Active(true), m_pBackend(parent?parent:TransitionalContactBackend::instance()) @@ -72,6 +119,8 @@ Contact* Contact::PhoneNumbers::contact() const Contact::Contact(AbstractContactBackend* parent):QObject(parent?parent:TransitionalContactBackend::instance()), d(new ContactPrivate(this,parent)) { + d->m_isPlaceHolder = false; + d->m_lParents << this; } ///Destructor @@ -153,19 +202,19 @@ void Contact::setPhoneNumbers(PhoneNumbers numbers) disconnect(n,SIGNAL(presentChanged(bool)),this,SLOT(slotPresenceChanged())); d->m_Numbers = numbers; if (newCount < oldCount) //Rows need to be removed from models first - emit phoneNumberCountAboutToChange(newCount,oldCount); + d->phoneNumberCountAboutToChange(newCount,oldCount); foreach(PhoneNumber* n, d->m_Numbers) connect(n,SIGNAL(presentChanged(bool)),this,SLOT(slotPresenceChanged())); if (newCount > oldCount) //Need to be updated after the data to prevent invalid memory access - emit phoneNumberCountChanged(newCount,oldCount); - emit changed(); + d->phoneNumberCountChanged(newCount,oldCount); + d->changed(); } ///Set the nickname void Contact::setNickName(const QString& name) { d->m_NickName = name; - emit changed(); + d->changed(); } ///Set the first name @@ -173,7 +222,7 @@ void Contact::setFirstName(const QString& name) { d->m_FirstName = name; setObjectName(formattedName()); - emit changed(); + d->changed(); } ///Set the family name @@ -181,64 +230,64 @@ void Contact::setFamilyName(const QString& name) { d->m_SecondName = name; setObjectName(formattedName()); - emit changed(); + d->changed(); } ///Set the Photo/Avatar void Contact::setPhoto(QPixmap* photo) { d->m_pPhoto = photo; - emit changed(); + d->changed(); } ///Set the formatted name (display name) void Contact::setFormattedName(const QString& name) { d->m_FormattedName = name; - emit changed(); + d->changed(); } ///Set the organisation / business void Contact::setOrganization(const QString& name) { d->m_Organization = name; - emit changed(); + d->changed(); } ///Set the default email void Contact::setPreferredEmail(const QString& name) { d->m_PreferredEmail = name; - emit changed(); + d->changed(); } ///Set UID void Contact::setUid(const QByteArray& id) { d->m_Uid = id; - emit changed(); + d->changed(); } ///Set Group void Contact::setGroup(const QString& name) { d->m_Group = name; - emit changed(); + d->changed(); } ///Set department void Contact::setDepartment(const QString& name) { d->m_Department = name; - emit changed(); + d->changed(); } ///If the contact have been deleted or not yet fully created void Contact::setActive( bool active) { d->m_Active = active; - emit statusChanged(d->m_Active); - emit changed(); + d->statusChanged(d->m_Active); + d->changed(); } ///Return if one of the PhoneNumber is present @@ -295,7 +344,7 @@ time_t Contact::PhoneNumbers::lastUsedTimeStamp() const ///Callback when one of the phone number presence change void Contact::slotPresenceChanged() { - emit changed(); + d->changed(); } ///Save the contact @@ -322,3 +371,49 @@ bool Contact::addPhoneNumber(PhoneNumber* n) { return d->m_pBackend->addPhoneNumber(this,n); } + +///Create a placeholder contact, it will eventually be replaced when the real one is loaded +ContactPlaceHolder::ContactPlaceHolder(const QByteArray& uid) +{ + setUid(uid); + d->m_isPlaceHolder = true; +} + + +bool ContactPlaceHolder::merge(Contact* contact) +{ + ContactPrivate* currentD = d; + replaceDPointer(contact); + currentD->m_lParents.removeAll(this); + if (!currentD->m_lParents.size()) + delete currentD; + return true; +} + +void Contact::replaceDPointer(Contact* c) +{ + this->d = c->d; + d->m_lParents << this; + emit changed(); + emit rebased(c); +} + +bool Contact::operator==(Contact* other) +{ + return this->d == other->d; +} + +bool Contact::operator==(const Contact* other) const +{ + return this->d == other->d; +} + +bool Contact::operator==(Contact& other) +{ + return this->d == other.d; +} + +bool Contact::operator==(const Contact& other) const +{ + return this->d == other.d; +} diff --git a/src/contact.h b/src/contact.h index 3e0fc08243ba6b915ba27b906069db2081750fc6..a42a982e45bef4295554be0348ed959677d4189f 100644 --- a/src/contact.h +++ b/src/contact.h @@ -22,7 +22,6 @@ #include <QtCore/QObject> #include <QtCore/QVariant> -#include <QtCore/QSharedPointer> #include <time.h> //Qt @@ -84,9 +83,10 @@ public: Q_INVOKABLE bool remove() ; Q_INVOKABLE bool addPhoneNumber(PhoneNumber* n); -private: +protected: //The D-Pointer can be shared if a PlaceHolderContact is merged with a real one - QSharedPointer<ContactPrivate> d; + ContactPrivate* d; + void replaceDPointer(Contact* other); public: //Constructors & Destructors @@ -126,6 +126,12 @@ public: void setPhoto ( QPixmap* photo ); void setActive ( bool active ); + //Operator + bool operator==(Contact* other); + bool operator==(const Contact* other) const; + bool operator==(Contact& other); + bool operator==(const Contact& other) const; + private Q_SLOTS: void slotPresenceChanged(); @@ -135,12 +141,21 @@ Q_SIGNALS: void changed ( ); void phoneNumberCountChanged(int,int); void phoneNumberCountAboutToChange(int,int); + void rebased ( Contact* ); protected: //Presence secret methods void updatePresenceInformations(const QString& uri, bool status, const QString& message); }; +class LIB_EXPORT ContactPlaceHolder : public Contact { + Q_OBJECT +public: + ContactPlaceHolder(const QByteArray& uid); + bool merge(Contact* contact); +}; + + Q_DECLARE_METATYPE(Contact*) #endif diff --git a/src/contactmodel.cpp b/src/contactmodel.cpp index e4c0da7becc9b4290a3d5b7f4315839d5a7ef019..6d7923b898a0bd16f26589f42e5fb42424cc48c8 100644 --- a/src/contactmodel.cpp +++ b/src/contactmodel.cpp @@ -163,6 +163,25 @@ Contact* ContactModel::getContactByUid(const QByteArray& uid) return m_hContactsByUid[uid]; } +/** + * Create a temporary contact or return the existing one for an UID + * This temporary contact should eventually be merged into the real one + */ +Contact* ContactModel::getPlaceHolder(const QByteArray& uid ) +{ + Contact* ct = m_hContactsByUid[uid]; + + //Do not create a placeholder if the real deal exist + if (ct) { + return ct; + } + + ContactPlaceHolder* ct2 = new ContactPlaceHolder(uid); + + m_hPlaceholders[ct2->uid()] = ct2; + return ct2; +} + ///Return if there is backends bool ContactModel::hasBackends() const { @@ -208,6 +227,12 @@ bool ContactModel::addContact(Contact* c) beginInsertRows(QModelIndex(),m_lContacts.size()-1,m_lContacts.size()); m_lContacts << c; m_hContactsByUid[c->uid()] = c; + + //Deprecate the placeholder + if (m_hPlaceholders.contains(c->uid())) { + m_hPlaceholders[c->uid()]->merge(c); + m_hPlaceholders[c->uid()] = nullptr; + } endInsertRows(); emit layoutChanged(); emit newContactAdded(c); diff --git a/src/contactmodel.h b/src/contactmodel.h index e708f41990c482ac5afca26b50709d3d5482e2d5..01c8c6f8765a9090bcb131a03e6be3c54de297ba 100644 --- a/src/contactmodel.h +++ b/src/contactmodel.h @@ -70,6 +70,7 @@ public: //Getters Contact* getContactByUid ( const QByteArray& uid ); + Contact* getPlaceHolder(const QByteArray& uid ); bool hasBackends () const; const ContactList contacts() const; virtual const QVector<AbstractContactBackend*> enabledBackends() const; @@ -96,6 +97,7 @@ private: static ContactModel* m_spInstance; QVector<AbstractContactBackend*> m_lBackends; CommonItemBackendModel* m_pBackendModel; + QHash<QByteArray,ContactPlaceHolder*> m_hPlaceholders; //Indexes QHash<QByteArray,Contact*> m_hContactsByUid; @@ -114,4 +116,5 @@ Q_SIGNALS: void newBackendAdded(AbstractContactBackend* backend); }; + #endif diff --git a/src/phonedirectorymodel.cpp b/src/phonedirectorymodel.cpp index db352611d1bb279ccfbfbb8bda5d7f405da4a616..b78118afb930e2cbe42d4090b173e59a0b067de7 100644 --- a/src/phonedirectorymodel.cpp +++ b/src/phonedirectorymodel.cpp @@ -409,6 +409,9 @@ PhoneNumber* PhoneDirectoryModel::getNumber(const QString& uri, Account* account PhoneNumber* PhoneDirectoryModel::getNumber(const QString& uri, Contact* contact, Account* account, const QString& type) { + if (!contact) + return getNumber(uri,account,type); + const QString strippedUri = PhoneNumber::stripUri(uri); //See if the number is already loaded @@ -426,7 +429,9 @@ PhoneNumber* PhoneDirectoryModel::getNumber(const QString& uri, Contact* contact if ((!number->hasType()) && (!type.isEmpty())) { number->setCategory(NumberCategoryModel::instance()->getCategory(type)); } - if (((!contact) || number->contact() == contact) && ((!account) || number->account() == account)) + //Use the operator== check to avoid issues with placeholders + if (((!contact) || (number->contact() && (*number->contact()) == (*contact))) + && ((!account) || number->account() == account)) return number; } } diff --git a/src/phonenumber.cpp b/src/phonenumber.cpp index 1bd1701cf8317236d91e04cd8bd8572787543155..1e6f703f499ff41d6bd15f441d041dfb604baf14 100644 --- a/src/phonenumber.cpp +++ b/src/phonenumber.cpp @@ -162,6 +162,7 @@ void PhoneNumber::setContact(Contact* contact) PhoneDirectoryModel::instance()->indexNumber(this,d->m_hNames.keys()+QStringList(contact->formattedName())); d->m_PrimaryName_cache = contact->formattedName(); emit primaryNameChanged(d->m_PrimaryName_cache); + connect(contact,SIGNAL(rebased(Contact*)),this,SLOT(contactRebased(Contact*))); } emit changed(); } @@ -443,6 +444,20 @@ void PhoneNumber::accountDestroyed(QObject* o) d->m_pAccount = nullptr; } +/** + * When the PhoneNumber contact is merged with another one, the phone number + * data might be replaced, like the preferred name. + */ +void PhoneNumber::contactRebased(Contact* other) +{ + d->m_PrimaryName_cache = other->formattedName(); + emit primaryNameChanged(d->m_PrimaryName_cache); + emit changed(); + + //It is a "partial" rebase, so the PhoneNumber data stay the same + emit rebased(this); +} + /** * Merge two phone number to share the same data. This avoid having to change * pointers all over the place. The PhoneNumber objects remain intact, the diff --git a/src/phonenumber.h b/src/phonenumber.h index 0367861296db8cc98538063a95e6451110575157..d8824b7bc0d15aed37916d3b6b7d05e77baff223 100644 --- a/src/phonenumber.h +++ b/src/phonenumber.h @@ -183,6 +183,7 @@ private: private Q_SLOTS: void accountDestroyed(QObject* o); + void contactRebased(Contact* other); Q_SIGNALS: void callAdded(Call* call); @@ -191,6 +192,7 @@ Q_SIGNALS: void presenceMessageChanged(const QString&); void trackedChanged(bool); void primaryNameChanged(const QString& name); + void rebased(PhoneNumber* other); }; Q_DECLARE_METATYPE(PhoneNumber*) diff --git a/src/sflphone_const.h b/src/sflphone_const.h index ed13598eb89b72f7f18ae0536c3e655b4607d853..3312859d02ecfefeffba6d6f35dfc3a588e9fec8 100644 --- a/src/sflphone_const.h +++ b/src/sflphone_const.h @@ -22,19 +22,6 @@ #ifndef SFLPHONE_CONST_H #define SFLPHONE_CONST_H -#include <QtCore/QString> - -#define ACTION_LABEL_CALL i18n("New call") -#define ACTION_LABEL_PLACE_CALL i18n("Place call") -#define ACTION_LABEL_HANG_UP i18n("Hang up") -#define ACTION_LABEL_HOLD i18n("Hold on") -#define ACTION_LABEL_TRANSFER i18n("Transfer") -#define ACTION_LABEL_RECORD i18n("Record") -#define ACTION_LABEL_ACCEPT i18n("Pick up") -#define ACTION_LABEL_REFUSE i18n("Hang up") -#define ACTION_LABEL_UNHOLD i18n("Hold off") -#define ACTION_LABEL_GIVE_UP_TRANSF i18n("Give up transfer") -#define ACTION_LABEL_MAILBOX i18n("Voicemail") #define ICON_INCOMING ":/images/icons/ring.svg" #define ICON_RINGING ":/images/icons/ring.svg" @@ -63,20 +50,9 @@ #define ICON_HISTORY_MISSED ":/images/icons/missed.svg" #define ICON_SFLPHONE ":/images/icons/sflphone.svg" -/** Maybe to remove **/ -static const QString REGISTRATION_ENABLED_TRUE("true"); -static const QString REGISTRATION_ENABLED_FALSE("false"); // #define ACCOUNT_TYPES_TAB {QString(Account::ProtocolName::SIP), QString(Account::ProtocolName::IAX)} /*********************/ -/** Hooks settings */ -#define HOOKS_ADD_PREFIX "PHONE_NUMBER_HOOK_ADD_PREFIX" -#define HOOKS_ENABLED "PHONE_NUMBER_HOOK_ENABLED" -#define HOOKS_COMMAND "URLHOOK_COMMAND" -#define HOOKS_IAX2_ENABLED "URLHOOK_IAX2_ENABLED" -#define HOOKS_SIP_ENABLED "URLHOOK_SIP_ENABLED" -#define HOOKS_SIP_FIELD "URLHOOK_SIP_FIELD" - /** MIME API */ #define MIME_CALLID "text/sflphone.call.id" #define MIME_CONTACT "text/sflphone.contact"